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.PackageInfo;
50 import android.content.pm.PackageItemInfo;
51 import android.content.pm.PackageManager;
52 import android.content.res.Resources;
53 import android.graphics.drawable.Icon;
54 import android.os.Build;
55 import android.os.Bundle;
56 import android.os.Process;
57 import android.os.UserHandle;
58 import android.permission.flags.Flags;
59 import android.text.Annotation;
60 import android.text.SpannableString;
61 import android.text.Spanned;
62 import android.text.style.ClickableSpan;
63 import android.util.ArraySet;
64 import android.util.Log;
65 import android.util.Pair;
66 import android.view.KeyEvent;
67 import android.view.View;
68 import android.view.View.OnAttachStateChangeListener;
69 import android.view.Window;
70 import android.view.WindowManager;
71 import android.view.inputmethod.InputMethodManager;
72 
73 import androidx.activity.result.ActivityResultLauncher;
74 import androidx.activity.result.contract.ActivityResultContracts;
75 import androidx.annotation.ChecksSdkIntAtLeast;
76 import androidx.annotation.GuardedBy;
77 import androidx.annotation.NonNull;
78 import androidx.annotation.Nullable;
79 import androidx.annotation.RequiresApi;
80 import androidx.annotation.StringRes;
81 import androidx.core.util.Preconditions;
82 
83 import com.android.modules.utils.build.SdkLevel;
84 import com.android.permissioncontroller.DeviceUtils;
85 import com.android.permissioncontroller.R;
86 import com.android.permissioncontroller.ecm.EnhancedConfirmationStatsLogUtils;
87 import com.android.permissioncontroller.permission.ui.auto.GrantPermissionsAutoViewHandler;
88 import com.android.permissioncontroller.permission.ui.model.DenyButton;
89 import com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModel;
90 import com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModel.RequestInfo;
91 import com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModelFactory;
92 import com.android.permissioncontroller.permission.ui.model.Prompt;
93 import com.android.permissioncontroller.permission.ui.wear.GrantPermissionsWearViewHandler;
94 import com.android.permissioncontroller.permission.utils.ContextCompat;
95 import com.android.permissioncontroller.permission.utils.KotlinUtils;
96 import com.android.permissioncontroller.permission.utils.PermissionMapping;
97 import com.android.permissioncontroller.permission.utils.Utils;
98 import com.android.permissioncontroller.permission.utils.v35.MultiDeviceUtils;
99 
100 import java.util.ArrayList;
101 import java.util.Arrays;
102 import java.util.HashMap;
103 import java.util.List;
104 import java.util.Map;
105 import java.util.Objects;
106 import java.util.Random;
107 import java.util.Set;
108 
109 /**
110  * An activity which displays runtime permission prompts on behalf of an app.
111  */
112 public class GrantPermissionsActivity extends SettingsActivity
113         implements GrantPermissionsViewHandler.ResultListener {
114 
115     private static final String LOG_TAG = "GrantPermissionsActivity";
116 
117     private static final String KEY_SESSION_ID = GrantPermissionsActivity.class.getName()
118             + "_REQUEST_ID";
119     public static final String KEY_RESTRICTED_REQUESTED_PERMISSIONS =
120             GrantPermissionsActivity.class.getName() + "_RESTRICTED_REQUESTED_PERMISSIONS";
121     public static final String KEY_UNRESTRICTED_REQUESTED_PERMISSIONS =
122             GrantPermissionsActivity.class.getName() + "_UNRESTRICTED_REQUESTED_PERMISSIONS";
123     public static final String KEY_ORIGINAL_REQUESTED_PERMISSIONS =
124             GrantPermissionsActivity.class.getName() + "_ORIGINAL_REQUESTED_PERMISSIONS";
125 
126     public static final String ANNOTATION_ID = "link";
127 
128     public static final int NEXT_BUTTON = 15;
129     public static final int ALLOW_BUTTON = 0;
130     public static final int ALLOW_ALWAYS_BUTTON = 1; // Used in auto
131     public static final int ALLOW_FOREGROUND_BUTTON = 2;
132     public static final int DENY_BUTTON = 3;
133     public static final int DENY_AND_DONT_ASK_AGAIN_BUTTON = 4;
134     public static final int ALLOW_ONE_TIME_BUTTON = 5;
135     public static final int NO_UPGRADE_BUTTON = 6;
136     public static final int NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON = 7;
137     public static final int NO_UPGRADE_OT_BUTTON = 8; // one-time
138     public static final int NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON = 9; // one-time
139     public static final int LINK_TO_SETTINGS = 10;
140     public static final int ALLOW_ALL_BUTTON = 11; // button for options with a picker, allow all
141     public static final int ALLOW_SELECTED_BUTTON = 12; // allow selected, with picker
142     // button to cancel a request for more data with a picker
143     public static final int DONT_ALLOW_MORE_SELECTED_BUTTON = 13;
144     public static final int LINK_TO_PERMISSION_RATIONALE = 14;
145 
146     public static final int NEXT_LOCATION_DIALOG = 6;
147     public static final int LOCATION_ACCURACY_LAYOUT = 0;
148     public static final int FINE_RADIO_BUTTON = 1;
149     public static final int COARSE_RADIO_BUTTON = 2;
150     public static final int DIALOG_WITH_BOTH_LOCATIONS = 3;
151     public static final int DIALOG_WITH_FINE_LOCATION_ONLY = 4;
152     public static final int DIALOG_WITH_COARSE_LOCATION_ONLY = 5;
153 
154     // The maximum number of dialogs we will allow the same package, on the same task, to launch
155     // simultaneously
156     public static final int MAX_DIALOGS_PER_PKG_TASK = 10;
157 
158     public static final Map<String, Integer> PERMISSION_TO_BIT_SHIFT =
159             Map.of(
160                     ACCESS_COARSE_LOCATION, 0,
161                     ACCESS_FINE_LOCATION, 1);
162 
163     public static final String INTENT_PHOTOS_SELECTED = "intent_extra_result";
164 
165     /**
166      * A map of the currently shown GrantPermissionsActivity for this user, per package and task ID
167      */
168     @GuardedBy("sCurrentGrantRequests")
169     public static final Map<Pair<String, Integer>, GrantPermissionsActivity> sCurrentGrantRequests =
170             new HashMap<>();
171 
172     /** Unique Id of a request */
173     private long mSessionId;
174 
175     /**
176      * The permission group that was showing, before a new permission request came in on top of an
177      * existing request
178      */
179     private String mPreMergeShownGroupName;
180 
181     /** The current list of permissions requested, across all current requests for this app */
182     private List<String> mRequestedPermissions = new ArrayList<>();
183 
184     /**
185      * If any requested permissions are considered restricted by ECM, they will be stored here.
186      */
187     private ArrayList<String> mRestrictedRequestedPermissionGroups = null;
188 
189     /**
190      * If any requested permissions are considered restricted by ECM, the non-restricted
191      * permissions will be stored here.
192      */
193     private List<String> mUnrestrictedRequestedPermissions = null;
194 
195     /** A list of permissions requested on an app's behalf by the system. Usually Implicitly
196      * requested, although this isn't necessarily always the case.
197      */
198     private List<String> mSystemRequestedPermissions = new ArrayList<>();
199     /** A copy of the list of permissions originally requested in the intent to this activity */
200     private String[] mOriginalRequestedPermissions = new String[0];
201 
202     private boolean[] mButtonVisibilities;
203     private int mRequestCounts = 0;
204     private List<RequestInfo> mRequestInfos = new ArrayList<>();
205     private GrantPermissionsViewHandler mViewHandler;
206     private GrantPermissionsViewModel mViewModel;
207 
208     /**
209      * A list of other GrantPermissionActivities for the same package which passed their list of
210      * permissions to this one. They need to be informed when this activity finishes.
211      */
212     private List<GrantPermissionsActivity> mFollowerActivities = new ArrayList<>();
213 
214     /** Whether this activity has asked another GrantPermissionsActivity to show on its behalf */
215     private boolean mDelegated;
216 
217     /** Whether this activity has been triggered by the system */
218     private boolean mIsSystemTriggered = false;
219 
220     /** The set result code, or MAX_VALUE if it hasn't been set yet */
221     private int mResultCode = Integer.MAX_VALUE;
222 
223     /** Package that shall have permissions granted */
224     private String mTargetPackage;
225 
226     /** A key representing this activity, defined by the target package and task ID */
227     private Pair<String, Integer> mKey;
228 
229     private float mOriginalDimAmount;
230     private View mRootView;
231     private int mStoragePermGroupIcon = R.drawable.ic_empty_icon;
232 
233     /** Which device the permission will affect. Default is the primary device. */
234     private int mTargetDeviceId = ContextCompat.DEVICE_ID_DEFAULT;
235 
236     private PackageManager mPackageManager;
237 
238     private ActivityResultLauncher<Intent> mShowWarningDialog =
239             registerForActivityResult(
240                     new ActivityResultContracts.StartActivityForResult(),
241                     result -> {
242                         int resultCode = result.getResultCode();
243                         if (resultCode == RESULT_OK) {
244                             finishAfterTransition();
245                         }
246                     });
247 
248     @Override
onCreate(Bundle icicle)249     public void onCreate(Bundle icicle) {
250         mPackageManager = getPackageManager();
251         if (DeviceUtils.isAuto(this)) {
252             setTheme(R.style.GrantPermissions_Car_FilterTouches);
253         }
254         super.onCreate(icicle);
255 
256         if (icicle == null) {
257             mSessionId = new Random().nextLong();
258         } else {
259             mSessionId = icicle.getLong(KEY_SESSION_ID);
260         }
261 
262         getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
263         if (DeviceUtils.isWear(this)) {
264             // Do not grab input focus and hide keyboard.
265             getWindow().addFlags(FLAG_ALT_FOCUSABLE_IM);
266         }
267 
268         if (PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER.equals(getIntent().getAction())) {
269             mIsSystemTriggered = true;
270             mTargetPackage = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
271             if (mTargetPackage == null) {
272                 Log.e(LOG_TAG, "null EXTRA_PACKAGE_NAME. Must be set for "
273                         + "REQUEST_PERMISSIONS_FOR_OTHER activity");
274                 finishAfterTransition();
275                 return;
276             }
277         } else {
278             // Cache this as this can only read on onCreate, not later.
279             mTargetPackage = getCallingPackage();
280             if (mTargetPackage == null) {
281                 Log.e(LOG_TAG, "null callingPackageName. Please use \"RequestPermission\" to "
282                         + "request permissions");
283                 finishAfterTransition();
284                 return;
285             }
286             try {
287                 PackageInfo packageInfo = mPackageManager.getPackageInfo(mTargetPackage, 0);
288             } catch (PackageManager.NameNotFoundException e) {
289                 Log.e(LOG_TAG, "Unable to get package info for the calling package.", e);
290                 finishAfterTransition();
291                 return;
292             }
293         }
294 
295         String[] requestedPermissionsArray =
296                 getIntent().getStringArrayExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES);
297         if (requestedPermissionsArray == null) {
298             setResultAndFinish();
299             return;
300         }
301 
302         mRequestedPermissions = removeNullOrEmptyPermissions(requestedPermissionsArray);
303         mOriginalRequestedPermissions = mRequestedPermissions.toArray(new String[0]);
304 
305         // Do validation if permissions are requested for a remote device or the dialog is being
306         // streamed to a remote device.
307         if (isDeviceAwarePermissionSupported(getApplicationContext())) {
308             mTargetDeviceId = getIntent().getIntExtra(
309                     PackageManager.EXTRA_REQUEST_PERMISSIONS_DEVICE_ID,
310                     ContextCompat.DEVICE_ID_DEFAULT);
311 
312             if (mTargetDeviceId != ContextCompat.DEVICE_ID_DEFAULT) {
313                 mPackageManager = ContextCompat.createDeviceContext(this, mTargetDeviceId)
314                         .getPackageManager();
315             }
316 
317             // When the dialog is streamed to a remote device, verify requested permissions are all
318             // device aware and target device is the same as the remote device. Otherwise show a
319             // warning dialog.
320             if (getDeviceId() != ContextCompat.DEVICE_ID_DEFAULT) {
321                 boolean showWarningDialog = mTargetDeviceId != getDeviceId();
322 
323                 for (String permission : mRequestedPermissions) {
324                     if (!MultiDeviceUtils.isPermissionDeviceAware(
325                             getApplicationContext(), mTargetDeviceId, permission)) {
326                         showWarningDialog = true;
327                     }
328                 }
329 
330                 if (showWarningDialog) {
331                     mShowWarningDialog.launch(
332                             new Intent(this, PermissionDialogStreamingBlockedActivity.class));
333                     return;
334                 }
335             } else if (mTargetDeviceId != ContextCompat.DEVICE_ID_DEFAULT) {
336                 // On the default device, when requested permissions are for a remote device,
337                 // filter out non-device aware permissions.
338                 for (int i = mRequestedPermissions.size() - 1; i >= 0; i--) {
339                     if (!MultiDeviceUtils.isPermissionDeviceAware(
340                             getApplicationContext(),
341                             mTargetDeviceId,
342                             mRequestedPermissions.get(i))) {
343                         Log.e(
344                                 LOG_TAG,
345                                 "non-device aware permission is requested for a remote device: "
346                                         + mRequestedPermissions.get(i));
347                         mRequestedPermissions.remove(i);
348                     }
349                 }
350             }
351         }
352 
353         if (mRequestedPermissions.isEmpty()) {
354             setResultAndFinish();
355             return;
356         }
357 
358         if (mIsSystemTriggered) {
359             mSystemRequestedPermissions.addAll(mRequestedPermissions);
360         }
361 
362         if (blockRestrictedPermissions(icicle)) {
363             return;
364         }
365 
366         GrantPermissionsViewModelFactory factory =
367                 new GrantPermissionsViewModelFactory(
368                         getApplication(),
369                         mTargetPackage,
370                         mTargetDeviceId,
371                         mRequestedPermissions,
372                         mSystemRequestedPermissions,
373                         mSessionId,
374                         icicle);
375         mViewModel = factory.create(GrantPermissionsViewModel.class);
376 
377         synchronized (sCurrentGrantRequests) {
378             mKey = new Pair<>(mTargetPackage, getTaskId());
379             GrantPermissionsActivity current = sCurrentGrantRequests.get(mKey);
380             if (current == null) {
381                 sCurrentGrantRequests.put(mKey, this);
382                 finishSystemStartedDialogsOnOtherTasksLocked();
383             } else if (mIsSystemTriggered) {
384                 // The system triggered dialog doesn't require results. Delegate, and finish.
385                 current.onNewFollowerActivity(null, mRequestedPermissions, false);
386                 finishAfterTransition();
387                 return;
388             } else if (current.mIsSystemTriggered) {
389                 // merge into the system triggered dialog, which has task overlay set
390                 mDelegated = true;
391                 current.onNewFollowerActivity(this, mRequestedPermissions, false);
392             } else {
393                 // this + current + current.mFollowerActivities
394                 if ((current.mFollowerActivities.size() + 2) > MAX_DIALOGS_PER_PKG_TASK) {
395                     // If there are too many dialogs for the same package, in the same task, cancel
396                     finishAfterTransition();
397                     return;
398                 }
399                 // Merge the old dialogs into the new
400                 onNewFollowerActivity(current, current.mRequestedPermissions, true);
401                 sCurrentGrantRequests.put(mKey, this);
402             }
403         }
404 
405         setFinishOnTouchOutside(false);
406 
407         setTitle(R.string.permission_request_title);
408 
409         if (DeviceUtils.isTelevision(this)) {
410             mViewHandler = new com.android.permissioncontroller.permission.ui.television
411                     .GrantPermissionsViewHandlerImpl(this,
412                     mTargetPackage).setResultListener(this);
413         } else if (DeviceUtils.isWear(this)) {
414             mViewHandler = new GrantPermissionsWearViewHandler(this).setResultListener(this);
415         } else if (DeviceUtils.isAuto(this)) {
416             mViewHandler = new GrantPermissionsAutoViewHandler(this, mTargetPackage)
417                     .setResultListener(this);
418         } else {
419             mViewHandler = new com.android.permissioncontroller.permission.ui.handheld
420                     .GrantPermissionsViewHandlerImpl(this, this);
421         }
422 
423         if (!mDelegated) {
424             mViewModel.getRequestInfosLiveData().observe(this, this::onRequestInfoLoad);
425         }
426 
427         mRootView = mViewHandler.createView();
428         mRootView.setVisibility(View.GONE);
429         setContentView(mRootView);
430         Window window = getWindow();
431         WindowManager.LayoutParams layoutParams = window.getAttributes();
432         mOriginalDimAmount = layoutParams.dimAmount;
433         mViewHandler.updateWindowAttributes(layoutParams);
434         window.setAttributes(layoutParams);
435 
436         if (SdkLevel.isAtLeastS() && getResources().getBoolean(R.bool.config_useWindowBlur)) {
437             java.util.function.Consumer<Boolean> blurEnabledListener = enabled -> {
438                 mViewHandler.onBlurEnabledChanged(window, enabled);
439             };
440             mRootView.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
441                 @Override
442                 public void onViewAttachedToWindow(View v) {
443                     window.getWindowManager().addCrossWindowBlurEnabledListener(
444                             blurEnabledListener);
445                 }
446 
447                 @Override
448                 public void onViewDetachedFromWindow(View v) {
449                     window.getWindowManager().removeCrossWindowBlurEnabledListener(
450                             blurEnabledListener);
451                 }
452             });
453         }
454         // Restore UI state after lifecycle events. This has to be before we show the first request,
455         // as the UI behaves differently for updates and initial creations.
456         if (icicle != null) {
457             mViewHandler.loadInstanceState(icicle);
458         } else if (mRootView == null || mRootView.getVisibility() != View.VISIBLE) {
459             // Do not show screen dim until data is loaded
460             window.setDimAmount(0f);
461         }
462 
463         PackageItemInfo storageGroupInfo =
464                 Utils.getGroupInfo(Manifest.permission_group.STORAGE, this.getApplicationContext());
465         if (storageGroupInfo != null) {
466             mStoragePermGroupIcon = storageGroupInfo.icon;
467         }
468     }
469 
470     /*
471      * Block permissions that are restricted by ECM (Enhanced Confirmation Mode).
472      *
473      * If any requested permissions are restricted, then:
474      *
475      * - Strip them from mRequestedPermissions (so no grant dialog appears for those permissions).
476      * - Group the restricted permissions into permission groups.
477      * - Show the EnhancedConfirmationDialogActivity for each group. Each showing requires a
478      *   cross-activity loop during which GrantPermissionActivity will be recreated.
479      * - Finally, continue processing all non-restricted requested permissions normally
480      *
481      * Returns true if we're going to show the ECM dialog (and therefore GrantPermissionsActivity
482      * will be recreated)
483      */
484     @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.VANILLA_ICE_CREAM, codename = "VanillaIceCream")
blockRestrictedPermissions(Bundle icicle)485     private boolean blockRestrictedPermissions(Bundle icicle) {
486         if (!SdkLevel.isAtLeastV() || !Flags.enhancedConfirmationModeApisEnabled()) {
487             return false;
488         }
489         Context userContext = Utils.getUserContext(this, Process.myUserHandle());
490         EnhancedConfirmationManager ecm = Utils.getSystemServiceSafe(userContext,
491                 EnhancedConfirmationManager.class);
492 
493         // Retrieve ECM-related persisted permission lists
494         if (icicle != null) {
495             mOriginalRequestedPermissions = icicle.getStringArray(
496                     KEY_ORIGINAL_REQUESTED_PERMISSIONS);
497             mRestrictedRequestedPermissionGroups = icicle.getStringArrayList(
498                     KEY_RESTRICTED_REQUESTED_PERMISSIONS);
499             mUnrestrictedRequestedPermissions = icicle.getStringArrayList(
500                     KEY_UNRESTRICTED_REQUESTED_PERMISSIONS);
501         }
502         // If these lists aren't persisted yet, it means we haven't yet divided
503         // mRequestedPermissions into restricted-vs-unrestricted, so do so.
504         if (mRestrictedRequestedPermissionGroups == null) {
505             ArraySet<String> restrictedPermGroups = new ArraySet<>();
506             ArrayList<String> unrestrictedPermissions = new ArrayList<>();
507 
508             for (String requestedPermission : mRequestedPermissions) {
509                 String requestedPermGroup = PermissionMapping.getGroupOfPlatformPermission(
510                         requestedPermission);
511                 if (restrictedPermGroups.contains(requestedPermGroup)) {
512                     continue;
513                 }
514                 if (requestedPermGroup != null && isPermissionEcmRestricted(ecm,
515                         requestedPermission, mTargetPackage)) {
516                     restrictedPermGroups.add(requestedPermGroup);
517                 } else {
518                     unrestrictedPermissions.add(requestedPermission);
519                 }
520             }
521             mUnrestrictedRequestedPermissions = unrestrictedPermissions;
522             // If there are restricted permissions, and the ECM dialog has already been shown
523             // for this app, then we don't want to show it again. Act as if these restricted
524             // permissions weren't // requested at all, and log that we ignored them.
525             if (!restrictedPermGroups.isEmpty() && wasEcmDialogAlreadyShown(ecm, mTargetPackage)) {
526                 for (String ignoredPermGroup : restrictedPermGroups) {
527                     EnhancedConfirmationStatsLogUtils.INSTANCE.logDialogResultReported(
528                             getPackageUid(getCallingPackage(), Process.myUserHandle()),
529                             /* settingIdentifier */ ignoredPermGroup, /* firstShowForApp */ false,
530                             EnhancedConfirmationStatsLogUtils.DialogResult.Suppressed);
531                 }
532                 mRestrictedRequestedPermissionGroups = new ArrayList<>();
533             } else {
534                 mRestrictedRequestedPermissionGroups = new ArrayList<>(restrictedPermGroups);
535             }
536         }
537         // If there are remaining restricted permission groups to process, show the ECM dialog
538         // for the next one, then recreate this activity.
539         if (!mRestrictedRequestedPermissionGroups.isEmpty()) {
540             String nextRestrictedPermissionGroup = mRestrictedRequestedPermissionGroups.remove(0);
541             try {
542                 Intent intent = ecm.createRestrictedSettingDialogIntent(mTargetPackage,
543                         nextRestrictedPermissionGroup);
544                 intent.putExtra(EXTRA_IS_ECM_IN_APP, true);
545                 startActivityForResult(intent, ECM_REQUEST_CODE);
546                 return true;
547             } catch (PackageManager.NameNotFoundException e) {
548                 mRequestedPermissions = mUnrestrictedRequestedPermissions;
549             }
550         } else {
551             mRequestedPermissions = mUnrestrictedRequestedPermissions;
552         }
553         return false;
554     }
555 
556     @SuppressLint("MissingPermission")
getPackageUid(String packageName, UserHandle user)557     private int getPackageUid(String packageName, UserHandle user) {
558         try {
559             return mPackageManager.getApplicationInfoAsUser(packageName, 0, user).uid;
560         } catch (PackageManager.NameNotFoundException e) {
561             return android.os.Process.INVALID_UID;
562         }
563     }
564 
565     @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
isPermissionEcmRestricted(EnhancedConfirmationManager ecm, String requestedPermission, String packageName)566     private boolean isPermissionEcmRestricted(EnhancedConfirmationManager ecm,
567             String requestedPermission, String packageName) {
568         try {
569             return ecm.isRestricted(packageName, requestedPermission);
570         } catch (PackageManager.NameNotFoundException e) {
571             return false;
572         }
573     }
574 
575     @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
wasEcmDialogAlreadyShown(EnhancedConfirmationManager ecm, String packageName)576     private boolean wasEcmDialogAlreadyShown(EnhancedConfirmationManager ecm,
577             String packageName) {
578         try {
579             return ecm.isClearRestrictionAllowed(packageName);
580         } catch (PackageManager.NameNotFoundException e) {
581             return false;
582         }
583     }
584 
585     /**
586      * A new GrantPermissionsActivity has opened for this same package. Merge its requested
587      * permissions with the original ones set in the intent, and recalculate the grant states.
588      * @param follower The activity requesting permissions, which needs to be informed upon this
589      *                 activity finishing
590      * @param newPermissions The new permissions requested in the activity
591      */
onNewFollowerActivity(@ullable GrantPermissionsActivity follower, @NonNull List<String> newPermissions, boolean followerIsOlder)592     private void onNewFollowerActivity(@Nullable GrantPermissionsActivity follower,
593             @NonNull List<String> newPermissions, boolean followerIsOlder) {
594         if (follower != null) {
595             // Ensure the list of follower activities is a stack
596             mFollowerActivities.add(0, follower);
597             follower.mViewModel = mViewModel;
598             if (followerIsOlder) {
599                 follower.mDelegated = true;
600             }
601         }
602 
603         // If the follower is older, examine it to find the pre-merge group
604         GrantPermissionsActivity olderActivity = follower != null && followerIsOlder
605                 ? follower : this;
606         boolean isShowingGroup = olderActivity.mRootView != null
607                 && olderActivity.mRootView.getVisibility() == View.VISIBLE;
608         List<RequestInfo> currentGroups =
609                 olderActivity.mViewModel.getRequestInfosLiveData().getValue();
610         if (mPreMergeShownGroupName == null && isShowingGroup
611                 && currentGroups != null && !currentGroups.isEmpty()) {
612             mPreMergeShownGroupName = currentGroups.get(0).getGroupName();
613         }
614 
615         if (isShowingGroup && mPreMergeShownGroupName != null
616                 && followerIsOlder && currentGroups != null) {
617             // Load a request from the old activity
618             mRequestInfos = currentGroups;
619             showNextRequest();
620             olderActivity.mRootView.setVisibility(View.GONE);
621         }
622         if (follower != null && followerIsOlder) {
623             follower.mFollowerActivities.forEach((oldFollower) ->
624                     onNewFollowerActivity(oldFollower, new ArrayList<>(), true));
625             follower.mFollowerActivities.clear();
626         }
627 
628         if (mRequestedPermissions.containsAll(newPermissions)) {
629             return;
630         }
631 
632         ArrayList<String> currentPermissions = new ArrayList<>(mRequestedPermissions);
633         for (String newPerm : newPermissions) {
634             if (!currentPermissions.contains(newPerm)) {
635                 currentPermissions.add(newPerm);
636             }
637         }
638         mRequestedPermissions = currentPermissions;
639 
640         Bundle oldState = new Bundle();
641         mViewModel.getRequestInfosLiveData().removeObservers(this);
642         mViewModel.saveInstanceState(oldState);
643         GrantPermissionsViewModelFactory factory =
644                 new GrantPermissionsViewModelFactory(
645                         getApplication(),
646                         mTargetPackage,
647                         mTargetDeviceId,
648                         mRequestedPermissions,
649                         mSystemRequestedPermissions,
650                         mSessionId,
651                         oldState);
652         mViewModel = factory.create(GrantPermissionsViewModel.class);
653         mViewModel.getRequestInfosLiveData().observe(this, this::onRequestInfoLoad);
654         if (follower != null) {
655             follower.mViewModel = mViewModel;
656         }
657     }
658 
659     /**
660      * When the leader activity this activity delegated to finishes, finish this activity
661      * @param resultCode the result of the leader
662      */
onLeaderActivityFinished(int resultCode)663     private void onLeaderActivityFinished(int resultCode) {
664         setResultIfNeeded(resultCode);
665         finishAfterTransition();
666     }
667 
onRequestInfoLoad(List<RequestInfo> requests)668     private void onRequestInfoLoad(List<RequestInfo> requests) {
669         if (!mViewModel.getRequestInfosLiveData().isInitialized() || isResultSet() || mDelegated) {
670             return;
671         } else if (requests == null) {
672             finishAfterTransition();
673             return;
674         } else if (requests.isEmpty()) {
675             setResultAndFinish();
676             return;
677         }
678 
679         mRequestInfos = requests;
680 
681         // If we were already showing a group, and then another request came in with more groups,
682         // keep the current group showing until the user makes a decision
683         if (mPreMergeShownGroupName != null) {
684             return;
685         }
686 
687         showNextRequest();
688     }
689 
showNextRequest()690     private void showNextRequest() {
691         if (mRequestInfos.isEmpty() || mDelegated) {
692             return;
693         }
694 
695         RequestInfo info = mRequestInfos.get(0);
696 
697         if (info.getPrompt() == Prompt.NO_UI_SETTINGS_REDIRECT) {
698             mViewModel.sendDirectlyToSettings(this, info.getGroupName());
699             return;
700         } else if (info.getPrompt() == Prompt.NO_UI_PHOTO_PICKER_REDIRECT) {
701             mViewModel.openPhotoPicker(this);
702             return;
703         } else if (info.getPrompt() == Prompt.NO_UI_FILTER_THIS_GROUP) {
704             // Filtered permissions should be removed from the requested permissions list entirely,
705             // and not have status returned to the app
706             List<String> permissionsToFilter =
707                     PermissionMapping.getPlatformPermissionNamesOfGroup(info.getGroupName());
708             mRequestedPermissions.removeAll(permissionsToFilter);
709             mRequestInfos.remove(info);
710             onRequestInfoLoad(mRequestInfos);
711             return;
712         } else if (info.getPrompt() == Prompt.NO_UI_HEALTH_REDIRECT) {
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 != mTargetDeviceId;
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                     grantResults[i] =
1120                             mPackageManager.checkPermission(resultPermissions[i], 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