diff options
7 files changed, 433 insertions, 4 deletions
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_flashlight_off.xml b/packages/SystemUI/res-keyguard/drawable/ic_flashlight_off.xml new file mode 100644 index 000000000000..e850d6823e97 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/ic_flashlight_off.xml @@ -0,0 +1,25 @@ +<!-- + ~ Copyright (C) 2022 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 + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="#1f1f1f" + android:pathData="M8,22V11L6,8V2H18V8L16,11V22ZM12,15.5Q11.375,15.5 10.938,15.062Q10.5,14.625 10.5,14Q10.5,13.375 10.938,12.938Q11.375,12.5 12,12.5Q12.625,12.5 13.062,12.938Q13.5,13.375 13.5,14Q13.5,14.625 13.062,15.062Q12.625,15.5 12,15.5ZM8,5H16V4H8ZM16,7H8V7.4L10,10.4V20H14V10.4L16,7.4ZM12,12Z"/> +</vector> diff --git a/packages/SystemUI/res-keyguard/drawable/ic_flashlight_on.xml b/packages/SystemUI/res-keyguard/drawable/ic_flashlight_on.xml new file mode 100644 index 000000000000..91b9ae56b145 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/ic_flashlight_on.xml @@ -0,0 +1,25 @@ +<!-- + ~ Copyright (C) 2022 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 + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="#1f1f1f" + android:pathData="M6,5V2H18V5ZM12,15.5Q12.625,15.5 13.062,15.062Q13.5,14.625 13.5,14Q13.5,13.375 13.062,12.938Q12.625,12.5 12,12.5Q11.375,12.5 10.938,12.938Q10.5,13.375 10.5,14Q10.5,14.625 10.938,15.062Q11.375,15.5 12,15.5ZM8,22V11L6,8V7H18V8L16,11V22Z"/> +</vector> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt index f5220b8fae92..73dbeab3030b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt @@ -25,6 +25,7 @@ package com.android.systemui.keyguard.data.quickaffordance object BuiltInKeyguardQuickAffordanceKeys { // Please keep alphabetical order of const names to simplify future maintenance. const val CAMERA = "camera" + const val FLASHLIGHT = "flashlight" const val HOME_CONTROLS = "home" const val QR_CODE_SCANNER = "qr_code_scanner" const val QUICK_ACCESS_WALLET = "wallet" diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt new file mode 100644 index 000000000000..49527d32d229 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2022 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.keyguard.data.quickaffordance + +import android.content.Context +import com.android.systemui.R +import com.android.systemui.animation.Expandable +import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.keyguard.shared.quickaffordance.ActivationState +import com.android.systemui.statusbar.policy.FlashlightController +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +@SysUISingleton +class FlashlightQuickAffordanceConfig @Inject constructor( + @Application private val context: Context, + private val flashlightController: FlashlightController, +) : KeyguardQuickAffordanceConfig { + + private sealed class FlashlightState { + + abstract fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState + + object On: FlashlightState() { + override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState = + KeyguardQuickAffordanceConfig.LockScreenState.Visible( + Icon.Resource( + R.drawable.ic_flashlight_on, + ContentDescription.Resource(R.string.quick_settings_flashlight_label) + ), + ActivationState.Active + ) + } + + object OffAvailable: FlashlightState() { + override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState = + KeyguardQuickAffordanceConfig.LockScreenState.Visible( + Icon.Resource( + R.drawable.ic_flashlight_off, + ContentDescription.Resource(R.string.quick_settings_flashlight_label) + ), + ActivationState.Inactive + ) + } + + object Unavailable: FlashlightState() { + override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState = + KeyguardQuickAffordanceConfig.LockScreenState.Hidden + } + } + + override val key: String + get() = BuiltInKeyguardQuickAffordanceKeys.FLASHLIGHT + + override val pickerName: String + get() = context.getString(R.string.quick_settings_flashlight_label) + + override val pickerIconResourceId: Int + get() = if (flashlightController.isEnabled) { + R.drawable.ic_flashlight_on + } else { + R.drawable.ic_flashlight_off + } + + override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> = + conflatedCallbackFlow { + val flashlightCallback = object : FlashlightController.FlashlightListener { + override fun onFlashlightChanged(enabled: Boolean) { + trySendWithFailureLogging( + if (enabled) { + FlashlightState.On.toLockScreenState() + } else { + FlashlightState.OffAvailable.toLockScreenState() + }, + TAG + ) + } + + override fun onFlashlightError() { + trySendWithFailureLogging(FlashlightState.OffAvailable.toLockScreenState(), TAG) + } + + override fun onFlashlightAvailabilityChanged(available: Boolean) { + trySendWithFailureLogging( + if (!available) { + FlashlightState.Unavailable.toLockScreenState() + } else { + if (flashlightController.isEnabled) { + FlashlightState.On.toLockScreenState() + } else { + FlashlightState.OffAvailable.toLockScreenState() + } + }, + TAG + ) + } + } + + flashlightController.addCallback(flashlightCallback) + + awaitClose { + flashlightController.removeCallback(flashlightCallback) + } + } + + override fun onTriggered(expandable: Expandable?): + KeyguardQuickAffordanceConfig.OnTriggeredResult { + flashlightController + .setFlashlight(flashlightController.isAvailable && !flashlightController.isEnabled) + return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + } + + override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState = + if (flashlightController.isAvailable) { + KeyguardQuickAffordanceConfig.PickerScreenState.Default + } else { + KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice + } + + companion object { + private const val TAG = "FlashlightQuickAffordanceConfig" + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt index f7225a249eda..3013227c21c0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt @@ -26,6 +26,7 @@ object KeyguardDataQuickAffordanceModule { @Provides @ElementsIntoSet fun quickAffordanceConfigs( + flashlight: FlashlightQuickAffordanceConfig, home: HomeControlsKeyguardQuickAffordanceConfig, quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig, qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig, @@ -33,6 +34,7 @@ object KeyguardDataQuickAffordanceModule { ): Set<KeyguardQuickAffordanceConfig> { return setOf( camera, + flashlight, home, quickAccessWallet, qrCodeScanner, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt new file mode 100644 index 000000000000..cda701819d60 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2022 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.keyguard.data.quickaffordance + +import android.content.Context +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.keyguard.shared.quickaffordance.ActivationState +import com.android.systemui.statusbar.policy.FlashlightController +import com.android.systemui.utils.leaks.FakeFlashlightController +import com.android.systemui.utils.leaks.LeakCheckedTest +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(JUnit4::class) +class FlashlightQuickAffordanceConfigTest : LeakCheckedTest() { + + @Mock private lateinit var context: Context + private lateinit var flashlightController: FakeFlashlightController + private lateinit var underTest : FlashlightQuickAffordanceConfig + + @Before + fun setUp() { + injectLeakCheckedDependency(FlashlightController::class.java) + MockitoAnnotations.initMocks(this) + + flashlightController = SysuiLeakCheck().getLeakChecker(FlashlightController::class.java) as FakeFlashlightController + underTest = FlashlightQuickAffordanceConfig(context, flashlightController) + } + + @Test + fun `flashlight is off -- triggered -- icon is on and active`() = runTest { + //given + flashlightController.isEnabled = false + flashlightController.isAvailable = true + val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>() + val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)} + + //when + underTest.onTriggered(null) + val lastValue = values.last() + + //then + assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible) + assertEquals(R.drawable.ic_flashlight_on, + ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon as? Icon.Resource)?.res) + job.cancel() + } + + @Test + fun `flashlight is on -- triggered -- icon is off and inactive`() = runTest { + //given + flashlightController.isEnabled = true + flashlightController.isAvailable = true + val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>() + val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)} + + //when + underTest.onTriggered(null) + val lastValue = values.last() + + //then + assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible) + assertEquals(R.drawable.ic_flashlight_off, + ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon as? Icon.Resource)?.res) + job.cancel() + } + + @Test + fun `flashlight is on -- receives error -- icon is off and inactive`() = runTest { + //given + flashlightController.isEnabled = true + flashlightController.isAvailable = false + val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>() + val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)} + + //when + flashlightController.onFlashlightError() + val lastValue = values.last() + + //then + assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible) + assertEquals(R.drawable.ic_flashlight_off, + ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon as? Icon.Resource)?.res) + job.cancel() + } + + @Test + fun `flashlight availability now off -- hidden`() = runTest { + //given + flashlightController.isEnabled = true + flashlightController.isAvailable = false + val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>() + val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)} + + //when + flashlightController.onFlashlightAvailabilityChanged(false) + val lastValue = values.last() + + //then + assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Hidden) + job.cancel() + } + + @Test + fun `flashlight availability now on -- flashlight on -- inactive and icon off`() = runTest { + //given + flashlightController.isEnabled = true + flashlightController.isAvailable = false + val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>() + val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)} + + //when + flashlightController.onFlashlightAvailabilityChanged(true) + val lastValue = values.last() + + //then + assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible) + assertTrue((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState is ActivationState.Active) + assertEquals(R.drawable.ic_flashlight_on, (lastValue.icon as? Icon.Resource)?.res) + job.cancel() + } + + @Test + fun `flashlight availability now on -- flashlight off -- inactive and icon off`() = runTest { + //given + flashlightController.isEnabled = false + flashlightController.isAvailable = false + val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>() + val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values)} + + //when + flashlightController.onFlashlightAvailabilityChanged(true) + val lastValue = values.last() + + //then + assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible) + assertTrue((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState is ActivationState.Inactive) + assertEquals(R.drawable.ic_flashlight_off, (lastValue.icon as? Icon.Resource)?.res) + job.cancel() + } + + @Test + fun `flashlight available -- picker state default`() = runTest { + //given + flashlightController.isAvailable = true + + //when + val result = underTest.getPickerScreenState() + + //then + assertTrue(result is KeyguardQuickAffordanceConfig.PickerScreenState.Default) + } + + @Test + fun `flashlight not available -- picker state unavailable`() = runTest { + //given + flashlightController.isAvailable = false + + //when + val result = underTest.getPickerScreenState() + + //then + assertTrue(result is KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java index f6fd2cb8f3b1..f68baf5546d7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java @@ -16,32 +16,71 @@ package com.android.systemui.utils.leaks; import android.testing.LeakCheck; +import androidx.annotation.VisibleForTesting; + import com.android.systemui.statusbar.policy.FlashlightController; import com.android.systemui.statusbar.policy.FlashlightController.FlashlightListener; +import java.util.ArrayList; +import java.util.List; + public class FakeFlashlightController extends BaseLeakChecker<FlashlightListener> implements FlashlightController { + + private final List<FlashlightListener> callbacks = new ArrayList<>(); + + @VisibleForTesting + public boolean isAvailable; + @VisibleForTesting + public boolean isEnabled; + @VisibleForTesting + public boolean hasFlashlight; + public FakeFlashlightController(LeakCheck test) { super(test, "flashlight"); } + @VisibleForTesting + public void onFlashlightAvailabilityChanged(boolean newValue) { + callbacks.forEach( + flashlightListener -> flashlightListener.onFlashlightAvailabilityChanged(newValue) + ); + } + + @VisibleForTesting + public void onFlashlightError() { + callbacks.forEach(FlashlightListener::onFlashlightError); + } + @Override public boolean hasFlashlight() { - return false; + return hasFlashlight; } @Override public void setFlashlight(boolean newState) { - + callbacks.forEach(flashlightListener -> flashlightListener.onFlashlightChanged(newState)); } @Override public boolean isAvailable() { - return false; + return isAvailable; } @Override public boolean isEnabled() { - return false; + return isEnabled; + } + + @Override + public void addCallback(FlashlightListener listener) { + super.addCallback(listener); + callbacks.add(listener); + } + + @Override + public void removeCallback(FlashlightListener listener) { + super.removeCallback(listener); + callbacks.remove(listener); } } |