Files
Lawnchair/wmshell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
T
2024-11-02 10:55:28 +08:00

370 lines
15 KiB
Java

/*
* 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.view.Display.DEFAULT_DISPLAY;
import static android.window.StartingWindowRemovalInfo.DEFER_MODE_NONE;
import static android.window.StartingWindowRemovalInfo.DEFER_MODE_NORMAL;
import static android.window.StartingWindowRemovalInfo.DEFER_MODE_ROTATION;
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.app.TaskInfo;
import android.app.WindowConfiguration;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Color;
import android.hardware.display.DisplayManager;
import android.util.SparseArray;
import android.view.IWindow;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.WindowManager;
import android.view.WindowlessWindowManager;
import android.window.SplashScreenView;
import android.window.StartingWindowInfo;
import android.window.StartingWindowInfo.StartingWindowType;
import android.window.StartingWindowRemovalInfo;
import android.window.TaskSnapshot;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.annotations.ShellSplashscreenThread;
/**
* A class which able to draw splash screen or snapshot as the starting window for a task.
*/
@ShellSplashscreenThread
public class StartingSurfaceDrawer {
private final ShellExecutor mSplashScreenExecutor;
@VisibleForTesting
final SplashscreenContentDrawer mSplashscreenContentDrawer;
@VisibleForTesting
final SplashscreenWindowCreator mSplashscreenWindowCreator;
private final SnapshotWindowCreator mSnapshotWindowCreator;
private final WindowlessSplashWindowCreator mWindowlessSplashWindowCreator;
private final WindowlessSnapshotWindowCreator mWindowlessSnapshotWindowCreator;
@VisibleForTesting
final StartingWindowRecordManager mWindowRecords = new StartingWindowRecordManager();
// Windowless surface could co-exist with starting window in a task.
@VisibleForTesting
final StartingWindowRecordManager mWindowlessRecords = new StartingWindowRecordManager();
/**
* @param splashScreenExecutor The thread used to control add and remove starting window.
*/
public StartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor,
IconProvider iconProvider, TransactionPool pool) {
mSplashScreenExecutor = splashScreenExecutor;
final DisplayManager displayManager = context.getSystemService(DisplayManager.class);
mSplashscreenContentDrawer = new SplashscreenContentDrawer(context, iconProvider, pool);
displayManager.getDisplay(DEFAULT_DISPLAY);
mSplashscreenWindowCreator = new SplashscreenWindowCreator(mSplashscreenContentDrawer,
context, splashScreenExecutor, displayManager, mWindowRecords);
mSnapshotWindowCreator = new SnapshotWindowCreator(splashScreenExecutor,
mWindowRecords);
mWindowlessSplashWindowCreator = new WindowlessSplashWindowCreator(
mSplashscreenContentDrawer, context, splashScreenExecutor, displayManager,
mWindowlessRecords, pool);
mWindowlessSnapshotWindowCreator = new WindowlessSnapshotWindowCreator(
mWindowlessRecords, context, displayManager, mSplashscreenContentDrawer, pool);
}
void setSysuiProxy(StartingSurface.SysuiProxy sysuiProxy) {
mSplashscreenWindowCreator.setSysuiProxy(sysuiProxy);
mWindowlessSplashWindowCreator.setSysuiProxy(sysuiProxy);
}
/**
* Called when a task need a splash screen starting window.
*
* @param suggestType The suggestion type to draw the splash screen.
*/
void addSplashScreenStartingWindow(StartingWindowInfo windowInfo,
@StartingWindowType int suggestType) {
mSplashscreenWindowCreator.addSplashScreenStartingWindow(windowInfo, suggestType);
}
int getStartingWindowBackgroundColorForTask(int taskId) {
final StartingWindowRecord startingWindowRecord = mWindowRecords.getRecord(taskId);
if (startingWindowRecord == null) {
return Color.TRANSPARENT;
}
return startingWindowRecord.getBGColor();
}
int estimateTaskBackgroundColor(TaskInfo taskInfo) {
return mSplashscreenWindowCreator.estimateTaskBackgroundColor(taskInfo);
}
/**
* Called when a task need a snapshot starting window.
*/
void makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, TaskSnapshot snapshot) {
mSnapshotWindowCreator.makeTaskSnapshotWindow(startingWindowInfo, snapshot);
}
/**
* Called when the content of a task is ready to show, starting window can be removed.
*/
public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
if (removalInfo.windowlessSurface) {
mWindowlessRecords.removeWindow(removalInfo, removalInfo.removeImmediately);
} else {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
"Task start finish, remove starting surface for task: %d",
removalInfo.taskId);
mWindowRecords.removeWindow(removalInfo, removalInfo.removeImmediately);
}
}
/**
* Create a windowless starting surface and attach to the root surface.
*/
void addWindowlessStartingSurface(StartingWindowInfo windowInfo) {
if (windowInfo.taskSnapshot != null) {
mWindowlessSnapshotWindowCreator.makeTaskSnapshotWindow(windowInfo,
windowInfo.rootSurface, windowInfo.taskSnapshot, mSplashScreenExecutor);
} else {
mWindowlessSplashWindowCreator.addSplashScreenStartingWindow(
windowInfo, windowInfo.rootSurface);
}
}
/**
* Clear all starting windows immediately.
*/
public void clearAllWindows() {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
"Clear all starting windows immediately");
mWindowRecords.clearAllWindows();
mWindowlessRecords.clearAllWindows();
}
/**
* Called when the Task wants to copy the splash screen.
*/
public void copySplashScreenView(int taskId) {
mSplashscreenWindowCreator.copySplashScreenView(taskId);
}
/**
* Called when the {@link SplashScreenView} is removed from the client Activity view's hierarchy
* or when the Activity is clean up.
*
* @param taskId The Task id on which the splash screen was attached
*/
public void onAppSplashScreenViewRemoved(int taskId) {
mSplashscreenWindowCreator.onAppSplashScreenViewRemoved(taskId);
}
void onImeDrawnOnTask(int taskId) {
onImeDrawnOnTask(mWindowRecords, taskId);
onImeDrawnOnTask(mWindowlessRecords, taskId);
}
private void onImeDrawnOnTask(StartingWindowRecordManager records, int taskId) {
final StartingSurfaceDrawer.StartingWindowRecord sRecord =
records.getRecord(taskId);
final SnapshotRecord record = sRecord instanceof SnapshotRecord
? (SnapshotRecord) sRecord : null;
if (record != null && record.hasImeSurface()) {
records.removeWindow(taskId);
}
}
static class WindowlessStartingWindow extends WindowlessWindowManager {
SurfaceControl mChildSurface;
WindowlessStartingWindow(Configuration c, SurfaceControl rootSurface) {
super(c, rootSurface, null /* hostInputToken */);
}
@Override
protected SurfaceControl getParentSurface(IWindow window,
WindowManager.LayoutParams attrs) {
final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
.setContainerLayer()
.setName("Windowless window")
.setHidden(false)
.setParent(mRootSurface)
.setCallsite("WindowlessStartingWindow#attachToParentSurface");
mChildSurface = builder.build();
try (SurfaceControl.Transaction t = new SurfaceControl.Transaction()) {
t.setLayer(mChildSurface, Integer.MAX_VALUE);
t.apply();
}
return mChildSurface;
}
}
abstract static class StartingWindowRecord {
protected int mBGColor;
/**
* Remove the starting window with the given {@link StartingWindowRemovalInfo} if possible.
* @param info The removal info sent from the task organizer controller in the WM core.
* @param immediately {@code true} means removing the starting window immediately,
* {@code false} otherwise.
* @return {@code true} means {@link StartingWindowRecordManager} can safely remove the
* record itself. {@code false} means {@link StartingWindowRecordManager} requires
* to manage the record reference and remove it later.
*/
abstract boolean removeIfPossible(StartingWindowRemovalInfo info, boolean immediately);
int getBGColor() {
return mBGColor;
}
}
abstract static class SnapshotRecord extends StartingWindowRecord {
private static final long DELAY_REMOVAL_TIME_GENERAL = 100;
/**
* The max delay time in milliseconds for removing the task snapshot window with IME
* visible.
* Ideally the delay time will be shorter when receiving
* {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}.
*/
private static final long MAX_DELAY_REMOVAL_TIME_IME_VISIBLE = 600;
/**
* The max delay time in milliseconds for removing the task snapshot window with IME
* visible after the fixed rotation finished.
* Ideally the delay time will be shorter when receiving
* {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}.
*/
private static final long MAX_DELAY_REMOVAL_TIME_FIXED_ROTATION = 3000;
private final Runnable mScheduledRunnable = this::removeImmediately;
@WindowConfiguration.ActivityType protected final int mActivityType;
protected final ShellExecutor mRemoveExecutor;
private final int mTaskId;
private final StartingWindowRecordManager mRecordManager;
SnapshotRecord(int activityType, ShellExecutor removeExecutor, int taskId,
StartingWindowRecordManager recordManager) {
mActivityType = activityType;
mRemoveExecutor = removeExecutor;
mTaskId = taskId;
mRecordManager = recordManager;
}
@Override
public final boolean removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
if (immediately
// Show the latest content as soon as possible for unlocking to home.
|| mActivityType == ACTIVITY_TYPE_HOME
|| info.deferRemoveMode == DEFER_MODE_NONE) {
removeImmediately();
return true;
}
scheduleRemove(info.deferRemoveMode);
return false;
}
void scheduleRemove(@StartingWindowRemovalInfo.DeferMode int deferRemoveForImeMode) {
mRemoveExecutor.removeCallbacks(mScheduledRunnable);
final long delayRemovalTime;
switch (deferRemoveForImeMode) {
case DEFER_MODE_ROTATION:
delayRemovalTime = MAX_DELAY_REMOVAL_TIME_FIXED_ROTATION;
break;
case DEFER_MODE_NORMAL:
delayRemovalTime = MAX_DELAY_REMOVAL_TIME_IME_VISIBLE;
break;
default:
delayRemovalTime = DELAY_REMOVAL_TIME_GENERAL;
}
mRemoveExecutor.executeDelayed(mScheduledRunnable, delayRemovalTime);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
"Defer removing snapshot surface in %d", delayRemovalTime);
}
protected abstract boolean hasImeSurface();
@CallSuper
protected void removeImmediately() {
mRemoveExecutor.removeCallbacks(mScheduledRunnable);
mRecordManager.onRecordRemoved(this, mTaskId);
}
}
static class StartingWindowRecordManager {
private final StartingWindowRemovalInfo mTmpRemovalInfo = new StartingWindowRemovalInfo();
private final SparseArray<StartingWindowRecord> mStartingWindowRecords =
new SparseArray<>();
void clearAllWindows() {
final int taskSize = mStartingWindowRecords.size();
final int[] taskIds = new int[taskSize];
for (int i = taskSize - 1; i >= 0; --i) {
taskIds[i] = mStartingWindowRecords.keyAt(i);
}
for (int i = taskSize - 1; i >= 0; --i) {
removeWindow(taskIds[i]);
}
}
void addRecord(int taskId, StartingWindowRecord record) {
final StartingWindowRecord original = mStartingWindowRecords.get(taskId);
if (original != null) {
mTmpRemovalInfo.taskId = taskId;
original.removeIfPossible(mTmpRemovalInfo, true /* immediately */);
}
mStartingWindowRecords.put(taskId, record);
}
void removeWindow(StartingWindowRemovalInfo removeInfo, boolean immediately) {
final int taskId = removeInfo.taskId;
final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
if (record != null) {
final boolean canRemoveRecord = record.removeIfPossible(removeInfo, immediately);
if (canRemoveRecord) {
mStartingWindowRecords.remove(taskId);
}
}
}
void removeWindow(int taskId) {
mTmpRemovalInfo.taskId = taskId;
removeWindow(mTmpRemovalInfo, true/* immediately */);
}
void onRecordRemoved(@NonNull StartingWindowRecord record, int taskId) {
final StartingWindowRecord currentRecord = mStartingWindowRecords.get(taskId);
if (currentRecord == record) {
mStartingWindowRecords.remove(taskId);
}
}
StartingWindowRecord getRecord(int taskId) {
return mStartingWindowRecords.get(taskId);
}
@VisibleForTesting
int recordSize() {
return mStartingWindowRecords.size();
}
}
}