Moving recent tasks and images to /data/system_ce

Moved user sensitive recents data to the credential encrypted directory
/data/system_ce. Since this directory is not available before the user
is unlocked, and unavailable task ids are needed to be known before
assigning an id to a new task, this change adds a file in /data/system_de
for each user, to store the ids of the tasks present in /data/system_ce.

Bug: b/24569398
Change-Id: I340d1abe23e2594e2cee4bff89a697bd8dc61fc7
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ccaa1d2..150ea7c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1892,7 +1892,7 @@
             case SYSTEM_USER_UNLOCK_MSG: {
                 final int userId = msg.arg1;
                 mSystemServiceManager.unlockUser(userId);
-                mRecentTasks.cleanupLocked(userId);
+                mRecentTasks.loadUserRecentsLocked(userId);
                 installEncryptionUnawareProviders(userId);
                 break;
             }
@@ -2539,7 +2539,7 @@
     }
 
     void onUserStoppedLocked(int userId) {
-        mRecentTasks.unloadUserRecentsLocked(userId);
+        mRecentTasks.unloadUserDataFromMemoryLocked(userId);
     }
 
     public void initPowerManagement() {
@@ -8725,6 +8725,10 @@
                     android.Manifest.permission.GET_DETAILED_TASKS)
                     == PackageManager.PERMISSION_GRANTED;
 
+            if (!isUserRunning(userId, ActivityManager.FLAG_AND_UNLOCKED)) {
+                Slog.i(TAG, "user " + userId + " is still locked. Cannot load recents");
+                return Collections.emptyList();
+            }
             mRecentTasks.loadUserRecentsLocked(userId);
 
             final int recentsCount = mRecentTasks.size();
@@ -12519,7 +12523,7 @@
             // Make sure we have the current profile info, since it is needed for security checks.
             mUserController.onSystemReady();
 
-            mRecentTasks.onSystemReady();
+            mRecentTasks.onSystemReadyLocked();
             // Check to see if there are any update receivers to run.
             if (!mDidUpdate) {
                 if (mWaitingUpdate) {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 662c51e..f3f0561 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -719,14 +719,14 @@
     }
 
     int getNextTaskIdForUserLocked(int userId) {
-        mRecentTasks.loadUserRecentsLocked(userId);
         final int currentTaskId = mCurTaskIdForUser.get(userId, userId * MAX_TASK_IDS_PER_USER);
         // for a userId u, a taskId can only be in the range
         // [u*MAX_TASK_IDS_PER_USER, (u+1)*MAX_TASK_IDS_PER_USER-1], so if MAX_TASK_IDS_PER_USER
         // was 10, user 0 could only have taskIds 0 to 9, user 1: 10 to 19, user 2: 20 to 29, so on.
         int candidateTaskId = currentTaskId;
-        while (anyTaskForIdLocked(candidateTaskId, !RESTORE_FROM_RECENTS,
-                INVALID_STACK_ID) != null) {
+        while (mRecentTasks.taskIdTakenForUserLocked(candidateTaskId, userId)
+                || anyTaskForIdLocked(candidateTaskId, !RESTORE_FROM_RECENTS,
+                        INVALID_STACK_ID) != null) {
             candidateTaskId++;
             if (candidateTaskId == (userId + 1) * MAX_TASK_IDS_PER_USER) {
                 // Wrap around as there will be smaller task ids that are available now.
diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java
index 9c139d5..7209814 100644
--- a/services/core/java/com/android/server/am/RecentTasks.java
+++ b/services/core/java/com/android/server/am/RecentTasks.java
@@ -37,8 +37,12 @@
 import android.graphics.Bitmap;
 import android.os.Environment;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.UserHandle;
+import android.provider.Settings.System;
+import android.util.ArraySet;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 
 import java.io.File;
@@ -59,12 +63,22 @@
 
     // Maximum number recent bitmaps to keep in memory.
     private static final int MAX_RECENT_BITMAPS = 3;
+    private static final int DEFAULT_INITIAL_CAPACITY = 5;
 
     /**
      * Save recent tasks information across reboots.
      */
     private final TaskPersister mTaskPersister;
-    private final SparseBooleanArray mUsersWithRecentsLoaded = new SparseBooleanArray(5);
+    private final ActivityManagerService mService;
+    private final SparseBooleanArray mUsersWithRecentsLoaded = new SparseBooleanArray(
+            DEFAULT_INITIAL_CAPACITY);
+
+    /**
+     * Stores for each user task ids that are taken by tasks residing in persistent storage. These
+     * tasks may or may not currently be in memory.
+     */
+    final SparseArray<SparseBooleanArray> mPersistedTaskIds = new SparseArray<>(
+            DEFAULT_INITIAL_CAPACITY);
 
     // Mainly to avoid object recreation on multiple calls.
     private final ArrayList<TaskRecord> mTmpRecents = new ArrayList<TaskRecord>();
@@ -75,6 +89,7 @@
 
     RecentTasks(ActivityManagerService service, ActivityStackSupervisor mStackSupervisor) {
         File systemDir = Environment.getDataSystemDirectory();
+        mService = service;
         mTaskPersister = new TaskPersister(systemDir, mStackSupervisor, service, this);
         mStackSupervisor.setRecentTasks(this);
     }
@@ -87,6 +102,8 @@
      */
     void loadUserRecentsLocked(int userId) {
         if (!mUsersWithRecentsLoaded.get(userId)) {
+            // Load the task ids if not loaded.
+            loadPersistedTaskIdsForUserLocked(userId);
             Slog.i(TAG, "Loading recents for user " + userId + " into memory.");
             addAll(mTaskPersister.restoreTasksForUserLocked(userId));
             cleanupLocked(userId);
@@ -94,21 +111,49 @@
         }
     }
 
+    private void loadPersistedTaskIdsForUserLocked(int userId) {
+        // An empty instead of a null set here means that no persistent taskIds were present
+        // on file when we loaded them.
+        if (mPersistedTaskIds.get(userId) == null) {
+            mPersistedTaskIds.put(userId, mTaskPersister.loadPersistedTaskIdsForUser(userId));
+        }
+    }
+
+    boolean taskIdTakenForUserLocked(int taskId, int userId) {
+        loadPersistedTaskIdsForUserLocked(userId);
+        return mPersistedTaskIds.get(userId).get(taskId);
+    }
+
     void notifyTaskPersisterLocked(TaskRecord task, boolean flush) {
         if (task != null && task.stack != null && task.stack.isHomeStack()) {
             // Never persist the home stack.
             return;
         }
+        syncPersistentTaskIdsLocked();
         mTaskPersister.wakeup(task, flush);
     }
 
-    void onSystemReady() {
-        clear();
-        loadUserRecentsLocked(UserHandle.USER_SYSTEM);
-        startPersisting();
+    private void syncPersistentTaskIdsLocked() {
+        for (int i = mPersistedTaskIds.size() - 1; i >= 0; i--) {
+            int userId = mPersistedTaskIds.keyAt(i);
+            if (mUsersWithRecentsLoaded.get(userId)) {
+                // Recents are loaded only after task ids are loaded. Therefore, the set of taskids
+                // referenced here should not be null.
+                mPersistedTaskIds.valueAt(i).clear();
+            }
+        }
+        for (int i = size() - 1; i >= 0; i--) {
+            TaskRecord task = get(i);
+            if (task.isPersistable && (task.stack == null || !task.stack.isHomeStack())) {
+                // Set of persisted taskIds for task.userId should not be null here
+                mPersistedTaskIds.get(task.userId).put(task.taskId, true);
+            }
+        }
     }
 
-    void startPersisting() {
+
+    void onSystemReadyLocked() {
+        clear();
         mTaskPersister.startPersisting();
     }
 
@@ -125,11 +170,14 @@
     }
 
     void flush() {
+        synchronized (mService) {
+            syncPersistentTaskIdsLocked();
+        }
         mTaskPersister.flush();
     }
 
     /**
-     * Returns all userIds for which recents from storage are loaded
+     * Returns all userIds for which recents from persistent storage are loaded into this list.
      *
      * @return an array of userIds.
      */
@@ -149,12 +197,7 @@
         return usersWithRecentsLoaded;
     }
 
-    /**
-     * Removes recent tasks for this user if they are loaded, does not do anything otherwise.
-     *
-     * @param userId the user id.
-     */
-    void unloadUserRecentsLocked(int userId) {
+    private void unloadUserRecentsLocked(int userId) {
         if (mUsersWithRecentsLoaded.get(userId)) {
             Slog.i(TAG, "Unloading recents for user " + userId + " from memory.");
             mUsersWithRecentsLoaded.delete(userId);
@@ -162,6 +205,18 @@
         }
     }
 
+    /**
+     * Removes recent tasks and any other state kept in memory for the passed in user. Does not
+     * touch the information present on persistent storage.
+     *
+     * @param userId the id of the user
+     */
+    void unloadUserDataFromMemoryLocked(int userId) {
+        unloadUserRecentsLocked(userId);
+        mPersistedTaskIds.delete(userId);
+        mTaskPersister.unloadUserDataFromMemory(userId);
+    }
+
     TaskRecord taskForIdLocked(int id) {
         final int recentsCount = size();
         for (int i = 0; i < recentsCount; i++) {
diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java
index 283939e..11fd3bc 100644
--- a/services/core/java/com/android/server/am/TaskPersister.java
+++ b/services/core/java/com/android/server/am/TaskPersister.java
@@ -16,6 +16,9 @@
 
 package com.android.server.am;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.os.Debug;
@@ -26,11 +29,13 @@
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.util.Xml;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.XmlUtils;
-
 import libcore.io.IoUtils;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -38,9 +43,12 @@
 import org.xmlpull.v1.XmlSerializer;
 
 import java.io.BufferedReader;
+import java.io.BufferedWriter;
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.FileReader;
+import java.io.FileWriter;
 import java.io.IOException;
 import java.io.StringWriter;
 import java.util.ArrayList;
@@ -71,6 +79,7 @@
     private static final String TASKS_DIRNAME = "recent_tasks";
     private static final String TASK_EXTENSION = ".xml";
     private static final String IMAGES_DIRNAME = "recent_images";
+    private static final String PERSISTED_TASK_IDS_FILENAME = "persisted_taskIds.txt";
     static final String IMAGE_EXTENSION = ".png";
 
     private static final String TAG_TASK = "task";
@@ -78,6 +87,7 @@
     private final ActivityManagerService mService;
     private final ActivityStackSupervisor mStackSupervisor;
     private final RecentTasks mRecentTasks;
+    private final SparseArray<SparseBooleanArray> mTaskIdsInFile = new SparseArray<>();
 
     /**
      * Value determines write delay mode as follows: < 0 We are Flushing. No delays between writes
@@ -170,6 +180,64 @@
         }
     }
 
+    @NonNull
+    SparseBooleanArray loadPersistedTaskIdsForUser(int userId) {
+        if (mTaskIdsInFile.get(userId) != null) {
+            return mTaskIdsInFile.get(userId).clone();
+        }
+        final SparseBooleanArray persistedTaskIds = new SparseBooleanArray();
+        BufferedReader reader = null;
+        String line;
+        try {
+            reader = new BufferedReader(new FileReader(getUserPersistedTaskIdsFile(userId)));
+            while ((line = reader.readLine()) != null) {
+                for (String taskIdString : line.split("\\s+")) {
+                    int id = Integer.parseInt(taskIdString);
+                    persistedTaskIds.put(id, true);
+                }
+            }
+        } catch (FileNotFoundException e) {
+            // File doesn't exist. Ignore.
+        } catch (Exception e) {
+            Slog.e(TAG, "Error while reading taskIds file for user " + userId, e);
+        } finally {
+            IoUtils.closeQuietly(reader);
+        }
+        mTaskIdsInFile.put(userId, persistedTaskIds);
+        return persistedTaskIds.clone();
+    }
+
+    private void maybeWritePersistedTaskIdsForUser(@NonNull SparseBooleanArray taskIds,
+            int userId) {
+        if (userId < 0) {
+            return;
+        }
+        SparseBooleanArray persistedIdsInFile = mTaskIdsInFile.get(userId);
+        if (persistedIdsInFile != null && persistedIdsInFile.equals(taskIds)) {
+            return;
+        }
+        final File persistedTaskIdsFile = getUserPersistedTaskIdsFile(userId);
+        BufferedWriter writer = null;
+        try {
+            writer = new BufferedWriter(new FileWriter(persistedTaskIdsFile));
+            for (int i = 0; i < taskIds.size(); i++) {
+                if (taskIds.valueAt(i)) {
+                    writer.write(String.valueOf(taskIds.keyAt(i)));
+                    writer.newLine();
+                }
+            }
+        } catch (Exception e) {
+            Slog.e(TAG, "Error while writing taskIds file for user " + userId, e);
+        } finally {
+            IoUtils.closeQuietly(writer);
+        }
+        mTaskIdsInFile.put(userId, taskIds.clone());
+    }
+
+    void unloadUserDataFromMemory(int userId) {
+        mTaskIdsInFile.delete(userId);
+    }
+
     void wakeup(TaskRecord task, boolean flush) {
         synchronized (this) {
             if (task != null) {
@@ -336,14 +404,16 @@
 
         File[] recentFiles = userTasksDir.listFiles();
         if (recentFiles == null) {
-            Slog.e(TAG, "restoreTasksForUser: Unable to list files from " + userTasksDir);
+            Slog.e(TAG, "restoreTasksForUserLocked: Unable to list files from " + userTasksDir);
             return tasks;
         }
 
         for (int taskNdx = 0; taskNdx < recentFiles.length; ++taskNdx) {
             File taskFile = recentFiles[taskNdx];
-            if (DEBUG) Slog.d(TAG, "restoreTasksForUser: userId=" + userId
-                    + ", taskFile=" + taskFile.getName());
+            if (DEBUG) {
+                Slog.d(TAG, "restoreTasksForUserLocked: userId=" + userId
+                        + ", taskFile=" + taskFile.getName());
+            }
             BufferedReader reader = null;
             boolean deleteFile = false;
             try {
@@ -366,20 +436,29 @@
                                 // out the stuff we just read, if we don't write it we will
                                 // read the same thing again.
                                 // mWriteQueue.add(new TaskWriteQueueItem(task));
+
                                 final int taskId = task.taskId;
-                                mStackSupervisor.setNextTaskIdForUserLocked(taskId, userId);
-                                // Check if it's a valid user id. Don't add tasks for removed users.
-                                if (userId == task.userId) {
+                                if (mStackSupervisor.anyTaskForIdLocked(taskId,
+                                        /* restoreFromRecents= */ false, 0) != null) {
+                                    // Should not happen.
+                                    Slog.wtf(TAG, "Existing task with taskId " + taskId + "found");
+                                } else if (userId != task.userId) {
+                                    // Should not happen.
+                                    Slog.wtf(TAG, "Task with userId " + task.userId + " found in "
+                                            + userTasksDir.getAbsolutePath());
+                                } else {
+                                    // Looks fine.
+                                    mStackSupervisor.setNextTaskIdForUserLocked(taskId, userId);
                                     task.isPersistable = true;
                                     tasks.add(task);
                                     recoveredTaskIds.add(taskId);
                                 }
                             } else {
-                                Slog.e(TAG, "restoreTasksForUser: Unable to restore taskFile="
+                                Slog.e(TAG, "restoreTasksForUserLocked: Unable to restore taskFile="
                                         + taskFile + ": " + fileToString(taskFile));
                             }
                         } else {
-                            Slog.wtf(TAG, "restoreTasksForUser: Unknown xml event=" + event
+                            Slog.wtf(TAG, "restoreTasksForUserLocked: Unknown xml event=" + event
                                     + " name=" + name);
                         }
                     }
@@ -454,6 +533,20 @@
         }
     }
 
+    private void writeTaskIdsFiles() {
+        int candidateUserIds[];
+        synchronized (mService) {
+            candidateUserIds = mRecentTasks.usersWithRecentsLoadedLocked();
+        }
+        SparseBooleanArray taskIdsToSave;
+        for (int userId : candidateUserIds) {
+            synchronized (mService) {
+                taskIdsToSave = mRecentTasks.mPersistedTaskIds.get(userId).clone();
+            }
+            maybeWritePersistedTaskIdsForUser(taskIdsToSave, userId);
+        }
+    }
+
     private void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds) {
         int[] candidateUserIds;
         synchronized (mService) {
@@ -472,8 +565,12 @@
         return BitmapFactory.decodeFile(filename);
     }
 
+    static File getUserPersistedTaskIdsFile(int userId) {
+        return new File(Environment.getDataSystemDeDirectory(userId), PERSISTED_TASK_IDS_FILENAME);
+    }
+
     static File getUserTasksDir(int userId) {
-        File userTasksDir = new File(Environment.getUserSystemDirectory(userId), TASKS_DIRNAME);
+        File userTasksDir = new File(Environment.getDataSystemCeDirectory(userId), TASKS_DIRNAME);
 
         if (!userTasksDir.exists()) {
             if (!userTasksDir.mkdir()) {
@@ -485,7 +582,7 @@
     }
 
     static File getUserImagesDir(int userId) {
-        File userImagesDir = new File(Environment.getUserSystemDirectory(userId), IMAGES_DIRNAME);
+        File userImagesDir = new File(Environment.getDataSystemCeDirectory(userId), IMAGES_DIRNAME);
 
         if (!userImagesDir.exists()) {
             if (!userImagesDir.mkdir()) {
@@ -535,6 +632,7 @@
                     }
                     removeObsoleteFiles(persistentTaskIds);
                 }
+                writeTaskIdsFiles();
 
                 // If mNextWriteTime, then don't delay between each call to saveToXml().
                 final WriteQueueItem item;