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.content.Context;
19 import android.graphics.Matrix;
20 import android.graphics.Rect;
21 import android.graphics.RectF;
22 import android.os.Bundle;
23 import android.os.Handler;
24 import android.os.Parcelable;
25 import android.os.ResultReceiver;
26 import android.transition.Transition;
27 import android.transition.TransitionSet;
28 import android.transition.Visibility;
29 import android.util.ArrayMap;
30 import android.view.GhostView;
31 import android.view.View;
32 import android.view.ViewGroup;
33 import android.view.ViewGroupOverlay;
34 import android.view.ViewParent;
35 import android.view.ViewRootImpl;
36 import android.view.ViewTreeObserver;
37 import android.view.Window;
38 import android.widget.ImageView;
39 
40 import java.util.ArrayList;
41 import java.util.Collection;
42 
43 /**
44  * Base class for ExitTransitionCoordinator and EnterTransitionCoordinator, classes
45  * that manage activity transitions and the communications coordinating them between
46  * Activities. The ExitTransitionCoordinator is created in the
47  * ActivityOptions#makeSceneTransitionAnimation. The EnterTransitionCoordinator
48  * is created by ActivityOptions#createEnterActivityTransition by Activity when the window is
49  * attached.
50  *
51  * Typical startActivity goes like this:
52  * 1) ExitTransitionCoordinator created with ActivityOptions#makeSceneTransitionAnimation
53  * 2) Activity#startActivity called and that calls startExit() through
54  *    ActivityOptions#dispatchStartExit
55  *    - Exit transition starts by setting transitioning Views to INVISIBLE
56  * 3) Launched Activity starts, creating an EnterTransitionCoordinator.
57  *    - The Window is made translucent
58  *    - The Window background alpha is set to 0
59  *    - The transitioning views are made INVISIBLE
60  *    - MSG_SET_LISTENER is sent back to the ExitTransitionCoordinator.
61  * 4) The shared element transition completes.
62  *    - MSG_TAKE_SHARED_ELEMENTS is sent to the EnterTransitionCoordinator
63  * 5) The MSG_TAKE_SHARED_ELEMENTS is received by the EnterTransitionCoordinator.
64  *    - Shared elements are made VISIBLE
65  *    - Shared elements positions and size are set to match the end state of the calling
66  *      Activity.
67  *    - The shared element transition is started
68  *    - If the window allows overlapping transitions, the views transition is started by setting
69  *      the entering Views to VISIBLE and the background alpha is animated to opaque.
70  *    - MSG_HIDE_SHARED_ELEMENTS is sent to the ExitTransitionCoordinator
71  * 6) MSG_HIDE_SHARED_ELEMENTS is received by the ExitTransitionCoordinator
72  *    - The shared elements are made INVISIBLE
73  * 7) The exit transition completes in the calling Activity.
74  *    - MSG_EXIT_TRANSITION_COMPLETE is sent to the EnterTransitionCoordinator.
75  * 8) The MSG_EXIT_TRANSITION_COMPLETE is received by the EnterTransitionCoordinator.
76  *    - If the window doesn't allow overlapping enter transitions, the enter transition is started
77  *      by setting entering views to VISIBLE and the background is animated to opaque.
78  * 9) The background opacity animation completes.
79  *    - The window is made opaque
80  * 10) The calling Activity gets an onStop() call
81  *    - onActivityStopped() is called and all exited Views are made VISIBLE.
82  *
83  * Typical finishAfterTransition goes like this:
84  * 1) finishAfterTransition() creates an ExitTransitionCoordinator and calls startExit()
85  *    - The Window start transitioning to Translucent with a new ActivityOptions.
86  *    - If no background exists, a black background is substituted
87  *    - The shared elements in the scene are matched against those shared elements
88  *      that were sent by comparing the names.
89  *    - The exit transition is started by setting Views to INVISIBLE.
90  * 2) The ActivityOptions is received by the Activity and an EnterTransitionCoordinator is created.
91  *    - All transitioning views are made VISIBLE to reverse what was done when onActivityStopped()
92  *      was called
93  * 3) The Window is made translucent and a callback is received
94  *    - The background alpha is animated to 0
95  * 4) The background alpha animation completes
96  * 5) The shared element transition completes
97  *    - After both 4 & 5 complete, MSG_TAKE_SHARED_ELEMENTS is sent to the
98  *      EnterTransitionCoordinator
99  * 6) MSG_TAKE_SHARED_ELEMENTS is received by EnterTransitionCoordinator
100  *    - Shared elements are made VISIBLE
101  *    - Shared elements positions and size are set to match the end state of the calling
102  *      Activity.
103  *    - The shared element transition is started
104  *    - If the window allows overlapping transitions, the views transition is started by setting
105  *      the entering Views to VISIBLE.
106  *    - MSG_HIDE_SHARED_ELEMENTS is sent to the ExitTransitionCoordinator
107  * 7) MSG_HIDE_SHARED_ELEMENTS is received by the ExitTransitionCoordinator
108  *    - The shared elements are made INVISIBLE
109  * 8) The exit transition completes in the finishing Activity.
110  *    - MSG_EXIT_TRANSITION_COMPLETE is sent to the EnterTransitionCoordinator.
111  *    - finish() is called on the exiting Activity
112  * 9) The MSG_EXIT_TRANSITION_COMPLETE is received by the EnterTransitionCoordinator.
113  *    - If the window doesn't allow overlapping enter transitions, the enter transition is started
114  *      by setting entering views to VISIBLE.
115  */
116 abstract class ActivityTransitionCoordinator extends ResultReceiver {
117     private static final String TAG = "ActivityTransitionCoordinator";
118 
119     /**
120      * For Activity transitions, the called Activity's listener to receive calls
121      * when transitions complete.
122      */
123     static final String KEY_REMOTE_RECEIVER = "android:remoteReceiver";
124 
125     protected static final String KEY_SCREEN_LEFT = "shared_element:screenLeft";
126     protected static final String KEY_SCREEN_TOP = "shared_element:screenTop";
127     protected static final String KEY_SCREEN_RIGHT = "shared_element:screenRight";
128     protected static final String KEY_SCREEN_BOTTOM= "shared_element:screenBottom";
129     protected static final String KEY_TRANSLATION_Z = "shared_element:translationZ";
130     protected static final String KEY_SNAPSHOT = "shared_element:bitmap";
131     protected static final String KEY_SCALE_TYPE = "shared_element:scaleType";
132     protected static final String KEY_IMAGE_MATRIX = "shared_element:imageMatrix";
133     protected static final String KEY_ELEVATION = "shared_element:elevation";
134 
135     protected static final ImageView.ScaleType[] SCALE_TYPE_VALUES = ImageView.ScaleType.values();
136 
137     /**
138      * Sent by the exiting coordinator (either EnterTransitionCoordinator
139      * or ExitTransitionCoordinator) after the shared elements have
140      * become stationary (shared element transition completes). This tells
141      * the remote coordinator to take control of the shared elements and
142      * that animations may begin. The remote Activity won't start entering
143      * until this message is received, but may wait for
144      * MSG_EXIT_TRANSITION_COMPLETE if allowOverlappingTransitions() is true.
145      */
146     public static final int MSG_SET_REMOTE_RECEIVER = 100;
147 
148     /**
149      * Sent by the entering coordinator to tell the exiting coordinator
150      * to hide its shared elements after it has started its shared
151      * element transition. This is temporary until the
152      * interlock of shared elements is figured out.
153      */
154     public static final int MSG_HIDE_SHARED_ELEMENTS = 101;
155 
156     /**
157      * Sent by the exiting coordinator (either EnterTransitionCoordinator
158      * or ExitTransitionCoordinator) after the shared elements have
159      * become stationary (shared element transition completes). This tells
160      * the remote coordinator to take control of the shared elements and
161      * that animations may begin. The remote Activity won't start entering
162      * until this message is received, but may wait for
163      * MSG_EXIT_TRANSITION_COMPLETE if allowOverlappingTransitions() is true.
164      */
165     public static final int MSG_TAKE_SHARED_ELEMENTS = 103;
166 
167     /**
168      * Sent by the exiting coordinator (either
169      * EnterTransitionCoordinator or ExitTransitionCoordinator) after
170      * the exiting Views have finished leaving the scene. This will
171      * be ignored if allowOverlappingTransitions() is true on the
172      * remote coordinator. If it is false, it will trigger the enter
173      * transition to start.
174      */
175     public static final int MSG_EXIT_TRANSITION_COMPLETE = 104;
176 
177     /**
178      * Sent by Activity#startActivity to begin the exit transition.
179      */
180     public static final int MSG_START_EXIT_TRANSITION = 105;
181 
182     /**
183      * It took too long for a message from the entering Activity, so we canceled the transition.
184      */
185     public static final int MSG_CANCEL = 106;
186 
187     /**
188      * When returning, this is the destination location for the shared element.
189      */
190     public static final int MSG_SHARED_ELEMENT_DESTINATION = 107;
191 
192     private Window mWindow;
193     final protected ArrayList<String> mAllSharedElementNames;
194     final protected ArrayList<View> mSharedElements = new ArrayList<View>();
195     final protected ArrayList<String> mSharedElementNames = new ArrayList<String>();
196     protected ArrayList<View> mTransitioningViews = new ArrayList<View>();
197     protected SharedElementCallback mListener;
198     protected ResultReceiver mResultReceiver;
199     final private FixedEpicenterCallback mEpicenterCallback = new FixedEpicenterCallback();
200     final protected boolean mIsReturning;
201     private Runnable mPendingTransition;
202     private boolean mIsStartingTransition;
203     private ArrayList<GhostViewListeners> mGhostViewListeners =
204             new ArrayList<GhostViewListeners>();
205     private ArrayMap<View, Float> mOriginalAlphas = new ArrayMap<View, Float>();
206     private ArrayList<Matrix> mSharedElementParentMatrices;
207     private boolean mSharedElementTransitionComplete;
208     private boolean mViewsTransitionComplete;
209 
ActivityTransitionCoordinator(Window window, ArrayList<String> allSharedElementNames, SharedElementCallback listener, boolean isReturning)210     public ActivityTransitionCoordinator(Window window,
211             ArrayList<String> allSharedElementNames,
212             SharedElementCallback listener, boolean isReturning) {
213         super(new Handler());
214         mWindow = window;
215         mListener = listener;
216         mAllSharedElementNames = allSharedElementNames;
217         mIsReturning = isReturning;
218     }
219 
viewsReady(ArrayMap<String, View> sharedElements)220     protected void viewsReady(ArrayMap<String, View> sharedElements) {
221         sharedElements.retainAll(mAllSharedElementNames);
222         if (mListener != null) {
223             mListener.onMapSharedElements(mAllSharedElementNames, sharedElements);
224         }
225         setSharedElements(sharedElements);
226         if (getViewsTransition() != null && mTransitioningViews != null) {
227             ViewGroup decorView = getDecor();
228             if (decorView != null) {
229                 decorView.captureTransitioningViews(mTransitioningViews);
230             }
231             mTransitioningViews.removeAll(mSharedElements);
232         }
233         setEpicenter();
234     }
235 
236     /**
237      * Iterates over the shared elements and adds them to the members in order.
238      * Shared elements that are nested in other shared elements are placed after the
239      * elements that they are nested in. This means that layout ordering can be done
240      * from first to last.
241      *
242      * @param sharedElements The map of transition names to shared elements to set into
243      *                       the member fields.
244      */
setSharedElements(ArrayMap<String, View> sharedElements)245     private void setSharedElements(ArrayMap<String, View> sharedElements) {
246         boolean isFirstRun = true;
247         while (!sharedElements.isEmpty()) {
248             final int numSharedElements = sharedElements.size();
249             for (int i = numSharedElements - 1; i >= 0; i--) {
250                 final View view = sharedElements.valueAt(i);
251                 final String name = sharedElements.keyAt(i);
252                 if (isFirstRun && (view == null || !view.isAttachedToWindow() || name == null)) {
253                     sharedElements.removeAt(i);
254                 } else if (!isNested(view, sharedElements)) {
255                     mSharedElementNames.add(name);
256                     mSharedElements.add(view);
257                     sharedElements.removeAt(i);
258                 }
259             }
260             isFirstRun = false;
261         }
262     }
263 
264     /**
265      * Returns true when view is nested in any of the values of sharedElements.
266      */
isNested(View view, ArrayMap<String, View> sharedElements)267     private static boolean isNested(View view, ArrayMap<String, View> sharedElements) {
268         ViewParent parent = view.getParent();
269         boolean isNested = false;
270         while (parent instanceof View) {
271             View parentView = (View) parent;
272             if (sharedElements.containsValue(parentView)) {
273                 isNested = true;
274                 break;
275             }
276             parent = parentView.getParent();
277         }
278         return isNested;
279     }
280 
stripOffscreenViews()281     protected void stripOffscreenViews() {
282         if (mTransitioningViews == null) {
283             return;
284         }
285         Rect r = new Rect();
286         for (int i = mTransitioningViews.size() - 1; i >= 0; i--) {
287             View view = mTransitioningViews.get(i);
288             if (!view.getGlobalVisibleRect(r)) {
289                 mTransitioningViews.remove(i);
290                 showView(view, true);
291             }
292         }
293     }
294 
getWindow()295     protected Window getWindow() {
296         return mWindow;
297     }
298 
getDecor()299     public ViewGroup getDecor() {
300         return (mWindow == null) ? null : (ViewGroup) mWindow.getDecorView();
301     }
302 
303     /**
304      * Sets the transition epicenter to the position of the first shared element.
305      */
setEpicenter()306     protected void setEpicenter() {
307         View epicenter = null;
308         if (!mAllSharedElementNames.isEmpty() && !mSharedElementNames.isEmpty()) {
309             int index = mSharedElementNames.indexOf(mAllSharedElementNames.get(0));
310             if (index >= 0) {
311                 epicenter = mSharedElements.get(index);
312             }
313         }
314         setEpicenter(epicenter);
315     }
316 
setEpicenter(View view)317     private void setEpicenter(View view) {
318         if (view == null) {
319             mEpicenterCallback.setEpicenter(null);
320         } else {
321             Rect epicenter = new Rect();
322             view.getBoundsOnScreen(epicenter);
323             mEpicenterCallback.setEpicenter(epicenter);
324         }
325     }
326 
getAcceptedNames()327     public ArrayList<String> getAcceptedNames() {
328         return mSharedElementNames;
329     }
330 
getMappedNames()331     public ArrayList<String> getMappedNames() {
332         ArrayList<String> names = new ArrayList<String>(mSharedElements.size());
333         for (int i = 0; i < mSharedElements.size(); i++) {
334             names.add(mSharedElements.get(i).getTransitionName());
335         }
336         return names;
337     }
338 
copyMappedViews()339     public ArrayList<View> copyMappedViews() {
340         return new ArrayList<View>(mSharedElements);
341     }
342 
getAllSharedElementNames()343     public ArrayList<String> getAllSharedElementNames() { return mAllSharedElementNames; }
344 
setTargets(Transition transition, boolean add)345     protected Transition setTargets(Transition transition, boolean add) {
346         if (transition == null || (add &&
347                 (mTransitioningViews == null || mTransitioningViews.isEmpty()))) {
348             return null;
349         }
350         // Add the targets to a set containing transition so that transition
351         // remains unaffected. We don't want to modify the targets of transition itself.
352         TransitionSet set = new TransitionSet();
353         if (mTransitioningViews != null) {
354             for (int i = mTransitioningViews.size() - 1; i >= 0; i--) {
355                 View view = mTransitioningViews.get(i);
356                 if (add) {
357                     set.addTarget(view);
358                 } else {
359                     set.excludeTarget(view, true);
360                 }
361             }
362         }
363         // By adding the transition after addTarget, we prevent addTarget from
364         // affecting transition.
365         set.addTransition(transition);
366 
367         if (!add && mTransitioningViews != null && !mTransitioningViews.isEmpty()) {
368             // Allow children of excluded transitioning views, but not the views themselves
369             set = new TransitionSet().addTransition(set);
370         }
371 
372         return set;
373     }
374 
configureTransition(Transition transition, boolean includeTransitioningViews)375     protected Transition configureTransition(Transition transition,
376             boolean includeTransitioningViews) {
377         if (transition != null) {
378             transition = transition.clone();
379             transition.setEpicenterCallback(mEpicenterCallback);
380             transition = setTargets(transition, includeTransitioningViews);
381         }
382         noLayoutSuppressionForVisibilityTransitions(transition);
383         return transition;
384     }
385 
mergeTransitions(Transition transition1, Transition transition2)386     protected static Transition mergeTransitions(Transition transition1, Transition transition2) {
387         if (transition1 == null) {
388             return transition2;
389         } else if (transition2 == null) {
390             return transition1;
391         } else {
392             TransitionSet transitionSet = new TransitionSet();
393             transitionSet.addTransition(transition1);
394             transitionSet.addTransition(transition2);
395             return transitionSet;
396         }
397     }
398 
mapSharedElements(ArrayList<String> accepted, ArrayList<View> localViews)399     protected ArrayMap<String, View> mapSharedElements(ArrayList<String> accepted,
400             ArrayList<View> localViews) {
401         ArrayMap<String, View> sharedElements = new ArrayMap<String, View>();
402         if (accepted != null) {
403             for (int i = 0; i < accepted.size(); i++) {
404                 sharedElements.put(accepted.get(i), localViews.get(i));
405             }
406         } else {
407             ViewGroup decorView = getDecor();
408             if (decorView != null) {
409                 decorView.findNamedViews(sharedElements);
410             }
411         }
412         return sharedElements;
413     }
414 
setResultReceiver(ResultReceiver resultReceiver)415     protected void setResultReceiver(ResultReceiver resultReceiver) {
416         mResultReceiver = resultReceiver;
417     }
418 
getViewsTransition()419     protected abstract Transition getViewsTransition();
420 
setSharedElementState(View view, String name, Bundle transitionArgs, Matrix tempMatrix, RectF tempRect, int[] decorLoc)421     private void setSharedElementState(View view, String name, Bundle transitionArgs,
422             Matrix tempMatrix, RectF tempRect, int[] decorLoc) {
423         Bundle sharedElementBundle = transitionArgs.getBundle(name);
424         if (sharedElementBundle == null) {
425             return;
426         }
427 
428         if (view instanceof ImageView) {
429             int scaleTypeInt = sharedElementBundle.getInt(KEY_SCALE_TYPE, -1);
430             if (scaleTypeInt >= 0) {
431                 ImageView imageView = (ImageView) view;
432                 ImageView.ScaleType scaleType = SCALE_TYPE_VALUES[scaleTypeInt];
433                 imageView.setScaleType(scaleType);
434                 if (scaleType == ImageView.ScaleType.MATRIX) {
435                     float[] matrixValues = sharedElementBundle.getFloatArray(KEY_IMAGE_MATRIX);
436                     tempMatrix.setValues(matrixValues);
437                     imageView.setImageMatrix(tempMatrix);
438                 }
439             }
440         }
441 
442         float z = sharedElementBundle.getFloat(KEY_TRANSLATION_Z);
443         view.setTranslationZ(z);
444         float elevation = sharedElementBundle.getFloat(KEY_ELEVATION);
445         view.setElevation(elevation);
446 
447         float left = sharedElementBundle.getFloat(KEY_SCREEN_LEFT);
448         float top = sharedElementBundle.getFloat(KEY_SCREEN_TOP);
449         float right = sharedElementBundle.getFloat(KEY_SCREEN_RIGHT);
450         float bottom = sharedElementBundle.getFloat(KEY_SCREEN_BOTTOM);
451 
452         if (decorLoc != null) {
453             left -= decorLoc[0];
454             top -= decorLoc[1];
455             right -= decorLoc[0];
456             bottom -= decorLoc[1];
457         } else {
458             // Find the location in the view's parent
459             getSharedElementParentMatrix(view, tempMatrix);
460             tempRect.set(left, top, right, bottom);
461             tempMatrix.mapRect(tempRect);
462 
463             float leftInParent = tempRect.left;
464             float topInParent = tempRect.top;
465 
466             // Find the size of the view
467             view.getInverseMatrix().mapRect(tempRect);
468             float width = tempRect.width();
469             float height = tempRect.height();
470 
471             // Now determine the offset due to view transform:
472             view.setLeft(0);
473             view.setTop(0);
474             view.setRight(Math.round(width));
475             view.setBottom(Math.round(height));
476             tempRect.set(0, 0, width, height);
477             view.getMatrix().mapRect(tempRect);
478 
479             left = leftInParent - tempRect.left;
480             top = topInParent - tempRect.top;
481             right = left + width;
482             bottom = top + height;
483         }
484 
485         int x = Math.round(left);
486         int y = Math.round(top);
487         int width = Math.round(right) - x;
488         int height = Math.round(bottom) - y;
489         int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
490         int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
491         view.measure(widthSpec, heightSpec);
492 
493         view.layout(x, y, x + width, y + height);
494     }
495 
setSharedElementMatrices()496     private void setSharedElementMatrices() {
497         int numSharedElements = mSharedElements.size();
498         if (numSharedElements > 0) {
499             mSharedElementParentMatrices = new ArrayList<Matrix>(numSharedElements);
500         }
501         for (int i = 0; i < numSharedElements; i++) {
502             View view = mSharedElements.get(i);
503 
504             // Find the location in the view's parent
505             ViewGroup parent = (ViewGroup) view.getParent();
506             Matrix matrix = new Matrix();
507             parent.transformMatrixToLocal(matrix);
508             matrix.postTranslate(parent.getScrollX(), parent.getScrollY());
509             mSharedElementParentMatrices.add(matrix);
510         }
511     }
512 
getSharedElementParentMatrix(View view, Matrix matrix)513     private void getSharedElementParentMatrix(View view, Matrix matrix) {
514         final int index = mSharedElementParentMatrices == null ? -1
515                 : mSharedElements.indexOf(view);
516         if (index < 0) {
517             matrix.reset();
518             ViewParent viewParent = view.getParent();
519             if (viewParent instanceof ViewGroup) {
520                 // Find the location in the view's parent
521                 ViewGroup parent = (ViewGroup) viewParent;
522                 parent.transformMatrixToLocal(matrix);
523                 matrix.postTranslate(parent.getScrollX(), parent.getScrollY());
524             }
525         } else {
526             // The indices of mSharedElementParentMatrices matches the
527             // mSharedElement matrices.
528             Matrix parentMatrix = mSharedElementParentMatrices.get(index);
529             matrix.set(parentMatrix);
530         }
531     }
532 
setSharedElementState( Bundle sharedElementState, final ArrayList<View> snapshots)533     protected ArrayList<SharedElementOriginalState> setSharedElementState(
534             Bundle sharedElementState, final ArrayList<View> snapshots) {
535         ArrayList<SharedElementOriginalState> originalImageState =
536                 new ArrayList<SharedElementOriginalState>();
537         if (sharedElementState != null) {
538             Matrix tempMatrix = new Matrix();
539             RectF tempRect = new RectF();
540             final int numSharedElements = mSharedElements.size();
541             for (int i = 0; i < numSharedElements; i++) {
542                 View sharedElement = mSharedElements.get(i);
543                 String name = mSharedElementNames.get(i);
544                 SharedElementOriginalState originalState = getOldSharedElementState(sharedElement,
545                         name, sharedElementState);
546                 originalImageState.add(originalState);
547                 setSharedElementState(sharedElement, name, sharedElementState,
548                         tempMatrix, tempRect, null);
549             }
550         }
551         if (mListener != null) {
552             mListener.onSharedElementStart(mSharedElementNames, mSharedElements, snapshots);
553         }
554         return originalImageState;
555     }
556 
notifySharedElementEnd(ArrayList<View> snapshots)557     protected void notifySharedElementEnd(ArrayList<View> snapshots) {
558         if (mListener != null) {
559             mListener.onSharedElementEnd(mSharedElementNames, mSharedElements, snapshots);
560         }
561     }
562 
scheduleSetSharedElementEnd(final ArrayList<View> snapshots)563     protected void scheduleSetSharedElementEnd(final ArrayList<View> snapshots) {
564         final View decorView = getDecor();
565         if (decorView != null) {
566             decorView.getViewTreeObserver().addOnPreDrawListener(
567                     new ViewTreeObserver.OnPreDrawListener() {
568                         @Override
569                         public boolean onPreDraw() {
570                             decorView.getViewTreeObserver().removeOnPreDrawListener(this);
571                             notifySharedElementEnd(snapshots);
572                             return true;
573                         }
574                     }
575             );
576         }
577     }
578 
getOldSharedElementState(View view, String name, Bundle transitionArgs)579     private static SharedElementOriginalState getOldSharedElementState(View view, String name,
580             Bundle transitionArgs) {
581 
582         SharedElementOriginalState state = new SharedElementOriginalState();
583         state.mLeft = view.getLeft();
584         state.mTop = view.getTop();
585         state.mRight = view.getRight();
586         state.mBottom = view.getBottom();
587         state.mMeasuredWidth = view.getMeasuredWidth();
588         state.mMeasuredHeight = view.getMeasuredHeight();
589         state.mTranslationZ = view.getTranslationZ();
590         state.mElevation = view.getElevation();
591         if (!(view instanceof ImageView)) {
592             return state;
593         }
594         Bundle bundle = transitionArgs.getBundle(name);
595         if (bundle == null) {
596             return state;
597         }
598         int scaleTypeInt = bundle.getInt(KEY_SCALE_TYPE, -1);
599         if (scaleTypeInt < 0) {
600             return state;
601         }
602 
603         ImageView imageView = (ImageView) view;
604         state.mScaleType = imageView.getScaleType();
605         if (state.mScaleType == ImageView.ScaleType.MATRIX) {
606             state.mMatrix = new Matrix(imageView.getImageMatrix());
607         }
608         return state;
609     }
610 
createSnapshots(Bundle state, Collection<String> names)611     protected ArrayList<View> createSnapshots(Bundle state, Collection<String> names) {
612         int numSharedElements = names.size();
613         ArrayList<View> snapshots = new ArrayList<View>(numSharedElements);
614         if (numSharedElements == 0) {
615             return snapshots;
616         }
617         Context context = getWindow().getContext();
618         int[] decorLoc = new int[2];
619         ViewGroup decorView = getDecor();
620         if (decorView != null) {
621             decorView.getLocationOnScreen(decorLoc);
622         }
623         Matrix tempMatrix = new Matrix();
624         for (String name: names) {
625             Bundle sharedElementBundle = state.getBundle(name);
626             View snapshot = null;
627             if (sharedElementBundle != null) {
628                 Parcelable parcelable = sharedElementBundle.getParcelable(KEY_SNAPSHOT);
629                 if (parcelable != null && mListener != null) {
630                     snapshot = mListener.onCreateSnapshotView(context, parcelable);
631                 }
632                 if (snapshot != null) {
633                     setSharedElementState(snapshot, name, state, tempMatrix, null, decorLoc);
634                 }
635             }
636             // Even null snapshots are added so they remain in the same order as shared elements.
637             snapshots.add(snapshot);
638         }
639         return snapshots;
640     }
641 
setOriginalSharedElementState(ArrayList<View> sharedElements, ArrayList<SharedElementOriginalState> originalState)642     protected static void setOriginalSharedElementState(ArrayList<View> sharedElements,
643             ArrayList<SharedElementOriginalState> originalState) {
644         for (int i = 0; i < originalState.size(); i++) {
645             View view = sharedElements.get(i);
646             SharedElementOriginalState state = originalState.get(i);
647             if (view instanceof ImageView && state.mScaleType != null) {
648                 ImageView imageView = (ImageView) view;
649                 imageView.setScaleType(state.mScaleType);
650                 if (state.mScaleType == ImageView.ScaleType.MATRIX) {
651                   imageView.setImageMatrix(state.mMatrix);
652                 }
653             }
654             view.setElevation(state.mElevation);
655             view.setTranslationZ(state.mTranslationZ);
656             int widthSpec = View.MeasureSpec.makeMeasureSpec(state.mMeasuredWidth,
657                     View.MeasureSpec.EXACTLY);
658             int heightSpec = View.MeasureSpec.makeMeasureSpec(state.mMeasuredHeight,
659                     View.MeasureSpec.EXACTLY);
660             view.measure(widthSpec, heightSpec);
661             view.layout(state.mLeft, state.mTop, state.mRight, state.mBottom);
662         }
663     }
664 
captureSharedElementState()665     protected Bundle captureSharedElementState() {
666         Bundle bundle = new Bundle();
667         RectF tempBounds = new RectF();
668         Matrix tempMatrix = new Matrix();
669         for (int i = 0; i < mSharedElements.size(); i++) {
670             View sharedElement = mSharedElements.get(i);
671             String name = mSharedElementNames.get(i);
672             captureSharedElementState(sharedElement, name, bundle, tempMatrix, tempBounds);
673         }
674         return bundle;
675     }
676 
clearState()677     protected void clearState() {
678         // Clear the state so that we can't hold any references accidentally and leak memory.
679         mWindow = null;
680         mSharedElements.clear();
681         mTransitioningViews = null;
682         mOriginalAlphas.clear();
683         mResultReceiver = null;
684         mPendingTransition = null;
685         mListener = null;
686         mSharedElementParentMatrices = null;
687     }
688 
getFadeDuration()689     protected long getFadeDuration() {
690         return getWindow().getTransitionBackgroundFadeDuration();
691     }
692 
hideViews(ArrayList<View> views)693     protected void hideViews(ArrayList<View> views) {
694         int count = views.size();
695         for (int i = 0; i < count; i++) {
696             View view = views.get(i);
697             if (!mOriginalAlphas.containsKey(view)) {
698                 mOriginalAlphas.put(view, view.getAlpha());
699             }
700             view.setAlpha(0f);
701         }
702     }
703 
showViews(ArrayList<View> views, boolean setTransitionAlpha)704     protected void showViews(ArrayList<View> views, boolean setTransitionAlpha) {
705         int count = views.size();
706         for (int i = 0; i < count; i++) {
707             showView(views.get(i), setTransitionAlpha);
708         }
709     }
710 
showView(View view, boolean setTransitionAlpha)711     private void showView(View view, boolean setTransitionAlpha) {
712         Float alpha = mOriginalAlphas.remove(view);
713         if (alpha != null) {
714             view.setAlpha(alpha);
715         }
716         if (setTransitionAlpha) {
717             view.setTransitionAlpha(1f);
718         }
719     }
720 
721     /**
722      * Captures placement information for Views with a shared element name for
723      * Activity Transitions.
724      *
725      * @param view           The View to capture the placement information for.
726      * @param name           The shared element name in the target Activity to apply the placement
727      *                       information for.
728      * @param transitionArgs Bundle to store shared element placement information.
729      * @param tempBounds     A temporary Rect for capturing the current location of views.
730      */
captureSharedElementState(View view, String name, Bundle transitionArgs, Matrix tempMatrix, RectF tempBounds)731     protected void captureSharedElementState(View view, String name, Bundle transitionArgs,
732             Matrix tempMatrix, RectF tempBounds) {
733         Bundle sharedElementBundle = new Bundle();
734         tempMatrix.reset();
735         view.transformMatrixToGlobal(tempMatrix);
736         tempBounds.set(0, 0, view.getWidth(), view.getHeight());
737         tempMatrix.mapRect(tempBounds);
738 
739         sharedElementBundle.putFloat(KEY_SCREEN_LEFT, tempBounds.left);
740         sharedElementBundle.putFloat(KEY_SCREEN_RIGHT, tempBounds.right);
741         sharedElementBundle.putFloat(KEY_SCREEN_TOP, tempBounds.top);
742         sharedElementBundle.putFloat(KEY_SCREEN_BOTTOM, tempBounds.bottom);
743         sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ());
744         sharedElementBundle.putFloat(KEY_ELEVATION, view.getElevation());
745 
746         Parcelable bitmap = null;
747         if (mListener != null) {
748             bitmap = mListener.onCaptureSharedElementSnapshot(view, tempMatrix, tempBounds);
749         }
750 
751         if (bitmap != null) {
752             sharedElementBundle.putParcelable(KEY_SNAPSHOT, bitmap);
753         }
754 
755         if (view instanceof ImageView) {
756             ImageView imageView = (ImageView) view;
757             int scaleTypeInt = scaleTypeToInt(imageView.getScaleType());
758             sharedElementBundle.putInt(KEY_SCALE_TYPE, scaleTypeInt);
759             if (imageView.getScaleType() == ImageView.ScaleType.MATRIX) {
760                 float[] matrix = new float[9];
761                 imageView.getImageMatrix().getValues(matrix);
762                 sharedElementBundle.putFloatArray(KEY_IMAGE_MATRIX, matrix);
763             }
764         }
765 
766         transitionArgs.putBundle(name, sharedElementBundle);
767     }
768 
769 
startTransition(Runnable runnable)770     protected void startTransition(Runnable runnable) {
771         if (mIsStartingTransition) {
772             mPendingTransition = runnable;
773         } else {
774             mIsStartingTransition = true;
775             runnable.run();
776         }
777     }
778 
transitionStarted()779     protected void transitionStarted() {
780         mIsStartingTransition = false;
781     }
782 
783     /**
784      * Cancels any pending transitions and returns true if there is a transition is in
785      * the middle of starting.
786      */
cancelPendingTransitions()787     protected boolean cancelPendingTransitions() {
788         mPendingTransition = null;
789         return mIsStartingTransition;
790     }
791 
moveSharedElementsToOverlay()792     protected void moveSharedElementsToOverlay() {
793         if (mWindow == null || !mWindow.getSharedElementsUseOverlay()) {
794             return;
795         }
796         setSharedElementMatrices();
797         int numSharedElements = mSharedElements.size();
798         ViewGroup decor = getDecor();
799         if (decor != null) {
800             boolean moveWithParent = moveSharedElementWithParent();
801             Matrix tempMatrix = new Matrix();
802             for (int i = 0; i < numSharedElements; i++) {
803                 View view = mSharedElements.get(i);
804                 tempMatrix.reset();
805                 mSharedElementParentMatrices.get(i).invert(tempMatrix);
806                 GhostView.addGhost(view, decor, tempMatrix);
807                 ViewGroup parent = (ViewGroup) view.getParent();
808                 if (moveWithParent && !isInTransitionGroup(parent, decor)) {
809                     GhostViewListeners listener = new GhostViewListeners(view, parent, decor);
810                     parent.getViewTreeObserver().addOnPreDrawListener(listener);
811                     mGhostViewListeners.add(listener);
812                 }
813             }
814         }
815     }
816 
moveSharedElementWithParent()817     protected boolean moveSharedElementWithParent() {
818         return true;
819     }
820 
isInTransitionGroup(ViewParent viewParent, ViewGroup decor)821     public static boolean isInTransitionGroup(ViewParent viewParent, ViewGroup decor) {
822         if (viewParent == decor || !(viewParent instanceof ViewGroup)) {
823             return false;
824         }
825         ViewGroup parent = (ViewGroup) viewParent;
826         if (parent.isTransitionGroup()) {
827             return true;
828         } else {
829             return isInTransitionGroup(parent.getParent(), decor);
830         }
831     }
832 
moveSharedElementsFromOverlay()833     protected void moveSharedElementsFromOverlay() {
834         int numListeners = mGhostViewListeners.size();
835         for (int i = 0; i < numListeners; i++) {
836             GhostViewListeners listener = mGhostViewListeners.get(i);
837             ViewGroup parent = (ViewGroup) listener.getView().getParent();
838             parent.getViewTreeObserver().removeOnPreDrawListener(listener);
839         }
840         mGhostViewListeners.clear();
841 
842         if (mWindow == null || !mWindow.getSharedElementsUseOverlay()) {
843             return;
844         }
845         ViewGroup decor = getDecor();
846         if (decor != null) {
847             ViewGroupOverlay overlay = decor.getOverlay();
848             int count = mSharedElements.size();
849             for (int i = 0; i < count; i++) {
850                 View sharedElement = mSharedElements.get(i);
851                 GhostView.removeGhost(sharedElement);
852             }
853         }
854     }
855 
setGhostVisibility(int visibility)856     protected void setGhostVisibility(int visibility) {
857         int numSharedElements = mSharedElements.size();
858         for (int i = 0; i < numSharedElements; i++) {
859             GhostView ghostView = GhostView.getGhost(mSharedElements.get(i));
860             if (ghostView != null) {
861                 ghostView.setVisibility(visibility);
862             }
863         }
864     }
865 
scheduleGhostVisibilityChange(final int visibility)866     protected void scheduleGhostVisibilityChange(final int visibility) {
867         final View decorView = getDecor();
868         if (decorView != null) {
869             decorView.getViewTreeObserver()
870                     .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
871                         @Override
872                         public boolean onPreDraw() {
873                             decorView.getViewTreeObserver().removeOnPreDrawListener(this);
874                             setGhostVisibility(visibility);
875                             return true;
876                         }
877                     });
878         }
879     }
880 
isViewsTransitionComplete()881     protected boolean isViewsTransitionComplete() {
882         return mViewsTransitionComplete;
883     }
884 
viewsTransitionComplete()885     protected void viewsTransitionComplete() {
886         mViewsTransitionComplete = true;
887         startInputWhenTransitionsComplete();
888     }
889 
sharedElementTransitionComplete()890     protected void sharedElementTransitionComplete() {
891         mSharedElementTransitionComplete = true;
892         startInputWhenTransitionsComplete();
893     }
startInputWhenTransitionsComplete()894     private void startInputWhenTransitionsComplete() {
895         if (mViewsTransitionComplete && mSharedElementTransitionComplete) {
896             final View decor = getDecor();
897             if (decor != null) {
898                 final ViewRootImpl viewRoot = decor.getViewRootImpl();
899                 if (viewRoot != null) {
900                     viewRoot.setPausedForTransition(false);
901                 }
902             }
903             onTransitionsComplete();
904         }
905     }
906 
pauseInput()907     protected void pauseInput() {
908         final View decor = getDecor();
909         final ViewRootImpl viewRoot = decor == null ? null : decor.getViewRootImpl();
910         if (viewRoot != null) {
911             viewRoot.setPausedForTransition(true);
912         }
913     }
914 
onTransitionsComplete()915     protected void onTransitionsComplete() {}
916 
917     protected class ContinueTransitionListener extends Transition.TransitionListenerAdapter {
918         @Override
onTransitionStart(Transition transition)919         public void onTransitionStart(Transition transition) {
920             mIsStartingTransition = false;
921             Runnable pending = mPendingTransition;
922             mPendingTransition = null;
923             if (pending != null) {
924                 startTransition(pending);
925             }
926         }
927     }
928 
scaleTypeToInt(ImageView.ScaleType scaleType)929     private static int scaleTypeToInt(ImageView.ScaleType scaleType) {
930         for (int i = 0; i < SCALE_TYPE_VALUES.length; i++) {
931             if (scaleType == SCALE_TYPE_VALUES[i]) {
932                 return i;
933             }
934         }
935         return -1;
936     }
937 
setTransitioningViewsVisiblity(int visiblity, boolean invalidate)938     protected void setTransitioningViewsVisiblity(int visiblity, boolean invalidate) {
939         final int numElements = mTransitioningViews == null ? 0 : mTransitioningViews.size();
940         for (int i = 0; i < numElements; i++) {
941             final View view = mTransitioningViews.get(i);
942             view.setTransitionVisibility(visiblity);
943             if (invalidate) {
944                 view.invalidate();
945             }
946         }
947     }
948 
949     /**
950      * Blocks suppressLayout from Visibility transitions. It is ok to suppress the layout,
951      * but we don't want to force the layout when suppressLayout becomes false. This leads
952      * to visual glitches.
953      */
noLayoutSuppressionForVisibilityTransitions(Transition transition)954     private static void noLayoutSuppressionForVisibilityTransitions(Transition transition) {
955         if (transition instanceof Visibility) {
956             final Visibility visibility = (Visibility) transition;
957             visibility.setSuppressLayout(false);
958         } else if (transition instanceof TransitionSet) {
959             final TransitionSet set = (TransitionSet) transition;
960             final int count = set.getTransitionCount();
961             for (int i = 0; i < count; i++) {
962                 noLayoutSuppressionForVisibilityTransitions(set.getTransitionAt(i));
963             }
964         }
965     }
966 
967     private static class FixedEpicenterCallback extends Transition.EpicenterCallback {
968         private Rect mEpicenter;
969 
setEpicenter(Rect epicenter)970         public void setEpicenter(Rect epicenter) { mEpicenter = epicenter; }
971 
972         @Override
onGetEpicenter(Transition transition)973         public Rect onGetEpicenter(Transition transition) {
974             return mEpicenter;
975         }
976     }
977 
978     private static class GhostViewListeners implements ViewTreeObserver.OnPreDrawListener {
979         private View mView;
980         private ViewGroup mDecor;
981         private View mParent;
982         private Matrix mMatrix = new Matrix();
983 
GhostViewListeners(View view, View parent, ViewGroup decor)984         public GhostViewListeners(View view, View parent, ViewGroup decor) {
985             mView = view;
986             mParent = parent;
987             mDecor = decor;
988         }
989 
getView()990         public View getView() {
991             return mView;
992         }
993 
994         @Override
onPreDraw()995         public boolean onPreDraw() {
996             GhostView ghostView = GhostView.getGhost(mView);
997             if (ghostView == null) {
998                 mParent.getViewTreeObserver().removeOnPreDrawListener(this);
999             } else {
1000                 GhostView.calculateMatrix(mView, mDecor, mMatrix);
1001                 ghostView.setMatrix(mMatrix);
1002             }
1003             return true;
1004         }
1005     }
1006 
1007     static class SharedElementOriginalState {
1008         int mLeft;
1009         int mTop;
1010         int mRight;
1011         int mBottom;
1012         int mMeasuredWidth;
1013         int mMeasuredHeight;
1014         ImageView.ScaleType mScaleType;
1015         Matrix mMatrix;
1016         float mTranslationZ;
1017         float mElevation;
1018     }
1019 }
1020