blob: 1e2f0d094944d0f2690c1ee193d0a285234ce22c [file] [log] [blame]
Craig Mautner21d24a22014-04-23 11:45:37 -07001/*
2 * Copyright (C) 2014 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
Wale Ogunwale59507092018-10-29 09:00:30 -070017package com.android.server.wm;
Craig Mautner21d24a22014-04-23 11:45:37 -070018
Wale Ogunwaled32da472018-11-16 07:19:28 -080019import static com.android.server.wm.RootActivityContainer.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS;
Garfield Tana3549832018-09-24 15:22:18 -070020
Suprabh Shukla4bccb462016-02-10 18:45:12 -080021import android.annotation.NonNull;
Craig Mautner21d24a22014-04-23 11:45:37 -070022import android.graphics.Bitmap;
23import android.graphics.BitmapFactory;
24import android.os.Debug;
Suprabh Shukla23593142015-11-03 17:31:15 -080025import android.os.Environment;
26import android.os.FileUtils;
Craig Mautner21d24a22014-04-23 11:45:37 -070027import android.os.SystemClock;
28import android.util.ArraySet;
29import android.util.AtomicFile;
30import android.util.Slog;
Suprabh Shukla4bccb462016-02-10 18:45:12 -080031import android.util.SparseArray;
32import android.util.SparseBooleanArray;
Craig Mautner21d24a22014-04-23 11:45:37 -070033import android.util.Xml;
Wale Ogunwale18795a22014-12-03 11:38:33 -080034
Suprabh Shukla4bccb462016-02-10 18:45:12 -080035import com.android.internal.annotations.VisibleForTesting;
Craig Mautner21d24a22014-04-23 11:45:37 -070036import com.android.internal.util.FastXmlSerializer;
37import com.android.internal.util.XmlUtils;
Garfield Tana3549832018-09-24 15:22:18 -070038
Suprabh Shukla09a88f52015-12-02 14:36:31 -080039import libcore.io.IoUtils;
40
Craig Mautner21d24a22014-04-23 11:45:37 -070041import org.xmlpull.v1.XmlPullParser;
42import org.xmlpull.v1.XmlPullParserException;
43import org.xmlpull.v1.XmlSerializer;
44
45import java.io.BufferedReader;
Suprabh Shukla4bccb462016-02-10 18:45:12 -080046import java.io.BufferedWriter;
Craig Mautner21d24a22014-04-23 11:45:37 -070047import java.io.File;
Suprabh Shukla4bccb462016-02-10 18:45:12 -080048import java.io.FileNotFoundException;
Craig Mautner21d24a22014-04-23 11:45:37 -070049import java.io.FileOutputStream;
50import java.io.FileReader;
Suprabh Shukla4bccb462016-02-10 18:45:12 -080051import java.io.FileWriter;
Craig Mautner21d24a22014-04-23 11:45:37 -070052import java.io.IOException;
53import java.io.StringWriter;
54import java.util.ArrayList;
Suprabh Shukla09a88f52015-12-02 14:36:31 -080055import java.util.Collections;
Craig Mautner21d24a22014-04-23 11:45:37 -070056import java.util.Comparator;
Suprabh Shukla23593142015-11-03 17:31:15 -080057import java.util.List;
Wale Ogunwale18795a22014-12-03 11:38:33 -080058
Garfield Tana3549832018-09-24 15:22:18 -070059/**
60 * Persister that saves recent tasks into disk.
61 */
62public class TaskPersister implements PersisterQueue.Listener {
Craig Mautner21d24a22014-04-23 11:45:37 -070063 static final String TAG = "TaskPersister";
Stefan Kuhnee88d1e52015-05-18 10:33:45 -070064 static final boolean DEBUG = false;
Garfield Tana3549832018-09-24 15:22:18 -070065 static final String IMAGE_EXTENSION = ".png";
Craig Mautner63f10902014-09-16 23:57:21 -070066
Craig Mautner21d24a22014-04-23 11:45:37 -070067 private static final String TASKS_DIRNAME = "recent_tasks";
Daichi Hirono4cb941e2017-03-31 14:30:41 +090068 private static final String TASK_FILENAME_SUFFIX = "_task.xml";
Craig Mautner21d24a22014-04-23 11:45:37 -070069 private static final String IMAGES_DIRNAME = "recent_images";
Suprabh Shukla4bccb462016-02-10 18:45:12 -080070 private static final String PERSISTED_TASK_IDS_FILENAME = "persisted_taskIds.txt";
Craig Mautner21d24a22014-04-23 11:45:37 -070071
Winson Chung36f3f032016-09-08 23:29:43 +000072 private static final String TAG_TASK = "task";
Craig Mautner21d24a22014-04-23 11:45:37 -070073
Wale Ogunwale16e505a2018-05-07 15:00:49 -070074 private final ActivityTaskManagerService mService;
Craig Mautner21d24a22014-04-23 11:45:37 -070075 private final ActivityStackSupervisor mStackSupervisor;
Wale Ogunwalec82f2f52014-12-09 09:32:50 -080076 private final RecentTasks mRecentTasks;
Suprabh Shukla4bccb462016-02-10 18:45:12 -080077 private final SparseArray<SparseBooleanArray> mTaskIdsInFile = new SparseArray<>();
Suprabh Shuklaf50b4582016-02-23 17:44:22 -080078 private final File mTaskIdsDir;
Suprabh Shuklafd0bd4f2016-08-24 14:08:29 -070079 // To lock file operations in TaskPersister
80 private final Object mIoLock = new Object();
Garfield Tana3549832018-09-24 15:22:18 -070081 private final PersisterQueue mPersisterQueue;
Craig Mautner21d24a22014-04-23 11:45:37 -070082
Garfield Tana3549832018-09-24 15:22:18 -070083 private final ArraySet<Integer> mTmpTaskIds = new ArraySet<>();
Craig Mautnerf4f8bb72014-07-29 10:41:40 -070084
Wale Ogunwalec82f2f52014-12-09 09:32:50 -080085 TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor,
Garfield Tan891146c2018-10-09 12:14:00 -070086 ActivityTaskManagerService service, RecentTasks recentTasks,
87 PersisterQueue persisterQueue) {
Suprabh Shukla23593142015-11-03 17:31:15 -080088
89 final File legacyImagesDir = new File(systemDir, IMAGES_DIRNAME);
90 if (legacyImagesDir.exists()) {
91 if (!FileUtils.deleteContents(legacyImagesDir) || !legacyImagesDir.delete()) {
92 Slog.i(TAG, "Failure deleting legacy images directory: " + legacyImagesDir);
Craig Mautner21d24a22014-04-23 11:45:37 -070093 }
94 }
95
Suprabh Shukla23593142015-11-03 17:31:15 -080096 final File legacyTasksDir = new File(systemDir, TASKS_DIRNAME);
97 if (legacyTasksDir.exists()) {
98 if (!FileUtils.deleteContents(legacyTasksDir) || !legacyTasksDir.delete()) {
99 Slog.i(TAG, "Failure deleting legacy tasks directory: " + legacyTasksDir);
Craig Mautner21d24a22014-04-23 11:45:37 -0700100 }
101 }
102
Suprabh Shuklaf50b4582016-02-23 17:44:22 -0800103 mTaskIdsDir = new File(Environment.getDataDirectory(), "system_de");
Craig Mautner21d24a22014-04-23 11:45:37 -0700104 mStackSupervisor = stackSupervisor;
Suprabh Shukla09a88f52015-12-02 14:36:31 -0800105 mService = service;
Wale Ogunwalec82f2f52014-12-09 09:32:50 -0800106 mRecentTasks = recentTasks;
Garfield Tan891146c2018-10-09 12:14:00 -0700107 mPersisterQueue = persisterQueue;
Garfield Tana3549832018-09-24 15:22:18 -0700108 mPersisterQueue.addListener(this);
Craig Mautner21d24a22014-04-23 11:45:37 -0700109 }
110
Suprabh Shuklaf50b4582016-02-23 17:44:22 -0800111 @VisibleForTesting
112 TaskPersister(File workingDir) {
113 mTaskIdsDir = workingDir;
114 mStackSupervisor = null;
115 mService = null;
116 mRecentTasks = null;
Garfield Tana3549832018-09-24 15:22:18 -0700117 mPersisterQueue = new PersisterQueue();
118 mPersisterQueue.addListener(this);
Suprabh Shuklaf50b4582016-02-23 17:44:22 -0800119 }
120
Louis Changcdec0802019-11-11 11:45:07 +0800121 private void removeThumbnails(Task task) {
Garfield Tana3549832018-09-24 15:22:18 -0700122 mPersisterQueue.removeItems(
123 item -> {
124 File file = new File(item.mFilePath);
Wale Ogunwale4e79a1c2019-10-05 20:52:40 -0700125 return file.getName().startsWith(Integer.toString(task.mTaskId));
Garfield Tana3549832018-09-24 15:22:18 -0700126 },
127 ImageWriteQueueItem.class);
Craig Mautner63f10902014-09-16 23:57:21 -0700128 }
129
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800130 @NonNull
131 SparseBooleanArray loadPersistedTaskIdsForUser(int userId) {
132 if (mTaskIdsInFile.get(userId) != null) {
133 return mTaskIdsInFile.get(userId).clone();
134 }
135 final SparseBooleanArray persistedTaskIds = new SparseBooleanArray();
Suprabh Shuklafd0bd4f2016-08-24 14:08:29 -0700136 synchronized (mIoLock) {
137 BufferedReader reader = null;
138 String line;
139 try {
140 reader = new BufferedReader(new FileReader(getUserPersistedTaskIdsFile(userId)));
141 while ((line = reader.readLine()) != null) {
142 for (String taskIdString : line.split("\\s+")) {
143 int id = Integer.parseInt(taskIdString);
144 persistedTaskIds.put(id, true);
145 }
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800146 }
Suprabh Shuklafd0bd4f2016-08-24 14:08:29 -0700147 } catch (FileNotFoundException e) {
148 // File doesn't exist. Ignore.
149 } catch (Exception e) {
150 Slog.e(TAG, "Error while reading taskIds file for user " + userId, e);
151 } finally {
152 IoUtils.closeQuietly(reader);
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800153 }
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800154 }
155 mTaskIdsInFile.put(userId, persistedTaskIds);
156 return persistedTaskIds.clone();
157 }
158
Suprabh Shuklafd0bd4f2016-08-24 14:08:29 -0700159
Suprabh Shuklaf50b4582016-02-23 17:44:22 -0800160 @VisibleForTesting
Suprabh Shuklafd0bd4f2016-08-24 14:08:29 -0700161 void writePersistedTaskIdsForUser(@NonNull SparseBooleanArray taskIds, int userId) {
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800162 if (userId < 0) {
163 return;
164 }
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800165 final File persistedTaskIdsFile = getUserPersistedTaskIdsFile(userId);
Suprabh Shuklafd0bd4f2016-08-24 14:08:29 -0700166 synchronized (mIoLock) {
167 BufferedWriter writer = null;
168 try {
169 writer = new BufferedWriter(new FileWriter(persistedTaskIdsFile));
170 for (int i = 0; i < taskIds.size(); i++) {
171 if (taskIds.valueAt(i)) {
172 writer.write(String.valueOf(taskIds.keyAt(i)));
173 writer.newLine();
174 }
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800175 }
Suprabh Shuklafd0bd4f2016-08-24 14:08:29 -0700176 } catch (Exception e) {
177 Slog.e(TAG, "Error while writing taskIds file for user " + userId, e);
178 } finally {
179 IoUtils.closeQuietly(writer);
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800180 }
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800181 }
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800182 }
183
184 void unloadUserDataFromMemory(int userId) {
185 mTaskIdsInFile.delete(userId);
186 }
187
Louis Changcdec0802019-11-11 11:45:07 +0800188 void wakeup(Task task, boolean flush) {
Garfield Tana3549832018-09-24 15:22:18 -0700189 synchronized (mPersisterQueue) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700190 if (task != null) {
Garfield Tana3549832018-09-24 15:22:18 -0700191 final TaskWriteQueueItem item = mPersisterQueue.findLastItem(
192 queueItem -> task == queueItem.mTask, TaskWriteQueueItem.class);
193 if (item != null && !task.inRecents) {
194 removeThumbnails(task);
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700195 }
Garfield Tana3549832018-09-24 15:22:18 -0700196
197 if (item == null && task.isPersistable) {
198 mPersisterQueue.addItem(new TaskWriteQueueItem(task, mService), flush);
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700199 }
200 } else {
Suprabh Shukla09a88f52015-12-02 14:36:31 -0800201 // Dummy. Ensures removeObsoleteFiles is called when LazyTaskThreadWriter is
202 // notified.
Garfield Tana3549832018-09-24 15:22:18 -0700203 mPersisterQueue.addItem(PersisterQueue.EMPTY_ITEM, flush);
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700204 }
Garfield Tana3549832018-09-24 15:22:18 -0700205 if (DEBUG) {
206 Slog.d(TAG, "wakeup: task=" + task + " flush=" + flush + " Callers="
207 + Debug.getCallers(4));
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700208 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700209 }
Craig Mautner63f10902014-09-16 23:57:21 -0700210
Garfield Tana3549832018-09-24 15:22:18 -0700211 mPersisterQueue.yieldIfQueueTooDeep();
Craig Mautner21d24a22014-04-23 11:45:37 -0700212 }
213
Dianne Hackbornce0fd762014-09-19 12:58:15 -0700214 void flush() {
Garfield Tana3549832018-09-24 15:22:18 -0700215 mPersisterQueue.flush();
Dianne Hackbornce0fd762014-09-19 12:58:15 -0700216 }
217
Suprabh Shukla23593142015-11-03 17:31:15 -0800218 void saveImage(Bitmap image, String filePath) {
Garfield Tan891146c2018-10-09 12:14:00 -0700219 mPersisterQueue.updateLastOrAddItem(new ImageWriteQueueItem(filePath, image),
220 /* flush */ false);
221 if (DEBUG) {
222 Slog.d(TAG, "saveImage: filePath=" + filePath + " now="
223 + SystemClock.uptimeMillis() + " Callers=" + Debug.getCallers(4));
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700224 }
225 }
226
Suprabh Shukla23593142015-11-03 17:31:15 -0800227 Bitmap getTaskDescriptionIcon(String filePath) {
Craig Mautner648f69b2014-09-18 14:16:26 -0700228 // See if it is in the write queue
Suprabh Shukla23593142015-11-03 17:31:15 -0800229 final Bitmap icon = getImageFromWriteQueue(filePath);
Craig Mautner648f69b2014-09-18 14:16:26 -0700230 if (icon != null) {
231 return icon;
232 }
Suprabh Shukla23593142015-11-03 17:31:15 -0800233 return restoreImage(filePath);
Craig Mautner648f69b2014-09-18 14:16:26 -0700234 }
235
Garfield Tana3549832018-09-24 15:22:18 -0700236 private Bitmap getImageFromWriteQueue(String filePath) {
237 final ImageWriteQueueItem item = mPersisterQueue.findLastItem(
238 queueItem -> queueItem.mFilePath.equals(filePath), ImageWriteQueueItem.class);
239 return item != null ? item.mImage : null;
Craig Mautner21d24a22014-04-23 11:45:37 -0700240 }
241
Craig Mautner77b04262014-06-27 15:22:12 -0700242 private String fileToString(File file) {
243 final String newline = System.lineSeparator();
244 try {
245 BufferedReader reader = new BufferedReader(new FileReader(file));
246 StringBuffer sb = new StringBuffer((int) file.length() * 2);
247 String line;
248 while ((line = reader.readLine()) != null) {
249 sb.append(line + newline);
250 }
251 reader.close();
252 return sb.toString();
253 } catch (IOException ioe) {
254 Slog.e(TAG, "Couldn't read file " + file.getName());
255 return null;
256 }
257 }
258
Louis Changcdec0802019-11-11 11:45:07 +0800259 private Task taskIdToTask(int taskId, ArrayList<Task> tasks) {
Craig Mautnera228ae92014-07-09 05:44:55 -0700260 if (taskId < 0) {
261 return null;
262 }
263 for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
Louis Changcdec0802019-11-11 11:45:07 +0800264 final Task task = tasks.get(taskNdx);
Wale Ogunwale4e79a1c2019-10-05 20:52:40 -0700265 if (task.mTaskId == taskId) {
Craig Mautnera228ae92014-07-09 05:44:55 -0700266 return task;
267 }
268 }
269 Slog.e(TAG, "Restore affiliation error looking for taskId=" + taskId);
270 return null;
271 }
272
Louis Changcdec0802019-11-11 11:45:07 +0800273 List<Task> restoreTasksForUserLocked(final int userId, SparseBooleanArray preaddedTasks) {
274 final ArrayList<Task> tasks = new ArrayList<Task>();
Craig Mautner21d24a22014-04-23 11:45:37 -0700275 ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>();
276
Suprabh Shukla23593142015-11-03 17:31:15 -0800277 File userTasksDir = getUserTasksDir(userId);
Winson Chung36f3f032016-09-08 23:29:43 +0000278
Suprabh Shukla23593142015-11-03 17:31:15 -0800279 File[] recentFiles = userTasksDir.listFiles();
Craig Mautner21d24a22014-04-23 11:45:37 -0700280 if (recentFiles == null) {
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800281 Slog.e(TAG, "restoreTasksForUserLocked: Unable to list files from " + userTasksDir);
Craig Mautner21d24a22014-04-23 11:45:37 -0700282 return tasks;
283 }
284
285 for (int taskNdx = 0; taskNdx < recentFiles.length; ++taskNdx) {
286 File taskFile = recentFiles[taskNdx];
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800287 if (DEBUG) {
288 Slog.d(TAG, "restoreTasksForUserLocked: userId=" + userId
289 + ", taskFile=" + taskFile.getName());
290 }
Daichi Hirono4cb941e2017-03-31 14:30:41 +0900291
292 if (!taskFile.getName().endsWith(TASK_FILENAME_SUFFIX)) {
293 continue;
294 }
295 try {
296 final int taskId = Integer.parseInt(taskFile.getName().substring(
297 0 /* beginIndex */,
298 taskFile.getName().length() - TASK_FILENAME_SUFFIX.length()));
299 if (preaddedTasks.get(taskId, false)) {
300 Slog.w(TAG, "Task #" + taskId +
301 " has already been created so we don't restore again");
302 continue;
303 }
304 } catch (NumberFormatException e) {
305 Slog.w(TAG, "Unexpected task file name", e);
306 continue;
307 }
308
Craig Mautner21d24a22014-04-23 11:45:37 -0700309 BufferedReader reader = null;
Craig Mautnere0129b32014-05-25 16:41:09 -0700310 boolean deleteFile = false;
Craig Mautner21d24a22014-04-23 11:45:37 -0700311 try {
312 reader = new BufferedReader(new FileReader(taskFile));
313 final XmlPullParser in = Xml.newPullParser();
314 in.setInput(reader);
315
316 int event;
317 while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
318 event != XmlPullParser.END_TAG) {
319 final String name = in.getName();
320 if (event == XmlPullParser.START_TAG) {
Suprabh Shukla09a88f52015-12-02 14:36:31 -0800321 if (DEBUG) Slog.d(TAG, "restoreTasksForUserLocked: START_TAG name=" + name);
Craig Mautner21d24a22014-04-23 11:45:37 -0700322 if (TAG_TASK.equals(name)) {
Louis Changcdec0802019-11-11 11:45:07 +0800323 final Task task = Task.restoreFromXml(in, mStackSupervisor);
Suprabh Shukla09a88f52015-12-02 14:36:31 -0800324 if (DEBUG) Slog.d(TAG, "restoreTasksForUserLocked: restored task="
Suprabh Shukla23593142015-11-03 17:31:15 -0800325 + task);
Craig Mautner21d24a22014-04-23 11:45:37 -0700326 if (task != null) {
Winson Chung36f3f032016-09-08 23:29:43 +0000327 // XXX Don't add to write queue... there is no reason to write
328 // out the stuff we just read, if we don't write it we will
329 // read the same thing again.
330 // mWriteQueue.add(new TaskWriteQueueItem(task));
331
Wale Ogunwale4e79a1c2019-10-05 20:52:40 -0700332 final int taskId = task.mTaskId;
Wale Ogunwaled32da472018-11-16 07:19:28 -0800333 if (mService.mRootActivityContainer.anyTaskForId(taskId,
Wale Ogunwale0568aed2017-09-08 13:29:37 -0700334 MATCH_TASK_IN_STACKS_OR_RECENT_TASKS) != null) {
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800335 // Should not happen.
336 Slog.wtf(TAG, "Existing task with taskId " + taskId + "found");
Wale Ogunwale4e79a1c2019-10-05 20:52:40 -0700337 } else if (userId != task.mUserId) {
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800338 // Should not happen.
Wale Ogunwale4e79a1c2019-10-05 20:52:40 -0700339 Slog.wtf(TAG, "Task with userId " + task.mUserId + " found in "
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800340 + userTasksDir.getAbsolutePath());
341 } else {
342 // Looks fine.
343 mStackSupervisor.setNextTaskIdForUserLocked(taskId, userId);
Amith Yamasani515d4062015-09-28 11:30:06 -0700344 task.isPersistable = true;
345 tasks.add(task);
346 recoveredTaskIds.add(taskId);
347 }
Craig Mautner77b04262014-06-27 15:22:12 -0700348 } else {
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800349 Slog.e(TAG, "restoreTasksForUserLocked: Unable to restore taskFile="
Suprabh Shukla23593142015-11-03 17:31:15 -0800350 + taskFile + ": " + fileToString(taskFile));
Craig Mautner21d24a22014-04-23 11:45:37 -0700351 }
352 } else {
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800353 Slog.wtf(TAG, "restoreTasksForUserLocked: Unknown xml event=" + event
Suprabh Shukla23593142015-11-03 17:31:15 -0800354 + " name=" + name);
Craig Mautner21d24a22014-04-23 11:45:37 -0700355 }
356 }
357 XmlUtils.skipCurrentTag(in);
358 }
Craig Mautnere0129b32014-05-25 16:41:09 -0700359 } catch (Exception e) {
Craig Mautnera228ae92014-07-09 05:44:55 -0700360 Slog.wtf(TAG, "Unable to parse " + taskFile + ". Error ", e);
Craig Mautner77b04262014-06-27 15:22:12 -0700361 Slog.e(TAG, "Failing file: " + fileToString(taskFile));
Craig Mautnere0129b32014-05-25 16:41:09 -0700362 deleteFile = true;
Craig Mautner21d24a22014-04-23 11:45:37 -0700363 } finally {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800364 IoUtils.closeQuietly(reader);
Stefan Kuhnee88d1e52015-05-18 10:33:45 -0700365 if (deleteFile) {
366 if (DEBUG) Slog.d(TAG, "Deleting file=" + taskFile.getName());
Craig Mautnere0129b32014-05-25 16:41:09 -0700367 taskFile.delete();
368 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700369 }
370 }
371
Stefan Kuhnee88d1e52015-05-18 10:33:45 -0700372 if (!DEBUG) {
Suprabh Shukla23593142015-11-03 17:31:15 -0800373 removeObsoleteFiles(recoveredTaskIds, userTasksDir.listFiles());
374 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700375
Suprabh Shukla23593142015-11-03 17:31:15 -0800376 // Fix up task affiliation from taskIds
Craig Mautnera228ae92014-07-09 05:44:55 -0700377 for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
Louis Changcdec0802019-11-11 11:45:07 +0800378 final Task task = tasks.get(taskNdx);
Craig Mautnera228ae92014-07-09 05:44:55 -0700379 task.setPrevAffiliate(taskIdToTask(task.mPrevAffiliateTaskId, tasks));
380 task.setNextAffiliate(taskIdToTask(task.mNextAffiliateTaskId, tasks));
381 }
382
Louis Changcdec0802019-11-11 11:45:07 +0800383 Collections.sort(tasks, new Comparator<Task>() {
Craig Mautner21d24a22014-04-23 11:45:37 -0700384 @Override
Louis Changcdec0802019-11-11 11:45:07 +0800385 public int compare(Task lhs, Task rhs) {
Craig Mautner43e52ed2014-06-16 17:18:52 -0700386 final long diff = rhs.mLastTimeMoved - lhs.mLastTimeMoved;
Craig Mautner21d24a22014-04-23 11:45:37 -0700387 if (diff < 0) {
388 return -1;
389 } else if (diff > 0) {
390 return +1;
391 } else {
392 return 0;
393 }
394 }
395 });
Suprabh Shukla09a88f52015-12-02 14:36:31 -0800396 return tasks;
Craig Mautner21d24a22014-04-23 11:45:37 -0700397 }
398
Garfield Tana3549832018-09-24 15:22:18 -0700399 @Override
400 public void onPreProcessItem(boolean queueEmpty) {
401 // We can't lock mService while locking the queue, but we don't want to
402 // call removeObsoleteFiles before every item, only the last time
403 // before going to sleep. The risk is that we call removeObsoleteFiles()
404 // successively.
405 if (queueEmpty) {
406 if (DEBUG) Slog.d(TAG, "Looking for obsolete files.");
407 mTmpTaskIds.clear();
408 synchronized (mService.mGlobalLock) {
409 if (DEBUG) Slog.d(TAG, "mRecents=" + mRecentTasks);
410 mRecentTasks.getPersistableTaskIds(mTmpTaskIds);
411 mService.mWindowManager.removeObsoleteTaskFiles(mTmpTaskIds,
412 mRecentTasks.usersWithRecentsLoadedLocked());
413 }
414 removeObsoleteFiles(mTmpTaskIds);
415 }
416 writeTaskIdsFiles();
417 }
418
Craig Mautnere0129b32014-05-25 16:41:09 -0700419 private static void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) {
Suprabh Shukla23593142015-11-03 17:31:15 -0800420 if (DEBUG) Slog.d(TAG, "removeObsoleteFiles: persistentTaskIds=" + persistentTaskIds +
Stefan Kuhnee88d1e52015-05-18 10:33:45 -0700421 " files=" + files);
Craig Mautnera5badf02014-09-11 12:47:03 -0700422 if (files == null) {
Suprabh Shukladc4b80d2016-04-20 15:24:31 -0700423 Slog.e(TAG, "File error accessing recents directory (directory doesn't exist?).");
Craig Mautnera5badf02014-09-11 12:47:03 -0700424 return;
425 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700426 for (int fileNdx = 0; fileNdx < files.length; ++fileNdx) {
427 File file = files[fileNdx];
428 String filename = file.getName();
Craig Mautnerffcfcaa2014-06-05 09:54:38 -0700429 final int taskIdEnd = filename.indexOf('_');
Craig Mautner21d24a22014-04-23 11:45:37 -0700430 if (taskIdEnd > 0) {
431 final int taskId;
432 try {
Narayan Kamatha09b4d22016-04-15 18:32:45 +0100433 taskId = Integer.parseInt(filename.substring(0, taskIdEnd));
Suprabh Shukla23593142015-11-03 17:31:15 -0800434 if (DEBUG) Slog.d(TAG, "removeObsoleteFiles: Found taskId=" + taskId);
Craig Mautner21d24a22014-04-23 11:45:37 -0700435 } catch (Exception e) {
Suprabh Shukla23593142015-11-03 17:31:15 -0800436 Slog.wtf(TAG, "removeObsoleteFiles: Can't parse file=" + file.getName());
Craig Mautner21d24a22014-04-23 11:45:37 -0700437 file.delete();
438 continue;
439 }
440 if (!persistentTaskIds.contains(taskId)) {
Suprabh Shukla23593142015-11-03 17:31:15 -0800441 if (DEBUG) Slog.d(TAG, "removeObsoleteFiles: deleting file=" + file.getName());
Craig Mautner21d24a22014-04-23 11:45:37 -0700442 file.delete();
443 }
444 }
445 }
446 }
447
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800448 private void writeTaskIdsFiles() {
Suprabh Shuklafd0bd4f2016-08-24 14:08:29 -0700449 SparseArray<SparseBooleanArray> changedTaskIdsPerUser = new SparseArray<>();
Wale Ogunwale16e505a2018-05-07 15:00:49 -0700450 synchronized (mService.mGlobalLock) {
Suprabh Shuklafd0bd4f2016-08-24 14:08:29 -0700451 for (int userId : mRecentTasks.usersWithRecentsLoadedLocked()) {
Winson Chung1dbc8112017-09-28 18:05:31 -0700452 SparseBooleanArray taskIdsToSave = mRecentTasks.getTaskIdsForUser(userId);
Suprabh Shuklafd0bd4f2016-08-24 14:08:29 -0700453 SparseBooleanArray persistedIdsInFile = mTaskIdsInFile.get(userId);
454 if (persistedIdsInFile != null && persistedIdsInFile.equals(taskIdsToSave)) {
455 continue;
456 } else {
457 SparseBooleanArray taskIdsToSaveCopy = taskIdsToSave.clone();
458 mTaskIdsInFile.put(userId, taskIdsToSaveCopy);
459 changedTaskIdsPerUser.put(userId, taskIdsToSaveCopy);
460 }
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800461 }
Suprabh Shuklafd0bd4f2016-08-24 14:08:29 -0700462 }
463 for (int i = 0; i < changedTaskIdsPerUser.size(); i++) {
464 writePersistedTaskIdsForUser(changedTaskIdsPerUser.valueAt(i),
465 changedTaskIdsPerUser.keyAt(i));
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800466 }
467 }
468
Craig Mautner21d24a22014-04-23 11:45:37 -0700469 private void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds) {
Suprabh Shukla09a88f52015-12-02 14:36:31 -0800470 int[] candidateUserIds;
Wale Ogunwale16e505a2018-05-07 15:00:49 -0700471 synchronized (mService.mGlobalLock) {
Suprabh Shukla09a88f52015-12-02 14:36:31 -0800472 // Remove only from directories of the users who have recents in memory synchronized
473 // with persistent storage.
474 candidateUserIds = mRecentTasks.usersWithRecentsLoadedLocked();
475 }
476 for (int userId : candidateUserIds) {
Suprabh Shukla23593142015-11-03 17:31:15 -0800477 removeObsoleteFiles(persistentTaskIds, getUserImagesDir(userId).listFiles());
478 removeObsoleteFiles(persistentTaskIds, getUserTasksDir(userId).listFiles());
479 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700480 }
481
482 static Bitmap restoreImage(String filename) {
Stefan Kuhnee88d1e52015-05-18 10:33:45 -0700483 if (DEBUG) Slog.d(TAG, "restoreImage: restoring " + filename);
Suprabh Shukla23593142015-11-03 17:31:15 -0800484 return BitmapFactory.decodeFile(filename);
485 }
486
Suprabh Shuklaf50b4582016-02-23 17:44:22 -0800487 private File getUserPersistedTaskIdsFile(int userId) {
488 File userTaskIdsDir = new File(mTaskIdsDir, String.valueOf(userId));
489 if (!userTaskIdsDir.exists() && !userTaskIdsDir.mkdirs()) {
490 Slog.e(TAG, "Error while creating user directory: " + userTaskIdsDir);
491 }
492 return new File(userTaskIdsDir, PERSISTED_TASK_IDS_FILENAME);
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800493 }
494
Garfield Tan0d407f42019-03-07 11:47:01 -0800495 private static File getUserTasksDir(int userId) {
496 return new File(Environment.getDataSystemCeDirectory(userId), TASKS_DIRNAME);
Suprabh Shukla23593142015-11-03 17:31:15 -0800497 }
498
499 static File getUserImagesDir(int userId) {
Suprabh Shukladc4b80d2016-04-20 15:24:31 -0700500 return new File(Environment.getDataSystemCeDirectory(userId), IMAGES_DIRNAME);
501 }
Suprabh Shukla23593142015-11-03 17:31:15 -0800502
Suprabh Shukladc4b80d2016-04-20 15:24:31 -0700503 private static boolean createParentDirectory(String filePath) {
504 File parentDir = new File(filePath).getParentFile();
505 return parentDir.exists() || parentDir.mkdirs();
Craig Mautner21d24a22014-04-23 11:45:37 -0700506 }
507
Garfield Tana3549832018-09-24 15:22:18 -0700508 private static class TaskWriteQueueItem implements PersisterQueue.WriteQueueItem {
509 private final ActivityTaskManagerService mService;
Louis Changcdec0802019-11-11 11:45:07 +0800510 private final Task mTask;
Craig Mautner21d24a22014-04-23 11:45:37 -0700511
Louis Changcdec0802019-11-11 11:45:07 +0800512 TaskWriteQueueItem(Task task, ActivityTaskManagerService service) {
Garfield Tana3549832018-09-24 15:22:18 -0700513 mTask = task;
514 mService = service;
515 }
516
Louis Changcdec0802019-11-11 11:45:07 +0800517 private StringWriter saveToXml(Task task) throws IOException, XmlPullParserException {
Garfield Tana3549832018-09-24 15:22:18 -0700518 if (DEBUG) Slog.d(TAG, "saveToXml: task=" + task);
519 final XmlSerializer xmlSerializer = new FastXmlSerializer();
520 StringWriter stringWriter = new StringWriter();
521 xmlSerializer.setOutput(stringWriter);
522
523 if (DEBUG) {
524 xmlSerializer.setFeature(
525 "http://xmlpull.org/v1/doc/features.html#indent-output", true);
526 }
527
528 // save task
529 xmlSerializer.startDocument(null, true);
530
531 xmlSerializer.startTag(null, TAG_TASK);
532 task.saveToXml(xmlSerializer);
533 xmlSerializer.endTag(null, TAG_TASK);
534
535 xmlSerializer.endDocument();
536 xmlSerializer.flush();
537
538 return stringWriter;
Craig Mautner21d24a22014-04-23 11:45:37 -0700539 }
540
541 @Override
Garfield Tana3549832018-09-24 15:22:18 -0700542 public void process() {
543 // Write out one task.
544 StringWriter stringWriter = null;
Louis Changcdec0802019-11-11 11:45:07 +0800545 Task task = mTask;
Garfield Tana3549832018-09-24 15:22:18 -0700546 if (DEBUG) Slog.d(TAG, "Writing task=" + task);
547 synchronized (mService.mGlobalLock) {
548 if (task.inRecents) {
549 // Still there.
550 try {
551 if (DEBUG) Slog.d(TAG, "Saving task=" + task);
552 stringWriter = saveToXml(task);
553 } catch (IOException e) {
554 } catch (XmlPullParserException e) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700555 }
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700556 }
Garfield Tana3549832018-09-24 15:22:18 -0700557 }
558 if (stringWriter != null) {
559 // Write out xml file while not holding mService lock.
560 FileOutputStream file = null;
561 AtomicFile atomicFile = null;
562 try {
Wale Ogunwale4e79a1c2019-10-05 20:52:40 -0700563 File userTasksDir = getUserTasksDir(task.mUserId);
Garfield Tan0d407f42019-03-07 11:47:01 -0800564 if (!userTasksDir.isDirectory() && !userTasksDir.mkdirs()) {
Wale Ogunwale4e79a1c2019-10-05 20:52:40 -0700565 Slog.e(TAG, "Failure creating tasks directory for user " + task.mUserId
Garfield Tan0d407f42019-03-07 11:47:01 -0800566 + ": " + userTasksDir + " Dropping persistence for task " + task);
567 return;
568 }
569 atomicFile = new AtomicFile(new File(userTasksDir,
Wale Ogunwale4e79a1c2019-10-05 20:52:40 -0700570 String.valueOf(task.mTaskId) + TASK_FILENAME_SUFFIX));
Garfield Tana3549832018-09-24 15:22:18 -0700571 file = atomicFile.startWrite();
572 file.write(stringWriter.toString().getBytes());
573 file.write('\n');
574 atomicFile.finishWrite(file);
575 } catch (IOException e) {
576 if (file != null) {
577 atomicFile.failWrite(file);
578 }
579 Slog.e(TAG,
580 "Unable to open " + atomicFile + " for persisting. " + e);
581 }
Makoto Onuki62a93aa2017-08-08 10:24:59 -0700582 }
583 }
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700584
Garfield Tana3549832018-09-24 15:22:18 -0700585 @Override
586 public String toString() {
587 return "TaskWriteQueueItem{task=" + mTask + "}";
588 }
589 }
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700590
Garfield Tan891146c2018-10-09 12:14:00 -0700591 private static class ImageWriteQueueItem implements
592 PersisterQueue.WriteQueueItem<ImageWriteQueueItem> {
Garfield Tana3549832018-09-24 15:22:18 -0700593 final String mFilePath;
594 Bitmap mImage;
Craig Mautner21d24a22014-04-23 11:45:37 -0700595
Garfield Tana3549832018-09-24 15:22:18 -0700596 ImageWriteQueueItem(String filePath, Bitmap image) {
597 mFilePath = filePath;
598 mImage = image;
599 }
Makoto Onuki62a93aa2017-08-08 10:24:59 -0700600
Garfield Tana3549832018-09-24 15:22:18 -0700601 @Override
602 public void process() {
603 final String filePath = mFilePath;
604 if (!createParentDirectory(filePath)) {
605 Slog.e(TAG, "Error while creating images directory for file: " + filePath);
606 return;
Makoto Onuki62a93aa2017-08-08 10:24:59 -0700607 }
Garfield Tana3549832018-09-24 15:22:18 -0700608 final Bitmap bitmap = mImage;
609 if (DEBUG) Slog.d(TAG, "writing bitmap: filename=" + filePath);
610 FileOutputStream imageFile = null;
611 try {
612 imageFile = new FileOutputStream(new File(filePath));
613 bitmap.compress(Bitmap.CompressFormat.PNG, 100, imageFile);
614 } catch (Exception e) {
615 Slog.e(TAG, "saveImage: unable to save " + filePath, e);
616 } finally {
617 IoUtils.closeQuietly(imageFile);
Craig Mautner21d24a22014-04-23 11:45:37 -0700618 }
619 }
Garfield Tana3549832018-09-24 15:22:18 -0700620
621 @Override
Garfield Tan891146c2018-10-09 12:14:00 -0700622 public boolean matches(ImageWriteQueueItem item) {
623 return mFilePath.equals(item.mFilePath);
624 }
625
626 @Override
627 public void updateFrom(ImageWriteQueueItem item) {
628 mImage = item.mImage;
629 }
630
631 @Override
Garfield Tana3549832018-09-24 15:22:18 -0700632 public String toString() {
633 return "ImageWriteQueueItem{path=" + mFilePath
634 + ", image=(" + mImage.getWidth() + "x" + mImage.getHeight() + ")}";
635 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700636 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700637}