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