Fallback to ShortcutManager if AppPredictor has crashed
Bug: 269230501
Test: unit tests
Change-Id: If5ac83d0f301bda8d00721f56ed8df88332a147f
diff --git a/java/src/com/android/intentresolver/shortcuts/ShortcutLoader.kt b/java/src/com/android/intentresolver/shortcuts/ShortcutLoader.kt
index 6f7542f..29e706d 100644
--- a/java/src/com/android/intentresolver/shortcuts/ShortcutLoader.kt
+++ b/java/src/com/android/intentresolver/shortcuts/ShortcutLoader.kt
@@ -77,6 +77,7 @@
private val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager
private val activeRequest = AtomicReference(NO_REQUEST)
private val appPredictorCallback = AppPredictor.Callback { onAppPredictorCallback(it) }
+ @Volatile
private var isDestroyed = false
@MainThread
@@ -134,8 +135,14 @@
@WorkerThread
private fun queryDirectShareTargets(skipAppPredictionService: Boolean) {
if (!skipAppPredictionService && appPredictor != null) {
- appPredictor.requestPredictionUpdate()
- return
+ try {
+ appPredictor.requestPredictionUpdate()
+ return
+ } catch (e: Throwable) {
+ // we might have been destroyed concurrently, nothing left to do
+ if (isDestroyed) return
+ Log.e(TAG, "Failed to query AppPredictor", e)
+ }
}
// Default to just querying ShortcutManager if AppPredictor not present.
if (targetIntentFilter == null) return
diff --git a/java/tests/src/com/android/intentresolver/shortcuts/ShortcutLoaderTest.kt b/java/tests/src/com/android/intentresolver/shortcuts/ShortcutLoaderTest.kt
index 0c817cb..e8e2f86 100644
--- a/java/tests/src/com/android/intentresolver/shortcuts/ShortcutLoaderTest.kt
+++ b/java/tests/src/com/android/intentresolver/shortcuts/ShortcutLoaderTest.kt
@@ -58,7 +58,7 @@
private val pm = mock<PackageManager> {
whenever(getApplicationInfo(any(), any<ApplicationInfoFlags>())).thenReturn(appInfo)
}
- val userManager = mock<UserManager> {
+ private val userManager = mock<UserManager> {
whenever(isUserRunning(any<UserHandle>())).thenReturn(true)
whenever(isUserUnlocked(any<UserHandle>())).thenReturn(true)
whenever(isQuietModeEnabled(any<UserHandle>())).thenReturn(false)
@@ -72,14 +72,15 @@
private val intentFilter = mock<IntentFilter>()
private val appPredictor = mock<ShortcutLoader.AppPredictorProxy>()
private val callback = mock<Consumer<ShortcutLoader.Result>>()
+ private val componentName = ComponentName("pkg", "Class")
+ private val appTarget = mock<DisplayResolveInfo> {
+ whenever(resolvedComponentName).thenReturn(componentName)
+ }
+ private val appTargets = arrayOf(appTarget)
+ private val matchingShortcutInfo = createShortcutInfo("id-0", componentName, 1)
@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,
@@ -93,7 +94,6 @@
testSubject.queryShortcuts(appTargets)
- val matchingShortcutInfo = createShortcutInfo("id-0", componentName, 1)
val matchingAppTarget = createAppTarget(matchingShortcutInfo)
val shortcuts = listOf(
matchingAppTarget,
@@ -131,12 +131,6 @@
@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
@@ -182,12 +176,6 @@
@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
@@ -238,6 +226,55 @@
}
@Test
+ fun test_queryShortcuts_onAppPredictorFailure_fallbackToShortcutManager() {
+ 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)
+ whenever(appPredictor.requestPredictionUpdate())
+ .thenThrow(IllegalStateException("Test exception"))
+ val testSubject = ShortcutLoader(
+ context,
+ appPredictor,
+ UserHandle.of(0),
+ true,
+ intentFilter,
+ executor,
+ executor,
+ callback
+ )
+
+ testSubject.queryShortcuts(appTargets)
+
+ verify(appPredictor, times(1)).requestPredictionUpdate()
+
+ 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)
}