Loading core/java/android/window/StartingWindowInfo.java +8 −0 Original line number Diff line number Diff line Loading @@ -142,6 +142,12 @@ public final class StartingWindowInfo implements Parcelable { */ public boolean isKeyguardOccluded = false; /** * TaskSnapshot. * @hide */ public TaskSnapshot mTaskSnapshot; public StartingWindowInfo() { } Loading @@ -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) { Loading @@ -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 Loading libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java +51 −9 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 { Loading @@ -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) { Loading @@ -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() { Loading Loading @@ -146,7 +190,7 @@ public class SplashscreenContentDrawer { } } SplashScreenView makeSplashScreenContentView(Context context, ActivityInfo ai) { private SplashScreenView makeSplashScreenContentView(Context context, ActivityInfo ai) { updateDensity(); getWindowAttrs(context, mTmpAttrs); Loading Loading @@ -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; Loading Loading @@ -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); Loading @@ -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() { Loading Loading @@ -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 Loading libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java +37 −17 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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) { synchronized (mPaint) { if (mCacheComplete) { return; } Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "cachePaint"); final Bitmap layersBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 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, final Shader layersShader = new BitmapShader(layersBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); mPaint.setShader(mLayersShader); mPaint.setShader(layersShader); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); mCacheComplete = true; } } @Override public void draw(Canvas canvas) { 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 Loading libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java +120 −24 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 { Loading @@ -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; Loading @@ -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<>(); Loading Loading @@ -267,36 +308,91 @@ public class StartingSurfaceDrawer { params.setTitle("Splash Screen " + activityInfo.packageName); // TODO(b/173975965) tracking performance SplashScreenView sView = null; try { final View view = win.getDecorView(); final WindowManager wm = mContext.getSystemService(WindowManager.class); // 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) { // 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); // 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); 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); } } 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; } } } Loading libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java +10 −27 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; } Loading Loading @@ -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; } } /* Loading @@ -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); Loading @@ -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); }); } Loading Loading
core/java/android/window/StartingWindowInfo.java +8 −0 Original line number Diff line number Diff line Loading @@ -142,6 +142,12 @@ public final class StartingWindowInfo implements Parcelable { */ public boolean isKeyguardOccluded = false; /** * TaskSnapshot. * @hide */ public TaskSnapshot mTaskSnapshot; public StartingWindowInfo() { } Loading @@ -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) { Loading @@ -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 Loading
libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java +51 −9 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 { Loading @@ -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) { Loading @@ -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() { Loading Loading @@ -146,7 +190,7 @@ public class SplashscreenContentDrawer { } } SplashScreenView makeSplashScreenContentView(Context context, ActivityInfo ai) { private SplashScreenView makeSplashScreenContentView(Context context, ActivityInfo ai) { updateDensity(); getWindowAttrs(context, mTmpAttrs); Loading Loading @@ -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; Loading Loading @@ -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); Loading @@ -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() { Loading Loading @@ -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 Loading
libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java +37 −17 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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) { synchronized (mPaint) { if (mCacheComplete) { return; } Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "cachePaint"); final Bitmap layersBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 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, final Shader layersShader = new BitmapShader(layersBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); mPaint.setShader(mLayersShader); mPaint.setShader(layersShader); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); mCacheComplete = true; } } @Override public void draw(Canvas canvas) { 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 Loading
libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java +120 −24 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 { Loading @@ -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; Loading @@ -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<>(); Loading Loading @@ -267,36 +308,91 @@ public class StartingSurfaceDrawer { params.setTitle("Splash Screen " + activityInfo.packageName); // TODO(b/173975965) tracking performance SplashScreenView sView = null; try { final View view = win.getDecorView(); final WindowManager wm = mContext.getSystemService(WindowManager.class); // 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) { // 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); // 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); 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); } } 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; } } } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java +10 −27 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; } Loading Loading @@ -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; } } /* Loading @@ -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); Loading @@ -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); }); } Loading