• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  */
16 package android.app;
17 
18 import android.animation.Animator;
19 import android.animation.AnimatorListenerAdapter;
20 import android.animation.ObjectAnimator;
21 import android.app.SharedElementCallback.OnSharedElementsReadyListener;
22 import android.content.Intent;
23 import android.graphics.Color;
24 import android.graphics.Matrix;
25 import android.graphics.RectF;
26 import android.graphics.drawable.ColorDrawable;
27 import android.graphics.drawable.Drawable;
28 import android.os.Build.VERSION_CODES;
29 import android.os.Bundle;
30 import android.os.Handler;
31 import android.os.Message;
32 import android.os.ResultReceiver;
33 import android.transition.Transition;
34 import android.transition.TransitionManager;
35 import android.view.View;
36 import android.view.ViewGroup;
37 import android.view.ViewTreeObserver;
38 import android.view.Window;
39 
40 import java.util.ArrayList;
41 
42 /**
43  * This ActivityTransitionCoordinator is created in ActivityOptions#makeSceneTransitionAnimation
44  * to govern the exit of the Scene and the shared elements when calling an Activity as well as
45  * the reentry of the Scene when coming back from the called Activity.
46  */
47 class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
48     private static final String TAG = "ExitTransitionCoordinator";
49     private static final long MAX_WAIT_MS = 1000;
50 
51     private Bundle mSharedElementBundle;
52     private boolean mExitNotified;
53     private boolean mSharedElementNotified;
54     private Activity mActivity;
55     private boolean mIsBackgroundReady;
56     private boolean mIsCanceled;
57     private Handler mHandler;
58     private ObjectAnimator mBackgroundAnimator;
59     private boolean mIsHidden;
60     private Bundle mExitSharedElementBundle;
61     private boolean mIsExitStarted;
62     private boolean mSharedElementsHidden;
63     private HideSharedElementsCallback mHideSharedElementsCallback;
64 
ExitTransitionCoordinator(Activity activity, Window window, SharedElementCallback listener, ArrayList<String> names, ArrayList<String> accepted, ArrayList<View> mapped, boolean isReturning)65     public ExitTransitionCoordinator(Activity activity, Window window,
66             SharedElementCallback listener, ArrayList<String> names,
67             ArrayList<String> accepted, ArrayList<View> mapped, boolean isReturning) {
68         super(window, names, listener, isReturning);
69         viewsReady(mapSharedElements(accepted, mapped));
70         stripOffscreenViews();
71         mIsBackgroundReady = !isReturning;
72         mActivity = activity;
73     }
74 
setHideSharedElementsCallback(HideSharedElementsCallback callback)75     void setHideSharedElementsCallback(HideSharedElementsCallback callback) {
76         mHideSharedElementsCallback = callback;
77     }
78 
79     @Override
onReceiveResult(int resultCode, Bundle resultData)80     protected void onReceiveResult(int resultCode, Bundle resultData) {
81         switch (resultCode) {
82             case MSG_SET_REMOTE_RECEIVER:
83                 stopCancel();
84                 mResultReceiver = resultData.getParcelable(KEY_REMOTE_RECEIVER);
85                 if (mIsCanceled) {
86                     mResultReceiver.send(MSG_CANCEL, null);
87                     mResultReceiver = null;
88                 } else {
89                     notifyComplete();
90                 }
91                 break;
92             case MSG_HIDE_SHARED_ELEMENTS:
93                 stopCancel();
94                 if (!mIsCanceled) {
95                     hideSharedElements();
96                 }
97                 break;
98             case MSG_START_EXIT_TRANSITION:
99                 mHandler.removeMessages(MSG_CANCEL);
100                 startExit();
101                 break;
102             case MSG_SHARED_ELEMENT_DESTINATION:
103                 mExitSharedElementBundle = resultData;
104                 sharedElementExitBack();
105                 break;
106             case MSG_CANCEL:
107                 mIsCanceled = true;
108                 finish();
109                 break;
110         }
111     }
112 
stopCancel()113     private void stopCancel() {
114         if (mHandler != null) {
115             mHandler.removeMessages(MSG_CANCEL);
116         }
117     }
118 
delayCancel()119     private void delayCancel() {
120         if (mHandler != null) {
121             mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS);
122         }
123     }
124 
resetViews()125     public void resetViews() {
126         if (mTransitioningViews != null) {
127             showViews(mTransitioningViews, true);
128             setTransitioningViewsVisiblity(View.VISIBLE, true);
129         }
130         showViews(mSharedElements, true);
131         mIsHidden = true;
132         ViewGroup decorView = getDecor();
133         if (!mIsReturning && decorView != null) {
134             decorView.suppressLayout(false);
135         }
136         moveSharedElementsFromOverlay();
137         clearState();
138     }
139 
sharedElementExitBack()140     private void sharedElementExitBack() {
141         final ViewGroup decorView = getDecor();
142         if (decorView != null) {
143             decorView.suppressLayout(true);
144         }
145         if (decorView != null && mExitSharedElementBundle != null &&
146                 !mExitSharedElementBundle.isEmpty() &&
147                 !mSharedElements.isEmpty() && getSharedElementTransition() != null) {
148             startTransition(new Runnable() {
149                 public void run() {
150                     startSharedElementExit(decorView);
151                 }
152             });
153         } else {
154             sharedElementTransitionComplete();
155         }
156     }
157 
startSharedElementExit(final ViewGroup decorView)158     private void startSharedElementExit(final ViewGroup decorView) {
159         Transition transition = getSharedElementExitTransition();
160         transition.addListener(new Transition.TransitionListenerAdapter() {
161             @Override
162             public void onTransitionEnd(Transition transition) {
163                 transition.removeListener(this);
164                 if (isViewsTransitionComplete()) {
165                     delayCancel();
166                 }
167             }
168         });
169         final ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle,
170                 mSharedElementNames);
171         decorView.getViewTreeObserver()
172                 .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
173                     @Override
174                     public boolean onPreDraw() {
175                         decorView.getViewTreeObserver().removeOnPreDrawListener(this);
176                         setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots);
177                         return true;
178                     }
179                 });
180         setGhostVisibility(View.INVISIBLE);
181         scheduleGhostVisibilityChange(View.INVISIBLE);
182         if (mListener != null) {
183             mListener.onSharedElementEnd(mSharedElementNames, mSharedElements,
184                     sharedElementSnapshots);
185         }
186         TransitionManager.beginDelayedTransition(decorView, transition);
187         scheduleGhostVisibilityChange(View.VISIBLE);
188         setGhostVisibility(View.VISIBLE);
189         decorView.invalidate();
190     }
191 
hideSharedElements()192     private void hideSharedElements() {
193         moveSharedElementsFromOverlay();
194         if (mHideSharedElementsCallback != null) {
195             mHideSharedElementsCallback.hideSharedElements();
196         }
197         if (!mIsHidden) {
198             hideViews(mSharedElements);
199         }
200         mSharedElementsHidden = true;
201         finishIfNecessary();
202     }
203 
startExit()204     public void startExit() {
205         if (!mIsExitStarted) {
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 
startExit(int resultCode, Intent data)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.BLACK));
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 
stop()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 
startExitTransition()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 
fadeOutBackground()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                     }
309                 });
310                 mBackgroundAnimator.setDuration(getFadeDuration());
311                 mBackgroundAnimator.start();
312             } else {
313                 mIsBackgroundReady = true;
314             }
315         }
316     }
317 
getExitTransition()318     private Transition getExitTransition() {
319         Transition viewsTransition = null;
320         if (mTransitioningViews != null && !mTransitioningViews.isEmpty()) {
321             viewsTransition = configureTransition(getViewsTransition(), true);
322         }
323         if (viewsTransition == null) {
324             viewsTransitionComplete();
325         } else {
326             final ArrayList<View> transitioningViews = mTransitioningViews;
327             viewsTransition.addListener(new ContinueTransitionListener() {
328                 @Override
329                 public void onTransitionEnd(Transition transition) {
330                     transition.removeListener(this);
331                     viewsTransitionComplete();
332                     if (mIsHidden && transitioningViews != null) {
333                         showViews(transitioningViews, true);
334                         setTransitioningViewsVisiblity(View.VISIBLE, true);
335                     }
336                     if (mSharedElementBundle != null) {
337                         delayCancel();
338                     }
339                     super.onTransitionEnd(transition);
340                 }
341             });
342         }
343         return viewsTransition;
344     }
345 
getSharedElementExitTransition()346     private Transition getSharedElementExitTransition() {
347         Transition sharedElementTransition = null;
348         if (!mSharedElements.isEmpty()) {
349             sharedElementTransition = configureTransition(getSharedElementTransition(), false);
350         }
351         if (sharedElementTransition == null) {
352             sharedElementTransitionComplete();
353         } else {
354             sharedElementTransition.addListener(new ContinueTransitionListener() {
355                 @Override
356                 public void onTransitionEnd(Transition transition) {
357                     transition.removeListener(this);
358                     sharedElementTransitionComplete();
359                     if (mIsHidden) {
360                         showViews(mSharedElements, true);
361                     }
362                 }
363             });
364             mSharedElements.get(0).invalidate();
365         }
366         return sharedElementTransition;
367     }
368 
beginTransitions()369     private void beginTransitions() {
370         Transition sharedElementTransition = getSharedElementExitTransition();
371         Transition viewsTransition = getExitTransition();
372 
373         Transition transition = mergeTransitions(sharedElementTransition, viewsTransition);
374         ViewGroup decorView = getDecor();
375         if (transition != null && decorView != null) {
376             setGhostVisibility(View.INVISIBLE);
377             scheduleGhostVisibilityChange(View.INVISIBLE);
378             if (viewsTransition != null) {
379                 setTransitioningViewsVisiblity(View.VISIBLE, false);
380             }
381             TransitionManager.beginDelayedTransition(decorView, transition);
382             scheduleGhostVisibilityChange(View.VISIBLE);
383             setGhostVisibility(View.VISIBLE);
384             if (viewsTransition != null) {
385                 setTransitioningViewsVisiblity(View.INVISIBLE, false);
386             }
387             decorView.invalidate();
388         } else {
389             transitionStarted();
390         }
391     }
392 
isReadyToNotify()393     protected boolean isReadyToNotify() {
394         return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady;
395     }
396 
397     @Override
sharedElementTransitionComplete()398     protected void sharedElementTransitionComplete() {
399         mSharedElementBundle = mExitSharedElementBundle == null
400                 ? captureSharedElementState() : captureExitSharedElementsState();
401         super.sharedElementTransitionComplete();
402     }
403 
captureExitSharedElementsState()404     private Bundle captureExitSharedElementsState() {
405         Bundle bundle = new Bundle();
406         RectF bounds = new RectF();
407         Matrix matrix = new Matrix();
408         for (int i = 0; i < mSharedElements.size(); i++) {
409             String name = mSharedElementNames.get(i);
410             Bundle sharedElementState = mExitSharedElementBundle.getBundle(name);
411             if (sharedElementState != null) {
412                 bundle.putBundle(name, sharedElementState);
413             } else {
414                 View view = mSharedElements.get(i);
415                 captureSharedElementState(view, name, bundle, matrix, bounds);
416             }
417         }
418         return bundle;
419     }
420 
421     @Override
onTransitionsComplete()422     protected void onTransitionsComplete() {
423         notifyComplete();
424     }
425 
notifyComplete()426     protected void notifyComplete() {
427         if (isReadyToNotify()) {
428             if (!mSharedElementNotified) {
429                 mSharedElementNotified = true;
430                 delayCancel();
431                 if (mListener == null) {
432                     mResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, mSharedElementBundle);
433                     notifyExitComplete();
434                 } else {
435                     final ResultReceiver resultReceiver = mResultReceiver;
436                     final Bundle sharedElementBundle = mSharedElementBundle;
437                     mListener.onSharedElementsArrived(mSharedElementNames, mSharedElements,
438                             new OnSharedElementsReadyListener() {
439                                 @Override
440                                 public void onSharedElementsReady() {
441                                     resultReceiver.send(MSG_TAKE_SHARED_ELEMENTS,
442                                             sharedElementBundle);
443                                     notifyExitComplete();
444                                 }
445                             });
446                 }
447             } else {
448                 notifyExitComplete();
449             }
450         }
451     }
452 
notifyExitComplete()453     private void notifyExitComplete() {
454         if (!mExitNotified && isViewsTransitionComplete()) {
455             mExitNotified = true;
456             mResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null);
457             mResultReceiver = null; // done talking
458             ViewGroup decorView = getDecor();
459             if (!mIsReturning && decorView != null) {
460                 decorView.suppressLayout(false);
461             }
462             finishIfNecessary();
463         }
464     }
465 
finishIfNecessary()466     private void finishIfNecessary() {
467         if (mIsReturning && mExitNotified && mActivity != null && (mSharedElements.isEmpty() ||
468                 mSharedElementsHidden)) {
469             finish();
470         }
471         if (!mIsReturning && mExitNotified) {
472             mActivity = null; // don't need it anymore
473         }
474     }
475 
finish()476     private void finish() {
477         stopCancel();
478         if (mActivity != null) {
479             mActivity.mActivityTransitionState.clear();
480             mActivity.finish();
481             mActivity.overridePendingTransition(0, 0);
482             mActivity = null;
483         }
484         // Clear the state so that we can't hold any references accidentally and leak memory.
485         clearState();
486     }
487 
488     @Override
clearState()489     protected void clearState() {
490         mHandler = null;
491         mSharedElementBundle = null;
492         if (mBackgroundAnimator != null) {
493             mBackgroundAnimator.cancel();
494             mBackgroundAnimator = null;
495         }
496         mExitSharedElementBundle = null;
497         super.clearState();
498     }
499 
500     @Override
moveSharedElementWithParent()501     protected boolean moveSharedElementWithParent() {
502         return !mIsReturning;
503     }
504 
505     @Override
getViewsTransition()506     protected Transition getViewsTransition() {
507         if (mIsReturning) {
508             return getWindow().getReturnTransition();
509         } else {
510             return getWindow().getExitTransition();
511         }
512     }
513 
getSharedElementTransition()514     protected Transition getSharedElementTransition() {
515         if (mIsReturning) {
516             return getWindow().getSharedElementReturnTransition();
517         } else {
518             return getWindow().getSharedElementExitTransition();
519         }
520     }
521 
522     interface HideSharedElementsCallback {
hideSharedElements()523         void hideSharedElements();
524     }
525 }
526