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