aboutsummaryrefslogtreecommitdiff
path: root/tests/unit/src/com/android/intentresolver/ResolverListAdapterTest.kt
diff options
context:
space:
mode:
Diffstat (limited to 'tests/unit/src/com/android/intentresolver/ResolverListAdapterTest.kt')
-rw-r--r--tests/unit/src/com/android/intentresolver/ResolverListAdapterTest.kt1048
1 files changed, 1048 insertions, 0 deletions
diff --git a/tests/unit/src/com/android/intentresolver/ResolverListAdapterTest.kt b/tests/unit/src/com/android/intentresolver/ResolverListAdapterTest.kt
new file mode 100644
index 0000000..61b9fd9
--- /dev/null
+++ b/tests/unit/src/com/android/intentresolver/ResolverListAdapterTest.kt
@@ -0,0 +1,1048 @@
+/*
+ * 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.intentresolver
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.os.UserHandle
+import android.os.UserManager
+import android.view.LayoutInflater
+import com.android.intentresolver.ResolverDataProvider.createActivityInfo
+import com.android.intentresolver.ResolverListAdapter.ResolverListCommunicator
+import com.android.intentresolver.icons.TargetDataLoader
+import com.android.intentresolver.util.TestExecutor
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+private const val PKG_NAME = "org.pkg.app"
+private const val PKG_NAME_TWO = "org.pkg.two.app"
+private const val PKG_NAME_THREE = "org.pkg.three.app"
+private const val CLASS_NAME = "org.pkg.app.TheClass"
+
+class ResolverListAdapterTest {
+ private val layoutInflater = mock<LayoutInflater>()
+ private val packageManager = mock<PackageManager>()
+ private val userManager = mock<UserManager> { whenever(isManagedProfile).thenReturn(false) }
+ private val context =
+ mock<Context> {
+ whenever(getSystemService(Context.LAYOUT_INFLATER_SERVICE)).thenReturn(layoutInflater)
+ whenever(getSystemService(Context.USER_SERVICE)).thenReturn(userManager)
+ whenever(packageManager).thenReturn(this@ResolverListAdapterTest.packageManager)
+ }
+ private val targetIntent = Intent(Intent.ACTION_SEND)
+ private val payloadIntents = listOf(targetIntent)
+ private val resolverListController =
+ mock<ResolverListController> {
+ whenever(filterIneligibleActivities(any(), anyBoolean())).thenReturn(null)
+ whenever(filterLowPriority(any(), anyBoolean())).thenReturn(null)
+ }
+ private val resolverListCommunicator = FakeResolverListCommunicator()
+ private val userHandle = UserHandle.of(UserHandle.USER_CURRENT)
+ private val targetDataLoader = mock<TargetDataLoader>()
+ private val backgroundExecutor = TestExecutor()
+ private val immediateExecutor = TestExecutor(immediate = true)
+
+ @Test
+ fun test_oneTargetNoLastChosen_oneTargetInAdapter() {
+ val resolvedTargets = createResolvedComponents(ComponentName(PKG_NAME, CLASS_NAME))
+ whenever(
+ resolverListController.getResolversForIntentAsUser(
+ true,
+ resolverListCommunicator.shouldGetActivityMetadata(),
+ resolverListCommunicator.shouldGetOnlyDefaultActivities(),
+ payloadIntents,
+ userHandle
+ )
+ )
+ .thenReturn(resolvedTargets)
+ val testSubject =
+ ResolverListAdapter(
+ context,
+ payloadIntents,
+ /*initialIntents=*/ null,
+ /*rList=*/ null,
+ /*filterLastUsed=*/ true,
+ resolverListController,
+ userHandle,
+ targetIntent,
+ resolverListCommunicator,
+ /*initialIntentsUserSpace=*/ userHandle,
+ targetDataLoader,
+ backgroundExecutor,
+ immediateExecutor,
+ )
+ val doPostProcessing = true
+
+ val isLoaded = testSubject.rebuildList(doPostProcessing)
+
+ assertThat(isLoaded).isTrue()
+ assertThat(testSubject.count).isEqualTo(resolvedTargets.size)
+ assertThat(testSubject.placeholderCount).isEqualTo(0)
+ assertThat(testSubject.hasFilteredItem()).isFalse()
+ assertThat(testSubject.filteredItem).isNull()
+ assertThat(testSubject.filteredPosition).isLessThan(0)
+ assertThat(testSubject.unfilteredResolveList).containsExactlyElementsIn(resolvedTargets)
+ assertThat(testSubject.isTabLoaded).isTrue()
+ assertThat(backgroundExecutor.pendingCommandCount).isEqualTo(0)
+ assertThat(resolverListCommunicator.updateProfileViewButtonCount).isEqualTo(0)
+ assertThat(resolverListCommunicator.sendVoiceCommandCount).isEqualTo(1)
+ }
+
+ @Test
+ fun test_oneTargetThatWasLastChosen_NoTargetsInAdapter() {
+ val resolvedTargets = createResolvedComponents(ComponentName(PKG_NAME, CLASS_NAME))
+ whenever(
+ resolverListController.getResolversForIntentAsUser(
+ true,
+ resolverListCommunicator.shouldGetActivityMetadata(),
+ resolverListCommunicator.shouldGetOnlyDefaultActivities(),
+ payloadIntents,
+ userHandle
+ )
+ )
+ .thenReturn(resolvedTargets)
+ whenever(resolverListController.lastChosen)
+ .thenReturn(resolvedTargets[0].getResolveInfoAt(0))
+ val testSubject =
+ ResolverListAdapter(
+ context,
+ payloadIntents,
+ /*initialIntents=*/ null,
+ /*rList=*/ null,
+ /*filterLastUsed=*/ true,
+ resolverListController,
+ userHandle,
+ targetIntent,
+ resolverListCommunicator,
+ /*initialIntentsUserSpace=*/ userHandle,
+ targetDataLoader,
+ backgroundExecutor,
+ immediateExecutor,
+ )
+ val doPostProcessing = true
+
+ val isLoaded = testSubject.rebuildList(doPostProcessing)
+
+ assertThat(isLoaded).isTrue()
+ assertThat(testSubject.count).isEqualTo(0)
+ assertThat(testSubject.placeholderCount).isEqualTo(0)
+ assertThat(testSubject.hasFilteredItem()).isTrue()
+ assertThat(testSubject.filteredItem).isNotNull()
+ assertThat(testSubject.filteredPosition).isEqualTo(0)
+ assertThat(testSubject.unfilteredResolveList).containsExactlyElementsIn(resolvedTargets)
+ assertThat(testSubject.isTabLoaded).isTrue()
+ assertThat(backgroundExecutor.pendingCommandCount).isEqualTo(0)
+ }
+
+ @Test
+ fun test_oneTargetLastChosenNotInTheList_oneTargetInAdapter() {
+ val resolvedTargets = createResolvedComponents(ComponentName(PKG_NAME, CLASS_NAME))
+ whenever(
+ resolverListController.getResolversForIntentAsUser(
+ true,
+ resolverListCommunicator.shouldGetActivityMetadata(),
+ resolverListCommunicator.shouldGetOnlyDefaultActivities(),
+ payloadIntents,
+ userHandle
+ )
+ )
+ .thenReturn(resolvedTargets)
+ whenever(resolverListController.lastChosen)
+ .thenReturn(createResolveInfo(PKG_NAME_TWO, CLASS_NAME))
+ val testSubject =
+ ResolverListAdapter(
+ context,
+ payloadIntents,
+ /*initialIntents=*/ null,
+ /*rList=*/ null,
+ /*filterLastUsed=*/ true,
+ resolverListController,
+ userHandle,
+ targetIntent,
+ resolverListCommunicator,
+ /*initialIntentsUserSpace=*/ userHandle,
+ targetDataLoader,
+ backgroundExecutor,
+ immediateExecutor,
+ )
+ val doPostProcessing = true
+
+ val isLoaded = testSubject.rebuildList(doPostProcessing)
+
+ assertThat(isLoaded).isTrue()
+ assertThat(testSubject.count).isEqualTo(resolvedTargets.size)
+ assertThat(testSubject.placeholderCount).isEqualTo(0)
+ assertThat(testSubject.hasFilteredItem()).isTrue()
+ assertThat(testSubject.filteredItem).isNull()
+ assertThat(testSubject.filteredPosition).isLessThan(0)
+ assertThat(testSubject.unfilteredResolveList).containsExactlyElementsIn(resolvedTargets)
+ assertThat(testSubject.isTabLoaded).isTrue()
+ assertThat(backgroundExecutor.pendingCommandCount).isEqualTo(0)
+ }
+
+ @Test
+ fun test_oneTargetThatWasLastChosenFilteringDisabled_oneTargetInAdapter() {
+ val resolvedTargets = createResolvedComponents(ComponentName(PKG_NAME, CLASS_NAME))
+ whenever(
+ resolverListController.getResolversForIntentAsUser(
+ true,
+ resolverListCommunicator.shouldGetActivityMetadata(),
+ resolverListCommunicator.shouldGetOnlyDefaultActivities(),
+ payloadIntents,
+ userHandle
+ )
+ )
+ .thenReturn(resolvedTargets)
+ whenever(resolverListController.lastChosen)
+ .thenReturn(resolvedTargets[0].getResolveInfoAt(0))
+ val testSubject =
+ ResolverListAdapter(
+ context,
+ payloadIntents,
+ /*initialIntents=*/ null,
+ /*rList=*/ null,
+ /*filterLastUsed=*/ false,
+ resolverListController,
+ userHandle,
+ targetIntent,
+ resolverListCommunicator,
+ /*initialIntentsUserSpace=*/ userHandle,
+ targetDataLoader,
+ backgroundExecutor,
+ immediateExecutor,
+ )
+ val doPostProcessing = true
+
+ val isLoaded = testSubject.rebuildList(doPostProcessing)
+
+ assertThat(isLoaded).isTrue()
+ assertThat(testSubject.count).isEqualTo(resolvedTargets.size)
+ // we don't reset placeholder count
+ assertThat(testSubject.placeholderCount).isEqualTo(0)
+ assertThat(testSubject.hasFilteredItem()).isFalse()
+ assertThat(testSubject.filteredItem).isNull()
+ assertThat(testSubject.filteredPosition).isLessThan(0)
+ assertThat(testSubject.unfilteredResolveList).containsExactlyElementsIn(resolvedTargets)
+ assertThat(testSubject.isTabLoaded).isTrue()
+ }
+
+ @Test
+ fun test_twoTargetsNoLastChosenUseLayoutWithDefaults_twoTargetsInAdapter() {
+ testTwoTargets(hasLastChosen = false, useLayoutWithDefaults = true)
+ }
+
+ @Test
+ fun test_twoTargetsNoLastChosenDontUseLayoutWithDefaults_twoTargetsInAdapter() {
+ testTwoTargets(hasLastChosen = false, useLayoutWithDefaults = false)
+ }
+
+ @Test
+ fun test_twoTargetsLastChosenUseLayoutWithDefaults_oneTargetInAdapter() {
+ testTwoTargets(hasLastChosen = true, useLayoutWithDefaults = true)
+ }
+
+ @Test
+ fun test_twoTargetsLastChosenDontUseLayoutWithDefaults_oneTargetInAdapter() {
+ testTwoTargets(hasLastChosen = true, useLayoutWithDefaults = false)
+ }
+
+ private fun testTwoTargets(hasLastChosen: Boolean, useLayoutWithDefaults: Boolean) {
+ val resolvedTargets =
+ createResolvedComponents(
+ ComponentName(PKG_NAME, CLASS_NAME),
+ ComponentName(PKG_NAME_TWO, CLASS_NAME),
+ )
+ if (hasLastChosen) {
+ whenever(resolverListController.lastChosen)
+ .thenReturn(resolvedTargets[0].getResolveInfoAt(0))
+ }
+ whenever(
+ resolverListController.getResolversForIntentAsUser(
+ true,
+ resolverListCommunicator.shouldGetActivityMetadata(),
+ resolverListCommunicator.shouldGetOnlyDefaultActivities(),
+ payloadIntents,
+ userHandle
+ )
+ )
+ .thenReturn(resolvedTargets)
+ val resolverListCommunicator = FakeResolverListCommunicator(useLayoutWithDefaults)
+ val testSubject =
+ ResolverListAdapter(
+ context,
+ payloadIntents,
+ /*initialIntents=*/ null,
+ /*rList=*/ null,
+ /*filterLastUsed=*/ true,
+ resolverListController,
+ userHandle,
+ targetIntent,
+ resolverListCommunicator,
+ /*initialIntentsUserSpace=*/ userHandle,
+ targetDataLoader,
+ backgroundExecutor,
+ immediateExecutor,
+ )
+ val doPostProcessing = true
+
+ val isLoaded = testSubject.rebuildList(doPostProcessing)
+
+ assertThat(isLoaded).isFalse()
+ val placeholderCount = resolvedTargets.size - (if (useLayoutWithDefaults) 1 else 0)
+ assertThat(testSubject.count).isEqualTo(placeholderCount)
+ assertThat(testSubject.placeholderCount).isEqualTo(placeholderCount)
+ assertThat(testSubject.hasFilteredItem()).isEqualTo(hasLastChosen)
+ assertThat(testSubject.filteredItem).isNull()
+ assertThat(testSubject.filteredPosition).isLessThan(0)
+ assertThat(testSubject.unfilteredResolveList).containsExactlyElementsIn(resolvedTargets)
+ assertThat(testSubject.isTabLoaded).isFalse()
+ assertThat(backgroundExecutor.pendingCommandCount).isEqualTo(1)
+ assertThat(resolverListCommunicator.updateProfileViewButtonCount).isEqualTo(0)
+ assertThat(resolverListCommunicator.sendVoiceCommandCount).isEqualTo(0)
+
+ backgroundExecutor.runUntilIdle()
+
+ // we don't reset placeholder count (legacy logic, likely an oversight?)
+ assertThat(testSubject.placeholderCount).isEqualTo(placeholderCount)
+ assertThat(testSubject.hasFilteredItem()).isEqualTo(hasLastChosen)
+ if (hasLastChosen) {
+ assertThat(testSubject.count).isEqualTo(resolvedTargets.size - 1)
+ assertThat(testSubject.filteredItem).isNotNull()
+ assertThat(testSubject.filteredPosition).isEqualTo(0)
+ } else {
+ assertThat(testSubject.count).isEqualTo(resolvedTargets.size)
+ assertThat(testSubject.filteredItem).isNull()
+ assertThat(testSubject.filteredPosition).isLessThan(0)
+ }
+ assertThat(testSubject.unfilteredResolveList).containsExactlyElementsIn(resolvedTargets)
+ assertThat(testSubject.isTabLoaded).isTrue()
+ assertThat(resolverListCommunicator.updateProfileViewButtonCount).isEqualTo(1)
+ assertThat(resolverListCommunicator.sendVoiceCommandCount).isEqualTo(1)
+ assertThat(backgroundExecutor.pendingCommandCount).isEqualTo(0)
+ }
+
+ @Test
+ fun test_twoTargetsLastChosenNotInTheList_twoTargetsInAdapter() {
+ val resolvedTargets =
+ createResolvedComponents(
+ ComponentName(PKG_NAME, CLASS_NAME),
+ ComponentName(PKG_NAME_TWO, CLASS_NAME),
+ )
+ whenever(resolverListController.lastChosen)
+ .thenReturn(createResolveInfo(PKG_NAME, CLASS_NAME + "2"))
+ whenever(
+ resolverListController.getResolversForIntentAsUser(
+ true,
+ resolverListCommunicator.shouldGetActivityMetadata(),
+ resolverListCommunicator.shouldGetOnlyDefaultActivities(),
+ payloadIntents,
+ userHandle
+ )
+ )
+ .thenReturn(resolvedTargets)
+ val testSubject =
+ ResolverListAdapter(
+ context,
+ payloadIntents,
+ /*initialIntents=*/ null,
+ /*rList=*/ null,
+ /*filterLastUsed=*/ true,
+ resolverListController,
+ userHandle,
+ targetIntent,
+ resolverListCommunicator,
+ /*initialIntentsUserSpace=*/ userHandle,
+ targetDataLoader,
+ backgroundExecutor,
+ immediateExecutor,
+ )
+ val doPostProcessing = false
+
+ val isLoaded = testSubject.rebuildList(doPostProcessing)
+
+ assertThat(isLoaded).isFalse()
+ val placeholderCount = resolvedTargets.size - 1
+ assertThat(testSubject.count).isEqualTo(placeholderCount)
+ assertThat(testSubject.placeholderCount).isEqualTo(placeholderCount)
+ assertThat(testSubject.hasFilteredItem()).isTrue()
+ assertThat(testSubject.filteredItem).isNull()
+ assertThat(testSubject.filteredPosition).isLessThan(0)
+ assertThat(testSubject.unfilteredResolveList).containsExactlyElementsIn(resolvedTargets)
+ assertThat(testSubject.isTabLoaded).isFalse()
+ assertThat(backgroundExecutor.pendingCommandCount).isEqualTo(1)
+ assertThat(resolverListCommunicator.updateProfileViewButtonCount).isEqualTo(0)
+
+ backgroundExecutor.runUntilIdle()
+
+ // we don't reset placeholder count (legacy logic, likely an oversight?)
+ assertThat(testSubject.placeholderCount).isEqualTo(placeholderCount)
+ assertThat(testSubject.hasFilteredItem()).isTrue()
+ assertThat(testSubject.count).isEqualTo(resolvedTargets.size)
+ assertThat(testSubject.filteredItem).isNull()
+ assertThat(testSubject.filteredPosition).isLessThan(0)
+ assertThat(testSubject.unfilteredResolveList).containsExactlyElementsIn(resolvedTargets)
+ assertThat(testSubject.isTabLoaded).isTrue()
+ assertThat(resolverListCommunicator.updateProfileViewButtonCount).isEqualTo(0)
+ assertThat(backgroundExecutor.pendingCommandCount).isEqualTo(0)
+ }
+
+ @Test
+ fun test_twoTargetsWithOtherProfileAndLastChosen_oneTargetInAdapter() {
+ val resolvedTargets =
+ createResolvedComponents(
+ ComponentName(PKG_NAME, CLASS_NAME),
+ ComponentName(PKG_NAME_TWO, CLASS_NAME),
+ )
+ resolvedTargets[1].getResolveInfoAt(0).targetUserId = 10
+ whenever(resolvedTargets[1].getResolveInfoAt(0).loadLabel(any())).thenReturn("Label")
+ whenever(resolverListController.lastChosen)
+ .thenReturn(resolvedTargets[0].getResolveInfoAt(0))
+ whenever(
+ resolverListController.getResolversForIntentAsUser(
+ true,
+ resolverListCommunicator.shouldGetActivityMetadata(),
+ resolverListCommunicator.shouldGetOnlyDefaultActivities(),
+ payloadIntents,
+ userHandle
+ )
+ )
+ .thenReturn(resolvedTargets)
+ val testSubject =
+ ResolverListAdapter(
+ context,
+ payloadIntents,
+ /*initialIntents=*/ null,
+ /*rList=*/ null,
+ /*filterLastUsed=*/ true,
+ resolverListController,
+ userHandle,
+ targetIntent,
+ resolverListCommunicator,
+ /*initialIntentsUserSpace=*/ userHandle,
+ targetDataLoader,
+ backgroundExecutor,
+ immediateExecutor,
+ )
+ val doPostProcessing = true
+
+ val isLoaded = testSubject.rebuildList(doPostProcessing)
+
+ assertThat(isLoaded).isTrue()
+ assertThat(testSubject.count).isEqualTo(1)
+ assertThat(testSubject.placeholderCount).isEqualTo(0)
+ assertThat(testSubject.otherProfile).isNotNull()
+ assertThat(testSubject.hasFilteredItem()).isFalse()
+ assertThat(testSubject.filteredItem).isNull()
+ assertThat(testSubject.filteredPosition).isLessThan(0)
+ assertThat(testSubject.unfilteredResolveList).containsExactlyElementsIn(resolvedTargets)
+ assertThat(testSubject.isTabLoaded).isTrue()
+ assertThat(backgroundExecutor.pendingCommandCount).isEqualTo(0)
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ @Test
+ fun test_resultsSorted_appearInSortedOrderInAdapter() {
+ val resolvedTargets =
+ createResolvedComponents(
+ ComponentName(PKG_NAME, CLASS_NAME),
+ ComponentName(PKG_NAME_TWO, CLASS_NAME),
+ )
+ whenever(
+ resolverListController.getResolversForIntentAsUser(
+ true,
+ resolverListCommunicator.shouldGetActivityMetadata(),
+ resolverListCommunicator.shouldGetOnlyDefaultActivities(),
+ payloadIntents,
+ userHandle
+ )
+ )
+ .thenReturn(resolvedTargets)
+ whenever(resolverListController.sort(any())).thenAnswer { invocation ->
+ val components = invocation.arguments[0] as MutableList<ResolvedComponentInfo>
+ components[0] = components[1].also { components[1] = components[0] }
+ null
+ }
+ val testSubject =
+ ResolverListAdapter(
+ context,
+ payloadIntents,
+ /*initialIntents=*/ null,
+ /*rList=*/ null,
+ /*filterLastUsed=*/ true,
+ resolverListController,
+ userHandle,
+ targetIntent,
+ resolverListCommunicator,
+ /*initialIntentsUserSpace=*/ userHandle,
+ targetDataLoader,
+ backgroundExecutor,
+ immediateExecutor,
+ )
+ val doPostProcessing = true
+
+ testSubject.rebuildList(doPostProcessing)
+
+ backgroundExecutor.runUntilIdle()
+
+ // we don't reset placeholder count (legacy logic, likely an oversight?)
+ assertThat(testSubject.count).isEqualTo(resolvedTargets.size)
+ assertThat(resolvedTargets[0].getResolveInfoAt(0).activityInfo.packageName)
+ .isEqualTo(PKG_NAME_TWO)
+ assertThat(resolvedTargets[1].getResolveInfoAt(0).activityInfo.packageName)
+ .isEqualTo(PKG_NAME)
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ @Test
+ fun test_ineligibleActivityFilteredOut_filteredComponentNotPresentInAdapter() {
+ val resolvedTargets =
+ createResolvedComponents(
+ ComponentName(PKG_NAME, CLASS_NAME),
+ ComponentName(PKG_NAME_TWO, CLASS_NAME),
+ )
+ whenever(
+ resolverListController.getResolversForIntentAsUser(
+ true,
+ resolverListCommunicator.shouldGetActivityMetadata(),
+ resolverListCommunicator.shouldGetOnlyDefaultActivities(),
+ payloadIntents,
+ userHandle
+ )
+ )
+ .thenReturn(resolvedTargets)
+ whenever(resolverListController.filterIneligibleActivities(any(), anyBoolean()))
+ .thenAnswer { invocation ->
+ val components = invocation.arguments[0] as MutableList<ResolvedComponentInfo>
+ val original = ArrayList(components)
+ components.removeAt(1)
+ original
+ }
+ val testSubject =
+ ResolverListAdapter(
+ context,
+ payloadIntents,
+ /*initialIntents=*/ null,
+ /*rList=*/ null,
+ /*filterLastUsed=*/ true,
+ resolverListController,
+ userHandle,
+ targetIntent,
+ resolverListCommunicator,
+ /*initialIntentsUserSpace=*/ userHandle,
+ targetDataLoader,
+ backgroundExecutor,
+ immediateExecutor,
+ )
+ val doPostProcessing = true
+
+ testSubject.rebuildList(doPostProcessing)
+
+ backgroundExecutor.runUntilIdle()
+
+ // we don't reset placeholder count (legacy logic, likely an oversight?)
+ assertThat(testSubject.count).isEqualTo(1)
+ assertThat(testSubject.getItem(0)?.resolveInfo)
+ .isEqualTo(resolvedTargets[0].getResolveInfoAt(0))
+ assertThat(testSubject.unfilteredResolveList).hasSize(2)
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ @Test
+ fun test_baseResolveList_excludedFromIneligibleActivityFiltering() {
+ val rList = listOf(createResolveInfo(PKG_NAME, CLASS_NAME))
+ whenever(resolverListController.addResolveListDedupe(any(), eq(targetIntent), eq(rList)))
+ .thenAnswer { invocation ->
+ val result = invocation.arguments[0] as MutableList<ResolvedComponentInfo>
+ result.addAll(
+ createResolvedComponents(
+ ComponentName(PKG_NAME, CLASS_NAME),
+ ComponentName(PKG_NAME_TWO, CLASS_NAME),
+ )
+ )
+ null
+ }
+ whenever(resolverListController.filterIneligibleActivities(any(), anyBoolean()))
+ .thenAnswer { invocation ->
+ val components = invocation.arguments[0] as MutableList<ResolvedComponentInfo>
+ val original = ArrayList(components)
+ components.clear()
+ original
+ }
+ val testSubject =
+ ResolverListAdapter(
+ context,
+ payloadIntents,
+ /*initialIntents=*/ null,
+ rList,
+ /*filterLastUsed=*/ true,
+ resolverListController,
+ userHandle,
+ targetIntent,
+ resolverListCommunicator,
+ /*initialIntentsUserSpace=*/ userHandle,
+ targetDataLoader,
+ backgroundExecutor,
+ immediateExecutor,
+ )
+ val doPostProcessing = true
+
+ testSubject.rebuildList(doPostProcessing)
+
+ backgroundExecutor.runUntilIdle()
+
+ // we don't reset placeholder count (legacy logic, likely an oversight?)
+ assertThat(testSubject.count).isEqualTo(2)
+ assertThat(testSubject.unfilteredResolveList).hasSize(2)
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ @Test
+ fun test_lowPriorityComponentFilteredOut_filteredComponentNotPresentInAdapter() {
+ val resolvedTargets =
+ createResolvedComponents(
+ ComponentName(PKG_NAME, CLASS_NAME),
+ ComponentName(PKG_NAME_TWO, CLASS_NAME),
+ )
+ whenever(
+ resolverListController.getResolversForIntentAsUser(
+ true,
+ resolverListCommunicator.shouldGetActivityMetadata(),
+ resolverListCommunicator.shouldGetOnlyDefaultActivities(),
+ payloadIntents,
+ userHandle
+ )
+ )
+ .thenReturn(resolvedTargets)
+ whenever(resolverListController.filterLowPriority(any(), anyBoolean())).thenAnswer {
+ invocation ->
+ val components = invocation.arguments[0] as MutableList<ResolvedComponentInfo>
+ val original = ArrayList(components)
+ components.removeAt(1)
+ original
+ }
+ val testSubject =
+ ResolverListAdapter(
+ context,
+ payloadIntents,
+ /*initialIntents=*/ null,
+ /*rList=*/ null,
+ /*filterLastUsed=*/ true,
+ resolverListController,
+ userHandle,
+ targetIntent,
+ resolverListCommunicator,
+ /*initialIntentsUserSpace=*/ userHandle,
+ targetDataLoader,
+ backgroundExecutor,
+ immediateExecutor,
+ )
+ val doPostProcessing = true
+
+ testSubject.rebuildList(doPostProcessing)
+
+ backgroundExecutor.runUntilIdle()
+
+ // we don't reset placeholder count (legacy logic, likely an oversight?)
+ assertThat(testSubject.count).isEqualTo(1)
+ assertThat(testSubject.getItem(0)?.resolveInfo)
+ .isEqualTo(resolvedTargets[0].getResolveInfoAt(0))
+ assertThat(testSubject.unfilteredResolveList).hasSize(2)
+ }
+
+ @Test
+ fun test_twoTargetsWithNonOverlappingInitialIntent_threeTargetsInAdapter() {
+ val resolvedTargets =
+ createResolvedComponents(
+ ComponentName(PKG_NAME, CLASS_NAME),
+ ComponentName(PKG_NAME_TWO, CLASS_NAME),
+ )
+ whenever(
+ resolverListController.getResolversForIntentAsUser(
+ true,
+ resolverListCommunicator.shouldGetActivityMetadata(),
+ resolverListCommunicator.shouldGetOnlyDefaultActivities(),
+ payloadIntents,
+ userHandle
+ )
+ )
+ .thenReturn(resolvedTargets)
+ val initialComponent = ComponentName(PKG_NAME_THREE, CLASS_NAME)
+ val initialIntents =
+ arrayOf(Intent(Intent.ACTION_SEND).apply { component = initialComponent })
+ whenever(packageManager.getActivityInfo(eq(initialComponent), eq(0)))
+ .thenReturn(createActivityInfo(initialComponent))
+ val testSubject =
+ ResolverListAdapter(
+ context,
+ payloadIntents,
+ initialIntents,
+ /*rList=*/ null,
+ /*filterLastUsed=*/ true,
+ resolverListController,
+ userHandle,
+ targetIntent,
+ resolverListCommunicator,
+ /*initialIntentsUserSpace=*/ userHandle,
+ targetDataLoader,
+ backgroundExecutor,
+ immediateExecutor,
+ )
+ val doPostProcessing = true
+
+ val isLoaded = testSubject.rebuildList(doPostProcessing)
+
+ assertThat(isLoaded).isFalse()
+ val placeholderCount = resolvedTargets.size - 1
+ assertThat(testSubject.count).isEqualTo(placeholderCount)
+ assertThat(testSubject.placeholderCount).isEqualTo(placeholderCount)
+ assertThat(testSubject.hasFilteredItem()).isFalse()
+ assertThat(testSubject.filteredItem).isNull()
+ assertThat(testSubject.filteredPosition).isLessThan(0)
+ assertThat(testSubject.unfilteredResolveList).containsExactlyElementsIn(resolvedTargets)
+ assertThat(testSubject.isTabLoaded).isFalse()
+ assertThat(backgroundExecutor.pendingCommandCount).isEqualTo(1)
+ assertThat(resolverListCommunicator.updateProfileViewButtonCount).isEqualTo(0)
+ assertThat(resolverListCommunicator.sendVoiceCommandCount).isEqualTo(0)
+
+ backgroundExecutor.runUntilIdle()
+
+ // we don't reset placeholder count (legacy logic, likely an oversight?)
+ assertThat(testSubject.placeholderCount).isEqualTo(placeholderCount)
+ assertThat(testSubject.hasFilteredItem()).isFalse()
+ assertThat(testSubject.count).isEqualTo(resolvedTargets.size + initialIntents.size)
+ assertThat(testSubject.getItem(0)?.targetIntent?.component)
+ .isEqualTo(initialIntents[0].component)
+ assertThat(testSubject.filteredItem).isNull()
+ assertThat(testSubject.filteredPosition).isLessThan(0)
+ assertThat(testSubject.unfilteredResolveList).containsExactlyElementsIn(resolvedTargets)
+ assertThat(testSubject.isTabLoaded).isTrue()
+ assertThat(resolverListCommunicator.updateProfileViewButtonCount).isEqualTo(1)
+ assertThat(resolverListCommunicator.sendVoiceCommandCount).isEqualTo(1)
+ assertThat(backgroundExecutor.pendingCommandCount).isEqualTo(0)
+ }
+
+ @Test
+ fun test_twoTargetsWithOverlappingInitialIntent_twoTargetsInAdapter() {
+ val resolvedTargets =
+ createResolvedComponents(
+ ComponentName(PKG_NAME, CLASS_NAME),
+ ComponentName(PKG_NAME_TWO, CLASS_NAME),
+ )
+ whenever(
+ resolverListController.getResolversForIntentAsUser(
+ true,
+ resolverListCommunicator.shouldGetActivityMetadata(),
+ resolverListCommunicator.shouldGetOnlyDefaultActivities(),
+ payloadIntents,
+ userHandle
+ )
+ )
+ .thenReturn(resolvedTargets)
+ val initialComponent = ComponentName(PKG_NAME_TWO, CLASS_NAME)
+ val initialIntents =
+ arrayOf(Intent(Intent.ACTION_SEND).apply { component = initialComponent })
+ whenever(packageManager.getActivityInfo(eq(initialComponent), eq(0)))
+ .thenReturn(createActivityInfo(initialComponent))
+ val testSubject =
+ ResolverListAdapter(
+ context,
+ payloadIntents,
+ initialIntents,
+ /*rList=*/ null,
+ /*filterLastUsed=*/ true,
+ resolverListController,
+ userHandle,
+ targetIntent,
+ resolverListCommunicator,
+ /*initialIntentsUserSpace=*/ userHandle,
+ targetDataLoader,
+ backgroundExecutor,
+ immediateExecutor,
+ )
+ val doPostProcessing = true
+
+ val isLoaded = testSubject.rebuildList(doPostProcessing)
+
+ assertThat(isLoaded).isFalse()
+ val placeholderCount = resolvedTargets.size - 1
+ assertThat(testSubject.count).isEqualTo(placeholderCount)
+ assertThat(testSubject.placeholderCount).isEqualTo(placeholderCount)
+ assertThat(testSubject.hasFilteredItem()).isFalse()
+ assertThat(testSubject.filteredItem).isNull()
+ assertThat(testSubject.filteredPosition).isLessThan(0)
+ assertThat(testSubject.unfilteredResolveList).containsExactlyElementsIn(resolvedTargets)
+ assertThat(testSubject.isTabLoaded).isFalse()
+ assertThat(backgroundExecutor.pendingCommandCount).isEqualTo(1)
+ assertThat(resolverListCommunicator.updateProfileViewButtonCount).isEqualTo(0)
+ assertThat(resolverListCommunicator.sendVoiceCommandCount).isEqualTo(0)
+
+ backgroundExecutor.runUntilIdle()
+
+ // we don't reset placeholder count (legacy logic, likely an oversight?)
+ assertThat(testSubject.placeholderCount).isEqualTo(placeholderCount)
+ assertThat(testSubject.hasFilteredItem()).isFalse()
+ assertThat(testSubject.count).isEqualTo(resolvedTargets.size)
+ assertThat(testSubject.getItem(0)?.targetIntent?.component)
+ .isEqualTo(initialIntents[0].component)
+ assertThat(testSubject.filteredItem).isNull()
+ assertThat(testSubject.filteredPosition).isLessThan(0)
+ assertThat(testSubject.unfilteredResolveList).containsExactlyElementsIn(resolvedTargets)
+ assertThat(testSubject.isTabLoaded).isTrue()
+ assertThat(resolverListCommunicator.updateProfileViewButtonCount).isEqualTo(1)
+ assertThat(resolverListCommunicator.sendVoiceCommandCount).isEqualTo(1)
+ assertThat(backgroundExecutor.pendingCommandCount).isEqualTo(0)
+ }
+
+ @Test
+ fun testPostListReadyAtEndOfRebuild_synchronous() {
+ val communicator = mock<ResolverListCommunicator> {}
+ val testSubject =
+ ResolverListAdapter(
+ context,
+ payloadIntents,
+ /*initialIntents=*/ null,
+ /*rList=*/ null,
+ /*filterLastUsed=*/ true,
+ resolverListController,
+ userHandle,
+ targetIntent,
+ communicator,
+ /*initialIntentsUserSpace=*/ userHandle,
+ targetDataLoader,
+ backgroundExecutor,
+ immediateExecutor,
+ )
+ val doPostProcessing = false
+
+ testSubject.rebuildList(doPostProcessing)
+
+ verify(communicator).onPostListReady(testSubject, doPostProcessing, true)
+ }
+
+ @Test
+ fun testPostListReadyAtEndOfRebuild_stages() {
+ // We need at least two targets to trigger asynchronous sorting/"staged" progress callbacks.
+ val resolvedTargets =
+ createResolvedComponents(
+ ComponentName(PKG_NAME, CLASS_NAME),
+ ComponentName(PKG_NAME_TWO, CLASS_NAME),
+ )
+ // TODO: there's a lot of boilerplate required for this test even to trigger the expected
+ // conditions; if the configuration is incorrect, the test may accidentally pass for the
+ // wrong reasons. Separating responsibilities to other components will help minimize the
+ // *amount* of boilerplate, but we should also consider setting up test defaults that work
+ // according to our usual expectations so that we don't overlook false-negative results.
+ whenever(
+ resolverListController.getResolversForIntentAsUser(
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ )
+ )
+ .thenReturn(resolvedTargets)
+ val communicator =
+ mock<ResolverListCommunicator> {
+ whenever(getReplacementIntent(any(), any())).thenAnswer { invocation ->
+ invocation.arguments[1]
+ }
+ }
+ val testSubject =
+ ResolverListAdapter(
+ context,
+ payloadIntents,
+ /*initialIntents=*/ null,
+ /*rList=*/ null,
+ /*filterLastUsed=*/ true,
+ resolverListController,
+ userHandle,
+ targetIntent,
+ communicator,
+ /*initialIntentsUserSpace=*/ userHandle,
+ targetDataLoader,
+ backgroundExecutor,
+ immediateExecutor,
+ )
+ val doPostProcessing = false
+
+ testSubject.rebuildList(doPostProcessing)
+
+ backgroundExecutor.runUntilIdle()
+
+ val inOrder = inOrder(communicator)
+ inOrder.verify(communicator).onPostListReady(testSubject, doPostProcessing, false)
+ inOrder.verify(communicator).onPostListReady(testSubject, doPostProcessing, true)
+ }
+
+ @Test
+ fun testPostListReadyAtEndOfRebuild_queued() {
+ val queuedCallbacksExecutor = TestExecutor()
+
+ // We need at least two targets to trigger asynchronous sorting/"staged" progress callbacks.
+ val resolvedTargets =
+ createResolvedComponents(
+ ComponentName(PKG_NAME, CLASS_NAME),
+ ComponentName(PKG_NAME_TWO, CLASS_NAME),
+ )
+ // TODO: there's a lot of boilerplate required for this test even to trigger the expected
+ // conditions; if the configuration is incorrect, the test may accidentally pass for the
+ // wrong reasons. Separating responsibilities to other components will help minimize the
+ // *amount* of boilerplate, but we should also consider setting up test defaults that work
+ // according to our usual expectations so that we don't overlook false-negative results.
+ whenever(
+ resolverListController.getResolversForIntentAsUser(
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ )
+ )
+ .thenReturn(resolvedTargets)
+ val communicator =
+ mock<ResolverListCommunicator> {
+ whenever(getReplacementIntent(any(), any())).thenAnswer { invocation ->
+ invocation.arguments[1]
+ }
+ }
+ val testSubject =
+ ResolverListAdapter(
+ context,
+ payloadIntents,
+ /*initialIntents=*/ null,
+ /*rList=*/ null,
+ /*filterLastUsed=*/ true,
+ resolverListController,
+ userHandle,
+ targetIntent,
+ communicator,
+ /*initialIntentsUserSpace=*/ userHandle,
+ targetDataLoader,
+ backgroundExecutor,
+ queuedCallbacksExecutor
+ )
+ val doPostProcessing = false
+ testSubject.rebuildList(doPostProcessing)
+
+ // Finish all the background work (enqueueing both the "partial" and "complete" progress
+ // callbacks) before dequeueing either callback.
+ backgroundExecutor.runUntilIdle()
+ queuedCallbacksExecutor.runUntilIdle()
+
+ // TODO: we may not necessarily care to assert that there's a "partial progress" callback in
+ // this case, since there won't be a chance to reflect the "partial" state in the UI before
+ // the "completion" is queued (and if we depend on seeing an intermediate state, that could
+ // be a bad sign for our handling in the "synchronous" case?). But we should probably at
+ // least assert that the "partial" callback never arrives *after* the completion?
+ val inOrder = inOrder(communicator)
+ inOrder.verify(communicator).onPostListReady(testSubject, doPostProcessing, false)
+ inOrder.verify(communicator).onPostListReady(testSubject, doPostProcessing, true)
+ }
+
+ @Test
+ fun testPostListReadyAtEndOfRebuild_skippedIfStillQueuedOnDestroy() {
+ val queuedCallbacksExecutor = TestExecutor()
+
+ // We need at least two targets to trigger asynchronous sorting/"staged" progress callbacks.
+ val resolvedTargets =
+ createResolvedComponents(
+ ComponentName(PKG_NAME, CLASS_NAME),
+ ComponentName(PKG_NAME_TWO, CLASS_NAME),
+ )
+ // TODO: there's a lot of boilerplate required for this test even to trigger the expected
+ // conditions; if the configuration is incorrect, the test may accidentally pass for the
+ // wrong reasons. Separating responsibilities to other components will help minimize the
+ // *amount* of boilerplate, but we should also consider setting up test defaults that work
+ // according to our usual expectations so that we don't overlook false-negative results.
+ whenever(
+ resolverListController.getResolversForIntentAsUser(
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ )
+ )
+ .thenReturn(resolvedTargets)
+ val communicator =
+ mock<ResolverListCommunicator> {
+ whenever(getReplacementIntent(any(), any())).thenAnswer { invocation ->
+ invocation.arguments[1]
+ }
+ }
+ val testSubject =
+ ResolverListAdapter(
+ context,
+ payloadIntents,
+ /*initialIntents=*/ null,
+ /*rList=*/ null,
+ /*filterLastUsed=*/ true,
+ resolverListController,
+ userHandle,
+ targetIntent,
+ communicator,
+ /*initialIntentsUserSpace=*/ userHandle,
+ targetDataLoader,
+ backgroundExecutor,
+ queuedCallbacksExecutor
+ )
+ val doPostProcessing = false
+ testSubject.rebuildList(doPostProcessing)
+
+ // Finish all the background work (enqueueing both the "partial" and "complete" progress
+ // callbacks) before dequeueing either callback.
+ backgroundExecutor.runUntilIdle()
+
+ // Notify that our activity is being destroyed while the callbacks are still queued.
+ testSubject.onDestroy()
+
+ queuedCallbacksExecutor.runUntilIdle()
+
+ verify(communicator, never()).onPostListReady(eq(testSubject), eq(doPostProcessing), any())
+ }
+
+ private fun createResolvedComponents(
+ vararg components: ComponentName
+ ): List<ResolvedComponentInfo> {
+ val result = ArrayList<ResolvedComponentInfo>(components.size)
+ for (component in components) {
+ val resolvedComponentInfo =
+ ResolvedComponentInfo(
+ ComponentName(PKG_NAME, CLASS_NAME),
+ targetIntent,
+ createResolveInfo(component.packageName, component.className)
+ )
+ result.add(resolvedComponentInfo)
+ }
+ return result
+ }
+
+ private fun createResolveInfo(packageName: String, className: String): ResolveInfo =
+ mock<ResolveInfo> {
+ activityInfo = createActivityInfo(ComponentName(packageName, className))
+ targetUserId = this@ResolverListAdapterTest.userHandle.identifier
+ userHandle = this@ResolverListAdapterTest.userHandle
+ }
+}