1 /*
2  * Copyright (C) 2023 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 
17 package com.android.internal.util;
18 
19 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
20 import static android.os.UserHandle.USER_NULL;
21 import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
22 import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
23 
24 import android.annotation.NonNull;
25 import android.content.ComponentName;
26 import android.graphics.Bitmap;
27 import android.graphics.ColorSpace;
28 import android.graphics.Insets;
29 import android.graphics.ParcelableColorSpace;
30 import android.graphics.Rect;
31 import android.hardware.HardwareBuffer;
32 import android.os.Bundle;
33 import android.os.Parcel;
34 import android.os.Parcelable;
35 import android.util.Log;
36 import android.view.WindowManager;
37 
38 import java.util.Objects;
39 
40 /**
41  * Describes a screenshot request.
42  */
43 public class ScreenshotRequest implements Parcelable {
44     private static final String TAG = "ScreenshotRequest";
45 
46     @WindowManager.ScreenshotType
47     private final int mType;
48     @WindowManager.ScreenshotSource
49     private final int mSource;
50     private final ComponentName mTopComponent;
51     private final int mTaskId;
52     private final int mUserId;
53     private final Bitmap mBitmap;
54     private final Rect mBoundsInScreen;
55     private final Insets mInsets;
56 
ScreenshotRequest( @indowManager.ScreenshotType int type, @WindowManager.ScreenshotSource int source, ComponentName topComponent, int taskId, int userId, Bitmap bitmap, Rect boundsInScreen, Insets insets)57     private ScreenshotRequest(
58             @WindowManager.ScreenshotType int type, @WindowManager.ScreenshotSource int source,
59             ComponentName topComponent, int taskId, int userId,
60             Bitmap bitmap, Rect boundsInScreen, Insets insets) {
61         mType = type;
62         mSource = source;
63         mTopComponent = topComponent;
64         mTaskId = taskId;
65         mUserId = userId;
66         mBitmap = bitmap;
67         mBoundsInScreen = boundsInScreen;
68         mInsets = insets;
69     }
70 
ScreenshotRequest(Parcel in)71     ScreenshotRequest(Parcel in) {
72         mType = in.readInt();
73         mSource = in.readInt();
74         mTopComponent = in.readTypedObject(ComponentName.CREATOR);
75         mTaskId = in.readInt();
76         mUserId = in.readInt();
77         mBitmap = HardwareBitmapBundler.bundleToHardwareBitmap(in.readTypedObject(Bundle.CREATOR));
78         mBoundsInScreen = in.readTypedObject(Rect.CREATOR);
79         mInsets = in.readTypedObject(Insets.CREATOR);
80     }
81 
82     @WindowManager.ScreenshotType
getType()83     public int getType() {
84         return mType;
85     }
86 
87     @WindowManager.ScreenshotSource
getSource()88     public int getSource() {
89         return mSource;
90     }
91 
getBitmap()92     public Bitmap getBitmap() {
93         return mBitmap;
94     }
95 
getBoundsInScreen()96     public Rect getBoundsInScreen() {
97         return mBoundsInScreen;
98     }
99 
getInsets()100     public Insets getInsets() {
101         return mInsets;
102     }
103 
getTaskId()104     public int getTaskId() {
105         return mTaskId;
106     }
107 
getUserId()108     public int getUserId() {
109         return mUserId;
110     }
111 
getTopComponent()112     public ComponentName getTopComponent() {
113         return mTopComponent;
114     }
115 
116     @Override
describeContents()117     public int describeContents() {
118         return 0;
119     }
120 
121     @Override
writeToParcel(Parcel dest, int flags)122     public void writeToParcel(Parcel dest, int flags) {
123         dest.writeInt(mType);
124         dest.writeInt(mSource);
125         dest.writeTypedObject(mTopComponent, 0);
126         dest.writeInt(mTaskId);
127         dest.writeInt(mUserId);
128         dest.writeTypedObject(HardwareBitmapBundler.hardwareBitmapToBundle(mBitmap), 0);
129         dest.writeTypedObject(mBoundsInScreen, 0);
130         dest.writeTypedObject(mInsets, 0);
131     }
132 
133     @NonNull
134     public static final Parcelable.Creator<ScreenshotRequest> CREATOR =
135             new Parcelable.Creator<ScreenshotRequest>() {
136 
137                 @Override
138                 public ScreenshotRequest createFromParcel(Parcel source) {
139                     return new ScreenshotRequest(source);
140                 }
141 
142                 @Override
143                 public ScreenshotRequest[] newArray(int size) {
144                     return new ScreenshotRequest[size];
145                 }
146             };
147 
148     /**
149      * Builder class for {@link ScreenshotRequest} objects.
150      */
151     public static class Builder {
152         @WindowManager.ScreenshotType
153         private final int mType;
154 
155         @WindowManager.ScreenshotSource
156         private final int mSource;
157 
158         private Bitmap mBitmap;
159         private Rect mBoundsInScreen;
160         private Insets mInsets = Insets.NONE;
161         private int mTaskId = INVALID_TASK_ID;
162         private int mUserId = USER_NULL;
163         private ComponentName mTopComponent;
164 
165         /**
166          * Begin building a ScreenshotRequest.
167          *
168          * @param type   The type of the screenshot request, defined by {@link
169          *               WindowManager.ScreenshotType}
170          * @param source The source of the screenshot request, defined by {@link
171          *               WindowManager.ScreenshotSource}
172          */
Builder( @indowManager.ScreenshotType int type, @WindowManager.ScreenshotSource int source)173         public Builder(
174                 @WindowManager.ScreenshotType int type,
175                 @WindowManager.ScreenshotSource int source) {
176             if (type != TAKE_SCREENSHOT_FULLSCREEN && type != TAKE_SCREENSHOT_PROVIDED_IMAGE) {
177                 throw new IllegalArgumentException("Invalid screenshot type requested!");
178             }
179             mType = type;
180             mSource = source;
181         }
182 
183         /**
184          * Construct a new {@link ScreenshotRequest} with the set parameters.
185          */
build()186         public ScreenshotRequest build() {
187             if (mType == TAKE_SCREENSHOT_FULLSCREEN && mBitmap != null) {
188                 Log.w(TAG, "Bitmap provided, but request is fullscreen. Bitmap will be ignored.");
189             }
190             if (mType == TAKE_SCREENSHOT_PROVIDED_IMAGE && mBitmap == null) {
191                 throw new IllegalStateException(
192                         "Request is PROVIDED_IMAGE, but no bitmap is provided!");
193             }
194 
195             return new ScreenshotRequest(mType, mSource, mTopComponent, mTaskId, mUserId, mBitmap,
196                     mBoundsInScreen, mInsets);
197         }
198 
199         /**
200          * Set the top component associated with this request.
201          *
202          * @param topComponent The component name of the top component running in the task.
203          */
setTopComponent(ComponentName topComponent)204         public Builder setTopComponent(ComponentName topComponent) {
205             mTopComponent = topComponent;
206             return this;
207         }
208 
209         /**
210          * Set the task id associated with this request.
211          *
212          * @param taskId The taskId of the task that the screenshot was taken of.
213          */
setTaskId(int taskId)214         public Builder setTaskId(int taskId) {
215             mTaskId = taskId;
216             return this;
217         }
218 
219         /**
220          * Set the user id associated with this request.
221          *
222          * @param userId The userId of user running the task provided in taskId.
223          */
setUserId(int userId)224         public Builder setUserId(int userId) {
225             mUserId = userId;
226             return this;
227         }
228 
229         /**
230          * Set the bitmap associated with this request.
231          *
232          * @param bitmap The provided screenshot.
233          */
setBitmap(Bitmap bitmap)234         public Builder setBitmap(Bitmap bitmap) {
235             mBitmap = bitmap;
236             return this;
237         }
238 
239         /**
240          * Set the bounds for the provided bitmap.
241          *
242          * @param bounds The bounds in screen coordinates that the bitmap originated from.
243          */
setBoundsOnScreen(Rect bounds)244         public Builder setBoundsOnScreen(Rect bounds) {
245             mBoundsInScreen = bounds;
246             return this;
247         }
248 
249         /**
250          * Set the insets for the provided bitmap.
251          *
252          * @param insets The insets that the image was shown with, inside the screen bounds.
253          */
setInsets(@onNull Insets insets)254         public Builder setInsets(@NonNull Insets insets) {
255             mInsets = insets;
256             return this;
257         }
258     }
259 
260     /**
261      * Bundler used to convert between a hardware bitmap and a bundle without copying the internal
262      * content. This is used together with a fully-defined ScreenshotRequest to handle a hardware
263      * bitmap as a screenshot.
264      */
265     private static final class HardwareBitmapBundler {
266         private static final String KEY_BUFFER = "bitmap_util_buffer";
267         private static final String KEY_COLOR_SPACE = "bitmap_util_color_space";
268 
HardwareBitmapBundler()269         private HardwareBitmapBundler() {
270         }
271 
272         /**
273          * Creates a Bundle that represents the given Bitmap.
274          * <p>The Bundle will contain a wrapped version of the Bitmaps HardwareBuffer, so will
275          * avoid
276          * copies when passing across processes, only pass to processes you trust.
277          *
278          * <p>Returns a new Bundle rather than modifying an exiting one to avoid key collisions,
279          * the
280          * returned Bundle should be treated as a standalone object.
281          *
282          * @param bitmap to convert to bundle
283          * @return a Bundle representing the bitmap, should only be parsed by
284          * {@link #bundleToHardwareBitmap(Bundle)}
285          */
hardwareBitmapToBundle(Bitmap bitmap)286         private static Bundle hardwareBitmapToBundle(Bitmap bitmap) {
287             if (bitmap == null) {
288                 return null;
289             }
290             if (bitmap.getConfig() != Bitmap.Config.HARDWARE) {
291                 throw new IllegalArgumentException(
292                         "Passed bitmap must have hardware config, found: "
293                                 + bitmap.getConfig());
294             }
295 
296             // Bitmap assumes SRGB for null color space
297             ParcelableColorSpace colorSpace =
298                     bitmap.getColorSpace() == null
299                             ? new ParcelableColorSpace(ColorSpace.get(ColorSpace.Named.SRGB))
300                             : new ParcelableColorSpace(bitmap.getColorSpace());
301 
302             Bundle bundle = new Bundle();
303             bundle.putParcelable(KEY_BUFFER, bitmap.getHardwareBuffer());
304             bundle.putParcelable(KEY_COLOR_SPACE, colorSpace);
305 
306             return bundle;
307         }
308 
309         /**
310          * Extracts the Bitmap added to a Bundle with {@link #hardwareBitmapToBundle(Bitmap)}.
311          *
312          * <p>This Bitmap contains the HardwareBuffer from the original caller, be careful
313          * passing
314          * this Bitmap on to any other source.
315          *
316          * @param bundle containing the bitmap
317          * @return a hardware Bitmap
318          */
bundleToHardwareBitmap(Bundle bundle)319         private static Bitmap bundleToHardwareBitmap(Bundle bundle) {
320             if (bundle == null) {
321                 return null;
322             }
323             if (!bundle.containsKey(KEY_BUFFER) || !bundle.containsKey(KEY_COLOR_SPACE)) {
324                 throw new IllegalArgumentException("Bundle does not contain a hardware bitmap");
325             }
326 
327             HardwareBuffer buffer = bundle.getParcelable(KEY_BUFFER, HardwareBuffer.class);
328             ParcelableColorSpace colorSpace = bundle.getParcelable(KEY_COLOR_SPACE,
329                     ParcelableColorSpace.class);
330 
331             return Bitmap.wrapHardwareBuffer(Objects.requireNonNull(buffer),
332                     colorSpace.getColorSpace());
333         }
334     }
335 }
336