summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/res-keyguard/drawable/ic_flashlight_off.xml25
-rw-r--r--packages/SystemUI/res-keyguard/drawable/ic_flashlight_on.xml25
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt144
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt193
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java47
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);
}
}