blob: 83bcdc13138ea313514b8efe56f320789f3c5434 [file] [log] [blame]
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.locationattribution;
import android.Manifest;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.Uri;
import androidx.core.app.ActivityCompat;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextPaint;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.text.style.URLSpan;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
/**
* Non-framework Location Attribution sample application.
*
* <p>This location attribution sample application demonstrates how to give user visibility
* and control of non-user-emergency location access by non-framework entities accessing GNSS
* chipset API directly bypassing the standard Android framework location permission settings.
*
* <p>Displays text to the user about the benefits of giving location permission to this app so
* that the non-framework entity or entities this app represents can access device location from
* GNSS chipset directly.
*
* <p>Provides a button to allow the user to modify the location permission settings for this app.
*/
public class MainActivity extends AppCompatActivity {
private static final String APPLICATION_ID = "com.example.android.locationattribution";
private static final String TAG = "LocationAttribution";
private static final String PREFS_FILE_NAME = "LocationAttributionPrefs";
private static final int NON_FRAMEWORK_LOCATION_PERMISSION = 100;
private static final String URL_PREFIX = "location_attribution_app://";
private static final String LINK_LEARN_MORE = URL_PREFIX + "learn_more";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Set non-framework location access use case description to display.
setTextViewAppInfoContent();
// Show button for the user to modify location settings for this app.
Button button = (Button)findViewById(R.id.buttonModifyLocationSettings);
button.setOnClickListener(createModifyLocationSettingsButtonClickListener());
}
private void setTextViewAppInfoContent() {
// This text is seen by the user when this app is opened through the App info screen in
// Android Settings or when an intent is sent by carrier's own app.
TextView textViewAppInfo = findViewById(R.id.textViewAppInfo);
SpannableStringBuilder textViewAppInfoText = new SpannableStringBuilder();
for (CharSequence paragraph : getResources().getTextArray(
R.array.textViewAppInfo_Paragraphs)) {
textViewAppInfoText.append(paragraph);
}
replaceUrlSpansWithClickableSpans(textViewAppInfoText);
textViewAppInfo.setText(textViewAppInfoText);
textViewAppInfo.setMovementMethod(LinkMovementMethod.getInstance());
}
private View.OnClickListener createModifyLocationSettingsButtonClickListener() {
return new View.OnClickListener() {
@Override
public void onClick(View v) {
if (isLocationPermissionGranted(Manifest.permission.ACCESS_FINE_LOCATION)) {
if (!isLocationPermissionGranted(
Manifest.permission.ACCESS_BACKGROUND_LOCATION)) {
// Request 'Allow all the time' permission if the user didn't select
// 'Don't ask again' option earlier.
if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
Manifest.permission.ACCESS_BACKGROUND_LOCATION)) {
showRequestBackgroundLocationPermissionDialog();
return;
}
}
// We can't show tri-state dialog when permission is already granted.
// So, go to the location permission settings screen directly.
showLocationPermissionSettingsDashboard();
return;
}
if (isFirstTimeAskingLocationPermission()) {
// Show tri-state dialog to change permission.
setFirstTimeAskingLocationPermission(false);
showRequestLocationPermissionDialog();
return;
}
if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
Manifest.permission.ACCESS_FINE_LOCATION)) {
// The user has previously denied the request. Show the tri-state dialog again.
showRequestLocationPermissionDialog();
} else {
// User has denied permission and selected 'Don't ask again' option.
showLocationPermissionSettingsDashboard();
}
}
};
}
private boolean isLocationPermissionGranted(String locationPermissionType) {
return ActivityCompat.checkSelfPermission(this, locationPermissionType)
== PackageManager.PERMISSION_GRANTED;
}
private void showRequestLocationPermissionDialog() {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
NON_FRAMEWORK_LOCATION_PERMISSION);
}
private void showRequestBackgroundLocationPermissionDialog() {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_BACKGROUND_LOCATION},
NON_FRAMEWORK_LOCATION_PERMISSION);
}
private void showLocationPermissionSettingsDashboard() {
startActivity(new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.parse("package:" + APPLICATION_ID)));
}
private void setFirstTimeAskingLocationPermission(boolean isFirstTime) {
SharedPreferences sharedPreference = getApplicationContext().getSharedPreferences(
PREFS_FILE_NAME, MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreference.edit();
editor.putBoolean(Manifest.permission.ACCESS_FINE_LOCATION, isFirstTime).apply();
editor.commit();
}
private boolean isFirstTimeAskingLocationPermission() {
return getApplicationContext().getSharedPreferences(PREFS_FILE_NAME,
MODE_PRIVATE).getBoolean(Manifest.permission.ACCESS_FINE_LOCATION, true);
}
/**
* A clickable text listener.
*
* <p>Used to listen to click events for clickable text in the description displayed by this
* activity's main screen and navigate to the appropriate screen based on the text link
* clicked.
*/
private class AppInfoTextLinkClickableSpan extends ClickableSpan {
private final String mUrl;
private AppInfoTextLinkClickableSpan(String url) {
mUrl = url;
}
@Override
public void onClick(View textView) {
switch (mUrl) {
case LINK_LEARN_MORE:
startActivity(new Intent(Intent.ACTION_VIEW,
Uri.parse(getString(R.string.urlLearnMore))));
break;
default:
Log.e(TAG, "@string/textViewAppInfo contains invalid URL: " + mUrl);
}
}
@Override
public void updateDrawState(TextPaint drawState) {
super.updateDrawState(drawState);
drawState.setUnderlineText(false);
}
}
/*
* The description text in {@code textAppInfo} shown in the activity screen has URL links.
* Replace those links with clickable links so that we get notified when those links are
* clicked. We can then navigate to different screens based on the links clicked.
*/
private void replaceUrlSpansWithClickableSpans(Spannable textAppInfo) {
for(URLSpan span: textAppInfo.getSpans(0, textAppInfo.length(), URLSpan.class)) {
int start = textAppInfo.getSpanStart(span);
int end = textAppInfo.getSpanEnd(span);
textAppInfo.removeSpan(span);
textAppInfo.setSpan(new AppInfoTextLinkClickableSpan(span.getURL()), start, end, 0);
}
}
}