/* * 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. */ @file:OptIn(InternalNoteTaskApi::class) package com.android.systemui.notetask import android.app.ActivityManager 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 import android.content.Intent.ACTION_CREATE_NOTE import android.content.Intent.ACTION_MAIN import android.content.Intent.ACTION_MANAGE_DEFAULT_APP import android.content.Intent.CATEGORY_HOME import android.content.Intent.EXTRA_USE_STYLUS_MODE 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.ShortcutManager import android.content.pm.UserInfo import android.graphics.drawable.Icon import android.os.UserHandle import android.os.UserManager import android.provider.Settings import androidx.test.ext.truth.content.IntentSubject.assertThat 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.NoteTaskEntryPoint.APP_CLIPS import com.android.systemui.notetask.NoteTaskEntryPoint.KEYBOARD_SHORTCUT import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE import com.android.systemui.notetask.NoteTaskEntryPoint.TAIL_BUTTON import com.android.systemui.notetask.NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity import com.android.systemui.settings.FakeUserTracker import com.android.systemui.util.mockito.any 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.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.withArgCaptor import com.android.systemui.util.settings.SecureSettings import com.android.wm.shell.bubbles.Bubble import com.android.wm.shell.bubbles.Bubbles import com.google.common.truth.Truth.assertThat import java.util.Optional import kotlin.test.assertNotNull import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito.atLeastOnce import org.mockito.Mockito.doNothing import org.mockito.Mockito.never import org.mockito.Mockito.spy import org.mockito.Mockito.verify import org.mockito.Mockito.verifyZeroInteractions import org.mockito.MockitoAnnotations /** atest SystemUITests:NoteTaskControllerTest */ @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) internal class NoteTaskControllerTest : SysuiTestCase() { @Mock private lateinit var context: Context @Mock private lateinit var workProfileContext: Context @Mock private lateinit var packageManager: PackageManager @Mock private lateinit var workProfilePackageManager: PackageManager @Mock private lateinit var resolver: NoteTaskInfoResolver @Mock private lateinit var bubbles: Bubbles @Mock private lateinit var keyguardManager: KeyguardManager @Mock private lateinit var userManager: UserManager @Mock private lateinit var eventLogger: NoteTaskEventLogger @Mock private lateinit var roleManager: RoleManager @Mock private lateinit var shortcutManager: ShortcutManager @Mock private lateinit var activityManager: ActivityManager @Mock private lateinit var devicePolicyManager: DevicePolicyManager @Mock private lateinit var secureSettings: SecureSettings private val userTracker = FakeUserTracker() private val testDispatcher = UnconfinedTestDispatcher() private val testScope = TestScope(testDispatcher) @Before fun setUp() { MockitoAnnotations.initMocks(this) whenever(context.getString(R.string.note_task_button_label)) .thenReturn(NOTE_TASK_SHORT_LABEL) whenever(context.getString(eq(R.string.note_task_shortcut_long_label), any())) .thenReturn(NOTE_TASK_LONG_LABEL) whenever(context.packageManager).thenReturn(packageManager) whenever(context.createContextAsUser(any(), any())).thenReturn(context) whenever(packageManager.getApplicationInfo(any(), any())).thenReturn(mock()) whenever(packageManager.getApplicationLabel(any())).thenReturn(NOTE_TASK_LONG_LABEL) whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(NOTE_TASK_INFO) whenever(userManager.isUserUnlocked).thenReturn(true) whenever(userManager.isUserUnlocked(any())).thenReturn(true) whenever(userManager.isUserUnlocked(any())).thenReturn(true) whenever( devicePolicyManager.getKeyguardDisabledFeatures( /* admin= */ eq(null), /* userHandle= */ anyInt() ) ) .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE) whenever(roleManager.getRoleHoldersAsUser(ROLE_NOTES, userTracker.userHandle)) .thenReturn(listOf(NOTE_TASK_PACKAGE_NAME)) whenever(activityManager.getRunningTasks(anyInt())).thenReturn(emptyList()) whenever(userManager.isManagedProfile(workUserInfo.id)).thenReturn(true) whenever(context.resources).thenReturn(getContext().resources) whenever(secureSettings.userTracker).thenReturn(userTracker) } private fun createNoteTaskController( isEnabled: Boolean = true, bubbles: Bubbles? = this.bubbles, ): NoteTaskController = NoteTaskController( context = context, resolver = resolver, eventLogger = eventLogger, userManager = userManager, keyguardManager = keyguardManager, isEnabled = isEnabled, devicePolicyManager = devicePolicyManager, userTracker = userTracker, roleManager = roleManager, shortcutManager = shortcutManager, activityManager = activityManager, secureSettings = secureSettings, noteTaskBubblesController = FakeNoteTaskBubbleController(context, testDispatcher, Optional.ofNullable(bubbles)), applicationScope = testScope, ) // region onBubbleExpandChanged @Test fun onBubbleExpandChanged_expanding_logNoteTaskOpened() { val expectedInfo = NOTE_TASK_INFO.copy(isKeyguardLocked = false) createNoteTaskController() .apply { infoReference.set(expectedInfo) } .onBubbleExpandChanged( isExpanding = true, key = Bubble.getAppBubbleKeyForApp(expectedInfo.packageName, expectedInfo.user), ) verify(eventLogger).logNoteTaskOpened(expectedInfo) verifyZeroInteractions(bubbles, keyguardManager, userManager) } @Test fun onBubbleExpandChanged_collapsing_logNoteTaskClosed() { val expectedInfo = NOTE_TASK_INFO.copy(isKeyguardLocked = false) createNoteTaskController() .apply { infoReference.set(expectedInfo) } .onBubbleExpandChanged( isExpanding = false, key = Bubble.getAppBubbleKeyForApp(expectedInfo.packageName, expectedInfo.user), ) verify(eventLogger).logNoteTaskClosed(expectedInfo) verifyZeroInteractions(bubbles, keyguardManager, userManager) } @Test fun onBubbleExpandChanged_expandingAndKeyguardLocked_shouldDoNothing() { val expectedInfo = NOTE_TASK_INFO.copy(isKeyguardLocked = true) createNoteTaskController() .apply { infoReference.set(expectedInfo) } .onBubbleExpandChanged( isExpanding = true, key = Bubble.getAppBubbleKeyForApp(expectedInfo.packageName, expectedInfo.user), ) verifyZeroInteractions(bubbles, keyguardManager, userManager, eventLogger) } @Test fun onBubbleExpandChanged_notExpandingAndKeyguardLocked_shouldDoNothing() { val expectedInfo = NOTE_TASK_INFO.copy(isKeyguardLocked = true) createNoteTaskController() .apply { infoReference.set(expectedInfo) } .onBubbleExpandChanged( isExpanding = false, key = Bubble.getAppBubbleKeyForApp(expectedInfo.packageName, expectedInfo.user), ) verifyZeroInteractions(bubbles, keyguardManager, userManager, eventLogger) } @Test fun onBubbleExpandChanged_notKeyAppBubble_shouldDoNothing() { createNoteTaskController() .onBubbleExpandChanged( isExpanding = true, key = "any other key", ) verifyZeroInteractions(bubbles, keyguardManager, userManager, eventLogger) } @Test fun onBubbleExpandChanged_flagDisabled_shouldDoNothing() { createNoteTaskController(isEnabled = false) .onBubbleExpandChanged( isExpanding = true, key = Bubble.getAppBubbleKeyForApp(NOTE_TASK_INFO.packageName, NOTE_TASK_INFO.user), ) verifyZeroInteractions(bubbles, keyguardManager, userManager, eventLogger) } // endregion // region showNoteTask fun showNoteTaskAsUser_keyguardIsLocked_shouldStartActivityWithExpectedUserAndLogUiEvent() { val user10 = UserHandle.of(/* userId= */ 10) val expectedInfo = NOTE_TASK_INFO.copy( entryPoint = TAIL_BUTTON, isKeyguardLocked = true, user = user10, ) whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked) whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo) createNoteTaskController() .showNoteTaskAsUser(entryPoint = expectedInfo.entryPoint!!, user = user10) val intentCaptor = argumentCaptor() val userCaptor = argumentCaptor() verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor)) assertThat(intentCaptor.value).run { hasAction(ACTION_CREATE_NOTE) hasPackage(NOTE_TASK_PACKAGE_NAME) hasFlags(FLAG_ACTIVITY_NEW_TASK) hasFlags(FLAG_ACTIVITY_MULTIPLE_TASK) hasFlags(FLAG_ACTIVITY_NEW_DOCUMENT) extras().bool(EXTRA_USE_STYLUS_MODE).isTrue() } assertThat(userCaptor.value).isEqualTo(user10) verify(eventLogger).logNoteTaskOpened(expectedInfo) verifyZeroInteractions(bubbles) } @Test fun showNoteTask_keyguardIsLocked_notesIsClosed_shouldStartActivityAndLogUiEvent() { val expectedInfo = NOTE_TASK_INFO.copy(entryPoint = TAIL_BUTTON, isKeyguardLocked = true) whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked) whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo) createNoteTaskController().showNoteTask(entryPoint = expectedInfo.entryPoint!!) val intentCaptor = argumentCaptor() val userCaptor = argumentCaptor() verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor)) assertThat(intentCaptor.value).run { hasAction(ACTION_CREATE_NOTE) hasPackage(NOTE_TASK_PACKAGE_NAME) hasFlags(FLAG_ACTIVITY_NEW_TASK) hasFlags(FLAG_ACTIVITY_MULTIPLE_TASK) hasFlags(FLAG_ACTIVITY_NEW_DOCUMENT) extras().bool(EXTRA_USE_STYLUS_MODE).isTrue() } assertThat(userCaptor.value).isEqualTo(userTracker.userHandle) verify(eventLogger).logNoteTaskOpened(expectedInfo) verifyZeroInteractions(bubbles) } @Test fun showNoteTask_keyguardIsLocked_noteIsOpen_shouldCloseActivityAndLogUiEvent() { val expectedInfo = NOTE_TASK_INFO.copy(entryPoint = TAIL_BUTTON, isKeyguardLocked = true) whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked) whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo) whenever(activityManager.getRunningTasks(anyInt())) .thenReturn(listOf(NOTE_RUNNING_TASK_INFO)) createNoteTaskController().showNoteTask(entryPoint = expectedInfo.entryPoint!!) val intentCaptor = argumentCaptor() val userCaptor = argumentCaptor() verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor)) assertThat(intentCaptor.value).run { hasAction(ACTION_MAIN) categories().contains(CATEGORY_HOME) hasFlags(FLAG_ACTIVITY_NEW_TASK) } assertThat(userCaptor.value).isEqualTo(userTracker.userHandle) verify(eventLogger).logNoteTaskClosed(expectedInfo) verifyZeroInteractions(bubbles) } @Test fun showNoteTask_keyguardIsUnlocked_noteIsClosed_shouldStartBubblesWithoutLoggingUiEvent() { val expectedInfo = NOTE_TASK_INFO.copy(entryPoint = TAIL_BUTTON, isKeyguardLocked = false) whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo) whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked) createNoteTaskController().showNoteTask(entryPoint = expectedInfo.entryPoint!!) // Context package name used to create bubble icon from drawable resource id verify(context, atLeastOnce()).packageName verifyNoteTaskOpenInBubbleInUser(userTracker.userHandle) verifyZeroInteractions(eventLogger) } @Test fun showNoteTask_keyguardIsUnlocked_noteIsOpen_shouldStartBubblesWithoutLoggingUiEvent() { val expectedInfo = NOTE_TASK_INFO.copy(entryPoint = TAIL_BUTTON, isKeyguardLocked = false) whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo) whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked) whenever(activityManager.getRunningTasks(anyInt())) .thenReturn(listOf(NOTE_RUNNING_TASK_INFO)) createNoteTaskController().showNoteTask(entryPoint = expectedInfo.entryPoint!!) // Context package name used to create bubble icon from drawable resource id verify(context, atLeastOnce()).packageName verifyNoteTaskOpenInBubbleInUser(userTracker.userHandle) verifyZeroInteractions(eventLogger) } @Test fun showNoteTask_defaultUserSet_shouldStartActivityWithExpectedUserAndLogUiEvent() { whenever( secureSettings.getIntForUser( /* name= */ eq(Settings.Secure.DEFAULT_NOTE_TASK_PROFILE), /* def= */ any(), /* userHandle= */ any() ) ) .thenReturn(10) val user10 = UserHandle.of(/* userId= */ 10) val expectedInfo = NOTE_TASK_INFO.copy( entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, isKeyguardLocked = true, user = user10, ) whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked) whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo) createNoteTaskController() .showNoteTask( entryPoint = expectedInfo.entryPoint!!, ) val intentCaptor = argumentCaptor() val userCaptor = argumentCaptor() verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor)) intentCaptor.value.let { intent -> assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE) assertThat(intent.`package`).isEqualTo(NOTE_TASK_PACKAGE_NAME) assertThat(intent.flags and FLAG_ACTIVITY_NEW_TASK).isEqualTo(FLAG_ACTIVITY_NEW_TASK) assertThat(intent.flags and FLAG_ACTIVITY_MULTIPLE_TASK) .isEqualTo(FLAG_ACTIVITY_MULTIPLE_TASK) assertThat(intent.flags and FLAG_ACTIVITY_NEW_DOCUMENT) .isEqualTo(FLAG_ACTIVITY_NEW_DOCUMENT) assertThat(intent.getBooleanExtra(Intent.EXTRA_USE_STYLUS_MODE, false)).isTrue() } assertThat(userCaptor.value).isEqualTo(user10) verify(eventLogger).logNoteTaskOpened(expectedInfo) verifyZeroInteractions(bubbles) } @Test fun showNoteTask_bubblesIsNull_shouldDoNothing() { createNoteTaskController(bubbles = null).showNoteTask(entryPoint = TAIL_BUTTON) verifyZeroInteractions(bubbles, eventLogger) } @Test fun showNoteTask_intentResolverReturnsNull_shouldShowToast() { whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(null) val noteTaskController = spy(createNoteTaskController()) doNothing().whenever(noteTaskController).showNoDefaultNotesAppToast() noteTaskController.showNoteTask(entryPoint = TAIL_BUTTON) verify(noteTaskController).showNoDefaultNotesAppToast() verifyZeroInteractions(bubbles, eventLogger) } @Test fun showNoteTask_flagDisabled_shouldDoNothing() { createNoteTaskController(isEnabled = false).showNoteTask(entryPoint = TAIL_BUTTON) verifyZeroInteractions(bubbles, eventLogger) } @Test fun showNoteTask_userIsLocked_shouldDoNothing() { whenever(userManager.isUserUnlocked).thenReturn(false) createNoteTaskController().showNoteTask(entryPoint = TAIL_BUTTON) verifyZeroInteractions(bubbles, eventLogger) } @Test fun showNoteTask_keyboardShortcut_shouldStartActivity() { val expectedInfo = NOTE_TASK_INFO.copy(entryPoint = KEYBOARD_SHORTCUT, isKeyguardLocked = true) whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked) whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo) createNoteTaskController().showNoteTask(entryPoint = expectedInfo.entryPoint!!) val intentCaptor = argumentCaptor() val userCaptor = argumentCaptor() verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor)) assertThat(intentCaptor.value).run { hasAction(ACTION_CREATE_NOTE) hasPackage(NOTE_TASK_PACKAGE_NAME) hasFlags(FLAG_ACTIVITY_NEW_TASK) hasFlags(FLAG_ACTIVITY_MULTIPLE_TASK) hasFlags(FLAG_ACTIVITY_NEW_DOCUMENT) extras().bool(EXTRA_USE_STYLUS_MODE).isFalse() } assertThat(userCaptor.value).isEqualTo(userTracker.userHandle) verify(eventLogger).logNoteTaskOpened(expectedInfo) verifyZeroInteractions(bubbles) } // endregion // region setNoteTaskShortcutEnabled @Test fun setNoteTaskShortcutEnabled_setTrue() { createNoteTaskController().setNoteTaskShortcutEnabled(value = true, userTracker.userHandle) val argument = argumentCaptor() verify(context.packageManager) .setComponentEnabledSetting( argument.capture(), eq(COMPONENT_ENABLED_STATE_ENABLED), eq(PackageManager.DONT_KILL_APP), ) assertThat(argument.value.className) .isEqualTo(CreateNoteTaskShortcutActivity::class.java.name) } @Test fun setNoteTaskShortcutEnabled_setFalse() { createNoteTaskController().setNoteTaskShortcutEnabled(value = false, 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) } @Test fun setNoteTaskShortcutEnabled_workProfileUser_setTrue() { whenever(context.createContextAsUser(eq(workUserInfo.userHandle), any())) .thenReturn(workProfileContext) whenever(workProfileContext.packageManager).thenReturn(workProfilePackageManager) userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo)) createNoteTaskController().setNoteTaskShortcutEnabled(value = true, workUserInfo.userHandle) val argument = argumentCaptor() verify(workProfilePackageManager) .setComponentEnabledSetting( argument.capture(), eq(COMPONENT_ENABLED_STATE_ENABLED), eq(PackageManager.DONT_KILL_APP), ) assertThat(argument.value.className) .isEqualTo(CreateNoteTaskShortcutActivity::class.java.name) } @Test fun setNoteTaskShortcutEnabled_workProfileUser_setFalse() { whenever(context.createContextAsUser(eq(workUserInfo.userHandle), any())) .thenReturn(workProfileContext) whenever(workProfileContext.packageManager).thenReturn(workProfilePackageManager) userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo)) createNoteTaskController() .setNoteTaskShortcutEnabled(value = false, workUserInfo.userHandle) val argument = argumentCaptor() verify(workProfilePackageManager) .setComponentEnabledSetting( argument.capture(), eq(COMPONENT_ENABLED_STATE_DISABLED), eq(PackageManager.DONT_KILL_APP), ) assertThat(argument.value.className) .isEqualTo(CreateNoteTaskShortcutActivity::class.java.name) } // endregion // region keyguard policy @Test fun showNoteTask_keyguardLocked_keyguardDisableShortcutsAll_shouldDoNothing() { whenever(keyguardManager.isKeyguardLocked).thenReturn(true) whenever( devicePolicyManager.getKeyguardDisabledFeatures( /* admin= */ eq(null), /* userHandle= */ anyInt() ) ) .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL) createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE) verifyZeroInteractions(bubbles, eventLogger) } @Test fun showNoteTask_keyguardLocked_keyguardDisableFeaturesAll_shouldDoNothing() { whenever(keyguardManager.isKeyguardLocked).thenReturn(true) whenever( devicePolicyManager.getKeyguardDisabledFeatures( /* admin= */ eq(null), /* userHandle= */ anyInt() ) ) .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL) createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE) verifyZeroInteractions(bubbles, eventLogger) } @Test fun showNoteTask_keyguardUnlocked_keyguardDisableShortcutsAll_shouldStartBubble() { whenever(keyguardManager.isKeyguardLocked).thenReturn(false) whenever( devicePolicyManager.getKeyguardDisabledFeatures( /* admin= */ eq(null), /* userHandle= */ anyInt() ) ) .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL) createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE) verifyNoteTaskOpenInBubbleInUser(userTracker.userHandle) } @Test fun showNoteTask_keyguardUnlocked_keyguardDisableFeaturesAll_shouldStartBubble() { whenever(keyguardManager.isKeyguardLocked).thenReturn(false) whenever( devicePolicyManager.getKeyguardDisabledFeatures( /* admin= */ eq(null), /* userHandle= */ anyInt() ) ) .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL) createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE) verifyNoteTaskOpenInBubbleInUser(userTracker.userHandle) } // endregion // region showNoteTask, COPE devices @Test fun showNoteTask_copeDevices_quickAffordanceEntryPoint_managedProfileNotFound_shouldStartBubbleInTheMainProfile() { // ktlint-disable max-line-length whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true) userTracker.set(listOf(mainUserInfo), mainAndWorkProfileUsers.indexOf(mainUserInfo)) createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE) verifyNoteTaskOpenInBubbleInUser(mainUserInfo.userHandle) } @Test fun showNoteTask_copeDevices_quickAffordanceEntryPoint_shouldStartBubbleInWorkProfile() { whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true) userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo)) createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE) verifyNoteTaskOpenInBubbleInUser(workUserInfo.userHandle) } @Test fun showNoteTask_copeDevices_tailButtonEntryPoint_shouldStartBubbleInTheUserSelectedUser() { whenever( secureSettings.getIntForUser( /* name= */ eq(Settings.Secure.DEFAULT_NOTE_TASK_PROFILE), /* def= */ any(), /* userHandle= */ any() ) ) .thenReturn(mainUserInfo.id) whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true) userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo)) createNoteTaskController().showNoteTask(entryPoint = TAIL_BUTTON) verifyNoteTaskOpenInBubbleInUser(mainUserInfo.userHandle) } @Test fun showNoteTask_copeDevices_shortcutsEntryPoint_shouldStartBubbleInTheSelectedUser() { whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true) userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo)) createNoteTaskController().showNoteTask(entryPoint = WIDGET_PICKER_SHORTCUT) verifyNoteTaskOpenInBubbleInUser(mainUserInfo.userHandle) } @Test fun showNoteTask_copeDevices_appClipsEntryPoint_shouldStartBubbleInTheSelectedUser() { whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true) userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo)) createNoteTaskController().showNoteTask(entryPoint = APP_CLIPS) verifyNoteTaskOpenInBubbleInUser(mainUserInfo.userHandle) } // endregion private fun verifyNoteTaskOpenInBubbleInUser(userHandle: UserHandle) { val intentCaptor = argumentCaptor() val iconCaptor = argumentCaptor() verify(bubbles) .showOrHideAppBubble(capture(intentCaptor), eq(userHandle), capture(iconCaptor)) assertThat(intentCaptor.value).run { hasAction(ACTION_CREATE_NOTE) hasPackage(NOTE_TASK_PACKAGE_NAME) hasFlags(FLAG_ACTIVITY_NEW_TASK) extras().bool(EXTRA_USE_STYLUS_MODE).isTrue() } iconCaptor.value?.let { icon -> assertNotNull(icon) assertThat(icon.resId).isEqualTo(R.drawable.ic_note_task_shortcut_widget) } } // region onRoleHoldersChanged @Test fun onRoleHoldersChanged_notNotesRole_doNothing() { val user = UserHandle.of(0) createNoteTaskController(isEnabled = true).onRoleHoldersChanged("NOT_NOTES", user) verify(context, never()).startActivityAsUser(any(), any()) } @Test fun onRoleHoldersChanged_notesRole_shouldUpdateShortcuts() { val user = userTracker.userHandle val controller = spy(createNoteTaskController()) doNothing().whenever(controller).updateNoteTaskAsUser(any()) controller.onRoleHoldersChanged(ROLE_NOTES, user) verify(controller).updateNoteTaskAsUser(user) } // endregion // region updateNoteTaskAsUser @Test fun updateNoteTaskAsUser_sameUser_shouldUpdateShortcuts() { val user = UserHandle.CURRENT val controller = spy(createNoteTaskController()) doNothing().whenever(controller).updateNoteTaskAsUserInternal(any()) whenever(controller.getCurrentRunningUser()).thenReturn(user) controller.updateNoteTaskAsUser(user) verify(controller).updateNoteTaskAsUserInternal(user) verify(context, never()).startServiceAsUser(any(), any()) } @Test fun updateNoteTaskAsUser_differentUser_shouldUpdateShortcutsInUserProcess() { val user = UserHandle.CURRENT val controller = spy(createNoteTaskController(isEnabled = true)) doNothing().whenever(controller).updateNoteTaskAsUserInternal(any()) whenever(controller.getCurrentRunningUser()).thenReturn(UserHandle.SYSTEM) controller.updateNoteTaskAsUser(user) verify(controller, never()).updateNoteTaskAsUserInternal(any()) val intent = withArgCaptor { verify(context).startServiceAsUser(capture(), eq(user)) } assertThat(intent).hasComponentClass(NoteTaskControllerUpdateService::class.java) } // endregion // region internalUpdateNoteTaskAsUser @Test fun updateNoteTaskAsUserInternal_withNotesRole_withShortcuts_shouldUpdateShortcuts() { createNoteTaskController(isEnabled = true) .updateNoteTaskAsUserInternal(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 shortcutInfo = withArgCaptor { verify(shortcutManager).updateShortcuts(capture()) } with(shortcutInfo.first()) { assertThat(id).isEqualTo(SHORTCUT_ID) assertThat(intent).run { hasComponentClass(LaunchNoteTaskActivity::class.java) hasAction(ACTION_CREATE_NOTE) } assertThat(shortLabel).isEqualTo(NOTE_TASK_SHORT_LABEL) assertThat(longLabel).isEqualTo(NOTE_TASK_LONG_LABEL) assertThat(isLongLived).isEqualTo(true) assertThat(icon?.resId).isEqualTo(R.drawable.ic_note_task_shortcut_widget) assertThat(extras?.getString(EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE)) .isEqualTo(NOTE_TASK_PACKAGE_NAME) } } @Test fun updateNoteTaskAsUserInternal_noNotesRole_shouldDisableShortcuts() { whenever(roleManager.getRoleHoldersAsUser(ROLE_NOTES, userTracker.userHandle)) .thenReturn(emptyList()) createNoteTaskController(isEnabled = true) .updateNoteTaskAsUserInternal(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 updateNoteTaskAsUserInternal_flagDisabled_shouldDisableShortcuts() { createNoteTaskController(isEnabled = false) .updateNoteTaskAsUserInternal(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 // startregion updateNoteTaskForAllUsers @Test fun updateNoteTaskForAllUsers_shouldRunUpdateForCurrentUserAndProfiles() { userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo)) val controller = spy(createNoteTaskController()) doNothing().whenever(controller).updateNoteTaskAsUser(any()) controller.updateNoteTaskForCurrentUserAndManagedProfiles() verify(controller).updateNoteTaskAsUser(mainUserInfo.userHandle) verify(controller).updateNoteTaskAsUser(workUserInfo.userHandle) } // 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_userSelectedWorkProfile_tailButton_shouldReturnWorkProfileUser() { // ktlint-disable max-line-length whenever( secureSettings.getIntForUser( /* name= */ eq(Settings.Secure.DEFAULT_NOTE_TASK_PROFILE), /* def= */ any(), /* userHandle= */ any() ) ) .thenReturn(workUserInfo.id) 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_userSelectedMainProfile_tailButton_shouldReturnMainProfileUser() { // ktlint-disable max-line-length whenever( secureSettings.getIntForUser( /* name= */ eq(Settings.Secure.DEFAULT_NOTE_TASK_PROFILE), /* def= */ any(), /* userHandle= */ any() ) ) .thenReturn(mainUserInfo.id) whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true) userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo)) val user = createNoteTaskController().getUserForHandlingNotesTaking(TAIL_BUTTON) assertThat(user).isEqualTo(UserHandle.of(mainUserInfo.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() val userCaptor = argumentCaptor() verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor)) assertThat(intentCaptor.value).hasAction(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() val userCaptor = argumentCaptor() verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor)) assertThat(intentCaptor.value).hasAction(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() val userCaptor = argumentCaptor() verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor)) assertThat(intentCaptor.value).hasAction(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() val userCaptor = argumentCaptor() verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor)) assertThat(intentCaptor.value).hasAction(ACTION_MANAGE_DEFAULT_APP) assertThat(userCaptor.value).isEqualTo(UserHandle.of(mainUserInfo.id)) } // endregion private companion object { const val NOTE_TASK_SHORT_LABEL = "Note-taking" const val NOTE_TASK_LONG_LABEL = "Note-taking, App" const val NOTE_TASK_ACTIVITY_NAME = "NoteTaskActivity" const val NOTE_TASK_PACKAGE_NAME = "com.android.note.app" const val NOTE_TASK_UID = 123456 private val NOTE_TASK_INFO = NoteTaskInfo( packageName = NOTE_TASK_PACKAGE_NAME, uid = NOTE_TASK_UID, user = UserHandle.of(0), ) private val NOTE_RUNNING_TASK_INFO = ActivityManager.RunningTaskInfo().apply { topActivity = ComponentName(NOTE_TASK_PACKAGE_NAME, NOTE_TASK_ACTIVITY_NAME) } val mainUserInfo = UserInfo(/* id= */ 0, /* name= */ "primary", /* flags= */ UserInfo.FLAG_MAIN) val workUserInfo = UserInfo(/* id= */ 10, /* name= */ "work", /* flags= */ UserInfo.FLAG_PROFILE) val mainAndWorkProfileUsers = listOf(mainUserInfo, workUserInfo) } }