1 /* 2 * Copyright 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.example.android.locationattribution; 18 19 import android.Manifest; 20 import android.content.Intent; 21 import android.content.SharedPreferences; 22 import android.content.pm.PackageManager; 23 import android.net.Uri; 24 import androidx.core.app.ActivityCompat; 25 import androidx.appcompat.app.AppCompatActivity; 26 import android.os.Bundle; 27 import android.text.Spannable; 28 import android.text.SpannableStringBuilder; 29 import android.text.TextPaint; 30 import android.text.method.LinkMovementMethod; 31 import android.text.style.ClickableSpan; 32 import android.text.style.URLSpan; 33 import android.util.Log; 34 import android.view.View; 35 import android.widget.Button; 36 import android.widget.TextView; 37 38 /** 39 * Non-framework Location Attribution sample application. 40 * 41 * <p>This location attribution sample application demonstrates how to give user visibility 42 * and control of non-user-emergency location access by non-framework entities accessing GNSS 43 * chipset API directly bypassing the standard Android framework location permission settings. 44 * 45 * <p>Displays text to the user about the benefits of giving location permission to this app so 46 * that the non-framework entity or entities this app represents can access device location from 47 * GNSS chipset directly. 48 * 49 * <p>Provides a button to allow the user to modify the location permission settings for this app. 50 */ 51 public class MainActivity extends AppCompatActivity { 52 private static final String APPLICATION_ID = "com.example.android.locationattribution"; 53 private static final String TAG = "LocationAttribution"; 54 private static final String PREFS_FILE_NAME = "LocationAttributionPrefs"; 55 private static final int NON_FRAMEWORK_LOCATION_PERMISSION = 100; 56 57 private static final String URL_PREFIX = "location_attribution_app://"; 58 private static final String LINK_LEARN_MORE = URL_PREFIX + "learn_more"; 59 60 @Override onCreate(Bundle savedInstanceState)61 protected void onCreate(Bundle savedInstanceState) { 62 super.onCreate(savedInstanceState); 63 setContentView(R.layout.activity_main); 64 65 // Set non-framework location access use case description to display. 66 setTextViewAppInfoContent(); 67 68 // Show button for the user to modify location settings for this app. 69 Button button = (Button)findViewById(R.id.buttonModifyLocationSettings); 70 button.setOnClickListener(createModifyLocationSettingsButtonClickListener()); 71 } 72 setTextViewAppInfoContent()73 private void setTextViewAppInfoContent() { 74 // This text is seen by the user when this app is opened through the App info screen in 75 // Android Settings or when an intent is sent by carrier's own app. 76 TextView textViewAppInfo = findViewById(R.id.textViewAppInfo); 77 SpannableStringBuilder textViewAppInfoText = new SpannableStringBuilder(); 78 for (CharSequence paragraph : getResources().getTextArray( 79 R.array.textViewAppInfo_Paragraphs)) { 80 textViewAppInfoText.append(paragraph); 81 } 82 83 replaceUrlSpansWithClickableSpans(textViewAppInfoText); 84 textViewAppInfo.setText(textViewAppInfoText); 85 textViewAppInfo.setMovementMethod(LinkMovementMethod.getInstance()); 86 } 87 createModifyLocationSettingsButtonClickListener()88 private View.OnClickListener createModifyLocationSettingsButtonClickListener() { 89 return new View.OnClickListener() { 90 @Override 91 public void onClick(View v) { 92 if (isLocationPermissionGranted(Manifest.permission.ACCESS_FINE_LOCATION)) { 93 if (!isLocationPermissionGranted( 94 Manifest.permission.ACCESS_BACKGROUND_LOCATION)) { 95 // Request 'Allow all the time' permission if the user didn't select 96 // 'Don't ask again' option earlier. 97 if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, 98 Manifest.permission.ACCESS_BACKGROUND_LOCATION)) { 99 showRequestBackgroundLocationPermissionDialog(); 100 return; 101 } 102 } 103 104 // We can't show tri-state dialog when permission is already granted. 105 // So, go to the location permission settings screen directly. 106 showLocationPermissionSettingsDashboard(); 107 return; 108 } 109 110 if (isFirstTimeAskingLocationPermission()) { 111 // Show tri-state dialog to change permission. 112 setFirstTimeAskingLocationPermission(false); 113 showRequestLocationPermissionDialog(); 114 return; 115 } 116 117 if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, 118 Manifest.permission.ACCESS_FINE_LOCATION)) { 119 // The user has previously denied the request. Show the tri-state dialog again. 120 showRequestLocationPermissionDialog(); 121 } else { 122 // User has denied permission and selected 'Don't ask again' option. 123 showLocationPermissionSettingsDashboard(); 124 } 125 } 126 }; 127 } 128 129 private boolean isLocationPermissionGranted(String locationPermissionType) { 130 return ActivityCompat.checkSelfPermission(this, locationPermissionType) 131 == PackageManager.PERMISSION_GRANTED; 132 } 133 134 private void showRequestLocationPermissionDialog() { 135 ActivityCompat.requestPermissions(this, 136 new String[]{Manifest.permission.ACCESS_FINE_LOCATION, 137 Manifest.permission.ACCESS_BACKGROUND_LOCATION}, 138 NON_FRAMEWORK_LOCATION_PERMISSION); 139 } 140 141 private void showRequestBackgroundLocationPermissionDialog() { 142 ActivityCompat.requestPermissions(this, 143 new String[]{Manifest.permission.ACCESS_BACKGROUND_LOCATION}, 144 NON_FRAMEWORK_LOCATION_PERMISSION); 145 } 146 147 private void showLocationPermissionSettingsDashboard() { 148 startActivity(new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS, 149 Uri.parse("package:" + APPLICATION_ID))); 150 } 151 152 private void setFirstTimeAskingLocationPermission(boolean isFirstTime) { 153 SharedPreferences sharedPreference = getApplicationContext().getSharedPreferences( 154 PREFS_FILE_NAME, MODE_PRIVATE); 155 SharedPreferences.Editor editor = sharedPreference.edit(); 156 editor.putBoolean(Manifest.permission.ACCESS_FINE_LOCATION, isFirstTime).apply(); 157 editor.commit(); 158 } 159 160 private boolean isFirstTimeAskingLocationPermission() { 161 return getApplicationContext().getSharedPreferences(PREFS_FILE_NAME, 162 MODE_PRIVATE).getBoolean(Manifest.permission.ACCESS_FINE_LOCATION, true); 163 } 164 165 /** 166 * A clickable text listener. 167 * 168 * <p>Used to listen to click events for clickable text in the description displayed by this 169 * activity's main screen and navigate to the appropriate screen based on the text link 170 * clicked. 171 */ 172 private class AppInfoTextLinkClickableSpan extends ClickableSpan { 173 private final String mUrl; 174 175 private AppInfoTextLinkClickableSpan(String url) { 176 mUrl = url; 177 } 178 179 @Override 180 public void onClick(View textView) { 181 switch (mUrl) { 182 case LINK_LEARN_MORE: 183 startActivity(new Intent(Intent.ACTION_VIEW, 184 Uri.parse(getString(R.string.urlLearnMore)))); 185 break; 186 default: 187 Log.e(TAG, "@string/textViewAppInfo contains invalid URL: " + mUrl); 188 } 189 } 190 191 @Override 192 public void updateDrawState(TextPaint drawState) { 193 super.updateDrawState(drawState); 194 drawState.setUnderlineText(false); 195 } 196 } 197 198 /* 199 * The description text in {@code textAppInfo} shown in the activity screen has URL links. 200 * Replace those links with clickable links so that we get notified when those links are 201 * clicked. We can then navigate to different screens based on the links clicked. 202 */ 203 private void replaceUrlSpansWithClickableSpans(Spannable textAppInfo) { 204 for(URLSpan span: textAppInfo.getSpans(0, textAppInfo.length(), URLSpan.class)) { 205 int start = textAppInfo.getSpanStart(span); 206 int end = textAppInfo.getSpanEnd(span); 207 textAppInfo.removeSpan(span); 208 textAppInfo.setSpan(new AppInfoTextLinkClickableSpan(span.getURL()), start, end, 0); 209 } 210 } 211 } 212