Loading services/core/java/com/android/server/wm/ActivityTaskManagerService.java +8 −5 Original line number Diff line number Diff line Loading @@ -2505,6 +2505,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { userId = handleIncomingUser(Binder.getCallingPid(), callingUid, userId, "getRecentTasks"); final boolean allowed = isGetTasksAllowed("getRecentTasks", Binder.getCallingPid(), callingUid); if (!mAmInternal.isUserRunning(userId, ActivityManager.FLAG_AND_UNLOCKED)) { Slog.i(TAG, "User " + userId + " is locked. Cannot load recents"); return ParceledListSlice.emptyList(); } mRecentTasks.loadRecentTasksIfNeeded(userId); synchronized (mGlobalLock) { return mRecentTasks.getRecentTasks(maxNum, flags, allowed, userId, callingUid); } Loading Loading @@ -7056,12 +7061,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Override public void loadRecentTasksForUser(int userId) { synchronized (mGlobalLock) { mRecentTasks.loadUserRecentsLocked(userId); // TODO renaming the methods(?) // This runs on android.fg thread when the user is unlocking. mRecentTasks.loadRecentTasksIfNeeded(userId); mPackageConfigPersister.loadUserPackages(userId); } } @Override public void onPackagesSuspendedChanged(String[] packages, boolean suspended, int userId) { Loading services/core/java/com/android/server/wm/RecentTasks.java +58 −52 Original line number Diff line number Diff line Loading @@ -16,7 +16,6 @@ package com.android.server.wm; import static android.app.ActivityManager.FLAG_AND_UNLOCKED; import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE; import static android.app.ActivityManager.RECENT_WITH_EXCLUDED; import static android.app.ActivityTaskManager.INVALID_TASK_ID; Loading Loading @@ -69,6 +68,7 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.text.TextUtils; import android.util.ArraySet; import android.util.IntArray; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; Loading @@ -89,9 +89,9 @@ import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; /** * Class for managing the recent tasks list. The list is ordered by most recent (index 0) to the Loading Loading @@ -167,8 +167,9 @@ class RecentTasks { /** * Mapping of user id -> whether recent tasks have been loaded for that user. * The AtomicBoolean per user will be locked when reading persisted task from storage. */ private final SparseBooleanArray mUsersWithRecentsLoaded = new SparseBooleanArray( private final SparseArray<AtomicBoolean> mUsersWithRecentsLoaded = new SparseArray<>( DEFAULT_INITIAL_CAPACITY); /** Loading Loading @@ -481,29 +482,49 @@ class RecentTasks { /** * Loads the persistent recentTasks for {@code userId} into this list from persistent storage. * Does nothing if they are already loaded. * * @param userId the user Id * Does nothing if they are already loaded. This may perform IO operation, so the caller should * not hold a lock. */ void loadUserRecentsLocked(int userId) { if (mUsersWithRecentsLoaded.get(userId)) { // User already loaded, return early void loadRecentTasksIfNeeded(int userId) { AtomicBoolean userLoaded; synchronized (mService.mGlobalLock) { userLoaded = mUsersWithRecentsLoaded.get(userId); if (userLoaded == null) { mUsersWithRecentsLoaded.append(userId, userLoaded = new AtomicBoolean()); } } synchronized (userLoaded) { if (userLoaded.get()) { // The recent tasks of the user are already loaded. return; } // Read task files from storage. final SparseBooleanArray persistedTaskIds = mTaskPersister.readPersistedTaskIdsFromFileForUser(userId); final TaskPersister.RecentTaskFiles taskFiles = TaskPersister.loadTasksForUser(userId); synchronized (mService.mGlobalLock) { restoreRecentTasksLocked(userId, persistedTaskIds, taskFiles); } userLoaded.set(true); } } // Load the task ids if not loaded. loadPersistedTaskIdsForUserLocked(userId); // Check if any tasks are added before recents is loaded final SparseBooleanArray preaddedTasks = new SparseBooleanArray(); for (final Task task : mTasks) { /** Restores recent tasks from raw data (the files are already read into memory). */ private void restoreRecentTasksLocked(int userId, SparseBooleanArray persistedTaskIds, TaskPersister.RecentTaskFiles taskFiles) { mTaskPersister.setPersistedTaskIds(userId, persistedTaskIds); mPersistedTaskIds.put(userId, persistedTaskIds.clone()); // Check if any tasks are added before recents is loaded. final IntArray existedTaskIds = new IntArray(); for (int i = mTasks.size() - 1; i >= 0; i--) { final Task task = mTasks.get(i); if (task.mUserId == userId && shouldPersistTaskLocked(task)) { preaddedTasks.put(task.mTaskId, true); existedTaskIds.add(task.mTaskId); } } Slog.i(TAG, "Loading recents for user " + userId + " into memory."); List<Task> tasks = mTaskPersister.restoreTasksForUserLocked(userId, preaddedTasks); Slog.i(TAG, "Restoring recents for user " + userId); final ArrayList<Task> tasks = mTaskPersister.restoreTasksForUserLocked(userId, taskFiles, existedTaskIds); // Tasks are ordered from most recent to least recent. Update the last active time to be // in sync with task recency when device reboots, so the most recent task has the Loading @@ -516,37 +537,34 @@ class RecentTasks { mTasks.addAll(tasks); cleanupLocked(userId); mUsersWithRecentsLoaded.put(userId, true); // If we have tasks added before loading recents, we need to update persistent task IDs. if (preaddedTasks.size() > 0) { if (existedTaskIds.size() > 0) { syncPersistentTaskIdsLocked(); } } 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)); Slog.i(TAG, "Loaded persisted task ids for user " + userId); } private boolean isRecentTasksLoaded(int userId) { final AtomicBoolean userLoaded = mUsersWithRecentsLoaded.get(userId); return userLoaded != null && userLoaded.get(); } /** * @return whether the {@param taskId} is currently in use for the given user. */ boolean containsTaskId(int taskId, int userId) { loadPersistedTaskIdsForUserLocked(userId); return mPersistedTaskIds.get(userId).get(taskId); final SparseBooleanArray taskIds = mPersistedTaskIds.get(userId); return taskIds != null && taskIds.get(taskId); } /** * @return all the task ids for the user with the given {@param userId}. */ SparseBooleanArray getTaskIdsForUser(int userId) { loadPersistedTaskIdsForUserLocked(userId); return mPersistedTaskIds.get(userId); /** Returns all the task ids for the user from {@link #usersWithRecentsLoadedLocked}. */ SparseBooleanArray getTaskIdsForLoadedUser(int loadedUserId) { final SparseBooleanArray taskIds = mPersistedTaskIds.get(loadedUserId); if (taskIds == null) { Slog.wtf(TAG, "Loaded user without loaded tasks, userId=" + loadedUserId); return new SparseBooleanArray(); } return taskIds; } /** Loading @@ -565,7 +583,7 @@ class RecentTasks { private void syncPersistentTaskIdsLocked() { for (int i = mPersistedTaskIds.size() - 1; i >= 0; i--) { int userId = mPersistedTaskIds.keyAt(i); if (mUsersWithRecentsLoaded.get(userId)) { if (isRecentTasksLoaded(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(); Loading Loading @@ -621,7 +639,7 @@ class RecentTasks { int len = 0; for (int i = 0; i < usersWithRecentsLoaded.length; i++) { int userId = mUsersWithRecentsLoaded.keyAt(i); if (mUsersWithRecentsLoaded.valueAt(i)) { if (mUsersWithRecentsLoaded.valueAt(i).get()) { usersWithRecentsLoaded[len++] = userId; } } Loading @@ -639,7 +657,7 @@ class RecentTasks { * @param userId the id of the user */ void unloadUserDataFromMemoryLocked(int userId) { if (mUsersWithRecentsLoaded.get(userId)) { if (isRecentTasksLoaded(userId)) { Slog.i(TAG, "Unloading recents for user " + userId + " from memory."); mUsersWithRecentsLoaded.delete(userId); removeTasksForUserLocked(userId); Loading Loading @@ -922,11 +940,6 @@ class RecentTasks { return mService.mAmInternal.getCurrentProfileIds(); } @VisibleForTesting boolean isUserRunning(int userId, int flags) { return mService.mAmInternal.isUserRunning(userId, flags); } /** * @return the list of recent tasks for presentation. */ Loading @@ -942,13 +955,6 @@ class RecentTasks { private ArrayList<ActivityManager.RecentTaskInfo> getRecentTasksImpl(int maxNum, int flags, boolean getTasksAllowed, int userId, int callingUid) { final boolean withExcluded = (flags & RECENT_WITH_EXCLUDED) != 0; if (!isUserRunning(userId, FLAG_AND_UNLOCKED)) { Slog.i(TAG, "user " + userId + " is still locked. Cannot load recents"); return new ArrayList<>(); } loadUserRecentsLocked(userId); final Set<Integer> includedUsers = getProfileIds(userId); includedUsers.add(Integer.valueOf(userId)); Loading services/core/java/com/android/server/wm/TaskPersister.java +83 −37 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import android.os.FileUtils; import android.os.SystemClock; import android.util.ArraySet; import android.util.AtomicFile; import android.util.IntArray; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; Loading @@ -43,20 +44,20 @@ import org.xmlpull.v1.XmlPullParser; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; /** * Persister that saves recent tasks into disk. Loading Loading @@ -129,11 +130,9 @@ public class TaskPersister implements PersisterQueue.Listener { ImageWriteQueueItem.class); } /** Reads task ids from file. This should not be called in lock. */ @NonNull SparseBooleanArray loadPersistedTaskIdsForUser(int userId) { if (mTaskIdsInFile.get(userId) != null) { return mTaskIdsInFile.get(userId).clone(); } SparseBooleanArray readPersistedTaskIdsFromFileForUser(int userId) { final SparseBooleanArray persistedTaskIds = new SparseBooleanArray(); synchronized (mIoLock) { BufferedReader reader = null; Loading @@ -154,11 +153,10 @@ public class TaskPersister implements PersisterQueue.Listener { IoUtils.closeQuietly(reader); } } mTaskIdsInFile.put(userId, persistedTaskIds); return persistedTaskIds.clone(); Slog.i(TAG, "Loaded persisted task ids for user " + userId); return persistedTaskIds; } @VisibleForTesting void writePersistedTaskIdsForUser(@NonNull SparseBooleanArray taskIds, int userId) { if (userId < 0) { Loading @@ -183,6 +181,10 @@ public class TaskPersister implements PersisterQueue.Listener { } } void setPersistedTaskIds(int userId, @NonNull SparseBooleanArray taskIds) { mTaskIdsInFile.put(userId, taskIds); } void unloadUserDataFromMemory(int userId) { mTaskIdsInFile.delete(userId); } Loading Loading @@ -241,7 +243,7 @@ public class TaskPersister implements PersisterQueue.Listener { return item != null ? item.mImage : null; } private String fileToString(File file) { private static String fileToString(File file) { final String newline = System.lineSeparator(); try { BufferedReader reader = new BufferedReader(new FileReader(file)); Loading Loading @@ -272,44 +274,64 @@ public class TaskPersister implements PersisterQueue.Listener { return null; } List<Task> restoreTasksForUserLocked(final int userId, SparseBooleanArray preaddedTasks) { final ArrayList<Task> tasks = new ArrayList<Task>(); ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>(); File userTasksDir = getUserTasksDir(userId); File[] recentFiles = userTasksDir.listFiles(); /** Loads task files from disk. This should not be called in lock. */ static RecentTaskFiles loadTasksForUser(int userId) { final ArrayList<RecentTaskFile> taskFiles = new ArrayList<>(); final File userTasksDir = getUserTasksDir(userId); final File[] recentFiles = userTasksDir.listFiles(); if (recentFiles == null) { 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, "restoreTasksForUserLocked: userId=" + userId + ", taskFile=" + taskFile.getName()); Slog.i(TAG, "loadTasksForUser: Unable to list files from " + userTasksDir + " exists=" + userTasksDir.exists()); return new RecentTaskFiles(new File[0], taskFiles); } for (File taskFile : recentFiles) { if (!taskFile.getName().endsWith(TASK_FILENAME_SUFFIX)) { continue; } final int taskId; try { final int taskId = Integer.parseInt(taskFile.getName().substring( taskId = Integer.parseInt(taskFile.getName().substring( 0 /* beginIndex */, taskFile.getName().length() - TASK_FILENAME_SUFFIX.length())); if (preaddedTasks.get(taskId, false)) { Slog.w(TAG, "Task #" + taskId + " has already been created so we don't restore again"); continue; } } catch (NumberFormatException e) { Slog.w(TAG, "Unexpected task file name", e); continue; } try { taskFiles.add(new RecentTaskFile(taskId, taskFile)); } catch (IOException e) { Slog.w(TAG, "Failed to read file: " + fileToString(taskFile), e); taskFile.delete(); } } return new RecentTaskFiles(recentFiles, taskFiles); } /** Restores tasks from raw bytes (no read storage operation). */ ArrayList<Task> restoreTasksForUserLocked(int userId, RecentTaskFiles recentTaskFiles, IntArray existedTaskIds) { final ArrayList<Task> tasks = new ArrayList<>(); final ArrayList<RecentTaskFile> taskFiles = recentTaskFiles.mLoadedFiles; if (taskFiles.isEmpty()) { return tasks; } final ArraySet<Integer> recoveredTaskIds = new ArraySet<>(); for (int taskNdx = 0; taskNdx < taskFiles.size(); ++taskNdx) { final RecentTaskFile recentTask = taskFiles.get(taskNdx); if (existedTaskIds.contains(recentTask.mTaskId)) { Slog.w(TAG, "Task #" + recentTask.mTaskId + " has already been created, so skip restoring"); continue; } final File taskFile = recentTask.mFile; if (DEBUG) { Slog.d(TAG, "restoreTasksForUserLocked: userId=" + userId + ", taskFile=" + taskFile.getName()); } boolean deleteFile = false; try (InputStream is = new FileInputStream(taskFile)) { try (InputStream is = recentTask.mXmlContent) { final TypedXmlPullParser in = Xml.resolvePullParser(is); int event; Loading Loading @@ -345,7 +367,7 @@ public class TaskPersister implements PersisterQueue.Listener { } else if (userId != task.mUserId) { // Should not happen. Slog.wtf(TAG, "Task with userId " + task.mUserId + " found in " + userTasksDir.getAbsolutePath()); + taskFile.getAbsolutePath()); } else { // Looks fine. mTaskSupervisor.setNextTaskIdForUser(taskId, userId); Loading Loading @@ -377,7 +399,7 @@ public class TaskPersister implements PersisterQueue.Listener { } if (!DEBUG) { removeObsoleteFiles(recoveredTaskIds, userTasksDir.listFiles()); removeObsoleteFiles(recoveredTaskIds, recentTaskFiles.mUserTaskFiles); } // Fix up task affiliation from taskIds Loading Loading @@ -456,7 +478,7 @@ public class TaskPersister implements PersisterQueue.Listener { SparseArray<SparseBooleanArray> changedTaskIdsPerUser = new SparseArray<>(); synchronized (mService.mGlobalLock) { for (int userId : mRecentTasks.usersWithRecentsLoadedLocked()) { SparseBooleanArray taskIdsToSave = mRecentTasks.getTaskIdsForUser(userId); SparseBooleanArray taskIdsToSave = mRecentTasks.getTaskIdsForLoadedUser(userId); SparseBooleanArray persistedIdsInFile = mTaskIdsInFile.get(userId); if (persistedIdsInFile != null && persistedIdsInFile.equals(taskIdsToSave)) { continue; Loading Loading @@ -512,6 +534,30 @@ public class TaskPersister implements PersisterQueue.Listener { return parentDir.isDirectory() || parentDir.mkdir(); } private static class RecentTaskFile { final int mTaskId; final File mFile; final ByteArrayInputStream mXmlContent; RecentTaskFile(int taskId, File file) throws IOException { mTaskId = taskId; mFile = file; mXmlContent = new ByteArrayInputStream(Files.readAllBytes(file.toPath())); } } static class RecentTaskFiles { /** All files under the user task directory. */ final File[] mUserTaskFiles; /** The successfully loaded files. */ final ArrayList<RecentTaskFile> mLoadedFiles; RecentTaskFiles(File[] userFiles, ArrayList<RecentTaskFile> loadedFiles) { mUserTaskFiles = userFiles; mLoadedFiles = loadedFiles; } } private static class TaskWriteQueueItem implements PersisterQueue.WriteQueueItem { private final ActivityTaskManagerService mService; private final Task mTask; Loading services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +14 −15 Original line number Diff line number Diff line Loading @@ -46,7 +46,6 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; Loading @@ -73,6 +72,7 @@ import android.os.SystemClock; import android.os.UserManager; import android.platform.test.annotations.Presubmit; import android.util.ArraySet; import android.util.IntArray; import android.util.SparseBooleanArray; import android.view.Surface; import android.window.TaskSnapshot; Loading Loading @@ -527,20 +527,20 @@ public class RecentTasksTest extends WindowTestsBase { mTaskPersister.mUserTaskIdsOverride.put(1, true); mTaskPersister.mUserTaskIdsOverride.put(2, true); mTaskPersister.mUserTasksOverride = new ArrayList<>(); mTaskPersister.mUserTasksOverride.add(createTaskBuilder(".UserTask1").build()); mTaskPersister.mUserTasksOverride.add(createTaskBuilder(".UserTask2").build()); mTaskPersister.mUserTasksOverride.add(mTasks.get(0)); mTaskPersister.mUserTasksOverride.add(mTasks.get(1)); // Assert no user tasks are initially loaded assertThat(mRecentTasks.usersWithRecentsLoadedLocked()).hasLength(0); // Load user 0 tasks mRecentTasks.loadUserRecentsLocked(TEST_USER_0_ID); mRecentTasks.loadRecentTasksIfNeeded(TEST_USER_0_ID); assertThat(mRecentTasks.usersWithRecentsLoadedLocked()).asList().contains(TEST_USER_0_ID); assertTrue(mRecentTasks.containsTaskId(1, TEST_USER_0_ID)); assertTrue(mRecentTasks.containsTaskId(2, TEST_USER_0_ID)); // Load user 1 tasks mRecentTasks.loadUserRecentsLocked(TEST_USER_1_ID); mRecentTasks.loadRecentTasksIfNeeded(TEST_USER_1_ID); assertThat(mRecentTasks.usersWithRecentsLoadedLocked()).asList().contains(TEST_USER_0_ID); assertThat(mRecentTasks.usersWithRecentsLoadedLocked()).asList().contains(TEST_USER_1_ID); assertTrue(mRecentTasks.containsTaskId(1, TEST_USER_0_ID)); Loading Loading @@ -575,15 +575,15 @@ public class RecentTasksTest extends WindowTestsBase { mTaskPersister.mUserTaskIdsOverride.put(2, true); mTaskPersister.mUserTaskIdsOverride.put(3, true); mTaskPersister.mUserTasksOverride = new ArrayList<>(); mTaskPersister.mUserTasksOverride.add(createTaskBuilder(".UserTask1").build()); mTaskPersister.mUserTasksOverride.add(createTaskBuilder(".UserTask2").build()); mTaskPersister.mUserTasksOverride.add(createTaskBuilder(".UserTask3").build()); mTaskPersister.mUserTasksOverride.add(mTasks.get(0)); mTaskPersister.mUserTasksOverride.add(mTasks.get(1)); mTaskPersister.mUserTasksOverride.add(mTasks.get(2)); // Assert no user tasks are initially loaded assertThat(mRecentTasks.usersWithRecentsLoadedLocked()).hasLength(0); // Load tasks mRecentTasks.loadUserRecentsLocked(TEST_USER_0_ID); mRecentTasks.loadRecentTasksIfNeeded(TEST_USER_0_ID); assertThat(mRecentTasks.usersWithRecentsLoadedLocked()).asList().contains(TEST_USER_0_ID); // Sort the time descendingly so the order should be in-sync with task recency (most Loading Loading @@ -1419,8 +1419,6 @@ public class RecentTasksTest extends WindowTestsBase { } private List<RecentTaskInfo> getRecentTasks(int flags) { doNothing().when(mRecentTasks).loadUserRecentsLocked(anyInt()); doReturn(true).when(mRecentTasks).isUserRunning(anyInt(), anyInt()); return mRecentTasks.getRecentTasks(MAX_VALUE, flags, true /* getTasksAllowed */, TEST_USER_0_ID, 0 /* callingUid */).getList(); } Loading Loading @@ -1590,19 +1588,20 @@ public class RecentTasksTest extends WindowTestsBase { } @Override SparseBooleanArray loadPersistedTaskIdsForUser(int userId) { SparseBooleanArray readPersistedTaskIdsFromFileForUser(int userId) { if (mUserTaskIdsOverride != null) { return mUserTaskIdsOverride; } return super.loadPersistedTaskIdsForUser(userId); return super.readPersistedTaskIdsFromFileForUser(userId); } @Override List<Task> restoreTasksForUserLocked(int userId, SparseBooleanArray preaddedTasks) { ArrayList<Task> restoreTasksForUserLocked(int userId, RecentTaskFiles recentTaskFiles, IntArray existedTaskIds) { if (mUserTasksOverride != null) { return mUserTasksOverride; } return super.restoreTasksForUserLocked(userId, preaddedTasks); return super.restoreTasksForUserLocked(userId, recentTaskFiles, existedTaskIds); } } Loading services/tests/wmtests/src/com/android/server/wm/TaskPersisterTest.java +1 −3 Original line number Diff line number Diff line Loading @@ -29,8 +29,6 @@ import android.os.UserManager; import android.platform.test.annotations.Presubmit; import android.util.SparseBooleanArray; import androidx.test.filters.FlakyTest; import org.junit.After; import org.junit.Before; import org.junit.Test; Loading Loading @@ -81,7 +79,7 @@ public class TaskPersisterTest { } mTaskPersister.writePersistedTaskIdsForUser(taskIdsOnFile, mTestUserId); SparseBooleanArray newTaskIdsOnFile = mTaskPersister .loadPersistedTaskIdsForUser(mTestUserId); .readPersistedTaskIdsFromFileForUser(mTestUserId); assertEquals("TaskIds written differ from TaskIds read back from file", taskIdsOnFile, newTaskIdsOnFile); } Loading Loading
services/core/java/com/android/server/wm/ActivityTaskManagerService.java +8 −5 Original line number Diff line number Diff line Loading @@ -2505,6 +2505,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { userId = handleIncomingUser(Binder.getCallingPid(), callingUid, userId, "getRecentTasks"); final boolean allowed = isGetTasksAllowed("getRecentTasks", Binder.getCallingPid(), callingUid); if (!mAmInternal.isUserRunning(userId, ActivityManager.FLAG_AND_UNLOCKED)) { Slog.i(TAG, "User " + userId + " is locked. Cannot load recents"); return ParceledListSlice.emptyList(); } mRecentTasks.loadRecentTasksIfNeeded(userId); synchronized (mGlobalLock) { return mRecentTasks.getRecentTasks(maxNum, flags, allowed, userId, callingUid); } Loading Loading @@ -7056,12 +7061,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Override public void loadRecentTasksForUser(int userId) { synchronized (mGlobalLock) { mRecentTasks.loadUserRecentsLocked(userId); // TODO renaming the methods(?) // This runs on android.fg thread when the user is unlocking. mRecentTasks.loadRecentTasksIfNeeded(userId); mPackageConfigPersister.loadUserPackages(userId); } } @Override public void onPackagesSuspendedChanged(String[] packages, boolean suspended, int userId) { Loading
services/core/java/com/android/server/wm/RecentTasks.java +58 −52 Original line number Diff line number Diff line Loading @@ -16,7 +16,6 @@ package com.android.server.wm; import static android.app.ActivityManager.FLAG_AND_UNLOCKED; import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE; import static android.app.ActivityManager.RECENT_WITH_EXCLUDED; import static android.app.ActivityTaskManager.INVALID_TASK_ID; Loading Loading @@ -69,6 +68,7 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.text.TextUtils; import android.util.ArraySet; import android.util.IntArray; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; Loading @@ -89,9 +89,9 @@ import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; /** * Class for managing the recent tasks list. The list is ordered by most recent (index 0) to the Loading Loading @@ -167,8 +167,9 @@ class RecentTasks { /** * Mapping of user id -> whether recent tasks have been loaded for that user. * The AtomicBoolean per user will be locked when reading persisted task from storage. */ private final SparseBooleanArray mUsersWithRecentsLoaded = new SparseBooleanArray( private final SparseArray<AtomicBoolean> mUsersWithRecentsLoaded = new SparseArray<>( DEFAULT_INITIAL_CAPACITY); /** Loading Loading @@ -481,29 +482,49 @@ class RecentTasks { /** * Loads the persistent recentTasks for {@code userId} into this list from persistent storage. * Does nothing if they are already loaded. * * @param userId the user Id * Does nothing if they are already loaded. This may perform IO operation, so the caller should * not hold a lock. */ void loadUserRecentsLocked(int userId) { if (mUsersWithRecentsLoaded.get(userId)) { // User already loaded, return early void loadRecentTasksIfNeeded(int userId) { AtomicBoolean userLoaded; synchronized (mService.mGlobalLock) { userLoaded = mUsersWithRecentsLoaded.get(userId); if (userLoaded == null) { mUsersWithRecentsLoaded.append(userId, userLoaded = new AtomicBoolean()); } } synchronized (userLoaded) { if (userLoaded.get()) { // The recent tasks of the user are already loaded. return; } // Read task files from storage. final SparseBooleanArray persistedTaskIds = mTaskPersister.readPersistedTaskIdsFromFileForUser(userId); final TaskPersister.RecentTaskFiles taskFiles = TaskPersister.loadTasksForUser(userId); synchronized (mService.mGlobalLock) { restoreRecentTasksLocked(userId, persistedTaskIds, taskFiles); } userLoaded.set(true); } } // Load the task ids if not loaded. loadPersistedTaskIdsForUserLocked(userId); // Check if any tasks are added before recents is loaded final SparseBooleanArray preaddedTasks = new SparseBooleanArray(); for (final Task task : mTasks) { /** Restores recent tasks from raw data (the files are already read into memory). */ private void restoreRecentTasksLocked(int userId, SparseBooleanArray persistedTaskIds, TaskPersister.RecentTaskFiles taskFiles) { mTaskPersister.setPersistedTaskIds(userId, persistedTaskIds); mPersistedTaskIds.put(userId, persistedTaskIds.clone()); // Check if any tasks are added before recents is loaded. final IntArray existedTaskIds = new IntArray(); for (int i = mTasks.size() - 1; i >= 0; i--) { final Task task = mTasks.get(i); if (task.mUserId == userId && shouldPersistTaskLocked(task)) { preaddedTasks.put(task.mTaskId, true); existedTaskIds.add(task.mTaskId); } } Slog.i(TAG, "Loading recents for user " + userId + " into memory."); List<Task> tasks = mTaskPersister.restoreTasksForUserLocked(userId, preaddedTasks); Slog.i(TAG, "Restoring recents for user " + userId); final ArrayList<Task> tasks = mTaskPersister.restoreTasksForUserLocked(userId, taskFiles, existedTaskIds); // Tasks are ordered from most recent to least recent. Update the last active time to be // in sync with task recency when device reboots, so the most recent task has the Loading @@ -516,37 +537,34 @@ class RecentTasks { mTasks.addAll(tasks); cleanupLocked(userId); mUsersWithRecentsLoaded.put(userId, true); // If we have tasks added before loading recents, we need to update persistent task IDs. if (preaddedTasks.size() > 0) { if (existedTaskIds.size() > 0) { syncPersistentTaskIdsLocked(); } } 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)); Slog.i(TAG, "Loaded persisted task ids for user " + userId); } private boolean isRecentTasksLoaded(int userId) { final AtomicBoolean userLoaded = mUsersWithRecentsLoaded.get(userId); return userLoaded != null && userLoaded.get(); } /** * @return whether the {@param taskId} is currently in use for the given user. */ boolean containsTaskId(int taskId, int userId) { loadPersistedTaskIdsForUserLocked(userId); return mPersistedTaskIds.get(userId).get(taskId); final SparseBooleanArray taskIds = mPersistedTaskIds.get(userId); return taskIds != null && taskIds.get(taskId); } /** * @return all the task ids for the user with the given {@param userId}. */ SparseBooleanArray getTaskIdsForUser(int userId) { loadPersistedTaskIdsForUserLocked(userId); return mPersistedTaskIds.get(userId); /** Returns all the task ids for the user from {@link #usersWithRecentsLoadedLocked}. */ SparseBooleanArray getTaskIdsForLoadedUser(int loadedUserId) { final SparseBooleanArray taskIds = mPersistedTaskIds.get(loadedUserId); if (taskIds == null) { Slog.wtf(TAG, "Loaded user without loaded tasks, userId=" + loadedUserId); return new SparseBooleanArray(); } return taskIds; } /** Loading @@ -565,7 +583,7 @@ class RecentTasks { private void syncPersistentTaskIdsLocked() { for (int i = mPersistedTaskIds.size() - 1; i >= 0; i--) { int userId = mPersistedTaskIds.keyAt(i); if (mUsersWithRecentsLoaded.get(userId)) { if (isRecentTasksLoaded(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(); Loading Loading @@ -621,7 +639,7 @@ class RecentTasks { int len = 0; for (int i = 0; i < usersWithRecentsLoaded.length; i++) { int userId = mUsersWithRecentsLoaded.keyAt(i); if (mUsersWithRecentsLoaded.valueAt(i)) { if (mUsersWithRecentsLoaded.valueAt(i).get()) { usersWithRecentsLoaded[len++] = userId; } } Loading @@ -639,7 +657,7 @@ class RecentTasks { * @param userId the id of the user */ void unloadUserDataFromMemoryLocked(int userId) { if (mUsersWithRecentsLoaded.get(userId)) { if (isRecentTasksLoaded(userId)) { Slog.i(TAG, "Unloading recents for user " + userId + " from memory."); mUsersWithRecentsLoaded.delete(userId); removeTasksForUserLocked(userId); Loading Loading @@ -922,11 +940,6 @@ class RecentTasks { return mService.mAmInternal.getCurrentProfileIds(); } @VisibleForTesting boolean isUserRunning(int userId, int flags) { return mService.mAmInternal.isUserRunning(userId, flags); } /** * @return the list of recent tasks for presentation. */ Loading @@ -942,13 +955,6 @@ class RecentTasks { private ArrayList<ActivityManager.RecentTaskInfo> getRecentTasksImpl(int maxNum, int flags, boolean getTasksAllowed, int userId, int callingUid) { final boolean withExcluded = (flags & RECENT_WITH_EXCLUDED) != 0; if (!isUserRunning(userId, FLAG_AND_UNLOCKED)) { Slog.i(TAG, "user " + userId + " is still locked. Cannot load recents"); return new ArrayList<>(); } loadUserRecentsLocked(userId); final Set<Integer> includedUsers = getProfileIds(userId); includedUsers.add(Integer.valueOf(userId)); Loading
services/core/java/com/android/server/wm/TaskPersister.java +83 −37 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import android.os.FileUtils; import android.os.SystemClock; import android.util.ArraySet; import android.util.AtomicFile; import android.util.IntArray; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; Loading @@ -43,20 +44,20 @@ import org.xmlpull.v1.XmlPullParser; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; /** * Persister that saves recent tasks into disk. Loading Loading @@ -129,11 +130,9 @@ public class TaskPersister implements PersisterQueue.Listener { ImageWriteQueueItem.class); } /** Reads task ids from file. This should not be called in lock. */ @NonNull SparseBooleanArray loadPersistedTaskIdsForUser(int userId) { if (mTaskIdsInFile.get(userId) != null) { return mTaskIdsInFile.get(userId).clone(); } SparseBooleanArray readPersistedTaskIdsFromFileForUser(int userId) { final SparseBooleanArray persistedTaskIds = new SparseBooleanArray(); synchronized (mIoLock) { BufferedReader reader = null; Loading @@ -154,11 +153,10 @@ public class TaskPersister implements PersisterQueue.Listener { IoUtils.closeQuietly(reader); } } mTaskIdsInFile.put(userId, persistedTaskIds); return persistedTaskIds.clone(); Slog.i(TAG, "Loaded persisted task ids for user " + userId); return persistedTaskIds; } @VisibleForTesting void writePersistedTaskIdsForUser(@NonNull SparseBooleanArray taskIds, int userId) { if (userId < 0) { Loading @@ -183,6 +181,10 @@ public class TaskPersister implements PersisterQueue.Listener { } } void setPersistedTaskIds(int userId, @NonNull SparseBooleanArray taskIds) { mTaskIdsInFile.put(userId, taskIds); } void unloadUserDataFromMemory(int userId) { mTaskIdsInFile.delete(userId); } Loading Loading @@ -241,7 +243,7 @@ public class TaskPersister implements PersisterQueue.Listener { return item != null ? item.mImage : null; } private String fileToString(File file) { private static String fileToString(File file) { final String newline = System.lineSeparator(); try { BufferedReader reader = new BufferedReader(new FileReader(file)); Loading Loading @@ -272,44 +274,64 @@ public class TaskPersister implements PersisterQueue.Listener { return null; } List<Task> restoreTasksForUserLocked(final int userId, SparseBooleanArray preaddedTasks) { final ArrayList<Task> tasks = new ArrayList<Task>(); ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>(); File userTasksDir = getUserTasksDir(userId); File[] recentFiles = userTasksDir.listFiles(); /** Loads task files from disk. This should not be called in lock. */ static RecentTaskFiles loadTasksForUser(int userId) { final ArrayList<RecentTaskFile> taskFiles = new ArrayList<>(); final File userTasksDir = getUserTasksDir(userId); final File[] recentFiles = userTasksDir.listFiles(); if (recentFiles == null) { 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, "restoreTasksForUserLocked: userId=" + userId + ", taskFile=" + taskFile.getName()); Slog.i(TAG, "loadTasksForUser: Unable to list files from " + userTasksDir + " exists=" + userTasksDir.exists()); return new RecentTaskFiles(new File[0], taskFiles); } for (File taskFile : recentFiles) { if (!taskFile.getName().endsWith(TASK_FILENAME_SUFFIX)) { continue; } final int taskId; try { final int taskId = Integer.parseInt(taskFile.getName().substring( taskId = Integer.parseInt(taskFile.getName().substring( 0 /* beginIndex */, taskFile.getName().length() - TASK_FILENAME_SUFFIX.length())); if (preaddedTasks.get(taskId, false)) { Slog.w(TAG, "Task #" + taskId + " has already been created so we don't restore again"); continue; } } catch (NumberFormatException e) { Slog.w(TAG, "Unexpected task file name", e); continue; } try { taskFiles.add(new RecentTaskFile(taskId, taskFile)); } catch (IOException e) { Slog.w(TAG, "Failed to read file: " + fileToString(taskFile), e); taskFile.delete(); } } return new RecentTaskFiles(recentFiles, taskFiles); } /** Restores tasks from raw bytes (no read storage operation). */ ArrayList<Task> restoreTasksForUserLocked(int userId, RecentTaskFiles recentTaskFiles, IntArray existedTaskIds) { final ArrayList<Task> tasks = new ArrayList<>(); final ArrayList<RecentTaskFile> taskFiles = recentTaskFiles.mLoadedFiles; if (taskFiles.isEmpty()) { return tasks; } final ArraySet<Integer> recoveredTaskIds = new ArraySet<>(); for (int taskNdx = 0; taskNdx < taskFiles.size(); ++taskNdx) { final RecentTaskFile recentTask = taskFiles.get(taskNdx); if (existedTaskIds.contains(recentTask.mTaskId)) { Slog.w(TAG, "Task #" + recentTask.mTaskId + " has already been created, so skip restoring"); continue; } final File taskFile = recentTask.mFile; if (DEBUG) { Slog.d(TAG, "restoreTasksForUserLocked: userId=" + userId + ", taskFile=" + taskFile.getName()); } boolean deleteFile = false; try (InputStream is = new FileInputStream(taskFile)) { try (InputStream is = recentTask.mXmlContent) { final TypedXmlPullParser in = Xml.resolvePullParser(is); int event; Loading Loading @@ -345,7 +367,7 @@ public class TaskPersister implements PersisterQueue.Listener { } else if (userId != task.mUserId) { // Should not happen. Slog.wtf(TAG, "Task with userId " + task.mUserId + " found in " + userTasksDir.getAbsolutePath()); + taskFile.getAbsolutePath()); } else { // Looks fine. mTaskSupervisor.setNextTaskIdForUser(taskId, userId); Loading Loading @@ -377,7 +399,7 @@ public class TaskPersister implements PersisterQueue.Listener { } if (!DEBUG) { removeObsoleteFiles(recoveredTaskIds, userTasksDir.listFiles()); removeObsoleteFiles(recoveredTaskIds, recentTaskFiles.mUserTaskFiles); } // Fix up task affiliation from taskIds Loading Loading @@ -456,7 +478,7 @@ public class TaskPersister implements PersisterQueue.Listener { SparseArray<SparseBooleanArray> changedTaskIdsPerUser = new SparseArray<>(); synchronized (mService.mGlobalLock) { for (int userId : mRecentTasks.usersWithRecentsLoadedLocked()) { SparseBooleanArray taskIdsToSave = mRecentTasks.getTaskIdsForUser(userId); SparseBooleanArray taskIdsToSave = mRecentTasks.getTaskIdsForLoadedUser(userId); SparseBooleanArray persistedIdsInFile = mTaskIdsInFile.get(userId); if (persistedIdsInFile != null && persistedIdsInFile.equals(taskIdsToSave)) { continue; Loading Loading @@ -512,6 +534,30 @@ public class TaskPersister implements PersisterQueue.Listener { return parentDir.isDirectory() || parentDir.mkdir(); } private static class RecentTaskFile { final int mTaskId; final File mFile; final ByteArrayInputStream mXmlContent; RecentTaskFile(int taskId, File file) throws IOException { mTaskId = taskId; mFile = file; mXmlContent = new ByteArrayInputStream(Files.readAllBytes(file.toPath())); } } static class RecentTaskFiles { /** All files under the user task directory. */ final File[] mUserTaskFiles; /** The successfully loaded files. */ final ArrayList<RecentTaskFile> mLoadedFiles; RecentTaskFiles(File[] userFiles, ArrayList<RecentTaskFile> loadedFiles) { mUserTaskFiles = userFiles; mLoadedFiles = loadedFiles; } } private static class TaskWriteQueueItem implements PersisterQueue.WriteQueueItem { private final ActivityTaskManagerService mService; private final Task mTask; Loading
services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +14 −15 Original line number Diff line number Diff line Loading @@ -46,7 +46,6 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; Loading @@ -73,6 +72,7 @@ import android.os.SystemClock; import android.os.UserManager; import android.platform.test.annotations.Presubmit; import android.util.ArraySet; import android.util.IntArray; import android.util.SparseBooleanArray; import android.view.Surface; import android.window.TaskSnapshot; Loading Loading @@ -527,20 +527,20 @@ public class RecentTasksTest extends WindowTestsBase { mTaskPersister.mUserTaskIdsOverride.put(1, true); mTaskPersister.mUserTaskIdsOverride.put(2, true); mTaskPersister.mUserTasksOverride = new ArrayList<>(); mTaskPersister.mUserTasksOverride.add(createTaskBuilder(".UserTask1").build()); mTaskPersister.mUserTasksOverride.add(createTaskBuilder(".UserTask2").build()); mTaskPersister.mUserTasksOverride.add(mTasks.get(0)); mTaskPersister.mUserTasksOverride.add(mTasks.get(1)); // Assert no user tasks are initially loaded assertThat(mRecentTasks.usersWithRecentsLoadedLocked()).hasLength(0); // Load user 0 tasks mRecentTasks.loadUserRecentsLocked(TEST_USER_0_ID); mRecentTasks.loadRecentTasksIfNeeded(TEST_USER_0_ID); assertThat(mRecentTasks.usersWithRecentsLoadedLocked()).asList().contains(TEST_USER_0_ID); assertTrue(mRecentTasks.containsTaskId(1, TEST_USER_0_ID)); assertTrue(mRecentTasks.containsTaskId(2, TEST_USER_0_ID)); // Load user 1 tasks mRecentTasks.loadUserRecentsLocked(TEST_USER_1_ID); mRecentTasks.loadRecentTasksIfNeeded(TEST_USER_1_ID); assertThat(mRecentTasks.usersWithRecentsLoadedLocked()).asList().contains(TEST_USER_0_ID); assertThat(mRecentTasks.usersWithRecentsLoadedLocked()).asList().contains(TEST_USER_1_ID); assertTrue(mRecentTasks.containsTaskId(1, TEST_USER_0_ID)); Loading Loading @@ -575,15 +575,15 @@ public class RecentTasksTest extends WindowTestsBase { mTaskPersister.mUserTaskIdsOverride.put(2, true); mTaskPersister.mUserTaskIdsOverride.put(3, true); mTaskPersister.mUserTasksOverride = new ArrayList<>(); mTaskPersister.mUserTasksOverride.add(createTaskBuilder(".UserTask1").build()); mTaskPersister.mUserTasksOverride.add(createTaskBuilder(".UserTask2").build()); mTaskPersister.mUserTasksOverride.add(createTaskBuilder(".UserTask3").build()); mTaskPersister.mUserTasksOverride.add(mTasks.get(0)); mTaskPersister.mUserTasksOverride.add(mTasks.get(1)); mTaskPersister.mUserTasksOverride.add(mTasks.get(2)); // Assert no user tasks are initially loaded assertThat(mRecentTasks.usersWithRecentsLoadedLocked()).hasLength(0); // Load tasks mRecentTasks.loadUserRecentsLocked(TEST_USER_0_ID); mRecentTasks.loadRecentTasksIfNeeded(TEST_USER_0_ID); assertThat(mRecentTasks.usersWithRecentsLoadedLocked()).asList().contains(TEST_USER_0_ID); // Sort the time descendingly so the order should be in-sync with task recency (most Loading Loading @@ -1419,8 +1419,6 @@ public class RecentTasksTest extends WindowTestsBase { } private List<RecentTaskInfo> getRecentTasks(int flags) { doNothing().when(mRecentTasks).loadUserRecentsLocked(anyInt()); doReturn(true).when(mRecentTasks).isUserRunning(anyInt(), anyInt()); return mRecentTasks.getRecentTasks(MAX_VALUE, flags, true /* getTasksAllowed */, TEST_USER_0_ID, 0 /* callingUid */).getList(); } Loading Loading @@ -1590,19 +1588,20 @@ public class RecentTasksTest extends WindowTestsBase { } @Override SparseBooleanArray loadPersistedTaskIdsForUser(int userId) { SparseBooleanArray readPersistedTaskIdsFromFileForUser(int userId) { if (mUserTaskIdsOverride != null) { return mUserTaskIdsOverride; } return super.loadPersistedTaskIdsForUser(userId); return super.readPersistedTaskIdsFromFileForUser(userId); } @Override List<Task> restoreTasksForUserLocked(int userId, SparseBooleanArray preaddedTasks) { ArrayList<Task> restoreTasksForUserLocked(int userId, RecentTaskFiles recentTaskFiles, IntArray existedTaskIds) { if (mUserTasksOverride != null) { return mUserTasksOverride; } return super.restoreTasksForUserLocked(userId, preaddedTasks); return super.restoreTasksForUserLocked(userId, recentTaskFiles, existedTaskIds); } } Loading
services/tests/wmtests/src/com/android/server/wm/TaskPersisterTest.java +1 −3 Original line number Diff line number Diff line Loading @@ -29,8 +29,6 @@ import android.os.UserManager; import android.platform.test.annotations.Presubmit; import android.util.SparseBooleanArray; import androidx.test.filters.FlakyTest; import org.junit.After; import org.junit.Before; import org.junit.Test; Loading Loading @@ -81,7 +79,7 @@ public class TaskPersisterTest { } mTaskPersister.writePersistedTaskIdsForUser(taskIdsOnFile, mTestUserId); SparseBooleanArray newTaskIdsOnFile = mTaskPersister .loadPersistedTaskIdsForUser(mTestUserId); .readPersistedTaskIdsFromFileForUser(mTestUserId); assertEquals("TaskIds written differ from TaskIds read back from file", taskIdsOnFile, newTaskIdsOnFile); } Loading