ActivityOptions.java revision 206e30cd93afe3eb72ec94178324417db5424ed2
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 android.animation.Animator;
20import android.content.Context;
21import android.graphics.Bitmap;
22import android.os.Bundle;
23import android.os.Handler;
24import android.os.IRemoteCallback;
25import android.os.RemoteException;
26import android.transition.Transition;
27import android.util.ArrayMap;
28import android.util.Log;
29import android.view.View;
30
31import java.util.ArrayList;
32import java.util.Map;
33
34/**
35 * Helper class for building an options Bundle that can be used with
36 * {@link android.content.Context#startActivity(android.content.Intent, android.os.Bundle)
37 * Context.startActivity(Intent, Bundle)} and related methods.
38 */
39public class ActivityOptions {
40    private static final String TAG = "ActivityOptions";
41
42    /**
43     * The package name that created the options.
44     * @hide
45     */
46    public static final String KEY_PACKAGE_NAME = "android:packageName";
47
48    /**
49     * Type of animation that arguments specify.
50     * @hide
51     */
52    public static final String KEY_ANIM_TYPE = "android:animType";
53
54    /**
55     * Custom enter animation resource ID.
56     * @hide
57     */
58    public static final String KEY_ANIM_ENTER_RES_ID = "android:animEnterRes";
59
60    /**
61     * Custom exit animation resource ID.
62     * @hide
63     */
64    public static final String KEY_ANIM_EXIT_RES_ID = "android:animExitRes";
65
66    /**
67     * Bitmap for thumbnail animation.
68     * @hide
69     */
70    public static final String KEY_ANIM_THUMBNAIL = "android:animThumbnail";
71
72    /**
73     * Start X position of thumbnail animation.
74     * @hide
75     */
76    public static final String KEY_ANIM_START_X = "android:animStartX";
77
78    /**
79     * Start Y position of thumbnail animation.
80     * @hide
81     */
82    public static final String KEY_ANIM_START_Y = "android:animStartY";
83
84    /**
85     * Initial width of the animation.
86     * @hide
87     */
88    public static final String KEY_ANIM_START_WIDTH = "android:animStartWidth";
89
90    /**
91     * Initial height of the animation.
92     * @hide
93     */
94    public static final String KEY_ANIM_START_HEIGHT = "android:animStartHeight";
95
96    /**
97     * Callback for when animation is started.
98     * @hide
99     */
100    public static final String KEY_ANIM_START_LISTENER = "android:animStartListener";
101
102    /**
103     * Arguments for the scene transition about to begin.
104     * @hide
105     */
106    public static final String KEY_SCENE_TRANSITION_ARGS = "android:sceneTransitionArgs";
107
108    /**
109     * For Activity transitions, the calling Activity's TransitionListener used to
110     * notify the called Activity when the shared element and the exit transitions
111     * complete.
112     */
113    private static final String KEY_TRANSITION_COMPLETE_LISTENER
114            = "android:transitionCompleteListener";
115
116    /**
117     * For Activity transitions, the called Activity's listener to receive calls
118     * when transitions complete.
119     */
120    private static final String KEY_TRANSITION_TARGET_LISTENER = "android:transitionTargetListener";
121
122    /**
123     * The shared element's texture ID (TODO: not used yet).
124     */
125    private static final String KEY_SHARED_ELEMENT_TEXTURE_ID = "android:sharedElementTextureId";
126
127    /** @hide */
128    public static final int ANIM_NONE = 0;
129    /** @hide */
130    public static final int ANIM_CUSTOM = 1;
131    /** @hide */
132    public static final int ANIM_SCALE_UP = 2;
133    /** @hide */
134    public static final int ANIM_THUMBNAIL_SCALE_UP = 3;
135    /** @hide */
136    public static final int ANIM_THUMBNAIL_SCALE_DOWN = 4;
137    /** @hide */
138    public static final int ANIM_SCENE_TRANSITION = 5;
139
140    private String mPackageName;
141    private int mAnimationType = ANIM_NONE;
142    private int mCustomEnterResId;
143    private int mCustomExitResId;
144    private Bitmap mThumbnail;
145    private int mStartX;
146    private int mStartY;
147    private int mStartWidth;
148    private int mStartHeight;
149    private Bundle mTransitionArgs;
150    private IRemoteCallback mAnimationStartedListener;
151    private IRemoteCallback mTransitionCompleteListener;
152
153    /**
154     * Create an ActivityOptions specifying a custom animation to run when
155     * the activity is displayed.
156     *
157     * @param context Who is defining this.  This is the application that the
158     * animation resources will be loaded from.
159     * @param enterResId A resource ID of the animation resource to use for
160     * the incoming activity.  Use 0 for no animation.
161     * @param exitResId A resource ID of the animation resource to use for
162     * the outgoing activity.  Use 0 for no animation.
163     * @return Returns a new ActivityOptions object that you can use to
164     * supply these options as the options Bundle when starting an activity.
165     */
166    public static ActivityOptions makeCustomAnimation(Context context,
167            int enterResId, int exitResId) {
168        return makeCustomAnimation(context, enterResId, exitResId, null, null);
169    }
170
171    /**
172     * Create an ActivityOptions specifying a custom animation to run when
173     * the activity is displayed.
174     *
175     * @param context Who is defining this.  This is the application that the
176     * animation resources will be loaded from.
177     * @param enterResId A resource ID of the animation resource to use for
178     * the incoming activity.  Use 0 for no animation.
179     * @param exitResId A resource ID of the animation resource to use for
180     * the outgoing activity.  Use 0 for no animation.
181     * @param handler If <var>listener</var> is non-null this must be a valid
182     * Handler on which to dispatch the callback; otherwise it should be null.
183     * @param listener Optional OnAnimationStartedListener to find out when the
184     * requested animation has started running.  If for some reason the animation
185     * is not executed, the callback will happen immediately.
186     * @return Returns a new ActivityOptions object that you can use to
187     * supply these options as the options Bundle when starting an activity.
188     * @hide
189     */
190    public static ActivityOptions makeCustomAnimation(Context context,
191            int enterResId, int exitResId, Handler handler, OnAnimationStartedListener listener) {
192        ActivityOptions opts = new ActivityOptions();
193        opts.mPackageName = context.getPackageName();
194        opts.mAnimationType = ANIM_CUSTOM;
195        opts.mCustomEnterResId = enterResId;
196        opts.mCustomExitResId = exitResId;
197        opts.setOnAnimationStartedListener(handler, listener);
198        return opts;
199    }
200
201    private void setOnAnimationStartedListener(Handler handler,
202            OnAnimationStartedListener listener) {
203        if (listener != null) {
204            final Handler h = handler;
205            final OnAnimationStartedListener finalListener = listener;
206            mAnimationStartedListener = new IRemoteCallback.Stub() {
207                @Override public void sendResult(Bundle data) throws RemoteException {
208                    h.post(new Runnable() {
209                        @Override public void run() {
210                            finalListener.onAnimationStarted();
211                        }
212                    });
213                }
214            };
215        }
216    }
217
218    /**
219     * Callback for use with {@link ActivityOptions#makeThumbnailScaleUpAnimation}
220     * to find out when the given animation has started running.
221     * @hide
222     */
223    public interface OnAnimationStartedListener {
224        void onAnimationStarted();
225    }
226
227    /** @hide */
228    public interface ActivityTransitionTarget {
229        void sharedElementTransitionComplete();
230        void exitTransitionComplete();
231    }
232
233    /**
234     * Create an ActivityOptions specifying an animation where the new
235     * activity is scaled from a small originating area of the screen to
236     * its final full representation.
237     *
238     * <p>If the Intent this is being used with has not set its
239     * {@link android.content.Intent#setSourceBounds Intent.setSourceBounds},
240     * those bounds will be filled in for you based on the initial
241     * bounds passed in here.
242     *
243     * @param source The View that the new activity is animating from.  This
244     * defines the coordinate space for <var>startX</var> and <var>startY</var>.
245     * @param startX The x starting location of the new activity, relative to <var>source</var>.
246     * @param startY The y starting location of the activity, relative to <var>source</var>.
247     * @param startWidth The initial width of the new activity.
248     * @param startHeight The initial height of the new activity.
249     * @return Returns a new ActivityOptions object that you can use to
250     * supply these options as the options Bundle when starting an activity.
251     */
252    public static ActivityOptions makeScaleUpAnimation(View source,
253            int startX, int startY, int startWidth, int startHeight) {
254        ActivityOptions opts = new ActivityOptions();
255        opts.mPackageName = source.getContext().getPackageName();
256        opts.mAnimationType = ANIM_SCALE_UP;
257        int[] pts = new int[2];
258        source.getLocationOnScreen(pts);
259        opts.mStartX = pts[0] + startX;
260        opts.mStartY = pts[1] + startY;
261        opts.mStartWidth = startWidth;
262        opts.mStartHeight = startHeight;
263        return opts;
264    }
265
266    /**
267     * Create an ActivityOptions specifying an animation where a thumbnail
268     * is scaled from a given position to the new activity window that is
269     * being started.
270     *
271     * <p>If the Intent this is being used with has not set its
272     * {@link android.content.Intent#setSourceBounds Intent.setSourceBounds},
273     * those bounds will be filled in for you based on the initial
274     * thumbnail location and size provided here.
275     *
276     * @param source The View that this thumbnail is animating from.  This
277     * defines the coordinate space for <var>startX</var> and <var>startY</var>.
278     * @param thumbnail The bitmap that will be shown as the initial thumbnail
279     * of the animation.
280     * @param startX The x starting location of the bitmap, relative to <var>source</var>.
281     * @param startY The y starting location of the bitmap, relative to <var>source</var>.
282     * @return Returns a new ActivityOptions object that you can use to
283     * supply these options as the options Bundle when starting an activity.
284     */
285    public static ActivityOptions makeThumbnailScaleUpAnimation(View source,
286            Bitmap thumbnail, int startX, int startY) {
287        return makeThumbnailScaleUpAnimation(source, thumbnail, startX, startY, null);
288    }
289
290    /**
291     * Create an ActivityOptions specifying an animation where a thumbnail
292     * is scaled from a given position to the new activity window that is
293     * being started.
294     *
295     * @param source The View that this thumbnail is animating from.  This
296     * defines the coordinate space for <var>startX</var> and <var>startY</var>.
297     * @param thumbnail The bitmap that will be shown as the initial thumbnail
298     * of the animation.
299     * @param startX The x starting location of the bitmap, relative to <var>source</var>.
300     * @param startY The y starting location of the bitmap, relative to <var>source</var>.
301     * @param listener Optional OnAnimationStartedListener to find out when the
302     * requested animation has started running.  If for some reason the animation
303     * is not executed, the callback will happen immediately.
304     * @return Returns a new ActivityOptions object that you can use to
305     * supply these options as the options Bundle when starting an activity.
306     * @hide
307     */
308    public static ActivityOptions makeThumbnailScaleUpAnimation(View source,
309            Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener) {
310        return makeThumbnailAnimation(source, thumbnail, startX, startY, listener, true);
311    }
312
313    /**
314     * Create an ActivityOptions specifying an animation where an activity window
315     * is scaled from a given position to a thumbnail at a specified location.
316     *
317     * @param source The View that this thumbnail is animating to.  This
318     * defines the coordinate space for <var>startX</var> and <var>startY</var>.
319     * @param thumbnail The bitmap that will be shown as the final thumbnail
320     * of the animation.
321     * @param startX The x end location of the bitmap, relative to <var>source</var>.
322     * @param startY The y end location of the bitmap, relative to <var>source</var>.
323     * @param listener Optional OnAnimationStartedListener to find out when the
324     * requested animation has started running.  If for some reason the animation
325     * is not executed, the callback will happen immediately.
326     * @return Returns a new ActivityOptions object that you can use to
327     * supply these options as the options Bundle when starting an activity.
328     * @hide
329     */
330    public static ActivityOptions makeThumbnailScaleDownAnimation(View source,
331            Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener) {
332        return makeThumbnailAnimation(source, thumbnail, startX, startY, listener, false);
333    }
334
335    private static ActivityOptions makeThumbnailAnimation(View source,
336            Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener,
337            boolean scaleUp) {
338        ActivityOptions opts = new ActivityOptions();
339        opts.mPackageName = source.getContext().getPackageName();
340        opts.mAnimationType = scaleUp ? ANIM_THUMBNAIL_SCALE_UP : ANIM_THUMBNAIL_SCALE_DOWN;
341        opts.mThumbnail = thumbnail;
342        int[] pts = new int[2];
343        source.getLocationOnScreen(pts);
344        opts.mStartX = pts[0] + startX;
345        opts.mStartY = pts[1] + startY;
346        opts.setOnAnimationStartedListener(source.getHandler(), listener);
347        return opts;
348    }
349
350    /**
351     * Create an ActivityOptions to transition between Activities using cross-Activity animation.
352     * When visual elements are to carry between Activities, args should be used to tell the called
353     * Activity about the location and size.
354     *
355     * TODO: Provide facility to capture layout and bitmap of shared elements.
356     *
357     * <p>When
358     * {@link android.app.Activity#startActivities(android.content.Intent[], android.os.Bundle)}
359     * is used with the {@link #toBundle()} result, the Activity's content scene will automatically
360     * transition out by setting their visibility to {@link View#INVISIBLE}. Shared elements
361     * ({@link android.view.View#setSharedElementName(String)}) are unmodified during the
362     * transition to allow the started Activity to seamlessly take it over. ViewGroups typically
363     * don't transition out, and instead transition out their children unless they have a
364     * background. To modify this behavior, use
365     * {@link android.view.ViewGroup#setTransitionGroup(boolean)}.</p>
366     *
367     * <p>This requires {@link android.view.Window#FEATURE_CONTENT_TRANSITIONS} to be
368     * enabled on the calling Activity to cause an exit transition. The same must be in
369     * the called Activity to get an entering transition.</p>
370     *
371     * @param args Contains information for transferring a view between this Activity and the
372     *             target Activity. Will be used by the called Activity to transition the
373     *             view to its eventual destination
374     * @see android.app.Activity#startSharedElementTransition(android.os.Bundle)
375     */
376    public static ActivityOptions makeSceneTransitionAnimation(Bundle args) {
377        ActivityOptions opts = new ActivityOptions();
378        opts.mAnimationType = ANIM_SCENE_TRANSITION;
379        opts.mTransitionArgs = args;
380        return opts;
381    }
382
383    private ActivityOptions() {
384    }
385
386    /** @hide */
387    public ActivityOptions(Bundle opts) {
388        mPackageName = opts.getString(KEY_PACKAGE_NAME);
389        mAnimationType = opts.getInt(KEY_ANIM_TYPE);
390        switch (mAnimationType) {
391            case ANIM_CUSTOM:
392                mCustomEnterResId = opts.getInt(KEY_ANIM_ENTER_RES_ID, 0);
393                mCustomExitResId = opts.getInt(KEY_ANIM_EXIT_RES_ID, 0);
394                mAnimationStartedListener = IRemoteCallback.Stub.asInterface(
395                        opts.getBinder(KEY_ANIM_START_LISTENER));
396                break;
397
398            case ANIM_SCALE_UP:
399                mStartX = opts.getInt(KEY_ANIM_START_X, 0);
400                mStartY = opts.getInt(KEY_ANIM_START_Y, 0);
401                mStartWidth = opts.getInt(KEY_ANIM_START_WIDTH, 0);
402                mStartHeight = opts.getInt(KEY_ANIM_START_HEIGHT, 0);
403                break;
404
405            case ANIM_THUMBNAIL_SCALE_UP:
406            case ANIM_THUMBNAIL_SCALE_DOWN:
407                mThumbnail = (Bitmap)opts.getParcelable(KEY_ANIM_THUMBNAIL);
408                mStartX = opts.getInt(KEY_ANIM_START_X, 0);
409                mStartY = opts.getInt(KEY_ANIM_START_Y, 0);
410                mAnimationStartedListener = IRemoteCallback.Stub.asInterface(
411                        opts.getBinder(KEY_ANIM_START_LISTENER));
412                break;
413
414            case ANIM_SCENE_TRANSITION:
415                mTransitionArgs = opts.getBundle(KEY_SCENE_TRANSITION_ARGS);
416                mTransitionCompleteListener = IRemoteCallback.Stub.asInterface(
417                        opts.getBinder(KEY_TRANSITION_COMPLETE_LISTENER));
418                break;
419        }
420    }
421
422    /** @hide */
423    public String getPackageName() {
424        return mPackageName;
425    }
426
427    /** @hide */
428    public int getAnimationType() {
429        return mAnimationType;
430    }
431
432    /** @hide */
433    public int getCustomEnterResId() {
434        return mCustomEnterResId;
435    }
436
437    /** @hide */
438    public int getCustomExitResId() {
439        return mCustomExitResId;
440    }
441
442    /** @hide */
443    public Bitmap getThumbnail() {
444        return mThumbnail;
445    }
446
447    /** @hide */
448    public int getStartX() {
449        return mStartX;
450    }
451
452    /** @hide */
453    public int getStartY() {
454        return mStartY;
455    }
456
457    /** @hide */
458    public int getStartWidth() {
459        return mStartWidth;
460    }
461
462    /** @hide */
463    public int getStartHeight() {
464        return mStartHeight;
465    }
466
467    /** @hide */
468    public Bundle getSceneTransitionArgs() {
469        return mTransitionArgs;
470    }
471
472    /** @hide */
473    public IRemoteCallback getOnAnimationStartListener() {
474        return mAnimationStartedListener;
475    }
476
477    /** @hide */
478    public void dispatchSceneTransitionStarted(final ActivityTransitionTarget target) {
479        boolean listenerSent = false;
480        if (mTransitionCompleteListener != null) {
481            IRemoteCallback callback = new IRemoteCallback.Stub() {
482                @Override
483                public void sendResult(Bundle data) throws RemoteException {
484                    if (data == null) {
485                        target.exitTransitionComplete();
486                    } else {
487                        // TODO: Use texture id
488                        target.sharedElementTransitionComplete();
489                    }
490                }
491            };
492            Bundle bundle = new Bundle();
493            bundle.putBinder(KEY_TRANSITION_TARGET_LISTENER, callback.asBinder());
494            try {
495                mTransitionCompleteListener.sendResult(bundle);
496                listenerSent = true;
497            } catch (RemoteException e) {
498                Log.w(TAG, "Couldn't retrieve transition notifications", e);
499            }
500        }
501        if (!listenerSent) {
502            target.sharedElementTransitionComplete();
503            target.exitTransitionComplete();
504        }
505    }
506
507    /** @hide */
508    public void abort() {
509        if (mAnimationStartedListener != null) {
510            try {
511                mAnimationStartedListener.sendResult(null);
512            } catch (RemoteException e) {
513            }
514        }
515    }
516
517    /** @hide */
518    public static void abort(Bundle options) {
519        if (options != null) {
520            (new ActivityOptions(options)).abort();
521        }
522    }
523
524    /**
525     * Update the current values in this ActivityOptions from those supplied
526     * in <var>otherOptions</var>.  Any values
527     * defined in <var>otherOptions</var> replace those in the base options.
528     */
529    public void update(ActivityOptions otherOptions) {
530        if (otherOptions.mPackageName != null) {
531            mPackageName = otherOptions.mPackageName;
532        }
533        switch (otherOptions.mAnimationType) {
534            case ANIM_CUSTOM:
535                mAnimationType = otherOptions.mAnimationType;
536                mCustomEnterResId = otherOptions.mCustomEnterResId;
537                mCustomExitResId = otherOptions.mCustomExitResId;
538                mThumbnail = null;
539                if (mAnimationStartedListener != null) {
540                    try {
541                        mAnimationStartedListener.sendResult(null);
542                    } catch (RemoteException e) {
543                    }
544                }
545                mAnimationStartedListener = otherOptions.mAnimationStartedListener;
546                mTransitionCompleteListener = null;
547                mTransitionArgs = null;
548                break;
549            case ANIM_SCALE_UP:
550                mAnimationType = otherOptions.mAnimationType;
551                mStartX = otherOptions.mStartX;
552                mStartY = otherOptions.mStartY;
553                mStartWidth = otherOptions.mStartWidth;
554                mStartHeight = otherOptions.mStartHeight;
555                if (mAnimationStartedListener != null) {
556                    try {
557                        mAnimationStartedListener.sendResult(null);
558                    } catch (RemoteException e) {
559                    }
560                }
561                mAnimationStartedListener = null;
562                mTransitionCompleteListener = null;
563                mTransitionArgs = null;
564                break;
565            case ANIM_THUMBNAIL_SCALE_UP:
566            case ANIM_THUMBNAIL_SCALE_DOWN:
567                mAnimationType = otherOptions.mAnimationType;
568                mThumbnail = otherOptions.mThumbnail;
569                mStartX = otherOptions.mStartX;
570                mStartY = otherOptions.mStartY;
571                if (mAnimationStartedListener != null) {
572                    try {
573                        mAnimationStartedListener.sendResult(null);
574                    } catch (RemoteException e) {
575                    }
576                }
577                mAnimationStartedListener = otherOptions.mAnimationStartedListener;
578                mTransitionCompleteListener = null;
579                mTransitionArgs = null;
580                break;
581            case ANIM_SCENE_TRANSITION:
582                mAnimationType = otherOptions.mAnimationType;
583                mTransitionCompleteListener = otherOptions.mTransitionCompleteListener;
584                mTransitionArgs = otherOptions.mTransitionArgs;
585                mThumbnail = null;
586                mAnimationStartedListener = null;
587                break;
588        }
589    }
590
591    /**
592     * Returns the created options as a Bundle, which can be passed to
593     * {@link android.content.Context#startActivity(android.content.Intent, android.os.Bundle)
594     * Context.startActivity(Intent, Bundle)} and related methods.
595     * Note that the returned Bundle is still owned by the ActivityOptions
596     * object; you must not modify it, but can supply it to the startActivity
597     * methods that take an options Bundle.
598     */
599    public Bundle toBundle() {
600        Bundle b = new Bundle();
601        if (mPackageName != null) {
602            b.putString(KEY_PACKAGE_NAME, mPackageName);
603        }
604        switch (mAnimationType) {
605            case ANIM_CUSTOM:
606                b.putInt(KEY_ANIM_TYPE, mAnimationType);
607                b.putInt(KEY_ANIM_ENTER_RES_ID, mCustomEnterResId);
608                b.putInt(KEY_ANIM_EXIT_RES_ID, mCustomExitResId);
609                b.putBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
610                        != null ? mAnimationStartedListener.asBinder() : null);
611                break;
612            case ANIM_SCALE_UP:
613                b.putInt(KEY_ANIM_TYPE, mAnimationType);
614                b.putInt(KEY_ANIM_START_X, mStartX);
615                b.putInt(KEY_ANIM_START_Y, mStartY);
616                b.putInt(KEY_ANIM_START_WIDTH, mStartWidth);
617                b.putInt(KEY_ANIM_START_HEIGHT, mStartHeight);
618                break;
619            case ANIM_THUMBNAIL_SCALE_UP:
620            case ANIM_THUMBNAIL_SCALE_DOWN:
621                b.putInt(KEY_ANIM_TYPE, mAnimationType);
622                b.putParcelable(KEY_ANIM_THUMBNAIL, mThumbnail);
623                b.putInt(KEY_ANIM_START_X, mStartX);
624                b.putInt(KEY_ANIM_START_Y, mStartY);
625                b.putBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
626                        != null ? mAnimationStartedListener.asBinder() : null);
627                break;
628            case ANIM_SCENE_TRANSITION:
629                b.putInt(KEY_ANIM_TYPE, mAnimationType);
630                b.putBundle(KEY_SCENE_TRANSITION_ARGS, mTransitionArgs);
631                if (mTransitionCompleteListener != null) {
632                    b.putBinder(KEY_TRANSITION_COMPLETE_LISTENER,
633                            mTransitionCompleteListener.asBinder());
634                }
635                break;
636        }
637        return b;
638    }
639
640    /**
641     * Return the filtered options only meant to be seen by the target activity itself
642     * @hide
643     */
644    public ActivityOptions forTargetActivity() {
645        if (mAnimationType == ANIM_SCENE_TRANSITION) {
646            final ActivityOptions result = new ActivityOptions();
647            result.update(this);
648            return result;
649        }
650
651        return null;
652    }
653
654    /** @hide */
655    public interface SharedElementSource {
656        int getTextureId();
657    }
658
659    /**
660     * In the calling Activity when transitioning out, sets the Transition to listen for
661     * changes.
662     * @hide
663     */
664    public void setExitTransition(Transition transition, SharedElementSource sharedElementSource) {
665        mTransitionCompleteListener = new ExitTransitionListener(transition, sharedElementSource);
666    }
667
668    private static class ExitTransitionListener extends IRemoteCallback.Stub
669            implements Transition.TransitionListener, Animator.AnimatorListener {
670        private ArrayList<Animator> mSharedElementAnimators = new ArrayList<Animator>();
671        private boolean mSharedElementNotified;
672        private Transition mExitTransition;
673        private IRemoteCallback mTransitionCompleteCallback;
674        private boolean mExitComplete;
675        private SharedElementSource mSharedElementSource;
676
677        public ExitTransitionListener(Transition transition, SharedElementSource sharedElementSource) {
678            mSharedElementSource = sharedElementSource;
679            mExitTransition = transition;
680            mExitTransition.addListener(this);
681        }
682
683        @Override
684        public void sendResult(Bundle data) throws RemoteException {
685            if (data != null) {
686                mTransitionCompleteCallback = IRemoteCallback.Stub.asInterface(
687                        data.getBinder(KEY_TRANSITION_TARGET_LISTENER));
688                notifySharedElement();
689                notifyExit();
690            }
691        }
692
693        @Override
694        public void onTransitionStart(Transition transition) {
695            ArrayMap<Animator, Transition.AnimationInfo> runningAnimators
696                    = Transition.getRunningAnimators();
697            for (Map.Entry<Animator, Transition.AnimationInfo> entry : runningAnimators.entrySet()) {
698                if (entry.getValue().view.getSharedElementName() != null) {
699                    mSharedElementAnimators.add(entry.getKey());
700                    entry.getKey().addListener(this);
701                }
702            }
703            notifySharedElement();
704        }
705
706        @Override
707        public void onTransitionEnd(Transition transition) {
708            mExitComplete = true;
709            notifyExit();
710            mExitTransition.removeListener(this);
711        }
712
713        @Override
714        public void onTransitionCancel(Transition transition) {
715            mExitComplete = true;
716            notifyExit();
717            mExitTransition.removeListener(this);
718        }
719
720        @Override
721        public void onTransitionPause(Transition transition) {
722        }
723
724        @Override
725        public void onTransitionResume(Transition transition) {
726        }
727
728        @Override
729        public void onAnimationStart(Animator animation) {
730        }
731
732        @Override
733        public void onAnimationEnd(Animator animation) {
734            mSharedElementAnimators.remove(animation);
735            notifySharedElement();
736        }
737
738        @Override
739        public void onAnimationCancel(Animator animation) {
740            mSharedElementAnimators.remove(animation);
741            notifySharedElement();
742        }
743
744        @Override
745        public void onAnimationRepeat(Animator animation) {
746        }
747
748        private void notifySharedElement() {
749            if (!mSharedElementNotified && mSharedElementAnimators.isEmpty()
750                    && mTransitionCompleteCallback != null) {
751                mSharedElementNotified = true;
752                try {
753                    Bundle bundle = new Bundle();
754                    bundle.putInt(KEY_SHARED_ELEMENT_TEXTURE_ID, mSharedElementSource.getTextureId());
755                    mTransitionCompleteCallback.sendResult(bundle);
756                } catch (RemoteException e) {
757                    Log.w(TAG, "Couldn't notify that the transition ended", e);
758                }
759            }
760        }
761
762        private void notifyExit() {
763            if (mExitComplete && mTransitionCompleteCallback != null) {
764                try {
765                    mTransitionCompleteCallback.sendResult(null);
766                } catch (RemoteException e) {
767                    Log.w(TAG, "Couldn't notify that the transition ended", e);
768                }
769            }
770        }
771    }
772}
773