• 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.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
24 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
25 
26 import static com.android.permissioncontroller.Constants.EXTRA_IS_ECM_IN_APP;
27 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.CANCELED;
28 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED;
29 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED_DO_NOT_ASK_AGAIN;
30 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED_MORE;
31 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_ALWAYS;
32 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_FOREGROUND_ONLY;
33 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_ONE_TIME;
34 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_USER_SELECTED;
35 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.LINKED_TO_PERMISSION_RATIONALE;
36 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.LINKED_TO_SETTINGS;
37 import static com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModel.APP_PERMISSION_REQUEST_CODE;
38 import static com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModel.ECM_REQUEST_CODE;
39 import static com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModel.PHOTO_PICKER_REQUEST_CODE;
40 import static com.android.permissioncontroller.permission.utils.Utils.getRequestMessage;
41 import static com.android.permissioncontroller.permission.utils.v35.MultiDeviceUtils.isDeviceAwarePermissionSupported;
42 
43 import android.Manifest;
44 import android.annotation.SuppressLint;
45 import android.app.KeyguardManager;
46 import android.app.ecm.EnhancedConfirmationManager;
47 import android.content.Context;
48 import android.content.Intent;
49 import android.content.pm.PackageItemInfo;
50 import android.content.pm.PackageManager;
51 import android.content.res.Resources;
52 import android.graphics.drawable.Icon;
53 import android.os.Build;
54 import android.os.Bundle;
55 import android.os.Process;
56 import android.os.UserHandle;
57 import android.permission.flags.Flags;
58 import android.text.Annotation;
59 import android.text.SpannableString;
60 import android.text.Spanned;
61 import android.text.style.ClickableSpan;
62 import android.util.ArraySet;
63 import android.util.Log;
64 import android.util.Pair;
65 import android.view.KeyEvent;
66 import android.view.View;
67 import android.view.View.OnAttachStateChangeListener;
68 import android.view.Window;
69 import android.view.WindowManager;
70 import android.view.inputmethod.InputMethodManager;
71 
72 import androidx.activity.result.ActivityResultLauncher;
73 import androidx.activity.result.contract.ActivityResultContracts;
74 import androidx.annotation.ChecksSdkIntAtLeast;
75 import androidx.annotation.GuardedBy;
76 import androidx.annotation.NonNull;
77 import androidx.annotation.Nullable;
78 import androidx.annotation.RequiresApi;
79 import androidx.annotation.StringRes;
80 import androidx.core.util.Preconditions;
81 
82 import com.android.modules.utils.build.SdkLevel;
83 import com.android.permissioncontroller.DeviceUtils;
84 import com.android.permissioncontroller.R;
85 import com.android.permissioncontroller.ecm.EnhancedConfirmationStatsLogUtils;
86 import com.android.permissioncontroller.permission.ui.auto.GrantPermissionsAutoViewHandler;
87 import com.android.permissioncontroller.permission.ui.model.DenyButton;
88 import com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModel;
89 import com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModel.RequestInfo;
90 import com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModelFactory;
91 import com.android.permissioncontroller.permission.ui.model.Prompt;
92 import com.android.permissioncontroller.permission.ui.wear.GrantPermissionsWearViewHandler;
93 import com.android.permissioncontroller.permission.utils.ContextCompat;
94 import com.android.permissioncontroller.permission.utils.KotlinUtils;
95 import com.android.permissioncontroller.permission.utils.PermissionMapping;
96 import com.android.permissioncontroller.permission.utils.Utils;
97 import com.android.permissioncontroller.permission.utils.v35.MultiDeviceUtils;
98 
99 import java.util.ArrayList;
100 import java.util.Arrays;
101 import java.util.HashMap;
102 import java.util.List;
103 import java.util.Map;
104 import java.util.Objects;
105 import java.util.Random;
106 import java.util.Set;
107 
108 /**
109  * An activity which displays runtime permission prompts on behalf of an app.
110  */
111 public class GrantPermissionsActivity extends SettingsActivity
112         implements GrantPermissionsViewHandler.ResultListener {
113 
114     private static final String LOG_TAG = "GrantPermissionsActivity";
115 
116     private static final String KEY_SESSION_ID = GrantPermissionsActivity.class.getName()
117             + "_REQUEST_ID";
118     public static final String KEY_RESTRICTED_REQUESTED_PERMISSIONS =
119             GrantPermissionsActivity.class.getName() + "_RESTRICTED_REQUESTED_PERMISSIONS";
120     public static final String KEY_UNRESTRICTED_REQUESTED_PERMISSIONS =
121             GrantPermissionsActivity.class.getName() + "_UNRESTRICTED_REQUESTED_PERMISSIONS";
122     public static final String KEY_ORIGINAL_REQUESTED_PERMISSIONS =
123             GrantPermissionsActivity.class.getName() + "_ORIGINAL_REQUESTED_PERMISSIONS";
124 
125     public static final String ANNOTATION_ID = "link";
126 
127     public static final int NEXT_BUTTON = 15;
128     public static final int ALLOW_BUTTON = 0;
129     public static final int ALLOW_ALWAYS_BUTTON = 1; // Used in auto
130     public static final int ALLOW_FOREGROUND_BUTTON = 2;
131     public static final int DENY_BUTTON = 3;
132     public static final int DENY_AND_DONT_ASK_AGAIN_BUTTON = 4;
133     public static final int ALLOW_ONE_TIME_BUTTON = 5;
134     public static final int NO_UPGRADE_BUTTON = 6;
135     public static final int NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON = 7;
136     public static final int NO_UPGRADE_OT_BUTTON = 8; // one-time
137     public static final int NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON = 9; // one-time
138     public static final int LINK_TO_SETTINGS = 10;
139     public static final int ALLOW_ALL_BUTTON = 11; // button for options with a picker, allow all
140     public static final int ALLOW_SELECTED_BUTTON = 12; // allow selected, with picker
141     // button to cancel a request for more data with a picker
142     public static final int DONT_ALLOW_MORE_SELECTED_BUTTON = 13;
143     public static final int LINK_TO_PERMISSION_RATIONALE = 14;
144 
145     public static final int NEXT_LOCATION_DIALOG = 6;
146     public static final int LOCATION_ACCURACY_LAYOUT = 0;
147     public static final int FINE_RADIO_BUTTON = 1;
148     public static final int COARSE_RADIO_BUTTON = 2;
149     public static final int DIALOG_WITH_BOTH_LOCATIONS = 3;
150     public static final int DIALOG_WITH_FINE_LOCATION_ONLY = 4;
151     public static final int DIALOG_WITH_COARSE_LOCATION_ONLY = 5;
152 
153     // The maximum number of dialogs we will allow the same package, on the same task, to launch
154     // simultaneously
155     public static final int MAX_DIALOGS_PER_PKG_TASK = 10;
156 
157     public static final Map<String, Integer> PERMISSION_TO_BIT_SHIFT =
158             Map.of(
159                     ACCESS_COARSE_LOCATION, 0,
160                     ACCESS_FINE_LOCATION, 1);
161 
162     public static final String INTENT_PHOTOS_SELECTED = "intent_extra_result";
163 
164     /**
165      * A map of the currently shown GrantPermissionsActivity for this user, per package and task ID
166      */
167     @GuardedBy("sCurrentGrantRequests")
168     public static final Map<Pair<String, Integer>, GrantPermissionsActivity> sCurrentGrantRequests =
169             new HashMap<>();
170 
171     /** Unique Id of a request */
172     private long mSessionId;
173 
174     /**
175      * The permission group that was showing, before a new permission request came in on top of an
176      * existing request
177      */
178     private String mPreMergeShownGroupName;
179 
180     /** The current list of permissions requested, across all current requests for this app */
181     private List<String> mRequestedPermissions = new ArrayList<>();
182 
183     /**
184      * If any requested permissions are considered restricted by ECM, they will be stored here.
185      */
186     private ArrayList<String> mRestrictedRequestedPermissionGroups = null;
187 
188     /**
189      * If any requested permissions are considered restricted by ECM, the non-restricted
190      * permissions will be stored here.
191      */
192     private List<String> mUnrestrictedRequestedPermissions = null;
193 
194     /** A list of permissions requested on an app's behalf by the system. Usually Implicitly
195      * requested, although this isn't necessarily always the case.
196      */
197     private final List<String> mSystemRequestedPermissions = new ArrayList<>();
198     /** A copy of the list of permissions originally requested in the intent to this activity */
199     private String[] mOriginalRequestedPermissions = new String[0];
200 
201     private boolean[] mButtonVisibilities;
202     private int mRequestCounts = 0;
203     private List<RequestInfo> mRequestInfos = new ArrayList<>();
204     private GrantPermissionsViewHandler mViewHandler;
205     private GrantPermissionsViewModel mViewModel;
206 
207     /**
208      * A list of other GrantPermissionActivities for the same package which passed their list of
209      * permissions to this one. They need to be informed when this activity finishes.
210      */
211     private final List<GrantPermissionsActivity> mFollowerActivities = new ArrayList<>();
212 
213     /** Whether this activity has asked another GrantPermissionsActivity to show on its behalf */
214     private boolean mDelegated;
215 
216     /** Whether this activity has been triggered by the system */
217     private boolean mIsSystemTriggered = false;
218 
219     /** The set result code, or MAX_VALUE if it hasn't been set yet */
220     private int mResultCode = Integer.MAX_VALUE;
221 
222     /** Package that shall have permissions granted */
223     private String mTargetPackage;
224 
225     /** A key representing this activity, defined by the target package and task ID */
226     private Pair<String, Integer> mKey;
227 
228     private float mOriginalDimAmount;
229     private View mRootView;
230     private int mStoragePermGroupIcon = R.drawable.ic_empty_icon;
231 
232     /** Which device the permission will affect. Default is the primary device. */
233     private int mTargetDeviceId = ContextCompat.DEVICE_ID_DEFAULT;
234 
235     private PackageManager mPackageManager;
236 
237     private final ActivityResultLauncher<Intent> mShowWarningDialog =
238             registerForActivityResult(
239                     new ActivityResultContracts.StartActivityForResult(),
240                     result -> {
241                         int resultCode = result.getResultCode();
242                         if (resultCode == RESULT_OK) {
243                             finishAfterTransition();
244                         }
245                     });
246 
247     @Override
onCreate(Bundle icicle)248     public void onCreate(Bundle icicle) {
249         mPackageManager = getPackageManager();
250         if (DeviceUtils.isAuto(this)) {
251             setTheme(R.style.GrantPermissions_Car_FilterTouches);
252         }
253         super.onCreate(icicle);
254 
255         if (icicle == null) {
256             mSessionId = new Random().nextLong();
257         } else {
258             mSessionId = icicle.getLong(KEY_SESSION_ID);
259         }
260 
261         getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
262         if (DeviceUtils.isWear(this)) {
263             // Do not grab input focus and hide keyboard.
264             getWindow().addFlags(FLAG_ALT_FOCUSABLE_IM);
265             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
266                 overrideActivityTransition(OVERRIDE_TRANSITION_OPEN, 0, 0);
267             }
268         }
269 
270         if (PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER.equals(getIntent().getAction())) {
271             mIsSystemTriggered = true;
272             mTargetPackage = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
273             if (mTargetPackage == null) {
274                 Log.e(LOG_TAG, "null EXTRA_PACKAGE_NAME. Must be set for "
275                         + "REQUEST_PERMISSIONS_FOR_OTHER activity");
276                 finishAfterTransition();
277                 return;
278             }
279         } else {
280             // Cache this as this can only read on onCreate, not later.
281             mTargetPackage = getCallingPackage();
282             if (mTargetPackage == null) {
283                 Log.e(LOG_TAG, "null callingPackageName. Please use \"RequestPermission\" to "
284                         + "request permissions");
285                 finishAfterTransition();
286                 return;
287             }
288             try {
289                 mPackageManager.getPackageInfo(mTargetPackage, 0);
290             } catch (PackageManager.NameNotFoundException e) {
291                 Log.e(LOG_TAG, "Unable to get package info for the calling package.", e);
292                 finishAfterTransition();
293                 return;
294             }
295         }
296 
297         String[] requestedPermissionsArray =
298                 getIntent().getStringArrayExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES);
299         if (requestedPermissionsArray == null) {
300             setResultAndFinish();
301             return;
302         }
303 
304         mRequestedPermissions = removeNullOrEmptyPermissions(requestedPermissionsArray);
305         mOriginalRequestedPermissions = mRequestedPermissions.toArray(new String[0]);
306 
307         // Do validation if permissions are requested for a remote device or the dialog is being
308         // streamed to a remote device.
309         if (isDeviceAwarePermissionSupported(getApplicationContext())) {
310             mTargetDeviceId = getIntent().getIntExtra(
311                     PackageManager.EXTRA_REQUEST_PERMISSIONS_DEVICE_ID,
312                     ContextCompat.DEVICE_ID_DEFAULT);
313 
314             mPackageManager = ContextCompat.createDeviceContext(this, mTargetDeviceId)
315                     .getPackageManager();
316 
317             // When the permission grant dialog is streamed to a virtual device, and when requested
318             // permissions include both device-aware permissions and non-device aware permissions,
319             // device-aware permissions will use virtual device id and non-device aware permissions
320             // will use default device id for granting. If flag is not enabled, we would show a
321             // warning dialog for this use case.
322             if (getDeviceId() != ContextCompat.DEVICE_ID_DEFAULT) {
323                 boolean showWarningDialog = mTargetDeviceId != getDeviceId();
324 
325                 for (String permission : mRequestedPermissions) {
326                     if (!MultiDeviceUtils.isPermissionDeviceAware(getApplicationContext(),
327                             mTargetDeviceId, permission)) {
328                         showWarningDialog = true;
329                         break;
330                     }
331                 }
332 
333                 if (showWarningDialog && !Flags.allowHostPermissionDialogsOnVirtualDevices()) {
334                     mShowWarningDialog.launch(
335                             new Intent(this, PermissionDialogStreamingBlockedActivity.class));
336                     return;
337                 }
338             }
339         }
340 
341         if (mRequestedPermissions.isEmpty()) {
342             setResultAndFinish();
343             return;
344         }
345 
346         if (mIsSystemTriggered) {
347             mSystemRequestedPermissions.addAll(mRequestedPermissions);
348         }
349 
350         if (blockRestrictedPermissions(icicle)) {
351             return;
352         }
353 
354         GrantPermissionsViewModelFactory factory =
355                 new GrantPermissionsViewModelFactory(
356                         getApplication(),
357                         mTargetPackage,
358                         mTargetDeviceId,
359                         mRequestedPermissions,
360                         mSystemRequestedPermissions,
361                         mSessionId,
362                         icicle);
363         mViewModel = factory.create(GrantPermissionsViewModel.class);
364 
365         synchronized (sCurrentGrantRequests) {
366             mKey = new Pair<>(mTargetPackage, getTaskId());
367             GrantPermissionsActivity current = sCurrentGrantRequests.get(mKey);
368             if (current == null) {
369                 sCurrentGrantRequests.put(mKey, this);
370                 finishSystemStartedDialogsOnOtherTasksLocked();
371             } else if (mIsSystemTriggered) {
372                 // The system triggered dialog doesn't require results. Delegate, and finish.
373                 current.onNewFollowerActivity(null, mRequestedPermissions, false);
374                 finishAfterTransition();
375                 return;
376             } else if (current.mIsSystemTriggered) {
377                 // merge into the system triggered dialog, which has task overlay set
378                 mDelegated = true;
379                 current.onNewFollowerActivity(this, mRequestedPermissions, false);
380             } else {
381                 // this + current + current.mFollowerActivities
382                 if ((current.mFollowerActivities.size() + 2) > MAX_DIALOGS_PER_PKG_TASK) {
383                     // If there are too many dialogs for the same package, in the same task, cancel
384                     finishAfterTransition();
385                     return;
386                 }
387                 // Merge the old dialogs into the new
388                 onNewFollowerActivity(current, current.mRequestedPermissions, true);
389                 sCurrentGrantRequests.put(mKey, this);
390             }
391         }
392 
393         setFinishOnTouchOutside(false);
394 
395         setTitle(R.string.permission_request_title);
396 
397         if (DeviceUtils.isTelevision(this)) {
398             mViewHandler = new com.android.permissioncontroller.permission.ui.television
399                     .GrantPermissionsViewHandlerImpl(this,
400                     mTargetPackage).setResultListener(this);
401         } else if (DeviceUtils.isWear(this)) {
402             mViewHandler = new GrantPermissionsWearViewHandler(this).setResultListener(this);
403         } else if (DeviceUtils.isAuto(this)) {
404             mViewHandler = new GrantPermissionsAutoViewHandler(this, mTargetPackage)
405                     .setResultListener(this);
406         } else {
407             mViewHandler = new com.android.permissioncontroller.permission.ui.handheld
408                     .GrantPermissionsViewHandlerImpl(this, this);
409         }
410 
411         if (!mDelegated) {
412             mViewModel.getRequestInfosLiveData().observe(this, this::onRequestInfoLoad);
413         }
414 
415         mRootView = mViewHandler.createView();
416         mRootView.setVisibility(View.GONE);
417         setContentView(mRootView);
418         Window window = getWindow();
419         WindowManager.LayoutParams layoutParams = window.getAttributes();
420         mOriginalDimAmount = layoutParams.dimAmount;
421         mViewHandler.updateWindowAttributes(layoutParams);
422         window.setAttributes(layoutParams);
423 
424         if (SdkLevel.isAtLeastS() && getResources().getBoolean(R.bool.config_useWindowBlur)) {
425             java.util.function.Consumer<Boolean> blurEnabledListener = enabled -> {
426                 mViewHandler.onBlurEnabledChanged(window, enabled);
427             };
428             mRootView.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
429                 @Override
430                 public void onViewAttachedToWindow(View v) {
431                     window.getWindowManager().addCrossWindowBlurEnabledListener(
432                             blurEnabledListener);
433                 }
434 
435                 @Override
436                 public void onViewDetachedFromWindow(View v) {
437                     window.getWindowManager().removeCrossWindowBlurEnabledListener(
438                             blurEnabledListener);
439                 }
440             });
441         }
442         // Restore UI state after lifecycle events. This has to be before we show the first request,
443         // as the UI behaves differently for updates and initial creations.
444         if (icicle != null) {
445             mViewHandler.loadInstanceState(icicle);
446         } else if (mRootView == null || mRootView.getVisibility() != View.VISIBLE) {
447             // Do not show screen dim until data is loaded
448             window.setDimAmount(0f);
449         }
450 
451         PackageItemInfo storageGroupInfo =
452                 Utils.getGroupInfo(Manifest.permission_group.STORAGE, this.getApplicationContext());
453         if (storageGroupInfo != null) {
454             mStoragePermGroupIcon = storageGroupInfo.icon;
455         }
456     }
457 
458     /*
459      * Block permissions that are restricted by ECM (Enhanced Confirmation Mode).
460      *
461      * If any requested permissions are restricted, then:
462      *
463      * - Strip them from mRequestedPermissions (so no grant dialog appears for those permissions).
464      * - Group the restricted permissions into permission groups.
465      * - Show the EnhancedConfirmationDialogActivity for each group. Each showing requires a
466      *   cross-activity loop during which GrantPermissionActivity will be recreated.
467      * - Finally, continue processing all non-restricted requested permissions normally
468      *
469      * Returns true if we're going to show the ECM dialog (and therefore GrantPermissionsActivity
470      * will be recreated)
471      */
472     @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.VANILLA_ICE_CREAM, codename = "VanillaIceCream")
blockRestrictedPermissions(Bundle icicle)473     private boolean blockRestrictedPermissions(Bundle icicle) {
474         if (!SdkLevel.isAtLeastV() || !Flags.enhancedConfirmationModeApisEnabled()) {
475             return false;
476         }
477         Context userContext = Utils.getUserContext(this, Process.myUserHandle());
478         EnhancedConfirmationManager ecm = Utils.getSystemServiceSafe(userContext,
479                 EnhancedConfirmationManager.class);
480 
481         // Retrieve ECM-related persisted permission lists
482         if (icicle != null) {
483             mOriginalRequestedPermissions = icicle.getStringArray(
484                     KEY_ORIGINAL_REQUESTED_PERMISSIONS);
485             mRestrictedRequestedPermissionGroups = icicle.getStringArrayList(
486                     KEY_RESTRICTED_REQUESTED_PERMISSIONS);
487             mUnrestrictedRequestedPermissions = icicle.getStringArrayList(
488                     KEY_UNRESTRICTED_REQUESTED_PERMISSIONS);
489         }
490         // If these lists aren't persisted yet, it means we haven't yet divided
491         // mRequestedPermissions into restricted-vs-unrestricted, so do so.
492         if (mRestrictedRequestedPermissionGroups == null) {
493             ArraySet<String> restrictedPermGroups = new ArraySet<>();
494             ArrayList<String> unrestrictedPermissions = new ArrayList<>();
495 
496             for (String requestedPermission : mRequestedPermissions) {
497                 String requestedPermGroup = PermissionMapping.getGroupOfPlatformPermission(
498                         requestedPermission);
499                 if (restrictedPermGroups.contains(requestedPermGroup)) {
500                     continue;
501                 }
502                 if (requestedPermGroup != null && isPermissionEcmRestricted(ecm,
503                         requestedPermission, mTargetPackage)) {
504                     restrictedPermGroups.add(requestedPermGroup);
505                 } else {
506                     unrestrictedPermissions.add(requestedPermission);
507                 }
508             }
509             mUnrestrictedRequestedPermissions = unrestrictedPermissions;
510             // If there are restricted permissions, and the ECM dialog has already been shown
511             // for this app, then we don't want to show it again. Act as if these restricted
512             // permissions weren't // requested at all, and log that we ignored them.
513             if (!restrictedPermGroups.isEmpty() && wasEcmDialogAlreadyShown(ecm, mTargetPackage)) {
514                 for (String ignoredPermGroup : restrictedPermGroups) {
515                     EnhancedConfirmationStatsLogUtils.INSTANCE.logDialogResultReported(
516                             getPackageUid(getCallingPackage(), Process.myUserHandle()),
517                             /* settingIdentifier */ ignoredPermGroup, /* firstShowForApp */ false,
518                             EnhancedConfirmationStatsLogUtils.DialogResult.Suppressed);
519                 }
520                 mRestrictedRequestedPermissionGroups = new ArrayList<>();
521             } else {
522                 mRestrictedRequestedPermissionGroups = new ArrayList<>(restrictedPermGroups);
523             }
524         }
525         // If there are remaining restricted permission groups to process, show the ECM dialog
526         // for the next one, then recreate this activity.
527         if (!mRestrictedRequestedPermissionGroups.isEmpty()) {
528             String nextRestrictedPermissionGroup = mRestrictedRequestedPermissionGroups.remove(0);
529             try {
530                 Intent intent = ecm.createRestrictedSettingDialogIntent(mTargetPackage,
531                         nextRestrictedPermissionGroup);
532                 intent.putExtra(EXTRA_IS_ECM_IN_APP, true);
533                 startActivityForResult(intent, ECM_REQUEST_CODE);
534                 return true;
535             } catch (PackageManager.NameNotFoundException e) {
536                 mRequestedPermissions = mUnrestrictedRequestedPermissions;
537             }
538         } else {
539             mRequestedPermissions = mUnrestrictedRequestedPermissions;
540         }
541         return false;
542     }
543 
544     @SuppressLint("MissingPermission")
getPackageUid(String packageName, UserHandle user)545     private int getPackageUid(String packageName, UserHandle user) {
546         try {
547             return mPackageManager.getApplicationInfoAsUser(packageName, 0, user).uid;
548         } catch (PackageManager.NameNotFoundException e) {
549             return android.os.Process.INVALID_UID;
550         }
551     }
552 
553     @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
isPermissionEcmRestricted(EnhancedConfirmationManager ecm, String requestedPermission, String packageName)554     private boolean isPermissionEcmRestricted(EnhancedConfirmationManager ecm,
555             String requestedPermission, String packageName) {
556         try {
557             return ecm.isRestricted(packageName, requestedPermission);
558         } catch (PackageManager.NameNotFoundException e) {
559             return false;
560         }
561     }
562 
563     @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
wasEcmDialogAlreadyShown(EnhancedConfirmationManager ecm, String packageName)564     private boolean wasEcmDialogAlreadyShown(EnhancedConfirmationManager ecm,
565             String packageName) {
566         try {
567             return ecm.isClearRestrictionAllowed(packageName);
568         } catch (PackageManager.NameNotFoundException e) {
569             return false;
570         }
571     }
572 
573     /**
574      * A new GrantPermissionsActivity has opened for this same package. Merge its requested
575      * permissions with the original ones set in the intent, and recalculate the grant states.
576      * @param follower The activity requesting permissions, which needs to be informed upon this
577      *                 activity finishing
578      * @param newPermissions The new permissions requested in the activity
579      */
onNewFollowerActivity(@ullable GrantPermissionsActivity follower, @NonNull List<String> newPermissions, boolean followerIsOlder)580     private void onNewFollowerActivity(@Nullable GrantPermissionsActivity follower,
581             @NonNull List<String> newPermissions, boolean followerIsOlder) {
582         if (follower != null) {
583             // Ensure the list of follower activities is a stack
584             mFollowerActivities.add(0, follower);
585             follower.mViewModel = mViewModel;
586             if (followerIsOlder) {
587                 follower.mDelegated = true;
588             }
589         }
590 
591         // If the follower is older, examine it to find the pre-merge group
592         GrantPermissionsActivity olderActivity = follower != null && followerIsOlder
593                 ? follower : this;
594         boolean isShowingGroup = olderActivity.mRootView != null
595                 && olderActivity.mRootView.getVisibility() == View.VISIBLE;
596         List<RequestInfo> currentGroups =
597                 olderActivity.mViewModel.getRequestInfosLiveData().getValue();
598         if (mPreMergeShownGroupName == null && isShowingGroup
599                 && currentGroups != null && !currentGroups.isEmpty()) {
600             mPreMergeShownGroupName = currentGroups.get(0).getGroupName();
601         }
602 
603         if (isShowingGroup && mPreMergeShownGroupName != null
604                 && followerIsOlder && currentGroups != null) {
605             // Load a request from the old activity
606             mRequestInfos = currentGroups;
607             showNextRequest();
608             olderActivity.mRootView.setVisibility(View.GONE);
609         }
610         if (follower != null && followerIsOlder) {
611             follower.mFollowerActivities.forEach((oldFollower) ->
612                     onNewFollowerActivity(oldFollower, new ArrayList<>(), true));
613             follower.mFollowerActivities.clear();
614         }
615 
616         if (mRequestedPermissions.containsAll(newPermissions)) {
617             return;
618         }
619 
620         ArrayList<String> currentPermissions = new ArrayList<>(mRequestedPermissions);
621         for (String newPerm : newPermissions) {
622             if (!currentPermissions.contains(newPerm)) {
623                 currentPermissions.add(newPerm);
624             }
625         }
626         mRequestedPermissions = currentPermissions;
627 
628         Bundle oldState = new Bundle();
629         mViewModel.getRequestInfosLiveData().removeObservers(this);
630         mViewModel.saveInstanceState(oldState);
631         GrantPermissionsViewModelFactory factory =
632                 new GrantPermissionsViewModelFactory(
633                         getApplication(),
634                         mTargetPackage,
635                         mTargetDeviceId,
636                         mRequestedPermissions,
637                         mSystemRequestedPermissions,
638                         mSessionId,
639                         oldState);
640         mViewModel = factory.create(GrantPermissionsViewModel.class);
641         mViewModel.getRequestInfosLiveData().observe(this, this::onRequestInfoLoad);
642         if (follower != null) {
643             follower.mViewModel = mViewModel;
644         }
645     }
646 
647     /**
648      * When the leader activity this activity delegated to finishes, finish this activity
649      * @param resultCode the result of the leader
650      */
onLeaderActivityFinished(int resultCode)651     private void onLeaderActivityFinished(int resultCode) {
652         setResultIfNeeded(resultCode);
653         finishAfterTransition();
654     }
655 
onRequestInfoLoad(List<RequestInfo> requests)656     private void onRequestInfoLoad(List<RequestInfo> requests) {
657         if (!mViewModel.getRequestInfosLiveData().isInitialized() || isResultSet() || mDelegated) {
658             return;
659         } else if (requests == null) {
660             finishAfterTransition();
661             return;
662         } else if (requests.isEmpty()) {
663             setResultAndFinish();
664             return;
665         }
666 
667         mRequestInfos = requests;
668 
669         // If we were already showing a group, and then another request came in with more groups,
670         // keep the current group showing until the user makes a decision
671         if (mPreMergeShownGroupName != null) {
672             return;
673         }
674 
675         showNextRequest();
676     }
677 
showNextRequest()678     private void showNextRequest() {
679         if (mRequestInfos.isEmpty() || mDelegated) {
680             return;
681         }
682 
683         RequestInfo info = mRequestInfos.get(0);
684 
685         if (info.getPrompt() == Prompt.NO_UI_SETTINGS_REDIRECT) {
686             mViewModel.sendDirectlyToSettings(this, info.getGroupName());
687             return;
688         } else if (info.getPrompt() == Prompt.NO_UI_PHOTO_PICKER_REDIRECT) {
689             mViewModel.openPhotoPicker(this);
690             return;
691         } else if (info.getPrompt() == Prompt.NO_UI_FILTER_THIS_GROUP) {
692             // Filtered permissions should be removed from the requested permissions list entirely,
693             // and not have status returned to the app
694             List<String> permissionsToFilter =
695                     PermissionMapping.getPlatformPermissionNamesOfGroup(info.getGroupName());
696             mRequestedPermissions.removeAll(permissionsToFilter);
697             mRequestInfos.remove(info);
698             onRequestInfoLoad(mRequestInfos);
699             return;
700         } else if (info.getPrompt() == Prompt.NO_UI_HEALTH_REDIRECT) {
701             // Clear UI on current PermissionController screen to avoid flashing back to previous
702             // permission group UI when returned from Health&Fitness.
703             mViewHandler.updateUi(
704                 /* groupName= */ "",
705                 /* groupCount= */ 0,
706                 /* groupIndex= */ 0,
707                 /* icon= */ null,
708                 /* message= */ "",
709                 /* detailMessage= */ "",
710                 /* permissionRationaleMessage= */ "",
711                 /* buttonVisibilities= */ new boolean[] {},
712                 /* locationVisibilities=*/ new boolean[] {});
713             mViewModel.handleHealthConnectPermissions(this);
714             return;
715         }
716 
717         String appLabel =
718                 KotlinUtils.INSTANCE.getPackageLabel(
719                         getApplication(), mTargetPackage, Process.myUserHandle());
720 
721         // Show device name in the dialog when the dialog is streamed to a remote device OR
722         // target device is different from streamed device.
723         int dialogDisplayDeviceId = ContextCompat.getDeviceId(this);
724         boolean isMessageDeviceAware =
725                 dialogDisplayDeviceId != ContextCompat.DEVICE_ID_DEFAULT
726                         || dialogDisplayDeviceId != info.getDeviceId();
727 
728         int messageId = getMessageId(info.getGroupName(), info.getPrompt(), isMessageDeviceAware);
729         CharSequence message =
730                 getRequestMessage(
731                         appLabel,
732                         mTargetPackage,
733                         info.getGroupName(),
734                         MultiDeviceUtils.getDeviceName(getApplicationContext(), info.getDeviceId()),
735                         this,
736                         isMessageDeviceAware,
737                         messageId);
738 
739         int detailMessageId = getDetailMessageId(info.getGroupName(), info.getPrompt());
740         Spanned detailMessage = null;
741         if (detailMessageId != 0) {
742             detailMessage = new SpannableString(getText(detailMessageId));
743             Annotation[] annotations =
744                     detailMessage.getSpans(0, detailMessage.length(), Annotation.class);
745             int numAnnotations = annotations.length;
746             for (int i = 0; i < numAnnotations; i++) {
747                 Annotation annotation = annotations[i];
748                 if (annotation.getValue().equals(ANNOTATION_ID)) {
749                     int start = detailMessage.getSpanStart(annotation);
750                     int end = detailMessage.getSpanEnd(annotation);
751                     ClickableSpan clickableSpan = getLinkToAppPermissions(info);
752                     SpannableString spannableString = new SpannableString(detailMessage);
753                     spannableString.setSpan(clickableSpan, start, end, 0);
754                     detailMessage = spannableString;
755                     break;
756                 }
757             }
758         }
759 
760         Icon icon = null;
761         try {
762             if (info.getPrompt() == Prompt.STORAGE_SUPERGROUP_Q_TO_S
763                     || info.getPrompt() == Prompt.STORAGE_SUPERGROUP_PRE_Q) {
764                 icon = Icon.createWithResource(getPackageName(), mStoragePermGroupIcon);
765             } else {
766                 icon = Icon.createWithResource(
767                         info.getGroupInfo().getPackageName(),
768                         info.getGroupInfo().getIcon());
769             }
770         } catch (Resources.NotFoundException e) {
771             Log.e(LOG_TAG, "Cannot load icon for group" + info.getGroupName(), e);
772         }
773 
774         // Set the permission message as the title so it can be announced. Skip on Wear
775         // because the dialog title is already announced, as is the default selection which
776         // is a text view containing the title.
777         if (!DeviceUtils.isWear(this)) {
778             setTitle(message);
779         }
780 
781         mButtonVisibilities = getButtonsForPrompt(info.getPrompt(), info.getDeny(),
782                 info.getShowRationale());
783 
784         CharSequence permissionRationaleMessage = null;
785         if (isPermissionRationaleVisible()) {
786             permissionRationaleMessage =
787                 getString(
788                     getPermissionRationaleMessageResIdForPermissionGroup(
789                         info.getGroupName()));
790         }
791 
792         boolean[] locationVisibilities = getLocationButtonsForPrompt(info.getPrompt());
793 
794         if (mRequestCounts < mRequestInfos.size()) {
795             mRequestCounts = mRequestInfos.size();
796         }
797 
798         int pageIdx = mRequestCounts - mRequestInfos.size();
799         mViewHandler.updateUi(info.getGroupName(), mRequestCounts, pageIdx, icon,
800                 message, detailMessage, permissionRationaleMessage, mButtonVisibilities,
801                 locationVisibilities);
802 
803         getWindow().setDimAmount(mOriginalDimAmount);
804         if (mRootView.getVisibility() == View.GONE) {
805             if (mIsSystemTriggered) {
806                 // We don't want the keyboard obscuring system-triggered dialogs
807                 InputMethodManager manager = getSystemService(InputMethodManager.class);
808                 manager.hideSoftInputFromWindow(mRootView.getWindowToken(), 0);
809             }
810             mRootView.setVisibility(View.VISIBLE);
811         }
812     }
813 
getMessageId(String permGroupName, Prompt prompt, Boolean isDeviceAware)814     private int getMessageId(String permGroupName, Prompt prompt, Boolean isDeviceAware) {
815         return switch (prompt) {
816             case UPGRADE_SETTINGS_LINK, OT_UPGRADE_SETTINGS_LINK -> Utils.getUpgradeRequest(
817                     permGroupName, isDeviceAware);
818             case SETTINGS_LINK_FOR_BG, SETTINGS_LINK_WITH_OT -> Utils.getBackgroundRequest(
819                     permGroupName, isDeviceAware);
820             case LOCATION_FINE_UPGRADE -> Utils.getFineLocationRequest(isDeviceAware);
821             case LOCATION_COARSE_ONLY -> Utils.getCoarseLocationRequest(isDeviceAware);
822             case STORAGE_SUPERGROUP_PRE_Q -> R.string.permgrouprequest_storage_pre_q;
823             case STORAGE_SUPERGROUP_Q_TO_S -> R.string.permgrouprequest_storage_q_to_s;
824             case SELECT_MORE_PHOTOS -> Utils.getMorePhotosRequest(isDeviceAware);
825             default -> Utils.getRequest(permGroupName, isDeviceAware);
826         };
827     }
828 
getDetailMessageId(String permGroupName, Prompt prompt)829     private int getDetailMessageId(String permGroupName, Prompt prompt) {
830         return switch (prompt) {
831             case UPGRADE_SETTINGS_LINK, OT_UPGRADE_SETTINGS_LINK ->
832                     Utils.getUpgradeRequestDetail(permGroupName);
833             case SETTINGS_LINK_FOR_BG, SETTINGS_LINK_WITH_OT ->
834                     Utils.getBackgroundRequestDetail(permGroupName);
835             default -> 0;
836         };
837     }
838 
839     private boolean[] getButtonsForPrompt(Prompt prompt, DenyButton denyButton,
840                                           boolean shouldShowRationale) {
841         ArraySet<Integer> buttons = new ArraySet<>();
842         switch (prompt) {
843             case BASIC, STORAGE_SUPERGROUP_PRE_Q, STORAGE_SUPERGROUP_Q_TO_S ->
844                     buttons.add(ALLOW_BUTTON);
845             case FG_ONLY, SETTINGS_LINK_FOR_BG ->  buttons.add(ALLOW_FOREGROUND_BUTTON);
846             case ONE_TIME_FG, SETTINGS_LINK_WITH_OT, LOCATION_TWO_BUTTON_COARSE_HIGHLIGHT,
847                     LOCATION_TWO_BUTTON_FINE_HIGHLIGHT, LOCATION_COARSE_ONLY,
848                     LOCATION_FINE_UPGRADE ->
849                 buttons.addAll(Arrays.asList(ALLOW_FOREGROUND_BUTTON, ALLOW_ONE_TIME_BUTTON));
850             case SELECT_PHOTOS, SELECT_MORE_PHOTOS ->
851                 buttons.addAll(Arrays.asList(ALLOW_ALL_BUTTON, ALLOW_SELECTED_BUTTON));
852         }
853 
854         switch (denyButton) {
855             case DENY -> buttons.add(DENY_BUTTON);
856             case DENY_DONT_ASK_AGAIN -> buttons.add(DENY_AND_DONT_ASK_AGAIN_BUTTON);
857             case DONT_SELECT_MORE -> buttons.add(DONT_ALLOW_MORE_SELECTED_BUTTON);
858             case NO_UPGRADE -> buttons.add(NO_UPGRADE_BUTTON);
859             case NO_UPGRADE_OT -> buttons.add(NO_UPGRADE_OT_BUTTON);
860             case NO_UPGRADE_AND_DONT_ASK_AGAIN ->
861                     buttons.add(NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON);
862             case NO_UPGRADE_AND_DONT_ASK_AGAIN_OT ->
863                     buttons.add(NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON);
864         }
865 
866         if (shouldShowRationale) {
867             buttons.add(LINK_TO_PERMISSION_RATIONALE);
868         }
869         return convertSetToBoolList(buttons, NEXT_BUTTON);
870     }
871 
872     private boolean[] getLocationButtonsForPrompt(Prompt prompt) {
873         ArraySet<Integer> locationButtons = new ArraySet<>();
874         switch (prompt) {
875             case LOCATION_TWO_BUTTON_COARSE_HIGHLIGHT ->
876                 locationButtons.addAll(Arrays.asList(LOCATION_ACCURACY_LAYOUT,
877                         DIALOG_WITH_BOTH_LOCATIONS, COARSE_RADIO_BUTTON));
878             case LOCATION_TWO_BUTTON_FINE_HIGHLIGHT ->
879                 locationButtons.addAll(Arrays.asList(LOCATION_ACCURACY_LAYOUT,
880                         DIALOG_WITH_BOTH_LOCATIONS, FINE_RADIO_BUTTON));
881             case LOCATION_COARSE_ONLY ->
882                 locationButtons.addAll(Arrays.asList(LOCATION_ACCURACY_LAYOUT,
883                         DIALOG_WITH_COARSE_LOCATION_ONLY));
884             case LOCATION_FINE_UPGRADE ->
885                 locationButtons.addAll(Arrays.asList(LOCATION_ACCURACY_LAYOUT,
886                         DIALOG_WITH_FINE_LOCATION_ONLY));
887         }
888         return convertSetToBoolList(locationButtons, NEXT_LOCATION_DIALOG);
889     }
890 
891     private boolean[] convertSetToBoolList(Set<Integer> buttonSet, int size) {
892         boolean[] buttonArray = new boolean[size];
893         for (int button: buttonSet) {
894             buttonArray[button] = true;
895         }
896         return buttonArray;
897     }
898 
899     @Override
900     public boolean onKeyDown(int keyCode, KeyEvent event) {
901         if (keyCode == KeyEvent.KEYCODE_ESCAPE
902                 && event.getRepeatCount() == 0
903                 && event.hasNoModifiers()) {
904             event.startTracking();
905             mViewHandler.onCancelled();
906             finishAfterTransition();
907             return true;
908         }
909         return super.onKeyDown(keyCode, event);
910     }
911 
912     @Override
913     public boolean onKeyUp(int keyCode, KeyEvent event) {
914         if (keyCode == KeyEvent.KEYCODE_ESCAPE
915                 && event.isTracking()
916                 && !event.isCanceled()) {
917             // Mark it as handled since we did handle the down event
918             return true;
919         }
920         return super.onKeyUp(keyCode, event);
921     }
922 
923     @Override
924     protected void onSaveInstanceState(@NonNull Bundle outState) {
925         super.onSaveInstanceState(outState);
926 
927         if (SdkLevel.isAtLeastV() && Flags.enhancedConfirmationModeApisEnabled()) {
928             outState.putStringArrayList(KEY_RESTRICTED_REQUESTED_PERMISSIONS,
929                     mRestrictedRequestedPermissionGroups != null ? new ArrayList<>(
930                             mRestrictedRequestedPermissionGroups) : null);
931             outState.putStringArrayList(KEY_UNRESTRICTED_REQUESTED_PERMISSIONS,
932                     mUnrestrictedRequestedPermissions != null ? new ArrayList<>(
933                             mUnrestrictedRequestedPermissions) : null);
934             outState.putStringArray(KEY_ORIGINAL_REQUESTED_PERMISSIONS,
935                     mOriginalRequestedPermissions);
936         }
937 
938         if (mViewHandler == null || mViewModel == null) {
939             return;
940         }
941 
942         mViewHandler.saveInstanceState(outState);
943         mViewModel.saveInstanceState(outState);
944 
945         outState.putLong(KEY_SESSION_ID, mSessionId);
946     }
947 
948     private ClickableSpan getLinkToAppPermissions(RequestInfo info) {
949         return new ClickableSpan() {
950             @Override
951             public void onClick(View widget) {
952                 logGrantPermissionActivityButtons(info.getGroupName(), null, LINKED_TO_SETTINGS);
953                 mViewModel.sendToSettingsFromLink(GrantPermissionsActivity.this,
954                         info.getGroupName());
955             }
956         };
957     }
958 
959 
960     @Override
961     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
962         super.onActivityResult(requestCode, resultCode, data);
963         if (SdkLevel.isAtLeastV() && Flags.enhancedConfirmationModeApisEnabled()) {
964             if (requestCode == ECM_REQUEST_CODE) {
965                 recreate();
966                 return;
967             }
968         }
969         if (requestCode != APP_PERMISSION_REQUEST_CODE
970                 && requestCode != PHOTO_PICKER_REQUEST_CODE) {
971             return;
972         }
973         if (requestCode == PHOTO_PICKER_REQUEST_CODE) {
974             data = new Intent("").putExtra(INTENT_PHOTOS_SELECTED, resultCode == RESULT_OK);
975         }
976         mViewModel.handleCallback(data, requestCode);
977     }
978 
979     @Override
980     public void onPermissionGrantResult(String name,
981             @GrantPermissionsViewHandler.Result int result) {
982         onPermissionGrantResult(name, null, result);
983     }
984 
985     @Override
986     public void onPermissionGrantResult(String name, List<String> affectedForegroundPermissions,
987             @GrantPermissionsViewHandler.Result int result) {
988         if (checkKgm(name, affectedForegroundPermissions, result)) {
989             return;
990         }
991 
992         if (name == null || name.equals(mPreMergeShownGroupName)) {
993             mPreMergeShownGroupName = null;
994         }
995 
996         if (Objects.equals(READ_MEDIA_VISUAL, name) && result == GRANTED_USER_SELECTED) {
997             mViewModel.openPhotoPicker(this);
998             logGrantPermissionActivityButtons(name, affectedForegroundPermissions, result);
999             return;
1000         }
1001 
1002         logGrantPermissionActivityButtons(name, affectedForegroundPermissions, result);
1003         mViewModel.onPermissionGrantResult(name, affectedForegroundPermissions, result);
1004         if (result == CANCELED) {
1005             setResultAndFinish();
1006         }
1007     }
1008 
1009     @Override
1010     public void onPermissionRationaleClicked(String groupName) {
1011         logGrantPermissionActivityButtons(groupName,
1012                 /* affectedForegroundPermissions= */ null,
1013                 LINKED_TO_PERMISSION_RATIONALE);
1014         mViewModel.showPermissionRationaleActivity(this, groupName);
1015     }
1016 
1017     @Override
1018     public void onBackPressed() {
1019         if (mViewHandler == null) {
1020             return;
1021         }
1022         mViewHandler.onBackPressed();
1023     }
1024 
1025     @Override
1026     public void finishAfterTransition() {
1027         if (!setResultIfNeeded(RESULT_CANCELED)) {
1028             return;
1029         }
1030         if (mViewModel != null) {
1031             mViewModel.autoGrantNotify();
1032         }
1033         super.finishAfterTransition();
1034     }
1035 
1036     @Override
1037     public void onDestroy() {
1038         super.onDestroy();
1039         if (!isResultSet()) {
1040             removeActivityFromMap();
1041         }
1042     }
1043 
1044     /**
1045      * Remove this activity from the map of activities
1046      */
1047     private void removeActivityFromMap() {
1048         synchronized (sCurrentGrantRequests) {
1049             GrantPermissionsActivity leader = sCurrentGrantRequests.get(mKey);
1050             if (this.equals(leader)) {
1051                 sCurrentGrantRequests.remove(mKey);
1052             } else if (leader != null) {
1053                 leader.mFollowerActivities.remove(this);
1054             }
1055         }
1056         for (GrantPermissionsActivity activity: mFollowerActivities) {
1057             activity.onLeaderActivityFinished(mResultCode);
1058         }
1059         mFollowerActivities.clear();
1060     }
1061 
1062     private boolean checkKgm(String name, List<String> affectedForegroundPermissions,
1063             @GrantPermissionsViewHandler.Result int result) {
1064         if (result == GRANTED_ALWAYS || result == GRANTED_FOREGROUND_ONLY
1065                 || result == DENIED_DO_NOT_ASK_AGAIN) {
1066             KeyguardManager kgm = getSystemService(KeyguardManager.class);
1067 
1068             if (kgm != null && kgm.isDeviceLocked()) {
1069                 kgm.requestDismissKeyguard(this, new KeyguardManager.KeyguardDismissCallback() {
1070                     @Override
1071                     public void onDismissError() {
1072                         Log.e(LOG_TAG, "Cannot dismiss keyguard perm=" + name
1073                                 + " result=" + result);
1074                     }
1075 
1076                     @Override
1077                     public void onDismissCancelled() {
1078                         // do nothing (i.e. stay at the current permission group)
1079                     }
1080 
1081                     @Override
1082                     public void onDismissSucceeded() {
1083                         // Now the keyguard is dismissed, hence the device is not locked
1084                         // anymore
1085                         onPermissionGrantResult(name, affectedForegroundPermissions, result);
1086                     }
1087                 });
1088                 return true;
1089             }
1090         }
1091         return false;
1092     }
1093 
1094     private boolean setResultIfNeeded(int resultCode) {
1095         if (!isResultSet()) {
1096             List<String> oldRequestedPermissions = mRequestedPermissions;
1097             mResultCode = resultCode;
1098             removeActivityFromMap();
1099             // If a new merge request came in before we managed to remove this activity from the
1100             // map, then cancel the result set for now.
1101             if (!Objects.equals(oldRequestedPermissions, mRequestedPermissions)) {
1102                 // Reset the result code back to its starting value of MAX_VALUE;
1103                 mResultCode = Integer.MAX_VALUE;
1104                 return false;
1105             }
1106 
1107             if (mViewModel != null) {
1108                 mViewModel.logRequestedPermissionGroups();
1109             }
1110 
1111             // Only include the originally requested permissions in the result
1112             Intent result = new Intent(PackageManager.ACTION_REQUEST_PERMISSIONS);
1113             String[] resultPermissions = mOriginalRequestedPermissions;
1114             int[] grantResults = new int[resultPermissions.length];
1115 
1116             if ((mDelegated || (mViewModel != null && mViewModel.shouldReturnPermissionState()))
1117                     && mTargetPackage != null) {
1118                 for (int i = 0; i < resultPermissions.length; i++) {
1119                     String permission = resultPermissions[i];
1120                     grantResults[i] = mPackageManager.checkPermission(permission, mTargetPackage);
1121                 }
1122             } else {
1123                 grantResults = new int[0];
1124                 resultPermissions = new String[0];
1125             }
1126 
1127             result.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES, resultPermissions);
1128             result.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS, grantResults);
1129             if (isDeviceAwarePermissionSupported(this)) {
1130                 result.putExtra(
1131                         PackageManager.EXTRA_REQUEST_PERMISSIONS_DEVICE_ID, mTargetDeviceId);
1132             }
1133             result.putExtra(Intent.EXTRA_PACKAGE_NAME, mTargetPackage);
1134             setResult(resultCode, result);
1135         }
1136         return true;
1137     }
1138 
1139     private void setResultAndFinish() {
1140         if (setResultIfNeeded(RESULT_OK)) {
1141             finishAfterTransition();
1142         }
1143     }
1144 
1145     private void logGrantPermissionActivityButtons(String permissionGroupName,
1146             List<String> affectedForegroundPermissions, int grantResult) {
1147         int clickedButton = 0;
1148         int presentedButtons = getButtonState();
1149         switch (grantResult) {
1150             case GRANTED_ALWAYS:
1151                 if (mButtonVisibilities[ALLOW_BUTTON]) {
1152                     clickedButton = 1 << ALLOW_BUTTON;
1153                 } else if (mButtonVisibilities[ALLOW_ALWAYS_BUTTON]) {
1154                     clickedButton = 1 << ALLOW_ALWAYS_BUTTON;
1155                 } else if (mButtonVisibilities[ALLOW_ALL_BUTTON]) {
1156                     clickedButton = 1 << ALLOW_ALL_BUTTON;
1157                 }
1158                 break;
1159             case GRANTED_FOREGROUND_ONLY:
1160                 clickedButton = 1 << ALLOW_FOREGROUND_BUTTON;
1161                 break;
1162             case DENIED:
1163                 if (mButtonVisibilities != null) {
1164                     if (mButtonVisibilities[NO_UPGRADE_BUTTON]) {
1165                         clickedButton = 1 << NO_UPGRADE_BUTTON;
1166                     } else if (mButtonVisibilities[NO_UPGRADE_OT_BUTTON]) {
1167                         clickedButton = 1 << NO_UPGRADE_OT_BUTTON;
1168                     } else if (mButtonVisibilities[DENY_BUTTON]) {
1169                         clickedButton = 1 << DENY_BUTTON;
1170                     }
1171                 }
1172                 break;
1173             case DENIED_DO_NOT_ASK_AGAIN:
1174                 if (mButtonVisibilities != null) {
1175                     if (mButtonVisibilities[NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON]) {
1176                         clickedButton = 1 << NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON;
1177                     } else if (mButtonVisibilities[NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON]) {
1178                         clickedButton = 1 << NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON;
1179                     } else if (mButtonVisibilities[DENY_AND_DONT_ASK_AGAIN_BUTTON]) {
1180                         clickedButton = 1 << DENY_AND_DONT_ASK_AGAIN_BUTTON;
1181                     }
1182                 }
1183                 break;
1184             case GRANTED_ONE_TIME:
1185                 clickedButton = 1 << ALLOW_ONE_TIME_BUTTON;
1186                 break;
1187             case LINKED_TO_SETTINGS:
1188                 clickedButton = 1 << LINK_TO_SETTINGS;
1189                 break;
1190             case GRANTED_USER_SELECTED:
1191                 clickedButton = 1 << ALLOW_SELECTED_BUTTON;
1192                 break;
1193             case DENIED_MORE:
1194                 clickedButton = 1 << DONT_ALLOW_MORE_SELECTED_BUTTON;
1195                 break;
1196             case LINKED_TO_PERMISSION_RATIONALE:
1197                 clickedButton = 1 << LINK_TO_PERMISSION_RATIONALE;
1198                 break;
1199             case CANCELED:
1200                 // fall through
1201             default:
1202                 break;
1203         }
1204 
1205         int selectedPrecision = 0;
1206         if (affectedForegroundPermissions != null) {
1207             for (Map.Entry<String, Integer> entry : PERMISSION_TO_BIT_SHIFT.entrySet()) {
1208                 if (affectedForegroundPermissions.contains(entry.getKey())) {
1209                     selectedPrecision |= 1 << entry.getValue();
1210                 }
1211             }
1212         }
1213 
1214         mViewModel.logClickedButtons(permissionGroupName, selectedPrecision, clickedButton,
1215                 presentedButtons, isPermissionRationaleVisible());
1216     }
1217 
1218     private int getButtonState() {
1219         if (mButtonVisibilities == null) {
1220             return 0;
1221         }
1222         int buttonState = 0;
1223         for (int i = NEXT_BUTTON - 1; i >= 0; i--) {
1224             buttonState *= 2;
1225             if (mButtonVisibilities[i]) {
1226                 buttonState++;
1227             }
1228         }
1229         return buttonState;
1230     }
1231 
1232     private boolean isPermissionRationaleVisible() {
1233         return mButtonVisibilities != null && mButtonVisibilities[LINK_TO_PERMISSION_RATIONALE];
1234     }
1235 
1236     private boolean isResultSet() {
1237         return mResultCode != Integer.MAX_VALUE;
1238     }
1239 
1240     // Remove null and empty permissions from an array, return a list
1241     private List<String> removeNullOrEmptyPermissions(String[] perms) {
1242         ArrayList<String> sanitized = new ArrayList<>();
1243         for (String perm : perms) {
1244             if (perm == null || perm.isEmpty()) {
1245                 continue;
1246             }
1247             sanitized.add(perm);
1248         }
1249         return sanitized;
1250     }
1251 
1252     /**
1253      * If there is another system-shown dialog on another task, that is not being relied upon by an
1254      * app-defined dialogs, these other dialogs should be finished.
1255      */
1256     @GuardedBy("sCurrentGrantRequests")
1257     private void finishSystemStartedDialogsOnOtherTasksLocked() {
1258         for (Pair<String, Integer> key : sCurrentGrantRequests.keySet()) {
1259             if (key.first.equals(mTargetPackage) && key.second != getTaskId()) {
1260                 GrantPermissionsActivity other = sCurrentGrantRequests.get(key);
1261                 if (other.mIsSystemTriggered && other.mFollowerActivities.isEmpty()) {
1262                     other.finish();
1263                 }
1264             }
1265         }
1266     }
1267 
1268     /**
1269      * Returns permission rationale message string resource id for the given permission group.
1270      *
1271      * <p> Supported permission groups: LOCATION
1272      *
1273      * @param permissionGroupName permission group for which to get a message string id
1274      * @throws IllegalArgumentException if passing unsupported permission group
1275      */
1276     @StringRes
1277     private int getPermissionRationaleMessageResIdForPermissionGroup(String permissionGroupName) {
1278         Preconditions.checkArgument(LOCATION.equals(permissionGroupName),
1279                 "Permission Rationale does not support %s", permissionGroupName);
1280 
1281         return R.string.permission_rationale_message_location;
1282     }
1283 }
1284