ExitTransitionCoordinator.java revision 0f0c473488f8e8becc8883d3cdd9610324610dc4
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 SharedElementListener 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);
130        showViews(mSharedElements);
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        getDecor().getViewTreeObserver()
169                .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
170                    @Override
171                    public boolean onPreDraw() {
172                        getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
173                        setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots);
174                        return true;
175                    }
176                });
177        setGhostVisibility(View.INVISIBLE);
178        scheduleGhostVisibilityChange(View.INVISIBLE);
179        mListener.setSharedElementEnd(mSharedElementNames, mSharedElements, sharedElementSnapshots);
180        TransitionManager.beginDelayedTransition(getDecor(), transition);
181        scheduleGhostVisibilityChange(View.VISIBLE);
182        setGhostVisibility(View.VISIBLE);
183        getDecor().invalidate();
184    }
185
186    private void hideSharedElements() {
187        moveSharedElementsFromOverlay();
188        if (!mIsHidden) {
189            hideViews(mSharedElements);
190        }
191        mSharedElementsHidden = true;
192        finishIfNecessary();
193    }
194
195    public void startExit() {
196        if (!mIsExitStarted) {
197            mIsExitStarted = true;
198            if (getDecor() != null) {
199                getDecor().suppressLayout(true);
200            }
201            moveSharedElementsToOverlay();
202            startTransition(new Runnable() {
203                @Override
204                public void run() {
205                    beginTransitions();
206                }
207            });
208        }
209    }
210
211    public void startExit(int resultCode, Intent data) {
212        if (!mIsExitStarted) {
213            mIsExitStarted = true;
214            if (getDecor() != null) {
215                getDecor().suppressLayout(true);
216            }
217            mHandler = new Handler() {
218                @Override
219                public void handleMessage(Message msg) {
220                    mIsCanceled = true;
221                    finish();
222                }
223            };
224            delayCancel();
225            moveSharedElementsToOverlay();
226            if (getDecor().getBackground() == null) {
227                getWindow().setBackgroundDrawable(new ColorDrawable(Color.BLACK));
228            }
229            ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(mActivity, this,
230                    mAllSharedElementNames, resultCode, data);
231            mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() {
232                @Override
233                public void onTranslucentConversionComplete(boolean drawComplete) {
234                    if (!mIsCanceled) {
235                        fadeOutBackground();
236                    }
237                }
238            }, options);
239            startTransition(new Runnable() {
240                @Override
241                public void run() {
242                    startExitTransition();
243                }
244            });
245        }
246    }
247
248    private void startExitTransition() {
249        Transition transition = getExitTransition();
250        if (transition != null) {
251            TransitionManager.beginDelayedTransition(getDecor(), transition);
252            mTransitioningViews.get(0).invalidate();
253        } else {
254            transitionStarted();
255        }
256    }
257
258    private void fadeOutBackground() {
259        if (mBackgroundAnimator == null) {
260            ViewGroup decor = getDecor();
261            Drawable background;
262            if (decor != null && (background = decor.getBackground()) != null) {
263                background = background.mutate();
264                getWindow().setBackgroundDrawable(background);
265                mBackgroundAnimator = ObjectAnimator.ofInt(background, "alpha", 0);
266                mBackgroundAnimator.addListener(new AnimatorListenerAdapter() {
267                    @Override
268                    public void onAnimationEnd(Animator animation) {
269                        mBackgroundAnimator = null;
270                        if (!mIsCanceled) {
271                            mIsBackgroundReady = true;
272                            notifyComplete();
273                        }
274                    }
275                });
276                mBackgroundAnimator.setDuration(getFadeDuration());
277                mBackgroundAnimator.start();
278            } else {
279                mIsBackgroundReady = true;
280            }
281        }
282    }
283
284    private Transition getExitTransition() {
285        Transition viewsTransition = null;
286        if (!mTransitioningViews.isEmpty()) {
287            viewsTransition = configureTransition(getViewsTransition(), true);
288        }
289        if (viewsTransition == null) {
290            exitTransitionComplete();
291        } else {
292            viewsTransition.addListener(new ContinueTransitionListener() {
293                @Override
294                public void onTransitionEnd(Transition transition) {
295                    transition.removeListener(this);
296                    exitTransitionComplete();
297                    if (mIsHidden) {
298                        showViews(mTransitioningViews);
299                    }
300                    if (mSharedElementBundle != null) {
301                        delayCancel();
302                    }
303                    super.onTransitionEnd(transition);
304                }
305            });
306            viewsTransition.forceVisibility(View.INVISIBLE, false);
307        }
308        return viewsTransition;
309    }
310
311    private Transition getSharedElementExitTransition() {
312        Transition sharedElementTransition = null;
313        if (!mSharedElements.isEmpty()) {
314            sharedElementTransition = configureTransition(getSharedElementTransition(), false);
315        }
316        if (sharedElementTransition == null) {
317            sharedElementTransitionComplete();
318        } else {
319            sharedElementTransition.addListener(new ContinueTransitionListener() {
320                @Override
321                public void onTransitionEnd(Transition transition) {
322                    transition.removeListener(this);
323                    sharedElementTransitionComplete();
324                    if (mIsHidden) {
325                        showViews(mSharedElements);
326                    }
327                }
328            });
329            mSharedElements.get(0).invalidate();
330        }
331        return sharedElementTransition;
332    }
333
334    private void beginTransitions() {
335        Transition sharedElementTransition = getSharedElementExitTransition();
336        Transition viewsTransition = getExitTransition();
337
338        Transition transition = mergeTransitions(sharedElementTransition, viewsTransition);
339        if (transition != null) {
340            setGhostVisibility(View.INVISIBLE);
341            scheduleGhostVisibilityChange(View.INVISIBLE);
342            TransitionManager.beginDelayedTransition(getDecor(), transition);
343            scheduleGhostVisibilityChange(View.VISIBLE);
344            setGhostVisibility(View.VISIBLE);
345            getDecor().invalidate();
346        } else {
347            transitionStarted();
348        }
349    }
350
351    private void exitTransitionComplete() {
352        mExitComplete = true;
353        notifyComplete();
354    }
355
356    protected boolean isReadyToNotify() {
357        return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady;
358    }
359
360    private void sharedElementTransitionComplete() {
361        mSharedElementBundle = mExitSharedElementBundle == null
362                ? captureSharedElementState() : captureExitSharedElementsState();
363        notifyComplete();
364    }
365
366    private Bundle captureExitSharedElementsState() {
367        Bundle bundle = new Bundle();
368        RectF bounds = new RectF();
369        Matrix matrix = new Matrix();
370        for (int i = 0; i < mSharedElements.size(); i++) {
371            String name = mSharedElementNames.get(i);
372            Bundle sharedElementState = mExitSharedElementBundle.getBundle(name);
373            if (sharedElementState != null) {
374                bundle.putBundle(name, sharedElementState);
375            } else {
376                View view = mSharedElements.get(i);
377                captureSharedElementState(view, name, bundle, matrix, bounds);
378            }
379        }
380        return bundle;
381    }
382
383    protected void notifyComplete() {
384        if (isReadyToNotify()) {
385            if (!mSharedElementNotified) {
386                mSharedElementNotified = true;
387                delayCancel();
388                mResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, mSharedElementBundle);
389            }
390            if (!mExitNotified && mExitComplete) {
391                mExitNotified = true;
392                mResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null);
393                mResultReceiver = null; // done talking
394                if (!mIsReturning && getDecor() != null) {
395                    getDecor().suppressLayout(false);
396                }
397                finishIfNecessary();
398            }
399        }
400    }
401
402    private void finishIfNecessary() {
403        if (mIsReturning && mExitNotified && mActivity != null && (mSharedElements.isEmpty() ||
404                mSharedElementsHidden)) {
405            finish();
406        }
407        if (!mIsReturning && mExitNotified) {
408            mActivity = null; // don't need it anymore
409        }
410    }
411
412    private void finish() {
413        stopCancel();
414        mActivity.mActivityTransitionState.clear();
415        // Clear the state so that we can't hold any references accidentally and leak memory.
416        mHandler.removeMessages(MSG_CANCEL);
417        mHandler = null;
418        mActivity.finish();
419        mActivity.overridePendingTransition(0, 0);
420        mActivity = null;
421        mSharedElementBundle = null;
422        if (mBackgroundAnimator != null) {
423            mBackgroundAnimator.cancel();
424            mBackgroundAnimator = null;
425        }
426        mExitSharedElementBundle = null;
427        clearState();
428    }
429
430    @Override
431    protected boolean moveSharedElementWithParent() {
432        return !mIsReturning;
433    }
434
435    @Override
436    protected Transition getViewsTransition() {
437        if (mIsReturning) {
438            return getWindow().getReturnTransition();
439        } else {
440            return getWindow().getExitTransition();
441        }
442    }
443
444    protected Transition getSharedElementTransition() {
445        if (mIsReturning) {
446            return getWindow().getSharedElementReturnTransition();
447        } else {
448            return getWindow().getSharedElementExitTransition();
449        }
450    }
451}
452