• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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.android.permissioncontroller.permission.ui;
18 
19 import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
20 import static android.Manifest.permission.ACCESS_FINE_LOCATION;
21 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
22 
23 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.CANCELED;
24 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED;
25 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED_DO_NOT_ASK_AGAIN;
26 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_ALWAYS;
27 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_FOREGROUND_ONLY;
28 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_ONE_TIME;
29 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.LINKED_TO_SETTINGS;
30 import static com.android.permissioncontroller.permission.utils.Utils.getRequestMessage;
31 
32 import android.app.KeyguardManager;
33 import android.content.Intent;
34 import android.content.pm.PackageManager;
35 import android.content.res.Resources;
36 import android.graphics.drawable.Icon;
37 import android.os.Bundle;
38 import android.os.Process;
39 import android.text.Annotation;
40 import android.text.SpannableString;
41 import android.text.Spanned;
42 import android.text.style.ClickableSpan;
43 import android.util.Log;
44 import android.view.MotionEvent;
45 import android.view.View;
46 import android.view.View.OnAttachStateChangeListener;
47 import android.view.Window;
48 import android.view.WindowManager;
49 
50 import androidx.annotation.NonNull;
51 import androidx.core.util.Consumer;
52 
53 import com.android.modules.utils.build.SdkLevel;
54 import com.android.permissioncontroller.DeviceUtils;
55 import com.android.permissioncontroller.R;
56 import com.android.permissioncontroller.permission.ui.auto.GrantPermissionsAutoViewHandler;
57 import com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModel;
58 import com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModel.RequestInfo;
59 import com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModelFactory;
60 import com.android.permissioncontroller.permission.ui.wear.GrantPermissionsWearViewHandler;
61 import com.android.permissioncontroller.permission.utils.KotlinUtils;
62 import com.android.permissioncontroller.permission.utils.Utils;
63 
64 import java.util.ArrayList;
65 import java.util.HashMap;
66 import java.util.List;
67 import java.util.Map;
68 import java.util.Random;
69 
70 /**
71  * An activity which displays runtime permission prompts on behalf of an app.
72  */
73 public class GrantPermissionsActivity extends SettingsActivity
74         implements GrantPermissionsViewHandler.ResultListener {
75 
76     private static final String LOG_TAG = "GrantPermissionsActivit";
77 
78     private static final String KEY_SESSION_ID = GrantPermissionsActivity.class.getName()
79             + "_REQUEST_ID";
80     public static final String ANNOTATION_ID = "link";
81 
82     public static final int NEXT_BUTTON = 11;
83     public static final int ALLOW_BUTTON = 0;
84     public static final int ALLOW_ALWAYS_BUTTON = 1; // Used in auto
85     public static final int ALLOW_FOREGROUND_BUTTON = 2;
86     public static final int DENY_BUTTON = 3;
87     public static final int DENY_AND_DONT_ASK_AGAIN_BUTTON = 4;
88     public static final int ALLOW_ONE_TIME_BUTTON = 5;
89     public static final int NO_UPGRADE_BUTTON = 6;
90     public static final int NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON = 7;
91     public static final int NO_UPGRADE_OT_BUTTON = 8; // one-time
92     public static final int NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON = 9; // one-time
93     public static final int LINK_TO_SETTINGS = 10;
94 
95     public static final int NEXT_LOCATION_DIALOG = 6;
96     public static final int LOCATION_ACCURACY_LAYOUT = 0;
97     public static final int FINE_RADIO_BUTTON = 1;
98     public static final int COARSE_RADIO_BUTTON = 2;
99     public static final int DIALOG_WITH_BOTH_LOCATIONS = 3;
100     public static final int DIALOG_WITH_FINE_LOCATION_ONLY = 4;
101     public static final int DIALOG_WITH_COARSE_LOCATION_ONLY = 5;
102 
103     public static final Map<String, Integer> PERMISSION_TO_BIT_SHIFT =
104             new HashMap<String, Integer>() {{
105                 put(ACCESS_COARSE_LOCATION, 0);
106                 put(ACCESS_FINE_LOCATION, 1);
107             }};
108 
109     private static final int APP_PERMISSION_REQUEST_CODE = 1;
110 
111     /** Unique Id of a request */
112     private long mSessionId;
113 
114     private String[] mRequestedPermissions;
115     private boolean[] mButtonVisibilities;
116     private boolean[] mLocationVisibilities;
117     private List<RequestInfo> mRequestInfos = new ArrayList<>();
118     private GrantPermissionsViewHandler mViewHandler;
119     private GrantPermissionsViewModel mViewModel;
120     private boolean mResultSet;
121     /** Package that requested the permission grant */
122     private String mCallingPackage;
123     private int mTotalRequests = 0;
124     private int mCurrentRequestIdx = 0;
125     private float mOriginalDimAmount;
126     private View mRootView;
127 
128     @Override
onCreate(Bundle icicle)129     public void onCreate(Bundle icicle) {
130         super.onCreate(icicle);
131 
132         if (icicle == null) {
133             mSessionId = new Random().nextLong();
134         } else {
135             mSessionId = icicle.getLong(KEY_SESSION_ID);
136         }
137 
138         getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
139 
140         mRequestedPermissions = getIntent().getStringArrayExtra(
141                 PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES);
142         if (mRequestedPermissions == null || mRequestedPermissions.length == 0) {
143             setResultAndFinish();
144             return;
145         }
146 
147         // Cache this as this can only read on onCreate, not later.
148         mCallingPackage = getCallingPackage();
149         if (mCallingPackage == null) {
150             Log.e(LOG_TAG, "null callingPackageName. Please use \"RequestPermission\" to "
151                     + "request permissions");
152             setResultAndFinish();
153             return;
154         }
155 
156         setFinishOnTouchOutside(false);
157 
158         setTitle(R.string.permission_request_title);
159 
160         if (DeviceUtils.isTelevision(this)) {
161             mViewHandler = new com.android.permissioncontroller.permission.ui.television
162                     .GrantPermissionsViewHandlerImpl(this,
163                     mCallingPackage).setResultListener(this);
164         } else if (DeviceUtils.isWear(this)) {
165             mViewHandler = new GrantPermissionsWearViewHandler(this).setResultListener(this);
166         } else if (DeviceUtils.isAuto(this)) {
167             mViewHandler = new GrantPermissionsAutoViewHandler(this, mCallingPackage)
168                     .setResultListener(this);
169         } else {
170             mViewHandler = new com.android.permissioncontroller.permission.ui.handheld
171                     .GrantPermissionsViewHandlerImpl(this, mCallingPackage,
172                     Process.myUserHandle()).setResultListener(this);
173         }
174 
175         GrantPermissionsViewModelFactory factory = new GrantPermissionsViewModelFactory(
176                 getApplication(), mCallingPackage, mRequestedPermissions, mSessionId, icicle);
177         mViewModel = factory.create(GrantPermissionsViewModel.class);
178         mViewModel.getRequestInfosLiveData().observe(this, this::onRequestInfoLoad);
179 
180         mRootView = mViewHandler.createView();
181         mRootView.setVisibility(View.GONE);
182         setContentView(mRootView);
183         Window window = getWindow();
184         WindowManager.LayoutParams layoutParams = window.getAttributes();
185         mOriginalDimAmount = layoutParams.dimAmount;
186         mViewHandler.updateWindowAttributes(layoutParams);
187         window.setAttributes(layoutParams);
188 
189         if (SdkLevel.isAtLeastS() && getResources().getBoolean(R.bool.config_useWindowBlur)) {
190             java.util.function.Consumer<Boolean> blurEnabledListener = enabled -> {
191                 mViewHandler.onBlurEnabledChanged(window, enabled);
192             };
193             mRootView.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
194                 @Override
195                 public void onViewAttachedToWindow(View v) {
196                     window.getWindowManager().addCrossWindowBlurEnabledListener(
197                             blurEnabledListener);
198                 }
199 
200                 @Override
201                 public void onViewDetachedFromWindow(View v) {
202                     window.getWindowManager().removeCrossWindowBlurEnabledListener(
203                             blurEnabledListener);
204                 }
205             });
206         }
207         // Restore UI state after lifecycle events. This has to be before we show the first request,
208         // as the UI behaves differently for updates and initial creations.
209         if (icicle != null) {
210             mViewHandler.loadInstanceState(icicle);
211         } else {
212             // Do not show screen dim until data is loaded
213             window.setDimAmount(0f);
214         }
215     }
216 
onRequestInfoLoad(List<RequestInfo> requests)217     private void onRequestInfoLoad(List<RequestInfo> requests) {
218         if (!mViewModel.getRequestInfosLiveData().isInitialized() || mResultSet) {
219             return;
220         } else if (requests == null) {
221             finishAfterTransition();
222             return;
223         } else if (requests.isEmpty()) {
224             setResultAndFinish();
225             return;
226         }
227 
228         if (mRequestInfos == null) {
229             mTotalRequests = requests.size();
230         }
231         mRequestInfos = requests;
232 
233         showNextRequest();
234     }
235 
showNextRequest()236     private void showNextRequest() {
237         if (mRequestInfos == null || mRequestInfos.isEmpty()) {
238             return;
239         }
240 
241         RequestInfo info = mRequestInfos.get(0);
242 
243         if (info.getSendToSettingsImmediately()) {
244             mViewModel.sendDirectlyToSettings(this, info.getGroupName());
245             return;
246         }
247 
248         CharSequence appLabel = KotlinUtils.INSTANCE.getPackageLabel(getApplication(),
249                 mCallingPackage, Process.myUserHandle());
250 
251         int messageId = 0;
252         switch(info.getMessage()) {
253             case FG_MESSAGE:
254                 messageId = Utils.getRequest(info.getGroupName());
255                 break;
256             case FG_FINE_LOCATION_MESSAGE:
257                 messageId = R.string.permgrouprequest_fineupgrade;
258                 break;
259             case FG_COARSE_LOCATION_MESSAGE:
260                 messageId = R.string.permgrouprequest_coarselocation;
261                 break;
262             case BG_MESSAGE:
263                 messageId = Utils.getBackgroundRequest(info.getGroupName());
264                 break;
265             case UPGRADE_MESSAGE:
266                 messageId = Utils.getUpgradeRequest(info.getGroupName());
267         }
268 
269         CharSequence message = getRequestMessage(appLabel, mCallingPackage,
270                 info.getGroupName(), this, messageId);
271 
272         int detailMessageId = 0;
273         switch(info.getDetailMessage()) {
274             case FG_MESSAGE:
275                 detailMessageId = Utils.getRequestDetail(info.getGroupName());
276                 break;
277             case BG_MESSAGE:
278                 detailMessageId = Utils.getBackgroundRequestDetail(info.getGroupName());
279                 break;
280             case UPGRADE_MESSAGE:
281                 detailMessageId = Utils.getUpgradeRequestDetail(info.getGroupName());
282         }
283 
284         Spanned detailMessage = null;
285         if (detailMessageId != 0) {
286             detailMessage =
287                     new SpannableString(getText(detailMessageId));
288             Annotation[] annotations = detailMessage.getSpans(
289                     0, detailMessage.length(), Annotation.class);
290             int numAnnotations = annotations.length;
291             for (int i = 0; i < numAnnotations; i++) {
292                 Annotation annotation = annotations[i];
293                 if (annotation.getValue().equals(ANNOTATION_ID)) {
294                     int start = detailMessage.getSpanStart(annotation);
295                     int end = detailMessage.getSpanEnd(annotation);
296                     ClickableSpan clickableSpan = getLinkToAppPermissions(info);
297                     SpannableString spannableString =
298                             new SpannableString(detailMessage);
299                     spannableString.setSpan(clickableSpan, start, end, 0);
300                     detailMessage = spannableString;
301                     break;
302                 }
303             }
304         }
305 
306         Icon icon = null;
307         try {
308             icon = Icon.createWithResource(info.getGroupInfo().getPackageName(),
309                     info.getGroupInfo().getIcon());
310         } catch (Resources.NotFoundException e) {
311             Log.e(LOG_TAG, "Cannot load icon for group" + info.getGroupName(), e);
312         }
313 
314         boolean showingNewGroup = message == null || !message.equals(getTitle());
315 
316         // Set the permission message as the title so it can be announced. Skip on Wear
317         // because the dialog title is already announced, as is the default selection which
318         // is a text view containing the title.
319         if (!DeviceUtils.isWear(this)) {
320             setTitle(message);
321         }
322 
323         ArrayList<Integer> idxs = new ArrayList<>();
324         mButtonVisibilities = new boolean[info.getButtonVisibilities().size()];
325         for (int i = 0; i < info.getButtonVisibilities().size(); i++) {
326             mButtonVisibilities[i] = info.getButtonVisibilities().get(i);
327             if (mButtonVisibilities[i]) {
328                 idxs.add(i);
329             }
330         }
331 
332         mLocationVisibilities = new boolean[info.getLocationVisibilities().size()];
333         for (int i = 0; i < info.getLocationVisibilities().size(); i++) {
334             mLocationVisibilities[i] = info.getLocationVisibilities().get(i);
335         }
336 
337         mViewHandler.updateUi(info.getGroupName(), mTotalRequests, mCurrentRequestIdx, icon,
338                 message, detailMessage, mButtonVisibilities, mLocationVisibilities);
339         if (showingNewGroup) {
340             mCurrentRequestIdx++;
341         }
342 
343         getWindow().setDimAmount(mOriginalDimAmount);
344         mRootView.setVisibility(View.VISIBLE);
345     }
346 
347     @Override
dispatchTouchEvent(MotionEvent ev)348     public boolean dispatchTouchEvent(MotionEvent ev) {
349         View rootView = getWindow().getDecorView();
350         if (rootView.getTop() != 0) {
351             // We are animating the top view, need to compensate for that in motion events.
352             ev.setLocation(ev.getX(), ev.getY() - rootView.getTop());
353         }
354         return super.dispatchTouchEvent(ev);
355     }
356 
357     @Override
onSaveInstanceState(@onNull Bundle outState)358     protected void onSaveInstanceState(@NonNull Bundle outState) {
359         super.onSaveInstanceState(outState);
360 
361         mViewHandler.saveInstanceState(outState);
362         mViewModel.saveInstanceState(outState);
363 
364         outState.putLong(KEY_SESSION_ID, mSessionId);
365     }
366 
getLinkToAppPermissions(RequestInfo info)367     private ClickableSpan getLinkToAppPermissions(RequestInfo info) {
368         return new ClickableSpan() {
369             @Override
370             public void onClick(View widget) {
371                 logGrantPermissionActivityButtons(info.getGroupName(), null, LINKED_TO_SETTINGS);
372                 mViewModel.sendToSettingsFromLink(GrantPermissionsActivity.this,
373                         info.getGroupName());
374             }
375         };
376     }
377 
378 
379     @Override
380     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
381         super.onActivityResult(requestCode, resultCode, data);
382         Consumer<Intent> callback = mViewModel.getActivityResultCallback();
383 
384         if (requestCode == APP_PERMISSION_REQUEST_CODE && callback != null) {
385             callback.accept(data);
386             mViewModel.setActivityResultCallback(null);
387         }
388     }
389 
390     @Override
391     public void onPermissionGrantResult(String name,
392             @GrantPermissionsViewHandler.Result int result) {
393         if (checkKgm(name, null, result)) {
394             return;
395         }
396 
397         logGrantPermissionActivityButtons(name, null, result);
398         mViewModel.onPermissionGrantResult(name, null, result);
399         showNextRequest();
400         if (result == CANCELED) {
401             setResultAndFinish();
402         }
403     }
404 
405     @Override
406     public void onPermissionGrantResult(String name, List<String> affectedForegroundPermissions,
407             @GrantPermissionsViewHandler.Result int result) {
408         if (checkKgm(name, affectedForegroundPermissions, result)) {
409             return;
410         }
411 
412         logGrantPermissionActivityButtons(name, affectedForegroundPermissions, result);
413         mViewModel.onPermissionGrantResult(name, affectedForegroundPermissions, result);
414         showNextRequest();
415         if (result == CANCELED) {
416             setResultAndFinish();
417         }
418     }
419 
420     @Override
421     public void onBackPressed() {
422         mViewHandler.onBackPressed();
423     }
424 
425     @Override
426     public void finishAfterTransition() {
427         setResultIfNeeded(RESULT_CANCELED);
428         if (mViewModel != null) {
429             mViewModel.autoGrantNotify();
430         }
431         super.finishAfterTransition();
432     }
433 
434     private boolean checkKgm(String name, List<String> affectedForegroundPermissions,
435             @GrantPermissionsViewHandler.Result int result) {
436         if (result == GRANTED_ALWAYS || result == GRANTED_FOREGROUND_ONLY
437                 || result == DENIED_DO_NOT_ASK_AGAIN) {
438             KeyguardManager kgm = getSystemService(KeyguardManager.class);
439 
440             if (kgm != null && kgm.isDeviceLocked()) {
441                 kgm.requestDismissKeyguard(this, new KeyguardManager.KeyguardDismissCallback() {
442                     @Override
443                     public void onDismissError() {
444                         Log.e(LOG_TAG, "Cannot dismiss keyguard perm=" + name
445                                 + " result=" + result);
446                     }
447 
448                     @Override
449                     public void onDismissCancelled() {
450                         // do nothing (i.e. stay at the current permission group)
451                     }
452 
453                     @Override
454                     public void onDismissSucceeded() {
455                         // Now the keyguard is dismissed, hence the device is not locked
456                         // anymore
457                         onPermissionGrantResult(name, affectedForegroundPermissions, result);
458                     }
459                 });
460                 return true;
461             }
462         }
463         return false;
464     }
465 
466     private void setResultIfNeeded(int resultCode) {
467         if (!mResultSet) {
468             mResultSet = true;
469             if (mViewModel != null) {
470                 mViewModel.logRequestedPermissionGroups();
471             }
472             Intent result = new Intent(PackageManager.ACTION_REQUEST_PERMISSIONS);
473             String[] resultPermissions = mRequestedPermissions != null
474                     ? mRequestedPermissions : new String[0];
475             int[] grantResults = new int[resultPermissions.length];
476 
477             if (mViewModel != null && mViewModel.shouldReturnPermissionState()
478                     && mCallingPackage != null) {
479                 PackageManager pm = getPackageManager();
480                 for (int i = 0; i < resultPermissions.length; i++) {
481                     grantResults[i] = pm.checkPermission(resultPermissions[i], mCallingPackage);
482                 }
483             } else {
484                 grantResults = new int[0];
485                 resultPermissions = new String[0];
486             }
487             result.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES, resultPermissions);
488             result.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS, grantResults);
489             setResult(resultCode, result);
490         }
491     }
492 
493     private void setResultAndFinish() {
494         setResultIfNeeded(RESULT_OK);
495         finishAfterTransition();
496     }
497 
498     private void logGrantPermissionActivityButtons(String permissionGroupName,
499             List<String> affectedForegroundPermissions, int grantResult) {
500         int clickedButton = 0;
501         int presentedButtons = getButtonState();
502         switch (grantResult) {
503             case GRANTED_ALWAYS:
504                 clickedButton = 1 << ALLOW_BUTTON;
505                 break;
506             case GRANTED_FOREGROUND_ONLY:
507                 clickedButton = 1 << ALLOW_FOREGROUND_BUTTON;
508                 break;
509             case DENIED:
510                 if (mButtonVisibilities != null) {
511                     if (mButtonVisibilities[NO_UPGRADE_BUTTON]) {
512                         clickedButton = 1 << NO_UPGRADE_BUTTON;
513                     } else if (mButtonVisibilities[NO_UPGRADE_OT_BUTTON]) {
514                         clickedButton = 1 << NO_UPGRADE_OT_BUTTON;
515                     } else if (mButtonVisibilities[DENY_BUTTON]) {
516                         clickedButton = 1 << DENY_BUTTON;
517                     }
518                 }
519                 break;
520             case DENIED_DO_NOT_ASK_AGAIN:
521                 if (mButtonVisibilities != null) {
522                     if (mButtonVisibilities[NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON]) {
523                         clickedButton = 1 << NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON;
524                     } else if (mButtonVisibilities[NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON]) {
525                         clickedButton = 1 << NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON;
526                     } else if (mButtonVisibilities[DENY_AND_DONT_ASK_AGAIN_BUTTON]) {
527                         clickedButton = 1 << DENY_AND_DONT_ASK_AGAIN_BUTTON;
528                     }
529                 }
530                 break;
531             case GRANTED_ONE_TIME:
532                 clickedButton = 1 << ALLOW_ONE_TIME_BUTTON;
533                 break;
534             case LINKED_TO_SETTINGS:
535                 clickedButton = 1 << LINK_TO_SETTINGS;
536             case CANCELED:
537                 // fall through
538             default:
539                 break;
540         }
541 
542         int selectedPrecision = 0;
543         if (affectedForegroundPermissions != null) {
544             for (Map.Entry<String, Integer> entry : PERMISSION_TO_BIT_SHIFT.entrySet()) {
545                 if (affectedForegroundPermissions.contains(entry.getKey())) {
546                     selectedPrecision |= 1 << entry.getValue();
547                 }
548             }
549         }
550 
551         mViewModel.logClickedButtons(permissionGroupName, selectedPrecision, clickedButton,
552                 presentedButtons);
553     }
554 
555     private int getButtonState() {
556         if (mButtonVisibilities == null) {
557             return 0;
558         }
559         int buttonState = 0;
560         for (int i = NEXT_BUTTON - 1; i >= 0; i--) {
561             buttonState *= 2;
562             if (mButtonVisibilities[i]) {
563                 buttonState++;
564             }
565         }
566         return buttonState;
567     }
568 }
569