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

Commit e5cf5cc3 authored by Matthew DeVore's avatar Matthew DeVore
Browse files

CD topology: ensure wallpaper is visible

If a fullscreen app is shown on a display, its wallpaper won't be
visible and we cannot mirror it. This forces the wallpaper to be
visible by adding a View to the window manager which shows the
wallpaper which would be underneath it.

Bug: b/412755041
Bug: b/415345340
Flag: com.android.settings.flags.display_topology_pane_in_display_list
Test: DisplayTopologyPreferenceTest
Change-Id: Iae52514e254939abc208827cee7b530a8f915c04
parent 7f610ad8
Loading
Loading
Loading
Loading
+39 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.settings.connecteddevice.display

import android.content.Context
import android.graphics.PixelFormat
import android.hardware.display.DisplayManager
import android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED
import android.hardware.display.DisplayManager.EVENT_TYPE_DISPLAY_ADDED
@@ -37,12 +38,23 @@ import android.view.DisplayInfo
import android.view.IWindowManager
import android.view.SurfaceControl
import android.view.SurfaceView
import android.view.View
import android.view.ViewManager
import android.view.WindowManager
import android.view.WindowManagerGlobal
import com.android.server.display.feature.flags.Flags.enableModeLimitForExternalDisplay
import com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.VIRTUAL_DISPLAY_PACKAGE_NAME_SYSTEM_PROPERTY
import com.android.settings.flags.FeatureFlagsImpl
import java.util.function.Consumer

/**
 * Wallpaper is forced-revealed using a View added to the window manager with
 * LayoutParams.FLAG_SHOW_WALLPAPER set. In order to clean these views up and avoid adding more than
 * one, we keep RevealedWallpaper instances as markers, both to avoid re-adding and to remove when
 * needed.
 */
data class RevealedWallpaper(val displayId: Int, val revealer: View, val viewManager: ViewManager)

open class ConnectedDisplayInjector(open val context: Context?) {

    open val flags: DesktopExperienceFlags by lazy { DesktopExperienceFlags(FeatureFlagsImpl()) }
@@ -62,6 +74,33 @@ open class ConnectedDisplayInjector(open val context: Context?) {
    /** The window manager instance, or null if it cannot be retrieved. */
    val windowManager: IWindowManager? by lazy { WindowManagerGlobal.getWindowManagerService() }

    /**
     * Reveals the wallpaper on the given display using a View with FLAG_SHOW_WALLPAPER flag set
     * in LayoutParams. This can be cleaned up later using the returned RevealedWallpaper object.
     *
     * @return a RevealedWallpaper which contains the display's window manager and the view that
     *         was added to it, or null if the view could not be added or the WindowManager was not
     *         available
     */
    open fun revealWallpaper(displayId: Int): RevealedWallpaper? {
        val display = displayManager?.getDisplay(displayId) ?: return null
        val windowCtx = context?.createWindowContext(
                display, WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
                /* options= */ null)
        val windowManager = windowCtx?.getSystemService(WindowManager::class.java) ?: return null

        val view = View(windowCtx)
        windowManager.addView(view, WindowManager.LayoutParams().also {
            it.width = 1
            it.height = 1
            it.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
            it.flags = (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    or WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER)
            it.format = PixelFormat.TRANSLUCENT
        })
        return RevealedWallpaper(display.displayId, view, windowManager)
    }

    private fun wrapDmDisplay(display: Display, isEnabled: DisplayIsEnabled): DisplayDevice =
        DisplayDevice(display.displayId, display.name, display.mode,
                display.getSupportedModes().asList(), isEnabled)
+24 −0
Original line number Diff line number Diff line
@@ -109,6 +109,12 @@ class DisplayTopologyPreference(val injector: ConnectedDisplayInjector)

    override fun onDetached() {
        super.onDetached()

        // No longer need to reveal wallpapers since the blocks are not visible; these will be
        // revealed again upon invocation of refreshPane.
        mRevealedWallpapers.forEach { it.viewManager.removeView(it.revealer) }
        mRevealedWallpapers = listOf()

        injector.unregisterTopologyListener(mTopologyListener)
    }

@@ -127,6 +133,8 @@ class DisplayTopologyPreference(val injector: ConnectedDisplayInjector)
            val topology: DisplayTopology, val scaling: TopologyScale,
            val positions: List<Pair<Int, RectF>>)

    private var mRevealedWallpapers: List<RevealedWallpaper> = emptyList()

    /**
     * Holds information about the current drag operation. The initial rawX, rawY values of the
     * cursor are recorded in order to detect whether the drag was a substantial drag or likely
@@ -243,6 +251,22 @@ class DisplayTopologyPreference(val injector: ConnectedDisplayInjector)

        mTopologyInfo = TopologyInfo(topology, scaling, newBounds)

        // Construct a map containing revealers that we want to keep (keepRevealing). Then create a
        // list comprised of the values of that map as well as new revealers (mRevealedWallpapers).
        val keepRevealing = buildMap<Int, RevealedWallpaper> {
            mRevealedWallpapers.forEach { r ->
                if (idToNode.containsKey(r.displayId)) {
                    put(r.displayId, r)
                } else {
                    r.viewManager.removeView(r.revealer)
                }
            }
        }
        mRevealedWallpapers = idToNode.keys
            .map { keepRevealing.get(it) ?: injector.revealWallpaper(it) }
            .filterNotNull()
            .toList()

        // Cancel the drag if one is in progress.
        mDrag = null
    }
+53 −0
Original line number Diff line number Diff line
@@ -27,6 +27,8 @@ import android.hardware.display.DisplayTopology
import android.util.DisplayMetrics
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.ViewManager
import android.widget.FrameLayout
import androidx.preference.PreferenceViewHolder
import androidx.test.core.app.ApplicationProvider
@@ -64,6 +66,11 @@ class DisplayTopologyPreferenceTest {
            get() = topology
            set(value) { topology = value }

        /**
         * A log of events related to wallpaper revealing.
         */
        val revealLog = mutableListOf<String>()

        override val densityDpi = DisplayMetrics.DENSITY_DEFAULT

        override fun registerTopologyListener(listener: Consumer<DisplayTopology>) {
@@ -80,6 +87,31 @@ class DisplayTopologyPreferenceTest {
            }
            topologyListener = null
        }

        override fun revealWallpaper(displayId: Int): RevealedWallpaper? {
            val childView = View(context)
            val viewManager = object : ViewManager {
                override fun addView(view: View, params: ViewGroup.LayoutParams) {
                    revealLog.add("unexpected invocation of addView")
                }

                override fun updateViewLayout(view: View, params: ViewGroup.LayoutParams) {
                    revealLog.add("unexpected invocation of updateViewLayout")
                }

                override fun removeView(view: View) {
                    if (childView === view) {
                        revealLog.add("removed wallpaper revealer for display $displayId")
                    } else {
                        revealLog.add("invalid invocation of removeView")
                    }
                }
            }

            revealLog.add("revealWallpaper invoked for display $displayId")

            return RevealedWallpaper(displayId, childView, viewManager)
        }
    }

    @Test
@@ -234,6 +266,12 @@ class DisplayTopologyPreferenceTest {
        val (leftBlock, rightBlock) = setupTwoDisplays()
        val leftBounds = virtualBounds(leftBlock)

        assertThat(injector.revealLog).containsExactly(
                "revealWallpaper invoked for display 1",
                "revealWallpaper invoked for display 42")

        injector.revealLog.clear()

        preference.mTimesRefreshedBlocks = 0

        val downEvent = MotionEventBuilder.newBuilder()
@@ -256,6 +294,8 @@ class DisplayTopologyPreferenceTest {
        rightBlock.dispatchTouchEvent(moveEvent)
        rightBlock.dispatchTouchEvent(upEvent)

        assertThat(injector.revealLog).isEmpty()

        val rootChildren = injector.topology!!.root!!.children
        assertThat(rootChildren).hasSize(1)
        val child = rootChildren[0]
@@ -302,14 +342,27 @@ class DisplayTopologyPreferenceTest {
    @Test
    fun keepOriginalViewsWhenAddingMore() {
        setupTwoDisplays()
        assertThat(injector.revealLog).containsExactly(
                "revealWallpaper invoked for display 1",
                "revealWallpaper invoked for display 42")
        injector.revealLog.clear()
        val childrenBefore = getPaneChildren()
        injector.topology!!.addDisplay(/* displayId= */ 101, 320, 240, /* logicalDensity= */ 160)
        preference.refreshPane()
        assertThat(injector.revealLog).containsExactly("revealWallpaper invoked for display 101")
        injector.revealLog.clear()
        val childrenAfter = getPaneChildren()

        assertThat(childrenBefore).hasSize(2)
        assertThat(childrenAfter).hasSize(3)
        assertThat(childrenAfter.subList(0, 2)).isEqualTo(childrenBefore)

        assertThat(injector.topology!!.removeDisplay(42)).isTrue()
        assertThat(injector.topology!!.removeDisplay(101)).isTrue()
        preference.refreshPane()
        assertThat(injector.revealLog).containsExactly(
                "removed wallpaper revealer for display 42",
                "removed wallpaper revealer for display 101")
    }

    @Test