Loading src/com/android/settings/connecteddevice/display/ConnectedDisplayInjector.kt +39 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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()) } Loading @@ -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) Loading src/com/android/settings/connecteddevice/display/DisplayTopologyPreference.kt +24 −0 Original line number Diff line number Diff line Loading @@ -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) } Loading @@ -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 Loading Loading @@ -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 } Loading tests/robotests/src/com/android/settings/connecteddevice/display/DisplayTopologyPreferenceTest.kt +53 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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>) { Loading @@ -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 Loading Loading @@ -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() Loading @@ -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] Loading Loading @@ -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 Loading Loading
src/com/android/settings/connecteddevice/display/ConnectedDisplayInjector.kt +39 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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()) } Loading @@ -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) Loading
src/com/android/settings/connecteddevice/display/DisplayTopologyPreference.kt +24 −0 Original line number Diff line number Diff line Loading @@ -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) } Loading @@ -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 Loading Loading @@ -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 } Loading
tests/robotests/src/com/android/settings/connecteddevice/display/DisplayTopologyPreferenceTest.kt +53 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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>) { Loading @@ -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 Loading Loading @@ -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() Loading @@ -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] Loading Loading @@ -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 Loading