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