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