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