diff options
| author | 2023-09-27 20:06:53 +0000 | |
|---|---|---|
| committer | 2023-09-27 20:06:53 +0000 | |
| commit | 9f7b4d19b7a3668091b81ef4fcc752e66f713202 (patch) | |
| tree | 396d62ac0f13537f35fc88ba17f189812e176750 /java/tests | |
| parent | faac60a597aa8f23f03a86feab76ad0f86854219 (diff) | |
| parent | 607f13d48aec6fdd8030273ae4a6cf7a712f4258 (diff) | |
Merge "Add unit tests for initial intents in Resolver and Chooser adapters" into main
Diffstat (limited to 'java/tests')
4 files changed, 392 insertions, 89 deletions
diff --git a/java/tests/src/com/android/intentresolver/ChooserListAdapterDataTest.kt b/java/tests/src/com/android/intentresolver/ChooserListAdapterDataTest.kt new file mode 100644 index 00000000..e5927e36 --- /dev/null +++ b/java/tests/src/com/android/intentresolver/ChooserListAdapterDataTest.kt @@ -0,0 +1,179 @@ +package com.android.intentresolver + +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.content.pm.PackageManager.ComponentInfoFlags +import android.os.UserHandle +import android.os.UserManager +import android.view.LayoutInflater +import com.android.intentresolver.ResolverDataProvider.createActivityInfo +import com.android.intentresolver.ResolverDataProvider.createResolvedComponentInfo +import com.android.intentresolver.icons.TargetDataLoader +import com.android.intentresolver.logging.FakeEventLog +import com.android.intentresolver.util.TestExecutor +import com.android.internal.logging.InstanceId +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.mockito.Mockito + +class ChooserListAdapterDataTest { + private val layoutInflater = mock<LayoutInflater>() + private val packageManager = mock<PackageManager>() + private val userManager = mock<UserManager> { whenever(isManagedProfile).thenReturn(false) } + private val resources = + mock<android.content.res.Resources> { + whenever(getInteger(R.integer.config_maxShortcutTargetsPerApp)).thenReturn(2) + } + private val context = + mock<Context> { + whenever(getSystemService(Context.LAYOUT_INFLATER_SERVICE)).thenReturn(layoutInflater) + whenever(getSystemService(Context.USER_SERVICE)).thenReturn(userManager) + whenever(packageManager).thenReturn(this@ChooserListAdapterDataTest.packageManager) + whenever(resources).thenReturn(this@ChooserListAdapterDataTest.resources) + } + private val targetIntent = Intent(Intent.ACTION_SEND) + private val payloadIntents = listOf(targetIntent) + private val resolverListController = + mock<ResolverListController> { + whenever(filterIneligibleActivities(any(), Mockito.anyBoolean())).thenReturn(null) + whenever(filterLowPriority(any(), Mockito.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) + private val chooserRequestParams = + ChooserRequestParameters( + Intent.createChooser(targetIntent, ""), + "org.referrer.package", + null + ) + + @Test + fun test_twoTargetsWithNonOverlappingInitialIntent_threeTargetsInResolverAdapter() { + val resolvedTargets = + listOf( + createResolvedComponentInfo(1), + createResolvedComponentInfo(2), + ) + val targetIntent = Intent(Intent.ACTION_SEND) + whenever( + resolverListController.getResolversForIntentAsUser( + true, + resolverListCommunicator.shouldGetActivityMetadata(), + resolverListCommunicator.shouldGetOnlyDefaultActivities(), + payloadIntents, + userHandle + ) + ) + .thenReturn(resolvedTargets) + val initialActivityInfo = createActivityInfo(3) + val initialIntents = + arrayOf( + Intent(Intent.ACTION_SEND).apply { component = initialActivityInfo.componentName } + ) + whenever( + packageManager.getActivityInfo( + eq(initialActivityInfo.componentName), + any<ComponentInfoFlags>() + ) + ) + .thenReturn(initialActivityInfo) + val testSubject = + ChooserListAdapter( + context, + payloadIntents, + initialIntents, + /*rList=*/ null, + /*filterLastUsed=*/ false, + resolverListController, + userHandle, + targetIntent, + resolverListCommunicator, + packageManager, + FakeEventLog(InstanceId.fakeInstanceId(1)), + chooserRequestParams, + /*maxRankedTargets=*/ 2, + /*initialIntentsUserSpace=*/ userHandle, + targetDataLoader, + backgroundExecutor, + immediateExecutor, + ) + val doPostProcessing = true + + val isLoaded = testSubject.rebuildList(doPostProcessing) + + assertThat(isLoaded).isFalse() + assertThat(testSubject.displayResolveInfoCount).isEqualTo(0) + assertThat(backgroundExecutor.pendingCommandCount).isEqualTo(1) + + backgroundExecutor.runUntilIdle() + + // we don't reset placeholder count (legacy logic, likely an oversight?) + assertThat(testSubject.displayResolveInfoCount).isEqualTo(resolvedTargets.size) + } + + @Test + fun test_twoTargetsWithOverlappingInitialIntent_oneTargetsInResolverAdapter() { + val resolvedTargets = + listOf( + createResolvedComponentInfo(1), + createResolvedComponentInfo(2), + ) + val targetIntent = Intent(Intent.ACTION_SEND) + whenever( + resolverListController.getResolversForIntentAsUser( + true, + resolverListCommunicator.shouldGetActivityMetadata(), + resolverListCommunicator.shouldGetOnlyDefaultActivities(), + payloadIntents, + userHandle + ) + ) + .thenReturn(resolvedTargets) + val activityInfo = resolvedTargets[1].getResolveInfoAt(0).activityInfo + val initialIntents = + arrayOf(Intent(Intent.ACTION_SEND).apply { component = activityInfo.componentName }) + whenever( + packageManager.getActivityInfo( + eq(activityInfo.componentName), + any<ComponentInfoFlags>() + ) + ) + .thenReturn(activityInfo) + val testSubject = + ChooserListAdapter( + context, + payloadIntents, + initialIntents, + /*rList=*/ null, + /*filterLastUsed=*/ false, + resolverListController, + userHandle, + targetIntent, + resolverListCommunicator, + packageManager, + FakeEventLog(InstanceId.fakeInstanceId(1)), + chooserRequestParams, + /*maxRankedTargets=*/ 2, + /*initialIntentsUserSpace=*/ userHandle, + targetDataLoader, + backgroundExecutor, + immediateExecutor, + ) + val doPostProcessing = true + + val isLoaded = testSubject.rebuildList(doPostProcessing) + + assertThat(isLoaded).isFalse() + assertThat(testSubject.displayResolveInfoCount).isEqualTo(0) + assertThat(backgroundExecutor.pendingCommandCount).isEqualTo(1) + + backgroundExecutor.runUntilIdle() + + // we don't reset placeholder count (legacy logic, likely an oversight?) + assertThat(testSubject.displayResolveInfoCount).isEqualTo(resolvedTargets.size - 1) + } +} diff --git a/java/tests/src/com/android/intentresolver/FakeResolverListCommunicator.kt b/java/tests/src/com/android/intentresolver/FakeResolverListCommunicator.kt new file mode 100644 index 00000000..5e9cd98f --- /dev/null +++ b/java/tests/src/com/android/intentresolver/FakeResolverListCommunicator.kt @@ -0,0 +1,56 @@ +/* + * 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.Intent +import android.content.pm.ActivityInfo +import java.util.concurrent.atomic.AtomicInteger + +class FakeResolverListCommunicator(private val layoutWithDefaults: Boolean = true) : + ResolverListAdapter.ResolverListCommunicator { + private val sendVoiceCounter = AtomicInteger() + private val updateProfileViewButtonCounter = AtomicInteger() + + val sendVoiceCommandCount + get() = sendVoiceCounter.get() + val updateProfileViewButtonCount + get() = updateProfileViewButtonCounter.get() + + override fun getReplacementIntent(activityInfo: ActivityInfo?, defIntent: Intent): Intent { + return defIntent + } + + override fun onPostListReady( + listAdapter: ResolverListAdapter?, + updateUi: Boolean, + rebuildCompleted: Boolean, + ) = Unit + + override fun sendVoiceChoicesIfNeeded() { + sendVoiceCounter.incrementAndGet() + } + + override fun updateProfileViewButton() { + updateProfileViewButtonCounter.incrementAndGet() + } + + override fun useLayoutWithDefault(): Boolean = layoutWithDefaults + + override fun shouldGetActivityMetadata(): Boolean = true + + override fun onHandlePackagesChanged(listAdapter: ResolverListAdapter?) {} +} diff --git a/java/tests/src/com/android/intentresolver/ResolverListAdapterTest.kt b/java/tests/src/com/android/intentresolver/ResolverListAdapterTest.kt index 53c90199..61b9fd9c 100644 --- a/java/tests/src/com/android/intentresolver/ResolverListAdapterTest.kt +++ b/java/tests/src/com/android/intentresolver/ResolverListAdapterTest.kt @@ -19,16 +19,16 @@ package com.android.intentresolver import android.content.ComponentName import android.content.Context 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 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 java.util.concurrent.atomic.AtomicInteger import org.junit.Test import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.inOrder @@ -36,14 +36,19 @@ 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.pkgtwo.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) @@ -53,7 +58,7 @@ class ResolverListAdapterTest { whenever(filterLowPriority(any(), anyBoolean())).thenReturn(null) } private val resolverListCommunicator = FakeResolverListCommunicator() - private val userHandle = UserHandle.of(0) + private val userHandle = UserHandle.of(UserHandle.USER_CURRENT) private val targetDataLoader = mock<TargetDataLoader>() private val backgroundExecutor = TestExecutor() private val immediateExecutor = TestExecutor(immediate = true) @@ -666,6 +671,150 @@ class ResolverListAdapterTest { } @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 = @@ -892,47 +1041,8 @@ class ResolverListAdapterTest { private fun createResolveInfo(packageName: String, className: String): ResolveInfo = mock<ResolveInfo> { - activityInfo = - ActivityInfo().apply { - name = className - this.packageName = packageName - applicationInfo = ApplicationInfo().apply { this.packageName = packageName } - } - targetUserId = UserHandle.USER_CURRENT + activityInfo = createActivityInfo(ComponentName(packageName, className)) + targetUserId = this@ResolverListAdapterTest.userHandle.identifier + userHandle = this@ResolverListAdapterTest.userHandle } } - -private class FakeResolverListCommunicator(private val layoutWithDefaults: Boolean = true) : - ResolverListAdapter.ResolverListCommunicator { - private val sendVoiceCounter = AtomicInteger() - private val updateProfileViewButtonCounter = AtomicInteger() - - val sendVoiceCommandCount - get() = sendVoiceCounter.get() - val updateProfileViewButtonCount - get() = updateProfileViewButtonCounter.get() - - override fun getReplacementIntent(activityInfo: ActivityInfo?, defIntent: Intent): Intent { - return defIntent - } - - override fun onPostListReady( - listAdapter: ResolverListAdapter?, - updateUi: Boolean, - rebuildCompleted: Boolean, - ) = Unit - - override fun sendVoiceChoicesIfNeeded() { - sendVoiceCounter.incrementAndGet() - } - - override fun updateProfileViewButton() { - updateProfileViewButtonCounter.incrementAndGet() - } - - override fun useLayoutWithDefault(): Boolean = layoutWithDefaults - - override fun shouldGetActivityMetadata(): Boolean = true - - override fun onHandlePackagesChanged(listAdapter: ResolverListAdapter?) {} -} diff --git a/java/tests/src/com/android/intentresolver/util/TestImmediateHandler.kt b/java/tests/src/com/android/intentresolver/util/TestImmediateHandler.kt deleted file mode 100644 index 9e6fc989..00000000 --- a/java/tests/src/com/android/intentresolver/util/TestImmediateHandler.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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.util - -import android.os.Handler -import android.os.Looper -import android.os.Message - -/** - * A test Handler that executes posted [Runnable] immediately regardless of the target time (delay). - * Does not support messages. - */ -class TestImmediateHandler : Handler(createTestLooper()) { - override fun sendMessageAtTime(msg: Message, uptimeMillis: Long): Boolean { - msg.callback.run() - return true - } - - companion object { - private val looperConstructor by lazy { - Looper::class.java.getDeclaredConstructor(java.lang.Boolean.TYPE).apply { - isAccessible = true - } - } - - private fun createTestLooper(): Looper = looperConstructor.newInstance(true) - } -} |