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