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.app.Activity;
20 import android.app.ActivityOptions;
21 import android.app.PendingIntent;
22 import android.content.Context;
23 import android.graphics.Bitmap;
24 import android.graphics.Rect;
25 import android.os.Build;
26 import android.os.Bundle;
27 import android.view.View;
28 
29 import androidx.annotation.NonNull;
30 import androidx.annotation.Nullable;
31 import androidx.annotation.RequiresApi;
32 import androidx.core.util.Pair;
33 
34 /**
35  * Helper for accessing features in {@link android.app.ActivityOptions} in a backwards compatible
36  * fashion.
37  */
38 public class ActivityOptionsCompat {
39     /**
40      * A long in the extras delivered by {@link #requestUsageTimeReport} that contains
41      * the total time (in ms) the user spent in the app flow.
42      */
43     public static final String EXTRA_USAGE_TIME_REPORT = "android.activity.usage_time";
44 
45     /**
46      * A Bundle in the extras delivered by {@link #requestUsageTimeReport} that contains
47      * detailed information about the time spent in each package associated with the app;
48      * each key is a package name, whose value is a long containing the time (in ms).
49      */
50     public static final String EXTRA_USAGE_TIME_REPORT_PACKAGES = "android.usage_time_packages";
51 
52     /**
53      * Create an ActivityOptions specifying a custom animation to run when the
54      * activity is displayed.
55      *
56      * @param context Who is defining this. This is the application that the
57      * animation resources will be loaded from.
58      * @param enterResId A resource ID of the animation resource to use for the
59      * incoming activity. Use 0 for no animation.
60      * @param exitResId A resource ID of the animation resource to use for the
61      * outgoing activity. Use 0 for no animation.
62      * @return Returns a new ActivityOptions object that you can use to supply
63      * these options as the options Bundle when starting an activity.
64      */
65     @NonNull
makeCustomAnimation(@onNull Context context, int enterResId, int exitResId)66     public static ActivityOptionsCompat makeCustomAnimation(@NonNull Context context,
67             int enterResId, int exitResId) {
68         if (Build.VERSION.SDK_INT >= 16) {
69             return new ActivityOptionsCompatImpl(ActivityOptions.makeCustomAnimation(context,
70                     enterResId, exitResId));
71         }
72         return new ActivityOptionsCompat();
73     }
74 
75     /**
76      * Create an ActivityOptions specifying an animation where the new activity is
77      * scaled from a small originating area of the screen to its final full
78      * representation.
79      * <p/>
80      * If the Intent this is being used with has not set its
81      * {@link android.content.Intent#setSourceBounds(android.graphics.Rect)},
82      * those bounds will be filled in for you based on the initial bounds passed
83      * in here.
84      *
85      * @param source The View that the new activity is animating from. This
86      * defines the coordinate space for startX and startY.
87      * @param startX The x starting location of the new activity, relative to
88      * source.
89      * @param startY The y starting location of the activity, relative to source.
90      * @param startWidth The initial width of the new activity.
91      * @param startHeight The initial height of the new activity.
92      * @return Returns a new ActivityOptions object that you can use to supply
93      * these options as the options Bundle when starting an activity.
94      */
95     @NonNull
makeScaleUpAnimation(@onNull View source, int startX, int startY, int startWidth, int startHeight)96     public static ActivityOptionsCompat makeScaleUpAnimation(@NonNull View source,
97             int startX, int startY, int startWidth, int startHeight) {
98         if (Build.VERSION.SDK_INT >= 16) {
99             return new ActivityOptionsCompatImpl(ActivityOptions.makeScaleUpAnimation(
100                     source, startX, startY, startWidth, startHeight));
101         }
102         return new ActivityOptionsCompat();
103     }
104 
105     /**
106      * Create an ActivityOptions specifying an animation where the new
107      * activity is revealed from a small originating area of the screen to
108      * its final full representation.
109      *
110      * @param source The View that the new activity is animating from.  This
111      * defines the coordinate space for <var>startX</var> and <var>startY</var>.
112      * @param startX The x starting location of the new activity, relative to <var>source</var>.
113      * @param startY The y starting location of the activity, relative to <var>source</var>.
114      * @param width The initial width of the new activity.
115      * @param height The initial height of the new activity.
116      * @return Returns a new ActivityOptions object that you can use to
117      * supply these options as the options Bundle when starting an activity.
118      */
119     @NonNull
makeClipRevealAnimation(@onNull View source, int startX, int startY, int width, int height)120     public static ActivityOptionsCompat makeClipRevealAnimation(@NonNull View source,
121             int startX, int startY, int width, int height) {
122         if (Build.VERSION.SDK_INT >= 23) {
123             return new ActivityOptionsCompatImpl(ActivityOptions.makeClipRevealAnimation(
124                     source, startX, startY, width, height));
125         }
126         return new ActivityOptionsCompat();
127     }
128 
129     /**
130      * Create an ActivityOptions specifying an animation where a thumbnail is
131      * scaled from a given position to the new activity window that is being
132      * started.
133      * <p/>
134      * If the Intent this is being used with has not set its
135      * {@link android.content.Intent#setSourceBounds(android.graphics.Rect)},
136      * those bounds will be filled in for you based on the initial thumbnail
137      * location and size provided here.
138      *
139      * @param source The View that this thumbnail is animating from. This
140      * defines the coordinate space for startX and startY.
141      * @param thumbnail The bitmap that will be shown as the initial thumbnail
142      * of the animation.
143      * @param startX The x starting location of the bitmap, relative to source.
144      * @param startY The y starting location of the bitmap, relative to source.
145      * @return Returns a new ActivityOptions object that you can use to supply
146      * these options as the options Bundle when starting an activity.
147      */
148     @NonNull
makeThumbnailScaleUpAnimation(@onNull View source, @NonNull Bitmap thumbnail, int startX, int startY)149     public static ActivityOptionsCompat makeThumbnailScaleUpAnimation(@NonNull View source,
150             @NonNull Bitmap thumbnail, int startX, int startY) {
151         if (Build.VERSION.SDK_INT >= 16) {
152             return new ActivityOptionsCompatImpl(ActivityOptions.makeThumbnailScaleUpAnimation(
153                     source, thumbnail, startX, startY));
154         }
155         return new ActivityOptionsCompat();
156     }
157 
158     /**
159      * Create an ActivityOptions to transition between Activities using cross-Activity scene
160      * animations. This method carries the position of one shared element to the started Activity.
161      * The position of <code>sharedElement</code> will be used as the epicenter for the
162      * exit Transition. The position of the shared element in the launched Activity will be the
163      * epicenter of its entering Transition.
164      *
165      * <p>This requires {@link android.view.Window#FEATURE_CONTENT_TRANSITIONS} to be
166      * enabled on the calling Activity to cause an exit transition. The same must be in
167      * the called Activity to get an entering transition.</p>
168      * @param activity The Activity whose window contains the shared elements.
169      * @param sharedElement The View to transition to the started Activity. sharedElement must
170      *                      have a non-null sharedElementName.
171      * @param sharedElementName The shared element name as used in the target Activity. This may
172      *                          be null if it has the same name as sharedElement.
173      * @return Returns a new ActivityOptions object that you can use to
174      *         supply these options as the options Bundle when starting an activity.
175      */
176     @NonNull
makeSceneTransitionAnimation(@onNull Activity activity, @NonNull View sharedElement, @NonNull String sharedElementName)177     public static ActivityOptionsCompat makeSceneTransitionAnimation(@NonNull Activity activity,
178             @NonNull View sharedElement, @NonNull String sharedElementName) {
179         if (Build.VERSION.SDK_INT >= 21) {
180             return new ActivityOptionsCompatImpl(ActivityOptions.makeSceneTransitionAnimation(
181                     activity, sharedElement, sharedElementName));
182         }
183         return new ActivityOptionsCompat();
184     }
185 
186     /**
187      * Create an ActivityOptions to transition between Activities using cross-Activity scene
188      * animations. This method carries the position of multiple shared elements to the started
189      * Activity. The position of the first element in sharedElements
190      * will be used as the epicenter for the exit Transition. The position of the associated
191      * shared element in the launched Activity will be the epicenter of its entering Transition.
192      *
193      * <p>This requires {@link android.view.Window#FEATURE_CONTENT_TRANSITIONS} to be
194      * enabled on the calling Activity to cause an exit transition. The same must be in
195      * the called Activity to get an entering transition.</p>
196      * @param activity The Activity whose window contains the shared elements.
197      * @param sharedElements The names of the shared elements to transfer to the called
198      *                       Activity and their associated Views. The Views must each have
199      *                       a unique shared element name.
200      * @return Returns a new ActivityOptions object that you can use to
201      *         supply these options as the options Bundle when starting an activity.
202      */
203     @NonNull
204     @SuppressWarnings("unchecked")
makeSceneTransitionAnimation(@onNull Activity activity, Pair<View, String>... sharedElements)205     public static ActivityOptionsCompat makeSceneTransitionAnimation(@NonNull Activity activity,
206             Pair<View, String>... sharedElements) {
207         if (Build.VERSION.SDK_INT >= 21) {
208             android.util.Pair<View, String>[] pairs = null;
209             if (sharedElements != null) {
210                 pairs = new android.util.Pair[sharedElements.length];
211                 for (int i = 0; i < sharedElements.length; i++) {
212                     pairs[i] = android.util.Pair.create(
213                             sharedElements[i].first, sharedElements[i].second);
214                 }
215             }
216             return new ActivityOptionsCompatImpl(
217                     ActivityOptions.makeSceneTransitionAnimation(activity, pairs));
218         }
219         return new ActivityOptionsCompat();
220     }
221 
222     /**
223      * If set along with Intent.FLAG_ACTIVITY_NEW_DOCUMENT then the task being launched will not be
224      * presented to the user but will instead be only available through the recents task list.
225      * In addition, the new task wil be affiliated with the launching activity's task.
226      * Affiliated tasks are grouped together in the recents task list.
227      *
228      * <p>This behavior is not supported for activities with
229      * {@link android.R.attr#launchMode launchMode} values of
230      * <code>singleInstance</code> or <code>singleTask</code>.
231      */
232     @NonNull
makeTaskLaunchBehind()233     public static ActivityOptionsCompat makeTaskLaunchBehind() {
234         if (Build.VERSION.SDK_INT >= 21) {
235             return new ActivityOptionsCompatImpl(ActivityOptions.makeTaskLaunchBehind());
236         }
237         return new ActivityOptionsCompat();
238     }
239 
240     /**
241      * Create a basic ActivityOptions that has no special animation associated with it.
242      * Other options can still be set.
243      */
244     @NonNull
makeBasic()245     public static ActivityOptionsCompat makeBasic() {
246         if (Build.VERSION.SDK_INT >= 23) {
247             return new ActivityOptionsCompatImpl(ActivityOptions.makeBasic());
248         }
249         return new ActivityOptionsCompat();
250     }
251 
252     @RequiresApi(16)
253     private static class ActivityOptionsCompatImpl extends ActivityOptionsCompat {
254         private final ActivityOptions mActivityOptions;
255 
ActivityOptionsCompatImpl(ActivityOptions activityOptions)256         ActivityOptionsCompatImpl(ActivityOptions activityOptions) {
257             mActivityOptions = activityOptions;
258         }
259 
260         @Override
toBundle()261         public Bundle toBundle() {
262             return mActivityOptions.toBundle();
263         }
264 
265         @Override
update(ActivityOptionsCompat otherOptions)266         public void update(ActivityOptionsCompat otherOptions) {
267             if (otherOptions instanceof ActivityOptionsCompatImpl) {
268                 ActivityOptionsCompatImpl otherImpl =
269                         (ActivityOptionsCompatImpl) otherOptions;
270                 mActivityOptions.update(otherImpl.mActivityOptions);
271             }
272         }
273 
274         @Override
requestUsageTimeReport(PendingIntent receiver)275         public void requestUsageTimeReport(PendingIntent receiver) {
276             if (Build.VERSION.SDK_INT >= 23) {
277                 mActivityOptions.requestUsageTimeReport(receiver);
278             }
279         }
280 
281         @Override
setLaunchBounds(@ullable Rect screenSpacePixelRect)282         public ActivityOptionsCompat setLaunchBounds(@Nullable Rect screenSpacePixelRect) {
283             if (Build.VERSION.SDK_INT < 24) {
284                 return this;
285             }
286             return new ActivityOptionsCompatImpl(
287                     mActivityOptions.setLaunchBounds(screenSpacePixelRect));
288         }
289 
290         @Override
getLaunchBounds()291         public Rect getLaunchBounds() {
292             if (Build.VERSION.SDK_INT < 24) {
293                 return null;
294             }
295             return mActivityOptions.getLaunchBounds();
296         }
297     }
298 
ActivityOptionsCompat()299     protected ActivityOptionsCompat() {
300     }
301 
302     /**
303      * Sets the bounds (window size) that the activity should be launched in.
304      * Rect position should be provided in pixels and in screen coordinates.
305      * Set to null explicitly for fullscreen.
306      * <p>
307      * <strong>NOTE:<strong/> This value is ignored on devices that don't have
308      * {@link android.content.pm.PackageManager#FEATURE_FREEFORM_WINDOW_MANAGEMENT} or
309      * {@link android.content.pm.PackageManager#FEATURE_PICTURE_IN_PICTURE} enabled.
310      * @param screenSpacePixelRect Launch bounds to use for the activity or null for fullscreen.
311      */
312     @NonNull
setLaunchBounds(@ullable Rect screenSpacePixelRect)313     public ActivityOptionsCompat setLaunchBounds(@Nullable Rect screenSpacePixelRect) {
314         return this;
315     }
316 
317     /**
318      * Returns the bounds that should be used to launch the activity.
319      * @see #setLaunchBounds(Rect)
320      * @return Bounds used to launch the activity.
321      */
322     @Nullable
getLaunchBounds()323     public Rect getLaunchBounds() {
324         return null;
325     }
326 
327     /**
328      * Returns the created options as a Bundle, which can be passed to
329      * {@link androidx.core.content.ContextCompat#startActivity(Context, android.content.Intent, Bundle)}.
330      * Note that the returned Bundle is still owned by the ActivityOptions
331      * object; you must not modify it, but can supply it to the startActivity
332      * methods that take an options Bundle.
333      */
334     @Nullable
toBundle()335     public Bundle toBundle() {
336         return null;
337     }
338 
339     /**
340      * Update the current values in this ActivityOptions from those supplied in
341      * otherOptions. Any values defined in otherOptions replace those in the
342      * base options.
343      */
update(@onNull ActivityOptionsCompat otherOptions)344     public void update(@NonNull ActivityOptionsCompat otherOptions) {
345         // Do nothing.
346     }
347 
348     /**
349      * Ask the the system track that time the user spends in the app being launched, and
350      * report it back once done.  The report will be sent to the given receiver, with
351      * the extras {@link #EXTRA_USAGE_TIME_REPORT} and {@link #EXTRA_USAGE_TIME_REPORT_PACKAGES}
352      * filled in.
353      *
354      * <p>The time interval tracked is from launching this activity until the user leaves
355      * that activity's flow.  They are considered to stay in the flow as long as
356      * new activities are being launched or returned to from the original flow,
357      * even if this crosses package or task boundaries.  For example, if the originator
358      * starts an activity to view an image, and while there the user selects to share,
359      * which launches their email app in a new task, and they complete the share, the
360      * time during that entire operation will be included until they finally hit back from
361      * the original image viewer activity.</p>
362      *
363      * <p>The user is considered to complete a flow once they switch to another
364      * activity that is not part of the tracked flow.  This may happen, for example, by
365      * using the notification shade, launcher, or recents to launch or switch to another
366      * app.  Simply going in to these navigation elements does not break the flow (although
367      * the launcher and recents stops time tracking of the session); it is the act of
368      * going somewhere else that completes the tracking.</p>
369      *
370      * @param receiver A broadcast receiver that will receive the report.
371      */
requestUsageTimeReport(@onNull PendingIntent receiver)372     public void requestUsageTimeReport(@NonNull PendingIntent receiver) {
373         // Do nothing.
374     }
375 }
376