Merge "Mirror TaskSnapshotSurface to WMShell(2/N)"

This commit is contained in:
Wei Sheng Shih
2020-12-21 01:39:04 +00:00
committed by Android (Google) Code Review
21 changed files with 1438 additions and 112 deletions

View File

@@ -2461,6 +2461,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();
@@ -2472,7 +2479,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[]);
@@ -2483,7 +2490,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();

View File

@@ -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

View File

@@ -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;

View File

@@ -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];
}
};
}

View File

@@ -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

View File

@@ -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

View File

@@ -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 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;
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;
@@ -249,19 +359,35 @@ public class StartingSurfaceDrawer {
postAddWindow(taskInfo.taskId, appToken, view, wm, params);
}
/**
* 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);
}
mTaskScreenViews.remove(taskId);
if (record.mTaskSnapshotWindow != null) {
if (DEBUG_TASK_SNAPSHOT) {
Slog.v(TAG, "Removing task snapshot window for " + taskId);
}
record.mTaskSnapshotWindow.remove(mMainExecutor);
}
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;
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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) {

View File

@@ -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());
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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;
}
/**

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;
@@ -506,12 +507,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) { }
@@ -571,12 +572,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) { }
@@ -642,12 +643,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) { }
@@ -786,9 +787,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;

View File

@@ -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) {