diff options
author | 2024-02-12 21:12:25 +0000 | |
---|---|---|
committer | 2024-02-29 15:07:51 +0000 | |
commit | 39013e2a8a53f84e50dba23404f7985855c643a8 (patch) | |
tree | 0c014a1a37779396149df53e46b0503374ec127d | |
parent | a81775741a0491d72ccc0f2d0e6482be34ca6316 (diff) |
Migrate rotation lock tile
Flag: aconfig com.android.systemui.qs_new_tiles DEVELOPMENT
Fixes: 301056367
Test: atest SystemUiRoboTests
Test: atest RotationLockTileDataInteractorTest RotationLockTileUserActionInteractorTest
RotationLockTileMapperTest CameraAutoRotateRepositoryImplTest CameraSensorPrivacyRepositoryImplTest FakeCameraAutoRotateRepositoryTest FakeCameraSensorPrivacyRepositoryTest
Change-Id: I32b12a7163bd07fdf42e11c4e53a96b02f431fdc
26 files changed, 1691 insertions, 2 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepositoryImplTest.kt new file mode 100644 index 000000000000..9cfa57257053 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepositoryImplTest.kt @@ -0,0 +1,125 @@ +/* + * 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.systemui.camera.data.repository + +import android.os.UserHandle +import android.provider.Settings +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectValues +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.util.settings.fakeSettings +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +@android.platform.test.annotations.EnabledOnRavenwood +class CameraAutoRotateRepositoryImplTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val testScope = kosmos.testScope + private val settings = kosmos.fakeSettings + private val testUser = UserHandle.of(1) + + private val underTest = + CameraAutoRotateRepositoryImpl(settings, testScope.testScheduler, testScope.backgroundScope) + + /** 3 changes => 3 change signals + 1 signal emitted at start => 4 signals */ + @Test + fun isCameraAutoRotateSettingEnabled_3times() = + testScope.runTest { + settings.putIntForUser(SETTING_NAME, DISABLE, testUser.identifier) + val isCameraAutoRotateSettingEnabled by + collectValues(underTest.isCameraAutoRotateSettingEnabled(testUser)) + runCurrent() + assertThat(isCameraAutoRotateSettingEnabled.last()).isFalse() + + settings.putIntForUser(SETTING_NAME, ENABLE, testUser.identifier) + runCurrent() + assertThat(isCameraAutoRotateSettingEnabled.last()).isTrue() + + settings.putIntForUser(SETTING_NAME, DISABLE, testUser.identifier) + runCurrent() + assertThat(isCameraAutoRotateSettingEnabled.last()).isFalse() + + settings.putIntForUser(SETTING_NAME, ENABLE, testUser.identifier) + runCurrent() + assertThat(isCameraAutoRotateSettingEnabled.last()).isTrue() + + assertThat(isCameraAutoRotateSettingEnabled).hasSize(4) + } + + @Test + fun isCameraAutoRotateSettingEnabled_emitsOnStart() = + testScope.runTest { + val isCameraAutoRotateSettingEnabled: List<Boolean> by + collectValues(underTest.isCameraAutoRotateSettingEnabled(testUser)) + + runCurrent() + + assertThat(isCameraAutoRotateSettingEnabled).hasSize(1) + } + + /** 0 for 0 changes + 1 signal emitted on start => 1 signal */ + @Test + fun isCameraAutoRotateSettingEnabled_0Times() = + testScope.runTest { + settings.putIntForUser(SETTING_NAME, DISABLE, testUser.identifier) + val isCameraAutoRotateSettingEnabled: List<Boolean> by + collectValues(underTest.isCameraAutoRotateSettingEnabled(testUser)) + runCurrent() + + settings.putIntForUser(SETTING_NAME, DISABLE, testUser.identifier) + runCurrent() + + assertThat(isCameraAutoRotateSettingEnabled).hasSize(1) + assertThat(isCameraAutoRotateSettingEnabled[0]).isFalse() + } + + /** Maintain that flows are cached by user */ + @Test + fun sameUserCallsIsCameraAutoRotateSettingEnabledTwice_getsSameFlow() = + testScope.runTest { + val flow1 = underTest.isCameraAutoRotateSettingEnabled(testUser) + val flow2 = underTest.isCameraAutoRotateSettingEnabled(testUser) + + assertThat(flow1).isEqualTo(flow2) + } + + @Test + fun differentUsersCallIsCameraAutoRotateSettingEnabled_getDifferentFlow() = + testScope.runTest { + val user2 = UserHandle.of(2) + val flow1 = underTest.isCameraAutoRotateSettingEnabled(testUser) + val flow2 = underTest.isCameraAutoRotateSettingEnabled(user2) + + assertThat(flow1).isNotEqualTo(flow2) + } + + private companion object { + private const val SETTING_NAME = Settings.Secure.CAMERA_AUTOROTATE + private const val DISABLE = 0 + private const val ENABLE = 1 + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepositoryImplTest.kt new file mode 100644 index 000000000000..29de58e2b28f --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepositoryImplTest.kt @@ -0,0 +1,104 @@ +/* + * 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.systemui.camera.data.repository + +import android.hardware.SensorPrivacyManager +import android.os.UserHandle +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers +import org.mockito.Mockito + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +@android.platform.test.annotations.EnabledOnRavenwood +class CameraSensorPrivacyRepositoryImplTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val testScope = kosmos.testScope + private val testUser = UserHandle.of(1) + private val privacyManager = mock<SensorPrivacyManager>() + private val underTest = + CameraSensorPrivacyRepositoryImpl( + testScope.testScheduler, + testScope.backgroundScope, + privacyManager + ) + + @Test + fun isEnabled_2TimesForSameUserReturnsCachedFlow() = + testScope.runTest { + val flow1 = underTest.isEnabled(testUser) + val flow2 = underTest.isEnabled(testUser) + runCurrent() + + assertThat(flow1).isEqualTo(flow2) + } + + @Test + fun isEnabled_2TimesForDifferentUsersReturnsTwoDifferentFlows() = + testScope.runTest { + val user2 = UserHandle.of(2) + + val flow1 = underTest.isEnabled(testUser) + val flow2 = underTest.isEnabled(user2) + runCurrent() + + assertThat(flow1).isNotEqualTo(flow2) + } + + @Test + fun isEnabled_dataMatchesSensorPrivacyManager() = + testScope.runTest { + val isEnabled = collectLastValue(underTest.isEnabled(testUser)) + + val captor = + ArgumentCaptor.forClass( + SensorPrivacyManager.OnSensorPrivacyChangedListener::class.java + ) + runCurrent() + assertThat(isEnabled()).isEqualTo(false) + + Mockito.verify(privacyManager) + .addSensorPrivacyListener( + ArgumentMatchers.eq(SensorPrivacyManager.Sensors.CAMERA), + ArgumentMatchers.eq(testUser.identifier), + captor.capture() + ) + val sensorPrivacyCallback = captor.value!! + + sensorPrivacyCallback.onSensorPrivacyChanged(SensorPrivacyManager.Sensors.CAMERA, true) + runCurrent() + assertThat(isEnabled()).isEqualTo(true) + + sensorPrivacyCallback.onSensorPrivacyChanged(SensorPrivacyManager.Sensors.CAMERA, false) + runCurrent() + assertThat(isEnabled()).isEqualTo(false) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepositoryTest.kt new file mode 100644 index 000000000000..f75e036d78d4 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepositoryTest.kt @@ -0,0 +1,78 @@ +/* + * 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.systemui.camera.data.repository + +import android.os.UserHandle +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectValues +import com.android.systemui.kosmos.Kosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +@android.platform.test.annotations.EnabledOnRavenwood +class FakeCameraAutoRotateRepositoryTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val underTest = kosmos.fakeCameraAutoRotateRepository + private val testUser = UserHandle.of(1) + + @Test + fun isCameraAutoRotateSettingEnabled_emitsFalseOnStart() = runTest { + val isCameraAutoRotateSettingEnabled by + collectValues(underTest.isCameraAutoRotateSettingEnabled(testUser)) + + assertThat(isCameraAutoRotateSettingEnabled).hasSize(1) + assertThat(isCameraAutoRotateSettingEnabled.first()).isFalse() + } + + /** + * The value explicitly set in this test is not distinct, therefore only 1 value is collected. + */ + @Test + fun isCameraAutoRotateSettingEnabled_emitsDistinctValueOnly() = runTest { + val isCameraAutoRotateSettingEnabled by + collectValues(underTest.isCameraAutoRotateSettingEnabled(testUser)) + underTest.setEnabled(testUser, false) + runCurrent() + + assertThat(isCameraAutoRotateSettingEnabled).hasSize(1) + assertThat(isCameraAutoRotateSettingEnabled.first()).isFalse() + } + + @Test + fun isCameraAutoRotateSettingEnabled_canSetValue3Times() = runTest { + val isCameraAutoRotateSettingEnabled by + collectValues(underTest.isCameraAutoRotateSettingEnabled(testUser)) + runCurrent() + underTest.setEnabled(testUser, true) + runCurrent() + underTest.setEnabled(testUser, false) + runCurrent() + underTest.setEnabled(testUser, true) + runCurrent() + assertThat(isCameraAutoRotateSettingEnabled).hasSize(4) + assertThat(isCameraAutoRotateSettingEnabled.last()).isTrue() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepositoryTest.kt new file mode 100644 index 000000000000..7fa1be3d20ff --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepositoryTest.kt @@ -0,0 +1,75 @@ +/* + * 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.systemui.camera.data.repository + +import android.os.UserHandle +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectValues +import com.android.systemui.kosmos.Kosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +@android.platform.test.annotations.EnabledOnRavenwood +class FakeCameraSensorPrivacyRepositoryTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val underTest = kosmos.fakeCameraSensorPrivacyRepository + private val testUser = UserHandle.of(1) + + @Test + fun isCameraSensorPrivacyEnabled_emitsFalseOnStart() = runTest { + val isCameraSensorPrivacySettingEnabled by collectValues(underTest.isEnabled(testUser)) + + assertThat(isCameraSensorPrivacySettingEnabled).hasSize(1) + assertThat(isCameraSensorPrivacySettingEnabled.first()).isFalse() + } + + /** + * The value explicitly set in this test is not distinct, therefore only 1 value is collected. + */ + @Test + fun isCameraSensorPrivacyEnabled_emitsDistinctValueOnly() = runTest { + val isCameraSensorPrivacySettingEnabled by collectValues(underTest.isEnabled(testUser)) + underTest.setEnabled(testUser, false) + runCurrent() + + assertThat(isCameraSensorPrivacySettingEnabled).hasSize(1) + assertThat(isCameraSensorPrivacySettingEnabled.first()).isFalse() + } + + @Test + fun isCameraSensorPrivacyEnabled_canSetValue3Times() = runTest { + val isCameraSensorPrivacySettingEnabled by collectValues(underTest.isEnabled(testUser)) + runCurrent() + underTest.setEnabled(testUser, true) + runCurrent() + underTest.setEnabled(testUser, false) + runCurrent() + underTest.setEnabled(testUser, true) + runCurrent() + assertThat(isCameraSensorPrivacySettingEnabled).hasSize(4) + assertThat(isCameraSensorPrivacySettingEnabled.last()).isTrue() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractorTest.kt new file mode 100644 index 000000000000..266875e9e12a --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractorTest.kt @@ -0,0 +1,227 @@ +/* + * 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.systemui.qs.tiles.impl.rotation.domain.interactor + +import android.Manifest +import android.content.packageManager +import android.content.pm.PackageManager +import android.os.UserHandle +import android.platform.test.annotations.EnabledOnRavenwood +import android.testing.LeakCheck +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.camera.data.repository.fakeCameraAutoRotateRepository +import com.android.systemui.camera.data.repository.fakeCameraSensorPrivacyRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.whenever +import com.android.systemui.utils.leaks.FakeBatteryController +import com.android.systemui.utils.leaks.FakeRotationLockController +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.toCollection +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@EnabledOnRavenwood +@RunWith(AndroidJUnit4::class) +class RotationLockTileDataInteractorTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val testScope = kosmos.testScope + private val batteryController = FakeBatteryController(LeakCheck()) + private val rotationController = FakeRotationLockController(LeakCheck()) + private val fakeCameraAutoRotateRepository = kosmos.fakeCameraAutoRotateRepository + private val fakeCameraSensorPrivacyRepository = kosmos.fakeCameraSensorPrivacyRepository + private val packageManager = kosmos.packageManager + + private val testUser = UserHandle.of(1) + private lateinit var underTest: RotationLockTileDataInteractor + + @Before + fun setup() { + whenever(packageManager.rotationResolverPackageName).thenReturn(TEST_PACKAGE_NAME) + whenever( + packageManager.checkPermission( + eq(Manifest.permission.CAMERA), + eq(TEST_PACKAGE_NAME) + ) + ) + .thenReturn(PackageManager.PERMISSION_GRANTED) + + underTest = + RotationLockTileDataInteractor( + rotationController, + batteryController, + fakeCameraAutoRotateRepository, + fakeCameraSensorPrivacyRepository, + packageManager, + context.orCreateTestableResources + .apply { + addOverride(com.android.internal.R.bool.config_allowRotationResolver, true) + } + .resources + ) + } + + @Test + fun availability_isTrue() = + testScope.runTest { + val availability = underTest.availability(testUser).toCollection(mutableListOf()) + + assertThat(availability).hasSize(1) + assertThat(availability.last()).isTrue() + } + + @Test + fun tileData_isRotationLockedMatchesRotationController() = + testScope.runTest { + val data by + collectLastValue( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + + runCurrent() + assertThat(data!!.isRotationLocked).isEqualTo(false) + + rotationController.setRotationLocked(true, CALLER) + runCurrent() + assertThat(data!!.isRotationLocked).isEqualTo(true) + + rotationController.setRotationLocked(false, CALLER) + runCurrent() + assertThat(data!!.isRotationLocked).isEqualTo(false) + } + + @Test + fun tileData_cameraRotationMatchesBatteryController() = + testScope.runTest { + setupControllersToEnableCameraRotation() + val data by + collectLastValue( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + + runCurrent() + assertThat(data!!.isCameraRotationEnabled).isTrue() + + batteryController.setPowerSaveMode(true) + runCurrent() + assertThat(data!!.isCameraRotationEnabled).isFalse() + + batteryController.setPowerSaveMode(false) + runCurrent() + assertThat(data!!.isCameraRotationEnabled).isTrue() + } + + @Test + fun tileData_cameraRotationMatchesSensorPrivacyRepository() = + testScope.runTest { + setupControllersToEnableCameraRotation() + val lastValue by + this.collectLastValue( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + runCurrent() + assertThat(lastValue!!.isCameraRotationEnabled).isTrue() + + fakeCameraSensorPrivacyRepository.setEnabled(testUser, true) + runCurrent() + assertThat(lastValue!!.isCameraRotationEnabled).isFalse() + + fakeCameraSensorPrivacyRepository.setEnabled(testUser, false) + runCurrent() + assertThat(lastValue!!.isCameraRotationEnabled).isTrue() + } + + @Test + fun tileData_cameraRotationMatchesAutoRotateRepository() = + testScope.runTest { + setupControllersToEnableCameraRotation() + + val lastValue by + collectLastValue( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + runCurrent() + assertThat(lastValue!!.isCameraRotationEnabled).isTrue() + + fakeCameraAutoRotateRepository.setEnabled(testUser, false) + runCurrent() + assertThat(lastValue!!.isCameraRotationEnabled).isFalse() + + fakeCameraAutoRotateRepository.setEnabled(testUser, true) + runCurrent() + assertThat(lastValue!!.isCameraRotationEnabled).isTrue() + } + + @Test + fun tileData_matchesPackageManagerPermissionDenied() = + testScope.runTest { + whenever( + packageManager.checkPermission( + eq(Manifest.permission.CAMERA), + eq(TEST_PACKAGE_NAME) + ) + ) + .thenReturn(PackageManager.PERMISSION_DENIED) + + val lastValue by + collectLastValue( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + runCurrent() + assertThat(lastValue!!.isCameraRotationEnabled).isEqualTo(false) + } + + @Test + fun tileData_setConfigAllowRotationResolverToFalse_cameraRotationIsNotEnabled() = + testScope.runTest { + underTest.apply { + overrideResource(com.android.internal.R.bool.config_allowRotationResolver, false) + } + setupControllersToEnableCameraRotation() + val lastValue by + collectLastValue( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + runCurrent() + + assertThat(lastValue!!.isCameraRotationEnabled).isEqualTo(false) + } + + private fun setupControllersToEnableCameraRotation() { + rotationController.setRotationLocked(true, CALLER) + batteryController.setPowerSaveMode(false) + fakeCameraSensorPrivacyRepository.setEnabled(testUser, false) + fakeCameraAutoRotateRepository.setEnabled(testUser, true) + } + + private companion object { + private const val CALLER = "RotationLockTileDataInteractorTest" + private const val TEST_PACKAGE_NAME = "com.test" + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileUserActionInteractorTest.kt new file mode 100644 index 000000000000..1653ce369ea1 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileUserActionInteractorTest.kt @@ -0,0 +1,96 @@ +/* + * 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.systemui.qs.tiles.impl.rotation.domain.interactor + +import android.platform.test.annotations.EnabledOnRavenwood +import android.provider.Settings +import android.testing.LeakCheck +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject +import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx +import com.android.systemui.qs.tiles.impl.rotation.domain.model.RotationLockTileModel +import com.android.systemui.utils.leaks.FakeRotationLockController +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@EnabledOnRavenwood +@RunWith(AndroidJUnit4::class) +class RotationLockTileUserActionInteractorTest : SysuiTestCase() { + private val controller = FakeRotationLockController(LeakCheck()) + private val inputHandler = FakeQSTileIntentUserInputHandler() + + private val underTest = + RotationLockTileUserActionInteractor( + controller, + inputHandler, + ) + + @Test + fun handleClickWhenEnabled() = runTest { + val wasEnabled = true + controller.setRotationLocked(wasEnabled, null) + + underTest.handleInput(QSTileInputTestKtx.click(RotationLockTileModel(wasEnabled, false))) + + assertThat(controller.isRotationLocked).isEqualTo(!wasEnabled) + } + + @Test + fun handleClickWhenDisabled() = runTest { + val wasEnabled = false + controller.setRotationLocked(wasEnabled, null) + + underTest.handleInput(QSTileInputTestKtx.click(RotationLockTileModel(wasEnabled, false))) + + assertThat(controller.isRotationLocked).isEqualTo(!wasEnabled) + } + + @Test + fun handleLongClickWhenDisabled() = runTest { + val enabled = false + + underTest.handleInput( + QSTileInputTestKtx.longClick( + RotationLockTileModel( + enabled, + false, + ) + ) + ) + + QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput { + assertThat(it.intent.action).isEqualTo(Settings.ACTION_AUTO_ROTATE_SETTINGS) + } + } + + @Test + fun handleLongClickWhenEnabled() = runTest { + val enabled = true + + underTest.handleInput(QSTileInputTestKtx.longClick(RotationLockTileModel(enabled, false))) + + QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput { + assertThat(it.intent.action).isEqualTo(Settings.ACTION_AUTO_ROTATE_SETTINGS) + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt new file mode 100644 index 000000000000..60c69f427ef3 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt @@ -0,0 +1,190 @@ +/* + * 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.systemui.qs.tiles.impl.rotation.ui.mapper + +import android.graphics.drawable.TestStubDrawable +import android.widget.Switch +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject +import com.android.systemui.qs.tiles.impl.rotation.domain.model.RotationLockTileModel +import com.android.systemui.qs.tiles.impl.rotation.qsRotationLockTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import com.android.systemui.statusbar.policy.DevicePostureController +import com.android.systemui.statusbar.policy.devicePostureController +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class RotationLockTileMapperTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val rotationLockTileConfig = kosmos.qsRotationLockTileConfig + private val devicePostureController = kosmos.devicePostureController + + private lateinit var mapper: RotationLockTileMapper + + @Before + fun setup() { + whenever(devicePostureController.devicePosture) + .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED) + + mapper = + RotationLockTileMapper( + context.orCreateTestableResources + .apply { + addOverride(R.drawable.qs_auto_rotate_icon_off, TestStubDrawable()) + addOverride(R.drawable.qs_auto_rotate_icon_on, TestStubDrawable()) + addOverride(com.android.internal.R.bool.config_allowRotationResolver, true) + addOverride( + com.android.internal.R.array.config_foldedDeviceStates, + intArrayOf() // empty array <=> device is not foldable + ) + } + .resources, + context.theme, + devicePostureController + ) + } + + @Test + fun rotationNotLocked_cameraRotationDisabled() { + val inputModel = RotationLockTileModel(false, false) + + val outputState = mapper.map(rotationLockTileConfig, inputModel) + + val expectedState = + createRotationLockTileState( + QSTileState.ActivationState.ACTIVE, + EMPTY_SECONDARY_STRING, + R.drawable.qs_auto_rotate_icon_on + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun rotationNotLocked_cameraRotationEnabled() { + val inputModel = RotationLockTileModel(false, true) + + val outputState = mapper.map(rotationLockTileConfig, inputModel) + + val expectedState = + createRotationLockTileState( + QSTileState.ActivationState.ACTIVE, + context.getString(R.string.rotation_lock_camera_rotation_on), + R.drawable.qs_auto_rotate_icon_on + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun rotationLocked_cameraRotationNotEnabled() { + val inputModel = RotationLockTileModel(true, false) + + val outputState = mapper.map(rotationLockTileConfig, inputModel) + + val expectedState = + createRotationLockTileState( + QSTileState.ActivationState.INACTIVE, + EMPTY_SECONDARY_STRING, + R.drawable.qs_auto_rotate_icon_off + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun deviceFoldableAndClosed_secondaryLabelIsFoldableSpecific() { + setDeviceFoldable() + val inputModel = RotationLockTileModel(false, false) + + val outputState = mapper.map(rotationLockTileConfig, inputModel) + + val expectedSecondaryLabelEnding = + context.getString(R.string.quick_settings_rotation_posture_folded) + assertThat( + context.resources.getIntArray( + com.android.internal.R.array.config_foldedDeviceStates + ) + ) + .isNotEmpty() + val actualSecondaryLabel = outputState.secondaryLabel + assertThat(actualSecondaryLabel).isNotNull() + assertThat(actualSecondaryLabel!!.endsWith(expectedSecondaryLabelEnding)).isTrue() + } + + @Test + fun deviceFoldableAndNotClosed_secondaryLabelIsFoldableSpecific() { + setDeviceFoldable() + whenever(devicePostureController.devicePosture) + .thenReturn(DevicePostureController.DEVICE_POSTURE_OPENED) + val inputModel = RotationLockTileModel(false, false) + + val outputState = mapper.map(rotationLockTileConfig, inputModel) + + val expectedSecondaryLabelEnding = + context.getString(R.string.quick_settings_rotation_posture_unfolded) + assertThat( + context.orCreateTestableResources.resources.getIntArray( + com.android.internal.R.array.config_foldedDeviceStates + ) + ) + .isNotEmpty() + val actualSecondaryLabel = outputState.secondaryLabel + assertThat(actualSecondaryLabel).isNotNull() + assertThat(actualSecondaryLabel!!.endsWith(expectedSecondaryLabelEnding)).isTrue() + } + + private fun setDeviceFoldable() { + mapper.apply { + overrideResource( + com.android.internal.R.array.config_foldedDeviceStates, + intArrayOf(1, 2, 3) + ) + } + } + + private fun createRotationLockTileState( + activationState: QSTileState.ActivationState, + secondaryLabel: String, + iconRes: Int + ): QSTileState { + val label = context.getString(R.string.quick_settings_rotation_unlocked_label) + return QSTileState( + { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, + label, + activationState, + secondaryLabel, + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK), + context.getString(R.string.accessibility_quick_settings_rotation), + secondaryLabel, + QSTileState.SideViewIcon.None, + QSTileState.EnabledState.ENABLED, + Switch::class.qualifiedName + ) + } + + private companion object { + private const val EMPTY_SECONDARY_STRING = "" + } +} diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index a7d93e70fda3..a40968b79209 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -3233,6 +3233,12 @@ <!-- Text for education page content description for unfolded animation. [CHAR_LIMIT=NONE] --> <string name="rear_display_accessibility_unfolded_animation">Foldable device being flipped around</string> + <!-- QuickSettings: Additional label for the auto-rotation quicksettings tile indicating that the setting corresponds to the folded posture for a foldable device [CHAR LIMIT=32] --> + <string name="quick_settings_rotation_posture_folded">folded</string> + <!-- QuickSettings: Additional label for the auto-rotation quicksettings tile indicating that the setting corresponds to the unfolded posture for a foldable device [CHAR LIMIT=32] --> + <string name="quick_settings_rotation_posture_unfolded">unfolded</string> + <!-- QuickSettings: template for rotation tile foldable secondary label [CHAR LIMIT=64] !--> + <string name="rotation_tile_with_posture_secondary_label_template">%1$s / %2$s</string> <!-- Title for notification of low stylus battery with percentage. "percentage" is the value of the battery capacity remaining [CHAR LIMIT=none]--> <string name="stylus_battery_low_percentage"><xliff:g id="percentage" example="16%">%s</xliff:g> battery remaining</string> diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraRotationModule.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraRotationModule.kt new file mode 100644 index 000000000000..f12382801887 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/camera/CameraRotationModule.kt @@ -0,0 +1,33 @@ +/* + * 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.systemui.camera + +import com.android.systemui.camera.data.repository.CameraAutoRotateRepository +import com.android.systemui.camera.data.repository.CameraAutoRotateRepositoryImpl +import com.android.systemui.camera.data.repository.CameraSensorPrivacyRepository +import com.android.systemui.camera.data.repository.CameraSensorPrivacyRepositoryImpl +import dagger.Binds +import dagger.Module + +/** Module for repositories that provide data regarding camera rotation state. */ +@Module +interface CameraRotationModule { + + @Binds + fun bindsPrivacyRepoImpl(impl: CameraSensorPrivacyRepositoryImpl): CameraSensorPrivacyRepository + @Binds fun bindsRotateRepoImpl(impl: CameraAutoRotateRepositoryImpl): CameraAutoRotateRepository +} diff --git a/packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepository.kt b/packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepository.kt new file mode 100644 index 000000000000..023fd285c6d8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepository.kt @@ -0,0 +1,70 @@ +/* + * 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.systemui.camera.data.repository + +import android.os.UserHandle +import android.provider.Settings +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.util.settings.SecureSettings +import com.android.systemui.util.settings.SettingsProxyExt.observerFlow +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.stateIn + +interface CameraAutoRotateRepository { + /** @return true if camera auto rotate setting is enabled */ + fun isCameraAutoRotateSettingEnabled(userHandle: UserHandle): StateFlow<Boolean> +} + +@SysUISingleton +class CameraAutoRotateRepositoryImpl +@Inject +constructor( + private val secureSettings: SecureSettings, + @Background private val bgCoroutineContext: CoroutineContext, + @Application private val applicationScope: CoroutineScope, +) : CameraAutoRotateRepository { + private val userMap = mutableMapOf<Int, StateFlow<Boolean>>() + + override fun isCameraAutoRotateSettingEnabled(userHandle: UserHandle): StateFlow<Boolean> { + return userMap.getOrPut(userHandle.identifier) { + secureSettings + .observerFlow(userHandle.identifier, Settings.Secure.CAMERA_AUTOROTATE) + .map { isAutoRotateSettingEnabled(userHandle.identifier) } + .onStart { emit(isAutoRotateSettingEnabled(userHandle.identifier)) } + .flowOn(bgCoroutineContext) + .stateIn(applicationScope, SharingStarted.WhileSubscribed(), false) + } + } + + private fun isAutoRotateSettingEnabled(userId: Int) = + secureSettings.getIntForUser(SETTING_NAME, DISABLED, userId) == ENABLED + + private companion object { + const val SETTING_NAME = Settings.Secure.CAMERA_AUTOROTATE + const val DISABLED = 0 + const val ENABLED = 1 + } +} diff --git a/packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepository.kt b/packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepository.kt new file mode 100644 index 000000000000..7816a1487c01 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepository.kt @@ -0,0 +1,76 @@ +/* + * 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.systemui.camera.data.repository + +import android.hardware.SensorPrivacyManager +import android.hardware.SensorPrivacyManager.Sensors.CAMERA +import android.os.UserHandle +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.stateIn + +interface CameraSensorPrivacyRepository { + /** Tracks whether camera sensor privacy is enabled. */ + fun isEnabled(userHandle: UserHandle): StateFlow<Boolean> +} + +@SysUISingleton +class CameraSensorPrivacyRepositoryImpl +@Inject +constructor( + @Background private val bgCoroutineContext: CoroutineContext, + @Application private val scope: CoroutineScope, + private val privacyManager: SensorPrivacyManager, +) : CameraSensorPrivacyRepository { + private val userMap = mutableMapOf<Int, StateFlow<Boolean>>() + + /** Whether camera sensor privacy is enabled */ + override fun isEnabled(userHandle: UserHandle): StateFlow<Boolean> = + userMap.getOrPut(userHandle.identifier) { + privacyManager + .isEnabled(userHandle) + .flowOn(bgCoroutineContext) + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + } +} + +fun SensorPrivacyManager.isEnabled(userHandle: UserHandle): Flow<Boolean> { + return conflatedCallbackFlow { + val privacyCallback = + SensorPrivacyManager.OnSensorPrivacyChangedListener { sensor, enabled -> + if (sensor == CAMERA) { + trySend(enabled) + } + } + addSensorPrivacyListener(CAMERA, userHandle.identifier, privacyCallback) + awaitClose { removeSensorPrivacyListener(privacyCallback) } + } + .onStart { emit(isSensorPrivacyEnabled(SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE, CAMERA)) } + .distinctUntilChanged() +} diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java index f7bc5cdc69c2..a4011fd7718c 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java @@ -43,6 +43,7 @@ import com.android.systemui.reardisplay.RearDisplayModule; import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsImplementation; import com.android.systemui.rotationlock.RotationLockModule; +import com.android.systemui.rotationlock.RotationLockNewModule; import com.android.systemui.scene.SceneContainerFrameworkModule; import com.android.systemui.screenshot.ReferenceScreenshotModule; import com.android.systemui.settings.MultiUserUtilsModule; @@ -110,6 +111,7 @@ import javax.inject.Named; RearDisplayModule.class, ReferenceScreenshotModule.class, RotationLockModule.class, + RotationLockNewModule.class, ScreenDecorationsModule.class, SystemActionsModule.class, ShadeModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractor.kt new file mode 100644 index 000000000000..736e1a5cb9b6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractor.kt @@ -0,0 +1,94 @@ +/* + * 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.systemui.qs.tiles.impl.rotation.domain.interactor + +import android.Manifest +import android.content.pm.PackageManager +import android.content.res.Resources +import android.os.UserHandle +import com.android.systemui.camera.data.repository.CameraAutoRotateRepository +import com.android.systemui.camera.data.repository.CameraSensorPrivacyRepository +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor +import com.android.systemui.qs.tiles.impl.rotation.domain.model.RotationLockTileModel +import com.android.systemui.statusbar.policy.BatteryController +import com.android.systemui.statusbar.policy.RotationLockController +import com.android.systemui.util.kotlin.isBatteryPowerSaveEnabled +import com.android.systemui.util.kotlin.isRotationLockEnabled +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flowOf + +/** Observes rotation lock state changes providing the [RotationLockTileModel]. */ +class RotationLockTileDataInteractor +@Inject +constructor( + private val rotationLockController: RotationLockController, + private val batteryController: BatteryController, + private val cameraAutoRotateRepository: CameraAutoRotateRepository, + private val cameraSensorPrivacyRepository: CameraSensorPrivacyRepository, + private val packageManager: PackageManager, + @Main private val resources: Resources, +) : QSTileDataInteractor<RotationLockTileModel> { + + override fun tileData( + user: UserHandle, + triggers: Flow<DataUpdateTrigger> + ): Flow<RotationLockTileModel> = + combine( + rotationLockController.isRotationLockEnabled(), + cameraSensorPrivacyRepository.isEnabled(user), + batteryController.isBatteryPowerSaveEnabled(), + cameraAutoRotateRepository.isCameraAutoRotateSettingEnabled(user) + ) { + isRotationLockEnabled, + isCamPrivacySensorEnabled, + isBatteryPowerSaveEnabled, + isCameraAutoRotateEnabled, + -> + RotationLockTileModel( + isRotationLockEnabled, + isCameraRotationEnabled( + isBatteryPowerSaveEnabled, + isCamPrivacySensorEnabled, + isCameraAutoRotateEnabled + ), + ) + } + + override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true) + + private fun hasSufficientPermission(): Boolean { + val rotationPackage: String = packageManager.rotationResolverPackageName + return rotationPackage != null && + packageManager.checkPermission(Manifest.permission.CAMERA, rotationPackage) == + PackageManager.PERMISSION_GRANTED + } + + private fun isCameraRotationEnabled( + isBatteryPowerSaverModeOn: Boolean, + isCameraSensorPrivacyEnabled: Boolean, + isCameraAutoRotateEnabled: Boolean + ): Boolean = + resources.getBoolean(com.android.internal.R.bool.config_allowRotationResolver) && + !isBatteryPowerSaverModeOn && + !isCameraSensorPrivacyEnabled && + hasSufficientPermission() && + isCameraAutoRotateEnabled +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileUserActionInteractor.kt new file mode 100644 index 000000000000..8530926e68e0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileUserActionInteractor.kt @@ -0,0 +1,56 @@ +/* + * 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.systemui.qs.tiles.impl.rotation.domain.interactor + +import android.content.Intent +import android.provider.Settings +import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.interactor.QSTileInput +import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.rotation.domain.model.RotationLockTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction +import com.android.systemui.statusbar.policy.RotationLockController +import javax.inject.Inject + +/** Handles rotation lock tile clicks. */ +class RotationLockTileUserActionInteractor +@Inject +constructor( + private val controller: RotationLockController, + private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler, +) : QSTileUserActionInteractor<RotationLockTileModel> { + + override suspend fun handleInput(input: QSTileInput<RotationLockTileModel>) { + with(input) { + when (action) { + is QSTileUserAction.Click -> { + controller.setRotationLocked(!data.isRotationLocked, CALLER) + } + is QSTileUserAction.LongClick -> { + qsTileIntentUserActionHandler.handle( + action.view, + Intent(Settings.ACTION_AUTO_ROTATE_SETTINGS) + ) + } + } + } + } + + companion object { + private const val CALLER = "QSTileUserActionInteractor#handleInput" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/model/RotationLockTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/model/RotationLockTileModel.kt new file mode 100644 index 000000000000..32e6cb8cb52c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/model/RotationLockTileModel.kt @@ -0,0 +1,23 @@ +/* + * 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.systemui.qs.tiles.impl.rotation.domain.model + +/** Model for rotation lock tile */ +class RotationLockTileModel( + val isRotationLocked: Boolean, + val isCameraRotationEnabled: Boolean, +) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt new file mode 100644 index 000000000000..070cdef336e5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt @@ -0,0 +1,107 @@ +/* + * 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.systemui.qs.tiles.impl.rotation.ui.mapper + +import android.content.res.Resources +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper +import com.android.systemui.qs.tiles.impl.rotation.domain.model.RotationLockTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import com.android.systemui.statusbar.policy.DevicePostureController +import javax.inject.Inject + +/** Maps [RotationLockTileModel] to [QSTileState]. */ +class RotationLockTileMapper +@Inject +constructor( + @Main private val resources: Resources, + private val theme: Resources.Theme, + private val devicePostureController: DevicePostureController +) : QSTileDataToStateMapper<RotationLockTileModel> { + override fun map(config: QSTileConfig, data: RotationLockTileModel): QSTileState = + QSTileState.build(resources, theme, config.uiConfig) { + this.label = resources.getString(R.string.quick_settings_rotation_unlocked_label) + this.contentDescription = + resources.getString(R.string.accessibility_quick_settings_rotation) + + if (data.isRotationLocked) { + activationState = QSTileState.ActivationState.INACTIVE + this.secondaryLabel = EMPTY_SECONDARY_STRING + this.icon = { + Icon.Loaded( + resources.getDrawable(R.drawable.qs_auto_rotate_icon_off, theme), + contentDescription = null + ) + } + } else { + activationState = QSTileState.ActivationState.ACTIVE + this.secondaryLabel = + if (data.isCameraRotationEnabled) { + resources.getString(R.string.rotation_lock_camera_rotation_on) + } else { + EMPTY_SECONDARY_STRING + } + this.icon = { + Icon.Loaded( + resources.getDrawable(R.drawable.qs_auto_rotate_icon_on, theme), + contentDescription = null + ) + } + } + if (isDeviceFoldable()) { + this.secondaryLabel = getSecondaryLabelWithPosture(this.activationState) + } + this.stateDescription = this.secondaryLabel + this.sideViewIcon = QSTileState.SideViewIcon.None + supportedActions = + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) + } + + private fun isDeviceFoldable(): Boolean { + val intArray = resources.getIntArray(com.android.internal.R.array.config_foldedDeviceStates) + return intArray.isNotEmpty() + } + + private fun getSecondaryLabelWithPosture(activationState: QSTileState.ActivationState): String { + val stateNames = resources.getStringArray(R.array.tile_states_rotation) + val stateName = + stateNames[ + if (activationState == QSTileState.ActivationState.ACTIVE) ON_INDEX else OFF_INDEX] + val posture = + if ( + devicePostureController.devicePosture == + DevicePostureController.DEVICE_POSTURE_CLOSED + ) + resources.getString(R.string.quick_settings_rotation_posture_folded) + else resources.getString(R.string.quick_settings_rotation_posture_unfolded) + + return resources.getString( + R.string.rotation_tile_with_posture_secondary_label_template, + stateName, + posture + ) + } + + private companion object { + const val EMPTY_SECONDARY_STRING = "" + const val OFF_INDEX = 1 + const val ON_INDEX = 2 + } +} diff --git a/packages/SystemUI/src/com/android/systemui/rotationlock/RotationLockNewModule.kt b/packages/SystemUI/src/com/android/systemui/rotationlock/RotationLockNewModule.kt new file mode 100644 index 000000000000..eb64dd609a72 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/rotationlock/RotationLockNewModule.kt @@ -0,0 +1,73 @@ +/* + * 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.systemui.rotationlock + +import com.android.systemui.camera.CameraRotationModule +import com.android.systemui.qs.QsEventLogger +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory +import com.android.systemui.qs.tiles.impl.rotation.domain.interactor.RotationLockTileDataInteractor +import com.android.systemui.qs.tiles.impl.rotation.domain.interactor.RotationLockTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.rotation.domain.model.RotationLockTileModel +import com.android.systemui.qs.tiles.impl.rotation.ui.mapper.RotationLockTileMapper +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel +import com.android.systemui.res.R +import dagger.Module +import dagger.Provides +import dagger.multibindings.IntoMap +import dagger.multibindings.StringKey + +@Module(includes = [CameraRotationModule::class]) +interface RotationLockNewModule { + companion object { + private const val ROTATION_TILE_SPEC = "rotation" + + /** Inject rotation tile config */ + @Provides + @IntoMap + @StringKey(ROTATION_TILE_SPEC) + fun provideRotationTileConfig(uiEventLogger: QsEventLogger): QSTileConfig = + QSTileConfig( + tileSpec = TileSpec.create(ROTATION_TILE_SPEC), + uiConfig = + QSTileUIConfig.Resource( + iconRes = R.drawable.qs_auto_rotate_icon_off, + labelRes = R.string.quick_settings_rotation_unlocked_label, + ), + instanceId = uiEventLogger.getNewInstanceId(), + ) + + /** Inject Rotation tile into tileViewModelMap in QSModule */ + @Provides + @IntoMap + @StringKey(ROTATION_TILE_SPEC) + fun provideRotationTileViewModel( + factory: QSTileViewModelFactory.Static<RotationLockTileModel>, + mapper: RotationLockTileMapper, + stateInteractor: RotationLockTileDataInteractor, + userActionInteractor: RotationLockTileUserActionInteractor + ): QSTileViewModel = + factory.create( + TileSpec.create(ROTATION_TILE_SPEC), + userActionInteractor, + stateInteractor, + mapper, + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt new file mode 100644 index 000000000000..0128eb762296 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt @@ -0,0 +1,37 @@ +/* + * 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.systemui.util.kotlin + +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.statusbar.policy.BatteryController +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.onStart + +fun BatteryController.isBatteryPowerSaveEnabled(): Flow<Boolean> { + return conflatedCallbackFlow { + val batteryCallback = + object : BatteryController.BatteryStateChangeCallback { + override fun onPowerSaveChanged(isPowerSave: Boolean) { + trySend(isPowerSave) + } + } + addCallback(batteryCallback) + awaitClose { removeCallback(batteryCallback) } + } + .onStart { emit(isPowerSave) } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/RotationLockControllerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/RotationLockControllerExt.kt new file mode 100644 index 000000000000..22cc8dd7745d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/RotationLockControllerExt.kt @@ -0,0 +1,35 @@ +/* + * 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.systemui.util.kotlin + +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.statusbar.policy.RotationLockController +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.onStart + +fun RotationLockController.isRotationLockEnabled(): Flow<Boolean> { + return conflatedCallbackFlow { + val rotationLockCallback = + RotationLockController.RotationLockControllerCallback { rotationLocked, _ -> + trySend(rotationLocked) + } + addCallback(rotationLockCallback) + awaitClose { removeCallback(rotationLockCallback) } + } + .onStart { emit(isRotationLocked) } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepository.kt new file mode 100644 index 000000000000..b8284acc997b --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepository.kt @@ -0,0 +1,37 @@ +/* + * 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.systemui.camera.data.repository + +import android.os.UserHandle +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +class FakeCameraAutoRotateRepository : CameraAutoRotateRepository { + private val userMap = mutableMapOf<Int, MutableStateFlow<Boolean>>() + + /** Send a Unit signal when value changes */ + override fun isCameraAutoRotateSettingEnabled(userHandle: UserHandle): StateFlow<Boolean> = + getFlow(userHandle.identifier) + + fun setEnabled(userHandle: UserHandle, enabled: Boolean) { + getFlow(userHandle.identifier).value = enabled + } + + /** initializes the flow if already not */ + private fun getFlow(userId: Int): MutableStateFlow<Boolean> = + userMap.getOrPut(userId) { MutableStateFlow(false) } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepositoryKosmos.kt new file mode 100644 index 000000000000..615c59661f1d --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepositoryKosmos.kt @@ -0,0 +1,22 @@ +/* + * 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.systemui.camera.data.repository + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.fakeCameraAutoRotateRepository: FakeCameraAutoRotateRepository by + Kosmos.Fixture { FakeCameraAutoRotateRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepository.kt new file mode 100644 index 000000000000..994e9b2f3683 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepository.kt @@ -0,0 +1,36 @@ +/* + * 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.systemui.camera.data.repository + +import android.os.UserHandle +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +class FakeCameraSensorPrivacyRepository : CameraSensorPrivacyRepository { + + private val userMap = mutableMapOf<Int, MutableStateFlow<Boolean>>() + override fun isEnabled(userHandle: UserHandle): StateFlow<Boolean> = + getFlow(userHandle.identifier) + + fun setEnabled(userHandle: UserHandle, enabled: Boolean) { + getFlow(userHandle.identifier).value = enabled + } + + /** initializes the flow if already not */ + private fun getFlow(userId: Int): MutableStateFlow<Boolean> = + userMap.getOrPut(userId) { MutableStateFlow(false) } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepositoryKosmos.kt new file mode 100644 index 000000000000..c7e704cab42f --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepositoryKosmos.kt @@ -0,0 +1,22 @@ +/* + * 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.systemui.camera.data.repository + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.fakeCameraSensorPrivacyRepository: FakeCameraSensorPrivacyRepository by + Kosmos.Fixture { FakeCameraSensorPrivacyRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/rotation/RotationLockTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/rotation/RotationLockTileKosmos.kt new file mode 100644 index 000000000000..ecf8ce5bb8b8 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/rotation/RotationLockTileKosmos.kt @@ -0,0 +1,24 @@ +/* + * 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.systemui.qs.tiles.impl.rotation + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.qsEventLogger +import com.android.systemui.rotationlock.RotationLockNewModule + +val Kosmos.qsRotationLockTileConfig by + Kosmos.Fixture { RotationLockNewModule.provideRotationTileConfig(qsEventLogger) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/DevicePostureControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/DevicePostureControllerKosmos.kt new file mode 100644 index 000000000000..89eaf15a52d9 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/DevicePostureControllerKosmos.kt @@ -0,0 +1,22 @@ +/* + * 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.systemui.statusbar.policy + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +val Kosmos.devicePostureController by Kosmos.Fixture { mock<DevicePostureController>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeRotationLockController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeRotationLockController.java index be57658a4266..4aa85a79c934 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeRotationLockController.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeRotationLockController.java @@ -19,13 +19,29 @@ import android.testing.LeakCheck; import com.android.systemui.statusbar.policy.RotationLockController; import com.android.systemui.statusbar.policy.RotationLockController.RotationLockControllerCallback; +import java.util.ArrayList; +import java.util.List; + public class FakeRotationLockController extends BaseLeakChecker<RotationLockControllerCallback> implements RotationLockController { + private boolean mIsLocked = false; + private final List<RotationLockControllerCallback> mCallbacks = new ArrayList<>(); public FakeRotationLockController(LeakCheck test) { super(test, "rotation"); } @Override + public void addCallback(RotationLockControllerCallback listener) { + mCallbacks.add(listener); + listener.onRotationLockStateChanged(mIsLocked, isRotationLockAffordanceVisible()); + } + + @Override + public void removeCallback(RotationLockControllerCallback listener) { + mCallbacks.remove(listener); + } + + @Override public void setListening(boolean listening) { } @@ -42,12 +58,15 @@ public class FakeRotationLockController extends BaseLeakChecker<RotationLockCont @Override public boolean isRotationLocked() { - return false; + return mIsLocked; } @Override public void setRotationLocked(boolean locked, String caller) { - + mIsLocked = locked; + for (RotationLockControllerCallback callback : mCallbacks) { + callback.onRotationLockStateChanged(locked, isRotationLockAffordanceVisible()); + } } @Override |