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

Commit 86495d9b authored by Jorge Gil's avatar Jorge Gil
Browse files

[6/N] WindowDecorViewHost: Warm up SCVHs

Spiritual revert^2 of I08111bfd4728e5223ed078916255313b13a4093f, but
broken down into smaller changes.

Warms up two WindowDecorViewHosts on shell init by instantiating them
and adding a 0x0 view hierarchy and immediately releasing them back into
the pool.
The warm up size is selected based on the possibility of launching a
split-pair after the system starts, which would require two SCVHs
simultaneously. Freeform SCVH are expected to be launched one at a time,
so there is no need for additional warm up at the moment.

Bug: 360452034
Flag: com.android.window.flags.enable_desktop_windowing_scvh_cache_bug_fix
Test: check perfetto trace for initial task launch using warmed up SCVHs

Change-Id: I2973cf4bb9b649f5a8239ca52984cadb7c5a3a7c
parent 513caf7c
Loading
Loading
Loading
Loading
+17 −0
Original line number Diff line number Diff line
@@ -91,6 +91,9 @@ public class DesktopModeStatus {
    /** The maximum override density allowed for tasks inside the desktop. */
    private static final int DESKTOP_DENSITY_MAX = 1000;

    /** The number of [WindowDecorViewHost] instances to warm up on system start. */
    private static final int WINDOW_DECOR_PRE_WARM_SIZE = 2;

    /**
     * Sysprop declaring whether to enters desktop mode by default when the windowing mode of the
     * display's root TaskDisplayArea is set to WINDOWING_MODE_FREEFORM.
@@ -121,6 +124,14 @@ public class DesktopModeStatus {
     */
    private static final String MAX_TASK_LIMIT_SYS_PROP = "persist.wm.debug.desktop_max_task_limit";

    /**
     * Sysprop declaring the number of [WindowDecorViewHost] instances to warm up on system start.
     *
     * <p>If it is not defined, then [WINDOW_DECOR_PRE_WARM_SIZE] is used.
     */
    private static final String WINDOW_DECOR_PRE_WARM_SIZE_SYS_PROP =
            "persist.wm.debug.desktop_window_decor_pre_warm_size";

    /**
     * Return {@code true} if veiled resizing is active. If false, fluid resizing is used.
     */
@@ -176,6 +187,12 @@ public class DesktopModeStatus {
        return 0;
    }

    /** The number of [WindowDecorViewHost] instances to warm up on system start. */
    public static int getWindowDecorPreWarmSize() {
        return SystemProperties.getInt(WINDOW_DECOR_PRE_WARM_SIZE_SYS_PROP,
                WINDOW_DECOR_PRE_WARM_SIZE);
    }

    /**
     * Return {@code true} if the current device supports desktop mode.
     */
+5 −2
Original line number Diff line number Diff line
@@ -349,10 +349,13 @@ public abstract class WMShellModule {
    @Provides
    static WindowDecorViewHostSupplier<WindowDecorViewHost> provideWindowDecorViewHostSupplier(
            @NonNull Context context,
            @ShellMainThread @NonNull CoroutineScope mainScope) {
            @ShellMainThread @NonNull CoroutineScope mainScope,
            @NonNull ShellInit shellInit) {
        final int poolSize = DesktopModeStatus.getWindowDecorScvhPoolSize(context);
        final int preWarmSize = DesktopModeStatus.getWindowDecorPreWarmSize();
        if (DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(context) && poolSize > 0) {
            return new PooledWindowDecorViewHostSupplier(mainScope, poolSize);
            return new PooledWindowDecorViewHostSupplier(
                    context, mainScope, shellInit, poolSize, preWarmSize);
        }
        return new DefaultWindowDecorViewHostSupplier(mainScope);
    }
+37 −5
Original line number Diff line number Diff line
@@ -21,25 +21,57 @@ import android.util.Pools
import android.view.Display
import android.view.SurfaceControl
import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.sysui.ShellInit
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

/**
 * A [WindowDecorViewHostSupplier] backed by a pool to allow recycling view hosts which may be
 * expensive to recreate for each new or updated window decoration.
 *
 * Callers can obtain a [WindowDecorViewHost] using [acquire], which will return a pooled
 * object if available, or create a new instance and return it if needed. When finished using a
 * [WindowDecorViewHost], it must be released using [release] to allow it to be sent back
 * into the pool and reused later on.
 * Callers can obtain a [WindowDecorViewHost] using [acquire], which will return a pooled object if
 * available, or create a new instance and return it if needed. When finished using a
 * [WindowDecorViewHost], it must be released using [release] to allow it to be sent back into the
 * pool and reused later on.
 *
 * This class also supports pre-warming [ReusableWindowDecorViewHost] instances, which will be put
 * into the pool immediately after creation.
 */
class PooledWindowDecorViewHostSupplier(
    private val context: Context,
    @ShellMainThread private val mainScope: CoroutineScope,
    shellInit: ShellInit,
    maxPoolSize: Int,
    private val preWarmSize: Int,
) : WindowDecorViewHostSupplier<WindowDecorViewHost> {

    private val pool: Pools.Pool<WindowDecorViewHost> = Pools.SynchronizedPool(maxPoolSize)
    private var nextDecorViewHostId = 0

    init {
        require(preWarmSize <= maxPoolSize) { "Pre-warm size should not exceed pool size" }
        shellInit.addInitCallback(this::onShellInit, this)
    }

    private fun onShellInit() {
        if (preWarmSize <= 0) {
            return
        }
        preWarmViewHosts(preWarmSize)
    }

    private fun preWarmViewHosts(preWarmSize: Int) {
        mainScope.launch {
            // Applying isn't needed, as the surface was never actually shown.
            val t = SurfaceControl.Transaction()
            repeat(preWarmSize) {
                val warmedViewHost = newInstance(context, context.display).apply { warmUp() }
                // Put the warmed view host in the pool by releasing it.
                release(warmedViewHost, t)
            }
        }
    }

    override fun acquire(context: Context, display: Display): WindowDecorViewHost {
        val pooledViewHost = pool.acquire()
        if (pooledViewHost != null) {
@@ -64,7 +96,7 @@ class PooledWindowDecorViewHostSupplier(
            context = context,
            mainScope = mainScope,
            display = display,
            id = nextDecorViewHostId++
            id = nextDecorViewHostId++,
        )
    }
}
+32 −1
Original line number Diff line number Diff line
@@ -17,11 +17,15 @@ package com.android.wm.shell.windowdecor.common.viewhost

import android.content.Context
import android.content.res.Configuration
import android.graphics.PixelFormat
import android.graphics.Region
import android.view.Display
import android.view.SurfaceControl
import android.view.View
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
import android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
import android.view.WindowManager.LayoutParams.TYPE_APPLICATION
import android.widget.FrameLayout
import androidx.tracing.Trace
import com.android.internal.annotations.VisibleForTesting
@@ -35,6 +39,9 @@ import kotlinx.coroutines.launch
 * 1) Replacing the root [View], meaning [WindowDecorViewHost.updateView] maybe be called with
 *    different [View] instances. This is useful when reusing [WindowDecorViewHost]s instances for
 *    vastly different view hierarchies, such as Desktop Windowing's App Handles and App Headers.
 * 2) Pre-warming of the underlying [SurfaceControlViewHostAdapter]s. Useful because their creation
 *    and first root view assignment are expensive, which is undesirable in latency-sensitive code
 *    paths like during a shell transition.
 */
class ReusableWindowDecorViewHost(
    private val context: Context,
@@ -44,7 +51,7 @@ class ReusableWindowDecorViewHost(
    @VisibleForTesting
    val viewHostAdapter: SurfaceControlViewHostAdapter =
        SurfaceControlViewHostAdapter(context, display),
) : WindowDecorViewHost {
) : WindowDecorViewHost, Warmable {
    @VisibleForTesting val rootView = FrameLayout(context)

    private var currentUpdateJob: Job? = null
@@ -52,6 +59,30 @@ class ReusableWindowDecorViewHost(
    override val surfaceControl: SurfaceControl
        get() = viewHostAdapter.rootSurface

    override fun warmUp() {
        if (viewHostAdapter.isInitialized()) {
            // Already warmed up.
            return
        }
        Trace.beginSection("$TAG#warmUp")
        viewHostAdapter.prepareViewHost(context.resources.configuration, touchableRegion = null)
        viewHostAdapter.updateView(
            rootView,
            WindowManager.LayoutParams(
                    0 /* width*/,
                    0 /* height */,
                    TYPE_APPLICATION,
                    FLAG_NOT_FOCUSABLE or FLAG_SPLIT_TOUCH,
                    PixelFormat.TRANSPARENT,
                )
                .apply {
                    setTitle("View root of $TAG#$id")
                    setTrustedOverlay()
                },
        )
        Trace.endSection()
    }

    override fun updateView(
        view: View,
        attrs: WindowManager.LayoutParams,
+23 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.wm.shell.windowdecor.common.viewhost

/**
 * An interface for an object that can be warmed up before it's needed.
 */
interface Warmable {
    fun warmUp()
}
Loading