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
17package android.app;
18
19import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
20import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
21
22import android.annotation.Nullable;
23import android.annotation.TestApi;
24import android.content.Context;
25import android.content.Intent;
26import android.graphics.Bitmap;
27import android.graphics.Rect;
28import android.os.Bundle;
29import android.os.Handler;
30import android.os.IRemoteCallback;
31import android.os.Parcelable;
32import android.os.RemoteException;
33import android.os.ResultReceiver;
34import android.util.Pair;
35import android.util.Slog;
36import android.view.AppTransitionAnimationSpec;
37import android.view.View;
38import android.view.Window;
39
40import java.util.ArrayList;
41
42/**
43 * Helper class for building an options Bundle that can be used with
44 * {@link android.content.Context#startActivity(android.content.Intent, android.os.Bundle)
45 * Context.startActivity(Intent, Bundle)} and related methods.
46 */
47public class ActivityOptions {
48    private static final String TAG = "ActivityOptions";
49
50    /**
51     * A long in the extras delivered by {@link #requestUsageTimeReport} that contains
52     * the total time (in ms) the user spent in the app flow.
53     */
54    public static final String EXTRA_USAGE_TIME_REPORT = "android.activity.usage_time";
55
56    /**
57     * A Bundle in the extras delivered by {@link #requestUsageTimeReport} that contains
58     * detailed information about the time spent in each package associated with the app;
59     * each key is a package name, whose value is a long containing the time (in ms).
60     */
61    public static final String EXTRA_USAGE_TIME_REPORT_PACKAGES = "android.usage_time_packages";
62
63    /**
64     * The package name that created the options.
65     * @hide
66     */
67    public static final String KEY_PACKAGE_NAME = "android:activity.packageName";
68
69    /**
70     * The bounds (window size) that the activity should be launched in. Set to null explicitly for
71     * full screen. If the key is not found, previous bounds will be preserved.
72     * NOTE: This value is ignored on devices that don't have
73     * {@link android.content.pm.PackageManager#FEATURE_FREEFORM_WINDOW_MANAGEMENT} or
74     * {@link android.content.pm.PackageManager#FEATURE_PICTURE_IN_PICTURE} enabled.
75     * @hide
76     */
77    public static final String KEY_LAUNCH_BOUNDS = "android:activity.launchBounds";
78
79    /**
80     * Type of animation that arguments specify.
81     * @hide
82     */
83    public static final String KEY_ANIM_TYPE = "android:activity.animType";
84
85    /**
86     * Custom enter animation resource ID.
87     * @hide
88     */
89    public static final String KEY_ANIM_ENTER_RES_ID = "android:activity.animEnterRes";
90
91    /**
92     * Custom exit animation resource ID.
93     * @hide
94     */
95    public static final String KEY_ANIM_EXIT_RES_ID = "android:activity.animExitRes";
96
97    /**
98     * Custom in-place animation resource ID.
99     * @hide
100     */
101    public static final String KEY_ANIM_IN_PLACE_RES_ID = "android:activity.animInPlaceRes";
102
103    /**
104     * Bitmap for thumbnail animation.
105     * @hide
106     */
107    public static final String KEY_ANIM_THUMBNAIL = "android:activity.animThumbnail";
108
109    /**
110     * Start X position of thumbnail animation.
111     * @hide
112     */
113    public static final String KEY_ANIM_START_X = "android:activity.animStartX";
114
115    /**
116     * Start Y position of thumbnail animation.
117     * @hide
118     */
119    public static final String KEY_ANIM_START_Y = "android:activity.animStartY";
120
121    /**
122     * Initial width of the animation.
123     * @hide
124     */
125    public static final String KEY_ANIM_WIDTH = "android:activity.animWidth";
126
127    /**
128     * Initial height of the animation.
129     * @hide
130     */
131    public static final String KEY_ANIM_HEIGHT = "android:activity.animHeight";
132
133    /**
134     * Callback for when animation is started.
135     * @hide
136     */
137    public static final String KEY_ANIM_START_LISTENER = "android:activity.animStartListener";
138
139    /**
140     * Callback for when the last frame of the animation is played.
141     * @hide
142     */
143    private static final String KEY_ANIMATION_FINISHED_LISTENER =
144            "android:activity.animationFinishedListener";
145
146    /**
147     * Descriptions of app transition animations to be played during the activity launch.
148     */
149    private static final String KEY_ANIM_SPECS = "android:activity.animSpecs";
150
151    /**
152     * The stack id the activity should be launched into.
153     * @hide
154     */
155    private static final String KEY_LAUNCH_STACK_ID = "android.activity.launchStackId";
156
157    /**
158     * The task id the activity should be launched into.
159     * @hide
160     */
161    private static final String KEY_LAUNCH_TASK_ID = "android.activity.launchTaskId";
162
163    /**
164     * See {@link #setTaskOverlay}.
165     * @hide
166     */
167    private static final String KEY_TASK_OVERLAY = "android.activity.taskOverlay";
168
169    /**
170     * Where the docked stack should be positioned.
171     * @hide
172     */
173    private static final String KEY_DOCK_CREATE_MODE = "android:activity.dockCreateMode";
174
175    /**
176     * For Activity transitions, the calling Activity's TransitionListener used to
177     * notify the called Activity when the shared element and the exit transitions
178     * complete.
179     */
180    private static final String KEY_TRANSITION_COMPLETE_LISTENER
181            = "android:activity.transitionCompleteListener";
182
183    private static final String KEY_TRANSITION_IS_RETURNING
184            = "android:activity.transitionIsReturning";
185    private static final String KEY_TRANSITION_SHARED_ELEMENTS
186            = "android:activity.sharedElementNames";
187    private static final String KEY_RESULT_DATA = "android:activity.resultData";
188    private static final String KEY_RESULT_CODE = "android:activity.resultCode";
189    private static final String KEY_EXIT_COORDINATOR_INDEX
190            = "android:activity.exitCoordinatorIndex";
191
192    private static final String KEY_USAGE_TIME_REPORT = "android:activity.usageTimeReport";
193
194    /** @hide */
195    public static final int ANIM_NONE = 0;
196    /** @hide */
197    public static final int ANIM_CUSTOM = 1;
198    /** @hide */
199    public static final int ANIM_SCALE_UP = 2;
200    /** @hide */
201    public static final int ANIM_THUMBNAIL_SCALE_UP = 3;
202    /** @hide */
203    public static final int ANIM_THUMBNAIL_SCALE_DOWN = 4;
204    /** @hide */
205    public static final int ANIM_SCENE_TRANSITION = 5;
206    /** @hide */
207    public static final int ANIM_DEFAULT = 6;
208    /** @hide */
209    public static final int ANIM_LAUNCH_TASK_BEHIND = 7;
210    /** @hide */
211    public static final int ANIM_THUMBNAIL_ASPECT_SCALE_UP = 8;
212    /** @hide */
213    public static final int ANIM_THUMBNAIL_ASPECT_SCALE_DOWN = 9;
214    /** @hide */
215    public static final int ANIM_CUSTOM_IN_PLACE = 10;
216    /** @hide */
217    public static final int ANIM_CLIP_REVEAL = 11;
218
219    private String mPackageName;
220    private Rect mLaunchBounds;
221    private int mAnimationType = ANIM_NONE;
222    private int mCustomEnterResId;
223    private int mCustomExitResId;
224    private int mCustomInPlaceResId;
225    private Bitmap mThumbnail;
226    private int mStartX;
227    private int mStartY;
228    private int mWidth;
229    private int mHeight;
230    private IRemoteCallback mAnimationStartedListener;
231    private IRemoteCallback mAnimationFinishedListener;
232    private ResultReceiver mTransitionReceiver;
233    private boolean mIsReturning;
234    private ArrayList<String> mSharedElementNames;
235    private Intent mResultData;
236    private int mResultCode;
237    private int mExitCoordinatorIndex;
238    private PendingIntent mUsageTimeReport;
239    private int mLaunchStackId = INVALID_STACK_ID;
240    private int mLaunchTaskId = -1;
241    private int mDockCreateMode = DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
242    private boolean mTaskOverlay;
243    private AppTransitionAnimationSpec mAnimSpecs[];
244
245    /**
246     * Create an ActivityOptions specifying a custom animation to run when
247     * the activity is displayed.
248     *
249     * @param context Who is defining this.  This is the application that the
250     * animation resources will be loaded from.
251     * @param enterResId A resource ID of the animation resource to use for
252     * the incoming activity.  Use 0 for no animation.
253     * @param exitResId A resource ID of the animation resource to use for
254     * the outgoing activity.  Use 0 for no animation.
255     * @return Returns a new ActivityOptions object that you can use to
256     * supply these options as the options Bundle when starting an activity.
257     */
258    public static ActivityOptions makeCustomAnimation(Context context,
259            int enterResId, int exitResId) {
260        return makeCustomAnimation(context, enterResId, exitResId, null, null);
261    }
262
263    /**
264     * Create an ActivityOptions specifying a custom animation to run when
265     * the activity is displayed.
266     *
267     * @param context Who is defining this.  This is the application that the
268     * animation resources will be loaded from.
269     * @param enterResId A resource ID of the animation resource to use for
270     * the incoming activity.  Use 0 for no animation.
271     * @param exitResId A resource ID of the animation resource to use for
272     * the outgoing activity.  Use 0 for no animation.
273     * @param handler If <var>listener</var> is non-null this must be a valid
274     * Handler on which to dispatch the callback; otherwise it should be null.
275     * @param listener Optional OnAnimationStartedListener to find out when the
276     * requested animation has started running.  If for some reason the animation
277     * is not executed, the callback will happen immediately.
278     * @return Returns a new ActivityOptions object that you can use to
279     * supply these options as the options Bundle when starting an activity.
280     * @hide
281     */
282    public static ActivityOptions makeCustomAnimation(Context context,
283            int enterResId, int exitResId, Handler handler, OnAnimationStartedListener listener) {
284        ActivityOptions opts = new ActivityOptions();
285        opts.mPackageName = context.getPackageName();
286        opts.mAnimationType = ANIM_CUSTOM;
287        opts.mCustomEnterResId = enterResId;
288        opts.mCustomExitResId = exitResId;
289        opts.setOnAnimationStartedListener(handler, listener);
290        return opts;
291    }
292
293    /**
294     * Creates an ActivityOptions specifying a custom animation to run in place on an existing
295     * activity.
296     *
297     * @param context Who is defining this.  This is the application that the
298     * animation resources will be loaded from.
299     * @param animId A resource ID of the animation resource to use for
300     * the incoming activity.
301     * @return Returns a new ActivityOptions object that you can use to
302     * supply these options as the options Bundle when running an in-place animation.
303     * @hide
304     */
305    public static ActivityOptions makeCustomInPlaceAnimation(Context context, int animId) {
306        if (animId == 0) {
307            throw new RuntimeException("You must specify a valid animation.");
308        }
309
310        ActivityOptions opts = new ActivityOptions();
311        opts.mPackageName = context.getPackageName();
312        opts.mAnimationType = ANIM_CUSTOM_IN_PLACE;
313        opts.mCustomInPlaceResId = animId;
314        return opts;
315    }
316
317    private void setOnAnimationStartedListener(final Handler handler,
318            final OnAnimationStartedListener listener) {
319        if (listener != null) {
320            mAnimationStartedListener = new IRemoteCallback.Stub() {
321                @Override
322                public void sendResult(Bundle data) throws RemoteException {
323                    handler.post(new Runnable() {
324                        @Override public void run() {
325                            listener.onAnimationStarted();
326                        }
327                    });
328                }
329            };
330        }
331    }
332
333    /**
334     * Callback for use with {@link ActivityOptions#makeThumbnailScaleUpAnimation}
335     * to find out when the given animation has started running.
336     * @hide
337     */
338    public interface OnAnimationStartedListener {
339        void onAnimationStarted();
340    }
341
342    private void setOnAnimationFinishedListener(final Handler handler,
343            final OnAnimationFinishedListener listener) {
344        if (listener != null) {
345            mAnimationFinishedListener = new IRemoteCallback.Stub() {
346                @Override
347                public void sendResult(Bundle data) throws RemoteException {
348                    handler.post(new Runnable() {
349                        @Override
350                        public void run() {
351                            listener.onAnimationFinished();
352                        }
353                    });
354                }
355            };
356        }
357    }
358
359    /**
360     * Callback for use with {@link ActivityOptions#makeThumbnailAspectScaleDownAnimation}
361     * to find out when the given animation has drawn its last frame.
362     * @hide
363     */
364    public interface OnAnimationFinishedListener {
365        void onAnimationFinished();
366    }
367
368    /**
369     * Create an ActivityOptions specifying an animation where the new
370     * activity is scaled from a small originating area of the screen to
371     * its final full representation.
372     *
373     * <p>If the Intent this is being used with has not set its
374     * {@link android.content.Intent#setSourceBounds Intent.setSourceBounds},
375     * those bounds will be filled in for you based on the initial
376     * bounds passed in here.
377     *
378     * @param source The View that the new activity is animating from.  This
379     * defines the coordinate space for <var>startX</var> and <var>startY</var>.
380     * @param startX The x starting location of the new activity, relative to <var>source</var>.
381     * @param startY The y starting location of the activity, relative to <var>source</var>.
382     * @param width The initial width of the new activity.
383     * @param height The initial height of the new activity.
384     * @return Returns a new ActivityOptions object that you can use to
385     * supply these options as the options Bundle when starting an activity.
386     */
387    public static ActivityOptions makeScaleUpAnimation(View source,
388            int startX, int startY, int width, int height) {
389        ActivityOptions opts = new ActivityOptions();
390        opts.mPackageName = source.getContext().getPackageName();
391        opts.mAnimationType = ANIM_SCALE_UP;
392        int[] pts = new int[2];
393        source.getLocationOnScreen(pts);
394        opts.mStartX = pts[0] + startX;
395        opts.mStartY = pts[1] + startY;
396        opts.mWidth = width;
397        opts.mHeight = height;
398        return opts;
399    }
400
401    /**
402     * Create an ActivityOptions specifying an animation where the new
403     * activity is revealed from a small originating area of the screen to
404     * its final full representation.
405     *
406     * @param source The View that the new activity is animating from.  This
407     * defines the coordinate space for <var>startX</var> and <var>startY</var>.
408     * @param startX The x starting location of the new activity, relative to <var>source</var>.
409     * @param startY The y starting location of the activity, relative to <var>source</var>.
410     * @param width The initial width of the new activity.
411     * @param height The initial height of the new activity.
412     * @return Returns a new ActivityOptions object that you can use to
413     * supply these options as the options Bundle when starting an activity.
414     */
415    public static ActivityOptions makeClipRevealAnimation(View source,
416            int startX, int startY, int width, int height) {
417        ActivityOptions opts = new ActivityOptions();
418        opts.mAnimationType = ANIM_CLIP_REVEAL;
419        int[] pts = new int[2];
420        source.getLocationOnScreen(pts);
421        opts.mStartX = pts[0] + startX;
422        opts.mStartY = pts[1] + startY;
423        opts.mWidth = width;
424        opts.mHeight = height;
425        return opts;
426    }
427
428    /**
429     * Create an ActivityOptions specifying an animation where a thumbnail
430     * is scaled from a given position to the new activity window that is
431     * being started.
432     *
433     * <p>If the Intent this is being used with has not set its
434     * {@link android.content.Intent#setSourceBounds Intent.setSourceBounds},
435     * those bounds will be filled in for you based on the initial
436     * thumbnail location and size provided here.
437     *
438     * @param source The View that this thumbnail is animating from.  This
439     * defines the coordinate space for <var>startX</var> and <var>startY</var>.
440     * @param thumbnail The bitmap that will be shown as the initial thumbnail
441     * of the animation.
442     * @param startX The x starting location of the bitmap, relative to <var>source</var>.
443     * @param startY The y starting location of the bitmap, relative to <var>source</var>.
444     * @return Returns a new ActivityOptions object that you can use to
445     * supply these options as the options Bundle when starting an activity.
446     */
447    public static ActivityOptions makeThumbnailScaleUpAnimation(View source,
448            Bitmap thumbnail, int startX, int startY) {
449        return makeThumbnailScaleUpAnimation(source, thumbnail, startX, startY, null);
450    }
451
452    /**
453     * Create an ActivityOptions specifying an animation where a thumbnail
454     * is scaled from a given position to the new activity window that is
455     * being started.
456     *
457     * @param source The View that this thumbnail is animating from.  This
458     * defines the coordinate space for <var>startX</var> and <var>startY</var>.
459     * @param thumbnail The bitmap that will be shown as the initial thumbnail
460     * of the animation.
461     * @param startX The x starting location of the bitmap, relative to <var>source</var>.
462     * @param startY The y starting location of the bitmap, relative to <var>source</var>.
463     * @param listener Optional OnAnimationStartedListener to find out when the
464     * requested animation has started running.  If for some reason the animation
465     * is not executed, the callback will happen immediately.
466     * @return Returns a new ActivityOptions object that you can use to
467     * supply these options as the options Bundle when starting an activity.
468     * @hide
469     */
470    public static ActivityOptions makeThumbnailScaleUpAnimation(View source,
471            Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener) {
472        return makeThumbnailAnimation(source, thumbnail, startX, startY, listener, true);
473    }
474
475    /**
476     * Create an ActivityOptions specifying an animation where an activity window
477     * is scaled from a given position to a thumbnail at a specified location.
478     *
479     * @param source The View that this thumbnail is animating to.  This
480     * defines the coordinate space for <var>startX</var> and <var>startY</var>.
481     * @param thumbnail The bitmap that will be shown as the final thumbnail
482     * of the animation.
483     * @param startX The x end location of the bitmap, relative to <var>source</var>.
484     * @param startY The y end location of the bitmap, relative to <var>source</var>.
485     * @param listener Optional OnAnimationStartedListener to find out when the
486     * requested animation has started running.  If for some reason the animation
487     * is not executed, the callback will happen immediately.
488     * @return Returns a new ActivityOptions object that you can use to
489     * supply these options as the options Bundle when starting an activity.
490     * @hide
491     */
492    public static ActivityOptions makeThumbnailScaleDownAnimation(View source,
493            Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener) {
494        return makeThumbnailAnimation(source, thumbnail, startX, startY, listener, false);
495    }
496
497    private static ActivityOptions makeThumbnailAnimation(View source,
498            Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener,
499            boolean scaleUp) {
500        ActivityOptions opts = new ActivityOptions();
501        opts.mPackageName = source.getContext().getPackageName();
502        opts.mAnimationType = scaleUp ? ANIM_THUMBNAIL_SCALE_UP : ANIM_THUMBNAIL_SCALE_DOWN;
503        opts.mThumbnail = thumbnail;
504        int[] pts = new int[2];
505        source.getLocationOnScreen(pts);
506        opts.mStartX = pts[0] + startX;
507        opts.mStartY = pts[1] + startY;
508        opts.setOnAnimationStartedListener(source.getHandler(), listener);
509        return opts;
510    }
511
512    /**
513     * Create an ActivityOptions specifying an animation where the new activity
514     * window and a thumbnail is aspect-scaled to a new location.
515     *
516     * @param source The View that this thumbnail is animating from.  This
517     * defines the coordinate space for <var>startX</var> and <var>startY</var>.
518     * @param thumbnail The bitmap that will be shown as the initial thumbnail
519     * of the animation.
520     * @param startX The x starting location of the bitmap, relative to <var>source</var>.
521     * @param startY The y starting location of the bitmap, relative to <var>source</var>.
522     * @param handler If <var>listener</var> is non-null this must be a valid
523     * Handler on which to dispatch the callback; otherwise it should be null.
524     * @param listener Optional OnAnimationStartedListener to find out when the
525     * requested animation has started running.  If for some reason the animation
526     * is not executed, the callback will happen immediately.
527     * @return Returns a new ActivityOptions object that you can use to
528     * supply these options as the options Bundle when starting an activity.
529     * @hide
530     */
531    public static ActivityOptions makeThumbnailAspectScaleUpAnimation(View source,
532            Bitmap thumbnail, int startX, int startY, int targetWidth, int targetHeight,
533            Handler handler, OnAnimationStartedListener listener) {
534        return makeAspectScaledThumbnailAnimation(source, thumbnail, startX, startY,
535                targetWidth, targetHeight, handler, listener, true);
536    }
537
538    /**
539     * Create an ActivityOptions specifying an animation where the new activity
540     * window and a thumbnail is aspect-scaled to a new location.
541     *
542     * @param source The View that this thumbnail is animating to.  This
543     * defines the coordinate space for <var>startX</var> and <var>startY</var>.
544     * @param thumbnail The bitmap that will be shown as the final thumbnail
545     * of the animation.
546     * @param startX The x end location of the bitmap, relative to <var>source</var>.
547     * @param startY The y end location of the bitmap, relative to <var>source</var>.
548     * @param handler If <var>listener</var> is non-null this must be a valid
549     * Handler on which to dispatch the callback; otherwise it should be null.
550     * @param listener Optional OnAnimationStartedListener to find out when the
551     * requested animation has started running.  If for some reason the animation
552     * is not executed, the callback will happen immediately.
553     * @return Returns a new ActivityOptions object that you can use to
554     * supply these options as the options Bundle when starting an activity.
555     * @hide
556     */
557    public static ActivityOptions makeThumbnailAspectScaleDownAnimation(View source,
558            Bitmap thumbnail, int startX, int startY, int targetWidth, int targetHeight,
559            Handler handler, OnAnimationStartedListener listener) {
560        return makeAspectScaledThumbnailAnimation(source, thumbnail, startX, startY,
561                targetWidth, targetHeight, handler, listener, false);
562    }
563
564    private static ActivityOptions makeAspectScaledThumbnailAnimation(View source, Bitmap thumbnail,
565            int startX, int startY, int targetWidth, int targetHeight,
566            Handler handler, OnAnimationStartedListener listener, boolean scaleUp) {
567        ActivityOptions opts = new ActivityOptions();
568        opts.mPackageName = source.getContext().getPackageName();
569        opts.mAnimationType = scaleUp ? ANIM_THUMBNAIL_ASPECT_SCALE_UP :
570                ANIM_THUMBNAIL_ASPECT_SCALE_DOWN;
571        opts.mThumbnail = thumbnail;
572        int[] pts = new int[2];
573        source.getLocationOnScreen(pts);
574        opts.mStartX = pts[0] + startX;
575        opts.mStartY = pts[1] + startY;
576        opts.mWidth = targetWidth;
577        opts.mHeight = targetHeight;
578        opts.setOnAnimationStartedListener(handler, listener);
579        return opts;
580    }
581
582    /** @hide */
583    public static ActivityOptions makeThumbnailAspectScaleDownAnimation(View source,
584            AppTransitionAnimationSpec[] specs, Handler handler,
585            OnAnimationStartedListener onAnimationStartedListener,
586            OnAnimationFinishedListener onAnimationFinishedListener) {
587        ActivityOptions opts = new ActivityOptions();
588        opts.mPackageName = source.getContext().getPackageName();
589        opts.mAnimationType = ANIM_THUMBNAIL_ASPECT_SCALE_DOWN;
590        opts.mAnimSpecs = specs;
591        opts.setOnAnimationStartedListener(handler, onAnimationStartedListener);
592        opts.setOnAnimationFinishedListener(handler, onAnimationFinishedListener);
593        return opts;
594    }
595
596    /**
597     * Create an ActivityOptions to transition between Activities using cross-Activity scene
598     * animations. This method carries the position of one shared element to the started Activity.
599     * The position of <code>sharedElement</code> will be used as the epicenter for the
600     * exit Transition. The position of the shared element in the launched Activity will be the
601     * epicenter of its entering Transition.
602     *
603     * <p>This requires {@link android.view.Window#FEATURE_ACTIVITY_TRANSITIONS} to be
604     * enabled on the calling Activity to cause an exit transition. The same must be in
605     * the called Activity to get an entering transition.</p>
606     * @param activity The Activity whose window contains the shared elements.
607     * @param sharedElement The View to transition to the started Activity.
608     * @param sharedElementName The shared element name as used in the target Activity. This
609     *                          must not be null.
610     * @return Returns a new ActivityOptions object that you can use to
611     *         supply these options as the options Bundle when starting an activity.
612     * @see android.transition.Transition#setEpicenterCallback(
613     *          android.transition.Transition.EpicenterCallback)
614     */
615    public static ActivityOptions makeSceneTransitionAnimation(Activity activity,
616            View sharedElement, String sharedElementName) {
617        return makeSceneTransitionAnimation(activity, Pair.create(sharedElement, sharedElementName));
618    }
619
620    /**
621     * Create an ActivityOptions to transition between Activities using cross-Activity scene
622     * animations. This method carries the position of multiple shared elements to the started
623     * Activity. The position of the first element in sharedElements
624     * will be used as the epicenter for the exit Transition. The position of the associated
625     * shared element in the launched Activity will be the epicenter of its entering Transition.
626     *
627     * <p>This requires {@link android.view.Window#FEATURE_ACTIVITY_TRANSITIONS} to be
628     * enabled on the calling Activity to cause an exit transition. The same must be in
629     * the called Activity to get an entering transition.</p>
630     * @param activity The Activity whose window contains the shared elements.
631     * @param sharedElements The names of the shared elements to transfer to the called
632     *                       Activity and their associated Views. The Views must each have
633     *                       a unique shared element name.
634     * @return Returns a new ActivityOptions object that you can use to
635     *         supply these options as the options Bundle when starting an activity.
636     * @see android.transition.Transition#setEpicenterCallback(
637     *          android.transition.Transition.EpicenterCallback)
638     */
639    @SafeVarargs
640    public static ActivityOptions makeSceneTransitionAnimation(Activity activity,
641            Pair<View, String>... sharedElements) {
642        ActivityOptions opts = new ActivityOptions();
643        if (!activity.getWindow().hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)) {
644            opts.mAnimationType = ANIM_DEFAULT;
645            return opts;
646        }
647        opts.mAnimationType = ANIM_SCENE_TRANSITION;
648
649        ArrayList<String> names = new ArrayList<String>();
650        ArrayList<View> views = new ArrayList<View>();
651
652        if (sharedElements != null) {
653            for (int i = 0; i < sharedElements.length; i++) {
654                Pair<View, String> sharedElement = sharedElements[i];
655                String sharedElementName = sharedElement.second;
656                if (sharedElementName == null) {
657                    throw new IllegalArgumentException("Shared element name must not be null");
658                }
659                names.add(sharedElementName);
660                View view = sharedElement.first;
661                if (view == null) {
662                    throw new IllegalArgumentException("Shared element must not be null");
663                }
664                views.add(sharedElement.first);
665            }
666        }
667
668        ExitTransitionCoordinator exit = new ExitTransitionCoordinator(activity, names, names,
669                views, false);
670        opts.mTransitionReceiver = exit;
671        opts.mSharedElementNames = names;
672        opts.mIsReturning = false;
673        opts.mExitCoordinatorIndex =
674                activity.mActivityTransitionState.addExitTransitionCoordinator(exit);
675        return opts;
676    }
677
678    /** @hide */
679    public static ActivityOptions makeSceneTransitionAnimation(Activity activity,
680            ExitTransitionCoordinator exitCoordinator, ArrayList<String> sharedElementNames,
681            int resultCode, Intent resultData) {
682        ActivityOptions opts = new ActivityOptions();
683        opts.mAnimationType = ANIM_SCENE_TRANSITION;
684        opts.mSharedElementNames = sharedElementNames;
685        opts.mTransitionReceiver = exitCoordinator;
686        opts.mIsReturning = true;
687        opts.mResultCode = resultCode;
688        opts.mResultData = resultData;
689        opts.mExitCoordinatorIndex =
690                activity.mActivityTransitionState.addExitTransitionCoordinator(exitCoordinator);
691        return opts;
692    }
693
694    /**
695     * If set along with Intent.FLAG_ACTIVITY_NEW_DOCUMENT then the task being launched will not be
696     * presented to the user but will instead be only available through the recents task list.
697     * In addition, the new task wil be affiliated with the launching activity's task.
698     * Affiliated tasks are grouped together in the recents task list.
699     *
700     * <p>This behavior is not supported for activities with {@link
701     * android.R.styleable#AndroidManifestActivity_launchMode launchMode} values of
702     * <code>singleInstance</code> or <code>singleTask</code>.
703     */
704    public static ActivityOptions makeTaskLaunchBehind() {
705        final ActivityOptions opts = new ActivityOptions();
706        opts.mAnimationType = ANIM_LAUNCH_TASK_BEHIND;
707        return opts;
708    }
709
710    /**
711     * Create a basic ActivityOptions that has no special animation associated with it.
712     * Other options can still be set.
713     */
714    public static ActivityOptions makeBasic() {
715        final ActivityOptions opts = new ActivityOptions();
716        return opts;
717    }
718
719    /** @hide */
720    public boolean getLaunchTaskBehind() {
721        return mAnimationType == ANIM_LAUNCH_TASK_BEHIND;
722    }
723
724    private ActivityOptions() {
725    }
726
727    /** @hide */
728    public ActivityOptions(Bundle opts) {
729        // If the remote side sent us bad parcelables, they won't get the
730        // results they want, which is their loss.
731        opts.setDefusable(true);
732
733        mPackageName = opts.getString(KEY_PACKAGE_NAME);
734        try {
735            mUsageTimeReport = opts.getParcelable(KEY_USAGE_TIME_REPORT);
736        } catch (RuntimeException e) {
737            Slog.w(TAG, e);
738        }
739        mLaunchBounds = opts.getParcelable(KEY_LAUNCH_BOUNDS);
740        mAnimationType = opts.getInt(KEY_ANIM_TYPE);
741        switch (mAnimationType) {
742            case ANIM_CUSTOM:
743                mCustomEnterResId = opts.getInt(KEY_ANIM_ENTER_RES_ID, 0);
744                mCustomExitResId = opts.getInt(KEY_ANIM_EXIT_RES_ID, 0);
745                mAnimationStartedListener = IRemoteCallback.Stub.asInterface(
746                        opts.getBinder(KEY_ANIM_START_LISTENER));
747                break;
748
749            case ANIM_CUSTOM_IN_PLACE:
750                mCustomInPlaceResId = opts.getInt(KEY_ANIM_IN_PLACE_RES_ID, 0);
751                break;
752
753            case ANIM_SCALE_UP:
754            case ANIM_CLIP_REVEAL:
755                mStartX = opts.getInt(KEY_ANIM_START_X, 0);
756                mStartY = opts.getInt(KEY_ANIM_START_Y, 0);
757                mWidth = opts.getInt(KEY_ANIM_WIDTH, 0);
758                mHeight = opts.getInt(KEY_ANIM_HEIGHT, 0);
759                break;
760
761            case ANIM_THUMBNAIL_SCALE_UP:
762            case ANIM_THUMBNAIL_SCALE_DOWN:
763            case ANIM_THUMBNAIL_ASPECT_SCALE_UP:
764            case ANIM_THUMBNAIL_ASPECT_SCALE_DOWN:
765                mThumbnail = (Bitmap) opts.getParcelable(KEY_ANIM_THUMBNAIL);
766                mStartX = opts.getInt(KEY_ANIM_START_X, 0);
767                mStartY = opts.getInt(KEY_ANIM_START_Y, 0);
768                mWidth = opts.getInt(KEY_ANIM_WIDTH, 0);
769                mHeight = opts.getInt(KEY_ANIM_HEIGHT, 0);
770                mAnimationStartedListener = IRemoteCallback.Stub.asInterface(
771                        opts.getBinder(KEY_ANIM_START_LISTENER));
772                break;
773
774            case ANIM_SCENE_TRANSITION:
775                mTransitionReceiver = opts.getParcelable(KEY_TRANSITION_COMPLETE_LISTENER);
776                mIsReturning = opts.getBoolean(KEY_TRANSITION_IS_RETURNING, false);
777                mSharedElementNames = opts.getStringArrayList(KEY_TRANSITION_SHARED_ELEMENTS);
778                mResultData = opts.getParcelable(KEY_RESULT_DATA);
779                mResultCode = opts.getInt(KEY_RESULT_CODE);
780                mExitCoordinatorIndex = opts.getInt(KEY_EXIT_COORDINATOR_INDEX);
781                break;
782        }
783        mLaunchStackId = opts.getInt(KEY_LAUNCH_STACK_ID, INVALID_STACK_ID);
784        mLaunchTaskId = opts.getInt(KEY_LAUNCH_TASK_ID, -1);
785        mTaskOverlay = opts.getBoolean(KEY_TASK_OVERLAY, false);
786        mDockCreateMode = opts.getInt(KEY_DOCK_CREATE_MODE, DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT);
787        if (opts.containsKey(KEY_ANIM_SPECS)) {
788            Parcelable[] specs = opts.getParcelableArray(KEY_ANIM_SPECS);
789            mAnimSpecs = new AppTransitionAnimationSpec[specs.length];
790            for (int i = specs.length - 1; i >= 0; i--) {
791                mAnimSpecs[i] = (AppTransitionAnimationSpec) specs[i];
792            }
793        }
794        if (opts.containsKey(KEY_ANIMATION_FINISHED_LISTENER)) {
795            mAnimationFinishedListener = IRemoteCallback.Stub.asInterface(
796                    opts.getBinder(KEY_ANIMATION_FINISHED_LISTENER));
797        }
798    }
799
800    /**
801     * Sets the bounds (window size) that the activity should be launched in.
802     * Rect position should be provided in pixels and in screen coordinates.
803     * Set to null explicitly for fullscreen.
804     * <p>
805     * <strong>NOTE:<strong/> This value is ignored on devices that don't have
806     * {@link android.content.pm.PackageManager#FEATURE_FREEFORM_WINDOW_MANAGEMENT} or
807     * {@link android.content.pm.PackageManager#FEATURE_PICTURE_IN_PICTURE} enabled.
808     * @param screenSpacePixelRect Launch bounds to use for the activity or null for fullscreen.
809     */
810    public ActivityOptions setLaunchBounds(@Nullable Rect screenSpacePixelRect) {
811        mLaunchBounds = screenSpacePixelRect != null ? new Rect(screenSpacePixelRect) : null;
812        return this;
813    }
814
815    /** @hide */
816    public String getPackageName() {
817        return mPackageName;
818    }
819
820    /**
821     * Returns the bounds that should be used to launch the activity.
822     * @see #setLaunchBounds(Rect)
823     * @return Bounds used to launch the activity.
824     */
825    @Nullable
826    public Rect getLaunchBounds() {
827        return mLaunchBounds;
828    }
829
830    /** @hide */
831    public int getAnimationType() {
832        return mAnimationType;
833    }
834
835    /** @hide */
836    public int getCustomEnterResId() {
837        return mCustomEnterResId;
838    }
839
840    /** @hide */
841    public int getCustomExitResId() {
842        return mCustomExitResId;
843    }
844
845    /** @hide */
846    public int getCustomInPlaceResId() {
847        return mCustomInPlaceResId;
848    }
849
850    /** @hide */
851    public Bitmap getThumbnail() {
852        return mThumbnail;
853    }
854
855    /** @hide */
856    public int getStartX() {
857        return mStartX;
858    }
859
860    /** @hide */
861    public int getStartY() {
862        return mStartY;
863    }
864
865    /** @hide */
866    public int getWidth() {
867        return mWidth;
868    }
869
870    /** @hide */
871    public int getHeight() {
872        return mHeight;
873    }
874
875    /** @hide */
876    public IRemoteCallback getOnAnimationStartListener() {
877        return mAnimationStartedListener;
878    }
879
880    /** @hide */
881    public IRemoteCallback getAnimationFinishedListener() {
882        return mAnimationFinishedListener;
883    }
884
885    /** @hide */
886    public int getExitCoordinatorKey() { return mExitCoordinatorIndex; }
887
888    /** @hide */
889    public void abort() {
890        if (mAnimationStartedListener != null) {
891            try {
892                mAnimationStartedListener.sendResult(null);
893            } catch (RemoteException e) {
894            }
895        }
896    }
897
898    /** @hide */
899    public boolean isReturning() {
900        return mIsReturning;
901    }
902
903    /** @hide */
904    public ArrayList<String> getSharedElementNames() {
905        return mSharedElementNames;
906    }
907
908    /** @hide */
909    public ResultReceiver getResultReceiver() { return mTransitionReceiver; }
910
911    /** @hide */
912    public int getResultCode() { return mResultCode; }
913
914    /** @hide */
915    public Intent getResultData() { return mResultData; }
916
917    /** @hide */
918    public PendingIntent getUsageTimeReport() {
919        return mUsageTimeReport;
920    }
921
922    /** @hide */
923    public AppTransitionAnimationSpec[] getAnimSpecs() { return mAnimSpecs; }
924
925    /** @hide */
926    public static ActivityOptions fromBundle(Bundle bOptions) {
927        return bOptions != null ? new ActivityOptions(bOptions) : null;
928    }
929
930    /** @hide */
931    public static void abort(ActivityOptions options) {
932        if (options != null) {
933            options.abort();
934        }
935    }
936
937    /** @hide */
938    public int getLaunchStackId() {
939        return mLaunchStackId;
940    }
941
942    /** @hide */
943    @TestApi
944    public void setLaunchStackId(int launchStackId) {
945        mLaunchStackId = launchStackId;
946    }
947
948    /**
949     * Sets the task the activity will be launched in.
950     * @hide
951     */
952    public void setLaunchTaskId(int taskId) {
953        mLaunchTaskId = taskId;
954    }
955
956    /**
957     * @hide
958     */
959    public int getLaunchTaskId() {
960        return mLaunchTaskId;
961    }
962
963    /**
964     * Set's whether the activity launched with this option should be a task overlay. That is the
965     * activity will always be the top activity of the task and doesn't cause the task to be moved
966     * to the front when it is added.
967     * @hide
968     */
969    public void setTaskOverlay(boolean taskOverlay) {
970        mTaskOverlay = taskOverlay;
971    }
972
973    /**
974     * @hide
975     */
976    public boolean getTaskOverlay() {
977        return mTaskOverlay;
978    }
979
980    /** @hide */
981    public int getDockCreateMode() {
982        return mDockCreateMode;
983    }
984
985    /** @hide */
986    public void setDockCreateMode(int dockCreateMode) {
987        mDockCreateMode = dockCreateMode;
988    }
989
990    /**
991     * Update the current values in this ActivityOptions from those supplied
992     * in <var>otherOptions</var>.  Any values
993     * defined in <var>otherOptions</var> replace those in the base options.
994     */
995    public void update(ActivityOptions otherOptions) {
996        if (otherOptions.mPackageName != null) {
997            mPackageName = otherOptions.mPackageName;
998        }
999        mUsageTimeReport = otherOptions.mUsageTimeReport;
1000        mTransitionReceiver = null;
1001        mSharedElementNames = null;
1002        mIsReturning = false;
1003        mResultData = null;
1004        mResultCode = 0;
1005        mExitCoordinatorIndex = 0;
1006        mAnimationType = otherOptions.mAnimationType;
1007        switch (otherOptions.mAnimationType) {
1008            case ANIM_CUSTOM:
1009                mCustomEnterResId = otherOptions.mCustomEnterResId;
1010                mCustomExitResId = otherOptions.mCustomExitResId;
1011                mThumbnail = null;
1012                if (mAnimationStartedListener != null) {
1013                    try {
1014                        mAnimationStartedListener.sendResult(null);
1015                    } catch (RemoteException e) {
1016                    }
1017                }
1018                mAnimationStartedListener = otherOptions.mAnimationStartedListener;
1019                break;
1020            case ANIM_CUSTOM_IN_PLACE:
1021                mCustomInPlaceResId = otherOptions.mCustomInPlaceResId;
1022                break;
1023            case ANIM_SCALE_UP:
1024                mStartX = otherOptions.mStartX;
1025                mStartY = otherOptions.mStartY;
1026                mWidth = otherOptions.mWidth;
1027                mHeight = otherOptions.mHeight;
1028                if (mAnimationStartedListener != null) {
1029                    try {
1030                        mAnimationStartedListener.sendResult(null);
1031                    } catch (RemoteException e) {
1032                    }
1033                }
1034                mAnimationStartedListener = null;
1035                break;
1036            case ANIM_THUMBNAIL_SCALE_UP:
1037            case ANIM_THUMBNAIL_SCALE_DOWN:
1038            case ANIM_THUMBNAIL_ASPECT_SCALE_UP:
1039            case ANIM_THUMBNAIL_ASPECT_SCALE_DOWN:
1040                mThumbnail = otherOptions.mThumbnail;
1041                mStartX = otherOptions.mStartX;
1042                mStartY = otherOptions.mStartY;
1043                mWidth = otherOptions.mWidth;
1044                mHeight = otherOptions.mHeight;
1045                if (mAnimationStartedListener != null) {
1046                    try {
1047                        mAnimationStartedListener.sendResult(null);
1048                    } catch (RemoteException e) {
1049                    }
1050                }
1051                mAnimationStartedListener = otherOptions.mAnimationStartedListener;
1052                break;
1053            case ANIM_SCENE_TRANSITION:
1054                mTransitionReceiver = otherOptions.mTransitionReceiver;
1055                mSharedElementNames = otherOptions.mSharedElementNames;
1056                mIsReturning = otherOptions.mIsReturning;
1057                mThumbnail = null;
1058                mAnimationStartedListener = null;
1059                mResultData = otherOptions.mResultData;
1060                mResultCode = otherOptions.mResultCode;
1061                mExitCoordinatorIndex = otherOptions.mExitCoordinatorIndex;
1062                break;
1063        }
1064        mAnimSpecs = otherOptions.mAnimSpecs;
1065        mAnimationFinishedListener = otherOptions.mAnimationFinishedListener;
1066    }
1067
1068    /**
1069     * Returns the created options as a Bundle, which can be passed to
1070     * {@link android.content.Context#startActivity(android.content.Intent, android.os.Bundle)
1071     * Context.startActivity(Intent, Bundle)} and related methods.
1072     * Note that the returned Bundle is still owned by the ActivityOptions
1073     * object; you must not modify it, but can supply it to the startActivity
1074     * methods that take an options Bundle.
1075     */
1076    public Bundle toBundle() {
1077        if (mAnimationType == ANIM_DEFAULT) {
1078            return null;
1079        }
1080        Bundle b = new Bundle();
1081        if (mPackageName != null) {
1082            b.putString(KEY_PACKAGE_NAME, mPackageName);
1083        }
1084        if (mLaunchBounds != null) {
1085            b.putParcelable(KEY_LAUNCH_BOUNDS, mLaunchBounds);
1086        }
1087        b.putInt(KEY_ANIM_TYPE, mAnimationType);
1088        if (mUsageTimeReport != null) {
1089            b.putParcelable(KEY_USAGE_TIME_REPORT, mUsageTimeReport);
1090        }
1091        switch (mAnimationType) {
1092            case ANIM_CUSTOM:
1093                b.putInt(KEY_ANIM_ENTER_RES_ID, mCustomEnterResId);
1094                b.putInt(KEY_ANIM_EXIT_RES_ID, mCustomExitResId);
1095                b.putBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
1096                        != null ? mAnimationStartedListener.asBinder() : null);
1097                break;
1098            case ANIM_CUSTOM_IN_PLACE:
1099                b.putInt(KEY_ANIM_IN_PLACE_RES_ID, mCustomInPlaceResId);
1100                break;
1101            case ANIM_SCALE_UP:
1102            case ANIM_CLIP_REVEAL:
1103                b.putInt(KEY_ANIM_START_X, mStartX);
1104                b.putInt(KEY_ANIM_START_Y, mStartY);
1105                b.putInt(KEY_ANIM_WIDTH, mWidth);
1106                b.putInt(KEY_ANIM_HEIGHT, mHeight);
1107                break;
1108            case ANIM_THUMBNAIL_SCALE_UP:
1109            case ANIM_THUMBNAIL_SCALE_DOWN:
1110            case ANIM_THUMBNAIL_ASPECT_SCALE_UP:
1111            case ANIM_THUMBNAIL_ASPECT_SCALE_DOWN:
1112                b.putParcelable(KEY_ANIM_THUMBNAIL, mThumbnail);
1113                b.putInt(KEY_ANIM_START_X, mStartX);
1114                b.putInt(KEY_ANIM_START_Y, mStartY);
1115                b.putInt(KEY_ANIM_WIDTH, mWidth);
1116                b.putInt(KEY_ANIM_HEIGHT, mHeight);
1117                b.putBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
1118                        != null ? mAnimationStartedListener.asBinder() : null);
1119                break;
1120            case ANIM_SCENE_TRANSITION:
1121                if (mTransitionReceiver != null) {
1122                    b.putParcelable(KEY_TRANSITION_COMPLETE_LISTENER, mTransitionReceiver);
1123                }
1124                b.putBoolean(KEY_TRANSITION_IS_RETURNING, mIsReturning);
1125                b.putStringArrayList(KEY_TRANSITION_SHARED_ELEMENTS, mSharedElementNames);
1126                b.putParcelable(KEY_RESULT_DATA, mResultData);
1127                b.putInt(KEY_RESULT_CODE, mResultCode);
1128                b.putInt(KEY_EXIT_COORDINATOR_INDEX, mExitCoordinatorIndex);
1129                break;
1130        }
1131        b.putInt(KEY_LAUNCH_STACK_ID, mLaunchStackId);
1132        b.putInt(KEY_LAUNCH_TASK_ID, mLaunchTaskId);
1133        b.putBoolean(KEY_TASK_OVERLAY, mTaskOverlay);
1134        b.putInt(KEY_DOCK_CREATE_MODE, mDockCreateMode);
1135        if (mAnimSpecs != null) {
1136            b.putParcelableArray(KEY_ANIM_SPECS, mAnimSpecs);
1137        }
1138        if (mAnimationFinishedListener != null) {
1139            b.putBinder(KEY_ANIMATION_FINISHED_LISTENER, mAnimationFinishedListener.asBinder());
1140        }
1141
1142        return b;
1143    }
1144
1145    /**
1146     * Ask the the system track that time the user spends in the app being launched, and
1147     * report it back once done.  The report will be sent to the given receiver, with
1148     * the extras {@link #EXTRA_USAGE_TIME_REPORT} and {@link #EXTRA_USAGE_TIME_REPORT_PACKAGES}
1149     * filled in.
1150     *
1151     * <p>The time interval tracked is from launching this activity until the user leaves
1152     * that activity's flow.  They are considered to stay in the flow as long as
1153     * new activities are being launched or returned to from the original flow,
1154     * even if this crosses package or task boundaries.  For example, if the originator
1155     * starts an activity to view an image, and while there the user selects to share,
1156     * which launches their email app in a new task, and they complete the share, the
1157     * time during that entire operation will be included until they finally hit back from
1158     * the original image viewer activity.</p>
1159     *
1160     * <p>The user is considered to complete a flow once they switch to another
1161     * activity that is not part of the tracked flow.  This may happen, for example, by
1162     * using the notification shade, launcher, or recents to launch or switch to another
1163     * app.  Simply going in to these navigation elements does not break the flow (although
1164     * the launcher and recents stops time tracking of the session); it is the act of
1165     * going somewhere else that completes the tracking.</p>
1166     *
1167     * @param receiver A broadcast receiver that willl receive the report.
1168     */
1169    public void requestUsageTimeReport(PendingIntent receiver) {
1170        mUsageTimeReport = receiver;
1171    }
1172
1173    /**
1174     * Return the filtered options only meant to be seen by the target activity itself
1175     * @hide
1176     */
1177    public ActivityOptions forTargetActivity() {
1178        if (mAnimationType == ANIM_SCENE_TRANSITION) {
1179            final ActivityOptions result = new ActivityOptions();
1180            result.update(this);
1181            return result;
1182        }
1183
1184        return null;
1185    }
1186
1187    /** @hide */
1188    @Override
1189    public String toString() {
1190        return "ActivityOptions(" + hashCode() + "), mPackageName=" + mPackageName
1191                + ", mAnimationType=" + mAnimationType + ", mStartX=" + mStartX + ", mStartY="
1192                + mStartY + ", mWidth=" + mWidth + ", mHeight=" + mHeight;
1193    }
1194}
1195