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.os.Process; 28 import android.os.SystemClock; 29 import android.os.Trace; 30 import android.util.AtomicFile; 31 import android.util.Slog; 32 import android.window.TaskSnapshot; 33 34 import com.android.internal.annotations.GuardedBy; 35 import com.android.internal.annotations.VisibleForTesting; 36 import com.android.server.LocalServices; 37 import com.android.server.pm.UserManagerInternal; 38 import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider; 39 import com.android.server.wm.nano.WindowManagerProtos.TaskSnapshotProto; 40 41 import java.io.File; 42 import java.io.FileOutputStream; 43 import java.io.IOException; 44 import java.io.PrintWriter; 45 import java.util.ArrayDeque; 46 47 /** 48 * Singleton worker thread to queue up persist or delete tasks of {@link TaskSnapshot}s to disk. 49 */ 50 class SnapshotPersistQueue { 51 private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotPersister" : TAG_WM; 52 private static final long DELAY_MS = 100; 53 private static final int MAX_STORE_QUEUE_DEPTH = 2; 54 private static final int COMPRESS_QUALITY = 95; 55 56 @GuardedBy("mLock") 57 private final ArrayDeque<WriteQueueItem> mWriteQueue = new ArrayDeque<>(); 58 @GuardedBy("mLock") 59 private final ArrayDeque<StoreWriteQueueItem> mStoreQueueItems = new ArrayDeque<>(); 60 @GuardedBy("mLock") 61 private boolean mQueueIdling; 62 @GuardedBy("mLock") 63 private boolean mPaused; 64 private boolean mStarted; 65 private final Object mLock = new Object(); 66 private final UserManagerInternal mUserManagerInternal; 67 SnapshotPersistQueue()68 SnapshotPersistQueue() { 69 mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); 70 } 71 getLock()72 Object getLock() { 73 return mLock; 74 } 75 systemReady()76 void systemReady() { 77 start(); 78 } 79 80 /** 81 * Starts persisting. 82 */ start()83 void start() { 84 if (!mStarted) { 85 mStarted = true; 86 mPersister.start(); 87 } 88 } 89 90 /** 91 * Temporarily pauses/unpauses persisting of task snapshots. 92 * 93 * @param paused Whether task snapshot persisting should be paused. 94 */ setPaused(boolean paused)95 void setPaused(boolean paused) { 96 synchronized (mLock) { 97 mPaused = paused; 98 if (!paused) { 99 mLock.notifyAll(); 100 } 101 } 102 } 103 104 @VisibleForTesting waitForQueueEmpty()105 void waitForQueueEmpty() { 106 while (true) { 107 synchronized (mLock) { 108 if (mWriteQueue.isEmpty() && mQueueIdling) { 109 return; 110 } 111 } 112 SystemClock.sleep(DELAY_MS); 113 } 114 } 115 116 @VisibleForTesting peekQueueSize()117 int peekQueueSize() { 118 synchronized (mLock) { 119 return mWriteQueue.size(); 120 } 121 } 122 addToQueueInternal(WriteQueueItem item, boolean insertToFront)123 private void addToQueueInternal(WriteQueueItem item, boolean insertToFront) { 124 mWriteQueue.removeFirstOccurrence(item); 125 if (insertToFront) { 126 mWriteQueue.addFirst(item); 127 } else { 128 mWriteQueue.addLast(item); 129 } 130 item.onQueuedLocked(); 131 ensureStoreQueueDepthLocked(); 132 if (!mPaused) { 133 mLock.notifyAll(); 134 } 135 } 136 137 @GuardedBy("mLock") sendToQueueLocked(WriteQueueItem item)138 void sendToQueueLocked(WriteQueueItem item) { 139 addToQueueInternal(item, false /* insertToFront */); 140 } 141 142 @GuardedBy("mLock") insertQueueAtFirstLocked(WriteQueueItem item)143 void insertQueueAtFirstLocked(WriteQueueItem item) { 144 addToQueueInternal(item, true /* insertToFront */); 145 } 146 147 @GuardedBy("mLock") ensureStoreQueueDepthLocked()148 private void ensureStoreQueueDepthLocked() { 149 while (mStoreQueueItems.size() > MAX_STORE_QUEUE_DEPTH) { 150 final StoreWriteQueueItem item = mStoreQueueItems.poll(); 151 mWriteQueue.remove(item); 152 Slog.i(TAG, "Queue is too deep! Purged item with index=" + item.mId); 153 } 154 } 155 deleteSnapshot(int index, int userId, PersistInfoProvider provider)156 void deleteSnapshot(int index, int userId, PersistInfoProvider provider) { 157 final File protoFile = provider.getProtoFile(index, userId); 158 final File bitmapLowResFile = provider.getLowResolutionBitmapFile(index, userId); 159 if (protoFile.exists()) { 160 protoFile.delete(); 161 } 162 if (bitmapLowResFile.exists()) { 163 bitmapLowResFile.delete(); 164 } 165 final File bitmapFile = provider.getHighResolutionBitmapFile(index, userId); 166 if (bitmapFile.exists()) { 167 bitmapFile.delete(); 168 } 169 } 170 171 private final Thread mPersister = new Thread("TaskSnapshotPersister") { 172 public void run() { 173 android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 174 while (true) { 175 WriteQueueItem next; 176 boolean isReadyToWrite = false; 177 synchronized (mLock) { 178 if (mPaused) { 179 next = null; 180 } else { 181 next = mWriteQueue.poll(); 182 if (next != null) { 183 if (next.isReady(mUserManagerInternal)) { 184 isReadyToWrite = true; 185 next.onDequeuedLocked(); 186 } else { 187 mWriteQueue.addLast(next); 188 } 189 } 190 } 191 } 192 if (next != null) { 193 if (isReadyToWrite) { 194 next.write(); 195 } 196 SystemClock.sleep(DELAY_MS); 197 } 198 synchronized (mLock) { 199 final boolean writeQueueEmpty = mWriteQueue.isEmpty(); 200 if (!writeQueueEmpty && !mPaused) { 201 continue; 202 } 203 try { 204 mQueueIdling = writeQueueEmpty; 205 mLock.wait(); 206 mQueueIdling = false; 207 } catch (InterruptedException e) { 208 } 209 } 210 } 211 } 212 }; 213 214 abstract static class WriteQueueItem { 215 protected final PersistInfoProvider mPersistInfoProvider; 216 protected final int mUserId; WriteQueueItem(@onNull PersistInfoProvider persistInfoProvider, int userId)217 WriteQueueItem(@NonNull PersistInfoProvider persistInfoProvider, int userId) { 218 mPersistInfoProvider = persistInfoProvider; 219 mUserId = userId; 220 } 221 /** 222 * @return {@code true} if item is ready to have {@link WriteQueueItem#write} called 223 */ isReady(UserManagerInternal userManager)224 boolean isReady(UserManagerInternal userManager) { 225 return userManager.isUserUnlocked(mUserId); 226 } 227 write()228 abstract void write(); 229 230 /** 231 * Called when this queue item has been put into the queue. 232 */ onQueuedLocked()233 void onQueuedLocked() { 234 } 235 236 /** 237 * Called when this queue item has been taken out of the queue. 238 */ onDequeuedLocked()239 void onDequeuedLocked() { 240 } 241 } 242 createStoreWriteQueueItem(int id, int userId, TaskSnapshot snapshot, PersistInfoProvider provider)243 StoreWriteQueueItem createStoreWriteQueueItem(int id, int userId, TaskSnapshot snapshot, 244 PersistInfoProvider provider) { 245 return new StoreWriteQueueItem(id, userId, snapshot, provider); 246 } 247 248 class StoreWriteQueueItem extends WriteQueueItem { 249 private final int mId; 250 private final TaskSnapshot mSnapshot; 251 StoreWriteQueueItem(int id, int userId, TaskSnapshot snapshot, PersistInfoProvider provider)252 StoreWriteQueueItem(int id, int userId, TaskSnapshot snapshot, 253 PersistInfoProvider provider) { 254 super(provider, userId); 255 mId = id; 256 snapshot.addReference(TaskSnapshot.REFERENCE_PERSIST); 257 mSnapshot = snapshot; 258 } 259 260 @GuardedBy("mLock") 261 @Override onQueuedLocked()262 void onQueuedLocked() { 263 // Remove duplicate request. 264 mStoreQueueItems.remove(this); 265 mStoreQueueItems.offer(this); 266 } 267 268 @GuardedBy("mLock") 269 @Override onDequeuedLocked()270 void onDequeuedLocked() { 271 mStoreQueueItems.remove(this); 272 } 273 274 @Override write()275 void write() { 276 if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) { 277 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "StoreWriteQueueItem#" + mId); 278 } 279 if (!mPersistInfoProvider.createDirectory(mUserId)) { 280 Slog.e(TAG, "Unable to create snapshot directory for user dir=" 281 + mPersistInfoProvider.getDirectory(mUserId)); 282 } 283 boolean failed = false; 284 if (!writeProto()) { 285 failed = true; 286 } 287 if (!writeBuffer()) { 288 failed = true; 289 } 290 if (failed) { 291 deleteSnapshot(mId, mUserId, mPersistInfoProvider); 292 } 293 mSnapshot.removeReference(TaskSnapshot.REFERENCE_PERSIST); 294 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); 295 } 296 writeProto()297 boolean writeProto() { 298 final TaskSnapshotProto proto = new TaskSnapshotProto(); 299 proto.orientation = mSnapshot.getOrientation(); 300 proto.rotation = mSnapshot.getRotation(); 301 proto.taskWidth = mSnapshot.getTaskSize().x; 302 proto.taskHeight = mSnapshot.getTaskSize().y; 303 proto.insetLeft = mSnapshot.getContentInsets().left; 304 proto.insetTop = mSnapshot.getContentInsets().top; 305 proto.insetRight = mSnapshot.getContentInsets().right; 306 proto.insetBottom = mSnapshot.getContentInsets().bottom; 307 proto.letterboxInsetLeft = mSnapshot.getLetterboxInsets().left; 308 proto.letterboxInsetTop = mSnapshot.getLetterboxInsets().top; 309 proto.letterboxInsetRight = mSnapshot.getLetterboxInsets().right; 310 proto.letterboxInsetBottom = mSnapshot.getLetterboxInsets().bottom; 311 proto.isRealSnapshot = mSnapshot.isRealSnapshot(); 312 proto.windowingMode = mSnapshot.getWindowingMode(); 313 proto.appearance = mSnapshot.getAppearance(); 314 proto.isTranslucent = mSnapshot.isTranslucent(); 315 proto.topActivityComponent = mSnapshot.getTopActivityComponent().flattenToString(); 316 proto.id = mSnapshot.getId(); 317 final byte[] bytes = TaskSnapshotProto.toByteArray(proto); 318 final File file = mPersistInfoProvider.getProtoFile(mId, mUserId); 319 final AtomicFile atomicFile = new AtomicFile(file); 320 FileOutputStream fos = null; 321 try { 322 fos = atomicFile.startWrite(); 323 fos.write(bytes); 324 atomicFile.finishWrite(fos); 325 } catch (IOException e) { 326 atomicFile.failWrite(fos); 327 Slog.e(TAG, "Unable to open " + file + " for persisting. " + e); 328 return false; 329 } 330 return true; 331 } 332 writeBuffer()333 boolean writeBuffer() { 334 if (AbsAppSnapshotController.isInvalidHardwareBuffer(mSnapshot.getHardwareBuffer())) { 335 Slog.e(TAG, "Invalid task snapshot hw buffer, taskId=" + mId); 336 return false; 337 } 338 final Bitmap bitmap = Bitmap.wrapHardwareBuffer( 339 mSnapshot.getHardwareBuffer(), mSnapshot.getColorSpace()); 340 if (bitmap == null) { 341 Slog.e(TAG, "Invalid task snapshot hw bitmap"); 342 return false; 343 } 344 345 final Bitmap swBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false /* isMutable */); 346 if (swBitmap == null) { 347 Slog.e(TAG, "Bitmap conversion from (config=" + bitmap.getConfig() + ", isMutable=" 348 + bitmap.isMutable() + ") to (config=ARGB_8888, isMutable=false) failed."); 349 return false; 350 } 351 final int width = bitmap.getWidth(); 352 final int height = bitmap.getHeight(); 353 bitmap.recycle(); 354 355 final File file = mPersistInfoProvider.getHighResolutionBitmapFile(mId, mUserId); 356 try { 357 FileOutputStream fos = new FileOutputStream(file); 358 swBitmap.compress(JPEG, COMPRESS_QUALITY, fos); 359 fos.close(); 360 } catch (IOException e) { 361 Slog.e(TAG, "Unable to open " + file + " for persisting.", e); 362 return false; 363 } 364 365 if (!mPersistInfoProvider.enableLowResSnapshots()) { 366 swBitmap.recycle(); 367 return true; 368 } 369 370 final Bitmap lowResBitmap = Bitmap.createScaledBitmap(swBitmap, 371 (int) (width * mPersistInfoProvider.lowResScaleFactor()), 372 (int) (height * mPersistInfoProvider.lowResScaleFactor()), 373 true /* filter */); 374 swBitmap.recycle(); 375 376 final File lowResFile = mPersistInfoProvider.getLowResolutionBitmapFile(mId, mUserId); 377 try { 378 FileOutputStream lowResFos = new FileOutputStream(lowResFile); 379 lowResBitmap.compress(JPEG, COMPRESS_QUALITY, lowResFos); 380 lowResFos.close(); 381 } catch (IOException e) { 382 Slog.e(TAG, "Unable to open " + lowResFile + " for persisting.", e); 383 return false; 384 } 385 lowResBitmap.recycle(); 386 387 return true; 388 } 389 390 @Override equals(Object o)391 public boolean equals(Object o) { 392 if (o == null || getClass() != o.getClass()) return false; 393 final StoreWriteQueueItem other = (StoreWriteQueueItem) o; 394 return mId == other.mId && mUserId == other.mUserId 395 && mPersistInfoProvider == other.mPersistInfoProvider; 396 } 397 398 @Override toString()399 public String toString() { 400 return "StoreWriteQueueItem{ID=" + mId + ", UserId=" + mUserId + "}"; 401 } 402 } 403 createDeleteWriteQueueItem(int id, int userId, PersistInfoProvider provider)404 DeleteWriteQueueItem createDeleteWriteQueueItem(int id, int userId, 405 PersistInfoProvider provider) { 406 return new DeleteWriteQueueItem(id, userId, provider); 407 } 408 409 private class DeleteWriteQueueItem extends WriteQueueItem { 410 private final int mId; 411 DeleteWriteQueueItem(int id, int userId, PersistInfoProvider provider)412 DeleteWriteQueueItem(int id, int userId, PersistInfoProvider provider) { 413 super(provider, userId); 414 mId = id; 415 } 416 417 @Override write()418 void write() { 419 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "DeleteWriteQueueItem"); 420 deleteSnapshot(mId, mUserId, mPersistInfoProvider); 421 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); 422 } 423 424 @Override toString()425 public String toString() { 426 return "DeleteWriteQueueItem{ID=" + mId + ", UserId=" + mUserId + "}"; 427 } 428 } 429 dump(PrintWriter pw, String prefix)430 void dump(PrintWriter pw, String prefix) { 431 final WriteQueueItem[] items; 432 synchronized (mLock) { 433 items = mWriteQueue.toArray(new WriteQueueItem[0]); 434 } 435 if (items.length == 0) { 436 return; 437 } 438 pw.println(prefix + "PersistQueue contains:"); 439 for (int i = items.length - 1; i >= 0; --i) { 440 pw.println(prefix + " " + items[i] + ""); 441 } 442 } 443 } 444