Initial implementation of snapshots

All this functionality is hidden behind a flag. If this flag is
active, we disable the regular screenshots.

Instead, we take a screenshot when an app transition for which a
task is disappearing is starting. The screenshot gets stored
into a gralloc buffer. SystemUI uses a new method to retrieve
a snapshot gralloc buffer and then draws it using GraphicBuffer.
createHardwareBitmap().

When starting an existing activity in an existing tasks, or when
bringing an existing tasks to front from recents, we add a new
snapshot starting window. For that, we reuse the existing
starting window, but when creating the window, we use a fake
window that draws the contents of the starting window.

Test: runtest frameworks-services -c
com.android.server.wm.TaskSnapshotControllerTest
Bug: 31339431
Change-Id: If72df07b3e56f30413db5029d0887b8c9665aaf4
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index d2f604d..4dbe35f 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -31,6 +31,7 @@
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
+import android.graphics.GraphicBuffer;
 import android.os.Debug;
 import android.os.Handler;
 import android.os.IBinder;
@@ -50,6 +51,10 @@
 public class AppWindowContainerController
         extends WindowContainerController<AppWindowToken, AppWindowContainerListener> {
 
+    private static final int STARTING_WINDOW_TYPE_NONE = 0;
+    private static final int STARTING_WINDOW_TYPE_SNAPSHOT = 1;
+    private static final int STARTING_WINDOW_TYPE_SPLASH_SCREEN = 2;
+
     private final IApplicationToken mToken;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
 
@@ -80,16 +85,39 @@
         mListener.onWindowsGone();
     };
 
+    private final Runnable mRemoveStartingWindow = () -> {
+        StartingSurface surface = null;
+        StartingData data = null;
+        synchronized (mWindowMap) {
+            if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Remove starting " + mContainer
+                    + ": startingWindow=" + mContainer.startingWindow
+                    + " startingView=" + mContainer.startingSurface);
+            if (mContainer.startingWindow != null) {
+                surface = mContainer.startingSurface;
+                data = mContainer.startingData;
+                mContainer.startingData = null;
+                mContainer.startingSurface = null;
+                mContainer.startingWindow = null;
+                mContainer.startingDisplayed = false;
+            }
+        }
+        if (data != null && surface != null) {
+            try {
+                surface.remove();
+            } catch (Exception e) {
+                Slog.w(TAG_WM, "Exception when removing starting window", e);
+            }
+        }
+    };
+
     private final Runnable mAddStartingWindow = () -> {
         final StartingData startingData;
-        final Configuration mergedOverrideConfiguration;
 
         synchronized (mWindowMap) {
             if (mContainer == null) {
                 return;
             }
             startingData = mContainer.startingData;
-            mergedOverrideConfiguration = mContainer.getMergedOverrideConfiguration();
         }
 
         if (startingData == null) {
@@ -98,20 +126,16 @@
         }
 
         if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Add starting "
-                + this + ": pkg=" + mContainer.startingData.pkg);
+                + this + ": startingData=" + mContainer.startingData);
 
-        StartingSurface contents = null;
+        StartingSurface surface = null;
         try {
-            contents = mService.mPolicy.addSplashScreen(mContainer.token, startingData.pkg,
-                    startingData.theme, startingData.compatInfo, startingData.nonLocalizedLabel,
-                    startingData.labelRes, startingData.icon, startingData.logo,
-                    startingData.windowFlags, mergedOverrideConfiguration);
+            surface = startingData.createStartingSurface();
         } catch (Exception e) {
             Slog.w(TAG_WM, "Exception when adding starting window", e);
         }
-        if (contents != null) {
+        if (surface != null) {
             boolean abort = false;
-
             synchronized(mWindowMap) {
                 if (mContainer.removed || mContainer.startingData == null) {
                     // If the window was successfully added, then
@@ -121,12 +145,10 @@
                                 "Aborted starting " + mContainer
                                         + ": removed=" + mContainer.removed
                                         + " startingData=" + mContainer.startingData);
-                        mContainer.startingWindow = null;
-                        mContainer.startingData = null;
                         abort = true;
                     }
                 } else {
-                    mContainer.startingSurface = contents;
+                    mContainer.startingSurface = surface;
                 }
                 if (DEBUG_STARTING_WINDOW && !abort) Slog.v(TAG_WM,
                         "Added starting " + mContainer
@@ -134,42 +156,11 @@
                                 + mContainer.startingWindow + " startingView="
                                 + mContainer.startingSurface);
             }
-
             if (abort) {
-                try {
-                    mService.mPolicy.removeSplashScreen(mContainer.token, contents);
-                } catch (Exception e) {
-                    Slog.w(TAG_WM, "Exception when removing starting window", e);
-                }
-            }
-        }
-    };
-
-    private final Runnable mRemoveStartingWindow = () -> {
-        IBinder token = null;
-        StartingSurface contents = null;
-        synchronized (mWindowMap) {
-            if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Remove starting "
-                    + mContainer + ": startingWindow="
-                    + mContainer.startingWindow + " startingView="
-                    + mContainer.startingSurface);
+                mRemoveStartingWindow.run();
             if (mContainer == null) {
                 return;
             }
-            if (mContainer.startingWindow != null) {
-                contents = mContainer.startingSurface;
-                token = mContainer.token;
-                mContainer.startingData = null;
-                mContainer.startingSurface = null;
-                mContainer.startingWindow = null;
-                mContainer.startingDisplayed = false;
-            }
-        }
-        if (contents != null) {
-            try {
-                mService.mPolicy.removeSplashScreen(token, contents);
-            } catch (Exception e) {
-                Slog.w(TAG_WM, "Exception when removing starting window", e);
             }
         }
     };
@@ -389,7 +380,7 @@
 
     public boolean addStartingWindow(String pkg, int theme, CompatibilityInfo compatInfo,
             CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, int windowFlags,
-            IBinder transferFrom, boolean createIfNeeded) {
+            IBinder transferFrom, boolean newTask, boolean taskSwitch, boolean processRunning) {
         synchronized(mWindowMap) {
             if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "setAppStartingWindow: token=" + mToken
                     + " pkg=" + pkg + " transferFrom=" + transferFrom);
@@ -409,6 +400,12 @@
                 return false;
             }
 
+            final int type = getStartingWindowType(newTask, taskSwitch, processRunning);
+
+            if (type == STARTING_WINDOW_TYPE_SNAPSHOT) {
+                return createSnapshot();
+            }
+
             // If this is a translucent window, then don't show a starting window -- the current
             // effect (a full-screen opaque starting window that fades away to the real contents
             // when it is ready) does not work for this.
@@ -458,22 +455,32 @@
                 return true;
             }
 
-            // There is no existing starting window, and the caller doesn't
-            // want us to create one, so that's it!
-            if (!createIfNeeded) {
+            // There is no existing starting window, and we don't want to create a splash screen, so
+            // that's it!
+            if (type != STARTING_WINDOW_TYPE_SPLASH_SCREEN) {
                 return false;
             }
 
             if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Creating StartingData");
-            mContainer.startingData = new StartingData(pkg, theme, compatInfo, nonLocalizedLabel,
-                    labelRes, icon, logo, windowFlags);
+            mContainer.startingData = new SplashScreenStartingData(mService, mContainer, pkg, theme,
+                    compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
+                    mContainer.getMergedOverrideConfiguration());
             scheduleAddStartingWindow();
         }
         return true;
     }
 
-    void scheduleAddStartingWindow() {
+    private int getStartingWindowType(boolean newTask, boolean taskSwitch, boolean processRunning) {
+        if (newTask || !processRunning) {
+            return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
+        } else if (taskSwitch) {
+            return STARTING_WINDOW_TYPE_SNAPSHOT;
+        } else {
+            return STARTING_WINDOW_TYPE_NONE;
+        }
+    }
 
+    void scheduleAddStartingWindow() {
         // Note: we really want to do sendMessageAtFrontOfQueue() because we
         // want to process the message ASAP, before any other queued
         // messages.
@@ -481,6 +488,19 @@
         mHandler.postAtFrontOfQueue(mAddStartingWindow);
     }
 
+    private boolean createSnapshot() {
+        final GraphicBuffer snapshot = mService.mTaskSnapshotController.getSnapshot(
+                mContainer.mTask);
+
+        if (snapshot == null) {
+            return false;
+        }
+
+        mContainer.startingData = new SnapshotStartingData(mService, mContainer, snapshot);
+        scheduleAddStartingWindow();
+        return true;
+    }
+
     public void removeStartingWindow() {
         synchronized (mWindowMap) {
             if (mHandler.hasCallbacks(mRemoveStartingWindow)) {
@@ -593,7 +613,7 @@
             }
             return dc.screenshotApplications(mToken.asBinder(), width, height,
                     false /* includeFullDisplay */, frameScale, Bitmap.Config.RGB_565,
-                    false /* wallpaperOnly */);
+                    false /* wallpaperOnly */, false /* includeDecor */, true /* toAshmem */);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
         }