ExitTransitionCoordinator.java revision b5ef7f8c6d4629b2998de6c0b27bc1a4779b3e49
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.Rect;
23import android.graphics.drawable.ColorDrawable;
24import android.graphics.drawable.Drawable;
25import android.os.Bundle;
26import android.os.Handler;
27import android.os.Message;
28import android.transition.Transition;
29import android.transition.TransitionManager;
30import android.util.ArrayMap;
31import android.view.View;
32import android.view.ViewGroup;
33import android.view.ViewGroupOverlay;
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),
76                isReturning);
77        viewsReady(mapSharedElements(accepted, mapped));
78        stripOffscreenViews();
79        mIsBackgroundReady = !isReturning;
80        mActivity = activity;
81    }
82
83    private static SharedElementListener getListener(Activity activity, boolean isReturning) {
84        return isReturning ? activity.mEnterTransitionListener : activity.mExitTransitionListener;
85    }
86
87    @Override
88    protected void onReceiveResult(int resultCode, Bundle resultData) {
89        switch (resultCode) {
90            case MSG_SET_REMOTE_RECEIVER:
91                mResultReceiver = resultData.getParcelable(KEY_REMOTE_RECEIVER);
92                if (mIsCanceled) {
93                    mResultReceiver.send(MSG_CANCEL, null);
94                    mResultReceiver = null;
95                } else {
96                    if (mHandler != null) {
97                        mHandler.removeMessages(MSG_CANCEL);
98                    }
99                    notifyComplete();
100                }
101                break;
102            case MSG_HIDE_SHARED_ELEMENTS:
103                if (!mIsCanceled) {
104                    hideSharedElements();
105                }
106                break;
107            case MSG_START_EXIT_TRANSITION:
108                startExit();
109                break;
110            case MSG_SHARED_ELEMENT_DESTINATION:
111                mExitSharedElementBundle = resultData;
112                sharedElementExitBack();
113                break;
114        }
115    }
116
117    public void resetViews() {
118        setTransitionAlpha(mTransitioningViews, 1);
119        setTransitionAlpha(mSharedElements, 1);
120        mIsHidden = true;
121        clearState();
122    }
123
124    private void sharedElementExitBack() {
125        if (!mSharedElements.isEmpty() && getSharedElementTransition() != null) {
126            startTransition(new Runnable() {
127                public void run() {
128                    startSharedElementExit();
129                }
130            });
131        } else {
132            sharedElementTransitionComplete();
133        }
134    }
135
136    private void startSharedElementExit() {
137        Transition transition = getSharedElementExitTransition();
138        final ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle,
139                mSharedElementNames);
140        transition.addListener(new Transition.TransitionListenerAdapter() {
141            @Override
142            public void onTransitionEnd(Transition transition) {
143                transition.removeListener(this);
144                int count = mSharedElements.size();
145                for (int i = 0; i < count; i++) {
146                    View sharedElement = mSharedElements.get(i);
147                    ((ViewGroup)sharedElement.getParent()).suppressLayout(true);
148                }
149            }
150        });
151        getDecor().getViewTreeObserver()
152                .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
153                    @Override
154                    public boolean onPreDraw() {
155                        getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
156                        setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots);
157                        return true;
158                    }
159                });
160        TransitionManager.beginDelayedTransition(getDecor(), transition);
161        getDecor().invalidate();
162    }
163
164    private void hideSharedElements() {
165        if (!mIsHidden) {
166            setTransitionAlpha(mSharedElements, 0);
167        }
168        mSharedElementsHidden = true;
169        finishIfNecessary();
170    }
171
172    public void startExit() {
173        if (!mIsExitStarted) {
174            mIsExitStarted = true;
175            startTransition(new Runnable() {
176                @Override
177                public void run() {
178                    beginTransitions();
179                }
180            });
181        }
182    }
183
184    public void startExit(int resultCode, Intent data) {
185        if (!mIsExitStarted) {
186            mIsExitStarted = true;
187            mHandler = new Handler() {
188                @Override
189                public void handleMessage(Message msg) {
190                    mIsCanceled = true;
191                    finish();
192                }
193            };
194            mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS);
195            if (getDecor().getBackground() == null) {
196                ColorDrawable black = new ColorDrawable(0xFF000000);
197                black.setAlpha(0);
198                getWindow().setBackgroundDrawable(black);
199                black.setAlpha(255);
200            }
201            ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(mActivity, this,
202                    mAllSharedElementNames, resultCode, data);
203            mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() {
204                @Override
205                public void onTranslucentConversionComplete(boolean drawComplete) {
206                    if (!mIsCanceled) {
207                        fadeOutBackground();
208                    }
209                }
210            }, options);
211            startTransition(new Runnable() {
212                @Override
213                public void run() {
214                    startExitTransition();
215                }
216            });
217        }
218    }
219
220    private void startExitTransition() {
221        Transition transition = getExitTransition();
222        if (transition != null) {
223            TransitionManager.beginDelayedTransition(getDecor(), transition);
224            mTransitioningViews.get(0).invalidate();
225        }
226    }
227
228    private void fadeOutBackground() {
229        if (mBackgroundAnimator == null) {
230            ViewGroup decor = getDecor();
231            Drawable background;
232            if (decor != null && (background = decor.getBackground()) != null) {
233                mBackgroundAnimator = ObjectAnimator.ofInt(background, "alpha", 0);
234                mBackgroundAnimator.addListener(new AnimatorListenerAdapter() {
235                    @Override
236                    public void onAnimationEnd(Animator animation) {
237                        mBackgroundAnimator = null;
238                        if (!mIsCanceled) {
239                            mIsBackgroundReady = true;
240                            notifyComplete();
241                        }
242                    }
243                });
244                mBackgroundAnimator.setDuration(getFadeDuration());
245                mBackgroundAnimator.start();
246            } else {
247                mIsBackgroundReady = true;
248            }
249        }
250    }
251
252    private Transition getExitTransition() {
253        Transition viewsTransition = null;
254        if (!mTransitioningViews.isEmpty()) {
255            viewsTransition = configureTransition(getViewsTransition(), true);
256        }
257        if (viewsTransition == null) {
258            exitTransitionComplete();
259        } else {
260            viewsTransition.addListener(new ContinueTransitionListener() {
261                @Override
262                public void onTransitionEnd(Transition transition) {
263                    transition.removeListener(this);
264                    exitTransitionComplete();
265                    if (mIsHidden) {
266                        setTransitionAlpha(mTransitioningViews, 1);
267                    }
268                }
269
270                @Override
271                public void onTransitionCancel(Transition transition) {
272                    super.onTransitionCancel(transition);
273                }
274            });
275            viewsTransition.forceVisibility(View.INVISIBLE, false);
276        }
277        return viewsTransition;
278    }
279
280    private Transition getSharedElementExitTransition() {
281        Transition sharedElementTransition = null;
282        if (!mSharedElements.isEmpty()) {
283            sharedElementTransition = configureTransition(getSharedElementTransition(), false);
284        }
285        if (sharedElementTransition == null) {
286            sharedElementTransitionComplete();
287        } else {
288            sharedElementTransition.addListener(new ContinueTransitionListener() {
289                @Override
290                public void onTransitionEnd(Transition transition) {
291                    transition.removeListener(this);
292                    sharedElementTransitionComplete();
293                    if (mIsHidden) {
294                        setTransitionAlpha(mSharedElements, 1);
295                    }
296                }
297            });
298            mSharedElements.get(0).invalidate();
299        }
300        return sharedElementTransition;
301    }
302
303    private void beginTransitions() {
304        Transition sharedElementTransition = getSharedElementExitTransition();
305        Transition viewsTransition = getExitTransition();
306
307        Transition transition = mergeTransitions(sharedElementTransition, viewsTransition);
308        if (transition != null) {
309            TransitionManager.beginDelayedTransition(getDecor(), transition);
310            getDecor().invalidate();
311        }
312    }
313
314    private void exitTransitionComplete() {
315        mExitComplete = true;
316        notifyComplete();
317    }
318
319    protected boolean isReadyToNotify() {
320        return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady;
321    }
322
323    private void sharedElementTransitionComplete() {
324        mSharedElementBundle = mExitSharedElementBundle == null
325                ? captureSharedElementState() : captureExitSharedElementsState();
326        notifyComplete();
327    }
328
329    private Bundle captureExitSharedElementsState() {
330        Bundle bundle = new Bundle();
331        Rect bounds = new Rect();
332        for (int i = 0; i < mSharedElementNames.size(); i++) {
333            String name = mSharedElementNames.get(i);
334            Bundle sharedElementState = mExitSharedElementBundle.getBundle(name);
335            if (sharedElementState != null) {
336                bundle.putBundle(name, sharedElementState);
337            } else {
338                View view = mSharedElements.get(i);
339                captureSharedElementState(view, name, bundle, bounds);
340            }
341        }
342        return bundle;
343    }
344
345    protected void notifyComplete() {
346        if (isReadyToNotify()) {
347            if (!mSharedElementNotified) {
348                mSharedElementNotified = true;
349                mResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, mSharedElementBundle);
350            }
351            if (!mExitNotified && mExitComplete) {
352                mExitNotified = true;
353                mResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null);
354                mResultReceiver = null; // done talking
355                finishIfNecessary();
356            }
357        }
358    }
359
360    private void finishIfNecessary() {
361        if (mIsReturning && mExitNotified && mActivity != null && (mSharedElements.isEmpty() ||
362                mSharedElementsHidden)) {
363            finish();
364        }
365        if (!mIsReturning && mExitNotified) {
366            mActivity = null; // don't need it anymore
367        }
368    }
369
370    private void finish() {
371        mActivity.mActivityTransitionState.clear();
372        // Clear the state so that we can't hold any references accidentally and leak memory.
373        mHandler.removeMessages(MSG_CANCEL);
374        mHandler = null;
375        mActivity.finish();
376        mActivity.overridePendingTransition(0, 0);
377        mActivity = null;
378        mSharedElementBundle = null;
379        if (mBackgroundAnimator != null) {
380            mBackgroundAnimator.cancel();
381            mBackgroundAnimator = null;
382        }
383        mExitSharedElementBundle = null;
384        clearState();
385    }
386
387    @Override
388    protected Transition getViewsTransition() {
389        if (mIsReturning) {
390            return getWindow().getEnterTransition();
391        } else {
392            return getWindow().getExitTransition();
393        }
394    }
395
396    protected Transition getSharedElementTransition() {
397        if (mIsReturning) {
398            return getWindow().getSharedElementEnterTransition();
399        } else {
400            return getWindow().getSharedElementExitTransition();
401        }
402    }
403}
404