TaskPersister.java revision c0ffce5ddd6446f1d46a49cdfaeda4a2ce408e1d
1/*
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
17package com.android.server.am;
18
19import android.app.ActivityManager;
20import android.graphics.Bitmap;
21import android.graphics.BitmapFactory;
22import android.os.Debug;
23import android.os.SystemClock;
24import android.util.ArraySet;
25import android.util.AtomicFile;
26import android.util.Slog;
27import android.util.Xml;
28import com.android.internal.util.FastXmlSerializer;
29import com.android.internal.util.XmlUtils;
30import org.xmlpull.v1.XmlPullParser;
31import org.xmlpull.v1.XmlPullParserException;
32import org.xmlpull.v1.XmlSerializer;
33
34import java.io.BufferedReader;
35import java.io.File;
36import java.io.FileOutputStream;
37import java.io.FileReader;
38import java.io.IOException;
39import java.io.StringWriter;
40import java.util.ArrayList;
41import java.util.Arrays;
42import java.util.Comparator;
43import java.util.LinkedList;
44
45public class TaskPersister {
46    static final String TAG = "TaskPersister";
47    static final boolean DEBUG = false;
48
49    /** When in slow mode don't write tasks out faster than this */
50    private static final long INTER_TASK_DELAY_MS = 10000;
51    private static final long DEBUG_INTER_TASK_DELAY_MS = 5000;
52
53    private static final String RECENTS_FILENAME = "_task";
54    private static final String TASKS_DIRNAME = "recent_tasks";
55    private static final String TASK_EXTENSION = ".xml";
56    private static final String IMAGES_DIRNAME = "recent_images";
57    static final String IMAGE_EXTENSION = ".png";
58
59    private static final String TAG_TASK = "task";
60
61    static File sImagesDir;
62    static File sTasksDir;
63
64    private final ActivityManagerService mService;
65    private final ActivityStackSupervisor mStackSupervisor;
66
67    private boolean mRecentsChanged = false;
68
69    private final LazyTaskWriterThread mLazyTaskWriterThread;
70
71    TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor) {
72        sTasksDir = new File(systemDir, TASKS_DIRNAME);
73        if (!sTasksDir.exists()) {
74            if (DEBUG) Slog.d(TAG, "Creating tasks directory " + sTasksDir);
75            if (!sTasksDir.mkdir()) {
76                Slog.e(TAG, "Failure creating tasks directory " + sTasksDir);
77            }
78        }
79
80        sImagesDir = new File(systemDir, IMAGES_DIRNAME);
81        if (!sImagesDir.exists()) {
82            if (DEBUG) Slog.d(TAG, "Creating images directory " + sTasksDir);
83            if (!sImagesDir.mkdir()) {
84                Slog.e(TAG, "Failure creating images directory " + sImagesDir);
85            }
86        }
87
88        mStackSupervisor = stackSupervisor;
89        mService = stackSupervisor.mService;
90
91        mLazyTaskWriterThread = new LazyTaskWriterThread("LazyTaskWriterThread");
92    }
93
94    void startPersisting() {
95        mLazyTaskWriterThread.start();
96    }
97
98    public void notify(TaskRecord task, boolean flush) {
99        if (DEBUG) Slog.d(TAG, "notify: task=" + task + " flush=" + flush +
100                " Callers=" + Debug.getCallers(4));
101        if (task != null) {
102            task.needsPersisting = true;
103        }
104        synchronized (this) {
105            mLazyTaskWriterThread.mSlow = !flush;
106            mRecentsChanged = true;
107            notifyAll();
108        }
109    }
110
111    private StringWriter saveToXml(TaskRecord task) throws IOException, XmlPullParserException {
112        if (DEBUG) Slog.d(TAG, "saveToXml: task=" + task);
113        final XmlSerializer xmlSerializer = new FastXmlSerializer();
114        StringWriter stringWriter = new StringWriter();
115        xmlSerializer.setOutput(stringWriter);
116
117        if (DEBUG) xmlSerializer.setFeature(
118                    "http://xmlpull.org/v1/doc/features.html#indent-output", true);
119
120        // save task
121        xmlSerializer.startDocument(null, true);
122
123        xmlSerializer.startTag(null, TAG_TASK);
124        task.saveToXml(xmlSerializer);
125        xmlSerializer.endTag(null, TAG_TASK);
126
127        xmlSerializer.endDocument();
128        xmlSerializer.flush();
129
130        return stringWriter;
131    }
132
133    void saveImage(Bitmap image, String filename) {
134        mLazyTaskWriterThread.saveImage(image, filename);
135    }
136
137    private String fileToString(File file) {
138        final String newline = System.lineSeparator();
139        try {
140            BufferedReader reader = new BufferedReader(new FileReader(file));
141            StringBuffer sb = new StringBuffer((int) file.length() * 2);
142            String line;
143            while ((line = reader.readLine()) != null) {
144                sb.append(line + newline);
145            }
146            reader.close();
147            return sb.toString();
148        } catch (IOException ioe) {
149            Slog.e(TAG, "Couldn't read file " + file.getName());
150            return null;
151        }
152    }
153
154    ArrayList<TaskRecord> restoreTasksLocked() {
155        final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
156        ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>();
157
158        File[] recentFiles = sTasksDir.listFiles();
159        if (recentFiles == null) {
160            Slog.e(TAG, "Unable to list files from " + sTasksDir);
161            return tasks;
162        }
163
164        for (int taskNdx = 0; taskNdx < recentFiles.length; ++taskNdx) {
165            File taskFile = recentFiles[taskNdx];
166            if (DEBUG) Slog.d(TAG, "restoreTasksLocked: taskFile=" + taskFile.getName());
167            BufferedReader reader = null;
168            boolean deleteFile = false;
169            try {
170                reader = new BufferedReader(new FileReader(taskFile));
171                final XmlPullParser in = Xml.newPullParser();
172                in.setInput(reader);
173
174                int event;
175                while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
176                        event != XmlPullParser.END_TAG) {
177                    final String name = in.getName();
178                    if (event == XmlPullParser.START_TAG) {
179                        if (DEBUG) Slog.d(TAG, "restoreTasksLocked: START_TAG name=" + name);
180                        if (TAG_TASK.equals(name)) {
181                            final TaskRecord task =
182                                    TaskRecord.restoreFromXml(in, mStackSupervisor);
183                            if (true || DEBUG) Slog.d(TAG, "restoreTasksLocked: restored task=" +
184                                    task);
185                            if (task != null) {
186                                task.isPersistable = true;
187                                task.needsPersisting = true;
188                                tasks.add(task);
189                                final int taskId = task.taskId;
190                                recoveredTaskIds.add(taskId);
191                                mStackSupervisor.setNextTaskId(taskId);
192                            } else {
193                                Slog.e(TAG, "Unable to restore taskFile=" + taskFile + ": " +
194                                        fileToString(taskFile));
195                            }
196                        } else {
197                            Slog.wtf(TAG, "restoreTasksLocked Unknown xml event=" + event +
198                                    " name=" + name);
199                        }
200                    }
201                    XmlUtils.skipCurrentTag(in);
202                }
203            } catch (Exception e) {
204                Slog.wtf(TAG, "Unable to parse " + taskFile + ". Error " + e);
205                Slog.e(TAG, "Failing file: " + fileToString(taskFile));
206                deleteFile = true;
207            } finally {
208                if (reader != null) {
209                    try {
210                        reader.close();
211                    } catch (IOException e) {
212                    }
213                }
214                if (!DEBUG && deleteFile) {
215                    if (true || DEBUG) Slog.d(TAG, "Deleting file=" + taskFile.getName());
216                    taskFile.delete();
217                }
218            }
219        }
220
221        if (!DEBUG) {
222            removeObsoleteFiles(recoveredTaskIds);
223        }
224
225        TaskRecord[] tasksArray = new TaskRecord[tasks.size()];
226        tasks.toArray(tasksArray);
227        Arrays.sort(tasksArray, new Comparator<TaskRecord>() {
228            @Override
229            public int compare(TaskRecord lhs, TaskRecord rhs) {
230                final long diff = rhs.mLastTimeMoved - lhs.mLastTimeMoved;
231                if (diff < 0) {
232                    return -1;
233                } else if (diff > 0) {
234                    return +1;
235                } else {
236                    return 0;
237                }
238            }
239        });
240
241        return new ArrayList<TaskRecord>(Arrays.asList(tasksArray));
242    }
243
244    private static void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) {
245        for (int fileNdx = 0; fileNdx < files.length; ++fileNdx) {
246            File file = files[fileNdx];
247            String filename = file.getName();
248            final int taskIdEnd = filename.indexOf('_');
249            if (taskIdEnd > 0) {
250                final int taskId;
251                try {
252                    taskId = Integer.valueOf(filename.substring(0, taskIdEnd));
253                } catch (Exception e) {
254                    Slog.wtf(TAG, "removeObsoleteFile: Can't parse file=" + file.getName());
255                    file.delete();
256                    continue;
257                }
258                if (!persistentTaskIds.contains(taskId)) {
259                    if (true || DEBUG) Slog.d(TAG, "removeObsoleteFile: deleting file=" +
260                            file.getName());
261                    file.delete();
262                }
263            }
264        }
265    }
266
267    private void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds) {
268        removeObsoleteFiles(persistentTaskIds, sTasksDir.listFiles());
269        removeObsoleteFiles(persistentTaskIds, sImagesDir.listFiles());
270    }
271
272    static Bitmap restoreImage(String filename) {
273        if (DEBUG) Slog.d(TAG, "restoreImage: restoring " + filename);
274        return BitmapFactory.decodeFile(sImagesDir + File.separator + filename);
275    }
276
277    private class LazyTaskWriterThread extends Thread {
278        boolean mSlow = true;
279        LinkedList<BitmapQueueEntry> mSaveImagesQueue = new LinkedList<BitmapQueueEntry>();
280
281        LazyTaskWriterThread(String name) {
282            super(name);
283        }
284
285        class BitmapQueueEntry {
286            final Bitmap mImage;
287            final String mFilename;
288            BitmapQueueEntry(Bitmap image, String filename) {
289                mImage = image;
290                mFilename = filename;
291            }
292        }
293
294        void saveImage(Bitmap image, String filename) {
295            synchronized (mSaveImagesQueue) {
296                mSaveImagesQueue.add(new BitmapQueueEntry(image, filename));
297            }
298            TaskPersister.this.notify(null, false);
299        }
300
301        @Override
302        public void run() {
303            ArraySet<Integer> persistentTaskIds = new ArraySet<Integer>();
304            while (true) {
305                // If mSlow, then delay between each call to saveToXml().
306                synchronized (TaskPersister.this) {
307                    long now = SystemClock.uptimeMillis();
308                    final long releaseTime =
309                            now + (DEBUG ? DEBUG_INTER_TASK_DELAY_MS: INTER_TASK_DELAY_MS);
310                    while (mSlow && now < releaseTime) {
311                        try {
312                            if (DEBUG) Slog.d(TAG, "LazyTaskWriter: waiting " +
313                                    (releaseTime - now));
314                            TaskPersister.this.wait(releaseTime - now);
315                        } catch (InterruptedException e) {
316                        }
317                        now = SystemClock.uptimeMillis();
318                    }
319                }
320
321                // Write out one bitmap that needs saving each time through.
322                BitmapQueueEntry entry;
323                synchronized (mSaveImagesQueue) {
324                    entry = mSaveImagesQueue.poll();
325                    // Are there any more after this one?
326                    mRecentsChanged |= !mSaveImagesQueue.isEmpty();
327                }
328                if (entry != null) {
329                    final String filename = entry.mFilename;
330                    if (DEBUG) Slog.d(TAG, "saveImage: filename=" + filename);
331                    FileOutputStream imageFile = null;
332                    try {
333                        imageFile = new FileOutputStream(new File(sImagesDir, filename));
334                        entry.mImage.compress(Bitmap.CompressFormat.PNG, 100, imageFile);
335                    } catch (Exception e) {
336                        Slog.e(TAG, "saveImage: unable to save " + filename, e);
337                    } finally {
338                        if (imageFile != null) {
339                            try {
340                                imageFile.close();
341                            } catch (IOException e) {
342                            }
343                        }
344                    }
345                }
346
347                StringWriter stringWriter = null;
348                TaskRecord task = null;
349                synchronized(mService) {
350                    final ArrayList<TaskRecord> tasks = mService.mRecentTasks;
351                    persistentTaskIds.clear();
352                    if (DEBUG) Slog.d(TAG, "mRecents=" + tasks);
353                    for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
354                        task = tasks.get(taskNdx);
355                        if (DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task + " persistable=" +
356                                task.isPersistable + " needsPersisting=" + task.needsPersisting);
357                        if (task.isPersistable && !task.stack.isHomeStack()) {
358                            if (DEBUG) Slog.d(TAG, "adding to persistentTaskIds task=" + task);
359                            persistentTaskIds.add(task.taskId);
360
361                            if (task.needsPersisting) {
362                                try {
363                                    if (DEBUG) Slog.d(TAG, "Saving task=" + task);
364                                    stringWriter = saveToXml(task);
365                                    break;
366                                } catch (IOException e) {
367                                } catch (XmlPullParserException e) {
368                                } finally {
369                                    task.needsPersisting = false;
370                                }
371                            }
372                        } else {
373                            if (DEBUG) Slog.d(TAG, "omitting from persistentTaskIds task=" + task);
374                        }
375                    }
376                }
377
378                if (stringWriter != null) {
379                    // Write out xml file while not holding mService lock.
380                    FileOutputStream file = null;
381                    AtomicFile atomicFile = null;
382                    try {
383                        atomicFile = new AtomicFile(new File(sTasksDir,
384                                String.valueOf(task.taskId) + RECENTS_FILENAME + TASK_EXTENSION));
385                        file = atomicFile.startWrite();
386                        file.write(stringWriter.toString().getBytes());
387                        file.write('\n');
388                        atomicFile.finishWrite(file);
389                    } catch (IOException e) {
390                        if (file != null) {
391                            atomicFile.failWrite(file);
392                        }
393                        Slog.e(TAG, "Unable to open " + atomicFile + " for persisting. " + e);
394                    }
395                } else {
396                    // Made it through the entire list and didn't find anything new that needed
397                    // persisting.
398                    if (!DEBUG) {
399                        if (DEBUG) Slog.d(TAG, "Calling removeObsoleteFiles persistentTaskIds=" +
400                                persistentTaskIds);
401                        removeObsoleteFiles(persistentTaskIds);
402                    }
403
404                    // Wait here for someone to call setRecentsChanged().
405                    synchronized (TaskPersister.this) {
406                        while (!mRecentsChanged) {
407                            if (DEBUG) Slog.d(TAG, "LazyTaskWriter: Waiting.");
408                            try {
409                                TaskPersister.this.wait();
410                            } catch (InterruptedException e) {
411                            }
412                        }
413                        mRecentsChanged = false;
414                        if (DEBUG) Slog.d(TAG, "LazyTaskWriter: Awake");
415                    }
416                }
417                // Some recents file needs to be written.
418            }
419        }
420    }
421}
422