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