1 /*
2  * Copyright (C) 2012 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.annotation.SuppressLint;
20 import android.app.Activity;
21 import android.app.ActivityOptions;
22 import android.app.PendingIntent;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.graphics.Bitmap;
26 import android.graphics.Rect;
27 import android.os.Build;
28 import android.os.Bundle;
29 import android.view.Display;
30 import android.view.View;
31 
32 import androidx.annotation.IntDef;
33 import androidx.annotation.RestrictTo;
34 import androidx.core.util.Pair;
35 
36 import org.jspecify.annotations.NonNull;
37 import org.jspecify.annotations.Nullable;
38 
39 import java.lang.annotation.Retention;
40 import java.lang.annotation.RetentionPolicy;
41 
42 /**
43  * Helper for accessing features in {@link android.app.ActivityOptions} in a backwards compatible
44  * fashion.
45  */
46 public class ActivityOptionsCompat {
47     /**
48      * A long in the extras delivered by {@link #requestUsageTimeReport} that contains
49      * the total time (in ms) the user spent in the app flow.
50      */
51     public static final String EXTRA_USAGE_TIME_REPORT = "android.activity.usage_time";
52 
53     /**
54      * A Bundle in the extras delivered by {@link #requestUsageTimeReport} that contains
55      * detailed information about the time spent in each package associated with the app;
56      * each key is a package name, whose value is a long containing the time (in ms).
57      */
58     public static final String EXTRA_USAGE_TIME_REPORT_PACKAGES = "android.usage_time_packages";
59 
60     /**
61      * Enumeration of background activity start modes.
62      * <p/>
63      * These define if an app wants to grant it's background activity start privileges to a
64      * {@link PendingIntent}.
65      */
66     @Retention(RetentionPolicy.SOURCE)
67     @RestrictTo(RestrictTo.Scope.LIBRARY)
68     @IntDef(value = {
69             ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED,
70             ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED,
71             ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED})
72     public @interface BackgroundActivityStartMode {}
73 
74     /**
75      * Create an ActivityOptions specifying a custom animation to run when the
76      * activity is displayed.
77      *
78      * @param context Who is defining this. This is the application that the
79      * animation resources will be loaded from.
80      * @param enterResId A resource ID of the animation resource to use for the
81      * incoming activity. Use 0 for no animation.
82      * @param exitResId A resource ID of the animation resource to use for the
83      * outgoing activity. Use 0 for no animation.
84      * @return Returns a new ActivityOptions object that you can use to supply
85      * these options as the options Bundle when starting an activity.
86      */
makeCustomAnimation(@onNull Context context, int enterResId, int exitResId)87     public static @NonNull ActivityOptionsCompat makeCustomAnimation(@NonNull Context context,
88             int enterResId, int exitResId) {
89         return new ActivityOptionsCompatImpl(
90                 ActivityOptions.makeCustomAnimation(context, enterResId, exitResId));
91     }
92 
93     /**
94      * Create an ActivityOptions specifying an animation where the new activity is
95      * scaled from a small originating area of the screen to its final full
96      * representation.
97      * <p/>
98      * If the Intent this is being used with has not set its
99      * {@link android.content.Intent#setSourceBounds(android.graphics.Rect)},
100      * those bounds will be filled in for you based on the initial bounds passed
101      * in here.
102      *
103      * @param source The View that the new activity is animating from. This
104      * defines the coordinate space for startX and startY.
105      * @param startX The x starting location of the new activity, relative to
106      * source.
107      * @param startY The y starting location of the activity, relative to source.
108      * @param startWidth The initial width of the new activity.
109      * @param startHeight The initial height of the new activity.
110      * @return Returns a new ActivityOptions object that you can use to supply
111      * these options as the options Bundle when starting an activity.
112      */
makeScaleUpAnimation(@onNull View source, int startX, int startY, int startWidth, int startHeight)113     public static @NonNull ActivityOptionsCompat makeScaleUpAnimation(@NonNull View source,
114             int startX, int startY, int startWidth, int startHeight) {
115         return new ActivityOptionsCompatImpl(
116                 ActivityOptions.makeScaleUpAnimation(source, startX, startY, startWidth,
117                         startHeight));
118     }
119 
120     /**
121      * Create an ActivityOptions specifying an animation where the new
122      * activity is revealed from a small originating area of the screen to
123      * its final full representation.
124      *
125      * @param source The View that the new activity is animating from.  This
126      * defines the coordinate space for <var>startX</var> and <var>startY</var>.
127      * @param startX The x starting location of the new activity, relative to <var>source</var>.
128      * @param startY The y starting location of the activity, relative to <var>source</var>.
129      * @param width The initial width of the new activity.
130      * @param height The initial height of the new activity.
131      * @return Returns a new ActivityOptions object that you can use to
132      * supply these options as the options Bundle when starting an activity.
133      */
makeClipRevealAnimation(@onNull View source, int startX, int startY, int width, int height)134     public static @NonNull ActivityOptionsCompat makeClipRevealAnimation(@NonNull View source,
135             int startX, int startY, int width, int height) {
136         if (Build.VERSION.SDK_INT >= 23) {
137             return new ActivityOptionsCompatImpl(
138                     ActivityOptions.makeClipRevealAnimation(source, startX, startY, width, height));
139         }
140         return new ActivityOptionsCompat();
141     }
142 
143     /**
144      * Create an ActivityOptions specifying an animation where a thumbnail is
145      * scaled from a given position to the new activity window that is being
146      * started.
147      * <p/>
148      * If the Intent this is being used with has not set its
149      * {@link android.content.Intent#setSourceBounds(android.graphics.Rect)},
150      * those bounds will be filled in for you based on the initial thumbnail
151      * location and size provided here.
152      *
153      * @param source The View that this thumbnail is animating from. This
154      * defines the coordinate space for startX and startY.
155      * @param thumbnail The bitmap that will be shown as the initial thumbnail
156      * of the animation.
157      * @param startX The x starting location of the bitmap, relative to source.
158      * @param startY The y starting location of the bitmap, relative to source.
159      * @return Returns a new ActivityOptions object that you can use to supply
160      * these options as the options Bundle when starting an activity.
161      */
makeThumbnailScaleUpAnimation(@onNull View source, @NonNull Bitmap thumbnail, int startX, int startY)162     public static @NonNull ActivityOptionsCompat makeThumbnailScaleUpAnimation(@NonNull View source,
163             @NonNull Bitmap thumbnail, int startX, int startY) {
164         return new ActivityOptionsCompatImpl(
165                 ActivityOptions.makeThumbnailScaleUpAnimation(source, thumbnail, startX, startY));
166     }
167 
168     /**
169      * Create an ActivityOptions to transition between Activities using cross-Activity scene
170      * animations. This method carries the position of one shared element to the started Activity.
171      * The position of <code>sharedElement</code> will be used as the epicenter for the
172      * exit Transition. The position of the shared element in the launched Activity will be the
173      * epicenter of its entering Transition.
174      *
175      * <p>This requires {@link android.view.Window#FEATURE_CONTENT_TRANSITIONS} to be
176      * enabled on the calling Activity to cause an exit transition. The same must be in
177      * the called Activity to get an entering transition.</p>
178      * @param activity The Activity whose window contains the shared elements.
179      * @param sharedElement The View to transition to the started Activity. sharedElement must
180      *                      have a non-null sharedElementName.
181      * @param sharedElementName The shared element name as used in the target Activity. This may
182      *                          be null if it has the same name as sharedElement.
183      * @return Returns a new ActivityOptions object that you can use to
184      *         supply these options as the options Bundle when starting an activity.
185      */
makeSceneTransitionAnimation( @onNull Activity activity, @NonNull View sharedElement, @NonNull String sharedElementName)186     public static @NonNull ActivityOptionsCompat makeSceneTransitionAnimation(
187             @NonNull Activity activity, @NonNull View sharedElement,
188             @NonNull String sharedElementName) {
189         return new ActivityOptionsCompatImpl(
190                 ActivityOptions.makeSceneTransitionAnimation(activity, sharedElement,
191                         sharedElementName));
192     }
193 
194     /**
195      * Create an ActivityOptions to transition between Activities using cross-Activity scene
196      * animations. This method carries the position of multiple shared elements to the started
197      * Activity. The position of the first element in sharedElements
198      * will be used as the epicenter for the exit Transition. The position of the associated
199      * shared element in the launched Activity will be the epicenter of its entering Transition.
200      *
201      * <p>This requires {@link android.view.Window#FEATURE_CONTENT_TRANSITIONS} to be
202      * enabled on the calling Activity to cause an exit transition. The same must be in
203      * the called Activity to get an entering transition.</p>
204      * @param activity The Activity whose window contains the shared elements.
205      * @param sharedElements The names of the shared elements to transfer to the called
206      *                       Activity and their associated Views. The Views must each have
207      *                       a unique shared element name.
208      * @return Returns a new ActivityOptions object that you can use to
209      *         supply these options as the options Bundle when starting an activity.
210      */
211     @SuppressWarnings("unchecked")
makeSceneTransitionAnimation( @onNull Activity activity, Pair<View, String> @Nullable ... sharedElements)212     public static @NonNull ActivityOptionsCompat makeSceneTransitionAnimation(
213             @NonNull Activity activity, Pair<View, String> @Nullable ... sharedElements) {
214         android.util.Pair<View, String>[] pairs = null;
215         if (sharedElements != null) {
216             pairs = new android.util.Pair[sharedElements.length];
217             for (int i = 0; i < sharedElements.length; i++) {
218                 pairs[i] = android.util.Pair.create(
219                         sharedElements[i].first, sharedElements[i].second);
220             }
221         }
222         return new ActivityOptionsCompatImpl(
223                 ActivityOptions.makeSceneTransitionAnimation(activity, pairs));
224     }
225 
226     /**
227      * If set along with Intent.FLAG_ACTIVITY_NEW_DOCUMENT then the task being launched will not be
228      * presented to the user but will instead be only available through the recents task list.
229      * In addition, the new task wil be affiliated with the launching activity's task.
230      * Affiliated tasks are grouped together in the recents task list.
231      *
232      * <p>This behavior is not supported for activities with
233      * {@link android.R.attr#launchMode launchMode} values of
234      * <code>singleInstance</code> or <code>singleTask</code>.
235      */
makeTaskLaunchBehind()236     public static @NonNull ActivityOptionsCompat makeTaskLaunchBehind() {
237         return new ActivityOptionsCompatImpl(ActivityOptions.makeTaskLaunchBehind());
238     }
239 
240     /**
241      * Create a basic ActivityOptions that has no special animation associated with it.
242      * Other options can still be set.
243      */
makeBasic()244     public static @NonNull ActivityOptionsCompat makeBasic() {
245         if (Build.VERSION.SDK_INT >= 23) {
246             return new ActivityOptionsCompatImpl(ActivityOptions.makeBasic());
247         }
248         return new ActivityOptionsCompat();
249     }
250 
251     private static class ActivityOptionsCompatImpl extends ActivityOptionsCompat {
252         private final ActivityOptions mActivityOptions;
253 
ActivityOptionsCompatImpl(ActivityOptions activityOptions)254         ActivityOptionsCompatImpl(ActivityOptions activityOptions) {
255             mActivityOptions = activityOptions;
256         }
257 
258         @Override
toBundle()259         public Bundle toBundle() {
260             return mActivityOptions.toBundle();
261         }
262 
263         @Override
update(@onNull ActivityOptionsCompat otherOptions)264         public void update(@NonNull ActivityOptionsCompat otherOptions) {
265             if (otherOptions instanceof ActivityOptionsCompatImpl) {
266                 ActivityOptionsCompatImpl otherImpl =
267                         (ActivityOptionsCompatImpl) otherOptions;
268                 mActivityOptions.update(otherImpl.mActivityOptions);
269             }
270         }
271 
272         @Override
requestUsageTimeReport(@onNull PendingIntent receiver)273         public void requestUsageTimeReport(@NonNull PendingIntent receiver) {
274             if (Build.VERSION.SDK_INT >= 23) {
275                 mActivityOptions.requestUsageTimeReport(receiver);
276             }
277         }
278 
279         @Override
setLaunchBounds(@ullable Rect screenSpacePixelRect)280         public @NonNull ActivityOptionsCompat setLaunchBounds(@Nullable Rect screenSpacePixelRect) {
281             if (Build.VERSION.SDK_INT < 24) {
282                 return this;
283             }
284             return new ActivityOptionsCompatImpl(
285                     mActivityOptions.setLaunchBounds(screenSpacePixelRect));
286         }
287 
288         @Override
getLaunchBounds()289         public Rect getLaunchBounds() {
290             if (Build.VERSION.SDK_INT < 24) {
291                 return null;
292             }
293             return mActivityOptions.getLaunchBounds();
294         }
295 
296         @Override
setShareIdentityEnabled(boolean shareIdentity)297         public @NonNull ActivityOptionsCompat setShareIdentityEnabled(boolean shareIdentity) {
298             if (Build.VERSION.SDK_INT < 34) {
299                 return this;
300             }
301             return new ActivityOptionsCompatImpl(
302                     mActivityOptions.setShareIdentityEnabled(shareIdentity));
303         }
304 
305         @SuppressLint("WrongConstant")
306         @Override
setPendingIntentBackgroundActivityStartMode( @ackgroundActivityStartMode int state)307         public @NonNull ActivityOptionsCompat setPendingIntentBackgroundActivityStartMode(
308                 @BackgroundActivityStartMode int state) {
309             if (Build.VERSION.SDK_INT >= 34) {
310                 mActivityOptions.setPendingIntentBackgroundActivityStartMode(state);
311             } else if (Build.VERSION.SDK_INT >= 33) {
312                 // Matches the behavior of isPendingIntentBackgroundActivityLaunchAllowed().
313                 boolean isAllowed = state != ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
314                 mActivityOptions.setPendingIntentBackgroundActivityLaunchAllowed(isAllowed);
315             }
316             return this;
317         }
318 
319         @Override
getLaunchDisplayId()320         public int getLaunchDisplayId() {
321             if (Build.VERSION.SDK_INT >= 26) {
322                 return mActivityOptions.getLaunchDisplayId();
323             } else {
324                 return Display.INVALID_DISPLAY;
325             }
326         }
327 
328         @Override
setLaunchDisplayId(int launchDisplayId)329         public @NonNull ActivityOptionsCompat setLaunchDisplayId(int launchDisplayId) {
330             if (Build.VERSION.SDK_INT >= 26) {
331                 mActivityOptions.setLaunchDisplayId(launchDisplayId);
332             }
333             return this;
334         }
335     }
336 
ActivityOptionsCompat()337     protected ActivityOptionsCompat() {
338     }
339 
340     /**
341      * Sets the bounds (window size) that the activity should be launched in.
342      * Rect position should be provided in pixels and in screen coordinates.
343      * Set to null explicitly for fullscreen.
344      * <p>
345      * <strong>NOTE:<strong/> This value is ignored on devices that don't have
346      * {@link android.content.pm.PackageManager#FEATURE_FREEFORM_WINDOW_MANAGEMENT} or
347      * {@link android.content.pm.PackageManager#FEATURE_PICTURE_IN_PICTURE} enabled.
348      * @param screenSpacePixelRect Launch bounds to use for the activity or null for fullscreen.
349      */
setLaunchBounds(@ullable Rect screenSpacePixelRect)350     public @NonNull ActivityOptionsCompat setLaunchBounds(@Nullable Rect screenSpacePixelRect) {
351         return this;
352     }
353 
354     /**
355      * Returns the bounds that should be used to launch the activity.
356      * @see #setLaunchBounds(Rect)
357      * @return Bounds used to launch the activity.
358      */
getLaunchBounds()359     public @Nullable Rect getLaunchBounds() {
360         return null;
361     }
362 
363     /**
364      * Returns the created options as a Bundle, which can be passed to
365      * {@link androidx.core.content.ContextCompat#startActivity(Context, android.content.Intent, Bundle)}.
366      * Note that the returned Bundle is still owned by the ActivityOptions
367      * object; you must not modify it, but can supply it to the startActivity
368      * methods that take an options Bundle.
369      */
toBundle()370     public @Nullable Bundle toBundle() {
371         return null;
372     }
373 
374     /**
375      * Update the current values in this ActivityOptions from those supplied in
376      * otherOptions. Any values defined in otherOptions replace those in the
377      * base options.
378      */
update(@onNull ActivityOptionsCompat otherOptions)379     public void update(@NonNull ActivityOptionsCompat otherOptions) {
380         // Do nothing.
381     }
382 
383     /**
384      * Ask the the system track that time the user spends in the app being launched, and
385      * report it back once done.  The report will be sent to the given receiver, with
386      * the extras {@link #EXTRA_USAGE_TIME_REPORT} and {@link #EXTRA_USAGE_TIME_REPORT_PACKAGES}
387      * filled in.
388      *
389      * <p>The time interval tracked is from launching this activity until the user leaves
390      * that activity's flow.  They are considered to stay in the flow as long as
391      * new activities are being launched or returned to from the original flow,
392      * even if this crosses package or task boundaries.  For example, if the originator
393      * starts an activity to view an image, and while there the user selects to share,
394      * which launches their email app in a new task, and they complete the share, the
395      * time during that entire operation will be included until they finally hit back from
396      * the original image viewer activity.</p>
397      *
398      * <p>The user is considered to complete a flow once they switch to another
399      * activity that is not part of the tracked flow.  This may happen, for example, by
400      * using the notification shade, launcher, or recents to launch or switch to another
401      * app.  Simply going in to these navigation elements does not break the flow (although
402      * the launcher and recents stops time tracking of the session); it is the act of
403      * going somewhere else that completes the tracking.</p>
404      *
405      * @param receiver A broadcast receiver that will receive the report.
406      */
requestUsageTimeReport(@onNull PendingIntent receiver)407     public void requestUsageTimeReport(@NonNull PendingIntent receiver) {
408         // Do nothing.
409     }
410 
411     /**
412      * Sets whether the identity of the launching app should be shared with the activity.
413      *
414      * <p>Use this option when starting an activity that needs to know the identity of the
415      * launching app; with this set to {@code true}, the activity will have access to the launching
416      * app's package name and uid.
417      *
418      * <p>Defaults to {@code false} if not set. This is a no-op before U.
419      *
420      * <p>Note, even if the launching app does not explicitly enable sharing of its identity, if
421      * the activity is started with {@code Activity#startActivityForResult}, then {@link
422      * Activity#getCallingPackage()} will still return the launching app's package name to
423      * allow validation of the result's recipient. Also, an activity running within a package
424      * signed by the same key used to sign the platform (some system apps such as Settings will
425      * be signed with the platform's key) will have access to the launching app's identity.
426      *
427      * @param shareIdentity whether the launching app's identity should be shared with the activity
428      * @return {@code this} {@link ActivityOptions} instance.
429      * @see Activity#getLaunchedFromPackage()
430      * @see Activity#getLaunchedFromUid()
431      */
setShareIdentityEnabled(boolean shareIdentity)432     public @NonNull ActivityOptionsCompat setShareIdentityEnabled(boolean shareIdentity) {
433         return this;
434     }
435 
436     /**
437      * Sets the mode for allowing or denying the senders privileges to start background activities
438      * to the PendingIntent.
439      * <p/>
440      * This is typically used in when executing {@link PendingIntent#send(Context, int, Intent)} or
441      * similar methods. A privileged sender of a PendingIntent should only grant
442      * {@link ActivityOptions#MODE_BACKGROUND_ACTIVITY_START_ALLOWED} if the PendingIntent is from a
443      * trusted source and/or executed on behalf the user.
444      */
setPendingIntentBackgroundActivityStartMode( @ackgroundActivityStartMode int state)445     public @NonNull ActivityOptionsCompat setPendingIntentBackgroundActivityStartMode(
446             @BackgroundActivityStartMode int state) {
447         return this;
448     }
449 
450     /**
451      * Gets the id of the display where activity should be launched.
452      * <p>
453      * On API 25 and below, this method always returns {@link Display#INVALID_DISPLAY}.
454      *
455      * @return The id of the display where activity should be launched,
456      *         {@link android.view.Display#INVALID_DISPLAY} if not set.
457      * @see #setLaunchDisplayId(int)
458      */
getLaunchDisplayId()459     public int getLaunchDisplayId() {
460         return Display.INVALID_DISPLAY;
461     }
462 
463     /**
464      * Sets the id of the display where the activity should be launched.
465      * An app can launch activities on public displays or displays where the app already has
466      * activities. Otherwise, trying to launch on a private display or providing an invalid display
467      * id will result in an exception.
468      * <p>
469      * Setting launch display id will be ignored on devices that don't have
470      * {@link android.content.pm.PackageManager#FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS}.
471      * <p>
472      * On API 25 and below, calling this method has no effect.
473      *
474      * @param launchDisplayId The id of the display where the activity should be launched.
475      * @return {@code this} {@link ActivityOptions} instance.
476      */
setLaunchDisplayId(int launchDisplayId)477     public @NonNull ActivityOptionsCompat setLaunchDisplayId(int launchDisplayId) {
478         return this;
479     }
480 }
481