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.view.View;
26
27/**
28 * Helper class for building an options Bundle that can be used with
29 * {@link android.content.Context#startActivity(android.content.Intent, android.os.Bundle)
30 * Context.startActivity(Intent, Bundle)} and related methods.
31 */
32public class ActivityOptions {
33    /**
34     * The package name that created the options.
35     * @hide
36     */
37    public static final String KEY_PACKAGE_NAME = "android:packageName";
38
39    /**
40     * Type of animation that arguments specify.
41     * @hide
42     */
43    public static final String KEY_ANIM_TYPE = "android:animType";
44
45    /**
46     * Custom enter animation resource ID.
47     * @hide
48     */
49    public static final String KEY_ANIM_ENTER_RES_ID = "android:animEnterRes";
50
51    /**
52     * Custom exit animation resource ID.
53     * @hide
54     */
55    public static final String KEY_ANIM_EXIT_RES_ID = "android:animExitRes";
56
57    /**
58     * Bitmap for thumbnail animation.
59     * @hide
60     */
61    public static final String KEY_ANIM_THUMBNAIL = "android:animThumbnail";
62
63    /**
64     * Start X position of thumbnail animation.
65     * @hide
66     */
67    public static final String KEY_ANIM_START_X = "android:animStartX";
68
69    /**
70     * Start Y position of thumbnail animation.
71     * @hide
72     */
73    public static final String KEY_ANIM_START_Y = "android:animStartY";
74
75    /**
76     * Initial width of the animation.
77     * @hide
78     */
79    public static final String KEY_ANIM_START_WIDTH = "android:animStartWidth";
80
81    /**
82     * Initial height of the animation.
83     * @hide
84     */
85    public static final String KEY_ANIM_START_HEIGHT = "android:animStartHeight";
86
87    /**
88     * Callback for when animation is started.
89     * @hide
90     */
91    public static final String KEY_ANIM_START_LISTENER = "android:animStartListener";
92
93    /** @hide */
94    public static final int ANIM_NONE = 0;
95    /** @hide */
96    public static final int ANIM_CUSTOM = 1;
97    /** @hide */
98    public static final int ANIM_SCALE_UP = 2;
99    /** @hide */
100    public static final int ANIM_THUMBNAIL_SCALE_UP = 3;
101    /** @hide */
102    public static final int ANIM_THUMBNAIL_SCALE_DOWN = 4;
103
104    private String mPackageName;
105    private int mAnimationType = ANIM_NONE;
106    private int mCustomEnterResId;
107    private int mCustomExitResId;
108    private Bitmap mThumbnail;
109    private int mStartX;
110    private int mStartY;
111    private int mStartWidth;
112    private int mStartHeight;
113    private IRemoteCallback mAnimationStartedListener;
114
115    /**
116     * Create an ActivityOptions specifying a custom animation to run when
117     * the activity is displayed.
118     *
119     * @param context Who is defining this.  This is the application that the
120     * animation resources will be loaded from.
121     * @param enterResId A resource ID of the animation resource to use for
122     * the incoming activity.  Use 0 for no animation.
123     * @param exitResId A resource ID of the animation resource to use for
124     * the outgoing activity.  Use 0 for no animation.
125     * @return Returns a new ActivityOptions object that you can use to
126     * supply these options as the options Bundle when starting an activity.
127     */
128    public static ActivityOptions makeCustomAnimation(Context context,
129            int enterResId, int exitResId) {
130        return makeCustomAnimation(context, enterResId, exitResId, null, null);
131    }
132
133    /**
134     * Create an ActivityOptions specifying a custom animation to run when
135     * the activity is displayed.
136     *
137     * @param context Who is defining this.  This is the application that the
138     * animation resources will be loaded from.
139     * @param enterResId A resource ID of the animation resource to use for
140     * the incoming activity.  Use 0 for no animation.
141     * @param exitResId A resource ID of the animation resource to use for
142     * the outgoing activity.  Use 0 for no animation.
143     * @param handler If <var>listener</var> is non-null this must be a valid
144     * Handler on which to dispatch the callback; otherwise it should be null.
145     * @param listener Optional OnAnimationStartedListener to find out when the
146     * requested animation has started running.  If for some reason the animation
147     * is not executed, the callback will happen immediately.
148     * @return Returns a new ActivityOptions object that you can use to
149     * supply these options as the options Bundle when starting an activity.
150     * @hide
151     */
152    public static ActivityOptions makeCustomAnimation(Context context,
153            int enterResId, int exitResId, Handler handler, OnAnimationStartedListener listener) {
154        ActivityOptions opts = new ActivityOptions();
155        opts.mPackageName = context.getPackageName();
156        opts.mAnimationType = ANIM_CUSTOM;
157        opts.mCustomEnterResId = enterResId;
158        opts.mCustomExitResId = exitResId;
159        opts.setListener(handler, listener);
160        return opts;
161    }
162
163    private void setListener(Handler handler, OnAnimationStartedListener listener) {
164        if (listener != null) {
165            final Handler h = handler;
166            final OnAnimationStartedListener finalListener = listener;
167            mAnimationStartedListener = new IRemoteCallback.Stub() {
168                @Override public void sendResult(Bundle data) throws RemoteException {
169                    h.post(new Runnable() {
170                        @Override public void run() {
171                            finalListener.onAnimationStarted();
172                        }
173                    });
174                }
175            };
176        }
177    }
178
179    /**
180     * Callback for use with {@link ActivityOptions#makeThumbnailScaleUpAnimation}
181     * to find out when the given animation has started running.
182     * @hide
183     */
184    public interface OnAnimationStartedListener {
185        void onAnimationStarted();
186    }
187
188    /**
189     * Create an ActivityOptions specifying an animation where the new
190     * activity is scaled from a small originating area of the screen to
191     * its final full representation.
192     *
193     * <p>If the Intent this is being used with has not set its
194     * {@link android.content.Intent#setSourceBounds Intent.setSourceBounds},
195     * those bounds will be filled in for you based on the initial
196     * bounds passed in here.
197     *
198     * @param source The View that the new activity is animating from.  This
199     * defines the coordinate space for <var>startX</var> and <var>startY</var>.
200     * @param startX The x starting location of the new activity, relative to <var>source</var>.
201     * @param startY The y starting location of the activity, relative to <var>source</var>.
202     * @param startWidth The initial width of the new activity.
203     * @param startHeight The initial height of the new activity.
204     * @return Returns a new ActivityOptions object that you can use to
205     * supply these options as the options Bundle when starting an activity.
206     */
207    public static ActivityOptions makeScaleUpAnimation(View source,
208            int startX, int startY, int startWidth, int startHeight) {
209        ActivityOptions opts = new ActivityOptions();
210        opts.mPackageName = source.getContext().getPackageName();
211        opts.mAnimationType = ANIM_SCALE_UP;
212        int[] pts = new int[2];
213        source.getLocationOnScreen(pts);
214        opts.mStartX = pts[0] + startX;
215        opts.mStartY = pts[1] + startY;
216        opts.mStartWidth = startWidth;
217        opts.mStartHeight = startHeight;
218        return opts;
219    }
220
221    /**
222     * Create an ActivityOptions specifying an animation where a thumbnail
223     * is scaled from a given position to the new activity window that is
224     * being started.
225     *
226     * <p>If the Intent this is being used with has not set its
227     * {@link android.content.Intent#setSourceBounds Intent.setSourceBounds},
228     * those bounds will be filled in for you based on the initial
229     * thumbnail location and size provided here.
230     *
231     * @param source The View that this thumbnail is animating from.  This
232     * defines the coordinate space for <var>startX</var> and <var>startY</var>.
233     * @param thumbnail The bitmap that will be shown as the initial thumbnail
234     * of the animation.
235     * @param startX The x starting location of the bitmap, relative to <var>source</var>.
236     * @param startY The y starting location of the bitmap, relative to <var>source</var>.
237     * @return Returns a new ActivityOptions object that you can use to
238     * supply these options as the options Bundle when starting an activity.
239     */
240    public static ActivityOptions makeThumbnailScaleUpAnimation(View source,
241            Bitmap thumbnail, int startX, int startY) {
242        return makeThumbnailScaleUpAnimation(source, thumbnail, startX, startY, null);
243    }
244
245    /**
246     * Create an ActivityOptions specifying an animation where a thumbnail
247     * is scaled from a given position to the new activity window that is
248     * being started.
249     *
250     * @param source The View that this thumbnail is animating from.  This
251     * defines the coordinate space for <var>startX</var> and <var>startY</var>.
252     * @param thumbnail The bitmap that will be shown as the initial thumbnail
253     * of the animation.
254     * @param startX The x starting location of the bitmap, relative to <var>source</var>.
255     * @param startY The y starting location of the bitmap, relative to <var>source</var>.
256     * @param listener Optional OnAnimationStartedListener to find out when the
257     * requested animation has started running.  If for some reason the animation
258     * is not executed, the callback will happen immediately.
259     * @return Returns a new ActivityOptions object that you can use to
260     * supply these options as the options Bundle when starting an activity.
261     * @hide
262     */
263    public static ActivityOptions makeThumbnailScaleUpAnimation(View source,
264            Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener) {
265        return makeThumbnailAnimation(source, thumbnail, startX, startY, listener, true);
266    }
267
268    /**
269     * Create an ActivityOptions specifying an animation where an activity window
270     * is scaled from a given position to a thumbnail at a specified location.
271     *
272     * @param source The View that this thumbnail is animating to.  This
273     * defines the coordinate space for <var>startX</var> and <var>startY</var>.
274     * @param thumbnail The bitmap that will be shown as the final thumbnail
275     * of the animation.
276     * @param startX The x end location of the bitmap, relative to <var>source</var>.
277     * @param startY The y end location of the bitmap, relative to <var>source</var>.
278     * @param listener Optional OnAnimationStartedListener to find out when the
279     * requested animation has started running.  If for some reason the animation
280     * is not executed, the callback will happen immediately.
281     * @return Returns a new ActivityOptions object that you can use to
282     * supply these options as the options Bundle when starting an activity.
283     * @hide
284     */
285    public static ActivityOptions makeThumbnailScaleDownAnimation(View source,
286            Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener) {
287        return makeThumbnailAnimation(source, thumbnail, startX, startY, listener, false);
288    }
289
290    private static ActivityOptions makeThumbnailAnimation(View source,
291            Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener,
292            boolean scaleUp) {
293        ActivityOptions opts = new ActivityOptions();
294        opts.mPackageName = source.getContext().getPackageName();
295        opts.mAnimationType = scaleUp ? ANIM_THUMBNAIL_SCALE_UP : ANIM_THUMBNAIL_SCALE_DOWN;
296        opts.mThumbnail = thumbnail;
297        int[] pts = new int[2];
298        source.getLocationOnScreen(pts);
299        opts.mStartX = pts[0] + startX;
300        opts.mStartY = pts[1] + startY;
301        opts.setListener(source.getHandler(), listener);
302        return opts;
303    }
304
305    private ActivityOptions() {
306    }
307
308    /** @hide */
309    public ActivityOptions(Bundle opts) {
310        mPackageName = opts.getString(KEY_PACKAGE_NAME);
311        mAnimationType = opts.getInt(KEY_ANIM_TYPE);
312        if (mAnimationType == ANIM_CUSTOM) {
313            mCustomEnterResId = opts.getInt(KEY_ANIM_ENTER_RES_ID, 0);
314            mCustomExitResId = opts.getInt(KEY_ANIM_EXIT_RES_ID, 0);
315            mAnimationStartedListener = IRemoteCallback.Stub.asInterface(
316                    opts.getIBinder(KEY_ANIM_START_LISTENER));
317        } else if (mAnimationType == ANIM_SCALE_UP) {
318            mStartX = opts.getInt(KEY_ANIM_START_X, 0);
319            mStartY = opts.getInt(KEY_ANIM_START_Y, 0);
320            mStartWidth = opts.getInt(KEY_ANIM_START_WIDTH, 0);
321            mStartHeight = opts.getInt(KEY_ANIM_START_HEIGHT, 0);
322        } else if (mAnimationType == ANIM_THUMBNAIL_SCALE_UP ||
323                mAnimationType == ANIM_THUMBNAIL_SCALE_DOWN) {
324            mThumbnail = (Bitmap)opts.getParcelable(KEY_ANIM_THUMBNAIL);
325            mStartX = opts.getInt(KEY_ANIM_START_X, 0);
326            mStartY = opts.getInt(KEY_ANIM_START_Y, 0);
327            mAnimationStartedListener = IRemoteCallback.Stub.asInterface(
328                    opts.getIBinder(KEY_ANIM_START_LISTENER));
329        }
330    }
331
332    /** @hide */
333    public String getPackageName() {
334        return mPackageName;
335    }
336
337    /** @hide */
338    public int getAnimationType() {
339        return mAnimationType;
340    }
341
342    /** @hide */
343    public int getCustomEnterResId() {
344        return mCustomEnterResId;
345    }
346
347    /** @hide */
348    public int getCustomExitResId() {
349        return mCustomExitResId;
350    }
351
352    /** @hide */
353    public Bitmap getThumbnail() {
354        return mThumbnail;
355    }
356
357    /** @hide */
358    public int getStartX() {
359        return mStartX;
360    }
361
362    /** @hide */
363    public int getStartY() {
364        return mStartY;
365    }
366
367    /** @hide */
368    public int getStartWidth() {
369        return mStartWidth;
370    }
371
372    /** @hide */
373    public int getStartHeight() {
374        return mStartHeight;
375    }
376
377    /** @hide */
378    public IRemoteCallback getOnAnimationStartListener() {
379        return mAnimationStartedListener;
380    }
381
382    /** @hide */
383    public void abort() {
384        if (mAnimationStartedListener != null) {
385            try {
386                mAnimationStartedListener.sendResult(null);
387            } catch (RemoteException e) {
388            }
389        }
390    }
391
392    /** @hide */
393    public static void abort(Bundle options) {
394        if (options != null) {
395            (new ActivityOptions(options)).abort();
396        }
397    }
398
399    /**
400     * Update the current values in this ActivityOptions from those supplied
401     * in <var>otherOptions</var>.  Any values
402     * defined in <var>otherOptions</var> replace those in the base options.
403     */
404    public void update(ActivityOptions otherOptions) {
405        if (otherOptions.mPackageName != null) {
406            mPackageName = otherOptions.mPackageName;
407        }
408        switch (otherOptions.mAnimationType) {
409            case ANIM_CUSTOM:
410                mAnimationType = otherOptions.mAnimationType;
411                mCustomEnterResId = otherOptions.mCustomEnterResId;
412                mCustomExitResId = otherOptions.mCustomExitResId;
413                mThumbnail = null;
414                if (otherOptions.mAnimationStartedListener != null) {
415                    try {
416                        otherOptions.mAnimationStartedListener.sendResult(null);
417                    } catch (RemoteException e) {
418                    }
419                }
420                mAnimationStartedListener = otherOptions.mAnimationStartedListener;
421                break;
422            case ANIM_SCALE_UP:
423                mAnimationType = otherOptions.mAnimationType;
424                mStartX = otherOptions.mStartX;
425                mStartY = otherOptions.mStartY;
426                mStartWidth = otherOptions.mStartWidth;
427                mStartHeight = otherOptions.mStartHeight;
428                if (otherOptions.mAnimationStartedListener != null) {
429                    try {
430                        otherOptions.mAnimationStartedListener.sendResult(null);
431                    } catch (RemoteException e) {
432                    }
433                }
434                mAnimationStartedListener = null;
435                break;
436            case ANIM_THUMBNAIL_SCALE_UP:
437            case ANIM_THUMBNAIL_SCALE_DOWN:
438                mAnimationType = otherOptions.mAnimationType;
439                mThumbnail = otherOptions.mThumbnail;
440                mStartX = otherOptions.mStartX;
441                mStartY = otherOptions.mStartY;
442                if (otherOptions.mAnimationStartedListener != null) {
443                    try {
444                        otherOptions.mAnimationStartedListener.sendResult(null);
445                    } catch (RemoteException e) {
446                    }
447                }
448                mAnimationStartedListener = otherOptions.mAnimationStartedListener;
449                break;
450        }
451    }
452
453    /**
454     * Returns the created options as a Bundle, which can be passed to
455     * {@link android.content.Context#startActivity(android.content.Intent, android.os.Bundle)
456     * Context.startActivity(Intent, Bundle)} and related methods.
457     * Note that the returned Bundle is still owned by the ActivityOptions
458     * object; you must not modify it, but can supply it to the startActivity
459     * methods that take an options Bundle.
460     */
461    public Bundle toBundle() {
462        Bundle b = new Bundle();
463        if (mPackageName != null) {
464            b.putString(KEY_PACKAGE_NAME, mPackageName);
465        }
466        switch (mAnimationType) {
467            case ANIM_CUSTOM:
468                b.putInt(KEY_ANIM_TYPE, mAnimationType);
469                b.putInt(KEY_ANIM_ENTER_RES_ID, mCustomEnterResId);
470                b.putInt(KEY_ANIM_EXIT_RES_ID, mCustomExitResId);
471                b.putIBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
472                        != null ? mAnimationStartedListener.asBinder() : null);
473                break;
474            case ANIM_SCALE_UP:
475                b.putInt(KEY_ANIM_TYPE, mAnimationType);
476                b.putInt(KEY_ANIM_START_X, mStartX);
477                b.putInt(KEY_ANIM_START_Y, mStartY);
478                b.putInt(KEY_ANIM_START_WIDTH, mStartWidth);
479                b.putInt(KEY_ANIM_START_HEIGHT, mStartHeight);
480                break;
481            case ANIM_THUMBNAIL_SCALE_UP:
482            case ANIM_THUMBNAIL_SCALE_DOWN:
483                b.putInt(KEY_ANIM_TYPE, mAnimationType);
484                b.putParcelable(KEY_ANIM_THUMBNAIL, mThumbnail);
485                b.putInt(KEY_ANIM_START_X, mStartX);
486                b.putInt(KEY_ANIM_START_Y, mStartY);
487                b.putIBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
488                        != null ? mAnimationStartedListener.asBinder() : null);
489                break;
490        }
491        return b;
492    }
493}
494