1 /* 2 * Copyright (C) 2022 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.server.wm; 18 19 import static android.graphics.Bitmap.CompressFormat.JPEG; 20 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; 21 22 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; 23 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; 24 25 import android.annotation.NonNull; 26 import android.graphics.Bitmap; 27 import android.graphics.PixelFormat; 28 import android.hardware.HardwareBuffer; 29 import android.media.Image; 30 import android.media.ImageReader; 31 import android.os.Process; 32 import android.os.SystemClock; 33 import android.os.Trace; 34 import android.util.AtomicFile; 35 import android.util.Slog; 36 import android.window.TaskSnapshot; 37 38 import com.android.internal.annotations.GuardedBy; 39 import com.android.internal.annotations.VisibleForTesting; 40 import com.android.internal.policy.TransitionAnimation; 41 import com.android.server.LocalServices; 42 import com.android.server.pm.UserManagerInternal; 43 import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider; 44 import com.android.server.wm.nano.WindowManagerProtos.TaskSnapshotProto; 45 import com.android.window.flags.Flags; 46 47 import java.io.File; 48 import java.io.FileOutputStream; 49 import java.io.IOException; 50 import java.io.PrintWriter; 51 import java.util.ArrayDeque; 52 53 /** 54 * Singleton worker thread to queue up persist or delete tasks of {@link TaskSnapshot}s to disk. 55 */ 56 class SnapshotPersistQueue { 57 private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotPersister" : TAG_WM; 58 private static final long DELAY_MS = 100; 59 static final int MAX_STORE_QUEUE_DEPTH = 2; 60 private static final int COMPRESS_QUALITY = 95; 61 62 @GuardedBy("mLock") 63 private final ArrayDeque<WriteQueueItem> mWriteQueue = new ArrayDeque<>(); 64 @GuardedBy("mLock") 65 private final ArrayDeque<StoreWriteQueueItem> mStoreQueueItems = new ArrayDeque<>(); 66 @GuardedBy("mLock") 67 private boolean mQueueIdling; 68 @GuardedBy("mLock") 69 private boolean mPaused; 70 private boolean mStarted; 71 private final Object mLock = new Object(); 72 private final UserManagerInternal mUserManagerInternal; 73 private boolean mShutdown; 74 SnapshotPersistQueue()75 SnapshotPersistQueue() { 76 mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); 77 } 78 getLock()79 Object getLock() { 80 return mLock; 81 } 82 systemReady()83 void systemReady() { 84 start(); 85 } 86 87 /** 88 * Starts persisting. 89 */ start()90 void start() { 91 if (!mStarted) { 92 mStarted = true; 93 mPersister.start(); 94 } 95 } 96 97 /** 98 * Temporarily pauses/unpauses persisting of task snapshots. 99 * 100 * @param paused Whether task snapshot persisting should be paused. 101 */ setPaused(boolean paused)102 void setPaused(boolean paused) { 103 synchronized (mLock) { 104 mPaused = paused; 105 if (!paused) { 106 mLock.notifyAll(); 107 } 108 } 109 } 110 111 /** 112 * Prepare to enqueue all visible task snapshots because of shutdown. 113 */ prepareShutdown()114 void prepareShutdown() { 115 synchronized (mLock) { 116 mShutdown = true; 117 } 118 } 119 isQueueEmpty()120 private boolean isQueueEmpty() { 121 synchronized (mLock) { 122 return mWriteQueue.isEmpty() || mQueueIdling || mPaused; 123 } 124 } 125 waitFlush(long timeout)126 void waitFlush(long timeout) { 127 if (timeout <= 0) { 128 return; 129 } 130 final long endTime = System.currentTimeMillis() + timeout; 131 while (true) { 132 if (!isQueueEmpty()) { 133 long timeRemaining = endTime - System.currentTimeMillis(); 134 if (timeRemaining > 0) { 135 synchronized (mLock) { 136 try { 137 mLock.wait(timeRemaining); 138 } catch (InterruptedException e) { 139 } 140 } 141 } else { 142 Slog.w(TAG, "Snapshot Persist Queue flush timed out"); 143 break; 144 } 145 } else { 146 break; 147 } 148 } 149 } 150 151 @VisibleForTesting waitForQueueEmpty()152 void waitForQueueEmpty() { 153 while (true) { 154 synchronized (mLock) { 155 if (mWriteQueue.isEmpty() && mQueueIdling) { 156 return; 157 } 158 } 159 SystemClock.sleep(DELAY_MS); 160 } 161 } 162 peekWriteQueueSize()163 int peekWriteQueueSize() { 164 synchronized (mLock) { 165 return mStoreQueueItems.size(); 166 } 167 } 168 peekQueueSize()169 int peekQueueSize() { 170 synchronized (mLock) { 171 return mWriteQueue.size(); 172 } 173 } 174 addToQueueInternal(WriteQueueItem item, boolean insertToFront)175 private void addToQueueInternal(WriteQueueItem item, boolean insertToFront) { 176 mWriteQueue.removeFirstOccurrence(item); 177 if (insertToFront) { 178 mWriteQueue.addFirst(item); 179 } else { 180 mWriteQueue.addLast(item); 181 } 182 item.onQueuedLocked(); 183 if (!mShutdown) { 184 ensureStoreQueueDepthLocked(); 185 } 186 if (!mPaused) { 187 mLock.notifyAll(); 188 } 189 } 190 191 @GuardedBy("mLock") sendToQueueLocked(WriteQueueItem item)192 void sendToQueueLocked(WriteQueueItem item) { 193 addToQueueInternal(item, false /* insertToFront */); 194 } 195 196 @GuardedBy("mLock") insertQueueAtFirstLocked(WriteQueueItem item)197 void insertQueueAtFirstLocked(WriteQueueItem item) { 198 addToQueueInternal(item, true /* insertToFront */); 199 } 200 201 @GuardedBy("mLock") ensureStoreQueueDepthLocked()202 private void ensureStoreQueueDepthLocked() { 203 while (mStoreQueueItems.size() > MAX_STORE_QUEUE_DEPTH) { 204 final StoreWriteQueueItem item = mStoreQueueItems.poll(); 205 mWriteQueue.remove(item); 206 Slog.i(TAG, "Queue is too deep! Purged item with index=" + item.mId); 207 } 208 } 209 deleteSnapshot(int index, int userId, PersistInfoProvider provider)210 void deleteSnapshot(int index, int userId, PersistInfoProvider provider) { 211 final File protoFile = provider.getProtoFile(index, userId); 212 final File bitmapLowResFile = provider.getLowResolutionBitmapFile(index, userId); 213 if (protoFile.exists()) { 214 protoFile.delete(); 215 } 216 if (bitmapLowResFile.exists()) { 217 bitmapLowResFile.delete(); 218 } 219 final File bitmapFile = provider.getHighResolutionBitmapFile(index, userId); 220 if (bitmapFile.exists()) { 221 bitmapFile.delete(); 222 } 223 } 224 225 private final Thread mPersister = new Thread("TaskSnapshotPersister") { 226 public void run() { 227 android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 228 while (true) { 229 WriteQueueItem next; 230 boolean isReadyToWrite = false; 231 synchronized (mLock) { 232 if (mPaused) { 233 next = null; 234 } else { 235 next = mWriteQueue.poll(); 236 if (next != null) { 237 if (next.isReady(mUserManagerInternal)) { 238 isReadyToWrite = true; 239 next.onDequeuedLocked(); 240 } else if (!mShutdown) { 241 mWriteQueue.addLast(next); 242 } else { 243 // User manager is locked and device is shutting down, skip writing 244 // this item. 245 next.onDequeuedLocked(); 246 next = null; 247 } 248 } 249 } 250 } 251 if (next != null) { 252 if (isReadyToWrite) { 253 next.write(); 254 } 255 if (!mShutdown) { 256 SystemClock.sleep(DELAY_MS); 257 } 258 } 259 synchronized (mLock) { 260 final boolean writeQueueEmpty = mWriteQueue.isEmpty(); 261 if (!writeQueueEmpty && !mPaused) { 262 continue; 263 } 264 if (mShutdown && writeQueueEmpty) { 265 mLock.notifyAll(); 266 } 267 try { 268 mQueueIdling = writeQueueEmpty; 269 mLock.wait(); 270 mQueueIdling = false; 271 } catch (InterruptedException e) { 272 } 273 } 274 } 275 } 276 }; 277 278 abstract static class WriteQueueItem { 279 protected final PersistInfoProvider mPersistInfoProvider; 280 protected final int mUserId; WriteQueueItem(@onNull PersistInfoProvider persistInfoProvider, int userId)281 WriteQueueItem(@NonNull PersistInfoProvider persistInfoProvider, int userId) { 282 mPersistInfoProvider = persistInfoProvider; 283 mUserId = userId; 284 } 285 /** 286 * @return {@code true} if item is ready to have {@link WriteQueueItem#write} called 287 */ isReady(UserManagerInternal userManager)288 boolean isReady(UserManagerInternal userManager) { 289 return userManager.isUserUnlocked(mUserId); 290 } 291 write()292 abstract void write(); 293 294 /** 295 * Called when this queue item has been put into the queue. 296 */ onQueuedLocked()297 void onQueuedLocked() { 298 } 299 300 /** 301 * Called when this queue item has been taken out of the queue. 302 */ onDequeuedLocked()303 void onDequeuedLocked() { 304 } 305 } 306 createStoreWriteQueueItem(int id, int userId, TaskSnapshot snapshot, PersistInfoProvider provider)307 StoreWriteQueueItem createStoreWriteQueueItem(int id, int userId, TaskSnapshot snapshot, 308 PersistInfoProvider provider) { 309 return new StoreWriteQueueItem(id, userId, snapshot, provider); 310 } 311 312 class StoreWriteQueueItem extends WriteQueueItem { 313 private final int mId; 314 private final TaskSnapshot mSnapshot; 315 StoreWriteQueueItem(int id, int userId, TaskSnapshot snapshot, PersistInfoProvider provider)316 StoreWriteQueueItem(int id, int userId, TaskSnapshot snapshot, 317 PersistInfoProvider provider) { 318 super(provider, userId); 319 mId = id; 320 snapshot.addReference(TaskSnapshot.REFERENCE_PERSIST); 321 mSnapshot = snapshot; 322 } 323 324 @GuardedBy("mLock") 325 @Override onQueuedLocked()326 void onQueuedLocked() { 327 // Remove duplicate request. 328 mStoreQueueItems.removeIf(item -> { 329 if (item.equals(this) && item.mSnapshot != mSnapshot) { 330 item.mSnapshot.removeReference(TaskSnapshot.REFERENCE_PERSIST); 331 return true; 332 } 333 return false; 334 }); 335 mStoreQueueItems.offer(this); 336 } 337 338 @GuardedBy("mLock") 339 @Override onDequeuedLocked()340 void onDequeuedLocked() { 341 mStoreQueueItems.remove(this); 342 } 343 344 @Override write()345 void write() { 346 if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) { 347 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "StoreWriteQueueItem#" + mId); 348 } 349 if (!mPersistInfoProvider.createDirectory(mUserId)) { 350 Slog.e(TAG, "Unable to create snapshot directory for user dir=" 351 + mPersistInfoProvider.getDirectory(mUserId)); 352 } 353 boolean failed = false; 354 if (!writeProto()) { 355 failed = true; 356 } 357 if (!writeBuffer()) { 358 failed = true; 359 } 360 if (failed) { 361 deleteSnapshot(mId, mUserId, mPersistInfoProvider); 362 } 363 mSnapshot.removeReference(TaskSnapshot.REFERENCE_PERSIST); 364 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); 365 } 366 writeProto()367 boolean writeProto() { 368 final TaskSnapshotProto proto = new TaskSnapshotProto(); 369 proto.orientation = mSnapshot.getOrientation(); 370 proto.rotation = mSnapshot.getRotation(); 371 proto.taskWidth = mSnapshot.getTaskSize().x; 372 proto.taskHeight = mSnapshot.getTaskSize().y; 373 proto.insetLeft = mSnapshot.getContentInsets().left; 374 proto.insetTop = mSnapshot.getContentInsets().top; 375 proto.insetRight = mSnapshot.getContentInsets().right; 376 proto.insetBottom = mSnapshot.getContentInsets().bottom; 377 proto.letterboxInsetLeft = mSnapshot.getLetterboxInsets().left; 378 proto.letterboxInsetTop = mSnapshot.getLetterboxInsets().top; 379 proto.letterboxInsetRight = mSnapshot.getLetterboxInsets().right; 380 proto.letterboxInsetBottom = mSnapshot.getLetterboxInsets().bottom; 381 proto.isRealSnapshot = mSnapshot.isRealSnapshot(); 382 proto.windowingMode = mSnapshot.getWindowingMode(); 383 proto.appearance = mSnapshot.getAppearance(); 384 proto.isTranslucent = mSnapshot.isTranslucent(); 385 proto.topActivityComponent = mSnapshot.getTopActivityComponent().flattenToString(); 386 proto.uiMode = mSnapshot.getUiMode(); 387 proto.id = mSnapshot.getId(); 388 final byte[] bytes = TaskSnapshotProto.toByteArray(proto); 389 final File file = mPersistInfoProvider.getProtoFile(mId, mUserId); 390 final AtomicFile atomicFile = new AtomicFile(file); 391 FileOutputStream fos = null; 392 try { 393 fos = atomicFile.startWrite(); 394 fos.write(bytes); 395 atomicFile.finishWrite(fos); 396 } catch (IOException e) { 397 atomicFile.failWrite(fos); 398 Slog.e(TAG, "Unable to open " + file + " for persisting. " + e); 399 return false; 400 } 401 return true; 402 } 403 writeBuffer()404 boolean writeBuffer() { 405 if (AbsAppSnapshotController.isInvalidHardwareBuffer(mSnapshot.getHardwareBuffer())) { 406 Slog.e(TAG, "Invalid task snapshot hw buffer, taskId=" + mId); 407 return false; 408 } 409 410 final HardwareBuffer hwBuffer = mSnapshot.getHardwareBuffer(); 411 final int width = hwBuffer.getWidth(); 412 final int height = hwBuffer.getHeight(); 413 final int pixelFormat = hwBuffer.getFormat(); 414 final Bitmap swBitmap = !Flags.reduceTaskSnapshotMemoryUsage() 415 || (pixelFormat != PixelFormat.RGB_565 && pixelFormat != PixelFormat.RGBA_8888) 416 || !mSnapshot.isRealSnapshot() 417 || TransitionAnimation.hasProtectedContent(hwBuffer) 418 ? copyToSwBitmapReadBack() 419 : copyToSwBitmapDirect(width, height, pixelFormat); 420 if (swBitmap == null) { 421 return false; 422 } 423 final File file = mPersistInfoProvider.getHighResolutionBitmapFile(mId, mUserId); 424 try (FileOutputStream fos = new FileOutputStream(file)) { 425 swBitmap.compress(JPEG, COMPRESS_QUALITY, fos); 426 } catch (IOException e) { 427 Slog.e(TAG, "Unable to open " + file + " for persisting.", e); 428 return false; 429 } 430 431 if (!mPersistInfoProvider.enableLowResSnapshots()) { 432 swBitmap.recycle(); 433 return true; 434 } 435 436 final Bitmap lowResBitmap = Bitmap.createScaledBitmap(swBitmap, 437 (int) (width * mPersistInfoProvider.lowResScaleFactor()), 438 (int) (height * mPersistInfoProvider.lowResScaleFactor()), 439 true /* filter */); 440 swBitmap.recycle(); 441 442 final File lowResFile = mPersistInfoProvider.getLowResolutionBitmapFile(mId, mUserId); 443 try (FileOutputStream lowResFos = new FileOutputStream(lowResFile)) { 444 lowResBitmap.compress(JPEG, COMPRESS_QUALITY, lowResFos); 445 } catch (IOException e) { 446 Slog.e(TAG, "Unable to open " + lowResFile + " for persisting.", e); 447 return false; 448 } 449 lowResBitmap.recycle(); 450 451 return true; 452 } 453 copyToSwBitmapReadBack()454 private Bitmap copyToSwBitmapReadBack() { 455 final Bitmap bitmap = Bitmap.wrapHardwareBuffer( 456 mSnapshot.getHardwareBuffer(), mSnapshot.getColorSpace()); 457 if (bitmap == null) { 458 Slog.e(TAG, "Invalid task snapshot hw bitmap"); 459 return null; 460 } 461 462 final Bitmap swBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false /* isMutable */); 463 if (swBitmap == null) { 464 Slog.e(TAG, "Bitmap conversion from (config=" + bitmap.getConfig() 465 + ", isMutable=" + bitmap.isMutable() 466 + ") to (config=ARGB_8888, isMutable=false) failed."); 467 return null; 468 } 469 bitmap.recycle(); 470 return swBitmap; 471 } 472 473 /** 474 * Use ImageReader to create the software bitmap, so SkImage won't create an extra texture. 475 */ copyToSwBitmapDirect(int width, int height, int pixelFormat)476 private Bitmap copyToSwBitmapDirect(int width, int height, int pixelFormat) { 477 try (ImageReader ir = ImageReader.newInstance(width, height, 478 pixelFormat, 1 /* maxImages */)) { 479 ir.getSurface().attachAndQueueBufferWithColorSpace(mSnapshot.getHardwareBuffer(), 480 mSnapshot.getColorSpace()); 481 try (Image image = ir.acquireLatestImage()) { 482 if (image == null || image.getPlaneCount() < 1) { 483 Slog.e(TAG, "Image reader cannot acquire image"); 484 return null; 485 } 486 487 final Image.Plane[] planes = image.getPlanes(); 488 if (planes.length != 1) { 489 Slog.e(TAG, "Image reader cannot get plane"); 490 return null; 491 } 492 final Image.Plane plane = planes[0]; 493 final int rowPadding = plane.getRowStride() - plane.getPixelStride() 494 * image.getWidth(); 495 final Bitmap swBitmap = Bitmap.createBitmap( 496 image.getWidth() + rowPadding / plane.getPixelStride() /* width */, 497 image.getHeight() /* height */, 498 pixelFormat == PixelFormat.RGB_565 499 ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888); 500 swBitmap.copyPixelsFromBuffer(plane.getBuffer()); 501 return swBitmap; 502 } 503 } 504 } 505 506 @Override equals(Object o)507 public boolean equals(Object o) { 508 if (o == null || getClass() != o.getClass()) return false; 509 final StoreWriteQueueItem other = (StoreWriteQueueItem) o; 510 return mId == other.mId && mUserId == other.mUserId 511 && mPersistInfoProvider == other.mPersistInfoProvider; 512 } 513 514 @Override toString()515 public String toString() { 516 return "StoreWriteQueueItem{ID=" + mId + ", UserId=" + mUserId + "}"; 517 } 518 } 519 createDeleteWriteQueueItem(int id, int userId, PersistInfoProvider provider)520 DeleteWriteQueueItem createDeleteWriteQueueItem(int id, int userId, 521 PersistInfoProvider provider) { 522 return new DeleteWriteQueueItem(id, userId, provider); 523 } 524 525 private class DeleteWriteQueueItem extends WriteQueueItem { 526 private final int mId; 527 DeleteWriteQueueItem(int id, int userId, PersistInfoProvider provider)528 DeleteWriteQueueItem(int id, int userId, PersistInfoProvider provider) { 529 super(provider, userId); 530 mId = id; 531 } 532 533 @Override write()534 void write() { 535 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "DeleteWriteQueueItem"); 536 deleteSnapshot(mId, mUserId, mPersistInfoProvider); 537 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); 538 } 539 540 @Override toString()541 public String toString() { 542 return "DeleteWriteQueueItem{ID=" + mId + ", UserId=" + mUserId + "}"; 543 } 544 } 545 dump(PrintWriter pw, String prefix)546 void dump(PrintWriter pw, String prefix) { 547 final WriteQueueItem[] items; 548 synchronized (mLock) { 549 items = mWriteQueue.toArray(new WriteQueueItem[0]); 550 } 551 if (items.length == 0) { 552 return; 553 } 554 pw.println(prefix + "PersistQueue contains:"); 555 for (int i = items.length - 1; i >= 0; --i) { 556 pw.println(prefix + " " + items[i] + ""); 557 } 558 } 559 } 560