blob: 9705d42464d57505038973126cd1212dfd064793 [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 Ogunwale59507092018-10-29 09:00:30 -070019import static com.android.server.wm.ActivityStackSupervisor.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,
Wale Ogunwale16e505a2018-05-07 15:00:49 -070086 ActivityTaskManagerService service, RecentTasks recentTasks) {
Suprabh Shukla23593142015-11-03 17:31:15 -080087
88 final File legacyImagesDir = new File(systemDir, IMAGES_DIRNAME);
89 if (legacyImagesDir.exists()) {
90 if (!FileUtils.deleteContents(legacyImagesDir) || !legacyImagesDir.delete()) {
91 Slog.i(TAG, "Failure deleting legacy images directory: " + legacyImagesDir);
Craig Mautner21d24a22014-04-23 11:45:37 -070092 }
93 }
94
Suprabh Shukla23593142015-11-03 17:31:15 -080095 final File legacyTasksDir = new File(systemDir, TASKS_DIRNAME);
96 if (legacyTasksDir.exists()) {
97 if (!FileUtils.deleteContents(legacyTasksDir) || !legacyTasksDir.delete()) {
98 Slog.i(TAG, "Failure deleting legacy tasks directory: " + legacyTasksDir);
Craig Mautner21d24a22014-04-23 11:45:37 -070099 }
100 }
101
Suprabh Shuklaf50b4582016-02-23 17:44:22 -0800102 mTaskIdsDir = new File(Environment.getDataDirectory(), "system_de");
Craig Mautner21d24a22014-04-23 11:45:37 -0700103 mStackSupervisor = stackSupervisor;
Suprabh Shukla09a88f52015-12-02 14:36:31 -0800104 mService = service;
Wale Ogunwalec82f2f52014-12-09 09:32:50 -0800105 mRecentTasks = recentTasks;
Garfield Tana3549832018-09-24 15:22:18 -0700106 mPersisterQueue = new PersisterQueue();
107 mPersisterQueue.addListener(this);
Craig Mautner21d24a22014-04-23 11:45:37 -0700108 }
109
Suprabh Shuklaf50b4582016-02-23 17:44:22 -0800110 @VisibleForTesting
111 TaskPersister(File workingDir) {
112 mTaskIdsDir = workingDir;
113 mStackSupervisor = null;
114 mService = null;
115 mRecentTasks = null;
Garfield Tana3549832018-09-24 15:22:18 -0700116 mPersisterQueue = new PersisterQueue();
117 mPersisterQueue.addListener(this);
Suprabh Shuklaf50b4582016-02-23 17:44:22 -0800118 }
119
Garfield Tana3549832018-09-24 15:22:18 -0700120 void onSystemReady() {
121 mPersisterQueue.startPersisting();
Craig Mautner21d24a22014-04-23 11:45:37 -0700122 }
123
Craig Mautner63f10902014-09-16 23:57:21 -0700124 private void removeThumbnails(TaskRecord task) {
Garfield Tana3549832018-09-24 15:22:18 -0700125 mPersisterQueue.removeItems(
126 item -> {
127 File file = new File(item.mFilePath);
128 return file.getName().startsWith(Integer.toString(task.taskId));
129 },
130 ImageWriteQueueItem.class);
Craig Mautner63f10902014-09-16 23:57:21 -0700131 }
132
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800133 @NonNull
134 SparseBooleanArray loadPersistedTaskIdsForUser(int userId) {
135 if (mTaskIdsInFile.get(userId) != null) {
136 return mTaskIdsInFile.get(userId).clone();
137 }
138 final SparseBooleanArray persistedTaskIds = new SparseBooleanArray();
Suprabh Shuklafd0bd4f2016-08-24 14:08:29 -0700139 synchronized (mIoLock) {
140 BufferedReader reader = null;
141 String line;
142 try {
143 reader = new BufferedReader(new FileReader(getUserPersistedTaskIdsFile(userId)));
144 while ((line = reader.readLine()) != null) {
145 for (String taskIdString : line.split("\\s+")) {
146 int id = Integer.parseInt(taskIdString);
147 persistedTaskIds.put(id, true);
148 }
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800149 }
Suprabh Shuklafd0bd4f2016-08-24 14:08:29 -0700150 } catch (FileNotFoundException e) {
151 // File doesn't exist. Ignore.
152 } catch (Exception e) {
153 Slog.e(TAG, "Error while reading taskIds file for user " + userId, e);
154 } finally {
155 IoUtils.closeQuietly(reader);
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800156 }
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800157 }
158 mTaskIdsInFile.put(userId, persistedTaskIds);
159 return persistedTaskIds.clone();
160 }
161
Suprabh Shuklafd0bd4f2016-08-24 14:08:29 -0700162
Suprabh Shuklaf50b4582016-02-23 17:44:22 -0800163 @VisibleForTesting
Suprabh Shuklafd0bd4f2016-08-24 14:08:29 -0700164 void writePersistedTaskIdsForUser(@NonNull SparseBooleanArray taskIds, int userId) {
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800165 if (userId < 0) {
166 return;
167 }
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800168 final File persistedTaskIdsFile = getUserPersistedTaskIdsFile(userId);
Suprabh Shuklafd0bd4f2016-08-24 14:08:29 -0700169 synchronized (mIoLock) {
170 BufferedWriter writer = null;
171 try {
172 writer = new BufferedWriter(new FileWriter(persistedTaskIdsFile));
173 for (int i = 0; i < taskIds.size(); i++) {
174 if (taskIds.valueAt(i)) {
175 writer.write(String.valueOf(taskIds.keyAt(i)));
176 writer.newLine();
177 }
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800178 }
Suprabh Shuklafd0bd4f2016-08-24 14:08:29 -0700179 } catch (Exception e) {
180 Slog.e(TAG, "Error while writing taskIds file for user " + userId, e);
181 } finally {
182 IoUtils.closeQuietly(writer);
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800183 }
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800184 }
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800185 }
186
187 void unloadUserDataFromMemory(int userId) {
188 mTaskIdsInFile.delete(userId);
189 }
190
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700191 void wakeup(TaskRecord task, boolean flush) {
Garfield Tana3549832018-09-24 15:22:18 -0700192 synchronized (mPersisterQueue) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700193 if (task != null) {
Garfield Tana3549832018-09-24 15:22:18 -0700194 final TaskWriteQueueItem item = mPersisterQueue.findLastItem(
195 queueItem -> task == queueItem.mTask, TaskWriteQueueItem.class);
196 if (item != null && !task.inRecents) {
197 removeThumbnails(task);
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700198 }
Garfield Tana3549832018-09-24 15:22:18 -0700199
200 if (item == null && task.isPersistable) {
201 mPersisterQueue.addItem(new TaskWriteQueueItem(task, mService), flush);
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700202 }
203 } else {
Suprabh Shukla09a88f52015-12-02 14:36:31 -0800204 // Dummy. Ensures removeObsoleteFiles is called when LazyTaskThreadWriter is
205 // notified.
Garfield Tana3549832018-09-24 15:22:18 -0700206 mPersisterQueue.addItem(PersisterQueue.EMPTY_ITEM, flush);
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700207 }
Garfield Tana3549832018-09-24 15:22:18 -0700208 if (DEBUG) {
209 Slog.d(TAG, "wakeup: task=" + task + " flush=" + flush + " Callers="
210 + Debug.getCallers(4));
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700211 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700212 }
Craig Mautner63f10902014-09-16 23:57:21 -0700213
Garfield Tana3549832018-09-24 15:22:18 -0700214 mPersisterQueue.yieldIfQueueTooDeep();
Craig Mautner21d24a22014-04-23 11:45:37 -0700215 }
216
Dianne Hackbornce0fd762014-09-19 12:58:15 -0700217 void flush() {
Garfield Tana3549832018-09-24 15:22:18 -0700218 mPersisterQueue.flush();
Dianne Hackbornce0fd762014-09-19 12:58:15 -0700219 }
220
Suprabh Shukla23593142015-11-03 17:31:15 -0800221 void saveImage(Bitmap image, String filePath) {
Garfield Tana3549832018-09-24 15:22:18 -0700222 synchronized (mPersisterQueue) {
223 final ImageWriteQueueItem item = mPersisterQueue.findLastItem(
224 queueItem -> queueItem.mFilePath.equals(filePath), ImageWriteQueueItem.class);
225 if (item != null) {
226 // replace the Bitmap with the new one.
227 item.mImage = image;
228 } else {
229 mPersisterQueue.addItem(new ImageWriteQueueItem(filePath, image),
230 /* flush */ false);
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700231 }
Suprabh Shukla23593142015-11-03 17:31:15 -0800232 if (DEBUG) Slog.d(TAG, "saveImage: filePath=" + filePath + " now=" +
Garfield Tana3549832018-09-24 15:22:18 -0700233 SystemClock.uptimeMillis() + " Callers=" + Debug.getCallers(4));
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700234 }
Craig Mautner63f10902014-09-16 23:57:21 -0700235
Garfield Tana3549832018-09-24 15:22:18 -0700236 mPersisterQueue.yieldIfQueueTooDeep();
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700237 }
238
Suprabh Shukla23593142015-11-03 17:31:15 -0800239 Bitmap getTaskDescriptionIcon(String filePath) {
Craig Mautner648f69b2014-09-18 14:16:26 -0700240 // See if it is in the write queue
Suprabh Shukla23593142015-11-03 17:31:15 -0800241 final Bitmap icon = getImageFromWriteQueue(filePath);
Craig Mautner648f69b2014-09-18 14:16:26 -0700242 if (icon != null) {
243 return icon;
244 }
Suprabh Shukla23593142015-11-03 17:31:15 -0800245 return restoreImage(filePath);
Craig Mautner648f69b2014-09-18 14:16:26 -0700246 }
247
Garfield Tana3549832018-09-24 15:22:18 -0700248 private Bitmap getImageFromWriteQueue(String filePath) {
249 final ImageWriteQueueItem item = mPersisterQueue.findLastItem(
250 queueItem -> queueItem.mFilePath.equals(filePath), ImageWriteQueueItem.class);
251 return item != null ? item.mImage : null;
Craig Mautner21d24a22014-04-23 11:45:37 -0700252 }
253
Craig Mautner77b04262014-06-27 15:22:12 -0700254 private String fileToString(File file) {
255 final String newline = System.lineSeparator();
256 try {
257 BufferedReader reader = new BufferedReader(new FileReader(file));
258 StringBuffer sb = new StringBuffer((int) file.length() * 2);
259 String line;
260 while ((line = reader.readLine()) != null) {
261 sb.append(line + newline);
262 }
263 reader.close();
264 return sb.toString();
265 } catch (IOException ioe) {
266 Slog.e(TAG, "Couldn't read file " + file.getName());
267 return null;
268 }
269 }
270
Craig Mautnera228ae92014-07-09 05:44:55 -0700271 private TaskRecord taskIdToTask(int taskId, ArrayList<TaskRecord> tasks) {
272 if (taskId < 0) {
273 return null;
274 }
275 for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
276 final TaskRecord task = tasks.get(taskNdx);
277 if (task.taskId == taskId) {
278 return task;
279 }
280 }
281 Slog.e(TAG, "Restore affiliation error looking for taskId=" + taskId);
282 return null;
283 }
284
Daichi Hirono4cb941e2017-03-31 14:30:41 +0900285 List<TaskRecord> restoreTasksForUserLocked(final int userId, SparseBooleanArray preaddedTasks) {
Suprabh Shukla09a88f52015-12-02 14:36:31 -0800286 final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
Craig Mautner21d24a22014-04-23 11:45:37 -0700287 ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>();
288
Suprabh Shukla23593142015-11-03 17:31:15 -0800289 File userTasksDir = getUserTasksDir(userId);
Winson Chung36f3f032016-09-08 23:29:43 +0000290
Suprabh Shukla23593142015-11-03 17:31:15 -0800291 File[] recentFiles = userTasksDir.listFiles();
Craig Mautner21d24a22014-04-23 11:45:37 -0700292 if (recentFiles == null) {
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800293 Slog.e(TAG, "restoreTasksForUserLocked: Unable to list files from " + userTasksDir);
Craig Mautner21d24a22014-04-23 11:45:37 -0700294 return tasks;
295 }
296
297 for (int taskNdx = 0; taskNdx < recentFiles.length; ++taskNdx) {
298 File taskFile = recentFiles[taskNdx];
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800299 if (DEBUG) {
300 Slog.d(TAG, "restoreTasksForUserLocked: userId=" + userId
301 + ", taskFile=" + taskFile.getName());
302 }
Daichi Hirono4cb941e2017-03-31 14:30:41 +0900303
304 if (!taskFile.getName().endsWith(TASK_FILENAME_SUFFIX)) {
305 continue;
306 }
307 try {
308 final int taskId = Integer.parseInt(taskFile.getName().substring(
309 0 /* beginIndex */,
310 taskFile.getName().length() - TASK_FILENAME_SUFFIX.length()));
311 if (preaddedTasks.get(taskId, false)) {
312 Slog.w(TAG, "Task #" + taskId +
313 " has already been created so we don't restore again");
314 continue;
315 }
316 } catch (NumberFormatException e) {
317 Slog.w(TAG, "Unexpected task file name", e);
318 continue;
319 }
320
Craig Mautner21d24a22014-04-23 11:45:37 -0700321 BufferedReader reader = null;
Craig Mautnere0129b32014-05-25 16:41:09 -0700322 boolean deleteFile = false;
Craig Mautner21d24a22014-04-23 11:45:37 -0700323 try {
324 reader = new BufferedReader(new FileReader(taskFile));
325 final XmlPullParser in = Xml.newPullParser();
326 in.setInput(reader);
327
328 int event;
329 while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
330 event != XmlPullParser.END_TAG) {
331 final String name = in.getName();
332 if (event == XmlPullParser.START_TAG) {
Suprabh Shukla09a88f52015-12-02 14:36:31 -0800333 if (DEBUG) Slog.d(TAG, "restoreTasksForUserLocked: START_TAG name=" + name);
Craig Mautner21d24a22014-04-23 11:45:37 -0700334 if (TAG_TASK.equals(name)) {
Winson Chung36f3f032016-09-08 23:29:43 +0000335 final TaskRecord task = TaskRecord.restoreFromXml(in, mStackSupervisor);
Suprabh Shukla09a88f52015-12-02 14:36:31 -0800336 if (DEBUG) Slog.d(TAG, "restoreTasksForUserLocked: restored task="
Suprabh Shukla23593142015-11-03 17:31:15 -0800337 + task);
Craig Mautner21d24a22014-04-23 11:45:37 -0700338 if (task != null) {
Winson Chung36f3f032016-09-08 23:29:43 +0000339 // XXX Don't add to write queue... there is no reason to write
340 // out the stuff we just read, if we don't write it we will
341 // read the same thing again.
342 // mWriteQueue.add(new TaskWriteQueueItem(task));
343
Craig Mautner21d24a22014-04-23 11:45:37 -0700344 final int taskId = task.taskId;
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800345 if (mStackSupervisor.anyTaskForIdLocked(taskId,
Wale Ogunwale0568aed2017-09-08 13:29:37 -0700346 MATCH_TASK_IN_STACKS_OR_RECENT_TASKS) != null) {
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800347 // Should not happen.
348 Slog.wtf(TAG, "Existing task with taskId " + taskId + "found");
349 } else if (userId != task.userId) {
350 // Should not happen.
351 Slog.wtf(TAG, "Task with userId " + task.userId + " found in "
352 + userTasksDir.getAbsolutePath());
353 } else {
354 // Looks fine.
355 mStackSupervisor.setNextTaskIdForUserLocked(taskId, userId);
Amith Yamasani515d4062015-09-28 11:30:06 -0700356 task.isPersistable = true;
357 tasks.add(task);
358 recoveredTaskIds.add(taskId);
359 }
Craig Mautner77b04262014-06-27 15:22:12 -0700360 } else {
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800361 Slog.e(TAG, "restoreTasksForUserLocked: Unable to restore taskFile="
Suprabh Shukla23593142015-11-03 17:31:15 -0800362 + taskFile + ": " + fileToString(taskFile));
Craig Mautner21d24a22014-04-23 11:45:37 -0700363 }
364 } else {
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800365 Slog.wtf(TAG, "restoreTasksForUserLocked: Unknown xml event=" + event
Suprabh Shukla23593142015-11-03 17:31:15 -0800366 + " name=" + name);
Craig Mautner21d24a22014-04-23 11:45:37 -0700367 }
368 }
369 XmlUtils.skipCurrentTag(in);
370 }
Craig Mautnere0129b32014-05-25 16:41:09 -0700371 } catch (Exception e) {
Craig Mautnera228ae92014-07-09 05:44:55 -0700372 Slog.wtf(TAG, "Unable to parse " + taskFile + ". Error ", e);
Craig Mautner77b04262014-06-27 15:22:12 -0700373 Slog.e(TAG, "Failing file: " + fileToString(taskFile));
Craig Mautnere0129b32014-05-25 16:41:09 -0700374 deleteFile = true;
Craig Mautner21d24a22014-04-23 11:45:37 -0700375 } finally {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800376 IoUtils.closeQuietly(reader);
Stefan Kuhnee88d1e52015-05-18 10:33:45 -0700377 if (deleteFile) {
378 if (DEBUG) Slog.d(TAG, "Deleting file=" + taskFile.getName());
Craig Mautnere0129b32014-05-25 16:41:09 -0700379 taskFile.delete();
380 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700381 }
382 }
383
Stefan Kuhnee88d1e52015-05-18 10:33:45 -0700384 if (!DEBUG) {
Suprabh Shukla23593142015-11-03 17:31:15 -0800385 removeObsoleteFiles(recoveredTaskIds, userTasksDir.listFiles());
386 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700387
Suprabh Shukla23593142015-11-03 17:31:15 -0800388 // Fix up task affiliation from taskIds
Craig Mautnera228ae92014-07-09 05:44:55 -0700389 for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
390 final TaskRecord task = tasks.get(taskNdx);
391 task.setPrevAffiliate(taskIdToTask(task.mPrevAffiliateTaskId, tasks));
392 task.setNextAffiliate(taskIdToTask(task.mNextAffiliateTaskId, tasks));
393 }
394
Suprabh Shukla09a88f52015-12-02 14:36:31 -0800395 Collections.sort(tasks, new Comparator<TaskRecord>() {
Craig Mautner21d24a22014-04-23 11:45:37 -0700396 @Override
397 public int compare(TaskRecord lhs, TaskRecord rhs) {
Craig Mautner43e52ed2014-06-16 17:18:52 -0700398 final long diff = rhs.mLastTimeMoved - lhs.mLastTimeMoved;
Craig Mautner21d24a22014-04-23 11:45:37 -0700399 if (diff < 0) {
400 return -1;
401 } else if (diff > 0) {
402 return +1;
403 } else {
404 return 0;
405 }
406 }
407 });
Suprabh Shukla09a88f52015-12-02 14:36:31 -0800408 return tasks;
Craig Mautner21d24a22014-04-23 11:45:37 -0700409 }
410
Garfield Tana3549832018-09-24 15:22:18 -0700411 @Override
412 public void onPreProcessItem(boolean queueEmpty) {
413 // We can't lock mService while locking the queue, but we don't want to
414 // call removeObsoleteFiles before every item, only the last time
415 // before going to sleep. The risk is that we call removeObsoleteFiles()
416 // successively.
417 if (queueEmpty) {
418 if (DEBUG) Slog.d(TAG, "Looking for obsolete files.");
419 mTmpTaskIds.clear();
420 synchronized (mService.mGlobalLock) {
421 if (DEBUG) Slog.d(TAG, "mRecents=" + mRecentTasks);
422 mRecentTasks.getPersistableTaskIds(mTmpTaskIds);
423 mService.mWindowManager.removeObsoleteTaskFiles(mTmpTaskIds,
424 mRecentTasks.usersWithRecentsLoadedLocked());
425 }
426 removeObsoleteFiles(mTmpTaskIds);
427 }
428 writeTaskIdsFiles();
429 }
430
Craig Mautnere0129b32014-05-25 16:41:09 -0700431 private static void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) {
Suprabh Shukla23593142015-11-03 17:31:15 -0800432 if (DEBUG) Slog.d(TAG, "removeObsoleteFiles: persistentTaskIds=" + persistentTaskIds +
Stefan Kuhnee88d1e52015-05-18 10:33:45 -0700433 " files=" + files);
Craig Mautnera5badf02014-09-11 12:47:03 -0700434 if (files == null) {
Suprabh Shukladc4b80d2016-04-20 15:24:31 -0700435 Slog.e(TAG, "File error accessing recents directory (directory doesn't exist?).");
Craig Mautnera5badf02014-09-11 12:47:03 -0700436 return;
437 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700438 for (int fileNdx = 0; fileNdx < files.length; ++fileNdx) {
439 File file = files[fileNdx];
440 String filename = file.getName();
Craig Mautnerffcfcaa2014-06-05 09:54:38 -0700441 final int taskIdEnd = filename.indexOf('_');
Craig Mautner21d24a22014-04-23 11:45:37 -0700442 if (taskIdEnd > 0) {
443 final int taskId;
444 try {
Narayan Kamatha09b4d22016-04-15 18:32:45 +0100445 taskId = Integer.parseInt(filename.substring(0, taskIdEnd));
Suprabh Shukla23593142015-11-03 17:31:15 -0800446 if (DEBUG) Slog.d(TAG, "removeObsoleteFiles: Found taskId=" + taskId);
Craig Mautner21d24a22014-04-23 11:45:37 -0700447 } catch (Exception e) {
Suprabh Shukla23593142015-11-03 17:31:15 -0800448 Slog.wtf(TAG, "removeObsoleteFiles: Can't parse file=" + file.getName());
Craig Mautner21d24a22014-04-23 11:45:37 -0700449 file.delete();
450 continue;
451 }
452 if (!persistentTaskIds.contains(taskId)) {
Suprabh Shukla23593142015-11-03 17:31:15 -0800453 if (DEBUG) Slog.d(TAG, "removeObsoleteFiles: deleting file=" + file.getName());
Craig Mautner21d24a22014-04-23 11:45:37 -0700454 file.delete();
455 }
456 }
457 }
458 }
459
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800460 private void writeTaskIdsFiles() {
Suprabh Shuklafd0bd4f2016-08-24 14:08:29 -0700461 SparseArray<SparseBooleanArray> changedTaskIdsPerUser = new SparseArray<>();
Wale Ogunwale16e505a2018-05-07 15:00:49 -0700462 synchronized (mService.mGlobalLock) {
Suprabh Shuklafd0bd4f2016-08-24 14:08:29 -0700463 for (int userId : mRecentTasks.usersWithRecentsLoadedLocked()) {
Winson Chung1dbc8112017-09-28 18:05:31 -0700464 SparseBooleanArray taskIdsToSave = mRecentTasks.getTaskIdsForUser(userId);
Suprabh Shuklafd0bd4f2016-08-24 14:08:29 -0700465 SparseBooleanArray persistedIdsInFile = mTaskIdsInFile.get(userId);
466 if (persistedIdsInFile != null && persistedIdsInFile.equals(taskIdsToSave)) {
467 continue;
468 } else {
469 SparseBooleanArray taskIdsToSaveCopy = taskIdsToSave.clone();
470 mTaskIdsInFile.put(userId, taskIdsToSaveCopy);
471 changedTaskIdsPerUser.put(userId, taskIdsToSaveCopy);
472 }
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800473 }
Suprabh Shuklafd0bd4f2016-08-24 14:08:29 -0700474 }
475 for (int i = 0; i < changedTaskIdsPerUser.size(); i++) {
476 writePersistedTaskIdsForUser(changedTaskIdsPerUser.valueAt(i),
477 changedTaskIdsPerUser.keyAt(i));
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800478 }
479 }
480
Craig Mautner21d24a22014-04-23 11:45:37 -0700481 private void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds) {
Suprabh Shukla09a88f52015-12-02 14:36:31 -0800482 int[] candidateUserIds;
Wale Ogunwale16e505a2018-05-07 15:00:49 -0700483 synchronized (mService.mGlobalLock) {
Suprabh Shukla09a88f52015-12-02 14:36:31 -0800484 // Remove only from directories of the users who have recents in memory synchronized
485 // with persistent storage.
486 candidateUserIds = mRecentTasks.usersWithRecentsLoadedLocked();
487 }
488 for (int userId : candidateUserIds) {
Suprabh Shukla23593142015-11-03 17:31:15 -0800489 removeObsoleteFiles(persistentTaskIds, getUserImagesDir(userId).listFiles());
490 removeObsoleteFiles(persistentTaskIds, getUserTasksDir(userId).listFiles());
491 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700492 }
493
494 static Bitmap restoreImage(String filename) {
Stefan Kuhnee88d1e52015-05-18 10:33:45 -0700495 if (DEBUG) Slog.d(TAG, "restoreImage: restoring " + filename);
Suprabh Shukla23593142015-11-03 17:31:15 -0800496 return BitmapFactory.decodeFile(filename);
497 }
498
Suprabh Shuklaf50b4582016-02-23 17:44:22 -0800499 private File getUserPersistedTaskIdsFile(int userId) {
500 File userTaskIdsDir = new File(mTaskIdsDir, String.valueOf(userId));
501 if (!userTaskIdsDir.exists() && !userTaskIdsDir.mkdirs()) {
502 Slog.e(TAG, "Error while creating user directory: " + userTaskIdsDir);
503 }
504 return new File(userTaskIdsDir, PERSISTED_TASK_IDS_FILENAME);
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800505 }
506
Suprabh Shukla23593142015-11-03 17:31:15 -0800507 static File getUserTasksDir(int userId) {
Suprabh Shukla4bccb462016-02-10 18:45:12 -0800508 File userTasksDir = new File(Environment.getDataSystemCeDirectory(userId), TASKS_DIRNAME);
Suprabh Shukla23593142015-11-03 17:31:15 -0800509
510 if (!userTasksDir.exists()) {
511 if (!userTasksDir.mkdir()) {
512 Slog.e(TAG, "Failure creating tasks directory for user " + userId + ": "
513 + userTasksDir);
514 }
515 }
516 return userTasksDir;
517 }
518
519 static File getUserImagesDir(int userId) {
Suprabh Shukladc4b80d2016-04-20 15:24:31 -0700520 return new File(Environment.getDataSystemCeDirectory(userId), IMAGES_DIRNAME);
521 }
Suprabh Shukla23593142015-11-03 17:31:15 -0800522
Suprabh Shukladc4b80d2016-04-20 15:24:31 -0700523 private static boolean createParentDirectory(String filePath) {
524 File parentDir = new File(filePath).getParentFile();
525 return parentDir.exists() || parentDir.mkdirs();
Craig Mautner21d24a22014-04-23 11:45:37 -0700526 }
527
Garfield Tana3549832018-09-24 15:22:18 -0700528 private static class TaskWriteQueueItem implements PersisterQueue.WriteQueueItem {
529 private final ActivityTaskManagerService mService;
530 private final TaskRecord mTask;
Craig Mautner21d24a22014-04-23 11:45:37 -0700531
Garfield Tana3549832018-09-24 15:22:18 -0700532 TaskWriteQueueItem(TaskRecord task, ActivityTaskManagerService service) {
533 mTask = task;
534 mService = service;
535 }
536
537 private StringWriter saveToXml(TaskRecord task) throws IOException, XmlPullParserException {
538 if (DEBUG) Slog.d(TAG, "saveToXml: task=" + task);
539 final XmlSerializer xmlSerializer = new FastXmlSerializer();
540 StringWriter stringWriter = new StringWriter();
541 xmlSerializer.setOutput(stringWriter);
542
543 if (DEBUG) {
544 xmlSerializer.setFeature(
545 "http://xmlpull.org/v1/doc/features.html#indent-output", true);
546 }
547
548 // save task
549 xmlSerializer.startDocument(null, true);
550
551 xmlSerializer.startTag(null, TAG_TASK);
552 task.saveToXml(xmlSerializer);
553 xmlSerializer.endTag(null, TAG_TASK);
554
555 xmlSerializer.endDocument();
556 xmlSerializer.flush();
557
558 return stringWriter;
Craig Mautner21d24a22014-04-23 11:45:37 -0700559 }
560
561 @Override
Garfield Tana3549832018-09-24 15:22:18 -0700562 public void process() {
563 // Write out one task.
564 StringWriter stringWriter = null;
565 TaskRecord task = mTask;
566 if (DEBUG) Slog.d(TAG, "Writing task=" + task);
567 synchronized (mService.mGlobalLock) {
568 if (task.inRecents) {
569 // Still there.
570 try {
571 if (DEBUG) Slog.d(TAG, "Saving task=" + task);
572 stringWriter = saveToXml(task);
573 } catch (IOException e) {
574 } catch (XmlPullParserException e) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700575 }
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700576 }
Garfield Tana3549832018-09-24 15:22:18 -0700577 }
578 if (stringWriter != null) {
579 // Write out xml file while not holding mService lock.
580 FileOutputStream file = null;
581 AtomicFile atomicFile = null;
582 try {
583 atomicFile = new AtomicFile(new File(
584 getUserTasksDir(task.userId),
585 String.valueOf(task.taskId) + TASK_FILENAME_SUFFIX));
586 file = atomicFile.startWrite();
587 file.write(stringWriter.toString().getBytes());
588 file.write('\n');
589 atomicFile.finishWrite(file);
590 } catch (IOException e) {
591 if (file != null) {
592 atomicFile.failWrite(file);
593 }
594 Slog.e(TAG,
595 "Unable to open " + atomicFile + " for persisting. " + e);
596 }
Makoto Onuki62a93aa2017-08-08 10:24:59 -0700597 }
598 }
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700599
Garfield Tana3549832018-09-24 15:22:18 -0700600 @Override
601 public String toString() {
602 return "TaskWriteQueueItem{task=" + mTask + "}";
603 }
604 }
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700605
Garfield Tana3549832018-09-24 15:22:18 -0700606 private static class ImageWriteQueueItem implements PersisterQueue.WriteQueueItem {
607 final String mFilePath;
608 Bitmap mImage;
Craig Mautner21d24a22014-04-23 11:45:37 -0700609
Garfield Tana3549832018-09-24 15:22:18 -0700610 ImageWriteQueueItem(String filePath, Bitmap image) {
611 mFilePath = filePath;
612 mImage = image;
613 }
Makoto Onuki62a93aa2017-08-08 10:24:59 -0700614
Garfield Tana3549832018-09-24 15:22:18 -0700615 @Override
616 public void process() {
617 final String filePath = mFilePath;
618 if (!createParentDirectory(filePath)) {
619 Slog.e(TAG, "Error while creating images directory for file: " + filePath);
620 return;
Makoto Onuki62a93aa2017-08-08 10:24:59 -0700621 }
Garfield Tana3549832018-09-24 15:22:18 -0700622 final Bitmap bitmap = mImage;
623 if (DEBUG) Slog.d(TAG, "writing bitmap: filename=" + filePath);
624 FileOutputStream imageFile = null;
625 try {
626 imageFile = new FileOutputStream(new File(filePath));
627 bitmap.compress(Bitmap.CompressFormat.PNG, 100, imageFile);
628 } catch (Exception e) {
629 Slog.e(TAG, "saveImage: unable to save " + filePath, e);
630 } finally {
631 IoUtils.closeQuietly(imageFile);
Craig Mautner21d24a22014-04-23 11:45:37 -0700632 }
633 }
Garfield Tana3549832018-09-24 15:22:18 -0700634
635 @Override
636 public String toString() {
637 return "ImageWriteQueueItem{path=" + mFilePath
638 + ", image=(" + mImage.getWidth() + "x" + mImage.getHeight() + ")}";
639 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700640 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700641}