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