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