summaryrefslogtreecommitdiff
path: root/src/com/example/android/locationattribution/MainActivity.java
blob: 83bcdc13138ea313514b8efe56f320789f3c5434 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
/*
 * 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);
        }
    }
}