Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b7c82c65 authored by Wei Sheng Shih's avatar Wei Sheng Shih Committed by Automerger Merge Worker
Browse files

Merge "Starting window performance tuning for binder block." into sc-dev am: 9dea87c0

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/13991071

Change-Id: I5e59a13af672e7cd11b812e7807346b0b0e6e3b7
parents 55bedb92 9dea87c0
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -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
+51 −9
Original line number Diff line number Diff line
@@ -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 −17
Original line number Diff line number Diff line
@@ -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) {
            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
+120 −24
Original line number Diff line number Diff line
@@ -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;
        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;
            }
        }
    }
+10 −27
Original line number Diff line number Diff line
@@ -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);
        });
    }

Loading