ExitTransitionCoordinator.java revision 995f4b5721e092478881b8929bdd235ec5855646
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.app.SharedElementCallback.OnSharedElementsReadyListener;
22import android.content.Intent;
23import android.graphics.Color;
24import android.graphics.Matrix;
25import android.graphics.RectF;
26import android.graphics.drawable.ColorDrawable;
27import android.graphics.drawable.Drawable;
28import android.os.Build.VERSION_CODES;
29import android.os.Bundle;
30import android.os.Handler;
31import android.os.Message;
32import android.os.ResultReceiver;
33import android.transition.Transition;
34import android.transition.TransitionListenerAdapter;
35import android.transition.TransitionManager;
36import android.view.View;
37import android.view.ViewGroup;
38import android.view.Window;
39
40import com.android.internal.view.OneShotPreDrawListener;
41
42import java.util.ArrayList;
43
44/**
45 * This ActivityTransitionCoordinator is created in ActivityOptions#makeSceneTransitionAnimation
46 * to govern the exit of the Scene and the shared elements when calling an Activity as well as
47 * the reentry of the Scene when coming back from the called Activity.
48 */
49class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
50    private static final String TAG = "ExitTransitionCoordinator";
51    private static final long MAX_WAIT_MS = 1000;
52
53    private Bundle mSharedElementBundle;
54    private boolean mExitNotified;
55    private boolean mSharedElementNotified;
56    private Activity mActivity;
57    private boolean mIsBackgroundReady;
58    private boolean mIsCanceled;
59    private Handler mHandler;
60    private ObjectAnimator mBackgroundAnimator;
61    private boolean mIsHidden;
62    private Bundle mExitSharedElementBundle;
63    private boolean mIsExitStarted;
64    private boolean mSharedElementsHidden;
65    private HideSharedElementsCallback mHideSharedElementsCallback;
66
67    public ExitTransitionCoordinator(Activity activity, Window window,
68            SharedElementCallback listener, ArrayList<String> names,
69            ArrayList<String> accepted, ArrayList<View> mapped, boolean isReturning) {
70        super(window, names, listener, isReturning);
71        viewsReady(mapSharedElements(accepted, mapped));
72        stripOffscreenViews();
73        mIsBackgroundReady = !isReturning;
74        mActivity = activity;
75    }
76
77    void setHideSharedElementsCallback(HideSharedElementsCallback callback) {
78        mHideSharedElementsCallback = callback;
79    }
80
81    @Override
82    protected void onReceiveResult(int resultCode, Bundle resultData) {
83        switch (resultCode) {
84            case MSG_SET_REMOTE_RECEIVER:
85                stopCancel();
86                mResultReceiver = resultData.getParcelable(KEY_REMOTE_RECEIVER);
87                if (mIsCanceled) {
88                    mResultReceiver.send(MSG_CANCEL, null);
89                    mResultReceiver = null;
90                } else {
91                    notifyComplete();
92                }
93                break;
94            case MSG_HIDE_SHARED_ELEMENTS:
95                stopCancel();
96                if (!mIsCanceled) {
97                    hideSharedElements();
98                }
99                break;
100            case MSG_START_EXIT_TRANSITION:
101                mHandler.removeMessages(MSG_CANCEL);
102                startExit();
103                break;
104            case MSG_SHARED_ELEMENT_DESTINATION:
105                mExitSharedElementBundle = resultData;
106                sharedElementExitBack();
107                break;
108            case MSG_CANCEL:
109                mIsCanceled = true;
110                finish();
111                break;
112        }
113    }
114
115    private void stopCancel() {
116        if (mHandler != null) {
117            mHandler.removeMessages(MSG_CANCEL);
118        }
119    }
120
121    private void delayCancel() {
122        if (mHandler != null) {
123            mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS);
124        }
125    }
126
127    public void resetViews() {
128        ViewGroup decorView = getDecor();
129        if (decorView != null) {
130            TransitionManager.endTransitions(decorView);
131        }
132        if (mTransitioningViews != null) {
133            showViews(mTransitioningViews, true);
134            setTransitioningViewsVisiblity(View.VISIBLE, true);
135        }
136        showViews(mSharedElements, true);
137        mIsHidden = true;
138        if (!mIsReturning && decorView != null) {
139            decorView.suppressLayout(false);
140        }
141        moveSharedElementsFromOverlay();
142        clearState();
143    }
144
145    private void sharedElementExitBack() {
146        final ViewGroup decorView = getDecor();
147        if (decorView != null) {
148            decorView.suppressLayout(true);
149        }
150        if (decorView != null && mExitSharedElementBundle != null &&
151                !mExitSharedElementBundle.isEmpty() &&
152                !mSharedElements.isEmpty() && getSharedElementTransition() != null) {
153            startTransition(new Runnable() {
154                public void run() {
155                    startSharedElementExit(decorView);
156                }
157            });
158        } else {
159            sharedElementTransitionComplete();
160        }
161    }
162
163    private void startSharedElementExit(final ViewGroup decorView) {
164        Transition transition = getSharedElementExitTransition();
165        transition.addListener(new TransitionListenerAdapter() {
166            @Override
167            public void onTransitionEnd(Transition transition) {
168                transition.removeListener(this);
169                if (isViewsTransitionComplete()) {
170                    delayCancel();
171                }
172            }
173        });
174        final ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle,
175                mSharedElementNames);
176        OneShotPreDrawListener.add(decorView, () -> {
177            setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots);
178        });
179        setGhostVisibility(View.INVISIBLE);
180        scheduleGhostVisibilityChange(View.INVISIBLE);
181        if (mListener != null) {
182            mListener.onSharedElementEnd(mSharedElementNames, mSharedElements,
183                    sharedElementSnapshots);
184        }
185        TransitionManager.beginDelayedTransition(decorView, transition);
186        scheduleGhostVisibilityChange(View.VISIBLE);
187        setGhostVisibility(View.VISIBLE);
188        decorView.invalidate();
189    }
190
191    private void hideSharedElements() {
192        moveSharedElementsFromOverlay();
193        if (mHideSharedElementsCallback != null) {
194            mHideSharedElementsCallback.hideSharedElements();
195        }
196        if (!mIsHidden) {
197            hideViews(mSharedElements);
198        }
199        mSharedElementsHidden = true;
200        finishIfNecessary();
201    }
202
203    public void startExit() {
204        if (!mIsExitStarted) {
205            backgroundAnimatorComplete();
206            mIsExitStarted = true;
207            pauseInput();
208            ViewGroup decorView = getDecor();
209            if (decorView != null) {
210                decorView.suppressLayout(true);
211            }
212            moveSharedElementsToOverlay();
213            startTransition(new Runnable() {
214                @Override
215                public void run() {
216                    if (mActivity != null) {
217                        beginTransitions();
218                    } else {
219                        startExitTransition();
220                    }
221                }
222            });
223        }
224    }
225
226    public void startExit(int resultCode, Intent data) {
227        if (!mIsExitStarted) {
228            mIsExitStarted = true;
229            pauseInput();
230            ViewGroup decorView = getDecor();
231            if (decorView != null) {
232                decorView.suppressLayout(true);
233            }
234            mHandler = new Handler() {
235                @Override
236                public void handleMessage(Message msg) {
237                    mIsCanceled = true;
238                    finish();
239                }
240            };
241            delayCancel();
242            moveSharedElementsToOverlay();
243            if (decorView != null && decorView.getBackground() == null) {
244                getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
245            }
246            final boolean targetsM = decorView == null || decorView.getContext()
247                    .getApplicationInfo().targetSdkVersion >= VERSION_CODES.M;
248            ArrayList<String> sharedElementNames = targetsM ? mSharedElementNames :
249                    mAllSharedElementNames;
250            ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(mActivity, this,
251                    sharedElementNames, resultCode, data);
252            mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() {
253                @Override
254                public void onTranslucentConversionComplete(boolean drawComplete) {
255                    if (!mIsCanceled) {
256                        fadeOutBackground();
257                    }
258                }
259            }, options);
260            startTransition(new Runnable() {
261                @Override
262                public void run() {
263                    startExitTransition();
264                }
265            });
266        }
267    }
268
269    public void stop() {
270        if (mIsReturning && mActivity != null) {
271            // Override the previous ActivityOptions. We don't want the
272            // activity to have options since we're essentially canceling the
273            // transition and finishing right now.
274            mActivity.convertToTranslucent(null, null);
275            finish();
276        }
277    }
278
279    private void startExitTransition() {
280        Transition transition = getExitTransition();
281        ViewGroup decorView = getDecor();
282        if (transition != null && decorView != null && mTransitioningViews != null) {
283            setTransitioningViewsVisiblity(View.VISIBLE, false);
284            TransitionManager.beginDelayedTransition(decorView, transition);
285            setTransitioningViewsVisiblity(View.INVISIBLE, false);
286            decorView.invalidate();
287        } else {
288            transitionStarted();
289        }
290    }
291
292    private void fadeOutBackground() {
293        if (mBackgroundAnimator == null) {
294            ViewGroup decor = getDecor();
295            Drawable background;
296            if (decor != null && (background = decor.getBackground()) != null) {
297                background = background.mutate();
298                getWindow().setBackgroundDrawable(background);
299                mBackgroundAnimator = ObjectAnimator.ofInt(background, "alpha", 0);
300                mBackgroundAnimator.addListener(new AnimatorListenerAdapter() {
301                    @Override
302                    public void onAnimationEnd(Animator animation) {
303                        mBackgroundAnimator = null;
304                        if (!mIsCanceled) {
305                            mIsBackgroundReady = true;
306                            notifyComplete();
307                        }
308                        backgroundAnimatorComplete();
309                    }
310                });
311                mBackgroundAnimator.setDuration(getFadeDuration());
312                mBackgroundAnimator.start();
313            } else {
314                backgroundAnimatorComplete();
315                mIsBackgroundReady = true;
316            }
317        }
318    }
319
320    private Transition getExitTransition() {
321        Transition viewsTransition = null;
322        if (mTransitioningViews != null && !mTransitioningViews.isEmpty()) {
323            viewsTransition = configureTransition(getViewsTransition(), true);
324        }
325        if (viewsTransition == null) {
326            viewsTransitionComplete();
327        } else {
328            final ArrayList<View> transitioningViews = mTransitioningViews;
329            viewsTransition.addListener(new ContinueTransitionListener() {
330                @Override
331                public void onTransitionEnd(Transition transition) {
332                    viewsTransitionComplete();
333                    if (mIsHidden && transitioningViews != null) {
334                        showViews(transitioningViews, true);
335                        setTransitioningViewsVisiblity(View.VISIBLE, true);
336                    }
337                    if (mSharedElementBundle != null) {
338                        delayCancel();
339                    }
340                    super.onTransitionEnd(transition);
341                }
342            });
343        }
344        return viewsTransition;
345    }
346
347    private Transition getSharedElementExitTransition() {
348        Transition sharedElementTransition = null;
349        if (!mSharedElements.isEmpty()) {
350            sharedElementTransition = configureTransition(getSharedElementTransition(), false);
351        }
352        if (sharedElementTransition == null) {
353            sharedElementTransitionComplete();
354        } else {
355            sharedElementTransition.addListener(new ContinueTransitionListener() {
356                @Override
357                public void onTransitionEnd(Transition transition) {
358                    sharedElementTransitionComplete();
359                    if (mIsHidden) {
360                        showViews(mSharedElements, true);
361                    }
362                    super.onTransitionEnd(transition);
363                }
364            });
365            mSharedElements.get(0).invalidate();
366        }
367        return sharedElementTransition;
368    }
369
370    private void beginTransitions() {
371        Transition sharedElementTransition = getSharedElementExitTransition();
372        Transition viewsTransition = getExitTransition();
373
374        Transition transition = mergeTransitions(sharedElementTransition, viewsTransition);
375        ViewGroup decorView = getDecor();
376        if (transition != null && decorView != null) {
377            setGhostVisibility(View.INVISIBLE);
378            scheduleGhostVisibilityChange(View.INVISIBLE);
379            if (viewsTransition != null) {
380                setTransitioningViewsVisiblity(View.VISIBLE, false);
381            }
382            TransitionManager.beginDelayedTransition(decorView, transition);
383            scheduleGhostVisibilityChange(View.VISIBLE);
384            setGhostVisibility(View.VISIBLE);
385            if (viewsTransition != null) {
386                setTransitioningViewsVisiblity(View.INVISIBLE, false);
387            }
388            decorView.invalidate();
389        } else {
390            transitionStarted();
391        }
392    }
393
394    protected boolean isReadyToNotify() {
395        return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady;
396    }
397
398    @Override
399    protected void sharedElementTransitionComplete() {
400        mSharedElementBundle = mExitSharedElementBundle == null
401                ? captureSharedElementState() : captureExitSharedElementsState();
402        super.sharedElementTransitionComplete();
403    }
404
405    private Bundle captureExitSharedElementsState() {
406        Bundle bundle = new Bundle();
407        RectF bounds = new RectF();
408        Matrix matrix = new Matrix();
409        for (int i = 0; i < mSharedElements.size(); i++) {
410            String name = mSharedElementNames.get(i);
411            Bundle sharedElementState = mExitSharedElementBundle.getBundle(name);
412            if (sharedElementState != null) {
413                bundle.putBundle(name, sharedElementState);
414            } else {
415                View view = mSharedElements.get(i);
416                captureSharedElementState(view, name, bundle, matrix, bounds);
417            }
418        }
419        return bundle;
420    }
421
422    @Override
423    protected void onTransitionsComplete() {
424        notifyComplete();
425    }
426
427    protected void notifyComplete() {
428        if (isReadyToNotify()) {
429            if (!mSharedElementNotified) {
430                mSharedElementNotified = true;
431                delayCancel();
432                if (mListener == null) {
433                    mResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, mSharedElementBundle);
434                    notifyExitComplete();
435                } else {
436                    final ResultReceiver resultReceiver = mResultReceiver;
437                    final Bundle sharedElementBundle = mSharedElementBundle;
438                    mListener.onSharedElementsArrived(mSharedElementNames, mSharedElements,
439                            new OnSharedElementsReadyListener() {
440                                @Override
441                                public void onSharedElementsReady() {
442                                    resultReceiver.send(MSG_TAKE_SHARED_ELEMENTS,
443                                            sharedElementBundle);
444                                    notifyExitComplete();
445                                }
446                            });
447                }
448            } else {
449                notifyExitComplete();
450            }
451        }
452    }
453
454    private void notifyExitComplete() {
455        if (!mExitNotified && isViewsTransitionComplete()) {
456            mExitNotified = true;
457            mResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null);
458            mResultReceiver = null; // done talking
459            ViewGroup decorView = getDecor();
460            if (!mIsReturning && decorView != null) {
461                decorView.suppressLayout(false);
462            }
463            finishIfNecessary();
464        }
465    }
466
467    private void finishIfNecessary() {
468        if (mIsReturning && mExitNotified && mActivity != null && (mSharedElements.isEmpty() ||
469                mSharedElementsHidden)) {
470            finish();
471        }
472        if (!mIsReturning && mExitNotified) {
473            mActivity = null; // don't need it anymore
474        }
475    }
476
477    private void finish() {
478        stopCancel();
479        if (mActivity != null) {
480            mActivity.mActivityTransitionState.clear();
481            mActivity.finish();
482            mActivity.overridePendingTransition(0, 0);
483            mActivity = null;
484        }
485        // Clear the state so that we can't hold any references accidentally and leak memory.
486        clearState();
487    }
488
489    @Override
490    protected void clearState() {
491        mHandler = null;
492        mSharedElementBundle = null;
493        if (mBackgroundAnimator != null) {
494            mBackgroundAnimator.cancel();
495            mBackgroundAnimator = null;
496        }
497        mExitSharedElementBundle = null;
498        super.clearState();
499    }
500
501    @Override
502    protected boolean moveSharedElementWithParent() {
503        return !mIsReturning;
504    }
505
506    @Override
507    protected Transition getViewsTransition() {
508        if (mIsReturning) {
509            return getWindow().getReturnTransition();
510        } else {
511            return getWindow().getExitTransition();
512        }
513    }
514
515    protected Transition getSharedElementTransition() {
516        if (mIsReturning) {
517            return getWindow().getSharedElementReturnTransition();
518        } else {
519            return getWindow().getSharedElementExitTransition();
520        }
521    }
522
523    interface HideSharedElementsCallback {
524        void hideSharedElements();
525    }
526}
527