blob: 164d3e055e949378d42db37df41eea93c67fcfde [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;
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";
Jorim Jaggif9084ec2017-01-16 13:16:59 +010055 private static final long DELAY_MS = 100;
Jorim Jaggi35e3f532017-03-17 17:06:50 +010056 private static final int QUALITY = 95;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010057 private static final String PROTO_EXTENSION = ".proto";
Jorim Jaggi35e3f532017-03-17 17:06:50 +010058 private static final String BITMAP_EXTENSION = ".jpg";
Jorim Jaggief3651c2017-05-18 23:58:09 +020059 private static final int MAX_STORE_QUEUE_DEPTH = 2;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010060
61 @GuardedBy("mLock")
62 private final ArrayDeque<WriteQueueItem> mWriteQueue = new ArrayDeque<>();
63 @GuardedBy("mLock")
Jorim Jaggief3651c2017-05-18 23:58:09 +020064 private final ArrayDeque<StoreWriteQueueItem> mStoreQueueItems = new ArrayDeque<>();
65 @GuardedBy("mLock")
Jorim Jaggif9084ec2017-01-16 13:16:59 +010066 private boolean mQueueIdling;
Jorim Jaggia41b7292017-05-11 23:50:34 +020067 @GuardedBy("mLock")
68 private boolean mPaused;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010069 private boolean mStarted;
70 private final Object mLock = new Object();
71 private final DirectoryResolver mDirectoryResolver;
Peter Kalauskas84f02a82020-01-22 16:06:18 -080072 private final float mLowResScaleFactor;
73 private boolean mEnableLowResSnapshots;
Chiawei Wang02202d12019-01-03 18:12:13 +080074 private final boolean mUse16BitFormat;
Jorim Jaggif9084ec2017-01-16 13:16:59 +010075
76 /**
77 * The list of ids of the tasks that have been persisted since {@link #removeObsoleteFiles} was
78 * called.
79 */
80 @GuardedBy("mLock")
81 private final ArraySet<Integer> mPersistedTaskIdsSinceLastRemoveObsolete = new ArraySet<>();
82
Kevin716a9db2018-12-18 16:53:39 -080083 TaskSnapshotPersister(WindowManagerService service, DirectoryResolver resolver) {
Jorim Jaggif9084ec2017-01-16 13:16:59 +010084 mDirectoryResolver = resolver;
Peter Kalauskas84f02a82020-01-22 16:06:18 -080085 final float highResTaskSnapshotScale = service.mContext.getResources().getFloat(
86 com.android.internal.R.dimen.config_highResTaskSnapshotScale);
87 final float lowResTaskSnapshotScale = service.mContext.getResources().getFloat(
88 com.android.internal.R.dimen.config_lowResTaskSnapshotScale);
Peter Kalauskase934da92020-01-21 15:15:22 -080089
Peter Kalauskas84f02a82020-01-22 16:06:18 -080090 if (lowResTaskSnapshotScale < 0 || 1 <= lowResTaskSnapshotScale) {
91 throw new RuntimeException("Low-res scale must be between 0 and 1");
Peter Kalauskase934da92020-01-21 15:15:22 -080092 }
Peter Kalauskas84f02a82020-01-22 16:06:18 -080093 if (highResTaskSnapshotScale <= 0 || 1 < highResTaskSnapshotScale) {
94 throw new RuntimeException("High-res scale must be between 0 and 1");
95 }
96 if (highResTaskSnapshotScale <= lowResTaskSnapshotScale) {
97 throw new RuntimeException("High-res scale must be greater than low-res scale");
98 }
99
100 if (lowResTaskSnapshotScale > 0) {
101 mLowResScaleFactor = lowResTaskSnapshotScale / highResTaskSnapshotScale;
102 setEnableLowResSnapshots(true);
103 } else {
104 mLowResScaleFactor = 0;
105 setEnableLowResSnapshots(false);
106 }
107
Chiawei Wang02202d12019-01-03 18:12:13 +0800108 mUse16BitFormat = service.mContext.getResources().getBoolean(
109 com.android.internal.R.bool.config_use16BitTaskSnapshotPixelFormat);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100110 }
111
112 /**
113 * Starts persisting.
114 */
115 void start() {
116 if (!mStarted) {
117 mStarted = true;
118 mPersister.start();
119 }
120 }
121
122 /**
123 * Persists a snapshot of a task to disk.
124 *
125 * @param taskId The id of the task that needs to be persisted.
126 * @param userId The id of the user this tasks belongs to.
127 * @param snapshot The snapshot to persist.
128 */
129 void persistSnapshot(int taskId, int userId, TaskSnapshot snapshot) {
130 synchronized (mLock) {
131 mPersistedTaskIdsSinceLastRemoveObsolete.add(taskId);
132 sendToQueueLocked(new StoreWriteQueueItem(taskId, userId, snapshot));
133 }
134 }
135
136 /**
137 * Callend when a task has been removed.
138 *
139 * @param taskId The id of task that has been removed.
140 * @param userId The id of the user the task belonged to.
141 */
142 void onTaskRemovedFromRecents(int taskId, int userId) {
143 synchronized (mLock) {
144 mPersistedTaskIdsSinceLastRemoveObsolete.remove(taskId);
145 sendToQueueLocked(new DeleteWriteQueueItem(taskId, userId));
146 }
147 }
148
149 /**
150 * In case a write/delete operation was lost because the system crashed, this makes sure to
151 * clean up the directory to remove obsolete files.
152 *
153 * @param persistentTaskIds A set of task ids that exist in our in-memory model.
154 * @param runningUserIds The ids of the list of users that have tasks loaded in our in-memory
155 * model.
156 */
157 void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
158 synchronized (mLock) {
159 mPersistedTaskIdsSinceLastRemoveObsolete.clear();
160 sendToQueueLocked(new RemoveObsoleteFilesQueueItem(persistentTaskIds, runningUserIds));
161 }
162 }
163
Jorim Jaggia41b7292017-05-11 23:50:34 +0200164 void setPaused(boolean paused) {
165 synchronized (mLock) {
166 mPaused = paused;
167 if (!paused) {
168 mLock.notifyAll();
169 }
170 }
171 }
172
Peter Kalauskas84f02a82020-01-22 16:06:18 -0800173 boolean enableLowResSnapshots() {
174 return mEnableLowResSnapshots;
175 }
176
Kevin716a9db2018-12-18 16:53:39 -0800177 /**
Peter Kalauskas84f02a82020-01-22 16:06:18 -0800178 * Not to be used. Only here for testing.
Kevin716a9db2018-12-18 16:53:39 -0800179 */
Peter Kalauskas84f02a82020-01-22 16:06:18 -0800180 @VisibleForTesting
181 void setEnableLowResSnapshots(boolean enabled) {
182 mEnableLowResSnapshots = enabled;
Kevin716a9db2018-12-18 16:53:39 -0800183 }
184
Chiawei Wang02202d12019-01-03 18:12:13 +0800185 /**
186 * Return if task snapshots are stored in 16 bit pixel format.
187 *
188 * @return true if task snapshots are stored in 16 bit pixel format.
189 */
190 boolean use16BitFormat() {
191 return mUse16BitFormat;
192 }
193
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100194 @TestApi
195 void waitForQueueEmpty() {
196 while (true) {
197 synchronized (mLock) {
198 if (mWriteQueue.isEmpty() && mQueueIdling) {
199 return;
200 }
201 }
202 SystemClock.sleep(100);
203 }
204 }
205
206 @GuardedBy("mLock")
207 private void sendToQueueLocked(WriteQueueItem item) {
208 mWriteQueue.offer(item);
Jorim Jaggief3651c2017-05-18 23:58:09 +0200209 item.onQueuedLocked();
210 ensureStoreQueueDepthLocked();
Jorim Jaggia41b7292017-05-11 23:50:34 +0200211 if (!mPaused) {
212 mLock.notifyAll();
213 }
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100214 }
215
Jorim Jaggief3651c2017-05-18 23:58:09 +0200216 @GuardedBy("mLock")
217 private void ensureStoreQueueDepthLocked() {
218 while (mStoreQueueItems.size() > MAX_STORE_QUEUE_DEPTH) {
219 final StoreWriteQueueItem item = mStoreQueueItems.poll();
220 mWriteQueue.remove(item);
221 Slog.i(TAG, "Queue is too deep! Purged item with taskid=" + item.mTaskId);
222 }
223 }
224
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100225 private File getDirectory(int userId) {
226 return new File(mDirectoryResolver.getSystemDirectoryForUser(userId), SNAPSHOTS_DIRNAME);
227 }
228
229 File getProtoFile(int taskId, int userId) {
230 return new File(getDirectory(userId), taskId + PROTO_EXTENSION);
231 }
232
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800233 File getHighResolutionBitmapFile(int taskId, int userId) {
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100234 return new File(getDirectory(userId), taskId + BITMAP_EXTENSION);
235 }
236
Peter Kalauskas84f02a82020-01-22 16:06:18 -0800237 @NonNull
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800238 File getLowResolutionBitmapFile(int taskId, int userId) {
239 return new File(getDirectory(userId), taskId + LOW_RES_FILE_POSTFIX + BITMAP_EXTENSION);
Jorim Jaggi35e3f532017-03-17 17:06:50 +0100240 }
241
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100242 private boolean createDirectory(int userId) {
243 final File dir = getDirectory(userId);
244 return dir.exists() || dir.mkdirs();
245 }
246
247 private void deleteSnapshot(int taskId, int userId) {
248 final File protoFile = getProtoFile(taskId, userId);
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800249 final File bitmapLowResFile = getLowResolutionBitmapFile(taskId, userId);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100250 protoFile.delete();
Peter Kalauskas84f02a82020-01-22 16:06:18 -0800251 if (bitmapLowResFile.exists()) {
252 bitmapLowResFile.delete();
253 }
254 final File bitmapFile = getHighResolutionBitmapFile(taskId, userId);
255 if (bitmapFile.exists()) {
Matthew Ngcb7ac672017-07-21 17:27:42 -0700256 bitmapFile.delete();
257 }
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100258 }
259
260 interface DirectoryResolver {
261 File getSystemDirectoryForUser(int userId);
262 }
263
264 private Thread mPersister = new Thread("TaskSnapshotPersister") {
265 public void run() {
266 android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
267 while (true) {
268 WriteQueueItem next;
269 synchronized (mLock) {
Jorim Jaggia41b7292017-05-11 23:50:34 +0200270 if (mPaused) {
271 next = null;
272 } else {
273 next = mWriteQueue.poll();
Jorim Jaggief3651c2017-05-18 23:58:09 +0200274 if (next != null) {
275 next.onDequeuedLocked();
276 }
Jorim Jaggia41b7292017-05-11 23:50:34 +0200277 }
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100278 }
279 if (next != null) {
280 next.write();
281 SystemClock.sleep(DELAY_MS);
282 }
283 synchronized (mLock) {
Jorim Jaggi2f9c7a22017-05-16 14:03:08 +0200284 final boolean writeQueueEmpty = mWriteQueue.isEmpty();
285 if (!writeQueueEmpty && !mPaused) {
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100286 continue;
287 }
288 try {
Jorim Jaggi2f9c7a22017-05-16 14:03:08 +0200289 mQueueIdling = writeQueueEmpty;
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100290 mLock.wait();
291 mQueueIdling = false;
292 } catch (InterruptedException e) {
293 }
294 }
295 }
296 }
297 };
298
299 private abstract class WriteQueueItem {
300 abstract void write();
Jorim Jaggief3651c2017-05-18 23:58:09 +0200301
302 /**
303 * Called when this queue item has been put into the queue.
304 */
305 void onQueuedLocked() {
306 }
307
308 /**
309 * Called when this queue item has been taken out of the queue.
310 */
311 void onDequeuedLocked() {
312 }
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100313 }
314
315 private class StoreWriteQueueItem extends WriteQueueItem {
316 private final int mTaskId;
317 private final int mUserId;
318 private final TaskSnapshot mSnapshot;
319
320 StoreWriteQueueItem(int taskId, int userId, TaskSnapshot snapshot) {
321 mTaskId = taskId;
322 mUserId = userId;
323 mSnapshot = snapshot;
324 }
325
Andreas Gampea36dc622018-02-05 17:19:22 -0800326 @GuardedBy("mLock")
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100327 @Override
Jorim Jaggief3651c2017-05-18 23:58:09 +0200328 void onQueuedLocked() {
329 mStoreQueueItems.offer(this);
330 }
331
Andreas Gampea36dc622018-02-05 17:19:22 -0800332 @GuardedBy("mLock")
Jorim Jaggief3651c2017-05-18 23:58:09 +0200333 @Override
334 void onDequeuedLocked() {
335 mStoreQueueItems.remove(this);
336 }
337
338 @Override
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100339 void write() {
340 if (!createDirectory(mUserId)) {
341 Slog.e(TAG, "Unable to create snapshot directory for user dir="
342 + getDirectory(mUserId));
343 }
344 boolean failed = false;
345 if (!writeProto()) {
346 failed = true;
347 }
348 if (!writeBuffer()) {
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100349 failed = true;
350 }
351 if (failed) {
352 deleteSnapshot(mTaskId, mUserId);
353 }
354 }
355
356 boolean writeProto() {
357 final TaskSnapshotProto proto = new TaskSnapshotProto();
358 proto.orientation = mSnapshot.getOrientation();
Vinit Nayakffd9dff2019-11-05 15:20:11 -0800359 proto.rotation = mSnapshot.getRotation();
Peter Kalauskasfe0a4132020-01-31 16:50:42 -0800360 proto.taskWidth = mSnapshot.getTaskSize().x;
361 proto.taskHeight = mSnapshot.getTaskSize().y;
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100362 proto.insetLeft = mSnapshot.getContentInsets().left;
363 proto.insetTop = mSnapshot.getContentInsets().top;
364 proto.insetRight = mSnapshot.getContentInsets().right;
365 proto.insetBottom = mSnapshot.getContentInsets().bottom;
Winson Chungf3e412e2018-03-08 11:07:40 -0800366 proto.isRealSnapshot = mSnapshot.isRealSnapshot();
Winson Chunga4fa8d52018-04-20 15:54:51 -0700367 proto.windowingMode = mSnapshot.getWindowingMode();
Winson Chung173020c2018-05-04 15:36:47 -0700368 proto.systemUiVisibility = mSnapshot.getSystemUiVisibility();
369 proto.isTranslucent = mSnapshot.isTranslucent();
Winson Chung48b25652018-10-22 14:04:30 -0700370 proto.topActivityComponent = mSnapshot.getTopActivityComponent().flattenToString();
Hongwei Wang3b9bdcf2019-07-15 10:23:03 -0700371 proto.id = mSnapshot.getId();
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100372 final byte[] bytes = TaskSnapshotProto.toByteArray(proto);
373 final File file = getProtoFile(mTaskId, mUserId);
374 final AtomicFile atomicFile = new AtomicFile(file);
375 FileOutputStream fos = null;
376 try {
377 fos = atomicFile.startWrite();
378 fos.write(bytes);
379 atomicFile.finishWrite(fos);
380 } catch (IOException e) {
381 atomicFile.failWrite(fos);
382 Slog.e(TAG, "Unable to open " + file + " for persisting. " + e);
383 return false;
384 }
385 return true;
386 }
387
388 boolean writeBuffer() {
Peiyong Lin9d427402019-01-23 18:39:06 -0800389 final Bitmap bitmap = Bitmap.wrapHardwareBuffer(
Sunny Goyal62915b22019-04-10 12:28:47 -0700390 mSnapshot.getSnapshot(), mSnapshot.getColorSpace());
Winson Chungdab16732017-06-19 17:00:40 -0700391 if (bitmap == null) {
Winson Chung3e13ef82017-06-29 12:41:14 -0700392 Slog.e(TAG, "Invalid task snapshot hw bitmap");
Winson Chungdab16732017-06-19 17:00:40 -0700393 return false;
394 }
395
Jorim Jaggi2dae8552017-05-02 14:10:58 +0200396 final Bitmap swBitmap = bitmap.copy(Config.ARGB_8888, false /* isMutable */);
Peter Kalauskas84f02a82020-01-22 16:06:18 -0800397
398 final File file = getHighResolutionBitmapFile(mTaskId, mUserId);
399 try {
400 FileOutputStream fos = new FileOutputStream(file);
401 swBitmap.compress(JPEG, QUALITY, fos);
402 fos.close();
403 } catch (IOException e) {
404 Slog.e(TAG, "Unable to open " + file + " for persisting.", e);
405 return false;
406 }
407
408 if (!enableLowResSnapshots()) {
409 swBitmap.recycle();
410 return true;
411 }
412
413 final Bitmap lowResBitmap = Bitmap.createScaledBitmap(swBitmap,
414 (int) (bitmap.getWidth() * mLowResScaleFactor),
415 (int) (bitmap.getHeight() * mLowResScaleFactor), true /* filter */);
416 swBitmap.recycle();
Winson Chungffde2ea2019-06-17 17:19:13 -0700417
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800418 final File lowResFile = getLowResolutionBitmapFile(mTaskId, mUserId);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100419 try {
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800420 FileOutputStream lowResFos = new FileOutputStream(lowResFile);
421 lowResBitmap.compress(JPEG, QUALITY, lowResFos);
422 lowResFos.close();
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100423 } catch (IOException e) {
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800424 Slog.e(TAG, "Unable to open " + lowResFile + " for persisting.", e);
Matthew Ngcb7ac672017-07-21 17:27:42 -0700425 return false;
426 }
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800427 lowResBitmap.recycle();
Matthew Ngcb7ac672017-07-21 17:27:42 -0700428
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100429 return true;
430 }
431 }
432
433 private class DeleteWriteQueueItem extends WriteQueueItem {
434 private final int mTaskId;
435 private final int mUserId;
436
437 DeleteWriteQueueItem(int taskId, int userId) {
438 mTaskId = taskId;
439 mUserId = userId;
440 }
441
442 @Override
443 void write() {
444 deleteSnapshot(mTaskId, mUserId);
445 }
446 }
447
448 @VisibleForTesting
449 class RemoveObsoleteFilesQueueItem extends WriteQueueItem {
450 private final ArraySet<Integer> mPersistentTaskIds;
451 private final int[] mRunningUserIds;
452
453 @VisibleForTesting
454 RemoveObsoleteFilesQueueItem(ArraySet<Integer> persistentTaskIds,
455 int[] runningUserIds) {
Jorim Jaggi2b78b1f2018-06-18 15:48:06 +0200456 mPersistentTaskIds = new ArraySet<>(persistentTaskIds);
457 mRunningUserIds = Arrays.copyOf(runningUserIds, runningUserIds.length);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100458 }
459
460 @Override
461 void write() {
462 final ArraySet<Integer> newPersistedTaskIds;
463 synchronized (mLock) {
464 newPersistedTaskIds = new ArraySet<>(mPersistedTaskIdsSinceLastRemoveObsolete);
465 }
466 for (int userId : mRunningUserIds) {
467 final File dir = getDirectory(userId);
468 final String[] files = dir.list();
469 if (files == null) {
470 continue;
471 }
472 for (String file : files) {
473 final int taskId = getTaskId(file);
474 if (!mPersistentTaskIds.contains(taskId)
475 && !newPersistedTaskIds.contains(taskId)) {
476 new File(dir, file).delete();
477 }
478 }
479 }
480 }
481
482 @VisibleForTesting
483 int getTaskId(String fileName) {
484 if (!fileName.endsWith(PROTO_EXTENSION) && !fileName.endsWith(BITMAP_EXTENSION)) {
485 return -1;
486 }
487 final int end = fileName.lastIndexOf('.');
488 if (end == -1) {
489 return -1;
490 }
Jorim Jaggi35e3f532017-03-17 17:06:50 +0100491 String name = fileName.substring(0, end);
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800492 if (name.endsWith(LOW_RES_FILE_POSTFIX)) {
493 name = name.substring(0, name.length() - LOW_RES_FILE_POSTFIX.length());
Jorim Jaggi35e3f532017-03-17 17:06:50 +0100494 }
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100495 try {
Jorim Jaggi35e3f532017-03-17 17:06:50 +0100496 return Integer.parseInt(name);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100497 } catch (NumberFormatException e) {
498 return -1;
499 }
500 }
501 }
502}