Merge "Hide notes app lock screen shortcut when there is no default notes app set" into udc-dev

This commit is contained in:
TreeHugger Robot
2023-04-27 21:16:12 +00:00
committed by Android (Google) Code Review
10 changed files with 510 additions and 35 deletions

View File

@@ -992,6 +992,18 @@
android:excludeFromRecents="true"
android:resizeableActivity="false"
android:theme="@android:style/Theme.NoDisplay" />
<activity
android:name=".notetask.LaunchNotesRoleSettingsTrampolineActivity"
android:exported="true"
android:excludeFromRecents="true"
android:resizeableActivity="false"
android:theme="@android:style/Theme.NoDisplay" >
<intent-filter>
<action android:name="com.android.systemui.action.MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<!-- endregion -->
<!-- started from ControlsRequestReceiver -->

View File

@@ -3038,6 +3038,19 @@
-->
<string name="keyguard_affordance_enablement_dialog_home_instruction_2">&#8226; At least one device is available</string>
<!---
Requirement for the notes app to be available for the user to use. This is shown as part of a
bulleted list of requirements. When all requirements are met, the app can be accessed through a
shortcut button on the lock screen. [CHAR LIMIT=NONE] -->
<string name="keyguard_affordance_enablement_dialog_notes_app_instruction">Select a default notes app to use notetaking shortcut</string>
<!---
The action to make the lock screen shortcut for the notes app to be available for the user to
use. This is shown as the action button in the dialog listing the requirements. When all
requirements are met, the app can be accessed through a shortcut button on the lock screen.
[CHAR LIMIT=NONE] -->
<string name="keyguard_affordance_enablement_dialog_notes_app_action">Open settings</string>
<!--
Error message shown when a shortcut must be pressed and held to activate it, usually shown when
the user tried to tap the shortcut or held it for too short a time. [CHAR LIMIT=32].

View File

@@ -0,0 +1,54 @@
/*
* 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.notetask
import android.os.Bundle
import androidx.activity.ComponentActivity
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import javax.inject.Inject
/**
* An internal proxy activity that starts the notes role setting.
*
* This activity is introduced mainly for the error handling of the notes app lock screen shortcut
* picker, which only supports package + action but not extras. See
* [KeyguardQuickAffordanceConfig.PickerScreenState.Disabled.actionComponentName].
*/
class LaunchNotesRoleSettingsTrampolineActivity
@Inject
constructor(
private val controller: NoteTaskController,
) : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val entryPoint =
if (intent?.action == ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE) {
NoteTaskEntryPoint.QUICK_AFFORDANCE
} else {
null
}
controller.startNotesRoleSetting(this, entryPoint)
finish()
}
companion object {
const val ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE =
"com.android.systemui.action.MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE"
}
}

View File

@@ -112,6 +112,43 @@ constructor(
)
}
/** Starts the notes role setting. */
fun startNotesRoleSetting(activityContext: Context, entryPoint: NoteTaskEntryPoint?) {
val user =
if (entryPoint == null) {
userTracker.userHandle
} else {
getUserForHandlingNotesTaking(entryPoint)
}
activityContext.startActivityAsUser(
Intent(Intent.ACTION_MANAGE_DEFAULT_APP).apply {
putExtra(Intent.EXTRA_ROLE_NAME, ROLE_NOTES)
},
user
)
}
/**
* Returns the [UserHandle] of an android user that should handle the notes taking [entryPoint].
*
* On company owned personally enabled (COPE) devices, if the given [entryPoint] is in the
* [FORCE_WORK_NOTE_APPS_ENTRY_POINTS_ON_COPE_DEVICES] list, the default notes app in the work
* profile user will always be launched.
*
* On non managed devices or devices with other management modes, the current [UserHandle] is
* returned.
*/
fun getUserForHandlingNotesTaking(entryPoint: NoteTaskEntryPoint): UserHandle =
if (
entryPoint in FORCE_WORK_NOTE_APPS_ENTRY_POINTS_ON_COPE_DEVICES &&
devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile
) {
userTracker.userProfiles.firstOrNull { userManager.isManagedProfile(it.id) }?.userHandle
?: userTracker.userHandle
} else {
userTracker.userHandle
}
/**
* Shows a note task. How the task is shown will depend on when the method is invoked.
*
@@ -122,30 +159,13 @@ constructor(
* bubble is already opened.
*
* That will let users open other apps in full screen, and take contextual notes.
*
* On company owned personally enabled (COPE) devices, if the given [entryPoint] is in the
* [FORCE_WORK_NOTE_APPS_ENTRY_POINTS_ON_COPE_DEVICES] list, the default notes app in the work
* profile user will always be launched.
*/
fun showNoteTask(
entryPoint: NoteTaskEntryPoint,
) {
if (!isEnabled) return
val user: UserHandle =
if (
entryPoint in FORCE_WORK_NOTE_APPS_ENTRY_POINTS_ON_COPE_DEVICES &&
devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile
) {
userTracker.userProfiles
.firstOrNull { userManager.isManagedProfile(it.id) }
?.userHandle
?: userTracker.userHandle
} else {
userTracker.userHandle
}
showNoteTaskAsUser(entryPoint, user)
showNoteTaskAsUser(entryPoint, getUserForHandlingNotesTaking(entryPoint))
}
/** A variant of [showNoteTask] which launches note task in the given [user]. */

View File

@@ -45,6 +45,10 @@ interface NoteTaskModule {
@[Binds IntoMap ClassKey(LaunchNoteTaskManagedProfileProxyActivity::class)]
fun LaunchNoteTaskManagedProfileProxyActivity.bindNoteTaskLauncherProxyActivity(): Activity
@[Binds IntoMap ClassKey(LaunchNotesRoleSettingsTrampolineActivity::class)]
fun LaunchNotesRoleSettingsTrampolineActivity.bindLaunchNotesRoleSettingsTrampolineActivity():
Activity
companion object {
@[Provides NoteTaskEnabledKey]

View File

@@ -16,9 +16,12 @@
package com.android.systemui.notetask.quickaffordance
import android.app.role.OnRoleHoldersChangedListener
import android.app.role.RoleManager
import android.content.Context
import android.hardware.input.InputSettings
import android.os.Build
import android.os.UserHandle
import android.os.UserManager
import android.util.Log
import com.android.keyguard.KeyguardUpdateMonitor
@@ -27,17 +30,22 @@ import com.android.systemui.R
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.LockScreenState
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.PickerScreenState
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.notetask.LaunchNotesRoleSettingsTrampolineActivity.Companion.ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE
import com.android.systemui.notetask.NoteTaskController
import com.android.systemui.notetask.NoteTaskEnabledKey
import com.android.systemui.notetask.NoteTaskEntryPoint
import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE
import com.android.systemui.notetask.NoteTaskInfoResolver
import com.android.systemui.shared.customization.data.content.CustomizationProviderContract.LockScreenQuickAffordances.AffordanceTable.COMPONENT_NAME_SEPARATOR
import com.android.systemui.stylus.StylusManager
import dagger.Lazy
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.trySendBlocking
@@ -49,13 +57,16 @@ import kotlinx.coroutines.flow.onEach
class NoteTaskQuickAffordanceConfig
@Inject
constructor(
context: Context,
private val context: Context,
private val controller: NoteTaskController,
private val noteTaskInfoResolver: NoteTaskInfoResolver,
private val stylusManager: StylusManager,
private val roleManager: RoleManager,
private val keyguardMonitor: KeyguardUpdateMonitor,
private val userManager: UserManager,
private val lazyRepository: Lazy<KeyguardQuickAffordanceRepository>,
@NoteTaskEnabledKey private val isEnabled: Boolean,
@Background private val backgroundExecutor: Executor,
) : KeyguardQuickAffordanceConfig {
override val key = BuiltInKeyguardQuickAffordanceKeys.CREATE_NOTE
@@ -73,15 +84,24 @@ constructor(
val configSelectedFlow = repository.createConfigSelectedFlow(key)
val stylusEverUsedFlow = stylusManager.createStylusEverUsedFlow(context)
val userUnlockedFlow = userManager.createUserUnlockedFlow(keyguardMonitor)
combine(userUnlockedFlow, stylusEverUsedFlow, configSelectedFlow) {
val defaultNotesAppFlow =
roleManager.createNotesRoleFlow(backgroundExecutor, controller, noteTaskInfoResolver)
combine(userUnlockedFlow, stylusEverUsedFlow, configSelectedFlow, defaultNotesAppFlow) {
isUserUnlocked,
isStylusEverUsed,
isConfigSelected ->
isConfigSelected,
isDefaultNotesAppSet ->
logDebug { "lockScreenState:isUserUnlocked=$isUserUnlocked" }
logDebug { "lockScreenState:isStylusEverUsed=$isStylusEverUsed" }
logDebug { "lockScreenState:isConfigSelected=$isConfigSelected" }
logDebug { "lockScreenState:isDefaultNotesAppSet=$isDefaultNotesAppSet" }
if (isEnabled && isUserUnlocked && (isConfigSelected || isStylusEverUsed)) {
if (
isEnabled &&
isUserUnlocked &&
isDefaultNotesAppSet &&
(isConfigSelected || isStylusEverUsed)
) {
val contentDescription = ContentDescription.Resource(pickerNameResourceId)
val icon = Icon.Resource(pickerIconResourceId, contentDescription)
LockScreenState.Visible(icon)
@@ -92,15 +112,34 @@ constructor(
.onEach { state -> logDebug { "lockScreenState=$state" } }
}
override suspend fun getPickerScreenState() =
if (isEnabled) {
PickerScreenState.Default()
} else {
PickerScreenState.UnavailableOnDevice
override suspend fun getPickerScreenState(): PickerScreenState {
val isDefaultNotesAppSet =
noteTaskInfoResolver.resolveInfo(
QUICK_AFFORDANCE,
user = controller.getUserForHandlingNotesTaking(QUICK_AFFORDANCE)
) != null
return when {
isEnabled && isDefaultNotesAppSet -> PickerScreenState.Default()
isEnabled -> {
PickerScreenState.Disabled(
listOf(
context.getString(
R.string.keyguard_affordance_enablement_dialog_notes_app_instruction
)
),
context.getString(
R.string.keyguard_affordance_enablement_dialog_notes_app_action
),
"${context.packageName}$COMPONENT_NAME_SEPARATOR" +
"$ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE",
)
}
else -> PickerScreenState.UnavailableOnDevice
}
}
override fun onTriggered(expandable: Expandable?): OnTriggeredResult {
controller.showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
controller.showNoteTask(entryPoint = QUICK_AFFORDANCE)
return OnTriggeredResult.Handled
}
}
@@ -129,6 +168,27 @@ private fun StylusManager.createStylusEverUsedFlow(context: Context) = callbackF
awaitClose { unregisterCallback(callback) }
}
private fun RoleManager.createNotesRoleFlow(
executor: Executor,
noteTaskController: NoteTaskController,
noteTaskInfoResolver: NoteTaskInfoResolver,
) = callbackFlow {
fun isDefaultNotesAppSetForUser() =
noteTaskInfoResolver.resolveInfo(
QUICK_AFFORDANCE,
user = noteTaskController.getUserForHandlingNotesTaking(QUICK_AFFORDANCE)
) != null
trySendBlocking(isDefaultNotesAppSetForUser())
val callback = OnRoleHoldersChangedListener { roleName, _ ->
if (roleName == RoleManager.ROLE_NOTES) {
trySendBlocking(isDefaultNotesAppSetForUser())
}
}
addOnRoleHoldersChangedListenerAsUser(executor, callback, UserHandle.ALL)
awaitClose { removeOnRoleHoldersChangedListenerAsUser(callback, UserHandle.ALL) }
}
private fun KeyguardQuickAffordanceRepository.createConfigSelectedFlow(key: String) =
selections.map { selected ->
selected.values.flatten().any { selectedConfig -> selectedConfig.key == key }

View File

@@ -196,6 +196,17 @@
android:exported="false"
android:permission="com.android.systemui.permission.SELF"
android:excludeFromRecents="true" />
<activity
android:name="com.android.systemui.notetask.LaunchNotesRoleSettingsTrampolineActivity"
android:exported="false"
android:permission="com.android.systemui.permission.SELF"
android:excludeFromRecents="true" >
<intent-filter>
<action android:name="com.android.systemui.action.MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
<instrumentation android:name="android.testing.TestableInstrumentation"

View File

@@ -0,0 +1,86 @@
/*
* 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.notetask
import android.content.Context
import android.content.Intent
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import androidx.test.rule.ActivityTestRule
import androidx.test.runner.intercepting.SingleActivityFactory
import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
import com.android.systemui.SysuiTestCase
import com.android.systemui.notetask.LaunchNotesRoleSettingsTrampolineActivity.Companion.ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE
import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@SmallTest
@TestableLooper.RunWithLooper
class LaunchNotesRoleSettingsTrampolineActivityTest : SysuiTestCase() {
@Mock lateinit var noteTaskController: NoteTaskController
@Rule
@JvmField
val activityRule =
ActivityTestRule<LaunchNotesRoleSettingsTrampolineActivity>(
/* activityFactory= */ object :
SingleActivityFactory<LaunchNotesRoleSettingsTrampolineActivity>(
LaunchNotesRoleSettingsTrampolineActivity::class.java
) {
override fun create(intent: Intent?) =
LaunchNotesRoleSettingsTrampolineActivity(noteTaskController)
},
/* initialTouchMode= */ false,
/* launchActivity= */ false,
)
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
}
@After
fun tearDown() {
activityRule.finishActivity()
}
@Test
fun startActivity_noAction_shouldLaunchNotesRoleSettingTaskWithNullEntryPoint() {
activityRule.launchActivity(/* startIntent= */ null)
verify(noteTaskController).startNotesRoleSetting(any(Context::class.java), eq(null))
}
@Test
fun startActivity_quickAffordanceAction_shouldLaunchNotesRoleSettingTaskWithQuickAffordanceEntryPoint() { // ktlint-disable max-line-length
activityRule.launchActivity(Intent(ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE))
verify(noteTaskController)
.startNotesRoleSetting(any(Context::class.java), eq(QUICK_AFFORDANCE))
}
}

View File

@@ -47,6 +47,9 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.notetask.NoteTaskController.Companion.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE
import com.android.systemui.notetask.NoteTaskController.Companion.SETTINGS_CREATE_NOTE_TASK_SHORTCUT_COMPONENT
import com.android.systemui.notetask.NoteTaskController.Companion.SHORTCUT_ID
import com.android.systemui.notetask.NoteTaskEntryPoint.APP_CLIPS
import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE
import com.android.systemui.notetask.NoteTaskEntryPoint.TAIL_BUTTON
import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
import com.android.systemui.notetask.shortcut.LaunchNoteTaskManagedProfileProxyActivity
import com.android.systemui.settings.FakeUserTracker
@@ -493,7 +496,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
)
.thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL)
createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE)
verifyZeroInteractions(context, bubbles, eventLogger)
}
@@ -509,7 +512,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
)
.thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL)
createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE)
verifyZeroInteractions(context, bubbles, eventLogger)
}
@@ -525,7 +528,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
)
.thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL)
createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE)
verifyNoteTaskOpenInBubbleInUser(userTracker.userHandle)
}
@@ -541,7 +544,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
)
.thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL)
createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE)
verifyNoteTaskOpenInBubbleInUser(userTracker.userHandle)
}
@@ -553,7 +556,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true)
userTracker.set(listOf(mainUserInfo), mainAndWorkProfileUsers.indexOf(mainUserInfo))
createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE)
verifyNoteTaskOpenInBubbleInUser(mainUserInfo.userHandle)
}
@@ -563,7 +566,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true)
userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE)
verifyNoteTaskOpenInBubbleInUser(workUserInfo.userHandle)
}
@@ -734,6 +737,129 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
}
// endregion
// region getUserForHandlingNotesTaking
@Test
fun getUserForHandlingNotesTaking_cope_quickAffordance_shouldReturnWorkProfileUser() {
whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true)
userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
val user = createNoteTaskController().getUserForHandlingNotesTaking(QUICK_AFFORDANCE)
assertThat(user).isEqualTo(UserHandle.of(workUserInfo.id))
}
@Test
fun getUserForHandlingNotesTaking_cope_tailButton_shouldReturnWorkProfileUser() {
whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true)
userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
val user = createNoteTaskController().getUserForHandlingNotesTaking(TAIL_BUTTON)
assertThat(user).isEqualTo(UserHandle.of(workUserInfo.id))
}
@Test
fun getUserForHandlingNotesTaking_cope_appClip_shouldReturnCurrentUser() {
whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true)
userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
val user = createNoteTaskController().getUserForHandlingNotesTaking(APP_CLIPS)
assertThat(user).isEqualTo(UserHandle.of(mainUserInfo.id))
}
@Test
fun getUserForHandlingNotesTaking_noManagement_quickAffordance_shouldReturnCurrentUser() {
userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
val user = createNoteTaskController().getUserForHandlingNotesTaking(QUICK_AFFORDANCE)
assertThat(user).isEqualTo(UserHandle.of(mainUserInfo.id))
}
@Test
fun getUserForHandlingNotesTaking_noManagement_tailButton_shouldReturnCurrentUser() {
userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
val user = createNoteTaskController().getUserForHandlingNotesTaking(TAIL_BUTTON)
assertThat(user).isEqualTo(UserHandle.of(mainUserInfo.id))
}
@Test
fun getUserForHandlingNotesTaking_noManagement_appClip_shouldReturnCurrentUser() {
userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
val user = createNoteTaskController().getUserForHandlingNotesTaking(APP_CLIPS)
assertThat(user).isEqualTo(UserHandle.of(mainUserInfo.id))
}
// endregion
// startregion startNotesRoleSetting
@Test
fun startNotesRoleSetting_cope_quickAffordance_shouldStartNoteRoleIntentWithWorkProfileUser() {
whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true)
userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
createNoteTaskController().startNotesRoleSetting(context, QUICK_AFFORDANCE)
val intentCaptor = argumentCaptor<Intent>()
val userCaptor = argumentCaptor<UserHandle>()
verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
intentCaptor.value.let { intent ->
assertThat(intent).hasAction(Intent.ACTION_MANAGE_DEFAULT_APP)
}
assertThat(userCaptor.value).isEqualTo(UserHandle.of(workUserInfo.id))
}
@Test
fun startNotesRoleSetting_cope_nullEntryPoint_shouldStartNoteRoleIntentWithCurrentUser() {
whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true)
userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
createNoteTaskController().startNotesRoleSetting(context, entryPoint = null)
val intentCaptor = argumentCaptor<Intent>()
val userCaptor = argumentCaptor<UserHandle>()
verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
intentCaptor.value.let { intent ->
assertThat(intent).hasAction(Intent.ACTION_MANAGE_DEFAULT_APP)
}
assertThat(userCaptor.value).isEqualTo(UserHandle.of(mainUserInfo.id))
}
@Test
fun startNotesRoleSetting_noManagement_quickAffordance_shouldStartNoteRoleIntentWithCurrentUser() { // ktlint-disable max-line-length
userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
createNoteTaskController().startNotesRoleSetting(context, QUICK_AFFORDANCE)
val intentCaptor = argumentCaptor<Intent>()
val userCaptor = argumentCaptor<UserHandle>()
verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
intentCaptor.value.let { intent ->
assertThat(intent).hasAction(Intent.ACTION_MANAGE_DEFAULT_APP)
}
assertThat(userCaptor.value).isEqualTo(UserHandle.of(mainUserInfo.id))
}
@Test
fun startNotesRoleSetting_noManagement_nullEntryPoint_shouldStartNoteRoleIntentWithCurrentUser() { // ktlint-disable max-line-length
userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
createNoteTaskController().startNotesRoleSetting(context, entryPoint = null)
val intentCaptor = argumentCaptor<Intent>()
val userCaptor = argumentCaptor<UserHandle>()
verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
intentCaptor.value.let { intent ->
assertThat(intent).hasAction(Intent.ACTION_MANAGE_DEFAULT_APP)
}
assertThat(userCaptor.value).isEqualTo(UserHandle.of(mainUserInfo.id))
}
// endregion
private companion object {
const val NOTE_TASK_SHORT_LABEL = "Notetaking"
const val NOTE_TASK_ACTIVITY_NAME = "NoteTaskActivity"

View File

@@ -18,7 +18,12 @@
package com.android.systemui.notetask.quickaffordance
import android.app.role.RoleManager
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.ApplicationInfoFlags
import android.hardware.input.InputSettings
import android.os.UserHandle
import android.os.UserManager
import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
@@ -31,11 +36,18 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.LockScreenState
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.notetask.LaunchNotesRoleSettingsTrampolineActivity.Companion.ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE
import com.android.systemui.notetask.NoteTaskController
import com.android.systemui.notetask.NoteTaskEntryPoint
import com.android.systemui.notetask.NoteTaskInfoResolver
import com.android.systemui.shared.customization.data.content.CustomizationProviderContract.LockScreenQuickAffordances.AffordanceTable.COMPONENT_NAME_SEPARATOR
import com.android.systemui.stylus.StylusManager
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -45,6 +57,7 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.anyString
import org.mockito.Mockito.verify
import org.mockito.MockitoSession
import org.mockito.quality.Strictness
@@ -58,6 +71,8 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() {
@Mock lateinit var stylusManager: StylusManager
@Mock lateinit var repository: KeyguardQuickAffordanceRepository
@Mock lateinit var userManager: UserManager
@Mock lateinit var roleManager: RoleManager
@Mock lateinit var packageManager: PackageManager
private lateinit var mockitoSession: MockitoSession
@@ -69,6 +84,23 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() {
.mockStatic(InputSettings::class.java)
.strictness(Strictness.LENIENT)
.startMocking()
whenever(
packageManager.getApplicationInfoAsUser(
anyString(),
any(ApplicationInfoFlags::class.java),
any(UserHandle::class.java)
)
)
.thenReturn(ApplicationInfo())
whenever(controller.getUserForHandlingNotesTaking(any())).thenReturn(UserHandle.SYSTEM)
whenever(
roleManager.getRoleHoldersAsUser(
eq(RoleManager.ROLE_NOTES),
any(UserHandle::class.java)
)
)
.thenReturn(listOf("com.google.test.notes"))
}
@After
@@ -85,6 +117,9 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() {
keyguardMonitor = mock(),
lazyRepository = { repository },
isEnabled = isEnabled,
backgroundExecutor = FakeExecutor(FakeSystemClock()),
roleManager = roleManager,
noteTaskInfoResolver = NoteTaskInfoResolver(roleManager, packageManager)
)
private fun createLockScreenStateVisible(): LockScreenState =
@@ -111,6 +146,27 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() {
assertThat(actual).isEqualTo(createLockScreenStateVisible())
}
@Test
fun lockScreenState_stylusUsed_userUnlocked_isSelected_noDefaultNotesAppSet_shouldEmitHidden() =
runTest {
TestConfig()
.setStylusEverUsed(true)
.setUserUnlocked(true)
.setConfigSelections(mock<NoteTaskQuickAffordanceConfig>())
whenever(
roleManager.getRoleHoldersAsUser(
eq(RoleManager.ROLE_NOTES),
any(UserHandle::class.java)
)
)
.thenReturn(emptyList())
val underTest = createUnderTest()
val actual by collectLastValue(underTest.lockScreenState)
assertThat(actual).isEqualTo(LockScreenState.Hidden)
}
@Test
fun lockScreenState_stylusUnused_userUnlocked_isSelected_shouldEmitHidden() = runTest {
TestConfig()
@@ -217,6 +273,39 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() {
verify(controller).showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
}
// region getPickerScreenState
@Test
fun getPickerScreenState_defaultNoteAppSet_shouldReturnDefault() = runTest {
val underTest = createUnderTest(isEnabled = true)
assertThat(underTest.getPickerScreenState())
.isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default())
}
@Test
fun getPickerScreenState_nodefaultNoteAppSet_shouldReturnDisable() = runTest {
val underTest = createUnderTest(isEnabled = true)
whenever(
roleManager.getRoleHoldersAsUser(
eq(RoleManager.ROLE_NOTES),
any(UserHandle::class.java)
)
)
.thenReturn(emptyList())
assertThat(underTest.getPickerScreenState())
.isEqualTo(
KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
listOf("Select a default notes app to use notetaking shortcut"),
actionText = "Open settings",
actionComponentName =
"${context.packageName}$COMPONENT_NAME_SEPARATOR" +
"$ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE"
)
)
}
// endregion
private inner class TestConfig {
fun setStylusEverUsed(value: Boolean) = also {