ExitTransitionCoordinator.java revision ce2ee3d6de670e52abed7f432778701113ba9c07
1/*
2 * Copyright (C) 2014 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 */
16package android.app;
17
18import android.animation.Animator;
19import android.animation.AnimatorListenerAdapter;
20import android.animation.ObjectAnimator;
21import android.content.Intent;
22import android.graphics.Color;
23import android.graphics.Matrix;
24import android.graphics.RectF;
25import android.graphics.drawable.ColorDrawable;
26import android.graphics.drawable.Drawable;
27import android.os.Bundle;
28import android.os.Handler;
29import android.os.Message;
30import android.transition.Transition;
31import android.transition.TransitionManager;
32import android.view.View;
33import android.view.ViewGroup;
34import android.view.ViewTreeObserver;
35
36import java.util.ArrayList;
37
38/**
39 * This ActivityTransitionCoordinator is created in ActivityOptions#makeSceneTransitionAnimation
40 * to govern the exit of the Scene and the shared elements when calling an Activity as well as
41 * the reentry of the Scene when coming back from the called Activity.
42 */
43class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
44    private static final String TAG = "ExitTransitionCoordinator";
45    private static final long MAX_WAIT_MS = 1000;
46
47    private boolean mExitComplete;
48
49    private Bundle mSharedElementBundle;
50
51    private boolean mExitNotified;
52
53    private boolean mSharedElementNotified;
54
55    private Activity mActivity;
56
57    private boolean mIsBackgroundReady;
58
59    private boolean mIsCanceled;
60
61    private Handler mHandler;
62
63    private ObjectAnimator mBackgroundAnimator;
64
65    private boolean mIsHidden;
66
67    private Bundle mExitSharedElementBundle;
68
69    private boolean mIsExitStarted;
70
71    private boolean mSharedElementsHidden;
72
73    public ExitTransitionCoordinator(Activity activity, ArrayList<String> names,
74            ArrayList<String> accepted, ArrayList<View> mapped, boolean isReturning) {
75        super(activity.getWindow(), names, getListener(activity, isReturning), isReturning);
76        viewsReady(mapSharedElements(accepted, mapped));
77        stripOffscreenViews();
78        mIsBackgroundReady = !isReturning;
79        mActivity = activity;
80    }
81
82    private static SharedElementCallback getListener(Activity activity, boolean isReturning) {
83        return isReturning ? activity.mEnterTransitionListener : activity.mExitTransitionListener;
84    }
85
86    @Override
87    protected void onReceiveResult(int resultCode, Bundle resultData) {
88        switch (resultCode) {
89            case MSG_SET_REMOTE_RECEIVER:
90                stopCancel();
91                mResultReceiver = resultData.getParcelable(KEY_REMOTE_RECEIVER);
92                if (mIsCanceled) {
93                    mResultReceiver.send(MSG_CANCEL, null);
94                    mResultReceiver = null;
95                } else {
96                    notifyComplete();
97                }
98                break;
99            case MSG_HIDE_SHARED_ELEMENTS:
100                stopCancel();
101                if (!mIsCanceled) {
102                    hideSharedElements();
103                }
104                break;
105            case MSG_START_EXIT_TRANSITION:
106                mHandler.removeMessages(MSG_CANCEL);
107                startExit();
108                break;
109            case MSG_SHARED_ELEMENT_DESTINATION:
110                mExitSharedElementBundle = resultData;
111                sharedElementExitBack();
112                break;
113        }
114    }
115
116    private void stopCancel() {
117        if (mHandler != null) {
118            mHandler.removeMessages(MSG_CANCEL);
119        }
120    }
121
122    private void delayCancel() {
123        if (mHandler != null) {
124            mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS);
125        }
126    }
127
128    public void resetViews() {
129        showViews(mTransitioningViews, true);
130        showViews(mSharedElements, true);
131        mIsHidden = true;
132        if (!mIsReturning && getDecor() != null) {
133            getDecor().suppressLayout(false);
134        }
135        moveSharedElementsFromOverlay();
136        clearState();
137    }
138
139    private void sharedElementExitBack() {
140        if (getDecor() != null) {
141            getDecor().suppressLayout(true);
142        }
143        if (mExitSharedElementBundle != null && !mExitSharedElementBundle.isEmpty() &&
144                !mSharedElements.isEmpty() && getSharedElementTransition() != null) {
145            startTransition(new Runnable() {
146                public void run() {
147                    startSharedElementExit();
148                }
149            });
150        } else {
151            sharedElementTransitionComplete();
152        }
153    }
154
155    private void startSharedElementExit() {
156        Transition transition = getSharedElementExitTransition();
157        transition.addListener(new Transition.TransitionListenerAdapter() {
158            @Override
159            public void onTransitionEnd(Transition transition) {
160                transition.removeListener(this);
161                if (mExitComplete) {
162                    delayCancel();
163                }
164            }
165        });
166        final ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle,
167                mSharedElementNames);
168        final View decorView = getDecor();
169        decorView.getViewTreeObserver()
170                .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
171                    @Override
172                    public boolean onPreDraw() {
173                        decorView.getViewTreeObserver().removeOnPreDrawListener(this);
174                        setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots);
175                        return true;
176                    }
177                });
178        setGhostVisibility(View.INVISIBLE);
179        scheduleGhostVisibilityChange(View.INVISIBLE);
180        mListener.onSharedElementEnd(mSharedElementNames, mSharedElements, sharedElementSnapshots);
181        TransitionManager.beginDelayedTransition(getDecor(), transition);
182        scheduleGhostVisibilityChange(View.VISIBLE);
183        setGhostVisibility(View.VISIBLE);
184        getDecor().invalidate();
185    }
186
187    private void hideSharedElements() {
188        moveSharedElementsFromOverlay();
189        if (!mIsHidden) {
190            hideViews(mSharedElements);
191        }
192        mSharedElementsHidden = true;
193        finishIfNecessary();
194    }
195
196    public void startExit() {
197        if (!mIsExitStarted) {
198            mIsExitStarted = true;
199            if (getDecor() != null) {
200                getDecor().suppressLayout(true);
201            }
202            moveSharedElementsToOverlay();
203            startTransition(new Runnable() {
204                @Override
205                public void run() {
206                    beginTransitions();
207                }
208            });
209        }
210    }
211
212    public void startExit(int resultCode, Intent data) {
213        if (!mIsExitStarted) {
214            mIsExitStarted = true;
215            if (getDecor() != null) {
216                getDecor().suppressLayout(true);
217            }
218            mHandler = new Handler() {
219                @Override
220                public void handleMessage(Message msg) {
221                    mIsCanceled = true;
222                    finish();
223                }
224            };
225            delayCancel();
226            moveSharedElementsToOverlay();
227            if (getDecor().getBackground() == null) {
228                getWindow().setBackgroundDrawable(new ColorDrawable(Color.BLACK));
229            }
230            ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(mActivity, this,
231                    mAllSharedElementNames, resultCode, data);
232            mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() {
233                @Override
234                public void onTranslucentConversionComplete(boolean drawComplete) {
235                    if (!mIsCanceled) {
236                        fadeOutBackground();
237                    }
238                }
239            }, options);
240            startTransition(new Runnable() {
241                @Override
242                public void run() {
243                    startExitTransition();
244                }
245            });
246        }
247    }
248
249    private void startExitTransition() {
250        Transition transition = getExitTransition();
251        if (transition != null) {
252            TransitionManager.beginDelayedTransition(getDecor(), transition);
253            mTransitioningViews.get(0).invalidate();
254        } else {
255            transitionStarted();
256        }
257    }
258
259    private void fadeOutBackground() {
260        if (mBackgroundAnimator == null) {
261            ViewGroup decor = getDecor();
262            Drawable background;
263            if (decor != null && (background = decor.getBackground()) != null) {
264                background = background.mutate();
265                getWindow().setBackgroundDrawable(background);
266                mBackgroundAnimator = ObjectAnimator.ofInt(background, "alpha", 0);
267                mBackgroundAnimator.addListener(new AnimatorListenerAdapter() {
268                    @Override
269                    public void onAnimationEnd(Animator animation) {
270                        mBackgroundAnimator = null;
271                        if (!mIsCanceled) {
272                            mIsBackgroundReady = true;
273                            notifyComplete();
274                        }
275                    }
276                });
277                mBackgroundAnimator.setDuration(getFadeDuration());
278                mBackgroundAnimator.start();
279            } else {
280                mIsBackgroundReady = true;
281            }
282        }
283    }
284
285    private Transition getExitTransition() {
286        Transition viewsTransition = null;
287        if (!mTransitioningViews.isEmpty()) {
288            viewsTransition = configureTransition(getViewsTransition(), true);
289        }
290        if (viewsTransition == null) {
291            exitTransitionComplete();
292        } else {
293            viewsTransition.addListener(new ContinueTransitionListener() {
294                @Override
295                public void onTransitionEnd(Transition transition) {
296                    transition.removeListener(this);
297                    exitTransitionComplete();
298                    if (mIsHidden) {
299                        showViews(mTransitioningViews, true);
300                    }
301                    if (mSharedElementBundle != null) {
302                        delayCancel();
303                    }
304                    super.onTransitionEnd(transition);
305                }
306            });
307            viewsTransition.forceVisibility(View.INVISIBLE, false);
308        }
309        return viewsTransition;
310    }
311
312    private Transition getSharedElementExitTransition() {
313        Transition sharedElementTransition = null;
314        if (!mSharedElements.isEmpty()) {
315            sharedElementTransition = configureTransition(getSharedElementTransition(), false);
316        }
317        if (sharedElementTransition == null) {
318            sharedElementTransitionComplete();
319        } else {
320            sharedElementTransition.addListener(new ContinueTransitionListener() {
321                @Override
322                public void onTransitionEnd(Transition transition) {
323                    transition.removeListener(this);
324                    sharedElementTransitionComplete();
325                    if (mIsHidden) {
326                        showViews(mSharedElements, true);
327                    }
328                }
329            });
330            mSharedElements.get(0).invalidate();
331        }
332        return sharedElementTransition;
333    }
334
335    private void beginTransitions() {
336        Transition sharedElementTransition = getSharedElementExitTransition();
337        Transition viewsTransition = getExitTransition();
338
339        Transition transition = mergeTransitions(sharedElementTransition, viewsTransition);
340        if (transition != null) {
341            setGhostVisibility(View.INVISIBLE);
342            scheduleGhostVisibilityChange(View.INVISIBLE);
343            TransitionManager.beginDelayedTransition(getDecor(), transition);
344            scheduleGhostVisibilityChange(View.VISIBLE);
345            setGhostVisibility(View.VISIBLE);
346            getDecor().invalidate();
347        } else {
348            transitionStarted();
349        }
350    }
351
352    private void exitTransitionComplete() {
353        mExitComplete = true;
354        notifyComplete();
355    }
356
357    protected boolean isReadyToNotify() {
358        return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady;
359    }
360
361    private void sharedElementTransitionComplete() {
362        mSharedElementBundle = mExitSharedElementBundle == null
363                ? captureSharedElementState() : captureExitSharedElementsState();
364        notifyComplete();
365    }
366
367    private Bundle captureExitSharedElementsState() {
368        Bundle bundle = new Bundle();
369        RectF bounds = new RectF();
370        Matrix matrix = new Matrix();
371        for (int i = 0; i < mSharedElements.size(); i++) {
372            String name = mSharedElementNames.get(i);
373            Bundle sharedElementState = mExitSharedElementBundle.getBundle(name);
374            if (sharedElementState != null) {
375                bundle.putBundle(name, sharedElementState);
376            } else {
377                View view = mSharedElements.get(i);
378                captureSharedElementState(view, name, bundle, matrix, bounds);
379            }
380        }
381        return bundle;
382    }
383
384    protected void notifyComplete() {
385        if (isReadyToNotify()) {
386            if (!mSharedElementNotified) {
387                mSharedElementNotified = true;
388                delayCancel();
389                mResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, mSharedElementBundle);
390            }
391            if (!mExitNotified && mExitComplete) {
392                mExitNotified = true;
393                mResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null);
394                mResultReceiver = null; // done talking
395                if (!mIsReturning && getDecor() != null) {
396                    getDecor().suppressLayout(false);
397                }
398                finishIfNecessary();
399            }
400        }
401    }
402
403    private void finishIfNecessary() {
404        if (mIsReturning && mExitNotified && mActivity != null && (mSharedElements.isEmpty() ||
405                mSharedElementsHidden)) {
406            finish();
407        }
408        if (!mIsReturning && mExitNotified) {
409            mActivity = null; // don't need it anymore
410        }
411    }
412
413    private void finish() {
414        stopCancel();
415        mActivity.mActivityTransitionState.clear();
416        // Clear the state so that we can't hold any references accidentally and leak memory.
417        mHandler.removeMessages(MSG_CANCEL);
418        mHandler = null;
419        mActivity.finish();
420        mActivity.overridePendingTransition(0, 0);
421        mActivity = null;
422        mSharedElementBundle = null;
423        if (mBackgroundAnimator != null) {
424            mBackgroundAnimator.cancel();
425            mBackgroundAnimator = null;
426        }
427        mExitSharedElementBundle = null;
428        clearState();
429    }
430
431    @Override
432    protected boolean moveSharedElementWithParent() {
433        return !mIsReturning;
434    }
435
436    @Override
437    protected Transition getViewsTransition() {
438        if (mIsReturning) {
439            return getWindow().getReturnTransition();
440        } else {
441            return getWindow().getExitTransition();
442        }
443    }
444
445    protected Transition getSharedElementTransition() {
446        if (mIsReturning) {
447            return getWindow().getSharedElementReturnTransition();
448        } else {
449            return getWindow().getSharedElementExitTransition();
450        }
451    }
452}
453