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