diff options
| author | 2020-09-28 17:43:40 +0800 | |
|---|---|---|
| committer | 2020-12-17 11:00:07 +0800 | |
| commit | 285feba16858c65c33f7d18b133b5f3ada9caad1 (patch) | |
| tree | 683935c57b5908fff1144ba591b913e61ab4c779 | |
| parent | f2bda9e3e3cde06ca15645e771ac5fd99e8964b6 (diff) | |
Mirror TaskSnapshotSurface to WMShell(2/N)
- Let WMShell able to draw task snapshot surface.
- Create new class StartingWindowInfo to pass needed data from WM to
Shell.
- Rename TaskSnapshotSurface -> TaskSnapshotWindow in WMShell.
- Mirror and modify a new TaskSnapshotWindowTest in WMShell.
Bug: 131727939
Test: atest WindowOrganizerTests ActivityRecordTests
TaskSnapshotWindowTest StartingSurfaceDrawerTests
Test: build and flash, check shell able to show/remove
TaskSnapshotWindow
Change-Id: I70dec8ce2bf1ed7d876657324e51357690c8b396
21 files changed, 1439 insertions, 113 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt index f51bb95bd710..5120fb3ee314 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2457,6 +2457,13 @@ package android.window { field public static final int FEATURE_WINDOW_TOKENS = 2; // 0x2 } + public final class StartingWindowInfo implements android.os.Parcelable { + ctor public StartingWindowInfo(); + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.window.StartingWindowInfo> CREATOR; + } + public final class TaskAppearedInfo implements android.os.Parcelable { ctor public TaskAppearedInfo(@NonNull android.app.ActivityManager.RunningTaskInfo, @NonNull android.view.SurfaceControl); method public int describeContents(); @@ -2468,7 +2475,7 @@ package android.window { public class TaskOrganizer extends android.window.WindowOrganizer { ctor public TaskOrganizer(); - method @BinderThread public void addStartingWindow(@NonNull android.app.ActivityManager.RunningTaskInfo, @NonNull android.os.IBinder); + method @BinderThread public void addStartingWindow(@NonNull android.window.StartingWindowInfo, @NonNull android.os.IBinder); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void createRootTask(int, int, @Nullable android.os.IBinder); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public boolean deleteRootTask(@NonNull android.window.WindowContainerToken); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public java.util.List<android.app.ActivityManager.RunningTaskInfo> getChildTasks(@NonNull android.window.WindowContainerToken, @NonNull int[]); @@ -2479,7 +2486,7 @@ package android.window { method @BinderThread public void onTaskInfoChanged(@NonNull android.app.ActivityManager.RunningTaskInfo); method @BinderThread public void onTaskVanished(@NonNull android.app.ActivityManager.RunningTaskInfo); method @CallSuper @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public java.util.List<android.window.TaskAppearedInfo> registerOrganizer(); - method @BinderThread public void removeStartingWindow(@NonNull android.app.ActivityManager.RunningTaskInfo); + method @BinderThread public void removeStartingWindow(int); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void setInterceptBackPressedOnTaskRoot(@NonNull android.window.WindowContainerToken, boolean); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void setLaunchRoot(int, @NonNull android.window.WindowContainerToken); method @CallSuper @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void unregisterOrganizer(); diff --git a/core/java/android/window/ITaskOrganizer.aidl b/core/java/android/window/ITaskOrganizer.aidl index b503184f4c51..88b2257a55b1 100644 --- a/core/java/android/window/ITaskOrganizer.aidl +++ b/core/java/android/window/ITaskOrganizer.aidl @@ -18,6 +18,7 @@ package android.window; import android.view.SurfaceControl; import android.app.ActivityManager; +import android.window.StartingWindowInfo; import android.window.WindowContainerToken; /** @@ -30,15 +31,15 @@ oneway interface ITaskOrganizer { * application is starting. The client is responsible to add/remove the starting window if it * has create a starting window for the Task. * - * @param taskInfo The information about the Task that's available + * @param info The information about the Task that's available * @param appToken Token of the application being started. */ - void addStartingWindow(in ActivityManager.RunningTaskInfo taskInfo, IBinder appToken); + void addStartingWindow(in StartingWindowInfo info, IBinder appToken); /** * Called when the Task want to remove the starting window. */ - void removeStartingWindow(in ActivityManager.RunningTaskInfo taskInfo); + void removeStartingWindow(int taskId); /** * A callback when the Task is available for the registered organizer. The client is responsible diff --git a/core/java/android/window/StartingWindowInfo.aidl b/core/java/android/window/StartingWindowInfo.aidl new file mode 100644 index 000000000000..69b18f05fa8c --- /dev/null +++ b/core/java/android/window/StartingWindowInfo.aidl @@ -0,0 +1,20 @@ +/** + * 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 android.window; + +/** @hide */ +parcelable StartingWindowInfo;
\ No newline at end of file diff --git a/core/java/android/window/StartingWindowInfo.java b/core/java/android/window/StartingWindowInfo.java new file mode 100644 index 000000000000..2282cc567936 --- /dev/null +++ b/core/java/android/window/StartingWindowInfo.java @@ -0,0 +1,150 @@ +/* + * 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 android.window; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.TestApi; +import android.app.ActivityManager; +import android.app.TaskInfo; +import android.os.Parcel; +import android.os.Parcelable; +import android.view.InsetsState; +import android.view.WindowManager; + +/** + * Information you can retrieve about a starting window of a particular task that is currently + * start in the system. + * @hide + */ +@TestApi +public final class StartingWindowInfo implements Parcelable { + /** + * The {@link TaskInfo} from this task. + * @hide + */ + @NonNull + public ActivityManager.RunningTaskInfo taskInfo; + + /** + * InsetsState of TopFullscreenOpaqueWindow + * @hide + */ + @Nullable + public InsetsState topOpaqueWindowInsetsState; + + /** + * LayoutParams of TopFullscreenOpaqueWindow + * @hide + */ + @Nullable + public WindowManager.LayoutParams topOpaqueWindowLayoutParams; + + /** + * LayoutParams of MainWindow + * @hide + */ + @Nullable + public WindowManager.LayoutParams mainWindowLayoutParams; + + /** + * @hide + */ + @IntDef(flag = true, prefix = "TYPE_PARAMETER_", value = { + TYPE_PARAMETER_NEW_TASK, + TYPE_PARAMETER_TASK_SWITCH, + TYPE_PARAMETER_PROCESS_RUNNING, + TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT, + TYPE_PARAMETER_ACTIVITY_CREATED + }) + public @interface StartingTypeParams {} + + /** + * The parameters of the starting window... + * @hide + */ + public static final int TYPE_PARAMETER_NEW_TASK = 0x00000001; + /** @hide */ + public static final int TYPE_PARAMETER_TASK_SWITCH = 0x00000002; + /** @hide */ + public static final int TYPE_PARAMETER_PROCESS_RUNNING = 0x00000004; + /** @hide */ + public static final int TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT = 0x00000008; + /** @hide */ + public static final int TYPE_PARAMETER_ACTIVITY_CREATED = 0x00000010; + + /** + * The parameters which effect the starting window type. + * @see android.window.StartingWindowInfo.StartingTypeParams + * @hide + */ + public int startingWindowTypeParameter; + + public StartingWindowInfo() { + + } + + private StartingWindowInfo(@NonNull Parcel source) { + readFromParcel(source); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeTypedObject(taskInfo, flags); + dest.writeInt(startingWindowTypeParameter); + dest.writeTypedObject(topOpaqueWindowInsetsState, flags); + dest.writeTypedObject(topOpaqueWindowLayoutParams, flags); + dest.writeTypedObject(mainWindowLayoutParams, flags); + } + + void readFromParcel(@NonNull Parcel source) { + taskInfo = source.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR); + startingWindowTypeParameter = source.readInt(); + topOpaqueWindowInsetsState = source.readTypedObject(InsetsState.CREATOR); + topOpaqueWindowLayoutParams = source.readTypedObject( + WindowManager.LayoutParams.CREATOR); + mainWindowLayoutParams = source.readTypedObject(WindowManager.LayoutParams.CREATOR); + } + + @Override + public String toString() { + return "StartingWindowInfo{taskId=" + taskInfo.taskId + + " displayId=" + taskInfo.displayId + + " topActivityType=" + taskInfo.topActivityType + + " preferredStartingWindowType=" + + Integer.toHexString(startingWindowTypeParameter) + + " insetsState=" + topOpaqueWindowInsetsState + + " topWindowLayoutParams=" + topOpaqueWindowLayoutParams + + " mainWindowLayoutParams=" + mainWindowLayoutParams; + } + + public static final @android.annotation.NonNull Creator<StartingWindowInfo> CREATOR = + new Creator<StartingWindowInfo>() { + public StartingWindowInfo createFromParcel(@NonNull Parcel source) { + return new StartingWindowInfo(source); + } + public StartingWindowInfo[] newArray(int size) { + return new StartingWindowInfo[size]; + } + }; +} diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java index 12c4b5b7794a..73b2fe1ff4d5 100644 --- a/core/java/android/window/TaskOrganizer.java +++ b/core/java/android/window/TaskOrganizer.java @@ -89,19 +89,19 @@ public class TaskOrganizer extends WindowOrganizer { * application is starting. The client is responsible to add/remove the starting window if it * has create a starting window for the Task. * - * @param taskInfo The information about the Task that's available + * @param info The information about the Task that's available * @param appToken Token of the application being started. * context to for resources */ @BinderThread - public void addStartingWindow(@NonNull ActivityManager.RunningTaskInfo taskInfo, + public void addStartingWindow(@NonNull StartingWindowInfo info, @NonNull IBinder appToken) {} /** * Called when the Task want to remove the starting window. */ @BinderThread - public void removeStartingWindow(@NonNull ActivityManager.RunningTaskInfo taskInfo) {} + public void removeStartingWindow(int taskId) {} /** * Called when a task with the registered windowing mode can be controlled by this task @@ -221,13 +221,15 @@ public class TaskOrganizer extends WindowOrganizer { private final ITaskOrganizer mInterface = new ITaskOrganizer.Stub() { @Override - public void addStartingWindow(ActivityManager.RunningTaskInfo taskInfo, IBinder appToken) { - mExecutor.execute(() -> TaskOrganizer.this.addStartingWindow(taskInfo, appToken)); + + public void addStartingWindow(StartingWindowInfo windowInfo, + IBinder appToken) { + mExecutor.execute(() -> TaskOrganizer.this.addStartingWindow(windowInfo, appToken)); } @Override - public void removeStartingWindow(ActivityManager.RunningTaskInfo taskInfo) { - mExecutor.execute(() -> TaskOrganizer.this.removeStartingWindow(taskInfo)); + public void removeStartingWindow(int taskId) { + mExecutor.execute(() -> TaskOrganizer.this.removeStartingWindow(taskId)); } @Override 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 8d1508019cda..c9b38d00c0ae 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()); + } +} diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 9efa24914915..3390d6728d3a 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1759,6 +1759,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final TaskSnapshot snapshot = mWmService.mTaskSnapshotController.getSnapshot(task.mTaskId, task.mUserId, false /* restoreFromDisk */, false /* isLowResolution */); + final int typeParameter = mWmService.mStartingSurfaceController + .makeStartingWindowTypeParameter(newTask, taskSwitch, processRunning, + allowTaskSnapshot, activityCreated); final int type = getStartingWindowType(newTask, taskSwitch, processRunning, allowTaskSnapshot, activityCreated, snapshot); @@ -1773,7 +1776,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return false; } } - return createSnapshot(snapshot); + return createSnapshot(snapshot, typeParameter); } // If this is a translucent window, then don't show a starting window -- the current @@ -1833,18 +1836,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Creating SplashScreenStartingData"); mStartingData = new SplashScreenStartingData(mWmService, pkg, theme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags, - getMergedOverrideConfiguration()); + getMergedOverrideConfiguration(), typeParameter); scheduleAddStartingWindow(); return true; } - private boolean createSnapshot(TaskSnapshot snapshot) { + private boolean createSnapshot(TaskSnapshot snapshot, int typeParams) { if (snapshot == null) { return false; } ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Creating SnapshotStartingData"); - mStartingData = new SnapshotStartingData(mWmService, snapshot); + mStartingData = new SnapshotStartingData(mWmService, snapshot, typeParams); scheduleAddStartingWindow(); return true; } diff --git a/services/core/java/com/android/server/wm/SnapshotStartingData.java b/services/core/java/com/android/server/wm/SnapshotStartingData.java index 6ad3f15f4a5c..2124ed6fd39f 100644 --- a/services/core/java/com/android/server/wm/SnapshotStartingData.java +++ b/services/core/java/com/android/server/wm/SnapshotStartingData.java @@ -28,14 +28,15 @@ class SnapshotStartingData extends StartingData { private final WindowManagerService mService; private final TaskSnapshot mSnapshot; - SnapshotStartingData(WindowManagerService service, TaskSnapshot snapshot) { - super(service); + SnapshotStartingData(WindowManagerService service, TaskSnapshot snapshot, int typeParams) { + super(service, typeParams); mService = service; mSnapshot = snapshot; } @Override StartingSurface createStartingSurface(ActivityRecord activity) { - return mService.mTaskSnapshotController.createStartingSurface(activity, mSnapshot); + return mService.mStartingSurfaceController.createTaskSnapshotSurface(activity, + mSnapshot); } } diff --git a/services/core/java/com/android/server/wm/SplashScreenStartingData.java b/services/core/java/com/android/server/wm/SplashScreenStartingData.java index 50a101d58ce3..185a317271ff 100644 --- a/services/core/java/com/android/server/wm/SplashScreenStartingData.java +++ b/services/core/java/com/android/server/wm/SplashScreenStartingData.java @@ -38,8 +38,8 @@ class SplashScreenStartingData extends StartingData { SplashScreenStartingData(WindowManagerService service, String pkg, int theme, CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon, - int logo, int windowFlags, Configuration mergedOverrideConfiguration) { - super(service); + int logo, int windowFlags, Configuration mergedOverrideConfiguration, int typeParams) { + super(service, typeParams); mPkg = pkg; mTheme = theme; mCompatInfo = compatInfo; diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java index 2e6e777dd0d0..a5bd797cbc86 100644 --- a/services/core/java/com/android/server/wm/StartingData.java +++ b/services/core/java/com/android/server/wm/StartingData.java @@ -24,9 +24,11 @@ import com.android.server.policy.WindowManagerPolicy.StartingSurface; public abstract class StartingData { protected final WindowManagerService mService; + protected final int mTypeParams; - protected StartingData(WindowManagerService service) { + protected StartingData(WindowManagerService service, int typeParams) { mService = service; + mTypeParams = typeParams; } /** diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java index 6d7ddf607ffe..94e14dd0a6b8 100644 --- a/services/core/java/com/android/server/wm/StartingSurfaceController.java +++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java @@ -16,9 +16,20 @@ package com.android.server.wm; +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 static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; + import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.os.SystemProperties; +import android.util.Slog; +import android.window.TaskSnapshot; import com.android.server.policy.WindowManagerPolicy.StartingSurface; @@ -26,7 +37,8 @@ import com.android.server.policy.WindowManagerPolicy.StartingSurface; * Managing to create and release a starting window surface. */ public class StartingSurfaceController { - + private static final String TAG = TAG_WITH_CLASS_NAME + ? StartingSurfaceController.class.getSimpleName() : TAG_WM; /** Set to {@code true} to enable shell starting surface drawer. */ private static final boolean DEBUG_ENABLE_SHELL_DRAWER = SystemProperties.getBoolean("persist.debug.shell_starting_surface", false); @@ -49,15 +61,79 @@ public class StartingSurfaceController { final Task task = activity.getTask(); if (task != null && mService.mAtmService.mTaskOrganizerController.addStartingWindow(task, activity.token)) { - return new SplashScreenContainerSurface(task); + return new ShellStartingSurface(task); } return null; } - private final class SplashScreenContainerSurface implements StartingSurface { + int makeStartingWindowTypeParameter(boolean newTask, boolean taskSwitch, + boolean processRunning, boolean allowTaskSnapshot, boolean activityCreated) { + int parameter = 0; + if (newTask) { + parameter |= TYPE_PARAMETER_NEW_TASK; + } + if (taskSwitch) { + parameter |= TYPE_PARAMETER_TASK_SWITCH; + } + if (processRunning) { + parameter |= TYPE_PARAMETER_PROCESS_RUNNING; + } + if (allowTaskSnapshot) { + parameter |= TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT; + } + if (activityCreated) { + parameter |= TYPE_PARAMETER_ACTIVITY_CREATED; + } + return parameter; + } + + StartingSurface createTaskSnapshotSurface(ActivityRecord activity, TaskSnapshot taskSnapshot) { + final WindowState topFullscreenOpaqueWindow; + final Task task; + synchronized (mService.mGlobalLock) { + final WindowState mainWindow = activity.findMainWindow(); + task = activity.getTask(); + if (task == null) { + Slog.w(TAG, "TaskSnapshotSurface.create: Failed to find task for activity=" + + activity); + return null; + } + final ActivityRecord topFullscreenActivity = + activity.getTask().getTopFullscreenActivity(); + if (topFullscreenActivity == null) { + Slog.w(TAG, "TaskSnapshotSurface.create: Failed to find top fullscreen for task=" + + task); + return null; + } + topFullscreenOpaqueWindow = topFullscreenActivity.getTopFullscreenOpaqueWindow(); + if (mainWindow == null || topFullscreenOpaqueWindow == null) { + Slog.w(TAG, "TaskSnapshotSurface.create: Failed to find main window for activity=" + + activity); + return null; + } + if (topFullscreenActivity.getWindowConfiguration().getRotation() + != taskSnapshot.getRotation()) { + // The snapshot should have been checked by ActivityRecord#isSnapshotCompatible + // that the activity will be updated to the same rotation as the snapshot. Since + // the transition is not started yet, fixed rotation transform needs to be applied + // earlier to make the snapshot show in a rotated container. + activity.mDisplayContent.handleTopActivityLaunchingInDifferentOrientation( + topFullscreenActivity, false /* checkOpening */); + } + } + if (!DEBUG_ENABLE_SHELL_DRAWER) { + return mService.mTaskSnapshotController + .createStartingSurface(activity, taskSnapshot); + } + mService.mAtmService.mTaskOrganizerController.addStartingWindow(task, activity.token); + return new ShellStartingSurface(task); + } + + + private final class ShellStartingSurface implements StartingSurface { private final Task mTask; - SplashScreenContainerSurface(Task task) { + ShellStartingSurface(Task task) { mTask = task; } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index abd51eef93aa..85b8400cb3ec 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -156,7 +156,6 @@ import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManager.TaskDescription; -import android.window.TaskSnapshot; import android.app.ActivityOptions; import android.app.ActivityTaskManager; import android.app.AppGlobals; @@ -207,6 +206,8 @@ import android.view.SurfaceControl; import android.view.WindowManager; import android.view.WindowManager.TransitionOldType; import android.window.ITaskOrganizer; +import android.window.StartingWindowInfo; +import android.window.TaskSnapshot; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -4145,6 +4146,34 @@ class Task extends WindowContainer<WindowContainer> { return info; } + StartingWindowInfo getStartingWindowInfo() { + final StartingWindowInfo info = new StartingWindowInfo(); + info.taskInfo = getTaskInfo(); + + final ActivityRecord topActivity = getTopMostActivity(); + if (topActivity != null) { + info.startingWindowTypeParameter = + topActivity.mStartingData != null + ? topActivity.mStartingData.mTypeParams + : 0; + final WindowState mainWindow = topActivity.findMainWindow(); + if (mainWindow != null) { + info.mainWindowLayoutParams = mainWindow.getAttrs(); + } + } + final ActivityRecord topFullscreenActivity = getTopFullscreenActivity(); + if (topFullscreenActivity != null) { + final WindowState topFullscreenOpaqueWindow = + topFullscreenActivity.getTopFullscreenOpaqueWindow(); + if (topFullscreenOpaqueWindow != null) { + info.topOpaqueWindowInsetsState = + topFullscreenOpaqueWindow.getInsetsStateWithVisibilityOverride(); + info.topOpaqueWindowLayoutParams = topFullscreenOpaqueWindow.getAttrs(); + } + } + return info; + } + boolean isTaskId(int taskId) { return mTaskId == taskId; } diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 9f8c35b067c5..9b966b3dfe85 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -39,6 +39,7 @@ import android.util.Slog; import android.view.SurfaceControl; import android.window.ITaskOrganizer; import android.window.ITaskOrganizerController; +import android.window.StartingWindowInfo; import android.window.TaskAppearedInfo; import android.window.WindowContainerToken; @@ -116,10 +117,10 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } void addStartingWindow(Task task, IBinder appToken) { - final RunningTaskInfo taskInfo = task.getTaskInfo(); + final StartingWindowInfo info = task.getStartingWindowInfo(); mDeferTaskOrgCallbacksConsumer.accept(() -> { try { - mTaskOrganizer.addStartingWindow(taskInfo, appToken); + mTaskOrganizer.addStartingWindow(info, appToken); } catch (RemoteException e) { Slog.e(TAG, "Exception sending onTaskStart callback", e); } @@ -127,10 +128,9 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } void removeStartingWindow(Task task) { - final RunningTaskInfo taskInfo = task.getTaskInfo(); mDeferTaskOrgCallbacksConsumer.accept(() -> { try { - mTaskOrganizer.removeStartingWindow(taskInfo); + mTaskOrganizer.removeStartingWindow(task.mTaskId); } catch (RemoteException e) { Slog.e(TAG, "Exception sending onStartTaskFinished callback", e); } diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java index d5e4dac4a484..8c458a207cc3 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java @@ -49,7 +49,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.Nullable; import android.app.ActivityManager.TaskDescription; -import android.window.TaskSnapshot; import android.app.ActivityThread; import android.content.Context; import android.graphics.Canvas; @@ -78,6 +77,7 @@ import android.view.ViewGroup.LayoutParams; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.window.ClientWindowFrames; +import android.window.TaskSnapshot; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; @@ -100,7 +100,7 @@ class TaskSnapshotSurface implements StartingSurface { * 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. */ - private static final int FLAG_INHERIT_EXCLUDES = FLAG_NOT_FOCUSABLE + static final int FLAG_INHERIT_EXCLUDES = FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE | FLAG_NOT_TOUCH_MODAL | FLAG_ALT_FOCUSABLE_IM @@ -180,34 +180,10 @@ class TaskSnapshotSurface implements StartingSurface { synchronized (service.mGlobalLock) { final WindowState mainWindow = activity.findMainWindow(); final Task task = activity.getTask(); - if (task == null) { - Slog.w(TAG, "TaskSnapshotSurface.create: Failed to find task for activity=" - + activity); - return null; - } final ActivityRecord topFullscreenActivity = activity.getTask().getTopFullscreenActivity(); - if (topFullscreenActivity == null) { - Slog.w(TAG, "TaskSnapshotSurface.create: Failed to find top fullscreen for task=" - + task); - return null; - } + // Already check the nullity in StartingSurfaceController#createTaskSnapshotSurface topFullscreenOpaqueWindow = topFullscreenActivity.getTopFullscreenOpaqueWindow(); - if (mainWindow == null || topFullscreenOpaqueWindow == null) { - Slog.w(TAG, "TaskSnapshotSurface.create: Failed to find main window for activity=" - + activity); - return null; - } - if (topFullscreenActivity.getWindowConfiguration().getRotation() - != snapshot.getRotation()) { - // The snapshot should have been checked by ActivityRecord#isSnapshotCompatible - // that the activity will be updated to the same rotation as the snapshot. Since - // the transition is not started yet, fixed rotation transform needs to be applied - // earlier to make the snapshot show in a rotated container. - activity.mDisplayContent.handleTopActivityLaunchingInDifferentOrientation( - topFullscreenActivity, false /* checkOpening */); - } - WindowManager.LayoutParams attrs = topFullscreenOpaqueWindow.mAttrs; appearance = attrs.insetsFlags.appearance; windowFlags = attrs.flags; diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 2d71d7f837ef..95a1b61bb5a3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -75,7 +75,6 @@ import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.never; -import android.window.TaskSnapshot; import android.app.ActivityOptions; import android.app.WindowConfiguration; import android.app.servertransaction.ActivityConfigurationChangeItem; @@ -102,6 +101,7 @@ import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationTarget; import android.view.WindowManager; import android.view.WindowManagerGlobal; +import android.window.TaskSnapshot; import androidx.test.filters.MediumTest; @@ -1662,7 +1662,8 @@ public class ActivityRecordTests extends WindowTestsBase { any() /* requestedVisibility */, any() /* outFrame */, any() /* outDisplayCutout */, any() /* outInputChannel */, any() /* outInsetsState */, any() /* outActiveControls */); - TaskSnapshotSurface.create(mAtm.mWindowManager, activity, snapshot); + mAtm.mWindowManager.mStartingSurfaceController + .createTaskSnapshotSurface(activity, snapshot); } catch (RemoteException ignored) { } finally { reset(session); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index 6b9993aa36cc..62e4990c1487 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -74,6 +74,7 @@ import android.view.Display; import android.view.SurfaceControl; import android.window.ITaskOrganizer; import android.window.IWindowContainerTransactionCallback; +import android.window.StartingWindowInfo; import android.window.TaskAppearedInfo; import android.window.WindowContainerTransaction; @@ -538,12 +539,12 @@ public class WindowOrganizerTests extends WindowTestsBase { public void testTileAddRemoveChild() { ITaskOrganizer listener = new ITaskOrganizer.Stub() { @Override - public void addStartingWindow(ActivityManager.RunningTaskInfo info, IBinder appToken) { + public void addStartingWindow(StartingWindowInfo info, IBinder appToken) { } @Override - public void removeStartingWindow(ActivityManager.RunningTaskInfo info) { } + public void removeStartingWindow(int taskId) { } @Override public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { } @@ -603,12 +604,12 @@ public class WindowOrganizerTests extends WindowTestsBase { final boolean[] called = {false}; ITaskOrganizer listener = new ITaskOrganizer.Stub() { @Override - public void addStartingWindow(ActivityManager.RunningTaskInfo info, IBinder appToken) { + public void addStartingWindow(StartingWindowInfo info, IBinder appToken) { } @Override - public void removeStartingWindow(ActivityManager.RunningTaskInfo info) { } + public void removeStartingWindow(int taskId) { } @Override public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { } @@ -674,12 +675,12 @@ public class WindowOrganizerTests extends WindowTestsBase { final ArrayMap<IBinder, RunningTaskInfo> lastReportedTiles = new ArrayMap<>(); ITaskOrganizer listener = new ITaskOrganizer.Stub() { @Override - public void addStartingWindow(ActivityManager.RunningTaskInfo info, IBinder appToken) { + public void addStartingWindow(StartingWindowInfo info, IBinder appToken) { } @Override - public void removeStartingWindow(ActivityManager.RunningTaskInfo info) { } + public void removeStartingWindow(int taskId) { } @Override public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { } @@ -818,9 +819,9 @@ public class WindowOrganizerTests extends WindowTestsBase { RunningTaskInfo mInfo; @Override - public void addStartingWindow(ActivityManager.RunningTaskInfo info, IBinder appToken) { } + public void addStartingWindow(StartingWindowInfo info, IBinder appToken) { } @Override - public void removeStartingWindow(ActivityManager.RunningTaskInfo info) { } + public void removeStartingWindow(int taskId) { } @Override public void onTaskAppeared(RunningTaskInfo info, SurfaceControl leash) { mInfo = info; diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index e13a5952ccac..89b9707e3e12 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -84,6 +84,7 @@ import android.view.View; import android.view.WindowManager; import android.view.WindowManager.DisplayImePolicy; import android.window.ITaskOrganizer; +import android.window.StartingWindowInfo; import com.android.internal.util.ArrayUtils; import com.android.server.AttributeCache; @@ -1090,10 +1091,10 @@ class WindowTestsBase extends SystemServiceTestsBase { mMoveToSecondaryOnEnter = move; } @Override - public void addStartingWindow(ActivityManager.RunningTaskInfo info, IBinder appToken) { + public void addStartingWindow(StartingWindowInfo info, IBinder appToken) { } @Override - public void removeStartingWindow(ActivityManager.RunningTaskInfo info) { + public void removeStartingWindow(int taskId) { } @Override public void onTaskAppeared(ActivityManager.RunningTaskInfo info, SurfaceControl leash) { |