blob: 31212b8a6bd934cc374d9d7ed2799a93e6266849 [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
24import android.annotation.TestApi;
Matthew Ngcb7ac672017-07-21 17:27:42 -070025import android.app.ActivityManager;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010026import 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;
31import android.util.ArraySet;
Al Sutton7407e2c2019-09-06 09:17:01 +010032import android.util.AtomicFile;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010033import android.util.Slog;
34
35import com.android.internal.annotations.GuardedBy;
36import com.android.internal.annotations.VisibleForTesting;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010037import com.android.server.wm.nano.WindowManagerProtos.TaskSnapshotProto;
38
39import java.io.File;
40import java.io.FileOutputStream;
41import java.io.IOException;
42import java.util.ArrayDeque;
Jorim Jaggi2b78b1f2018-06-18 15:48:06 +020043import java.util.Arrays;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010044
45/**
46 * Persists {@link TaskSnapshot}s to disk.
47 * <p>
48 * Test class: {@link TaskSnapshotPersisterLoaderTest}
49 */
50class TaskSnapshotPersister {
51
52 private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotPersister" : TAG_WM;
53 private static final String SNAPSHOTS_DIRNAME = "snapshots";
Peter Kalauskas4dc04602020-02-12 18:49:03 -080054 private static final String LOW_RES_FILE_POSTFIX = "_reduced";
Peter Kalauskas5e59b442020-01-07 14:18:41 -080055 private static final float LOW_RAM_REDUCED_SCALE = .8f;
Peter Kalauskas4dc04602020-02-12 18:49:03 -080056 static final boolean DISABLE_HIGH_RES_BITMAPS = ActivityManager.isLowRamDeviceStatic();
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 Kalauskas4dc04602020-02-12 18:49:03 -080074 private final float mLowResScale;
Chiawei Wang02202d12019-01-03 18:12:13 +080075 private final boolean mUse16BitFormat;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010076
77 /**
78 * The list of ids of the tasks that have been persisted since {@link #removeObsoleteFiles} was
79 * called.
80 */
81 @GuardedBy("mLock")
82 private final ArraySet<Integer> mPersistedTaskIdsSinceLastRemoveObsolete = new ArraySet<>();
83
Kevin716a9db2018-12-18 16:53:39 -080084 TaskSnapshotPersister(WindowManagerService service, DirectoryResolver resolver) {
Jorim Jaggif9084ec2017-01-16 13:16:59 +010085 mDirectoryResolver = resolver;
Peter Kalauskase934da92020-01-21 15:15:22 -080086
87 if (ActivityManager.isLowRamDeviceStatic()) {
Peter Kalauskas4dc04602020-02-12 18:49:03 -080088 mLowResScale = LOW_RAM_REDUCED_SCALE;
Peter Kalauskase934da92020-01-21 15:15:22 -080089 } else {
Peter Kalauskas4dc04602020-02-12 18:49:03 -080090 mLowResScale = service.mContext.getResources().getFloat(
91 com.android.internal.R.dimen.config_lowResTaskSnapshotScale);
Peter Kalauskase934da92020-01-21 15:15:22 -080092 }
Chiawei Wang02202d12019-01-03 18:12:13 +080093 mUse16BitFormat = service.mContext.getResources().getBoolean(
94 com.android.internal.R.bool.config_use16BitTaskSnapshotPixelFormat);
Jorim Jaggif9084ec2017-01-16 13:16:59 +010095 }
96
97 /**
98 * Starts persisting.
99 */
100 void start() {
101 if (!mStarted) {
102 mStarted = true;
103 mPersister.start();
104 }
105 }
106
107 /**
108 * Persists a snapshot of a task to disk.
109 *
110 * @param taskId The id of the task that needs to be persisted.
111 * @param userId The id of the user this tasks belongs to.
112 * @param snapshot The snapshot to persist.
113 */
114 void persistSnapshot(int taskId, int userId, TaskSnapshot snapshot) {
115 synchronized (mLock) {
116 mPersistedTaskIdsSinceLastRemoveObsolete.add(taskId);
117 sendToQueueLocked(new StoreWriteQueueItem(taskId, userId, snapshot));
118 }
119 }
120
121 /**
122 * Callend when a task has been removed.
123 *
124 * @param taskId The id of task that has been removed.
125 * @param userId The id of the user the task belonged to.
126 */
127 void onTaskRemovedFromRecents(int taskId, int userId) {
128 synchronized (mLock) {
129 mPersistedTaskIdsSinceLastRemoveObsolete.remove(taskId);
130 sendToQueueLocked(new DeleteWriteQueueItem(taskId, userId));
131 }
132 }
133
134 /**
135 * In case a write/delete operation was lost because the system crashed, this makes sure to
136 * clean up the directory to remove obsolete files.
137 *
138 * @param persistentTaskIds A set of task ids that exist in our in-memory model.
139 * @param runningUserIds The ids of the list of users that have tasks loaded in our in-memory
140 * model.
141 */
142 void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
143 synchronized (mLock) {
144 mPersistedTaskIdsSinceLastRemoveObsolete.clear();
145 sendToQueueLocked(new RemoveObsoleteFilesQueueItem(persistentTaskIds, runningUserIds));
146 }
147 }
148
Jorim Jaggia41b7292017-05-11 23:50:34 +0200149 void setPaused(boolean paused) {
150 synchronized (mLock) {
151 mPaused = paused;
152 if (!paused) {
153 mLock.notifyAll();
154 }
155 }
156 }
157
Kevin716a9db2018-12-18 16:53:39 -0800158 /**
159 * Gets the scaling the persister uses for low resolution task snapshots.
160 *
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800161 * @return the lowResBitmap scale of task snapshots when they are set to be low res
Kevin716a9db2018-12-18 16:53:39 -0800162 */
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800163 float getLowResScale() {
164 return mLowResScale;
Kevin716a9db2018-12-18 16:53:39 -0800165 }
166
Chiawei Wang02202d12019-01-03 18:12:13 +0800167 /**
168 * Return if task snapshots are stored in 16 bit pixel format.
169 *
170 * @return true if task snapshots are stored in 16 bit pixel format.
171 */
172 boolean use16BitFormat() {
173 return mUse16BitFormat;
174 }
175
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100176 @TestApi
177 void waitForQueueEmpty() {
178 while (true) {
179 synchronized (mLock) {
180 if (mWriteQueue.isEmpty() && mQueueIdling) {
181 return;
182 }
183 }
184 SystemClock.sleep(100);
185 }
186 }
187
188 @GuardedBy("mLock")
189 private void sendToQueueLocked(WriteQueueItem item) {
190 mWriteQueue.offer(item);
Jorim Jaggief3651c2017-05-18 23:58:09 +0200191 item.onQueuedLocked();
192 ensureStoreQueueDepthLocked();
Jorim Jaggia41b7292017-05-11 23:50:34 +0200193 if (!mPaused) {
194 mLock.notifyAll();
195 }
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100196 }
197
Jorim Jaggief3651c2017-05-18 23:58:09 +0200198 @GuardedBy("mLock")
199 private void ensureStoreQueueDepthLocked() {
200 while (mStoreQueueItems.size() > MAX_STORE_QUEUE_DEPTH) {
201 final StoreWriteQueueItem item = mStoreQueueItems.poll();
202 mWriteQueue.remove(item);
203 Slog.i(TAG, "Queue is too deep! Purged item with taskid=" + item.mTaskId);
204 }
205 }
206
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100207 private File getDirectory(int userId) {
208 return new File(mDirectoryResolver.getSystemDirectoryForUser(userId), SNAPSHOTS_DIRNAME);
209 }
210
211 File getProtoFile(int taskId, int userId) {
212 return new File(getDirectory(userId), taskId + PROTO_EXTENSION);
213 }
214
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800215 File getHighResolutionBitmapFile(int taskId, int userId) {
Matthew Ngcb7ac672017-07-21 17:27:42 -0700216 // Full sized bitmaps are disabled on low ram devices
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800217 if (DISABLE_HIGH_RES_BITMAPS) {
Matthew Ngcb7ac672017-07-21 17:27:42 -0700218 Slog.wtf(TAG, "This device does not support full sized resolution bitmaps.");
219 return null;
220 }
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100221 return new File(getDirectory(userId), taskId + BITMAP_EXTENSION);
222 }
223
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800224 File getLowResolutionBitmapFile(int taskId, int userId) {
225 return new File(getDirectory(userId), taskId + LOW_RES_FILE_POSTFIX + BITMAP_EXTENSION);
Jorim Jaggi35e3f532017-03-17 17:06:50 +0100226 }
227
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100228 private boolean createDirectory(int userId) {
229 final File dir = getDirectory(userId);
230 return dir.exists() || dir.mkdirs();
231 }
232
233 private void deleteSnapshot(int taskId, int userId) {
234 final File protoFile = getProtoFile(taskId, userId);
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800235 final File bitmapLowResFile = getLowResolutionBitmapFile(taskId, userId);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100236 protoFile.delete();
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800237 bitmapLowResFile.delete();
Matthew Ngcb7ac672017-07-21 17:27:42 -0700238
239 // Low ram devices do not have a full sized file to delete
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800240 if (!DISABLE_HIGH_RES_BITMAPS) {
241 final File bitmapFile = getHighResolutionBitmapFile(taskId, userId);
Matthew Ngcb7ac672017-07-21 17:27:42 -0700242 bitmapFile.delete();
243 }
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100244 }
245
246 interface DirectoryResolver {
247 File getSystemDirectoryForUser(int userId);
248 }
249
250 private Thread mPersister = new Thread("TaskSnapshotPersister") {
251 public void run() {
252 android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
253 while (true) {
254 WriteQueueItem next;
255 synchronized (mLock) {
Jorim Jaggia41b7292017-05-11 23:50:34 +0200256 if (mPaused) {
257 next = null;
258 } else {
259 next = mWriteQueue.poll();
Jorim Jaggief3651c2017-05-18 23:58:09 +0200260 if (next != null) {
261 next.onDequeuedLocked();
262 }
Jorim Jaggia41b7292017-05-11 23:50:34 +0200263 }
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100264 }
265 if (next != null) {
266 next.write();
267 SystemClock.sleep(DELAY_MS);
268 }
269 synchronized (mLock) {
Jorim Jaggi2f9c7a22017-05-16 14:03:08 +0200270 final boolean writeQueueEmpty = mWriteQueue.isEmpty();
271 if (!writeQueueEmpty && !mPaused) {
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100272 continue;
273 }
274 try {
Jorim Jaggi2f9c7a22017-05-16 14:03:08 +0200275 mQueueIdling = writeQueueEmpty;
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100276 mLock.wait();
277 mQueueIdling = false;
278 } catch (InterruptedException e) {
279 }
280 }
281 }
282 }
283 };
284
285 private abstract class WriteQueueItem {
286 abstract void write();
Jorim Jaggief3651c2017-05-18 23:58:09 +0200287
288 /**
289 * Called when this queue item has been put into the queue.
290 */
291 void onQueuedLocked() {
292 }
293
294 /**
295 * Called when this queue item has been taken out of the queue.
296 */
297 void onDequeuedLocked() {
298 }
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100299 }
300
301 private class StoreWriteQueueItem extends WriteQueueItem {
302 private final int mTaskId;
303 private final int mUserId;
304 private final TaskSnapshot mSnapshot;
305
306 StoreWriteQueueItem(int taskId, int userId, TaskSnapshot snapshot) {
307 mTaskId = taskId;
308 mUserId = userId;
309 mSnapshot = snapshot;
310 }
311
Andreas Gampea36dc622018-02-05 17:19:22 -0800312 @GuardedBy("mLock")
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100313 @Override
Jorim Jaggief3651c2017-05-18 23:58:09 +0200314 void onQueuedLocked() {
315 mStoreQueueItems.offer(this);
316 }
317
Andreas Gampea36dc622018-02-05 17:19:22 -0800318 @GuardedBy("mLock")
Jorim Jaggief3651c2017-05-18 23:58:09 +0200319 @Override
320 void onDequeuedLocked() {
321 mStoreQueueItems.remove(this);
322 }
323
324 @Override
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100325 void write() {
326 if (!createDirectory(mUserId)) {
327 Slog.e(TAG, "Unable to create snapshot directory for user dir="
328 + getDirectory(mUserId));
329 }
330 boolean failed = false;
331 if (!writeProto()) {
332 failed = true;
333 }
334 if (!writeBuffer()) {
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100335 failed = true;
336 }
337 if (failed) {
338 deleteSnapshot(mTaskId, mUserId);
339 }
340 }
341
342 boolean writeProto() {
343 final TaskSnapshotProto proto = new TaskSnapshotProto();
344 proto.orientation = mSnapshot.getOrientation();
Vinit Nayakffd9dff2019-11-05 15:20:11 -0800345 proto.rotation = mSnapshot.getRotation();
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100346 proto.insetLeft = mSnapshot.getContentInsets().left;
347 proto.insetTop = mSnapshot.getContentInsets().top;
348 proto.insetRight = mSnapshot.getContentInsets().right;
349 proto.insetBottom = mSnapshot.getContentInsets().bottom;
Winson Chungf3e412e2018-03-08 11:07:40 -0800350 proto.isRealSnapshot = mSnapshot.isRealSnapshot();
Winson Chunga4fa8d52018-04-20 15:54:51 -0700351 proto.windowingMode = mSnapshot.getWindowingMode();
Winson Chung173020c2018-05-04 15:36:47 -0700352 proto.systemUiVisibility = mSnapshot.getSystemUiVisibility();
353 proto.isTranslucent = mSnapshot.isTranslucent();
Winson Chung48b25652018-10-22 14:04:30 -0700354 proto.topActivityComponent = mSnapshot.getTopActivityComponent().flattenToString();
Winson Chungffde2ea2019-06-17 17:19:13 -0700355 proto.scale = mSnapshot.getScale();
Hongwei Wang3b9bdcf2019-07-15 10:23:03 -0700356 proto.id = mSnapshot.getId();
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100357 final byte[] bytes = TaskSnapshotProto.toByteArray(proto);
358 final File file = getProtoFile(mTaskId, mUserId);
359 final AtomicFile atomicFile = new AtomicFile(file);
360 FileOutputStream fos = null;
361 try {
362 fos = atomicFile.startWrite();
363 fos.write(bytes);
364 atomicFile.finishWrite(fos);
365 } catch (IOException e) {
366 atomicFile.failWrite(fos);
367 Slog.e(TAG, "Unable to open " + file + " for persisting. " + e);
368 return false;
369 }
370 return true;
371 }
372
373 boolean writeBuffer() {
Peiyong Lin9d427402019-01-23 18:39:06 -0800374 final Bitmap bitmap = Bitmap.wrapHardwareBuffer(
Sunny Goyal62915b22019-04-10 12:28:47 -0700375 mSnapshot.getSnapshot(), mSnapshot.getColorSpace());
Winson Chungdab16732017-06-19 17:00:40 -0700376 if (bitmap == null) {
Winson Chung3e13ef82017-06-29 12:41:14 -0700377 Slog.e(TAG, "Invalid task snapshot hw bitmap");
Winson Chungdab16732017-06-19 17:00:40 -0700378 return false;
379 }
380
Jorim Jaggi2dae8552017-05-02 14:10:58 +0200381 final Bitmap swBitmap = bitmap.copy(Config.ARGB_8888, false /* isMutable */);
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800382 final Bitmap lowResBitmap = mSnapshot.isLowResolution()
Matthew Ngcb7ac672017-07-21 17:27:42 -0700383 ? swBitmap
384 : Bitmap.createScaledBitmap(swBitmap,
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800385 (int) (bitmap.getWidth() * mLowResScale),
386 (int) (bitmap.getHeight() * mLowResScale), true /* filter */);
Winson Chungffde2ea2019-06-17 17:19:13 -0700387
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800388 final File lowResFile = getLowResolutionBitmapFile(mTaskId, mUserId);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100389 try {
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800390 FileOutputStream lowResFos = new FileOutputStream(lowResFile);
391 lowResBitmap.compress(JPEG, QUALITY, lowResFos);
392 lowResFos.close();
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100393 } catch (IOException e) {
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800394 Slog.e(TAG, "Unable to open " + lowResFile + " for persisting.", e);
Matthew Ngcb7ac672017-07-21 17:27:42 -0700395 return false;
396 }
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800397 lowResBitmap.recycle();
Matthew Ngcb7ac672017-07-21 17:27:42 -0700398
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800399 // For snapshots with lowResBitmap resolution, do not create or save full sized bitmaps
400 if (mSnapshot.isLowResolution()) {
Tim Murrayaf853922018-10-15 16:50:23 -0700401 swBitmap.recycle();
Matthew Ngcb7ac672017-07-21 17:27:42 -0700402 return true;
403 }
404
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800405 final File file = getHighResolutionBitmapFile(mTaskId, mUserId);
Matthew Ngcb7ac672017-07-21 17:27:42 -0700406 try {
407 FileOutputStream fos = new FileOutputStream(file);
408 swBitmap.compress(JPEG, QUALITY, fos);
409 fos.close();
410 } catch (IOException e) {
411 Slog.e(TAG, "Unable to open " + file + " for persisting.", e);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100412 return false;
413 }
Tim Murrayaf853922018-10-15 16:50:23 -0700414 swBitmap.recycle();
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100415 return true;
416 }
417 }
418
419 private class DeleteWriteQueueItem extends WriteQueueItem {
420 private final int mTaskId;
421 private final int mUserId;
422
423 DeleteWriteQueueItem(int taskId, int userId) {
424 mTaskId = taskId;
425 mUserId = userId;
426 }
427
428 @Override
429 void write() {
430 deleteSnapshot(mTaskId, mUserId);
431 }
432 }
433
434 @VisibleForTesting
435 class RemoveObsoleteFilesQueueItem extends WriteQueueItem {
436 private final ArraySet<Integer> mPersistentTaskIds;
437 private final int[] mRunningUserIds;
438
439 @VisibleForTesting
440 RemoveObsoleteFilesQueueItem(ArraySet<Integer> persistentTaskIds,
441 int[] runningUserIds) {
Jorim Jaggi2b78b1f2018-06-18 15:48:06 +0200442 mPersistentTaskIds = new ArraySet<>(persistentTaskIds);
443 mRunningUserIds = Arrays.copyOf(runningUserIds, runningUserIds.length);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100444 }
445
446 @Override
447 void write() {
448 final ArraySet<Integer> newPersistedTaskIds;
449 synchronized (mLock) {
450 newPersistedTaskIds = new ArraySet<>(mPersistedTaskIdsSinceLastRemoveObsolete);
451 }
452 for (int userId : mRunningUserIds) {
453 final File dir = getDirectory(userId);
454 final String[] files = dir.list();
455 if (files == null) {
456 continue;
457 }
458 for (String file : files) {
459 final int taskId = getTaskId(file);
460 if (!mPersistentTaskIds.contains(taskId)
461 && !newPersistedTaskIds.contains(taskId)) {
462 new File(dir, file).delete();
463 }
464 }
465 }
466 }
467
468 @VisibleForTesting
469 int getTaskId(String fileName) {
470 if (!fileName.endsWith(PROTO_EXTENSION) && !fileName.endsWith(BITMAP_EXTENSION)) {
471 return -1;
472 }
473 final int end = fileName.lastIndexOf('.');
474 if (end == -1) {
475 return -1;
476 }
Jorim Jaggi35e3f532017-03-17 17:06:50 +0100477 String name = fileName.substring(0, end);
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800478 if (name.endsWith(LOW_RES_FILE_POSTFIX)) {
479 name = name.substring(0, name.length() - LOW_RES_FILE_POSTFIX.length());
Jorim Jaggi35e3f532017-03-17 17:06:50 +0100480 }
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100481 try {
Jorim Jaggi35e3f532017-03-17 17:06:50 +0100482 return Integer.parseInt(name);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100483 } catch (NumberFormatException e) {
484 return -1;
485 }
486 }
487 }
488}