Merge "Update Note Task shortcut when Notes Role is changed" into udc-dev

This commit is contained in:
Marcello Galhardo
2023-03-22 18:54:30 +00:00
committed by Android (Google) Code Review
9 changed files with 315 additions and 119 deletions

View File

@@ -235,7 +235,10 @@
<uses-permission android:name="android.permission.MANAGE_NOTIFICATIONS" />
<uses-permission android:name="android.permission.GET_RUNTIME_PERMISSIONS" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- role holder APIs -->
<uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" />
<uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" />
<!-- It's like, reality, but, you know, virtual -->
<uses-permission android:name="android.permission.ACCESS_VR_MANAGER" />

View File

@@ -0,0 +1,34 @@
/*
* 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
/**
* Marks declarations that are **internal** in note task API, which means that should not be used
* outside of `com.android.systemui.notetask`.
*/
@Retention(value = AnnotationRetention.BINARY)
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.FUNCTION,
AnnotationTarget.TYPEALIAS,
AnnotationTarget.PROPERTY
)
@RequiresOptIn(
level = RequiresOptIn.Level.ERROR,
message = "This is an internal API, do not it outside `com.android.systemui.notetask`",
)
internal annotation class InternalNoteTaskApi

View File

@@ -14,18 +14,21 @@
* limitations under the License.
*/
@file:OptIn(InternalNoteTaskApi::class)
package com.android.systemui.notetask
import android.app.KeyguardManager
import android.app.admin.DevicePolicyManager
import android.app.role.OnRoleHoldersChangedListener
import android.app.role.RoleManager
import android.app.role.RoleManager.ROLE_NOTES
import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK
import android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.content.pm.PackageManager
import android.content.pm.ShortcutManager
import android.os.Build
import android.os.UserHandle
import android.os.UserManager
@@ -33,6 +36,8 @@ import android.util.Log
import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled
import com.android.systemui.notetask.NoteTaskRoleManagerExt.createNoteShortcutInfoAsUser
import com.android.systemui.notetask.NoteTaskRoleManagerExt.getDefaultRoleHolderAsUser
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.kotlin.getOrNull
@@ -55,6 +60,8 @@ class NoteTaskController
@Inject
constructor(
private val context: Context,
private val roleManager: RoleManager,
private val shortcutManager: ShortcutManager,
private val resolver: NoteTaskInfoResolver,
private val eventLogger: NoteTaskEventLogger,
private val optionalBubbles: Optional<Bubbles>,
@@ -133,7 +140,7 @@ constructor(
infoReference.set(info)
// TODO(b/266686199): We should handle when app not available. For now, we log.
val intent = createNoteIntent(info)
val intent = createNoteTaskIntent(info)
try {
logDebug { "onShowNoteTask - start: $info on user#${user.identifier}" }
when (info.launchMode) {
@@ -182,30 +189,71 @@ constructor(
logDebug { "setNoteTaskShortcutEnabled - completed: $isEnabled" }
}
/**
* Updates all [NoteTaskController] related information, including but not exclusively the
* widget shortcut created by the [user] - by default it will use the current user.
*
* Keep in mind the shortcut API has a
* [rate limiting](https://developer.android.com/develop/ui/views/launch/shortcuts/managing-shortcuts#rate-limiting)
* and may not be updated in real-time. To reduce the chance of stale shortcuts, we run the
* function during System UI initialization.
*/
fun updateNoteTaskAsUser(user: UserHandle) {
val packageName = roleManager.getDefaultRoleHolderAsUser(ROLE_NOTES, user)
val hasNotesRoleHolder = isEnabled && !packageName.isNullOrEmpty()
setNoteTaskShortcutEnabled(hasNotesRoleHolder)
if (hasNotesRoleHolder) {
shortcutManager.enableShortcuts(listOf(SHORTCUT_ID))
val updatedShortcut = roleManager.createNoteShortcutInfoAsUser(context, user)
shortcutManager.updateShortcuts(listOf(updatedShortcut))
} else {
shortcutManager.disableShortcuts(listOf(SHORTCUT_ID))
}
}
/** @see OnRoleHoldersChangedListener */
fun onRoleHoldersChanged(roleName: String, user: UserHandle) {
if (roleName == ROLE_NOTES) updateNoteTaskAsUser(user)
}
companion object {
val TAG = NoteTaskController::class.simpleName.orEmpty()
const val SHORTCUT_ID = "note_task_shortcut_id"
/**
* Shortcut extra which can point to a package name and can be used to indicate an alternate
* badge info. Launcher only reads this if the shortcut comes from a system app.
*
* Duplicated from [com.android.launcher3.icons.IconCache].
*
* @see com.android.launcher3.icons.IconCache.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE
*/
const val EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE = "extra_shortcut_badge_override_package"
}
}
private fun createNoteIntent(info: NoteTaskInfo): Intent =
/** Creates an [Intent] for [ROLE_NOTES]. */
private fun createNoteTaskIntent(info: NoteTaskInfo): Intent =
Intent(Intent.ACTION_CREATE_NOTE).apply {
setPackage(info.packageName)
// EXTRA_USE_STYLUS_MODE does not mean a stylus is in-use, but a stylus entrypoint
// was used to start it.
// was used to start the note task.
putExtra(Intent.EXTRA_USE_STYLUS_MODE, true)
addFlags(FLAG_ACTIVITY_NEW_TASK)
// We should ensure the note experience can be open both as a full screen (lock screen)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
// We should ensure the note experience can be opened both as a full screen (lockscreen)
// and inside the app bubble (contextual). These additional flags will do that.
if (info.launchMode == NoteTaskLaunchMode.Activity) {
addFlags(FLAG_ACTIVITY_MULTIPLE_TASK)
addFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
}
}
private inline fun logDebug(message: () -> String) {
if (Build.IS_DEBUGGABLE) {
Log.d(NoteTaskController.TAG, message())
}
/** [Log.println] a [Log.DEBUG] message, only when [Build.IS_DEBUGGABLE]. */
private inline fun Any.logDebug(message: () -> String) {
if (Build.IS_DEBUGGABLE) Log.d(this::class.java.simpleName.orEmpty(), message())
}

View File

@@ -14,13 +14,17 @@
* limitations under the License.
*/
@file:OptIn(InternalNoteTaskApi::class)
package com.android.systemui.notetask
import android.app.role.RoleManager
import android.app.role.RoleManager.ROLE_NOTES
import android.content.pm.PackageManager
import android.content.pm.PackageManager.ApplicationInfoFlags
import android.os.UserHandle
import android.util.Log
import com.android.systemui.notetask.NoteTaskRoleManagerExt.getDefaultRoleHolderAsUser
import com.android.systemui.settings.UserTracker
import javax.inject.Inject
@@ -36,10 +40,9 @@ constructor(
entryPoint: NoteTaskEntryPoint? = null,
isKeyguardLocked: Boolean = false,
): NoteTaskInfo? {
// TODO(b/267634412): Select UserHandle depending on where the user initiated note-taking.
val user = userTracker.userHandle
val packageName =
roleManager.getRoleHoldersAsUser(RoleManager.ROLE_NOTES, user).firstOrNull()
val packageName = roleManager.getDefaultRoleHolderAsUser(ROLE_NOTES, user)
if (packageName.isNullOrEmpty()) return null

View File

@@ -15,11 +15,15 @@
*/
package com.android.systemui.notetask
import android.app.role.RoleManager
import android.os.UserHandle
import android.view.KeyEvent
import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.statusbar.CommandQueue
import com.android.wm.shell.bubbles.Bubbles
import java.util.Optional
import java.util.concurrent.Executor
import javax.inject.Inject
/** Class responsible to "glue" all note task dependencies. */
@@ -27,8 +31,10 @@ internal class NoteTaskInitializer
@Inject
constructor(
private val controller: NoteTaskController,
private val roleManager: RoleManager,
private val commandQueue: CommandQueue,
private val optionalBubbles: Optional<Bubbles>,
@Background private val backgroundExecutor: Executor,
@NoteTaskEnabledKey private val isEnabled: Boolean,
) {
@@ -43,11 +49,15 @@ constructor(
}
fun initialize() {
controller.setNoteTaskShortcutEnabled(isEnabled)
// Guard against feature not being enabled or mandatory dependencies aren't available.
if (!isEnabled || optionalBubbles.isEmpty) return
controller.setNoteTaskShortcutEnabled(true)
commandQueue.addCallback(callbacks)
roleManager.addOnRoleHoldersChangedListenerAsUser(
backgroundExecutor,
controller::onRoleHoldersChanged,
UserHandle.ALL,
)
}
}

View File

@@ -0,0 +1,61 @@
/*
* 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.app.role.RoleManager
import android.app.role.RoleManager.ROLE_NOTES
import android.content.Context
import android.content.pm.ShortcutInfo
import android.graphics.drawable.Icon
import android.os.PersistableBundle
import android.os.UserHandle
import com.android.systemui.R
import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
/** Extension functions for [RoleManager] used **internally** by note task. */
@InternalNoteTaskApi
internal object NoteTaskRoleManagerExt {
/**
* Gets package name of the default (first) app holding the [role]. If none, returns either an
* empty string or null.
*/
fun RoleManager.getDefaultRoleHolderAsUser(role: String, user: UserHandle): String? =
getRoleHoldersAsUser(role, user).firstOrNull()
/** Creates a [ShortcutInfo] for [ROLE_NOTES]. */
fun RoleManager.createNoteShortcutInfoAsUser(
context: Context,
user: UserHandle,
): ShortcutInfo {
val extras = PersistableBundle()
getDefaultRoleHolderAsUser(ROLE_NOTES, user)?.let { packageName ->
// Set custom app badge using the icon from ROLES_NOTES default app.
extras.putString(NoteTaskController.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE, packageName)
}
val icon = Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget)
return ShortcutInfo.Builder(context, NoteTaskController.SHORTCUT_ID)
.setIntent(LaunchNoteTaskActivity.newIntent(context = context))
.setShortLabel(context.getString(R.string.note_task_button_label))
.setLongLived(true)
.setIcon(icon)
.setExtras(extras)
.build()
}
}

View File

@@ -14,19 +14,17 @@
* limitations under the License.
*/
@file:OptIn(InternalNoteTaskApi::class)
package com.android.systemui.notetask.shortcut
import android.app.Activity
import android.app.role.RoleManager
import android.content.Intent
import android.content.pm.ShortcutManager
import android.os.Bundle
import android.os.PersistableBundle
import androidx.activity.ComponentActivity
import androidx.annotation.DrawableRes
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat
import com.android.systemui.R
import com.android.systemui.notetask.InternalNoteTaskApi
import com.android.systemui.notetask.NoteTaskRoleManagerExt.createNoteShortcutInfoAsUser
import javax.inject.Inject
/**
@@ -42,62 +40,16 @@ class CreateNoteTaskShortcutActivity
@Inject
constructor(
private val roleManager: RoleManager,
private val shortcutManager: ShortcutManager,
) : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val intent =
createShortcutIntent(
id = SHORTCUT_ID,
shortLabel = getString(R.string.note_task_button_label),
intent = LaunchNoteTaskActivity.newIntent(context = this),
iconResource = R.drawable.ic_note_task_shortcut_widget,
)
setResult(Activity.RESULT_OK, intent)
val shortcutInfo = roleManager.createNoteShortcutInfoAsUser(context = this, user)
val shortcutIntent = shortcutManager.createShortcutResultIntent(shortcutInfo)
setResult(Activity.RESULT_OK, shortcutIntent)
finish()
}
private fun createShortcutIntent(
id: String,
shortLabel: String,
intent: Intent,
@DrawableRes iconResource: Int,
): Intent {
val extras = PersistableBundle()
roleManager.getRoleHoldersAsUser(RoleManager.ROLE_NOTES, user).firstOrNull()?.let { name ->
extras.putString(EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE, name)
}
val shortcutInfo =
ShortcutInfoCompat.Builder(this, id)
.setIntent(intent)
.setShortLabel(shortLabel)
.setLongLived(true)
.setIcon(IconCompat.createWithResource(this, iconResource))
.setExtras(extras)
.build()
return ShortcutManagerCompat.createShortcutResultIntent(
this,
shortcutInfo,
)
}
private companion object {
private const val SHORTCUT_ID = "note-task-shortcut-id"
/**
* Shortcut extra which can point to a package name and can be used to indicate an alternate
* badge info. Launcher only reads this if the shortcut comes from a system app.
*
* Duplicated from [com.android.launcher3.icons.IconCache].
*
* @see com.android.launcher3.icons.IconCache.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE
*/
private const val EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE =
"extra_shortcut_badge_override_package"
}
}

View File

@@ -13,10 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:OptIn(InternalNoteTaskApi::class)
package com.android.systemui.notetask
import android.app.KeyguardManager
import android.app.admin.DevicePolicyManager
import android.app.role.RoleManager
import android.app.role.RoleManager.ROLE_NOTES
import android.content.ComponentName
import android.content.Context
import android.content.Intent
@@ -24,12 +28,20 @@ import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK
import android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.content.pm.PackageManager
import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED
import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED
import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager
import android.os.UserHandle
import android.os.UserManager
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.notetask.NoteTaskController.Companion.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE
import com.android.systemui.notetask.NoteTaskController.Companion.SHORTCUT_ID
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.any
@@ -47,6 +59,7 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.isNull
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
@@ -63,15 +76,17 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
@Mock lateinit var keyguardManager: KeyguardManager
@Mock lateinit var userManager: UserManager
@Mock lateinit var eventLogger: NoteTaskEventLogger
@Mock lateinit var roleManager: RoleManager
@Mock lateinit var shortcutManager: ShortcutManager
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
private val userTracker: UserTracker = FakeUserTracker()
private val noteTaskInfo = NoteTaskInfo(packageName = NOTES_PACKAGE_NAME, uid = NOTES_UID)
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(context.getString(R.string.note_task_button_label)).thenReturn(NOTES_SHORT_LABEL)
whenever(context.packageManager).thenReturn(packageManager)
whenever(resolver.resolveInfo(any(), any())).thenReturn(noteTaskInfo)
whenever(userManager.isUserUnlocked).thenReturn(true)
@@ -82,6 +97,8 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
)
)
.thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE)
whenever(roleManager.getRoleHoldersAsUser(ROLE_NOTES, userTracker.userHandle))
.thenReturn(listOf(NOTES_PACKAGE_NAME))
}
private fun createNoteTaskController(
@@ -98,6 +115,8 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
isEnabled = isEnabled,
devicePolicyManager = devicePolicyManager,
userTracker = userTracker,
roleManager = roleManager,
shortcutManager = shortcutManager,
)
// region onBubbleExpandChanged
@@ -132,7 +151,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
}
@Test
fun onBubbleExpandChanged_expandingAndKeyguardLocked_doNothing() {
fun onBubbleExpandChanged_expandingAndKeyguardLocked_shouldDoNothing() {
val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = true)
createNoteTaskController()
@@ -146,7 +165,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
}
@Test
fun onBubbleExpandChanged_notExpandingAndKeyguardLocked_doNothing() {
fun onBubbleExpandChanged_notExpandingAndKeyguardLocked_shouldDoNothing() {
val expectedInfo = noteTaskInfo.copy(isKeyguardLocked = true)
createNoteTaskController()
@@ -268,8 +287,8 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
verifyZeroInteractions(context)
val intentCaptor = argumentCaptor<Intent>()
verify(bubbles).showOrHideAppBubble(capture(intentCaptor), eq(userTracker.userHandle),
isNull())
verify(bubbles)
.showOrHideAppBubble(capture(intentCaptor), eq(userTracker.userHandle), isNull())
intentCaptor.value.let { intent ->
assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
@@ -333,11 +352,11 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
verify(context.packageManager)
.setComponentEnabledSetting(
argument.capture(),
eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED),
eq(COMPONENT_ENABLED_STATE_ENABLED),
eq(PackageManager.DONT_KILL_APP),
)
val expected = ComponentName(context, CreateNoteTaskShortcutActivity::class.java)
assertThat(argument.value.flattenToString()).isEqualTo(expected.flattenToString())
assertThat(argument.value.className)
.isEqualTo(CreateNoteTaskShortcutActivity::class.java.name)
}
@Test
@@ -348,11 +367,11 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
verify(context.packageManager)
.setComponentEnabledSetting(
argument.capture(),
eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
eq(COMPONENT_ENABLED_STATE_DISABLED),
eq(PackageManager.DONT_KILL_APP),
)
val expected = ComponentName(context, CreateNoteTaskShortcutActivity::class.java)
assertThat(argument.value.flattenToString()).isEqualTo(expected.flattenToString())
assertThat(argument.value.className)
.isEqualTo(CreateNoteTaskShortcutActivity::class.java.name)
}
// endregion
@@ -403,8 +422,8 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
val intentCaptor = argumentCaptor<Intent>()
verify(bubbles).showOrHideAppBubble(capture(intentCaptor), eq(userTracker.userHandle),
isNull())
verify(bubbles)
.showOrHideAppBubble(capture(intentCaptor), eq(userTracker.userHandle), isNull())
intentCaptor.value.let { intent ->
assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
@@ -427,8 +446,8 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE)
val intentCaptor = argumentCaptor<Intent>()
verify(bubbles).showOrHideAppBubble(capture(intentCaptor), eq(userTracker.userHandle),
isNull())
verify(bubbles)
.showOrHideAppBubble(capture(intentCaptor), eq(userTracker.userHandle), isNull())
intentCaptor.value.let { intent ->
assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
@@ -438,7 +457,78 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
}
// endregion
// region updateNoteTaskAsUser
@Test
fun updateNoteTaskAsUser_withNotesRole_withShortcuts_shouldUpdateShortcuts() {
createNoteTaskController(isEnabled = true).updateNoteTaskAsUser(userTracker.userHandle)
val actualComponent = argumentCaptor<ComponentName>()
verify(context.packageManager)
.setComponentEnabledSetting(
actualComponent.capture(),
eq(COMPONENT_ENABLED_STATE_ENABLED),
eq(PackageManager.DONT_KILL_APP),
)
assertThat(actualComponent.value.className)
.isEqualTo(CreateNoteTaskShortcutActivity::class.java.name)
verify(shortcutManager, never()).disableShortcuts(any())
verify(shortcutManager).enableShortcuts(listOf(SHORTCUT_ID))
val actualShortcuts = argumentCaptor<List<ShortcutInfo>>()
verify(shortcutManager).updateShortcuts(actualShortcuts.capture())
val actualShortcut = actualShortcuts.value.first()
assertThat(actualShortcut.id).isEqualTo(SHORTCUT_ID)
assertThat(actualShortcut.intent?.component?.className)
.isEqualTo(LaunchNoteTaskActivity::class.java.name)
assertThat(actualShortcut.intent?.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
assertThat(actualShortcut.shortLabel).isEqualTo(NOTES_SHORT_LABEL)
assertThat(actualShortcut.isLongLived).isEqualTo(true)
assertThat(actualShortcut.icon.resId).isEqualTo(R.drawable.ic_note_task_shortcut_widget)
assertThat(actualShortcut.extras?.getString(EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE))
.isEqualTo(NOTES_PACKAGE_NAME)
}
@Test
fun updateNoteTaskAsUser_noNotesRole_shouldDisableShortcuts() {
whenever(roleManager.getRoleHoldersAsUser(ROLE_NOTES, userTracker.userHandle))
.thenReturn(emptyList())
createNoteTaskController(isEnabled = true).updateNoteTaskAsUser(userTracker.userHandle)
val argument = argumentCaptor<ComponentName>()
verify(context.packageManager)
.setComponentEnabledSetting(
argument.capture(),
eq(COMPONENT_ENABLED_STATE_DISABLED),
eq(PackageManager.DONT_KILL_APP),
)
assertThat(argument.value.className)
.isEqualTo(CreateNoteTaskShortcutActivity::class.java.name)
verify(shortcutManager).disableShortcuts(listOf(SHORTCUT_ID))
verify(shortcutManager, never()).enableShortcuts(any())
verify(shortcutManager, never()).updateShortcuts(any())
}
@Test
fun updateNoteTaskAsUser_flagDisabled_shouldDisableShortcuts() {
createNoteTaskController(isEnabled = false).updateNoteTaskAsUser(userTracker.userHandle)
val argument = argumentCaptor<ComponentName>()
verify(context.packageManager)
.setComponentEnabledSetting(
argument.capture(),
eq(COMPONENT_ENABLED_STATE_DISABLED),
eq(PackageManager.DONT_KILL_APP),
)
assertThat(argument.value.className)
.isEqualTo(CreateNoteTaskShortcutActivity::class.java.name)
verify(shortcutManager).disableShortcuts(listOf(SHORTCUT_ID))
verify(shortcutManager, never()).enableShortcuts(any())
verify(shortcutManager, never()).updateShortcuts(any())
}
// endregion
private companion object {
const val NOTES_SHORT_LABEL = "Notetaking"
const val NOTES_PACKAGE_NAME = "com.android.note.app"
const val NOTES_UID = 123456
}

View File

@@ -15,36 +15,37 @@
*/
package com.android.systemui.notetask
import android.app.role.RoleManager
import android.test.suitebuilder.annotation.SmallTest
import android.view.KeyEvent
import androidx.test.runner.AndroidJUnit4
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.time.FakeSystemClock
import com.android.wm.shell.bubbles.Bubbles
import java.util.Optional
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
/**
* Tests for [NoteTaskController].
*
* Build/Install/Run:
* - atest SystemUITests:NoteTaskInitializerTest
*/
/** atest SystemUITests:NoteTaskInitializerTest */
@SmallTest
@RunWith(AndroidJUnit4::class)
internal class NoteTaskInitializerTest : SysuiTestCase() {
@Mock lateinit var commandQueue: CommandQueue
@Mock lateinit var bubbles: Bubbles
@Mock lateinit var noteTaskController: NoteTaskController
@Mock lateinit var controller: NoteTaskController
@Mock lateinit var roleManager: RoleManager
private val clock = FakeSystemClock()
private val executor = FakeExecutor(clock)
@Before
fun setUp() {
@@ -56,47 +57,41 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {
bubbles: Bubbles? = this.bubbles,
): NoteTaskInitializer {
return NoteTaskInitializer(
controller = noteTaskController,
controller = controller,
commandQueue = commandQueue,
optionalBubbles = Optional.ofNullable(bubbles),
isEnabled = isEnabled,
roleManager = roleManager,
backgroundExecutor = executor,
)
}
// region initializer
@Test
fun initialize_shouldAddCallbacks() {
fun initialize() {
createNoteTaskInitializer().initialize()
verify(controller).setNoteTaskShortcutEnabled(true)
verify(commandQueue).addCallback(any())
verify(roleManager).addOnRoleHoldersChangedListenerAsUser(any(), any(), any())
}
@Test
fun initialize_flagDisabled_shouldDoNothing() {
fun initialize_flagDisabled() {
createNoteTaskInitializer(isEnabled = false).initialize()
verify(controller, never()).setNoteTaskShortcutEnabled(any())
verify(commandQueue, never()).addCallback(any())
verify(roleManager, never()).addOnRoleHoldersChangedListenerAsUser(any(), any(), any())
}
@Test
fun initialize_bubblesNotPresent_shouldDoNothing() {
fun initialize_bubblesNotPresent() {
createNoteTaskInitializer(bubbles = null).initialize()
verify(controller, never()).setNoteTaskShortcutEnabled(any())
verify(commandQueue, never()).addCallback(any())
}
@Test
fun initialize_flagEnabled_shouldEnableShortcut() {
createNoteTaskInitializer().initialize()
verify(noteTaskController).setNoteTaskShortcutEnabled(true)
}
@Test
fun initialize_flagDisabled_shouldDisableShortcut() {
createNoteTaskInitializer(isEnabled = false).initialize()
verify(noteTaskController).setNoteTaskShortcutEnabled(false)
verify(roleManager, never()).addOnRoleHoldersChangedListenerAsUser(any(), any(), any())
}
// endregion
@@ -105,14 +100,14 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {
fun handleSystemKey_receiveValidSystemKey_shouldShowNoteTask() {
createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL)
verify(noteTaskController).showNoteTask(entryPoint = NoteTaskEntryPoint.TAIL_BUTTON)
verify(controller).showNoteTask(entryPoint = NoteTaskEntryPoint.TAIL_BUTTON)
}
@Test
fun handleSystemKey_receiveInvalidSystemKey_shouldDoNothing() {
createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent.KEYCODE_UNKNOWN)
verifyZeroInteractions(noteTaskController)
verifyZeroInteractions(controller)
}
// endregion
}