Log UI events for all note-taking entry points

Bug: 261988802
Test: atest SystemUITests:NoteTaskInfoResolverTest
Test: atest SystemUITests:NoteTaskControllerTest
Test: atest SystemUITests:NoteTaskInitializerTest
Test: atest SystemUITests:NoteTaskQuickAffordanceConfigTest
Change-Id: I07fc6a8d7d71d4a2daacaf0561b31831c87de9ae
This commit is contained in:
Petr Čermák
2023-01-30 13:10:57 +00:00
parent 71592cf7ff
commit 6c4b646d4f
12 changed files with 424 additions and 195 deletions

View File

@@ -20,9 +20,12 @@ import android.app.KeyguardManager
import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.UserManager
import android.util.Log
import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
import com.android.systemui.util.kotlin.getOrNull
@@ -42,11 +45,12 @@ internal class NoteTaskController
@Inject
constructor(
private val context: Context,
private val intentResolver: NoteTaskIntentResolver,
private val resolver: NoteTaskInfoResolver,
private val optionalBubbles: Optional<Bubbles>,
private val optionalKeyguardManager: Optional<KeyguardManager>,
private val optionalUserManager: Optional<UserManager>,
@NoteTaskEnabledKey private val isEnabled: Boolean,
private val uiEventLogger: UiEventLogger,
) {
/**
@@ -64,7 +68,9 @@ constructor(
*
* That will let users open other apps in full screen, and take contextual notes.
*/
fun showNoteTask(isInMultiWindowMode: Boolean = false) {
@JvmOverloads
fun showNoteTask(isInMultiWindowMode: Boolean = false, uiEvent: ShowNoteTaskUiEvent? = null) {
if (!isEnabled) return
val bubbles = optionalBubbles.getOrNull() ?: return
@@ -74,9 +80,12 @@ constructor(
// TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing.
if (!userManager.isUserUnlocked) return
val intent = intentResolver.resolveIntent() ?: return
val noteTaskInfo = resolver.resolveInfo() ?: return
uiEvent?.let { uiEventLogger.log(it, noteTaskInfo.uid, noteTaskInfo.packageName) }
// TODO(b/266686199): We should handle when app not available. For now, we log.
val intent = noteTaskInfo.toCreateNoteIntent()
try {
if (isInMultiWindowMode || keyguardManager.isKeyguardLocked) {
context.startActivity(intent)
@@ -84,9 +93,7 @@ constructor(
bubbles.showOrHideAppBubble(intent)
}
} catch (e: ActivityNotFoundException) {
val message =
"Activity not found for action: ${NoteTaskIntentResolver.ACTION_CREATE_NOTE}."
Log.e(TAG, message, e)
Log.e(TAG, "Activity not found for action: $ACTION_CREATE_NOTE.", e)
}
}
@@ -114,10 +121,47 @@ constructor(
)
}
/** IDs of UI events accepted by [showNoteTask]. */
enum class ShowNoteTaskUiEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
@UiEvent(doc = "User opened a note by tapping on the lockscreen shortcut.")
NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE(1294),
/* ktlint-disable max-line-length */
@UiEvent(
doc =
"User opened a note by pressing the stylus tail button while the screen was unlocked."
)
NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON(1295),
@UiEvent(
doc =
"User opened a note by pressing the stylus tail button while the screen was locked."
)
NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED(1296),
@UiEvent(doc = "User opened a note by tapping on an app shortcut.")
NOTE_OPENED_VIA_SHORTCUT(1297);
override fun getId() = _id
}
companion object {
private val TAG = NoteTaskController::class.simpleName.orEmpty()
private fun NoteTaskInfoResolver.NoteTaskInfo.toCreateNoteIntent(): Intent {
return Intent(ACTION_CREATE_NOTE)
.setPackage(packageName)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
// EXTRA_USE_STYLUS_MODE does not mean a stylus is in-use, but a stylus entrypoint
// was used to start it.
.putExtra(INTENT_EXTRA_USE_STYLUS_MODE, true)
}
// TODO(b/254604589): Use final KeyEvent.KEYCODE_* instead.
const val NOTE_TASK_KEY_EVENT = 311
// TODO(b/265912743): Use Intent.ACTION_CREATE_NOTE instead.
const val ACTION_CREATE_NOTE = "android.intent.action.CREATE_NOTE"
// TODO(b/265912743): Use Intent.INTENT_EXTRA_USE_STYLUS_MODE instead.
const val INTENT_EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE"
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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.content.Context
import android.content.pm.PackageManager
import android.os.UserHandle
import android.util.Log
import javax.inject.Inject
internal class NoteTaskInfoResolver
@Inject
constructor(
private val context: Context,
private val roleManager: RoleManager,
private val packageManager: PackageManager,
) {
fun resolveInfo(): NoteTaskInfo? {
// TODO(b/267634412): Select UserHandle depending on where the user initiated note-taking.
val user = context.user
val packageName = roleManager.getRoleHoldersAsUser(ROLE_NOTES, user).firstOrNull()
if (packageName.isNullOrEmpty()) return null
return NoteTaskInfo(packageName, packageManager.getUidOf(packageName, user))
}
/** Package name and kernel user-ID of a note-taking app. */
data class NoteTaskInfo(val packageName: String, val uid: Int)
companion object {
private val TAG = NoteTaskInfoResolver::class.simpleName.orEmpty()
private val EMPTY_APPLICATION_INFO_FLAGS = PackageManager.ApplicationInfoFlags.of(0)!!
/**
* Returns the kernel user-ID of [packageName] for a [user]. Returns zero if the app cannot
* be found.
*/
private fun PackageManager.getUidOf(packageName: String, user: UserHandle): Int {
val applicationInfo =
try {
getApplicationInfoAsUser(packageName, EMPTY_APPLICATION_INFO_FLAGS, user)
} catch (e: PackageManager.NameNotFoundException) {
Log.e(TAG, "Couldn't find notes app UID", e)
return 0
}
return applicationInfo.uid
}
// TODO(b/265912743): Use RoleManager.NOTES_ROLE instead.
const val ROLE_NOTES = "android.app.role.NOTES"
}
}

View File

@@ -16,8 +16,10 @@
package com.android.systemui.notetask
import android.app.KeyguardManager
import androidx.annotation.VisibleForTesting
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.util.kotlin.getOrNull
import com.android.wm.shell.bubbles.Bubbles
import java.util.Optional
import javax.inject.Inject
@@ -30,6 +32,7 @@ constructor(
private val noteTaskController: NoteTaskController,
private val commandQueue: CommandQueue,
@NoteTaskEnabledKey private val isEnabled: Boolean,
private val optionalKeyguardManager: Optional<KeyguardManager>,
) {
@VisibleForTesting
@@ -37,11 +40,21 @@ constructor(
object : CommandQueue.Callbacks {
override fun handleSystemKey(keyCode: Int) {
if (keyCode == NoteTaskController.NOTE_TASK_KEY_EVENT) {
noteTaskController.showNoteTask()
showNoteTask()
}
}
}
private fun showNoteTask() {
val uiEvent =
if (optionalKeyguardManager.isKeyguardLocked) {
NoteTaskController.ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED
} else {
NoteTaskController.ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON
}
noteTaskController.showNoteTask(uiEvent = uiEvent)
}
fun initialize() {
if (isEnabled && optionalBubbles.isPresent) {
commandQueue.addCallback(callbacks)
@@ -49,3 +62,7 @@ constructor(
noteTaskController.setNoteTaskShortcutEnabled(isEnabled)
}
}
private val Optional<KeyguardManager>.isKeyguardLocked: Boolean
// If there's no KeyguardManager, assume that the keyguard is not locked.
get() = getOrNull()?.isKeyguardLocked ?: false

View File

@@ -1,54 +0,0 @@
/*
* Copyright (C) 2022 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.content.Context
import android.content.Intent
import javax.inject.Inject
internal class NoteTaskIntentResolver
@Inject
constructor(
private val context: Context,
private val roleManager: RoleManager,
) {
fun resolveIntent(): Intent? {
val packageName = roleManager.getRoleHoldersAsUser(ROLE_NOTES, context.user).firstOrNull()
if (packageName.isNullOrEmpty()) return null
return Intent(ACTION_CREATE_NOTE)
.setPackage(packageName)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
// EXTRA_USE_STYLUS_MODE does not mean a stylus is in-use, but a stylus entrypoint was
// used to start it.
.putExtra(INTENT_EXTRA_USE_STYLUS_MODE, true)
}
companion object {
// TODO(b/265912743): Use Intent.ACTION_CREATE_NOTE instead.
const val ACTION_CREATE_NOTE = "android.intent.action.CREATE_NOTE"
// TODO(b/265912743): Use RoleManager.NOTES_ROLE instead.
const val ROLE_NOTES = "android.app.role.NOTES"
// TODO(b/265912743): Use Intent.INTENT_EXTRA_USE_STYLUS_MODE instead.
const val INTENT_EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE"
}
}

View File

@@ -51,7 +51,7 @@ internal interface NoteTaskModule {
featureFlags: FeatureFlags,
roleManager: RoleManager,
): Boolean {
val isRoleAvailable = roleManager.isRoleAvailable(NoteTaskIntentResolver.ROLE_NOTES)
val isRoleAvailable = roleManager.isRoleAvailable(NoteTaskInfoResolver.ROLE_NOTES)
val isFeatureEnabled = featureFlags.isEnabled(Flags.NOTE_TASKS)
return isRoleAvailable && isFeatureEnabled
}

View File

@@ -27,6 +27,7 @@ import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanc
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.PickerScreenState
import com.android.systemui.notetask.NoteTaskController
import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent
import com.android.systemui.notetask.NoteTaskEnabledKey
import javax.inject.Inject
import kotlinx.coroutines.flow.flowOf
@@ -64,7 +65,9 @@ constructor(
}
override fun onTriggered(expandable: Expandable?): OnTriggeredResult {
noteTaskController.showNoteTask()
noteTaskController.showNoteTask(
uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE
)
return OnTriggeredResult.Handled
}
}

View File

@@ -21,7 +21,7 @@ import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import com.android.systemui.notetask.NoteTaskController
import com.android.systemui.notetask.NoteTaskIntentResolver
import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent
import javax.inject.Inject
/** Activity responsible for launching the note experience, and finish. */
@@ -34,7 +34,10 @@ constructor(
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
noteTaskController.showNoteTask(isInMultiWindowMode)
noteTaskController.showNoteTask(
isInMultiWindowMode = isInMultiWindowMode,
uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT,
)
finish()
}
@@ -46,7 +49,7 @@ constructor(
return Intent(context, LaunchNoteTaskActivity::class.java).apply {
// Intent's action must be set in shortcuts, or an exception will be thrown.
// TODO(b/254606432): Use Intent.ACTION_CREATE_NOTE instead.
action = NoteTaskIntentResolver.ACTION_CREATE_NOTE
action = NoteTaskController.ACTION_CREATE_NOTE
}
}
}

View File

@@ -23,10 +23,14 @@ import android.content.pm.PackageManager
import android.os.UserManager
import android.test.suitebuilder.annotation.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.ACTION_CREATE_NOTE
import com.android.systemui.notetask.NoteTaskController.Companion.INTENT_EXTRA_USE_STYLUS_MODE
import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent
import com.android.systemui.notetask.NoteTaskInfoResolver.NoteTaskInfo
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.android.wm.shell.bubbles.Bubbles
@@ -36,8 +40,8 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
/**
@@ -50,24 +54,23 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
internal class NoteTaskControllerTest : SysuiTestCase() {
private val notesIntent = Intent(ACTION_CREATE_NOTE)
@Mock lateinit var context: Context
@Mock lateinit var packageManager: PackageManager
@Mock lateinit var noteTaskIntentResolver: NoteTaskIntentResolver
@Mock lateinit var resolver: NoteTaskInfoResolver
@Mock lateinit var bubbles: Bubbles
@Mock lateinit var optionalBubbles: Optional<Bubbles>
@Mock lateinit var keyguardManager: KeyguardManager
@Mock lateinit var optionalKeyguardManager: Optional<KeyguardManager>
@Mock lateinit var optionalUserManager: Optional<UserManager>
@Mock lateinit var userManager: UserManager
@Mock lateinit var uiEventLogger: UiEventLogger
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(context.packageManager).thenReturn(packageManager)
whenever(noteTaskIntentResolver.resolveIntent()).thenReturn(notesIntent)
whenever(resolver.resolveInfo()).thenReturn(NoteTaskInfo(NOTES_PACKAGE_NAME, NOTES_UID))
whenever(optionalBubbles.orElse(null)).thenReturn(bubbles)
whenever(optionalKeyguardManager.orElse(null)).thenReturn(keyguardManager)
whenever(optionalUserManager.orElse(null)).thenReturn(userManager)
@@ -77,101 +80,182 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
private fun createNoteTaskController(isEnabled: Boolean = true): NoteTaskController {
return NoteTaskController(
context = context,
intentResolver = noteTaskIntentResolver,
resolver = resolver,
optionalBubbles = optionalBubbles,
optionalKeyguardManager = optionalKeyguardManager,
optionalUserManager = optionalUserManager,
isEnabled = isEnabled,
uiEventLogger = uiEventLogger,
)
}
// region showNoteTask
@Test
fun showNoteTask_keyguardIsLocked_shouldStartActivity() {
fun showNoteTask_keyguardIsLocked_shouldStartActivityAndLogUiEvent() {
whenever(keyguardManager.isKeyguardLocked).thenReturn(true)
createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
createNoteTaskController()
.showNoteTask(
isInMultiWindowMode = false,
uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE,
)
verify(context).startActivity(notesIntent)
verify(bubbles, never()).showOrHideAppBubble(notesIntent)
val intentCaptor = argumentCaptor<Intent>()
verify(context).startActivity(capture(intentCaptor))
intentCaptor.value.let { intent ->
assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE)
assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue()
}
verifyZeroInteractions(bubbles)
verify(uiEventLogger)
.log(
ShowNoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE,
NOTES_UID,
NOTES_PACKAGE_NAME
)
}
@Test
fun showNoteTask_keyguardIsUnlocked_shouldStartBubbles() {
fun showNoteTask_keyguardIsUnlocked_shouldStartBubblesAndLogUiEvent() {
whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
createNoteTaskController()
.showNoteTask(
isInMultiWindowMode = false,
uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON,
)
verify(bubbles).showOrHideAppBubble(notesIntent)
verify(context, never()).startActivity(notesIntent)
verifyZeroInteractions(context)
val intentCaptor = argumentCaptor<Intent>()
verify(bubbles).showOrHideAppBubble(capture(intentCaptor))
intentCaptor.value.let { intent ->
assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE)
assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue()
}
verify(uiEventLogger)
.log(
ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON,
NOTES_UID,
NOTES_PACKAGE_NAME
)
}
@Test
fun showNoteTask_isInMultiWindowMode_shouldStartActivity() {
fun showNoteTask_keyguardIsUnlocked_uiEventIsNull_shouldStartBubblesWithoutLoggingUiEvent() {
whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
createNoteTaskController().showNoteTask(isInMultiWindowMode = true)
createNoteTaskController().showNoteTask(isInMultiWindowMode = false, uiEvent = null)
verify(context).startActivity(notesIntent)
verify(bubbles, never()).showOrHideAppBubble(notesIntent)
verifyZeroInteractions(context)
val intentCaptor = argumentCaptor<Intent>()
verify(bubbles).showOrHideAppBubble(capture(intentCaptor))
intentCaptor.value.let { intent ->
assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE)
assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue()
}
verifyZeroInteractions(uiEventLogger)
}
@Test
fun showNoteTask_isInMultiWindowMode_shouldStartActivityAndLogUiEvent() {
whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
createNoteTaskController()
.showNoteTask(
isInMultiWindowMode = true,
uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT,
)
val intentCaptor = argumentCaptor<Intent>()
verify(context).startActivity(capture(intentCaptor))
intentCaptor.value.let { intent ->
assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE)
assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue()
}
verifyZeroInteractions(bubbles)
verify(uiEventLogger)
.log(ShowNoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT, NOTES_UID, NOTES_PACKAGE_NAME)
}
@Test
fun showNoteTask_bubblesIsNull_shouldDoNothing() {
whenever(optionalBubbles.orElse(null)).thenReturn(null)
createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
createNoteTaskController()
.showNoteTask(
isInMultiWindowMode = false,
uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON
)
verify(context, never()).startActivity(notesIntent)
verify(bubbles, never()).showOrHideAppBubble(notesIntent)
verifyZeroInteractions(context, bubbles, uiEventLogger)
}
@Test
fun showNoteTask_keyguardManagerIsNull_shouldDoNothing() {
whenever(optionalKeyguardManager.orElse(null)).thenReturn(null)
createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
createNoteTaskController()
.showNoteTask(
isInMultiWindowMode = false,
uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON,
)
verify(context, never()).startActivity(notesIntent)
verify(bubbles, never()).showOrHideAppBubble(notesIntent)
verifyZeroInteractions(context, bubbles, uiEventLogger)
}
@Test
fun showNoteTask_userManagerIsNull_shouldDoNothing() {
whenever(optionalUserManager.orElse(null)).thenReturn(null)
createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
createNoteTaskController()
.showNoteTask(
isInMultiWindowMode = false,
uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON,
)
verify(context, never()).startActivity(notesIntent)
verify(bubbles, never()).showOrHideAppBubble(notesIntent)
verifyZeroInteractions(context, bubbles, uiEventLogger)
}
@Test
fun showNoteTask_intentResolverReturnsNull_shouldDoNothing() {
whenever(noteTaskIntentResolver.resolveIntent()).thenReturn(null)
whenever(resolver.resolveInfo()).thenReturn(null)
createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
createNoteTaskController()
.showNoteTask(
isInMultiWindowMode = false,
uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON,
)
verify(context, never()).startActivity(notesIntent)
verify(bubbles, never()).showOrHideAppBubble(notesIntent)
verifyZeroInteractions(context, bubbles, uiEventLogger)
}
@Test
fun showNoteTask_flagDisabled_shouldDoNothing() {
createNoteTaskController(isEnabled = false).showNoteTask()
createNoteTaskController(isEnabled = false)
.showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON)
verify(context, never()).startActivity(notesIntent)
verify(bubbles, never()).showOrHideAppBubble(notesIntent)
verifyZeroInteractions(context, bubbles, uiEventLogger)
}
@Test
fun showNoteTask_userIsLocked_shouldDoNothing() {
whenever(userManager.isUserUnlocked).thenReturn(false)
createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
createNoteTaskController()
.showNoteTask(
isInMultiWindowMode = false,
uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON,
)
verify(context, never()).startActivity(notesIntent)
verify(bubbles, never()).showOrHideAppBubble(notesIntent)
verifyZeroInteractions(context, bubbles, uiEventLogger)
}
// endregion
@@ -206,4 +290,9 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
assertThat(argument.value.flattenToString()).isEqualTo(expected.flattenToString())
}
// endregion
private companion object {
const val NOTES_PACKAGE_NAME = "com.android.note.app"
const val NOTES_UID = 123456
}
}

View File

@@ -0,0 +1,108 @@
/*
* 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.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.test.suitebuilder.annotation.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.any
import org.mockito.MockitoAnnotations
/**
* Tests for [NoteTaskInfoResolver].
*
* Build/Install/Run:
* - atest SystemUITests:NoteTaskInfoResolverTest
*/
@SmallTest
@RunWith(AndroidJUnit4::class)
internal class NoteTaskInfoResolverTest : SysuiTestCase() {
@Mock lateinit var packageManager: PackageManager
@Mock lateinit var roleManager: RoleManager
private lateinit var underTest: NoteTaskInfoResolver
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
underTest = NoteTaskInfoResolver(context, roleManager, packageManager)
}
@Test
fun resolveInfo_shouldReturnInfo() {
val packageName = "com.android.note.app"
val uid = 123456
whenever(roleManager.getRoleHoldersAsUser(NoteTaskInfoResolver.ROLE_NOTES, context.user))
.then { listOf(packageName) }
whenever(
packageManager.getApplicationInfoAsUser(
eq(packageName),
any<PackageManager.ApplicationInfoFlags>(),
eq(context.user)
)
)
.thenReturn(ApplicationInfo().apply { this.uid = uid })
val actual = underTest.resolveInfo()
requireNotNull(actual) { "Note task info must not be null" }
assertThat(actual.packageName).isEqualTo(packageName)
assertThat(actual.uid).isEqualTo(uid)
}
@Test
fun resolveInfo_packageManagerThrowsException_shouldReturnInfoWithZeroUid() {
val packageName = "com.android.note.app"
whenever(roleManager.getRoleHoldersAsUser(NoteTaskInfoResolver.ROLE_NOTES, context.user))
.then { listOf(packageName) }
whenever(
packageManager.getApplicationInfoAsUser(
eq(packageName),
any<PackageManager.ApplicationInfoFlags>(),
eq(context.user)
)
)
.thenThrow(PackageManager.NameNotFoundException(packageName))
val actual = underTest.resolveInfo()
requireNotNull(actual) { "Note task info must not be null" }
assertThat(actual.packageName).isEqualTo(packageName)
assertThat(actual.uid).isEqualTo(0)
}
@Test
fun resolveInfo_noRoleHolderIsSet_shouldReturnNull() {
whenever(roleManager.getRoleHoldersAsUser(eq(NoteTaskInfoResolver.ROLE_NOTES), any()))
.then { listOf<String>() }
val actual = underTest.resolveInfo()
assertThat(actual).isNull()
}
}

View File

@@ -15,11 +15,14 @@
*/
package com.android.systemui.notetask
import android.app.KeyguardManager
import android.test.suitebuilder.annotation.SmallTest
import android.view.KeyEvent
import androidx.test.runner.AndroidJUnit4
import com.android.systemui.SysuiTestCase
import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.wm.shell.bubbles.Bubbles
import java.util.Optional
@@ -30,6 +33,7 @@ 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
/**
@@ -55,12 +59,16 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {
whenever(optionalBubbles.orElse(null)).thenReturn(bubbles)
}
private fun createNoteTaskInitializer(isEnabled: Boolean = true): NoteTaskInitializer {
private fun createNoteTaskInitializer(
isEnabled: Boolean = true,
optionalKeyguardManager: Optional<KeyguardManager> = Optional.empty(),
): NoteTaskInitializer {
return NoteTaskInitializer(
optionalBubbles = optionalBubbles,
noteTaskController = noteTaskController,
commandQueue = commandQueue,
isEnabled = isEnabled,
optionalKeyguardManager = optionalKeyguardManager,
)
}
@@ -105,19 +113,44 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {
// region handleSystemKey
@Test
fun handleSystemKey_receiveValidSystemKey_shouldShowNoteTask() {
createNoteTaskInitializer()
fun handleSystemKey_receiveValidSystemKey_keyguardNotLocked_shouldShowNoteTaskWithUnlocked() {
val keyguardManager =
mock<KeyguardManager>() { whenever(isKeyguardLocked).thenReturn(false) }
createNoteTaskInitializer(optionalKeyguardManager = Optional.of(keyguardManager))
.callbacks
.handleSystemKey(NoteTaskController.NOTE_TASK_KEY_EVENT)
verify(noteTaskController).showNoteTask()
verify(noteTaskController)
.showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON)
}
@Test
fun handleSystemKey_receiveValidSystemKey_keyguardLocked_shouldShowNoteTaskWithLocked() {
val keyguardManager =
mock<KeyguardManager>() { whenever(isKeyguardLocked).thenReturn(true) }
createNoteTaskInitializer(optionalKeyguardManager = Optional.of(keyguardManager))
.callbacks
.handleSystemKey(NoteTaskController.NOTE_TASK_KEY_EVENT)
verify(noteTaskController)
.showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED)
}
@Test
fun handleSystemKey_receiveValidSystemKey_nullKeyguardManager_shouldShowNoteTaskWithUnlocked() {
createNoteTaskInitializer(optionalKeyguardManager = Optional.empty())
.callbacks
.handleSystemKey(NoteTaskController.NOTE_TASK_KEY_EVENT)
verify(noteTaskController)
.showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON)
}
@Test
fun handleSystemKey_receiveInvalidSystemKey_shouldDoNothing() {
createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent.KEYCODE_UNKNOWN)
verify(noteTaskController, never()).showNoteTask()
verifyZeroInteractions(noteTaskController)
}
// endregion
}

View File

@@ -1,83 +0,0 @@
/*
* Copyright (C) 2022 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.content.Intent
import android.content.pm.PackageManager
import android.test.suitebuilder.annotation.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.systemui.SysuiTestCase
import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.ACTION_CREATE_NOTE
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.any
import org.mockito.MockitoAnnotations
/**
* Tests for [NoteTaskIntentResolver].
*
* Build/Install/Run:
* - atest SystemUITests:NoteTaskIntentResolverTest
*/
@SmallTest
@RunWith(AndroidJUnit4::class)
internal class NoteTaskIntentResolverTest : SysuiTestCase() {
@Mock lateinit var packageManager: PackageManager
@Mock lateinit var roleManager: RoleManager
private lateinit var underTest: NoteTaskIntentResolver
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
underTest = NoteTaskIntentResolver(context, roleManager)
}
@Test
fun resolveIntent_shouldReturnIntentInStylusMode() {
val packageName = "com.android.note.app"
whenever(roleManager.getRoleHoldersAsUser(NoteTaskIntentResolver.ROLE_NOTES, context.user))
.then { listOf(packageName) }
val actual = underTest.resolveIntent()
requireNotNull(actual) { "Intent must not be null" }
assertThat(actual.action).isEqualTo(ACTION_CREATE_NOTE)
assertThat(actual.`package`).isEqualTo(packageName)
val expectedExtra = actual.getExtra(NoteTaskIntentResolver.INTENT_EXTRA_USE_STYLUS_MODE)
assertThat(expectedExtra).isEqualTo(true)
val expectedFlag = actual.flags and Intent.FLAG_ACTIVITY_NEW_TASK
assertThat(expectedFlag).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
}
@Test
fun resolveIntent_noRoleHolderIsSet_shouldReturnNull() {
whenever(roleManager.getRoleHoldersAsUser(eq(NoteTaskIntentResolver.ROLE_NOTES), any()))
.then { listOf<String>() }
val actual = underTest.resolveIntent()
assertThat(actual).isNull()
}
}

View File

@@ -27,7 +27,7 @@ import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.LockScreenState
import com.android.systemui.notetask.NoteTaskController
import com.android.systemui.util.mockito.whenever
import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
@@ -53,7 +53,6 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() {
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(noteTaskController.showNoteTask()).then {}
}
private fun createUnderTest(isEnabled: Boolean) =
@@ -96,6 +95,7 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() {
underTest.onTriggered(expandable = null)
verify(noteTaskController).showNoteTask()
verify(noteTaskController)
.showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE)
}
}