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