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.content.res.Resources;
20import android.graphics.Bitmap;
21import android.graphics.Matrix;
22import android.graphics.RectF;
23import android.graphics.drawable.BitmapDrawable;
24import android.graphics.drawable.Drawable;
25import android.os.Bundle;
26import android.os.Parcelable;
27import android.transition.TransitionUtils;
28import android.view.View;
29import android.widget.ImageView;
30import android.widget.ImageView.ScaleType;
31
32import java.util.List;
33import java.util.Map;
34
35/**
36 * Listener provided in
37 * {@link Activity#setEnterSharedElementCallback(SharedElementCallback)} and
38 * {@link Activity#setExitSharedElementCallback(SharedElementCallback)} as well as
39 * {@link Fragment#setEnterSharedElementCallback(SharedElementCallback)} and
40 * {@link Fragment#setExitSharedElementCallback(SharedElementCallback)}
41 * to monitor the Shared element transitions. The events can be used to customize Activity
42 * and Fragment Transition behavior.
43 */
44public abstract class SharedElementCallback {
45    private Matrix mTempMatrix;
46    private static final String BUNDLE_SNAPSHOT_BITMAP = "sharedElement:snapshot:bitmap";
47    private static final String BUNDLE_SNAPSHOT_IMAGE_SCALETYPE = "sharedElement:snapshot:imageScaleType";
48    private static final String BUNDLE_SNAPSHOT_IMAGE_MATRIX = "sharedElement:snapshot:imageMatrix";
49
50    static final SharedElementCallback NULL_CALLBACK = new SharedElementCallback() {
51    };
52
53    /**
54     * In Activity Transitions, onSharedElementStart is called immediately before
55     * capturing the start of the shared element state on enter and reenter transitions and
56     * immediately before capturing the end of the shared element state for exit and return
57     * transitions.
58     * <p>
59     * In Fragment Transitions, onSharedElementStart is called immediately before capturing the
60     * start state of all shared element transitions.
61     * <p>
62     * This call can be used to adjust the transition start state by modifying the shared
63     * element Views. Note that no layout step will be executed between onSharedElementStart
64     * and the transition state capture.
65     * <p>
66     * For Activity Transitions, any changes made in {@link #onSharedElementEnd(List, List, List)}
67     * that are not updated during by layout should be corrected in onSharedElementStart for exit and
68     * return transitions. For example, rotation or scale will not be affected by layout and
69     * if changed in {@link #onSharedElementEnd(List, List, List)}, it will also have to be reset
70     * in onSharedElementStart again to correct the end state.
71     *
72     * @param sharedElementNames The names of the shared elements that were accepted into
73     *                           the View hierarchy.
74     * @param sharedElements The shared elements that are part of the View hierarchy.
75     * @param sharedElementSnapshots The Views containing snap shots of the shared element
76     *                               from the launching Window. These elements will not
77     *                               be part of the scene, but will be positioned relative
78     *                               to the Window decor View. This list is null for Fragment
79     *                               Transitions.
80     */
81    public void onSharedElementStart(List<String> sharedElementNames,
82            List<View> sharedElements, List<View> sharedElementSnapshots) {}
83
84    /**
85     * In Activity Transitions, onSharedElementEnd is called immediately before
86     * capturing the end of the shared element state on enter and reenter transitions and
87     * immediately before capturing the start of the shared element state for exit and return
88     * transitions.
89     * <p>
90     * In Fragment Transitions, onSharedElementEnd is called immediately before capturing the
91     * end state of all shared element transitions.
92     * <p>
93     * This call can be used to adjust the transition end state by modifying the shared
94     * element Views. Note that no layout step will be executed between onSharedElementEnd
95     * and the transition state capture.
96     * <p>
97     * Any changes made in {@link #onSharedElementStart(List, List, List)} that are not updated
98     * during layout should be corrected in onSharedElementEnd. For example, rotation or scale
99     * will not be affected by layout and if changed in
100     * {@link #onSharedElementStart(List, List, List)}, it will also have to be reset in
101     * onSharedElementEnd again to correct the end state.
102     *
103     * @param sharedElementNames The names of the shared elements that were accepted into
104     *                           the View hierarchy.
105     * @param sharedElements The shared elements that are part of the View hierarchy.
106     * @param sharedElementSnapshots The Views containing snap shots of the shared element
107     *                               from the launching Window. These elements will not
108     *                               be part of the scene, but will be positioned relative
109     *                               to the Window decor View. This list will be null for
110     *                               Fragment Transitions.
111     */
112    public void onSharedElementEnd(List<String> sharedElementNames,
113            List<View> sharedElements, List<View> sharedElementSnapshots) {}
114
115    /**
116     * Called after {@link #onMapSharedElements(java.util.List, java.util.Map)} when
117     * transferring shared elements in. Any shared elements that have no mapping will be in
118     * <var>rejectedSharedElements</var>. The elements remaining in
119     * <var>rejectedSharedElements</var> will be transitioned out of the Scene. If a
120     * View is removed from <var>rejectedSharedElements</var>, it must be handled by the
121     * <code>SharedElementCallback</code>.
122     * <p>
123     * Views in rejectedSharedElements will have their position and size set to the
124     * position of the calling shared element, relative to the Window decor View and contain
125     * snapshots of the View from the calling Activity or Fragment. This
126     * view may be safely added to the decor View's overlay to remain in position.
127     * </p>
128     * <p>This method is not called for Fragment Transitions. All rejected shared elements
129     * will be handled by the exit transition.</p>
130     *
131     * @param rejectedSharedElements Views containing visual information of shared elements
132     *                               that are not part of the entering scene. These Views
133     *                               are positioned relative to the Window decor View. A
134     *                               View removed from this list will not be transitioned
135     *                               automatically.
136     */
137    public void onRejectSharedElements(List<View> rejectedSharedElements) {}
138
139    /**
140     * Lets the SharedElementCallback adjust the mapping of shared element names to
141     * Views.
142     *
143     * @param names The names of all shared elements transferred from the calling Activity
144     *              or Fragment in the order they were provided.
145     * @param sharedElements The mapping of shared element names to Views. The best guess
146     *                       will be filled into sharedElements based on the transitionNames.
147     */
148    public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {}
149
150    /**
151     * Creates a snapshot of a shared element to be used by the remote Activity and reconstituted
152     * with {@link #onCreateSnapshotView(android.content.Context, android.os.Parcelable)}. A
153     * null return value will mean that the remote Activity will have a null snapshot View in
154     * {@link #onSharedElementStart(java.util.List, java.util.List, java.util.List)} and
155     * {@link #onSharedElementEnd(java.util.List, java.util.List, java.util.List)}.
156     *
157     * <p>This is not called for Fragment Transitions.</p>
158     *
159     * @param sharedElement The shared element View to create a snapshot for.
160     * @param viewToGlobalMatrix A matrix containing a transform from the view to the screen
161     *                           coordinates.
162     * @param screenBounds The bounds of shared element in screen coordinate space. This is
163     *                     the bounds of the view with the viewToGlobalMatrix applied.
164     * @return A snapshot to send to the remote Activity to be reconstituted with
165     * {@link #onCreateSnapshotView(android.content.Context, android.os.Parcelable)} and passed
166     * into {@link #onSharedElementStart(java.util.List, java.util.List, java.util.List)} and
167     * {@link #onSharedElementEnd(java.util.List, java.util.List, java.util.List)}.
168     */
169    public Parcelable onCaptureSharedElementSnapshot(View sharedElement, Matrix viewToGlobalMatrix,
170            RectF screenBounds) {
171        if (sharedElement instanceof ImageView) {
172            ImageView imageView = ((ImageView) sharedElement);
173            Drawable d = imageView.getDrawable();
174            Drawable bg = imageView.getBackground();
175            if (d != null && (bg == null || bg.getAlpha() == 0)) {
176                Bitmap bitmap = TransitionUtils.createDrawableBitmap(d);
177                if (bitmap != null) {
178                    Bundle bundle = new Bundle();
179                    bundle.putParcelable(BUNDLE_SNAPSHOT_BITMAP, bitmap);
180                    bundle.putString(BUNDLE_SNAPSHOT_IMAGE_SCALETYPE,
181                            imageView.getScaleType().toString());
182                    if (imageView.getScaleType() == ScaleType.MATRIX) {
183                        Matrix matrix = imageView.getImageMatrix();
184                        float[] values = new float[9];
185                        matrix.getValues(values);
186                        bundle.putFloatArray(BUNDLE_SNAPSHOT_IMAGE_MATRIX, values);
187                    }
188                    return bundle;
189                }
190            }
191        }
192        if (mTempMatrix == null) {
193            mTempMatrix = new Matrix(viewToGlobalMatrix);
194        } else {
195            mTempMatrix.set(viewToGlobalMatrix);
196        }
197        return TransitionUtils.createViewBitmap(sharedElement, mTempMatrix, screenBounds);
198    }
199
200    /**
201     * Reconstitutes a snapshot View from a Parcelable returned in
202     * {@link #onCaptureSharedElementSnapshot(android.view.View, android.graphics.Matrix,
203     * android.graphics.RectF)} to be used in {@link #onSharedElementStart(java.util.List,
204     * java.util.List, java.util.List)} and {@link #onSharedElementEnd(java.util.List,
205     * java.util.List, java.util.List)}. The returned View will be sized and positioned after
206     * this call so that it is ready to be added to the decor View's overlay.
207     *
208     * <p>This is not called for Fragment Transitions.</p>
209     *
210     * @param context The Context used to create the snapshot View.
211     * @param snapshot The Parcelable returned by {@link #onCaptureSharedElementSnapshot(
212     * android.view.View, android.graphics.Matrix, android.graphics.RectF)}.
213     * @return A View to be sent in {@link #onSharedElementStart(java.util.List, java.util.List,
214     * java.util.List)} and {@link #onSharedElementEnd(java.util.List, java.util.List,
215     * java.util.List)}. A null value will produce a null snapshot value for those two methods.
216     */
217    public View onCreateSnapshotView(Context context, Parcelable snapshot) {
218        View view = null;
219        if (snapshot instanceof Bundle) {
220            Bundle bundle = (Bundle) snapshot;
221            Bitmap bitmap = (Bitmap) bundle.getParcelable(BUNDLE_SNAPSHOT_BITMAP);
222            if (bitmap == null) {
223                return null;
224            }
225            ImageView imageView = new ImageView(context);
226            view = imageView;
227            imageView.setImageBitmap(bitmap);
228            imageView.setScaleType(
229                    ScaleType.valueOf(bundle.getString(BUNDLE_SNAPSHOT_IMAGE_SCALETYPE)));
230            if (imageView.getScaleType() == ScaleType.MATRIX) {
231                float[] values = bundle.getFloatArray(BUNDLE_SNAPSHOT_IMAGE_MATRIX);
232                Matrix matrix = new Matrix();
233                matrix.setValues(values);
234                imageView.setImageMatrix(matrix);
235            }
236        } else if (snapshot instanceof Bitmap) {
237            Bitmap bitmap = (Bitmap) snapshot;
238            view = new View(context);
239            Resources resources = context.getResources();
240            view.setBackground(new BitmapDrawable(resources, bitmap));
241        }
242        return view;
243    }
244
245    /**
246     * Called during an Activity Transition when the shared elements have arrived at the
247     * final location and are ready to be transferred. This method is called for both the
248     * source and destination Activities.
249     * <p>
250     * When the shared elements are ready to be transferred,
251     * {@link OnSharedElementsReadyListener#onSharedElementsReady()}
252     * must be called to trigger the transfer.
253     * <p>
254     * The default behavior is to trigger the transfer immediately.
255     *
256     * @param sharedElementNames The names of the shared elements that are being transferred..
257     * @param sharedElements The shared elements that are part of the View hierarchy.
258     * @param listener The listener to call when the shared elements are ready to be hidden
259     *                 in the source Activity or shown in the destination Activity.
260     */
261    public void onSharedElementsArrived(List<String> sharedElementNames,
262            List<View> sharedElements, OnSharedElementsReadyListener listener) {
263        listener.onSharedElementsReady();
264    }
265
266    /**
267     * Listener to be called after {@link
268     * SharedElementCallback#onSharedElementsArrived(List, List, OnSharedElementsReadyListener)}
269     * when the shared elements are ready to be hidden in the source Activity and shown in the
270     * destination Activity.
271     */
272    public interface OnSharedElementsReadyListener {
273
274        /**
275         * Call this method during or after the OnSharedElementsReadyListener has been received
276         * in {@link SharedElementCallback#onSharedElementsArrived(List, List,
277         * OnSharedElementsReadyListener)} to indicate that the shared elements are ready to be
278         * hidden in the source and shown in the destination Activity.
279         */
280        void onSharedElementsReady();
281    }
282}
283