blob: 0c817cb2a4ab8d93be6effd221548aac40ebf20b [file] [log] [blame]
/*
* 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.intentresolver.shortcuts
import android.app.prediction.AppPredictor
import android.content.ComponentName
import android.content.Context
import android.content.IntentFilter
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.ApplicationInfoFlags
import android.content.pm.ShortcutManager
import android.os.UserHandle
import android.os.UserManager
import androidx.test.filters.SmallTest
import com.android.intentresolver.any
import com.android.intentresolver.argumentCaptor
import com.android.intentresolver.capture
import com.android.intentresolver.chooser.DisplayResolveInfo
import com.android.intentresolver.createAppTarget
import com.android.intentresolver.createShareShortcutInfo
import com.android.intentresolver.createShortcutInfo
import com.android.intentresolver.mock
import com.android.intentresolver.whenever
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import java.util.concurrent.Executor
import java.util.function.Consumer
@SmallTest
class ShortcutLoaderTest {
private val appInfo = ApplicationInfo().apply {
enabled = true
flags = 0
}
private val pm = mock<PackageManager> {
whenever(getApplicationInfo(any(), any<ApplicationInfoFlags>())).thenReturn(appInfo)
}
val userManager = mock<UserManager> {
whenever(isUserRunning(any<UserHandle>())).thenReturn(true)
whenever(isUserUnlocked(any<UserHandle>())).thenReturn(true)
whenever(isQuietModeEnabled(any<UserHandle>())).thenReturn(false)
}
private val context = mock<Context> {
whenever(packageManager).thenReturn(pm)
whenever(createContextAsUser(any(), anyInt())).thenReturn(this)
whenever(getSystemService(Context.USER_SERVICE)).thenReturn(userManager)
}
private val executor = ImmediateExecutor()
private val intentFilter = mock<IntentFilter>()
private val appPredictor = mock<ShortcutLoader.AppPredictorProxy>()
private val callback = mock<Consumer<ShortcutLoader.Result>>()
@Test
fun test_queryShortcuts_result_consistency_with_AppPredictor() {
val componentName = ComponentName("pkg", "Class")
val appTarget = mock<DisplayResolveInfo> {
whenever(resolvedComponentName).thenReturn(componentName)
}
val appTargets = arrayOf(appTarget)
val testSubject = ShortcutLoader(
context,
appPredictor,
UserHandle.of(0),
true,
intentFilter,
executor,
executor,
callback
)
testSubject.queryShortcuts(appTargets)
val matchingShortcutInfo = createShortcutInfo("id-0", componentName, 1)
val matchingAppTarget = createAppTarget(matchingShortcutInfo)
val shortcuts = listOf(
matchingAppTarget,
// an AppTarget that does not belong to any resolved application; should be ignored
createAppTarget(
createShortcutInfo("id-1", ComponentName("mismatching.pkg", "Class"), 1)
)
)
val appPredictorCallbackCaptor = argumentCaptor<AppPredictor.Callback>()
verify(appPredictor, atLeastOnce())
.registerPredictionUpdates(any(), capture(appPredictorCallbackCaptor))
appPredictorCallbackCaptor.value.onTargetsAvailable(shortcuts)
val resultCaptor = argumentCaptor<ShortcutLoader.Result>()
verify(callback, times(1)).accept(capture(resultCaptor))
val result = resultCaptor.value
assertTrue("An app predictor result is expected", result.isFromAppPredictor)
assertArrayEquals("Wrong input app targets in the result", appTargets, result.appTargets)
assertEquals("Wrong shortcut count", 1, result.shortcutsByApp.size)
assertEquals("Wrong app target", appTarget, result.shortcutsByApp[0].appTarget)
for (shortcut in result.shortcutsByApp[0].shortcuts) {
assertEquals(
"Wrong AppTarget in the cache",
matchingAppTarget,
result.directShareAppTargetCache[shortcut]
)
assertEquals(
"Wrong ShortcutInfo in the cache",
matchingShortcutInfo,
result.directShareShortcutInfoCache[shortcut]
)
}
}
@Test
fun test_queryShortcuts_result_consistency_with_ShortcutManager() {
val componentName = ComponentName("pkg", "Class")
val appTarget = mock<DisplayResolveInfo> {
whenever(resolvedComponentName).thenReturn(componentName)
}
val appTargets = arrayOf(appTarget)
val matchingShortcutInfo = createShortcutInfo("id-0", componentName, 1)
val shortcutManagerResult = listOf(
ShortcutManager.ShareShortcutInfo(matchingShortcutInfo, componentName),
// mismatching shortcut
createShareShortcutInfo("id-1", ComponentName("mismatching.pkg", "Class"), 1)
)
val shortcutManager = mock<ShortcutManager> {
whenever(getShareTargets(intentFilter)).thenReturn(shortcutManagerResult)
}
whenever(context.getSystemService(Context.SHORTCUT_SERVICE)).thenReturn(shortcutManager)
val testSubject = ShortcutLoader(
context,
null,
UserHandle.of(0),
true,
intentFilter,
executor,
executor,
callback
)
testSubject.queryShortcuts(appTargets)
val resultCaptor = argumentCaptor<ShortcutLoader.Result>()
verify(callback, times(1)).accept(capture(resultCaptor))
val result = resultCaptor.value
assertFalse("An ShortcutManager result is expected", result.isFromAppPredictor)
assertArrayEquals("Wrong input app targets in the result", appTargets, result.appTargets)
assertEquals("Wrong shortcut count", 1, result.shortcutsByApp.size)
assertEquals("Wrong app target", appTarget, result.shortcutsByApp[0].appTarget)
for (shortcut in result.shortcutsByApp[0].shortcuts) {
assertTrue(
"AppTargets are not expected the cache of a ShortcutManager result",
result.directShareAppTargetCache.isEmpty()
)
assertEquals(
"Wrong ShortcutInfo in the cache",
matchingShortcutInfo,
result.directShareShortcutInfoCache[shortcut]
)
}
}
@Test
fun test_queryShortcuts_falls_back_to_ShortcutManager_on_empty_reply() {
val componentName = ComponentName("pkg", "Class")
val appTarget = mock<DisplayResolveInfo> {
whenever(resolvedComponentName).thenReturn(componentName)
}
val appTargets = arrayOf(appTarget)
val matchingShortcutInfo = createShortcutInfo("id-0", componentName, 1)
val shortcutManagerResult = listOf(
ShortcutManager.ShareShortcutInfo(matchingShortcutInfo, componentName),
// mismatching shortcut
createShareShortcutInfo("id-1", ComponentName("mismatching.pkg", "Class"), 1)
)
val shortcutManager = mock<ShortcutManager> {
whenever(getShareTargets(intentFilter)).thenReturn(shortcutManagerResult)
}
whenever(context.getSystemService(Context.SHORTCUT_SERVICE)).thenReturn(shortcutManager)
val testSubject = ShortcutLoader(
context,
appPredictor,
UserHandle.of(0),
true,
intentFilter,
executor,
executor,
callback
)
testSubject.queryShortcuts(appTargets)
verify(appPredictor, times(1)).requestPredictionUpdate()
val appPredictorCallbackCaptor = argumentCaptor<AppPredictor.Callback>()
verify(appPredictor, times(1))
.registerPredictionUpdates(any(), capture(appPredictorCallbackCaptor))
appPredictorCallbackCaptor.value.onTargetsAvailable(emptyList())
val resultCaptor = argumentCaptor<ShortcutLoader.Result>()
verify(callback, times(1)).accept(capture(resultCaptor))
val result = resultCaptor.value
assertFalse("An ShortcutManager result is expected", result.isFromAppPredictor)
assertArrayEquals("Wrong input app targets in the result", appTargets, result.appTargets)
assertEquals("Wrong shortcut count", 1, result.shortcutsByApp.size)
assertEquals("Wrong app target", appTarget, result.shortcutsByApp[0].appTarget)
for (shortcut in result.shortcutsByApp[0].shortcuts) {
assertTrue(
"AppTargets are not expected the cache of a ShortcutManager result",
result.directShareAppTargetCache.isEmpty()
)
assertEquals(
"Wrong ShortcutInfo in the cache",
matchingShortcutInfo,
result.directShareShortcutInfoCache[shortcut]
)
}
}
@Test
fun test_queryShortcuts_do_not_call_services_for_not_running_work_profile() {
testDisabledWorkProfileDoNotCallSystem(isUserRunning = false)
}
@Test
fun test_queryShortcuts_do_not_call_services_for_locked_work_profile() {
testDisabledWorkProfileDoNotCallSystem(isUserUnlocked = false)
}
@Test
fun test_queryShortcuts_do_not_call_services_if_quite_mode_is_enabled_for_work_profile() {
testDisabledWorkProfileDoNotCallSystem(isQuietModeEnabled = true)
}
@Test
fun test_queryShortcuts_call_services_for_not_running_main_profile() {
testAlwaysCallSystemForMainProfile(isUserRunning = false)
}
@Test
fun test_queryShortcuts_call_services_for_locked_main_profile() {
testAlwaysCallSystemForMainProfile(isUserUnlocked = false)
}
@Test
fun test_queryShortcuts_call_services_if_quite_mode_is_enabled_for_main_profile() {
testAlwaysCallSystemForMainProfile(isQuietModeEnabled = true)
}
private fun testDisabledWorkProfileDoNotCallSystem(
isUserRunning: Boolean = true,
isUserUnlocked: Boolean = true,
isQuietModeEnabled: Boolean = false
) {
val userHandle = UserHandle.of(10)
with(userManager) {
whenever(isUserRunning(userHandle)).thenReturn(isUserRunning)
whenever(isUserUnlocked(userHandle)).thenReturn(isUserUnlocked)
whenever(isQuietModeEnabled(userHandle)).thenReturn(isQuietModeEnabled)
}
whenever(context.getSystemService(Context.USER_SERVICE)).thenReturn(userManager);
val appPredictor = mock<ShortcutLoader.AppPredictorProxy>()
val callback = mock<Consumer<ShortcutLoader.Result>>()
val testSubject = ShortcutLoader(
context,
appPredictor,
userHandle,
false,
intentFilter,
executor,
executor,
callback
)
testSubject.queryShortcuts(arrayOf<DisplayResolveInfo>(mock()))
verify(appPredictor, never()).requestPredictionUpdate()
}
private fun testAlwaysCallSystemForMainProfile(
isUserRunning: Boolean = true,
isUserUnlocked: Boolean = true,
isQuietModeEnabled: Boolean = false
) {
val userHandle = UserHandle.of(10)
with(userManager) {
whenever(isUserRunning(userHandle)).thenReturn(isUserRunning)
whenever(isUserUnlocked(userHandle)).thenReturn(isUserUnlocked)
whenever(isQuietModeEnabled(userHandle)).thenReturn(isQuietModeEnabled)
}
whenever(context.getSystemService(Context.USER_SERVICE)).thenReturn(userManager);
val appPredictor = mock<ShortcutLoader.AppPredictorProxy>()
val callback = mock<Consumer<ShortcutLoader.Result>>()
val testSubject = ShortcutLoader(
context,
appPredictor,
userHandle,
true,
intentFilter,
executor,
executor,
callback
)
testSubject.queryShortcuts(arrayOf<DisplayResolveInfo>(mock()))
verify(appPredictor, times(1)).requestPredictionUpdate()
}
}
private class ImmediateExecutor : Executor {
override fun execute(r: Runnable) {
r.run()
}
}