blob: 45023acf4466de314a898a519102848f8fc33683 [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;
Peter Kalauskasc3cc8b92020-03-09 18:55:04 -0700102 mEnableLowResSnapshots = true;
Peter Kalauskas84f02a82020-01-22 16:06:18 -0800103 } else {
104 mLowResScaleFactor = 0;
Peter Kalauskasc3cc8b92020-03-09 18:55:04 -0700105 mEnableLowResSnapshots = false;
Peter Kalauskas84f02a82020-01-22 16:06:18 -0800106 }
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 /**
Chiawei Wang02202d12019-01-03 18:12:13 +0800178 * Return if task snapshots are stored in 16 bit pixel format.
179 *
180 * @return true if task snapshots are stored in 16 bit pixel format.
181 */
182 boolean use16BitFormat() {
183 return mUse16BitFormat;
184 }
185
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100186 @TestApi
187 void waitForQueueEmpty() {
188 while (true) {
189 synchronized (mLock) {
190 if (mWriteQueue.isEmpty() && mQueueIdling) {
191 return;
192 }
193 }
194 SystemClock.sleep(100);
195 }
196 }
197
198 @GuardedBy("mLock")
199 private void sendToQueueLocked(WriteQueueItem item) {
200 mWriteQueue.offer(item);
Jorim Jaggief3651c2017-05-18 23:58:09 +0200201 item.onQueuedLocked();
202 ensureStoreQueueDepthLocked();
Jorim Jaggia41b7292017-05-11 23:50:34 +0200203 if (!mPaused) {
204 mLock.notifyAll();
205 }
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100206 }
207
Jorim Jaggief3651c2017-05-18 23:58:09 +0200208 @GuardedBy("mLock")
209 private void ensureStoreQueueDepthLocked() {
210 while (mStoreQueueItems.size() > MAX_STORE_QUEUE_DEPTH) {
211 final StoreWriteQueueItem item = mStoreQueueItems.poll();
212 mWriteQueue.remove(item);
213 Slog.i(TAG, "Queue is too deep! Purged item with taskid=" + item.mTaskId);
214 }
215 }
216
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100217 private File getDirectory(int userId) {
218 return new File(mDirectoryResolver.getSystemDirectoryForUser(userId), SNAPSHOTS_DIRNAME);
219 }
220
221 File getProtoFile(int taskId, int userId) {
222 return new File(getDirectory(userId), taskId + PROTO_EXTENSION);
223 }
224
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800225 File getHighResolutionBitmapFile(int taskId, int userId) {
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100226 return new File(getDirectory(userId), taskId + BITMAP_EXTENSION);
227 }
228
Peter Kalauskas84f02a82020-01-22 16:06:18 -0800229 @NonNull
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800230 File getLowResolutionBitmapFile(int taskId, int userId) {
231 return new File(getDirectory(userId), taskId + LOW_RES_FILE_POSTFIX + BITMAP_EXTENSION);
Jorim Jaggi35e3f532017-03-17 17:06:50 +0100232 }
233
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100234 private boolean createDirectory(int userId) {
235 final File dir = getDirectory(userId);
236 return dir.exists() || dir.mkdirs();
237 }
238
239 private void deleteSnapshot(int taskId, int userId) {
240 final File protoFile = getProtoFile(taskId, userId);
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800241 final File bitmapLowResFile = getLowResolutionBitmapFile(taskId, userId);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100242 protoFile.delete();
Peter Kalauskas84f02a82020-01-22 16:06:18 -0800243 if (bitmapLowResFile.exists()) {
244 bitmapLowResFile.delete();
245 }
246 final File bitmapFile = getHighResolutionBitmapFile(taskId, userId);
247 if (bitmapFile.exists()) {
Matthew Ngcb7ac672017-07-21 17:27:42 -0700248 bitmapFile.delete();
249 }
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100250 }
251
252 interface DirectoryResolver {
253 File getSystemDirectoryForUser(int userId);
254 }
255
256 private Thread mPersister = new Thread("TaskSnapshotPersister") {
257 public void run() {
258 android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
259 while (true) {
260 WriteQueueItem next;
261 synchronized (mLock) {
Jorim Jaggia41b7292017-05-11 23:50:34 +0200262 if (mPaused) {
263 next = null;
264 } else {
265 next = mWriteQueue.poll();
Jorim Jaggief3651c2017-05-18 23:58:09 +0200266 if (next != null) {
267 next.onDequeuedLocked();
268 }
Jorim Jaggia41b7292017-05-11 23:50:34 +0200269 }
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100270 }
271 if (next != null) {
272 next.write();
273 SystemClock.sleep(DELAY_MS);
274 }
275 synchronized (mLock) {
Jorim Jaggi2f9c7a22017-05-16 14:03:08 +0200276 final boolean writeQueueEmpty = mWriteQueue.isEmpty();
277 if (!writeQueueEmpty && !mPaused) {
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100278 continue;
279 }
280 try {
Jorim Jaggi2f9c7a22017-05-16 14:03:08 +0200281 mQueueIdling = writeQueueEmpty;
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100282 mLock.wait();
283 mQueueIdling = false;
284 } catch (InterruptedException e) {
285 }
286 }
287 }
288 }
289 };
290
291 private abstract class WriteQueueItem {
292 abstract void write();
Jorim Jaggief3651c2017-05-18 23:58:09 +0200293
294 /**
295 * Called when this queue item has been put into the queue.
296 */
297 void onQueuedLocked() {
298 }
299
300 /**
301 * Called when this queue item has been taken out of the queue.
302 */
303 void onDequeuedLocked() {
304 }
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100305 }
306
307 private class StoreWriteQueueItem extends WriteQueueItem {
308 private final int mTaskId;
309 private final int mUserId;
310 private final TaskSnapshot mSnapshot;
311
312 StoreWriteQueueItem(int taskId, int userId, TaskSnapshot snapshot) {
313 mTaskId = taskId;
314 mUserId = userId;
315 mSnapshot = snapshot;
316 }
317
Andreas Gampea36dc622018-02-05 17:19:22 -0800318 @GuardedBy("mLock")
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100319 @Override
Jorim Jaggief3651c2017-05-18 23:58:09 +0200320 void onQueuedLocked() {
321 mStoreQueueItems.offer(this);
322 }
323
Andreas Gampea36dc622018-02-05 17:19:22 -0800324 @GuardedBy("mLock")
Jorim Jaggief3651c2017-05-18 23:58:09 +0200325 @Override
326 void onDequeuedLocked() {
327 mStoreQueueItems.remove(this);
328 }
329
330 @Override
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100331 void write() {
332 if (!createDirectory(mUserId)) {
333 Slog.e(TAG, "Unable to create snapshot directory for user dir="
334 + getDirectory(mUserId));
335 }
336 boolean failed = false;
337 if (!writeProto()) {
338 failed = true;
339 }
340 if (!writeBuffer()) {
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100341 failed = true;
342 }
343 if (failed) {
344 deleteSnapshot(mTaskId, mUserId);
345 }
346 }
347
348 boolean writeProto() {
349 final TaskSnapshotProto proto = new TaskSnapshotProto();
350 proto.orientation = mSnapshot.getOrientation();
Vinit Nayakffd9dff2019-11-05 15:20:11 -0800351 proto.rotation = mSnapshot.getRotation();
Peter Kalauskasfe0a4132020-01-31 16:50:42 -0800352 proto.taskWidth = mSnapshot.getTaskSize().x;
353 proto.taskHeight = mSnapshot.getTaskSize().y;
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100354 proto.insetLeft = mSnapshot.getContentInsets().left;
355 proto.insetTop = mSnapshot.getContentInsets().top;
356 proto.insetRight = mSnapshot.getContentInsets().right;
357 proto.insetBottom = mSnapshot.getContentInsets().bottom;
Winson Chungf3e412e2018-03-08 11:07:40 -0800358 proto.isRealSnapshot = mSnapshot.isRealSnapshot();
Winson Chunga4fa8d52018-04-20 15:54:51 -0700359 proto.windowingMode = mSnapshot.getWindowingMode();
Winson Chung173020c2018-05-04 15:36:47 -0700360 proto.systemUiVisibility = mSnapshot.getSystemUiVisibility();
361 proto.isTranslucent = mSnapshot.isTranslucent();
Winson Chung48b25652018-10-22 14:04:30 -0700362 proto.topActivityComponent = mSnapshot.getTopActivityComponent().flattenToString();
Hongwei Wang3b9bdcf2019-07-15 10:23:03 -0700363 proto.id = mSnapshot.getId();
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100364 final byte[] bytes = TaskSnapshotProto.toByteArray(proto);
365 final File file = getProtoFile(mTaskId, mUserId);
366 final AtomicFile atomicFile = new AtomicFile(file);
367 FileOutputStream fos = null;
368 try {
369 fos = atomicFile.startWrite();
370 fos.write(bytes);
371 atomicFile.finishWrite(fos);
372 } catch (IOException e) {
373 atomicFile.failWrite(fos);
374 Slog.e(TAG, "Unable to open " + file + " for persisting. " + e);
375 return false;
376 }
377 return true;
378 }
379
380 boolean writeBuffer() {
Peiyong Lin9d427402019-01-23 18:39:06 -0800381 final Bitmap bitmap = Bitmap.wrapHardwareBuffer(
Sunny Goyal62915b22019-04-10 12:28:47 -0700382 mSnapshot.getSnapshot(), mSnapshot.getColorSpace());
Winson Chungdab16732017-06-19 17:00:40 -0700383 if (bitmap == null) {
Winson Chung3e13ef82017-06-29 12:41:14 -0700384 Slog.e(TAG, "Invalid task snapshot hw bitmap");
Winson Chungdab16732017-06-19 17:00:40 -0700385 return false;
386 }
387
Jorim Jaggi2dae8552017-05-02 14:10:58 +0200388 final Bitmap swBitmap = bitmap.copy(Config.ARGB_8888, false /* isMutable */);
Peter Kalauskas84f02a82020-01-22 16:06:18 -0800389
390 final File file = getHighResolutionBitmapFile(mTaskId, mUserId);
391 try {
392 FileOutputStream fos = new FileOutputStream(file);
393 swBitmap.compress(JPEG, QUALITY, fos);
394 fos.close();
395 } catch (IOException e) {
396 Slog.e(TAG, "Unable to open " + file + " for persisting.", e);
397 return false;
398 }
399
Peter Kalauskasc3cc8b92020-03-09 18:55:04 -0700400 if (!mEnableLowResSnapshots) {
Peter Kalauskas84f02a82020-01-22 16:06:18 -0800401 swBitmap.recycle();
402 return true;
403 }
404
405 final Bitmap lowResBitmap = Bitmap.createScaledBitmap(swBitmap,
406 (int) (bitmap.getWidth() * mLowResScaleFactor),
407 (int) (bitmap.getHeight() * mLowResScaleFactor), true /* filter */);
408 swBitmap.recycle();
Winson Chungffde2ea2019-06-17 17:19:13 -0700409
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800410 final File lowResFile = getLowResolutionBitmapFile(mTaskId, mUserId);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100411 try {
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800412 FileOutputStream lowResFos = new FileOutputStream(lowResFile);
413 lowResBitmap.compress(JPEG, QUALITY, lowResFos);
414 lowResFos.close();
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100415 } catch (IOException e) {
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800416 Slog.e(TAG, "Unable to open " + lowResFile + " for persisting.", e);
Matthew Ngcb7ac672017-07-21 17:27:42 -0700417 return false;
418 }
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800419 lowResBitmap.recycle();
Matthew Ngcb7ac672017-07-21 17:27:42 -0700420
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100421 return true;
422 }
423 }
424
425 private class DeleteWriteQueueItem extends WriteQueueItem {
426 private final int mTaskId;
427 private final int mUserId;
428
429 DeleteWriteQueueItem(int taskId, int userId) {
430 mTaskId = taskId;
431 mUserId = userId;
432 }
433
434 @Override
435 void write() {
436 deleteSnapshot(mTaskId, mUserId);
437 }
438 }
439
440 @VisibleForTesting
441 class RemoveObsoleteFilesQueueItem extends WriteQueueItem {
442 private final ArraySet<Integer> mPersistentTaskIds;
443 private final int[] mRunningUserIds;
444
445 @VisibleForTesting
446 RemoveObsoleteFilesQueueItem(ArraySet<Integer> persistentTaskIds,
447 int[] runningUserIds) {
Jorim Jaggi2b78b1f2018-06-18 15:48:06 +0200448 mPersistentTaskIds = new ArraySet<>(persistentTaskIds);
449 mRunningUserIds = Arrays.copyOf(runningUserIds, runningUserIds.length);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100450 }
451
452 @Override
453 void write() {
454 final ArraySet<Integer> newPersistedTaskIds;
455 synchronized (mLock) {
456 newPersistedTaskIds = new ArraySet<>(mPersistedTaskIdsSinceLastRemoveObsolete);
457 }
458 for (int userId : mRunningUserIds) {
459 final File dir = getDirectory(userId);
460 final String[] files = dir.list();
461 if (files == null) {
462 continue;
463 }
464 for (String file : files) {
465 final int taskId = getTaskId(file);
466 if (!mPersistentTaskIds.contains(taskId)
467 && !newPersistedTaskIds.contains(taskId)) {
468 new File(dir, file).delete();
469 }
470 }
471 }
472 }
473
474 @VisibleForTesting
475 int getTaskId(String fileName) {
476 if (!fileName.endsWith(PROTO_EXTENSION) && !fileName.endsWith(BITMAP_EXTENSION)) {
477 return -1;
478 }
479 final int end = fileName.lastIndexOf('.');
480 if (end == -1) {
481 return -1;
482 }
Jorim Jaggi35e3f532017-03-17 17:06:50 +0100483 String name = fileName.substring(0, end);
Peter Kalauskas4dc04602020-02-12 18:49:03 -0800484 if (name.endsWith(LOW_RES_FILE_POSTFIX)) {
485 name = name.substring(0, name.length() - LOW_RES_FILE_POSTFIX.length());
Jorim Jaggi35e3f532017-03-17 17:06:50 +0100486 }
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100487 try {
Jorim Jaggi35e3f532017-03-17 17:06:50 +0100488 return Integer.parseInt(name);
Jorim Jaggif9084ec2017-01-16 13:16:59 +0100489 } catch (NumberFormatException e) {
490 return -1;
491 }
492 }
493 }
494}