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/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
new file mode 100644
index 0000000..c3e3141
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TASK_SNAPSHOT;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.GraphicBuffer;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.view.Display;
+import android.view.IWindowSession;
+import android.view.Surface;
+import android.view.View;
+import android.view.ViewGroup.LayoutParams;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.view.WindowManagerPolicy.StartingSurface;
+
+import com.android.internal.view.BaseIWindow;
+
+/**
+ * This class represents a starting window that shows a snapshot.
+ * <p>
+ * DO NOT HOLD THE WINDOW MANAGER LOCK WHEN CALLING METHODS OF THIS CLASS!
+ */
+class TaskSnapshotSurface implements StartingSurface {
+
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "SnapshotStartingWindow" : TAG_WM;
+    private static final int MSG_REPORT_DRAW = 0;
+    private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=%s";
+    private final Window mWindow;
+    private final Surface mSurface;
+    private final IWindowSession mSession;
+    private final WindowManagerService mService;
+    private boolean mHasDrawn;
+    private boolean mReportNextDraw;
+
+    static TaskSnapshotSurface create(WindowManagerService service, AppWindowToken token,
+            GraphicBuffer snapshot) {
+
+        final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
+        final Window window = new Window();
+        final IWindowSession session = WindowManagerGlobal.getWindowSession();
+        window.setSession(session);
+        final Surface surface = new Surface();
+        final Rect tmpRect = new Rect();
+        final Rect tmpFrame = new Rect();
+        final Configuration tmpConfiguration = new Configuration();
+        synchronized (service.mWindowMap) {
+            layoutParams.type = TYPE_APPLICATION_STARTING;
+            layoutParams.format = snapshot.getFormat();
+            layoutParams.flags = FLAG_LAYOUT_INSET_DECOR
+                    | FLAG_LAYOUT_IN_SCREEN
+                    | FLAG_NOT_FOCUSABLE
+                    | FLAG_NOT_TOUCHABLE
+                    | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+            layoutParams.privateFlags = PRIVATE_FLAG_TASK_SNAPSHOT;
+            layoutParams.token = token.token;
+            layoutParams.width = LayoutParams.MATCH_PARENT;
+            layoutParams.height = LayoutParams.MATCH_PARENT;
+
+            // TODO: Inherit behavior whether to draw behind status bar/nav bar.
+            layoutParams.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+            layoutParams.setTitle(String.format(TITLE_FORMAT, token.mTask.mTaskId));
+        }
+        try {
+            final int res = session.addToDisplay(window, window.mSeq, layoutParams,
+                    View.VISIBLE, token.getDisplayContent().getDisplayId(), tmpRect, tmpRect,
+                    tmpRect, null);
+            if (res < 0) {
+                Slog.w(TAG, "Failed to add snapshot starting window res=" + res);
+                return null;
+            }
+        } catch (RemoteException e) {
+            // Local call.
+        }
+        final TaskSnapshotSurface snapshotSurface = new TaskSnapshotSurface(service, window,
+                surface);
+        window.setOuter(snapshotSurface);
+        try {
+            session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, tmpFrame,
+                    tmpRect, tmpRect, tmpRect, tmpRect, tmpRect, tmpRect, tmpConfiguration,
+                    surface);
+        } catch (RemoteException e) {
+            // Local call.
+        }
+        snapshotSurface.drawSnapshot(snapshot);
+        return snapshotSurface;
+    }
+
+    private TaskSnapshotSurface(WindowManagerService service, Window window, Surface surface) {
+        mService = service;
+        mSession = WindowManagerGlobal.getWindowSession();
+        mWindow = window;
+        mSurface = surface;
+    }
+
+    @Override
+    public void remove() {
+        try {
+            mSession.remove(mWindow);
+        } catch (RemoteException e) {
+            // Local call.
+        }
+    }
+
+    private void drawSnapshot(GraphicBuffer snapshot) {
+
+        // TODO: Just wrap the buffer here without any copying.
+        final Canvas c = mSurface.lockHardwareCanvas();
+        c.drawBitmap(Bitmap.createHardwareBitmap(snapshot), 0, 0, null);
+        mSurface.unlockCanvasAndPost(c);
+        final boolean reportNextDraw;
+        synchronized (mService.mWindowMap) {
+            mHasDrawn = true;
+            reportNextDraw = mReportNextDraw;
+        }
+        if (reportNextDraw) {
+            reportDrawn();
+        }
+    }
+
+    private void reportDrawn() {
+        synchronized (mService.mWindowMap) {
+            mReportNextDraw = false;
+        }
+        try {
+            mSession.finishDrawing(mWindow);
+        } catch (RemoteException e) {
+            // Local call.
+        }
+    }
+
+    private static Handler sHandler = new Handler() {
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_REPORT_DRAW:
+                    final boolean hasDrawn;
+                    final TaskSnapshotSurface surface = (TaskSnapshotSurface) msg.obj;
+                    synchronized (surface.mService.mWindowMap) {
+                        hasDrawn = surface.mHasDrawn;
+                        if (!hasDrawn) {
+                            surface.mReportNextDraw = true;
+                        }
+                    }
+                    if (hasDrawn) {
+                        surface.reportDrawn();
+                    }
+                    break;
+            }
+        }
+    };
+
+    private static class Window extends BaseIWindow {
+
+        private TaskSnapshotSurface mOuter;
+
+        public void setOuter(TaskSnapshotSurface outer) {
+            mOuter = outer;
+        }
+
+        @Override
+        public void resized(Rect frame, Rect overscanInsets, Rect contentInsets, Rect visibleInsets,
+                Rect stableInsets, Rect outsets, boolean reportDraw, Configuration newConfig,
+                Rect backDropFrame, boolean forceLayout, boolean alwaysConsumeNavBar) {
+            if (reportDraw) {
+                sHandler.obtainMessage(MSG_REPORT_DRAW, mOuter).sendToTarget();
+            }
+        }
+    }
+}