Merge "Starting window performance tuning for binder block." into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
9dea87c0d1
@@ -142,6 +142,12 @@ public final class StartingWindowInfo implements Parcelable {
|
||||
*/
|
||||
public boolean isKeyguardOccluded = false;
|
||||
|
||||
/**
|
||||
* TaskSnapshot.
|
||||
* @hide
|
||||
*/
|
||||
public TaskSnapshot mTaskSnapshot;
|
||||
|
||||
public StartingWindowInfo() {
|
||||
|
||||
}
|
||||
@@ -164,6 +170,7 @@ public final class StartingWindowInfo implements Parcelable {
|
||||
dest.writeTypedObject(mainWindowLayoutParams, flags);
|
||||
dest.writeInt(splashScreenThemeResId);
|
||||
dest.writeBoolean(isKeyguardOccluded);
|
||||
dest.writeTypedObject(mTaskSnapshot, flags);
|
||||
}
|
||||
|
||||
void readFromParcel(@NonNull Parcel source) {
|
||||
@@ -175,6 +182,7 @@ public final class StartingWindowInfo implements Parcelable {
|
||||
mainWindowLayoutParams = source.readTypedObject(WindowManager.LayoutParams.CREATOR);
|
||||
splashScreenThemeResId = source.readInt();
|
||||
isKeyguardOccluded = source.readBoolean();
|
||||
mTaskSnapshot = source.readTypedObject(TaskSnapshot.CREATOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.wm.shell.startingsurface;
|
||||
|
||||
import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST;
|
||||
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
|
||||
import static android.os.UserHandle.getUserHandleForUid;
|
||||
|
||||
@@ -35,6 +36,8 @@ import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Trace;
|
||||
import android.util.Slog;
|
||||
import android.view.SurfaceControl;
|
||||
@@ -48,10 +51,11 @@ import com.android.launcher3.icons.IconProvider;
|
||||
import com.android.wm.shell.common.TransactionPool;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Util class to create the view for a splash screen content.
|
||||
*
|
||||
* Everything execute in this class should be post to mSplashscreenWorkerHandler.
|
||||
* @hide
|
||||
*/
|
||||
public class SplashscreenContentDrawer {
|
||||
@@ -78,6 +82,7 @@ public class SplashscreenContentDrawer {
|
||||
private int mIconEarlyExitDistance;
|
||||
private final TransactionPool mTransactionPool;
|
||||
private final SplashScreenWindowAttrs mTmpAttrs = new SplashScreenWindowAttrs();
|
||||
private final Handler mSplashscreenWorkerHandler;
|
||||
|
||||
SplashscreenContentDrawer(Context context, int maxAnimatableIconDuration,
|
||||
int iconExitAnimDuration, int appRevealAnimDuration, TransactionPool pool) {
|
||||
@@ -87,6 +92,45 @@ public class SplashscreenContentDrawer {
|
||||
mAppRevealDuration = appRevealAnimDuration;
|
||||
mIconExitDuration = iconExitAnimDuration;
|
||||
mTransactionPool = pool;
|
||||
|
||||
// Initialize Splashscreen worker thread
|
||||
// TODO(b/185288910) move it into WMShellConcurrencyModule and provide an executor to make
|
||||
// it easier to test stuff that happens on that thread later.
|
||||
final HandlerThread shellSplashscreenWorkerThread =
|
||||
new HandlerThread("wmshell.splashworker", THREAD_PRIORITY_TOP_APP_BOOST);
|
||||
shellSplashscreenWorkerThread.start();
|
||||
mSplashscreenWorkerHandler = shellSplashscreenWorkerThread.getThreadHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a SplashScreenView object.
|
||||
*
|
||||
* In order to speed up the splash screen view to show on first frame, preparing the
|
||||
* view on background thread so the view and the drawable can be create and pre-draw in
|
||||
* parallel.
|
||||
*
|
||||
* @param consumer Receiving the SplashScreenView object, which will also be executed
|
||||
* on splash screen thread. Note that the view can be null if failed.
|
||||
*/
|
||||
void createContentView(Context context, int splashScreenResId, ActivityInfo info,
|
||||
int taskId, Consumer<SplashScreenView> consumer) {
|
||||
mSplashscreenWorkerHandler.post(() -> {
|
||||
SplashScreenView contentView;
|
||||
try {
|
||||
contentView = SplashscreenContentDrawer.makeSplashscreenContent(
|
||||
context, splashScreenResId);
|
||||
if (contentView == null) {
|
||||
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "makeSplashScreenContentView");
|
||||
contentView = makeSplashScreenContentView(context, info);
|
||||
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
Slog.w(TAG, "failed creating starting window content at taskId: "
|
||||
+ taskId, e);
|
||||
contentView = null;
|
||||
}
|
||||
consumer.accept(contentView);
|
||||
});
|
||||
}
|
||||
|
||||
private void updateDensity() {
|
||||
@@ -146,7 +190,7 @@ public class SplashscreenContentDrawer {
|
||||
}
|
||||
}
|
||||
|
||||
SplashScreenView makeSplashScreenContentView(Context context, ActivityInfo ai) {
|
||||
private SplashScreenView makeSplashScreenContentView(Context context, ActivityInfo ai) {
|
||||
updateDensity();
|
||||
|
||||
getWindowAttrs(context, mTmpAttrs);
|
||||
@@ -199,7 +243,7 @@ public class SplashscreenContentDrawer {
|
||||
}
|
||||
}
|
||||
|
||||
static class SplashScreenWindowAttrs {
|
||||
private static class SplashScreenWindowAttrs {
|
||||
private int mWindowBgResId = 0;
|
||||
private int mWindowBgColor = Color.TRANSPARENT;
|
||||
private Drawable mReplaceIcon = null;
|
||||
@@ -271,9 +315,7 @@ public class SplashscreenContentDrawer {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "The icon is not an AdaptiveIconDrawable");
|
||||
}
|
||||
mFinalIconDrawable = SplashscreenIconDrawableFactory.makeIconDrawable(
|
||||
mIconBackground != Color.TRANSPARENT
|
||||
? mIconBackground : mThemeColor, mIconDrawable, mIconSize);
|
||||
createIconDrawable(mIconDrawable, mIconSize);
|
||||
}
|
||||
final int iconSize = mFinalIconDrawable != null ? (int) (mIconSize * mScale) : 0;
|
||||
mCachedResult = fillViewWithIcon(mContext, iconSize, mFinalIconDrawable);
|
||||
@@ -283,8 +325,8 @@ public class SplashscreenContentDrawer {
|
||||
|
||||
private void createIconDrawable(Drawable iconDrawable, int iconSize) {
|
||||
mFinalIconDrawable = SplashscreenIconDrawableFactory.makeIconDrawable(
|
||||
mIconBackground != Color.TRANSPARENT
|
||||
? mIconBackground : mThemeColor, iconDrawable, iconSize);
|
||||
mIconBackground != Color.TRANSPARENT ? mIconBackground : mThemeColor,
|
||||
iconDrawable, iconSize, mSplashscreenWorkerHandler);
|
||||
}
|
||||
|
||||
private boolean processAdaptiveIcon() {
|
||||
@@ -399,7 +441,7 @@ public class SplashscreenContentDrawer {
|
||||
return root < 0.1;
|
||||
}
|
||||
|
||||
static SplashScreenView makeSplashscreenContent(Context ctx,
|
||||
private static SplashScreenView makeSplashscreenContent(Context ctx,
|
||||
int splashscreenContentResId) {
|
||||
// doesn't support windowSplashscreenContent after S
|
||||
// TODO add an allowlist to skip some packages if needed
|
||||
|
||||
@@ -37,6 +37,7 @@ import android.graphics.drawable.AdaptiveIconDrawable;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Handler;
|
||||
import android.os.Trace;
|
||||
import android.util.PathParser;
|
||||
import android.window.SplashScreenView;
|
||||
@@ -50,42 +51,61 @@ import com.android.internal.R;
|
||||
public class SplashscreenIconDrawableFactory {
|
||||
|
||||
static Drawable makeIconDrawable(@ColorInt int backgroundColor,
|
||||
@NonNull Drawable foregroundDrawable, int iconSize) {
|
||||
@NonNull Drawable foregroundDrawable, int iconSize,
|
||||
Handler splashscreenWorkerHandler) {
|
||||
if (foregroundDrawable instanceof Animatable) {
|
||||
return new AnimatableIconDrawable(backgroundColor, foregroundDrawable);
|
||||
} else if (foregroundDrawable instanceof AdaptiveIconDrawable) {
|
||||
return new ImmobileIconDrawable((AdaptiveIconDrawable) foregroundDrawable, iconSize);
|
||||
return new ImmobileIconDrawable((AdaptiveIconDrawable) foregroundDrawable, iconSize,
|
||||
splashscreenWorkerHandler);
|
||||
} else {
|
||||
return new ImmobileIconDrawable(new AdaptiveIconDrawable(
|
||||
new ColorDrawable(backgroundColor), foregroundDrawable), iconSize);
|
||||
new ColorDrawable(backgroundColor), foregroundDrawable), iconSize,
|
||||
splashscreenWorkerHandler);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ImmobileIconDrawable extends Drawable {
|
||||
private Shader mLayersShader;
|
||||
private boolean mCacheComplete;
|
||||
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG
|
||||
| Paint.FILTER_BITMAP_FLAG);
|
||||
|
||||
ImmobileIconDrawable(AdaptiveIconDrawable drawable, int iconSize) {
|
||||
cachePaint(drawable, iconSize, iconSize);
|
||||
ImmobileIconDrawable(AdaptiveIconDrawable drawable, int iconSize,
|
||||
Handler splashscreenWorkerHandler) {
|
||||
splashscreenWorkerHandler.post(() -> cachePaint(drawable, iconSize, iconSize));
|
||||
}
|
||||
|
||||
private void cachePaint(AdaptiveIconDrawable drawable, int width, int height) {
|
||||
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "cachePaint");
|
||||
final Bitmap layersBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
final Canvas canvas = new Canvas(layersBitmap);
|
||||
drawable.setBounds(0, 0, width, height);
|
||||
drawable.draw(canvas);
|
||||
mLayersShader = new BitmapShader(layersBitmap, Shader.TileMode.CLAMP,
|
||||
Shader.TileMode.CLAMP);
|
||||
mPaint.setShader(mLayersShader);
|
||||
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
|
||||
synchronized (mPaint) {
|
||||
if (mCacheComplete) {
|
||||
return;
|
||||
}
|
||||
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "cachePaint");
|
||||
final Bitmap layersBitmap = Bitmap.createBitmap(width, height,
|
||||
Bitmap.Config.ARGB_8888);
|
||||
final Canvas canvas = new Canvas(layersBitmap);
|
||||
drawable.setBounds(0, 0, width, height);
|
||||
drawable.draw(canvas);
|
||||
final Shader layersShader = new BitmapShader(layersBitmap, Shader.TileMode.CLAMP,
|
||||
Shader.TileMode.CLAMP);
|
||||
mPaint.setShader(layersShader);
|
||||
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
|
||||
mCacheComplete = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
final Rect bounds = getBounds();
|
||||
canvas.drawRect(bounds, mPaint);
|
||||
synchronized (mPaint) {
|
||||
if (mCacheComplete) {
|
||||
final Rect bounds = getBounds();
|
||||
canvas.drawRect(bounds, mPaint);
|
||||
} else {
|
||||
// this shouldn't happen, but if it really happen, invalidate self to wait
|
||||
// for cachePaint finish.
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -18,8 +18,10 @@ package com.android.wm.shell.startingsurface;
|
||||
|
||||
import static android.content.Context.CONTEXT_RESTRICTED;
|
||||
import static android.content.res.Configuration.EMPTY;
|
||||
import static android.view.Choreographer.CALLBACK_INSETS_ANIMATION;
|
||||
import static android.view.Display.DEFAULT_DISPLAY;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.app.ActivityManager.RunningTaskInfo;
|
||||
import android.app.ActivityTaskManager;
|
||||
import android.content.Context;
|
||||
@@ -29,16 +31,15 @@ import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.hardware.display.DisplayManager;
|
||||
import android.os.IBinder;
|
||||
import android.os.SystemClock;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.view.Choreographer;
|
||||
import android.view.Display;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.View;
|
||||
@@ -54,9 +55,44 @@ import com.android.wm.shell.common.ShellExecutor;
|
||||
import com.android.wm.shell.common.TransactionPool;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* A class which able to draw splash screen or snapshot as the starting window for a task.
|
||||
*
|
||||
* In order to speed up, there will use two threads to creating a splash screen in parallel.
|
||||
* Right now we are still using PhoneWindow to create splash screen window, so the view is added to
|
||||
* the ViewRootImpl, and those view won't be draw immediately because the ViewRootImpl will call
|
||||
* scheduleTraversal to register a callback from Choreographer, so the drawing result of the view
|
||||
* can synchronize on each frame.
|
||||
*
|
||||
* The bad thing is that we cannot decide when would Choreographer#doFrame happen, and drawing
|
||||
* the AdaptiveIconDrawable object can be time consuming, so we use the splash-screen background
|
||||
* thread to draw the AdaptiveIconDrawable object to a Bitmap and cache it to a BitmapShader after
|
||||
* the SplashScreenView just created, once we get the BitmapShader then the #draw call can be very
|
||||
* quickly.
|
||||
*
|
||||
* So basically we are using the spare time to prepare the SplashScreenView while splash screen
|
||||
* thread is waiting for
|
||||
* 1. WindowManager#addView(binder call to WM),
|
||||
* 2. Choreographer#doFrame happen(uncertain time for next frame, depends on device),
|
||||
* 3. Session#relayout(another binder call to WM which under Choreographer#doFrame, but will
|
||||
* always happen before #draw).
|
||||
* Because above steps are running on splash-screen thread, so pre-draw the BitmapShader on
|
||||
* splash-screen background tread can make they execute in parallel, which ensure it is faster then
|
||||
* to draw the AdaptiveIconDrawable when receive callback from Choreographer#doFrame.
|
||||
*
|
||||
* Here is the sequence to compare the difference between using single and two thread.
|
||||
*
|
||||
* Single thread:
|
||||
* => makeSplashScreenContentView -> WM#addView .. waiting for Choreographer#doFrame -> relayout
|
||||
* -> draw -> AdaptiveIconDrawable#draw
|
||||
*
|
||||
* Two threads:
|
||||
* => makeSplashScreenContentView -> cachePaint(=AdaptiveIconDrawable#draw)
|
||||
* => WM#addView -> .. waiting for Choreographer#doFrame -> relayout -> draw -> (draw the Paint
|
||||
* directly).
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class StartingSurfaceDrawer {
|
||||
@@ -68,7 +104,11 @@ public class StartingSurfaceDrawer {
|
||||
private final DisplayManager mDisplayManager;
|
||||
private final ShellExecutor mSplashScreenExecutor;
|
||||
private final SplashscreenContentDrawer mSplashscreenContentDrawer;
|
||||
private Choreographer mChoreographer;
|
||||
|
||||
/**
|
||||
* @param splashScreenExecutor The thread used to control add and remove starting window.
|
||||
*/
|
||||
public StartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor,
|
||||
TransactionPool pool) {
|
||||
mContext = context;
|
||||
@@ -82,6 +122,7 @@ public class StartingSurfaceDrawer {
|
||||
com.android.wm.shell.R.integer.starting_window_app_reveal_anim_duration);
|
||||
mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext,
|
||||
maxAnimatableIconDuration, iconExitAnimDuration, appRevealAnimDuration, pool);
|
||||
mSplashScreenExecutor.execute(() -> mChoreographer = Choreographer.getInstance());
|
||||
}
|
||||
|
||||
private final SparseArray<StartingWindowRecord> mStartingWindowRecords = new SparseArray<>();
|
||||
@@ -267,36 +308,91 @@ public class StartingSurfaceDrawer {
|
||||
params.setTitle("Splash Screen " + activityInfo.packageName);
|
||||
|
||||
// TODO(b/173975965) tracking performance
|
||||
SplashScreenView sView = null;
|
||||
// Prepare the splash screen content view on splash screen worker thread in parallel, so the
|
||||
// content view won't be blocked by binder call like addWindow and relayout.
|
||||
// 1. Trigger splash screen worker thread to create SplashScreenView before/while
|
||||
// Session#addWindow.
|
||||
// 2. Synchronize the SplashscreenView to splash screen thread before Choreographer start
|
||||
// traversal, which will call Session#relayout on splash screen thread.
|
||||
// 3. Pre-draw the BitmapShader if the icon is immobile on splash screen worker thread, at
|
||||
// the same time the splash screen thread should be executing Session#relayout. Blocking the
|
||||
// traversal -> draw on splash screen thread until the BitmapShader of the icon is ready.
|
||||
final Runnable setViewSynchronized;
|
||||
if (!emptyView) {
|
||||
// Record whether create splash screen view success, notify to current thread after
|
||||
// create splash screen view finished.
|
||||
final SplashScreenViewSupplier viewSupplier = new SplashScreenViewSupplier();
|
||||
setViewSynchronized = () -> {
|
||||
// waiting for setContentView before relayoutWindow
|
||||
SplashScreenView contentView = viewSupplier.get();
|
||||
final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
|
||||
// if record == null, either the starting window added fail or removed already.
|
||||
if (record != null) {
|
||||
// if view == null then creation of content view was failed.
|
||||
if (contentView != null) {
|
||||
try {
|
||||
win.setContentView(contentView);
|
||||
contentView.cacheRootWindow(win);
|
||||
} catch (RuntimeException e) {
|
||||
Slog.w(TAG, "failed set content view to starting window "
|
||||
+ "at taskId: " + taskId, e);
|
||||
contentView = null;
|
||||
}
|
||||
}
|
||||
record.setSplashScreenView(contentView);
|
||||
}
|
||||
};
|
||||
mSplashscreenContentDrawer.createContentView(context,
|
||||
splashscreenContentResId[0], activityInfo, taskId, viewSupplier::setView);
|
||||
} else {
|
||||
setViewSynchronized = null;
|
||||
}
|
||||
|
||||
try {
|
||||
final View view = win.getDecorView();
|
||||
final WindowManager wm = mContext.getSystemService(WindowManager.class);
|
||||
if (!emptyView) {
|
||||
// splash screen content will be deprecated after S.
|
||||
sView = SplashscreenContentDrawer.makeSplashscreenContent(
|
||||
context, splashscreenContentResId[0]);
|
||||
final boolean splashscreenContentCompatible = sView != null;
|
||||
if (splashscreenContentCompatible) {
|
||||
win.setContentView(sView);
|
||||
} else {
|
||||
sView = mSplashscreenContentDrawer
|
||||
.makeSplashScreenContentView(context, activityInfo);
|
||||
win.setContentView(sView);
|
||||
win.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
|
||||
sView.cacheRootWindow(win);
|
||||
}
|
||||
}
|
||||
postAddWindow(taskId, appToken, view, wm, params);
|
||||
|
||||
// all done
|
||||
if (emptyView) {
|
||||
return;
|
||||
}
|
||||
// We use the splash screen worker thread to create SplashScreenView while adding the
|
||||
// window, as otherwise Choreographer#doFrame might be delayed on this thread.
|
||||
// And since Choreographer#doFrame won't happen immediately after adding the window, if
|
||||
// the view is not added to the PhoneWindow on the first #doFrame, the view will not be
|
||||
// rendered on the first frame. So here we need to synchronize the view on the window
|
||||
// before first round relayoutWindow, which will happen after insets animation.
|
||||
mChoreographer.postCallback(CALLBACK_INSETS_ANIMATION, setViewSynchronized, null);
|
||||
} catch (RuntimeException e) {
|
||||
// don't crash if something else bad happens, for example a
|
||||
// failure loading resources because we are loading from an app
|
||||
// on external storage that has been unmounted.
|
||||
Slog.w(TAG, " failed creating starting window at taskId: " + taskId, e);
|
||||
sView = null;
|
||||
} finally {
|
||||
final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
|
||||
if (record != null) {
|
||||
record.setSplashScreenView(sView);
|
||||
Slog.w(TAG, "failed creating starting window at taskId: " + taskId, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class SplashScreenViewSupplier implements Supplier<SplashScreenView> {
|
||||
private SplashScreenView mView;
|
||||
private boolean mIsViewSet;
|
||||
void setView(SplashScreenView view) {
|
||||
synchronized (this) {
|
||||
mView = view;
|
||||
mIsViewSet = true;
|
||||
notify();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable SplashScreenView get() {
|
||||
synchronized (this) {
|
||||
while (!mIsViewSet) {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
}
|
||||
return mView;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package com.android.wm.shell.startingsurface;
|
||||
|
||||
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
|
||||
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
|
||||
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_EMPTY_SPLASH_SCREEN;
|
||||
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_NONE;
|
||||
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SNAPSHOT;
|
||||
@@ -30,11 +31,11 @@ import static android.window.StartingWindowInfo.TYPE_PARAMETER_TASK_SWITCH;
|
||||
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
|
||||
|
||||
import android.app.ActivityManager.RunningTaskInfo;
|
||||
import android.app.ActivityTaskManager;
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.Trace;
|
||||
import android.util.Slog;
|
||||
import android.view.SurfaceControl;
|
||||
import android.window.StartingWindowInfo;
|
||||
@@ -109,17 +110,9 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
|
||||
}
|
||||
|
||||
private static class StartingTypeChecker {
|
||||
TaskSnapshot mSnapshot;
|
||||
|
||||
StartingTypeChecker() { }
|
||||
|
||||
private void reset() {
|
||||
mSnapshot = null;
|
||||
}
|
||||
|
||||
private @StartingWindowInfo.StartingWindowType int
|
||||
estimateStartingWindowType(StartingWindowInfo windowInfo) {
|
||||
reset();
|
||||
final int parameter = windowInfo.startingWindowTypeParameter;
|
||||
final boolean newTask = (parameter & TYPE_PARAMETER_NEW_TASK) != 0;
|
||||
final boolean taskSwitch = (parameter & TYPE_PARAMETER_TASK_SWITCH) != 0;
|
||||
@@ -159,7 +152,7 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
|
||||
}
|
||||
}
|
||||
if (taskSwitch && allowTaskSnapshot) {
|
||||
final TaskSnapshot snapshot = getTaskSnapshot(windowInfo.taskInfo.taskId);
|
||||
final TaskSnapshot snapshot = windowInfo.mTaskSnapshot;
|
||||
if (isSnapshotCompatible(windowInfo, snapshot)) {
|
||||
return STARTING_WINDOW_TYPE_SNAPSHOT;
|
||||
}
|
||||
@@ -198,20 +191,6 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -232,7 +211,9 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
|
||||
*/
|
||||
public void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) {
|
||||
mSplashScreenExecutor.execute(() -> {
|
||||
final int suggestionType = mStartingTypeChecker.estimateStartingWindowType(windowInfo);
|
||||
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addStartingWindow");
|
||||
final int suggestionType = mStartingTypeChecker.estimateStartingWindowType(
|
||||
windowInfo);
|
||||
final RunningTaskInfo runningTaskInfo = windowInfo.taskInfo;
|
||||
if (mTaskLaunchingCallback != null && shouldSendToListener(suggestionType)) {
|
||||
mTaskLaunchingCallback.accept(runningTaskInfo.taskId, suggestionType);
|
||||
@@ -244,10 +225,12 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
|
||||
mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, appToken,
|
||||
true /* emptyView */);
|
||||
} else if (suggestionType == STARTING_WINDOW_TYPE_SNAPSHOT) {
|
||||
final TaskSnapshot snapshot = mStartingTypeChecker.mSnapshot;
|
||||
mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, appToken, snapshot);
|
||||
final TaskSnapshot snapshot = windowInfo.mTaskSnapshot;
|
||||
mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, appToken,
|
||||
snapshot);
|
||||
}
|
||||
// If prefer don't show, then don't show!
|
||||
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ 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.os.Trace.TRACE_TAG_WINDOW_MANAGER;
|
||||
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;
|
||||
@@ -60,6 +61,7 @@ import android.hardware.HardwareBuffer;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.os.Trace;
|
||||
import android.util.MergedConfiguration;
|
||||
import android.util.Slog;
|
||||
import android.view.IWindowSession;
|
||||
@@ -222,8 +224,10 @@ public class TaskSnapshotWindow {
|
||||
final InputChannel tmpInputChannel = new InputChannel();
|
||||
mainExecutor.execute(() -> {
|
||||
try {
|
||||
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#addToDisplay");
|
||||
final int res = session.addToDisplay(window, layoutParams, View.GONE, displayId,
|
||||
mTmpInsetsState, tmpInputChannel, mTmpInsetsState, mTempControls);
|
||||
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
|
||||
if (res < 0) {
|
||||
Slog.w(TAG, "Failed to add snapshot starting window res=" + res);
|
||||
return;
|
||||
@@ -233,9 +237,11 @@ public class TaskSnapshotWindow {
|
||||
}
|
||||
window.setOuter(snapshotSurface);
|
||||
try {
|
||||
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#relayout");
|
||||
session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, -1,
|
||||
tmpFrames, tmpMergedConfiguration, surfaceControl, mTmpInsetsState,
|
||||
mTempControls, TMP_SURFACE_SIZE);
|
||||
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
|
||||
} catch (RemoteException e) {
|
||||
snapshotSurface.clearWindowSynced();
|
||||
}
|
||||
|
||||
@@ -77,9 +77,9 @@ public class StartingSurfaceDrawerTests {
|
||||
int mAddWindowForTask = 0;
|
||||
int mViewThemeResId;
|
||||
|
||||
TestStartingSurfaceDrawer(Context context, ShellExecutor animExecutor,
|
||||
TestStartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor,
|
||||
TransactionPool pool) {
|
||||
super(context, animExecutor, pool);
|
||||
super(context, splashScreenExecutor, pool);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -120,8 +120,9 @@ public class StartingSurfaceDrawerTests {
|
||||
doReturn(metrics).when(mMockWindowManager).getMaximumWindowMetrics();
|
||||
doNothing().when(mMockWindowManager).addView(any(), any());
|
||||
|
||||
mStartingSurfaceDrawer = spy(new TestStartingSurfaceDrawer(context,
|
||||
new HandlerExecutor(new Handler(Looper.getMainLooper())),
|
||||
final HandlerExecutor testExecutor =
|
||||
new HandlerExecutor(new Handler(Looper.getMainLooper()));
|
||||
mStartingSurfaceDrawer = spy(new TestStartingSurfaceDrawer(context, testExecutor,
|
||||
mTransactionPool));
|
||||
}
|
||||
|
||||
|
||||
@@ -381,8 +381,8 @@ public abstract class WMShellBaseModule {
|
||||
@WMSingleton
|
||||
@Provides
|
||||
static StartingWindowController provideStartingWindowController(Context context,
|
||||
@ShellSplashscreenThread ShellExecutor executor, TransactionPool pool) {
|
||||
return new StartingWindowController(context, executor, pool);
|
||||
@ShellSplashscreenThread ShellExecutor splashScreenExecutor, TransactionPool pool) {
|
||||
return new StartingWindowController(context, splashScreenExecutor, pool);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -1926,12 +1926,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
|
||||
}
|
||||
|
||||
void scheduleAddStartingWindow() {
|
||||
// Note: we really want to do sendMessageAtFrontOfQueue() because we
|
||||
// want to process the message ASAP, before any other queued
|
||||
// messages.
|
||||
if (!mWmService.mAnimationHandler.hasCallbacks(mAddStartingWindow)) {
|
||||
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Enqueueing ADD_STARTING");
|
||||
mWmService.mAnimationHandler.postAtFrontOfQueue(mAddStartingWindow);
|
||||
if (StartingSurfaceController.DEBUG_ENABLE_SHELL_DRAWER) {
|
||||
mAddStartingWindow.run();
|
||||
} else {
|
||||
// Note: we really want to do sendMessageAtFrontOfQueue() because we
|
||||
// want to process the message ASAP, before any other queued
|
||||
// messages.
|
||||
if (!mWmService.mAnimationHandler.hasCallbacks(mAddStartingWindow)) {
|
||||
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Enqueueing ADD_STARTING");
|
||||
mWmService.mAnimationHandler.postAtFrontOfQueue(mAddStartingWindow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1943,7 +1947,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
|
||||
final StartingData startingData;
|
||||
synchronized (mWmService.mGlobalLock) {
|
||||
// There can only be one adding request, silly caller!
|
||||
mWmService.mAnimationHandler.removeCallbacks(this);
|
||||
if (!StartingSurfaceController.DEBUG_ENABLE_SHELL_DRAWER) {
|
||||
mWmService.mAnimationHandler.removeCallbacks(this);
|
||||
}
|
||||
|
||||
if (mStartingData == null) {
|
||||
// Animation has been canceled... do nothing.
|
||||
@@ -2192,17 +2198,22 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
|
||||
+ " startingView=%s Callers=%s", this, mStartingWindow, mStartingSurface,
|
||||
Debug.getCallers(5));
|
||||
|
||||
|
||||
// Use the same thread to remove the window as we used to add it, as otherwise we end up
|
||||
// with things in the view hierarchy being called from different threads.
|
||||
mWmService.mAnimationHandler.post(() -> {
|
||||
final Runnable removeSurface = () -> {
|
||||
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Removing startingView=%s", surface);
|
||||
try {
|
||||
surface.remove(prepareAnimation);
|
||||
} catch (Exception e) {
|
||||
Slog.w(TAG_WM, "Exception when removing starting window", e);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (StartingSurfaceController.DEBUG_ENABLE_SHELL_DRAWER) {
|
||||
removeSurface.run();
|
||||
} else {
|
||||
// Use the same thread to remove the window as we used to add it, as otherwise we end up
|
||||
// with things in the view hierarchy being called from different threads.
|
||||
mWmService.mAnimationHandler.post(removeSurface);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeAppTokenFromDisplay() {
|
||||
|
||||
@@ -61,7 +61,7 @@ public class StartingSurfaceController {
|
||||
synchronized (mService.mGlobalLock) {
|
||||
final Task task = activity.getTask();
|
||||
if (task != null && mService.mAtmService.mTaskOrganizerController.addStartingWindow(
|
||||
task, activity.token, theme)) {
|
||||
task, activity.token, theme, null /* taskSnapshot */)) {
|
||||
return new ShellStartingSurface(task);
|
||||
}
|
||||
}
|
||||
@@ -128,7 +128,7 @@ public class StartingSurfaceController {
|
||||
}
|
||||
if (DEBUG_ENABLE_SHELL_DRAWER) {
|
||||
mService.mAtmService.mTaskOrganizerController.addStartingWindow(task,
|
||||
activity.token, 0 /* launchTheme */);
|
||||
activity.token, 0 /* launchTheme */, taskSnapshot);
|
||||
return new ShellStartingSurface(task);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ import android.window.ITaskOrganizer;
|
||||
import android.window.ITaskOrganizerController;
|
||||
import android.window.StartingWindowInfo;
|
||||
import android.window.TaskAppearedInfo;
|
||||
import android.window.TaskSnapshot;
|
||||
import android.window.WindowContainerToken;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
@@ -118,25 +119,25 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
|
||||
return mTaskOrganizer.asBinder();
|
||||
}
|
||||
|
||||
void addStartingWindow(Task task, IBinder appToken, int launchTheme) {
|
||||
void addStartingWindow(Task task, IBinder appToken, int launchTheme,
|
||||
TaskSnapshot taskSnapshot) {
|
||||
final StartingWindowInfo info = task.getStartingWindowInfo();
|
||||
if (launchTheme != 0) {
|
||||
info.splashScreenThemeResId = launchTheme;
|
||||
}
|
||||
mDeferTaskOrgCallbacksConsumer.accept(() -> {
|
||||
try {
|
||||
mTaskOrganizer.addStartingWindow(info, appToken);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Exception sending onTaskStart callback", e);
|
||||
}
|
||||
});
|
||||
info.mTaskSnapshot = taskSnapshot;
|
||||
// make this happen prior than prepare surface
|
||||
try {
|
||||
mTaskOrganizer.addStartingWindow(info, appToken);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Exception sending onTaskStart callback", e);
|
||||
}
|
||||
}
|
||||
|
||||
void removeStartingWindow(Task task, boolean prepareAnimation) {
|
||||
mDeferTaskOrgCallbacksConsumer.accept(() -> {
|
||||
SurfaceControl firstWindowLeash = null;
|
||||
Rect mainFrame = null;
|
||||
// TODO enable shift up animation once we fix flicker test
|
||||
SurfaceControl firstWindowLeash = null;
|
||||
Rect mainFrame = null;
|
||||
// TODO enable shift up animation once we fix flicker test
|
||||
// final boolean playShiftUpAnimation = !task.inMultiWindowMode();
|
||||
// if (prepareAnimation && playShiftUpAnimation) {
|
||||
// final ActivityRecord topActivity = task.topActivityWithStartingWindow();
|
||||
@@ -144,32 +145,29 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
|
||||
// final WindowState mainWindow =
|
||||
// topActivity.findMainWindow(false/* includeStartingApp */);
|
||||
// if (mainWindow != null) {
|
||||
// TODO create proper leash instead of the copied SC
|
||||
// TODO create proper leash instead of the copied SC
|
||||
// firstWindowLeash = new SurfaceControl(mainWindow.getSurfaceControl(),
|
||||
// "TaskOrganizerController.removeStartingWindow");
|
||||
// mainFrame = mainWindow.getRelativeFrame();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
try {
|
||||
mTaskOrganizer.removeStartingWindow(task.mTaskId, firstWindowLeash, mainFrame,
|
||||
/* TODO(183004107) Revert this when jankiness is solved
|
||||
prepareAnimation); */ false);
|
||||
try {
|
||||
mTaskOrganizer.removeStartingWindow(task.mTaskId, firstWindowLeash, mainFrame,
|
||||
/* TODO(183004107) Revert this when jankiness is solved
|
||||
prepareAnimation); */ false);
|
||||
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Exception sending onStartTaskFinished callback", e);
|
||||
}
|
||||
});
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Exception sending onStartTaskFinished callback", e);
|
||||
}
|
||||
}
|
||||
|
||||
void copySplashScreenView(Task task) {
|
||||
mDeferTaskOrgCallbacksConsumer.accept(() -> {
|
||||
try {
|
||||
mTaskOrganizer.copySplashScreenView(task.mTaskId);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Exception sending copyStartingWindowView callback", e);
|
||||
}
|
||||
});
|
||||
try {
|
||||
mTaskOrganizer.copySplashScreenView(task.mTaskId);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Exception sending copyStartingWindowView callback", e);
|
||||
}
|
||||
}
|
||||
|
||||
SurfaceControl prepareLeash(Task task, boolean visible, String reason) {
|
||||
@@ -266,8 +264,9 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
|
||||
mUid = uid;
|
||||
}
|
||||
|
||||
void addStartingWindow(Task t, IBinder appToken, int launchTheme) {
|
||||
mOrganizer.addStartingWindow(t, appToken, launchTheme);
|
||||
void addStartingWindow(Task t, IBinder appToken, int launchTheme,
|
||||
TaskSnapshot taskSnapshot) {
|
||||
mOrganizer.addStartingWindow(t, appToken, launchTheme, taskSnapshot);
|
||||
}
|
||||
|
||||
void removeStartingWindow(Task t, boolean prepareAnimation) {
|
||||
@@ -505,14 +504,15 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
|
||||
return !ArrayUtils.contains(UNSUPPORTED_WINDOWING_MODES, winMode);
|
||||
}
|
||||
|
||||
boolean addStartingWindow(Task task, IBinder appToken, int launchTheme) {
|
||||
boolean addStartingWindow(Task task, IBinder appToken, int launchTheme,
|
||||
TaskSnapshot taskSnapshot) {
|
||||
final Task rootTask = task.getRootTask();
|
||||
if (rootTask == null || rootTask.mTaskOrganizer == null) {
|
||||
return false;
|
||||
}
|
||||
final TaskOrganizerState state =
|
||||
mTaskOrganizerStates.get(rootTask.mTaskOrganizer.asBinder());
|
||||
state.addStartingWindow(task, appToken, launchTheme);
|
||||
state.addStartingWindow(task, appToken, launchTheme, taskSnapshot);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -2357,21 +2357,6 @@ public class ActivityRecordTests extends WindowTestsBase {
|
||||
assertNoStartingWindow(activity);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddRemoveRace() {
|
||||
registerTestStartingWindowOrganizer();
|
||||
// There was once a race condition between adding and removing starting windows
|
||||
final ActivityRecord appToken = new ActivityBuilder(mAtm).setCreateTask(true).build();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
appToken.addStartingWindow(mPackageName,
|
||||
android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
|
||||
false, false);
|
||||
appToken.removeStartingWindow();
|
||||
waitUntilHandlersIdle();
|
||||
assertNoStartingWindow(appToken);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransferStartingWindow() {
|
||||
registerTestStartingWindowOrganizer();
|
||||
|
||||
Reference in New Issue
Block a user