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