summaryrefslogtreecommitdiff
path: root/java/tests
diff options
context:
space:
mode:
author Govinda Wasserman <gwasserman@google.com> 2023-11-22 09:21:08 -0500
committer Govinda Wasserman <gwasserman@google.com> 2023-11-22 09:24:53 -0500
commitb34ae5d8650a1a4f074db68cb64c3b9648cb9275 (patch)
tree368b28971e081060582ff39ee234421acf417b02 /java/tests
parent55da290431232e15b5a8c175561ea57796b572d5 (diff)
Splits list controller into interfaces
Each interface has a single concern, allowing for list controllers to be built by composition. New list controllers are not currently in use, but will be in a future change once resolver comparators and list adapters get updated. Test: atest com.android.intentresolver.v2.listcontroller BUG: 302113519 Change-Id: Ie1d24571c07d1408aa80f8a86311d0fee5e78255
Diffstat (limited to 'java/tests')
-rw-r--r--java/tests/src/com/android/intentresolver/model/AbstractResolverComparatorTest.java6
-rw-r--r--java/tests/src/com/android/intentresolver/v2/listcontroller/ChooserRequestFilteredComponentsTest.kt61
-rw-r--r--java/tests/src/com/android/intentresolver/v2/listcontroller/FakeResolverComparator.kt83
-rw-r--r--java/tests/src/com/android/intentresolver/v2/listcontroller/FilterableComponentsTest.kt77
-rw-r--r--java/tests/src/com/android/intentresolver/v2/listcontroller/IntentResolverTest.kt499
-rw-r--r--java/tests/src/com/android/intentresolver/v2/listcontroller/LastChosenManagerTest.kt111
-rw-r--r--java/tests/src/com/android/intentresolver/v2/listcontroller/PinnableComponentsTest.kt74
-rw-r--r--java/tests/src/com/android/intentresolver/v2/listcontroller/ResolveListDeduperTest.kt125
-rw-r--r--java/tests/src/com/android/intentresolver/v2/listcontroller/ResolvedComponentFilteringTest.kt309
-rw-r--r--java/tests/src/com/android/intentresolver/v2/listcontroller/ResolvedComponentSortingTest.kt197
-rw-r--r--java/tests/src/com/android/intentresolver/v2/listcontroller/SharedPreferencesPinnedComponentsTest.kt63
11 files changed, 1602 insertions, 3 deletions
diff --git a/java/tests/src/com/android/intentresolver/model/AbstractResolverComparatorTest.java b/java/tests/src/com/android/intentresolver/model/AbstractResolverComparatorTest.java
index 5f0ead7b..2140a67d 100644
--- a/java/tests/src/com/android/intentresolver/model/AbstractResolverComparatorTest.java
+++ b/java/tests/src/com/android/intentresolver/model/AbstractResolverComparatorTest.java
@@ -118,14 +118,14 @@ public class AbstractResolverComparatorTest {
Lists.newArrayList(context.getUser()), promoteToFirst) {
@Override
- int compare(ResolveInfo lhs, ResolveInfo rhs) {
+ public int compare(ResolveInfo lhs, ResolveInfo rhs) {
// Used for testing pinning, so we should never get here --- the overrides
// should determine the result instead.
return 1;
}
@Override
- void doCompute(List<ResolvedComponentInfo> targets) {}
+ public void doCompute(List<ResolvedComponentInfo> targets) {}
@Override
public float getScore(TargetInfo targetInfo) {
@@ -133,7 +133,7 @@ public class AbstractResolverComparatorTest {
}
@Override
- void handleResultMessage(Message message) {}
+ public void handleResultMessage(Message message) {}
};
return testComparator;
}
diff --git a/java/tests/src/com/android/intentresolver/v2/listcontroller/ChooserRequestFilteredComponentsTest.kt b/java/tests/src/com/android/intentresolver/v2/listcontroller/ChooserRequestFilteredComponentsTest.kt
new file mode 100644
index 00000000..59494bed
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/v2/listcontroller/ChooserRequestFilteredComponentsTest.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.v2.listcontroller
+
+import android.content.ComponentName
+import com.android.intentresolver.ChooserRequestParameters
+import com.android.intentresolver.whenever
+import com.google.common.collect.ImmutableList
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+class ChooserRequestFilteredComponentsTest {
+
+ @Mock lateinit var mockChooserRequestParameters: ChooserRequestParameters
+
+ private lateinit var chooserRequestFilteredComponents: ChooserRequestFilteredComponents
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ chooserRequestFilteredComponents =
+ ChooserRequestFilteredComponents(mockChooserRequestParameters)
+ }
+
+ @Test
+ fun isComponentFiltered_returnsRequestParametersFilteredState() {
+ // Arrange
+ whenever(mockChooserRequestParameters.filteredComponentNames)
+ .thenReturn(
+ ImmutableList.of(ComponentName("FilteredPackage", "FilteredClass")),
+ )
+ val testComponent = ComponentName("TestPackage", "TestClass")
+ val filteredComponent = ComponentName("FilteredPackage", "FilteredClass")
+
+ // Act
+ val result = chooserRequestFilteredComponents.isComponentFiltered(testComponent)
+ val filteredResult = chooserRequestFilteredComponents.isComponentFiltered(filteredComponent)
+
+ // Assert
+ assertThat(result).isFalse()
+ assertThat(filteredResult).isTrue()
+ }
+}
diff --git a/java/tests/src/com/android/intentresolver/v2/listcontroller/FakeResolverComparator.kt b/java/tests/src/com/android/intentresolver/v2/listcontroller/FakeResolverComparator.kt
new file mode 100644
index 00000000..ce40567e
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/v2/listcontroller/FakeResolverComparator.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.v2.listcontroller
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ResolveInfo
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.os.Message
+import android.os.UserHandle
+import com.android.intentresolver.ResolvedComponentInfo
+import com.android.intentresolver.chooser.TargetInfo
+import com.android.intentresolver.model.AbstractResolverComparator
+import com.android.intentresolver.whenever
+import java.util.Locale
+import org.mockito.Mockito
+
+class FakeResolverComparator(
+ context: Context =
+ Mockito.mock(Context::class.java).also {
+ val mockResources = Mockito.mock(Resources::class.java)
+ whenever(it.resources).thenReturn(mockResources)
+ whenever(mockResources.configuration)
+ .thenReturn(Configuration().apply { setLocale(Locale.US) })
+ },
+ targetIntent: Intent = Intent("TestAction"),
+ resolvedActivityUserSpaceList: List<UserHandle> = emptyList(),
+ promoteToFirst: ComponentName? = null,
+) :
+ AbstractResolverComparator(
+ context,
+ targetIntent,
+ resolvedActivityUserSpaceList,
+ promoteToFirst,
+ ) {
+ var lastUpdateModel: TargetInfo? = null
+ private set
+ var lastUpdateChooserCounts: Triple<String, UserHandle, String>? = null
+ private set
+ var destroyCalled = false
+ private set
+
+ override fun compare(lhs: ResolveInfo?, rhs: ResolveInfo?): Int =
+ lhs!!.activityInfo.packageName.compareTo(rhs!!.activityInfo.packageName)
+
+ override fun doCompute(targets: MutableList<ResolvedComponentInfo>?) {}
+
+ override fun getScore(targetInfo: TargetInfo?): Float = 1.23f
+
+ override fun handleResultMessage(message: Message?) {}
+
+ override fun updateModel(targetInfo: TargetInfo?) {
+ lastUpdateModel = targetInfo
+ }
+
+ override fun updateChooserCounts(
+ packageName: String,
+ user: UserHandle,
+ action: String,
+ ) {
+ lastUpdateChooserCounts = Triple(packageName, user, action)
+ }
+
+ override fun destroy() {
+ destroyCalled = true
+ }
+}
diff --git a/java/tests/src/com/android/intentresolver/v2/listcontroller/FilterableComponentsTest.kt b/java/tests/src/com/android/intentresolver/v2/listcontroller/FilterableComponentsTest.kt
new file mode 100644
index 00000000..396505e6
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/v2/listcontroller/FilterableComponentsTest.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.v2.listcontroller
+
+import android.content.ComponentName
+import com.android.intentresolver.ChooserRequestParameters
+import com.android.intentresolver.whenever
+import com.google.common.collect.ImmutableList
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+class FilterableComponentsTest {
+
+ @Mock lateinit var mockChooserRequestParameters: ChooserRequestParameters
+
+ private val unfilteredComponent = ComponentName("TestPackage", "TestClass")
+ private val filteredComponent = ComponentName("FilteredPackage", "FilteredClass")
+ private val noComponentFiltering = NoComponentFiltering()
+
+ private lateinit var chooserRequestFilteredComponents: ChooserRequestFilteredComponents
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ chooserRequestFilteredComponents =
+ ChooserRequestFilteredComponents(mockChooserRequestParameters)
+ }
+
+ @Test
+ fun isComponentFiltered_noComponentFiltering_neverFilters() {
+ // Arrange
+
+ // Act
+ val unfilteredResult = noComponentFiltering.isComponentFiltered(unfilteredComponent)
+ val filteredResult = noComponentFiltering.isComponentFiltered(filteredComponent)
+
+ // Assert
+ assertThat(unfilteredResult).isFalse()
+ assertThat(filteredResult).isFalse()
+ }
+
+ @Test
+ fun isComponentFiltered_chooserRequestFilteredComponents_filtersAccordingToChooserRequest() {
+ // Arrange
+ whenever(mockChooserRequestParameters.filteredComponentNames)
+ .thenReturn(
+ ImmutableList.of(filteredComponent),
+ )
+
+ // Act
+ val unfilteredResult =
+ chooserRequestFilteredComponents.isComponentFiltered(unfilteredComponent)
+ val filteredResult = chooserRequestFilteredComponents.isComponentFiltered(filteredComponent)
+
+ // Assert
+ assertThat(unfilteredResult).isFalse()
+ assertThat(filteredResult).isTrue()
+ }
+}
diff --git a/java/tests/src/com/android/intentresolver/v2/listcontroller/IntentResolverTest.kt b/java/tests/src/com/android/intentresolver/v2/listcontroller/IntentResolverTest.kt
new file mode 100644
index 00000000..09f6d373
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/v2/listcontroller/IntentResolverTest.kt
@@ -0,0 +1,499 @@
+/*
+ * 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.v2.listcontroller
+
+import android.content.ComponentName
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.net.Uri
+import android.os.UserHandle
+import com.android.intentresolver.any
+import com.android.intentresolver.eq
+import com.android.intentresolver.kotlinArgumentCaptor
+import com.android.intentresolver.whenever
+import com.google.common.truth.Truth.assertThat
+import java.lang.IndexOutOfBoundsException
+import org.junit.Assert.assertThrows
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+class IntentResolverTest {
+
+ @Mock lateinit var mockPackageManager: PackageManager
+
+ private lateinit var intentResolver: IntentResolver
+
+ private val fakePinnableComponents =
+ object : PinnableComponents {
+ override fun isComponentPinned(name: ComponentName): Boolean {
+ return name.packageName == "PinnedPackage"
+ }
+ }
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ intentResolver =
+ IntentResolverImpl(mockPackageManager, ResolveListDeduperImpl(fakePinnableComponents))
+ }
+
+ @Test
+ fun getResolversForIntentAsUser_noIntents_returnsEmptyList() {
+ // Arrange
+ val testIntents = emptyList<Intent>()
+
+ // Act
+ val result =
+ intentResolver.getResolversForIntentAsUser(
+ shouldGetResolvedFilter = false,
+ shouldGetActivityMetadata = false,
+ shouldGetOnlyDefaultActivities = false,
+ intents = testIntents,
+ userHandle = UserHandle(456),
+ )
+
+ // Assert
+ assertThat(result).isEmpty()
+ }
+
+ @Test
+ fun getResolversForIntentAsUser_noResolveInfo_returnsEmptyList() {
+ // Arrange
+ val testIntents = listOf(Intent("TestAction"))
+ val testResolveInfos = emptyList<ResolveInfo>()
+ whenever(mockPackageManager.queryIntentActivitiesAsUser(any(), anyInt(), any<UserHandle>()))
+ .thenReturn(testResolveInfos)
+
+ // Act
+ val result =
+ intentResolver.getResolversForIntentAsUser(
+ shouldGetResolvedFilter = false,
+ shouldGetActivityMetadata = false,
+ shouldGetOnlyDefaultActivities = false,
+ intents = testIntents,
+ userHandle = UserHandle(456),
+ )
+
+ // Assert
+ assertThat(result).isEmpty()
+ }
+
+ @Test
+ fun getResolversForIntentAsUser_returnsAllResolveComponentInfo() {
+ // Arrange
+ val testIntent1 = Intent("TestAction1")
+ val testIntent2 = Intent("TestAction2")
+ val testIntents = listOf(testIntent1, testIntent2)
+ val testResolveInfos1 =
+ listOf(
+ ResolveInfo().apply {
+ userHandle = UserHandle(456)
+ activityInfo = ActivityInfo()
+ activityInfo.packageName = "TestPackage1"
+ activityInfo.name = "TestClass1"
+ },
+ ResolveInfo().apply {
+ userHandle = UserHandle(456)
+ activityInfo = ActivityInfo()
+ activityInfo.packageName = "TestPackage2"
+ activityInfo.name = "TestClass2"
+ },
+ )
+ val testResolveInfos2 =
+ listOf(
+ ResolveInfo().apply {
+ userHandle = UserHandle(456)
+ activityInfo = ActivityInfo()
+ activityInfo.packageName = "TestPackage3"
+ activityInfo.name = "TestClass3"
+ },
+ ResolveInfo().apply {
+ userHandle = UserHandle(456)
+ activityInfo = ActivityInfo()
+ activityInfo.packageName = "TestPackage4"
+ activityInfo.name = "TestClass4"
+ },
+ )
+ whenever(
+ mockPackageManager.queryIntentActivitiesAsUser(
+ eq(testIntent1),
+ anyInt(),
+ any<UserHandle>(),
+ )
+ )
+ .thenReturn(testResolveInfos1)
+ whenever(
+ mockPackageManager.queryIntentActivitiesAsUser(
+ eq(testIntent2),
+ anyInt(),
+ any<UserHandle>(),
+ )
+ )
+ .thenReturn(testResolveInfos2)
+
+ // Act
+ val result =
+ intentResolver.getResolversForIntentAsUser(
+ shouldGetResolvedFilter = false,
+ shouldGetActivityMetadata = false,
+ shouldGetOnlyDefaultActivities = false,
+ intents = testIntents,
+ userHandle = UserHandle(456),
+ )
+
+ // Assert
+ result.forEachIndexed { index, it ->
+ val postfix = index + 1
+ assertThat(it.name.packageName).isEqualTo("TestPackage$postfix")
+ assertThat(it.name.className).isEqualTo("TestClass$postfix")
+ assertThrows(IndexOutOfBoundsException::class.java) { it.getIntentAt(1) }
+ }
+ assertThat(result.map { it.getIntentAt(0) })
+ .containsExactly(
+ testIntent1,
+ testIntent1,
+ testIntent2,
+ testIntent2,
+ )
+ }
+
+ @Test
+ fun getResolversForIntentAsUser_resolveInfoWithoutUserHandle_isSkipped() {
+ // Arrange
+ val testIntent = Intent("TestAction")
+ val testIntents = listOf(testIntent)
+ val testResolveInfos =
+ listOf(
+ ResolveInfo().apply {
+ activityInfo = ActivityInfo()
+ activityInfo.packageName = "TestPackage"
+ activityInfo.name = "TestClass"
+ },
+ )
+ whenever(
+ mockPackageManager.queryIntentActivitiesAsUser(
+ any(),
+ anyInt(),
+ any<UserHandle>(),
+ )
+ )
+ .thenReturn(testResolveInfos)
+
+ // Act
+ val result =
+ intentResolver.getResolversForIntentAsUser(
+ shouldGetResolvedFilter = false,
+ shouldGetActivityMetadata = false,
+ shouldGetOnlyDefaultActivities = false,
+ intents = testIntents,
+ userHandle = UserHandle(456),
+ )
+
+ // Assert
+ assertThat(result).isEmpty()
+ }
+
+ @Test
+ fun getResolversForIntentAsUser_duplicateComponents_areCombined() {
+ // Arrange
+ val testIntent1 = Intent("TestAction1")
+ val testIntent2 = Intent("TestAction2")
+ val testIntents = listOf(testIntent1, testIntent2)
+ val testResolveInfos1 =
+ listOf(
+ ResolveInfo().apply {
+ userHandle = UserHandle(456)
+ activityInfo = ActivityInfo()
+ activityInfo.packageName = "DuplicatePackage"
+ activityInfo.name = "DuplicateClass"
+ },
+ )
+ val testResolveInfos2 =
+ listOf(
+ ResolveInfo().apply {
+ userHandle = UserHandle(456)
+ activityInfo = ActivityInfo()
+ activityInfo.packageName = "DuplicatePackage"
+ activityInfo.name = "DuplicateClass"
+ },
+ )
+ whenever(
+ mockPackageManager.queryIntentActivitiesAsUser(
+ eq(testIntent1),
+ anyInt(),
+ any<UserHandle>(),
+ )
+ )
+ .thenReturn(testResolveInfos1)
+ whenever(
+ mockPackageManager.queryIntentActivitiesAsUser(
+ eq(testIntent2),
+ anyInt(),
+ any<UserHandle>(),
+ )
+ )
+ .thenReturn(testResolveInfos2)
+
+ // Act
+ val result =
+ intentResolver.getResolversForIntentAsUser(
+ shouldGetResolvedFilter = false,
+ shouldGetActivityMetadata = false,
+ shouldGetOnlyDefaultActivities = false,
+ intents = testIntents,
+ userHandle = UserHandle(456),
+ )
+
+ // Assert
+ assertThat(result).hasSize(1)
+ with(result.first()) {
+ assertThat(name.packageName).isEqualTo("DuplicatePackage")
+ assertThat(name.className).isEqualTo("DuplicateClass")
+ assertThat(getIntentAt(0)).isEqualTo(testIntent1)
+ assertThat(getIntentAt(1)).isEqualTo(testIntent2)
+ assertThrows(IndexOutOfBoundsException::class.java) { getIntentAt(2) }
+ }
+ }
+
+ @Test
+ fun getResolversForIntentAsUser_pinnedComponentsArePinned() {
+ // Arrange
+ val testIntent1 = Intent("TestAction1")
+ val testIntent2 = Intent("TestAction2")
+ val testIntents = listOf(testIntent1, testIntent2)
+ val testResolveInfos1 =
+ listOf(
+ ResolveInfo().apply {
+ userHandle = UserHandle(456)
+ activityInfo = ActivityInfo()
+ activityInfo.packageName = "UnpinnedPackage"
+ activityInfo.name = "UnpinnedClass"
+ },
+ )
+ val testResolveInfos2 =
+ listOf(
+ ResolveInfo().apply {
+ userHandle = UserHandle(456)
+ activityInfo = ActivityInfo()
+ activityInfo.packageName = "PinnedPackage"
+ activityInfo.name = "PinnedClass"
+ },
+ )
+ whenever(
+ mockPackageManager.queryIntentActivitiesAsUser(
+ eq(testIntent1),
+ anyInt(),
+ any<UserHandle>(),
+ )
+ )
+ .thenReturn(testResolveInfos1)
+ whenever(
+ mockPackageManager.queryIntentActivitiesAsUser(
+ eq(testIntent2),
+ anyInt(),
+ any<UserHandle>(),
+ )
+ )
+ .thenReturn(testResolveInfos2)
+
+ // Act
+ val result =
+ intentResolver.getResolversForIntentAsUser(
+ shouldGetResolvedFilter = false,
+ shouldGetActivityMetadata = false,
+ shouldGetOnlyDefaultActivities = false,
+ intents = testIntents,
+ userHandle = UserHandle(456),
+ )
+
+ // Assert
+ assertThat(result.map { it.isPinned }).containsExactly(false, true)
+ }
+
+ @Test
+ fun getResolversForIntentAsUser_whenNoExtraBehavior_usesBaseFlags() {
+ // Arrange
+ val baseFlags =
+ PackageManager.MATCH_DIRECT_BOOT_AWARE or
+ PackageManager.MATCH_DIRECT_BOOT_UNAWARE or
+ PackageManager.MATCH_CLONE_PROFILE
+ val testIntent = Intent()
+ val testIntents = listOf(testIntent)
+
+ // Act
+ intentResolver.getResolversForIntentAsUser(
+ shouldGetResolvedFilter = false,
+ shouldGetActivityMetadata = false,
+ shouldGetOnlyDefaultActivities = false,
+ intents = testIntents,
+ userHandle = UserHandle(456),
+ )
+
+ // Assert
+ val flags = kotlinArgumentCaptor<Int>()
+ verify(mockPackageManager)
+ .queryIntentActivitiesAsUser(
+ any(),
+ flags.capture(),
+ any<UserHandle>(),
+ )
+ assertThat(flags.value).isEqualTo(baseFlags)
+ }
+
+ @Test
+ fun getResolversForIntentAsUser_whenShouldGetResolvedFilter_usesGetResolvedFilterFlag() {
+ // Arrange
+ val testIntent = Intent()
+ val testIntents = listOf(testIntent)
+
+ // Act
+ intentResolver.getResolversForIntentAsUser(
+ shouldGetResolvedFilter = true,
+ shouldGetActivityMetadata = false,
+ shouldGetOnlyDefaultActivities = false,
+ intents = testIntents,
+ userHandle = UserHandle(456),
+ )
+
+ // Assert
+ val flags = kotlinArgumentCaptor<Int>()
+ verify(mockPackageManager)
+ .queryIntentActivitiesAsUser(
+ any(),
+ flags.capture(),
+ any<UserHandle>(),
+ )
+ assertThat(flags.value and PackageManager.GET_RESOLVED_FILTER)
+ .isEqualTo(PackageManager.GET_RESOLVED_FILTER)
+ }
+
+ @Test
+ fun getResolversForIntentAsUser_whenShouldGetActivityMetadata_usesGetMetaDataFlag() {
+ // Arrange
+ val testIntent = Intent()
+ val testIntents = listOf(testIntent)
+
+ // Act
+ intentResolver.getResolversForIntentAsUser(
+ shouldGetResolvedFilter = false,
+ shouldGetActivityMetadata = true,
+ shouldGetOnlyDefaultActivities = false,
+ intents = testIntents,
+ userHandle = UserHandle(456),
+ )
+
+ // Assert
+ val flags = kotlinArgumentCaptor<Int>()
+ verify(mockPackageManager)
+ .queryIntentActivitiesAsUser(
+ any(),
+ flags.capture(),
+ any<UserHandle>(),
+ )
+ assertThat(flags.value and PackageManager.GET_META_DATA)
+ .isEqualTo(PackageManager.GET_META_DATA)
+ }
+
+ @Test
+ fun getResolversForIntentAsUser_whenShouldGetOnlyDefaultActivities_usesMatchDefaultOnlyFlag() {
+ // Arrange
+ val testIntent = Intent()
+ val testIntents = listOf(testIntent)
+
+ // Act
+ intentResolver.getResolversForIntentAsUser(
+ shouldGetResolvedFilter = false,
+ shouldGetActivityMetadata = false,
+ shouldGetOnlyDefaultActivities = true,
+ intents = testIntents,
+ userHandle = UserHandle(456),
+ )
+
+ // Assert
+ val flags = kotlinArgumentCaptor<Int>()
+ verify(mockPackageManager)
+ .queryIntentActivitiesAsUser(
+ any(),
+ flags.capture(),
+ any<UserHandle>(),
+ )
+ assertThat(flags.value and PackageManager.MATCH_DEFAULT_ONLY)
+ .isEqualTo(PackageManager.MATCH_DEFAULT_ONLY)
+ }
+
+ @Test
+ fun getResolversForIntentAsUser_whenWebIntent_usesMatchInstantFlag() {
+ // Arrange
+ val testIntent = Intent(Intent.ACTION_VIEW, Uri.fromParts(IntentFilter.SCHEME_HTTP, "", ""))
+ val testIntents = listOf(testIntent)
+
+ // Act
+ intentResolver.getResolversForIntentAsUser(
+ shouldGetResolvedFilter = false,
+ shouldGetActivityMetadata = false,
+ shouldGetOnlyDefaultActivities = false,
+ intents = testIntents,
+ userHandle = UserHandle(456),
+ )
+
+ // Assert
+ val flags = kotlinArgumentCaptor<Int>()
+ verify(mockPackageManager)
+ .queryIntentActivitiesAsUser(
+ any(),
+ flags.capture(),
+ any<UserHandle>(),
+ )
+ assertThat(flags.value and PackageManager.MATCH_INSTANT)
+ .isEqualTo(PackageManager.MATCH_INSTANT)
+ }
+
+ @Test
+ fun getResolversForIntentAsUser_whenActivityMatchExternalFlag_usesMatchInstantFlag() {
+ // Arrange
+ val testIntent = Intent().addFlags(Intent.FLAG_ACTIVITY_MATCH_EXTERNAL)
+ val testIntents = listOf(testIntent)
+
+ // Act
+ intentResolver.getResolversForIntentAsUser(
+ shouldGetResolvedFilter = false,
+ shouldGetActivityMetadata = false,
+ shouldGetOnlyDefaultActivities = false,
+ intents = testIntents,
+ userHandle = UserHandle(456),
+ )
+
+ // Assert
+ val flags = kotlinArgumentCaptor<Int>()
+ verify(mockPackageManager)
+ .queryIntentActivitiesAsUser(
+ any(),
+ flags.capture(),
+ any<UserHandle>(),
+ )
+ assertThat(flags.value and PackageManager.MATCH_INSTANT)
+ .isEqualTo(PackageManager.MATCH_INSTANT)
+ }
+}
diff --git a/java/tests/src/com/android/intentresolver/v2/listcontroller/LastChosenManagerTest.kt b/java/tests/src/com/android/intentresolver/v2/listcontroller/LastChosenManagerTest.kt
new file mode 100644
index 00000000..ce5e52b1
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/v2/listcontroller/LastChosenManagerTest.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.v2.listcontroller
+
+import android.content.ComponentName
+import android.content.ContentResolver
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.IPackageManager
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import com.android.intentresolver.any
+import com.android.intentresolver.eq
+import com.android.intentresolver.nullable
+import com.android.intentresolver.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.isNull
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class LastChosenManagerTest {
+
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+ private val testTargetIntent = Intent("TestAction")
+
+ @Mock lateinit var mockContentResolver: ContentResolver
+ @Mock lateinit var mockIPackageManager: IPackageManager
+
+ private lateinit var lastChosenManager: LastChosenManager
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ lastChosenManager =
+ PackageManagerLastChosenManager(mockContentResolver, testDispatcher, testTargetIntent) {
+ mockIPackageManager
+ }
+ }
+
+ @Test
+ fun getLastChosen_returnsLastChosenActivity() =
+ testScope.runTest {
+ // Arrange
+ val testResolveInfo = ResolveInfo()
+ whenever(mockIPackageManager.getLastChosenActivity(any(), nullable(), any()))
+ .thenReturn(testResolveInfo)
+
+ // Act
+ val lastChosen = lastChosenManager.getLastChosen()
+ runCurrent()
+
+ // Assert
+ verify(mockIPackageManager)
+ .getLastChosenActivity(
+ eq(testTargetIntent),
+ isNull(),
+ eq(PackageManager.MATCH_DEFAULT_ONLY),
+ )
+ assertThat(lastChosen).isSameInstanceAs(testResolveInfo)
+ }
+
+ @Test
+ fun setLastChosen_setsLastChosenActivity() =
+ testScope.runTest {
+ // Arrange
+ val testComponent = ComponentName("TestPackage", "TestClass")
+ val testIntent = Intent().apply { component = testComponent }
+ val testIntentFilter = IntentFilter()
+ val testMatch = 456
+
+ // Act
+ lastChosenManager.setLastChosen(testIntent, testIntentFilter, testMatch)
+ runCurrent()
+
+ // Assert
+ verify(mockIPackageManager)
+ .setLastChosenActivity(
+ eq(testIntent),
+ isNull(),
+ eq(PackageManager.MATCH_DEFAULT_ONLY),
+ eq(testIntentFilter),
+ eq(testMatch),
+ eq(testComponent),
+ )
+ }
+}
diff --git a/java/tests/src/com/android/intentresolver/v2/listcontroller/PinnableComponentsTest.kt b/java/tests/src/com/android/intentresolver/v2/listcontroller/PinnableComponentsTest.kt
new file mode 100644
index 00000000..112342ad
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/v2/listcontroller/PinnableComponentsTest.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.v2.listcontroller
+
+import android.content.ComponentName
+import android.content.SharedPreferences
+import com.android.intentresolver.any
+import com.android.intentresolver.eq
+import com.android.intentresolver.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+class PinnableComponentsTest {
+
+ @Mock lateinit var mockSharedPreferences: SharedPreferences
+
+ private val unpinnedComponent = ComponentName("TestPackage", "TestClass")
+ private val pinnedComponent = ComponentName("PinnedPackage", "PinnedClass")
+ private val noComponentPinning = NoComponentPinning()
+
+ private lateinit var sharedPreferencesPinnedComponents: PinnableComponents
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ sharedPreferencesPinnedComponents = SharedPreferencesPinnedComponents(mockSharedPreferences)
+ }
+
+ @Test
+ fun isComponentPinned_noComponentPinning_neverPins() {
+ // Arrange
+
+ // Act
+ val unpinnedResult = noComponentPinning.isComponentPinned(unpinnedComponent)
+ val pinnedResult = noComponentPinning.isComponentPinned(pinnedComponent)
+
+ // Assert
+ assertThat(unpinnedResult).isFalse()
+ assertThat(pinnedResult).isFalse()
+ }
+
+ @Test
+ fun isComponentFiltered_chooserRequestFilteredComponents_filtersAccordingToChooserRequest() {
+ // Arrange
+ whenever(mockSharedPreferences.getBoolean(eq(pinnedComponent.flattenToString()), any()))
+ .thenReturn(true)
+
+ // Act
+ val unpinnedResult = sharedPreferencesPinnedComponents.isComponentPinned(unpinnedComponent)
+ val pinnedResult = sharedPreferencesPinnedComponents.isComponentPinned(pinnedComponent)
+
+ // Assert
+ assertThat(unpinnedResult).isFalse()
+ assertThat(pinnedResult).isTrue()
+ }
+}
diff --git a/java/tests/src/com/android/intentresolver/v2/listcontroller/ResolveListDeduperTest.kt b/java/tests/src/com/android/intentresolver/v2/listcontroller/ResolveListDeduperTest.kt
new file mode 100644
index 00000000..26f0199e
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/v2/listcontroller/ResolveListDeduperTest.kt
@@ -0,0 +1,125 @@
+/*
+ * 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.v2.listcontroller
+
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.ResolveInfo
+import android.os.UserHandle
+import com.android.intentresolver.ResolvedComponentInfo
+import com.google.common.truth.Truth.assertThat
+import java.lang.IndexOutOfBoundsException
+import org.junit.Assert.assertThrows
+import org.junit.Before
+import org.junit.Test
+
+class ResolveListDeduperTest {
+
+ private lateinit var resolveListDeduper: ResolveListDeduper
+
+ @Before
+ fun setup() {
+ resolveListDeduper = ResolveListDeduperImpl(NoComponentPinning())
+ }
+
+ @Test
+ fun addResolveListDedupe_addsDifferentComponents() {
+ // Arrange
+ val testIntent = Intent()
+ val testResolveInfo1 =
+ ResolveInfo().apply {
+ userHandle = UserHandle(456)
+ activityInfo = ActivityInfo()
+ activityInfo.packageName = "TestPackage1"
+ activityInfo.name = "TestClass1"
+ }
+ val testResolveInfo2 =
+ ResolveInfo().apply {
+ userHandle = UserHandle(456)
+ activityInfo = ActivityInfo()
+ activityInfo.packageName = "TestPackage2"
+ activityInfo.name = "TestClass2"
+ }
+ val testResolvedComponentInfo1 =
+ ResolvedComponentInfo(
+ ComponentName("TestPackage1", "TestClass1"),
+ testIntent,
+ testResolveInfo1,
+ )
+ .apply { isPinned = false }
+ val listUnderTest = mutableListOf(testResolvedComponentInfo1)
+ val listToAdd = listOf(testResolveInfo2)
+
+ // Act
+ resolveListDeduper.addToResolveListWithDedupe(
+ into = listUnderTest,
+ intent = testIntent,
+ from = listToAdd,
+ )
+
+ // Assert
+ listUnderTest.forEachIndexed { index, it ->
+ val postfix = index + 1
+ assertThat(it.name.packageName).isEqualTo("TestPackage$postfix")
+ assertThat(it.name.className).isEqualTo("TestClass$postfix")
+ assertThat(it.getIntentAt(0)).isEqualTo(testIntent)
+ assertThrows(IndexOutOfBoundsException::class.java) { it.getIntentAt(1) }
+ }
+ }
+
+ @Test
+ fun addResolveListDedupe_combinesDuplicateComponents() {
+ // Arrange
+ val testIntent = Intent()
+ val testResolveInfo1 =
+ ResolveInfo().apply {
+ userHandle = UserHandle(456)
+ activityInfo = ActivityInfo()
+ activityInfo.packageName = "DuplicatePackage"
+ activityInfo.name = "DuplicateClass"
+ }
+ val testResolveInfo2 =
+ ResolveInfo().apply {
+ userHandle = UserHandle(456)
+ activityInfo = ActivityInfo()
+ activityInfo.packageName = "DuplicatePackage"
+ activityInfo.name = "DuplicateClass"
+ }
+ val testResolvedComponentInfo1 =
+ ResolvedComponentInfo(
+ ComponentName("DuplicatePackage", "DuplicateClass"),
+ testIntent,
+ testResolveInfo1,
+ )
+ .apply { isPinned = false }
+ val listUnderTest = mutableListOf(testResolvedComponentInfo1)
+ val listToAdd = listOf(testResolveInfo2)
+
+ // Act
+ resolveListDeduper.addToResolveListWithDedupe(
+ into = listUnderTest,
+ intent = testIntent,
+ from = listToAdd,
+ )
+
+ // Assert
+ assertThat(listUnderTest).containsExactly(testResolvedComponentInfo1)
+ assertThat(testResolvedComponentInfo1.getResolveInfoAt(0)).isEqualTo(testResolveInfo1)
+ assertThat(testResolvedComponentInfo1.getResolveInfoAt(1)).isEqualTo(testResolveInfo2)
+ }
+}
diff --git a/java/tests/src/com/android/intentresolver/v2/listcontroller/ResolvedComponentFilteringTest.kt b/java/tests/src/com/android/intentresolver/v2/listcontroller/ResolvedComponentFilteringTest.kt
new file mode 100644
index 00000000..9786b801
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/v2/listcontroller/ResolvedComponentFilteringTest.kt
@@ -0,0 +1,309 @@
+/*
+ * 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.v2.listcontroller
+
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import com.android.intentresolver.ResolvedComponentInfo
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertThrows
+import org.junit.Before
+import org.junit.Test
+
+class ResolvedComponentFilteringTest {
+
+ private lateinit var resolvedComponentFiltering: ResolvedComponentFiltering
+
+ private val fakeFilterableComponents =
+ object : FilterableComponents {
+ override fun isComponentFiltered(name: ComponentName): Boolean {
+ return name.packageName == "FilteredPackage"
+ }
+ }
+
+ private val fakePermissionChecker =
+ object : PermissionChecker {
+ override suspend fun checkComponentPermission(
+ permission: String,
+ uid: Int,
+ owningUid: Int,
+ exported: Boolean
+ ): Int {
+ return if (permission == "MissingPermission") {
+ PackageManager.PERMISSION_DENIED
+ } else {
+ PackageManager.PERMISSION_GRANTED
+ }
+ }
+ }
+
+ @Before
+ fun setup() {
+ resolvedComponentFiltering =
+ ResolvedComponentFilteringImpl(
+ launchedFromUid = 123,
+ filterableComponents = fakeFilterableComponents,
+ permissionChecker = fakePermissionChecker,
+ )
+ }
+
+ @Test
+ fun filterIneligibleActivities_returnsListWithoutFilteredComponents() = runTest {
+ // Arrange
+ val testIntent = Intent("TestAction")
+ val testResolveInfo =
+ ResolveInfo().apply {
+ activityInfo = ActivityInfo()
+ activityInfo.packageName = "TestPackage"
+ activityInfo.name = "TestClass"
+ activityInfo.permission = "TestPermission"
+ activityInfo.applicationInfo = ApplicationInfo()
+ activityInfo.applicationInfo.uid = 456
+ activityInfo.exported = false
+ }
+ val filteredResolveInfo =
+ ResolveInfo().apply {
+ activityInfo = ActivityInfo()
+ activityInfo.packageName = "FilteredPackage"
+ activityInfo.name = "FilteredClass"
+ activityInfo.permission = "TestPermission"
+ activityInfo.applicationInfo = ApplicationInfo()
+ activityInfo.applicationInfo.uid = 456
+ activityInfo.exported = false
+ }
+ val missingPermissionResolveInfo =
+ ResolveInfo().apply {
+ activityInfo = ActivityInfo()
+ activityInfo.packageName = "NoPermissionPackage"
+ activityInfo.name = "NoPermissionClass"
+ activityInfo.permission = "MissingPermission"
+ activityInfo.applicationInfo = ApplicationInfo()
+ activityInfo.applicationInfo.uid = 456
+ activityInfo.exported = false
+ }
+ val testInput =
+ listOf(
+ ResolvedComponentInfo(
+ ComponentName("TestPackage", "TestClass"),
+ testIntent,
+ testResolveInfo,
+ ),
+ ResolvedComponentInfo(
+ ComponentName("FilteredPackage", "FilteredClass"),
+ testIntent,
+ filteredResolveInfo,
+ ),
+ ResolvedComponentInfo(
+ ComponentName("NoPermissionPackage", "NoPermissionClass"),
+ testIntent,
+ missingPermissionResolveInfo,
+ )
+ )
+
+ // Act
+ val result = resolvedComponentFiltering.filterIneligibleActivities(testInput)
+
+ // Assert
+ assertThat(result).hasSize(1)
+ with(result.first()) {
+ assertThat(name.packageName).isEqualTo("TestPackage")
+ assertThat(name.className).isEqualTo("TestClass")
+ assertThat(getIntentAt(0)).isEqualTo(testIntent)
+ assertThrows(IndexOutOfBoundsException::class.java) { getIntentAt(1) }
+ assertThat(getResolveInfoAt(0)).isEqualTo(testResolveInfo)
+ assertThrows(IndexOutOfBoundsException::class.java) { getResolveInfoAt(1) }
+ }
+ }
+
+ @Test
+ fun filterLowPriority_filtersAfterFirstDifferentPriority() {
+ // Arrange
+ val testIntent = Intent("TestAction")
+ val testResolveInfo =
+ ResolveInfo().apply {
+ priority = 1
+ isDefault = true
+ }
+ val equalResolveInfo =
+ ResolveInfo().apply {
+ priority = 1
+ isDefault = true
+ }
+ val diffResolveInfo =
+ ResolveInfo().apply {
+ priority = 2
+ isDefault = true
+ }
+ val testInput =
+ listOf(
+ ResolvedComponentInfo(
+ ComponentName("TestPackage", "TestClass"),
+ testIntent,
+ testResolveInfo,
+ ),
+ ResolvedComponentInfo(
+ ComponentName("EqualPackage", "EqualClass"),
+ testIntent,
+ equalResolveInfo,
+ ),
+ ResolvedComponentInfo(
+ ComponentName("DiffPackage", "DiffClass"),
+ testIntent,
+ diffResolveInfo,
+ ),
+ )
+
+ // Act
+ val result = resolvedComponentFiltering.filterLowPriority(testInput)
+
+ // Assert
+ assertThat(result).hasSize(2)
+ with(result.first()) {
+ assertThat(name.packageName).isEqualTo("TestPackage")
+ assertThat(name.className).isEqualTo("TestClass")
+ assertThat(getIntentAt(0)).isEqualTo(testIntent)
+ assertThrows(IndexOutOfBoundsException::class.java) { getIntentAt(1) }
+ assertThat(getResolveInfoAt(0)).isEqualTo(testResolveInfo)
+ assertThrows(IndexOutOfBoundsException::class.java) { getResolveInfoAt(1) }
+ }
+ with(result[1]) {
+ assertThat(name.packageName).isEqualTo("EqualPackage")
+ assertThat(name.className).isEqualTo("EqualClass")
+ assertThat(getIntentAt(0)).isEqualTo(testIntent)
+ assertThrows(IndexOutOfBoundsException::class.java) { getIntentAt(1) }
+ assertThat(getResolveInfoAt(0)).isEqualTo(equalResolveInfo)
+ assertThrows(IndexOutOfBoundsException::class.java) { getResolveInfoAt(1) }
+ }
+ }
+
+ @Test
+ fun filterLowPriority_filtersAfterFirstDifferentDefault() {
+ // Arrange
+ val testIntent = Intent("TestAction")
+ val testResolveInfo =
+ ResolveInfo().apply {
+ priority = 1
+ isDefault = true
+ }
+ val equalResolveInfo =
+ ResolveInfo().apply {
+ priority = 1
+ isDefault = true
+ }
+ val diffResolveInfo =
+ ResolveInfo().apply {
+ priority = 1
+ isDefault = false
+ }
+ val testInput =
+ listOf(
+ ResolvedComponentInfo(
+ ComponentName("TestPackage", "TestClass"),
+ testIntent,
+ testResolveInfo,
+ ),
+ ResolvedComponentInfo(
+ ComponentName("EqualPackage", "EqualClass"),
+ testIntent,
+ equalResolveInfo,
+ ),
+ ResolvedComponentInfo(
+ ComponentName("DiffPackage", "DiffClass"),
+ testIntent,
+ diffResolveInfo,
+ ),
+ )
+
+ // Act
+ val result = resolvedComponentFiltering.filterLowPriority(testInput)
+
+ // Assert
+ assertThat(result).hasSize(2)
+ with(result.first()) {
+ assertThat(name.packageName).isEqualTo("TestPackage")
+ assertThat(name.className).isEqualTo("TestClass")
+ assertThat(getIntentAt(0)).isEqualTo(testIntent)
+ assertThrows(IndexOutOfBoundsException::class.java) { getIntentAt(1) }
+ assertThat(getResolveInfoAt(0)).isEqualTo(testResolveInfo)
+ assertThrows(IndexOutOfBoundsException::class.java) { getResolveInfoAt(1) }
+ }
+ with(result[1]) {
+ assertThat(name.packageName).isEqualTo("EqualPackage")
+ assertThat(name.className).isEqualTo("EqualClass")
+ assertThat(getIntentAt(0)).isEqualTo(testIntent)
+ assertThrows(IndexOutOfBoundsException::class.java) { getIntentAt(1) }
+ assertThat(getResolveInfoAt(0)).isEqualTo(equalResolveInfo)
+ assertThrows(IndexOutOfBoundsException::class.java) { getResolveInfoAt(1) }
+ }
+ }
+
+ @Test
+ fun filterLowPriority_whenNoDifference_returnsOriginal() {
+ // Arrange
+ val testIntent = Intent("TestAction")
+ val testResolveInfo =
+ ResolveInfo().apply {
+ priority = 1
+ isDefault = true
+ }
+ val equalResolveInfo =
+ ResolveInfo().apply {
+ priority = 1
+ isDefault = true
+ }
+ val testInput =
+ listOf(
+ ResolvedComponentInfo(
+ ComponentName("TestPackage", "TestClass"),
+ testIntent,
+ testResolveInfo,
+ ),
+ ResolvedComponentInfo(
+ ComponentName("EqualPackage", "EqualClass"),
+ testIntent,
+ equalResolveInfo,
+ ),
+ )
+
+ // Act
+ val result = resolvedComponentFiltering.filterLowPriority(testInput)
+
+ // Assert
+ assertThat(result).hasSize(2)
+ with(result.first()) {
+ assertThat(name.packageName).isEqualTo("TestPackage")
+ assertThat(name.className).isEqualTo("TestClass")
+ assertThat(getIntentAt(0)).isEqualTo(testIntent)
+ assertThrows(IndexOutOfBoundsException::class.java) { getIntentAt(1) }
+ assertThat(getResolveInfoAt(0)).isEqualTo(testResolveInfo)
+ assertThrows(IndexOutOfBoundsException::class.java) { getResolveInfoAt(1) }
+ }
+ with(result[1]) {
+ assertThat(name.packageName).isEqualTo("EqualPackage")
+ assertThat(name.className).isEqualTo("EqualClass")
+ assertThat(getIntentAt(0)).isEqualTo(testIntent)
+ assertThrows(IndexOutOfBoundsException::class.java) { getIntentAt(1) }
+ assertThat(getResolveInfoAt(0)).isEqualTo(equalResolveInfo)
+ assertThrows(IndexOutOfBoundsException::class.java) { getResolveInfoAt(1) }
+ }
+ }
+}
diff --git a/java/tests/src/com/android/intentresolver/v2/listcontroller/ResolvedComponentSortingTest.kt b/java/tests/src/com/android/intentresolver/v2/listcontroller/ResolvedComponentSortingTest.kt
new file mode 100644
index 00000000..39b328ee
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/v2/listcontroller/ResolvedComponentSortingTest.kt
@@ -0,0 +1,197 @@
+/*
+ * 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.v2.listcontroller
+
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.ResolveInfo
+import android.os.UserHandle
+import com.android.intentresolver.ResolvedComponentInfo
+import com.android.intentresolver.chooser.DisplayResolveInfo
+import com.android.intentresolver.chooser.TargetInfo
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.async
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.mockito.Mockito
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class ResolvedComponentSortingTest {
+
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ private val fakeResolverComparator = FakeResolverComparator()
+
+ private val resolvedComponentSorting =
+ ResolvedComponentSortingImpl(testDispatcher, fakeResolverComparator)
+
+ @Test
+ fun sorted_onNullList_returnsNull() =
+ testScope.runTest {
+ // Arrange
+ val testInput: List<ResolvedComponentInfo>? = null
+
+ // Act
+ val result = resolvedComponentSorting.sorted(testInput)
+ runCurrent()
+
+ // Assert
+ assertThat(result).isNull()
+ }
+
+ @Test
+ fun sorted_onEmptyList_returnsEmptyList() =
+ testScope.runTest {
+ // Arrange
+ val testInput = emptyList<ResolvedComponentInfo>()
+
+ // Act
+ val result = resolvedComponentSorting.sorted(testInput)
+ runCurrent()
+
+ // Assert
+ assertThat(result).isEmpty()
+ }
+
+ @Test
+ fun sorted_returnsListSortedByGivenComparator() =
+ testScope.runTest {
+ // Arrange
+ val testIntent = Intent("TestAction")
+ val testInput =
+ listOf(
+ ResolveInfo().apply {
+ activityInfo = ActivityInfo()
+ activityInfo.packageName = "TestPackage3"
+ activityInfo.name = "TestClass3"
+ },
+ ResolveInfo().apply {
+ activityInfo = ActivityInfo()
+ activityInfo.packageName = "TestPackage1"
+ activityInfo.name = "TestClass1"
+ },
+ ResolveInfo().apply {
+ activityInfo = ActivityInfo()
+ activityInfo.packageName = "TestPackage2"
+ activityInfo.name = "TestClass2"
+ },
+ )
+ .map {
+ it.targetUserId = UserHandle.USER_CURRENT
+ ResolvedComponentInfo(
+ ComponentName(it.activityInfo.packageName, it.activityInfo.name),
+ testIntent,
+ it,
+ )
+ }
+
+ // Act
+ val result = async { resolvedComponentSorting.sorted(testInput) }
+ runCurrent()
+
+ // Assert
+ assertThat(result.await()?.map { it.name.packageName })
+ .containsExactly("TestPackage1", "TestPackage2", "TestPackage3")
+ .inOrder()
+ }
+
+ @Test
+ fun getScore_displayResolveInfo_returnsTheScoreAccordingToTheResolverComparator() {
+ // Arrange
+ val testTarget =
+ DisplayResolveInfo.newDisplayResolveInfo(
+ Intent(),
+ ResolveInfo().apply {
+ activityInfo = ActivityInfo()
+ activityInfo.name = "TestClass"
+ activityInfo.applicationInfo = ApplicationInfo()
+ activityInfo.applicationInfo.packageName = "TestPackage"
+ },
+ Intent(),
+ )
+
+ // Act
+ val result = resolvedComponentSorting.getScore(testTarget)
+
+ // Assert
+ assertThat(result).isEqualTo(1.23f)
+ }
+
+ @Test
+ fun getScore_targetInfo_returnsTheScoreAccordingToTheResolverComparator() {
+ // Arrange
+ val mockTargetInfo = Mockito.mock(TargetInfo::class.java)
+
+ // Act
+ val result = resolvedComponentSorting.getScore(mockTargetInfo)
+
+ // Assert
+ assertThat(result).isEqualTo(1.23f)
+ }
+
+ @Test
+ fun updateModel_updatesResolverComparatorModel() =
+ testScope.runTest {
+ // Arrange
+ val mockTargetInfo = Mockito.mock(TargetInfo::class.java)
+ assertThat(fakeResolverComparator.lastUpdateModel).isNull()
+
+ // Act
+ resolvedComponentSorting.updateModel(mockTargetInfo)
+ runCurrent()
+
+ // Assert
+ assertThat(fakeResolverComparator.lastUpdateModel).isSameInstanceAs(mockTargetInfo)
+ }
+
+ @Test
+ fun updateChooserCounts_updatesResolverComparaterChooserCounts() =
+ testScope.runTest {
+ // Arrange
+ val testPackageName = "TestPackage"
+ val testUser = UserHandle(456)
+ val testAction = "TestAction"
+ assertThat(fakeResolverComparator.lastUpdateChooserCounts).isNull()
+
+ // Act
+ resolvedComponentSorting.updateChooserCounts(testPackageName, testUser, testAction)
+ runCurrent()
+
+ // Assert
+ assertThat(fakeResolverComparator.lastUpdateChooserCounts)
+ .isEqualTo(Triple(testPackageName, testUser, testAction))
+ }
+
+ @Test
+ fun destroy_destroysResolverComparator() {
+ // Arrange
+ assertThat(fakeResolverComparator.destroyCalled).isFalse()
+
+ // Act
+ resolvedComponentSorting.destroy()
+
+ // Assert
+ assertThat(fakeResolverComparator.destroyCalled).isTrue()
+ }
+}
diff --git a/java/tests/src/com/android/intentresolver/v2/listcontroller/SharedPreferencesPinnedComponentsTest.kt b/java/tests/src/com/android/intentresolver/v2/listcontroller/SharedPreferencesPinnedComponentsTest.kt
new file mode 100644
index 00000000..9d6394fa
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/v2/listcontroller/SharedPreferencesPinnedComponentsTest.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.v2.listcontroller
+
+import android.content.ComponentName
+import android.content.SharedPreferences
+import com.android.intentresolver.any
+import com.android.intentresolver.eq
+import com.android.intentresolver.whenever
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+class SharedPreferencesPinnedComponentsTest {
+
+ @Mock lateinit var mockSharedPreferences: SharedPreferences
+
+ private lateinit var sharedPreferencesPinnedComponents: SharedPreferencesPinnedComponents
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ sharedPreferencesPinnedComponents = SharedPreferencesPinnedComponents(mockSharedPreferences)
+ }
+
+ @Test
+ fun isComponentPinned_returnsSavedPinnedState() {
+ // Arrange
+ val testComponent = ComponentName("TestPackage", "TestClass")
+ val pinnedComponent = ComponentName("PinnedPackage", "PinnedClass")
+ whenever(mockSharedPreferences.getBoolean(eq(pinnedComponent.flattenToString()), any()))
+ .thenReturn(true)
+
+ // Act
+ val result = sharedPreferencesPinnedComponents.isComponentPinned(testComponent)
+ val pinnedResult = sharedPreferencesPinnedComponents.isComponentPinned(pinnedComponent)
+
+ // Assert
+ Mockito.verify(mockSharedPreferences).getBoolean(eq(testComponent.flattenToString()), any())
+ Mockito.verify(mockSharedPreferences)
+ .getBoolean(eq(pinnedComponent.flattenToString()), any())
+ Truth.assertThat(result).isFalse()
+ Truth.assertThat(pinnedResult).isTrue()
+ }
+}