diff options
| author | 2020-12-21 01:39:04 +0000 | |
|---|---|---|
| committer | 2020-12-21 01:39:04 +0000 | |
| commit | 612dbde32af63fe0b33d82deb455e65f5fe869eb (patch) | |
| tree | 8a4f323695a1f263463cc7d88a69fea03f065383 /libs | |
| parent | ea470d5ca8b080c5a9ef8b63ac957976ee550d3d (diff) | |
| parent | 285feba16858c65c33f7d18b133b5f3ada9caad1 (diff) | |
Merge "Mirror TaskSnapshotSurface to WMShell(2/N)"
Diffstat (limited to 'libs')
5 files changed, 1099 insertions, 43 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index 10cec6d59cbe..faa4a0ed2294 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -34,6 +34,7 @@ import android.util.Log; import android.util.SparseArray; import android.view.SurfaceControl; import android.window.ITaskOrganizerController; +import android.window.StartingWindowInfo; import android.window.TaskAppearedInfo; import android.window.TaskOrganizer; @@ -112,7 +113,7 @@ public class ShellTaskOrganizer extends TaskOrganizer { // TODO(b/131727939) temporarily live here, the starting surface drawer should be controlled // by a controller, that class should be create while porting // ActivityRecord#addStartingWindow to WMShell. - mStartingSurfaceDrawer = new StartingSurfaceDrawer(context); + mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, mainExecutor); } @Override @@ -229,13 +230,13 @@ public class ShellTaskOrganizer extends TaskOrganizer { } @Override - public void addStartingWindow(RunningTaskInfo taskInfo, IBinder appToken) { - mStartingSurfaceDrawer.addStartingWindow(taskInfo, appToken); + public void addStartingWindow(StartingWindowInfo info, IBinder appToken) { + mStartingSurfaceDrawer.addStartingWindow(info, appToken); } @Override - public void removeStartingWindow(RunningTaskInfo taskInfo) { - mStartingSurfaceDrawer.removeStartingWindow(taskInfo); + public void removeStartingWindow(int taskId) { + mStartingSurfaceDrawer.removeStartingWindow(taskId); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java index ee79824ff4a7..e144ca322686 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java @@ -16,11 +16,18 @@ package com.android.wm.shell.startingsurface; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.content.Context.CONTEXT_RESTRICTED; import static android.content.res.Configuration.EMPTY; import static android.view.Display.DEFAULT_DISPLAY; - -import android.app.ActivityManager; +import static android.window.StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_CREATED; +import static android.window.StartingWindowInfo.TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT; +import static android.window.StartingWindowInfo.TYPE_PARAMETER_NEW_TASK; +import static android.window.StartingWindowInfo.TYPE_PARAMETER_PROCESS_RUNNING; +import static android.window.StartingWindowInfo.TYPE_PARAMETER_TASK_SWITCH; + +import android.app.ActivityManager.RunningTaskInfo; +import android.app.ActivityTaskManager; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; @@ -30,18 +37,20 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.hardware.display.DisplayManager; -import android.os.Handler; import android.os.IBinder; -import android.os.Looper; +import android.os.RemoteException; import android.util.Slog; import android.util.SparseArray; import android.view.Display; import android.view.View; import android.view.WindowManager; +import android.window.StartingWindowInfo; import android.window.TaskOrganizer; +import android.window.TaskSnapshot; import com.android.internal.R; import com.android.internal.policy.PhoneWindow; +import com.android.wm.shell.common.ShellExecutor; import java.util.function.Consumer; @@ -56,22 +65,24 @@ import java.util.function.Consumer; * @hide */ public class StartingSurfaceDrawer { - private static final String TAG = StartingSurfaceDrawer.class.getSimpleName(); - private static final boolean DEBUG_SPLASH_SCREEN = false; + static final String TAG = StartingSurfaceDrawer.class.getSimpleName(); + static final boolean DEBUG_SPLASH_SCREEN = false; + static final boolean DEBUG_TASK_SNAPSHOT = false; private final Context mContext; private final DisplayManager mDisplayManager; + final ShellExecutor mMainExecutor; // TODO(b/131727939) remove this when clearing ActivityRecord private static final int REMOVE_WHEN_TIMEOUT = 2000; - public StartingSurfaceDrawer(Context context) { + public StartingSurfaceDrawer(Context context, ShellExecutor mainExecutor) { mContext = context; mDisplayManager = mContext.getSystemService(DisplayManager.class); + mMainExecutor = mainExecutor; } - private final Handler mHandler = new Handler(Looper.getMainLooper()); - private final SparseArray<TaskScreenView> mTaskScreenViews = new SparseArray<>(); + private final SparseArray<StartingWindowRecord> mStartingWindowRecords = new SparseArray<>(); /** Obtain proper context for showing splash screen on the provided display. */ private Context getDisplayContext(Context context, int displayId) { @@ -90,12 +101,111 @@ public class StartingSurfaceDrawer { return context.createDisplayContext(targetDisplay); } + private static class PreferredStartingTypeHelper { + private static final int STARTING_TYPE_NO = 0; + private static final int STARTING_TYPE_SPLASH_SCREEN = 1; + private static final int STARTING_TYPE_SNAPSHOT = 2; + + TaskSnapshot mSnapshot; + int mPreferredType; + + PreferredStartingTypeHelper(StartingWindowInfo taskInfo) { + final int parameter = taskInfo.startingWindowTypeParameter; + final boolean newTask = (parameter & TYPE_PARAMETER_NEW_TASK) != 0; + final boolean taskSwitch = (parameter & TYPE_PARAMETER_TASK_SWITCH) != 0; + final boolean processRunning = (parameter & TYPE_PARAMETER_PROCESS_RUNNING) != 0; + final boolean allowTaskSnapshot = (parameter & TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT) != 0; + final boolean activityCreated = (parameter & TYPE_PARAMETER_ACTIVITY_CREATED) != 0; + mPreferredType = preferredStartingWindowType(taskInfo, newTask, taskSwitch, + processRunning, allowTaskSnapshot, activityCreated); + } + + // reference from ActivityRecord#getStartingWindowType + private int preferredStartingWindowType(StartingWindowInfo windowInfo, + boolean newTask, boolean taskSwitch, boolean processRunning, + boolean allowTaskSnapshot, boolean activityCreated) { + if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) { + Slog.d(TAG, "preferredStartingWindowType newTask " + newTask + + " taskSwitch " + taskSwitch + + " processRunning " + processRunning + + " allowTaskSnapshot " + allowTaskSnapshot + + " activityCreated " + activityCreated); + } + + if (newTask || !processRunning || (taskSwitch && !activityCreated)) { + return STARTING_TYPE_SPLASH_SCREEN; + } else if (taskSwitch && allowTaskSnapshot) { + final TaskSnapshot snapshot = getTaskSnapshot(windowInfo.taskInfo.taskId); + if (isSnapshotCompatible(windowInfo, snapshot)) { + return STARTING_TYPE_SNAPSHOT; + } + if (windowInfo.taskInfo.topActivityType != ACTIVITY_TYPE_HOME) { + return STARTING_TYPE_SPLASH_SCREEN; + } + return STARTING_TYPE_NO; + } else { + return STARTING_TYPE_NO; + } + } + + /** + * Returns {@code true} if the task snapshot is compatible with this activity (at least the + * rotation must be the same). + */ + private boolean isSnapshotCompatible(StartingWindowInfo windowInfo, TaskSnapshot snapshot) { + if (snapshot == null) { + if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) { + Slog.d(TAG, "isSnapshotCompatible no snapshot " + windowInfo.taskInfo.taskId); + } + return false; + } + + final int taskRotation = windowInfo.taskInfo.configuration + .windowConfiguration.getRotation(); + final int snapshotRotation = snapshot.getRotation(); + if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) { + Slog.d(TAG, "isSnapshotCompatible rotation " + taskRotation + + " snapshot " + snapshotRotation); + } + return taskRotation == snapshotRotation; + } + + private TaskSnapshot getTaskSnapshot(int taskId) { + if (mSnapshot != null) { + return mSnapshot; + } + try { + mSnapshot = ActivityTaskManager.getService().getTaskSnapshot(taskId, + false/* isLowResolution */); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to get snapshot for task: " + taskId + ", from: " + e); + return null; + } + return mSnapshot; + } + } + /** * Called when a task need a starting window. */ - public void addStartingWindow(ActivityManager.RunningTaskInfo taskInfo, IBinder appToken) { + public void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) { + final PreferredStartingTypeHelper helper = + new PreferredStartingTypeHelper(windowInfo); + final RunningTaskInfo runningTaskInfo = windowInfo.taskInfo; + if (helper.mPreferredType == PreferredStartingTypeHelper.STARTING_TYPE_SPLASH_SCREEN) { + addSplashScreenStartingWindow(runningTaskInfo, appToken); + } else if (helper.mPreferredType == PreferredStartingTypeHelper.STARTING_TYPE_SNAPSHOT) { + final TaskSnapshot snapshot = helper.mSnapshot; + makeTaskSnapshotWindow(windowInfo, appToken, snapshot); + } + // If prefer don't show, then don't show! + } + private void addSplashScreenStartingWindow(RunningTaskInfo taskInfo, IBinder appToken) { final ActivityInfo activityInfo = taskInfo.topActivityInfo; + if (activityInfo == null) { + return; + } final int displayId = taskInfo.displayId; if (activityInfo.packageName == null) { return; @@ -250,18 +360,34 @@ public class StartingSurfaceDrawer { } /** + * Called when a task need a snapshot starting window. + */ + private void makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, + IBinder appToken, TaskSnapshot snapshot) { + final int taskId = startingWindowInfo.taskInfo.taskId; + final TaskSnapshotWindow surface = TaskSnapshotWindow.create(startingWindowInfo, appToken, + snapshot, mMainExecutor, () -> removeWindowSynced(taskId) /* clearWindow */); + mMainExecutor.execute(() -> { + mMainExecutor.executeDelayed(() -> removeWindowSynced(taskId), REMOVE_WHEN_TIMEOUT); + final StartingWindowRecord tView = + new StartingWindowRecord(null/* decorView */, surface); + mStartingWindowRecords.put(taskId, tView); + }); + } + + /** * Called when the content of a task is ready to show, starting window can be removed. */ - public void removeStartingWindow(ActivityManager.RunningTaskInfo taskInfo) { - if (DEBUG_SPLASH_SCREEN) { - Slog.d(TAG, "Task start finish, remove starting surface for task " + taskInfo.taskId); + public void removeStartingWindow(int taskId) { + if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) { + Slog.d(TAG, "Task start finish, remove starting surface for task " + taskId); } - mHandler.post(() -> removeWindowSynced(taskInfo.taskId)); + mMainExecutor.execute(() -> removeWindowSynced(taskId)); } protected void postAddWindow(int taskId, IBinder appToken, View view, WindowManager wm, WindowManager.LayoutParams params) { - mHandler.post(() -> { + mMainExecutor.execute(() -> { boolean shouldSaveView = true; try { wm.addView(view, params); @@ -286,25 +412,32 @@ public class StartingSurfaceDrawer { if (shouldSaveView) { removeWindowSynced(taskId); - mHandler.postDelayed(() -> removeWindowSynced(taskId), REMOVE_WHEN_TIMEOUT); - final TaskScreenView tView = new TaskScreenView(view); - mTaskScreenViews.put(taskId, tView); + mMainExecutor.executeDelayed(() -> removeWindowSynced(taskId), REMOVE_WHEN_TIMEOUT); + final StartingWindowRecord tView = + new StartingWindowRecord(view, null /* TaskSnapshotWindow */); + mStartingWindowRecords.put(taskId, tView); } }); } protected void removeWindowSynced(int taskId) { - final TaskScreenView preView = mTaskScreenViews.get(taskId); - if (preView != null) { - if (preView.mDecorView != null) { + final StartingWindowRecord record = mStartingWindowRecords.get(taskId); + if (record != null) { + if (record.mDecorView != null) { if (DEBUG_SPLASH_SCREEN) { Slog.v(TAG, "Removing splash screen window for task: " + taskId); } - final WindowManager wm = preView.mDecorView.getContext() + final WindowManager wm = record.mDecorView.getContext() .getSystemService(WindowManager.class); - wm.removeView(preView.mDecorView); + wm.removeView(record.mDecorView); + } + if (record.mTaskSnapshotWindow != null) { + if (DEBUG_TASK_SNAPSHOT) { + Slog.v(TAG, "Removing task snapshot window for " + taskId); + } + record.mTaskSnapshotWindow.remove(mMainExecutor); } - mTaskScreenViews.remove(taskId); + mStartingWindowRecords.remove(taskId); } } @@ -315,13 +448,15 @@ public class StartingSurfaceDrawer { } /** - * Record the views in a starting window. + * Record the view or surface for a starting window. */ - private static class TaskScreenView { + private static class StartingWindowRecord { private final View mDecorView; + private final TaskSnapshotWindow mTaskSnapshotWindow; - TaskScreenView(View decorView) { + StartingWindowRecord(View decorView, TaskSnapshotWindow taskSnapshotWindow) { mDecorView = decorView; + mTaskSnapshotWindow = taskSnapshotWindow; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java new file mode 100644 index 000000000000..e66d85f55ca9 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java @@ -0,0 +1,622 @@ +/* + * Copyright (C) 2020 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.wm.shell.startingsurface; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.graphics.Color.WHITE; +import static android.graphics.Color.alpha; +import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; +import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; +import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; +import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; +import static android.view.WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES; +import static android.view.WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; +import static android.view.WindowManager.LayoutParams.FLAG_SCALED; +import static android.view.WindowManager.LayoutParams.FLAG_SECURE; +import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY; +import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; +import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION; +import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; +import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; + +import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES; +import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES; +import static com.android.internal.policy.DecorView.getNavigationBarRect; + +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.ActivityManager.TaskDescription; +import android.app.ActivityThread; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Point; +import android.graphics.Rect; +import android.graphics.RectF; +import android.hardware.HardwareBuffer; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.SystemClock; +import android.util.MergedConfiguration; +import android.util.Slog; +import android.view.IWindowSession; +import android.view.InputChannel; +import android.view.InsetsSourceControl; +import android.view.InsetsState; +import android.view.Surface; +import android.view.SurfaceControl; +import android.view.SurfaceSession; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.view.WindowManagerGlobal; +import android.window.ClientWindowFrames; +import android.window.StartingWindowInfo; +import android.window.TaskSnapshot; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.policy.DecorView; +import com.android.internal.view.BaseIWindow; +import com.android.wm.shell.common.ShellExecutor; + +/** + * This class represents a starting window that shows a snapshot. + * + * @hide + */ +public class TaskSnapshotWindow { + + private static final long SIZE_MISMATCH_MINIMUM_TIME_MS = 450; + + /** + * When creating the starting window, we use the exact same layout flags such that we end up + * with a window with the exact same dimensions etc. However, these flags are not used in layout + * and might cause other side effects so we exclude them. + */ + static final int FLAG_INHERIT_EXCLUDES = FLAG_NOT_FOCUSABLE + | FLAG_NOT_TOUCHABLE + | FLAG_NOT_TOUCH_MODAL + | FLAG_ALT_FOCUSABLE_IM + | FLAG_NOT_FOCUSABLE + | FLAG_HARDWARE_ACCELERATED + | FLAG_IGNORE_CHEEK_PRESSES + | FLAG_LOCAL_FOCUS_MODE + | FLAG_SLIPPERY + | FLAG_WATCH_OUTSIDE_TOUCH + | FLAG_SPLIT_TOUCH + | FLAG_SCALED + | FLAG_SECURE; + + private static final String TAG = StartingSurfaceDrawer.TAG; + private static final boolean DEBUG = StartingSurfaceDrawer.DEBUG_TASK_SNAPSHOT; + private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=%s"; + + //tmp vars for unused relayout params + private static final Point TMP_SURFACE_SIZE = new Point(); + + private final Window mWindow; + private final Surface mSurface; + private final Runnable mClearWindowHandler; + private SurfaceControl mSurfaceControl; + private SurfaceControl mChildSurfaceControl; + private final IWindowSession mSession; + private final Rect mTaskBounds; + private final Rect mFrame = new Rect(); + private final Rect mSystemBarInsets = new Rect(); + private TaskSnapshot mSnapshot; + private final RectF mTmpSnapshotSize = new RectF(); + private final RectF mTmpDstFrame = new RectF(); + private final CharSequence mTitle; + private boolean mHasDrawn; + private long mShownTime; + private boolean mSizeMismatch; + private final Paint mBackgroundPaint = new Paint(); + private final int mActivityType; + private final int mStatusBarColor; + private final SystemBarBackgroundPainter mSystemBarBackgroundPainter; + private final int mOrientationOnCreation; + private final SurfaceControl.Transaction mTransaction; + private final Matrix mSnapshotMatrix = new Matrix(); + private final float[] mTmpFloat9 = new float[9]; + + static TaskSnapshotWindow create(StartingWindowInfo info, IBinder appToken, + TaskSnapshot snapshot, ShellExecutor mainExecutor, Runnable clearWindowHandler) { + final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo; + final int taskId = runningTaskInfo.taskId; + if (DEBUG) { + Slog.d(TAG, "create taskSnapshot surface for task: " + taskId); + } + + final WindowManager.LayoutParams attrs = info.topOpaqueWindowLayoutParams; + final WindowManager.LayoutParams mainWindowParams = info.mainWindowLayoutParams; + final InsetsState topWindowInsetsState = info.topOpaqueWindowInsetsState; + if (attrs == null || mainWindowParams == null || topWindowInsetsState == null) { + Slog.w(TAG, "unable to create taskSnapshot surface for task: " + taskId); + return null; + } + final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); + + final int appearance = attrs.insetsFlags.appearance; + final int windowFlags = attrs.flags; + final int windowPrivateFlags = attrs.privateFlags; + + layoutParams.packageName = mainWindowParams.packageName; + layoutParams.windowAnimations = mainWindowParams.windowAnimations; + layoutParams.dimAmount = mainWindowParams.dimAmount; + layoutParams.type = TYPE_APPLICATION_STARTING; + layoutParams.format = snapshot.getHardwareBuffer().getFormat(); + layoutParams.flags = (windowFlags & ~FLAG_INHERIT_EXCLUDES) + | FLAG_NOT_FOCUSABLE + | FLAG_NOT_TOUCHABLE; + // Setting as trusted overlay to let touches pass through. This is safe because this + // window is controlled by the system. + layoutParams.privateFlags = (windowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) + | PRIVATE_FLAG_TRUSTED_OVERLAY; + layoutParams.token = appToken; + layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; + layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; + layoutParams.insetsFlags.appearance = appearance; + layoutParams.insetsFlags.behavior = attrs.insetsFlags.behavior; + layoutParams.layoutInDisplayCutoutMode = attrs.layoutInDisplayCutoutMode; + layoutParams.setFitInsetsTypes(attrs.getFitInsetsTypes()); + layoutParams.setFitInsetsSides(attrs.getFitInsetsSides()); + layoutParams.setFitInsetsIgnoringVisibility(attrs.isFitInsetsIgnoringVisibility()); + + layoutParams.setTitle(String.format(TITLE_FORMAT, taskId)); + + final Point taskSize = snapshot.getTaskSize(); + final Rect taskBounds = new Rect(0, 0, taskSize.x, taskSize.y); + final int orientation = snapshot.getOrientation(); + + final int activityType = runningTaskInfo.topActivityType; + final int displayId = runningTaskInfo.displayId; + + final IWindowSession session = WindowManagerGlobal.getWindowSession(); + final SurfaceControl surfaceControl = new SurfaceControl(); + final ClientWindowFrames tmpFrames = new ClientWindowFrames(); + + final InsetsSourceControl[] mTempControls = new InsetsSourceControl[0]; + final MergedConfiguration tmpMergedConfiguration = new MergedConfiguration(); + + final TaskDescription taskDescription; + if (runningTaskInfo.taskDescription != null) { + taskDescription = runningTaskInfo.taskDescription; + } else { + taskDescription = new TaskDescription(); + taskDescription.setBackgroundColor(WHITE); + } + + final TaskSnapshotWindow snapshotSurface = new TaskSnapshotWindow( + surfaceControl, snapshot, layoutParams.getTitle(), taskDescription, appearance, + windowFlags, windowPrivateFlags, taskBounds, orientation, activityType, + topWindowInsetsState, clearWindowHandler); + final Window window = snapshotSurface.mWindow; + + final InsetsState mTmpInsetsState = new InsetsState(); + final InputChannel tmpInputChannel = new InputChannel(); + mainExecutor.execute(() -> { + try { + final int res = session.addToDisplay(window, layoutParams, View.GONE, + displayId, mTmpInsetsState, tmpFrames.frame, + tmpFrames.displayCutout, tmpInputChannel/* outInputChannel */, + mTmpInsetsState, mTempControls); + if (res < 0) { + Slog.w(TAG, "Failed to add snapshot starting window res=" + res); + return; + } + } catch (RemoteException e) { + snapshotSurface.clearWindowSynced(); + } + window.setOuter(snapshotSurface, mainExecutor); + try { + session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, -1, + tmpFrames, tmpMergedConfiguration, surfaceControl, mTmpInsetsState, + mTempControls, TMP_SURFACE_SIZE); + } catch (RemoteException e) { + snapshotSurface.clearWindowSynced(); + } + + final Rect systemBarInsets = getSystemBarInsets(tmpFrames.frame, topWindowInsetsState); + snapshotSurface.setFrames(tmpFrames.frame, systemBarInsets); + snapshotSurface.drawSnapshot(); + }); + return snapshotSurface; + } + + public TaskSnapshotWindow(SurfaceControl surfaceControl, + TaskSnapshot snapshot, CharSequence title, TaskDescription taskDescription, + int appearance, int windowFlags, int windowPrivateFlags, Rect taskBounds, + int currentOrientation, int activityType, InsetsState topWindowInsetsState, + Runnable clearWindowHandler) { + mSurface = new Surface(); + mSession = WindowManagerGlobal.getWindowSession(); + mWindow = new Window(); + mWindow.setSession(mSession); + mSurfaceControl = surfaceControl; + mSnapshot = snapshot; + mTitle = title; + int backgroundColor = taskDescription.getBackgroundColor(); + mBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE); + mTaskBounds = taskBounds; + mSystemBarBackgroundPainter = new SystemBarBackgroundPainter(windowFlags, + windowPrivateFlags, appearance, taskDescription, 1f, topWindowInsetsState); + mStatusBarColor = taskDescription.getStatusBarColor(); + mOrientationOnCreation = currentOrientation; + mActivityType = activityType; + mTransaction = new SurfaceControl.Transaction(); + mClearWindowHandler = clearWindowHandler; + } + + /** + * Ask system bar background painter to draw status bar background. + * @hide + */ + public void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame) { + mSystemBarBackgroundPainter.drawStatusBarBackground(c, alreadyDrawnFrame, + mSystemBarBackgroundPainter.getStatusBarColorViewHeight()); + } + + /** + * Ask system bar background painter to draw navigation bar background. + * @hide + */ + public void drawNavigationBarBackground(Canvas c) { + mSystemBarBackgroundPainter.drawNavigationBarBackground(c); + } + + void remove(ShellExecutor mainExecutor) { + final long now = SystemClock.uptimeMillis(); + if (mSizeMismatch && now - mShownTime < SIZE_MISMATCH_MINIMUM_TIME_MS + // Show the latest content as soon as possible for unlocking to home. + && mActivityType != ACTIVITY_TYPE_HOME) { + final long delayTime = mShownTime + SIZE_MISMATCH_MINIMUM_TIME_MS - now; + mainExecutor.executeDelayed(() -> remove(mainExecutor), delayTime); + if (DEBUG) { + Slog.d(TAG, "Defer removing snapshot surface in " + delayTime); + } + return; + } + mainExecutor.execute(() -> { + try { + if (DEBUG) { + Slog.d(TAG, "Removing snapshot surface, mHasDrawn: " + mHasDrawn); + } + mSession.remove(mWindow); + } catch (RemoteException e) { + // nothing + } + }); + } + + /** + * Set frame size. + * @hide + */ + public void setFrames(Rect frame, Rect systemBarInsets) { + mFrame.set(frame); + mSystemBarInsets.set(systemBarInsets); + final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer(); + mSizeMismatch = (mFrame.width() != snapshot.getWidth() + || mFrame.height() != snapshot.getHeight()); + mSystemBarBackgroundPainter.setInsets(systemBarInsets); + } + + static Rect getSystemBarInsets(Rect frame, InsetsState state) { + return state.calculateInsets(frame, WindowInsets.Type.systemBars(), + false /* ignoreVisibility */); + } + + private void drawSnapshot() { + mSurface.copyFrom(mSurfaceControl); + if (DEBUG) { + Slog.d(TAG, "Drawing snapshot surface sizeMismatch= " + mSizeMismatch); + } + if (mSizeMismatch) { + // The dimensions of the buffer and the window don't match, so attaching the buffer + // will fail. Better create a child window with the exact dimensions and fill the parent + // window with the background color! + drawSizeMismatchSnapshot(); + } else { + drawSizeMatchSnapshot(); + } + mShownTime = SystemClock.uptimeMillis(); + mHasDrawn = true; + reportDrawn(); + + // In case window manager leaks us, make sure we don't retain the snapshot. + mSnapshot = null; + } + + private void drawSizeMatchSnapshot() { + mSurface.attachAndQueueBufferWithColorSpace(mSnapshot.getHardwareBuffer(), + mSnapshot.getColorSpace()); + mSurface.release(); + } + + private void drawSizeMismatchSnapshot() { + if (!mSurface.isValid()) { + throw new IllegalStateException("mSurface does not hold a valid surface."); + } + final HardwareBuffer buffer = mSnapshot.getHardwareBuffer(); + final SurfaceSession session = new SurfaceSession(); + + // We consider nearly matched dimensions as there can be rounding errors and the user won't + // notice very minute differences from scaling one dimension more than the other + final boolean aspectRatioMismatch = Math.abs( + ((float) buffer.getWidth() / buffer.getHeight()) + - ((float) mFrame.width() / mFrame.height())) > 0.01f; + + // Keep a reference to it such that it doesn't get destroyed when finalized. + mChildSurfaceControl = new SurfaceControl.Builder(session) + .setName(mTitle + " - task-snapshot-surface") + .setBufferSize(buffer.getWidth(), buffer.getHeight()) + .setFormat(buffer.getFormat()) + .setParent(mSurfaceControl) + .setCallsite("TaskSnapshotWindow.drawSizeMismatchSnapshot") + .build(); + Surface surface = new Surface(); + surface.copyFrom(mChildSurfaceControl); + + final Rect frame; + // We can just show the surface here as it will still be hidden as the parent is + // still hidden. + mTransaction.show(mChildSurfaceControl); + if (aspectRatioMismatch) { + // Clip off ugly navigation bar. + final Rect crop = calculateSnapshotCrop(); + frame = calculateSnapshotFrame(crop); + mTransaction.setWindowCrop(mChildSurfaceControl, crop); + mTransaction.setPosition(mChildSurfaceControl, frame.left, frame.top); + mTmpSnapshotSize.set(crop); + mTmpDstFrame.set(frame); + } else { + frame = null; + mTmpSnapshotSize.set(0, 0, buffer.getWidth(), buffer.getHeight()); + mTmpDstFrame.set(mFrame); + mTmpDstFrame.offsetTo(0, 0); + } + + // Scale the mismatch dimensions to fill the task bounds + mSnapshotMatrix.setRectToRect(mTmpSnapshotSize, mTmpDstFrame, Matrix.ScaleToFit.FILL); + mTransaction.setMatrix(mChildSurfaceControl, mSnapshotMatrix, mTmpFloat9); + + mTransaction.apply(); + surface.attachAndQueueBufferWithColorSpace(buffer, mSnapshot.getColorSpace()); + surface.release(); + + if (aspectRatioMismatch) { + final Canvas c = mSurface.lockCanvas(null); + drawBackgroundAndBars(c, frame); + mSurface.unlockCanvasAndPost(c); + mSurface.release(); + } + } + + /** + * Calculates the snapshot crop in snapshot coordinate space. + * + * @return crop rect in snapshot coordinate space. + */ + public Rect calculateSnapshotCrop() { + final Rect rect = new Rect(); + final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer(); + rect.set(0, 0, snapshot.getWidth(), snapshot.getHeight()); + final Rect insets = mSnapshot.getContentInsets(); + + final float scaleX = (float) snapshot.getWidth() / mSnapshot.getTaskSize().x; + final float scaleY = (float) snapshot.getHeight() / mSnapshot.getTaskSize().y; + + // Let's remove all system decorations except the status bar, but only if the task is at the + // very top of the screen. + final boolean isTop = mTaskBounds.top == 0 && mFrame.top == 0; + rect.inset((int) (insets.left * scaleX), + isTop ? 0 : (int) (insets.top * scaleY), + (int) (insets.right * scaleX), + (int) (insets.bottom * scaleY)); + return rect; + } + + /** + * Calculates the snapshot frame in window coordinate space from crop. + * + * @param crop rect that is in snapshot coordinate space. + */ + public Rect calculateSnapshotFrame(Rect crop) { + final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer(); + final float scaleX = (float) snapshot.getWidth() / mSnapshot.getTaskSize().x; + final float scaleY = (float) snapshot.getHeight() / mSnapshot.getTaskSize().y; + + // Rescale the frame from snapshot to window coordinate space + final Rect frame = new Rect(0, 0, + (int) (crop.width() / scaleX + 0.5f), + (int) (crop.height() / scaleY + 0.5f) + ); + + // However, we also need to make space for the navigation bar on the left side. + frame.offset(mSystemBarInsets.left, 0); + return frame; + } + + /** + * Draw status bar and navigation bar background. + * @hide + */ + public void drawBackgroundAndBars(Canvas c, Rect frame) { + final int statusBarHeight = mSystemBarBackgroundPainter.getStatusBarColorViewHeight(); + final boolean fillHorizontally = c.getWidth() > frame.right; + final boolean fillVertically = c.getHeight() > frame.bottom; + if (fillHorizontally) { + c.drawRect(frame.right, alpha(mStatusBarColor) == 0xFF ? statusBarHeight : 0, + c.getWidth(), fillVertically + ? frame.bottom + : c.getHeight(), + mBackgroundPaint); + } + if (fillVertically) { + c.drawRect(0, frame.bottom, c.getWidth(), c.getHeight(), mBackgroundPaint); + } + mSystemBarBackgroundPainter.drawDecors(c, frame); + } + + /** + * Clear window from drawer, must be post on main executor. + */ + private void clearWindowSynced() { + if (mClearWindowHandler != null) { + mClearWindowHandler.run(); + } + } + + private void reportDrawn() { + try { + mSession.finishDrawing(mWindow, null /* postDrawTransaction */); + } catch (RemoteException e) { + clearWindowSynced(); + } + } + + static class Window extends BaseIWindow { + private TaskSnapshotWindow mOuter; + private ShellExecutor mMainExecutor; + + public void setOuter(TaskSnapshotWindow outer, ShellExecutor mainExecutor) { + mOuter = outer; + mMainExecutor = mainExecutor; + } + + @Override + public void resized(ClientWindowFrames frames, boolean reportDraw, + MergedConfiguration mergedConfiguration, boolean forceLayout, + boolean alwaysConsumeSystemBars, int displayId) { + if (mOuter != null) { + if (mergedConfiguration != null + && mOuter.mOrientationOnCreation + != mergedConfiguration.getMergedConfiguration().orientation) { + // The orientation of the screen is changing. We better remove the snapshot ASAP + // as we are going to wait on the new window in any case to unfreeze the screen, + // and the starting window is not needed anymore. + mMainExecutor.execute(() -> { + mOuter.clearWindowSynced(); + }); + } else if (reportDraw) { + mMainExecutor.execute(() -> { + if (mOuter.mHasDrawn) { + mOuter.reportDrawn(); + } + }); + } + } + } + } + + /** + * Helper class to draw the background of the system bars in regions the task snapshot isn't + * filling the window. + */ + static class SystemBarBackgroundPainter { + private final Paint mStatusBarPaint = new Paint(); + private final Paint mNavigationBarPaint = new Paint(); + private final int mStatusBarColor; + private final int mNavigationBarColor; + private final int mWindowFlags; + private final int mWindowPrivateFlags; + private final float mScale; + private final InsetsState mInsetsState; + private final Rect mSystemBarInsets = new Rect(); + + SystemBarBackgroundPainter(int windowFlags, int windowPrivateFlags, int appearance, + TaskDescription taskDescription, float scale, InsetsState insetsState) { + mWindowFlags = windowFlags; + mWindowPrivateFlags = windowPrivateFlags; + mScale = scale; + final Context context = ActivityThread.currentActivityThread().getSystemUiContext(); + final int semiTransparent = context.getColor( + R.color.system_bar_background_semi_transparent); + mStatusBarColor = DecorView.calculateBarColor(windowFlags, FLAG_TRANSLUCENT_STATUS, + semiTransparent, taskDescription.getStatusBarColor(), appearance, + APPEARANCE_LIGHT_STATUS_BARS, + taskDescription.getEnsureStatusBarContrastWhenTransparent()); + mNavigationBarColor = DecorView.calculateBarColor(windowFlags, + FLAG_TRANSLUCENT_NAVIGATION, semiTransparent, + taskDescription.getNavigationBarColor(), appearance, + APPEARANCE_LIGHT_NAVIGATION_BARS, + taskDescription.getEnsureNavigationBarContrastWhenTransparent() + && context.getResources().getBoolean(R.bool.config_navBarNeedsScrim)); + mStatusBarPaint.setColor(mStatusBarColor); + mNavigationBarPaint.setColor(mNavigationBarColor); + mInsetsState = insetsState; + } + + void setInsets(Rect systemBarInsets) { + mSystemBarInsets.set(systemBarInsets); + } + + int getStatusBarColorViewHeight() { + final boolean forceBarBackground = + (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0; + if (STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible( + mInsetsState, mStatusBarColor, mWindowFlags, forceBarBackground)) { + return (int) (mSystemBarInsets.top * mScale); + } else { + return 0; + } + } + + private boolean isNavigationBarColorViewVisible() { + final boolean forceBarBackground = + (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0; + return NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES.isVisible( + mInsetsState, mNavigationBarColor, mWindowFlags, forceBarBackground); + } + + void drawDecors(Canvas c, @Nullable Rect alreadyDrawnFrame) { + drawStatusBarBackground(c, alreadyDrawnFrame, getStatusBarColorViewHeight()); + drawNavigationBarBackground(c); + } + + void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame, + int statusBarHeight) { + if (statusBarHeight > 0 && Color.alpha(mStatusBarColor) != 0 + && (alreadyDrawnFrame == null || c.getWidth() > alreadyDrawnFrame.right)) { + final int rightInset = (int) (mSystemBarInsets.right * mScale); + final int left = alreadyDrawnFrame != null ? alreadyDrawnFrame.right : 0; + c.drawRect(left, 0, c.getWidth() - rightInset, statusBarHeight, mStatusBarPaint); + } + } + + @VisibleForTesting + void drawNavigationBarBackground(Canvas c) { + final Rect navigationBarRect = new Rect(); + getNavigationBarRect(c.getWidth(), c.getHeight(), mSystemBarInsets, navigationBarRect, + mScale); + final boolean visible = isNavigationBarColorViewVisible(); + if (visible && Color.alpha(mNavigationBarColor) != 0 && !navigationBarRect.isEmpty()) { + c.drawRect(navigationBarRect, mNavigationBarPaint); + } + } + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java index f5628abb100f..07115c2b4d22 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java @@ -41,11 +41,14 @@ import android.testing.TestableContext; import android.view.View; import android.view.WindowManager; import android.view.WindowMetrics; +import android.window.StartingWindowInfo; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.wm.shell.common.HandlerExecutor; +import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.startingsurface.StartingSurfaceDrawer; import org.junit.Before; @@ -70,8 +73,8 @@ public class StartingSurfaceDrawerTests { static final class TestStartingSurfaceDrawer extends StartingSurfaceDrawer{ int mAddWindowForTask = 0; - TestStartingSurfaceDrawer(Context context) { - super(context); + TestStartingSurfaceDrawer(Context context, ShellExecutor executor) { + super(context, executor); } @Override @@ -109,36 +112,38 @@ public class StartingSurfaceDrawerTests { doReturn(metrics).when(mMockWindowManager).getMaximumWindowMetrics(); doNothing().when(mMockWindowManager).addView(any(), any()); - mStartingSurfaceDrawer = spy(new TestStartingSurfaceDrawer(context)); + mStartingSurfaceDrawer = spy(new TestStartingSurfaceDrawer(context, + new HandlerExecutor(new Handler(Looper.getMainLooper())))); } @Test public void testAddSplashScreenSurface() { final int taskId = 1; final Handler mainLoop = new Handler(Looper.getMainLooper()); - final ActivityManager.RunningTaskInfo taskInfo = - createTaskInfo(taskId, WINDOWING_MODE_FULLSCREEN); - mStartingSurfaceDrawer.addStartingWindow(taskInfo, mBinder); + final StartingWindowInfo windowInfo = + createWindowInfo(taskId, WINDOWING_MODE_FULLSCREEN); + mStartingSurfaceDrawer.addStartingWindow(windowInfo, mBinder); waitHandlerIdle(mainLoop); verify(mStartingSurfaceDrawer).postAddWindow(eq(taskId), eq(mBinder), any(), any(), any()); assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, taskId); - mStartingSurfaceDrawer.removeStartingWindow(taskInfo); + mStartingSurfaceDrawer.removeStartingWindow(windowInfo.taskInfo.taskId); waitHandlerIdle(mainLoop); verify(mStartingSurfaceDrawer).removeWindowSynced(eq(taskId)); assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, 0); } - private ActivityManager.RunningTaskInfo createTaskInfo(int taskId, int windowingMode) { - ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); + private StartingWindowInfo createWindowInfo(int taskId, int windowingMode) { + StartingWindowInfo windowInfo = new StartingWindowInfo(); final ActivityInfo info = new ActivityInfo(); info.applicationInfo = new ApplicationInfo(); info.packageName = "test"; info.theme = android.R.style.Theme; + final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); taskInfo.topActivityInfo = info; taskInfo.taskId = taskId; - taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode); - return taskInfo; + windowInfo.taskInfo = taskInfo; + return windowInfo; } private static void waitHandlerIdle(Handler handler) { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java new file mode 100644 index 000000000000..94af32999734 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java @@ -0,0 +1,293 @@ +/* + * 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 unittest.src.com.android.wm.shell.startingsurface; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.eq; + +import android.app.ActivityManager.TaskDescription; +import android.content.ComponentName; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorSpace; +import android.graphics.Point; +import android.graphics.Rect; +import android.hardware.HardwareBuffer; +import android.view.InsetsState; +import android.view.Surface; +import android.view.SurfaceControl; +import android.window.TaskSnapshot; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.startingsurface.TaskSnapshotWindow; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test class for {@link TaskSnapshotWindow}. + * + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class TaskSnapshotWindowTest { + + private TaskSnapshotWindow mWindow; + + private void setupSurface(int width, int height) { + setupSurface(width, height, new Rect(), 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, + new Rect(0, 0, width, height)); + } + + private void setupSurface(int width, int height, Rect contentInsets, int sysuiVis, + int windowFlags, Rect taskBounds) { + // Previously when constructing TaskSnapshots for this test, scale was 1.0f, so to mimic + // this behavior set the taskSize to be the same as the taskBounds width and height. The + // taskBounds passed here are assumed to be the same task bounds as when the snapshot was + // taken. We assume there is no aspect ratio mismatch between the screenshot and the + // taskBounds + assertEquals(width, taskBounds.width()); + assertEquals(height, taskBounds.height()); + Point taskSize = new Point(taskBounds.width(), taskBounds.height()); + + final TaskSnapshot snapshot = createTaskSnapshot(width, height, taskSize, contentInsets); + mWindow = new TaskSnapshotWindow(new SurfaceControl(), snapshot, "Test", + createTaskDescription(Color.WHITE, Color.RED, Color.BLUE), + 0 /* appearance */, windowFlags /* windowFlags */, 0 /* privateWindowFlags */, + taskBounds, ORIENTATION_PORTRAIT, ACTIVITY_TYPE_STANDARD, new InsetsState(), + null /* clearWindow */); + } + + private TaskSnapshot createTaskSnapshot(int width, int height, Point taskSize, + Rect contentInsets) { + final HardwareBuffer buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888, + 1, HardwareBuffer.USAGE_CPU_READ_RARELY); + return new TaskSnapshot( + System.currentTimeMillis(), + new ComponentName("", ""), buffer, + ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT, + Surface.ROTATION_0, taskSize, contentInsets, false, + true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN, + 0 /* systemUiVisibility */, false /* isTranslucent */); + } + + private static TaskDescription createTaskDescription(int background, int statusBar, + int navigationBar) { + final TaskDescription td = new TaskDescription(); + td.setBackgroundColor(background); + td.setStatusBarColor(statusBar); + td.setNavigationBarColor(navigationBar); + return td; + } + + @Test + public void fillEmptyBackground_fillHorizontally() { + setupSurface(200, 100); + final Canvas mockCanvas = mock(Canvas.class); + when(mockCanvas.getWidth()).thenReturn(200); + when(mockCanvas.getHeight()).thenReturn(100); + mWindow.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 200)); + verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any()); + } + + @Test + public void fillEmptyBackground_fillVertically() { + setupSurface(100, 200); + final Canvas mockCanvas = mock(Canvas.class); + when(mockCanvas.getWidth()).thenReturn(100); + when(mockCanvas.getHeight()).thenReturn(200); + mWindow.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 200, 100)); + verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(100.0f), eq(200.0f), any()); + } + + @Test + public void fillEmptyBackground_fillBoth() { + setupSurface(200, 200); + final Canvas mockCanvas = mock(Canvas.class); + when(mockCanvas.getWidth()).thenReturn(200); + when(mockCanvas.getHeight()).thenReturn(200); + mWindow.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 100)); + 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() { + setupSurface(100, 100); + final Canvas mockCanvas = mock(Canvas.class); + when(mockCanvas.getWidth()).thenReturn(100); + when(mockCanvas.getHeight()).thenReturn(100); + mWindow.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 100)); + verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any()); + } + + @Test + public void fillEmptyBackground_dontFill_bitmapLarger() { + setupSurface(100, 100); + final Canvas mockCanvas = mock(Canvas.class); + when(mockCanvas.getWidth()).thenReturn(100); + when(mockCanvas.getHeight()).thenReturn(100); + mWindow.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 200, 200)); + verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any()); + } + + @Test + public void testCalculateSnapshotCrop() { + setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, 0, new Rect(0, 0, 100, 100)); + assertEquals(new Rect(0, 0, 100, 90), mWindow.calculateSnapshotCrop()); + } + + @Test + public void testCalculateSnapshotCrop_taskNotOnTop() { + setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, 0, new Rect(0, 50, 100, 150)); + assertEquals(new Rect(0, 10, 100, 90), mWindow.calculateSnapshotCrop()); + } + + @Test + public void testCalculateSnapshotCrop_navBarLeft() { + setupSurface(100, 100, new Rect(10, 10, 0, 0), 0, 0, new Rect(0, 0, 100, 100)); + assertEquals(new Rect(10, 0, 100, 100), mWindow.calculateSnapshotCrop()); + } + + @Test + public void testCalculateSnapshotCrop_navBarRight() { + setupSurface(100, 100, new Rect(0, 10, 10, 0), 0, 0, new Rect(0, 0, 100, 100)); + assertEquals(new Rect(0, 0, 90, 100), mWindow.calculateSnapshotCrop()); + } + + @Test + public void testCalculateSnapshotCrop_waterfall() { + setupSurface(100, 100, new Rect(5, 10, 5, 10), 0, 0, new Rect(0, 0, 100, 100)); + assertEquals(new Rect(5, 0, 95, 90), mWindow.calculateSnapshotCrop()); + } + + @Test + public void testCalculateSnapshotFrame() { + setupSurface(100, 100); + final Rect insets = new Rect(0, 10, 0, 10); + mWindow.setFrames(new Rect(0, 0, 100, 100), insets); + assertEquals(new Rect(0, 0, 100, 80), + mWindow.calculateSnapshotFrame(new Rect(0, 10, 100, 90))); + } + + @Test + public void testCalculateSnapshotFrame_navBarLeft() { + setupSurface(100, 100); + final Rect insets = new Rect(10, 10, 0, 0); + mWindow.setFrames(new Rect(0, 0, 100, 100), insets); + assertEquals(new Rect(10, 0, 100, 90), + mWindow.calculateSnapshotFrame(new Rect(10, 10, 100, 100))); + } + + @Test + public void testCalculateSnapshotFrame_waterfall() { + setupSurface(100, 100, new Rect(5, 10, 5, 10), 0, 0, new Rect(0, 0, 100, 100)); + final Rect insets = new Rect(0, 10, 0, 10); + mWindow.setFrames(new Rect(5, 0, 95, 100), insets); + assertEquals(new Rect(0, 0, 90, 90), + mWindow.calculateSnapshotFrame(new Rect(5, 0, 95, 90))); + } + + @Test + public void testDrawStatusBarBackground() { + setupSurface(100, 100); + final Rect insets = new Rect(0, 10, 10, 0); + mWindow.setFrames(new Rect(0, 0, 100, 100), insets); + final Canvas mockCanvas = mock(Canvas.class); + when(mockCanvas.getWidth()).thenReturn(100); + when(mockCanvas.getHeight()).thenReturn(100); + mWindow.drawStatusBarBackground(mockCanvas, new Rect(0, 0, 50, 100)); + verify(mockCanvas).drawRect(eq(50.0f), eq(0.0f), eq(90.0f), eq(10.0f), any()); + } + + @Test + public void testDrawStatusBarBackground_nullFrame() { + setupSurface(100, 100); + final Rect insets = new Rect(0, 10, 10, 0); + mWindow.setFrames(new Rect(0, 0, 100, 100), insets); + final Canvas mockCanvas = mock(Canvas.class); + when(mockCanvas.getWidth()).thenReturn(100); + when(mockCanvas.getHeight()).thenReturn(100); + mWindow.drawStatusBarBackground(mockCanvas, null); + verify(mockCanvas).drawRect(eq(0.0f), eq(0.0f), eq(90.0f), eq(10.0f), any()); + } + + @Test + public void testDrawStatusBarBackground_nope() { + setupSurface(100, 100); + final Rect insets = new Rect(0, 10, 10, 0); + mWindow.setFrames(new Rect(0, 0, 100, 100), insets); + final Canvas mockCanvas = mock(Canvas.class); + when(mockCanvas.getWidth()).thenReturn(100); + when(mockCanvas.getHeight()).thenReturn(100); + mWindow.drawStatusBarBackground(mockCanvas, new Rect(0, 0, 100, 100)); + verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any()); + } + + @Test + public void testDrawNavigationBarBackground() { + final Rect insets = new Rect(0, 10, 0, 10); + setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, + new Rect(0, 0, 100, 100)); + mWindow.setFrames(new Rect(0, 0, 100, 100), insets); + final Canvas mockCanvas = mock(Canvas.class); + when(mockCanvas.getWidth()).thenReturn(100); + when(mockCanvas.getHeight()).thenReturn(100); + mWindow.drawNavigationBarBackground(mockCanvas); + verify(mockCanvas).drawRect(eq(new Rect(0, 90, 100, 100)), any()); + } + + @Test + public void testDrawNavigationBarBackground_left() { + final Rect insets = new Rect(10, 10, 0, 0); + setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, + new Rect(0, 0, 100, 100)); + mWindow.setFrames(new Rect(0, 0, 100, 100), insets); + final Canvas mockCanvas = mock(Canvas.class); + when(mockCanvas.getWidth()).thenReturn(100); + when(mockCanvas.getHeight()).thenReturn(100); + mWindow.drawNavigationBarBackground(mockCanvas); + verify(mockCanvas).drawRect(eq(new Rect(0, 0, 10, 100)), any()); + } + + @Test + public void testDrawNavigationBarBackground_right() { + final Rect insets = new Rect(0, 10, 10, 0); + setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, + new Rect(0, 0, 100, 100)); + mWindow.setFrames(new Rect(0, 0, 100, 100), insets); + final Canvas mockCanvas = mock(Canvas.class); + when(mockCanvas.getWidth()).thenReturn(100); + when(mockCanvas.getHeight()).thenReturn(100); + mWindow.drawNavigationBarBackground(mockCanvas); + verify(mockCanvas).drawRect(eq(new Rect(90, 0, 100, 100)), any()); + } +} |