ExitTransitionCoordinator.java revision 7bf379c8af399626d5b8b568fe1d4f96f56badcc
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        clearState();
121    }
122
123    private void sharedElementExitBack() {
124        if (!mSharedElements.isEmpty() && getSharedElementTransition() != null) {
125            startTransition(new Runnable() {
126                public void run() {
127                    startSharedElementExit();
128                }
129            });
130        } else {
131            sharedElementTransitionComplete();
132        }
133    }
134
135    private void startSharedElementExit() {
136        Transition transition = getSharedElementExitTransition();
137        final ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle,
138                mSharedElementNames);
139        transition.addListener(new Transition.TransitionListenerAdapter() {
140            @Override
141            public void onTransitionEnd(Transition transition) {
142                transition.removeListener(this);
143                int count = mSharedElements.size();
144                for (int i = 0; i < count; i++) {
145                    View sharedElement = mSharedElements.get(i);
146                    ((ViewGroup)sharedElement.getParent()).suppressLayout(true);
147                }
148            }
149        });
150        getDecor().getViewTreeObserver()
151                .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
152                    @Override
153                    public boolean onPreDraw() {
154                        getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
155                        setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots);
156                        return true;
157                    }
158                });
159        TransitionManager.beginDelayedTransition(getDecor(), transition);
160        getDecor().invalidate();
161    }
162
163    private void hideSharedElements() {
164        if (!mIsHidden) {
165            setTransitionAlpha(mSharedElements, 0);
166        }
167        mSharedElementsHidden = true;
168        finishIfNecessary();
169    }
170
171    public void startExit() {
172        if (!mIsExitStarted) {
173            mIsExitStarted = true;
174            startTransition(new Runnable() {
175                @Override
176                public void run() {
177                    beginTransitions();
178                }
179            });
180        }
181    }
182
183    public void startExit(int resultCode, Intent data) {
184        if (!mIsExitStarted) {
185            mIsExitStarted = true;
186            mHandler = new Handler() {
187                @Override
188                public void handleMessage(Message msg) {
189                    mIsCanceled = true;
190                    finish();
191                }
192            };
193            mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS);
194            if (getDecor().getBackground() == null) {
195                ColorDrawable black = new ColorDrawable(0xFF000000);
196                black.setAlpha(0);
197                getWindow().setBackgroundDrawable(black);
198                black.setAlpha(255);
199            }
200            ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(mActivity, this,
201                    mAllSharedElementNames, resultCode, data);
202            mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() {
203                @Override
204                public void onTranslucentConversionComplete(boolean drawComplete) {
205                    if (!mIsCanceled) {
206                        fadeOutBackground();
207                    }
208                }
209            }, options);
210            startTransition(new Runnable() {
211                @Override
212                public void run() {
213                    startExitTransition();
214                }
215            });
216        }
217    }
218
219    private void startExitTransition() {
220        Transition transition = getExitTransition();
221        if (transition != null) {
222            TransitionManager.beginDelayedTransition(getDecor(), transition);
223            mTransitioningViews.get(0).invalidate();
224        }
225    }
226
227    private void fadeOutBackground() {
228        if (mBackgroundAnimator == null) {
229            ViewGroup decor = getDecor();
230            Drawable background;
231            if (decor != null && (background = decor.getBackground()) != null) {
232                mBackgroundAnimator = ObjectAnimator.ofInt(background, "alpha", 0);
233                mBackgroundAnimator.addListener(new AnimatorListenerAdapter() {
234                    @Override
235                    public void onAnimationEnd(Animator animation) {
236                        mBackgroundAnimator = null;
237                        if (!mIsCanceled) {
238                            mIsBackgroundReady = true;
239                            notifyComplete();
240                        }
241                    }
242                });
243                mBackgroundAnimator.setDuration(getFadeDuration());
244                mBackgroundAnimator.start();
245            } else {
246                mIsBackgroundReady = true;
247            }
248        }
249    }
250
251    private Transition getExitTransition() {
252        Transition viewsTransition = null;
253        if (!mTransitioningViews.isEmpty()) {
254            viewsTransition = configureTransition(getViewsTransition(), true);
255        }
256        if (viewsTransition == null) {
257            exitTransitionComplete();
258        } else {
259            viewsTransition.addListener(new ContinueTransitionListener() {
260                @Override
261                public void onTransitionEnd(Transition transition) {
262                    transition.removeListener(this);
263                    exitTransitionComplete();
264                    if (mIsHidden) {
265                        setTransitionAlpha(mTransitioningViews, 1);
266                    }
267                }
268
269                @Override
270                public void onTransitionCancel(Transition transition) {
271                    super.onTransitionCancel(transition);
272                }
273            });
274            viewsTransition.forceVisibility(View.INVISIBLE, false);
275        }
276        return viewsTransition;
277    }
278
279    private Transition getSharedElementExitTransition() {
280        Transition sharedElementTransition = null;
281        if (!mSharedElements.isEmpty()) {
282            sharedElementTransition = configureTransition(getSharedElementTransition(), false);
283        }
284        if (sharedElementTransition == null) {
285            sharedElementTransitionComplete();
286        } else {
287            sharedElementTransition.addListener(new ContinueTransitionListener() {
288                @Override
289                public void onTransitionEnd(Transition transition) {
290                    transition.removeListener(this);
291                    sharedElementTransitionComplete();
292                    if (mIsHidden) {
293                        setTransitionAlpha(mSharedElements, 1);
294                    }
295                }
296            });
297            mSharedElements.get(0).invalidate();
298        }
299        return sharedElementTransition;
300    }
301
302    private void beginTransitions() {
303        Transition sharedElementTransition = getSharedElementExitTransition();
304        Transition viewsTransition = getExitTransition();
305
306        Transition transition = mergeTransitions(sharedElementTransition, viewsTransition);
307        if (transition != null) {
308            TransitionManager.beginDelayedTransition(getDecor(), transition);
309            getDecor().invalidate();
310        }
311    }
312
313    private void exitTransitionComplete() {
314        mExitComplete = true;
315        notifyComplete();
316    }
317
318    protected boolean isReadyToNotify() {
319        return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady;
320    }
321
322    private void sharedElementTransitionComplete() {
323        mSharedElementBundle = mExitSharedElementBundle == null
324                ? captureSharedElementState() : captureExitSharedElementsState();
325        notifyComplete();
326    }
327
328    private Bundle captureExitSharedElementsState() {
329        Bundle bundle = new Bundle();
330        RectF bounds = new RectF();
331        Matrix matrix = new Matrix();
332        for (int i = 0; i < mSharedElements.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, matrix, 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