Loading packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerTest.kt +97 −10 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import android.view.Surface import android.view.View import android.view.WindowManager import android.view.fakeWindowManager import android.widget.FrameLayout import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase Loading Loading @@ -71,12 +72,13 @@ class PrivacyDotWindowControllerTest : SysuiTestCase() { } @Test fun start_afterUiThreadExecutes_addsWindowsOnUiThread() { fun start_afterUiThreadExecutes_doesNotAddWindowsInitially() { underTest.start() executor.runAllReady() assertThat(windowManager.addedViews).hasSize(4) // Windows are now added dynamically, so immediately after initialization, // no windows should be present until a dot is "shown". assertThat(windowManager.addedViews).isEmpty() } @Test Loading @@ -93,6 +95,8 @@ class PrivacyDotWindowControllerTest : SysuiTestCase() { underTest.start() executor.runAllReady() // The ID should be on the dotView, not necessarily the container anymore. // Assuming R.id.privacy_dot_top_left_container is the ID of the inner dotView. assertThat(viewController.topLeft?.id).isEqualTo(R.id.privacy_dot_top_left_container) } Loading Loading @@ -121,13 +125,63 @@ class PrivacyDotWindowControllerTest : SysuiTestCase() { .isEqualTo(R.id.privacy_dot_bottom_right_container) } @Test fun onPrivacyDotShown_addsWindow() { underTest.start() executor.runAllReady() // Simulate the PrivacyDotViewController showing the top-left dot viewController.showingListener?.onPrivacyDotShown(viewController.topLeft!!) executor.runAllReady() // Ensure the addView call on UI thread is processed // Verify exactly one window was added assertThat(windowManager.addedViews).hasSize(1) // Get the FrameLayout that was added to the WindowManager val addedWindowRootView = windowManager.addedViews.keys.first() // Assert it's a FrameLayout (the expected container) expect.that(addedWindowRootView).isInstanceOf(FrameLayout::class.java) // Assert that this FrameLayout actually contains the specific dotView // (fakeViewController.topLeft) // The PrivacyDotWindowController's inflate method adds the dotView as a child of the // FrameLayout. expect .that((addedWindowRootView as FrameLayout).getChildAt(0)) .isEqualTo(viewController.topLeft) } @Test fun onPrivacyDotHidden_removesWindow() { underTest.start() executor.runAllReady() // Show the top-left dot first viewController.showingListener?.onPrivacyDotShown(viewController.topLeft) executor.runAllReady() assertThat(windowManager.addedViews).hasSize(1) // Now hide it viewController.showingListener?.onPrivacyDotHidden(viewController.topLeft) executor.runAllReady() assertThat(windowManager.addedViews).isEmpty() } @Test fun start_viewsAddedInRespectiveCorners() { context.display = mock { on { rotation } doReturn Surface.ROTATION_0 } underTest.start() executor.runAllReady() // Now, trigger the 'shown' event for each dot viewController.showingListener?.onPrivacyDotShown(viewController.topLeft) viewController.showingListener?.onPrivacyDotShown(viewController.topRight) viewController.showingListener?.onPrivacyDotShown(viewController.bottomLeft) viewController.showingListener?.onPrivacyDotShown(viewController.bottomRight) executor.runAllReady() // Process all addView calls expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(TOP or LEFT) expect.that(gravityForView(viewController.topRight!!)).isEqualTo(TOP or RIGHT) expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(BOTTOM or LEFT) Loading @@ -137,10 +191,16 @@ class PrivacyDotWindowControllerTest : SysuiTestCase() { @Test fun start_rotation90_viewsPositionIsShifted90degrees() { context.display = mock { on { rotation } doReturn Surface.ROTATION_90 } underTest.start() executor.runAllReady() // Now, trigger the 'shown' event for each dot viewController.showingListener?.onPrivacyDotShown(viewController.topLeft) viewController.showingListener?.onPrivacyDotShown(viewController.topRight) viewController.showingListener?.onPrivacyDotShown(viewController.bottomLeft) viewController.showingListener?.onPrivacyDotShown(viewController.bottomRight) executor.runAllReady() // Process all addView calls expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(BOTTOM or LEFT) expect.that(gravityForView(viewController.topRight!!)).isEqualTo(TOP or LEFT) expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(BOTTOM or RIGHT) Loading @@ -150,10 +210,16 @@ class PrivacyDotWindowControllerTest : SysuiTestCase() { @Test fun start_rotation180_viewsPositionIsShifted180degrees() { context.display = mock { on { rotation } doReturn Surface.ROTATION_180 } underTest.start() executor.runAllReady() // Now, trigger the 'shown' event for each dot viewController.showingListener?.onPrivacyDotShown(viewController.topLeft) viewController.showingListener?.onPrivacyDotShown(viewController.topRight) viewController.showingListener?.onPrivacyDotShown(viewController.bottomLeft) viewController.showingListener?.onPrivacyDotShown(viewController.bottomRight) executor.runAllReady() // Process all addView calls expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(BOTTOM or RIGHT) expect.that(gravityForView(viewController.topRight!!)).isEqualTo(BOTTOM or LEFT) expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(TOP or RIGHT) Loading @@ -163,10 +229,16 @@ class PrivacyDotWindowControllerTest : SysuiTestCase() { @Test fun start_rotation270_viewsPositionIsShifted270degrees() { context.display = mock { on { rotation } doReturn Surface.ROTATION_270 } underTest.start() executor.runAllReady() // Now, trigger the 'shown' event for each dot viewController.showingListener?.onPrivacyDotShown(viewController.topLeft) viewController.showingListener?.onPrivacyDotShown(viewController.topRight) viewController.showingListener?.onPrivacyDotShown(viewController.bottomLeft) viewController.showingListener?.onPrivacyDotShown(viewController.bottomRight) executor.runAllReady() // Process all addView calls expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(TOP or RIGHT) expect.that(gravityForView(viewController.topRight!!)).isEqualTo(BOTTOM or RIGHT) expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(TOP or LEFT) Loading @@ -174,19 +246,34 @@ class PrivacyDotWindowControllerTest : SysuiTestCase() { } @Test fun onStop_removeAllWindows() { fun onStop_removesAllCurrentlyAddedWindows() { underTest.start() executor.runAllReady() // Show all dots so their windows are added viewController.showingListener?.onPrivacyDotShown(viewController.topLeft) viewController.showingListener?.onPrivacyDotShown(viewController.topRight) viewController.showingListener?.onPrivacyDotShown(viewController.bottomLeft) viewController.showingListener?.onPrivacyDotShown(viewController.bottomRight) executor.runAllReady() assertThat(windowManager.addedViews).hasSize(4) // Now call stop underTest.stop() executor.runAllReady() assertThat(windowManager.addedViews).isEmpty() } private fun paramsForView(view: View): WindowManager.LayoutParams { // Helper functions: Note that paramsForView needs to find the *root* view (FrameLayout) // that was added to the window manager, not the inner dotView. private fun paramsForView(dotView: View): WindowManager.LayoutParams { // We're looking for the FrameLayout that contains the dotView. // The dotView has an ID, and the FrameLayout is its parent. return windowManager.addedViews.entries .first { it.key == view || it.key.findViewById<View>(view.id) != null } .first { (rootView, _) -> rootView is FrameLayout && rootView.findViewById<View>(dotView.id) == dotView } .value } Loading packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotCorner.kt +14 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,10 @@ package com.android.systemui.statusbar.events import android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM import android.view.DisplayCutout.BOUNDS_POSITION_LEFT import android.view.DisplayCutout.BOUNDS_POSITION_RIGHT import android.view.DisplayCutout.BOUNDS_POSITION_TOP import android.view.Gravity import android.view.Surface Loading @@ -25,30 +29,40 @@ enum class PrivacyDotCorner( val gravity: Int, val innerGravity: Int, val title: String, val alignedBound1: Int, val alignedBound2: Int, ) { TopLeft( index = 0, gravity = Gravity.TOP or Gravity.LEFT, innerGravity = Gravity.CENTER_VERTICAL or Gravity.RIGHT, title = "TopLeft", alignedBound1 = BOUNDS_POSITION_TOP, alignedBound2 = BOUNDS_POSITION_LEFT, ), TopRight( index = 1, gravity = Gravity.TOP or Gravity.RIGHT, innerGravity = Gravity.CENTER_VERTICAL or Gravity.LEFT, title = "TopRight", alignedBound1 = BOUNDS_POSITION_TOP, alignedBound2 = BOUNDS_POSITION_RIGHT, ), BottomRight( index = 2, gravity = Gravity.BOTTOM or Gravity.RIGHT, innerGravity = Gravity.CENTER_VERTICAL or Gravity.RIGHT, title = "BottomRight", alignedBound1 = BOUNDS_POSITION_BOTTOM, alignedBound2 = BOUNDS_POSITION_RIGHT, ), BottomLeft( index = 3, gravity = Gravity.BOTTOM or Gravity.LEFT, innerGravity = Gravity.CENTER_VERTICAL or Gravity.LEFT, title = "BottomLeft", alignedBound1 = BOUNDS_POSITION_BOTTOM, alignedBound2 = BOUNDS_POSITION_LEFT, ), } Loading packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotWindowController.kt +57 −26 Original line number Diff line number Diff line Loading @@ -18,10 +18,6 @@ package com.android.systemui.statusbar.events import android.util.Log import android.view.Display import android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM import android.view.DisplayCutout.BOUNDS_POSITION_LEFT import android.view.DisplayCutout.BOUNDS_POSITION_RIGHT import android.view.DisplayCutout.BOUNDS_POSITION_TOP import android.view.LayoutInflater import android.view.View import android.view.WindowManager Loading Loading @@ -59,50 +55,79 @@ constructor( @ScreenDecorationsThread private val uiExecutor: Executor, private val dotFactory: PrivacyDotDecorProviderFactory, ) { private val dotViews: MutableSet<View> = mutableSetOf() private val dotWindowViewsByCorner = mutableMapOf<PrivacyDotCorner, View>() private var displayRotationOnStartup = 0 fun start() { uiExecutor.execute { startOnUiThread() } } private fun startOnUiThread() { displayRotationOnStartup = inflater.context.display.rotation val providers = dotFactory.providers val topLeft = providers.inflate(BOUNDS_POSITION_TOP, BOUNDS_POSITION_LEFT) val topRight = providers.inflate(BOUNDS_POSITION_TOP, BOUNDS_POSITION_RIGHT) val bottomLeft = providers.inflate(BOUNDS_POSITION_BOTTOM, BOUNDS_POSITION_LEFT) val bottomRight = providers.inflate(BOUNDS_POSITION_BOTTOM, BOUNDS_POSITION_RIGHT) val topLeftContainer = providers.inflate(TopLeft) val topRightContainer = providers.inflate(TopRight) val bottomLeftContainer = providers.inflate(BottomLeft) val bottomRightContainer = providers.inflate(BottomRight) listOfNotNull( topLeft.addToWindow(TopLeft), topRight.addToWindow(TopRight), bottomLeft.addToWindow(BottomLeft), bottomRight.addToWindow(BottomRight), val dotViewContainersByView = mapOf( topLeftContainer.dotView to topLeftContainer, topRightContainer.dotView to topRightContainer, bottomLeftContainer.dotView to bottomLeftContainer, bottomRightContainer.dotView to bottomRightContainer, ) .forEach { dotViews.add(it) } privacyDotViewController.initialize(topLeft, topRight, bottomLeft, bottomRight) privacyDotViewController.showingListener = object : PrivacyDotViewController.ShowingListener { override fun onPrivacyDotShown(v: View?) { val dotViewContainer = dotViewContainersByView[v] if (v == null || dotViewContainer == null) { return } v.addToWindow(dotViewContainer.corner) dotWindowViewsByCorner[dotViewContainer.corner] = dotViewContainer.windowView } private fun List<DecorProvider>.inflate(alignedBound1: Int, alignedBound2: Int): View { override fun onPrivacyDotHidden(v: View?) { val dotViewContainer = dotViewContainersByView[v] val windowView = dotWindowViewsByCorner.remove(dotViewContainer?.corner) if (windowView != null) { windowManager.removeView(windowView) } } } privacyDotViewController.initialize( topLeftContainer.dotView, topRightContainer.dotView, bottomLeftContainer.dotView, bottomRightContainer.dotView, ) } private fun List<DecorProvider>.inflate(corner: PrivacyDotCorner): DotViewContainer { val provider = first { it.alignedBounds.containsExactly(alignedBound1, alignedBound2) } first { it.alignedBounds.containsExactly(corner.alignedBound1, corner.alignedBound2) } as PrivacyDotCornerDecorProviderImpl return inflater.inflate(/* resource= */ provider.layoutId, /* root= */ null) val dotView = inflater.inflate(/* resource= */ provider.layoutId, /* root= */ null) // PrivacyDotViewController expects the dot view to have a FrameLayout parent. val windowView = FrameLayout(dotView.context) windowView.addView(dotView) return DotViewContainer(windowView, dotView, corner) } private fun View.addToWindow(corner: PrivacyDotCorner): View? { private fun View.addToWindow(corner: PrivacyDotCorner) { val excludeFromScreenshots = displayId == Display.DEFAULT_DISPLAY val params = ScreenDecorations.getWindowLayoutBaseParams(excludeFromScreenshots).apply { width = WRAP_CONTENT height = WRAP_CONTENT gravity = corner.rotatedCorner(context.display.rotation).gravity gravity = corner.rotatedCorner(displayRotationOnStartup).gravity title = "PrivacyDot${corner.title}$displayId" } // PrivacyDotViewController expects the dot view to have a FrameLayout parent. val rootView = FrameLayout(context) rootView.addView(this) try { // Wrapping this in a try/catch to avoid crashes when a display is instantly removed // after being added, and initialization hasn't finished yet. Loading @@ -114,13 +139,19 @@ constructor( e, ) } return rootView return } fun stop() { dotViews.forEach { windowManager.removeView(it) } dotWindowViewsByCorner.forEach { windowManager.removeView(it.value) } } private data class DotViewContainer( val windowView: View, val dotView: View, val corner: PrivacyDotCorner, ) @AssistedFactory fun interface Factory { fun create( Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerTest.kt +97 −10 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import android.view.Surface import android.view.View import android.view.WindowManager import android.view.fakeWindowManager import android.widget.FrameLayout import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase Loading Loading @@ -71,12 +72,13 @@ class PrivacyDotWindowControllerTest : SysuiTestCase() { } @Test fun start_afterUiThreadExecutes_addsWindowsOnUiThread() { fun start_afterUiThreadExecutes_doesNotAddWindowsInitially() { underTest.start() executor.runAllReady() assertThat(windowManager.addedViews).hasSize(4) // Windows are now added dynamically, so immediately after initialization, // no windows should be present until a dot is "shown". assertThat(windowManager.addedViews).isEmpty() } @Test Loading @@ -93,6 +95,8 @@ class PrivacyDotWindowControllerTest : SysuiTestCase() { underTest.start() executor.runAllReady() // The ID should be on the dotView, not necessarily the container anymore. // Assuming R.id.privacy_dot_top_left_container is the ID of the inner dotView. assertThat(viewController.topLeft?.id).isEqualTo(R.id.privacy_dot_top_left_container) } Loading Loading @@ -121,13 +125,63 @@ class PrivacyDotWindowControllerTest : SysuiTestCase() { .isEqualTo(R.id.privacy_dot_bottom_right_container) } @Test fun onPrivacyDotShown_addsWindow() { underTest.start() executor.runAllReady() // Simulate the PrivacyDotViewController showing the top-left dot viewController.showingListener?.onPrivacyDotShown(viewController.topLeft!!) executor.runAllReady() // Ensure the addView call on UI thread is processed // Verify exactly one window was added assertThat(windowManager.addedViews).hasSize(1) // Get the FrameLayout that was added to the WindowManager val addedWindowRootView = windowManager.addedViews.keys.first() // Assert it's a FrameLayout (the expected container) expect.that(addedWindowRootView).isInstanceOf(FrameLayout::class.java) // Assert that this FrameLayout actually contains the specific dotView // (fakeViewController.topLeft) // The PrivacyDotWindowController's inflate method adds the dotView as a child of the // FrameLayout. expect .that((addedWindowRootView as FrameLayout).getChildAt(0)) .isEqualTo(viewController.topLeft) } @Test fun onPrivacyDotHidden_removesWindow() { underTest.start() executor.runAllReady() // Show the top-left dot first viewController.showingListener?.onPrivacyDotShown(viewController.topLeft) executor.runAllReady() assertThat(windowManager.addedViews).hasSize(1) // Now hide it viewController.showingListener?.onPrivacyDotHidden(viewController.topLeft) executor.runAllReady() assertThat(windowManager.addedViews).isEmpty() } @Test fun start_viewsAddedInRespectiveCorners() { context.display = mock { on { rotation } doReturn Surface.ROTATION_0 } underTest.start() executor.runAllReady() // Now, trigger the 'shown' event for each dot viewController.showingListener?.onPrivacyDotShown(viewController.topLeft) viewController.showingListener?.onPrivacyDotShown(viewController.topRight) viewController.showingListener?.onPrivacyDotShown(viewController.bottomLeft) viewController.showingListener?.onPrivacyDotShown(viewController.bottomRight) executor.runAllReady() // Process all addView calls expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(TOP or LEFT) expect.that(gravityForView(viewController.topRight!!)).isEqualTo(TOP or RIGHT) expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(BOTTOM or LEFT) Loading @@ -137,10 +191,16 @@ class PrivacyDotWindowControllerTest : SysuiTestCase() { @Test fun start_rotation90_viewsPositionIsShifted90degrees() { context.display = mock { on { rotation } doReturn Surface.ROTATION_90 } underTest.start() executor.runAllReady() // Now, trigger the 'shown' event for each dot viewController.showingListener?.onPrivacyDotShown(viewController.topLeft) viewController.showingListener?.onPrivacyDotShown(viewController.topRight) viewController.showingListener?.onPrivacyDotShown(viewController.bottomLeft) viewController.showingListener?.onPrivacyDotShown(viewController.bottomRight) executor.runAllReady() // Process all addView calls expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(BOTTOM or LEFT) expect.that(gravityForView(viewController.topRight!!)).isEqualTo(TOP or LEFT) expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(BOTTOM or RIGHT) Loading @@ -150,10 +210,16 @@ class PrivacyDotWindowControllerTest : SysuiTestCase() { @Test fun start_rotation180_viewsPositionIsShifted180degrees() { context.display = mock { on { rotation } doReturn Surface.ROTATION_180 } underTest.start() executor.runAllReady() // Now, trigger the 'shown' event for each dot viewController.showingListener?.onPrivacyDotShown(viewController.topLeft) viewController.showingListener?.onPrivacyDotShown(viewController.topRight) viewController.showingListener?.onPrivacyDotShown(viewController.bottomLeft) viewController.showingListener?.onPrivacyDotShown(viewController.bottomRight) executor.runAllReady() // Process all addView calls expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(BOTTOM or RIGHT) expect.that(gravityForView(viewController.topRight!!)).isEqualTo(BOTTOM or LEFT) expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(TOP or RIGHT) Loading @@ -163,10 +229,16 @@ class PrivacyDotWindowControllerTest : SysuiTestCase() { @Test fun start_rotation270_viewsPositionIsShifted270degrees() { context.display = mock { on { rotation } doReturn Surface.ROTATION_270 } underTest.start() executor.runAllReady() // Now, trigger the 'shown' event for each dot viewController.showingListener?.onPrivacyDotShown(viewController.topLeft) viewController.showingListener?.onPrivacyDotShown(viewController.topRight) viewController.showingListener?.onPrivacyDotShown(viewController.bottomLeft) viewController.showingListener?.onPrivacyDotShown(viewController.bottomRight) executor.runAllReady() // Process all addView calls expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(TOP or RIGHT) expect.that(gravityForView(viewController.topRight!!)).isEqualTo(BOTTOM or RIGHT) expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(TOP or LEFT) Loading @@ -174,19 +246,34 @@ class PrivacyDotWindowControllerTest : SysuiTestCase() { } @Test fun onStop_removeAllWindows() { fun onStop_removesAllCurrentlyAddedWindows() { underTest.start() executor.runAllReady() // Show all dots so their windows are added viewController.showingListener?.onPrivacyDotShown(viewController.topLeft) viewController.showingListener?.onPrivacyDotShown(viewController.topRight) viewController.showingListener?.onPrivacyDotShown(viewController.bottomLeft) viewController.showingListener?.onPrivacyDotShown(viewController.bottomRight) executor.runAllReady() assertThat(windowManager.addedViews).hasSize(4) // Now call stop underTest.stop() executor.runAllReady() assertThat(windowManager.addedViews).isEmpty() } private fun paramsForView(view: View): WindowManager.LayoutParams { // Helper functions: Note that paramsForView needs to find the *root* view (FrameLayout) // that was added to the window manager, not the inner dotView. private fun paramsForView(dotView: View): WindowManager.LayoutParams { // We're looking for the FrameLayout that contains the dotView. // The dotView has an ID, and the FrameLayout is its parent. return windowManager.addedViews.entries .first { it.key == view || it.key.findViewById<View>(view.id) != null } .first { (rootView, _) -> rootView is FrameLayout && rootView.findViewById<View>(dotView.id) == dotView } .value } Loading
packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotCorner.kt +14 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,10 @@ package com.android.systemui.statusbar.events import android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM import android.view.DisplayCutout.BOUNDS_POSITION_LEFT import android.view.DisplayCutout.BOUNDS_POSITION_RIGHT import android.view.DisplayCutout.BOUNDS_POSITION_TOP import android.view.Gravity import android.view.Surface Loading @@ -25,30 +29,40 @@ enum class PrivacyDotCorner( val gravity: Int, val innerGravity: Int, val title: String, val alignedBound1: Int, val alignedBound2: Int, ) { TopLeft( index = 0, gravity = Gravity.TOP or Gravity.LEFT, innerGravity = Gravity.CENTER_VERTICAL or Gravity.RIGHT, title = "TopLeft", alignedBound1 = BOUNDS_POSITION_TOP, alignedBound2 = BOUNDS_POSITION_LEFT, ), TopRight( index = 1, gravity = Gravity.TOP or Gravity.RIGHT, innerGravity = Gravity.CENTER_VERTICAL or Gravity.LEFT, title = "TopRight", alignedBound1 = BOUNDS_POSITION_TOP, alignedBound2 = BOUNDS_POSITION_RIGHT, ), BottomRight( index = 2, gravity = Gravity.BOTTOM or Gravity.RIGHT, innerGravity = Gravity.CENTER_VERTICAL or Gravity.RIGHT, title = "BottomRight", alignedBound1 = BOUNDS_POSITION_BOTTOM, alignedBound2 = BOUNDS_POSITION_RIGHT, ), BottomLeft( index = 3, gravity = Gravity.BOTTOM or Gravity.LEFT, innerGravity = Gravity.CENTER_VERTICAL or Gravity.LEFT, title = "BottomLeft", alignedBound1 = BOUNDS_POSITION_BOTTOM, alignedBound2 = BOUNDS_POSITION_LEFT, ), } Loading
packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotWindowController.kt +57 −26 Original line number Diff line number Diff line Loading @@ -18,10 +18,6 @@ package com.android.systemui.statusbar.events import android.util.Log import android.view.Display import android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM import android.view.DisplayCutout.BOUNDS_POSITION_LEFT import android.view.DisplayCutout.BOUNDS_POSITION_RIGHT import android.view.DisplayCutout.BOUNDS_POSITION_TOP import android.view.LayoutInflater import android.view.View import android.view.WindowManager Loading Loading @@ -59,50 +55,79 @@ constructor( @ScreenDecorationsThread private val uiExecutor: Executor, private val dotFactory: PrivacyDotDecorProviderFactory, ) { private val dotViews: MutableSet<View> = mutableSetOf() private val dotWindowViewsByCorner = mutableMapOf<PrivacyDotCorner, View>() private var displayRotationOnStartup = 0 fun start() { uiExecutor.execute { startOnUiThread() } } private fun startOnUiThread() { displayRotationOnStartup = inflater.context.display.rotation val providers = dotFactory.providers val topLeft = providers.inflate(BOUNDS_POSITION_TOP, BOUNDS_POSITION_LEFT) val topRight = providers.inflate(BOUNDS_POSITION_TOP, BOUNDS_POSITION_RIGHT) val bottomLeft = providers.inflate(BOUNDS_POSITION_BOTTOM, BOUNDS_POSITION_LEFT) val bottomRight = providers.inflate(BOUNDS_POSITION_BOTTOM, BOUNDS_POSITION_RIGHT) val topLeftContainer = providers.inflate(TopLeft) val topRightContainer = providers.inflate(TopRight) val bottomLeftContainer = providers.inflate(BottomLeft) val bottomRightContainer = providers.inflate(BottomRight) listOfNotNull( topLeft.addToWindow(TopLeft), topRight.addToWindow(TopRight), bottomLeft.addToWindow(BottomLeft), bottomRight.addToWindow(BottomRight), val dotViewContainersByView = mapOf( topLeftContainer.dotView to topLeftContainer, topRightContainer.dotView to topRightContainer, bottomLeftContainer.dotView to bottomLeftContainer, bottomRightContainer.dotView to bottomRightContainer, ) .forEach { dotViews.add(it) } privacyDotViewController.initialize(topLeft, topRight, bottomLeft, bottomRight) privacyDotViewController.showingListener = object : PrivacyDotViewController.ShowingListener { override fun onPrivacyDotShown(v: View?) { val dotViewContainer = dotViewContainersByView[v] if (v == null || dotViewContainer == null) { return } v.addToWindow(dotViewContainer.corner) dotWindowViewsByCorner[dotViewContainer.corner] = dotViewContainer.windowView } private fun List<DecorProvider>.inflate(alignedBound1: Int, alignedBound2: Int): View { override fun onPrivacyDotHidden(v: View?) { val dotViewContainer = dotViewContainersByView[v] val windowView = dotWindowViewsByCorner.remove(dotViewContainer?.corner) if (windowView != null) { windowManager.removeView(windowView) } } } privacyDotViewController.initialize( topLeftContainer.dotView, topRightContainer.dotView, bottomLeftContainer.dotView, bottomRightContainer.dotView, ) } private fun List<DecorProvider>.inflate(corner: PrivacyDotCorner): DotViewContainer { val provider = first { it.alignedBounds.containsExactly(alignedBound1, alignedBound2) } first { it.alignedBounds.containsExactly(corner.alignedBound1, corner.alignedBound2) } as PrivacyDotCornerDecorProviderImpl return inflater.inflate(/* resource= */ provider.layoutId, /* root= */ null) val dotView = inflater.inflate(/* resource= */ provider.layoutId, /* root= */ null) // PrivacyDotViewController expects the dot view to have a FrameLayout parent. val windowView = FrameLayout(dotView.context) windowView.addView(dotView) return DotViewContainer(windowView, dotView, corner) } private fun View.addToWindow(corner: PrivacyDotCorner): View? { private fun View.addToWindow(corner: PrivacyDotCorner) { val excludeFromScreenshots = displayId == Display.DEFAULT_DISPLAY val params = ScreenDecorations.getWindowLayoutBaseParams(excludeFromScreenshots).apply { width = WRAP_CONTENT height = WRAP_CONTENT gravity = corner.rotatedCorner(context.display.rotation).gravity gravity = corner.rotatedCorner(displayRotationOnStartup).gravity title = "PrivacyDot${corner.title}$displayId" } // PrivacyDotViewController expects the dot view to have a FrameLayout parent. val rootView = FrameLayout(context) rootView.addView(this) try { // Wrapping this in a try/catch to avoid crashes when a display is instantly removed // after being added, and initialization hasn't finished yet. Loading @@ -114,13 +139,19 @@ constructor( e, ) } return rootView return } fun stop() { dotViews.forEach { windowManager.removeView(it) } dotWindowViewsByCorner.forEach { windowManager.removeView(it.value) } } private data class DotViewContainer( val windowView: View, val dotView: View, val corner: PrivacyDotCorner, ) @AssistedFactory fun interface Factory { fun create( Loading