ActivityOptions.java revision e180337ee99b9155fe441ea55451f4d2167b5d9a
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.content.Context;
20import android.graphics.Bitmap;
21import android.os.Bundle;
22import android.os.Handler;
23import android.os.IRemoteCallback;
24import android.os.RemoteException;
25import android.transition.Transition;
26import android.util.Log;
27import android.util.Pair;
28import android.view.View;
29
30import java.util.ArrayList;
31import java.util.Map;
32
33/**
34 * Helper class for building an options Bundle that can be used with
35 * {@link android.content.Context#startActivity(android.content.Intent, android.os.Bundle)
36 * Context.startActivity(Intent, Bundle)} and related methods.
37 */
38public class ActivityOptions {
39    private static final String TAG = "ActivityOptions";
40
41    /**
42     * The package name that created the options.
43     * @hide
44     */
45    public static final String KEY_PACKAGE_NAME = "android:packageName";
46
47    /**
48     * Type of animation that arguments specify.
49     * @hide
50     */
51    public static final String KEY_ANIM_TYPE = "android:animType";
52
53    /**
54     * Custom enter animation resource ID.
55     * @hide
56     */
57    public static final String KEY_ANIM_ENTER_RES_ID = "android:animEnterRes";
58
59    /**
60     * Custom exit animation resource ID.
61     * @hide
62     */
63    public static final String KEY_ANIM_EXIT_RES_ID = "android:animExitRes";
64
65    /**
66     * Bitmap for thumbnail animation.
67     * @hide
68     */
69    public static final String KEY_ANIM_THUMBNAIL = "android:animThumbnail";
70
71    /**
72     * Start X position of thumbnail animation.
73     * @hide
74     */
75    public static final String KEY_ANIM_START_X = "android:animStartX";
76
77    /**
78     * Start Y position of thumbnail animation.
79     * @hide
80     */
81    public static final String KEY_ANIM_START_Y = "android:animStartY";
82
83    /**
84     * Initial width of the animation.
85     * @hide
86     */
87    public static final String KEY_ANIM_START_WIDTH = "android:animStartWidth";
88
89    /**
90     * Initial height of the animation.
91     * @hide
92     */
93    public static final String KEY_ANIM_START_HEIGHT = "android:animStartHeight";
94
95    /**
96     * Callback for when animation is started.
97     * @hide
98     */
99    public static final String KEY_ANIM_START_LISTENER = "android:animStartListener";
100
101    /**
102     * For Activity transitions, the calling Activity's TransitionListener used to
103     * notify the called Activity when the shared element and the exit transitions
104     * complete.
105     */
106    private static final String KEY_TRANSITION_COMPLETE_LISTENER
107            = "android:transitionCompleteListener";
108
109    /**
110     * For Activity transitions, the called Activity's listener to receive calls
111     * when transitions complete.
112     */
113    private static final String KEY_TRANSITION_TARGET_LISTENER = "android:transitionTargetListener";
114
115    /**
116     * The names of shared elements that are transitioned to the started Activity.
117     * This is also the name of shared elements that the started Activity accepted.
118     */
119    private static final String KEY_SHARED_ELEMENT_NAMES = "android:shared_element_names";
120
121    /**
122     * The shared elements names of the views in the calling Activity.
123     */
124    private static final String KEY_LOCAL_ELEMENT_NAMES = "android:local_element_names";
125
126    /** @hide */
127    public static final int ANIM_NONE = 0;
128    /** @hide */
129    public static final int ANIM_CUSTOM = 1;
130    /** @hide */
131    public static final int ANIM_SCALE_UP = 2;
132    /** @hide */
133    public static final int ANIM_THUMBNAIL_SCALE_UP = 3;
134    /** @hide */
135    public static final int ANIM_THUMBNAIL_SCALE_DOWN = 4;
136    /** @hide */
137    public static final int ANIM_SCENE_TRANSITION = 5;
138
139    private String mPackageName;
140    private int mAnimationType = ANIM_NONE;
141    private int mCustomEnterResId;
142    private int mCustomExitResId;
143    private Bitmap mThumbnail;
144    private int mStartX;
145    private int mStartY;
146    private int mStartWidth;
147    private int mStartHeight;
148    private IRemoteCallback mAnimationStartedListener;
149    private IRemoteCallback mTransitionCompleteListener;
150    private ArrayList<String> mSharedElementNames;
151    private ArrayList<String> mLocalElementNames;
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(Bundle transitionArgs);
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 scene
352     * animations. This method carries the position of one shared element to the started Activity.
353     *
354     * <p>This requires {@link android.view.Window#FEATURE_CONTENT_TRANSITIONS} to be
355     * enabled on the calling Activity to cause an exit transition. The same must be in
356     * the called Activity to get an entering transition.</p>
357     * @param sharedElement The View to transition to the started Activity. sharedElement must
358     *                      have a non-null sharedElementName.
359     * @param sharedElementName The shared element name as used in the target Activity. This may
360     *                          be null if it has the same name as sharedElement.
361     * @return Returns a new ActivityOptions object that you can use to
362     *         supply these options as the options Bundle when starting an activity.
363     */
364    public static ActivityOptions makeSceneTransitionAnimation(View sharedElement,
365            String sharedElementName) {
366        return makeSceneTransitionAnimation(
367                new Pair<View, String>(sharedElement, sharedElementName));
368    }
369
370    /**
371     * Create an ActivityOptions to transition between Activities using cross-Activity scene
372     * animations. This method carries the position of multiple shared elements to the started
373     * Activity.
374     *
375     * <p>This requires {@link android.view.Window#FEATURE_CONTENT_TRANSITIONS} to be
376     * enabled on the calling Activity to cause an exit transition. The same must be in
377     * the called Activity to get an entering transition.</p>
378     * @param sharedElements The View to transition to the started Activity along with the
379     *                       shared element name as used in the started Activity. The view
380     *                       must have a non-null sharedElementName.
381     * @return Returns a new ActivityOptions object that you can use to
382     *         supply these options as the options Bundle when starting an activity.
383     */
384    public static ActivityOptions makeSceneTransitionAnimation(
385            Pair<View, String>... sharedElements) {
386        ActivityOptions opts = new ActivityOptions();
387        opts.mAnimationType = ANIM_SCENE_TRANSITION;
388        opts.mSharedElementNames = new ArrayList<String>();
389        opts.mLocalElementNames = new ArrayList<String>();
390
391        if (sharedElements != null) {
392            for (Pair<View, String> sharedElement : sharedElements) {
393                opts.addSharedElement(sharedElement.first, sharedElement.second);
394            }
395        }
396        return opts;
397    }
398
399    private ActivityOptions() {
400    }
401
402    /** @hide */
403    public ActivityOptions(Bundle opts) {
404        mPackageName = opts.getString(KEY_PACKAGE_NAME);
405        mAnimationType = opts.getInt(KEY_ANIM_TYPE);
406        switch (mAnimationType) {
407            case ANIM_CUSTOM:
408                mCustomEnterResId = opts.getInt(KEY_ANIM_ENTER_RES_ID, 0);
409                mCustomExitResId = opts.getInt(KEY_ANIM_EXIT_RES_ID, 0);
410                mAnimationStartedListener = IRemoteCallback.Stub.asInterface(
411                        opts.getBinder(KEY_ANIM_START_LISTENER));
412                break;
413
414            case ANIM_SCALE_UP:
415                mStartX = opts.getInt(KEY_ANIM_START_X, 0);
416                mStartY = opts.getInt(KEY_ANIM_START_Y, 0);
417                mStartWidth = opts.getInt(KEY_ANIM_START_WIDTH, 0);
418                mStartHeight = opts.getInt(KEY_ANIM_START_HEIGHT, 0);
419                break;
420
421            case ANIM_THUMBNAIL_SCALE_UP:
422            case ANIM_THUMBNAIL_SCALE_DOWN:
423                mThumbnail = (Bitmap)opts.getParcelable(KEY_ANIM_THUMBNAIL);
424                mStartX = opts.getInt(KEY_ANIM_START_X, 0);
425                mStartY = opts.getInt(KEY_ANIM_START_Y, 0);
426                mAnimationStartedListener = IRemoteCallback.Stub.asInterface(
427                        opts.getBinder(KEY_ANIM_START_LISTENER));
428                break;
429
430            case ANIM_SCENE_TRANSITION:
431                mTransitionCompleteListener = IRemoteCallback.Stub.asInterface(
432                        opts.getBinder(KEY_TRANSITION_COMPLETE_LISTENER));
433                mSharedElementNames = opts.getStringArrayList(KEY_SHARED_ELEMENT_NAMES);
434                mLocalElementNames = opts.getStringArrayList(KEY_LOCAL_ELEMENT_NAMES);
435                break;
436        }
437    }
438
439    /** @hide */
440    public String getPackageName() {
441        return mPackageName;
442    }
443
444    /** @hide */
445    public int getAnimationType() {
446        return mAnimationType;
447    }
448
449    /** @hide */
450    public int getCustomEnterResId() {
451        return mCustomEnterResId;
452    }
453
454    /** @hide */
455    public int getCustomExitResId() {
456        return mCustomExitResId;
457    }
458
459    /** @hide */
460    public Bitmap getThumbnail() {
461        return mThumbnail;
462    }
463
464    /** @hide */
465    public int getStartX() {
466        return mStartX;
467    }
468
469    /** @hide */
470    public int getStartY() {
471        return mStartY;
472    }
473
474    /** @hide */
475    public int getStartWidth() {
476        return mStartWidth;
477    }
478
479    /** @hide */
480    public int getStartHeight() {
481        return mStartHeight;
482    }
483
484    /** @hide */
485    public IRemoteCallback getOnAnimationStartListener() {
486        return mAnimationStartedListener;
487    }
488
489    /** @hide */
490    public ArrayList<String> getSharedElementNames() { return mSharedElementNames; }
491
492    /** @hide */
493    public ArrayList<String> getLocalElementNames() { return mLocalElementNames; }
494
495    /** @hide */
496    public void dispatchSceneTransitionStarted(final ActivityTransitionTarget target,
497            ArrayList<String> sharedElementNames) {
498        boolean listenerSent = false;
499        if (mTransitionCompleteListener != null) {
500            IRemoteCallback callback = new IRemoteCallback.Stub() {
501                @Override
502                public void sendResult(Bundle data) throws RemoteException {
503                    if (data == null) {
504                        target.exitTransitionComplete();
505                    } else {
506                        target.sharedElementTransitionComplete(data);
507                    }
508                }
509            };
510            Bundle bundle = new Bundle();
511            bundle.putBinder(KEY_TRANSITION_TARGET_LISTENER, callback.asBinder());
512            bundle.putStringArrayList(KEY_SHARED_ELEMENT_NAMES, sharedElementNames);
513            try {
514                mTransitionCompleteListener.sendResult(bundle);
515                listenerSent = true;
516            } catch (RemoteException e) {
517                Log.w(TAG, "Couldn't retrieve transition notifications", e);
518            }
519        }
520        if (!listenerSent) {
521            target.sharedElementTransitionComplete(null);
522            target.exitTransitionComplete();
523        }
524    }
525
526    /** @hide */
527    public void dispatchSharedElementsReady() {
528        if (mTransitionCompleteListener != null) {
529            try {
530                mTransitionCompleteListener.sendResult(null);
531            } catch (RemoteException e) {
532                Log.w(TAG, "Couldn't synchronize shared elements", e);
533            }
534        }
535    }
536
537    /** @hide */
538    public void abort() {
539        if (mAnimationStartedListener != null) {
540            try {
541                mAnimationStartedListener.sendResult(null);
542            } catch (RemoteException e) {
543            }
544        }
545    }
546
547    /** @hide */
548    public static void abort(Bundle options) {
549        if (options != null) {
550            (new ActivityOptions(options)).abort();
551        }
552    }
553
554    /**
555     * Update the current values in this ActivityOptions from those supplied
556     * in <var>otherOptions</var>.  Any values
557     * defined in <var>otherOptions</var> replace those in the base options.
558     */
559    public void update(ActivityOptions otherOptions) {
560        if (otherOptions.mPackageName != null) {
561            mPackageName = otherOptions.mPackageName;
562        }
563        mSharedElementNames = null;
564        mLocalElementNames = null;
565        switch (otherOptions.mAnimationType) {
566            case ANIM_CUSTOM:
567                mAnimationType = otherOptions.mAnimationType;
568                mCustomEnterResId = otherOptions.mCustomEnterResId;
569                mCustomExitResId = otherOptions.mCustomExitResId;
570                mThumbnail = null;
571                if (mAnimationStartedListener != null) {
572                    try {
573                        mAnimationStartedListener.sendResult(null);
574                    } catch (RemoteException e) {
575                    }
576                }
577                mAnimationStartedListener = otherOptions.mAnimationStartedListener;
578                mTransitionCompleteListener = null;
579                break;
580            case ANIM_SCALE_UP:
581                mAnimationType = otherOptions.mAnimationType;
582                mStartX = otherOptions.mStartX;
583                mStartY = otherOptions.mStartY;
584                mStartWidth = otherOptions.mStartWidth;
585                mStartHeight = otherOptions.mStartHeight;
586                if (mAnimationStartedListener != null) {
587                    try {
588                        mAnimationStartedListener.sendResult(null);
589                    } catch (RemoteException e) {
590                    }
591                }
592                mAnimationStartedListener = null;
593                mTransitionCompleteListener = null;
594                break;
595            case ANIM_THUMBNAIL_SCALE_UP:
596            case ANIM_THUMBNAIL_SCALE_DOWN:
597                mAnimationType = otherOptions.mAnimationType;
598                mThumbnail = otherOptions.mThumbnail;
599                mStartX = otherOptions.mStartX;
600                mStartY = otherOptions.mStartY;
601                if (mAnimationStartedListener != null) {
602                    try {
603                        mAnimationStartedListener.sendResult(null);
604                    } catch (RemoteException e) {
605                    }
606                }
607                mAnimationStartedListener = otherOptions.mAnimationStartedListener;
608                mTransitionCompleteListener = null;
609                break;
610            case ANIM_SCENE_TRANSITION:
611                mAnimationType = otherOptions.mAnimationType;
612                mTransitionCompleteListener = otherOptions.mTransitionCompleteListener;
613                mThumbnail = null;
614                mAnimationStartedListener = null;
615                mSharedElementNames = otherOptions.mSharedElementNames;
616                mLocalElementNames = otherOptions.mLocalElementNames;
617                break;
618        }
619    }
620
621    /**
622     * Returns the created options as a Bundle, which can be passed to
623     * {@link android.content.Context#startActivity(android.content.Intent, android.os.Bundle)
624     * Context.startActivity(Intent, Bundle)} and related methods.
625     * Note that the returned Bundle is still owned by the ActivityOptions
626     * object; you must not modify it, but can supply it to the startActivity
627     * methods that take an options Bundle.
628     */
629    public Bundle toBundle() {
630        Bundle b = new Bundle();
631        if (mPackageName != null) {
632            b.putString(KEY_PACKAGE_NAME, mPackageName);
633        }
634        switch (mAnimationType) {
635            case ANIM_CUSTOM:
636                b.putInt(KEY_ANIM_TYPE, mAnimationType);
637                b.putInt(KEY_ANIM_ENTER_RES_ID, mCustomEnterResId);
638                b.putInt(KEY_ANIM_EXIT_RES_ID, mCustomExitResId);
639                b.putBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
640                        != null ? mAnimationStartedListener.asBinder() : null);
641                break;
642            case ANIM_SCALE_UP:
643                b.putInt(KEY_ANIM_TYPE, mAnimationType);
644                b.putInt(KEY_ANIM_START_X, mStartX);
645                b.putInt(KEY_ANIM_START_Y, mStartY);
646                b.putInt(KEY_ANIM_START_WIDTH, mStartWidth);
647                b.putInt(KEY_ANIM_START_HEIGHT, mStartHeight);
648                break;
649            case ANIM_THUMBNAIL_SCALE_UP:
650            case ANIM_THUMBNAIL_SCALE_DOWN:
651                b.putInt(KEY_ANIM_TYPE, mAnimationType);
652                b.putParcelable(KEY_ANIM_THUMBNAIL, mThumbnail);
653                b.putInt(KEY_ANIM_START_X, mStartX);
654                b.putInt(KEY_ANIM_START_Y, mStartY);
655                b.putBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
656                        != null ? mAnimationStartedListener.asBinder() : null);
657                break;
658            case ANIM_SCENE_TRANSITION:
659                b.putInt(KEY_ANIM_TYPE, mAnimationType);
660                if (mTransitionCompleteListener != null) {
661                    b.putBinder(KEY_TRANSITION_COMPLETE_LISTENER,
662                            mTransitionCompleteListener.asBinder());
663                }
664                b.putStringArrayList(KEY_SHARED_ELEMENT_NAMES, mSharedElementNames);
665                b.putStringArrayList(KEY_LOCAL_ELEMENT_NAMES, mLocalElementNames);
666                break;
667        }
668        return b;
669    }
670
671    /**
672     * Return the filtered options only meant to be seen by the target activity itself
673     * @hide
674     */
675    public ActivityOptions forTargetActivity() {
676        if (mAnimationType == ANIM_SCENE_TRANSITION) {
677            final ActivityOptions result = new ActivityOptions();
678            result.update(this);
679            return result;
680        }
681
682        return null;
683    }
684
685    /** @hide */
686    public void addSharedElements(Map<String, View> sharedElements) {
687        for (Map.Entry<String, View> entry : sharedElements.entrySet()) {
688            addSharedElement(entry.getValue(), entry.getKey());
689        }
690    }
691
692    /** @hide */
693    public void updateSceneTransitionAnimation(Transition exitTransition,
694            Transition sharedElementTransition, SharedElementSource sharedElementSource) {
695        mTransitionCompleteListener = new ExitTransitionListener(exitTransition,
696                sharedElementTransition, sharedElementSource);
697    }
698
699    private void addSharedElement(View view, String name) {
700        String sharedElementName = view.getSharedElementName();
701        if (name == null) {
702            name = sharedElementName;
703        }
704        mSharedElementNames.add(name);
705        mLocalElementNames.add(sharedElementName);
706    }
707
708    /** @hide */
709    public interface SharedElementSource {
710        Bundle getSharedElementExitState();
711        void acceptedSharedElements(ArrayList<String> sharedElementNames);
712        void hideSharedElements();
713    }
714
715    private static class ExitTransitionListener extends IRemoteCallback.Stub
716            implements Transition.TransitionListener {
717        private boolean mSharedElementNotified;
718        private Transition mExitTransition;
719        private Transition mSharedElementTransition;
720        private IRemoteCallback mTransitionCompleteCallback;
721        private boolean mExitComplete;
722        private boolean mSharedElementComplete;
723        private SharedElementSource mSharedElementSource;
724
725        public ExitTransitionListener(Transition exitTransition, Transition sharedElementTransition,
726                SharedElementSource sharedElementSource) {
727            mSharedElementSource = sharedElementSource;
728            mExitTransition = exitTransition;
729            mExitTransition.addListener(this);
730            mSharedElementTransition = sharedElementTransition;
731            mSharedElementTransition.addListener(this);
732        }
733
734        @Override
735        public void sendResult(Bundle data) throws RemoteException {
736            if (data != null) {
737                mTransitionCompleteCallback = IRemoteCallback.Stub.asInterface(
738                        data.getBinder(KEY_TRANSITION_TARGET_LISTENER));
739                ArrayList<String> sharedElementNames
740                        = data.getStringArrayList(KEY_SHARED_ELEMENT_NAMES);
741                mSharedElementSource.acceptedSharedElements(sharedElementNames);
742                notifySharedElement();
743                notifyExit();
744            } else {
745                mSharedElementSource.hideSharedElements();
746            }
747        }
748
749        @Override
750        public void onTransitionStart(Transition transition) {
751        }
752
753        @Override
754        public void onTransitionEnd(Transition transition) {
755            if (transition == mExitTransition) {
756                mExitComplete = true;
757                notifyExit();
758                mExitTransition.removeListener(this);
759            } else {
760                mSharedElementComplete = true;
761                notifySharedElement();
762                mSharedElementTransition.removeListener(this);
763            }
764        }
765
766        @Override
767        public void onTransitionCancel(Transition transition) {
768            onTransitionEnd(transition);
769        }
770
771        @Override
772        public void onTransitionPause(Transition transition) {
773        }
774
775        @Override
776        public void onTransitionResume(Transition transition) {
777        }
778
779        private void notifySharedElement() {
780            if (!mSharedElementNotified && mSharedElementComplete
781                    && mTransitionCompleteCallback != null) {
782                mSharedElementNotified = true;
783                try {
784                    Bundle sharedElementState = mSharedElementSource.getSharedElementExitState();
785                    mTransitionCompleteCallback.sendResult(sharedElementState);
786                } catch (RemoteException e) {
787                    Log.w(TAG, "Couldn't notify that the transition ended", e);
788                }
789            }
790        }
791
792        private void notifyExit() {
793            if (mExitComplete && mTransitionCompleteCallback != null) {
794                try {
795                    mTransitionCompleteCallback.sendResult(null);
796                } catch (RemoteException e) {
797                    Log.w(TAG, "Couldn't notify that the transition ended", e);
798                }
799            }
800        }
801    }
802}
803