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