Loading packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt +6 −32 Original line number Diff line number Diff line Loading @@ -16,32 +16,26 @@ package com.android.systemui.shade.domain.interactor import android.content.mockedContext import android.content.res.Configuration import android.content.res.mockResources import android.view.Display import android.view.mockWindowManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.scene.ui.view.mockShadeRootView import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository import com.android.systemui.testKosmos import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.advanceUntilIdle import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.inOrder import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.kotlin.any import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever @OptIn(ExperimentalCoroutinesApi::class) @RunWith(AndroidJUnit4::class) @SmallTest class ShadeDisplaysInteractorTest : SysuiTestCase() { Loading @@ -49,9 +43,7 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() { private val shadeRootview = kosmos.mockShadeRootView private val positionRepository = kosmos.fakeShadeDisplaysRepository private val shadeContext = kosmos.mockedContext private val testScope = kosmos.testScope private val shadeWm = kosmos.mockWindowManager private val shadeContext = kosmos.mockedWindowContext private val resources = kosmos.mockResources private val configuration = mock<Configuration>() private val display = mock<Display>() Loading @@ -66,8 +58,8 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() { whenever(resources.configuration).thenReturn(configuration) whenever(shadeContext.displayId).thenReturn(0) whenever(shadeContext.getSystemService(any())).thenReturn(shadeWm) whenever(shadeContext.resources).thenReturn(resources) whenever(shadeContext.display).thenReturn(display) } @Test Loading @@ -77,7 +69,7 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() { underTest.start() verifyNoMoreInteractions(shadeWm) verify(shadeContext, never()).reparentToDisplay(any()) } @Test Loading @@ -87,24 +79,6 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() { underTest.start() inOrder(shadeWm).apply { verify(shadeWm).removeView(eq(shadeRootview)) verify(shadeWm).addView(eq(shadeRootview), any()) } } @Test fun start_shadePositionChanges_removedThenAdded() { whenever(display.displayId).thenReturn(0) positionRepository.setDisplayId(0) underTest.start() positionRepository.setDisplayId(1) testScope.advanceUntilIdle() inOrder(shadeWm).apply { verify(shadeWm).removeView(eq(shadeRootview)) verify(shadeWm).addView(eq(shadeRootview), any()) } verify(shadeContext).reparentToDisplay(eq(1)) } } packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java +9 −0 Original line number Diff line number Diff line Loading @@ -165,6 +165,15 @@ public class NotificationShadeWindowView extends WindowRootView { return handled; } @Override public void onMovedToDisplay(int displayId, Configuration config) { super.onMovedToDisplay(displayId, config); ShadeWindowGoesAround.isUnexpectedlyInLegacyMode(); // When the window is moved we're only receiving a call to this method instead of the // onConfigurationChange itself. Let's just trigegr a normal config change. onConfigurationChanged(config); } @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); Loading packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt +17 −1 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.view.LayoutInflater import android.view.WindowManager import android.view.WindowManager.LayoutParams import android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE import android.window.WindowContext import com.android.systemui.CoreStartable import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.common.ui.ConfigurationStateImpl Loading Loading @@ -78,6 +79,19 @@ object ShadeDisplayAwareModule { } } @Provides @ShadeDisplayAware @SysUISingleton fun provideShadeDisplayAwareWindowContext(@ShadeDisplayAware context: Context): WindowContext { ShadeWindowGoesAround.isUnexpectedlyInLegacyMode() // We rely on the fact context is a WindowContext as the API to reparent windows is only // available there. return (context as? WindowContext) ?: error( "ShadeDisplayAware context must be a window context to allow window reparenting." ) } @Provides @ShadeDisplayAware @SysUISingleton Loading Loading @@ -203,7 +217,9 @@ object ShadeDisplayAwareModule { @Provides @IntoMap @ClassKey(ShadePrimaryDisplayCommand::class) fun provideShadePrimaryDisplayCommand(impl: Provider<ShadePrimaryDisplayCommand>): CoreStartable { fun provideShadePrimaryDisplayCommand( impl: Provider<ShadePrimaryDisplayCommand> ): CoreStartable { return if (ShadeWindowGoesAround.isEnabled) { impl.get() } else { Loading packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt +10 −27 Original line number Diff line number Diff line Loading @@ -16,10 +16,8 @@ package com.android.systemui.shade.domain.interactor import android.content.Context import android.util.Log import android.view.WindowManager import android.view.WindowManager.LayoutParams import android.window.WindowContext import androidx.annotation.UiThread import com.android.app.tracing.coroutines.launchTraced import com.android.app.tracing.traceSection Loading @@ -45,9 +43,7 @@ class ShadeDisplaysInteractor constructor( optionalShadeRootView: Optional<WindowRootView>, private val shadePositionRepository: ShadeDisplaysRepository, @ShadeDisplayAware private val shadeContext: Context, @ShadeDisplayAware private val shadeLayoutParams: LayoutParams, @ShadeDisplayAware private val wm: WindowManager, @ShadeDisplayAware private val shadeContext: WindowContext, @Background private val bgScope: CoroutineScope, @Main private val mainThreadContext: CoroutineContext, ) : CoreStartable { Loading @@ -72,7 +68,11 @@ constructor( /** Tries to move the shade. If anything wrong happens, fails gracefully without crashing. */ private suspend fun moveShadeWindowTo(destinationId: Int) { Log.d(TAG, "Trying to move shade window to display with id $destinationId") val currentDisplay = shadeRootView.display // Why using the shade context here instead of the view's Display? // The context's display is updated before the view one, so it is a better indicator of // which display the shade is supposed to be at. The View display is updated after the first // rendering with the new config. val currentDisplay = shadeContext.display if (currentDisplay == null) { Log.w(TAG, "Current shade display is null") return Loading @@ -83,7 +83,7 @@ constructor( return } try { withContext(mainThreadContext) { moveShadeWindow(toId = destinationId) } withContext(mainThreadContext) { reparentToDisplayId(id = destinationId) } } catch (e: IllegalStateException) { Log.e( TAG, Loading @@ -94,25 +94,8 @@ constructor( } @UiThread private fun moveShadeWindow(toId: Int) { traceSection({ "moveShadeWindow to $toId" }) { removeShadeWindow() updateContextDisplay(toId) addShadeWindow() } } @UiThread private fun removeShadeWindow(): Unit = traceSection("removeShadeWindow") { wm.removeView(shadeRootView) } @UiThread private fun addShadeWindow(): Unit = traceSection("addShadeWindow") { wm.addView(shadeRootView, shadeLayoutParams) } @UiThread private fun updateContextDisplay(newDisplayId: Int) { traceSection("updateContextDisplay") { shadeContext.updateDisplay(newDisplayId) } private fun reparentToDisplayId(id: Int) { traceSection({ "reparentToDisplayId(id=$id)" }) { shadeContext.reparentToDisplay(id) } } private companion object { Loading packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt +6 −7 Original line number Diff line number Diff line Loading @@ -17,25 +17,24 @@ package com.android.systemui.shade.domain.interactor import android.content.mockedContext import android.view.mockWindowManager import android.window.WindowContext import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.scene.ui.view.mockShadeRootView import com.android.systemui.shade.ShadeWindowLayoutParams import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository import java.util.Optional import org.mockito.kotlin.mock val Kosmos.shadeLayoutParams by Kosmos.Fixture { ShadeWindowLayoutParams.create(mockedContext) } val Kosmos.shadeLayoutParams by Kosmos.Fixture { ShadeWindowLayoutParams.create(mockedContext) } val Kosmos.mockedWindowContext by Kosmos.Fixture { mock<WindowContext>() } val Kosmos.shadeDisplaysInteractor by Kosmos.Fixture { ShadeDisplaysInteractor( Optional.of(mockShadeRootView), fakeShadeDisplaysRepository, mockedContext, shadeLayoutParams, mockWindowManager, mockedWindowContext, testScope.backgroundScope, testScope.backgroundScope.coroutineContext, ) Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt +6 −32 Original line number Diff line number Diff line Loading @@ -16,32 +16,26 @@ package com.android.systemui.shade.domain.interactor import android.content.mockedContext import android.content.res.Configuration import android.content.res.mockResources import android.view.Display import android.view.mockWindowManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.scene.ui.view.mockShadeRootView import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository import com.android.systemui.testKosmos import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.advanceUntilIdle import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.inOrder import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.kotlin.any import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever @OptIn(ExperimentalCoroutinesApi::class) @RunWith(AndroidJUnit4::class) @SmallTest class ShadeDisplaysInteractorTest : SysuiTestCase() { Loading @@ -49,9 +43,7 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() { private val shadeRootview = kosmos.mockShadeRootView private val positionRepository = kosmos.fakeShadeDisplaysRepository private val shadeContext = kosmos.mockedContext private val testScope = kosmos.testScope private val shadeWm = kosmos.mockWindowManager private val shadeContext = kosmos.mockedWindowContext private val resources = kosmos.mockResources private val configuration = mock<Configuration>() private val display = mock<Display>() Loading @@ -66,8 +58,8 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() { whenever(resources.configuration).thenReturn(configuration) whenever(shadeContext.displayId).thenReturn(0) whenever(shadeContext.getSystemService(any())).thenReturn(shadeWm) whenever(shadeContext.resources).thenReturn(resources) whenever(shadeContext.display).thenReturn(display) } @Test Loading @@ -77,7 +69,7 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() { underTest.start() verifyNoMoreInteractions(shadeWm) verify(shadeContext, never()).reparentToDisplay(any()) } @Test Loading @@ -87,24 +79,6 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() { underTest.start() inOrder(shadeWm).apply { verify(shadeWm).removeView(eq(shadeRootview)) verify(shadeWm).addView(eq(shadeRootview), any()) } } @Test fun start_shadePositionChanges_removedThenAdded() { whenever(display.displayId).thenReturn(0) positionRepository.setDisplayId(0) underTest.start() positionRepository.setDisplayId(1) testScope.advanceUntilIdle() inOrder(shadeWm).apply { verify(shadeWm).removeView(eq(shadeRootview)) verify(shadeWm).addView(eq(shadeRootview), any()) } verify(shadeContext).reparentToDisplay(eq(1)) } }
packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java +9 −0 Original line number Diff line number Diff line Loading @@ -165,6 +165,15 @@ public class NotificationShadeWindowView extends WindowRootView { return handled; } @Override public void onMovedToDisplay(int displayId, Configuration config) { super.onMovedToDisplay(displayId, config); ShadeWindowGoesAround.isUnexpectedlyInLegacyMode(); // When the window is moved we're only receiving a call to this method instead of the // onConfigurationChange itself. Let's just trigegr a normal config change. onConfigurationChanged(config); } @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); Loading
packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt +17 −1 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.view.LayoutInflater import android.view.WindowManager import android.view.WindowManager.LayoutParams import android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE import android.window.WindowContext import com.android.systemui.CoreStartable import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.common.ui.ConfigurationStateImpl Loading Loading @@ -78,6 +79,19 @@ object ShadeDisplayAwareModule { } } @Provides @ShadeDisplayAware @SysUISingleton fun provideShadeDisplayAwareWindowContext(@ShadeDisplayAware context: Context): WindowContext { ShadeWindowGoesAround.isUnexpectedlyInLegacyMode() // We rely on the fact context is a WindowContext as the API to reparent windows is only // available there. return (context as? WindowContext) ?: error( "ShadeDisplayAware context must be a window context to allow window reparenting." ) } @Provides @ShadeDisplayAware @SysUISingleton Loading Loading @@ -203,7 +217,9 @@ object ShadeDisplayAwareModule { @Provides @IntoMap @ClassKey(ShadePrimaryDisplayCommand::class) fun provideShadePrimaryDisplayCommand(impl: Provider<ShadePrimaryDisplayCommand>): CoreStartable { fun provideShadePrimaryDisplayCommand( impl: Provider<ShadePrimaryDisplayCommand> ): CoreStartable { return if (ShadeWindowGoesAround.isEnabled) { impl.get() } else { Loading
packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt +10 −27 Original line number Diff line number Diff line Loading @@ -16,10 +16,8 @@ package com.android.systemui.shade.domain.interactor import android.content.Context import android.util.Log import android.view.WindowManager import android.view.WindowManager.LayoutParams import android.window.WindowContext import androidx.annotation.UiThread import com.android.app.tracing.coroutines.launchTraced import com.android.app.tracing.traceSection Loading @@ -45,9 +43,7 @@ class ShadeDisplaysInteractor constructor( optionalShadeRootView: Optional<WindowRootView>, private val shadePositionRepository: ShadeDisplaysRepository, @ShadeDisplayAware private val shadeContext: Context, @ShadeDisplayAware private val shadeLayoutParams: LayoutParams, @ShadeDisplayAware private val wm: WindowManager, @ShadeDisplayAware private val shadeContext: WindowContext, @Background private val bgScope: CoroutineScope, @Main private val mainThreadContext: CoroutineContext, ) : CoreStartable { Loading @@ -72,7 +68,11 @@ constructor( /** Tries to move the shade. If anything wrong happens, fails gracefully without crashing. */ private suspend fun moveShadeWindowTo(destinationId: Int) { Log.d(TAG, "Trying to move shade window to display with id $destinationId") val currentDisplay = shadeRootView.display // Why using the shade context here instead of the view's Display? // The context's display is updated before the view one, so it is a better indicator of // which display the shade is supposed to be at. The View display is updated after the first // rendering with the new config. val currentDisplay = shadeContext.display if (currentDisplay == null) { Log.w(TAG, "Current shade display is null") return Loading @@ -83,7 +83,7 @@ constructor( return } try { withContext(mainThreadContext) { moveShadeWindow(toId = destinationId) } withContext(mainThreadContext) { reparentToDisplayId(id = destinationId) } } catch (e: IllegalStateException) { Log.e( TAG, Loading @@ -94,25 +94,8 @@ constructor( } @UiThread private fun moveShadeWindow(toId: Int) { traceSection({ "moveShadeWindow to $toId" }) { removeShadeWindow() updateContextDisplay(toId) addShadeWindow() } } @UiThread private fun removeShadeWindow(): Unit = traceSection("removeShadeWindow") { wm.removeView(shadeRootView) } @UiThread private fun addShadeWindow(): Unit = traceSection("addShadeWindow") { wm.addView(shadeRootView, shadeLayoutParams) } @UiThread private fun updateContextDisplay(newDisplayId: Int) { traceSection("updateContextDisplay") { shadeContext.updateDisplay(newDisplayId) } private fun reparentToDisplayId(id: Int) { traceSection({ "reparentToDisplayId(id=$id)" }) { shadeContext.reparentToDisplay(id) } } private companion object { Loading
packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt +6 −7 Original line number Diff line number Diff line Loading @@ -17,25 +17,24 @@ package com.android.systemui.shade.domain.interactor import android.content.mockedContext import android.view.mockWindowManager import android.window.WindowContext import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.scene.ui.view.mockShadeRootView import com.android.systemui.shade.ShadeWindowLayoutParams import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository import java.util.Optional import org.mockito.kotlin.mock val Kosmos.shadeLayoutParams by Kosmos.Fixture { ShadeWindowLayoutParams.create(mockedContext) } val Kosmos.shadeLayoutParams by Kosmos.Fixture { ShadeWindowLayoutParams.create(mockedContext) } val Kosmos.mockedWindowContext by Kosmos.Fixture { mock<WindowContext>() } val Kosmos.shadeDisplaysInteractor by Kosmos.Fixture { ShadeDisplaysInteractor( Optional.of(mockShadeRootView), fakeShadeDisplaysRepository, mockedContext, shadeLayoutParams, mockWindowManager, mockedWindowContext, testScope.backgroundScope, testScope.backgroundScope.coroutineContext, ) Loading