1 /*
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 
17 package com.android.server.wm;
18 
19 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
20 
21 import android.os.Trace;
22 import android.util.ArraySet;
23 import android.window.TaskSnapshot;
24 
25 import com.android.internal.annotations.GuardedBy;
26 import com.android.internal.annotations.VisibleForTesting;
27 
28 import java.io.File;
29 import java.util.Arrays;
30 
31 /**
32  * Persists {@link TaskSnapshot}s to disk.
33  * <p>
34  * Test class: {@link TaskSnapshotPersisterLoaderTest}
35  */
36 class TaskSnapshotPersister extends BaseAppSnapshotPersister {
37 
38     /**
39      * The list of ids of the tasks that have been persisted since {@link #removeObsoleteFiles} was
40      * called.
41      */
42     @GuardedBy("mLock")
43     private final ArraySet<Integer> mPersistedTaskIdsSinceLastRemoveObsolete = new ArraySet<>();
44 
TaskSnapshotPersister(SnapshotPersistQueue persistQueue, PersistInfoProvider persistInfoProvider)45     TaskSnapshotPersister(SnapshotPersistQueue persistQueue,
46             PersistInfoProvider persistInfoProvider) {
47         super(persistQueue, persistInfoProvider);
48     }
49 
50     /**
51      * Persists a snapshot of a task to disk.
52      *
53      * @param taskId The id of the task that needs to be persisted.
54      * @param userId The id of the user this tasks belongs to.
55      * @param snapshot The snapshot to persist.
56      */
persistSnapshot(int taskId, int userId, TaskSnapshot snapshot)57     void persistSnapshot(int taskId, int userId, TaskSnapshot snapshot) {
58         synchronized (mLock) {
59             mPersistedTaskIdsSinceLastRemoveObsolete.add(taskId);
60             super.persistSnapshot(taskId, userId, snapshot);
61         }
62     }
63 
64     /**
65      * Callend when a task has been removed.
66      *
67      * @param taskId The id of task that has been removed.
68      * @param userId The id of the user the task belonged to.
69      */
onTaskRemovedFromRecents(int taskId, int userId)70     void onTaskRemovedFromRecents(int taskId, int userId) {
71         synchronized (mLock) {
72             mPersistedTaskIdsSinceLastRemoveObsolete.remove(taskId);
73             super.removeSnap(taskId, userId);
74         }
75     }
76 
77     /**
78      * In case a write/delete operation was lost because the system crashed, this makes sure to
79      * clean up the directory to remove obsolete files.
80      *
81      * @param persistentTaskIds A set of task ids that exist in our in-memory model.
82      * @param runningUserIds The ids of the list of users that have tasks loaded in our in-memory
83      *                       model.
84      */
removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds)85     void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
86         synchronized (mLock) {
87             mPersistedTaskIdsSinceLastRemoveObsolete.clear();
88             mSnapshotPersistQueue.sendToQueueLocked(new RemoveObsoleteFilesQueueItem(
89                     persistentTaskIds, runningUserIds, mPersistInfoProvider));
90         }
91     }
92 
93     @VisibleForTesting
94     class RemoveObsoleteFilesQueueItem extends SnapshotPersistQueue.WriteQueueItem {
95         private final ArraySet<Integer> mPersistentTaskIds;
96         private final int[] mRunningUserIds;
97 
98         @VisibleForTesting
RemoveObsoleteFilesQueueItem(ArraySet<Integer> persistentTaskIds, int[] runningUserIds, PersistInfoProvider provider)99         RemoveObsoleteFilesQueueItem(ArraySet<Integer> persistentTaskIds,
100                 int[] runningUserIds, PersistInfoProvider provider) {
101             super(provider);
102             mPersistentTaskIds = new ArraySet<>(persistentTaskIds);
103             mRunningUserIds = Arrays.copyOf(runningUserIds, runningUserIds.length);
104         }
105 
106         @Override
write()107         void write() {
108             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "RemoveObsoleteFilesQueueItem");
109             final ArraySet<Integer> newPersistedTaskIds;
110             synchronized (mLock) {
111                 newPersistedTaskIds = new ArraySet<>(mPersistedTaskIdsSinceLastRemoveObsolete);
112             }
113             for (int userId : mRunningUserIds) {
114                 final File dir = mPersistInfoProvider.getDirectory(userId);
115                 final String[] files = dir.list();
116                 if (files == null) {
117                     continue;
118                 }
119                 for (String file : files) {
120                     final int taskId = getTaskId(file);
121                     if (!mPersistentTaskIds.contains(taskId)
122                             && !newPersistedTaskIds.contains(taskId)) {
123                         new File(dir, file).delete();
124                     }
125                 }
126             }
127             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
128         }
129 
130         @VisibleForTesting
getTaskId(String fileName)131         int getTaskId(String fileName) {
132             if (!fileName.endsWith(PROTO_EXTENSION) && !fileName.endsWith(BITMAP_EXTENSION)) {
133                 return -1;
134             }
135             final int end = fileName.lastIndexOf('.');
136             if (end == -1) {
137                 return -1;
138             }
139             String name = fileName.substring(0, end);
140             if (name.endsWith(LOW_RES_FILE_POSTFIX)) {
141                 name = name.substring(0, name.length() - LOW_RES_FILE_POSTFIX.length());
142             }
143             try {
144                 return Integer.parseInt(name);
145             } catch (NumberFormatException e) {
146                 return -1;
147             }
148         }
149     }
150 }
151