1 /* 2 * Copyright (C) 2011 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 androidx.core.app; 18 19 import android.Manifest; 20 import android.annotation.SuppressLint; 21 import android.app.Activity; 22 import android.content.Context; 23 import android.content.ContextWrapper; 24 import android.content.Intent; 25 import android.content.IntentSender; 26 import android.content.pm.PackageManager; 27 import android.graphics.Matrix; 28 import android.graphics.RectF; 29 import android.net.Uri; 30 import android.os.Build; 31 import android.os.Bundle; 32 import android.os.Handler; 33 import android.os.Looper; 34 import android.os.Parcelable; 35 import android.text.TextUtils; 36 import android.view.Display; 37 import android.view.DragEvent; 38 import android.view.View; 39 40 import androidx.annotation.IdRes; 41 import androidx.annotation.IntRange; 42 import androidx.annotation.RequiresApi; 43 import androidx.annotation.RestrictTo; 44 import androidx.core.content.ContextCompat; 45 import androidx.core.content.LocusIdCompat; 46 import androidx.core.view.DragAndDropPermissionsCompat; 47 48 import org.jspecify.annotations.NonNull; 49 import org.jspecify.annotations.Nullable; 50 51 import java.lang.reflect.InvocationTargetException; 52 import java.lang.reflect.Method; 53 import java.util.Arrays; 54 import java.util.HashSet; 55 import java.util.List; 56 import java.util.Map; 57 import java.util.Set; 58 59 /** 60 * Helper for accessing features in {@link android.app.Activity}. 61 */ 62 public class ActivityCompat extends ContextCompat { 63 64 /** 65 * This interface is the contract for receiving the results for permission requests. 66 */ 67 public interface OnRequestPermissionsResultCallback { 68 69 /** 70 * Callback for the result from requesting permissions. This method 71 * is invoked for every call on {@link #requestPermissions(android.app.Activity, 72 * String[], int)}. 73 * <p> 74 * <strong>Note:</strong> It is possible that the permissions request interaction 75 * with the user is interrupted. In this case you will receive empty permissions 76 * and results arrays which should be treated as a cancellation. 77 * </p> 78 * 79 * @param requestCode The request code passed in {@link #requestPermissions( 80 * android.app.Activity, String[], int)} 81 * @param permissions The requested permissions. Never null. 82 * @param grantResults The grant results for the corresponding permissions 83 * which is either {@link android.content.pm.PackageManager#PERMISSION_GRANTED} 84 * or {@link android.content.pm.PackageManager#PERMISSION_DENIED}. Never null. 85 * 86 * @see #requestPermissions(android.app.Activity, String[], int) 87 */ onRequestPermissionsResult(int requestCode, String @NonNull [] permissions, int @NonNull [] grantResults)88 void onRequestPermissionsResult(int requestCode, String @NonNull [] permissions, 89 int @NonNull [] grantResults); 90 } 91 92 /** 93 * Customizable delegate that allows delegating permission compatibility methods to a custom 94 * implementation. 95 * 96 * <p> 97 * To delegate permission compatibility methods to a custom class, implement this interface, 98 * and call {@code ActivityCompat.setPermissionCompatDelegate(delegate);}. All future calls 99 * to the permission compatibility methods in this class will first check whether the 100 * delegate can handle the method call, and invoke the corresponding method if it can. 101 * </p> 102 */ 103 public interface PermissionCompatDelegate { 104 105 /** 106 * Determines whether the delegate should handle 107 * {@link ActivityCompat#requestPermissions(Activity, String[], int)}, and request 108 * permissions if applicable. If this method returns true, it means that permission 109 * request is successfully handled by the delegate, and platform should not perform any 110 * further requests for permission. 111 * 112 * @param activity The target activity. 113 * @param permissions The requested permissions. Must be non-null and not empty. 114 * @param requestCode Application specific request code to match with a result reported to 115 * {@link 116 * OnRequestPermissionsResultCallback#onRequestPermissionsResult(int, String[], int[])}. 117 * Should be >= 0. 118 * 119 * @return Whether the delegate has handled the permission request. 120 * @see ActivityCompat#requestPermissions(Activity, String[], int) 121 */ requestPermissions(@onNull Activity activity, String @NonNull [] permissions, @IntRange(from = 0) int requestCode)122 boolean requestPermissions(@NonNull Activity activity, 123 String @NonNull [] permissions, @IntRange(from = 0) int requestCode); 124 125 /** 126 * Determines whether the delegate should handle the permission request as part of 127 * {@code FragmentActivity#onActivityResult(int, int, Intent)}. If this method returns true, 128 * it means that activity result is successfully handled by the delegate, and no further 129 * action is needed on this activity result. 130 * 131 * @param activity The target Activity. 132 * @param requestCode The integer request code originally supplied to 133 * {@code startActivityForResult()}, allowing you to identify who this 134 * result came from. 135 * @param resultCode The integer result code returned by the child activity 136 * through its {@code }setResult()}. 137 * @param data An Intent, which can return result data to the caller 138 * (various data can be attached to Intent "extras"). 139 * 140 * @return Whether the delegate has handled the activity result. 141 * @see ActivityCompat#requestPermissions(Activity, String[], int) 142 */ onActivityResult(@onNull Activity activity, @IntRange(from = 0) int requestCode, int resultCode, @Nullable Intent data)143 boolean onActivityResult(@NonNull Activity activity, 144 @IntRange(from = 0) int requestCode, int resultCode, @Nullable Intent data); 145 } 146 147 /** 148 */ 149 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) 150 public interface RequestPermissionsRequestCodeValidator { validateRequestPermissionsRequestCode(int requestCode)151 void validateRequestPermissionsRequestCode(int requestCode); 152 } 153 154 private static PermissionCompatDelegate sDelegate; 155 156 /** 157 * This class should not be instantiated, but the constructor must be 158 * visible for the class to be extended (as in support-v13). 159 */ ActivityCompat()160 protected ActivityCompat() { 161 // Not publicly instantiable, but may be extended. 162 } 163 164 /** 165 * Sets the permission delegate for {@code ActivityCompat}. Replaces the previously set 166 * delegate. 167 * 168 * @param delegate The delegate to be set. {@code null} to clear the set delegate. 169 */ setPermissionCompatDelegate( @ullable PermissionCompatDelegate delegate)170 public static void setPermissionCompatDelegate( 171 @Nullable PermissionCompatDelegate delegate) { 172 sDelegate = delegate; 173 } 174 175 /** 176 */ 177 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) getPermissionCompatDelegate()178 public static @Nullable PermissionCompatDelegate getPermissionCompatDelegate() { 179 return sDelegate; 180 } 181 182 /** 183 * Invalidate the activity's options menu, if able. 184 * 185 * <p>Before API level 11 (Android 3.0/Honeycomb) the lifecycle of the 186 * options menu was controlled primarily by the user's operation of 187 * the hardware menu key. When the user presses down on the menu key 188 * for the first time the menu was created and prepared by calls 189 * to {@link Activity#onCreateOptionsMenu(android.view.Menu)} and 190 * {@link Activity#onPrepareOptionsMenu(android.view.Menu)} respectively. 191 * Subsequent presses of the menu key kept the existing instance of the 192 * Menu itself and called {@link Activity#onPrepareOptionsMenu(android.view.Menu)} 193 * to give the activity an opportunity to contextually alter the menu 194 * before the menu panel was shown.</p> 195 * 196 * <p>In Android 3.0+ the Action Bar forces the options menu to be built early 197 * so that items chosen to show as actions may be displayed when the activity 198 * first becomes visible. The Activity method invalidateOptionsMenu forces 199 * the entire menu to be destroyed and recreated from 200 * {@link Activity#onCreateOptionsMenu(android.view.Menu)}, offering a similar 201 * though heavier-weight opportunity to change the menu's contents. Normally 202 * this functionality is used to support a changing configuration of Fragments.</p> 203 * 204 * <p>Applications may use this support helper to signal a significant change in 205 * activity state that should cause the options menu to be rebuilt. If the app 206 * is running on an older platform version that does not support menu invalidation 207 * the app will still receive {@link Activity#onPrepareOptionsMenu(android.view.Menu)} 208 * the next time the user presses the menu key and this method will return false. 209 * If this method returns true the options menu was successfully invalidated.</p> 210 * 211 * @param activity Invalidate the options menu of this activity 212 * @return true if this operation was supported and it completed; false if it was not available. 213 * @deprecated Use {@link Activity#invalidateOptionsMenu()} directly. 214 */ 215 @Deprecated invalidateOptionsMenu(Activity activity)216 public static boolean invalidateOptionsMenu(Activity activity) { 217 activity.invalidateOptionsMenu(); 218 return true; 219 } 220 221 /** 222 * Start new activity with options, if able, for which you would like a 223 * result when it finished. 224 * 225 * <p>In Android 4.1+ additional options were introduced to allow for more 226 * control on activity launch animations. Applications can use this method 227 * along with {@link ActivityOptionsCompat} to use these animations when 228 * available. When run on versions of the platform where this feature does 229 * not exist the activity will be launched normally.</p> 230 * 231 * @param activity Origin activity to launch from. 232 * @param intent The description of the activity to start. 233 * @param requestCode If >= 0, this code will be returned in 234 * onActivityResult() when the activity exits. 235 * @param options Additional options for how the Activity should be started. 236 * May be null if there are no options. See 237 * {@link ActivityOptionsCompat} for how to build the Bundle 238 * supplied here; there are no supported definitions for 239 * building it manually. 240 */ startActivityForResult(@onNull Activity activity, @NonNull Intent intent, int requestCode, @Nullable Bundle options)241 public static void startActivityForResult(@NonNull Activity activity, @NonNull Intent intent, 242 int requestCode, @Nullable Bundle options) { 243 activity.startActivityForResult(intent, requestCode, options); 244 } 245 246 /** 247 * Start new IntentSender with options, if able, for which you would like a 248 * result when it finished. 249 * 250 * <p>In Android 4.1+ additional options were introduced to allow for more 251 * control on activity launch animations. Applications can use this method 252 * along with {@link ActivityOptionsCompat} to use these animations when 253 * available. When run on versions of the platform where this feature does 254 * not exist the activity will be launched normally.</p> 255 * 256 * @param activity Origin activity to launch from. 257 * @param intent The IntentSender to launch. 258 * @param requestCode If >= 0, this code will be returned in 259 * onActivityResult() when the activity exits. 260 * @param fillInIntent If non-null, this will be provided as the 261 * intent parameter to {@link IntentSender#sendIntent}. 262 * @param flagsMask Intent flags in the original IntentSender that you 263 * would like to change. 264 * @param flagsValues Desired values for any bits set in <var>flagsMask</var> 265 * @param extraFlags Always set to 0. 266 * @param options Additional options for how the Activity should be started. 267 * May be null if there are no options. See 268 * {@link ActivityOptionsCompat} for how to build the Bundle 269 * supplied here; there are no supported definitions for 270 * building it manually. 271 */ startIntentSenderForResult(@onNull Activity activity, @NonNull IntentSender intent, int requestCode, @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, @Nullable Bundle options)272 public static void startIntentSenderForResult(@NonNull Activity activity, 273 @NonNull IntentSender intent, int requestCode, @Nullable Intent fillInIntent, 274 int flagsMask, int flagsValues, int extraFlags, @Nullable Bundle options) 275 throws IntentSender.SendIntentException { 276 activity.startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask, 277 flagsValues, extraFlags, options); 278 } 279 280 /** 281 * Finish this activity, and tries to finish all activities immediately below it 282 * in the current task that have the same affinity. 283 * 284 * <p>On Android 4.1+ calling this method will call through to the native version of this 285 * method. For other platforms {@link Activity#finish()} will be called instead.</p> 286 */ finishAffinity(@onNull Activity activity)287 public static void finishAffinity(@NonNull Activity activity) { 288 activity.finishAffinity(); 289 } 290 291 /** 292 * Reverses the Activity Scene entry Transition and triggers the calling Activity 293 * to reverse its exit Transition. When the exit Transition completes, 294 * {@link Activity#finish()} is called. If no entry Transition was used, finish() is called 295 * immediately and the Activity exit Transition is run. 296 * 297 * <p>On Android 4.4 or lower, this method only finishes the Activity with no 298 * special exit transition.</p> 299 */ finishAfterTransition(@onNull Activity activity)300 public static void finishAfterTransition(@NonNull Activity activity) { 301 if (Build.VERSION.SDK_INT >= 21) { 302 Api21Impl.finishAfterTransition(activity); 303 } else { 304 activity.finish(); 305 } 306 } 307 308 /** 309 * Return information about who launched this activity. If the launching Intent 310 * contains an {@link Intent#EXTRA_REFERRER Intent.EXTRA_REFERRER}, 311 * that will be returned as-is; otherwise, if known, an 312 * {@link Intent#URI_ANDROID_APP_SCHEME android-app:} referrer URI containing the 313 * package name that started the Intent will be returned. This may return null if no 314 * referrer can be identified -- it is neither explicitly specified, nor is it known which 315 * application package was involved. 316 * 317 * <p>If called while inside the handling of {@link Activity#onNewIntent}, this function will 318 * return the referrer that submitted that new intent to the activity. Otherwise, it 319 * always returns the referrer of the original Intent.</p> 320 * 321 * <p>Note that this is <em>not</em> a security feature -- you can not trust the 322 * referrer information, applications can spoof it.</p> 323 */ 324 @SuppressWarnings("deprecation") getReferrer(@onNull Activity activity)325 public static @Nullable Uri getReferrer(@NonNull Activity activity) { 326 if (Build.VERSION.SDK_INT >= 22) { 327 return Api22Impl.getReferrer(activity); 328 } 329 Intent intent = activity.getIntent(); 330 Uri referrer = intent.getParcelableExtra("android.intent.extra.REFERRER"); 331 if (referrer != null) { 332 return referrer; 333 } 334 String referrerName = intent.getStringExtra("android.intent.extra.REFERRER_NAME"); 335 if (referrerName != null) { 336 return Uri.parse(referrerName); 337 } 338 return null; 339 } 340 341 /** 342 * Finds a view that was identified by the {@code android:id} XML attribute that was processed 343 * in {@link Activity#onCreate}, or throws an IllegalArgumentException if the ID is invalid, or 344 * there is no matching view in the hierarchy. 345 * <p> 346 * <strong>Note:</strong> In most cases -- depending on compiler support -- 347 * the resulting view is automatically cast to the target class type. If 348 * the target class type is unconstrained, an explicit cast may be 349 * necessary. 350 * 351 * @param activity activity in which to find a view. 352 * @param id the ID to search for 353 * @return a view with given ID 354 * @see Activity#findViewById(int) 355 * @see androidx.core.view.ViewCompat#requireViewById(View, int) 356 */ 357 @SuppressWarnings("TypeParameterUnusedInFormals") requireViewById(@onNull Activity activity, @IdRes int id)358 public static <T extends View> @NonNull T requireViewById(@NonNull Activity activity, 359 @IdRes int id) { 360 if (Build.VERSION.SDK_INT >= 28) { 361 return Api28Impl.requireViewById(activity, id); 362 } 363 364 T view = activity.findViewById(id); 365 if (view == null) { 366 throw new IllegalArgumentException("ID does not reference a View inside this Activity"); 367 } 368 return view; 369 } 370 371 /** 372 * When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity, 373 * android.view.View, String)} was used to start an Activity, <var>callback</var> 374 * will be called to handle shared elements on the <i>launched</i> Activity. This requires 375 * {@link android.view.Window#FEATURE_CONTENT_TRANSITIONS}. 376 * 377 * @param activity activity for which to set the callback. 378 * @param callback Used to manipulate shared element transitions on the launched Activity. 379 */ setEnterSharedElementCallback(@onNull Activity activity, @Nullable SharedElementCallback callback)380 public static void setEnterSharedElementCallback(@NonNull Activity activity, 381 @Nullable SharedElementCallback callback) { 382 if (Build.VERSION.SDK_INT >= 21) { 383 android.app.SharedElementCallback frameworkCallback = callback != null 384 ? new SharedElementCallback21Impl(callback) 385 : null; 386 Api21Impl.setEnterSharedElementCallback(activity, frameworkCallback); 387 } 388 } 389 390 /** 391 * When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity, 392 * android.view.View, String)} was used to start an Activity, <var>callback</var> 393 * will be called to handle shared elements on the <i>launching</i> Activity. Most 394 * calls will only come when returning from the started Activity. 395 * This requires {@link android.view.Window#FEATURE_CONTENT_TRANSITIONS}. 396 * 397 * @param activity activity for which to set the callback. 398 * @param callback Used to manipulate shared element transitions on the launching Activity. 399 */ setExitSharedElementCallback(@onNull Activity activity, @Nullable SharedElementCallback callback)400 public static void setExitSharedElementCallback(@NonNull Activity activity, 401 @Nullable SharedElementCallback callback) { 402 if (Build.VERSION.SDK_INT >= 21) { 403 android.app.SharedElementCallback frameworkCallback = callback != null 404 ? new SharedElementCallback21Impl(callback) 405 : null; 406 Api21Impl.setExitSharedElementCallback(activity, frameworkCallback); 407 } 408 } 409 postponeEnterTransition(@onNull Activity activity)410 public static void postponeEnterTransition(@NonNull Activity activity) { 411 if (Build.VERSION.SDK_INT >= 21) { 412 Api21Impl.postponeEnterTransition(activity); 413 } 414 } 415 startPostponedEnterTransition(@onNull Activity activity)416 public static void startPostponedEnterTransition(@NonNull Activity activity) { 417 if (Build.VERSION.SDK_INT >= 21) { 418 Api21Impl.startPostponedEnterTransition(activity); 419 } 420 } 421 422 /** 423 * Requests permissions to be granted to this application. These permissions 424 * must be requested in your manifest, they should not be granted to your app, 425 * and they should have protection level {@link 426 * android.content.pm.PermissionInfo#PROTECTION_DANGEROUS dangerous}, regardless 427 * whether they are declared by the platform or a third-party app. 428 * <p> 429 * Normal permissions {@link android.content.pm.PermissionInfo#PROTECTION_NORMAL} 430 * are granted at install time if requested in the manifest. Signature permissions 431 * {@link android.content.pm.PermissionInfo#PROTECTION_SIGNATURE} are granted at 432 * install time if requested in the manifest and the signature of your app matches 433 * the signature of the app declaring the permissions. 434 * </p> 435 * <p> 436 * Call {@link #shouldShowRequestPermissionRationale(Activity, String)} before calling this API 437 * to check if the system recommends to show a rationale dialog before asking for a permission. 438 * </p> 439 * <p> 440 * If your app does not have the requested permissions the user will be presented 441 * with UI for accepting them. After the user has accepted or rejected the 442 * requested permissions you will receive a callback reporting whether the 443 * permissions were granted or not. Your activity has to implement {@link 444 * androidx.core.app.ActivityCompat.OnRequestPermissionsResultCallback} 445 * and the results of permission requests will be delivered to its {@link 446 * androidx.core.app.ActivityCompat.OnRequestPermissionsResultCallback#onRequestPermissionsResult( 447 * int, String[], int[])} method. 448 * </p> 449 * <p> 450 * Note that requesting a permission does not guarantee it will be granted and 451 * your app should be able to run without having this permission. 452 * </p> 453 * <p> 454 * This method may start an activity allowing the user to choose which permissions 455 * to grant and which to reject. Hence, you should be prepared that your activity 456 * may be paused and resumed. Further, granting some permissions may require 457 * a restart of you application. In such a case, the system will recreate the 458 * activity stack before delivering the result to your 459 * {@link OnRequestPermissionsResultCallback#onRequestPermissionsResult(int, String[], int[])}. 460 * </p> 461 * <p> 462 * When checking whether you have a permission you should use {@link 463 * #checkSelfPermission(android.content.Context, String)}. 464 * </p> 465 * <p> 466 * Calling this API for permissions already granted to your app would show UI 467 * to the user to decide whether the app can still hold these permissions. This 468 * can be useful if the way your app uses the data guarded by the permissions 469 * changes significantly. 470 * </p> 471 * <p> 472 * You cannot request a permission if your activity sets {@link 473 * android.R.attr#noHistory noHistory} to <code>true</code> in the manifest 474 * because in this case the activity would not receive result callbacks including 475 * {@link OnRequestPermissionsResultCallback#onRequestPermissionsResult(int, String[], int[])}. 476 * </p> 477 * <p> 478 * The <a href="https://github.com/googlesamples/android-RuntimePermissions"> 479 * RuntimePermissions</a> sample app demonstrates how to use this method to 480 * request permissions at run time. 481 * </p> 482 * <p> 483 * If {@link Manifest.permission#POST_NOTIFICATIONS} is requested before the device supports 484 * the notification permission, then {@link Manifest.permission#POST_NOTIFICATIONS} will be 485 * removed from {@link OnRequestPermissionsResultCallback#onRequestPermissionsResult}. 486 * For devices that don't support {@link Manifest.permission#POST_NOTIFICATIONS}, apps can 487 * send users to its notification settings to enable notifications. See 488 * {@link android.provider.Settings.ACTION_APP_NOTIFICATION_SETTINGS} for more information 489 * on launching notification settings. 490 * </p> 491 * 492 * @param activity The target activity. 493 * @param permissions The requested permissions. Must be non-null and not empty. 494 * @param requestCode Application specific request code to match with a result 495 * reported to {@link OnRequestPermissionsResultCallback#onRequestPermissionsResult(int, String[], int[])}. 496 * Should be >= 0. 497 * 498 * @see OnRequestPermissionsResultCallback#onRequestPermissionsResult(int, String[], int[]) 499 * @see #checkSelfPermission(android.content.Context, String) 500 * @see #shouldShowRequestPermissionRationale(android.app.Activity, String) 501 */ requestPermissions(final @NonNull Activity activity, final String @NonNull [] permissions, final @IntRange(from = 0) int requestCode)502 public static void requestPermissions(final @NonNull Activity activity, 503 final String @NonNull [] permissions, final @IntRange(from = 0) int requestCode) { 504 if (sDelegate != null 505 && sDelegate.requestPermissions(activity, permissions, requestCode)) { 506 // Delegate has handled the permission request. 507 return; 508 } 509 510 Set<Integer> indicesOfPermissionsToRemove = new HashSet<>(); 511 for (int i = 0; i < permissions.length; i++) { 512 if (TextUtils.isEmpty(permissions[i])) { 513 throw new IllegalArgumentException("Permission request for permissions " 514 + Arrays.toString(permissions) + " must not contain null or empty values"); 515 } 516 517 if (Build.VERSION.SDK_INT < 33) { 518 if (TextUtils.equals(permissions[i], Manifest.permission.POST_NOTIFICATIONS)) { 519 indicesOfPermissionsToRemove.add(i); 520 } 521 } 522 } 523 524 int numPermissionsToRemove = indicesOfPermissionsToRemove.size(); 525 final String[] permissionsArray = numPermissionsToRemove > 0 526 ? new String[permissions.length - numPermissionsToRemove] : permissions; 527 if (numPermissionsToRemove > 0) { 528 if (numPermissionsToRemove == permissions.length) { 529 return; 530 } 531 for (int i = 0, modifiedIndex = 0; i < permissions.length; i++) { 532 if (!indicesOfPermissionsToRemove.contains(i)) { 533 permissionsArray[modifiedIndex++] = permissions[i]; 534 } 535 } 536 } 537 538 if (Build.VERSION.SDK_INT >= 23) { 539 if (activity instanceof RequestPermissionsRequestCodeValidator) { 540 ((RequestPermissionsRequestCodeValidator) activity) 541 .validateRequestPermissionsRequestCode(requestCode); 542 } 543 Api23Impl.requestPermissions(activity, permissions, requestCode); 544 } else if (activity instanceof OnRequestPermissionsResultCallback) { 545 Handler handler = new Handler(Looper.getMainLooper()); 546 handler.post(new Runnable() { 547 @Override 548 public void run() { 549 final int[] grantResults = new int[permissionsArray.length]; 550 551 PackageManager packageManager = activity.getPackageManager(); 552 String packageName = activity.getPackageName(); 553 554 final int permissionCount = permissionsArray.length; 555 for (int i = 0; i < permissionCount; i++) { 556 grantResults[i] = packageManager.checkPermission( 557 permissionsArray[i], packageName); 558 } 559 560 ((OnRequestPermissionsResultCallback) activity).onRequestPermissionsResult( 561 requestCode, permissionsArray, grantResults); 562 } 563 }); 564 } 565 } 566 567 /** 568 * Gets whether you should show UI with rationale before requesting a permission. 569 * 570 * @param activity The target activity. 571 * @param permission A permission your app wants to request. 572 * @return Whether you should show permission rationale UI. 573 * 574 * @see #checkSelfPermission(Context, String) 575 * @see #requestPermissions(Activity, String[], int) 576 */ shouldShowRequestPermissionRationale(@onNull Activity activity, @NonNull String permission)577 public static boolean shouldShowRequestPermissionRationale(@NonNull Activity activity, 578 @NonNull String permission) { 579 if (Build.VERSION.SDK_INT < 33 580 && TextUtils.equals(Manifest.permission.POST_NOTIFICATIONS, permission)) { 581 // notification permission doesn't exist before T 582 return false; 583 } 584 if (Build.VERSION.SDK_INT >= 32) { 585 return Api32Impl.shouldShowRequestPermissionRationale(activity, permission); 586 } else if (Build.VERSION.SDK_INT == 31) { 587 return Api31Impl.shouldShowRequestPermissionRationale(activity, permission); 588 } else if (Build.VERSION.SDK_INT >= 23) { 589 return Api23Impl.shouldShowRequestPermissionRationale(activity, permission); 590 } 591 return false; 592 } 593 594 /** 595 * Indicates whether this activity is launched from a bubble. A bubble is a floating shortcut 596 * on the screen that expands to show an activity. 597 * 598 * If your activity can be used normally or as a bubble, you might use this method to check 599 * if the activity is bubbled to modify any behaviour that might be different between the 600 * normal activity and the bubbled activity. For example, if you normally cancel the 601 * notification associated with the activity when you open the activity, you might not want to 602 * do that when you're bubbled as that would remove the bubble. 603 * 604 * @return {@code true} if the activity is launched from a bubble. 605 * 606 * @see NotificationCompat.Builder#setBubbleMetadata(NotificationCompat.BubbleMetadata) 607 * @see NotificationCompat.BubbleMetadata.Builder#Builder(String) 608 * 609 * Compatibility behavior: 610 * <ul> 611 * <li>API 31 and above, this method matches platform behavior</li> 612 * <li>API 29, 30, this method checks the window display ID</li> 613 * <li>API 28 and earlier, this method is a no-op</li> 614 * </ul> 615 */ isLaunchedFromBubble(@onNull Activity activity)616 public static boolean isLaunchedFromBubble(@NonNull Activity activity) { 617 if (Build.VERSION.SDK_INT >= 31) { 618 return Api31Impl.isLaunchedFromBubble(activity); 619 } else if (Build.VERSION.SDK_INT == 30) { 620 return Api30Impl.getDisplay(activity) != null 621 && Api30Impl.getDisplay(activity).getDisplayId() != Display.DEFAULT_DISPLAY; 622 } else if (Build.VERSION.SDK_INT == 29) { 623 return activity.getWindowManager().getDefaultDisplay() != null 624 && activity.getWindowManager().getDefaultDisplay().getDisplayId() 625 != Display.DEFAULT_DISPLAY; 626 } 627 return false; 628 } 629 630 /** 631 * Create {@link DragAndDropPermissionsCompat} object bound to this activity and controlling 632 * the access permissions for content URIs associated with the {@link android.view.DragEvent}. 633 * @param activity activity for which to request the permission. 634 * @param dragEvent Drag event to request permission for 635 * @return The {@link DragAndDropPermissionsCompat} object used to control access to the content 636 * URIs. {@code null} if no content URIs are associated with the event or if permissions could 637 * not be granted. 638 */ requestDragAndDropPermissions( @onNull Activity activity, @NonNull DragEvent dragEvent)639 public static @Nullable DragAndDropPermissionsCompat requestDragAndDropPermissions( 640 @NonNull Activity activity, @NonNull DragEvent dragEvent) { 641 return DragAndDropPermissionsCompat.request(activity, dragEvent); 642 } 643 644 /** 645 * Cause the given Activity to be recreated with a new instance. This version of the method 646 * allows a consistent behavior across API levels, emulating what happens on Android Pie (and 647 * newer) when running on older platforms. 648 * 649 * @param activity The activity to recreate 650 */ recreate(final @NonNull Activity activity)651 public static void recreate(final @NonNull Activity activity) { 652 if (Build.VERSION.SDK_INT >= 28) { 653 // On Android P and later, we can safely rely on the platform recreate() 654 activity.recreate(); 655 } else { 656 // Prior to Android M, we can't call recreate() before the Activity has fully resumed, 657 // but we also can't inspect its current lifecycle state, so we'll just schedule the 658 // recreate for later. 659 Handler handler = new Handler(activity.getMainLooper()); 660 handler.post(() -> { 661 if (!activity.isFinishing() && !ActivityRecreator.recreate(activity)) { 662 // Fall back to the platform method if ActivityRecreator failed for any reason. 663 activity.recreate(); 664 } 665 }); 666 } 667 } 668 669 /** 670 * Sets the {@link LocusIdCompat} for this activity. The locus id helps identify different 671 * instances of the same {@code Activity} class. 672 * <p> For example, a locus id based on a specific conversation could be set on a 673 * conversation app's chat {@code Activity}. The system can then use this locus id 674 * along with app's contents to provide ranking signals in various UI surfaces 675 * including sharing, notifications, shortcuts and so on. 676 * <p> It is recommended to set the same locus id in the shortcut's locus id using 677 * {@link androidx.core.content.pm.ShortcutInfoCompat.Builder#setLocusId(LocusIdCompat)} 678 * so that the system can learn appropriate ranking signals linking the activity's 679 * locus id with the matching shortcut. 680 * 681 * @param activity activity for which to set locus id. 682 * @param locusId a unique, stable id that identifies this {@code Activity} instance. LocusId 683 * is an opaque ID that links this Activity's state to different Android concepts: 684 * {@link androidx.core.content.pm.ShortcutInfoCompat.Builder#setLocusId(LocusIdCompat)}. 685 * LocusID is null by default or if you explicitly reset it. 686 * @param bundle extras set or updated as part of this locus context. This may help provide 687 * additional metadata such as URLs, conversation participants specific to this 688 * {@code Activity}'s context. Bundle can be null if additional metadata is not needed. 689 * Bundle should always be null for null locusId. 690 * 691 * @see android.view.contentcapture.ContentCaptureManager 692 * @see android.view.contentcapture.ContentCaptureContext 693 * 694 * Compatibility behavior: 695 * <ul> 696 * <li>API 30 and above, this method matches platform behavior. 697 * <li>API 29 and earlier, this method is no-op. 698 * </ul> 699 */ setLocusContext(final @NonNull Activity activity, final @Nullable LocusIdCompat locusId, final @Nullable Bundle bundle)700 public static void setLocusContext(final @NonNull Activity activity, 701 final @Nullable LocusIdCompat locusId, final @Nullable Bundle bundle) { 702 if (Build.VERSION.SDK_INT >= 30) { 703 Api30Impl.setLocusContext(activity, locusId, bundle); 704 } 705 } 706 707 @RequiresApi(21) 708 static class SharedElementCallback21Impl extends android.app.SharedElementCallback { 709 private final SharedElementCallback mCallback; 710 SharedElementCallback21Impl(SharedElementCallback callback)711 SharedElementCallback21Impl(SharedElementCallback callback) { 712 mCallback = callback; 713 } 714 715 @Override onSharedElementStart(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots)716 public void onSharedElementStart(List<String> sharedElementNames, 717 List<View> sharedElements, List<View> sharedElementSnapshots) { 718 mCallback.onSharedElementStart(sharedElementNames, sharedElements, 719 sharedElementSnapshots); 720 } 721 722 @Override onSharedElementEnd(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots)723 public void onSharedElementEnd(List<String> sharedElementNames, List<View> sharedElements, 724 List<View> sharedElementSnapshots) { 725 mCallback.onSharedElementEnd(sharedElementNames, sharedElements, 726 sharedElementSnapshots); 727 } 728 729 @Override onRejectSharedElements(List<View> rejectedSharedElements)730 public void onRejectSharedElements(List<View> rejectedSharedElements) { 731 mCallback.onRejectSharedElements(rejectedSharedElements); 732 } 733 734 @Override onMapSharedElements(List<String> names, Map<String, View> sharedElements)735 public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) { 736 mCallback.onMapSharedElements(names, sharedElements); 737 } 738 739 @Override onCaptureSharedElementSnapshot(View sharedElement, Matrix viewToGlobalMatrix, RectF screenBounds)740 public Parcelable onCaptureSharedElementSnapshot(View sharedElement, 741 Matrix viewToGlobalMatrix, RectF screenBounds) { 742 return mCallback.onCaptureSharedElementSnapshot(sharedElement, viewToGlobalMatrix, 743 screenBounds); 744 } 745 746 @Override onCreateSnapshotView(Context context, Parcelable snapshot)747 public View onCreateSnapshotView(Context context, Parcelable snapshot) { 748 return mCallback.onCreateSnapshotView(context, snapshot); 749 } 750 751 @Override 752 @RequiresApi(23) // Callback added on 23. onSharedElementsArrived(List<String> sharedElementNames, List<View> sharedElements, final OnSharedElementsReadyListener listener)753 public void onSharedElementsArrived(List<String> sharedElementNames, 754 List<View> sharedElements, final OnSharedElementsReadyListener listener) { 755 mCallback.onSharedElementsArrived(sharedElementNames, sharedElements, 756 () -> Api23Impl.onSharedElementsReady(listener)); 757 } 758 } 759 760 @RequiresApi(30) 761 static class Api30Impl { Api30Impl()762 private Api30Impl() { 763 // This class is not instantiable. 764 } 765 setLocusContext(final @NonNull Activity activity, final @Nullable LocusIdCompat locusId, final @Nullable Bundle bundle)766 static void setLocusContext(final @NonNull Activity activity, 767 final @Nullable LocusIdCompat locusId, final @Nullable Bundle bundle) { 768 activity.setLocusContext(locusId == null ? null : locusId.toLocusId(), bundle); 769 } 770 getDisplay(ContextWrapper contextWrapper)771 static Display getDisplay(ContextWrapper contextWrapper) { 772 return contextWrapper.getDisplay(); 773 } 774 } 775 776 @RequiresApi(31) 777 static class Api31Impl { Api31Impl()778 private Api31Impl() { 779 // This class is not instantiable. 780 } 781 isLaunchedFromBubble(final @NonNull Activity activity)782 static boolean isLaunchedFromBubble(final @NonNull Activity activity) { 783 return activity.isLaunchedFromBubble(); 784 } 785 786 /** 787 * Fix memory leak on Android 12: 788 * <a href="https://github.com/androidx/androidx/pull/435"> 789 * https://github.com/androidx/androidx/pull/435 790 * </a> 791 */ 792 @SuppressLint("BanUncheckedReflection") shouldShowRequestPermissionRationale(Activity activity, String permission)793 static boolean shouldShowRequestPermissionRationale(Activity activity, String permission) { 794 try { 795 // 1. Background of the problem:Fix shouldShowRequestPermissionRationale causing memory leak in Android 12, 796 // this problem has been fixed on the Android 12L system, but it is still a historical problem for the Android 12 system 797 // 2. The reason for the problem: The culprit is that the PermissionUsageHelper holds the Context object as a field, 798 // and calls AppOpsManager.startWatchingStarted in the constructor to start the monitoring, 799 // so that the PermissionUsageHelper object will be added to the AppOpsManager#mStartedWatchers collection, 800 // which will lead to active calls in the Activity When finishing, stopWatchingStarted is not used to remove the watch, 801 // which causes the Activity object to be held in the AppOpsManager#mStartedWatchers collection, 802 // which indirectly causes the Activity object to not be recycled by the system. 803 // 3. The solution to the problem: The handling of this problem is also very simple and rude, that is, 804 // to replace the Context parameter passed in the outer layer from the Activity object to the Application object, 805 // because the Application life cycle is relatively long, but there is only the shouldShowRequestPermissionRationale method in the Activity, 806 // and What if there is no such method in Application? Looking at the implementation of this method, in fact, 807 // that method will eventually call the PackageManager.shouldShowRequestPermissionRationale method (hidden API, but not in the blacklist), 808 // so as long as the PackageManager object can be obtained, and finally use reflection to execute this method , which avoids memory leaks. 809 PackageManager packageManager = activity.getApplication().getPackageManager(); 810 Method method = PackageManager.class.getMethod("shouldShowRequestPermissionRationale", String.class); 811 return (boolean) method.invoke(packageManager, permission); 812 } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { 813 return activity.shouldShowRequestPermissionRationale(permission); 814 } 815 } 816 } 817 818 @RequiresApi(32) 819 static class Api32Impl { Api32Impl()820 private Api32Impl() { 821 // This class is not instantiable. 822 } 823 shouldShowRequestPermissionRationale(Activity activity, String permission)824 static boolean shouldShowRequestPermissionRationale(Activity activity, String permission) { 825 return activity.shouldShowRequestPermissionRationale(permission); 826 } 827 } 828 829 @RequiresApi(21) 830 static class Api21Impl { Api21Impl()831 private Api21Impl() { 832 // This class is not instantiable. 833 } 834 finishAfterTransition(Activity activity)835 static void finishAfterTransition(Activity activity) { 836 activity.finishAfterTransition(); 837 } 838 setEnterSharedElementCallback(Activity activity, android.app.SharedElementCallback callback)839 static void setEnterSharedElementCallback(Activity activity, 840 android.app.SharedElementCallback callback) { 841 activity.setEnterSharedElementCallback(callback); 842 } 843 setExitSharedElementCallback(Activity activity, android.app.SharedElementCallback callback)844 static void setExitSharedElementCallback(Activity activity, 845 android.app.SharedElementCallback callback) { 846 activity.setExitSharedElementCallback(callback); 847 } 848 postponeEnterTransition(Activity activity)849 static void postponeEnterTransition(Activity activity) { 850 activity.postponeEnterTransition(); 851 } 852 startPostponedEnterTransition(Activity activity)853 static void startPostponedEnterTransition(Activity activity) { 854 activity.startPostponedEnterTransition(); 855 } 856 } 857 858 @RequiresApi(22) 859 static class Api22Impl { Api22Impl()860 private Api22Impl() { 861 // This class is not instantiable. 862 } 863 getReferrer(Activity activity)864 static Uri getReferrer(Activity activity) { 865 return activity.getReferrer(); 866 } 867 } 868 869 @RequiresApi(28) 870 static class Api28Impl { Api28Impl()871 private Api28Impl() { 872 // This class is not instantiable. 873 } 874 875 @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"}) requireViewById(Activity activity, int id)876 static <T> T requireViewById(Activity activity, int id) { 877 return (T) activity.requireViewById(id); 878 } 879 } 880 881 @RequiresApi(23) 882 static class Api23Impl { Api23Impl()883 private Api23Impl() { 884 // This class is not instantiable. 885 } 886 requestPermissions(Activity activity, String[] permissions, int requestCode)887 static void requestPermissions(Activity activity, String[] permissions, int requestCode) { 888 activity.requestPermissions(permissions, requestCode); 889 } 890 shouldShowRequestPermissionRationale(Activity activity, String permission)891 static boolean shouldShowRequestPermissionRationale(Activity activity, String permission) { 892 return activity.shouldShowRequestPermissionRationale(permission); 893 } 894 onSharedElementsReady(Object onSharedElementsReadyListener)895 static void onSharedElementsReady(Object onSharedElementsReadyListener) { 896 ((android.app.SharedElementCallback.OnSharedElementsReadyListener) 897 onSharedElementsReadyListener).onSharedElementsReady(); 898 } 899 } 900 } 901