Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7361babf authored by Jorim Jaggi's avatar Jorim Jaggi
Browse files

Implement restoring & correct caching of snapshots

Introduce a retrieval cache that holds the last accessed
snapshots, in addition to the cache of the activities
that are the top most activtiy of a task that have a
running process.

Change everything to use an integer id instead of a Task object
to work around the issue that some tasks SystemUI might access
might not exist in WM yet (not yet restored from recents).

Don't put anything in the cache on the SystemUI side, but still
retrieve the thumbnails after a task changed event to make sure
the cache on the system_server side is fresh.

Test: runtest frameworks-services -c
com.android.server.wm.TaskSnapshotCacheTest

Bug: 31339431
Change-Id: I8e56365459a677735320adaa169da8fb033ceab0
parent cc9224c8
Loading
Loading
Loading
Loading
+5 −2
Original line number Diff line number Diff line
@@ -209,7 +209,8 @@ class BackgroundTaskLoader implements Runnable {
                            // When svelte, we trim the memory to just the visible thumbnails when
                            // leaving, so don't thrash the cache as the user scrolls (just load
                            // them from scratch each time)
                            if (config.svelteLevel < RecentsConfiguration.SVELTE_LIMIT_CACHE) {
                            if (config.svelteLevel < RecentsConfiguration.SVELTE_LIMIT_CACHE
                                    && !ActivityManager.ENABLE_TASK_SNAPSHOTS) {
                                mThumbnailCache.put(t.key, cachedThumbnailData);
                            }
                        }
@@ -553,7 +554,9 @@ public class RecentsTaskLoader {
                // Load the thumbnail from the system
                thumbnailData = ssp.getTaskThumbnail(taskKey.id);
                if (thumbnailData.thumbnail != null) {
                    if (!ActivityManager.ENABLE_TASK_SNAPSHOTS) {
                        mThumbnailCache.put(taskKey, thumbnailData);
                    }
                    return thumbnailData.thumbnail;
                }
            }
+4 −2
Original line number Diff line number Diff line
@@ -9781,15 +9781,17 @@ public class ActivityManagerService extends IActivityManager.Stub
        enforceCallingPermission(READ_FRAME_BUFFER, "getTaskSnapshot()");
        final long ident = Binder.clearCallingIdentity();
        try {
            final TaskRecord task;
            synchronized (this) {
                final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(
                task = mStackSupervisor.anyTaskForIdLocked(
                        taskId, !RESTORE_FROM_RECENTS, INVALID_STACK_ID);
                if (task == null) {
                    Slog.w(TAG, "getTaskSnapshot: taskId=" + taskId + " not found");
                    return null;
                }
                return task.getSnapshot();
            }
            // Don't call this while holding the lock as this operation might hit the disk.
            return task.getSnapshot();
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
+8 −5
Original line number Diff line number Diff line
@@ -583,11 +583,14 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta
        mWindowContainerController.cancelThumbnailTransition();
    }

    public TaskSnapshot getSnapshot() {
        if (mWindowContainerController == null) {
            return null;
        }
        return mWindowContainerController.getSnapshot();
    /**
     * DO NOT HOLD THE ACTIVITY MANAGER LOCK WHEN CALLING THIS METHOD!
     */
    TaskSnapshot getSnapshot() {

        // TODO: Move this to {@link TaskWindowContainerController} once recent tasks are more
        // synchronized between AM and WM.
        return mService.mWindowManager.getTaskSnapshot(taskId, userId);
    }

    void touchActiveTime() {
+1 −1
Original line number Diff line number Diff line
@@ -492,7 +492,7 @@ public class AppWindowContainerController

    private boolean createSnapshot() {
        final TaskSnapshot snapshot = mService.mTaskSnapshotController.getSnapshot(
                mContainer.mTask);
                mContainer.mTask.mTaskId, mContainer.mTask.mUserId, false /* restoreFromDisk */);

        if (snapshot == null) {
            return false;
+98 −21
Original line number Diff line number Diff line
@@ -19,8 +19,11 @@ package com.android.server.wm;
import android.annotation.Nullable;
import android.app.ActivityManager.TaskSnapshot;
import android.util.ArrayMap;
import android.util.LruCache;

import java.io.PrintWriter;
import java.util.Map;
import java.util.Map.Entry;

/**
 * Caches snapshots. See {@link TaskSnapshotController}.
@@ -29,51 +32,125 @@ import java.io.PrintWriter;
 */
class TaskSnapshotCache {

    private final ArrayMap<AppWindowToken, Task> mAppTaskMap = new ArrayMap<>();
    private final ArrayMap<Task, CacheEntry> mCache = new ArrayMap<>();
    // TODO: Make this more dynamic to accomodate for different clients.
    private static final int RETRIEVAL_CACHE_SIZE = 4;

    private final WindowManagerService mService;
    private final TaskSnapshotLoader mLoader;
    private final ArrayMap<AppWindowToken, Integer> mAppTaskMap = new ArrayMap<>();
    private final ArrayMap<Integer, CacheEntry> mRunningCache = new ArrayMap<>();
    private final LruCache<Integer, TaskSnapshot> mRetrievalCache =
            new LruCache<>(RETRIEVAL_CACHE_SIZE);

    TaskSnapshotCache(WindowManagerService service, TaskSnapshotLoader loader) {
        mService = service;
        mLoader = loader;
    }

    void putSnapshot(Task task, TaskSnapshot snapshot) {
        final CacheEntry entry = mCache.get(task);
        final CacheEntry entry = mRunningCache.get(task.mTaskId);
        if (entry != null) {
            mAppTaskMap.remove(entry.topApp);
        }
        final AppWindowToken top = task.getTopChild();
        mAppTaskMap.put(top, task);
        mCache.put(task, new CacheEntry(snapshot, task.getTopChild()));
        mAppTaskMap.put(top, task.mTaskId);
        mRunningCache.put(task.mTaskId, new CacheEntry(snapshot, task.getTopChild()));
        mRetrievalCache.put(task.mTaskId, snapshot);
    }

    /**
     * If {@param restoreFromDisk} equals {@code true}, DO NOT HOLD THE WINDOW MANAGER LOCK!
     */
    @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk) {

        synchronized (mService.mWindowMap) {
            // Try the running cache.
            final CacheEntry entry = mRunningCache.get(taskId);
            if (entry != null) {
                return entry.snapshot;
            }

            // Try the retrieval cache.
            final TaskSnapshot snapshot = mRetrievalCache.get(taskId);
            if (snapshot != null) {
                return snapshot;
            }
        }

        // Try to restore from disk if asked.
        if (!restoreFromDisk) {
            return null;
        }
        return tryRestoreFromDisk(taskId, userId);
    }

    /**
     * DO NOT HOLD THE WINDOW MANAGER LOCK WHEN CALLING THIS METHOD!
     */
    private TaskSnapshot tryRestoreFromDisk(int taskId, int userId) {
        final TaskSnapshot snapshot = mLoader.loadTask(taskId, userId);
        if (snapshot == null) {
            return null;
        }
        synchronized (mService.mWindowMap) {
            mRetrievalCache.put(taskId, snapshot);
        }
        return snapshot;
    }

    @Nullable TaskSnapshot getSnapshot(Task task) {
        final CacheEntry entry = mCache.get(task);
        return entry != null ? entry.snapshot : null;
    /**
     * Called when an app token has been removed
     */
    void onAppRemoved(AppWindowToken wtoken) {
        final Integer taskId = mAppTaskMap.get(wtoken);
        if (taskId != null) {
            removeRunningEntry(taskId);
        }
        if (wtoken.mTask != null) {
            mRetrievalCache.remove(wtoken.mTask.mTaskId);
        }
    }

    /**
     * Cleans the cache after an app window token's process died.
     * Callend when an app window token's process died.
     */
    void cleanCache(AppWindowToken wtoken) {
        final Task task = mAppTaskMap.get(wtoken);
        if (task != null) {
            removeEntry(task);
    void onAppDied(AppWindowToken wtoken) {
        final Integer taskId = mAppTaskMap.get(wtoken);
        if (taskId != null) {
            removeRunningEntry(taskId);
        }
    }

    private void removeEntry(Task task) {
        final CacheEntry entry = mCache.get(task);
    void onTaskRemoved(int taskId) {
        removeRunningEntry(taskId);
        mRetrievalCache.remove(taskId);
    }

    private void removeRunningEntry(int taskId) {
        final CacheEntry entry = mRunningCache.get(taskId);
        if (entry != null) {
            mAppTaskMap.remove(entry.topApp);
            mCache.remove(task);
            mRunningCache.remove(taskId);
        }
    }

    void dump(PrintWriter pw, String prefix) {
        final String doublePrefix = prefix + "  ";
        final String triplePrefix = doublePrefix + "  ";
        final String quadruplePrefix = triplePrefix + "  ";
        pw.println(prefix + "SnapshotCache");
        for (int i = mCache.size() - 1; i >= 0; i--) {
            final CacheEntry entry = mCache.valueAt(i);
            pw.println(doublePrefix + "Entry taskId=" + mCache.keyAt(i).mTaskId);
            pw.println(triplePrefix + "topApp=" + entry.topApp);
            pw.println(triplePrefix + "snapshot=" + entry.snapshot);
        pw.println(doublePrefix + "RunningCache");
        for (int i = mRunningCache.size() - 1; i >= 0; i--) {
            final CacheEntry entry = mRunningCache.valueAt(i);
            pw.println(triplePrefix + "Entry taskId=" + mRunningCache.keyAt(i));
            pw.println(quadruplePrefix + "topApp=" + entry.topApp);
            pw.println(quadruplePrefix + "snapshot=" + entry.snapshot);
        }
        pw.println(doublePrefix + "RetrievalCache");
        final Map<Integer, TaskSnapshot> retrievalSnapshot = mRetrievalCache.snapshot();
        for (Entry<Integer, TaskSnapshot> entry : retrievalSnapshot.entrySet()) {
            pw.println(triplePrefix + "Entry taskId=" + entry.getKey());
            pw.println(quadruplePrefix + "snapshot=" + entry.getValue());
        }
    }

Loading