• 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.Manifest.permission_group.LOCATION;
22 import static android.Manifest.permission_group.READ_MEDIA_VISUAL;
23 import static android.health.connect.HealthPermissions.HEALTH_PERMISSION_GROUP;
24 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
25 
26 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.CANCELED;
27 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED;
28 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED_DO_NOT_ASK_AGAIN;
29 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED_MORE;
30 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_ALWAYS;
31 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_FOREGROUND_ONLY;
32 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_ONE_TIME;
33 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_USER_SELECTED;
34 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.LINKED_TO_PERMISSION_RATIONALE;
35 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.LINKED_TO_SETTINGS;
36 import static com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModel.APP_PERMISSION_REQUEST_CODE;
37 import static com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModel.PHOTO_PICKER_REQUEST_CODE;
38 import static com.android.permissioncontroller.permission.utils.Utils.getRequestMessage;
39 
40 import android.Manifest;
41 import android.app.Activity;
42 import android.app.KeyguardManager;
43 import android.content.Intent;
44 import android.content.pm.PackageInfo;
45 import android.content.pm.PackageItemInfo;
46 import android.content.pm.PackageManager;
47 import android.content.res.Resources;
48 import android.graphics.drawable.Icon;
49 import android.os.Build;
50 import android.os.Bundle;
51 import android.os.Process;
52 import android.text.Annotation;
53 import android.text.SpannableString;
54 import android.text.Spanned;
55 import android.text.style.ClickableSpan;
56 import android.util.Log;
57 import android.util.Pair;
58 import android.view.MotionEvent;
59 import android.view.View;
60 import android.view.View.OnAttachStateChangeListener;
61 import android.view.Window;
62 import android.view.WindowManager;
63 import android.view.inputmethod.InputMethodManager;
64 
65 import androidx.annotation.GuardedBy;
66 import androidx.annotation.NonNull;
67 import androidx.annotation.Nullable;
68 import androidx.annotation.StringRes;
69 import androidx.core.util.Consumer;
70 import androidx.core.util.Preconditions;
71 
72 import com.android.modules.utils.build.SdkLevel;
73 import com.android.permissioncontroller.DeviceUtils;
74 import com.android.permissioncontroller.R;
75 import com.android.permissioncontroller.permission.ui.auto.GrantPermissionsAutoViewHandler;
76 import com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModel;
77 import com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModel.RequestInfo;
78 import com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModelFactory;
79 import com.android.permissioncontroller.permission.ui.wear.GrantPermissionsWearViewHandler;
80 import com.android.permissioncontroller.permission.utils.KotlinUtils;
81 import com.android.permissioncontroller.permission.utils.Utils;
82 
83 import java.util.ArrayList;
84 import java.util.HashMap;
85 import java.util.List;
86 import java.util.Map;
87 import java.util.Objects;
88 import java.util.Random;
89 
90 /**
91  * An activity which displays runtime permission prompts on behalf of an app.
92  */
93 public class GrantPermissionsActivity extends SettingsActivity
94         implements GrantPermissionsViewHandler.ResultListener {
95 
96     private static final String LOG_TAG = "GrantPermissionsActivity";
97 
98     private static final String KEY_SESSION_ID = GrantPermissionsActivity.class.getName()
99             + "_REQUEST_ID";
100     public static final String ANNOTATION_ID = "link";
101 
102     public static final int NEXT_BUTTON = 15;
103     public static final int ALLOW_BUTTON = 0;
104     public static final int ALLOW_ALWAYS_BUTTON = 1; // Used in auto
105     public static final int ALLOW_FOREGROUND_BUTTON = 2;
106     public static final int DENY_BUTTON = 3;
107     public static final int DENY_AND_DONT_ASK_AGAIN_BUTTON = 4;
108     public static final int ALLOW_ONE_TIME_BUTTON = 5;
109     public static final int NO_UPGRADE_BUTTON = 6;
110     public static final int NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON = 7;
111     public static final int NO_UPGRADE_OT_BUTTON = 8; // one-time
112     public static final int NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON = 9; // one-time
113     public static final int LINK_TO_SETTINGS = 10;
114     public static final int ALLOW_ALL_BUTTON = 11; // button for options with a picker, allow all
115     public static final int ALLOW_SELECTED_BUTTON = 12; // allow selected, with picker
116     // button to cancel a request for more data with a picker
117     public static final int DONT_ALLOW_MORE_SELECTED_BUTTON = 13;
118     public static final int LINK_TO_PERMISSION_RATIONALE = 14;
119 
120     public static final int NEXT_LOCATION_DIALOG = 6;
121     public static final int LOCATION_ACCURACY_LAYOUT = 0;
122     public static final int FINE_RADIO_BUTTON = 1;
123     public static final int COARSE_RADIO_BUTTON = 2;
124     public static final int DIALOG_WITH_BOTH_LOCATIONS = 3;
125     public static final int DIALOG_WITH_FINE_LOCATION_ONLY = 4;
126     public static final int DIALOG_WITH_COARSE_LOCATION_ONLY = 5;
127 
128     public static final Map<String, Integer> PERMISSION_TO_BIT_SHIFT = Map.of(
129             ACCESS_COARSE_LOCATION, 0,
130             ACCESS_FINE_LOCATION, 1);
131 
132     public static final String INTENT_PHOTOS_SELECTED = "intent_extra_result";
133 
134     /**
135      * A map of the currently shown GrantPermissionsActivity for this user, per package and task ID
136      */
137     @GuardedBy("sCurrentGrantRequests")
138     public static final Map<Pair<String, Integer>, GrantPermissionsActivity>
139             sCurrentGrantRequests = new HashMap<>();
140 
141     /** Unique Id of a request */
142     private long mSessionId;
143 
144     /**
145      * The permission group that was showing, before a new permission request came in on top of an
146      * existing request
147      */
148     private String mPreMergeShownGroupName;
149 
150     /** The current list of permissions requested, across all current requests for this app */
151     private List<String> mRequestedPermissions = new ArrayList<>();
152     /** A copy of the list of permissions originally requested in the intent to this activity */
153     private String[] mOriginalRequestedPermissions = new String[0];
154 
155     private boolean[] mButtonVisibilities;
156     private int mRequestCounts = 0;
157     private List<RequestInfo> mRequestInfos = new ArrayList<>();
158     private GrantPermissionsViewHandler mViewHandler;
159     private GrantPermissionsViewModel mViewModel;
160     /**
161      * A list of other GrantPermissionActivities for the same package which passed their list of
162      * permissions to this one. They need to be informed when this activity finishes.
163      */
164     private List<GrantPermissionsActivity> mFollowerActivities = new ArrayList<>();
165     /** Whether this activity has asked another GrantPermissionsActivity to show on its behalf */
166     private boolean mDelegated;
167     /** Whether this activity has been triggered by the system */
168     private boolean mIsSystemTriggered = false;
169     /** The set result code, or MAX_VALUE if it hasn't been set yet */
170     private int mResultCode = Integer.MAX_VALUE;
171     /** Package that shall have permissions granted */
172     private String mTargetPackage;
173     /** A key representing this activity, defined by the target package and task ID */
174     private Pair<String, Integer> mKey;
175     private int mCurrentRequestIdx = 0;
176     private float mOriginalDimAmount;
177     private View mRootView;
178     private int mStoragePermGroupIcon = R.drawable.ic_empty_icon;
179 
180     @Override
onCreate(Bundle icicle)181     public void onCreate(Bundle icicle) {
182         if (DeviceUtils.isAuto(this)) {
183             setTheme(R.style.GrantPermissions_Car_FilterTouches);
184         }
185         super.onCreate(icicle);
186 
187         if (icicle == null) {
188             mSessionId = new Random().nextLong();
189         } else {
190             mSessionId = icicle.getLong(KEY_SESSION_ID);
191         }
192 
193         getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
194 
195         int permissionsSdkLevel;
196         if (PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER.equals(getIntent().getAction())) {
197             mIsSystemTriggered = true;
198             mTargetPackage = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
199             if (mTargetPackage == null) {
200                 Log.e(LOG_TAG, "null EXTRA_PACKAGE_NAME. Must be set for "
201                         + "REQUEST_PERMISSIONS_FOR_OTHER activity");
202                 finishAfterTransition();
203                 return;
204             }
205             // We don't want to do any filtering in this case.
206             // These calls are coming from the system on behalf of the app.
207             permissionsSdkLevel = Build.VERSION_CODES.CUR_DEVELOPMENT;
208         } else {
209             // Cache this as this can only read on onCreate, not later.
210             mTargetPackage = getCallingPackage();
211             if (mTargetPackage == null) {
212                 Log.e(LOG_TAG, "null callingPackageName. Please use \"RequestPermission\" to "
213                         + "request permissions");
214                 finishAfterTransition();
215                 return;
216             }
217             try {
218                 PackageInfo packageInfo = getPackageManager().getPackageInfo(mTargetPackage, 0);
219                 permissionsSdkLevel = packageInfo.applicationInfo.targetSdkVersion;
220             } catch (PackageManager.NameNotFoundException e) {
221                 Log.e(LOG_TAG, "Unable to get package info for the calling package.", e);
222                 finishAfterTransition();
223                 return;
224             }
225         }
226 
227         String[] requestedPermissionsArray = getIntent().getStringArrayExtra(
228             PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES);
229         if (requestedPermissionsArray == null) {
230             setResultAndFinish();
231             return;
232         }
233 
234         mRequestedPermissions = GrantPermissionsViewModel.Companion.getSanitizedPermissionsList(
235             requestedPermissionsArray, permissionsSdkLevel);
236         if (mRequestedPermissions.isEmpty()) {
237             setResultAndFinish();
238             return;
239         }
240 
241         mOriginalRequestedPermissions = mRequestedPermissions.toArray(new String[0]);
242 
243         synchronized (sCurrentGrantRequests) {
244             mKey = new Pair<>(mTargetPackage, getTaskId());
245             if (!sCurrentGrantRequests.containsKey(mKey)) {
246                 sCurrentGrantRequests.put(mKey, this);
247                 finishSystemStartedDialogsOnOtherTasksLocked();
248             } else if (mIsSystemTriggered) {
249                 // The system triggered dialog doesn't require results. Delegate, and finish.
250                 sCurrentGrantRequests.get(mKey).onNewFollowerActivity(null,
251                         mRequestedPermissions);
252                 finishAfterTransition();
253                 return;
254             } else if (sCurrentGrantRequests.get(mKey).mIsSystemTriggered) {
255                 // Normal permission requests should only merge into the system triggered dialog,
256                 // which has task overlay set
257                 mDelegated = true;
258                 sCurrentGrantRequests.get(mKey).onNewFollowerActivity(this, mRequestedPermissions);
259             }
260         }
261 
262         setFinishOnTouchOutside(false);
263 
264         setTitle(R.string.permission_request_title);
265 
266         if (DeviceUtils.isTelevision(this)) {
267             mViewHandler = new com.android.permissioncontroller.permission.ui.television
268                     .GrantPermissionsViewHandlerImpl(this,
269                     mTargetPackage).setResultListener(this);
270         } else if (DeviceUtils.isWear(this)) {
271             mViewHandler = new GrantPermissionsWearViewHandler(this).setResultListener(this);
272         } else if (DeviceUtils.isAuto(this)) {
273             mViewHandler = new GrantPermissionsAutoViewHandler(this, mTargetPackage)
274                     .setResultListener(this);
275         } else {
276             mViewHandler = new com.android.permissioncontroller.permission.ui.handheld
277                     .GrantPermissionsViewHandlerImpl(this, this);
278         }
279 
280         GrantPermissionsViewModelFactory factory = new GrantPermissionsViewModelFactory(
281                 getApplication(), mTargetPackage, mRequestedPermissions, mSessionId, icicle);
282         if (!mDelegated) {
283             mViewModel = factory.create(GrantPermissionsViewModel.class);
284             mViewModel.getRequestInfosLiveData().observe(this, this::onRequestInfoLoad);
285         }
286 
287         mRootView = mViewHandler.createView();
288         mRootView.setVisibility(View.GONE);
289         setContentView(mRootView);
290         Window window = getWindow();
291         WindowManager.LayoutParams layoutParams = window.getAttributes();
292         mOriginalDimAmount = layoutParams.dimAmount;
293         mViewHandler.updateWindowAttributes(layoutParams);
294         window.setAttributes(layoutParams);
295 
296         if (SdkLevel.isAtLeastS() && getResources().getBoolean(R.bool.config_useWindowBlur)) {
297             java.util.function.Consumer<Boolean> blurEnabledListener = enabled -> {
298                 mViewHandler.onBlurEnabledChanged(window, enabled);
299             };
300             mRootView.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
301                 @Override
302                 public void onViewAttachedToWindow(View v) {
303                     window.getWindowManager().addCrossWindowBlurEnabledListener(
304                             blurEnabledListener);
305                 }
306 
307                 @Override
308                 public void onViewDetachedFromWindow(View v) {
309                     window.getWindowManager().removeCrossWindowBlurEnabledListener(
310                             blurEnabledListener);
311                 }
312             });
313         }
314         // Restore UI state after lifecycle events. This has to be before we show the first request,
315         // as the UI behaves differently for updates and initial creations.
316         if (icicle != null) {
317             mViewHandler.loadInstanceState(icicle);
318         } else {
319             // Do not show screen dim until data is loaded
320             window.setDimAmount(0f);
321         }
322 
323         PackageItemInfo storageGroupInfo =
324                 Utils.getGroupInfo(Manifest.permission_group.STORAGE, this.getApplicationContext());
325         if (storageGroupInfo != null) {
326             mStoragePermGroupIcon = storageGroupInfo.icon;
327         }
328     }
329 
330     /**
331      * A new GrantPermissionsActivity has opened for this same package. Merge its requested
332      * permissions with the original ones set in the intent, and recalculate the grant states.
333      * @param follower The activity requesting permissions, which needs to be informed upon this
334      *                 activity finishing
335      * @param newPermissions The new permissions requested in the activity
336      */
onNewFollowerActivity(@ullable GrantPermissionsActivity follower, @NonNull List<String> newPermissions)337     private void onNewFollowerActivity(@Nullable GrantPermissionsActivity follower,
338             @NonNull List<String> newPermissions) {
339         if (follower != null) {
340             // Ensure the list of follower activities is a stack
341             mFollowerActivities.add(0, follower);
342             follower.mViewModel = mViewModel;
343         }
344 
345         boolean isShowingGroup = mRootView != null && mRootView.getVisibility() == View.VISIBLE;
346         List<RequestInfo> currentGroups = mViewModel.getRequestInfosLiveData().getValue();
347         if (mPreMergeShownGroupName == null && isShowingGroup
348                 && currentGroups != null && !currentGroups.isEmpty()) {
349             mPreMergeShownGroupName = currentGroups.get(0).getGroupName();
350         }
351 
352         if (mRequestedPermissions.containsAll(newPermissions)) {
353             return;
354         }
355 
356         ArrayList<String> currentPermissions = new ArrayList<>(mRequestedPermissions);
357         for (String newPerm : newPermissions) {
358             if (!currentPermissions.contains(newPerm)) {
359                 currentPermissions.add(newPerm);
360             }
361         }
362         mRequestedPermissions = currentPermissions;
363 
364         Bundle oldState = new Bundle();
365         mViewModel.getRequestInfosLiveData().removeObservers(this);
366         mViewModel.saveInstanceState(oldState);
367         GrantPermissionsViewModelFactory factory = new GrantPermissionsViewModelFactory(
368                 getApplication(), mTargetPackage, mRequestedPermissions,
369                 mSessionId, oldState);
370         mViewModel = factory.create(GrantPermissionsViewModel.class);
371         mViewModel.getRequestInfosLiveData().observe(this, this::onRequestInfoLoad);
372         if (follower != null) {
373             follower.mViewModel = mViewModel;
374         }
375     }
376 
377     /**
378      * When the leader activity this activity delegated to finishes, finish this activity
379      * @param resultCode the result of the leader
380      */
onLeaderActivityFinished(int resultCode)381     private void onLeaderActivityFinished(int resultCode) {
382         setResultIfNeeded(resultCode);
383         finishAfterTransition();
384     }
385 
onRequestInfoLoad(List<RequestInfo> requests)386     private void onRequestInfoLoad(List<RequestInfo> requests) {
387         if (!mViewModel.getRequestInfosLiveData().isInitialized() || isResultSet() || mDelegated) {
388             return;
389         } else if (requests == null) {
390             finishAfterTransition();
391             return;
392         } else if (requests.isEmpty()) {
393             setResultAndFinish();
394             return;
395         }
396 
397         mRequestInfos = requests;
398 
399         // If we were already showing a group, and then another request came in with more groups,
400         // keep the current group showing until the user makes a decision
401         if (mPreMergeShownGroupName != null) {
402             return;
403         }
404 
405         showNextRequest();
406     }
407 
showNextRequest()408     private void showNextRequest() {
409         if (mRequestInfos.isEmpty()) {
410             return;
411         }
412 
413         RequestInfo info = mRequestInfos.get(0);
414 
415         // Only the top activity can receive activity results
416         Activity top = mFollowerActivities.isEmpty() ? this : mFollowerActivities.get(0);
417         if (info.getSendToSettingsImmediately()) {
418             mViewModel.sendDirectlyToSettings(top, info.getGroupName());
419             return;
420         } else if (info.getOpenPhotoPicker()) {
421             mViewModel.openPhotoPicker(top, GRANTED_USER_SELECTED);
422             return;
423         }
424 
425         if (Utils.isHealthPermissionUiEnabled() && HEALTH_PERMISSION_GROUP.equals(
426                 info.getGroupName())) {
427             mViewModel.handleHealthConnectPermissions(top);
428             return;
429         }
430 
431         CharSequence appLabel = KotlinUtils.INSTANCE.getPackageLabel(getApplication(),
432                 mTargetPackage, Process.myUserHandle());
433 
434         Icon icon = null;
435         int messageId = 0;
436         switch(info.getMessage()) {
437             case FG_MESSAGE:
438                 messageId = Utils.getRequest(info.getGroupName());
439                 break;
440             case FG_FINE_LOCATION_MESSAGE:
441                 messageId = R.string.permgrouprequest_fineupgrade;
442                 break;
443             case FG_COARSE_LOCATION_MESSAGE:
444                 messageId = R.string.permgrouprequest_coarselocation;
445                 break;
446             case BG_MESSAGE:
447                 messageId = Utils.getBackgroundRequest(info.getGroupName());
448                 break;
449             case UPGRADE_MESSAGE:
450                 messageId = Utils.getUpgradeRequest(info.getGroupName());
451                 break;
452             case STORAGE_SUPERGROUP_MESSAGE_Q_TO_S:
453                 icon = Icon.createWithResource(getPackageName(), mStoragePermGroupIcon);
454                 messageId = R.string.permgrouprequest_storage_q_to_s;
455                 break;
456             case STORAGE_SUPERGROUP_MESSAGE_PRE_Q:
457                 icon = Icon.createWithResource(getPackageName(), mStoragePermGroupIcon);
458                 messageId = R.string.permgrouprequest_storage_pre_q;
459                 break;
460             case MORE_PHOTOS_MESSAGE:
461                 messageId = R.string.permgrouprequest_more_photos;
462                 break;
463         }
464 
465         CharSequence message = getRequestMessage(appLabel, mTargetPackage,
466                 info.getGroupName(), this, messageId);
467 
468         int detailMessageId = 0;
469         switch(info.getDetailMessage()) {
470             case FG_MESSAGE:
471                 detailMessageId = Utils.getRequestDetail(info.getGroupName());
472                 break;
473             case BG_MESSAGE:
474                 detailMessageId = Utils.getBackgroundRequestDetail(info.getGroupName());
475                 break;
476             case UPGRADE_MESSAGE:
477                 detailMessageId = Utils.getUpgradeRequestDetail(info.getGroupName());
478         }
479 
480         Spanned detailMessage = null;
481         if (detailMessageId != 0) {
482             detailMessage =
483                     new SpannableString(getText(detailMessageId));
484             Annotation[] annotations = detailMessage.getSpans(
485                     0, detailMessage.length(), Annotation.class);
486             int numAnnotations = annotations.length;
487             for (int i = 0; i < numAnnotations; i++) {
488                 Annotation annotation = annotations[i];
489                 if (annotation.getValue().equals(ANNOTATION_ID)) {
490                     int start = detailMessage.getSpanStart(annotation);
491                     int end = detailMessage.getSpanEnd(annotation);
492                     ClickableSpan clickableSpan = getLinkToAppPermissions(info);
493                     SpannableString spannableString =
494                             new SpannableString(detailMessage);
495                     spannableString.setSpan(clickableSpan, start, end, 0);
496                     detailMessage = spannableString;
497                     break;
498                 }
499             }
500         }
501 
502         try {
503             icon = icon != null ? icon : Icon.createWithResource(
504                     info.getGroupInfo().getPackageName(),
505                     info.getGroupInfo().getIcon());
506         } catch (Resources.NotFoundException e) {
507             Log.e(LOG_TAG, "Cannot load icon for group" + info.getGroupName(), e);
508         }
509 
510         boolean showingNewGroup = message == null || !message.equals(getTitle());
511 
512         // Set the permission message as the title so it can be announced. Skip on Wear
513         // because the dialog title is already announced, as is the default selection which
514         // is a text view containing the title.
515         if (!DeviceUtils.isWear(this)) {
516             setTitle(message);
517         }
518 
519         ArrayList<Integer> idxs = new ArrayList<>();
520         mButtonVisibilities = new boolean[info.getButtonVisibilities().size()];
521         for (int i = 0; i < info.getButtonVisibilities().size(); i++) {
522             mButtonVisibilities[i] = info.getButtonVisibilities().get(i);
523             if (mButtonVisibilities[i]) {
524                 idxs.add(i);
525             }
526         }
527 
528         CharSequence permissionRationaleMessage = null;
529         if (isPermissionRationaleVisible()) {
530             permissionRationaleMessage =
531                 getString(
532                     getPermissionRationaleMessageResIdForPermissionGroup(
533                         info.getGroupName()));
534         }
535 
536         boolean[] locationVisibilities = new boolean[info.getLocationVisibilities().size()];
537         for (int i = 0; i < info.getLocationVisibilities().size(); i++) {
538             locationVisibilities[i] = info.getLocationVisibilities().get(i);
539         }
540 
541         if (mRequestCounts < mRequestInfos.size()) {
542             mRequestCounts = mRequestInfos.size();
543         }
544 
545         mViewHandler.updateUi(info.getGroupName(), mRequestCounts, mCurrentRequestIdx, icon,
546                 message, detailMessage, permissionRationaleMessage, mButtonVisibilities,
547                 locationVisibilities);
548         if (showingNewGroup) {
549             mCurrentRequestIdx++;
550         }
551 
552         getWindow().setDimAmount(mOriginalDimAmount);
553         if (mRootView.getVisibility() == View.GONE) {
554             InputMethodManager manager = getSystemService(InputMethodManager.class);
555             manager.hideSoftInputFromWindow(mRootView.getWindowToken(), 0);
556             mRootView.setVisibility(View.VISIBLE);
557         }
558     }
559 
560     // LINT.IfChange(dispatchTouchEvent)
561     @Override
dispatchTouchEvent(MotionEvent ev)562     public boolean dispatchTouchEvent(MotionEvent ev) {
563         View rootView = getWindow().getDecorView();
564         if (rootView.getTop() != 0) {
565             // We are animating the top view, need to compensate for that in motion events.
566             ev.setLocation(ev.getX(), ev.getY() - rootView.getTop());
567         }
568         final int x = (int) ev.getX();
569         final int y = (int) ev.getY();
570         if ((x < 0) || (y < 0) || (x > (rootView.getWidth())) || (y > (rootView.getHeight()))) {
571             if (MotionEvent.ACTION_DOWN == ev.getAction()) {
572                 mViewHandler.onCancelled();
573             }
574             finishAfterTransition();
575         }
576         return super.dispatchTouchEvent(ev);
577     }
578     // LINT.ThenChange(PermissionRationaleActivity.java:dispatchTouchEvent)
579 
580     @Override
onSaveInstanceState(@onNull Bundle outState)581     protected void onSaveInstanceState(@NonNull Bundle outState) {
582         super.onSaveInstanceState(outState);
583 
584         if (mViewHandler == null || mViewModel == null) {
585             return;
586         }
587 
588         mViewHandler.saveInstanceState(outState);
589         mViewModel.saveInstanceState(outState);
590 
591         outState.putLong(KEY_SESSION_ID, mSessionId);
592     }
593 
getLinkToAppPermissions(RequestInfo info)594     private ClickableSpan getLinkToAppPermissions(RequestInfo info) {
595         return new ClickableSpan() {
596             @Override
597             public void onClick(View widget) {
598                 logGrantPermissionActivityButtons(info.getGroupName(), null, LINKED_TO_SETTINGS);
599                 mViewModel.sendToSettingsFromLink(GrantPermissionsActivity.this,
600                         info.getGroupName());
601             }
602         };
603     }
604 
605 
606     @Override
607     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
608         super.onActivityResult(requestCode, resultCode, data);
609         Consumer<Intent> callback = mViewModel.getActivityResultCallback();
610         if (callback == null || (requestCode != APP_PERMISSION_REQUEST_CODE
611                 && requestCode != PHOTO_PICKER_REQUEST_CODE)) {
612             return;
613         }
614         if (requestCode == PHOTO_PICKER_REQUEST_CODE) {
615             data = new Intent("").putExtra(INTENT_PHOTOS_SELECTED, resultCode == RESULT_OK);
616         }
617         callback.accept(data);
618         mViewModel.setActivityResultCallback(null);
619     }
620 
621     @Override
622     public void onPermissionGrantResult(String name,
623             @GrantPermissionsViewHandler.Result int result) {
624         onPermissionGrantResult(name, null, result);
625     }
626 
627     @Override
628     public void onPermissionGrantResult(String name, List<String> affectedForegroundPermissions,
629             @GrantPermissionsViewHandler.Result int result) {
630         if (checkKgm(name, affectedForegroundPermissions, result)) {
631             return;
632         }
633 
634         if (name == null || name.equals(mPreMergeShownGroupName)) {
635             mPreMergeShownGroupName = null;
636         }
637 
638         if (Objects.equals(READ_MEDIA_VISUAL, name)
639                 && result == GrantPermissionsViewHandler.GRANTED_USER_SELECTED) {
640             // Only the top activity can receive activity results
641             Activity top = mFollowerActivities.isEmpty() ? this : mFollowerActivities.get(0);
642             mViewModel.openPhotoPicker(top, result);
643             logGrantPermissionActivityButtons(name, affectedForegroundPermissions, result);
644             return;
645         }
646 
647         logGrantPermissionActivityButtons(name, affectedForegroundPermissions, result);
648         mViewModel.onPermissionGrantResult(name, affectedForegroundPermissions, result);
649         showNextRequest();
650         if (result == CANCELED) {
651             setResultAndFinish();
652         }
653     }
654 
655     @Override
656     public void onPermissionRationaleClicked(String groupName) {
657         logGrantPermissionActivityButtons(groupName,
658                 /* affectedForegroundPermissions= */ null,
659                 LINKED_TO_PERMISSION_RATIONALE);
660         mViewModel.showPermissionRationaleActivity(this, groupName);
661     }
662 
663     @Override
664     public void onBackPressed() {
665         if (mViewHandler == null) {
666             return;
667         }
668         mViewHandler.onBackPressed();
669     }
670 
671     @Override
672     public void finishAfterTransition() {
673         if (!setResultIfNeeded(RESULT_CANCELED)) {
674             return;
675         }
676         if (mViewModel != null) {
677             mViewModel.autoGrantNotify();
678         }
679         super.finishAfterTransition();
680     }
681 
682     @Override
683     public void onDestroy() {
684         super.onDestroy();
685         if (!isResultSet()) {
686             removeActivityFromMap();
687         }
688     }
689 
690     /**
691      * Remove this activity from the map of activities
692      */
693     private void removeActivityFromMap() {
694         synchronized (sCurrentGrantRequests) {
695             GrantPermissionsActivity leader = sCurrentGrantRequests.get(mKey);
696             if (this.equals(leader)) {
697                 sCurrentGrantRequests.remove(mKey);
698             } else if (leader != null) {
699                 leader.mFollowerActivities.remove(this);
700             }
701         }
702         for (GrantPermissionsActivity activity: mFollowerActivities) {
703             activity.onLeaderActivityFinished(mResultCode);
704         }
705         mFollowerActivities.clear();
706     }
707 
708     private boolean checkKgm(String name, List<String> affectedForegroundPermissions,
709             @GrantPermissionsViewHandler.Result int result) {
710         if (result == GRANTED_ALWAYS || result == GRANTED_FOREGROUND_ONLY
711                 || result == DENIED_DO_NOT_ASK_AGAIN) {
712             KeyguardManager kgm = getSystemService(KeyguardManager.class);
713 
714             if (kgm != null && kgm.isDeviceLocked()) {
715                 kgm.requestDismissKeyguard(this, new KeyguardManager.KeyguardDismissCallback() {
716                     @Override
717                     public void onDismissError() {
718                         Log.e(LOG_TAG, "Cannot dismiss keyguard perm=" + name
719                                 + " result=" + result);
720                     }
721 
722                     @Override
723                     public void onDismissCancelled() {
724                         // do nothing (i.e. stay at the current permission group)
725                     }
726 
727                     @Override
728                     public void onDismissSucceeded() {
729                         // Now the keyguard is dismissed, hence the device is not locked
730                         // anymore
731                         onPermissionGrantResult(name, affectedForegroundPermissions, result);
732                     }
733                 });
734                 return true;
735             }
736         }
737         return false;
738     }
739 
740     private boolean setResultIfNeeded(int resultCode) {
741         if (!isResultSet()) {
742             List<String> oldRequestedPermissions = mRequestedPermissions;
743             removeActivityFromMap();
744             // If a new merge request came in before we managed to remove this activity from the
745             // map, then cancel the result set for now.
746             if (!Objects.equals(oldRequestedPermissions, mRequestedPermissions)) {
747                 return false;
748             }
749 
750             mResultCode = resultCode;
751             if (mViewModel != null) {
752                 mViewModel.logRequestedPermissionGroups();
753             }
754 
755             // Only include the originally requested permissions in the result
756             Intent result = new Intent(PackageManager.ACTION_REQUEST_PERMISSIONS);
757             String[] resultPermissions = mOriginalRequestedPermissions;
758             int[] grantResults = new int[resultPermissions.length];
759 
760             if ((mDelegated || (mViewModel != null && mViewModel.shouldReturnPermissionState()))
761                     && mTargetPackage != null) {
762                 PackageManager pm = getPackageManager();
763                 for (int i = 0; i < resultPermissions.length; i++) {
764                     grantResults[i] = pm.checkPermission(resultPermissions[i], mTargetPackage);
765                 }
766             } else {
767                 grantResults = new int[0];
768                 resultPermissions = new String[0];
769             }
770 
771             result.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES, resultPermissions);
772             result.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS, grantResults);
773             result.putExtra(Intent.EXTRA_PACKAGE_NAME, mTargetPackage);
774             setResult(resultCode, result);
775         }
776         return true;
777     }
778 
779     private void setResultAndFinish() {
780         if (setResultIfNeeded(RESULT_OK)) {
781             finishAfterTransition();
782         }
783     }
784 
785     private void logGrantPermissionActivityButtons(String permissionGroupName,
786             List<String> affectedForegroundPermissions, int grantResult) {
787         int clickedButton = 0;
788         int presentedButtons = getButtonState();
789         switch (grantResult) {
790             case GRANTED_ALWAYS:
791                 if (mButtonVisibilities[ALLOW_BUTTON]) {
792                     clickedButton = 1 << ALLOW_BUTTON;
793                 } else if (mButtonVisibilities[ALLOW_ALWAYS_BUTTON]) {
794                     clickedButton = 1 << ALLOW_ALWAYS_BUTTON;
795                 } else if (mButtonVisibilities[ALLOW_ALL_BUTTON]) {
796                     clickedButton = 1 << ALLOW_ALL_BUTTON;
797                 }
798                 break;
799             case GRANTED_FOREGROUND_ONLY:
800                 clickedButton = 1 << ALLOW_FOREGROUND_BUTTON;
801                 break;
802             case DENIED:
803                 if (mButtonVisibilities != null) {
804                     if (mButtonVisibilities[NO_UPGRADE_BUTTON]) {
805                         clickedButton = 1 << NO_UPGRADE_BUTTON;
806                     } else if (mButtonVisibilities[NO_UPGRADE_OT_BUTTON]) {
807                         clickedButton = 1 << NO_UPGRADE_OT_BUTTON;
808                     } else if (mButtonVisibilities[DENY_BUTTON]) {
809                         clickedButton = 1 << DENY_BUTTON;
810                     }
811                 }
812                 break;
813             case DENIED_DO_NOT_ASK_AGAIN:
814                 if (mButtonVisibilities != null) {
815                     if (mButtonVisibilities[NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON]) {
816                         clickedButton = 1 << NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON;
817                     } else if (mButtonVisibilities[NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON]) {
818                         clickedButton = 1 << NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON;
819                     } else if (mButtonVisibilities[DENY_AND_DONT_ASK_AGAIN_BUTTON]) {
820                         clickedButton = 1 << DENY_AND_DONT_ASK_AGAIN_BUTTON;
821                     }
822                 }
823                 break;
824             case GRANTED_ONE_TIME:
825                 clickedButton = 1 << ALLOW_ONE_TIME_BUTTON;
826                 break;
827             case LINKED_TO_SETTINGS:
828                 clickedButton = 1 << LINK_TO_SETTINGS;
829                 break;
830             case GRANTED_USER_SELECTED:
831                 clickedButton = 1 << ALLOW_SELECTED_BUTTON;
832                 break;
833             case DENIED_MORE:
834                 clickedButton = 1 << DONT_ALLOW_MORE_SELECTED_BUTTON;
835                 break;
836             case LINKED_TO_PERMISSION_RATIONALE:
837                 clickedButton = 1 << LINK_TO_PERMISSION_RATIONALE;
838                 break;
839             case CANCELED:
840                 // fall through
841             default:
842                 break;
843         }
844 
845         int selectedPrecision = 0;
846         if (affectedForegroundPermissions != null) {
847             for (Map.Entry<String, Integer> entry : PERMISSION_TO_BIT_SHIFT.entrySet()) {
848                 if (affectedForegroundPermissions.contains(entry.getKey())) {
849                     selectedPrecision |= 1 << entry.getValue();
850                 }
851             }
852         }
853 
854         mViewModel.logClickedButtons(permissionGroupName, selectedPrecision, clickedButton,
855                 presentedButtons, isPermissionRationaleVisible());
856     }
857 
858     private int getButtonState() {
859         if (mButtonVisibilities == null) {
860             return 0;
861         }
862         int buttonState = 0;
863         for (int i = NEXT_BUTTON - 1; i >= 0; i--) {
864             buttonState *= 2;
865             if (mButtonVisibilities[i]) {
866                 buttonState++;
867             }
868         }
869         return buttonState;
870     }
871 
872     private boolean isPermissionRationaleVisible() {
873         return mButtonVisibilities != null && mButtonVisibilities[LINK_TO_PERMISSION_RATIONALE];
874     }
875 
876     private boolean isResultSet() {
877         return mResultCode != Integer.MAX_VALUE;
878     }
879 
880     /**
881      * If there is another system-shown dialog on another task, that is not being relied upon by an
882      * app-defined dialogs, these other dialogs should be finished.
883      */
884     @GuardedBy("sCurrentGrantRequests")
885     private void finishSystemStartedDialogsOnOtherTasksLocked() {
886         for (Pair<String, Integer> key : sCurrentGrantRequests.keySet()) {
887             if (key.first.equals(mTargetPackage) && key.second != getTaskId()) {
888                 GrantPermissionsActivity other = sCurrentGrantRequests.get(key);
889                 if (other.mIsSystemTriggered && other.mFollowerActivities.isEmpty()) {
890                     other.finish();
891                 }
892             }
893         }
894     }
895 
896     /**
897      * Returns permission rationale message string resource id for the given permission group.
898      *
899      * <p> Supported permission groups: LOCATION
900      *
901      * @param permissionGroupName permission group for which to get a message string id
902      * @throws IllegalArgumentException if passing unsupported permission group
903      */
904     @StringRes
905     private int getPermissionRationaleMessageResIdForPermissionGroup(String permissionGroupName) {
906         Preconditions.checkArgument(LOCATION.equals(permissionGroupName),
907                 "Permission Rationale does not support %s", permissionGroupName);
908 
909         return R.string.permission_rationale_message_location;
910     }
911 }
912