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