Merge "Quick affordance to open the camera in video mode." into tm-qpr-dev
This commit is contained in:
10
packages/SystemUI/res/drawable/ic_camera.xml
Normal file
10
packages/SystemUI/res/drawable/ic_camera.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M7,42Q5.8,42 4.9,41.1Q4,40.2 4,39V13.35Q4,12.15 4.9,11.25Q5.8,10.35 7,10.35H14.35L18,6H30L33.65,10.35H41Q42.2,10.35 43.1,11.25Q44,12.15 44,13.35V39Q44,40.2 43.1,41.1Q42.2,42 41,42ZM7,39H41Q41,39 41,39Q41,39 41,39V13.35Q41,13.35 41,13.35Q41,13.35 41,13.35H7Q7,13.35 7,13.35Q7,13.35 7,13.35V39Q7,39 7,39Q7,39 7,39ZM7,39Q7,39 7,39Q7,39 7,39V13.35Q7,13.35 7,13.35Q7,13.35 7,13.35Q7,13.35 7,13.35Q7,13.35 7,13.35V39Q7,39 7,39Q7,39 7,39ZM24,34.7Q27.5,34.7 30,32.225Q32.5,29.75 32.5,26.2Q32.5,22.7 30,20.2Q27.5,17.7 24,17.7Q20.45,17.7 17.975,20.2Q15.5,22.7 15.5,26.2Q15.5,29.75 17.975,32.225Q20.45,34.7 24,34.7ZM24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Z"/>
|
||||
</vector>
|
||||
10
packages/SystemUI/res/drawable/ic_videocam.xml
Normal file
10
packages/SystemUI/res/drawable/ic_videocam.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M7,40Q5.8,40 4.9,39.1Q4,38.2 4,37V11Q4,9.8 4.9,8.9Q5.8,8 7,8H33Q34.2,8 35.1,8.9Q36,9.8 36,11V21.75L44,13.75V34.25L36,26.25V37Q36,38.2 35.1,39.1Q34.2,40 33,40ZM7,37H33Q33,37 33,37Q33,37 33,37V11Q33,11 33,11Q33,11 33,11H7Q7,11 7,11Q7,11 7,11V37Q7,37 7,37Q7,37 7,37ZM7,37Q7,37 7,37Q7,37 7,37V11Q7,11 7,11Q7,11 7,11Q7,11 7,11Q7,11 7,11V37Q7,37 7,37Q7,37 7,37Z"/>
|
||||
</vector>
|
||||
@@ -2778,6 +2778,12 @@
|
||||
<!-- Subtitle for the notification sent when a stylus battery is low. [CHAR LIMIT=none]-->
|
||||
<string name="stylus_battery_low_subtitle">Connect your stylus to a charger</string>
|
||||
|
||||
<!-- Title for notification of low stylus battery. [CHAR_LIMIT=NONE] -->
|
||||
<string name="stylus_battery_low">Stylus battery low</string>
|
||||
|
||||
<!-- Label for a lock screen shortcut to start the camera in video mode. [CHAR_LIMIT=16] -->
|
||||
<string name="video_camera">Video camera</string>
|
||||
|
||||
<!-- Switch to work profile dialer app for placing a call dialog. -->
|
||||
<!-- Text for Switch to work profile dialog's Title. Switch to work profile dialog guide users to make call from work
|
||||
profile dialer app as it's not possible to make call from current profile due to an admin policy. [CHAR LIMIT=60] -->
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
|
||||
@@ -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<KeyguardQuickAffordanceConfig.LockScreenState>
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ interface KeyguardDataQuickAffordanceModule {
|
||||
quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
|
||||
qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
|
||||
camera: CameraQuickAffordanceConfig,
|
||||
videoCamera: VideoCameraQuickAffordanceConfig,
|
||||
): Set<KeyguardQuickAffordanceConfig> {
|
||||
return setOf(
|
||||
camera,
|
||||
@@ -47,6 +48,7 @@ interface KeyguardDataQuickAffordanceModule {
|
||||
home,
|
||||
quickAccessWallet,
|
||||
qrCodeScanner,
|
||||
videoCamera,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<KeyguardQuickAffordanceConfig.LockScreenState>
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user