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.TransitionSet;
28import android.util.ArrayMap;
29import android.view.GhostView;
30import android.view.View;
31import android.view.ViewGroup;
32import android.view.ViewGroupOverlay;
33import android.view.ViewParent;
34import android.view.ViewTreeObserver;
35import android.view.Window;
36import android.widget.ImageView;
37
38import java.util.ArrayList;
39import 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 */
114abstract 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
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
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     */
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     */
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
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
304    protected Window getWindow() {
305        return mWindow;
306    }
307
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     */
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
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
336    public ArrayList<String> getAcceptedNames() {
337        return mSharedElementNames;
338    }
339
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
348    public ArrayList<View> copyMappedViews() {
349        return new ArrayList<View>(mSharedElements);
350    }
351
352    public ArrayList<String> getAllSharedElementNames() { return mAllSharedElementNames; }
353
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
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
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
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
423    protected void setResultReceiver(ResultReceiver resultReceiver) {
424        mResultReceiver = resultReceiver;
425    }
426
427    protected abstract Transition getViewsTransition();
428
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
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
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
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
575    protected void notifySharedElementEnd(ArrayList<View> snapshots) {
576        if (mListener != null) {
577            mListener.onSharedElementEnd(mSharedElementNames, mSharedElements, snapshots);
578        }
579    }
580
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
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
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
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
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
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
708    protected long getFadeDuration() {
709        return getWindow().getTransitionBackgroundFadeDuration();
710    }
711
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
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
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     */
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
789    protected void startTransition(Runnable runnable) {
790        if (mIsStartingTransition) {
791            mPendingTransition = runnable;
792        } else {
793            mIsStartingTransition = true;
794            runnable.run();
795        }
796    }
797
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     */
806    protected boolean cancelPendingTransitions() {
807        mPendingTransition = null;
808        return mIsStartingTransition;
809    }
810
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
833    protected boolean moveSharedElementWithParent() {
834        return true;
835    }
836
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
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
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
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
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
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
921        public void setEpicenter(Rect epicenter) { mEpicenter = epicenter; }
922
923        @Override
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
935        public GhostViewListeners(View view, View parent, ViewGroup decor) {
936            mView = view;
937            mParent = parent;
938            mDecor = decor;
939        }
940
941        public View getView() {
942            return mView;
943        }
944
945        @Override
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