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

Commit 6298f2c0 authored by wilsonshih's avatar wilsonshih
Browse files

Starting window performance tuning for binder block.

- Pass TaskSnapshot to Shell directly.
- Parallel process: create a splash screen worker thread to create the
splash screen view and pre-draw the icon drawable, and use splash
screen thread to do stuff about window's lifecycle like add/remove
window. We don't use shell main thread here so the starting window
won't blocking any task execute on main thread.
- Trigger addStartingWindow directly when startActivity, also
removeStartingWindow directly without post the animation thread.
- Do not defer addStartingWindow for prepare surface, on the contrast
it should be execute before prepare surface.
- Remove ActivityRecordTests#testAddRemoveRace, it wasn't been test as
expected because in original logic the add and remove starting window
are post to animation thread, so the test was actually testing on
handler#postAtFrontOfQueue and handler#removeCallbacks.
For now it will only cause OutOfMemoryError.

Bug: 183665220
Bug: 182836977
Test: atest StartingSurfaceDrawerTests SplashscreenTests
Change-Id: Ie9a529c7d81f07f923394eb69944518f3e9010ce
parent fca79abe
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