Also store reduced resolution screenshots

In order to speed up loading time when scrolling through it
in recents. They will be used in recents in the next CL. Also, we
use JPG instead as loading JPG is much faster than PNG.

Test: TaskSnapshotPersisterLoaderTest
Test: TaskSnapshotCacheTest
Bug: 34829962
Change-Id: I4c74b26969ae459bd3b1a42707011a49f425abd9
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 46552e2..18befef 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -10028,7 +10028,7 @@
     }
 
     @Override
-    public TaskSnapshot getTaskSnapshot(int taskId) {
+    public TaskSnapshot getTaskSnapshot(int taskId, boolean reducedResolution) {
         enforceCallingPermission(READ_FRAME_BUFFER, "getTaskSnapshot()");
         final long ident = Binder.clearCallingIdentity();
         try {
@@ -10042,7 +10042,7 @@
                 }
             }
             // Don't call this while holding the lock as this operation might hit the disk.
-            return task.getSnapshot();
+            return task.getSnapshot(reducedResolution);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 99fe418..13c8865 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -737,11 +737,11 @@
     /**
      * DO NOT HOLD THE ACTIVITY MANAGER LOCK WHEN CALLING THIS METHOD!
      */
-    TaskSnapshot getSnapshot() {
+    TaskSnapshot getSnapshot(boolean reducedResolution) {
 
         // TODO: Move this to {@link TaskWindowContainerController} once recent tasks are more
         // synchronized between AM and WM.
-        return mService.mWindowManager.getTaskSnapshot(taskId, userId);
+        return mService.mWindowManager.getTaskSnapshot(taskId, userId, reducedResolution);
     }
 
     void touchActiveTime() {
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index b90a82a..bd38be4 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -535,7 +535,7 @@
     private boolean createSnapshot() {
         final TaskSnapshot snapshot = mService.mTaskSnapshotController.getSnapshot(
                 mContainer.getTask().mTaskId, mContainer.getTask().mUserId,
-            false /* restoreFromDisk */);
+                false /* restoreFromDisk */, false /* reducedResolution */);
 
         if (snapshot == null) {
             return false;
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotCache.java b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
index 4028336..1ec0201 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotCache.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
@@ -61,7 +61,8 @@
     /**
      * If {@param restoreFromDisk} equals {@code true}, DO NOT HOLD THE WINDOW MANAGER LOCK!
      */
-    @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk) {
+    @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
+            boolean reducedResolution) {
 
         synchronized (mService.mWindowMap) {
             // Try the running cache.
@@ -81,19 +82,23 @@
         if (!restoreFromDisk) {
             return null;
         }
-        return tryRestoreFromDisk(taskId, userId);
+        return tryRestoreFromDisk(taskId, userId, reducedResolution);
     }
 
     /**
      * 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);
+    private TaskSnapshot tryRestoreFromDisk(int taskId, int userId, boolean reducedResolution) {
+        final TaskSnapshot snapshot = mLoader.loadTask(taskId, userId, reducedResolution);
         if (snapshot == null) {
             return null;
         }
-        synchronized (mService.mWindowMap) {
-            mRetrievalCache.put(taskId, snapshot);
+
+        // Only cache non-reduced snapshots.
+        if (!reducedResolution) {
+            synchronized (mService.mWindowMap) {
+                mRetrievalCache.put(taskId, snapshot);
+            }
         }
         return snapshot;
     }
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 5995bba..469a8a7 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -106,8 +106,9 @@
      * Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO HOLD THE WINDOW
      * MANAGER LOCK WHEN CALLING THIS METHOD!
      */
-    @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk) {
-        return mCache.getSnapshot(taskId, userId, restoreFromDisk);
+    @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
+            boolean reducedResolution) {
+        return mCache.getSnapshot(taskId, userId, restoreFromDisk, reducedResolution);
     }
 
     /**
@@ -130,7 +131,7 @@
             return null;
         }
         return new TaskSnapshot(buffer, top.getConfiguration().orientation,
-                top.findMainWindow().mStableInsets);
+                top.findMainWindow().mStableInsets, false /* reduced */, 1f /* scale */);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
index 4340822..ec21d25 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static com.android.server.wm.TaskSnapshotPersister.*;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
@@ -59,11 +60,14 @@
      *
      * @param taskId The id of the task to load.
      * @param userId The id of the user the task belonged to.
+     * @param reducedResolution Whether to load a reduced resolution version of the snapshot.
      * @return The loaded {@link TaskSnapshot} or {@code null} if it couldn't be loaded.
      */
-    TaskSnapshot loadTask(int taskId, int userId) {
+    TaskSnapshot loadTask(int taskId, int userId, boolean reducedResolution) {
         final File protoFile = mPersister.getProtoFile(taskId, userId);
-        final File bitmapFile = mPersister.getBitmapFile(taskId, userId);
+        final File bitmapFile = reducedResolution
+                ? mPersister.getReducedResolutionBitmapFile(taskId, userId)
+                : mPersister.getBitmapFile(taskId, userId);
         if (!protoFile.exists() || !bitmapFile.exists()) {
             return null;
         }
@@ -84,7 +88,8 @@
                 return null;
             }
             return new TaskSnapshot(buffer, proto.orientation,
-                    new Rect(proto.insetLeft, proto.insetTop, proto.insetRight, proto.insetBottom));
+                    new Rect(proto.insetLeft, proto.insetTop, proto.insetRight, proto.insetBottom),
+                    reducedResolution, reducedResolution ? REDUCED_SCALE : 1f);
         } catch (IOException e) {
             Slog.w(TAG, "Unable to load task snapshot data for taskId=" + taskId);
             return null;
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index 3a06c38..f2a92df 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.graphics.Bitmap.CompressFormat.*;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
@@ -47,9 +48,12 @@
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotPersister" : TAG_WM;
     private static final String SNAPSHOTS_DIRNAME = "snapshots";
+    private static final String REDUCED_POSTFIX = "_reduced";
+    static final float REDUCED_SCALE = 0.5f;
     private static final long DELAY_MS = 100;
+    private static final int QUALITY = 95;
     private static final String PROTO_EXTENSION = ".proto";
-    private static final String BITMAP_EXTENSION = ".png";
+    private static final String BITMAP_EXTENSION = ".jpg";
 
     @GuardedBy("mLock")
     private final ArrayDeque<WriteQueueItem> mWriteQueue = new ArrayDeque<>();
@@ -152,6 +156,10 @@
         return new File(getDirectory(userId), taskId + BITMAP_EXTENSION);
     }
 
+    File getReducedResolutionBitmapFile(int taskId, int userId) {
+        return new File(getDirectory(userId), taskId + REDUCED_POSTFIX + BITMAP_EXTENSION);
+    }
+
     private boolean createDirectory(int userId) {
         final File dir = getDirectory(userId);
         return dir.exists() || dir.mkdirs();
@@ -160,8 +168,10 @@
     private void deleteSnapshot(int taskId, int userId) {
         final File protoFile = getProtoFile(taskId, userId);
         final File bitmapFile = getBitmapFile(taskId, userId);
+        final File bitmapReducedFile = getReducedResolutionBitmapFile(taskId, userId);
         protoFile.delete();
         bitmapFile.delete();
+        bitmapReducedFile.delete();
     }
 
     interface DirectoryResolver {
@@ -254,13 +264,20 @@
 
         boolean writeBuffer() {
             final File file = getBitmapFile(mTaskId, mUserId);
+            final File reducedFile = getReducedResolutionBitmapFile(mTaskId, mUserId);
             final Bitmap bitmap = Bitmap.createHardwareBitmap(mSnapshot.getSnapshot());
+            final Bitmap reduced = Bitmap.createScaledBitmap(bitmap,
+                    (int) (bitmap.getWidth() * REDUCED_SCALE),
+                    (int) (bitmap.getHeight() * REDUCED_SCALE), true /* filter */);
             try {
                 FileOutputStream fos = new FileOutputStream(file);
-                bitmap.compress(CompressFormat.PNG, 0 /* quality */, fos);
+                bitmap.compress(JPEG, QUALITY, fos);
                 fos.close();
+                FileOutputStream reducedFos = new FileOutputStream(reducedFile);
+                reduced.compress(JPEG, QUALITY, reducedFos);
+                reducedFos.close();
             } catch (IOException e) {
-                Slog.e(TAG, "Unable to open " + file + " for persisting. " + e);
+                Slog.e(TAG, "Unable to open " + file + " or " + reducedFile +" for persisting.", e);
                 return false;
             }
             return true;
@@ -325,8 +342,12 @@
             if (end == -1) {
                 return -1;
             }
+            String name = fileName.substring(0, end);
+            if (name.endsWith(REDUCED_POSTFIX)) {
+                name = name.substring(0, name.length() - REDUCED_POSTFIX.length());
+            }
             try {
-                return Integer.parseInt(fileName.substring(0, end));
+                return Integer.parseInt(name);
             } catch (NumberFormatException e) {
                 return -1;
             }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 5edb82c..5844b0b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3621,8 +3621,9 @@
         return true;
     }
 
-    public TaskSnapshot getTaskSnapshot(int taskId, int userId) {
-        return mTaskSnapshotController.getSnapshot(taskId, userId, true /* restoreFromDisk */);
+    public TaskSnapshot getTaskSnapshot(int taskId, int userId, boolean reducedResolution) {
+        return mTaskSnapshotController.getSnapshot(taskId, userId, true /* restoreFromDisk */,
+                reducedResolution);
     }
 
     /**
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java
index f1fcba3..290f69a 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java
@@ -66,10 +66,10 @@
         final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
         mCache.putSnapshot(window.getTask(), createSnapshot());
         assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
-                false /* restoreFromDisk */));
+                false /* restoreFromDisk */, false /* reducedResolution */));
         mCache.onAppRemoved(window.mAppToken);
         assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
-                false /* restoreFromDisk */));
+                false /* restoreFromDisk */, false /* reducedResolution */));
     }
 
     @Test
@@ -77,12 +77,12 @@
         final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
         mCache.putSnapshot(window.getTask(), createSnapshot());
         assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
-                false /* restoreFromDisk */));
+                false /* restoreFromDisk */, false /* reducedResolution */));
         mCache.onAppDied(window.mAppToken);
 
         // Should still be in the retrieval cache.
         assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
-                false /* restoreFromDisk */));
+                false /* restoreFromDisk */, false /* reducedResolution */));
 
         // Trash retrieval cache.
         for (int i = 0; i < 20; i++) {
@@ -92,7 +92,7 @@
 
         // Should not be in cache anymore
         assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
-                false /* restoreFromDisk */));
+                false /* restoreFromDisk */, false /* reducedResolution */));
     }
 
     @Test
@@ -100,10 +100,27 @@
         final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
         mCache.putSnapshot(window.getTask(), createSnapshot());
         assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
-                false /* restoreFromDisk */));
+                false /* restoreFromDisk */, false /* reducedResolution */));
         mCache.onTaskRemoved(window.getTask().mTaskId);
         assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
-                false /* restoreFromDisk */));
+                false /* restoreFromDisk */, false /* reducedResolution */));
+    }
+
+    @Test
+    public void testReduced_notCached() throws Exception {
+        final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
+        mPersister.persistSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId, createSnapshot());
+        mPersister.waitForQueueEmpty();
+        assertNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
+                false /* restoreFromDisk */, false /* reducedResolution */));
+
+        // Load it from disk
+        assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
+                true /* restoreFromDisk */, true /* reducedResolution */));
+
+        // Make sure it's not in the cache now.
+        assertNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
+                false /* restoreFromDisk */, false /* reducedResolution */));
     }
 
     @Test
@@ -112,14 +129,14 @@
         mPersister.persistSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId, createSnapshot());
         mPersister.waitForQueueEmpty();
         assertNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
-                false /* restoreFromDisk */));
+                false /* restoreFromDisk */, false /* reducedResolution */));
 
         // Load it from disk
         assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
-                true /* restoreFromDisk */));
+                true /* restoreFromDisk */, false /* reducedResolution */));
 
         // Make sure it's in the cache now.
         assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
-                false /* restoreFromDisk */));
+                false /* restoreFromDisk */, false /* reducedResolution */));
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
index dc008b5..4121447 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
@@ -26,6 +26,7 @@
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.Rect;
+import android.os.Debug;
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 import android.support.test.filters.MediumTest;
@@ -50,7 +51,6 @@
 @RunWith(AndroidJUnit4.class)
 public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBase {
 
-    private static final String TEST_USER_NAME = "TaskSnapshotPersisterTest User";
     private static final Rect TEST_INSETS = new Rect(10, 20, 30, 40);
 
     @Test
@@ -58,9 +58,10 @@
         mPersister.persistSnapshot(1 , sTestUserId, createSnapshot());
         mPersister.waitForQueueEmpty();
         final File[] files = new File[] { new File(sFilesDir.getPath() + "/snapshots/1.proto"),
-                new File(sFilesDir.getPath() + "/snapshots/1.png") };
+                new File(sFilesDir.getPath() + "/snapshots/1.jpg"),
+                new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg")};
         assertTrueForFiles(files, File::exists, " must exist");
-        final TaskSnapshot snapshot = mLoader.loadTask(1, sTestUserId);
+        final TaskSnapshot snapshot = mLoader.loadTask(1, sTestUserId, false /* reduced */);
         assertNotNull(snapshot);
         assertEquals(TEST_INSETS, snapshot.getContentInsets());
         assertNotNull(snapshot.getSnapshot());
@@ -79,7 +80,8 @@
         mPersister.onTaskRemovedFromRecents(1, sTestUserId);
         mPersister.waitForQueueEmpty();
         assertFalse(new File(sFilesDir.getPath() + "/snapshots/1.proto").exists());
-        assertFalse(new File(sFilesDir.getPath() + "/snapshots/1.png").exists());
+        assertFalse(new File(sFilesDir.getPath() + "/snapshots/1.jpg").exists());
+        assertFalse(new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg").exists());
     }
 
     /**
@@ -105,9 +107,10 @@
         assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("blablablulp"));
         assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("nothing.err"));
         assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("/invalid/"));
-        assertEquals(12, removeObsoleteFilesQueueItem.getTaskId("12.png"));
+        assertEquals(12, removeObsoleteFilesQueueItem.getTaskId("12.jpg"));
         assertEquals(12, removeObsoleteFilesQueueItem.getTaskId("12.proto"));
-        assertEquals(1, removeObsoleteFilesQueueItem.getTaskId("1.png"));
+        assertEquals(1, removeObsoleteFilesQueueItem.getTaskId("1.jpg"));
+        assertEquals(1, removeObsoleteFilesQueueItem.getTaskId("1_reduced.jpg"));
     }
 
     @Test
@@ -120,10 +123,12 @@
         mPersister.waitForQueueEmpty();
         final File[] existsFiles = new File[] {
                 new File(sFilesDir.getPath() + "/snapshots/1.proto"),
-                new File(sFilesDir.getPath() + "/snapshots/1.png") };
+                new File(sFilesDir.getPath() + "/snapshots/1.jpg"),
+                new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg") };
         final File[] nonExistsFiles = new File[] {
                 new File(sFilesDir.getPath() + "/snapshots/2.proto"),
-                new File(sFilesDir.getPath() + "/snapshots/2.png") };
+                new File(sFilesDir.getPath() + "/snapshots/2.jpg"),
+                new File(sFilesDir.getPath() + "/snapshots/2_reduced.jpg")};
         assertTrueForFiles(existsFiles, File::exists, " must exist");
         assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist");
     }
@@ -138,9 +143,11 @@
         mPersister.waitForQueueEmpty();
         final File[] existsFiles = new File[] {
                 new File(sFilesDir.getPath() + "/snapshots/1.proto"),
-                new File(sFilesDir.getPath() + "/snapshots/1.png"),
+                new File(sFilesDir.getPath() + "/snapshots/1.jpg"),
+                new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg"),
                 new File(sFilesDir.getPath() + "/snapshots/2.proto"),
-                new File(sFilesDir.getPath() + "/snapshots/2.png") };
+                new File(sFilesDir.getPath() + "/snapshots/2.jpg"),
+                new File(sFilesDir.getPath() + "/snapshots/2_reduced.jpg")};
         assertTrueForFiles(existsFiles, File::exists, " must exist");
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
index 6fc6edb..5e7389d 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
@@ -106,6 +106,7 @@
         Canvas c = buffer.lockCanvas();
         c.drawColor(Color.RED);
         buffer.unlockCanvasAndPost(c);
-        return new TaskSnapshot(buffer, ORIENTATION_PORTRAIT, TEST_INSETS);
+        return new TaskSnapshot(buffer, ORIENTATION_PORTRAIT, TEST_INSETS,
+                false /* reducedResolution */, 1f /* scale */);
     }
 }