blob: c6e1c954be125fa0b2e4846bfcf877a15081eb20 [file] [log] [blame]
Jorim Jaggif9084ec2017-01-16 13:16:59 +01001/*
2 * Copyright (C) 2017 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
17package com.android.server.wm;
18
Winson Chung48b25652018-10-22 14:04:30 -070019import static android.graphics.Bitmap.CompressFormat.JPEG;
Tim Murrayaf853922018-10-15 16:50:23 -070020
Jorim Jaggif9084ec2017-01-16 13:16:59 +010021import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
22import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
23
Peter Kalauskas84f02a82020-01-22 16:06:18 -080024import android.annotation.NonNull;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010025import android.annotation.TestApi;
26import android.app.ActivityManager.TaskSnapshot;
27import android.graphics.Bitmap;
Jorim Jaggi2dae8552017-05-02 14:10:58 +020028import android.graphics.Bitmap.Config;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010029import android.os.Process;
30import android.os.SystemClock;
Guo Li2f9987f2020-04-23 21:39:01 +080031import android.os.UserManagerInternal;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010032import android.util.ArraySet;
Al Sutton7407e2c2019-09-06 09:17:01 +010033import android.util.AtomicFile;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010034import android.util.Slog;
35
36import com.android.internal.annotations.GuardedBy;
37import com.android.internal.annotations.VisibleForTesting;
Guo Li2f9987f2020-04-23 21:39:01 +080038import com.android.server.LocalServices;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010039import com.android.server.wm.nano.WindowManagerProtos.TaskSnapshotProto;
40
41import java.io.File;
42import java.io.FileOutputStream;
43import java.io.IOException;
44import java.util.ArrayDeque;
Jorim Jaggi2b78b1f2018-06-18 15:48:06 +020045import java.util.Arrays;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010046
47/**
48 * Persists {@link TaskSnapshot}s to disk.
49 * <p>
50 * Test class: {@link TaskSnapshotPersisterLoaderTest}
51 */
52class TaskSnapshotPersister {
53
54 private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotPersister" : TAG_WM;
55 private static final String SNAPSHOTS_DIRNAME = "snapshots";
Peter Kalauskas4dc04602020-02-12 18:49:03 -080056 private static final String LOW_RES_FILE_POSTFIX = "_reduced";
Jorim Jaggif9084ec2017-01-16 13:16:59 +010057 private static final long DELAY_MS = 100;
Jorim Jaggi35e3f532017-03-17 17:06:50 +010058 private static final int QUALITY = 95;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010059 private static final String PROTO_EXTENSION = ".proto";
Jorim Jaggi35e3f532017-03-17 17:06:50 +010060 private static final String BITMAP_EXTENSION = ".jpg";
Jorim Jaggief3651c2017-05-18 23:58:09 +020061 private static final int MAX_STORE_QUEUE_DEPTH = 2;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010062
63 @GuardedBy("mLock")
64 private final ArrayDeque<WriteQueueItem> mWriteQueue = new ArrayDeque<>();
65 @GuardedBy("mLock")
Jorim Jaggief3651c2017-05-18 23:58:09 +020066 private final ArrayDeque<StoreWriteQueueItem> mStoreQueueItems = new ArrayDeque<>();
67 @GuardedBy("mLock")
Jorim Jaggif9084ec2017-01-16 13:16:59 +010068 private boolean mQueueIdling;
Jorim Jaggia41b7292017-05-11 23:50:34 +020069 @GuardedBy("mLock")
70 private boolean mPaused;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010071 private boolean mStarted;
72 private final Object mLock = new Object();
73 private final DirectoryResolver mDirectoryResolver;
Peter Kalauskas84f02a82020-01-22 16:06:18 -080074 private final float mLowResScaleFactor;
75 private boolean mEnableLowResSnapshots;
Chiawei Wang02202d12019-01-03 18:12:13 +080076 private final boolean mUse16BitFormat;
Guo Li2f9987f2020-04-23 21:39:01 +080077 private final UserManagerInternal mUserManagerInternal;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010078
79 /**
80 * The list of ids of the tasks that have been persisted since {@link #removeObsoleteFiles} was
81 * called.
82 */
83 @GuardedBy("mLock")
84 private final ArraySet<Integer> mPersistedTaskIdsSinceLastRemoveObsolete = new ArraySet<>();
85
Kevin716a9db2018-12-18 16:53:39 -080086 TaskSnapshotPersister(WindowManagerService service, DirectoryResolver resolver) {
Jorim Jaggif9084ec2017-01-16 13:16:59 +010087 mDirectoryResolver = resolver;
Guo Li2f9987f2020-04-23 21:39:01 +080088 mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
89
Peter Kalauskas84f02a82020-01-22 16:06:18 -080090 final float highResTaskSnapshotScale = service.mContext.getResources().getFloat(
91 com.android.internal.R.dimen.config_highResTaskSnapshotScale);
92 final float lowResTaskSnapshotScale = service.mContext.getResources().getFloat(
93 com.android.internal.R.dimen.config_lowResTaskSnapshotScale);
Peter Kalauskase934da92020-01-21 15:15:22 -080094
Peter Kalauskas84f02a82020-01-22 16:06:18 -080095 if (lowResTaskSnapshotScale < 0 || 1 <= lowResTaskSnapshotScale) {
96 throw new RuntimeException("Low-res scale must be between 0 and 1");
Peter Kalauskase934da92020-01-21 15:15:22 -080097 }
Peter Kalauskas84f02a82020-01-22 16:06:18 -080098 if (highResTaskSnapshotScale <= 0 || 1 < highResTaskSnapshotScale) {
99 throw new RuntimeException("High-res scale must be between 0 and 1");
100 }
101 if (highResTaskSnapshotScale <= lowResTaskSnapshotScale) {
102 throw new RuntimeException("High-res scale must be greater than low-res scale");
103 }
104
105 if (lowResTaskSnapshotScale > 0) {
106 mLowResScaleFactor = lowResTaskSnapshotScale / highResTaskSnapshotScale;
Peter Kalauskasc3cc8b92020-03-09 18:55:04 -0700107 mEnableLowResSnapshots = true;
Peter Kalauskas84f02a82020-01-22 16:06:18 -0800108 } else {
109 mLowResScaleFactor = 0;
Peter Kalauskasc3cc8b92020-03-09 18:55:04 -0700110 mEnableLowResSnapshots = false;
Peter Kalauskas84f02a82020-01-22 16:06:18 -0800111 }
112
Chiawei Wang02202d12019-01-03 18:12:13 +0800113 mUse16BitFormat = service.mContext.getResources().getBoolean(
114 com.android.internal.R.bool.config_use16BitTaskSnapshotPixelFormat);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100115 }
116
117 /**
118 * Starts persisting.
119 */
120 void start() {
121 if (!mStarted) {
122 mStarted = true;
123 mPersister.start();
124 }
125 }
126
127 /**
128 * Persists a snapshot of a task to disk.
129 *
130 * @param taskId The id of the task that needs to be persisted.
131 * @param userId The id of the user this tasks belongs to.
132 * @param snapshot The snapshot to persist.
133 */
134 void persistSnapshot(int taskId, int userId, TaskSnapshot snapshot) {
135 synchronized (mLock) {
136 mPersistedTaskIdsSinceLastRemoveObsolete.add(taskId);
137 sendToQueueLocked(new StoreWriteQueueItem(taskId, userId, snapshot));
138 }
139 }
140
141 /**
142 * Callend when a task has been removed.
143 *
144 * @param taskId The id of task that has been removed.
145 * @param userId The id of the user the task belonged to.
146 */
147 void onTaskRemovedFromRecents(int taskId, int userId) {
148 synchronized (mLock) {
149 mPersistedTaskIdsSinceLastRemoveObsolete.remove(taskId);
150 sendToQueueLocked(new DeleteWriteQueueItem(taskId, userId));
151 }
152 }
153
154 /**
155 * In case a write/delete operation was lost because the system crashed, this makes sure to
156 * clean up the directory to remove obsolete files.
157 *
158 * @param persistentTaskIds A set of task ids that exist in our in-memory model.
159 * @param runningUserIds The ids of the list of users that have tasks loaded in our in-memory
160 * model.
161 */
162 void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
163 synchronized (mLock) {
164 mPersistedTaskIdsSinceLastRemoveObsolete.clear();
165 sendToQueueLocked(new RemoveObsoleteFilesQueueItem(persistentTaskIds, runningUserIds));
166 }
167 }
168
Jorim Jaggia41b7292017-05-11 23:50:34 +0200169 void setPaused(boolean paused) {
170 synchronized (mLock) {
171 mPaused = paused;
172 if (!paused) {
173 mLock.notifyAll();
174 }
175 }
176 }
177
Peter Kalauskas84f02a82020-01-22 16:06:18 -0800178 boolean enableLowResSnapshots() {
179 return mEnableLowResSnapshots;
180 }
181
Kevin716a9db2018-12-18 16:53:39 -0800182 /**
Chiawei Wang02202d12019-01-03 18:12:13 +0800183 * Return if task snapshots are stored in 16 bit pixel format.
184 *
185 * @return true if task snapshots are stored in 16 bit pixel format.
186 */
187 boolean use16BitFormat() {
188 return mUse16BitFormat;
189 }
190
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100191 @TestApi
192 void waitForQueueEmpty() {
193 while (true) {
194 synchronized (mLock) {
195 if (mWriteQueue.isEmpty() && mQueueIdling) {
196 return;
197 }
198 }
Guo Li2f9987f2020-04-23 21:39:01 +0800199 SystemClock.sleep(DELAY_MS);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100200 }
201 }
202
203 @GuardedBy("mLock")
204 private void sendToQueueLocked(WriteQueueItem item) {
205 mWriteQueue.offer(item);
Jorim Jaggief3651c2017-05-18 23:58:09 +0200206 item.onQueuedLocked();
207 ensureStoreQueueDepthLocked();
Jorim Jaggia41b7292017-05-11 23:50:34 +0200208 if (!mPaused) {
209 mLock.notifyAll();
210 }
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100211 }
212
Jorim Jaggief3651c2017-05-18 23:58:09 +0200213 @GuardedBy("mLock")
214 private void ensureStoreQueueDepthLocked() {
215 while (mStoreQueueItems.size() > MAX_STORE_QUEUE_DEPTH) {
216 final StoreWriteQueueItem item = mStoreQueueItems.poll();
217 mWriteQueue.remove(item);
218 Slog.i(TAG, "Queue is too deep! Purged item with taskid=" + item.mTaskId);
219 }
220 }
221
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100222 private File getDirectory(int userId) {
223 return new File(mDirectoryResolver.getSystemDirectoryForUser(userId), SNAPSHOTS_DIRNAME);
224 }
225
226 File getProtoFile(int taskId, int userId) {
227 return new File(getDirectory(userId), taskId + PROTO_EXTENSION);
228 }
229
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800230 File getHighResolutionBitmapFile(int taskId, int userId) {
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100231 return new File(getDirectory(userId), taskId + BITMAP_EXTENSION);
232 }
233
Peter Kalauskas84f02a82020-01-22 16:06:18 -0800234 @NonNull
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800235 File getLowResolutionBitmapFile(int taskId, int userId) {
236 return new File(getDirectory(userId), taskId + LOW_RES_FILE_POSTFIX + BITMAP_EXTENSION);
Jorim Jaggi35e3f532017-03-17 17:06:50 +0100237 }
238
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100239 private boolean createDirectory(int userId) {
240 final File dir = getDirectory(userId);
Guo Li2f9987f2020-04-23 21:39:01 +0800241 return dir.exists() || dir.mkdir();
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100242 }
243
244 private void deleteSnapshot(int taskId, int userId) {
245 final File protoFile = getProtoFile(taskId, userId);
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800246 final File bitmapLowResFile = getLowResolutionBitmapFile(taskId, userId);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100247 protoFile.delete();
Peter Kalauskas84f02a82020-01-22 16:06:18 -0800248 if (bitmapLowResFile.exists()) {
249 bitmapLowResFile.delete();
250 }
251 final File bitmapFile = getHighResolutionBitmapFile(taskId, userId);
252 if (bitmapFile.exists()) {
Matthew Ngcb7ac672017-07-21 17:27:42 -0700253 bitmapFile.delete();
254 }
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100255 }
256
257 interface DirectoryResolver {
258 File getSystemDirectoryForUser(int userId);
259 }
260
261 private Thread mPersister = new Thread("TaskSnapshotPersister") {
262 public void run() {
263 android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
264 while (true) {
265 WriteQueueItem next;
Guo Li2f9987f2020-04-23 21:39:01 +0800266 boolean isReadyToWrite = false;
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100267 synchronized (mLock) {
Jorim Jaggia41b7292017-05-11 23:50:34 +0200268 if (mPaused) {
269 next = null;
270 } else {
271 next = mWriteQueue.poll();
Jorim Jaggief3651c2017-05-18 23:58:09 +0200272 if (next != null) {
Guo Li2f9987f2020-04-23 21:39:01 +0800273 if (next.isReady()) {
274 isReadyToWrite = true;
275 next.onDequeuedLocked();
276 } else {
277 mWriteQueue.addLast(next);
278 }
Jorim Jaggief3651c2017-05-18 23:58:09 +0200279 }
Jorim Jaggia41b7292017-05-11 23:50:34 +0200280 }
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100281 }
282 if (next != null) {
Guo Li2f9987f2020-04-23 21:39:01 +0800283 if (isReadyToWrite) {
284 next.write();
285 }
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100286 SystemClock.sleep(DELAY_MS);
287 }
288 synchronized (mLock) {
Jorim Jaggi2f9c7a22017-05-16 14:03:08 +0200289 final boolean writeQueueEmpty = mWriteQueue.isEmpty();
290 if (!writeQueueEmpty && !mPaused) {
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100291 continue;
292 }
293 try {
Jorim Jaggi2f9c7a22017-05-16 14:03:08 +0200294 mQueueIdling = writeQueueEmpty;
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100295 mLock.wait();
296 mQueueIdling = false;
297 } catch (InterruptedException e) {
298 }
299 }
300 }
301 }
302 };
303
304 private abstract class WriteQueueItem {
Guo Li2f9987f2020-04-23 21:39:01 +0800305 /**
306 * @return {@code true} if item is ready to have {@link WriteQueueItem#write} called
307 */
308 boolean isReady() {
309 return true;
310 }
311
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100312 abstract void write();
Jorim Jaggief3651c2017-05-18 23:58:09 +0200313
314 /**
315 * Called when this queue item has been put into the queue.
316 */
317 void onQueuedLocked() {
318 }
319
320 /**
321 * Called when this queue item has been taken out of the queue.
322 */
323 void onDequeuedLocked() {
324 }
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100325 }
326
327 private class StoreWriteQueueItem extends WriteQueueItem {
328 private final int mTaskId;
329 private final int mUserId;
330 private final TaskSnapshot mSnapshot;
331
332 StoreWriteQueueItem(int taskId, int userId, TaskSnapshot snapshot) {
333 mTaskId = taskId;
334 mUserId = userId;
335 mSnapshot = snapshot;
336 }
337
Andreas Gampea36dc622018-02-05 17:19:22 -0800338 @GuardedBy("mLock")
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100339 @Override
Jorim Jaggief3651c2017-05-18 23:58:09 +0200340 void onQueuedLocked() {
341 mStoreQueueItems.offer(this);
342 }
343
Andreas Gampea36dc622018-02-05 17:19:22 -0800344 @GuardedBy("mLock")
Jorim Jaggief3651c2017-05-18 23:58:09 +0200345 @Override
346 void onDequeuedLocked() {
347 mStoreQueueItems.remove(this);
348 }
349
350 @Override
Guo Li2f9987f2020-04-23 21:39:01 +0800351 boolean isReady() {
352 return mUserManagerInternal.isUserUnlocked(mUserId);
353 }
354
355 @Override
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100356 void write() {
357 if (!createDirectory(mUserId)) {
358 Slog.e(TAG, "Unable to create snapshot directory for user dir="
359 + getDirectory(mUserId));
360 }
361 boolean failed = false;
362 if (!writeProto()) {
363 failed = true;
364 }
365 if (!writeBuffer()) {
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100366 failed = true;
367 }
368 if (failed) {
369 deleteSnapshot(mTaskId, mUserId);
370 }
371 }
372
373 boolean writeProto() {
374 final TaskSnapshotProto proto = new TaskSnapshotProto();
375 proto.orientation = mSnapshot.getOrientation();
Vinit Nayakffd9dff2019-11-05 15:20:11 -0800376 proto.rotation = mSnapshot.getRotation();
Peter Kalauskasfe0a4132020-01-31 16:50:42 -0800377 proto.taskWidth = mSnapshot.getTaskSize().x;
378 proto.taskHeight = mSnapshot.getTaskSize().y;
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100379 proto.insetLeft = mSnapshot.getContentInsets().left;
380 proto.insetTop = mSnapshot.getContentInsets().top;
381 proto.insetRight = mSnapshot.getContentInsets().right;
382 proto.insetBottom = mSnapshot.getContentInsets().bottom;
Winson Chungf3e412e2018-03-08 11:07:40 -0800383 proto.isRealSnapshot = mSnapshot.isRealSnapshot();
Winson Chunga4fa8d52018-04-20 15:54:51 -0700384 proto.windowingMode = mSnapshot.getWindowingMode();
Winson Chung173020c2018-05-04 15:36:47 -0700385 proto.systemUiVisibility = mSnapshot.getSystemUiVisibility();
386 proto.isTranslucent = mSnapshot.isTranslucent();
Winson Chung48b25652018-10-22 14:04:30 -0700387 proto.topActivityComponent = mSnapshot.getTopActivityComponent().flattenToString();
Hongwei Wang3b9bdcf2019-07-15 10:23:03 -0700388 proto.id = mSnapshot.getId();
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100389 final byte[] bytes = TaskSnapshotProto.toByteArray(proto);
390 final File file = getProtoFile(mTaskId, mUserId);
391 final AtomicFile atomicFile = new AtomicFile(file);
392 FileOutputStream fos = null;
393 try {
394 fos = atomicFile.startWrite();
395 fos.write(bytes);
396 atomicFile.finishWrite(fos);
397 } catch (IOException e) {
398 atomicFile.failWrite(fos);
399 Slog.e(TAG, "Unable to open " + file + " for persisting. " + e);
400 return false;
401 }
402 return true;
403 }
404
405 boolean writeBuffer() {
Peiyong Lin9d427402019-01-23 18:39:06 -0800406 final Bitmap bitmap = Bitmap.wrapHardwareBuffer(
Sunny Goyal62915b22019-04-10 12:28:47 -0700407 mSnapshot.getSnapshot(), mSnapshot.getColorSpace());
Winson Chungdab16732017-06-19 17:00:40 -0700408 if (bitmap == null) {
Winson Chung3e13ef82017-06-29 12:41:14 -0700409 Slog.e(TAG, "Invalid task snapshot hw bitmap");
Winson Chungdab16732017-06-19 17:00:40 -0700410 return false;
411 }
412
Jorim Jaggi2dae8552017-05-02 14:10:58 +0200413 final Bitmap swBitmap = bitmap.copy(Config.ARGB_8888, false /* isMutable */);
Peter Kalauskas84f02a82020-01-22 16:06:18 -0800414
415 final File file = getHighResolutionBitmapFile(mTaskId, mUserId);
416 try {
417 FileOutputStream fos = new FileOutputStream(file);
418 swBitmap.compress(JPEG, QUALITY, fos);
419 fos.close();
420 } catch (IOException e) {
421 Slog.e(TAG, "Unable to open " + file + " for persisting.", e);
422 return false;
423 }
424
Peter Kalauskasc3cc8b92020-03-09 18:55:04 -0700425 if (!mEnableLowResSnapshots) {
Peter Kalauskas84f02a82020-01-22 16:06:18 -0800426 swBitmap.recycle();
427 return true;
428 }
429
430 final Bitmap lowResBitmap = Bitmap.createScaledBitmap(swBitmap,
431 (int) (bitmap.getWidth() * mLowResScaleFactor),
432 (int) (bitmap.getHeight() * mLowResScaleFactor), true /* filter */);
433 swBitmap.recycle();
Winson Chungffde2ea2019-06-17 17:19:13 -0700434
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800435 final File lowResFile = getLowResolutionBitmapFile(mTaskId, mUserId);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100436 try {
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800437 FileOutputStream lowResFos = new FileOutputStream(lowResFile);
438 lowResBitmap.compress(JPEG, QUALITY, lowResFos);
439 lowResFos.close();
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100440 } catch (IOException e) {
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800441 Slog.e(TAG, "Unable to open " + lowResFile + " for persisting.", e);
Matthew Ngcb7ac672017-07-21 17:27:42 -0700442 return false;
443 }
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800444 lowResBitmap.recycle();
Matthew Ngcb7ac672017-07-21 17:27:42 -0700445
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100446 return true;
447 }
448 }
449
450 private class DeleteWriteQueueItem extends WriteQueueItem {
451 private final int mTaskId;
452 private final int mUserId;
453
454 DeleteWriteQueueItem(int taskId, int userId) {
455 mTaskId = taskId;
456 mUserId = userId;
457 }
458
459 @Override
460 void write() {
461 deleteSnapshot(mTaskId, mUserId);
462 }
463 }
464
465 @VisibleForTesting
466 class RemoveObsoleteFilesQueueItem extends WriteQueueItem {
467 private final ArraySet<Integer> mPersistentTaskIds;
468 private final int[] mRunningUserIds;
469
470 @VisibleForTesting
471 RemoveObsoleteFilesQueueItem(ArraySet<Integer> persistentTaskIds,
472 int[] runningUserIds) {
Jorim Jaggi2b78b1f2018-06-18 15:48:06 +0200473 mPersistentTaskIds = new ArraySet<>(persistentTaskIds);
474 mRunningUserIds = Arrays.copyOf(runningUserIds, runningUserIds.length);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100475 }
476
477 @Override
478 void write() {
479 final ArraySet<Integer> newPersistedTaskIds;
480 synchronized (mLock) {
481 newPersistedTaskIds = new ArraySet<>(mPersistedTaskIdsSinceLastRemoveObsolete);
482 }
483 for (int userId : mRunningUserIds) {
484 final File dir = getDirectory(userId);
485 final String[] files = dir.list();
486 if (files == null) {
487 continue;
488 }
489 for (String file : files) {
490 final int taskId = getTaskId(file);
491 if (!mPersistentTaskIds.contains(taskId)
492 && !newPersistedTaskIds.contains(taskId)) {
493 new File(dir, file).delete();
494 }
495 }
496 }
497 }
498
499 @VisibleForTesting
500 int getTaskId(String fileName) {
501 if (!fileName.endsWith(PROTO_EXTENSION) && !fileName.endsWith(BITMAP_EXTENSION)) {
502 return -1;
503 }
504 final int end = fileName.lastIndexOf('.');
505 if (end == -1) {
506 return -1;
507 }
Jorim Jaggi35e3f532017-03-17 17:06:50 +0100508 String name = fileName.substring(0, end);
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800509 if (name.endsWith(LOW_RES_FILE_POSTFIX)) {
510 name = name.substring(0, name.length() - LOW_RES_FILE_POSTFIX.length());
Jorim Jaggi35e3f532017-03-17 17:06:50 +0100511 }
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100512 try {
Jorim Jaggi35e3f532017-03-17 17:06:50 +0100513 return Integer.parseInt(name);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100514 } catch (NumberFormatException e) {
515 return -1;
516 }
517 }
518 }
519}