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