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