diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 650d5fabeb85a..904f36a9ad00b 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -235,7 +235,10 @@
+
+
+
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/InternalNoteTaskApi.kt b/packages/SystemUI/src/com/android/systemui/notetask/InternalNoteTaskApi.kt
new file mode 100644
index 0000000000000..5d03218406d00
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/InternalNoteTaskApi.kt
@@ -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
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index 58ac5b30972fe..93ed8591e738d 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -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,
@@ -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())
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt
index 8ecf08192e293..616f9b5261561 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt
@@ -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
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
index fb3c0cb54f843..04ed08b6fc209 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -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,
+ @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,
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
new file mode 100644
index 0000000000000..441b9f5d01819
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
@@ -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()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt
index 5c59532e0c2e9..0cfb0a5218200 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt
@@ -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"
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 0ee52ea7838aa..e64094675ff5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -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()
- 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()
- 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()
- 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()
+ 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>()
+ 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()
+ 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()
+ 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
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
index 46e02788b2df0..cd67e8d0a4c27 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
@@ -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
}