diff --git a/packages/SystemUI/res/drawable/ic_camera.xml b/packages/SystemUI/res/drawable/ic_camera.xml
new file mode 100644
index 0000000000000..ef1406c1c58ad
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_camera.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/packages/SystemUI/res/drawable/ic_videocam.xml b/packages/SystemUI/res/drawable/ic_videocam.xml
new file mode 100644
index 0000000000000..de2bc7bccdf13
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_videocam.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 43dea0a06898b..066b185c6d1f1 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2778,6 +2778,12 @@
Connect your stylus to a charger
+
+ Stylus battery low
+
+
+ Video camera
+
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
index 867faf9843fe5..cc43e7ed25a57 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
@@ -20,15 +20,13 @@ import android.content.Context
import android.content.Intent
import android.provider.MediaStore
import android.text.TextUtils
-
import com.android.systemui.R
class CameraIntents {
companion object {
- val DEFAULT_SECURE_CAMERA_INTENT_ACTION =
- MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE
- val DEFAULT_INSECURE_CAMERA_INTENT_ACTION =
- MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA
+ val DEFAULT_SECURE_CAMERA_INTENT_ACTION = MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE
+ val DEFAULT_INSECURE_CAMERA_INTENT_ACTION = MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA
+ private val VIDEO_CAMERA_INTENT_ACTION = MediaStore.INTENT_ACTION_VIDEO_CAMERA
const val EXTRA_LAUNCH_SOURCE = "com.android.systemui.camera_launch_source"
@JvmStatic
@@ -44,18 +42,14 @@ class CameraIntents {
@JvmStatic
fun getInsecureCameraIntent(context: Context): Intent {
val intent = Intent(DEFAULT_INSECURE_CAMERA_INTENT_ACTION)
- getOverrideCameraPackage(context)?.let {
- intent.setPackage(it)
- }
+ getOverrideCameraPackage(context)?.let { intent.setPackage(it) }
return intent
}
@JvmStatic
fun getSecureCameraIntent(context: Context): Intent {
val intent = Intent(DEFAULT_SECURE_CAMERA_INTENT_ACTION)
- getOverrideCameraPackage(context)?.let {
- intent.setPackage(it)
- }
+ getOverrideCameraPackage(context)?.let { intent.setPackage(it) }
return intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
}
@@ -68,5 +62,11 @@ class CameraIntents {
fun isInsecureCameraIntent(intent: Intent?): Boolean {
return intent?.getAction()?.equals(DEFAULT_INSECURE_CAMERA_INTENT_ACTION) ?: false
}
+
+ /** Returns an [Intent] that can be used to start the camera in video mode. */
+ @JvmStatic
+ fun getVideoCameraIntent(): Intent {
+ return Intent(VIDEO_CAMERA_INTENT_ACTION)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt
index cf02f8fb4a3cb..a434617f2da78 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt
@@ -21,7 +21,9 @@ import android.content.Intent
import javax.inject.Inject
/** Injectable wrapper around [CameraIntents]. */
-class CameraIntentsWrapper @Inject constructor(
+class CameraIntentsWrapper
+@Inject
+constructor(
private val context: Context,
) {
@@ -40,4 +42,9 @@ class CameraIntentsWrapper @Inject constructor(
fun getInsecureCameraIntent(): Intent {
return CameraIntents.getInsecureCameraIntent(context)
}
+
+ /** Returns an [Intent] that can be used to start the camera in video mode. */
+ fun getVideoCameraIntent(): Intent {
+ return CameraIntents.getVideoCameraIntent()
+ }
}
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 ea5b4f43cc75f..cd4dac0d59c51 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
@@ -30,5 +30,6 @@ object BuiltInKeyguardQuickAffordanceKeys {
const val HOME_CONTROLS = "home"
const val QR_CODE_SCANNER = "qr_code_scanner"
const val QUICK_ACCESS_WALLET = "wallet"
+ const val VIDEO_CAMERA = "video_camera"
// Please keep alphabetical order of const names to simplify future maintenance.
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
index dbc376e62950e..f6e6d6b7dc1b0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
@@ -26,6 +26,7 @@ import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.StatusBarState
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -46,7 +47,7 @@ constructor(
get() = context.getString(R.string.accessibility_camera_button)
override val pickerIconResourceId: Int
- get() = com.android.internal.R.drawable.perm_group_camera
+ get() = R.drawable.ic_camera
override val lockScreenState: Flow
get() =
@@ -54,12 +55,20 @@ constructor(
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
icon =
Icon.Resource(
- com.android.internal.R.drawable.perm_group_camera,
+ R.drawable.ic_camera,
ContentDescription.Resource(R.string.accessibility_camera_button)
)
)
)
+ override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
+ return if (isLaunchable()) {
+ super.getPickerScreenState()
+ } else {
+ KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+ }
+ }
+
override fun onTriggered(
expandable: Expandable?
): KeyguardQuickAffordanceConfig.OnTriggeredResult {
@@ -68,4 +77,8 @@ constructor(
.launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
}
+
+ private fun isLaunchable(): Boolean {
+ return cameraGestureHelper.get().canCameraGestureBeLaunched(StatusBarState.KEYGUARD)
+ }
}
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 71d01ebc84967..a1cce5c670baa 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
@@ -39,6 +39,7 @@ interface KeyguardDataQuickAffordanceModule {
quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
camera: CameraQuickAffordanceConfig,
+ videoCamera: VideoCameraQuickAffordanceConfig,
): Set {
return setOf(
camera,
@@ -47,6 +48,7 @@ interface KeyguardDataQuickAffordanceModule {
home,
quickAccessWallet,
qrCodeScanner,
+ videoCamera,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt
new file mode 100644
index 0000000000000..d9ec3b1c2f872
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 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.app.StatusBarManager
+import android.content.Context
+import android.content.Intent
+import com.android.systemui.ActivityIntentHelper
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.camera.CameraIntents
+import com.android.systemui.camera.CameraIntentsWrapper
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.settings.UserTracker
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+@SysUISingleton
+class VideoCameraQuickAffordanceConfig
+@Inject
+constructor(
+ @Application private val context: Context,
+ private val cameraIntents: CameraIntentsWrapper,
+ private val activityIntentHelper: ActivityIntentHelper,
+ private val userTracker: UserTracker,
+) : KeyguardQuickAffordanceConfig {
+
+ private val intent: Intent by lazy {
+ cameraIntents.getVideoCameraIntent().apply {
+ putExtra(
+ CameraIntents.EXTRA_LAUNCH_SOURCE,
+ StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE,
+ )
+ }
+ }
+
+ override val key: String
+ get() = BuiltInKeyguardQuickAffordanceKeys.VIDEO_CAMERA
+
+ override val pickerName: String
+ get() = context.getString(R.string.video_camera)
+
+ override val pickerIconResourceId: Int
+ get() = R.drawable.ic_videocam
+
+ override val lockScreenState: Flow
+ get() =
+ flowOf(
+ if (isLaunchable()) {
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon =
+ Icon.Resource(
+ R.drawable.ic_videocam,
+ ContentDescription.Resource(R.string.video_camera)
+ )
+ )
+ } else {
+ KeyguardQuickAffordanceConfig.LockScreenState.Hidden
+ }
+ )
+
+ override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
+ return if (isLaunchable()) {
+ super.getPickerScreenState()
+ } else {
+ KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+ }
+ }
+
+ override fun onTriggered(
+ expandable: Expandable?
+ ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
+ intent = intent,
+ canShowWhileLocked = false,
+ )
+ }
+
+ private fun isLaunchable(): Boolean {
+ return activityIntentHelper.getTargetActivityInfo(
+ intent,
+ userTracker.userId,
+ true,
+ ) != null
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
index 7205f3068abb5..8da4eae2f64a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
@@ -22,15 +22,21 @@ import android.content.Context
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.camera.CameraGestureHelper
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
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.Mockito.anyInt
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class CameraQuickAffordanceConfigTest : SysuiTestCase() {
@@ -62,4 +68,24 @@ class CameraQuickAffordanceConfigTest : SysuiTestCase() {
.launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
}
+
+ @Test
+ fun `getPickerScreenState - default when launchable`() = runTest {
+ setLaunchable(true)
+
+ Truth.assertThat(underTest.getPickerScreenState())
+ .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java)
+ }
+
+ @Test
+ fun `getPickerScreenState - unavailable when not launchable`() = runTest {
+ setLaunchable(false)
+
+ Truth.assertThat(underTest.getPickerScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+ }
+
+ private fun setLaunchable(isLaunchable: Boolean) {
+ whenever(cameraGestureHelper.canCameraGestureBeLaunched(anyInt())).thenReturn(isLaunchable)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt
new file mode 100644
index 0000000000000..805dcec0f5b19
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2023 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 androidx.test.filters.SmallTest
+import com.android.systemui.ActivityIntentHelper
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.camera.CameraIntentsWrapper
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class VideoCameraQuickAffordanceConfigTest : SysuiTestCase() {
+
+ @Mock private lateinit var activityIntentHelper: ActivityIntentHelper
+
+ private lateinit var underTest: VideoCameraQuickAffordanceConfig
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest =
+ VideoCameraQuickAffordanceConfig(
+ context = context,
+ cameraIntents = CameraIntentsWrapper(context),
+ activityIntentHelper = activityIntentHelper,
+ userTracker = FakeUserTracker(),
+ )
+ }
+
+ @Test
+ fun `lockScreenState - visible when launchable`() = runTest {
+ setLaunchable(true)
+
+ val lockScreenState = collectLastValue(underTest.lockScreenState)
+
+ assertThat(lockScreenState())
+ .isInstanceOf(KeyguardQuickAffordanceConfig.LockScreenState.Visible::class.java)
+ }
+
+ @Test
+ fun `lockScreenState - hidden when not launchable`() = runTest {
+ setLaunchable(false)
+
+ val lockScreenState = collectLastValue(underTest.lockScreenState)
+
+ assertThat(lockScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
+ }
+
+ @Test
+ fun `getPickerScreenState - default when launchable`() = runTest {
+ setLaunchable(true)
+
+ assertThat(underTest.getPickerScreenState())
+ .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java)
+ }
+
+ @Test
+ fun `getPickerScreenState - unavailable when not launchable`() = runTest {
+ setLaunchable(false)
+
+ assertThat(underTest.getPickerScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+ }
+
+ private fun setLaunchable(isLaunchable: Boolean) {
+ whenever(
+ activityIntentHelper.getTargetActivityInfo(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
+ .thenReturn(
+ if (isLaunchable) {
+ mock()
+ } else {
+ null
+ }
+ )
+ }
+}