Fill task snapshot with background color

Make sure to fill the portions that are not covered by the
snapshot are filled with the task background color.

Also fix an issue where the starting window was removed across
configuration changes.

Test: runtest frameworks-services -c
com.android.server.wm.TaskSnapshotSurfaceTest
Bug: 31339431
Change-Id: I2451be87aff79b337015ab4bba72cfa03c0d3582
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 2745fe5..7b4d289 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -416,7 +416,7 @@
         final Configuration overrideConfig = getOverrideConfiguration();
         mWindowContainerController = new TaskWindowContainerController(taskId, this, getStackId(),
                 userId, bounds, overrideConfig, mResizeMode, isHomeTask(), isOnTopLauncher(), onTop,
-                showForAllUsers);
+                showForAllUsers, lastTaskDescription);
     }
 
     void removeWindowContainer() {
@@ -1402,6 +1402,9 @@
             }
             lastTaskDescription = new TaskDescription(label, null, iconFilename, colorPrimary,
                     colorBackground);
+            if (mWindowContainerController != null) {
+                mWindowContainerController.setTaskDescription(lastTaskDescription);
+            }
             // Update the task affiliation color if we are the parent of the group
             if (taskId == mAffiliatedTaskId) {
                 mAffiliatedTaskColor = lastTaskDescription.getPrimaryColor();
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 29c685d..079dc8f 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -675,7 +675,7 @@
             // well there is no point now.
             if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Nulling last startingData");
             startingData = null;
-        } else if (mChildren.size() == 1 && startingSurface != null) {
+        } else if (mChildren.size() == 1 && startingSurface != null && !isRelaunching()) {
             // If this is the last window except for a starting transition window,
             // we need to get rid of the starting transition.
             if (getController() != null) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 680d0f2..3a3ec71 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -30,6 +30,7 @@
 import static com.android.server.wm.WindowManagerService.H.RESIZE_TASK;
 
 import android.app.ActivityManager.StackId;
+import android.app.ActivityManager.TaskDescription;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.graphics.Rect;
@@ -90,9 +91,11 @@
     // Whether this task is an on-top launcher task, which is determined by the root activity.
     private boolean mIsOnTopLauncher;
 
+    private TaskDescription mTaskDescription;
+
     Task(int taskId, TaskStack stack, int userId, WindowManagerService service, Rect bounds,
             Configuration overrideConfig, boolean isOnTopLauncher, int resizeMode, boolean homeTask,
-            TaskWindowContainerController controller) {
+            TaskDescription taskDescription, TaskWindowContainerController controller) {
         mTaskId = taskId;
         mStack = stack;
         mUserId = userId;
@@ -102,6 +105,7 @@
         mHomeTask = homeTask;
         setController(controller);
         setBounds(bounds, overrideConfig);
+        mTaskDescription = taskDescription;
     }
 
     DisplayContent getDisplayContent() {
@@ -647,6 +651,14 @@
         }
     }
 
+    void setTaskDescription(TaskDescription taskDescription) {
+        mTaskDescription = taskDescription;
+    }
+
+    TaskDescription getTaskDescription() {
+        return mTaskDescription;
+    }
+
     @Override
     boolean fillsParent() {
         return mFillsParent || !StackId.isTaskResizeAllowed(mStack.mStackId);
@@ -688,6 +700,5 @@
             pw.println(triplePrefix + "Activity #" + i + " " + wtoken);
             wtoken.dump(pw, triplePrefix);
         }
-
     }
 }
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index 4a09423..cfcbbd0 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -26,12 +26,16 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
+import android.app.ActivityManager.TaskDescription;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.GraphicBuffer;
+import android.graphics.Paint;
 import android.graphics.Rect;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -43,6 +47,7 @@
 import android.view.WindowManagerGlobal;
 import android.view.WindowManagerPolicy.StartingSurface;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.view.BaseIWindow;
 
 /**
@@ -61,6 +66,7 @@
     private final WindowManagerService mService;
     private boolean mHasDrawn;
     private boolean mReportNextDraw;
+    private Paint mFillBackgroundPaint = new Paint();
 
     static TaskSnapshotSurface create(WindowManagerService service, AppWindowToken token,
             GraphicBuffer snapshot) {
@@ -73,6 +79,7 @@
         final Rect tmpRect = new Rect();
         final Rect tmpFrame = new Rect();
         final Configuration tmpConfiguration = new Configuration();
+        int fillBackgroundColor = Color.WHITE;
         synchronized (service.mWindowMap) {
             layoutParams.type = TYPE_APPLICATION_STARTING;
             layoutParams.format = snapshot.getFormat();
@@ -90,6 +97,12 @@
             layoutParams.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                     | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
             layoutParams.setTitle(String.format(TITLE_FORMAT, token.mTask.mTaskId));
+            if (token.mTask != null) {
+                final TaskDescription taskDescription = token.mTask.getTaskDescription();
+                if (taskDescription != null) {
+                    fillBackgroundColor = taskDescription.getBackgroundColor();
+                }
+            }
         }
         try {
             final int res = session.addToDisplay(window, window.mSeq, layoutParams,
@@ -103,7 +116,7 @@
             // Local call.
         }
         final TaskSnapshotSurface snapshotSurface = new TaskSnapshotSurface(service, window,
-                surface);
+                surface, fillBackgroundColor);
         window.setOuter(snapshotSurface);
         try {
             session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, tmpFrame,
@@ -116,11 +129,14 @@
         return snapshotSurface;
     }
 
-    private TaskSnapshotSurface(WindowManagerService service, Window window, Surface surface) {
+    @VisibleForTesting
+    TaskSnapshotSurface(WindowManagerService service, Window window, Surface surface,
+            int fillBackgroundColor) {
         mService = service;
         mSession = WindowManagerGlobal.getWindowSession();
         mWindow = window;
         mSurface = surface;
+        mFillBackgroundPaint.setColor(fillBackgroundColor);
     }
 
     @Override
@@ -136,7 +152,9 @@
 
         // TODO: Just wrap the buffer here without any copying.
         final Canvas c = mSurface.lockHardwareCanvas();
-        c.drawBitmap(Bitmap.createHardwareBitmap(snapshot), 0, 0, null);
+        final Bitmap b = Bitmap.createHardwareBitmap(snapshot);
+        fillEmptyBackground(c, b);
+        c.drawBitmap(b, 0, 0, null);
         mSurface.unlockCanvasAndPost(c);
         final boolean reportNextDraw;
         synchronized (mService.mWindowMap) {
@@ -149,6 +167,21 @@
         mSurface.release();
     }
 
+    @VisibleForTesting
+    void fillEmptyBackground(Canvas c, Bitmap b) {
+        final boolean fillHorizontally = c.getWidth() > b.getWidth();
+        final boolean fillVertically = c.getHeight() > b.getHeight();
+        if (fillHorizontally) {
+            c.drawRect(b.getWidth(), 0, c.getWidth(), fillVertically
+                        ? b.getHeight()
+                        : c.getHeight(),
+                    mFillBackgroundPaint);
+        }
+        if (fillVertically) {
+            c.drawRect(0, b.getHeight(), c.getWidth(), c.getHeight(), mFillBackgroundPaint);
+        }
+    }
+
     private void reportDrawn() {
         synchronized (mService.mWindowMap) {
             mReportNextDraw = false;
@@ -160,7 +193,7 @@
         }
     }
 
-    private static Handler sHandler = new Handler() {
+    private static Handler sHandler = new Handler(Looper.getMainLooper()) {
 
         @Override
         public void handleMessage(Message msg) {
diff --git a/services/core/java/com/android/server/wm/TaskWindowContainerController.java b/services/core/java/com/android/server/wm/TaskWindowContainerController.java
index 3c438ca..61a2cd9 100644
--- a/services/core/java/com/android/server/wm/TaskWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/TaskWindowContainerController.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import android.app.ActivityManager.TaskDescription;
 import android.app.ActivityManager.TaskSnapshot;
 import android.content.res.Configuration;
 import android.graphics.Rect;
@@ -62,7 +63,8 @@
 
     public TaskWindowContainerController(int taskId, TaskWindowContainerListener listener,
             int stackId, int userId, Rect bounds, Configuration overrideConfig, int resizeMode,
-            boolean homeTask, boolean isOnTopLauncher, boolean toTop, boolean showForAllUsers) {
+            boolean homeTask, boolean isOnTopLauncher, boolean toTop, boolean showForAllUsers,
+            TaskDescription taskDescription) {
         super(listener, WindowManagerService.getInstance());
         mTaskId = taskId;
 
@@ -79,7 +81,7 @@
             }
             EventLog.writeEvent(WM_TASK_CREATED, taskId, stackId);
             final Task task = createTask(taskId, stack, userId, bounds, overrideConfig, resizeMode,
-                    homeTask, isOnTopLauncher);
+                    homeTask, isOnTopLauncher, taskDescription);
             final int position = toTop ? POSITION_TOP : POSITION_BOTTOM;
             stack.addTask(task, position, showForAllUsers, true /* moveParents */);
         }
@@ -88,9 +90,9 @@
     @VisibleForTesting
     Task createTask(int taskId, TaskStack stack, int userId, Rect bounds,
             Configuration overrideConfig, int resizeMode, boolean homeTask,
-            boolean isOnTopLauncher) {
+            boolean isOnTopLauncher, TaskDescription taskDescription) {
         return new Task(taskId, stack, userId, mService, bounds, overrideConfig, isOnTopLauncher,
-                resizeMode, homeTask, this);
+                resizeMode, homeTask, taskDescription, this);
     }
 
     @Override
@@ -263,6 +265,16 @@
         }
     }
 
+    public void setTaskDescription(TaskDescription taskDescription) {
+        synchronized (mWindowMap) {
+            if (mContainer == null) {
+                Slog.w(TAG_WM, "setTaskDescription: taskId " + mTaskId + " not found.");
+                return;
+            }
+            mContainer.setTaskDescription(taskDescription);
+        }
+    }
+
     void reportSnapshotChanged(TaskSnapshot snapshot) {
         mHandler.obtainMessage(REPORT_SNAPSHOT_CHANGED, snapshot).sendToTarget();
     }
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
new file mode 100644
index 0000000..aab75ee
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017 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 org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test class for {@link TaskSnapshotSurface}.
+ *
+ * runtest frameworks-services -c com.android.server.wm.TaskSnapshotSurfaceTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class TaskSnapshotSurfaceTest extends WindowTestsBase {
+
+    private TaskSnapshotSurface mSurface;
+
+    @Before
+    public void setUp() {
+        mSurface = new TaskSnapshotSurface(null, null, null, Color.WHITE);
+    }
+
+    @Test
+    public void fillEmptyBackground_fillHorizontally() throws Exception {
+        final Canvas mockCanvas = mock(Canvas.class);
+        when(mockCanvas.getWidth()).thenReturn(200);
+        when(mockCanvas.getHeight()).thenReturn(100);
+        final Bitmap b = Bitmap.createBitmap(100, 200, Config.ARGB_8888);
+        mSurface.fillEmptyBackground(mockCanvas, b);
+        verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any());
+    }
+
+    @Test
+    public void fillEmptyBackground_fillVertically() throws Exception {
+        final Canvas mockCanvas = mock(Canvas.class);
+        when(mockCanvas.getWidth()).thenReturn(100);
+        when(mockCanvas.getHeight()).thenReturn(200);
+        final Bitmap b = Bitmap.createBitmap(200, 100, Config.ARGB_8888);
+        mSurface.fillEmptyBackground(mockCanvas, b);
+        verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(100.0f), eq(200.0f), any());
+    }
+
+    @Test
+    public void fillEmptyBackground_fillBoth() throws Exception {
+        final Canvas mockCanvas = mock(Canvas.class);
+        when(mockCanvas.getWidth()).thenReturn(200);
+        when(mockCanvas.getHeight()).thenReturn(200);
+        final Bitmap b = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
+        mSurface.fillEmptyBackground(mockCanvas, b);
+        verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any());
+        verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(200.0f), eq(200.0f), any());
+    }
+
+    @Test
+    public void fillEmptyBackground_dontFill_sameSize() throws Exception {
+        final Canvas mockCanvas = mock(Canvas.class);
+        when(mockCanvas.getWidth()).thenReturn(100);
+        when(mockCanvas.getHeight()).thenReturn(100);
+        final Bitmap b = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
+        mSurface.fillEmptyBackground(mockCanvas, b);
+        verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any());
+    }
+
+    @Test
+    public void fillEmptyBackground_dontFill_bitmapLarger() throws Exception {
+        final Canvas mockCanvas = mock(Canvas.class);
+        when(mockCanvas.getWidth()).thenReturn(100);
+        when(mockCanvas.getHeight()).thenReturn(100);
+        final Bitmap b = Bitmap.createBitmap(200, 200, Config.ARGB_8888);
+        mSurface.fillEmptyBackground(mockCanvas, b);
+        verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
index 466da94..085cfd8 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
@@ -20,6 +20,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import android.app.ActivityManager.TaskDescription;
 import android.content.Context;
 import android.graphics.Rect;
 import android.os.Binder;
@@ -76,7 +77,7 @@
         final Rect mInsetBounds = new Rect();
         boolean mFullscreenForTest = true;
         TaskWithBounds(Rect bounds) {
-            super(0, mStubStack, 0, sWm, null, null, false, 0, false, null);
+            super(0, mStubStack, 0, sWm, null, null, false, 0, false, new TaskDescription(), null);
             mBounds = bounds;
         }
         @Override
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 7c25e43..ae344dd 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import android.app.ActivityManager.TaskDescription;
 import android.app.ActivityManagerInternal;
 import android.content.res.Configuration;
 import android.graphics.Rect;
@@ -180,7 +181,7 @@
     /**Creates a {@link Task} and adds it to the specified {@link TaskStack}. */
     static Task createTaskInStack(TaskStack stack, int userId) {
         final Task newTask = new Task(sNextTaskId++, stack, userId, sWm, null, EMPTY, false, 0,
-                false, null);
+                false, new TaskDescription(), null);
         stack.addTask(newTask, POSITION_TOP);
         return newTask;
     }
@@ -239,7 +240,7 @@
                 Configuration overrideConfig, boolean isOnTopLauncher, int resizeMode,
                 boolean homeTask, TaskWindowContainerController controller) {
             super(taskId, stack, userId, service, bounds, overrideConfig, isOnTopLauncher,
-                    resizeMode, homeTask, controller);
+                    resizeMode, homeTask, new TaskDescription(), controller);
         }
 
         boolean shouldDeferRemoval() {
@@ -270,13 +271,14 @@
         TestTaskWindowContainerController(int stackId) {
             super(sNextTaskId++, snapshot -> {}, stackId, 0 /* userId */, null /* bounds */,
                     EMPTY /* overrideConfig*/, RESIZE_MODE_UNRESIZEABLE, false /* homeTask*/,
-                    false /* isOnTopLauncher */, true /* toTop*/, true /* showForAllUsers */);
+                    false /* isOnTopLauncher */, true /* toTop*/, true /* showForAllUsers */,
+                    new TaskDescription());
         }
 
         @Override
         TestTask createTask(int taskId, TaskStack stack, int userId, Rect bounds,
                 Configuration overrideConfig, int resizeMode, boolean homeTask,
-                boolean isOnTopLauncher) {
+                boolean isOnTopLauncher, TaskDescription taskDescription) {
             return new TestTask(taskId, stack, userId, mService, bounds, overrideConfig,
                     isOnTopLauncher, resizeMode, homeTask, this);
         }