diff options
| author | 2023-11-21 13:31:12 -0500 | |
|---|---|---|
| committer | 2023-11-28 15:40:20 -0500 | |
| commit | c5d116c2977f5ed83f6994130a3b180deb33f266 (patch) | |
| tree | 29a9481348e00c98eaaa60fba4bb9c0c97bf0fde /java/tests | |
| parent | c1c24280a0dfe39745704398ce8d13cf14404eb2 (diff) | |
Splits tests
This splits the monolithic 'tests' target into multiple types
based on classification. See the corresponding README.md for
descriptions.
The target 'IntentResolverUnitTests' is replaced by:
IntentResolver-tests-unit
IntentResolver-tests-activity
IntentResolver-tests-integration
IntentResolver-tests-shared
To run everything, use:
atest IntentResolver-tests-*
To run TreeHugger presubmits:
atest --test-mapping pacakges/modules/IntentResolver
Equivalently for postsubmit:
atest --test-mapping pacakges/modules/IntentResolver:postsubmit
Bug: 300157408
Test: atest IntentResolver-tests-*
Change-Id: I9d499284f070a4bfa3e7c1b7c3bbfaa8adb3379b
Diffstat (limited to 'java/tests')
85 files changed, 0 insertions, 23190 deletions
diff --git a/java/tests/Android.bp b/java/tests/Android.bp deleted file mode 100644 index 75dd2c19..00000000 --- a/java/tests/Android.bp +++ /dev/null @@ -1,67 +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 { - default_applicable_licenses: ["Android-Apache-2.0"], -} - -android_test { - name: "IntentResolverUnitTests", - manifest: "AndroidManifest.xml", - // Include all test java files. - srcs: [ - "src/**/*.java", - "src/**/*.kt", - ], - - libs: [ - "android.test.runner", - "android.test.base", - "android.test.mock", - "framework", - "framework-res", - ], - - resource_dirs: ["res"], - test_config: "AndroidTest.xml", - static_libs: [ - "androidx.test.core", - "androidx.test.ext.junit", - "androidx.test.ext.truth", - "androidx.test.espresso.contrib", - "androidx.test.espresso.core", - "androidx.test.rules", - "androidx.lifecycle_lifecycle-common-java8", - "androidx.lifecycle_lifecycle-extensions", - "androidx.lifecycle_lifecycle-runtime-testing", - "hilt_android_testing", - "IntentResolver-core", - "junit", - "kotlinx_coroutines_test", - "mockito-target-minus-junit4", - "testables", - "truth", - "truth-java8-extension", - "flag-junit", - "platform-test-annotations", - ], - plugins: ["dagger2-compiler"], - test_suites: ["general-tests"], - sdk_version: "core_platform", - min_sdk_version: "current", - target_sdk_version: "current", - platform_apis: true, -} diff --git a/java/tests/AndroidManifest.xml b/java/tests/AndroidManifest.xml deleted file mode 100644 index 03e32c65..00000000 --- a/java/tests/AndroidManifest.xml +++ /dev/null @@ -1,42 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2021 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. ---> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.intentresolver.tests"> - - <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/> - <uses-permission android:name="android.permission.QUERY_USERS"/> - <uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND"/> - <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG"/> - <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> - - <application android:name="dagger.hilt.android.testing.HiltTestApplication"> - <uses-library android:name="android.test.runner" /> - <activity android:name="com.android.intentresolver.ChooserWrapperActivity" /> - <activity android:name="com.android.intentresolver.ResolverWrapperActivity" /> - <activity android:name="com.android.intentresolver.v2.ChooserWrapperActivity" /> - <activity android:name="com.android.intentresolver.v2.ResolverWrapperActivity" /> - <provider - android:authorities="com.android.intentresolver.tests" - android:name="com.android.intentresolver.TestContentProvider" - android:grantUriPermissions="true" /> - </application> - - <instrumentation android:name="android.testing.TestableInstrumentation" - android:targetPackage="com.android.intentresolver.tests"> - </instrumentation> - -</manifest> diff --git a/java/tests/AndroidTest.xml b/java/tests/AndroidTest.xml deleted file mode 100644 index d1d77c10..00000000 --- a/java/tests/AndroidTest.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2021 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. ---> -<configuration description="Run IntentResolver Tests."> - <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> - <option name="test-file-name" value="IntentResolverUnitTests.apk" /> - </target_preparer> - - <option name="test-suite-tag" value="apct" /> - <option name="test-tag" value="IntentResolverUnitTests" /> - <test class="com.android.tradefed.testtype.AndroidJUnitTest" > - <option name="package" value="com.android.intentresolver.tests" /> - <option name="runner" value="android.testing.TestableInstrumentation" /> - <option name="hidden-api-checks" value="false"/> - </test> -</configuration> diff --git a/java/tests/README.md b/java/tests/README.md deleted file mode 100644 index b42e8d27..00000000 --- a/java/tests/README.md +++ /dev/null @@ -1,7 +0,0 @@ - -__ABOUT__ - -This is package is configured as a self-instrumenting test APK. The IntentResolver application code -is bundled in and the instrumentation APK targets itself. This is used for in-process mocking and -verification involving live components that many tests rely on. - diff --git a/java/tests/res/drawable/test320x240.png b/java/tests/res/drawable/test320x240.png Binary files differdeleted file mode 100644 index 9b5800da..00000000 --- a/java/tests/res/drawable/test320x240.png +++ /dev/null diff --git a/java/tests/src/com/android/intentresolver/AnnotatedUserHandlesTest.kt b/java/tests/src/com/android/intentresolver/AnnotatedUserHandlesTest.kt deleted file mode 100644 index cd2fbc7a..00000000 --- a/java/tests/src/com/android/intentresolver/AnnotatedUserHandlesTest.kt +++ /dev/null @@ -1,79 +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 - -import android.os.UserHandle - -import com.google.common.truth.Truth.assertThat - -import org.junit.Test - -class AnnotatedUserHandlesTest { - - @Test - fun testBasicProperties() { // Fields that are reflected back w/o logic. - val info = AnnotatedUserHandles.newBuilder() - .setUserIdOfCallingApp(42) - .setUserHandleSharesheetLaunchedAs(UserHandle.of(116)) - .setPersonalProfileUserHandle(UserHandle.of(117)) - .setWorkProfileUserHandle(UserHandle.of(118)) - .setCloneProfileUserHandle(UserHandle.of(119)) - .build() - - assertThat(info.userIdOfCallingApp).isEqualTo(42) - assertThat(info.userHandleSharesheetLaunchedAs.identifier).isEqualTo(116) - assertThat(info.personalProfileUserHandle.identifier).isEqualTo(117) - assertThat(info.workProfileUserHandle?.identifier).isEqualTo(118) - assertThat(info.cloneProfileUserHandle?.identifier).isEqualTo(119) - } - - @Test - fun testWorkTabInitiallySelectedWhenLaunchedFromWorkProfile() { - val info = AnnotatedUserHandles.newBuilder() - .setUserIdOfCallingApp(42) - .setPersonalProfileUserHandle(UserHandle.of(101)) - .setWorkProfileUserHandle(UserHandle.of(202)) - .setUserHandleSharesheetLaunchedAs(UserHandle.of(202)) - .build() - - assertThat(info.tabOwnerUserHandleForLaunch.identifier).isEqualTo(202) - } - - @Test - fun testPersonalTabInitiallySelectedWhenLaunchedFromPersonalProfile() { - val info = AnnotatedUserHandles.newBuilder() - .setUserIdOfCallingApp(42) - .setPersonalProfileUserHandle(UserHandle.of(101)) - .setWorkProfileUserHandle(UserHandle.of(202)) - .setUserHandleSharesheetLaunchedAs(UserHandle.of(101)) - .build() - - assertThat(info.tabOwnerUserHandleForLaunch.identifier).isEqualTo(101) - } - - @Test - fun testPersonalTabInitiallySelectedWhenLaunchedFromOtherProfile() { - val info = AnnotatedUserHandles.newBuilder() - .setUserIdOfCallingApp(42) - .setPersonalProfileUserHandle(UserHandle.of(101)) - .setWorkProfileUserHandle(UserHandle.of(202)) - .setUserHandleSharesheetLaunchedAs(UserHandle.of(303)) - .build() - - assertThat(info.tabOwnerUserHandleForLaunch.identifier).isEqualTo(101) - } -} diff --git a/java/tests/src/com/android/intentresolver/ChooserActionFactoryTest.kt b/java/tests/src/com/android/intentresolver/ChooserActionFactoryTest.kt deleted file mode 100644 index 55a94ebd..00000000 --- a/java/tests/src/com/android/intentresolver/ChooserActionFactoryTest.kt +++ /dev/null @@ -1,227 +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 - -import android.app.Activity -import android.app.PendingIntent -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Context.RECEIVER_EXPORTED -import android.content.Intent -import android.content.IntentFilter -import android.content.res.Resources -import android.graphics.drawable.Icon -import android.service.chooser.ChooserAction -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import com.android.intentresolver.logging.EventLog -import com.google.common.collect.ImmutableList -import com.google.common.truth.Truth.assertThat -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit -import java.util.function.Consumer -import org.junit.After -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mockito - -@RunWith(AndroidJUnit4::class) -class ChooserActionFactoryTest { - private val context = InstrumentationRegistry.getInstrumentation().getContext() - - private val logger = mock<EventLog>() - private val actionLabel = "Action label" - private val modifyShareLabel = "Modify share" - private val testAction = "com.android.intentresolver.testaction" - private val countdown = CountDownLatch(1) - private val testReceiver: BroadcastReceiver = - object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - // Just doing at most a single countdown per test. - countdown.countDown() - } - } - private val resultConsumer = - object : Consumer<Int> { - var latestReturn = Integer.MIN_VALUE - - override fun accept(resultCode: Int) { - latestReturn = resultCode - } - } - - @Before - fun setup() { - context.registerReceiver(testReceiver, IntentFilter(testAction), RECEIVER_EXPORTED) - } - - @After - fun teardown() { - context.unregisterReceiver(testReceiver) - } - - @Test - fun testCreateCustomActions() { - val factory = createFactory() - - val customActions = factory.createCustomActions() - - assertThat(customActions.size).isEqualTo(1) - assertThat(customActions[0].label).isEqualTo(actionLabel) - - // click it - customActions[0].onClicked.run() - - Mockito.verify(logger).logCustomActionSelected(eq(0)) - assertEquals(Activity.RESULT_OK, resultConsumer.latestReturn) - // Verify the pending intent has been called - assertTrue("Timed out waiting for broadcast", countdown.await(2500, TimeUnit.MILLISECONDS)) - } - - @Test - fun testNoModifyShareAction() { - val factory = createFactory(includeModifyShare = false) - - assertThat(factory.modifyShareAction).isNull() - } - - @Test - fun testModifyShareAction() { - val factory = createFactory(includeModifyShare = true) - - val action = factory.modifyShareAction ?: error("Modify share action should not be null") - action.onClicked.run() - - Mockito.verify(logger).logActionSelected(eq(EventLog.SELECTION_TYPE_MODIFY_SHARE)) - assertEquals(Activity.RESULT_OK, resultConsumer.latestReturn) - // Verify the pending intent has been called - assertTrue("Timed out waiting for broadcast", countdown.await(2500, TimeUnit.MILLISECONDS)) - } - - @Test - fun nonSendAction_noCopyRunnable() { - val targetIntent = - Intent(Intent.ACTION_SEND_MULTIPLE).apply { - putExtra(Intent.EXTRA_TEXT, "Text to show") - } - - val chooserRequest = - mock<ChooserRequestParameters> { - whenever(this.targetIntent).thenReturn(targetIntent) - whenever(chooserActions).thenReturn(ImmutableList.of()) - } - val testSubject = - ChooserActionFactory( - context, - chooserRequest, - mock(), - logger, - {}, - { null }, - mock(), - {}, - ) - assertThat(testSubject.copyButtonRunnable).isNull() - } - - @Test - fun sendActionNoText_noCopyRunnable() { - val targetIntent = Intent(Intent.ACTION_SEND) - - val chooserRequest = - mock<ChooserRequestParameters> { - whenever(this.targetIntent).thenReturn(targetIntent) - whenever(chooserActions).thenReturn(ImmutableList.of()) - } - val testSubject = - ChooserActionFactory( - context, - chooserRequest, - mock(), - logger, - {}, - { null }, - mock(), - {}, - ) - assertThat(testSubject.copyButtonRunnable).isNull() - } - - @Test - fun sendActionWithText_nonNullCopyRunnable() { - val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_TEXT, "Text") } - - val chooserRequest = - mock<ChooserRequestParameters> { - whenever(this.targetIntent).thenReturn(targetIntent) - whenever(chooserActions).thenReturn(ImmutableList.of()) - } - val testSubject = - ChooserActionFactory( - context, - chooserRequest, - mock(), - logger, - {}, - { null }, - mock(), - {}, - ) - assertThat(testSubject.copyButtonRunnable).isNotNull() - } - - private fun createFactory(includeModifyShare: Boolean = false): ChooserActionFactory { - val testPendingIntent = - PendingIntent.getBroadcast(context, 0, Intent(testAction), PendingIntent.FLAG_IMMUTABLE) - val targetIntent = Intent() - val action = - ChooserAction.Builder( - Icon.createWithResource("", Resources.ID_NULL), - actionLabel, - testPendingIntent - ) - .build() - val chooserRequest = mock<ChooserRequestParameters>() - whenever(chooserRequest.targetIntent).thenReturn(targetIntent) - whenever(chooserRequest.chooserActions).thenReturn(ImmutableList.of(action)) - - if (includeModifyShare) { - val modifyShare = - ChooserAction.Builder( - Icon.createWithResource("", Resources.ID_NULL), - modifyShareLabel, - testPendingIntent - ) - .build() - whenever(chooserRequest.modifyShareAction).thenReturn(modifyShare) - } - - return ChooserActionFactory( - context, - chooserRequest, - mock(), - logger, - {}, - { null }, - mock(), - resultConsumer - ) - } -} diff --git a/java/tests/src/com/android/intentresolver/ChooserActivityOverrideData.java b/java/tests/src/com/android/intentresolver/ChooserActivityOverrideData.java deleted file mode 100644 index 3ee80c14..00000000 --- a/java/tests/src/com/android/intentresolver/ChooserActivityOverrideData.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2021 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 static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.database.Cursor; -import android.os.UserHandle; - -import com.android.intentresolver.chooser.TargetInfo; -import com.android.intentresolver.contentpreview.ImageLoader; -import com.android.intentresolver.emptystate.CrossProfileIntentsChecker; -import com.android.intentresolver.shortcuts.ShortcutLoader; - -import java.util.function.Consumer; -import java.util.function.Function; - -import kotlin.jvm.functions.Function2; - -/** - * Singleton providing overrides to be applied by any {@code IChooserWrapper} used in testing. - * We cannot directly mock the activity created since instrumentation creates it, so instead we use - * this singleton to modify behavior. - */ -public class ChooserActivityOverrideData { - private static ChooserActivityOverrideData sInstance = null; - - public static ChooserActivityOverrideData getInstance() { - if (sInstance == null) { - sInstance = new ChooserActivityOverrideData(); - } - return sInstance; - } - - @SuppressWarnings("Since15") - public Function<PackageManager, PackageManager> createPackageManager; - public Function<TargetInfo, Boolean> onSafelyStartInternalCallback; - public Function<TargetInfo, Boolean> onSafelyStartCallback; - public Function2<UserHandle, Consumer<ShortcutLoader.Result>, ShortcutLoader> - shortcutLoaderFactory = (userHandle, callback) -> null; - public ChooserActivity.ChooserListController resolverListController; - public ChooserActivity.ChooserListController workResolverListController; - public Boolean isVoiceInteraction; - public Cursor resolverCursor; - public boolean resolverForceException; - public ImageLoader imageLoader; - public int alternateProfileSetting; - public Resources resources; - public AnnotatedUserHandles annotatedUserHandles; - public boolean hasCrossProfileIntents; - public boolean isQuietModeEnabled; - public Integer myUserId; - public WorkProfileAvailabilityManager mWorkProfileAvailability; - public CrossProfileIntentsChecker mCrossProfileIntentsChecker; - public PackageManager packageManager; - - public void reset() { - onSafelyStartInternalCallback = null; - isVoiceInteraction = null; - createPackageManager = null; - imageLoader = null; - resolverCursor = null; - resolverForceException = false; - resolverListController = mock(ChooserActivity.ChooserListController.class); - workResolverListController = mock(ChooserActivity.ChooserListController.class); - alternateProfileSetting = 0; - resources = null; - annotatedUserHandles = AnnotatedUserHandles.newBuilder() - .setUserIdOfCallingApp(1234) // Must be non-negative. - .setUserHandleSharesheetLaunchedAs(UserHandle.SYSTEM) - .setPersonalProfileUserHandle(UserHandle.SYSTEM) - .build(); - hasCrossProfileIntents = true; - isQuietModeEnabled = false; - myUserId = null; - packageManager = null; - mWorkProfileAvailability = new WorkProfileAvailabilityManager(null, null, null) { - @Override - public boolean isQuietModeEnabled() { - return isQuietModeEnabled; - } - - @Override - public boolean isWorkProfileUserUnlocked() { - return true; - } - - @Override - public void requestQuietModeEnabled(boolean enabled) { - isQuietModeEnabled = enabled; - } - - @Override - public void markWorkProfileEnabledBroadcastReceived() {} - - @Override - public boolean isWaitingToEnableWorkProfile() { - return false; - } - }; - shortcutLoaderFactory = ((userHandle, resultConsumer) -> null); - - mCrossProfileIntentsChecker = mock(CrossProfileIntentsChecker.class); - when(mCrossProfileIntentsChecker.hasCrossProfileIntents(any(), anyInt(), anyInt())) - .thenAnswer(invocation -> hasCrossProfileIntents); - } - - private ChooserActivityOverrideData() {} -} - diff --git a/java/tests/src/com/android/intentresolver/ChooserIntegratedDeviceComponentsTest.kt b/java/tests/src/com/android/intentresolver/ChooserIntegratedDeviceComponentsTest.kt deleted file mode 100644 index 9a5dabdb..00000000 --- a/java/tests/src/com/android/intentresolver/ChooserIntegratedDeviceComponentsTest.kt +++ /dev/null @@ -1,71 +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 - -import android.content.ComponentName -import android.provider.Settings -import android.testing.TestableContext -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import com.google.common.truth.Truth.assertThat -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class ChooserIntegratedDeviceComponentsTest { - private val secureSettings = mock<SecureSettings>() - private val testableContext = - TestableContext(InstrumentationRegistry.getInstrumentation().getContext()) - - @Test - fun testEditorAndNearby() { - val resources = testableContext.getOrCreateTestableResources() - - resources.addOverride(R.string.config_systemImageEditor, "") - resources.addOverride(R.string.config_defaultNearbySharingComponent, "") - - var components = ChooserIntegratedDeviceComponents.get(testableContext, secureSettings) - - assertThat(components.editSharingComponent).isNull() - assertThat(components.nearbySharingComponent).isNull() - - val editor = ComponentName.unflattenFromString("com.android/com.android.Editor") - val nearby = ComponentName.unflattenFromString("com.android/com.android.nearby") - - resources.addOverride(R.string.config_systemImageEditor, editor?.flattenToString()) - resources.addOverride( - R.string.config_defaultNearbySharingComponent, nearby?.flattenToString()) - - components = ChooserIntegratedDeviceComponents.get(testableContext, secureSettings) - - assertThat(components.editSharingComponent).isEqualTo(editor) - assertThat(components.nearbySharingComponent).isEqualTo(nearby) - - val anotherNearby = - ComponentName.unflattenFromString("com.android/com.android.another_nearby") - whenever( - secureSettings.getString( - any(), - eq(Settings.Secure.NEARBY_SHARING_COMPONENT) - ) - ).thenReturn(anotherNearby?.flattenToString()) - - components = ChooserIntegratedDeviceComponents.get(testableContext, secureSettings) - - assertThat(components.nearbySharingComponent).isEqualTo(anotherNearby) - } -} diff --git a/java/tests/src/com/android/intentresolver/ChooserListAdapterDataTest.kt b/java/tests/src/com/android/intentresolver/ChooserListAdapterDataTest.kt deleted file mode 100644 index e5927e36..00000000 --- a/java/tests/src/com/android/intentresolver/ChooserListAdapterDataTest.kt +++ /dev/null @@ -1,179 +0,0 @@ -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/ChooserListAdapterTest.kt b/java/tests/src/com/android/intentresolver/ChooserListAdapterTest.kt deleted file mode 100644 index a4078365..00000000 --- a/java/tests/src/com/android/intentresolver/ChooserListAdapterTest.kt +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.intentresolver - -import android.content.ComponentName -import android.content.Intent -import android.content.pm.PackageManager -import android.content.pm.PackageManager.ResolveInfoFlags -import android.content.pm.ShortcutInfo -import android.os.UserHandle -import android.view.View -import android.widget.FrameLayout -import android.widget.ImageView -import android.widget.TextView -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import com.android.intentresolver.chooser.DisplayResolveInfo -import com.android.intentresolver.chooser.SelectableTargetInfo -import com.android.intentresolver.chooser.TargetInfo -import com.android.intentresolver.icons.TargetDataLoader -import com.android.intentresolver.logging.EventLogImpl -import com.android.internal.R -import com.google.common.truth.Truth.assertThat -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mockito.times -import org.mockito.Mockito.verify - -@RunWith(AndroidJUnit4::class) -class ChooserListAdapterTest { - private val userHandle: UserHandle = - InstrumentationRegistry.getInstrumentation().targetContext.user - - private val packageManager = - mock<PackageManager> { - whenever(resolveActivity(any(), any<ResolveInfoFlags>())).thenReturn(mock()) - } - private val context = InstrumentationRegistry.getInstrumentation().context - private val resolverListController = mock<ResolverListController>() - private val appLabel = "App" - private val targetLabel = "Target" - private val mEventLog = mock<EventLogImpl>() - private val mTargetDataLoader = mock<TargetDataLoader>() - - private val testSubject by lazy { - ChooserListAdapter( - context, - emptyList(), - emptyArray(), - emptyList(), - false, - resolverListController, - userHandle, - Intent(), - mock(), - packageManager, - mEventLog, - mock(), - 0, - null, - mTargetDataLoader - ) - } - - @Before - fun setup() { - // ChooserListAdapter reads DeviceConfig and needs a permission for that. - InstrumentationRegistry.getInstrumentation() - .uiAutomation - .adoptShellPermissionIdentity("android.permission.READ_DEVICE_CONFIG") - } - - @Test - fun testDirectShareTargetLoadingIconIsStarted() { - val view = createView() - val viewHolder = ResolverListAdapter.ViewHolder(view) - view.tag = viewHolder - val targetInfo = createSelectableTargetInfo() - testSubject.onBindView(view, targetInfo, 0) - - verify(mTargetDataLoader, times(1)).loadDirectShareIcon(any(), any(), any()) - } - - @Test - fun onBindView_DirectShareTargetIconAndLabelLoadedOnlyOnce() { - val view = createView() - val viewHolderOne = ResolverListAdapter.ViewHolder(view) - view.tag = viewHolderOne - val targetInfo = createSelectableTargetInfo() - testSubject.onBindView(view, targetInfo, 0) - - val viewHolderTwo = ResolverListAdapter.ViewHolder(view) - view.tag = viewHolderTwo - - testSubject.onBindView(view, targetInfo, 0) - - verify(mTargetDataLoader, times(1)).loadDirectShareIcon(any(), any(), any()) - } - - @Test - fun onBindView_AppTargetIconAndLabelLoadedOnlyOnce() { - val view = createView() - val viewHolderOne = ResolverListAdapter.ViewHolder(view) - view.tag = viewHolderOne - val targetInfo = - DisplayResolveInfo.newDisplayResolveInfo( - Intent(), - ResolverDataProvider.createResolveInfo(2, 0, userHandle), - null, - "extended info", - Intent() - ) - testSubject.onBindView(view, targetInfo, 0) - - val viewHolderTwo = ResolverListAdapter.ViewHolder(view) - view.tag = viewHolderTwo - - testSubject.onBindView(view, targetInfo, 0) - - verify(mTargetDataLoader, times(1)).loadAppTargetIcon(any(), any(), any()) - } - - @Test - fun onBindView_contentDescription() { - val view = createView() - val viewHolder = ResolverListAdapter.ViewHolder(view) - view.tag = viewHolder - val targetInfo = createSelectableTargetInfo() - testSubject.onBindView(view, targetInfo, 0) - - assertThat(view.contentDescription).isEqualTo("$targetLabel $appLabel") - } - - @Test - fun onBindView_contentDescriptionPinned() { - val view = createView() - val viewHolder = ResolverListAdapter.ViewHolder(view) - view.tag = viewHolder - val targetInfo = createSelectableTargetInfo(true) - testSubject.onBindView(view, targetInfo, 0) - - assertThat(view.contentDescription).isEqualTo("$targetLabel $appLabel. Pinned") - } - - @Test - fun onBindView_displayInfoContentDescriptionPinned() { - val view = createView() - val viewHolder = ResolverListAdapter.ViewHolder(view) - view.tag = viewHolder - val targetInfo = createDisplayResolveInfo(isPinned = true) - testSubject.onBindView(view, targetInfo, 0) - - assertThat(view.contentDescription).isEqualTo("$appLabel. Pinned") - } - - private fun createSelectableTargetInfo(isPinned: Boolean = false): TargetInfo { - val shortcutInfo = - createShortcutInfo("id-1", ComponentName("pkg", "Class"), 1).apply { - if (isPinned) { - addFlags(ShortcutInfo.FLAG_PINNED) - } - } - return SelectableTargetInfo.newSelectableTargetInfo( - /* sourceInfo = */ createDisplayResolveInfo(isPinned), - /* backupResolveInfo = */ mock(), - /* resolvedIntent = */ Intent(), - /* chooserTarget = */ createChooserTarget( - targetLabel, - 0.5f, - ComponentName("pkg", "Class"), - "id-1" - ), - /* modifiedScore = */ 1f, - shortcutInfo, - /* appTarget */ null, - /* referrerFillInIntent = */ Intent() - ) - } - - private fun createDisplayResolveInfo(isPinned: Boolean = false): DisplayResolveInfo = - DisplayResolveInfo.newDisplayResolveInfo( - Intent(), - ResolverDataProvider.createResolveInfo(2, 0, userHandle), - appLabel, - "extended info", - Intent(), - ) - .apply { - if (isPinned) { - setPinned(true) - } - } - - private fun createView(): View { - val view = FrameLayout(context) - TextView(context).apply { - id = R.id.text1 - view.addView(this) - } - TextView(context).apply { - id = R.id.text2 - view.addView(this) - } - ImageView(context).apply { - id = R.id.icon - view.addView(this) - } - return view - } -} diff --git a/java/tests/src/com/android/intentresolver/ChooserRefinementManagerTest.kt b/java/tests/src/com/android/intentresolver/ChooserRefinementManagerTest.kt deleted file mode 100644 index 61ac0c21..00000000 --- a/java/tests/src/com/android/intentresolver/ChooserRefinementManagerTest.kt +++ /dev/null @@ -1,242 +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 - -import android.app.Activity -import android.app.Application -import android.content.Intent -import android.content.IntentSender -import android.os.Bundle -import android.os.Handler -import android.os.Looper -import android.os.Message -import android.os.ResultReceiver -import androidx.lifecycle.Observer -import androidx.test.annotation.UiThreadTest -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.android.intentresolver.ChooserRefinementManager.RefinementCompletion -import com.android.intentresolver.chooser.ImmutableTargetInfo -import com.android.intentresolver.chooser.TargetInfo -import com.google.common.truth.Truth.assertThat -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentCaptor -import org.mockito.Mockito - -@RunWith(AndroidJUnit4::class) -@UiThreadTest -class ChooserRefinementManagerTest { - private val refinementManager = ChooserRefinementManager() - private val intentSender = mock<IntentSender>() - private val application = mock<Application>() - private val exampleSourceIntents = - listOf(Intent(Intent.ACTION_VIEW), Intent(Intent.ACTION_EDIT)) - private val exampleTargetInfo = - ImmutableTargetInfo.newBuilder().setAllSourceIntents(exampleSourceIntents).build() - - private val completionObserver = - object : Observer<RefinementCompletion> { - val failureCountDown = CountDownLatch(1) - val successCountDown = CountDownLatch(1) - var latestTargetInfo: TargetInfo? = null - - override fun onChanged(completion: RefinementCompletion) { - if (completion.consume()) { - val targetInfo = completion.targetInfo - if (targetInfo == null) { - failureCountDown.countDown() - } else { - latestTargetInfo = targetInfo - successCountDown.countDown() - } - } - } - } - - /** Synchronously executes post() calls. */ - private class FakeHandler(looper: Looper) : Handler(looper) { - override fun sendMessageAtTime(msg: Message, uptimeMillis: Long): Boolean { - dispatchMessage(msg) - return true - } - } - - @Before - fun setup() { - refinementManager.refinementCompletion.observeForever(completionObserver) - } - - @Test - fun testTypicalRefinementFlow() { - assertThat( - refinementManager.maybeHandleSelection( - exampleTargetInfo, - intentSender, - application, - FakeHandler(checkNotNull(Looper.myLooper())) - ) - ) - .isTrue() - - val intentCaptor = ArgumentCaptor.forClass(Intent::class.java) - Mockito.verify(intentSender) - .sendIntent(any(), eq(0), intentCaptor.capture(), eq(null), eq(null)) - - val intent = intentCaptor.value - assertThat(intent?.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)) - .isEqualTo(exampleSourceIntents[0]) - - val alternates = - intent?.getParcelableArrayExtra(Intent.EXTRA_ALTERNATE_INTENTS, Intent::class.java) - assertThat(alternates?.size).isEqualTo(1) - assertThat(alternates?.get(0)).isEqualTo(exampleSourceIntents[1]) - - // Complete the refinement - val receiver = - intent?.getParcelableExtra(Intent.EXTRA_RESULT_RECEIVER, ResultReceiver::class.java) - val bundle = Bundle().apply { putParcelable(Intent.EXTRA_INTENT, exampleSourceIntents[0]) } - receiver?.send(Activity.RESULT_OK, bundle) - - assertThat(completionObserver.successCountDown.await(1000, TimeUnit.MILLISECONDS)).isTrue() - assertThat(completionObserver.latestTargetInfo?.resolvedIntent?.action) - .isEqualTo(Intent.ACTION_VIEW) - } - - @Test - fun testRefinementCancelled() { - assertThat( - refinementManager.maybeHandleSelection( - exampleTargetInfo, - intentSender, - application, - FakeHandler(checkNotNull(Looper.myLooper())) - ) - ) - .isTrue() - - val intentCaptor = ArgumentCaptor.forClass(Intent::class.java) - Mockito.verify(intentSender) - .sendIntent(any(), eq(0), intentCaptor.capture(), eq(null), eq(null)) - - val intent = intentCaptor.value - - // Complete the refinement - val receiver = - intent?.getParcelableExtra(Intent.EXTRA_RESULT_RECEIVER, ResultReceiver::class.java) - val bundle = Bundle().apply { putParcelable(Intent.EXTRA_INTENT, exampleSourceIntents[0]) } - receiver?.send(Activity.RESULT_CANCELED, bundle) - - assertThat(completionObserver.failureCountDown.await(1000, TimeUnit.MILLISECONDS)).isTrue() - } - - @Test - fun testMaybeHandleSelection_noSourceIntents() { - assertThat( - refinementManager.maybeHandleSelection( - ImmutableTargetInfo.newBuilder().build(), - intentSender, - application, - FakeHandler(checkNotNull(Looper.myLooper())) - ) - ) - .isFalse() - } - - @Test - fun testMaybeHandleSelection_suspended() { - val targetInfo = - ImmutableTargetInfo.newBuilder() - .setAllSourceIntents(exampleSourceIntents) - .setIsSuspended(true) - .build() - - assertThat( - refinementManager.maybeHandleSelection( - targetInfo, - intentSender, - application, - FakeHandler(checkNotNull(Looper.myLooper())) - ) - ) - .isFalse() - } - - @Test - fun testMaybeHandleSelection_noIntentSender() { - assertThat( - refinementManager.maybeHandleSelection( - exampleTargetInfo, - /* IntentSender */ null, - application, - FakeHandler(checkNotNull(Looper.myLooper())) - ) - ) - .isFalse() - } - - @Test - fun testConfigurationChangeDuringRefinement() { - assertThat( - refinementManager.maybeHandleSelection( - exampleTargetInfo, - intentSender, - application, - FakeHandler(checkNotNull(Looper.myLooper())) - ) - ) - .isTrue() - - refinementManager.onActivityStop(/* config changing = */ true) - refinementManager.onActivityResume() - - assertThat(completionObserver.failureCountDown.count).isEqualTo(1) - } - - @Test - fun testResumeDuringRefinement() { - assertThat( - refinementManager.maybeHandleSelection( - exampleTargetInfo, - intentSender, - application, - FakeHandler(checkNotNull(Looper.myLooper())!!) - ) - ) - .isTrue() - - refinementManager.onActivityStop(/* config changing = */ false) - // Resume during refinement but not during a config change, so finish the activity. - refinementManager.onActivityResume() - - // Call should be synchronous, don't need to await for this one. - assertThat(completionObserver.failureCountDown.count).isEqualTo(0) - } - - @Test - fun testRefinementCompletion() { - val refinementCompletion = RefinementCompletion(exampleTargetInfo) - assertThat(refinementCompletion.targetInfo).isEqualTo(exampleTargetInfo) - assertThat(refinementCompletion.consume()).isTrue() - assertThat(refinementCompletion.targetInfo).isEqualTo(exampleTargetInfo) - - // can only consume once. - assertThat(refinementCompletion.consume()).isFalse() - } -} diff --git a/java/tests/src/com/android/intentresolver/ChooserRequestParametersTest.kt b/java/tests/src/com/android/intentresolver/ChooserRequestParametersTest.kt deleted file mode 100644 index 90f6cf93..00000000 --- a/java/tests/src/com/android/intentresolver/ChooserRequestParametersTest.kt +++ /dev/null @@ -1,87 +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 - -import android.app.PendingIntent -import android.content.Intent -import android.graphics.drawable.Icon -import android.net.Uri -import android.service.chooser.ChooserAction -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import com.google.common.truth.Truth.assertThat -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class ChooserRequestParametersTest { - - @Test - fun testChooserActions() { - val actionCount = 3 - val intent = Intent(Intent.ACTION_SEND) - val actions = createChooserActions(actionCount) - val chooserIntent = - Intent(Intent.ACTION_CHOOSER).apply { - putExtra(Intent.EXTRA_INTENT, intent) - putExtra(Intent.EXTRA_CHOOSER_CUSTOM_ACTIONS, actions) - } - val request = ChooserRequestParameters(chooserIntent, "", Uri.EMPTY) - assertThat(request.chooserActions).containsExactlyElementsIn(actions).inOrder() - } - - @Test - fun testChooserActions_empty() { - val intent = Intent(Intent.ACTION_SEND) - val chooserIntent = - Intent(Intent.ACTION_CHOOSER).apply { putExtra(Intent.EXTRA_INTENT, intent) } - val request = ChooserRequestParameters(chooserIntent, "", Uri.EMPTY) - assertThat(request.chooserActions).isEmpty() - } - - @Test - fun testChooserActions_tooMany() { - val intent = Intent(Intent.ACTION_SEND) - val chooserActions = createChooserActions(10) - val chooserIntent = - Intent(Intent.ACTION_CHOOSER).apply { - putExtra(Intent.EXTRA_INTENT, intent) - putExtra(Intent.EXTRA_CHOOSER_CUSTOM_ACTIONS, chooserActions) - } - - val request = ChooserRequestParameters(chooserIntent, "", Uri.EMPTY) - - val expectedActions = chooserActions.sliceArray(0 until 5) - assertThat(request.chooserActions).containsExactlyElementsIn(expectedActions).inOrder() - } - - private fun createChooserActions(count: Int): Array<ChooserAction> { - return Array(count) { i -> createChooserAction("$i") } - } - - private fun createChooserAction(label: CharSequence): ChooserAction { - val icon = Icon.createWithContentUri("content://org.package.app/image") - val pendingIntent = - PendingIntent.getBroadcast( - InstrumentationRegistry.getInstrumentation().getTargetContext(), - 0, - Intent("TESTACTION"), - PendingIntent.FLAG_IMMUTABLE - ) - return ChooserAction.Builder(icon, label, pendingIntent).build() - } -} diff --git a/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java b/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java deleted file mode 100644 index 72f1f452..00000000 --- a/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java +++ /dev/null @@ -1,274 +0,0 @@ -/* - * Copyright (C) 2008 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.app.prediction.AppPredictor; -import android.app.usage.UsageStatsManager; -import android.content.ComponentName; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.res.Resources; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.os.UserHandle; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.lifecycle.ViewModelProvider; - -import com.android.intentresolver.chooser.DisplayResolveInfo; -import com.android.intentresolver.chooser.TargetInfo; -import com.android.intentresolver.emptystate.CrossProfileIntentsChecker; -import com.android.intentresolver.grid.ChooserGridAdapter; -import com.android.intentresolver.icons.TargetDataLoader; -import com.android.intentresolver.shortcuts.ShortcutLoader; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; - -import java.util.List; -import java.util.function.Consumer; - -/** - * Simple wrapper around chooser activity to be able to initiate it under test. For more - * information, see {@code com.android.internal.app.ChooserWrapperActivity}. - */ -public class ChooserWrapperActivity extends ChooserActivity implements IChooserWrapper { - static final ChooserActivityOverrideData sOverrides = ChooserActivityOverrideData.getInstance(); - private UsageStatsManager mUsm; - - // ResolverActivity (the base class of ChooserActivity) inspects the launched-from UID at - // onCreate and needs to see some non-negative value in the test. - @Override - public int getLaunchedFromUid() { - return 1234; - } - - @Override - public ChooserListAdapter createChooserListAdapter( - Context context, - List<Intent> payloadIntents, - Intent[] initialIntents, - List<ResolveInfo> rList, - boolean filterLastUsed, - ResolverListController resolverListController, - UserHandle userHandle, - Intent targetIntent, - ChooserRequestParameters chooserRequest, - int maxTargetsPerRow, - TargetDataLoader targetDataLoader) { - PackageManager packageManager = - sOverrides.packageManager == null ? context.getPackageManager() - : sOverrides.packageManager; - return new ChooserListAdapter( - context, - payloadIntents, - initialIntents, - rList, - filterLastUsed, - createListController(userHandle), - userHandle, - targetIntent, - this, - packageManager, - getEventLog(), - chooserRequest, - maxTargetsPerRow, - userHandle, - targetDataLoader); - } - - @Override - public ChooserListAdapter getAdapter() { - return mChooserMultiProfilePagerAdapter.getActiveListAdapter(); - } - - @Override - public ChooserListAdapter getPersonalListAdapter() { - return ((ChooserGridAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(0)) - .getListAdapter(); - } - - @Override - public ChooserListAdapter getWorkListAdapter() { - if (mMultiProfilePagerAdapter.getInactiveListAdapter() == null) { - return null; - } - return ((ChooserGridAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(1)) - .getListAdapter(); - } - - @Override - public boolean getIsSelected() { - return mIsSuccessfullySelected; - } - - @Override - protected ChooserIntegratedDeviceComponents getIntegratedDeviceComponents() { - return new ChooserIntegratedDeviceComponents( - /* editSharingComponent=*/ null, - // An arbitrary pre-installed activity that handles this type of intent: - /* nearbySharingComponent=*/ new ComponentName( - "com.google.android.apps.messaging", - ".ui.conversationlist.ShareIntentActivity")); - } - - @Override - public UsageStatsManager getUsageStatsManager() { - if (mUsm == null) { - mUsm = getSystemService(UsageStatsManager.class); - } - return mUsm; - } - - @Override - public boolean isVoiceInteraction() { - if (sOverrides.isVoiceInteraction != null) { - return sOverrides.isVoiceInteraction; - } - return super.isVoiceInteraction(); - } - - @Override - protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() { - if (sOverrides.mCrossProfileIntentsChecker != null) { - return sOverrides.mCrossProfileIntentsChecker; - } - return super.createCrossProfileIntentsChecker(); - } - - @Override - protected WorkProfileAvailabilityManager createWorkProfileAvailabilityManager() { - if (sOverrides.mWorkProfileAvailability != null) { - return sOverrides.mWorkProfileAvailability; - } - return super.createWorkProfileAvailabilityManager(); - } - - @Override - public void safelyStartActivityInternal(TargetInfo cti, UserHandle user, - @Nullable Bundle options) { - if (sOverrides.onSafelyStartInternalCallback != null - && sOverrides.onSafelyStartInternalCallback.apply(cti)) { - return; - } - super.safelyStartActivityInternal(cti, user, options); - } - - @Override - protected ChooserListController createListController(UserHandle userHandle) { - if (userHandle == UserHandle.SYSTEM) { - return sOverrides.resolverListController; - } - return sOverrides.workResolverListController; - } - - @Override - public PackageManager getPackageManager() { - if (sOverrides.createPackageManager != null) { - return sOverrides.createPackageManager.apply(super.getPackageManager()); - } - return super.getPackageManager(); - } - - @Override - public Resources getResources() { - if (sOverrides.resources != null) { - return sOverrides.resources; - } - return super.getResources(); - } - - @Override - protected ViewModelProvider.Factory createPreviewViewModelFactory() { - return TestContentPreviewViewModel.Companion.wrap( - super.createPreviewViewModelFactory(), - sOverrides.imageLoader); - } - - @Override - public Cursor queryResolver(ContentResolver resolver, Uri uri) { - if (sOverrides.resolverCursor != null) { - return sOverrides.resolverCursor; - } - - if (sOverrides.resolverForceException) { - throw new SecurityException("Test exception handling"); - } - - return super.queryResolver(resolver, uri); - } - - @Override - protected boolean isWorkProfile() { - if (sOverrides.alternateProfileSetting != 0) { - return sOverrides.alternateProfileSetting == MetricsEvent.MANAGED_PROFILE; - } - return super.isWorkProfile(); - } - - @Override - public DisplayResolveInfo createTestDisplayResolveInfo( - Intent originalIntent, - ResolveInfo pri, - CharSequence pLabel, - CharSequence pInfo, - Intent replacementIntent) { - return DisplayResolveInfo.newDisplayResolveInfo( - originalIntent, - pri, - pLabel, - pInfo, - replacementIntent); - } - - @Override - protected AnnotatedUserHandles computeAnnotatedUserHandles() { - return sOverrides.annotatedUserHandles; - } - - @Override - public UserHandle getCurrentUserHandle() { - return mMultiProfilePagerAdapter.getCurrentUserHandle(); - } - - @NonNull - @Override - public Context createContextAsUser(UserHandle user, int flags) { - // return the current context as a work profile doesn't really exist in these tests - return this; - } - - @Override - protected ShortcutLoader createShortcutLoader( - Context context, - AppPredictor appPredictor, - UserHandle userHandle, - IntentFilter targetIntentFilter, - Consumer<ShortcutLoader.Result> callback) { - ShortcutLoader shortcutLoader = - sOverrides.shortcutLoaderFactory.invoke(userHandle, callback); - if (shortcutLoader != null) { - return shortcutLoader; - } - return super.createShortcutLoader( - context, appPredictor, userHandle, targetIntentFilter, callback); - } -} diff --git a/java/tests/src/com/android/intentresolver/EnterTransitionAnimationDelegateTest.kt b/java/tests/src/com/android/intentresolver/EnterTransitionAnimationDelegateTest.kt deleted file mode 100644 index c7d20000..00000000 --- a/java/tests/src/com/android/intentresolver/EnterTransitionAnimationDelegateTest.kt +++ /dev/null @@ -1,112 +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 - -import android.content.res.Resources -import android.view.View -import android.view.Window -import androidx.activity.ComponentActivity -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.testing.TestLifecycleOwner -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.TestCoroutineScheduler -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.mockito.Mockito.anyInt -import org.mockito.Mockito.never -import org.mockito.Mockito.times -import org.mockito.Mockito.verify - -private const val TIMEOUT_MS = 200 - -@OptIn(ExperimentalCoroutinesApi::class) -class EnterTransitionAnimationDelegateTest { - private val elementName = "shared-element" - private val scheduler = TestCoroutineScheduler() - private val dispatcher = StandardTestDispatcher(scheduler) - private val lifecycleOwner = TestLifecycleOwner() - - private val transitionTargetView = - mock<View> { - // avoid the request-layout path in the delegate - whenever(isInLayout).thenReturn(true) - } - - private val windowMock = mock<Window>() - private val resourcesMock = - mock<Resources> { whenever(getInteger(anyInt())).thenReturn(TIMEOUT_MS) } - private val activity = - mock<ComponentActivity> { - whenever(lifecycle).thenReturn(lifecycleOwner.lifecycle) - whenever(resources).thenReturn(resourcesMock) - whenever(isActivityTransitionRunning).thenReturn(true) - whenever(window).thenReturn(windowMock) - } - - private val testSubject = EnterTransitionAnimationDelegate(activity) { transitionTargetView } - - @Before - fun setup() { - Dispatchers.setMain(dispatcher) - lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_CREATE) - } - - @After - fun cleanup() { - lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) - Dispatchers.resetMain() - } - - @Test - fun test_postponeTransition_timeout() { - testSubject.postponeTransition() - testSubject.markOffsetCalculated() - - scheduler.advanceTimeBy(TIMEOUT_MS + 1L) - verify(activity, times(1)).startPostponedEnterTransition() - verify(windowMock, never()).setWindowAnimations(anyInt()) - } - - @Test - fun test_postponeTransition_animation_resumes_only_once() { - testSubject.postponeTransition() - testSubject.markOffsetCalculated() - testSubject.onTransitionElementReady(elementName) - testSubject.markOffsetCalculated() - testSubject.onTransitionElementReady(elementName) - - scheduler.advanceTimeBy(TIMEOUT_MS + 1L) - verify(activity, times(1)).startPostponedEnterTransition() - } - - @Test - fun test_postponeTransition_resume_animation_conditions() { - testSubject.postponeTransition() - verify(activity, never()).startPostponedEnterTransition() - - testSubject.markOffsetCalculated() - verify(activity, never()).startPostponedEnterTransition() - - testSubject.onAllTransitionElementsReady() - verify(activity, times(1)).startPostponedEnterTransition() - } -} diff --git a/java/tests/src/com/android/intentresolver/FakeResolverListCommunicator.kt b/java/tests/src/com/android/intentresolver/FakeResolverListCommunicator.kt deleted file mode 100644 index 5e9cd98f..00000000 --- a/java/tests/src/com/android/intentresolver/FakeResolverListCommunicator.kt +++ /dev/null @@ -1,56 +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 - -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/IChooserWrapper.java b/java/tests/src/com/android/intentresolver/IChooserWrapper.java deleted file mode 100644 index 481cf3b2..00000000 --- a/java/tests/src/com/android/intentresolver/IChooserWrapper.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2021 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.app.usage.UsageStatsManager; -import android.content.Intent; -import android.content.pm.ResolveInfo; -import android.os.UserHandle; - -import androidx.annotation.Nullable; - -import com.android.intentresolver.chooser.DisplayResolveInfo; - -import java.util.concurrent.Executor; - -/** - * Test-only extended API capabilities that an instrumented ChooserActivity subclass provides in - * order to expose the internals for override/inspection. Implementations should apply the overrides - * specified by the {@code ChooserActivityOverrideData} singleton. - */ -public interface IChooserWrapper { - ChooserListAdapter getAdapter(); - ChooserListAdapter getPersonalListAdapter(); - ChooserListAdapter getWorkListAdapter(); - boolean getIsSelected(); - UsageStatsManager getUsageStatsManager(); - DisplayResolveInfo createTestDisplayResolveInfo( - Intent originalIntent, - ResolveInfo pri, - CharSequence pLabel, - CharSequence pInfo, - @Nullable Intent replacementIntent); - UserHandle getCurrentUserHandle(); - Executor getMainExecutor(); -} diff --git a/java/tests/src/com/android/intentresolver/MatcherUtils.java b/java/tests/src/com/android/intentresolver/MatcherUtils.java deleted file mode 100644 index 97cc6984..00000000 --- a/java/tests/src/com/android/intentresolver/MatcherUtils.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2020 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 org.hamcrest.BaseMatcher; -import org.hamcrest.Description; -import org.hamcrest.Matcher; - -/** - * Utils for helping with more customized matching options, for example matching the first - * occurrence of a set criteria. - */ -public class MatcherUtils { - - /** - * Returns a {@link Matcher} which only matches the first occurrence of a set criteria. - */ - public static <T> Matcher<T> first(final Matcher<T> matcher) { - return new BaseMatcher<T>() { - boolean isFirstMatch = true; - - @Override - public boolean matches(final Object item) { - if (isFirstMatch && matcher.matches(item)) { - isFirstMatch = false; - return true; - } - return false; - } - - @Override - public void describeTo(final Description description) { - description.appendText("Returns the first matching item"); - } - }; - } -} diff --git a/java/tests/src/com/android/intentresolver/MockitoKotlinHelpers.kt b/java/tests/src/com/android/intentresolver/MockitoKotlinHelpers.kt deleted file mode 100644 index db9fbd93..00000000 --- a/java/tests/src/com/android/intentresolver/MockitoKotlinHelpers.kt +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.intentresolver - -/** - * Kotlin versions of popular mockito methods that can return null in situations when Kotlin expects - * a non-null value. Kotlin will throw an IllegalStateException when this takes place ("x must not - * be null"). To fix this, we can use methods that modify the return type to be nullable. This - * causes Kotlin to skip the null checks. - * Cloned from frameworks/base/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt - */ -import org.mockito.ArgumentCaptor -import org.mockito.ArgumentMatcher -import org.mockito.ArgumentMatchers -import org.mockito.MockSettings -import org.mockito.Mockito -import org.mockito.stubbing.Answer -import org.mockito.stubbing.OngoingStubbing -import org.mockito.stubbing.Stubber - -/** - * Returns Mockito.eq() as nullable type to avoid java.lang.IllegalStateException when - * null is returned. - * - * Generic T is nullable because implicitly bounded by Any?. - */ -fun <T> eq(obj: T): T = Mockito.eq<T>(obj) - -/** - * Returns Mockito.any() as nullable type to avoid java.lang.IllegalStateException when - * null is returned. - * - * Generic T is nullable because implicitly bounded by Any?. - */ -fun <T> any(type: Class<T>): T = Mockito.any<T>(type) -inline fun <reified T> any(): T = any(T::class.java) - -/** - * Returns Mockito.argThat() as nullable type to avoid java.lang.IllegalStateException when - * null is returned. - * - * Generic T is nullable because implicitly bounded by Any?. - */ -fun <T> argThat(matcher: ArgumentMatcher<T>): T = Mockito.argThat(matcher) - -/** - * Kotlin type-inferred version of Mockito.nullable() - */ -inline fun <reified T> nullable(): T? = Mockito.nullable(T::class.java) - -/** - * Returns ArgumentCaptor.capture() as nullable type to avoid java.lang.IllegalStateException - * when null is returned. - * - * Generic T is nullable because implicitly bounded by Any?. - */ -fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture() - -/** - * Helper function for creating an argumentCaptor in kotlin. - * - * Generic T is nullable because implicitly bounded by Any?. - */ -inline fun <reified T : Any> argumentCaptor(): ArgumentCaptor<T> = - ArgumentCaptor.forClass(T::class.java) - -/** - * Helper function for creating new mocks, without the need to pass in a [Class] instance. - * - * Generic T is nullable because implicitly bounded by Any?. - * - * @param apply builder function to simplify stub configuration by improving type inference. - */ -inline fun <reified T : Any> mock( - mockSettings: MockSettings = Mockito.withSettings(), - apply: T.() -> Unit = {} -): T = Mockito.mock(T::class.java, mockSettings).apply(apply) - -/** - * Helper function for stubbing methods without the need to use backticks. - * - * @see Mockito.when - */ -fun <T> whenever(methodCall: T): OngoingStubbing<T> = Mockito.`when`(methodCall) - -/** - * Helper function for stubbing methods without the need to use backticks. - */ -fun <T> Stubber.whenever(mock: T): T = `when`(mock) - -/** - * A kotlin implemented wrapper of [ArgumentCaptor] which prevents the following exception when - * kotlin tests are mocking kotlin objects and the methods take non-null parameters: - * - * java.lang.NullPointerException: capture() must not be null - */ -class KotlinArgumentCaptor<T> constructor(clazz: Class<T>) { - private val wrapped: ArgumentCaptor<T> = ArgumentCaptor.forClass(clazz) - fun capture(): T = wrapped.capture() - val value: T - get() = wrapped.value - val allValues: List<T> - get() = wrapped.allValues -} - -/** - * Helper function for creating an argumentCaptor in kotlin. - * - * Generic T is nullable because implicitly bounded by Any?. - */ -inline fun <reified T : Any> kotlinArgumentCaptor(): KotlinArgumentCaptor<T> = - KotlinArgumentCaptor(T::class.java) - -/** - * Helper function for creating and using a single-use ArgumentCaptor in kotlin. - * - * val captor = argumentCaptor<Foo>() - * verify(...).someMethod(captor.capture()) - * val captured = captor.value - * - * becomes: - * - * val captured = withArgCaptor<Foo> { verify(...).someMethod(capture()) } - * - * NOTE: this uses the KotlinArgumentCaptor to avoid the NullPointerException. - */ -inline fun <reified T : Any> withArgCaptor(block: KotlinArgumentCaptor<T>.() -> Unit): T = - kotlinArgumentCaptor<T>().apply { block() }.value - -/** - * Variant of [withArgCaptor] for capturing multiple arguments. - * - * val captor = argumentCaptor<Foo>() - * verify(...).someMethod(captor.capture()) - * val captured: List<Foo> = captor.allValues - * - * becomes: - * - * val capturedList = captureMany<Foo> { verify(...).someMethod(capture()) } - */ -inline fun <reified T : Any> captureMany(block: KotlinArgumentCaptor<T>.() -> Unit): List<T> = - kotlinArgumentCaptor<T>().apply { block() }.allValues - -inline fun <reified T> anyOrNull() = ArgumentMatchers.argThat(ArgumentMatcher<T?> { true }) - -/** - * Intended as a default Answer for a mock to prevent dependence on defaults. - * - * Use as: - * ``` - * val context = mock<Context>(withSettings() - * .defaultAnswer(THROWS_EXCEPTION)) - * ``` - * - * To avoid triggering the exception during stubbing, must ONLY use one of the doXXX() methods, such - * as: - * * [doAnswer][Mockito.doAnswer] - * * [doCallRealMethod][Mockito.doCallRealMethod] - * * [doNothing][Mockito.doNothing] - * * [doReturn][Mockito.doReturn] - * * [doThrow][Mockito.doThrow] - */ -val THROWS_EXCEPTION = Answer { error("Unstubbed behavior was accessed.") } diff --git a/java/tests/src/com/android/intentresolver/MultiProfilePagerAdapterTest.kt b/java/tests/src/com/android/intentresolver/MultiProfilePagerAdapterTest.kt deleted file mode 100644 index ed06f7d1..00000000 --- a/java/tests/src/com/android/intentresolver/MultiProfilePagerAdapterTest.kt +++ /dev/null @@ -1,277 +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 - -import android.os.UserHandle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.ListView -import androidx.test.platform.app.InstrumentationRegistry -import com.android.intentresolver.MultiProfilePagerAdapter.PROFILE_PERSONAL -import com.android.intentresolver.MultiProfilePagerAdapter.PROFILE_WORK -import com.android.intentresolver.emptystate.EmptyStateProvider -import com.google.common.collect.ImmutableList -import com.google.common.truth.Truth.assertThat -import java.util.Optional -import java.util.function.Supplier -import org.junit.Test -import org.mockito.Mockito.never -import org.mockito.Mockito.verify - -class MultiProfilePagerAdapterTest { - private val PERSONAL_USER_HANDLE = UserHandle.of(10) - private val WORK_USER_HANDLE = UserHandle.of(20) - - private val context = InstrumentationRegistry.getInstrumentation().getContext() - private val inflater = Supplier { - LayoutInflater.from(context).inflate(R.layout.resolver_list_per_profile, null, false) - as ViewGroup - } - - @Test - fun testSinglePageProfileAdapter() { - val personalListAdapter = - mock<ResolverListAdapter> { whenever(getUserHandle()).thenReturn(PERSONAL_USER_HANDLE) } - val pagerAdapter = - MultiProfilePagerAdapter( - { listAdapter: ResolverListAdapter -> listAdapter }, - { listView: ListView, bindAdapter: ResolverListAdapter -> - listView.setAdapter(bindAdapter) - }, - ImmutableList.of(personalListAdapter), - object : EmptyStateProvider {}, - { false }, - PROFILE_PERSONAL, - null, - null, - inflater, - { Optional.empty() } - ) - assertThat(pagerAdapter.count).isEqualTo(1) - assertThat(pagerAdapter.currentPage).isEqualTo(PROFILE_PERSONAL) - assertThat(pagerAdapter.currentUserHandle).isEqualTo(PERSONAL_USER_HANDLE) - assertThat(pagerAdapter.getAdapterForIndex(0)).isSameInstanceAs(personalListAdapter) - assertThat(pagerAdapter.activeListAdapter).isSameInstanceAs(personalListAdapter) - assertThat(pagerAdapter.inactiveListAdapter).isNull() - assertThat(pagerAdapter.personalListAdapter).isSameInstanceAs(personalListAdapter) - assertThat(pagerAdapter.workListAdapter).isNull() - assertThat(pagerAdapter.itemCount).isEqualTo(1) - // TODO: consider covering some of the package-private methods (and making them public?). - // TODO: consider exercising responsibilities as an implementation of a ViewPager adapter. - } - - @Test - fun testTwoProfilePagerAdapter() { - val personalListAdapter = - mock<ResolverListAdapter> { whenever(getUserHandle()).thenReturn(PERSONAL_USER_HANDLE) } - val workListAdapter = - mock<ResolverListAdapter> { whenever(getUserHandle()).thenReturn(WORK_USER_HANDLE) } - val pagerAdapter = - MultiProfilePagerAdapter( - { listAdapter: ResolverListAdapter -> listAdapter }, - { listView: ListView, bindAdapter: ResolverListAdapter -> - listView.setAdapter(bindAdapter) - }, - ImmutableList.of(personalListAdapter, workListAdapter), - object : EmptyStateProvider {}, - { false }, - PROFILE_PERSONAL, - WORK_USER_HANDLE, // TODO: why does this test pass even if this is null? - null, - inflater, - { Optional.empty() } - ) - assertThat(pagerAdapter.count).isEqualTo(2) - assertThat(pagerAdapter.currentPage).isEqualTo(PROFILE_PERSONAL) - assertThat(pagerAdapter.currentUserHandle).isEqualTo(PERSONAL_USER_HANDLE) - assertThat(pagerAdapter.getAdapterForIndex(0)).isSameInstanceAs(personalListAdapter) - assertThat(pagerAdapter.getAdapterForIndex(1)).isSameInstanceAs(workListAdapter) - assertThat(pagerAdapter.activeListAdapter).isSameInstanceAs(personalListAdapter) - assertThat(pagerAdapter.inactiveListAdapter).isSameInstanceAs(workListAdapter) - assertThat(pagerAdapter.personalListAdapter).isSameInstanceAs(personalListAdapter) - assertThat(pagerAdapter.workListAdapter).isSameInstanceAs(workListAdapter) - assertThat(pagerAdapter.itemCount).isEqualTo(2) - // TODO: consider covering some of the package-private methods (and making them public?). - // TODO: consider exercising responsibilities as an implementation of a ViewPager adapter; - // especially matching profiles to ListViews? - // TODO: test ProfileSelectedListener (and getters for "current" state) as the selected - // page changes. Currently there's no API to change the selected page directly; that's - // only possible through manipulation of the bound ViewPager. - } - - @Test - fun testTwoProfilePagerAdapter_workIsDefault() { - val personalListAdapter = - mock<ResolverListAdapter> { whenever(getUserHandle()).thenReturn(PERSONAL_USER_HANDLE) } - val workListAdapter = - mock<ResolverListAdapter> { whenever(getUserHandle()).thenReturn(WORK_USER_HANDLE) } - val pagerAdapter = - MultiProfilePagerAdapter( - { listAdapter: ResolverListAdapter -> listAdapter }, - { listView: ListView, bindAdapter: ResolverListAdapter -> - listView.setAdapter(bindAdapter) - }, - ImmutableList.of(personalListAdapter, workListAdapter), - object : EmptyStateProvider {}, - { false }, - PROFILE_WORK, // <-- This test specifically requests we start on work profile. - WORK_USER_HANDLE, // TODO: why does this test pass even if this is null? - null, - inflater, - { Optional.empty() } - ) - assertThat(pagerAdapter.count).isEqualTo(2) - assertThat(pagerAdapter.currentPage).isEqualTo(PROFILE_WORK) - assertThat(pagerAdapter.currentUserHandle).isEqualTo(WORK_USER_HANDLE) - assertThat(pagerAdapter.getAdapterForIndex(0)).isSameInstanceAs(personalListAdapter) - assertThat(pagerAdapter.getAdapterForIndex(1)).isSameInstanceAs(workListAdapter) - assertThat(pagerAdapter.activeListAdapter).isSameInstanceAs(workListAdapter) - assertThat(pagerAdapter.inactiveListAdapter).isSameInstanceAs(personalListAdapter) - assertThat(pagerAdapter.personalListAdapter).isSameInstanceAs(personalListAdapter) - assertThat(pagerAdapter.workListAdapter).isSameInstanceAs(workListAdapter) - assertThat(pagerAdapter.itemCount).isEqualTo(2) - // TODO: consider covering some of the package-private methods (and making them public?). - // TODO: test ProfileSelectedListener (and getters for "current" state) as the selected - // page changes. Currently there's no API to change the selected page directly; that's - // only possible through manipulation of the bound ViewPager. - } - - @Test - fun testBottomPaddingDelegate_default() { - val container = - mock<View> { - whenever(getPaddingLeft()).thenReturn(1) - whenever(getPaddingTop()).thenReturn(2) - whenever(getPaddingRight()).thenReturn(3) - whenever(getPaddingBottom()).thenReturn(4) - } - val pagerAdapter = - MultiProfilePagerAdapter( - { listAdapter: ResolverListAdapter -> listAdapter }, - { listView: ListView, bindAdapter: ResolverListAdapter -> - listView.setAdapter(bindAdapter) - }, - ImmutableList.of(), - object : EmptyStateProvider {}, - { false }, - PROFILE_PERSONAL, - null, - null, - inflater, - { Optional.empty() } - ) - pagerAdapter.setupContainerPadding(container) - verify(container, never()).setPadding(any(), any(), any(), any()) - } - - @Test - fun testBottomPaddingDelegate_override() { - val container = - mock<View> { - whenever(getPaddingLeft()).thenReturn(1) - whenever(getPaddingTop()).thenReturn(2) - whenever(getPaddingRight()).thenReturn(3) - whenever(getPaddingBottom()).thenReturn(4) - } - val pagerAdapter = - MultiProfilePagerAdapter( - { listAdapter: ResolverListAdapter -> listAdapter }, - { listView: ListView, bindAdapter: ResolverListAdapter -> - listView.setAdapter(bindAdapter) - }, - ImmutableList.of(), - object : EmptyStateProvider {}, - { false }, - PROFILE_PERSONAL, - null, - null, - inflater, - { Optional.of(42) } - ) - pagerAdapter.setupContainerPadding(container) - verify(container).setPadding(1, 2, 3, 42) - } - - @Test - fun testPresumedQuietModeEmptyStateForWorkProfile_whenQuiet() { - // TODO: this is "presumed" because the conditions to determine whether we "should" show an - // empty state aren't enforced to align with the conditions when we actually *would* -- I - // believe `shouldShowEmptyStateScreen` should be implemented in terms of the provider? - val personalListAdapter = - mock<ResolverListAdapter> { - whenever(getUserHandle()).thenReturn(PERSONAL_USER_HANDLE) - whenever(getUnfilteredCount()).thenReturn(1) - } - val workListAdapter = - mock<ResolverListAdapter> { - whenever(getUserHandle()).thenReturn(WORK_USER_HANDLE) - whenever(getUnfilteredCount()).thenReturn(1) - } - val pagerAdapter = - MultiProfilePagerAdapter( - { listAdapter: ResolverListAdapter -> listAdapter }, - { listView: ListView, bindAdapter: ResolverListAdapter -> - listView.setAdapter(bindAdapter) - }, - ImmutableList.of(personalListAdapter, workListAdapter), - object : EmptyStateProvider {}, - { true }, // <-- Work mode is quiet. - PROFILE_WORK, - WORK_USER_HANDLE, - null, - inflater, - { Optional.empty() } - ) - assertThat(pagerAdapter.shouldShowEmptyStateScreen(workListAdapter)).isTrue() - assertThat(pagerAdapter.shouldShowEmptyStateScreen(personalListAdapter)).isFalse() - } - - @Test - fun testPresumedQuietModeEmptyStateForWorkProfile_notWhenNotQuiet() { - // TODO: this is "presumed" because the conditions to determine whether we "should" show an - // empty state aren't enforced to align with the conditions when we actually *would* -- I - // believe `shouldShowEmptyStateScreen` should be implemented in terms of the provider? - val personalListAdapter = - mock<ResolverListAdapter> { - whenever(getUserHandle()).thenReturn(PERSONAL_USER_HANDLE) - whenever(getUnfilteredCount()).thenReturn(1) - } - val workListAdapter = - mock<ResolverListAdapter> { - whenever(getUserHandle()).thenReturn(WORK_USER_HANDLE) - whenever(getUnfilteredCount()).thenReturn(1) - } - val pagerAdapter = - MultiProfilePagerAdapter( - { listAdapter: ResolverListAdapter -> listAdapter }, - { listView: ListView, bindAdapter: ResolverListAdapter -> - listView.setAdapter(bindAdapter) - }, - ImmutableList.of(personalListAdapter, workListAdapter), - object : EmptyStateProvider {}, - { false }, // <-- Work mode is not quiet. - PROFILE_WORK, - WORK_USER_HANDLE, - null, - inflater, - { Optional.empty() } - ) - assertThat(pagerAdapter.shouldShowEmptyStateScreen(workListAdapter)).isFalse() - assertThat(pagerAdapter.shouldShowEmptyStateScreen(personalListAdapter)).isFalse() - } -} diff --git a/java/tests/src/com/android/intentresolver/ResolverActivityTest.java b/java/tests/src/com/android/intentresolver/ResolverActivityTest.java deleted file mode 100644 index dde2f980..00000000 --- a/java/tests/src/com/android/intentresolver/ResolverActivityTest.java +++ /dev/null @@ -1,1097 +0,0 @@ -/* - * Copyright (C) 2016 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 static androidx.test.espresso.Espresso.onView; -import static androidx.test.espresso.action.ViewActions.click; -import static androidx.test.espresso.action.ViewActions.swipeUp; -import static androidx.test.espresso.assertion.ViewAssertions.matches; -import static androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed; -import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; -import static androidx.test.espresso.matcher.ViewMatchers.isEnabled; -import static androidx.test.espresso.matcher.ViewMatchers.withId; -import static androidx.test.espresso.matcher.ViewMatchers.withText; - -import static com.android.intentresolver.MatcherUtils.first; -import static com.android.intentresolver.ResolverWrapperActivity.sOverrides; - -import static org.hamcrest.CoreMatchers.allOf; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.fail; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -import android.content.Intent; -import android.content.pm.ResolveInfo; -import android.net.Uri; -import android.os.RemoteException; -import android.os.UserHandle; -import android.text.TextUtils; -import android.view.View; -import android.widget.RelativeLayout; -import android.widget.TextView; - -import androidx.test.InstrumentationRegistry; -import androidx.test.espresso.Espresso; -import androidx.test.espresso.NoMatchingViewException; -import androidx.test.rule.ActivityTestRule; -import androidx.test.runner.AndroidJUnit4; - -import com.android.intentresolver.widget.ResolverDrawerLayout; - -import com.google.android.collect.Lists; - -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mockito; - -import java.util.ArrayList; -import java.util.List; - -/** - * Resolver activity instrumentation tests - */ -@RunWith(AndroidJUnit4.class) -public class ResolverActivityTest { - - private static final UserHandle PERSONAL_USER_HANDLE = androidx.test.platform.app - .InstrumentationRegistry.getInstrumentation().getTargetContext().getUser(); - private static final UserHandle WORK_PROFILE_USER_HANDLE = UserHandle.of(10); - private static final UserHandle CLONE_PROFILE_USER_HANDLE = UserHandle.of(11); - - @Rule - public ActivityTestRule<ResolverWrapperActivity> mActivityRule = - new ActivityTestRule<>(ResolverWrapperActivity.class, false, false); - - @Before - public void setup() { - // TODO: use the other form of `adoptShellPermissionIdentity()` where we explicitly list the - // permissions we require (which we'll read from the manifest at runtime). - androidx.test.platform.app.InstrumentationRegistry - .getInstrumentation() - .getUiAutomation() - .adoptShellPermissionIdentity(); - - sOverrides.reset(); - } - - @Test - public void twoOptionsAndUserSelectsOne() throws InterruptedException { - Intent sendIntent = createSendImageIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2, - PERSONAL_USER_HANDLE); - - setupResolverControllers(resolvedComponentInfos); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - Espresso.registerIdlingResources(activity.getLabelIdlingResource()); - waitForIdle(); - - assertThat(activity.getAdapter().getCount(), is(2)); - - ResolveInfo[] chosen = new ResolveInfo[1]; - sOverrides.onSafelyStartInternalCallback = result -> { - chosen[0] = result.first.getResolveInfo(); - return true; - }; - - ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0); - onView(withText(toChoose.activityInfo.name)) - .perform(click()); - onView(withId(com.android.internal.R.id.button_once)) - .perform(click()); - waitForIdle(); - assertThat(chosen[0], is(toChoose)); - } - - @Ignore // Failing - b/144929805 - @Test - public void setMaxHeight() throws Exception { - Intent sendIntent = createSendImageIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2, - PERSONAL_USER_HANDLE); - - setupResolverControllers(resolvedComponentInfos); - waitForIdle(); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - final View viewPager = activity.findViewById(com.android.internal.R.id.profile_pager); - final int initialResolverHeight = viewPager.getHeight(); - - activity.runOnUiThread(() -> { - ResolverDrawerLayout layout = (ResolverDrawerLayout) - activity.findViewById( - com.android.internal.R.id.contentPanel); - ((ResolverDrawerLayout.LayoutParams) viewPager.getLayoutParams()).maxHeight - = initialResolverHeight - 1; - // Force a relayout - layout.invalidate(); - layout.requestLayout(); - }); - waitForIdle(); - assertThat("Drawer should be capped at maxHeight", - viewPager.getHeight() == (initialResolverHeight - 1)); - - activity.runOnUiThread(() -> { - ResolverDrawerLayout layout = (ResolverDrawerLayout) - activity.findViewById( - com.android.internal.R.id.contentPanel); - ((ResolverDrawerLayout.LayoutParams) viewPager.getLayoutParams()).maxHeight - = initialResolverHeight + 1; - // Force a relayout - layout.invalidate(); - layout.requestLayout(); - }); - waitForIdle(); - assertThat("Drawer should not change height if its height is less than maxHeight", - viewPager.getHeight() == initialResolverHeight); - } - - @Ignore // Failing - b/144929805 - @Test - public void setShowAtTopToTrue() throws Exception { - Intent sendIntent = createSendImageIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2, - PERSONAL_USER_HANDLE); - - setupResolverControllers(resolvedComponentInfos); - waitForIdle(); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - final View viewPager = activity.findViewById(com.android.internal.R.id.profile_pager); - final View divider = activity.findViewById(com.android.internal.R.id.divider); - final RelativeLayout profileView = - (RelativeLayout) activity.findViewById(com.android.internal.R.id.profile_button) - .getParent(); - assertThat("Drawer should show at bottom by default", - profileView.getBottom() + divider.getHeight() == viewPager.getTop() - && profileView.getTop() > 0); - - activity.runOnUiThread(() -> { - ResolverDrawerLayout layout = (ResolverDrawerLayout) - activity.findViewById( - com.android.internal.R.id.contentPanel); - layout.setShowAtTop(true); - }); - waitForIdle(); - assertThat("Drawer should show at top with new attribute", - profileView.getBottom() + divider.getHeight() == viewPager.getTop() - && profileView.getTop() == 0); - } - - @Test - public void hasLastChosenActivity() throws Exception { - Intent sendIntent = createSendImageIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2, - PERSONAL_USER_HANDLE); - ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0); - - setupResolverControllers(resolvedComponentInfos); - when(sOverrides.resolverListController.getLastChosen()) - .thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0)); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - waitForIdle(); - - // The other entry is filtered to the last used slot - assertThat(activity.getAdapter().getCount(), is(1)); - assertThat(activity.getAdapter().getPlaceholderCount(), is(1)); - - ResolveInfo[] chosen = new ResolveInfo[1]; - sOverrides.onSafelyStartInternalCallback = result -> { - chosen[0] = result.first.getResolveInfo(); - return true; - }; - - onView(withId(com.android.internal.R.id.button_once)).perform(click()); - waitForIdle(); - assertThat(chosen[0], is(toChoose)); - } - - @Test - public void hasOtherProfileOneOption() throws Exception { - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10, - PERSONAL_USER_HANDLE); - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4, - WORK_PROFILE_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - - ResolveInfo toChoose = personalResolvedComponentInfos.get(1).getResolveInfoAt(0); - Intent sendIntent = createSendImageIntent(); - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - Espresso.registerIdlingResources(activity.getLabelIdlingResource()); - waitForIdle(); - - // The other entry is filtered to the last used slot - assertThat(activity.getAdapter().getCount(), is(1)); - - ResolveInfo[] chosen = new ResolveInfo[1]; - sOverrides.onSafelyStartInternalCallback = result -> { - chosen[0] = result.first.getResolveInfo(); - return true; - }; - // Make a stable copy of the components as the original list may be modified - List<ResolvedComponentInfo> stableCopy = - createResolvedComponentsForTestWithOtherProfile(2, /* userId= */ 10, - PERSONAL_USER_HANDLE); - // We pick the first one as there is another one in the work profile side - onView(first(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))) - .perform(click()); - onView(withId(com.android.internal.R.id.button_once)) - .perform(click()); - waitForIdle(); - assertThat(chosen[0], is(toChoose)); - } - - @Test - public void hasOtherProfileTwoOptionsAndUserSelectsOne() throws Exception { - Intent sendIntent = createSendImageIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE); - ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0); - - setupResolverControllers(resolvedComponentInfos); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - Espresso.registerIdlingResources(activity.getLabelIdlingResource()); - waitForIdle(); - - // The other entry is filtered to the other profile slot - assertThat(activity.getAdapter().getCount(), is(2)); - - ResolveInfo[] chosen = new ResolveInfo[1]; - sOverrides.onSafelyStartInternalCallback = result -> { - chosen[0] = result.first.getResolveInfo(); - return true; - }; - - // Confirm that the button bar is disabled by default - onView(withId(com.android.internal.R.id.button_once)).check(matches(not(isEnabled()))); - - // Make a stable copy of the components as the original list may be modified - List<ResolvedComponentInfo> stableCopy = - createResolvedComponentsForTestWithOtherProfile(2, PERSONAL_USER_HANDLE); - - onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)) - .perform(click()); - onView(withId(com.android.internal.R.id.button_once)).perform(click()); - waitForIdle(); - assertThat(chosen[0], is(toChoose)); - } - - - @Test - public void hasLastChosenActivityAndOtherProfile() throws Exception { - // In this case we prefer the other profile and don't display anything about the last - // chosen activity. - Intent sendIntent = createSendImageIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE); - ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0); - - setupResolverControllers(resolvedComponentInfos); - when(sOverrides.resolverListController.getLastChosen()) - .thenReturn(resolvedComponentInfos.get(1).getResolveInfoAt(0)); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - Espresso.registerIdlingResources(activity.getLabelIdlingResource()); - waitForIdle(); - - // The other entry is filtered to the other profile slot - assertThat(activity.getAdapter().getCount(), is(2)); - - ResolveInfo[] chosen = new ResolveInfo[1]; - sOverrides.onSafelyStartInternalCallback = result -> { - chosen[0] = result.first.getResolveInfo(); - return true; - }; - - // Confirm that the button bar is disabled by default - onView(withId(com.android.internal.R.id.button_once)).check(matches(not(isEnabled()))); - - // Make a stable copy of the components as the original list may be modified - List<ResolvedComponentInfo> stableCopy = - createResolvedComponentsForTestWithOtherProfile(2, PERSONAL_USER_HANDLE); - - onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)) - .perform(click()); - onView(withId(com.android.internal.R.id.button_once)).perform(click()); - waitForIdle(); - assertThat(chosen[0], is(toChoose)); - } - - @Test - public void testWorkTab_displayedWhenWorkProfileUserAvailable() { - Intent sendIntent = createSendImageIntent(); - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - - mActivityRule.launchActivity(sendIntent); - waitForIdle(); - - onView(withId(com.android.internal.R.id.tabs)).check(matches(isDisplayed())); - } - - @Test - public void testWorkTab_hiddenWhenWorkProfileUserNotAvailable() { - Intent sendIntent = createSendImageIntent(); - - mActivityRule.launchActivity(sendIntent); - waitForIdle(); - - onView(withId(com.android.internal.R.id.tabs)).check(matches(not(isDisplayed()))); - } - - @Test - public void testWorkTab_workTabListPopulatedBeforeGoingToTab() throws InterruptedException { - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId = */ 10, - PERSONAL_USER_HANDLE); - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4, - WORK_PROFILE_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, - new ArrayList<>(workResolvedComponentInfos)); - Intent sendIntent = createSendImageIntent(); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - waitForIdle(); - - assertThat(activity.getCurrentUserHandle().getIdentifier(), is(0)); - // The work list adapter must be populated in advance before tapping the other tab - assertThat(activity.getWorkListAdapter().getCount(), is(4)); - } - - @Test - public void testWorkTab_workTabUsesExpectedAdapter() { - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10, - PERSONAL_USER_HANDLE); - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4, - WORK_PROFILE_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - waitForIdle(); - onView(withText(R.string.resolver_work_tab)).perform(click()); - - assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10)); - assertThat(activity.getWorkListAdapter().getCount(), is(4)); - } - - @Test - public void testWorkTab_personalTabUsesExpectedAdapter() { - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE); - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4, - WORK_PROFILE_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - waitForIdle(); - onView(withText(R.string.resolver_work_tab)).perform(click()); - - assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10)); - assertThat(activity.getPersonalListAdapter().getCount(), is(2)); - } - - @Test - public void testWorkTab_workProfileHasExpectedNumberOfTargets() throws InterruptedException { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10, - PERSONAL_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4, - WORK_PROFILE_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - waitForIdle(); - - onView(withText(R.string.resolver_work_tab)) - .perform(click()); - waitForIdle(); - assertThat(activity.getWorkListAdapter().getCount(), is(4)); - } - - @Test - public void testWorkTab_selectingWorkTabAppOpensAppInWorkProfile() throws InterruptedException { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10, - PERSONAL_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4, - WORK_PROFILE_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - ResolveInfo[] chosen = new ResolveInfo[1]; - sOverrides.onSafelyStartInternalCallback = result -> { - chosen[0] = result.first.getResolveInfo(); - return true; - }; - - mActivityRule.launchActivity(sendIntent); - waitForIdle(); - onView(withText(R.string.resolver_work_tab)) - .perform(click()); - waitForIdle(); - onView(first(allOf(withText(workResolvedComponentInfos.get(0) - .getResolveInfoAt(0).activityInfo.applicationInfo.name), isCompletelyDisplayed()))) - .perform(click()); - onView(withId(com.android.internal.R.id.button_once)) - .perform(click()); - - waitForIdle(); - assertThat(chosen[0], is(workResolvedComponentInfos.get(0).getResolveInfoAt(0))); - } - - @Test - public void testWorkTab_noPersonalApps_workTabHasExpectedNumberOfTargets() - throws InterruptedException { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(1, PERSONAL_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4, - WORK_PROFILE_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - waitForIdle(); - onView(withText(R.string.resolver_work_tab)) - .perform(click()); - - waitForIdle(); - assertThat(activity.getWorkListAdapter().getCount(), is(4)); - } - - @Test - public void testWorkTab_headerIsVisibleInPersonalTab() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(1, PERSONAL_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4, - WORK_PROFILE_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createOpenWebsiteIntent(); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - waitForIdle(); - TextView headerText = activity.findViewById(com.android.internal.R.id.title); - String initialText = headerText.getText().toString(); - assertFalse("Header text is empty.", initialText.isEmpty()); - assertThat(headerText.getVisibility(), is(View.VISIBLE)); - } - - @Test - public void testWorkTab_switchTabs_headerStaysSame() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(1, PERSONAL_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4, - WORK_PROFILE_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createOpenWebsiteIntent(); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - waitForIdle(); - TextView headerText = activity.findViewById(com.android.internal.R.id.title); - String initialText = headerText.getText().toString(); - onView(withText(R.string.resolver_work_tab)) - .perform(click()); - - waitForIdle(); - String currentText = headerText.getText().toString(); - assertThat(headerText.getVisibility(), is(View.VISIBLE)); - assertThat(String.format("Header text is not the same when switching tabs, personal profile" - + " header was %s but work profile header is %s", initialText, currentText), - TextUtils.equals(initialText, currentText)); - } - - @Test - public void testWorkTab_noPersonalApps_canStartWorkApps() - throws InterruptedException { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId= */ 10, - PERSONAL_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4, - WORK_PROFILE_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - ResolveInfo[] chosen = new ResolveInfo[1]; - sOverrides.onSafelyStartInternalCallback = result -> { - chosen[0] = result.first.getResolveInfo(); - return true; - }; - - mActivityRule.launchActivity(sendIntent); - waitForIdle(); - onView(withText(R.string.resolver_work_tab)) - .perform(click()); - waitForIdle(); - onView(first(allOf( - withText(workResolvedComponentInfos.get(0) - .getResolveInfoAt(0).activityInfo.applicationInfo.name), - isDisplayed()))) - .perform(click()); - onView(withId(com.android.internal.R.id.button_once)) - .perform(click()); - waitForIdle(); - - assertThat(chosen[0], is(workResolvedComponentInfos.get(0).getResolveInfoAt(0))); - } - - @Test - public void testWorkTab_crossProfileIntentsDisabled_personalToWork_emptyStateShown() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - int workProfileTargets = 4; - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10, - PERSONAL_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(workProfileTargets, WORK_PROFILE_USER_HANDLE); - sOverrides.hasCrossProfileIntents = false; - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - sendIntent.setType("TestType"); - - mActivityRule.launchActivity(sendIntent); - waitForIdle(); - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - onView(withId(com.android.internal.R.id.contentPanel)) - .perform(swipeUp()); - - onView(withText(R.string.resolver_cross_profile_blocked)) - .check(matches(isDisplayed())); - } - - @Test - public void testWorkTab_workProfileDisabled_emptyStateShown() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - int workProfileTargets = 4; - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10, - PERSONAL_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(workProfileTargets, WORK_PROFILE_USER_HANDLE); - sOverrides.isQuietModeEnabled = true; - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - sendIntent.setType("TestType"); - - mActivityRule.launchActivity(sendIntent); - waitForIdle(); - onView(withId(com.android.internal.R.id.contentPanel)) - .perform(swipeUp()); - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - - onView(withText(R.string.resolver_turn_on_work_apps)) - .check(matches(isDisplayed())); - } - - @Test - public void testWorkTab_noWorkAppsAvailable_emptyStateShown() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(3, PERSONAL_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(0, WORK_PROFILE_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - sendIntent.setType("TestType"); - - mActivityRule.launchActivity(sendIntent); - waitForIdle(); - onView(withId(com.android.internal.R.id.contentPanel)) - .perform(swipeUp()); - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - - onView(withText(R.string.resolver_no_work_apps_available)) - .check(matches(isDisplayed())); - } - - @Test - public void testWorkTab_xProfileOff_noAppsAvailable_workOff_xProfileOffEmptyStateShown() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(3, PERSONAL_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(0, WORK_PROFILE_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - sendIntent.setType("TestType"); - sOverrides.isQuietModeEnabled = true; - sOverrides.hasCrossProfileIntents = false; - - mActivityRule.launchActivity(sendIntent); - waitForIdle(); - onView(withId(com.android.internal.R.id.contentPanel)) - .perform(swipeUp()); - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - - onView(withText(R.string.resolver_cross_profile_blocked)) - .check(matches(isDisplayed())); - } - - @Test - public void testMiniResolver() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(1, PERSONAL_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(1, WORK_PROFILE_USER_HANDLE); - // Personal profile only has a browser - personalResolvedComponentInfos.get(0).getResolveInfoAt(0).handleAllWebDataURI = true; - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - sendIntent.setType("TestType"); - - mActivityRule.launchActivity(sendIntent); - waitForIdle(); - onView(withId(com.android.internal.R.id.open_cross_profile)).check(matches(isDisplayed())); - } - - @Test - public void testMiniResolver_noCurrentProfileTarget() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(0, PERSONAL_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(1, WORK_PROFILE_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - sendIntent.setType("TestType"); - - mActivityRule.launchActivity(sendIntent); - waitForIdle(); - - // Need to ensure mini resolver doesn't trigger here. - assertNotMiniResolver(); - } - - private void assertNotMiniResolver() { - try { - onView(withId(com.android.internal.R.id.open_cross_profile)) - .check(matches(isDisplayed())); - } catch (NoMatchingViewException e) { - return; - } - fail("Mini resolver present but shouldn't be"); - } - - @Test - public void testWorkTab_noAppsAvailable_workOff_noAppsAvailableEmptyStateShown() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(3, PERSONAL_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(0, WORK_PROFILE_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - sendIntent.setType("TestType"); - sOverrides.isQuietModeEnabled = true; - - mActivityRule.launchActivity(sendIntent); - waitForIdle(); - onView(withId(com.android.internal.R.id.contentPanel)) - .perform(swipeUp()); - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - - onView(withText(R.string.resolver_no_work_apps_available)) - .check(matches(isDisplayed())); - } - - @Test - public void testWorkTab_onePersonalTarget_emptyStateOnWorkTarget_doesNotAutoLaunch() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - int workProfileTargets = 4; - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10, - PERSONAL_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(workProfileTargets, WORK_PROFILE_USER_HANDLE); - sOverrides.hasCrossProfileIntents = false; - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - sendIntent.setType("TestType"); - ResolveInfo[] chosen = new ResolveInfo[1]; - sOverrides.onSafelyStartInternalCallback = result -> { - chosen[0] = result.first.getResolveInfo(); - return true; - }; - - mActivityRule.launchActivity(sendIntent); - waitForIdle(); - - assertNull(chosen[0]); - } - - @Test - public void testLayoutWithDefault_withWorkTab_neverShown() throws RemoteException { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - - // In this case we prefer the other profile and don't display anything about the last - // chosen activity. - Intent sendIntent = createSendImageIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = - createResolvedComponentsForTest(2, PERSONAL_USER_HANDLE); - - setupResolverControllers(resolvedComponentInfos); - when(sOverrides.resolverListController.getLastChosen()) - .thenReturn(resolvedComponentInfos.get(1).getResolveInfoAt(0)); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - Espresso.registerIdlingResources(activity.getLabelIdlingResource()); - waitForIdle(); - - // The other entry is filtered to the last used slot - assertThat(activity.getAdapter().hasFilteredItem(), is(false)); - assertThat(activity.getAdapter().getCount(), is(2)); - assertThat(activity.getAdapter().getPlaceholderCount(), is(2)); - } - - @Test - public void testClonedProfilePresent_personalAdapterIsSetWithPersonalProfile() { - // enable cloneProfile - markOtherProfileAvailability(/* workAvailable= */ false, /* cloneAvailable= */ true); - List<ResolvedComponentInfo> resolvedComponentInfos = - createResolvedComponentsWithCloneProfileForTest( - 3, - PERSONAL_USER_HANDLE, - CLONE_PROFILE_USER_HANDLE); - setupResolverControllers(resolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - waitForIdle(); - - assertThat(activity.getCurrentUserHandle(), is(PERSONAL_USER_HANDLE)); - assertThat(activity.getAdapter().getCount(), is(3)); - } - - @Test - public void testClonedProfilePresent_personalTabUsesExpectedAdapter() { - // enable cloneProfile - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ true); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsWithCloneProfileForTest( - 3, - PERSONAL_USER_HANDLE, - CLONE_PROFILE_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4, - WORK_PROFILE_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - waitForIdle(); - - assertThat(activity.getCurrentUserHandle(), is(PERSONAL_USER_HANDLE)); - assertThat(activity.getAdapter().getCount(), is(3)); - } - - @Test - public void testClonedProfilePresent_layoutWithDefault_neverShown() throws Exception { - // enable cloneProfile - markOtherProfileAvailability(/* workAvailable= */ false, /* cloneAvailable= */ true); - Intent sendIntent = createSendImageIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = - createResolvedComponentsWithCloneProfileForTest( - 2, - PERSONAL_USER_HANDLE, - CLONE_PROFILE_USER_HANDLE); - - setupResolverControllers(resolvedComponentInfos); - when(sOverrides.resolverListController.getLastChosen()) - .thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0)); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - Espresso.registerIdlingResources(activity.getLabelIdlingResource()); - waitForIdle(); - - assertThat(activity.getAdapter().hasFilteredItem(), is(false)); - assertThat(activity.getAdapter().getCount(), is(2)); - assertThat(activity.getAdapter().getPlaceholderCount(), is(2)); - } - - @Test - public void testClonedProfilePresent_alwaysButtonDisabled() throws Exception { - // enable cloneProfile - markOtherProfileAvailability(/* workAvailable= */ false, /* cloneAvailable= */ true); - Intent sendIntent = createSendImageIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = - createResolvedComponentsWithCloneProfileForTest( - 3, - PERSONAL_USER_HANDLE, - CLONE_PROFILE_USER_HANDLE); - - setupResolverControllers(resolvedComponentInfos); - when(sOverrides.resolverListController.getLastChosen()) - .thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0)); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - waitForIdle(); - - // Confirm that the button bar is disabled by default - onView(withId(com.android.internal.R.id.button_once)).check(matches(not(isEnabled()))); - onView(withId(com.android.internal.R.id.button_always)).check(matches(not(isEnabled()))); - - // Make a stable copy of the components as the original list may be modified - List<ResolvedComponentInfo> stableCopy = - createResolvedComponentsForTestWithOtherProfile(2, PERSONAL_USER_HANDLE); - - onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)) - .perform(click()); - - onView(withId(com.android.internal.R.id.button_once)).check(matches(isEnabled())); - onView(withId(com.android.internal.R.id.button_always)).check(matches(not(isEnabled()))); - } - - @Test - public void testClonedProfilePresent_personalProfileActivityIsStartedInCorrectUser() - throws Exception { - // enable cloneProfile - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ true); - - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsWithCloneProfileForTest( - 3, - PERSONAL_USER_HANDLE, - CLONE_PROFILE_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(3, WORK_PROFILE_USER_HANDLE); - sOverrides.hasCrossProfileIntents = false; - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - sendIntent.setType("TestType"); - final UserHandle[] selectedActivityUserHandle = new UserHandle[1]; - sOverrides.onSafelyStartInternalCallback = result -> { - selectedActivityUserHandle[0] = result.second; - return true; - }; - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - waitForIdle(); - onView(first(allOf(withText(personalResolvedComponentInfos.get(0) - .getResolveInfoAt(0).activityInfo.applicationInfo.name), isCompletelyDisplayed()))) - .perform(click()); - onView(withId(com.android.internal.R.id.button_once)) - .perform(click()); - waitForIdle(); - - assertThat(selectedActivityUserHandle[0], is(activity.getAdapter().getUserHandle())); - } - - @Test - public void testClonedProfilePresent_workProfileActivityIsStartedInCorrectUser() - throws Exception { - // enable cloneProfile - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ true); - - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsWithCloneProfileForTest( - 3, - PERSONAL_USER_HANDLE, - CLONE_PROFILE_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(3, WORK_PROFILE_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - sendIntent.setType("TestType"); - final UserHandle[] selectedActivityUserHandle = new UserHandle[1]; - sOverrides.onSafelyStartInternalCallback = result -> { - selectedActivityUserHandle[0] = result.second; - return true; - }; - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - waitForIdle(); - onView(withText(R.string.resolver_work_tab)) - .perform(click()); - waitForIdle(); - onView(first(allOf(withText(workResolvedComponentInfos.get(0) - .getResolveInfoAt(0).activityInfo.applicationInfo.name), isCompletelyDisplayed()))) - .perform(click()); - onView(withId(com.android.internal.R.id.button_once)) - .perform(click()); - waitForIdle(); - - assertThat(selectedActivityUserHandle[0], is(activity.getAdapter().getUserHandle())); - } - - @Test - public void testClonedProfilePresent_personalProfileResolverComparatorHasCorrectUsers() - throws Exception { - // enable cloneProfile - markOtherProfileAvailability(/* workAvailable= */ false, /* cloneAvailable= */ true); - List<ResolvedComponentInfo> resolvedComponentInfos = - createResolvedComponentsWithCloneProfileForTest( - 3, - PERSONAL_USER_HANDLE, - CLONE_PROFILE_USER_HANDLE); - setupResolverControllers(resolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - waitForIdle(); - List<UserHandle> result = activity - .getResolverRankerServiceUserHandleList(PERSONAL_USER_HANDLE); - - assertThat(result.containsAll( - Lists.newArrayList(PERSONAL_USER_HANDLE, CLONE_PROFILE_USER_HANDLE)), is(true)); - } - - private Intent createSendImageIntent() { - Intent sendIntent = new Intent(); - sendIntent.setAction(Intent.ACTION_SEND); - sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending"); - sendIntent.setType("image/jpeg"); - return sendIntent; - } - - private Intent createOpenWebsiteIntent() { - Intent sendIntent = new Intent(); - sendIntent.setAction(Intent.ACTION_VIEW); - sendIntent.setData(Uri.parse("https://google.com")); - return sendIntent; - } - - private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults, - UserHandle resolvedForUser) { - List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); - for (int i = 0; i < numberOfResults; i++) { - infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, resolvedForUser)); - } - return infoList; - } - - private List<ResolvedComponentInfo> createResolvedComponentsWithCloneProfileForTest( - int numberOfResults, - UserHandle resolvedForPersonalUser, - UserHandle resolvedForClonedUser) { - List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); - for (int i = 0; i < 1; i++) { - infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, - resolvedForPersonalUser)); - } - for (int i = 1; i < numberOfResults; i++) { - infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, - resolvedForClonedUser)); - } - return infoList; - } - - private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile( - int numberOfResults, - UserHandle resolvedForUser) { - List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); - for (int i = 0; i < numberOfResults; i++) { - if (i == 0) { - infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, - resolvedForUser)); - } else { - infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, resolvedForUser)); - } - } - return infoList; - } - - private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile( - int numberOfResults, int userId, UserHandle resolvedForUser) { - List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); - for (int i = 0; i < numberOfResults; i++) { - if (i == 0) { - infoList.add( - ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId, - resolvedForUser)); - } else { - infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, resolvedForUser)); - } - } - return infoList; - } - - private void waitForIdle() { - InstrumentationRegistry.getInstrumentation().waitForIdleSync(); - } - - private void markOtherProfileAvailability(boolean workAvailable, boolean cloneAvailable) { - AnnotatedUserHandles.Builder handles = AnnotatedUserHandles.newBuilder(); - handles - .setUserIdOfCallingApp(1234) // Must be non-negative. - .setUserHandleSharesheetLaunchedAs(PERSONAL_USER_HANDLE) - .setPersonalProfileUserHandle(PERSONAL_USER_HANDLE); - if (workAvailable) { - handles.setWorkProfileUserHandle(WORK_PROFILE_USER_HANDLE); - } - if (cloneAvailable) { - handles.setCloneProfileUserHandle(CLONE_PROFILE_USER_HANDLE); - } - sOverrides.annotatedUserHandles = handles.build(); - } - - private void setupResolverControllers( - List<ResolvedComponentInfo> personalResolvedComponentInfos) { - setupResolverControllers(personalResolvedComponentInfos, new ArrayList<>()); - } - - private void setupResolverControllers( - List<ResolvedComponentInfo> personalResolvedComponentInfos, - List<ResolvedComponentInfo> workResolvedComponentInfos) { - when(sOverrides.resolverListController.getResolversForIntentAsUser( - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - eq(UserHandle.SYSTEM))) - .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); - when(sOverrides.workResolverListController.getResolversForIntentAsUser( - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - eq(UserHandle.SYSTEM))) - .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); - when(sOverrides.workResolverListController.getResolversForIntentAsUser( - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - eq(UserHandle.of(10)))) - .thenReturn(new ArrayList<>(workResolvedComponentInfos)); - } -} diff --git a/java/tests/src/com/android/intentresolver/ResolverDataProvider.java b/java/tests/src/com/android/intentresolver/ResolverDataProvider.java deleted file mode 100644 index db109941..00000000 --- a/java/tests/src/com/android/intentresolver/ResolverDataProvider.java +++ /dev/null @@ -1,257 +0,0 @@ -/* - * Copyright (C) 2008 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.ActivityInfo; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.res.Resources; -import android.os.UserHandle; -import android.test.mock.MockContext; -import android.test.mock.MockPackageManager; -import android.test.mock.MockResources; - -import androidx.annotation.NonNull; - -/** - * Utility class used by resolver tests to create mock data - */ -public class ResolverDataProvider { - - static private int USER_SOMEONE_ELSE = 10; - - static ResolvedComponentInfo createResolvedComponentInfo(int i) { - return new ResolvedComponentInfo( - createComponentName(i), - createResolverIntent(i), - createResolveInfo(i, UserHandle.USER_CURRENT)); - } - - public static ResolvedComponentInfo createResolvedComponentInfo(int i, - UserHandle resolvedForUser) { - return new ResolvedComponentInfo( - createComponentName(i), - createResolverIntent(i), - createResolveInfo(i, UserHandle.USER_CURRENT, resolvedForUser)); - } - - static ResolvedComponentInfo createResolvedComponentInfo( - ComponentName componentName, Intent intent) { - return new ResolvedComponentInfo( - componentName, - intent, - createResolveInfo(componentName, UserHandle.USER_CURRENT)); - } - - public static ResolvedComponentInfo createResolvedComponentInfo( - ComponentName componentName, Intent intent, UserHandle resolvedForUser) { - return new ResolvedComponentInfo( - componentName, - intent, - createResolveInfo(componentName, UserHandle.USER_CURRENT, resolvedForUser)); - } - - static ResolvedComponentInfo createResolvedComponentInfoWithOtherId(int i) { - return new ResolvedComponentInfo( - createComponentName(i), - createResolverIntent(i), - createResolveInfo(i, USER_SOMEONE_ELSE)); - } - - public static ResolvedComponentInfo createResolvedComponentInfoWithOtherId(int i, - UserHandle resolvedForUser) { - return new ResolvedComponentInfo( - createComponentName(i), - createResolverIntent(i), - createResolveInfo(i, USER_SOMEONE_ELSE, resolvedForUser)); - } - - static ResolvedComponentInfo createResolvedComponentInfoWithOtherId(int i, int userId) { - return new ResolvedComponentInfo( - createComponentName(i), - createResolverIntent(i), - createResolveInfo(i, userId)); - } - - public static ResolvedComponentInfo createResolvedComponentInfoWithOtherId(int i, - int userId, UserHandle resolvedForUser) { - return new ResolvedComponentInfo( - createComponentName(i), - createResolverIntent(i), - createResolveInfo(i, userId, resolvedForUser)); - } - - public static ComponentName createComponentName(int i) { - final String name = "component" + i; - return new ComponentName("foo.bar." + name, name); - } - - public static ResolveInfo createResolveInfo(int i, int userId) { - return createResolveInfo(i, userId, UserHandle.of(userId)); - } - - public static ResolveInfo createResolveInfo(int i, int userId, UserHandle resolvedForUser) { - return createResolveInfo(createActivityInfo(i), userId, resolvedForUser); - } - - public static ResolveInfo createResolveInfo(ComponentName componentName, int userId) { - return createResolveInfo(componentName, userId, UserHandle.of(userId)); - } - - public static ResolveInfo createResolveInfo( - ComponentName componentName, int userId, UserHandle resolvedForUser) { - return createResolveInfo(createActivityInfo(componentName), userId, resolvedForUser); - } - - public static ResolveInfo createResolveInfo( - ActivityInfo activityInfo, int userId, UserHandle resolvedForUser) { - final ResolveInfo resolveInfo = new ResolveInfo(); - resolveInfo.activityInfo = activityInfo; - resolveInfo.targetUserId = userId; - resolveInfo.userHandle = resolvedForUser; - return resolveInfo; - } - - static ActivityInfo createActivityInfo(int i) { - ActivityInfo ai = new ActivityInfo(); - ai.name = "activity_name" + i; - ai.packageName = "foo_bar" + i; - ai.enabled = true; - ai.exported = true; - ai.permission = null; - ai.applicationInfo = createApplicationInfo(); - return ai; - } - - static ActivityInfo createActivityInfo(ComponentName componentName) { - ActivityInfo ai = new ActivityInfo(); - ai.name = componentName.getClassName(); - ai.packageName = componentName.getPackageName(); - ai.enabled = true; - ai.exported = true; - ai.permission = null; - ai.applicationInfo = createApplicationInfo(); - ai.applicationInfo.packageName = componentName.getPackageName(); - return ai; - } - - static ApplicationInfo createApplicationInfo() { - ApplicationInfo ai = new ApplicationInfo(); - ai.name = "app_name"; - ai.packageName = "foo.bar"; - ai.enabled = true; - return ai; - } - - static class PackageManagerMockedInfo { - public Context ctx; - public ApplicationInfo appInfo; - public ActivityInfo activityInfo; - public ResolveInfo resolveInfo; - public String setAppLabel; - public String setActivityLabel; - public String setResolveInfoLabel; - } - - /** Create a {@link PackageManagerMockedInfo} with all distinct labels. */ - static PackageManagerMockedInfo createPackageManagerMockedInfo(boolean hasOverridePermission) { - return createPackageManagerMockedInfo( - hasOverridePermission, "app_label", "activity_label", "resolve_info_label"); - } - - static PackageManagerMockedInfo createPackageManagerMockedInfo( - boolean hasOverridePermission, - String appLabel, - String activityLabel, - String resolveInfoLabel) { - MockContext ctx = new MockContext() { - @Override - public PackageManager getPackageManager() { - return new MockPackageManager() { - @Override - public int checkPermission(String permName, String pkgName) { - if (hasOverridePermission) return PERMISSION_GRANTED; - return PERMISSION_DENIED; - } - }; - } - - @Override - public Resources getResources() { - return new MockResources() { - @NonNull - @Override - public String getString(int id) throws NotFoundException { - if (id == 1) return appLabel; - if (id == 2) return activityLabel; - if (id == 3) return resolveInfoLabel; - throw new NotFoundException(); - } - }; - } - }; - - ApplicationInfo appInfo = new ApplicationInfo() { - @NonNull - @Override - public CharSequence loadLabel(@NonNull PackageManager pm) { - return appLabel; - } - }; - appInfo.labelRes = 1; - - ActivityInfo activityInfo = new ActivityInfo() { - @NonNull - @Override - public CharSequence loadLabel(@NonNull PackageManager pm) { - return activityLabel; - } - }; - activityInfo.labelRes = 2; - activityInfo.applicationInfo = appInfo; - - ResolveInfo resolveInfo = new ResolveInfo() { - @NonNull - @Override - public CharSequence loadLabel(@NonNull PackageManager pm) { - return resolveInfoLabel; - } - }; - resolveInfo.activityInfo = activityInfo; - resolveInfo.resolvePackageName = "super.fake.packagename"; - resolveInfo.labelRes = 3; - - PackageManagerMockedInfo mockedInfo = new PackageManagerMockedInfo(); - mockedInfo.activityInfo = activityInfo; - mockedInfo.appInfo = appInfo; - mockedInfo.ctx = ctx; - mockedInfo.resolveInfo = resolveInfo; - mockedInfo.setAppLabel = appLabel; - mockedInfo.setActivityLabel = activityLabel; - mockedInfo.setResolveInfoLabel = resolveInfoLabel; - - return mockedInfo; - } - - static Intent createResolverIntent(int i) { - return new Intent("intentAction" + i); - } -} diff --git a/java/tests/src/com/android/intentresolver/ResolverListAdapterTest.kt b/java/tests/src/com/android/intentresolver/ResolverListAdapterTest.kt deleted file mode 100644 index 61b9fd9c..00000000 --- a/java/tests/src/com/android/intentresolver/ResolverListAdapterTest.kt +++ /dev/null @@ -1,1048 +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 - -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 - } -} diff --git a/java/tests/src/com/android/intentresolver/ResolverWrapperActivity.java b/java/tests/src/com/android/intentresolver/ResolverWrapperActivity.java deleted file mode 100644 index d1adfba9..00000000 --- a/java/tests/src/com/android/intentresolver/ResolverWrapperActivity.java +++ /dev/null @@ -1,285 +0,0 @@ -/* - * Copyright (C) 2017 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 static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.os.UserHandle; -import android.util.Pair; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.test.espresso.idling.CountingIdlingResource; - -import com.android.intentresolver.chooser.DisplayResolveInfo; -import com.android.intentresolver.chooser.SelectableTargetInfo; -import com.android.intentresolver.chooser.TargetInfo; -import com.android.intentresolver.emptystate.CrossProfileIntentsChecker; -import com.android.intentresolver.icons.LabelInfo; -import com.android.intentresolver.icons.TargetDataLoader; - -import java.util.List; -import java.util.function.Consumer; -import java.util.function.Function; - -/* - * Simple wrapper around chooser activity to be able to initiate it under test - */ -public class ResolverWrapperActivity extends ResolverActivity { - static final OverrideData sOverrides = new OverrideData(); - - private final CountingIdlingResource mLabelIdlingResource = - new CountingIdlingResource("LoadLabelTask"); - - public ResolverWrapperActivity() { - super(/* isIntentPicker= */ true); - } - - public CountingIdlingResource getLabelIdlingResource() { - return mLabelIdlingResource; - } - - @Override - public ResolverListAdapter createResolverListAdapter( - Context context, - List<Intent> payloadIntents, - Intent[] initialIntents, - List<ResolveInfo> rList, - boolean filterLastUsed, - UserHandle userHandle, - TargetDataLoader targetDataLoader) { - return new ResolverListAdapter( - context, - payloadIntents, - initialIntents, - rList, - filterLastUsed, - createListController(userHandle), - userHandle, - payloadIntents.get(0), // TODO: extract upstream - this, - userHandle, - new TargetDataLoaderWrapper(targetDataLoader, mLabelIdlingResource)); - } - - @Override - protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() { - if (sOverrides.mCrossProfileIntentsChecker != null) { - return sOverrides.mCrossProfileIntentsChecker; - } - return super.createCrossProfileIntentsChecker(); - } - - @Override - protected WorkProfileAvailabilityManager createWorkProfileAvailabilityManager() { - if (sOverrides.mWorkProfileAvailability != null) { - return sOverrides.mWorkProfileAvailability; - } - return super.createWorkProfileAvailabilityManager(); - } - - ResolverListAdapter getAdapter() { - return mMultiProfilePagerAdapter.getActiveListAdapter(); - } - - ResolverListAdapter getPersonalListAdapter() { - return ((ResolverListAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(0)); - } - - ResolverListAdapter getWorkListAdapter() { - if (mMultiProfilePagerAdapter.getInactiveListAdapter() == null) { - return null; - } - return ((ResolverListAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(1)); - } - - @Override - public boolean isVoiceInteraction() { - if (sOverrides.isVoiceInteraction != null) { - return sOverrides.isVoiceInteraction; - } - return super.isVoiceInteraction(); - } - - @Override - public void safelyStartActivityInternal(TargetInfo cti, UserHandle user, - @Nullable Bundle options) { - if (sOverrides.onSafelyStartInternalCallback != null - && sOverrides.onSafelyStartInternalCallback.apply(new Pair<>(cti, user))) { - return; - } - super.safelyStartActivityInternal(cti, user, options); - } - - @Override - protected ResolverListController createListController(UserHandle userHandle) { - if (userHandle == UserHandle.SYSTEM) { - return sOverrides.resolverListController; - } - return sOverrides.workResolverListController; - } - - @Override - public PackageManager getPackageManager() { - if (sOverrides.createPackageManager != null) { - return sOverrides.createPackageManager.apply(super.getPackageManager()); - } - return super.getPackageManager(); - } - - protected UserHandle getCurrentUserHandle() { - return mMultiProfilePagerAdapter.getCurrentUserHandle(); - } - - @Override - protected AnnotatedUserHandles computeAnnotatedUserHandles() { - return sOverrides.annotatedUserHandles; - } - @Override - public void startActivityAsUser( - @NonNull Intent intent, - Bundle options, - @NonNull UserHandle user - ) { - super.startActivityAsUser(intent, options, user); - } - - @Override - protected List<UserHandle> getResolverRankerServiceUserHandleListInternal(UserHandle - userHandle) { - return super.getResolverRankerServiceUserHandleListInternal(userHandle); - } - - /** - * We cannot directly mock the activity created since instrumentation creates it. - * <p> - * Instead, we use static instances of this object to modify behavior. - */ - static class OverrideData { - @SuppressWarnings("Since15") - public Function<PackageManager, PackageManager> createPackageManager; - public Function<Pair<TargetInfo, UserHandle>, Boolean> onSafelyStartInternalCallback; - public ResolverListController resolverListController; - public ResolverListController workResolverListController; - public Boolean isVoiceInteraction; - public AnnotatedUserHandles annotatedUserHandles; - public Integer myUserId; - public boolean hasCrossProfileIntents; - public boolean isQuietModeEnabled; - public WorkProfileAvailabilityManager mWorkProfileAvailability; - public CrossProfileIntentsChecker mCrossProfileIntentsChecker; - - public void reset() { - onSafelyStartInternalCallback = null; - isVoiceInteraction = null; - createPackageManager = null; - resolverListController = mock(ResolverListController.class); - workResolverListController = mock(ResolverListController.class); - annotatedUserHandles = AnnotatedUserHandles.newBuilder() - .setUserIdOfCallingApp(1234) // Must be non-negative. - .setUserHandleSharesheetLaunchedAs(UserHandle.SYSTEM) - .setPersonalProfileUserHandle(UserHandle.SYSTEM) - .build(); - myUserId = null; - hasCrossProfileIntents = true; - isQuietModeEnabled = false; - - mWorkProfileAvailability = new WorkProfileAvailabilityManager(null, null, null) { - @Override - public boolean isQuietModeEnabled() { - return isQuietModeEnabled; - } - - @Override - public boolean isWorkProfileUserUnlocked() { - return true; - } - - @Override - public void requestQuietModeEnabled(boolean enabled) { - isQuietModeEnabled = enabled; - } - - @Override - public void markWorkProfileEnabledBroadcastReceived() {} - - @Override - public boolean isWaitingToEnableWorkProfile() { - return false; - } - }; - - mCrossProfileIntentsChecker = mock(CrossProfileIntentsChecker.class); - when(mCrossProfileIntentsChecker.hasCrossProfileIntents(any(), anyInt(), anyInt())) - .thenAnswer(invocation -> hasCrossProfileIntents); - } - } - - private static class TargetDataLoaderWrapper extends TargetDataLoader { - private final TargetDataLoader mTargetDataLoader; - private final CountingIdlingResource mLabelIdlingResource; - - private TargetDataLoaderWrapper( - TargetDataLoader targetDataLoader, CountingIdlingResource labelIdlingResource) { - mTargetDataLoader = targetDataLoader; - mLabelIdlingResource = labelIdlingResource; - } - - @Override - public void loadAppTargetIcon( - @NonNull DisplayResolveInfo info, - @NonNull UserHandle userHandle, - @NonNull Consumer<Drawable> callback) { - mTargetDataLoader.loadAppTargetIcon(info, userHandle, callback); - } - - @Override - public void loadDirectShareIcon( - @NonNull SelectableTargetInfo info, - @NonNull UserHandle userHandle, - @NonNull Consumer<Drawable> callback) { - mTargetDataLoader.loadDirectShareIcon(info, userHandle, callback); - } - - @Override - public void loadLabel( - @NonNull DisplayResolveInfo info, - @NonNull Consumer<LabelInfo> callback) { - mLabelIdlingResource.increment(); - mTargetDataLoader.loadLabel( - info, - (result) -> { - mLabelIdlingResource.decrement(); - callback.accept(result); - }); - } - - @Override - public void getOrLoadLabel(@NonNull DisplayResolveInfo info) { - mTargetDataLoader.getOrLoadLabel(info); - } - } -} diff --git a/java/tests/src/com/android/intentresolver/ShortcutSelectionLogicTest.kt b/java/tests/src/com/android/intentresolver/ShortcutSelectionLogicTest.kt deleted file mode 100644 index 2346d98b..00000000 --- a/java/tests/src/com/android/intentresolver/ShortcutSelectionLogicTest.kt +++ /dev/null @@ -1,312 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.intentresolver - -import android.content.ComponentName -import android.content.Context -import android.content.Intent -import android.content.pm.ShortcutInfo -import android.os.UserHandle -import android.service.chooser.ChooserTarget -import com.android.intentresolver.chooser.DisplayResolveInfo -import com.android.intentresolver.chooser.TargetInfo -import androidx.test.filters.SmallTest -import androidx.test.platform.app.InstrumentationRegistry -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue -import org.junit.Test - -private const val PACKAGE_A = "package.a" -private const val PACKAGE_B = "package.b" -private const val CLASS_NAME = "./MainActivity" - -@SmallTest -class ShortcutSelectionLogicTest { - private val PERSONAL_USER_HANDLE: UserHandle = InstrumentationRegistry - .getInstrumentation().getTargetContext().getUser() - - private val packageTargets = HashMap<String, Array<ChooserTarget>>().apply { - arrayOf(PACKAGE_A, PACKAGE_B).forEach { pkg -> - // shortcuts in reverse priority order - val targets = Array(3) { i -> - createChooserTarget( - "Shortcut $i", - (i + 1).toFloat() / 10f, - ComponentName(pkg, CLASS_NAME), - pkg.shortcutId(i), - ) - } - this[pkg] = targets - } - } - - private val baseDisplayInfo = DisplayResolveInfo.newDisplayResolveInfo( - Intent(), - ResolverDataProvider.createResolveInfo(3, 0, PERSONAL_USER_HANDLE), - "label", - "extended info", - Intent() - ) - - private val otherBaseDisplayInfo = DisplayResolveInfo.newDisplayResolveInfo( - Intent(), - ResolverDataProvider.createResolveInfo(4, 0, PERSONAL_USER_HANDLE), - "label 2", - "extended info 2", - Intent() - ) - - private operator fun Map<String, Array<ChooserTarget>>.get(pkg: String, idx: Int) = - this[pkg]?.get(idx) ?: error("missing package $pkg") - - @Test - fun testAddShortcuts_no_limits() { - val serviceResults = ArrayList<TargetInfo>() - val sc1 = packageTargets[PACKAGE_A, 0] - val sc2 = packageTargets[PACKAGE_A, 1] - val testSubject = ShortcutSelectionLogic( - /* maxShortcutTargetsPerApp = */ 1, - /* applySharingAppLimits = */ false - ) - - val isUpdated = testSubject.addServiceResults( - /* origTarget = */ baseDisplayInfo, - /* origTargetScore = */ 0.1f, - /* targets = */ listOf(sc1, sc2), - /* isShortcutResult = */ true, - /* directShareToShortcutInfos = */ emptyMap(), - /* directShareToAppTargets = */ emptyMap(), - /* userContext = */ mock(), - /* targetIntent = */ mock(), - /* refererFillInIntent = */ mock(), - /* maxRankedTargets = */ 4, - /* serviceTargets = */ serviceResults - ) - - assertTrue("Updates are expected", isUpdated) - assertShortcutsInOrder( - listOf(sc2, sc1), - serviceResults, - "Two shortcuts are expected as we do not apply per-app shortcut limit" - ) - } - - @Test - fun testAddShortcuts_same_package_with_per_package_limit() { - val serviceResults = ArrayList<TargetInfo>() - val sc1 = packageTargets[PACKAGE_A, 0] - val sc2 = packageTargets[PACKAGE_A, 1] - val testSubject = ShortcutSelectionLogic( - /* maxShortcutTargetsPerApp = */ 1, - /* applySharingAppLimits = */ true - ) - - val isUpdated = testSubject.addServiceResults( - /* origTarget = */ baseDisplayInfo, - /* origTargetScore = */ 0.1f, - /* targets = */ listOf(sc1, sc2), - /* isShortcutResult = */ true, - /* directShareToShortcutInfos = */ emptyMap(), - /* directShareToAppTargets = */ emptyMap(), - /* userContext = */ mock(), - /* targetIntent = */ mock(), - /* refererFillInIntent = */ mock(), - /* maxRankedTargets = */ 4, - /* serviceTargets = */ serviceResults - ) - - assertTrue("Updates are expected", isUpdated) - assertShortcutsInOrder( - listOf(sc2), - serviceResults, - "One shortcut is expected as we apply per-app shortcut limit" - ) - } - - @Test - fun testAddShortcuts_same_package_no_per_app_limit_with_target_limit() { - val serviceResults = ArrayList<TargetInfo>() - val sc1 = packageTargets[PACKAGE_A, 0] - val sc2 = packageTargets[PACKAGE_A, 1] - val testSubject = ShortcutSelectionLogic( - /* maxShortcutTargetsPerApp = */ 1, - /* applySharingAppLimits = */ false - ) - - val isUpdated = testSubject.addServiceResults( - /* origTarget = */ baseDisplayInfo, - /* origTargetScore = */ 0.1f, - /* targets = */ listOf(sc1, sc2), - /* isShortcutResult = */ true, - /* directShareToShortcutInfos = */ emptyMap(), - /* directShareToAppTargets = */ emptyMap(), - /* userContext = */ mock(), - /* targetIntent = */ mock(), - /* refererFillInIntent = */ mock(), - /* maxRankedTargets = */ 1, - /* serviceTargets = */ serviceResults - ) - - assertTrue("Updates are expected", isUpdated) - assertShortcutsInOrder( - listOf(sc2), - serviceResults, - "One shortcut is expected as we apply overall shortcut limit" - ) - } - - @Test - fun testAddShortcuts_different_packages_with_per_package_limit() { - val serviceResults = ArrayList<TargetInfo>() - val pkgAsc1 = packageTargets[PACKAGE_A, 0] - val pkgAsc2 = packageTargets[PACKAGE_A, 1] - val pkgBsc1 = packageTargets[PACKAGE_B, 0] - val pkgBsc2 = packageTargets[PACKAGE_B, 1] - val testSubject = ShortcutSelectionLogic( - /* maxShortcutTargetsPerApp = */ 1, - /* applySharingAppLimits = */ true - ) - - testSubject.addServiceResults( - /* origTarget = */ baseDisplayInfo, - /* origTargetScore = */ 0.1f, - /* targets = */ listOf(pkgAsc1, pkgAsc2), - /* isShortcutResult = */ true, - /* directShareToShortcutInfos = */ emptyMap(), - /* directShareToAppTargets = */ emptyMap(), - /* userContext = */ mock(), - /* targetIntent = */ mock(), - /* refererFillInIntent = */ mock(), - /* maxRankedTargets = */ 4, - /* serviceTargets = */ serviceResults - ) - testSubject.addServiceResults( - /* origTarget = */ otherBaseDisplayInfo, - /* origTargetScore = */ 0.2f, - /* targets = */ listOf(pkgBsc1, pkgBsc2), - /* isShortcutResult = */ true, - /* directShareToShortcutInfos = */ emptyMap(), - /* directShareToAppTargets = */ emptyMap(), - /* userContext = */ mock(), - /* targetIntent = */ mock(), - /* refererFillInIntent = */ mock(), - /* maxRankedTargets = */ 4, - /* serviceTargets = */ serviceResults - ) - - assertShortcutsInOrder( - listOf(pkgBsc2, pkgAsc2), - serviceResults, - "Two shortcuts are expected as we apply per-app shortcut limit" - ) - } - - @Test - fun testAddShortcuts_pinned_shortcut() { - val serviceResults = ArrayList<TargetInfo>() - val sc1 = packageTargets[PACKAGE_A, 0] - val sc2 = packageTargets[PACKAGE_A, 1] - val testSubject = ShortcutSelectionLogic( - /* maxShortcutTargetsPerApp = */ 1, - /* applySharingAppLimits = */ false - ) - - val isUpdated = testSubject.addServiceResults( - /* origTarget = */ baseDisplayInfo, - /* origTargetScore = */ 0.1f, - /* targets = */ listOf(sc1, sc2), - /* isShortcutResult = */ true, - /* directShareToShortcutInfos = */ mapOf( - sc1 to createShortcutInfo( - PACKAGE_A.shortcutId(1), - sc1.componentName, 1).apply { - addFlags(ShortcutInfo.FLAG_PINNED) - } - ), - /* directShareToAppTargets = */ emptyMap(), - /* userContext = */ mock(), - /* targetIntent = */ mock(), - /* refererFillInIntent = */ mock(), - /* maxRankedTargets = */ 4, - /* serviceTargets = */ serviceResults - ) - - assertTrue("Updates are expected", isUpdated) - assertShortcutsInOrder( - listOf(sc1, sc2), - serviceResults, - "Two shortcuts are expected as we do not apply per-app shortcut limit" - ) - } - - @Test - fun test_available_caller_shortcuts_count_is_limited() { - val serviceResults = ArrayList<TargetInfo>() - val sc1 = packageTargets[PACKAGE_A, 0] - val sc2 = packageTargets[PACKAGE_A, 1] - val sc3 = packageTargets[PACKAGE_A, 2] - val testSubject = ShortcutSelectionLogic( - /* maxShortcutTargetsPerApp = */ 1, - /* applySharingAppLimits = */ true - ) - val context = mock<Context> { - whenever(packageManager).thenReturn(mock()) - } - - testSubject.addServiceResults( - /* origTarget = */ baseDisplayInfo, - /* origTargetScore = */ 0f, - /* targets = */ listOf(sc1, sc2, sc3), - /* isShortcutResult = */ false, - /* directShareToShortcutInfos = */ emptyMap(), - /* directShareToAppTargets = */ emptyMap(), - /* userContext = */ context, - /* targetIntent = */ mock(), - /* refererFillInIntent = */ mock(), - /* maxRankedTargets = */ 4, - /* serviceTargets = */ serviceResults - ) - - assertShortcutsInOrder( - listOf(sc3, sc2), - serviceResults, - "At most two caller-provided shortcuts are allowed" - ) - } - - // TODO: consider renaming. Not all `ChooserTarget`s are "shortcuts" and many of our test cases - // add results with `isShortcutResult = false` and `directShareToShortcutInfos = emptyMap()`. - private fun assertShortcutsInOrder( - expected: List<ChooserTarget>, actual: List<TargetInfo>, msg: String? = "" - ) { - assertEquals(msg, expected.size, actual.size) - for (i in expected.indices) { - assertEquals( - "Unexpected item at position $i", - expected[i].componentName, - actual[i].chooserTargetComponentName - ) - assertEquals( - "Unexpected item at position $i", - expected[i].title, - actual[i].displayLabel - ) - } - } - - private fun String.shortcutId(id: Int) = "$this.$id" -} diff --git a/java/tests/src/com/android/intentresolver/TargetPresentationGetterTest.kt b/java/tests/src/com/android/intentresolver/TargetPresentationGetterTest.kt deleted file mode 100644 index e62672a3..00000000 --- a/java/tests/src/com/android/intentresolver/TargetPresentationGetterTest.kt +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.intentresolver - -import com.android.intentresolver.ResolverDataProvider -import com.google.common.truth.Truth.assertThat -import org.junit.Test - -/** - * Unit tests for the various implementations of {@link TargetPresentationGetter}. - * TODO: consider expanding to cover icon logic (not just labels/sublabels). - * TODO: these are conceptually "acceptance tests" that provide comprehensive coverage of the - * apparent variations in the legacy implementation. The tests probably don't have to be so - * exhaustive if we're able to impose a simpler design on the implementation. - */ -class TargetPresentationGetterTest { - fun makeResolveInfoPresentationGetter( - withSubstitutePermission: Boolean, - appLabel: String, - activityLabel: String, - resolveInfoLabel: String): TargetPresentationGetter { - val testPackageInfo = ResolverDataProvider.createPackageManagerMockedInfo( - withSubstitutePermission, appLabel, activityLabel, resolveInfoLabel) - val factory = TargetPresentationGetter.Factory(testPackageInfo.ctx, 100) - return factory.makePresentationGetter(testPackageInfo.resolveInfo) - } - - fun makeActivityInfoPresentationGetter( - withSubstitutePermission: Boolean, - appLabel: String?, - activityLabel: String?): TargetPresentationGetter { - val testPackageInfo = ResolverDataProvider.createPackageManagerMockedInfo( - withSubstitutePermission, appLabel, activityLabel, "") - val factory = TargetPresentationGetter.Factory(testPackageInfo.ctx, 100) - return factory.makePresentationGetter(testPackageInfo.activityInfo) - } - - @Test - fun testActivityInfoLabels_noSubstitutePermission_distinctRequestedLabelAndSublabel() { - val presentationGetter = makeActivityInfoPresentationGetter( - false, "app_label", "activity_label") - assertThat(presentationGetter.getLabel()).isEqualTo("app_label") - assertThat(presentationGetter.getSubLabel()).isEqualTo("activity_label") - } - - @Test - fun testActivityInfoLabels_noSubstitutePermission_sameRequestedLabelAndSublabel() { - val presentationGetter = makeActivityInfoPresentationGetter( - false, "app_label", "app_label") - assertThat(presentationGetter.getLabel()).isEqualTo("app_label") - // Without the substitute permission, there's no logic to dedupe the labels. - // TODO: this matches our observations in the legacy code, but is it the right behavior? It - // seems like {@link ResolverListAdapter.ViewHolder#bindLabel()} has some logic to dedupe in - // the UI at least, but maybe that logic should be pulled back to the "presentation"? - assertThat(presentationGetter.getSubLabel()).isEqualTo("app_label") - } - - @Test - fun testActivityInfoLabels_noSubstitutePermission_nullRequestedLabel() { - val presentationGetter = makeActivityInfoPresentationGetter(false, null, "activity_label") - assertThat(presentationGetter.getLabel()).isNull() - assertThat(presentationGetter.getSubLabel()).isEqualTo("activity_label") - } - - @Test - fun testActivityInfoLabels_noSubstitutePermission_emptyRequestedLabel() { - val presentationGetter = makeActivityInfoPresentationGetter(false, "", "activity_label") - assertThat(presentationGetter.getLabel()).isEqualTo("") - assertThat(presentationGetter.getSubLabel()).isEqualTo("activity_label") - } - - @Test - fun testActivityInfoLabels_noSubstitutePermission_emptyRequestedSublabel() { - val presentationGetter = makeActivityInfoPresentationGetter(false, "app_label", "") - assertThat(presentationGetter.getLabel()).isEqualTo("app_label") - // Without the substitute permission, empty sublabels are passed through as-is. - assertThat(presentationGetter.getSubLabel()).isEqualTo("") - } - - @Test - fun testActivityInfoLabels_withSubstitutePermission_distinctRequestedLabelAndSublabel() { - val presentationGetter = makeActivityInfoPresentationGetter( - true, "app_label", "activity_label") - assertThat(presentationGetter.getLabel()).isEqualTo("activity_label") - // With the substitute permission, the same ("activity") label is requested as both the label - // and sublabel, even though the other value ("app_label") was distinct. Thus this behaves the - // same as a dupe. - assertThat(presentationGetter.getSubLabel()).isEqualTo(null) - } - - @Test - fun testActivityInfoLabels_withSubstitutePermission_sameRequestedLabelAndSublabel() { - val presentationGetter = makeActivityInfoPresentationGetter( - true, "app_label", "app_label") - assertThat(presentationGetter.getLabel()).isEqualTo("app_label") - // With the substitute permission, duped sublabels get converted to nulls. - assertThat(presentationGetter.getSubLabel()).isNull() - } - - @Test - fun testActivityInfoLabels_withSubstitutePermission_nullRequestedLabel() { - val presentationGetter = makeActivityInfoPresentationGetter(true, "app_label", null) - assertThat(presentationGetter.getLabel()).isEqualTo("app_label") - // With the substitute permission, null inputs are a special case that produces null outputs - // (i.e., they're not simply passed-through from the inputs). - assertThat(presentationGetter.getSubLabel()).isNull() - } - - @Test - fun testActivityInfoLabels_withSubstitutePermission_emptyRequestedLabel() { - val presentationGetter = makeActivityInfoPresentationGetter(true, "app_label", "") - // Empty "labels" are taken as-is and (unlike nulls) don't prompt a fallback to the sublabel. - // Thus (as in the previous case with substitute permission & "distinct" labels), this is - // treated as a dupe. - assertThat(presentationGetter.getLabel()).isEqualTo("") - assertThat(presentationGetter.getSubLabel()).isNull() - } - - @Test - fun testActivityInfoLabels_withSubstitutePermission_emptyRequestedSublabel() { - val presentationGetter = makeActivityInfoPresentationGetter(true, "", "activity_label") - assertThat(presentationGetter.getLabel()).isEqualTo("activity_label") - // With the substitute permission, empty sublabels get converted to nulls. - assertThat(presentationGetter.getSubLabel()).isNull() - } - - @Test - fun testResolveInfoLabels_noSubstitutePermission_distinctRequestedLabelAndSublabel() { - val presentationGetter = makeResolveInfoPresentationGetter( - false, "app_label", "activity_label", "resolve_info_label") - assertThat(presentationGetter.getLabel()).isEqualTo("app_label") - assertThat(presentationGetter.getSubLabel()).isEqualTo("resolve_info_label") - } - - @Test - fun testResolveInfoLabels_noSubstitutePermission_sameRequestedLabelAndSublabel() { - val presentationGetter = makeResolveInfoPresentationGetter( - false, "app_label", "activity_label", "app_label") - assertThat(presentationGetter.getLabel()).isEqualTo("app_label") - // Without the substitute permission, there's no logic to dedupe the labels. - // TODO: this matches our observations in the legacy code, but is it the right behavior? It - // seems like {@link ResolverListAdapter.ViewHolder#bindLabel()} has some logic to dedupe in - // the UI at least, but maybe that logic should be pulled back to the "presentation"? - assertThat(presentationGetter.getSubLabel()).isEqualTo("app_label") - } - - @Test - fun testResolveInfoLabels_noSubstitutePermission_emptyRequestedSublabel() { - val presentationGetter = makeResolveInfoPresentationGetter( - false, "app_label", "activity_label", "") - assertThat(presentationGetter.getLabel()).isEqualTo("app_label") - // Without the substitute permission, empty sublabels are passed through as-is. - assertThat(presentationGetter.getSubLabel()).isEqualTo("") - } - - @Test - fun testResolveInfoLabels_withSubstitutePermission_distinctRequestedLabelAndSublabel() { - val presentationGetter = makeResolveInfoPresentationGetter( - true, "app_label", "activity_label", "resolve_info_label") - assertThat(presentationGetter.getLabel()).isEqualTo("activity_label") - assertThat(presentationGetter.getSubLabel()).isEqualTo("resolve_info_label") - } - - @Test - fun testResolveInfoLabels_withSubstitutePermission_sameRequestedLabelAndSublabel() { - val presentationGetter = makeResolveInfoPresentationGetter( - true, "app_label", "activity_label", "activity_label") - assertThat(presentationGetter.getLabel()).isEqualTo("activity_label") - // With the substitute permission, duped sublabels get converted to nulls. - assertThat(presentationGetter.getSubLabel()).isNull() - } - - @Test - fun testResolveInfoLabels_withSubstitutePermission_emptyRequestedSublabel() { - val presentationGetter = makeResolveInfoPresentationGetter( - true, "app_label", "activity_label", "") - assertThat(presentationGetter.getLabel()).isEqualTo("activity_label") - // With the substitute permission, empty sublabels get converted to nulls. - assertThat(presentationGetter.getSubLabel()).isNull() - } - - @Test - fun testResolveInfoLabels_withSubstitutePermission_emptyRequestedLabelAndSublabel() { - val presentationGetter = makeResolveInfoPresentationGetter( - true, "app_label", "", "") - assertThat(presentationGetter.getLabel()).isEqualTo("") - // With the substitute permission, empty sublabels get converted to nulls. - assertThat(presentationGetter.getSubLabel()).isNull() - } -} diff --git a/java/tests/src/com/android/intentresolver/TestContentPreviewViewModel.kt b/java/tests/src/com/android/intentresolver/TestContentPreviewViewModel.kt deleted file mode 100644 index d239f612..00000000 --- a/java/tests/src/com/android/intentresolver/TestContentPreviewViewModel.kt +++ /dev/null @@ -1,56 +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 - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.viewmodel.CreationExtras -import com.android.intentresolver.contentpreview.BasePreviewViewModel -import com.android.intentresolver.contentpreview.ImageLoader -import com.android.intentresolver.contentpreview.PreviewDataProvider - -/** A test content preview model that supports image loader override. */ -class TestContentPreviewViewModel( - private val viewModel: BasePreviewViewModel, - private val imageLoader: ImageLoader? = null, -) : BasePreviewViewModel() { - override fun createOrReuseProvider( - chooserRequest: ChooserRequestParameters - ): PreviewDataProvider = viewModel.createOrReuseProvider(chooserRequest) - - override fun createOrReuseImageLoader(): ImageLoader = - imageLoader ?: viewModel.createOrReuseImageLoader() - - companion object { - fun wrap( - factory: ViewModelProvider.Factory, - imageLoader: ImageLoader?, - ): ViewModelProvider.Factory = - object : ViewModelProvider.Factory { - @Suppress("UNCHECKED_CAST") - override fun <T : ViewModel> create( - modelClass: Class<T>, - extras: CreationExtras - ): T { - return TestContentPreviewViewModel( - factory.create(modelClass, extras) as BasePreviewViewModel, - imageLoader, - ) as T - } - } - } -} diff --git a/java/tests/src/com/android/intentresolver/TestContentProvider.kt b/java/tests/src/com/android/intentresolver/TestContentProvider.kt deleted file mode 100644 index 426f9af2..00000000 --- a/java/tests/src/com/android/intentresolver/TestContentProvider.kt +++ /dev/null @@ -1,69 +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 - -import android.content.ContentProvider -import android.content.ContentValues -import android.database.Cursor -import android.net.Uri - -class TestContentProvider : ContentProvider() { - override fun query( - uri: Uri, - projection: Array<out String>?, - selection: String?, - selectionArgs: Array<out String>?, - sortOrder: String? - ): Cursor? = null - - override fun getType(uri: Uri): String? = - runCatching { uri.getQueryParameter(PARAM_MIME_TYPE) }.getOrNull() - - override fun getStreamTypes(uri: Uri, mimeTypeFilter: String): Array<String>? { - val delay = - runCatching { uri.getQueryParameter(PARAM_STREAM_TYPE_TIMEOUT)?.toLong() ?: 0L } - .getOrDefault(0L) - if (delay > 0) { - try { - Thread.sleep(delay) - } catch (e: InterruptedException) { - Thread.currentThread().interrupt() - } - } - return runCatching { uri.getQueryParameter(PARAM_STREAM_TYPE)?.let { arrayOf(it) } } - .getOrNull() - } - - override fun insert(uri: Uri, values: ContentValues?): Uri? = null - - override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int = 0 - - override fun update( - uri: Uri, - values: ContentValues?, - selection: String?, - selectionArgs: Array<out String>? - ): Int = 0 - - override fun onCreate(): Boolean = true - - companion object { - const val PARAM_MIME_TYPE = "mimeType" - const val PARAM_STREAM_TYPE = "streamType" - const val PARAM_STREAM_TYPE_TIMEOUT = "streamTypeTo" - } -} diff --git a/java/tests/src/com/android/intentresolver/TestHelpers.kt b/java/tests/src/com/android/intentresolver/TestHelpers.kt deleted file mode 100644 index 5b583fef..00000000 --- a/java/tests/src/com/android/intentresolver/TestHelpers.kt +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.intentresolver - -import android.app.prediction.AppTarget -import android.app.prediction.AppTargetId -import android.content.ComponentName -import android.content.Context -import android.content.Intent -import android.content.pm.ShortcutInfo -import android.content.pm.ShortcutManager.ShareShortcutInfo -import android.os.Bundle -import android.service.chooser.ChooserTarget -import org.mockito.Mockito.`when` as whenever - -internal fun createShareShortcutInfo( - id: String, - componentName: ComponentName, - rank: Int -): ShareShortcutInfo = - ShareShortcutInfo( - createShortcutInfo(id, componentName, rank), - componentName - ) - -internal fun createShortcutInfo( - id: String, - componentName: ComponentName, - rank: Int -): ShortcutInfo { - val context = mock<Context>() - whenever(context.packageName).thenReturn(componentName.packageName) - return ShortcutInfo.Builder(context, id) - .setShortLabel("Short Label $id") - .setLongLabel("Long Label $id") - .setActivity(componentName) - .setRank(rank) - .build() -} - -internal fun createAppTarget(shortcutInfo: ShortcutInfo) = - AppTarget( - AppTargetId(shortcutInfo.id), - shortcutInfo, - shortcutInfo.activity?.className ?: error("missing activity info") - ) - -fun createChooserTarget( - title: String, score: Float, componentName: ComponentName, shortcutId: String -): ChooserTarget = - ChooserTarget( - title, - null, - score, - componentName, - Bundle().apply { putString(Intent.EXTRA_SHORTCUT_ID, shortcutId) } - ) diff --git a/java/tests/src/com/android/intentresolver/TestPreviewImageLoader.kt b/java/tests/src/com/android/intentresolver/TestPreviewImageLoader.kt deleted file mode 100644 index 9c4d6187..00000000 --- a/java/tests/src/com/android/intentresolver/TestPreviewImageLoader.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.intentresolver - -import android.graphics.Bitmap -import android.net.Uri -import com.android.intentresolver.contentpreview.ImageLoader -import java.util.function.Consumer -import kotlinx.coroutines.CoroutineScope - -internal class TestPreviewImageLoader(private val bitmaps: Map<Uri, Bitmap>) : ImageLoader { - override fun loadImage(callerScope: CoroutineScope, uri: Uri, callback: Consumer<Bitmap?>) { - callback.accept(bitmaps[uri]) - } - - override suspend fun invoke(uri: Uri, caching: Boolean): Bitmap? = bitmaps[uri] - - override fun prePopulate(uris: List<Uri>) = Unit -} diff --git a/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java b/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java deleted file mode 100644 index f597d7f2..00000000 --- a/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java +++ /dev/null @@ -1,3127 +0,0 @@ -/* - * Copyright (C) 2016 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 static android.app.Activity.RESULT_OK; - -import static androidx.test.espresso.Espresso.onView; -import static androidx.test.espresso.action.ViewActions.click; -import static androidx.test.espresso.action.ViewActions.longClick; -import static androidx.test.espresso.action.ViewActions.swipeUp; -import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist; -import static androidx.test.espresso.assertion.ViewAssertions.matches; -import static androidx.test.espresso.matcher.ViewMatchers.hasSibling; -import static androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed; -import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; -import static androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility; -import static androidx.test.espresso.matcher.ViewMatchers.withId; -import static androidx.test.espresso.matcher.ViewMatchers.withText; - -import static com.android.intentresolver.ChooserActivity.TARGET_TYPE_CHOOSER_TARGET; -import static com.android.intentresolver.ChooserActivity.TARGET_TYPE_DEFAULT; -import static com.android.intentresolver.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE; -import static com.android.intentresolver.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER; -import static com.android.intentresolver.ChooserListAdapter.CALLER_TARGET_SCORE_BOOST; -import static com.android.intentresolver.ChooserListAdapter.SHORTCUT_TARGET_SCORE_BOOST; -import static com.android.intentresolver.MatcherUtils.first; - -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assertWithMessage; - -import static junit.framework.Assert.assertNull; - -import static org.hamcrest.CoreMatchers.allOf; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.app.PendingIntent; -import android.app.usage.UsageStatsManager; -import android.content.BroadcastReceiver; -import android.content.ClipData; -import android.content.ClipDescription; -import android.content.ClipboardManager; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.pm.ShortcutInfo; -import android.content.pm.ShortcutManager.ShareShortcutInfo; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Rect; -import android.graphics.Typeface; -import android.graphics.drawable.Icon; -import android.net.Uri; -import android.os.Bundle; -import android.os.UserHandle; -import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; -import android.provider.DeviceConfig; -import android.service.chooser.ChooserAction; -import android.service.chooser.ChooserTarget; -import android.text.Spannable; -import android.text.SpannableStringBuilder; -import android.text.Spanned; -import android.text.style.BackgroundColorSpan; -import android.text.style.ForegroundColorSpan; -import android.text.style.StyleSpan; -import android.text.style.UnderlineSpan; -import android.util.Pair; -import android.util.SparseArray; -import android.view.View; -import android.view.WindowManager; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.recyclerview.widget.GridLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import androidx.test.espresso.contrib.RecyclerViewActions; -import androidx.test.espresso.matcher.BoundedDiagnosingMatcher; -import androidx.test.espresso.matcher.ViewMatchers; -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.rule.ActivityTestRule; - -import com.android.intentresolver.chooser.DisplayResolveInfo; -import com.android.intentresolver.contentpreview.ImageLoader; -import com.android.intentresolver.logging.EventLog; -import com.android.intentresolver.logging.FakeEventLog; -import com.android.intentresolver.shortcuts.ShortcutLoader; -import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; - -import dagger.hilt.android.testing.HiltAndroidRule; -import dagger.hilt.android.testing.HiltAndroidTest; - -import org.hamcrest.Description; -import org.hamcrest.Matcher; -import org.hamcrest.Matchers; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; -import java.util.function.Function; - -/** - * Instrumentation tests for ChooserActivity. - * <p> - * Legacy test suite migrated from framework CoreTests. - * <p> - */ -@RunWith(Parameterized.class) -@HiltAndroidTest -public class UnbundledChooserActivityTest { - - private static FakeEventLog getEventLog(ChooserWrapperActivity activity) { - return (FakeEventLog) activity.mEventLog; - } - - private static final UserHandle PERSONAL_USER_HANDLE = InstrumentationRegistry - .getInstrumentation().getTargetContext().getUser(); - private static final UserHandle WORK_PROFILE_USER_HANDLE = UserHandle.of(10); - private static final UserHandle CLONE_PROFILE_USER_HANDLE = UserHandle.of(11); - - private static final Function<PackageManager, PackageManager> DEFAULT_PM = pm -> pm; - private static final Function<PackageManager, PackageManager> NO_APP_PREDICTION_SERVICE_PM = - pm -> { - PackageManager mock = Mockito.spy(pm); - when(mock.getAppPredictionServicePackageName()).thenReturn(null); - return mock; - }; - - @Parameterized.Parameters - public static Collection packageManagers() { - return Arrays.asList(new Object[][] { - // Default PackageManager - { DEFAULT_PM }, - // No App Prediction Service - { NO_APP_PREDICTION_SERVICE_PM} - }); - } - - private static final String TEST_MIME_TYPE = "application/TestType"; - - private static final int CONTENT_PREVIEW_IMAGE = 1; - private static final int CONTENT_PREVIEW_FILE = 2; - private static final int CONTENT_PREVIEW_TEXT = 3; - - @Rule(order = 0) - public CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); - - @Rule(order = 1) - public HiltAndroidRule mHiltAndroidRule = new HiltAndroidRule(this); - - @Rule(order = 2) - public ActivityTestRule<ChooserWrapperActivity> mActivityRule = - new ActivityTestRule<>(ChooserWrapperActivity.class, false, false); - - @Before - public void setUp() { - // TODO: use the other form of `adoptShellPermissionIdentity()` where we explicitly list the - // permissions we require (which we'll read from the manifest at runtime). - InstrumentationRegistry - .getInstrumentation() - .getUiAutomation() - .adoptShellPermissionIdentity(); - - cleanOverrideData(); - mHiltAndroidRule.inject(); - } - - private final Function<PackageManager, PackageManager> mPackageManagerOverride; - - public UnbundledChooserActivityTest( - Function<PackageManager, PackageManager> packageManagerOverride) { - mPackageManagerOverride = packageManagerOverride; - } - - private void setDeviceConfigProperty( - @NonNull String propertyName, - @NonNull String value) { - // TODO: consider running with {@link #runWithShellPermissionIdentity()} to more narrowly - // request WRITE_DEVICE_CONFIG permissions if we get rid of the broad grant we currently - // configure in {@link #setup()}. - // TODO: is it really appropriate that this is always set with makeDefault=true? - boolean valueWasSet = DeviceConfig.setProperty( - DeviceConfig.NAMESPACE_SYSTEMUI, - propertyName, - value, - true /* makeDefault */); - if (!valueWasSet) { - throw new IllegalStateException( - "Could not set " + propertyName + " to " + value); - } - } - - public void cleanOverrideData() { - ChooserActivityOverrideData.getInstance().reset(); - ChooserActivityOverrideData.getInstance().createPackageManager = mPackageManagerOverride; - - setDeviceConfigProperty( - SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI, - Boolean.toString(true)); - } - - @Test - public void customTitle() throws InterruptedException { - Intent viewIntent = createViewTextIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - final IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity( - Intent.createChooser(viewIntent, "chooser test")); - - waitForIdle(); - assertThat(activity.getAdapter().getCount(), is(2)); - assertThat(activity.getAdapter().getServiceTargetCount(), is(0)); - onView(withId(android.R.id.title)).check(matches(withText("chooser test"))); - } - - @Test - public void customTitleIgnoredForSendIntents() throws InterruptedException { - Intent sendIntent = createSendTextIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "chooser test")); - waitForIdle(); - onView(withId(android.R.id.title)) - .check(matches(withText(R.string.whichSendApplication))); - } - - @Test - public void emptyTitle() throws InterruptedException { - Intent sendIntent = createSendTextIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - onView(withId(android.R.id.title)) - .check(matches(withText(R.string.whichSendApplication))); - } - - @Test - public void test_shareRichTextWithRichTitle_richTextAndRichTitleDisplayed() { - CharSequence title = new SpannableStringBuilder() - .append("Rich", new UnderlineSpan(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE) - .append( - "Title", - new ForegroundColorSpan(Color.RED), - Spannable.SPAN_INCLUSIVE_EXCLUSIVE); - CharSequence sharedText = new SpannableStringBuilder() - .append( - "Rich", - new BackgroundColorSpan(Color.YELLOW), - Spanned.SPAN_INCLUSIVE_EXCLUSIVE) - .append( - "Text", - new StyleSpan(Typeface.ITALIC), - Spanned.SPAN_INCLUSIVE_EXCLUSIVE); - Intent sendIntent = createSendTextIntent(); - sendIntent.putExtra(Intent.EXTRA_TEXT, sharedText); - sendIntent.putExtra(Intent.EXTRA_TITLE, title); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - setupResolverControllers(resolvedComponentInfos); - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - onView(withId(com.android.internal.R.id.content_preview_title)) - .check((view, e) -> { - assertThat(view).isInstanceOf(TextView.class); - CharSequence text = ((TextView) view).getText(); - assertThat(text).isInstanceOf(Spanned.class); - Spanned spanned = (Spanned) text; - assertThat(spanned.getSpans(0, spanned.length(), Object.class)) - .hasLength(2); - assertThat(spanned.getSpans(0, 4, UnderlineSpan.class)).hasLength(1); - assertThat(spanned.getSpans(4, spanned.length(), ForegroundColorSpan.class)) - .hasLength(1); - }); - - onView(withId(com.android.internal.R.id.content_preview_text)) - .check((view, e) -> { - assertThat(view).isInstanceOf(TextView.class); - CharSequence text = ((TextView) view).getText(); - assertThat(text).isInstanceOf(Spanned.class); - Spanned spanned = (Spanned) text; - assertThat(spanned.getSpans(0, spanned.length(), Object.class)) - .hasLength(2); - assertThat(spanned.getSpans(0, 4, BackgroundColorSpan.class)).hasLength(1); - assertThat(spanned.getSpans(4, spanned.length(), StyleSpan.class)).hasLength(1); - }); - } - - @Test - public void emptyPreviewTitleAndThumbnail() throws InterruptedException { - Intent sendIntent = createSendTextIntentWithPreview(null, null); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - onView(withId(com.android.internal.R.id.content_preview_title)) - .check(matches(not(isDisplayed()))); - onView(withId(com.android.internal.R.id.content_preview_thumbnail)) - .check(matches(not(isDisplayed()))); - } - - @Test - public void visiblePreviewTitleWithoutThumbnail() throws InterruptedException { - String previewTitle = "My Content Preview Title"; - Intent sendIntent = createSendTextIntentWithPreview(previewTitle, null); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - onView(withId(com.android.internal.R.id.content_preview_title)) - .check(matches(isDisplayed())); - onView(withId(com.android.internal.R.id.content_preview_title)) - .check(matches(withText(previewTitle))); - onView(withId(com.android.internal.R.id.content_preview_thumbnail)) - .check(matches(not(isDisplayed()))); - } - - @Test - public void visiblePreviewTitleWithInvalidThumbnail() throws InterruptedException { - String previewTitle = "My Content Preview Title"; - Intent sendIntent = createSendTextIntentWithPreview(previewTitle, - Uri.parse("tel:(+49)12345789")); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - onView(withId(com.android.internal.R.id.content_preview_title)) - .check(matches(isDisplayed())); - onView(withId(com.android.internal.R.id.content_preview_thumbnail)) - .check(matches(not(isDisplayed()))); - } - - @Test - public void visiblePreviewTitleAndThumbnail() throws InterruptedException { - String previewTitle = "My Content Preview Title"; - Uri uri = Uri.parse( - "android.resource://com.android.frameworks.coretests/" - + com.android.intentresolver.tests.R.drawable.test320x240); - Intent sendIntent = createSendTextIntentWithPreview(previewTitle, uri); - ChooserActivityOverrideData.getInstance().imageLoader = - createImageLoader(uri, createBitmap()); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - onView(withId(com.android.internal.R.id.content_preview_title)) - .check(matches(isDisplayed())); - onView(withId(com.android.internal.R.id.content_preview_thumbnail)) - .check(matches(isDisplayed())); - } - - @Test @Ignore - public void twoOptionsAndUserSelectsOne() throws InterruptedException { - Intent sendIntent = createSendTextIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - assertThat(activity.getAdapter().getCount(), is(2)); - onView(withId(com.android.internal.R.id.profile_button)).check(doesNotExist()); - - ResolveInfo[] chosen = new ResolveInfo[1]; - ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { - chosen[0] = targetInfo.getResolveInfo(); - return true; - }; - - ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0); - onView(withText(toChoose.activityInfo.name)) - .perform(click()); - waitForIdle(); - assertThat(chosen[0], is(toChoose)); - } - - @Test @Ignore - public void fourOptionsStackedIntoOneTarget() throws InterruptedException { - Intent sendIntent = createSendTextIntent(); - - // create just enough targets to ensure the a-z list should be shown - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(1); - - // next create 4 targets in a single app that should be stacked into a single target - String packageName = "xxx.yyy"; - String appName = "aaa"; - ComponentName cn = new ComponentName(packageName, appName); - Intent intent = new Intent("fakeIntent"); - List<ResolvedComponentInfo> infosToStack = new ArrayList<>(); - for (int i = 0; i < 4; i++) { - ResolveInfo resolveInfo = ResolverDataProvider.createResolveInfo(i, - UserHandle.USER_CURRENT, PERSONAL_USER_HANDLE); - resolveInfo.activityInfo.applicationInfo.name = appName; - resolveInfo.activityInfo.applicationInfo.packageName = packageName; - resolveInfo.activityInfo.packageName = packageName; - resolveInfo.activityInfo.name = "ccc" + i; - infosToStack.add(new ResolvedComponentInfo(cn, intent, resolveInfo)); - } - resolvedComponentInfos.addAll(infosToStack); - - setupResolverControllers(resolvedComponentInfos); - - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - // expect 1 unique targets + 1 group + 4 ranked app targets - assertThat(activity.getAdapter().getCount(), is(6)); - - ResolveInfo[] chosen = new ResolveInfo[1]; - ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { - chosen[0] = targetInfo.getResolveInfo(); - return true; - }; - - onView(allOf(withText(appName), hasSibling(withText("")))).perform(click()); - waitForIdle(); - - // clicking will launch a dialog to choose the activity within the app - onView(withText(appName)).check(matches(isDisplayed())); - int i = 0; - for (ResolvedComponentInfo rci: infosToStack) { - onView(withText("ccc" + i)).check(matches(isDisplayed())); - ++i; - } - } - - @Test @Ignore - public void updateChooserCountsAndModelAfterUserSelection() throws InterruptedException { - Intent sendIntent = createSendTextIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - UsageStatsManager usm = activity.getUsageStatsManager(); - verify(ChooserActivityOverrideData.getInstance().resolverListController, times(1)) - .topK(any(List.class), anyInt()); - assertThat(activity.getIsSelected(), is(false)); - ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { - return true; - }; - ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0); - DisplayResolveInfo testDri = - activity.createTestDisplayResolveInfo( - sendIntent, toChoose, "testLabel", "testInfo", sendIntent); - onView(withText(toChoose.activityInfo.name)) - .perform(click()); - waitForIdle(); - verify(ChooserActivityOverrideData.getInstance().resolverListController, times(1)) - .updateChooserCounts(Mockito.anyString(), any(UserHandle.class), - Mockito.anyString()); - verify(ChooserActivityOverrideData.getInstance().resolverListController, times(1)) - .updateModel(testDri); - assertThat(activity.getIsSelected(), is(true)); - } - - @Ignore // b/148158199 - @Test - public void noResultsFromPackageManager() { - setupResolverControllers(null); - Intent sendIntent = createSendTextIntent(); - final ChooserActivity activity = - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - final IChooserWrapper wrapper = (IChooserWrapper) activity; - - waitForIdle(); - assertThat(activity.isFinishing(), is(false)); - - onView(withId(android.R.id.empty)).check(matches(isDisplayed())); - onView(withId(com.android.internal.R.id.profile_pager)).check(matches(not(isDisplayed()))); - InstrumentationRegistry.getInstrumentation().runOnMainSync( - () -> wrapper.getAdapter().handlePackagesChanged() - ); - // backward compatibility. looks like we finish when data is empty after package change - assertThat(activity.isFinishing(), is(true)); - } - - @Test - public void autoLaunchSingleResult() throws InterruptedException { - ResolveInfo[] chosen = new ResolveInfo[1]; - ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { - chosen[0] = targetInfo.getResolveInfo(); - return true; - }; - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(1); - setupResolverControllers(resolvedComponentInfos); - - Intent sendIntent = createSendTextIntent(); - final ChooserActivity activity = - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - assertThat(chosen[0], is(resolvedComponentInfos.get(0).getResolveInfoAt(0))); - assertThat(activity.isFinishing(), is(true)); - } - - @Test @Ignore - public void hasOtherProfileOneOption() { - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10); - List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - - ResolveInfo toChoose = personalResolvedComponentInfos.get(1).getResolveInfoAt(0); - Intent sendIntent = createSendTextIntent(); - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - // The other entry is filtered to the other profile slot - assertThat(activity.getAdapter().getCount(), is(1)); - - ResolveInfo[] chosen = new ResolveInfo[1]; - ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { - chosen[0] = targetInfo.getResolveInfo(); - return true; - }; - - // Make a stable copy of the components as the original list may be modified - List<ResolvedComponentInfo> stableCopy = - createResolvedComponentsForTestWithOtherProfile(2, /* userId= */ 10); - waitForIdle(); - - onView(first(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))) - .perform(click()); - waitForIdle(); - assertThat(chosen[0], is(toChoose)); - } - - @Test @Ignore - public void hasOtherProfileTwoOptionsAndUserSelectsOne() throws Exception { - Intent sendIntent = createSendTextIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3); - ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0); - - setupResolverControllers(resolvedComponentInfos); - when(ChooserActivityOverrideData.getInstance().resolverListController.getLastChosen()) - .thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0)); - - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - // The other entry is filtered to the other profile slot - assertThat(activity.getAdapter().getCount(), is(2)); - - ResolveInfo[] chosen = new ResolveInfo[1]; - ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { - chosen[0] = targetInfo.getResolveInfo(); - return true; - }; - - // Make a stable copy of the components as the original list may be modified - List<ResolvedComponentInfo> stableCopy = - createResolvedComponentsForTestWithOtherProfile(3); - onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)) - .perform(click()); - waitForIdle(); - assertThat(chosen[0], is(toChoose)); - } - - @Test @Ignore - public void hasLastChosenActivityAndOtherProfile() throws Exception { - Intent sendIntent = createSendTextIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3); - ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0); - - setupResolverControllers(resolvedComponentInfos); - - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - // The other entry is filtered to the last used slot - assertThat(activity.getAdapter().getCount(), is(2)); - - ResolveInfo[] chosen = new ResolveInfo[1]; - ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { - chosen[0] = targetInfo.getResolveInfo(); - return true; - }; - - // Make a stable copy of the components as the original list may be modified - List<ResolvedComponentInfo> stableCopy = - createResolvedComponentsForTestWithOtherProfile(3); - onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)) - .perform(click()); - waitForIdle(); - assertThat(chosen[0], is(toChoose)); - } - - @Test - @Ignore("b/285309527") - public void testFilePlusTextSharing_ExcludeText() { - Uri uri = createTestContentProviderUri(null, "image/png"); - Intent sendIntent = createSendImageIntent(uri); - ChooserActivityOverrideData.getInstance().imageLoader = - createImageLoader(uri, createBitmap()); - sendIntent.putExtra(Intent.EXTRA_TEXT, "https://google.com/search?q=google"); - - List<ResolvedComponentInfo> resolvedComponentInfos = Arrays.asList( - ResolverDataProvider.createResolvedComponentInfo( - new ComponentName("org.imageviewer", "ImageTarget"), - sendIntent, PERSONAL_USER_HANDLE), - ResolverDataProvider.createResolvedComponentInfo( - new ComponentName("org.textviewer", "UriTarget"), - new Intent("VIEW_TEXT"), PERSONAL_USER_HANDLE) - ); - - setupResolverControllers(resolvedComponentInfos); - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - onView(withId(R.id.include_text_action)) - .check(matches(isDisplayed())) - .perform(click()); - waitForIdle(); - - onView(withId(R.id.content_preview_text)).check(matches(withText("File only"))); - - AtomicReference<Intent> launchedIntentRef = new AtomicReference<>(); - ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { - launchedIntentRef.set(targetInfo.getTargetIntent()); - return true; - }; - - onView(withText(resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.name)) - .perform(click()); - waitForIdle(); - assertThat(launchedIntentRef.get().hasExtra(Intent.EXTRA_TEXT)).isFalse(); - } - - @Test - @Ignore("b/285309527") - public void testFilePlusTextSharing_RemoveAndAddBackText() { - Uri uri = createTestContentProviderUri("application/pdf", "image/png"); - Intent sendIntent = createSendImageIntent(uri); - ChooserActivityOverrideData.getInstance().imageLoader = - createImageLoader(uri, createBitmap()); - final String text = "https://google.com/search?q=google"; - sendIntent.putExtra(Intent.EXTRA_TEXT, text); - - List<ResolvedComponentInfo> resolvedComponentInfos = Arrays.asList( - ResolverDataProvider.createResolvedComponentInfo( - new ComponentName("org.imageviewer", "ImageTarget"), - sendIntent, PERSONAL_USER_HANDLE), - ResolverDataProvider.createResolvedComponentInfo( - new ComponentName("org.textviewer", "UriTarget"), - new Intent("VIEW_TEXT"), PERSONAL_USER_HANDLE) - ); - - setupResolverControllers(resolvedComponentInfos); - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - onView(withId(R.id.include_text_action)) - .check(matches(isDisplayed())) - .perform(click()); - waitForIdle(); - onView(withId(R.id.content_preview_text)).check(matches(withText("File only"))); - - onView(withId(R.id.include_text_action)) - .perform(click()); - waitForIdle(); - - onView(withId(R.id.content_preview_text)).check(matches(withText(text))); - - AtomicReference<Intent> launchedIntentRef = new AtomicReference<>(); - ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { - launchedIntentRef.set(targetInfo.getTargetIntent()); - return true; - }; - - onView(withText(resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.name)) - .perform(click()); - waitForIdle(); - assertThat(launchedIntentRef.get().getStringExtra(Intent.EXTRA_TEXT)).isEqualTo(text); - } - - @Test - @Ignore("b/285309527") - public void testFilePlusTextSharing_TextExclusionDoesNotAffectAlternativeIntent() { - Uri uri = createTestContentProviderUri("image/png", null); - Intent sendIntent = createSendImageIntent(uri); - ChooserActivityOverrideData.getInstance().imageLoader = - createImageLoader(uri, createBitmap()); - sendIntent.putExtra(Intent.EXTRA_TEXT, "https://google.com/search?q=google"); - - Intent alternativeIntent = createSendTextIntent(); - final String text = "alternative intent"; - alternativeIntent.putExtra(Intent.EXTRA_TEXT, text); - - List<ResolvedComponentInfo> resolvedComponentInfos = Arrays.asList( - ResolverDataProvider.createResolvedComponentInfo( - new ComponentName("org.imageviewer", "ImageTarget"), - sendIntent, PERSONAL_USER_HANDLE), - ResolverDataProvider.createResolvedComponentInfo( - new ComponentName("org.textviewer", "UriTarget"), - alternativeIntent, PERSONAL_USER_HANDLE) - ); - - setupResolverControllers(resolvedComponentInfos); - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - onView(withId(R.id.include_text_action)) - .check(matches(isDisplayed())) - .perform(click()); - waitForIdle(); - - AtomicReference<Intent> launchedIntentRef = new AtomicReference<>(); - ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { - launchedIntentRef.set(targetInfo.getTargetIntent()); - return true; - }; - - onView(withText(resolvedComponentInfos.get(1).getResolveInfoAt(0).activityInfo.name)) - .perform(click()); - waitForIdle(); - assertThat(launchedIntentRef.get().getStringExtra(Intent.EXTRA_TEXT)).isEqualTo(text); - } - - @Test - @Ignore("b/285309527") - public void testImagePlusTextSharing_failedThumbnailAndExcludedText_textChanges() { - Uri uri = createTestContentProviderUri("image/png", null); - Intent sendIntent = createSendImageIntent(uri); - ChooserActivityOverrideData.getInstance().imageLoader = - new TestPreviewImageLoader(Collections.emptyMap()); - sendIntent.putExtra(Intent.EXTRA_TEXT, "https://google.com/search?q=google"); - - List<ResolvedComponentInfo> resolvedComponentInfos = Arrays.asList( - ResolverDataProvider.createResolvedComponentInfo( - new ComponentName("org.imageviewer", "ImageTarget"), - sendIntent, PERSONAL_USER_HANDLE), - ResolverDataProvider.createResolvedComponentInfo( - new ComponentName("org.textviewer", "UriTarget"), - new Intent("VIEW_TEXT"), PERSONAL_USER_HANDLE) - ); - - setupResolverControllers(resolvedComponentInfos); - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - onView(withId(R.id.include_text_action)) - .check(matches(isDisplayed())) - .perform(click()); - waitForIdle(); - - onView(withId(R.id.image_view)) - .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE))); - onView(withId(R.id.content_preview_text)) - .check(matches(allOf(isDisplayed(), withText("Image only")))); - } - - @Test - public void copyTextToClipboard() { - Intent sendIntent = createSendTextIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - - final ChooserActivity activity = - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - onView(withId(R.id.copy)).check(matches(isDisplayed())); - onView(withId(R.id.copy)).perform(click()); - ClipboardManager clipboard = (ClipboardManager) activity.getSystemService( - Context.CLIPBOARD_SERVICE); - ClipData clipData = clipboard.getPrimaryClip(); - assertThat(clipData).isNotNull(); - assertThat(clipData.getItemAt(0).getText()).isEqualTo("testing intent sending"); - - ClipDescription clipDescription = clipData.getDescription(); - assertThat("text/plain", is(clipDescription.getMimeType(0))); - - assertEquals(mActivityRule.getActivityResult().getResultCode(), RESULT_OK); - } - - @Test - public void copyTextToClipboardLogging() { - Intent sendIntent = createSendTextIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - - ChooserWrapperActivity activity = - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - onView(withId(R.id.copy)).check(matches(isDisplayed())); - onView(withId(R.id.copy)).perform(click()); - FakeEventLog eventLog = getEventLog(activity); - assertThat(eventLog.getActionSelected()) - .isEqualTo(new FakeEventLog.ActionSelected( - /* targetType = */ EventLog.SELECTION_TYPE_COPY)); - } - - @Test - @Ignore - public void testNearbyShareLogging() throws Exception { - Intent sendIntent = createSendTextIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - onView(withId(com.android.internal.R.id.chooser_nearby_button)) - .check(matches(isDisplayed())); - onView(withId(com.android.internal.R.id.chooser_nearby_button)).perform(click()); - - // TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events. - } - - - - @Test @Ignore - public void testEditImageLogs() { - Uri uri = createTestContentProviderUri("image/png", null); - Intent sendIntent = createSendImageIntent(uri); - ChooserActivityOverrideData.getInstance().imageLoader = - createImageLoader(uri, createBitmap()); - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - onView(withId(com.android.internal.R.id.chooser_edit_button)).check(matches(isDisplayed())); - onView(withId(com.android.internal.R.id.chooser_edit_button)).perform(click()); - - // TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events. - } - - - @Test - public void oneVisibleImagePreview() { - Uri uri = createTestContentProviderUri("image/png", null); - - ArrayList<Uri> uris = new ArrayList<>(); - uris.add(uri); - - Intent sendIntent = createSendUriIntentWithPreview(uris); - ChooserActivityOverrideData.getInstance().imageLoader = - createImageLoader(uri, createWideBitmap()); - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - onView(withId(R.id.scrollable_image_preview)) - .check((view, exception) -> { - if (exception != null) { - throw exception; - } - RecyclerView recyclerView = (RecyclerView) view; - assertThat(recyclerView.getAdapter().getItemCount(), is(1)); - assertThat(recyclerView.getChildCount(), is(1)); - View imageView = recyclerView.getChildAt(0); - Rect rect = new Rect(); - boolean isPartiallyVisible = imageView.getGlobalVisibleRect(rect); - assertThat( - "image preview view is not fully visible", - isPartiallyVisible - && rect.width() == imageView.getWidth() - && rect.height() == imageView.getHeight()); - }); - } - - @Test - public void allThumbnailsFailedToLoad_hidePreview() { - Uri uri = createTestContentProviderUri("image/jpg", null); - - ArrayList<Uri> uris = new ArrayList<>(); - uris.add(uri); - uris.add(uri); - - Intent sendIntent = createSendUriIntentWithPreview(uris); - ChooserActivityOverrideData.getInstance().imageLoader = - new TestPreviewImageLoader(Collections.emptyMap()); - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - onView(withId(R.id.scrollable_image_preview)) - .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE))); - } - - @Test(timeout = 4_000) - public void testSlowUriMetadata_fallbackToFilePreview() { - Uri uri = createTestContentProviderUri( - "application/pdf", "image/png", /*streamTypeTimeout=*/8_000); - ArrayList<Uri> uris = new ArrayList<>(1); - uris.add(uri); - Intent sendIntent = createSendUriIntentWithPreview(uris); - ChooserActivityOverrideData.getInstance().imageLoader = - createImageLoader(uri, createBitmap()); - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - // The preview type resolution is expected to timeout and default to file preview, otherwise - // the test should timeout. - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed())); - onView(withId(R.id.content_preview_filename)).check(matches(withText("image.png"))); - onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed())); - } - - @Test(timeout = 4_000) - public void testSendManyFilesWithSmallMetadataDelayAndOneImage_fallbackToFilePreviewUi() { - Uri fileUri = createTestContentProviderUri( - "application/pdf", "application/pdf", /*streamTypeTimeout=*/300); - Uri imageUri = createTestContentProviderUri("application/pdf", "image/png"); - ArrayList<Uri> uris = new ArrayList<>(50); - for (int i = 0; i < 49; i++) { - uris.add(fileUri); - } - uris.add(imageUri); - Intent sendIntent = createSendUriIntentWithPreview(uris); - ChooserActivityOverrideData.getInstance().imageLoader = - createImageLoader(imageUri, createBitmap()); - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - setupResolverControllers(resolvedComponentInfos); - // The preview type resolution is expected to timeout and default to file preview, otherwise - // the test should timeout. - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - - waitForIdle(); - - onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed())); - onView(withId(R.id.content_preview_filename)).check(matches(withText("image.png"))); - onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed())); - } - - @Test - public void testManyVisibleImagePreview_ScrollableImagePreview() { - Uri uri = createTestContentProviderUri("image/png", null); - - ArrayList<Uri> uris = new ArrayList<>(); - uris.add(uri); - uris.add(uri); - uris.add(uri); - uris.add(uri); - uris.add(uri); - uris.add(uri); - uris.add(uri); - uris.add(uri); - uris.add(uri); - uris.add(uri); - - Intent sendIntent = createSendUriIntentWithPreview(uris); - ChooserActivityOverrideData.getInstance().imageLoader = - createImageLoader(uri, createBitmap()); - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - onView(withId(R.id.scrollable_image_preview)) - .perform(RecyclerViewActions.scrollToLastPosition()) - .check((view, exception) -> { - if (exception != null) { - throw exception; - } - RecyclerView recyclerView = (RecyclerView) view; - assertThat(recyclerView.getAdapter().getItemCount(), is(uris.size())); - }); - } - - @Test(timeout = 4_000) - public void testPartiallyLoadedMetadata_previewIsShownForTheLoadedPart() { - Uri imgOneUri = createTestContentProviderUri("image/png", null); - Uri imgTwoUri = createTestContentProviderUri("image/png", null) - .buildUpon() - .path("image-2.png") - .build(); - Uri docUri = createTestContentProviderUri("application/pdf", "image/png", 8_000); - ArrayList<Uri> uris = new ArrayList<>(2); - // two large previews to fill the screen and be presented right away and one - // document that would be delayed by the URI metadata reading - uris.add(imgOneUri); - uris.add(imgTwoUri); - uris.add(docUri); - - Intent sendIntent = createSendUriIntentWithPreview(uris); - Map<Uri, Bitmap> bitmaps = new HashMap<>(); - bitmaps.put(imgOneUri, createWideBitmap(Color.RED)); - bitmaps.put(imgTwoUri, createWideBitmap(Color.GREEN)); - bitmaps.put(docUri, createWideBitmap(Color.BLUE)); - ChooserActivityOverrideData.getInstance().imageLoader = - new TestPreviewImageLoader(bitmaps); - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - setupResolverControllers(resolvedComponentInfos); - - // the preview type is expected to be resolved quickly based on the first provided URI - // metadata. If, instead, it is dependent on the third URI metadata, the test should either - // timeout or (more probably due to inner timeout) default to file preview type; anyway the - // test will fail. - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - onView(withId(R.id.scrollable_image_preview)) - .check((view, exception) -> { - if (exception != null) { - throw exception; - } - RecyclerView recyclerView = (RecyclerView) view; - assertThat(recyclerView.getChildCount()).isAtLeast(1); - // the first view is a preview - View imageView = recyclerView.getChildAt(0).findViewById(R.id.image); - assertThat(imageView).isNotNull(); - }) - .perform(RecyclerViewActions.scrollToLastPosition()) - .check((view, exception) -> { - if (exception != null) { - throw exception; - } - RecyclerView recyclerView = (RecyclerView) view; - assertThat(recyclerView.getChildCount()).isAtLeast(1); - // check that the last view is a loading indicator - View loadingIndicator = - recyclerView.getChildAt(recyclerView.getChildCount() - 1); - assertThat(loadingIndicator).isNotNull(); - }); - waitForIdle(); - } - - @Test - public void testImageAndTextPreview() { - final Uri uri = createTestContentProviderUri("image/png", null); - final String sharedText = "text-" + System.currentTimeMillis(); - - ArrayList<Uri> uris = new ArrayList<>(); - uris.add(uri); - - Intent sendIntent = createSendUriIntentWithPreview(uris); - sendIntent.putExtra(Intent.EXTRA_TEXT, sharedText); - ChooserActivityOverrideData.getInstance().imageLoader = - createImageLoader(uri, createBitmap()); - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - onView(withText(sharedText)) - .check(matches(isDisplayed())); - } - - @Test - public void test_shareImageWithRichText_RichTextIsDisplayed() { - final Uri uri = createTestContentProviderUri("image/png", null); - final CharSequence sharedText = new SpannableStringBuilder() - .append( - "text-", - new StyleSpan(Typeface.BOLD_ITALIC), - Spannable.SPAN_INCLUSIVE_EXCLUSIVE) - .append( - Long.toString(System.currentTimeMillis()), - new ForegroundColorSpan(Color.RED), - Spanned.SPAN_INCLUSIVE_EXCLUSIVE); - - ArrayList<Uri> uris = new ArrayList<>(); - uris.add(uri); - - Intent sendIntent = createSendUriIntentWithPreview(uris); - sendIntent.putExtra(Intent.EXTRA_TEXT, sharedText); - ChooserActivityOverrideData.getInstance().imageLoader = - createImageLoader(uri, createBitmap()); - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - onView(withText(sharedText.toString())) - .check(matches(isDisplayed())) - .check((view, e) -> { - if (e != null) { - throw e; - } - assertThat(view).isInstanceOf(TextView.class); - CharSequence text = ((TextView) view).getText(); - assertThat(text).isInstanceOf(Spanned.class); - Spanned spanned = (Spanned) text; - Object[] spans = spanned.getSpans(0, text.length(), Object.class); - assertThat(spans).hasLength(2); - assertThat(spanned.getSpans(0, 5, StyleSpan.class)).hasLength(1); - assertThat(spanned.getSpans(5, text.length(), ForegroundColorSpan.class)) - .hasLength(1); - }); - } - - @Test - public void testTextPreviewWhenTextIsSharedWithMultipleImages() { - final Uri uri = createTestContentProviderUri("image/png", null); - final String sharedText = "text-" + System.currentTimeMillis(); - - ArrayList<Uri> uris = new ArrayList<>(); - uris.add(uri); - uris.add(uri); - - Intent sendIntent = createSendUriIntentWithPreview(uris); - sendIntent.putExtra(Intent.EXTRA_TEXT, sharedText); - ChooserActivityOverrideData.getInstance().imageLoader = - createImageLoader(uri, createBitmap()); - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - when( - ChooserActivityOverrideData - .getInstance() - .resolverListController - .getResolversForIntentAsUser( - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - Mockito.any(UserHandle.class))) - .thenReturn(resolvedComponentInfos); - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - onView(withText(sharedText)).check(matches(isDisplayed())); - } - - @Test - public void testOnCreateLogging() { - Intent sendIntent = createSendTextIntent(); - sendIntent.setType(TEST_MIME_TYPE); - - ChooserWrapperActivity activity = - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "logger test")); - waitForIdle(); - - FakeEventLog eventLog = getEventLog(activity); - FakeEventLog.ChooserActivityShown event = eventLog.getChooserActivityShown(); - assertThat(event).isNotNull(); - assertThat(event.isWorkProfile()).isFalse(); - assertThat(event.getTargetMimeType()).isEqualTo(TEST_MIME_TYPE); - } - - @Test - public void testOnCreateLoggingFromWorkProfile() { - Intent sendIntent = createSendTextIntent(); - sendIntent.setType(TEST_MIME_TYPE); - ChooserActivityOverrideData.getInstance().alternateProfileSetting = - MetricsEvent.MANAGED_PROFILE; - - ChooserWrapperActivity activity = - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "logger test")); - waitForIdle(); - - FakeEventLog eventLog = getEventLog(activity); - FakeEventLog.ChooserActivityShown event = eventLog.getChooserActivityShown(); - assertThat(event).isNotNull(); - assertThat(event.isWorkProfile()).isTrue(); - assertThat(event.getTargetMimeType()).isEqualTo(TEST_MIME_TYPE); - } - - @Test - public void testEmptyPreviewLogging() { - Intent sendIntent = createSendTextIntentWithPreview(null, null); - - ChooserWrapperActivity activity = - mActivityRule.launchActivity(Intent.createChooser(sendIntent, - "empty preview logger test")); - waitForIdle(); - - FakeEventLog eventLog = getEventLog(activity); - FakeEventLog.ChooserActivityShown event = eventLog.getChooserActivityShown(); - assertThat(event).isNotNull(); - assertThat(event.isWorkProfile()).isFalse(); - assertThat(event.getTargetMimeType()).isNull(); - } - - @Test - public void testTitlePreviewLogging() { - Intent sendIntent = createSendTextIntentWithPreview("TestTitle", null); - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - - ChooserWrapperActivity activity = - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - FakeEventLog eventLog = getEventLog(activity); - assertThat(eventLog.getActionShareWithPreview()) - .isEqualTo(new FakeEventLog.ActionShareWithPreview( - /* previewType = */ CONTENT_PREVIEW_TEXT)); - } - - @Test - public void testImagePreviewLogging() { - Uri uri = createTestContentProviderUri("image/png", null); - - ArrayList<Uri> uris = new ArrayList<>(); - uris.add(uri); - - Intent sendIntent = createSendUriIntentWithPreview(uris); - ChooserActivityOverrideData.getInstance().imageLoader = - createImageLoader(uri, createBitmap()); - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - - ChooserWrapperActivity activity = - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - FakeEventLog eventLog = getEventLog(activity); - assertThat(eventLog.getActionShareWithPreview()) - .isEqualTo(new FakeEventLog.ActionShareWithPreview( - /* previewType = */ CONTENT_PREVIEW_IMAGE)); - } - - @Test - public void oneVisibleFilePreview() throws InterruptedException { - Uri uri = Uri.parse("content://com.android.frameworks.coretests/app.pdf"); - - ArrayList<Uri> uris = new ArrayList<>(); - uris.add(uri); - - Intent sendIntent = createSendUriIntentWithPreview(uris); - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed())); - onView(withId(R.id.content_preview_filename)).check(matches(withText("app.pdf"))); - onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed())); - } - - - @Test - public void moreThanOneVisibleFilePreview() throws InterruptedException { - Uri uri = Uri.parse("content://com.android.frameworks.coretests/app.pdf"); - - ArrayList<Uri> uris = new ArrayList<>(); - uris.add(uri); - uris.add(uri); - uris.add(uri); - - Intent sendIntent = createSendUriIntentWithPreview(uris); - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed())); - onView(withId(R.id.content_preview_filename)).check(matches(withText("app.pdf"))); - onView(withId(R.id.content_preview_more_files)).check(matches(isDisplayed())); - onView(withId(R.id.content_preview_more_files)).check(matches(withText("+ 2 more files"))); - onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed())); - } - - @Test - public void contentProviderThrowSecurityException() throws InterruptedException { - Uri uri = Uri.parse("content://com.android.frameworks.coretests/app.pdf"); - - ArrayList<Uri> uris = new ArrayList<>(); - uris.add(uri); - - Intent sendIntent = createSendUriIntentWithPreview(uris); - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - setupResolverControllers(resolvedComponentInfos); - - ChooserActivityOverrideData.getInstance().resolverForceException = true; - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed())); - onView(withId(R.id.content_preview_filename)).check(matches(withText("app.pdf"))); - onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed())); - } - - @Test - public void contentProviderReturnsNoColumns() throws InterruptedException { - Uri uri = Uri.parse("content://com.android.frameworks.coretests/app.pdf"); - - ArrayList<Uri> uris = new ArrayList<>(); - uris.add(uri); - uris.add(uri); - - Intent sendIntent = createSendUriIntentWithPreview(uris); - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - setupResolverControllers(resolvedComponentInfos); - - Cursor cursor = mock(Cursor.class); - when(cursor.getCount()).thenReturn(1); - Mockito.doNothing().when(cursor).close(); - when(cursor.moveToFirst()).thenReturn(true); - when(cursor.getColumnIndex(Mockito.anyString())).thenReturn(-1); - - ChooserActivityOverrideData.getInstance().resolverCursor = cursor; - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed())); - onView(withId(R.id.content_preview_filename)).check(matches(withText("app.pdf"))); - onView(withId(R.id.content_preview_more_files)).check(matches(isDisplayed())); - onView(withId(R.id.content_preview_more_files)).check(matches(withText("+ 1 more file"))); - onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed())); - } - - @Test - public void testGetBaseScore() { - final float testBaseScore = 0.89f; - - Intent sendIntent = createSendTextIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - - when( - ChooserActivityOverrideData - .getInstance() - .resolverListController - .getScore(Mockito.isA(DisplayResolveInfo.class))) - .thenReturn(testBaseScore); - - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - final DisplayResolveInfo testDri = - activity.createTestDisplayResolveInfo( - sendIntent, - ResolverDataProvider.createResolveInfo(3, 0, PERSONAL_USER_HANDLE), - "testLabel", - "testInfo", - sendIntent); - final ChooserListAdapter adapter = activity.getAdapter(); - - assertThat(adapter.getBaseScore(null, 0), is(CALLER_TARGET_SCORE_BOOST)); - assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_DEFAULT), is(testBaseScore)); - assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_CHOOSER_TARGET), is(testBaseScore)); - assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE), - is(testBaseScore * SHORTCUT_TARGET_SCORE_BOOST)); - assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER), - is(testBaseScore * SHORTCUT_TARGET_SCORE_BOOST)); - } - - // This test is too long and too slow and should not be taken as an example for future tests. - @Test - public void testDirectTargetSelectionLogging() { - Intent sendIntent = createSendTextIntent(); - // We need app targets for direct targets to get displayed - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - setupResolverControllers(resolvedComponentInfos); - - // create test shortcut loader factory, remember loaders and their callbacks - SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders = - createShortcutLoaderFactory(); - - // Start activity - ChooserWrapperActivity activity = - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - // verify that ShortcutLoader was queried - ArgumentCaptor<DisplayResolveInfo[]> appTargets = - ArgumentCaptor.forClass(DisplayResolveInfo[].class); - verify(shortcutLoaders.get(0).first, times(1)).updateAppTargets(appTargets.capture()); - - // send shortcuts - assertThat( - "Wrong number of app targets", - appTargets.getValue().length, - is(resolvedComponentInfos.size())); - List<ChooserTarget> serviceTargets = createDirectShareTargets(1, ""); - ShortcutLoader.Result result = new ShortcutLoader.Result( - true, - appTargets.getValue(), - new ShortcutLoader.ShortcutResultInfo[] { - new ShortcutLoader.ShortcutResultInfo( - appTargets.getValue()[0], - serviceTargets - ) - }, - new HashMap<>(), - new HashMap<>() - ); - activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result)); - waitForIdle(); - - final ChooserListAdapter activeAdapter = activity.getAdapter(); - assertThat( - "Chooser should have 3 targets (2 apps, 1 direct)", - activeAdapter.getCount(), - is(3)); - assertThat( - "Chooser should have exactly one selectable direct target", - activeAdapter.getSelectableServiceTargetCount(), - is(1)); - assertThat( - "The resolver info must match the resolver info used to create the target", - activeAdapter.getItem(0).getResolveInfo(), - is(resolvedComponentInfos.get(0).getResolveInfoAt(0))); - - // Click on the direct target - String name = serviceTargets.get(0).getTitle().toString(); - onView(withText(name)) - .perform(click()); - waitForIdle(); - - FakeEventLog eventLog = getEventLog(activity); - assertThat(eventLog.getShareTargetSelected()).hasSize(1); - FakeEventLog.ShareTargetSelected call = eventLog.getShareTargetSelected().get(0); - assertThat(call.getTargetType()).isEqualTo(EventLog.SELECTION_TYPE_SERVICE); - assertThat(call.getDirectTargetAlsoRanked()).isEqualTo(-1); - var hashResult = call.getDirectTargetHashed(); - var hash = hashResult == null ? "" : hashResult.hashedString; - assertWithMessage("Hash is not predictable but must be obfuscated") - .that(hash).isNotEqualTo(name); - } - - // This test is too long and too slow and should not be taken as an example for future tests. - @Test - public void testDirectTargetLoggingWithRankedAppTarget() { - Intent sendIntent = createSendTextIntent(); - // We need app targets for direct targets to get displayed - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - setupResolverControllers(resolvedComponentInfos); - - // create test shortcut loader factory, remember loaders and their callbacks - SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders = - createShortcutLoaderFactory(); - - // Start activity - ChooserWrapperActivity activity = - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - // verify that ShortcutLoader was queried - ArgumentCaptor<DisplayResolveInfo[]> appTargets = - ArgumentCaptor.forClass(DisplayResolveInfo[].class); - verify(shortcutLoaders.get(0).first, times(1)).updateAppTargets(appTargets.capture()); - - // send shortcuts - assertThat( - "Wrong number of app targets", - appTargets.getValue().length, - is(resolvedComponentInfos.size())); - List<ChooserTarget> serviceTargets = createDirectShareTargets( - 1, - resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName); - ShortcutLoader.Result result = new ShortcutLoader.Result( - true, - appTargets.getValue(), - new ShortcutLoader.ShortcutResultInfo[] { - new ShortcutLoader.ShortcutResultInfo( - appTargets.getValue()[0], - serviceTargets - ) - }, - new HashMap<>(), - new HashMap<>() - ); - activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result)); - waitForIdle(); - - final ChooserListAdapter activeAdapter = activity.getAdapter(); - assertThat( - "Chooser should have 3 targets (2 apps, 1 direct)", - activeAdapter.getCount(), - is(3)); - assertThat( - "Chooser should have exactly one selectable direct target", - activeAdapter.getSelectableServiceTargetCount(), - is(1)); - assertThat( - "The resolver info must match the resolver info used to create the target", - activeAdapter.getItem(0).getResolveInfo(), - is(resolvedComponentInfos.get(0).getResolveInfoAt(0))); - - // Click on the direct target - String name = serviceTargets.get(0).getTitle().toString(); - onView(withText(name)) - .perform(click()); - waitForIdle(); - - FakeEventLog eventLog = getEventLog(activity); - assertThat(eventLog.getShareTargetSelected()).hasSize(1); - FakeEventLog.ShareTargetSelected call = eventLog.getShareTargetSelected().get(0); - - assertThat(call.getTargetType()).isEqualTo(EventLog.SELECTION_TYPE_SERVICE); - assertThat(call.getDirectTargetAlsoRanked()).isEqualTo(0); - } - - @Test - public void testShortcutTargetWithApplyAppLimits() { - // Set up resources - Resources resources = Mockito.spy( - InstrumentationRegistry.getInstrumentation().getContext().getResources()); - ChooserActivityOverrideData.getInstance().resources = resources; - doReturn(1).when(resources).getInteger(R.integer.config_maxShortcutTargetsPerApp); - Intent sendIntent = createSendTextIntent(); - // We need app targets for direct targets to get displayed - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - setupResolverControllers(resolvedComponentInfos); - - // create test shortcut loader factory, remember loaders and their callbacks - SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders = - createShortcutLoaderFactory(); - - // Start activity - final IChooserWrapper activity = (IChooserWrapper) mActivityRule - .launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - // verify that ShortcutLoader was queried - ArgumentCaptor<DisplayResolveInfo[]> appTargets = - ArgumentCaptor.forClass(DisplayResolveInfo[].class); - verify(shortcutLoaders.get(0).first, times(1)).updateAppTargets(appTargets.capture()); - - // send shortcuts - assertThat( - "Wrong number of app targets", - appTargets.getValue().length, - is(resolvedComponentInfos.size())); - List<ChooserTarget> serviceTargets = createDirectShareTargets( - 2, - resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName); - ShortcutLoader.Result result = new ShortcutLoader.Result( - true, - appTargets.getValue(), - new ShortcutLoader.ShortcutResultInfo[] { - new ShortcutLoader.ShortcutResultInfo( - appTargets.getValue()[0], - serviceTargets - ) - }, - new HashMap<>(), - new HashMap<>() - ); - activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result)); - waitForIdle(); - - final ChooserListAdapter activeAdapter = activity.getAdapter(); - assertThat( - "Chooser should have 3 targets (2 apps, 1 direct)", - activeAdapter.getCount(), - is(3)); - assertThat( - "Chooser should have exactly one selectable direct target", - activeAdapter.getSelectableServiceTargetCount(), - is(1)); - assertThat( - "The resolver info must match the resolver info used to create the target", - activeAdapter.getItem(0).getResolveInfo(), - is(resolvedComponentInfos.get(0).getResolveInfoAt(0))); - assertThat( - "The display label must match", - activeAdapter.getItem(0).getDisplayLabel(), - is("testTitle0")); - } - - @Test - public void testShortcutTargetWithoutApplyAppLimits() { - setDeviceConfigProperty( - SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI, - Boolean.toString(false)); - // Set up resources - Resources resources = Mockito.spy( - InstrumentationRegistry.getInstrumentation().getContext().getResources()); - ChooserActivityOverrideData.getInstance().resources = resources; - doReturn(1).when(resources).getInteger(R.integer.config_maxShortcutTargetsPerApp); - Intent sendIntent = createSendTextIntent(); - // We need app targets for direct targets to get displayed - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - setupResolverControllers(resolvedComponentInfos); - - // create test shortcut loader factory, remember loaders and their callbacks - SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders = - createShortcutLoaderFactory(); - - // Start activity - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - // verify that ShortcutLoader was queried - ArgumentCaptor<DisplayResolveInfo[]> appTargets = - ArgumentCaptor.forClass(DisplayResolveInfo[].class); - verify(shortcutLoaders.get(0).first, times(1)).updateAppTargets(appTargets.capture()); - - // send shortcuts - assertThat( - "Wrong number of app targets", - appTargets.getValue().length, - is(resolvedComponentInfos.size())); - List<ChooserTarget> serviceTargets = createDirectShareTargets( - 2, - resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName); - ShortcutLoader.Result result = new ShortcutLoader.Result( - true, - appTargets.getValue(), - new ShortcutLoader.ShortcutResultInfo[] { - new ShortcutLoader.ShortcutResultInfo( - appTargets.getValue()[0], - serviceTargets - ) - }, - new HashMap<>(), - new HashMap<>() - ); - activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result)); - waitForIdle(); - - final ChooserListAdapter activeAdapter = activity.getAdapter(); - assertThat( - "Chooser should have 4 targets (2 apps, 2 direct)", - activeAdapter.getCount(), - is(4)); - assertThat( - "Chooser should have exactly two selectable direct target", - activeAdapter.getSelectableServiceTargetCount(), - is(2)); - assertThat( - "The resolver info must match the resolver info used to create the target", - activeAdapter.getItem(0).getResolveInfo(), - is(resolvedComponentInfos.get(0).getResolveInfoAt(0))); - assertThat( - "The display label must match", - activeAdapter.getItem(0).getDisplayLabel(), - is("testTitle0")); - assertThat( - "The display label must match", - activeAdapter.getItem(1).getDisplayLabel(), - is("testTitle1")); - } - - @Test - public void testLaunchWithCallerProvidedTarget() { - setDeviceConfigProperty( - SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI, - Boolean.toString(false)); - // Set up resources - Resources resources = Mockito.spy( - InstrumentationRegistry.getInstrumentation().getContext().getResources()); - ChooserActivityOverrideData.getInstance().resources = resources; - doReturn(1).when(resources).getInteger(R.integer.config_maxShortcutTargetsPerApp); - - // We need app targets for direct targets to get displayed - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - setupResolverControllers(resolvedComponentInfos, resolvedComponentInfos); - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - - // set caller-provided target - Intent chooserIntent = Intent.createChooser(createSendTextIntent(), null); - String callerTargetLabel = "Caller Target"; - ChooserTarget[] targets = new ChooserTarget[] { - new ChooserTarget( - callerTargetLabel, - Icon.createWithBitmap(createBitmap()), - 0.1f, - resolvedComponentInfos.get(0).name, - new Bundle()) - }; - chooserIntent.putExtra(Intent.EXTRA_CHOOSER_TARGETS, targets); - - // create test shortcut loader factory, remember loaders and their callbacks - SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders = - createShortcutLoaderFactory(); - - // Start activity - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(chooserIntent); - waitForIdle(); - - // verify that ShortcutLoader was queried - ArgumentCaptor<DisplayResolveInfo[]> appTargets = - ArgumentCaptor.forClass(DisplayResolveInfo[].class); - verify(shortcutLoaders.get(0).first, times(1)).updateAppTargets(appTargets.capture()); - - // send shortcuts - assertThat( - "Wrong number of app targets", - appTargets.getValue().length, - is(resolvedComponentInfos.size())); - ShortcutLoader.Result result = new ShortcutLoader.Result( - true, - appTargets.getValue(), - new ShortcutLoader.ShortcutResultInfo[0], - new HashMap<>(), - new HashMap<>()); - activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result)); - waitForIdle(); - - final ChooserListAdapter activeAdapter = activity.getAdapter(); - assertThat( - "Chooser should have 3 targets (2 apps, 1 direct)", - activeAdapter.getCount(), - is(3)); - assertThat( - "Chooser should have exactly two selectable direct target", - activeAdapter.getSelectableServiceTargetCount(), - is(1)); - assertThat( - "The display label must match", - activeAdapter.getItem(0).getDisplayLabel(), - is(callerTargetLabel)); - - // Switch to work profile and ensure that the target *doesn't* show up there. - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - - for (int i = 0; i < activity.getWorkListAdapter().getCount(); i++) { - assertThat( - "Chooser target should not show up in opposite profile", - activity.getWorkListAdapter().getItem(i).getDisplayLabel(), - not(callerTargetLabel)); - } - } - - @Test - public void testLaunchWithCustomAction() throws InterruptedException { - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - setupResolverControllers(resolvedComponentInfos); - - Context testContext = InstrumentationRegistry.getInstrumentation().getContext(); - final String customActionLabel = "Custom Action"; - final String testAction = "test-broadcast-receiver-action"; - Intent chooserIntent = Intent.createChooser(createSendTextIntent(), null); - chooserIntent.putExtra( - Intent.EXTRA_CHOOSER_CUSTOM_ACTIONS, - new ChooserAction[] { - new ChooserAction.Builder( - Icon.createWithResource("", Resources.ID_NULL), - customActionLabel, - PendingIntent.getBroadcast( - testContext, - 123, - new Intent(testAction), - PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT)) - .build() - }); - // Start activity - mActivityRule.launchActivity(chooserIntent); - waitForIdle(); - - final CountDownLatch broadcastInvoked = new CountDownLatch(1); - BroadcastReceiver testReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - broadcastInvoked.countDown(); - } - }; - testContext.registerReceiver(testReceiver, new IntentFilter(testAction), - Context.RECEIVER_EXPORTED); - - try { - onView(withText(customActionLabel)).perform(click()); - assertTrue("Timeout waiting for broadcast", - broadcastInvoked.await(5000, TimeUnit.MILLISECONDS)); - } finally { - testContext.unregisterReceiver(testReceiver); - } - } - - @Test - public void testLaunchWithShareModification() throws InterruptedException { - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - setupResolverControllers(resolvedComponentInfos); - - Context testContext = InstrumentationRegistry.getInstrumentation().getContext(); - final String modifyShareAction = "test-broadcast-receiver-action"; - Intent chooserIntent = Intent.createChooser(createSendTextIntent(), null); - String label = "modify share"; - PendingIntent pendingIntent = PendingIntent.getBroadcast( - testContext, - 123, - new Intent(modifyShareAction), - PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT); - ChooserAction action = new ChooserAction.Builder(Icon.createWithBitmap( - createBitmap()), label, pendingIntent).build(); - chooserIntent.putExtra( - Intent.EXTRA_CHOOSER_MODIFY_SHARE_ACTION, - action); - // Start activity - mActivityRule.launchActivity(chooserIntent); - waitForIdle(); - - final CountDownLatch broadcastInvoked = new CountDownLatch(1); - BroadcastReceiver testReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - broadcastInvoked.countDown(); - } - }; - testContext.registerReceiver(testReceiver, new IntentFilter(modifyShareAction), - Context.RECEIVER_EXPORTED); - - try { - onView(withText(label)).perform(click()); - assertTrue("Timeout waiting for broadcast", - broadcastInvoked.await(5000, TimeUnit.MILLISECONDS)); - - } finally { - testContext.unregisterReceiver(testReceiver); - } - } - - @Test - public void testUpdateMaxTargetsPerRow_columnCountIsUpdated() throws InterruptedException { - updateMaxTargetsPerRowResource(/* targetsPerRow= */ 4); - givenAppTargets(/* appCount= */ 16); - Intent sendIntent = createSendTextIntent(); - final ChooserActivity activity = - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - - updateMaxTargetsPerRowResource(/* targetsPerRow= */ 6); - InstrumentationRegistry.getInstrumentation() - .runOnMainSync(() -> activity.onConfigurationChanged( - InstrumentationRegistry.getInstrumentation() - .getContext().getResources().getConfiguration())); - - waitForIdle(); - onView(withId(com.android.internal.R.id.resolver_list)) - .check(matches(withGridColumnCount(6))); - } - - // This test is too long and too slow and should not be taken as an example for future tests. - @Test @Ignore - public void testDirectTargetLoggingWithAppTargetNotRankedPortrait() - throws InterruptedException { - testDirectTargetLoggingWithAppTargetNotRanked(Configuration.ORIENTATION_PORTRAIT, 4); - } - - @Test @Ignore - public void testDirectTargetLoggingWithAppTargetNotRankedLandscape() - throws InterruptedException { - testDirectTargetLoggingWithAppTargetNotRanked(Configuration.ORIENTATION_LANDSCAPE, 8); - } - - private void testDirectTargetLoggingWithAppTargetNotRanked( - int orientation, int appTargetsExpected) { - Configuration configuration = - new Configuration(InstrumentationRegistry.getInstrumentation().getContext() - .getResources().getConfiguration()); - configuration.orientation = orientation; - - Resources resources = Mockito.spy( - InstrumentationRegistry.getInstrumentation().getContext().getResources()); - ChooserActivityOverrideData.getInstance().resources = resources; - doReturn(configuration).when(resources).getConfiguration(); - - Intent sendIntent = createSendTextIntent(); - // We need app targets for direct targets to get displayed - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(15); - setupResolverControllers(resolvedComponentInfos); - - // Create direct share target - List<ChooserTarget> serviceTargets = createDirectShareTargets(1, - resolvedComponentInfos.get(14).getResolveInfoAt(0).activityInfo.packageName); - ResolveInfo ri = ResolverDataProvider.createResolveInfo(16, 0, PERSONAL_USER_HANDLE); - - // Start activity - ChooserWrapperActivity activity = - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - // Insert the direct share target - Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos = new HashMap<>(); - directShareToShortcutInfos.put(serviceTargets.get(0), null); - InstrumentationRegistry.getInstrumentation().runOnMainSync( - () -> activity.getAdapter().addServiceResults( - activity.createTestDisplayResolveInfo(sendIntent, - ri, - "testLabel", - "testInfo", - sendIntent), - serviceTargets, - TARGET_TYPE_CHOOSER_TARGET, - directShareToShortcutInfos, - /* directShareToAppTargets */ null) - ); - - assertThat( - String.format("Chooser should have %d targets (%d apps, 1 direct, 15 A-Z)", - appTargetsExpected + 16, appTargetsExpected), - activity.getAdapter().getCount(), is(appTargetsExpected + 16)); - assertThat("Chooser should have exactly one selectable direct target", - activity.getAdapter().getSelectableServiceTargetCount(), is(1)); - assertThat("The resolver info must match the resolver info used to create the target", - activity.getAdapter().getItem(0).getResolveInfo(), is(ri)); - - // Click on the direct target - String name = serviceTargets.get(0).getTitle().toString(); - onView(withText(name)) - .perform(click()); - waitForIdle(); - - FakeEventLog eventLog = getEventLog(activity); - var invocations = eventLog.getShareTargetSelected(); - assertWithMessage("Only one ShareTargetSelected event logged") - .that(invocations).hasSize(1); - FakeEventLog.ShareTargetSelected call = invocations.get(0); - assertWithMessage("targetType should be SELECTION_TYPE_SERVICE") - .that(call.getTargetType()).isEqualTo(EventLog.SELECTION_TYPE_SERVICE); - assertWithMessage( - "The packages shouldn't match for app target and direct target") - .that(call.getDirectTargetAlsoRanked()).isEqualTo(-1); - } - - @Test - public void testWorkTab_displayedWhenWorkProfileUserAvailable() { - Intent sendIntent = createSendTextIntent(); - sendIntent.setType(TEST_MIME_TYPE); - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); - waitForIdle(); - - onView(withId(android.R.id.tabs)).check(matches(isDisplayed())); - } - - @Test - public void testWorkTab_hiddenWhenWorkProfileUserNotAvailable() { - Intent sendIntent = createSendTextIntent(); - sendIntent.setType(TEST_MIME_TYPE); - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); - waitForIdle(); - - onView(withId(android.R.id.tabs)).check(matches(not(isDisplayed()))); - } - - @Test - public void testWorkTab_eachTabUsesExpectedAdapter() { - int personalProfileTargets = 3; - int otherProfileTargets = 1; - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile( - personalProfileTargets + otherProfileTargets, /* userID */ 10); - int workProfileTargets = 4; - List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest( - workProfileTargets); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendTextIntent(); - sendIntent.setType(TEST_MIME_TYPE); - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); - waitForIdle(); - - assertThat(activity.getCurrentUserHandle().getIdentifier(), is(0)); - onView(withText(R.string.resolver_work_tab)).perform(click()); - assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10)); - assertThat(activity.getPersonalListAdapter().getCount(), is(personalProfileTargets)); - assertThat(activity.getWorkListAdapter().getCount(), is(workProfileTargets)); - } - - @Test - public void testWorkTab_workProfileHasExpectedNumberOfTargets() throws InterruptedException { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - int workProfileTargets = 4; - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(workProfileTargets); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendTextIntent(); - sendIntent.setType(TEST_MIME_TYPE); - - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); - waitForIdle(); - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - - assertThat(activity.getWorkListAdapter().getCount(), is(workProfileTargets)); - } - - @Test @Ignore - public void testWorkTab_selectingWorkTabAppOpensAppInWorkProfile() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); - int workProfileTargets = 4; - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(workProfileTargets); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendTextIntent(); - sendIntent.setType(TEST_MIME_TYPE); - ResolveInfo[] chosen = new ResolveInfo[1]; - ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { - chosen[0] = targetInfo.getResolveInfo(); - return true; - }; - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); - waitForIdle(); - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - - onView(first(allOf( - withText(workResolvedComponentInfos.get(0) - .getResolveInfoAt(0).activityInfo.applicationInfo.name), - isDisplayed()))) - .perform(click()); - waitForIdle(); - assertThat(chosen[0], is(workResolvedComponentInfos.get(0).getResolveInfoAt(0))); - } - - @Test - public void testWorkTab_crossProfileIntentsDisabled_personalToWork_emptyStateShown() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - int workProfileTargets = 4; - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(workProfileTargets); - ChooserActivityOverrideData.getInstance().hasCrossProfileIntents = false; - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendTextIntent(); - sendIntent.setType(TEST_MIME_TYPE); - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); - waitForIdle(); - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - onView(withId(com.android.internal.R.id.contentPanel)) - .perform(swipeUp()); - - onView(withText(R.string.resolver_cross_profile_blocked)) - .check(matches(isDisplayed())); - } - - @Test - public void testWorkTab_workProfileDisabled_emptyStateShown() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - int workProfileTargets = 4; - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(workProfileTargets); - ChooserActivityOverrideData.getInstance().isQuietModeEnabled = true; - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendTextIntent(); - sendIntent.setType(TEST_MIME_TYPE); - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); - waitForIdle(); - onView(withId(com.android.internal.R.id.contentPanel)) - .perform(swipeUp()); - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - - onView(withText(R.string.resolver_turn_on_work_apps)) - .check(matches(isDisplayed())); - } - - @Test - public void testWorkTab_noWorkAppsAvailable_emptyStateShown() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(3); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(0); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendTextIntent(); - sendIntent.setType(TEST_MIME_TYPE); - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); - waitForIdle(); - onView(withId(com.android.internal.R.id.contentPanel)) - .perform(swipeUp()); - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - - onView(withText(R.string.resolver_no_work_apps_available)) - .check(matches(isDisplayed())); - } - - @Test - @RequiresFlagsEnabled(Flags.FLAG_SCROLLABLE_PREVIEW) - public void testWorkTab_previewIsScrollable() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(300); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(3); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - - Uri uri = createTestContentProviderUri("image/png", null); - - ArrayList<Uri> uris = new ArrayList<>(); - uris.add(uri); - - Intent sendIntent = createSendUriIntentWithPreview(uris); - ChooserActivityOverrideData.getInstance().imageLoader = - createImageLoader(uri, createWideBitmap()); - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "Scrollable preview test")); - waitForIdle(); - - onView(withId(com.android.intentresolver.R.id.scrollable_image_preview)) - .check(matches(isDisplayed())); - - onView(withId(com.android.internal.R.id.contentPanel)).perform(swipeUp()); - waitForIdle(); - - onView(withId(com.android.intentresolver.R.id.chooser_headline_row_container)) - .check(matches(isCompletelyDisplayed())); - onView(withId(com.android.intentresolver.R.id.headline)) - .check(matches(isDisplayed())); - onView(withId(com.android.intentresolver.R.id.scrollable_image_preview)) - .check(matches(not(isDisplayed()))); - } - - @Ignore // b/220067877 - @Test - public void testWorkTab_xProfileOff_noAppsAvailable_workOff_xProfileOffEmptyStateShown() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(3); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(0); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - ChooserActivityOverrideData.getInstance().isQuietModeEnabled = true; - ChooserActivityOverrideData.getInstance().hasCrossProfileIntents = false; - Intent sendIntent = createSendTextIntent(); - sendIntent.setType(TEST_MIME_TYPE); - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); - waitForIdle(); - onView(withId(com.android.internal.R.id.contentPanel)) - .perform(swipeUp()); - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - - onView(withText(R.string.resolver_cross_profile_blocked)) - .check(matches(isDisplayed())); - } - - @Test - public void testWorkTab_noAppsAvailable_workOff_noAppsAvailableEmptyStateShown() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(3); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(0); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - ChooserActivityOverrideData.getInstance().isQuietModeEnabled = true; - Intent sendIntent = createSendTextIntent(); - sendIntent.setType(TEST_MIME_TYPE); - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); - waitForIdle(); - onView(withId(com.android.internal.R.id.contentPanel)) - .perform(swipeUp()); - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - - onView(withText(R.string.resolver_no_work_apps_available)) - .check(matches(isDisplayed())); - } - - @Test @Ignore("b/222124533") - public void testAppTargetLogging() throws InterruptedException { - Intent sendIntent = createSendTextIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - // TODO(b/222124533): other test cases use a timeout to make sure that the UI is fully - // populated; without one, this test flakes. Ideally we should address the need for a - // timeout everywhere instead of introducing one to fix this particular test. - - assertThat(activity.getAdapter().getCount(), is(2)); - onView(withId(com.android.internal.R.id.profile_button)).check(doesNotExist()); - - ResolveInfo[] chosen = new ResolveInfo[1]; - ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { - chosen[0] = targetInfo.getResolveInfo(); - return true; - }; - - ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0); - onView(withText(toChoose.activityInfo.name)) - .perform(click()); - waitForIdle(); - - // TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events. - } - - @Test - public void testDirectTargetLogging() { - Intent sendIntent = createSendTextIntent(); - // We need app targets for direct targets to get displayed - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - setupResolverControllers(resolvedComponentInfos); - - // create test shortcut loader factory, remember loaders and their callbacks - SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders = - new SparseArray<>(); - ChooserActivityOverrideData.getInstance().shortcutLoaderFactory = - (userHandle, callback) -> { - Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>> pair = - new Pair<>(mock(ShortcutLoader.class), callback); - shortcutLoaders.put(userHandle.getIdentifier(), pair); - return pair.first; - }; - - // Start activity - ChooserWrapperActivity activity = - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - // verify that ShortcutLoader was queried - ArgumentCaptor<DisplayResolveInfo[]> appTargets = - ArgumentCaptor.forClass(DisplayResolveInfo[].class); - verify(shortcutLoaders.get(0).first, times(1)) - .updateAppTargets(appTargets.capture()); - - // send shortcuts - assertThat( - "Wrong number of app targets", - appTargets.getValue().length, - is(resolvedComponentInfos.size())); - List<ChooserTarget> serviceTargets = createDirectShareTargets(1, - resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName); - ShortcutLoader.Result result = new ShortcutLoader.Result( - // TODO: test another value as well - false, - appTargets.getValue(), - new ShortcutLoader.ShortcutResultInfo[] { - new ShortcutLoader.ShortcutResultInfo( - appTargets.getValue()[0], - serviceTargets - ) - }, - new HashMap<>(), - new HashMap<>() - ); - activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result)); - waitForIdle(); - - assertThat("Chooser should have 3 targets (2 apps, 1 direct)", - activity.getAdapter().getCount(), is(3)); - assertThat("Chooser should have exactly one selectable direct target", - activity.getAdapter().getSelectableServiceTargetCount(), is(1)); - assertThat( - "The resolver info must match the resolver info used to create the target", - activity.getAdapter().getItem(0).getResolveInfo(), - is(resolvedComponentInfos.get(0).getResolveInfoAt(0))); - - // Click on the direct target - String name = serviceTargets.get(0).getTitle().toString(); - onView(withText(name)) - .perform(click()); - waitForIdle(); - - FakeEventLog eventLog = getEventLog(activity); - assertThat(eventLog.getShareTargetSelected()).hasSize(1); - FakeEventLog.ShareTargetSelected call = eventLog.getShareTargetSelected().get(0); - assertThat(call.getTargetType()).isEqualTo(EventLog.SELECTION_TYPE_SERVICE); - } - - @Test - public void testDirectTargetPinningDialog() { - Intent sendIntent = createSendTextIntent(); - // We need app targets for direct targets to get displayed - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - setupResolverControllers(resolvedComponentInfos); - - // create test shortcut loader factory, remember loaders and their callbacks - SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders = - new SparseArray<>(); - ChooserActivityOverrideData.getInstance().shortcutLoaderFactory = - (userHandle, callback) -> { - Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>> pair = - new Pair<>(mock(ShortcutLoader.class), callback); - shortcutLoaders.put(userHandle.getIdentifier(), pair); - return pair.first; - }; - - // Start activity - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - // verify that ShortcutLoader was queried - ArgumentCaptor<DisplayResolveInfo[]> appTargets = - ArgumentCaptor.forClass(DisplayResolveInfo[].class); - verify(shortcutLoaders.get(0).first, times(1)) - .updateAppTargets(appTargets.capture()); - - // send shortcuts - List<ChooserTarget> serviceTargets = createDirectShareTargets( - 1, - resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName); - ShortcutLoader.Result result = new ShortcutLoader.Result( - // TODO: test another value as well - false, - appTargets.getValue(), - new ShortcutLoader.ShortcutResultInfo[] { - new ShortcutLoader.ShortcutResultInfo( - appTargets.getValue()[0], - serviceTargets - ) - }, - new HashMap<>(), - new HashMap<>() - ); - activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result)); - waitForIdle(); - - // Long-click on the direct target - String name = serviceTargets.get(0).getTitle().toString(); - onView(withText(name)).perform(longClick()); - waitForIdle(); - - onView(withId(R.id.chooser_dialog_content)).check(matches(isDisplayed())); - } - - @Test @Ignore - public void testEmptyDirectRowLogging() throws InterruptedException { - Intent sendIntent = createSendTextIntent(); - // We need app targets for direct targets to get displayed - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - setupResolverControllers(resolvedComponentInfos); - - // Start activity - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - - // Thread.sleep shouldn't be a thing in an integration test but it's - // necessary here because of the way the code is structured - Thread.sleep(3000); - - assertThat("Chooser should have 2 app targets", - activity.getAdapter().getCount(), is(2)); - assertThat("Chooser should have no direct targets", - activity.getAdapter().getSelectableServiceTargetCount(), is(0)); - - // TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events. - } - - @Ignore // b/220067877 - @Test - public void testCopyTextToClipboardLogging() throws Exception { - Intent sendIntent = createSendTextIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - onView(withId(com.android.internal.R.id.chooser_copy_button)).check(matches(isDisplayed())); - onView(withId(com.android.internal.R.id.chooser_copy_button)).perform(click()); - - // TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events. - } - - @Test @Ignore("b/222124533") - public void testSwitchProfileLogging() throws InterruptedException { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - int workProfileTargets = 4; - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(workProfileTargets); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendTextIntent(); - sendIntent.setType(TEST_MIME_TYPE); - - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); - waitForIdle(); - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - onView(withText(R.string.resolver_personal_tab)).perform(click()); - waitForIdle(); - - // TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events. - } - - @Test - public void testWorkTab_onePersonalTarget_emptyStateOnWorkTarget_doesNotAutoLaunch() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - int workProfileTargets = 4; - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(workProfileTargets); - ChooserActivityOverrideData.getInstance().hasCrossProfileIntents = false; - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendTextIntent(); - ResolveInfo[] chosen = new ResolveInfo[1]; - ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { - chosen[0] = targetInfo.getResolveInfo(); - return true; - }; - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "Test")); - waitForIdle(); - - assertNull(chosen[0]); - } - - @Test - public void testOneInitialIntent_noAutolaunch() { - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(1); - setupResolverControllers(personalResolvedComponentInfos); - Intent chooserIntent = createChooserIntent(createSendTextIntent(), - new Intent[] {new Intent("action.fake")}); - ResolveInfo[] chosen = new ResolveInfo[1]; - ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { - chosen[0] = targetInfo.getResolveInfo(); - return true; - }; - ChooserActivityOverrideData.getInstance().packageManager = mock(PackageManager.class); - ResolveInfo ri = createFakeResolveInfo(); - when( - ChooserActivityOverrideData - .getInstance().packageManager - .resolveActivity(any(Intent.class), any())) - .thenReturn(ri); - waitForIdle(); - - IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(chooserIntent); - waitForIdle(); - - assertNull(chosen[0]); - assertThat(activity - .getPersonalListAdapter().getCallerTargetCount(), is(1)); - } - - @Test - public void testWorkTab_withInitialIntents_workTabDoesNotIncludePersonalInitialIntents() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - int workProfileTargets = 1; - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(workProfileTargets); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent[] initialIntents = { - new Intent("action.fake1"), - new Intent("action.fake2") - }; - Intent chooserIntent = createChooserIntent(createSendTextIntent(), initialIntents); - ChooserActivityOverrideData.getInstance().packageManager = mock(PackageManager.class); - when( - ChooserActivityOverrideData - .getInstance() - .packageManager - .resolveActivity(any(Intent.class), any())) - .thenReturn(createFakeResolveInfo()); - waitForIdle(); - - IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(chooserIntent); - waitForIdle(); - - assertThat(activity.getPersonalListAdapter().getCallerTargetCount(), is(2)); - assertThat(activity.getWorkListAdapter().getCallerTargetCount(), is(0)); - } - - @Test - public void testWorkTab_xProfileIntentsDisabled_personalToWork_nonSendIntent_emptyStateShown() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - int workProfileTargets = 4; - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(workProfileTargets); - ChooserActivityOverrideData.getInstance().hasCrossProfileIntents = false; - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent[] initialIntents = { - new Intent("action.fake1"), - new Intent("action.fake2") - }; - Intent chooserIntent = createChooserIntent(new Intent(), initialIntents); - ChooserActivityOverrideData.getInstance().packageManager = mock(PackageManager.class); - when( - ChooserActivityOverrideData - .getInstance() - .packageManager - .resolveActivity(any(Intent.class), any())) - .thenReturn(createFakeResolveInfo()); - - mActivityRule.launchActivity(chooserIntent); - waitForIdle(); - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - onView(withId(com.android.internal.R.id.contentPanel)) - .perform(swipeUp()); - - onView(withText(R.string.resolver_cross_profile_blocked)) - .check(matches(isDisplayed())); - } - - @Test - public void testWorkTab_noWorkAppsAvailable_nonSendIntent_emptyStateShown() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(3); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(0); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent[] initialIntents = { - new Intent("action.fake1"), - new Intent("action.fake2") - }; - Intent chooserIntent = createChooserIntent(new Intent(), initialIntents); - ChooserActivityOverrideData.getInstance().packageManager = mock(PackageManager.class); - when( - ChooserActivityOverrideData - .getInstance() - .packageManager - .resolveActivity(any(Intent.class), any())) - .thenReturn(createFakeResolveInfo()); - - mActivityRule.launchActivity(chooserIntent); - waitForIdle(); - onView(withId(com.android.internal.R.id.contentPanel)) - .perform(swipeUp()); - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - - onView(withText(R.string.resolver_no_work_apps_available)) - .check(matches(isDisplayed())); - } - - @Test - public void testDeduplicateCallerTargetRankedTarget() { - // Create 4 ranked app targets. - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(4); - setupResolverControllers(personalResolvedComponentInfos); - // Create caller target which is duplicate with one of app targets - Intent chooserIntent = createChooserIntent(createSendTextIntent(), - new Intent[] {new Intent("action.fake")}); - ChooserActivityOverrideData.getInstance().packageManager = mock(PackageManager.class); - ResolveInfo ri = ResolverDataProvider.createResolveInfo(0, - UserHandle.USER_CURRENT, PERSONAL_USER_HANDLE); - when( - ChooserActivityOverrideData - .getInstance() - .packageManager - .resolveActivity(any(Intent.class), any())) - .thenReturn(ri); - waitForIdle(); - - IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(chooserIntent); - waitForIdle(); - - // Total 4 targets (1 caller target, 3 ranked targets) - assertThat(activity.getAdapter().getCount(), is(4)); - assertThat(activity.getAdapter().getCallerTargetCount(), is(1)); - assertThat(activity.getAdapter().getRankedTargetCount(), is(3)); - } - - @Test - public void test_query_shortcut_loader_for_the_selected_tab() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(3); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - ShortcutLoader personalProfileShortcutLoader = mock(ShortcutLoader.class); - ShortcutLoader workProfileShortcutLoader = mock(ShortcutLoader.class); - final SparseArray<ShortcutLoader> shortcutLoaders = new SparseArray<>(); - shortcutLoaders.put(0, personalProfileShortcutLoader); - shortcutLoaders.put(10, workProfileShortcutLoader); - ChooserActivityOverrideData.getInstance().shortcutLoaderFactory = - (userHandle, callback) -> shortcutLoaders.get(userHandle.getIdentifier(), null); - Intent sendIntent = createSendTextIntent(); - sendIntent.setType(TEST_MIME_TYPE); - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); - waitForIdle(); - onView(withId(com.android.internal.R.id.contentPanel)) - .perform(swipeUp()); - waitForIdle(); - - verify(personalProfileShortcutLoader, times(1)).updateAppTargets(any()); - - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - - verify(workProfileShortcutLoader, times(1)).updateAppTargets(any()); - } - - @Test - public void testClonedProfilePresent_personalAdapterIsSetWithPersonalProfile() { - // enable cloneProfile - markOtherProfileAvailability(/* workAvailable= */ false, /* cloneAvailable= */ true); - List<ResolvedComponentInfo> resolvedComponentInfos = - createResolvedComponentsWithCloneProfileForTest( - 3, - PERSONAL_USER_HANDLE, - CLONE_PROFILE_USER_HANDLE); - setupResolverControllers(resolvedComponentInfos); - Intent sendIntent = createSendTextIntent(); - - final IChooserWrapper activity = (IChooserWrapper) mActivityRule - .launchActivity(Intent.createChooser(sendIntent, "personalProfileTest")); - waitForIdle(); - - assertThat(activity.getPersonalListAdapter().getUserHandle(), is(PERSONAL_USER_HANDLE)); - assertThat(activity.getAdapter().getCount(), is(3)); - } - - @Test - public void testClonedProfilePresent_personalTabUsesExpectedAdapter() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ true); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(3); - List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest( - 4); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendTextIntent(); - sendIntent.setType(TEST_MIME_TYPE); - - - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "multi tab test")); - waitForIdle(); - - assertThat(activity.getCurrentUserHandle(), is(PERSONAL_USER_HANDLE)); - } - - private Intent createChooserIntent(Intent intent, Intent[] initialIntents) { - Intent chooserIntent = new Intent(); - chooserIntent.setAction(Intent.ACTION_CHOOSER); - chooserIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending"); - chooserIntent.putExtra(Intent.EXTRA_TITLE, "some title"); - chooserIntent.putExtra(Intent.EXTRA_INTENT, intent); - chooserIntent.setType("text/plain"); - if (initialIntents != null) { - chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, initialIntents); - } - return chooserIntent; - } - - /* This is a "test of a test" to make sure that our inherited test class - * is successfully configured to operate on the unbundled-equivalent - * ChooserWrapperActivity. - * - * TODO: remove after unbundling is complete. - */ - @Test - public void testWrapperActivityHasExpectedConcreteType() { - final ChooserActivity activity = mActivityRule.launchActivity( - Intent.createChooser(new Intent("ACTION_FOO"), "foo")); - waitForIdle(); - assertThat(activity).isInstanceOf(ChooserWrapperActivity.class); - } - - private ResolveInfo createFakeResolveInfo() { - ResolveInfo ri = new ResolveInfo(); - ri.activityInfo = new ActivityInfo(); - ri.activityInfo.name = "FakeActivityName"; - ri.activityInfo.packageName = "fake.package.name"; - ri.activityInfo.applicationInfo = new ApplicationInfo(); - ri.activityInfo.applicationInfo.packageName = "fake.package.name"; - ri.userHandle = UserHandle.CURRENT; - return ri; - } - - private Intent createSendTextIntent() { - Intent sendIntent = new Intent(); - sendIntent.setAction(Intent.ACTION_SEND); - sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending"); - sendIntent.setType("text/plain"); - return sendIntent; - } - - private Intent createSendImageIntent(Uri imageThumbnail) { - Intent sendIntent = new Intent(); - sendIntent.setAction(Intent.ACTION_SEND); - sendIntent.putExtra(Intent.EXTRA_STREAM, imageThumbnail); - sendIntent.setType("image/png"); - if (imageThumbnail != null) { - ClipData.Item clipItem = new ClipData.Item(imageThumbnail); - sendIntent.setClipData(new ClipData("Clip Label", new String[]{"image/png"}, clipItem)); - } - - return sendIntent; - } - - private Uri createTestContentProviderUri( - @Nullable String mimeType, @Nullable String streamType) { - return createTestContentProviderUri(mimeType, streamType, 0); - } - - private Uri createTestContentProviderUri( - @Nullable String mimeType, @Nullable String streamType, long streamTypeTimeout) { - String packageName = - InstrumentationRegistry.getInstrumentation().getContext().getPackageName(); - Uri.Builder builder = Uri.parse("content://" + packageName + "/image.png") - .buildUpon(); - if (mimeType != null) { - builder.appendQueryParameter(TestContentProvider.PARAM_MIME_TYPE, mimeType); - } - if (streamType != null) { - builder.appendQueryParameter(TestContentProvider.PARAM_STREAM_TYPE, streamType); - } - if (streamTypeTimeout > 0) { - builder.appendQueryParameter( - TestContentProvider.PARAM_STREAM_TYPE_TIMEOUT, - Long.toString(streamTypeTimeout)); - } - return builder.build(); - } - - private Intent createSendTextIntentWithPreview(String title, Uri imageThumbnail) { - Intent sendIntent = new Intent(); - sendIntent.setAction(Intent.ACTION_SEND); - sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending"); - sendIntent.putExtra(Intent.EXTRA_TITLE, title); - if (imageThumbnail != null) { - ClipData.Item clipItem = new ClipData.Item(imageThumbnail); - sendIntent.setClipData(new ClipData("Clip Label", new String[]{"image/png"}, clipItem)); - } - - return sendIntent; - } - - private Intent createSendUriIntentWithPreview(ArrayList<Uri> uris) { - Intent sendIntent = new Intent(); - - if (uris.size() > 1) { - sendIntent.setAction(Intent.ACTION_SEND_MULTIPLE); - sendIntent.putExtra(Intent.EXTRA_STREAM, uris); - } else { - sendIntent.setAction(Intent.ACTION_SEND); - sendIntent.putExtra(Intent.EXTRA_STREAM, uris.get(0)); - } - - return sendIntent; - } - - private Intent createViewTextIntent() { - Intent viewIntent = new Intent(); - viewIntent.setAction(Intent.ACTION_VIEW); - viewIntent.putExtra(Intent.EXTRA_TEXT, "testing intent viewing"); - return viewIntent; - } - - private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults) { - List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); - for (int i = 0; i < numberOfResults; i++) { - infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, PERSONAL_USER_HANDLE)); - } - return infoList; - } - - private List<ResolvedComponentInfo> createResolvedComponentsWithCloneProfileForTest( - int numberOfResults, - UserHandle resolvedForPersonalUser, - UserHandle resolvedForClonedUser) { - List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); - for (int i = 0; i < 1; i++) { - infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, - resolvedForPersonalUser)); - } - for (int i = 1; i < numberOfResults; i++) { - infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, - resolvedForClonedUser)); - } - return infoList; - } - - private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile( - int numberOfResults) { - List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); - for (int i = 0; i < numberOfResults; i++) { - if (i == 0) { - infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, - PERSONAL_USER_HANDLE)); - } else { - infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, - PERSONAL_USER_HANDLE)); - } - } - return infoList; - } - - private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile( - int numberOfResults, int userId) { - List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); - for (int i = 0; i < numberOfResults; i++) { - if (i == 0) { - infoList.add( - ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId, - PERSONAL_USER_HANDLE)); - } else { - infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, - PERSONAL_USER_HANDLE)); - } - } - return infoList; - } - - private List<ResolvedComponentInfo> createResolvedComponentsForTestWithUserId( - int numberOfResults, int userId) { - List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); - for (int i = 0; i < numberOfResults; i++) { - infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId, - PERSONAL_USER_HANDLE)); - } - return infoList; - } - - private List<ChooserTarget> createDirectShareTargets(int numberOfResults, String packageName) { - Icon icon = Icon.createWithBitmap(createBitmap()); - String testTitle = "testTitle"; - List<ChooserTarget> targets = new ArrayList<>(); - for (int i = 0; i < numberOfResults; i++) { - ComponentName componentName; - if (packageName.isEmpty()) { - componentName = ResolverDataProvider.createComponentName(i); - } else { - componentName = new ComponentName(packageName, packageName + ".class"); - } - ChooserTarget tempTarget = new ChooserTarget( - testTitle + i, - icon, - (float) (1 - ((i + 1) / 10.0)), - componentName, - null); - targets.add(tempTarget); - } - return targets; - } - - private void waitForIdle() { - InstrumentationRegistry.getInstrumentation().waitForIdleSync(); - } - - private Bitmap createBitmap() { - return createBitmap(200, 200); - } - - private Bitmap createWideBitmap() { - return createWideBitmap(Color.RED); - } - - private Bitmap createWideBitmap(int bgColor) { - WindowManager windowManager = InstrumentationRegistry.getInstrumentation() - .getTargetContext() - .getSystemService(WindowManager.class); - int width = 3000; - if (windowManager != null) { - Rect bounds = windowManager.getMaximumWindowMetrics().getBounds(); - width = bounds.width() + 200; - } - return createBitmap(width, 100, bgColor); - } - - private Bitmap createBitmap(int width, int height) { - return createBitmap(width, height, Color.RED); - } - - private Bitmap createBitmap(int width, int height, int bgColor) { - Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - - Paint paint = new Paint(); - paint.setColor(bgColor); - paint.setStyle(Paint.Style.FILL); - canvas.drawPaint(paint); - - paint.setColor(Color.WHITE); - paint.setAntiAlias(true); - paint.setTextSize(14.f); - paint.setTextAlign(Paint.Align.CENTER); - canvas.drawText("Hi!", (width / 2.f), (height / 2.f), paint); - - return bitmap; - } - - private List<ShareShortcutInfo> createShortcuts(Context context) { - Intent testIntent = new Intent("TestIntent"); - - List<ShareShortcutInfo> shortcuts = new ArrayList<>(); - shortcuts.add(new ShareShortcutInfo( - new ShortcutInfo.Builder(context, "shortcut1") - .setIntent(testIntent).setShortLabel("label1").setRank(3).build(), // 0 2 - new ComponentName("package1", "class1"))); - shortcuts.add(new ShareShortcutInfo( - new ShortcutInfo.Builder(context, "shortcut2") - .setIntent(testIntent).setShortLabel("label2").setRank(7).build(), // 1 3 - new ComponentName("package2", "class2"))); - shortcuts.add(new ShareShortcutInfo( - new ShortcutInfo.Builder(context, "shortcut3") - .setIntent(testIntent).setShortLabel("label3").setRank(1).build(), // 2 0 - new ComponentName("package3", "class3"))); - shortcuts.add(new ShareShortcutInfo( - new ShortcutInfo.Builder(context, "shortcut4") - .setIntent(testIntent).setShortLabel("label4").setRank(3).build(), // 3 2 - new ComponentName("package4", "class4"))); - - return shortcuts; - } - - private void markOtherProfileAvailability(boolean workAvailable, boolean cloneAvailable) { - AnnotatedUserHandles.Builder handles = AnnotatedUserHandles.newBuilder(); - handles - .setUserIdOfCallingApp(1234) // Must be non-negative. - .setUserHandleSharesheetLaunchedAs(PERSONAL_USER_HANDLE) - .setPersonalProfileUserHandle(PERSONAL_USER_HANDLE); - if (workAvailable) { - handles.setWorkProfileUserHandle(WORK_PROFILE_USER_HANDLE); - } - if (cloneAvailable) { - handles.setCloneProfileUserHandle(CLONE_PROFILE_USER_HANDLE); - } - ChooserWrapperActivity.sOverrides.annotatedUserHandles = handles.build(); - } - - private void setupResolverControllers( - List<ResolvedComponentInfo> personalResolvedComponentInfos) { - setupResolverControllers(personalResolvedComponentInfos, new ArrayList<>()); - } - - private void setupResolverControllers( - List<ResolvedComponentInfo> personalResolvedComponentInfos, - List<ResolvedComponentInfo> workResolvedComponentInfos) { - when( - ChooserActivityOverrideData - .getInstance() - .resolverListController - .getResolversForIntentAsUser( - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - eq(UserHandle.SYSTEM))) - .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); - when( - ChooserActivityOverrideData - .getInstance() - .workResolverListController - .getResolversForIntentAsUser( - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - eq(UserHandle.SYSTEM))) - .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); - when( - ChooserActivityOverrideData - .getInstance() - .workResolverListController - .getResolversForIntentAsUser( - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - eq(UserHandle.of(10)))) - .thenReturn(new ArrayList<>(workResolvedComponentInfos)); - } - - private static GridRecyclerSpanCountMatcher withGridColumnCount(int columnCount) { - return new GridRecyclerSpanCountMatcher(Matchers.is(columnCount)); - } - - private static class GridRecyclerSpanCountMatcher extends - BoundedDiagnosingMatcher<View, RecyclerView> { - - private final Matcher<Integer> mIntegerMatcher; - - private GridRecyclerSpanCountMatcher(Matcher<Integer> integerMatcher) { - super(RecyclerView.class); - this.mIntegerMatcher = integerMatcher; - } - - @Override - protected void describeMoreTo(Description description) { - description.appendText("RecyclerView grid layout span count to match: "); - this.mIntegerMatcher.describeTo(description); - } - - @Override - protected boolean matchesSafely(RecyclerView view, Description mismatchDescription) { - int spanCount = ((GridLayoutManager) view.getLayoutManager()).getSpanCount(); - if (this.mIntegerMatcher.matches(spanCount)) { - return true; - } else { - mismatchDescription.appendText("RecyclerView grid layout span count was ") - .appendValue(spanCount); - return false; - } - } - } - - private void givenAppTargets(int appCount) { - List<ResolvedComponentInfo> resolvedComponentInfos = - createResolvedComponentsForTest(appCount); - setupResolverControllers(resolvedComponentInfos); - } - - private void updateMaxTargetsPerRowResource(int targetsPerRow) { - Resources resources = Mockito.spy( - InstrumentationRegistry.getInstrumentation().getContext().getResources()); - ChooserActivityOverrideData.getInstance().resources = resources; - doReturn(targetsPerRow).when(resources).getInteger( - R.integer.config_chooser_max_targets_per_row); - } - - private SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> - createShortcutLoaderFactory() { - SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders = - new SparseArray<>(); - ChooserActivityOverrideData.getInstance().shortcutLoaderFactory = - (userHandle, callback) -> { - Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>> pair = - new Pair<>(mock(ShortcutLoader.class), callback); - shortcutLoaders.put(userHandle.getIdentifier(), pair); - return pair.first; - }; - return shortcutLoaders; - } - - private static ImageLoader createImageLoader(Uri uri, Bitmap bitmap) { - return new TestPreviewImageLoader(Collections.singletonMap(uri, bitmap)); - } -} diff --git a/java/tests/src/com/android/intentresolver/UnbundledChooserActivityWorkProfileTest.java b/java/tests/src/com/android/intentresolver/UnbundledChooserActivityWorkProfileTest.java deleted file mode 100644 index da879f74..00000000 --- a/java/tests/src/com/android/intentresolver/UnbundledChooserActivityWorkProfileTest.java +++ /dev/null @@ -1,480 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.intentresolver; - -import static android.testing.PollingCheck.waitFor; - -import static androidx.test.espresso.Espresso.onView; -import static androidx.test.espresso.action.ViewActions.click; -import static androidx.test.espresso.action.ViewActions.swipeUp; -import static androidx.test.espresso.assertion.ViewAssertions.matches; -import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; -import static androidx.test.espresso.matcher.ViewMatchers.isSelected; -import static androidx.test.espresso.matcher.ViewMatchers.withId; -import static androidx.test.espresso.matcher.ViewMatchers.withText; - -import static com.android.intentresolver.ChooserWrapperActivity.sOverrides; -import static com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.NO_BLOCKER; -import static com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.PERSONAL_PROFILE_ACCESS_BLOCKER; -import static com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.PERSONAL_PROFILE_SHARE_BLOCKER; -import static com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.WORK_PROFILE_ACCESS_BLOCKER; -import static com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.WORK_PROFILE_SHARE_BLOCKER; -import static com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.Tab.PERSONAL; -import static com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.Tab.WORK; - -import static org.hamcrest.CoreMatchers.not; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -import android.companion.DeviceFilter; -import android.content.Intent; -import android.os.UserHandle; - -import androidx.test.InstrumentationRegistry; -import androidx.test.espresso.NoMatchingViewException; -import androidx.test.rule.ActivityTestRule; - -import com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.Tab; - -import junit.framework.AssertionFailedError; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.mockito.Mockito; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; - -import dagger.hilt.android.testing.HiltAndroidRule; -import dagger.hilt.android.testing.HiltAndroidTest; - -@DeviceFilter.MediumType -@RunWith(Parameterized.class) -@HiltAndroidTest -public class UnbundledChooserActivityWorkProfileTest { - - private static final UserHandle PERSONAL_USER_HANDLE = InstrumentationRegistry - .getInstrumentation().getTargetContext().getUser(); - private static final UserHandle WORK_USER_HANDLE = UserHandle.of(10); - - @Rule(order = 0) - public HiltAndroidRule mHiltAndroidRule = new HiltAndroidRule(this); - - @Rule(order = 1) - public ActivityTestRule<ChooserWrapperActivity> mActivityRule = - new ActivityTestRule<>(ChooserWrapperActivity.class, false, - false); - private final TestCase mTestCase; - - public UnbundledChooserActivityWorkProfileTest(TestCase testCase) { - mTestCase = testCase; - } - - @Before - public void cleanOverrideData() { - // TODO: use the other form of `adoptShellPermissionIdentity()` where we explicitly list the - // permissions we require (which we'll read from the manifest at runtime). - InstrumentationRegistry - .getInstrumentation() - .getUiAutomation() - .adoptShellPermissionIdentity(); - - sOverrides.reset(); - } - - @Test - public void testBlocker() { - setUpPersonalAndWorkComponentInfos(); - sOverrides.hasCrossProfileIntents = mTestCase.hasCrossProfileIntents(); - - launchActivity(mTestCase.getIsSendAction()); - switchToTab(mTestCase.getTab()); - - switch (mTestCase.getExpectedBlocker()) { - case NO_BLOCKER: - assertNoBlockerDisplayed(); - break; - case PERSONAL_PROFILE_SHARE_BLOCKER: - assertCantSharePersonalAppsBlockerDisplayed(); - break; - case WORK_PROFILE_SHARE_BLOCKER: - assertCantShareWorkAppsBlockerDisplayed(); - break; - case PERSONAL_PROFILE_ACCESS_BLOCKER: - assertCantAccessPersonalAppsBlockerDisplayed(); - break; - case WORK_PROFILE_ACCESS_BLOCKER: - assertCantAccessWorkAppsBlockerDisplayed(); - break; - } - } - - @Parameterized.Parameters(name = "{0}") - public static Collection tests() { - return Arrays.asList( - new TestCase( - /* isSendAction= */ true, - /* hasCrossProfileIntents= */ true, - /* myUserHandle= */ WORK_USER_HANDLE, - /* tab= */ WORK, - /* expectedBlocker= */ NO_BLOCKER - ), - new TestCase( - /* isSendAction= */ true, - /* hasCrossProfileIntents= */ false, - /* myUserHandle= */ WORK_USER_HANDLE, - /* tab= */ WORK, - /* expectedBlocker= */ NO_BLOCKER - ), - new TestCase( - /* isSendAction= */ true, - /* hasCrossProfileIntents= */ true, - /* myUserHandle= */ PERSONAL_USER_HANDLE, - /* tab= */ WORK, - /* expectedBlocker= */ NO_BLOCKER - ), - new TestCase( - /* isSendAction= */ true, - /* hasCrossProfileIntents= */ false, - /* myUserHandle= */ PERSONAL_USER_HANDLE, - /* tab= */ WORK, - /* expectedBlocker= */ WORK_PROFILE_SHARE_BLOCKER - ), - new TestCase( - /* isSendAction= */ true, - /* hasCrossProfileIntents= */ true, - /* myUserHandle= */ WORK_USER_HANDLE, - /* tab= */ PERSONAL, - /* expectedBlocker= */ NO_BLOCKER - ), - new TestCase( - /* isSendAction= */ true, - /* hasCrossProfileIntents= */ false, - /* myUserHandle= */ WORK_USER_HANDLE, - /* tab= */ PERSONAL, - /* expectedBlocker= */ PERSONAL_PROFILE_SHARE_BLOCKER - ), - new TestCase( - /* isSendAction= */ true, - /* hasCrossProfileIntents= */ true, - /* myUserHandle= */ PERSONAL_USER_HANDLE, - /* tab= */ PERSONAL, - /* expectedBlocker= */ NO_BLOCKER - ), - new TestCase( - /* isSendAction= */ true, - /* hasCrossProfileIntents= */ false, - /* myUserHandle= */ PERSONAL_USER_HANDLE, - /* tab= */ PERSONAL, - /* expectedBlocker= */ NO_BLOCKER - ), - new TestCase( - /* isSendAction= */ false, - /* hasCrossProfileIntents= */ true, - /* myUserHandle= */ WORK_USER_HANDLE, - /* tab= */ WORK, - /* expectedBlocker= */ NO_BLOCKER - ), - new TestCase( - /* isSendAction= */ false, - /* hasCrossProfileIntents= */ false, - /* myUserHandle= */ WORK_USER_HANDLE, - /* tab= */ WORK, - /* expectedBlocker= */ NO_BLOCKER - ), - new TestCase( - /* isSendAction= */ false, - /* hasCrossProfileIntents= */ true, - /* myUserHandle= */ PERSONAL_USER_HANDLE, - /* tab= */ WORK, - /* expectedBlocker= */ NO_BLOCKER - ), - new TestCase( - /* isSendAction= */ false, - /* hasCrossProfileIntents= */ false, - /* myUserHandle= */ PERSONAL_USER_HANDLE, - /* tab= */ WORK, - /* expectedBlocker= */ WORK_PROFILE_ACCESS_BLOCKER - ), - new TestCase( - /* isSendAction= */ false, - /* hasCrossProfileIntents= */ true, - /* myUserHandle= */ WORK_USER_HANDLE, - /* tab= */ PERSONAL, - /* expectedBlocker= */ NO_BLOCKER - ), - new TestCase( - /* isSendAction= */ false, - /* hasCrossProfileIntents= */ false, - /* myUserHandle= */ WORK_USER_HANDLE, - /* tab= */ PERSONAL, - /* expectedBlocker= */ PERSONAL_PROFILE_ACCESS_BLOCKER - ), - new TestCase( - /* isSendAction= */ false, - /* hasCrossProfileIntents= */ true, - /* myUserHandle= */ PERSONAL_USER_HANDLE, - /* tab= */ PERSONAL, - /* expectedBlocker= */ NO_BLOCKER - ), - new TestCase( - /* isSendAction= */ false, - /* hasCrossProfileIntents= */ false, - /* myUserHandle= */ PERSONAL_USER_HANDLE, - /* tab= */ PERSONAL, - /* expectedBlocker= */ NO_BLOCKER - ) - ); - } - - private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile( - int numberOfResults, int userId, UserHandle resolvedForUser) { - List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); - for (int i = 0; i < numberOfResults; i++) { - infoList.add( - ResolverDataProvider - .createResolvedComponentInfoWithOtherId(i, userId, resolvedForUser)); - } - return infoList; - } - - private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults, - UserHandle resolvedForUser) { - List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); - for (int i = 0; i < numberOfResults; i++) { - infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, resolvedForUser)); - } - return infoList; - } - - private void setUpPersonalAndWorkComponentInfos() { - ChooserWrapperActivity.sOverrides.annotatedUserHandles = AnnotatedUserHandles.newBuilder() - .setUserIdOfCallingApp(1234) // Must be non-negative. - .setUserHandleSharesheetLaunchedAs(mTestCase.getMyUserHandle()) - .setPersonalProfileUserHandle(PERSONAL_USER_HANDLE) - .setWorkProfileUserHandle(WORK_USER_HANDLE) - .build(); - int workProfileTargets = 4; - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, - /* userId */ WORK_USER_HANDLE.getIdentifier(), PERSONAL_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(workProfileTargets, WORK_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - } - - private void setupResolverControllers( - List<ResolvedComponentInfo> personalResolvedComponentInfos, - List<ResolvedComponentInfo> workResolvedComponentInfos) { - when(sOverrides.resolverListController.getResolversForIntentAsUser( - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - eq(UserHandle.SYSTEM))) - .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); - when(sOverrides.workResolverListController.getResolversForIntentAsUser( - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - eq(UserHandle.SYSTEM))) - .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); - when(sOverrides.workResolverListController.getResolversForIntentAsUser( - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - eq(WORK_USER_HANDLE))) - .thenReturn(new ArrayList<>(workResolvedComponentInfos)); - } - - private void waitForIdle() { - InstrumentationRegistry.getInstrumentation().waitForIdleSync(); - } - - private void assertCantAccessWorkAppsBlockerDisplayed() { - onView(withText(R.string.resolver_cross_profile_blocked)) - .check(matches(isDisplayed())); - onView(withText(R.string.resolver_cant_access_work_apps_explanation)) - .check(matches(isDisplayed())); - } - - private void assertCantAccessPersonalAppsBlockerDisplayed() { - onView(withText(R.string.resolver_cross_profile_blocked)) - .check(matches(isDisplayed())); - onView(withText(R.string.resolver_cant_access_personal_apps_explanation)) - .check(matches(isDisplayed())); - } - - private void assertCantShareWorkAppsBlockerDisplayed() { - onView(withText(R.string.resolver_cross_profile_blocked)) - .check(matches(isDisplayed())); - onView(withText(R.string.resolver_cant_share_with_work_apps_explanation)) - .check(matches(isDisplayed())); - } - - private void assertCantSharePersonalAppsBlockerDisplayed() { - onView(withText(R.string.resolver_cross_profile_blocked)) - .check(matches(isDisplayed())); - onView(withText(R.string.resolver_cant_share_with_personal_apps_explanation)) - .check(matches(isDisplayed())); - } - - private void assertNoBlockerDisplayed() { - try { - onView(withText(R.string.resolver_cross_profile_blocked)) - .check(matches(not(isDisplayed()))); - } catch (NoMatchingViewException ignored) { - } - } - - private void switchToTab(Tab tab) { - final int stringId = tab == Tab.WORK ? R.string.resolver_work_tab - : R.string.resolver_personal_tab; - - waitFor(() -> { - onView(withText(stringId)).perform(click()); - waitForIdle(); - - try { - onView(withText(stringId)).check(matches(isSelected())); - return true; - } catch (AssertionFailedError e) { - return false; - } - }); - - onView(withId(com.android.internal.R.id.contentPanel)) - .perform(swipeUp()); - waitForIdle(); - } - - private Intent createTextIntent(boolean isSendAction) { - Intent sendIntent = new Intent(); - if (isSendAction) { - sendIntent.setAction(Intent.ACTION_SEND); - } - sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending"); - sendIntent.setType("text/plain"); - return sendIntent; - } - - private void launchActivity(boolean isSendAction) { - Intent sendIntent = createTextIntent(isSendAction); - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "Test")); - waitForIdle(); - } - - public static class TestCase { - private final boolean mIsSendAction; - private final boolean mHasCrossProfileIntents; - private final UserHandle mMyUserHandle; - private final Tab mTab; - private final ExpectedBlocker mExpectedBlocker; - - public enum ExpectedBlocker { - NO_BLOCKER, - PERSONAL_PROFILE_SHARE_BLOCKER, - WORK_PROFILE_SHARE_BLOCKER, - PERSONAL_PROFILE_ACCESS_BLOCKER, - WORK_PROFILE_ACCESS_BLOCKER - } - - public enum Tab { - WORK, - PERSONAL - } - - public TestCase(boolean isSendAction, boolean hasCrossProfileIntents, - UserHandle myUserHandle, Tab tab, ExpectedBlocker expectedBlocker) { - mIsSendAction = isSendAction; - mHasCrossProfileIntents = hasCrossProfileIntents; - mMyUserHandle = myUserHandle; - mTab = tab; - mExpectedBlocker = expectedBlocker; - } - - public boolean getIsSendAction() { - return mIsSendAction; - } - - public boolean hasCrossProfileIntents() { - return mHasCrossProfileIntents; - } - - public UserHandle getMyUserHandle() { - return mMyUserHandle; - } - - public Tab getTab() { - return mTab; - } - - public ExpectedBlocker getExpectedBlocker() { - return mExpectedBlocker; - } - - @Override - public String toString() { - StringBuilder result = new StringBuilder("test"); - - if (mTab == WORK) { - result.append("WorkTab_"); - } else { - result.append("PersonalTab_"); - } - - if (mIsSendAction) { - result.append("sendAction_"); - } else { - result.append("notSendAction_"); - } - - if (mHasCrossProfileIntents) { - result.append("hasCrossProfileIntents_"); - } else { - result.append("doesNotHaveCrossProfileIntents_"); - } - - if (mMyUserHandle.equals(PERSONAL_USER_HANDLE)) { - result.append("myUserIsPersonal_"); - } else { - result.append("myUserIsWork_"); - } - - if (mExpectedBlocker == ExpectedBlocker.NO_BLOCKER) { - result.append("thenNoBlocker"); - } else if (mExpectedBlocker == PERSONAL_PROFILE_ACCESS_BLOCKER) { - result.append("thenAccessBlockerOnPersonalProfile"); - } else if (mExpectedBlocker == PERSONAL_PROFILE_SHARE_BLOCKER) { - result.append("thenShareBlockerOnPersonalProfile"); - } else if (mExpectedBlocker == WORK_PROFILE_ACCESS_BLOCKER) { - result.append("thenAccessBlockerOnWorkProfile"); - } else if (mExpectedBlocker == WORK_PROFILE_SHARE_BLOCKER) { - result.append("thenShareBlockerOnWorkProfile"); - } - - return result.toString(); - } - } -} diff --git a/java/tests/src/com/android/intentresolver/chooser/ImmutableTargetInfoTest.kt b/java/tests/src/com/android/intentresolver/chooser/ImmutableTargetInfoTest.kt deleted file mode 100644 index 6712bf31..00000000 --- a/java/tests/src/com/android/intentresolver/chooser/ImmutableTargetInfoTest.kt +++ /dev/null @@ -1,502 +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 - *3 - * 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.chooser - -import android.app.Activity -import android.app.prediction.AppTarget -import android.app.prediction.AppTargetId -import android.content.ComponentName -import android.content.Intent -import android.os.Bundle -import android.os.UserHandle -import com.android.intentresolver.createShortcutInfo -import com.android.intentresolver.mock -import com.android.intentresolver.ResolverActivity -import com.android.intentresolver.ResolverDataProvider -import com.google.common.truth.Truth.assertThat -import org.junit.Test -import androidx.test.platform.app.InstrumentationRegistry - -class ImmutableTargetInfoTest { - private val PERSONAL_USER_HANDLE: UserHandle = InstrumentationRegistry - .getInstrumentation().getTargetContext().getUser() - - private val resolvedIntent = Intent("resolved") - private val targetIntent = Intent("target") - private val referrerFillInIntent = Intent("referrer_fillin") - private val resolvedComponentName = ComponentName("resolved", "component") - private val chooserTargetComponentName = ComponentName("chooser", "target") - private val resolveInfo = ResolverDataProvider.createResolveInfo(1, 0, PERSONAL_USER_HANDLE) - private val displayLabel: CharSequence = "Display Label" - private val extendedInfo: CharSequence = "Extended Info" - private val displayIconHolder: TargetInfo.IconHolder = mock() - private val sourceIntent1 = Intent("source1") - private val sourceIntent2 = Intent("source2") - private val displayTarget1 = DisplayResolveInfo.newDisplayResolveInfo( - Intent("display1"), - ResolverDataProvider.createResolveInfo(2, 0, PERSONAL_USER_HANDLE), - "display1 label", - "display1 extended info", - Intent("display1_resolved") - ) - private val displayTarget2 = DisplayResolveInfo.newDisplayResolveInfo( - Intent("display2"), - ResolverDataProvider.createResolveInfo(3, 0, PERSONAL_USER_HANDLE), - "display2 label", - "display2 extended info", - Intent("display2_resolved") - ) - private val directShareShortcutInfo = createShortcutInfo( - "shortcutid", ResolverDataProvider.createComponentName(4), 4) - private val directShareAppTarget = AppTarget( - AppTargetId("apptargetid"), - "test.directshare", - "target", - UserHandle.CURRENT) - private val displayResolveInfo = DisplayResolveInfo.newDisplayResolveInfo( - Intent("displayresolve"), - ResolverDataProvider.createResolveInfo(5, 0, PERSONAL_USER_HANDLE), - "displayresolve label", - "displayresolve extended info", - Intent("display_resolved") - ) - private val hashProvider: ImmutableTargetInfo.TargetHashProvider = mock() - - @Test - fun testBasicProperties() { // Fields that are reflected back w/o logic. - // TODO: we could consider passing copies of all the values into the builder so that we can - // verify that they're not mutated (e.g. no extras added to the intents). For now that - // should be obvious from the implementation. - val info = ImmutableTargetInfo.newBuilder() - .setResolvedIntent(resolvedIntent) - .setTargetIntent(targetIntent) - .setReferrerFillInIntent(referrerFillInIntent) - .setResolvedComponentName(resolvedComponentName) - .setChooserTargetComponentName(chooserTargetComponentName) - .setResolveInfo(resolveInfo) - .setDisplayLabel(displayLabel) - .setExtendedInfo(extendedInfo) - .setDisplayIconHolder(displayIconHolder) - .setAlternateSourceIntents(listOf(sourceIntent1, sourceIntent2)) - .setAllDisplayTargets(listOf(displayTarget1, displayTarget2)) - .setIsSuspended(true) - .setIsPinned(true) - .setModifiedScore(42.0f) - .setDirectShareShortcutInfo(directShareShortcutInfo) - .setDirectShareAppTarget(directShareAppTarget) - .setDisplayResolveInfo(displayResolveInfo) - .setHashProvider(hashProvider) - .build() - - assertThat(info.resolvedIntent).isEqualTo(resolvedIntent) - assertThat(info.targetIntent).isEqualTo(targetIntent) - assertThat(info.referrerFillInIntent).isEqualTo(referrerFillInIntent) - assertThat(info.resolvedComponentName).isEqualTo(resolvedComponentName) - assertThat(info.chooserTargetComponentName).isEqualTo(chooserTargetComponentName) - assertThat(info.resolveInfo).isEqualTo(resolveInfo) - assertThat(info.displayLabel).isEqualTo(displayLabel) - assertThat(info.extendedInfo).isEqualTo(extendedInfo) - assertThat(info.displayIconHolder).isEqualTo(displayIconHolder) - assertThat(info.allSourceIntents).containsExactly( - resolvedIntent, sourceIntent1, sourceIntent2) - assertThat(info.allDisplayTargets).containsExactly(displayTarget1, displayTarget2) - assertThat(info.isSuspended).isTrue() - assertThat(info.isPinned).isTrue() - assertThat(info.modifiedScore).isEqualTo(42.0f) - assertThat(info.directShareShortcutInfo).isEqualTo(directShareShortcutInfo) - assertThat(info.directShareAppTarget).isEqualTo(directShareAppTarget) - assertThat(info.displayResolveInfo).isEqualTo(displayResolveInfo) - assertThat(info.isEmptyTargetInfo).isFalse() - assertThat(info.isPlaceHolderTargetInfo).isFalse() - assertThat(info.isNotSelectableTargetInfo).isFalse() - assertThat(info.isSelectableTargetInfo).isFalse() - assertThat(info.isChooserTargetInfo).isFalse() - assertThat(info.isMultiDisplayResolveInfo).isFalse() - assertThat(info.isDisplayResolveInfo).isFalse() - assertThat(info.hashProvider).isEqualTo(hashProvider) - } - - @Test - fun testToBuilderPreservesBasicProperties() { - // Note this is set up exactly as in `testBasicProperties`, but the assertions will be made - // against a *copy* of the object instead. - val infoToCopyFrom = ImmutableTargetInfo.newBuilder() - .setResolvedIntent(resolvedIntent) - .setTargetIntent(targetIntent) - .setReferrerFillInIntent(referrerFillInIntent) - .setResolvedComponentName(resolvedComponentName) - .setChooserTargetComponentName(chooserTargetComponentName) - .setResolveInfo(resolveInfo) - .setDisplayLabel(displayLabel) - .setExtendedInfo(extendedInfo) - .setDisplayIconHolder(displayIconHolder) - .setAlternateSourceIntents(listOf(sourceIntent1, sourceIntent2)) - .setAllDisplayTargets(listOf(displayTarget1, displayTarget2)) - .setIsSuspended(true) - .setIsPinned(true) - .setModifiedScore(42.0f) - .setDirectShareShortcutInfo(directShareShortcutInfo) - .setDirectShareAppTarget(directShareAppTarget) - .setDisplayResolveInfo(displayResolveInfo) - .setHashProvider(hashProvider) - .build() - - val info = infoToCopyFrom.toBuilder().build() - - assertThat(info.resolvedIntent).isEqualTo(resolvedIntent) - assertThat(info.targetIntent).isEqualTo(targetIntent) - assertThat(info.referrerFillInIntent).isEqualTo(referrerFillInIntent) - assertThat(info.resolvedComponentName).isEqualTo(resolvedComponentName) - assertThat(info.chooserTargetComponentName).isEqualTo(chooserTargetComponentName) - assertThat(info.resolveInfo).isEqualTo(resolveInfo) - assertThat(info.displayLabel).isEqualTo(displayLabel) - assertThat(info.extendedInfo).isEqualTo(extendedInfo) - assertThat(info.displayIconHolder).isEqualTo(displayIconHolder) - assertThat(info.allSourceIntents).containsExactly( - resolvedIntent, sourceIntent1, sourceIntent2) - assertThat(info.allDisplayTargets).containsExactly(displayTarget1, displayTarget2) - assertThat(info.isSuspended).isTrue() - assertThat(info.isPinned).isTrue() - assertThat(info.modifiedScore).isEqualTo(42.0f) - assertThat(info.directShareShortcutInfo).isEqualTo(directShareShortcutInfo) - assertThat(info.directShareAppTarget).isEqualTo(directShareAppTarget) - assertThat(info.displayResolveInfo).isEqualTo(displayResolveInfo) - assertThat(info.isEmptyTargetInfo).isFalse() - assertThat(info.isPlaceHolderTargetInfo).isFalse() - assertThat(info.isNotSelectableTargetInfo).isFalse() - assertThat(info.isSelectableTargetInfo).isFalse() - assertThat(info.isChooserTargetInfo).isFalse() - assertThat(info.isMultiDisplayResolveInfo).isFalse() - assertThat(info.isDisplayResolveInfo).isFalse() - assertThat(info.hashProvider).isEqualTo(hashProvider) - } - - @Test - fun testBaseIntentToSend_defaultsToResolvedIntent() { - val info = ImmutableTargetInfo.newBuilder().setResolvedIntent(resolvedIntent).build() - assertThat(info.baseIntentToSend.filterEquals(resolvedIntent)).isTrue() - } - - @Test - fun testBaseIntentToSend_fillsInFromReferrerIntent() { - val originalIntent = Intent() - originalIntent.setPackage("original") - - val referrerFillInIntent = Intent("REFERRER_FILL_IN") - referrerFillInIntent.setPackage("referrer") - - val info = ImmutableTargetInfo.newBuilder() - .setResolvedIntent(originalIntent) - .setReferrerFillInIntent(referrerFillInIntent) - .build() - - assertThat(info.baseIntentToSend.getPackage()).isEqualTo("original") // Only fill if empty. - assertThat(info.baseIntentToSend.action).isEqualTo("REFERRER_FILL_IN") - } - - @Test - fun testBaseIntentToSend_fillsInFromRefinementIntent() { - val originalIntent = Intent() - originalIntent.putExtra("ORIGINAL", true) - - val refinementIntent = Intent() - refinementIntent.putExtra("REFINEMENT", true) - - val originalInfo = ImmutableTargetInfo.newBuilder() - .setResolvedIntent(originalIntent) - .build() - val info = checkNotNull(originalInfo.tryToCloneWithAppliedRefinement(refinementIntent)) - - assertThat(info?.baseIntentToSend?.getBooleanExtra("ORIGINAL", false)).isTrue() - assertThat(info?.baseIntentToSend?.getBooleanExtra("REFINEMENT", false)).isTrue() - } - - @Test - fun testBaseIntentToSend_twoFillInSourcesFavorsRefinementRequest() { - val originalIntent = Intent("REFINE_ME") - originalIntent.setPackage("original") - - val referrerFillInIntent = Intent("REFERRER_FILL_IN") - referrerFillInIntent.setPackage("referrer_pkg") - referrerFillInIntent.setType("test/referrer") - - val infoWithReferrerFillIn = ImmutableTargetInfo.newBuilder() - .setResolvedIntent(originalIntent) - .setReferrerFillInIntent(referrerFillInIntent) - .build() - - val refinementIntent = Intent("REFINE_ME") - refinementIntent.setPackage("original") // Has to match for refinement. - - val info = - checkNotNull(infoWithReferrerFillIn.tryToCloneWithAppliedRefinement(refinementIntent)) - - assertThat(info?.baseIntentToSend?.getPackage()).isEqualTo("original") // Set all along. - assertThat(info?.baseIntentToSend?.action).isEqualTo("REFINE_ME") // Refinement wins. - assertThat(info?.baseIntentToSend?.type).isEqualTo("test/referrer") // Left for referrer. - } - - @Test - fun testBaseIntentToSend_doubleRefinementPreservesReferrerFillInButNotOriginalRefinement() { - val originalIntent = Intent("REFINE_ME") - val referrerFillInIntent = Intent("REFERRER_FILL_IN") - referrerFillInIntent.putExtra("TEST", "REFERRER") - val refinementIntent1 = Intent("REFINE_ME") - refinementIntent1.putExtra("TEST1", "1") - val refinementIntent2 = Intent("REFINE_ME") - refinementIntent2.putExtra("TEST2", "2") - - val originalInfo = ImmutableTargetInfo.newBuilder() - .setResolvedIntent(originalIntent) - .setReferrerFillInIntent(referrerFillInIntent) - .build() - - val refined1 = checkNotNull(originalInfo.tryToCloneWithAppliedRefinement(refinementIntent1)) - // Cloned clone. - val refined2 = checkNotNull(refined1.tryToCloneWithAppliedRefinement(refinementIntent2)) - - // Both clones get the same values filled in from the referrer intent. - assertThat(refined1?.baseIntentToSend?.getStringExtra("TEST")).isEqualTo("REFERRER") - assertThat(refined2?.baseIntentToSend?.getStringExtra("TEST")).isEqualTo("REFERRER") - // Each clone has the respective value that was set in their own refinement request. - assertThat(refined1?.baseIntentToSend?.getStringExtra("TEST1")).isEqualTo("1") - assertThat(refined2?.baseIntentToSend?.getStringExtra("TEST2")).isEqualTo("2") - // The clones don't have the data from each other's refinements, even though the intent - // field is empty (thus able to be populated by filling-in). - assertThat(refined1?.baseIntentToSend?.getStringExtra("TEST2")).isNull() - assertThat(refined2?.baseIntentToSend?.getStringExtra("TEST1")).isNull() - } - - @Test - fun testBaseIntentToSend_refinementToAlternateSourceIntent() { - val originalIntent = Intent("DONT_REFINE_ME") - originalIntent.putExtra("originalIntent", true) - val mismatchedAlternate = Intent("DOESNT_MATCH") - mismatchedAlternate.putExtra("mismatchedAlternate", true) - val targetAlternate = Intent("REFINE_ME") - targetAlternate.putExtra("targetAlternate", true) - val extraMatch = Intent("REFINE_ME") - extraMatch.putExtra("extraMatch", true) - - val originalInfo = ImmutableTargetInfo.newBuilder() - .setResolvedIntent(originalIntent) - .setAllSourceIntents(listOf( - originalIntent, mismatchedAlternate, targetAlternate, extraMatch)) - .build() - - val refinement = Intent("REFINE_ME") // First match is `targetAlternate` - refinement.putExtra("refinement", true) - - val refinedResult = checkNotNull(originalInfo.tryToCloneWithAppliedRefinement(refinement)) - assertThat(refinedResult?.baseIntentToSend?.getBooleanExtra("refinement", false)).isTrue() - assertThat(refinedResult?.baseIntentToSend?.getBooleanExtra("targetAlternate", false)) - .isTrue() - // None of the other source intents got merged in (not even the later one that matched): - assertThat(refinedResult?.baseIntentToSend?.getBooleanExtra("originalIntent", false)) - .isFalse() - assertThat(refinedResult?.baseIntentToSend?.getBooleanExtra("mismatchedAlternate", false)) - .isFalse() - assertThat(refinedResult?.baseIntentToSend?.getBooleanExtra("extraMatch", false)).isFalse() - } - - @Test - fun testBaseIntentToSend_noSourceIntentMatchingProposedRefinement() { - val originalIntent = Intent("DONT_REFINE_ME") - originalIntent.putExtra("originalIntent", true) - val mismatchedAlternate = Intent("DOESNT_MATCH") - mismatchedAlternate.putExtra("mismatchedAlternate", true) - - val originalInfo = ImmutableTargetInfo.newBuilder() - .setResolvedIntent(originalIntent) - .setAllSourceIntents(listOf(originalIntent, mismatchedAlternate)) - .build() - - val refinement = Intent("PROPOSED_REFINEMENT") - assertThat(originalInfo.tryToCloneWithAppliedRefinement(refinement)).isNull() - } - - @Test - fun testLegacySubclassRelationships_empty() { - val info = ImmutableTargetInfo.newBuilder() - .setLegacyType(ImmutableTargetInfo.LegacyTargetType.EMPTY_TARGET_INFO) - .build() - - assertThat(info.isEmptyTargetInfo).isTrue() - assertThat(info.isPlaceHolderTargetInfo).isFalse() - assertThat(info.isNotSelectableTargetInfo).isTrue() - assertThat(info.isSelectableTargetInfo).isFalse() - assertThat(info.isChooserTargetInfo).isTrue() - assertThat(info.isMultiDisplayResolveInfo).isFalse() - assertThat(info.isDisplayResolveInfo).isFalse() - } - - @Test - fun testLegacySubclassRelationships_placeholder() { - val info = ImmutableTargetInfo.newBuilder() - .setLegacyType(ImmutableTargetInfo.LegacyTargetType.PLACEHOLDER_TARGET_INFO) - .build() - - assertThat(info.isEmptyTargetInfo).isFalse() - assertThat(info.isPlaceHolderTargetInfo).isTrue() - assertThat(info.isNotSelectableTargetInfo).isTrue() - assertThat(info.isSelectableTargetInfo).isFalse() - assertThat(info.isChooserTargetInfo).isTrue() - assertThat(info.isMultiDisplayResolveInfo).isFalse() - assertThat(info.isDisplayResolveInfo).isFalse() - } - - @Test - fun testLegacySubclassRelationships_selectable() { - val info = ImmutableTargetInfo.newBuilder() - .setLegacyType(ImmutableTargetInfo.LegacyTargetType.SELECTABLE_TARGET_INFO) - .build() - - assertThat(info.isEmptyTargetInfo).isFalse() - assertThat(info.isPlaceHolderTargetInfo).isFalse() - assertThat(info.isNotSelectableTargetInfo).isFalse() - assertThat(info.isSelectableTargetInfo).isTrue() - assertThat(info.isChooserTargetInfo).isTrue() - assertThat(info.isMultiDisplayResolveInfo).isFalse() - assertThat(info.isDisplayResolveInfo).isFalse() - } - - @Test - fun testLegacySubclassRelationships_displayResolveInfo() { - val info = ImmutableTargetInfo.newBuilder() - .setLegacyType(ImmutableTargetInfo.LegacyTargetType.DISPLAY_RESOLVE_INFO) - .build() - - assertThat(info.isEmptyTargetInfo).isFalse() - assertThat(info.isPlaceHolderTargetInfo).isFalse() - assertThat(info.isNotSelectableTargetInfo).isFalse() - assertThat(info.isSelectableTargetInfo).isFalse() - assertThat(info.isChooserTargetInfo).isFalse() - assertThat(info.isMultiDisplayResolveInfo).isFalse() - assertThat(info.isDisplayResolveInfo).isTrue() - } - - @Test - fun testLegacySubclassRelationships_multiDisplayResolveInfo() { - val info = ImmutableTargetInfo.newBuilder() - .setLegacyType(ImmutableTargetInfo.LegacyTargetType.MULTI_DISPLAY_RESOLVE_INFO) - .build() - - assertThat(info.isEmptyTargetInfo).isFalse() - assertThat(info.isPlaceHolderTargetInfo).isFalse() - assertThat(info.isNotSelectableTargetInfo).isFalse() - assertThat(info.isSelectableTargetInfo).isFalse() - assertThat(info.isChooserTargetInfo).isFalse() - assertThat(info.isMultiDisplayResolveInfo).isTrue() - assertThat(info.isDisplayResolveInfo).isTrue() - } - - @Test - fun testActivityStarter_correctNumberOfInvocations_startAsCaller() { - val activityStarter = object : TestActivityStarter() { - override fun startAsUser( - target: TargetInfo, activity: Activity, options: Bundle, user: UserHandle - ): Boolean { - throw RuntimeException("Wrong API used: startAsUser") - } - } - - val info = ImmutableTargetInfo.newBuilder().setActivityStarter(activityStarter).build() - val activity: ResolverActivity = mock() - val options = Bundle() - options.putInt("TEST_KEY", 1) - - info.startAsCaller(activity, options, 42) - - assertThat(activityStarter.totalInvocations).isEqualTo(1) - assertThat(activityStarter.lastInvocationTargetInfo).isEqualTo(info) - assertThat(activityStarter.lastInvocationActivity).isEqualTo(activity) - assertThat(activityStarter.lastInvocationOptions).isEqualTo(options) - assertThat(activityStarter.lastInvocationUserId).isEqualTo(42) - assertThat(activityStarter.lastInvocationAsCaller).isTrue() - } - - @Test - fun testActivityStarter_correctNumberOfInvocations_startAsUser() { - val activityStarter = object : TestActivityStarter() { - override fun startAsCaller( - target: TargetInfo, activity: Activity, options: Bundle, userId: Int): Boolean { - throw RuntimeException("Wrong API used: startAsCaller") - } - } - - val info = ImmutableTargetInfo.newBuilder().setActivityStarter(activityStarter).build() - val activity: Activity = mock() - val options = Bundle() - options.putInt("TEST_KEY", 1) - - info.startAsUser(activity, options, UserHandle.of(42)) - - assertThat(activityStarter.totalInvocations).isEqualTo(1) - assertThat(activityStarter.lastInvocationTargetInfo).isEqualTo(info) - assertThat(activityStarter.lastInvocationActivity).isEqualTo(activity) - assertThat(activityStarter.lastInvocationOptions).isEqualTo(options) - assertThat(activityStarter.lastInvocationUserId).isEqualTo(42) - assertThat(activityStarter.lastInvocationAsCaller).isFalse() - } - - @Test - fun testActivityStarter_invokedWithRespectiveTargetInfoAfterCopy() { - val activityStarter = TestActivityStarter() - val info1 = ImmutableTargetInfo.newBuilder().setActivityStarter(activityStarter).build() - val info2 = info1.toBuilder().build() - - info1.startAsCaller(mock(), Bundle(), 42) - assertThat(activityStarter.lastInvocationTargetInfo).isEqualTo(info1) - info2.startAsCaller(mock(), Bundle(), 42) - assertThat(activityStarter.lastInvocationTargetInfo).isEqualTo(info2) - info2.startAsUser(mock(), Bundle(), UserHandle.of(42)) - assertThat(activityStarter.lastInvocationTargetInfo).isEqualTo(info2) - - assertThat(activityStarter.totalInvocations).isEqualTo(3) // Instance is still shared. - } -} - -private open class TestActivityStarter : ImmutableTargetInfo.TargetActivityStarter { - var totalInvocations = 0 - var lastInvocationTargetInfo: TargetInfo? = null - var lastInvocationActivity: Activity? = null - var lastInvocationOptions: Bundle? = null - var lastInvocationUserId: Integer? = null - var lastInvocationAsCaller = false - - override fun startAsCaller( - target: TargetInfo, activity: Activity, options: Bundle, userId: Int): Boolean { - ++totalInvocations - lastInvocationTargetInfo = target - lastInvocationActivity = activity - lastInvocationOptions = options - lastInvocationUserId = Integer(userId) - lastInvocationAsCaller = true - return true - } - - override fun startAsUser( - target: TargetInfo, activity: Activity, options: Bundle, user: UserHandle): Boolean { - ++totalInvocations - lastInvocationTargetInfo = target - lastInvocationActivity = activity - lastInvocationOptions = options - lastInvocationUserId = Integer(user.identifier) - lastInvocationAsCaller = false - return true - } -} diff --git a/java/tests/src/com/android/intentresolver/chooser/TargetInfoTest.kt b/java/tests/src/com/android/intentresolver/chooser/TargetInfoTest.kt deleted file mode 100644 index a7574c12..00000000 --- a/java/tests/src/com/android/intentresolver/chooser/TargetInfoTest.kt +++ /dev/null @@ -1,397 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.intentresolver.chooser - -import android.app.prediction.AppTarget -import android.app.prediction.AppTargetId -import android.content.ComponentName -import android.content.Intent -import android.content.pm.ActivityInfo -import android.content.pm.ResolveInfo -import android.graphics.drawable.AnimatedVectorDrawable -import android.os.UserHandle -import android.test.UiThreadTest -import androidx.test.platform.app.InstrumentationRegistry -import com.android.intentresolver.ResolverDataProvider -import com.android.intentresolver.createChooserTarget -import com.android.intentresolver.createShortcutInfo -import com.android.intentresolver.mock -import com.android.intentresolver.whenever -import com.google.common.truth.Truth.assertThat -import org.junit.Before -import org.junit.Test -import org.mockito.Mockito.any -import org.mockito.Mockito.never -import org.mockito.Mockito.spy -import org.mockito.Mockito.times -import org.mockito.Mockito.verify - -class TargetInfoTest { - private val PERSONAL_USER_HANDLE: UserHandle = InstrumentationRegistry - .getInstrumentation().getTargetContext().getUser() - - private val context = InstrumentationRegistry.getInstrumentation().getContext() - - @Before - fun setup() { - // SelectableTargetInfo reads DeviceConfig and needs a permission for that. - InstrumentationRegistry - .getInstrumentation() - .getUiAutomation() - .adoptShellPermissionIdentity("android.permission.READ_DEVICE_CONFIG") - } - - @Test - fun testNewEmptyTargetInfo() { - val info = NotSelectableTargetInfo.newEmptyTargetInfo() - assertThat(info.isEmptyTargetInfo()).isTrue() - assertThat(info.isChooserTargetInfo()).isTrue() // From legacy inheritance model. - assertThat(info.hasDisplayIcon()).isFalse() - assertThat(info.getDisplayIconHolder().getDisplayIcon()).isNull() - } - - @UiThreadTest // AnimatedVectorDrawable needs to start from a thread with a Looper. - @Test - fun testNewPlaceholderTargetInfo() { - val info = NotSelectableTargetInfo.newPlaceHolderTargetInfo(context) - assertThat(info.isPlaceHolderTargetInfo).isTrue() - assertThat(info.isChooserTargetInfo).isTrue() // From legacy inheritance model. - assertThat(info.hasDisplayIcon()).isTrue() - assertThat(info.displayIconHolder.displayIcon) - .isInstanceOf(AnimatedVectorDrawable::class.java) - // TODO: assert that the animation is pre-started/running (IIUC this requires synchronizing - // with some "render thread" per the `AnimatedVectorDrawable` docs). I believe this is - // possible using `AnimatorTestRule` but I couldn't find any sample usage in Kotlin nor get - // it working myself. - } - - @Test - fun testNewSelectableTargetInfo() { - val resolvedIntent = Intent() - val baseDisplayInfo = DisplayResolveInfo.newDisplayResolveInfo( - resolvedIntent, - ResolverDataProvider.createResolveInfo(1, 0, PERSONAL_USER_HANDLE), - "label", - "extended info", - resolvedIntent - ) - val chooserTarget = createChooserTarget( - "title", 0.3f, ResolverDataProvider.createComponentName(2), "test_shortcut_id") - val shortcutInfo = createShortcutInfo("id", ResolverDataProvider.createComponentName(3), 3) - val appTarget = AppTarget( - AppTargetId("id"), - chooserTarget.componentName.packageName, - chooserTarget.componentName.className, - UserHandle.CURRENT) - - val targetInfo = SelectableTargetInfo.newSelectableTargetInfo( - baseDisplayInfo, - mock(), - resolvedIntent, - chooserTarget, - 0.1f, - shortcutInfo, - appTarget, - mock(), - ) - assertThat(targetInfo.isSelectableTargetInfo).isTrue() - assertThat(targetInfo.isChooserTargetInfo).isTrue() // From legacy inheritance model. - assertThat(targetInfo.displayResolveInfo).isSameInstanceAs(baseDisplayInfo) - assertThat(targetInfo.chooserTargetComponentName).isEqualTo(chooserTarget.componentName) - assertThat(targetInfo.directShareShortcutId).isEqualTo(shortcutInfo.id) - assertThat(targetInfo.directShareShortcutInfo).isSameInstanceAs(shortcutInfo) - assertThat(targetInfo.directShareAppTarget).isSameInstanceAs(appTarget) - assertThat(targetInfo.resolvedIntent).isSameInstanceAs(resolvedIntent) - // TODO: make more meaningful assertions about the behavior of a selectable target. - } - - @Test - fun test_SelectableTargetInfo_componentName_no_source_info() { - val chooserTarget = createChooserTarget( - "title", 0.3f, ResolverDataProvider.createComponentName(1), "test_shortcut_id") - val shortcutInfo = createShortcutInfo("id", ResolverDataProvider.createComponentName(2), 3) - val appTarget = AppTarget( - AppTargetId("id"), - chooserTarget.componentName.packageName, - chooserTarget.componentName.className, - UserHandle.CURRENT) - val pkgName = "org.package" - val className = "MainActivity" - val backupResolveInfo = ResolveInfo().apply { - activityInfo = ActivityInfo().apply { - packageName = pkgName - name = className - } - } - - val targetInfo = SelectableTargetInfo.newSelectableTargetInfo( - null, - backupResolveInfo, - mock(), - chooserTarget, - 0.1f, - shortcutInfo, - appTarget, - mock(), - ) - assertThat(targetInfo.resolvedComponentName).isEqualTo(ComponentName(pkgName, className)) - } - - @Test - fun testSelectableTargetInfo_noSourceIntentMatchingProposedRefinement() { - val resolvedIntent = Intent("DONT_REFINE_ME") - resolvedIntent.putExtra("resolvedIntent", true) - - val baseDisplayInfo = DisplayResolveInfo.newDisplayResolveInfo( - resolvedIntent, - ResolverDataProvider.createResolveInfo(1, 0), - "label", - "extended info", - resolvedIntent - ) - val chooserTarget = createChooserTarget( - "title", 0.3f, ResolverDataProvider.createComponentName(2), "test_shortcut_id") - val shortcutInfo = createShortcutInfo("id", ResolverDataProvider.createComponentName(3), 3) - val appTarget = AppTarget( - AppTargetId("id"), - chooserTarget.componentName.packageName, - chooserTarget.componentName.className, - UserHandle.CURRENT) - - val targetInfo = SelectableTargetInfo.newSelectableTargetInfo( - baseDisplayInfo, - mock(), - resolvedIntent, - chooserTarget, - 0.1f, - shortcutInfo, - appTarget, - mock(), - ) - - val refinement = Intent("PROPOSED_REFINEMENT") - assertThat(targetInfo.tryToCloneWithAppliedRefinement(refinement)).isNull() - } - - @Test - fun testNewDisplayResolveInfo() { - val intent = Intent(Intent.ACTION_SEND) - intent.putExtra(Intent.EXTRA_TEXT, "testing intent sending") - intent.setType("text/plain") - - val resolveInfo = ResolverDataProvider.createResolveInfo(3, 0, PERSONAL_USER_HANDLE) - - val targetInfo = DisplayResolveInfo.newDisplayResolveInfo( - intent, - resolveInfo, - "label", - "extended info", - intent - ) - assertThat(targetInfo.isDisplayResolveInfo()).isTrue() - assertThat(targetInfo.isMultiDisplayResolveInfo()).isFalse() - assertThat(targetInfo.isChooserTargetInfo()).isFalse() - } - - @Test - fun test_DisplayResolveInfo_refinementToAlternateSourceIntent() { - val originalIntent = Intent("DONT_REFINE_ME") - originalIntent.putExtra("originalIntent", true) - val mismatchedAlternate = Intent("DOESNT_MATCH") - mismatchedAlternate.putExtra("mismatchedAlternate", true) - val targetAlternate = Intent("REFINE_ME") - targetAlternate.putExtra("targetAlternate", true) - val extraMatch = Intent("REFINE_ME") - extraMatch.putExtra("extraMatch", true) - - val originalInfo = DisplayResolveInfo.newDisplayResolveInfo( - originalIntent, - ResolverDataProvider.createResolveInfo(3, 0), - "label", - "extended info", - originalIntent - ) - originalInfo.addAlternateSourceIntent(mismatchedAlternate) - originalInfo.addAlternateSourceIntent(targetAlternate) - originalInfo.addAlternateSourceIntent(extraMatch) - - val refinement = Intent("REFINE_ME") // First match is `targetAlternate` - refinement.putExtra("refinement", true) - - val refinedResult = checkNotNull(originalInfo.tryToCloneWithAppliedRefinement(refinement)) - // Note `DisplayResolveInfo` targets merge refinements directly into their `resolvedIntent`. - assertThat(refinedResult?.resolvedIntent?.getBooleanExtra("refinement", false)).isTrue() - assertThat(refinedResult?.resolvedIntent?.getBooleanExtra("targetAlternate", false)) - .isTrue() - // None of the other source intents got merged in (not even the later one that matched): - assertThat(refinedResult?.resolvedIntent?.getBooleanExtra("originalIntent", false)) - .isFalse() - assertThat(refinedResult?.resolvedIntent?.getBooleanExtra("mismatchedAlternate", false)) - .isFalse() - assertThat(refinedResult?.resolvedIntent?.getBooleanExtra("extraMatch", false)).isFalse() - } - - @Test - fun testDisplayResolveInfo_noSourceIntentMatchingProposedRefinement() { - val originalIntent = Intent("DONT_REFINE_ME") - originalIntent.putExtra("originalIntent", true) - val mismatchedAlternate = Intent("DOESNT_MATCH") - mismatchedAlternate.putExtra("mismatchedAlternate", true) - - val originalInfo = DisplayResolveInfo.newDisplayResolveInfo( - originalIntent, - ResolverDataProvider.createResolveInfo(3, 0), - "label", - "extended info", - originalIntent - ) - originalInfo.addAlternateSourceIntent(mismatchedAlternate) - - val refinement = Intent("PROPOSED_REFINEMENT") - assertThat(originalInfo.tryToCloneWithAppliedRefinement(refinement)).isNull() - } - - @Test - fun testNewMultiDisplayResolveInfo() { - val intent = Intent(Intent.ACTION_SEND) - intent.putExtra(Intent.EXTRA_TEXT, "testing intent sending") - intent.setType("text/plain") - - val resolveInfo = ResolverDataProvider.createResolveInfo(3, 0, PERSONAL_USER_HANDLE) - val firstTargetInfo = DisplayResolveInfo.newDisplayResolveInfo( - intent, - resolveInfo, - "label 1", - "extended info 1", - intent - ) - val secondTargetInfo = DisplayResolveInfo.newDisplayResolveInfo( - intent, - resolveInfo, - "label 2", - "extended info 2", - intent - ) - - val multiTargetInfo = MultiDisplayResolveInfo.newMultiDisplayResolveInfo( - listOf(firstTargetInfo, secondTargetInfo)) - - assertThat(multiTargetInfo.isMultiDisplayResolveInfo()).isTrue() - assertThat(multiTargetInfo.isDisplayResolveInfo()).isTrue() // From legacy inheritance. - assertThat(multiTargetInfo.isChooserTargetInfo()).isFalse() - - assertThat(multiTargetInfo.getExtendedInfo()).isNull() - - assertThat(multiTargetInfo.getAllDisplayTargets()) - .containsExactly(firstTargetInfo, secondTargetInfo) - - assertThat(multiTargetInfo.hasSelected()).isFalse() - assertThat(multiTargetInfo.getSelectedTarget()).isNull() - - multiTargetInfo.setSelected(1) - - assertThat(multiTargetInfo.hasSelected()).isTrue() - assertThat(multiTargetInfo.getSelectedTarget()).isEqualTo(secondTargetInfo) - - val refined = multiTargetInfo.tryToCloneWithAppliedRefinement(intent) - assertThat(refined).isInstanceOf(MultiDisplayResolveInfo::class.java) - assertThat((refined as MultiDisplayResolveInfo).hasSelected()) - .isEqualTo(multiTargetInfo.hasSelected()) - - // TODO: consider exercising activity-start behavior. - // TODO: consider exercising DisplayResolveInfo base class behavior. - } - - @Test - fun testNewMultiDisplayResolveInfo_getAllSourceIntents_fromSelectedTarget() { - val sendImage = Intent("SEND").apply { type = "image/png" } - val sendUri = Intent("SEND").apply { type = "text/uri" } - - val resolveInfo = ResolverDataProvider.createResolveInfo(1, 0) - - val imageOnlyTarget = DisplayResolveInfo.newDisplayResolveInfo( - sendImage, - resolveInfo, - "Send Image", - "Sends only images", - sendImage - ) - - val textOnlyTarget = DisplayResolveInfo.newDisplayResolveInfo( - sendUri, - resolveInfo, - "Send Text", - "Sends only text", - sendUri - ) - - val imageOrTextTarget = DisplayResolveInfo.newDisplayResolveInfo( - sendImage, - resolveInfo, - "Send Image or Text", - "Sends images or text", - sendImage - ).apply { - addAlternateSourceIntent(sendUri) - } - - val multiTargetInfo = MultiDisplayResolveInfo.newMultiDisplayResolveInfo( - listOf(imageOnlyTarget, textOnlyTarget, imageOrTextTarget) - ) - - multiTargetInfo.setSelected(0) - assertThat(multiTargetInfo.selectedTarget).isEqualTo(imageOnlyTarget) - assertThat(multiTargetInfo.allSourceIntents).isEqualTo(imageOnlyTarget.allSourceIntents) - - multiTargetInfo.setSelected(1) - assertThat(multiTargetInfo.selectedTarget).isEqualTo(textOnlyTarget) - assertThat(multiTargetInfo.allSourceIntents).isEqualTo(textOnlyTarget.allSourceIntents) - - multiTargetInfo.setSelected(2) - assertThat(multiTargetInfo.selectedTarget).isEqualTo(imageOrTextTarget) - assertThat(multiTargetInfo.allSourceIntents).isEqualTo(imageOrTextTarget.allSourceIntents) - } - - @Test - fun testNewMultiDisplayResolveInfo_tryToCloneWithAppliedRefinement_delegatedToSelectedTarget() { - val refined = Intent("SEND") - val sendImage = Intent("SEND") - val targetOne = spy( - DisplayResolveInfo.newDisplayResolveInfo( - sendImage, - ResolverDataProvider.createResolveInfo(1, 0), - "Target One", - "Target One", - sendImage - ) - ) - val targetTwo = mock<DisplayResolveInfo> { - whenever(tryToCloneWithAppliedRefinement(any())).thenReturn(this) - } - - val multiTargetInfo = MultiDisplayResolveInfo.newMultiDisplayResolveInfo( - listOf(targetOne, targetTwo) - ) - - multiTargetInfo.setSelected(1) - assertThat(multiTargetInfo.selectedTarget).isEqualTo(targetTwo) - - multiTargetInfo.tryToCloneWithAppliedRefinement(refined) - verify(targetTwo, times(1)).tryToCloneWithAppliedRefinement(refined) - verify(targetOne, never()).tryToCloneWithAppliedRefinement(any()) - } -} diff --git a/java/tests/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt b/java/tests/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt deleted file mode 100644 index 55cde497..00000000 --- a/java/tests/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt +++ /dev/null @@ -1,139 +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.contentpreview - -import android.content.Intent -import android.net.Uri -import com.android.intentresolver.TestPreviewImageLoader -import com.android.intentresolver.contentpreview.ChooserContentPreviewUi.ActionFactory -import com.android.intentresolver.mock -import com.android.intentresolver.whenever -import com.android.intentresolver.widget.ActionRow -import com.android.intentresolver.widget.ImagePreviewView -import com.google.common.truth.Truth.assertThat -import java.util.function.Consumer -import kotlin.coroutines.EmptyCoroutineContext -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.UnconfinedTestDispatcher -import org.junit.Test -import org.mockito.Mockito.never -import org.mockito.Mockito.times -import org.mockito.Mockito.verify - -class ChooserContentPreviewUiTest { - private val testScope = TestScope(EmptyCoroutineContext + UnconfinedTestDispatcher()) - private val previewData = mock<PreviewDataProvider>() - private val headlineGenerator = mock<HeadlineGenerator>() - private val imageLoader = TestPreviewImageLoader(emptyMap()) - private val actionFactory = - object : ActionFactory { - override fun getCopyButtonRunnable(): Runnable? = null - override fun getEditButtonRunnable(): Runnable? = null - override fun createCustomActions(): List<ActionRow.Action> = emptyList() - override fun getModifyShareAction(): ActionRow.Action? = null - override fun getExcludeSharedTextAction(): Consumer<Boolean> = Consumer<Boolean> {} - } - private val transitionCallback = mock<ImagePreviewView.TransitionElementStatusCallback>() - - @Test - fun test_textPreviewType_useTextPreviewUi() { - whenever(previewData.previewType).thenReturn(ContentPreviewType.CONTENT_PREVIEW_TEXT) - val testSubject = - ChooserContentPreviewUi( - testScope, - previewData, - Intent(Intent.ACTION_VIEW), - imageLoader, - actionFactory, - transitionCallback, - headlineGenerator, - ) - assertThat(testSubject.preferredContentPreview) - .isEqualTo(ContentPreviewType.CONTENT_PREVIEW_TEXT) - assertThat(testSubject.mContentPreviewUi).isInstanceOf(TextContentPreviewUi::class.java) - verify(transitionCallback, times(1)).onAllTransitionElementsReady() - } - - @Test - fun test_filePreviewType_useFilePreviewUi() { - whenever(previewData.previewType).thenReturn(ContentPreviewType.CONTENT_PREVIEW_FILE) - val testSubject = - ChooserContentPreviewUi( - testScope, - previewData, - Intent(Intent.ACTION_SEND), - imageLoader, - actionFactory, - transitionCallback, - headlineGenerator, - ) - assertThat(testSubject.preferredContentPreview) - .isEqualTo(ContentPreviewType.CONTENT_PREVIEW_FILE) - assertThat(testSubject.mContentPreviewUi).isInstanceOf(FileContentPreviewUi::class.java) - verify(transitionCallback, times(1)).onAllTransitionElementsReady() - } - - @Test - fun test_imagePreviewTypeWithText_useFilePlusTextPreviewUi() { - val uri = Uri.parse("content://org.pkg.app/img.png") - whenever(previewData.previewType).thenReturn(ContentPreviewType.CONTENT_PREVIEW_IMAGE) - whenever(previewData.uriCount).thenReturn(2) - whenever(previewData.firstFileInfo) - .thenReturn(FileInfo.Builder(uri).withPreviewUri(uri).withMimeType("image/png").build()) - whenever(previewData.imagePreviewFileInfoFlow).thenReturn(MutableSharedFlow()) - val testSubject = - ChooserContentPreviewUi( - testScope, - previewData, - Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_TEXT, "Shared text") }, - imageLoader, - actionFactory, - transitionCallback, - headlineGenerator, - ) - assertThat(testSubject.mContentPreviewUi) - .isInstanceOf(FilesPlusTextContentPreviewUi::class.java) - verify(previewData, times(1)).imagePreviewFileInfoFlow - verify(transitionCallback, times(1)).onAllTransitionElementsReady() - } - - @Test - fun test_imagePreviewTypeWithoutText_useImagePreviewUi() { - val uri = Uri.parse("content://org.pkg.app/img.png") - whenever(previewData.previewType).thenReturn(ContentPreviewType.CONTENT_PREVIEW_IMAGE) - whenever(previewData.uriCount).thenReturn(2) - whenever(previewData.firstFileInfo) - .thenReturn(FileInfo.Builder(uri).withPreviewUri(uri).withMimeType("image/png").build()) - whenever(previewData.imagePreviewFileInfoFlow).thenReturn(MutableSharedFlow()) - val testSubject = - ChooserContentPreviewUi( - testScope, - previewData, - Intent(Intent.ACTION_SEND), - imageLoader, - actionFactory, - transitionCallback, - headlineGenerator, - ) - assertThat(testSubject.preferredContentPreview) - .isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE) - assertThat(testSubject.mContentPreviewUi).isInstanceOf(UnifiedContentPreviewUi::class.java) - verify(previewData, times(1)).imagePreviewFileInfoFlow - verify(transitionCallback, never()).onAllTransitionElementsReady() - } -} diff --git a/java/tests/src/com/android/intentresolver/contentpreview/ContentPreviewUiTest.kt b/java/tests/src/com/android/intentresolver/contentpreview/ContentPreviewUiTest.kt deleted file mode 100644 index 6db53a9e..00000000 --- a/java/tests/src/com/android/intentresolver/contentpreview/ContentPreviewUiTest.kt +++ /dev/null @@ -1,41 +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.contentpreview - -import com.android.intentresolver.widget.ScrollableImagePreviewView.PreviewType -import com.google.common.truth.Truth.assertThat -import org.junit.Test - -class ContentPreviewUiTest { - @Test - fun testPreviewTypes() { - val typeClassifier = - object : MimeTypeClassifier { - override fun isImageType(type: String?) = (type == "image") - override fun isVideoType(type: String?) = (type == "video") - } - - assertThat(ContentPreviewUi.getPreviewType(typeClassifier, "image")) - .isEqualTo(PreviewType.Image) - assertThat(ContentPreviewUi.getPreviewType(typeClassifier, "video")) - .isEqualTo(PreviewType.Video) - assertThat(ContentPreviewUi.getPreviewType(typeClassifier, "other")) - .isEqualTo(PreviewType.File) - assertThat(ContentPreviewUi.getPreviewType(typeClassifier, null)) - .isEqualTo(PreviewType.File) - } -} diff --git a/java/tests/src/com/android/intentresolver/contentpreview/FileContentPreviewUiTest.kt b/java/tests/src/com/android/intentresolver/contentpreview/FileContentPreviewUiTest.kt deleted file mode 100644 index d2d952ae..00000000 --- a/java/tests/src/com/android/intentresolver/contentpreview/FileContentPreviewUiTest.kt +++ /dev/null @@ -1,99 +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.contentpreview - -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.TextView -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import com.android.intentresolver.R -import com.android.intentresolver.mock -import com.android.intentresolver.whenever -import com.android.intentresolver.widget.ActionRow -import com.google.common.truth.Truth.assertThat -import java.util.function.Consumer -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class FileContentPreviewUiTest { - private val fileCount = 2 - private val text = "Sharing 2 files" - private val actionFactory = - object : ChooserContentPreviewUi.ActionFactory { - override fun getEditButtonRunnable(): Runnable? = null - override fun getCopyButtonRunnable(): Runnable? = null - override fun createCustomActions(): List<ActionRow.Action> = emptyList() - override fun getModifyShareAction(): ActionRow.Action? = null - override fun getExcludeSharedTextAction(): Consumer<Boolean> = Consumer<Boolean> {} - } - private val headlineGenerator = - mock<HeadlineGenerator> { whenever(getFilesHeadline(fileCount)).thenReturn(text) } - - private val context - get() = InstrumentationRegistry.getInstrumentation().context - - private val testSubject = - FileContentPreviewUi( - fileCount, - actionFactory, - headlineGenerator, - ) - - @Test - fun test_display_titleIsDisplayed() { - val layoutInflater = LayoutInflater.from(context) - val gridLayout = layoutInflater.inflate(R.layout.chooser_grid, null, false) as ViewGroup - - val previewView = - testSubject.display( - context.resources, - layoutInflater, - gridLayout, - /*headlineViewParent=*/ null - ) - - assertThat(previewView).isNotNull() - val headlineView = previewView?.findViewById<TextView>(R.id.headline) - assertThat(headlineView).isNotNull() - assertThat(headlineView?.text).isEqualTo(text) - } - - @Test - fun test_displayWithExternalHeaderView() { - val layoutInflater = LayoutInflater.from(context) - val gridLayout = - layoutInflater.inflate(R.layout.chooser_grid_scrollable_preview, null, false) - as ViewGroup - val externalHeaderView = - gridLayout.requireViewById<View>(R.id.chooser_headline_row_container) - - assertThat(externalHeaderView.findViewById<View>(R.id.headline)).isNull() - - val previewView = - testSubject.display(context.resources, layoutInflater, gridLayout, externalHeaderView) - - assertThat(previewView).isNotNull() - assertThat(previewView.findViewById<View>(R.id.headline)).isNull() - - val headlineView = externalHeaderView.findViewById<TextView>(R.id.headline) - assertThat(headlineView).isNotNull() - assertThat(headlineView?.text).isEqualTo(text) - } -} diff --git a/java/tests/src/com/android/intentresolver/contentpreview/FilesPlusTextContentPreviewUiTest.kt b/java/tests/src/com/android/intentresolver/contentpreview/FilesPlusTextContentPreviewUiTest.kt deleted file mode 100644 index 7cc0b4b2..00000000 --- a/java/tests/src/com/android/intentresolver/contentpreview/FilesPlusTextContentPreviewUiTest.kt +++ /dev/null @@ -1,423 +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.contentpreview - -import android.net.Uri -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.TextView -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation -import com.android.intentresolver.R -import com.android.intentresolver.mock -import com.android.intentresolver.whenever -import com.android.intentresolver.widget.ActionRow -import com.google.common.truth.Truth.assertThat -import com.google.common.truth.Truth.assertWithMessage -import java.util.function.Consumer -import kotlin.coroutines.EmptyCoroutineContext -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.UnconfinedTestDispatcher -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mockito.anyInt -import org.mockito.Mockito.never -import org.mockito.Mockito.times -import org.mockito.Mockito.verify - -private const val HEADLINE_IMAGES = "Image Headline" -private const val HEADLINE_VIDEOS = "Video Headline" -private const val HEADLINE_FILES = "Files Headline" -private const val SHARED_TEXT = "Some text to share" - -@RunWith(AndroidJUnit4::class) -class FilesPlusTextContentPreviewUiTest { - private val testScope = TestScope(EmptyCoroutineContext + UnconfinedTestDispatcher()) - private val actionFactory = - object : ChooserContentPreviewUi.ActionFactory { - override fun getEditButtonRunnable(): Runnable? = null - override fun getCopyButtonRunnable(): Runnable? = null - override fun createCustomActions(): List<ActionRow.Action> = emptyList() - override fun getModifyShareAction(): ActionRow.Action? = null - override fun getExcludeSharedTextAction(): Consumer<Boolean> = Consumer<Boolean> {} - } - private val imageLoader = mock<ImageLoader>() - private val headlineGenerator = - mock<HeadlineGenerator> { - whenever(getImagesHeadline(anyInt())).thenReturn(HEADLINE_IMAGES) - whenever(getVideosHeadline(anyInt())).thenReturn(HEADLINE_VIDEOS) - whenever(getFilesHeadline(anyInt())).thenReturn(HEADLINE_FILES) - } - - private val context - get() = getInstrumentation().context - - @Test - fun test_displayImagesPlusTextWithoutUriMetadata_showImagesHeadline() { - val sharedFileCount = 2 - val previewView = testLoadingHeadline("image/*", sharedFileCount) - - verify(headlineGenerator, times(1)).getImagesHeadline(sharedFileCount) - verifyPreviewHeadline(previewView, HEADLINE_IMAGES) - verifySharedText(previewView) - } - - @Test - fun test_displayImagesPlusTextWithoutUriMetadataExternalHeader_showImagesHeadline() { - val sharedFileCount = 2 - val (previewView, headerParent) = testLoadingExternalHeadline("image/*", sharedFileCount) - - verify(headlineGenerator, times(1)).getImagesHeadline(sharedFileCount) - verifyInternalHeadlineAbsence(previewView) - verifyPreviewHeadline(headerParent, HEADLINE_IMAGES) - verifySharedText(previewView) - } - - @Test - fun test_displayVideosPlusTextWithoutUriMetadata_showVideosHeadline() { - val sharedFileCount = 2 - val previewView = testLoadingHeadline("video/*", sharedFileCount) - - verify(headlineGenerator, times(1)).getVideosHeadline(sharedFileCount) - verifyPreviewHeadline(previewView, HEADLINE_VIDEOS) - verifySharedText(previewView) - } - - @Test - fun test_displayVideosPlusTextWithoutUriMetadataExternalHeader_showVideosHeadline() { - val sharedFileCount = 2 - val (previewView, headerParent) = testLoadingExternalHeadline("video/*", sharedFileCount) - - verify(headlineGenerator, times(1)).getVideosHeadline(sharedFileCount) - verifyInternalHeadlineAbsence(previewView) - verifyPreviewHeadline(headerParent, HEADLINE_VIDEOS) - verifySharedText(previewView) - } - - @Test - fun test_displayDocsPlusTextWithoutUriMetadata_showFilesHeadline() { - val sharedFileCount = 2 - val previewView = testLoadingHeadline("application/pdf", sharedFileCount) - - verify(headlineGenerator, times(1)).getFilesHeadline(sharedFileCount) - verifyPreviewHeadline(previewView, HEADLINE_FILES) - verifySharedText(previewView) - } - - @Test - fun test_displayDocsPlusTextWithoutUriMetadataExternalHeader_showFilesHeadline() { - val sharedFileCount = 2 - val (previewView, headerParent) = - testLoadingExternalHeadline("application/pdf", sharedFileCount) - - verify(headlineGenerator, times(1)).getFilesHeadline(sharedFileCount) - verifyInternalHeadlineAbsence(previewView) - verifyPreviewHeadline(headerParent, HEADLINE_FILES) - verifySharedText(previewView) - } - - @Test - fun test_displayMixedContentPlusTextWithoutUriMetadata_showFilesHeadline() { - val sharedFileCount = 2 - val previewView = testLoadingHeadline("*/*", sharedFileCount) - - verify(headlineGenerator, times(1)).getFilesHeadline(sharedFileCount) - verifyPreviewHeadline(previewView, HEADLINE_FILES) - verifySharedText(previewView) - } - - @Test - fun test_displayMixedContentPlusTextWithoutUriMetadataExternalHeader_showFilesHeadline() { - val sharedFileCount = 2 - val (previewView, headerParent) = testLoadingExternalHeadline("*/*", sharedFileCount) - - verify(headlineGenerator, times(1)).getFilesHeadline(sharedFileCount) - verifyInternalHeadlineAbsence(previewView) - verifyPreviewHeadline(headerParent, HEADLINE_FILES) - verifySharedText(previewView) - } - - @Test - fun test_displayImagesPlusTextWithUriMetadataSet_showImagesHeadline() { - val loadedFileMetadata = createFileInfosWithMimeTypes("image/png", "image/jpeg") - val sharedFileCount = loadedFileMetadata.size - val previewView = testLoadingHeadline("image/*", sharedFileCount, loadedFileMetadata) - - verify(headlineGenerator, times(1)).getImagesHeadline(sharedFileCount) - verifyPreviewHeadline(previewView, HEADLINE_IMAGES) - verifySharedText(previewView) - } - - @Test - fun test_displayImagesPlusTextWithUriMetadataSetExternalHeader_showImagesHeadline() { - val loadedFileMetadata = createFileInfosWithMimeTypes("image/png", "image/jpeg") - val sharedFileCount = loadedFileMetadata.size - val (previewView, headerParent) = - testLoadingExternalHeadline("image/*", sharedFileCount, loadedFileMetadata) - - verify(headlineGenerator, times(1)).getImagesHeadline(sharedFileCount) - verifyInternalHeadlineAbsence(previewView) - verifyPreviewHeadline(headerParent, HEADLINE_IMAGES) - verifySharedText(previewView) - } - - @Test - fun test_displayVideosPlusTextWithUriMetadataSet_showVideosHeadline() { - val loadedFileMetadata = createFileInfosWithMimeTypes("video/mp4", "video/mp4") - val sharedFileCount = loadedFileMetadata.size - val previewView = testLoadingHeadline("video/*", sharedFileCount, loadedFileMetadata) - - verify(headlineGenerator, times(1)).getVideosHeadline(sharedFileCount) - verifyPreviewHeadline(previewView, HEADLINE_VIDEOS) - verifySharedText(previewView) - } - - @Test - fun test_displayVideosPlusTextWithUriMetadataSetExternalHeader_showVideosHeadline() { - val loadedFileMetadata = createFileInfosWithMimeTypes("video/mp4", "video/mp4") - val sharedFileCount = loadedFileMetadata.size - val (previewView, headerParent) = - testLoadingExternalHeadline("video/*", sharedFileCount, loadedFileMetadata) - - verify(headlineGenerator, times(1)).getVideosHeadline(sharedFileCount) - verifyInternalHeadlineAbsence(previewView) - verifyPreviewHeadline(headerParent, HEADLINE_VIDEOS) - verifySharedText(previewView) - } - - @Test - fun test_displayImagesAndVideosPlusTextWithUriMetadataSet_showFilesHeadline() { - val loadedFileMetadata = createFileInfosWithMimeTypes("image/png", "video/mp4") - val sharedFileCount = loadedFileMetadata.size - val previewView = testLoadingHeadline("*/*", sharedFileCount, loadedFileMetadata) - - verify(headlineGenerator, times(1)).getFilesHeadline(sharedFileCount) - verifyPreviewHeadline(previewView, HEADLINE_FILES) - verifySharedText(previewView) - } - - @Test - fun test_displayImagesAndVideosPlusTextWithUriMetadataSetExternalHeader_showFilesHeadline() { - val loadedFileMetadata = createFileInfosWithMimeTypes("image/png", "video/mp4") - val sharedFileCount = loadedFileMetadata.size - val (previewView, headerParent) = - testLoadingExternalHeadline("*/*", sharedFileCount, loadedFileMetadata) - - verify(headlineGenerator, times(1)).getFilesHeadline(sharedFileCount) - verifyInternalHeadlineAbsence(previewView) - verifyPreviewHeadline(headerParent, HEADLINE_FILES) - verifySharedText(previewView) - } - - @Test - fun test_displayDocsPlusTextWithUriMetadataSet_showFilesHeadline() { - val loadedFileMetadata = createFileInfosWithMimeTypes("application/pdf", "application/pdf") - val sharedFileCount = loadedFileMetadata.size - val previewView = - testLoadingHeadline("application/pdf", sharedFileCount, loadedFileMetadata) - - verify(headlineGenerator, times(1)).getFilesHeadline(sharedFileCount) - verifyPreviewHeadline(previewView, HEADLINE_FILES) - verifySharedText(previewView) - } - - @Test - fun test_displayDocsPlusTextWithUriMetadataSetExternalHeader_showFilesHeadline() { - val loadedFileMetadata = createFileInfosWithMimeTypes("application/pdf", "application/pdf") - val sharedFileCount = loadedFileMetadata.size - val (previewView, headerParent) = - testLoadingExternalHeadline("application/pdf", sharedFileCount, loadedFileMetadata) - - verify(headlineGenerator, times(1)).getFilesHeadline(sharedFileCount) - verifyInternalHeadlineAbsence(previewView) - verifyPreviewHeadline(headerParent, HEADLINE_FILES) - verifySharedText(previewView) - } - - @Test - fun test_uriMetadataIsMoreSpecificThanIntentMimeType_headlineGetsUpdated() { - val sharedFileCount = 2 - val testSubject = - FilesPlusTextContentPreviewUi( - testScope, - /*isSingleImage=*/ false, - sharedFileCount, - SHARED_TEXT, - /*intentMimeType=*/ "*/*", - actionFactory, - imageLoader, - DefaultMimeTypeClassifier, - headlineGenerator - ) - val layoutInflater = LayoutInflater.from(context) - val gridLayout = layoutInflater.inflate(R.layout.chooser_grid, null, false) as ViewGroup - - val previewView = - testSubject.display(context.resources, LayoutInflater.from(context), gridLayout, null) - - verify(headlineGenerator, times(1)).getFilesHeadline(sharedFileCount) - verify(headlineGenerator, never()).getImagesHeadline(sharedFileCount) - verifyPreviewHeadline(previewView, HEADLINE_FILES) - - testSubject.updatePreviewMetadata(createFileInfosWithMimeTypes("image/png", "image/jpg")) - - verify(headlineGenerator, times(1)).getFilesHeadline(sharedFileCount) - verify(headlineGenerator, times(1)).getImagesHeadline(sharedFileCount) - verifyPreviewHeadline(previewView, HEADLINE_IMAGES) - } - - @Test - fun test_uriMetadataIsMoreSpecificThanIntentMimeTypeExternalHeader_headlineGetsUpdated() { - val sharedFileCount = 2 - val testSubject = - FilesPlusTextContentPreviewUi( - testScope, - /*isSingleImage=*/ false, - sharedFileCount, - SHARED_TEXT, - /*intentMimeType=*/ "*/*", - actionFactory, - imageLoader, - DefaultMimeTypeClassifier, - headlineGenerator - ) - val layoutInflater = LayoutInflater.from(context) - val gridLayout = - layoutInflater.inflate(R.layout.chooser_grid_scrollable_preview, null, false) - as ViewGroup - val externalHeaderView = - gridLayout.requireViewById<View>(R.id.chooser_headline_row_container) - - assertWithMessage("External headline should not be inflated by default") - .that(externalHeaderView.findViewById<View>(R.id.headline)) - .isNull() - - val previewView = - testSubject.display( - context.resources, - LayoutInflater.from(context), - gridLayout, - externalHeaderView - ) - - verify(headlineGenerator, times(1)).getFilesHeadline(sharedFileCount) - verify(headlineGenerator, never()).getImagesHeadline(sharedFileCount) - verifyInternalHeadlineAbsence(previewView) - verifyPreviewHeadline(externalHeaderView, HEADLINE_FILES) - - testSubject.updatePreviewMetadata(createFileInfosWithMimeTypes("image/png", "image/jpg")) - - verify(headlineGenerator, times(1)).getFilesHeadline(sharedFileCount) - verify(headlineGenerator, times(1)).getImagesHeadline(sharedFileCount) - verifyPreviewHeadline(externalHeaderView, HEADLINE_IMAGES) - } - - private fun testLoadingHeadline( - intentMimeType: String, - sharedFileCount: Int, - loadedFileMetadata: List<FileInfo>? = null, - ): ViewGroup? { - val testSubject = - FilesPlusTextContentPreviewUi( - testScope, - /*isSingleImage=*/ false, - sharedFileCount, - SHARED_TEXT, - intentMimeType, - actionFactory, - imageLoader, - DefaultMimeTypeClassifier, - headlineGenerator - ) - val layoutInflater = LayoutInflater.from(context) - val gridLayout = layoutInflater.inflate(R.layout.chooser_grid, null, false) as ViewGroup - - loadedFileMetadata?.let(testSubject::updatePreviewMetadata) - return testSubject.display( - context.resources, - LayoutInflater.from(context), - gridLayout, - /*headlineViewParent=*/ null - ) - } - - private fun testLoadingExternalHeadline( - intentMimeType: String, - sharedFileCount: Int, - loadedFileMetadata: List<FileInfo>? = null, - ): Pair<ViewGroup?, View> { - val testSubject = - FilesPlusTextContentPreviewUi( - testScope, - /*isSingleImage=*/ false, - sharedFileCount, - SHARED_TEXT, - intentMimeType, - actionFactory, - imageLoader, - DefaultMimeTypeClassifier, - headlineGenerator - ) - val layoutInflater = LayoutInflater.from(context) - val gridLayout = - layoutInflater.inflate(R.layout.chooser_grid_scrollable_preview, null, false) - as ViewGroup - val externalHeaderView = - gridLayout.requireViewById<View>(R.id.chooser_headline_row_container) - - assertWithMessage("External headline should not be inflated by default") - .that(externalHeaderView.findViewById<View>(R.id.headline)) - .isNull() - - loadedFileMetadata?.let(testSubject::updatePreviewMetadata) - return testSubject.display( - context.resources, - LayoutInflater.from(context), - gridLayout, - externalHeaderView - ) to externalHeaderView - } - - private fun createFileInfosWithMimeTypes(vararg mimeTypes: String): List<FileInfo> { - val uri = Uri.parse("content://pkg.app/file") - return mimeTypes.map { mimeType -> FileInfo.Builder(uri).withMimeType(mimeType).build() } - } - - private fun verifyPreviewHeadline(headerViewParent: View?, expectedText: String) { - assertThat(headerViewParent).isNotNull() - val headlineView = headerViewParent?.findViewById<TextView>(R.id.headline) - assertThat(headlineView).isNotNull() - assertThat(headlineView?.text).isEqualTo(expectedText) - } - - private fun verifySharedText(previewView: ViewGroup?) { - assertThat(previewView).isNotNull() - val textContentView = previewView?.findViewById<TextView>(R.id.content_preview_text) - assertThat(textContentView).isNotNull() - assertThat(textContentView?.text).isEqualTo(SHARED_TEXT) - } - - private fun verifyInternalHeadlineAbsence(previewView: ViewGroup?) { - assertWithMessage("Preview parent should not be null").that(previewView).isNotNull() - assertWithMessage( - "Preview headline should not be inflated when an external headline is used" - ) - .that(previewView?.findViewById<View>(R.id.headline)) - .isNull() - } -} diff --git a/java/tests/src/com/android/intentresolver/contentpreview/HeadlineGeneratorImplTest.kt b/java/tests/src/com/android/intentresolver/contentpreview/HeadlineGeneratorImplTest.kt deleted file mode 100644 index a65280e5..00000000 --- a/java/tests/src/com/android/intentresolver/contentpreview/HeadlineGeneratorImplTest.kt +++ /dev/null @@ -1,61 +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.contentpreview - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import org.junit.Test -import org.junit.runner.RunWith -import com.google.common.truth.Truth.assertThat - -@RunWith(AndroidJUnit4::class) -class HeadlineGeneratorImplTest { - @Test - fun testHeadlineGeneration() { - val generator = HeadlineGeneratorImpl( - InstrumentationRegistry.getInstrumentation().getTargetContext()) - val str = "Some string" - val url = "http://www.google.com" - - assertThat(generator.getTextHeadline(str)).isEqualTo("Sharing text") - assertThat(generator.getTextHeadline(url)).isEqualTo("Sharing link") - - assertThat(generator.getImagesWithTextHeadline(str, 1)).isEqualTo("Sharing image with text") - assertThat(generator.getImagesWithTextHeadline(url, 1)).isEqualTo("Sharing image with link") - assertThat(generator.getImagesWithTextHeadline(str, 5)).isEqualTo("Sharing 5 images with text") - assertThat(generator.getImagesWithTextHeadline(url, 5)).isEqualTo("Sharing 5 images with link") - - assertThat(generator.getVideosWithTextHeadline(str, 1)).isEqualTo("Sharing video with text") - assertThat(generator.getVideosWithTextHeadline(url, 1)).isEqualTo("Sharing video with link") - assertThat(generator.getVideosWithTextHeadline(str, 5)).isEqualTo("Sharing 5 videos with text") - assertThat(generator.getVideosWithTextHeadline(url, 5)).isEqualTo("Sharing 5 videos with link") - - assertThat(generator.getFilesWithTextHeadline(str, 1)).isEqualTo("Sharing file with text") - assertThat(generator.getFilesWithTextHeadline(url, 1)).isEqualTo("Sharing file with link") - assertThat(generator.getFilesWithTextHeadline(str, 5)).isEqualTo("Sharing 5 files with text") - assertThat(generator.getFilesWithTextHeadline(url, 5)).isEqualTo("Sharing 5 files with link") - - assertThat(generator.getImagesHeadline(1)).isEqualTo("Sharing image") - assertThat(generator.getImagesHeadline(4)).isEqualTo("Sharing 4 images") - - assertThat(generator.getVideosHeadline(1)).isEqualTo("Sharing video") - assertThat(generator.getVideosHeadline(4)).isEqualTo("Sharing 4 videos") - - assertThat(generator.getFilesHeadline(1)).isEqualTo("Sharing 1 file") - assertThat(generator.getFilesHeadline(4)).isEqualTo("Sharing 4 files") - } -} diff --git a/java/tests/src/com/android/intentresolver/contentpreview/ImagePreviewImageLoaderTest.kt b/java/tests/src/com/android/intentresolver/contentpreview/ImagePreviewImageLoaderTest.kt deleted file mode 100644 index 89978707..00000000 --- a/java/tests/src/com/android/intentresolver/contentpreview/ImagePreviewImageLoaderTest.kt +++ /dev/null @@ -1,367 +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.contentpreview - -import android.content.ContentResolver -import android.graphics.Bitmap -import android.net.Uri -import android.util.Size -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.coroutineScope -import androidx.lifecycle.testing.TestLifecycleOwner -import com.android.intentresolver.any -import com.android.intentresolver.anyOrNull -import com.android.intentresolver.mock -import com.android.intentresolver.whenever -import com.google.common.truth.Truth.assertThat -import java.util.ArrayDeque -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit.MILLISECONDS -import java.util.concurrent.TimeUnit.SECONDS -import java.util.concurrent.atomic.AtomicInteger -import kotlin.coroutines.CoroutineContext -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineName -import kotlinx.coroutines.CoroutineStart.UNDISPATCHED -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.Runnable -import kotlinx.coroutines.async -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.plus -import kotlinx.coroutines.sync.Semaphore -import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.TestCoroutineScheduler -import kotlinx.coroutines.test.UnconfinedTestDispatcher -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.runTest -import kotlinx.coroutines.test.setMain -import kotlinx.coroutines.yield -import org.junit.After -import org.junit.Assert.assertTrue -import org.junit.Before -import org.junit.Test -import org.mockito.Mockito.never -import org.mockito.Mockito.times -import org.mockito.Mockito.verify - -@OptIn(ExperimentalCoroutinesApi::class) -class ImagePreviewImageLoaderTest { - private val imageSize = Size(300, 300) - private val uriOne = Uri.parse("content://org.package.app/image-1.png") - private val uriTwo = Uri.parse("content://org.package.app/image-2.png") - private val bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) - private val contentResolver = - mock<ContentResolver> { - whenever(loadThumbnail(any(), any(), anyOrNull())).thenReturn(bitmap) - } - private val lifecycleOwner = TestLifecycleOwner() - private val dispatcher = UnconfinedTestDispatcher() - private lateinit var testSubject: ImagePreviewImageLoader - - @Before - fun setup() { - Dispatchers.setMain(dispatcher) - lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_CREATE) - // create test subject after we've updated the lifecycle dispatcher - testSubject = - ImagePreviewImageLoader( - lifecycleOwner.lifecycle.coroutineScope + dispatcher, - imageSize.width, - contentResolver, - cacheSize = 1, - ) - } - - @After - fun cleanup() { - lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) - Dispatchers.resetMain() - } - - @Test - fun prePopulate_cachesImagesUpToTheCacheSize() = runTest { - testSubject.prePopulate(listOf(uriOne, uriTwo)) - - verify(contentResolver, times(1)).loadThumbnail(uriOne, imageSize, null) - verify(contentResolver, never()).loadThumbnail(uriTwo, imageSize, null) - - testSubject(uriOne) - verify(contentResolver, times(1)).loadThumbnail(uriOne, imageSize, null) - } - - @Test - fun invoke_returnCachedImageWhenCalledTwice() = runTest { - testSubject(uriOne) - testSubject(uriOne) - - verify(contentResolver, times(1)).loadThumbnail(any(), any(), anyOrNull()) - } - - @Test - fun invoke_whenInstructed_doesNotCache() = runTest { - testSubject(uriOne, false) - testSubject(uriOne, false) - - verify(contentResolver, times(2)).loadThumbnail(any(), any(), anyOrNull()) - } - - @Test - fun invoke_overlappedRequests_Deduplicate() = runTest { - val scheduler = TestCoroutineScheduler() - val dispatcher = StandardTestDispatcher(scheduler) - val testSubject = - ImagePreviewImageLoader( - lifecycleOwner.lifecycle.coroutineScope + dispatcher, - imageSize.width, - contentResolver, - cacheSize = 1, - ) - coroutineScope { - launch(start = UNDISPATCHED) { testSubject(uriOne, false) } - launch(start = UNDISPATCHED) { testSubject(uriOne, false) } - scheduler.advanceUntilIdle() - } - - verify(contentResolver, times(1)).loadThumbnail(any(), any(), anyOrNull()) - } - - @Test - fun invoke_oldRecordsEvictedFromTheCache() = runTest { - testSubject(uriOne) - testSubject(uriTwo) - testSubject(uriTwo) - testSubject(uriOne) - - verify(contentResolver, times(2)).loadThumbnail(uriOne, imageSize, null) - verify(contentResolver, times(1)).loadThumbnail(uriTwo, imageSize, null) - } - - @Test - fun invoke_doNotCacheNulls() = runTest { - whenever(contentResolver.loadThumbnail(any(), any(), anyOrNull())).thenReturn(null) - testSubject(uriOne) - testSubject(uriOne) - - verify(contentResolver, times(2)).loadThumbnail(uriOne, imageSize, null) - } - - @Test(expected = CancellationException::class) - fun invoke_onClosedImageLoaderScope_throwsCancellationException() = runTest { - lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) - testSubject(uriOne) - } - - @Test(expected = CancellationException::class) - fun invoke_imageLoaderScopeClosedMidflight_throwsCancellationException() = runTest { - val scheduler = TestCoroutineScheduler() - val dispatcher = StandardTestDispatcher(scheduler) - val testSubject = - ImagePreviewImageLoader( - lifecycleOwner.lifecycle.coroutineScope + dispatcher, - imageSize.width, - contentResolver, - cacheSize = 1, - ) - coroutineScope { - val deferred = async(start = UNDISPATCHED) { testSubject(uriOne, false) } - lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) - scheduler.advanceUntilIdle() - deferred.await() - } - } - - @Test - fun invoke_multipleCallsWithDifferentCacheInstructions_cachingPrevails() = runTest { - val scheduler = TestCoroutineScheduler() - val dispatcher = StandardTestDispatcher(scheduler) - val testSubject = - ImagePreviewImageLoader( - lifecycleOwner.lifecycle.coroutineScope + dispatcher, - imageSize.width, - contentResolver, - cacheSize = 1, - ) - coroutineScope { - launch(start = UNDISPATCHED) { testSubject(uriOne, false) } - launch(start = UNDISPATCHED) { testSubject(uriOne, true) } - scheduler.advanceUntilIdle() - } - testSubject(uriOne, true) - - verify(contentResolver, times(1)).loadThumbnail(uriOne, imageSize, null) - } - - @Test - fun invoke_semaphoreGuardsContentResolverCalls() = runTest { - val contentResolver = - mock<ContentResolver> { - whenever(loadThumbnail(any(), any(), anyOrNull())) - .thenThrow(SecurityException("test")) - } - val acquireCount = AtomicInteger() - val releaseCount = AtomicInteger() - val testSemaphore = - object : Semaphore { - override val availablePermits: Int - get() = error("Unexpected invocation") - - override suspend fun acquire() { - acquireCount.getAndIncrement() - } - - override fun tryAcquire(): Boolean { - error("Unexpected invocation") - } - - override fun release() { - releaseCount.getAndIncrement() - } - } - - val testSubject = - ImagePreviewImageLoader( - lifecycleOwner.lifecycle.coroutineScope + dispatcher, - imageSize.width, - contentResolver, - cacheSize = 1, - testSemaphore, - ) - testSubject(uriOne, false) - - verify(contentResolver, times(1)).loadThumbnail(uriOne, imageSize, null) - assertThat(acquireCount.get()).isEqualTo(1) - assertThat(releaseCount.get()).isEqualTo(1) - } - - @Test - fun invoke_semaphoreIsReleasedAfterContentResolverFailure() = runTest { - val semaphoreDeferred = CompletableDeferred<Unit>() - val releaseCount = AtomicInteger() - val testSemaphore = - object : Semaphore { - override val availablePermits: Int - get() = error("Unexpected invocation") - - override suspend fun acquire() { - semaphoreDeferred.await() - } - - override fun tryAcquire(): Boolean { - error("Unexpected invocation") - } - - override fun release() { - releaseCount.getAndIncrement() - } - } - - val testSubject = - ImagePreviewImageLoader( - lifecycleOwner.lifecycle.coroutineScope + dispatcher, - imageSize.width, - contentResolver, - cacheSize = 1, - testSemaphore, - ) - launch(start = UNDISPATCHED) { testSubject(uriOne, false) } - - verify(contentResolver, never()).loadThumbnail(any(), any(), anyOrNull()) - - semaphoreDeferred.complete(Unit) - - verify(contentResolver, times(1)).loadThumbnail(uriOne, imageSize, null) - assertThat(releaseCount.get()).isEqualTo(1) - } - - @Test - fun invoke_multipleSimultaneousCalls_limitOnNumberOfSimultaneousOutgoingCallsIsRespected() { - val requestCount = 4 - val thumbnailCallsCdl = CountDownLatch(requestCount) - val pendingThumbnailCalls = ArrayDeque<CountDownLatch>() - val contentResolver = - mock<ContentResolver> { - whenever(loadThumbnail(any(), any(), anyOrNull())).thenAnswer { - val latch = CountDownLatch(1) - synchronized(pendingThumbnailCalls) { pendingThumbnailCalls.offer(latch) } - thumbnailCallsCdl.countDown() - assertTrue("Timeout waiting thumbnail calls", latch.await(1, SECONDS)) - bitmap - } - } - val name = "LoadImage" - val maxSimultaneousRequests = 2 - val threadsStartedCdl = CountDownLatch(requestCount) - val dispatcher = NewThreadDispatcher(name) { threadsStartedCdl.countDown() } - val testSubject = - ImagePreviewImageLoader( - lifecycleOwner.lifecycle.coroutineScope + dispatcher + CoroutineName(name), - imageSize.width, - contentResolver, - cacheSize = 1, - maxSimultaneousRequests, - ) - runTest { - repeat(requestCount) { - launch { testSubject(Uri.parse("content://org.pkg.app/image-$it.png")) } - } - yield() - // wait for all requests to be dispatched - assertThat(threadsStartedCdl.await(5, SECONDS)).isTrue() - - assertThat(thumbnailCallsCdl.await(100, MILLISECONDS)).isFalse() - synchronized(pendingThumbnailCalls) { - assertThat(pendingThumbnailCalls.size).isEqualTo(maxSimultaneousRequests) - } - - pendingThumbnailCalls.poll()?.countDown() - assertThat(thumbnailCallsCdl.await(100, MILLISECONDS)).isFalse() - synchronized(pendingThumbnailCalls) { - assertThat(pendingThumbnailCalls.size).isEqualTo(maxSimultaneousRequests) - } - - pendingThumbnailCalls.poll()?.countDown() - assertThat(thumbnailCallsCdl.await(100, MILLISECONDS)).isTrue() - synchronized(pendingThumbnailCalls) { - assertThat(pendingThumbnailCalls.size).isEqualTo(maxSimultaneousRequests) - } - for (cdl in pendingThumbnailCalls) { - cdl.countDown() - } - } - } -} - -private class NewThreadDispatcher( - private val coroutineName: String, - private val launchedCallback: () -> Unit -) : CoroutineDispatcher() { - override fun isDispatchNeeded(context: CoroutineContext): Boolean = true - - override fun dispatch(context: CoroutineContext, block: Runnable) { - Thread { - if (coroutineName == context[CoroutineName.Key]?.name) { - launchedCallback() - } - block.run() - } - .start() - } -} diff --git a/java/tests/src/com/android/intentresolver/contentpreview/PreviewDataProviderTest.kt b/java/tests/src/com/android/intentresolver/contentpreview/PreviewDataProviderTest.kt deleted file mode 100644 index 4a8c1392..00000000 --- a/java/tests/src/com/android/intentresolver/contentpreview/PreviewDataProviderTest.kt +++ /dev/null @@ -1,367 +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.contentpreview - -import android.content.ContentInterface -import android.content.Intent -import android.database.MatrixCursor -import android.media.MediaMetadata -import android.net.Uri -import android.provider.DocumentsContract -import com.android.intentresolver.mock -import com.android.intentresolver.whenever -import com.google.common.truth.Truth.assertThat -import kotlin.coroutines.EmptyCoroutineContext -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.toList -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.UnconfinedTestDispatcher -import kotlinx.coroutines.test.runTest -import org.junit.Test -import org.mockito.Mockito.any -import org.mockito.Mockito.never -import org.mockito.Mockito.times -import org.mockito.Mockito.verify - -@OptIn(ExperimentalCoroutinesApi::class) -class PreviewDataProviderTest { - private val contentResolver = mock<ContentInterface>() - private val mimeTypeClassifier = DefaultMimeTypeClassifier - private val testScope = TestScope(EmptyCoroutineContext + UnconfinedTestDispatcher()) - - @Test - fun test_nonSendIntentAction_resolvesToTextPreviewUiSynchronously() { - val targetIntent = Intent(Intent.ACTION_VIEW) - val testSubject = - PreviewDataProvider(testScope, targetIntent, contentResolver, mimeTypeClassifier) - - assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_TEXT) - verify(contentResolver, never()).getType(any()) - } - - @Test - fun test_sendSingleTextFileWithoutPreview_resolvesToFilePreviewUi() { - val uri = Uri.parse("content://org.pkg.app/notes.txt") - val targetIntent = - Intent(Intent.ACTION_SEND).apply { - putExtra(Intent.EXTRA_STREAM, uri) - type = "text/plain" - } - whenever(contentResolver.getType(uri)).thenReturn("text/plain") - val testSubject = - PreviewDataProvider(testScope, targetIntent, contentResolver, mimeTypeClassifier) - - assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_FILE) - assertThat(testSubject.uriCount).isEqualTo(1) - assertThat(testSubject.firstFileInfo?.uri).isEqualTo(uri) - verify(contentResolver, times(1)).getType(any()) - } - - @Test - fun test_sendIntentWithoutUris_resolvesToTextPreviewUiSynchronously() { - val targetIntent = Intent(Intent.ACTION_SEND).apply { type = "image/png" } - val testSubject = - PreviewDataProvider(testScope, targetIntent, contentResolver, mimeTypeClassifier) - - assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_TEXT) - verify(contentResolver, never()).getType(any()) - } - - @Test - fun test_sendSingleImage_resolvesToImagePreviewUi() { - val uri = Uri.parse("content://org.pkg.app/image.png") - val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_STREAM, uri) } - whenever(contentResolver.getType(uri)).thenReturn("image/png") - val testSubject = - PreviewDataProvider(testScope, targetIntent, contentResolver, mimeTypeClassifier) - - assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE) - assertThat(testSubject.uriCount).isEqualTo(1) - assertThat(testSubject.firstFileInfo?.uri).isEqualTo(uri) - assertThat(testSubject.firstFileInfo?.previewUri).isEqualTo(uri) - verify(contentResolver, times(1)).getType(any()) - } - - @Test - fun test_sendSingleNonImage_resolvesToFilePreviewUi() { - val uri = Uri.parse("content://org.pkg.app/paper.pdf") - val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_STREAM, uri) } - whenever(contentResolver.getType(uri)).thenReturn("application/pdf") - val testSubject = - PreviewDataProvider(testScope, targetIntent, contentResolver, mimeTypeClassifier) - - assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_FILE) - assertThat(testSubject.uriCount).isEqualTo(1) - assertThat(testSubject.firstFileInfo?.uri).isEqualTo(uri) - assertThat(testSubject.firstFileInfo?.previewUri).isNull() - verify(contentResolver, times(1)).getType(any()) - } - - @Test - fun test_sendSingleImageWithFailingGetType_resolvesToFilePreviewUi() { - val uri = Uri.parse("content://org.pkg.app/image.png") - val targetIntent = - Intent(Intent.ACTION_SEND).apply { - type = "image/png" - putExtra(Intent.EXTRA_STREAM, uri) - } - whenever(contentResolver.getType(uri)).thenThrow(SecurityException("test failure")) - val testSubject = - PreviewDataProvider(testScope, targetIntent, contentResolver, mimeTypeClassifier) - - assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_FILE) - assertThat(testSubject.uriCount).isEqualTo(1) - assertThat(testSubject.firstFileInfo?.uri).isEqualTo(uri) - assertThat(testSubject.firstFileInfo?.previewUri).isNull() - verify(contentResolver, times(1)).getType(any()) - } - - @Test - fun test_sendSingleImageWithFailingMetadata_resolvesToFilePreviewUi() { - val uri = Uri.parse("content://org.pkg.app/image.png") - val targetIntent = - Intent(Intent.ACTION_SEND).apply { - type = "image/png" - putExtra(Intent.EXTRA_STREAM, uri) - } - whenever(contentResolver.getStreamTypes(uri, "*/*")) - .thenThrow(SecurityException("test failure")) - whenever(contentResolver.query(uri, METADATA_COLUMNS, null, null)) - .thenThrow(SecurityException("test failure")) - val testSubject = - PreviewDataProvider(testScope, targetIntent, contentResolver, mimeTypeClassifier) - - assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_FILE) - assertThat(testSubject.uriCount).isEqualTo(1) - assertThat(testSubject.firstFileInfo?.uri).isEqualTo(uri) - assertThat(testSubject.firstFileInfo?.previewUri).isNull() - verify(contentResolver, times(1)).getType(any()) - } - - @Test - fun test_SingleNonImageUriWithImageTypeInGetStreamTypes_useImagePreviewUi() { - val uri = Uri.parse("content://org.pkg.app/paper.pdf") - val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_STREAM, uri) } - whenever(contentResolver.getStreamTypes(uri, "*/*")) - .thenReturn(arrayOf("application/pdf", "image/png")) - val testSubject = - PreviewDataProvider(testScope, targetIntent, contentResolver, mimeTypeClassifier) - - assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE) - assertThat(testSubject.uriCount).isEqualTo(1) - assertThat(testSubject.firstFileInfo?.uri).isEqualTo(uri) - assertThat(testSubject.firstFileInfo?.previewUri).isEqualTo(uri) - verify(contentResolver, times(1)).getType(any()) - } - - @Test - fun test_SingleNonImageUriWithThumbnailFlag_useImagePreviewUi() { - testMetadataToImagePreview( - columns = arrayOf(DocumentsContract.Document.COLUMN_FLAGS), - values = - arrayOf( - DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL or - DocumentsContract.Document.FLAG_SUPPORTS_METADATA - ) - ) - } - - @Test - fun test_SingleNonImageUriWithMetadataIconUri_useImagePreviewUi() { - testMetadataToImagePreview( - columns = arrayOf(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI), - values = arrayOf("content://org.pkg.app/test.pdf?thumbnail"), - ) - } - - private fun testMetadataToImagePreview(columns: Array<String>, values: Array<Any>) { - val uri = Uri.parse("content://org.pkg.app/test.pdf") - val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_STREAM, uri) } - whenever(contentResolver.getType(uri)).thenReturn("application/pdf") - val cursor = MatrixCursor(columns).apply { addRow(values) } - whenever(contentResolver.query(uri, METADATA_COLUMNS, null, null)).thenReturn(cursor) - - val testSubject = - PreviewDataProvider(testScope, targetIntent, contentResolver, mimeTypeClassifier) - - assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE) - assertThat(testSubject.uriCount).isEqualTo(1) - assertThat(testSubject.firstFileInfo?.uri).isEqualTo(uri) - assertThat(testSubject.firstFileInfo?.previewUri).isNotNull() - verify(contentResolver, times(1)).getType(any()) - assertThat(cursor.isClosed).isTrue() - } - - @Test - fun test_emptyQueryResult_cursorGetsClosed() { - val uri = Uri.parse("content://org.pkg.app/test.pdf") - val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_STREAM, uri) } - whenever(contentResolver.getType(uri)).thenReturn("application/pdf") - val cursor = MatrixCursor(emptyArray()) - whenever(contentResolver.query(uri, METADATA_COLUMNS, null, null)).thenReturn(cursor) - - val testSubject = - PreviewDataProvider(testScope, targetIntent, contentResolver, mimeTypeClassifier) - - assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_FILE) - verify(contentResolver, times(1)).query(uri, METADATA_COLUMNS, null, null) - assertThat(cursor.isClosed).isTrue() - } - - @Test - fun test_multipleImageUri_useImagePreviewUi() { - val uri1 = Uri.parse("content://org.pkg.app/test.png") - val uri2 = Uri.parse("content://org.pkg.app/test.jpg") - val targetIntent = - Intent(Intent.ACTION_SEND_MULTIPLE).apply { - putExtra( - Intent.EXTRA_STREAM, - ArrayList<Uri>().apply { - add(uri1) - add(uri2) - } - ) - } - whenever(contentResolver.getType(uri1)).thenReturn("image/png") - whenever(contentResolver.getType(uri2)).thenReturn("image/jpeg") - val testSubject = - PreviewDataProvider(testScope, targetIntent, contentResolver, mimeTypeClassifier) - - assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE) - assertThat(testSubject.uriCount).isEqualTo(2) - assertThat(testSubject.firstFileInfo?.uri).isEqualTo(uri1) - assertThat(testSubject.firstFileInfo?.previewUri).isEqualTo(uri1) - // preview type can be determined by the first URI type - verify(contentResolver, times(1)).getType(any()) - } - - @Test - fun test_SomeImageUri_useImagePreviewUi() { - val uri1 = Uri.parse("content://org.pkg.app/test.png") - val uri2 = Uri.parse("content://org.pkg.app/test.pdf") - whenever(contentResolver.getType(uri1)).thenReturn("image/png") - whenever(contentResolver.getType(uri2)).thenReturn("application/pdf") - val targetIntent = - Intent(Intent.ACTION_SEND_MULTIPLE).apply { - putExtra( - Intent.EXTRA_STREAM, - ArrayList<Uri>().apply { - add(uri1) - add(uri2) - } - ) - } - val testSubject = - PreviewDataProvider(testScope, targetIntent, contentResolver, mimeTypeClassifier) - - assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE) - assertThat(testSubject.uriCount).isEqualTo(2) - assertThat(testSubject.firstFileInfo?.uri).isEqualTo(uri1) - assertThat(testSubject.firstFileInfo?.previewUri).isEqualTo(uri1) - // preview type can be determined by the first URI type - verify(contentResolver, times(1)).getType(any()) - } - - @Test - fun test_someNonImageUriWithPreview_useImagePreviewUi() { - val uri1 = Uri.parse("content://org.pkg.app/test.mp4") - val uri2 = Uri.parse("content://org.pkg.app/test.pdf") - val targetIntent = - Intent(Intent.ACTION_SEND_MULTIPLE).apply { - putExtra( - Intent.EXTRA_STREAM, - ArrayList<Uri>().apply { - add(uri1) - add(uri2) - } - ) - } - whenever(contentResolver.getType(uri1)).thenReturn("video/mpeg4") - whenever(contentResolver.getStreamTypes(uri1, "*/*")).thenReturn(arrayOf("image/png")) - whenever(contentResolver.getType(uri2)).thenReturn("application/pdf") - val testSubject = - PreviewDataProvider(testScope, targetIntent, contentResolver, mimeTypeClassifier) - - assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE) - assertThat(testSubject.uriCount).isEqualTo(2) - assertThat(testSubject.firstFileInfo?.uri).isEqualTo(uri1) - assertThat(testSubject.firstFileInfo?.previewUri).isEqualTo(uri1) - verify(contentResolver, times(2)).getType(any()) - } - - @Test - fun test_allNonImageUrisWithoutPreview_useFilePreviewUi() { - val uri1 = Uri.parse("content://org.pkg.app/test.html") - val uri2 = Uri.parse("content://org.pkg.app/test.pdf") - val targetIntent = - Intent(Intent.ACTION_SEND_MULTIPLE).apply { - putExtra( - Intent.EXTRA_STREAM, - ArrayList<Uri>().apply { - add(uri1) - add(uri2) - } - ) - } - whenever(contentResolver.getType(uri1)).thenReturn("text/html") - whenever(contentResolver.getType(uri2)).thenReturn("application/pdf") - val testSubject = - PreviewDataProvider(testScope, targetIntent, contentResolver, mimeTypeClassifier) - - assertThat(testSubject.previewType).isEqualTo(ContentPreviewType.CONTENT_PREVIEW_FILE) - assertThat(testSubject.uriCount).isEqualTo(2) - assertThat(testSubject.firstFileInfo?.uri).isEqualTo(uri1) - assertThat(testSubject.firstFileInfo?.previewUri).isNull() - verify(contentResolver, times(2)).getType(any()) - } - - @Test - fun test_imagePreviewFileInfoFlow_dataLoadedOnce() = - testScope.runTest { - val uri1 = Uri.parse("content://org.pkg.app/test.html") - val uri2 = Uri.parse("content://org.pkg.app/test.pdf") - val targetIntent = - Intent(Intent.ACTION_SEND_MULTIPLE).apply { - putExtra( - Intent.EXTRA_STREAM, - ArrayList<Uri>().apply { - add(uri1) - add(uri2) - } - ) - } - whenever(contentResolver.getType(uri1)).thenReturn("text/html") - whenever(contentResolver.getType(uri2)).thenReturn("application/pdf") - whenever(contentResolver.getStreamTypes(uri1, "*/*")) - .thenReturn(arrayOf("text/html", "image/jpeg")) - whenever(contentResolver.getStreamTypes(uri2, "*/*")) - .thenReturn(arrayOf("application/pdf", "image/png")) - val testSubject = - PreviewDataProvider(testScope, targetIntent, contentResolver, mimeTypeClassifier) - - val fileInfoListOne = testSubject.imagePreviewFileInfoFlow.toList() - val fileInfoListTwo = testSubject.imagePreviewFileInfoFlow.toList() - - assertThat(fileInfoListOne).hasSize(2) - assertThat(fileInfoListOne).containsAtLeastElementsIn(fileInfoListTwo).inOrder() - - verify(contentResolver, times(1)).getType(uri1) - verify(contentResolver, times(1)).getStreamTypes(uri1, "*/*") - verify(contentResolver, times(1)).getType(uri2) - verify(contentResolver, times(1)).getStreamTypes(uri2, "*/*") - } -} diff --git a/java/tests/src/com/android/intentresolver/contentpreview/TextContentPreviewUiTest.kt b/java/tests/src/com/android/intentresolver/contentpreview/TextContentPreviewUiTest.kt deleted file mode 100644 index 35362401..00000000 --- a/java/tests/src/com/android/intentresolver/contentpreview/TextContentPreviewUiTest.kt +++ /dev/null @@ -1,108 +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.contentpreview - -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.TextView -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import com.android.intentresolver.R -import com.android.intentresolver.mock -import com.android.intentresolver.whenever -import com.android.intentresolver.widget.ActionRow -import com.google.common.truth.Truth.assertThat -import java.util.function.Consumer -import kotlin.coroutines.EmptyCoroutineContext -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.UnconfinedTestDispatcher -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class TextContentPreviewUiTest { - private val text = "Shared Text" - private val title = "Preview Title" - private val testScope = TestScope(EmptyCoroutineContext + UnconfinedTestDispatcher()) - private val actionFactory = - object : ChooserContentPreviewUi.ActionFactory { - override fun getEditButtonRunnable(): Runnable? = null - override fun getCopyButtonRunnable(): Runnable? = null - override fun createCustomActions(): List<ActionRow.Action> = emptyList() - override fun getModifyShareAction(): ActionRow.Action? = null - override fun getExcludeSharedTextAction(): Consumer<Boolean> = Consumer<Boolean> {} - } - private val imageLoader = mock<ImageLoader>() - private val headlineGenerator = - mock<HeadlineGenerator> { whenever(getTextHeadline(text)).thenReturn(text) } - - private val context - get() = InstrumentationRegistry.getInstrumentation().context - - private val testSubject = - TextContentPreviewUi( - testScope, - text, - title, - /*previewThumbnail=*/ null, - actionFactory, - imageLoader, - headlineGenerator, - ) - - @Test - fun test_display_headlineIsDisplayed() { - val layoutInflater = LayoutInflater.from(context) - val gridLayout = layoutInflater.inflate(R.layout.chooser_grid, null, false) as ViewGroup - - val previewView = - testSubject.display( - context.resources, - layoutInflater, - gridLayout, - /*headlineViewParent=*/ null - ) - - assertThat(previewView).isNotNull() - val headlineView = previewView?.findViewById<TextView>(R.id.headline) - assertThat(headlineView).isNotNull() - assertThat(headlineView?.text).isEqualTo(text) - } - - @Test - fun test_displayWithExternalHeaderView_externalHeaderIsDisplayed() { - val layoutInflater = LayoutInflater.from(context) - val gridLayout = - layoutInflater.inflate(R.layout.chooser_grid_scrollable_preview, null, false) - as ViewGroup - val externalHeaderView = - gridLayout.requireViewById<View>(R.id.chooser_headline_row_container) - - assertThat(externalHeaderView.findViewById<View>(R.id.headline)).isNull() - - val previewView = - testSubject.display(context.resources, layoutInflater, gridLayout, externalHeaderView) - - assertThat(previewView).isNotNull() - assertThat(previewView.findViewById<View>(R.id.headline)).isNull() - - val headlineView = externalHeaderView.findViewById<TextView>(R.id.headline) - assertThat(headlineView).isNotNull() - assertThat(headlineView?.text).isEqualTo(text) - } -} diff --git a/java/tests/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUiTest.kt b/java/tests/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUiTest.kt deleted file mode 100644 index 7e07e0ca..00000000 --- a/java/tests/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUiTest.kt +++ /dev/null @@ -1,348 +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.contentpreview - -import android.net.Uri -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.TextView -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation -import com.android.intentresolver.R -import com.android.intentresolver.mock -import com.android.intentresolver.whenever -import com.android.intentresolver.widget.ImagePreviewView.TransitionElementStatusCallback -import com.google.common.truth.Truth -import com.google.common.truth.Truth.assertWithMessage -import kotlin.coroutines.EmptyCoroutineContext -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.asFlow -import kotlinx.coroutines.flow.takeWhile -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.UnconfinedTestDispatcher -import kotlinx.coroutines.test.runTest -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mockito.anyInt -import org.mockito.Mockito.times -import org.mockito.Mockito.verify - -private const val IMAGE_HEADLINE = "Image Headline" -private const val VIDEO_HEADLINE = "Video Headline" -private const val FILES_HEADLINE = "Files Headline" - -@RunWith(AndroidJUnit4::class) -class UnifiedContentPreviewUiTest { - private val testScope = TestScope(EmptyCoroutineContext + UnconfinedTestDispatcher()) - private val actionFactory = - mock<ChooserContentPreviewUi.ActionFactory> { - whenever(createCustomActions()).thenReturn(emptyList()) - } - private val imageLoader = mock<ImageLoader>() - private val headlineGenerator = - mock<HeadlineGenerator> { - whenever(getImagesHeadline(anyInt())).thenReturn(IMAGE_HEADLINE) - whenever(getVideosHeadline(anyInt())).thenReturn(VIDEO_HEADLINE) - whenever(getFilesHeadline(anyInt())).thenReturn(FILES_HEADLINE) - } - - private val context - get() = getInstrumentation().context - - @Test - fun test_displayImagesWithoutUriMetadata_showImagesHeadline() { - testLoadingHeadline("image/*", files = null) { previewView -> - verify(headlineGenerator, times(1)).getImagesHeadline(2) - verifyPreviewHeadline(previewView, IMAGE_HEADLINE) - } - } - - @Test - fun test_displayImagesWithoutUriMetadataExternalHeader_showImagesHeadline() { - testLoadingExternalHeadline("image/*", files = null) { externalHeaderView -> - verify(headlineGenerator, times(1)).getImagesHeadline(2) - verifyPreviewHeadline(externalHeaderView, IMAGE_HEADLINE) - } - } - - @Test - fun test_displayVideosWithoutUriMetadata_showImagesHeadline() { - testLoadingHeadline("video/*", files = null) { previewView -> - verify(headlineGenerator, times(1)).getVideosHeadline(2) - verifyPreviewHeadline(previewView, VIDEO_HEADLINE) - } - } - - @Test - fun test_displayVideosWithoutUriMetadataExternalHeader_showImagesHeadline() { - testLoadingExternalHeadline("video/*", files = null) { externalHeaderView -> - verify(headlineGenerator, times(1)).getVideosHeadline(2) - verifyPreviewHeadline(externalHeaderView, VIDEO_HEADLINE) - } - } - - @Test - fun test_displayDocumentsWithoutUriMetadata_showImagesHeadline() { - testLoadingHeadline("application/pdf", files = null) { previewView -> - verify(headlineGenerator, times(1)).getFilesHeadline(2) - verifyPreviewHeadline(previewView, FILES_HEADLINE) - } - } - - @Test - fun test_displayDocumentsWithoutUriMetadataExternalHeader_showImagesHeadline() { - testLoadingExternalHeadline("application/pdf", files = null) { externalHeaderView -> - verify(headlineGenerator, times(1)).getFilesHeadline(2) - verifyPreviewHeadline(externalHeaderView, FILES_HEADLINE) - } - } - - @Test - fun test_displayMixedContentWithoutUriMetadata_showImagesHeadline() { - testLoadingHeadline("*/*", files = null) { previewView -> - verify(headlineGenerator, times(1)).getFilesHeadline(2) - verifyPreviewHeadline(previewView, FILES_HEADLINE) - } - } - - @Test - fun test_displayMixedContentWithoutUriMetadataExternalHeader_showImagesHeadline() { - testLoadingExternalHeadline("*/*", files = null) { externalHeader -> - verify(headlineGenerator, times(1)).getFilesHeadline(2) - verifyPreviewHeadline(externalHeader, FILES_HEADLINE) - } - } - - @Test - fun test_displayImagesWithUriMetadataSet_showImagesHeadline() { - val uri = Uri.parse("content://pkg.app/image.png") - val files = - listOf( - FileInfo.Builder(uri).withMimeType("image/png").build(), - FileInfo.Builder(uri).withMimeType("image/jpeg").build(), - ) - testLoadingHeadline("image/*", files) { preivewView -> - verify(headlineGenerator, times(1)).getImagesHeadline(2) - verifyPreviewHeadline(preivewView, IMAGE_HEADLINE) - } - } - - @Test - fun test_displayImagesWithUriMetadataSetExternalHeader_showImagesHeadline() { - val uri = Uri.parse("content://pkg.app/image.png") - val files = - listOf( - FileInfo.Builder(uri).withMimeType("image/png").build(), - FileInfo.Builder(uri).withMimeType("image/jpeg").build(), - ) - testLoadingExternalHeadline("image/*", files) { externalHeader -> - verify(headlineGenerator, times(1)).getImagesHeadline(2) - verifyPreviewHeadline(externalHeader, IMAGE_HEADLINE) - } - } - - @Test - fun test_displayVideosWithUriMetadataSet_showImagesHeadline() { - val uri = Uri.parse("content://pkg.app/image.png") - val files = - listOf( - FileInfo.Builder(uri).withMimeType("video/mp4").build(), - FileInfo.Builder(uri).withMimeType("video/mp4").build(), - ) - testLoadingHeadline("video/*", files) { previewView -> - verify(headlineGenerator, times(1)).getVideosHeadline(2) - verifyPreviewHeadline(previewView, VIDEO_HEADLINE) - } - } - - @Test - fun test_displayVideosWithUriMetadataSetExternalHeader_showImagesHeadline() { - val uri = Uri.parse("content://pkg.app/image.png") - val files = - listOf( - FileInfo.Builder(uri).withMimeType("video/mp4").build(), - FileInfo.Builder(uri).withMimeType("video/mp4").build(), - ) - testLoadingExternalHeadline("video/*", files) { externalHeader -> - verify(headlineGenerator, times(1)).getVideosHeadline(2) - verifyPreviewHeadline(externalHeader, VIDEO_HEADLINE) - } - } - - @Test - fun test_displayImagesAndVideosWithUriMetadataSet_showImagesHeadline() { - val uri = Uri.parse("content://pkg.app/image.png") - val files = - listOf( - FileInfo.Builder(uri).withMimeType("image/png").build(), - FileInfo.Builder(uri).withMimeType("video/mp4").build(), - ) - testLoadingHeadline("*/*", files) { previewView -> - verify(headlineGenerator, times(1)).getFilesHeadline(2) - verifyPreviewHeadline(previewView, FILES_HEADLINE) - } - } - - @Test - fun test_displayImagesAndVideosWithUriMetadataSetExternalHeader_showImagesHeadline() { - val uri = Uri.parse("content://pkg.app/image.png") - val files = - listOf( - FileInfo.Builder(uri).withMimeType("image/png").build(), - FileInfo.Builder(uri).withMimeType("video/mp4").build(), - ) - testLoadingExternalHeadline("*/*", files) { externalHeader -> - verify(headlineGenerator, times(1)).getFilesHeadline(2) - verifyPreviewHeadline(externalHeader, FILES_HEADLINE) - } - } - - @Test - fun test_displayDocumentsWithUriMetadataSet_showImagesHeadline() { - val uri = Uri.parse("content://pkg.app/image.png") - val files = - listOf( - FileInfo.Builder(uri).withMimeType("application/pdf").build(), - FileInfo.Builder(uri).withMimeType("application/pdf").build(), - ) - testLoadingHeadline("application/pdf", files) { previewView -> - verify(headlineGenerator, times(1)).getFilesHeadline(2) - verifyPreviewHeadline(previewView, FILES_HEADLINE) - } - } - - @Test - fun test_displayDocumentsWithUriMetadataSetExternalHeader_showImagesHeadline() { - val uri = Uri.parse("content://pkg.app/image.png") - val files = - listOf( - FileInfo.Builder(uri).withMimeType("application/pdf").build(), - FileInfo.Builder(uri).withMimeType("application/pdf").build(), - ) - testLoadingExternalHeadline("application/pdf", files) { externalHeader -> - verify(headlineGenerator, times(1)).getFilesHeadline(2) - verifyPreviewHeadline(externalHeader, FILES_HEADLINE) - } - } - - private fun testLoadingHeadline( - intentMimeType: String, - files: List<FileInfo>?, - verificationBlock: (ViewGroup?) -> Unit, - ) { - testScope.runTest { - val endMarker = FileInfo.Builder(Uri.EMPTY).build() - val emptySourceFlow = MutableSharedFlow<FileInfo>(replay = 1) - val testSubject = - UnifiedContentPreviewUi( - testScope, - /*isSingleImage=*/ false, - intentMimeType, - actionFactory, - imageLoader, - DefaultMimeTypeClassifier, - object : TransitionElementStatusCallback { - override fun onTransitionElementReady(name: String) = Unit - override fun onAllTransitionElementsReady() = Unit - }, - files?.let { it.asFlow() } ?: emptySourceFlow.takeWhile { it !== endMarker }, - /*itemCount=*/ 2, - headlineGenerator - ) - val layoutInflater = LayoutInflater.from(context) - val gridLayout = layoutInflater.inflate(R.layout.chooser_grid, null, false) as ViewGroup - - val previewView = - testSubject.display( - context.resources, - LayoutInflater.from(context), - gridLayout, - /*headlineViewParent=*/ null - ) - emptySourceFlow.tryEmit(endMarker) - - verificationBlock(previewView) - } - } - - private fun testLoadingExternalHeadline( - intentMimeType: String, - files: List<FileInfo>?, - verificationBlock: (View?) -> Unit, - ) { - testScope.runTest { - val endMarker = FileInfo.Builder(Uri.EMPTY).build() - val emptySourceFlow = MutableSharedFlow<FileInfo>(replay = 1) - val testSubject = - UnifiedContentPreviewUi( - testScope, - /*isSingleImage=*/ false, - intentMimeType, - actionFactory, - imageLoader, - DefaultMimeTypeClassifier, - object : TransitionElementStatusCallback { - override fun onTransitionElementReady(name: String) = Unit - override fun onAllTransitionElementsReady() = Unit - }, - files?.let { it.asFlow() } ?: emptySourceFlow.takeWhile { it !== endMarker }, - /*itemCount=*/ 2, - headlineGenerator - ) - val layoutInflater = LayoutInflater.from(context) - val gridLayout = - layoutInflater.inflate(R.layout.chooser_grid_scrollable_preview, null, false) - as ViewGroup - val externalHeaderView = - gridLayout.requireViewById<View>(R.id.chooser_headline_row_container) - - assertWithMessage("External headline should not be inflated by default") - .that(externalHeaderView.findViewById<View>(R.id.headline)) - .isNull() - - val previewView = - testSubject.display( - context.resources, - LayoutInflater.from(context), - gridLayout, - externalHeaderView, - ) - - emptySourceFlow.tryEmit(endMarker) - - verifyInternalHeadlineAbsence(previewView) - verificationBlock(externalHeaderView) - } - } - - private fun verifyPreviewHeadline(headerViewParent: View?, expectedText: String) { - Truth.assertThat(headerViewParent).isNotNull() - val headlineView = headerViewParent?.findViewById<TextView>(R.id.headline) - Truth.assertThat(headlineView).isNotNull() - Truth.assertThat(headlineView?.text).isEqualTo(expectedText) - } - - private fun verifyInternalHeadlineAbsence(previewView: ViewGroup?) { - assertWithMessage("Preview parent should not be null").that(previewView).isNotNull() - assertWithMessage( - "Preview headline should not be inflated when an external headline is used" - ) - .that(previewView?.findViewById<View>(R.id.headline)) - .isNull() - } -} diff --git a/java/tests/src/com/android/intentresolver/emptystate/CompositeEmptyStateProviderTest.kt b/java/tests/src/com/android/intentresolver/emptystate/CompositeEmptyStateProviderTest.kt deleted file mode 100644 index 4c05dfb1..00000000 --- a/java/tests/src/com/android/intentresolver/emptystate/CompositeEmptyStateProviderTest.kt +++ /dev/null @@ -1,65 +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.emptystate - -import com.android.intentresolver.ResolverListAdapter -import com.android.intentresolver.mock -import com.google.common.truth.Truth.assertThat -import org.junit.Test - -class CompositeEmptyStateProviderTest { - val listAdapter = mock<ResolverListAdapter>() - - val emptyState1 = object : EmptyState {} - val emptyState2 = object : EmptyState {} - - val positiveEmptyStateProvider1 = - object : EmptyStateProvider { - override fun getEmptyState(listAdapter: ResolverListAdapter) = emptyState1 - } - val positiveEmptyStateProvider2 = - object : EmptyStateProvider { - override fun getEmptyState(listAdapter: ResolverListAdapter) = emptyState2 - } - val nullEmptyStateProvider = - object : EmptyStateProvider { - override fun getEmptyState(listAdapter: ResolverListAdapter) = null - } - - @Test - fun testComposedProvider_returnsFirstEmptyStateInOrder() { - val provider = - CompositeEmptyStateProvider( - nullEmptyStateProvider, - positiveEmptyStateProvider1, - positiveEmptyStateProvider2 - ) - assertThat(provider.getEmptyState(listAdapter)).isSameInstanceAs(emptyState1) - } - - @Test - fun testComposedProvider_allProvidersReturnNull_composedResultIsNull() { - val provider = CompositeEmptyStateProvider(nullEmptyStateProvider) - assertThat(provider.getEmptyState(listAdapter)).isNull() - } - - @Test - fun testComposedProvider_noEmptyStateIfNoDelegateProviders() { - val provider = CompositeEmptyStateProvider() - assertThat(provider.getEmptyState(listAdapter)).isNull() - } -} diff --git a/java/tests/src/com/android/intentresolver/emptystate/CrossProfileIntentsCheckerTest.kt b/java/tests/src/com/android/intentresolver/emptystate/CrossProfileIntentsCheckerTest.kt deleted file mode 100644 index 2bcddf59..00000000 --- a/java/tests/src/com/android/intentresolver/emptystate/CrossProfileIntentsCheckerTest.kt +++ /dev/null @@ -1,84 +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.emptystate - -import android.content.ContentResolver -import android.content.Intent -import android.content.pm.IPackageManager -import com.android.intentresolver.mock -import com.android.intentresolver.whenever -import com.google.common.truth.Truth.assertThat -import org.junit.Test -import org.mockito.Mockito.any -import org.mockito.Mockito.anyInt -import org.mockito.Mockito.eq -import org.mockito.Mockito.nullable - -class CrossProfileIntentsCheckerTest { - private val PERSONAL_USER_ID = 10 - private val WORK_USER_ID = 20 - - private val contentResolver = mock<ContentResolver>() - - @Test - fun testChecker_hasCrossProfileIntents() { - val packageManager = - mock<IPackageManager> { - whenever( - canForwardTo( - any(Intent::class.java), - nullable(String::class.java), - eq(PERSONAL_USER_ID), - eq(WORK_USER_ID) - ) - ) - .thenReturn(true) - } - val checker = CrossProfileIntentsChecker(contentResolver, packageManager) - val intents = listOf(Intent()) - assertThat(checker.hasCrossProfileIntents(intents, PERSONAL_USER_ID, WORK_USER_ID)).isTrue() - } - - @Test - fun testChecker_noCrossProfileIntents() { - val packageManager = - mock<IPackageManager> { - whenever( - canForwardTo( - any(Intent::class.java), - nullable(String::class.java), - anyInt(), - anyInt() - ) - ) - .thenReturn(false) - } - val checker = CrossProfileIntentsChecker(contentResolver, packageManager) - val intents = listOf(Intent()) - assertThat(checker.hasCrossProfileIntents(intents, PERSONAL_USER_ID, WORK_USER_ID)) - .isFalse() - } - - @Test - fun testChecker_noIntents() { - val packageManager = mock<IPackageManager>() - val checker = CrossProfileIntentsChecker(contentResolver, packageManager) - val intents = listOf<Intent>() - assertThat(checker.hasCrossProfileIntents(intents, PERSONAL_USER_ID, WORK_USER_ID)) - .isFalse() - } -} diff --git a/java/tests/src/com/android/intentresolver/emptystate/EmptyStateUiHelperTest.kt b/java/tests/src/com/android/intentresolver/emptystate/EmptyStateUiHelperTest.kt deleted file mode 100644 index bc5545db..00000000 --- a/java/tests/src/com/android/intentresolver/emptystate/EmptyStateUiHelperTest.kt +++ /dev/null @@ -1,113 +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.emptystate - -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.FrameLayout -import androidx.test.platform.app.InstrumentationRegistry -import com.google.common.truth.Truth.assertThat -import org.junit.Before -import org.junit.Test - -class EmptyStateUiHelperTest { - private val context = InstrumentationRegistry.getInstrumentation().getContext() - - lateinit var rootContainer: ViewGroup - lateinit var emptyStateTitleView: View - lateinit var emptyStateSubtitleView: View - lateinit var emptyStateButtonView: View - lateinit var emptyStateProgressView: View - lateinit var emptyStateDefaultTextView: View - lateinit var emptyStateContainerView: View - lateinit var emptyStateRootView: View - lateinit var emptyStateUiHelper: EmptyStateUiHelper - - @Before - fun setup() { - rootContainer = FrameLayout(context) - LayoutInflater.from(context) - .inflate( - com.android.intentresolver.R.layout.resolver_list_per_profile, - rootContainer, - true - ) - emptyStateRootView = - rootContainer.requireViewById(com.android.internal.R.id.resolver_empty_state) - emptyStateTitleView = - rootContainer.requireViewById(com.android.internal.R.id.resolver_empty_state_title) - emptyStateSubtitleView = rootContainer.requireViewById( - com.android.internal.R.id.resolver_empty_state_subtitle) - emptyStateButtonView = rootContainer.requireViewById( - com.android.internal.R.id.resolver_empty_state_button) - emptyStateProgressView = rootContainer.requireViewById( - com.android.internal.R.id.resolver_empty_state_progress) - emptyStateDefaultTextView = - rootContainer.requireViewById(com.android.internal.R.id.empty) - emptyStateContainerView = rootContainer.requireViewById( - com.android.internal.R.id.resolver_empty_state_container) - emptyStateUiHelper = EmptyStateUiHelper(rootContainer) - } - - @Test - fun testResetViewVisibilities() { - // First set each view's visibility to differ from the expected "reset" state so we can then - // assert that they're all reset afterward. - // TODO: for historic reasons "reset" doesn't cover `emptyStateContainerView`; should it? - emptyStateRootView.visibility = View.GONE - emptyStateTitleView.visibility = View.GONE - emptyStateSubtitleView.visibility = View.GONE - emptyStateButtonView.visibility = View.VISIBLE - emptyStateProgressView.visibility = View.VISIBLE - emptyStateDefaultTextView.visibility = View.VISIBLE - - emptyStateUiHelper.resetViewVisibilities() - - assertThat(emptyStateRootView.visibility).isEqualTo(View.VISIBLE) - assertThat(emptyStateTitleView.visibility).isEqualTo(View.VISIBLE) - assertThat(emptyStateSubtitleView.visibility).isEqualTo(View.VISIBLE) - assertThat(emptyStateButtonView.visibility).isEqualTo(View.INVISIBLE) - assertThat(emptyStateProgressView.visibility).isEqualTo(View.GONE) - assertThat(emptyStateDefaultTextView.visibility).isEqualTo(View.GONE) - } - - @Test - fun testShowSpinner() { - emptyStateTitleView.visibility = View.VISIBLE - emptyStateButtonView.visibility = View.VISIBLE - emptyStateProgressView.visibility = View.GONE - emptyStateDefaultTextView.visibility = View.VISIBLE - - emptyStateUiHelper.showSpinner() - - // TODO: should this cover any other views? Subtitle? - assertThat(emptyStateTitleView.visibility).isEqualTo(View.INVISIBLE) - assertThat(emptyStateButtonView.visibility).isEqualTo(View.INVISIBLE) - assertThat(emptyStateProgressView.visibility).isEqualTo(View.VISIBLE) - assertThat(emptyStateDefaultTextView.visibility).isEqualTo(View.GONE) - } - - @Test - fun testHide() { - emptyStateRootView.visibility = View.VISIBLE - - emptyStateUiHelper.hide() - - assertThat(emptyStateRootView.visibility).isEqualTo(View.GONE) - } -} diff --git a/java/tests/src/com/android/intentresolver/logging/EventLogImplTest.java b/java/tests/src/com/android/intentresolver/logging/EventLogImplTest.java deleted file mode 100644 index d75ea99b..00000000 --- a/java/tests/src/com/android/intentresolver/logging/EventLogImplTest.java +++ /dev/null @@ -1,425 +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.logging; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.AdditionalMatchers.gt; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -import android.content.Intent; -import android.metrics.LogMaker; - -import com.android.intentresolver.logging.EventLogImpl.SharesheetStandardEvent; -import com.android.intentresolver.logging.EventLogImpl.SharesheetStartedEvent; -import com.android.intentresolver.logging.EventLogImpl.SharesheetTargetSelectedEvent; -import com.android.intentresolver.contentpreview.ContentPreviewType; -import com.android.internal.logging.InstanceId; -import com.android.internal.logging.InstanceIdSequence; -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.UiEventLogger; -import com.android.internal.logging.UiEventLogger.UiEventEnum; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.internal.util.FrameworkStatsLog; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; - -@RunWith(MockitoJUnitRunner.class) -public final class EventLogImplTest { - @Mock private UiEventLogger mUiEventLog; - @Mock private FrameworkStatsLogger mFrameworkLog; - @Mock private MetricsLogger mMetricsLogger; - - private EventLogImpl mChooserLogger; - - private final InstanceIdSequence mSequence = EventLogImpl.newIdSequence(); - - @Before - public void setUp() { - mChooserLogger = new EventLogImpl(mUiEventLog, mFrameworkLog, mMetricsLogger, - mSequence.newInstanceId()); - } - - @After - public void tearDown() { - verifyNoMoreInteractions(mUiEventLog); - verifyNoMoreInteractions(mFrameworkLog); - verifyNoMoreInteractions(mMetricsLogger); - } - - @Test - public void testLogChooserActivityShown_personalProfile() { - final boolean isWorkProfile = false; - final String mimeType = "application/TestType"; - final long systemCost = 456; - - mChooserLogger.logChooserActivityShown(isWorkProfile, mimeType, systemCost); - - ArgumentCaptor<LogMaker> eventCaptor = ArgumentCaptor.forClass(LogMaker.class); - verify(mMetricsLogger).write(eventCaptor.capture()); - LogMaker event = eventCaptor.getValue(); - - assertThat(event.getCategory()).isEqualTo(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN); - assertThat(event.getSubtype()).isEqualTo(MetricsEvent.PARENT_PROFILE); - assertThat(event.getTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE)).isEqualTo(mimeType); - assertThat(event.getTaggedData(MetricsEvent.FIELD_TIME_TO_APP_TARGETS)) - .isEqualTo(systemCost); - } - - @Test - public void testLogChooserActivityShown_workProfile() { - final boolean isWorkProfile = true; - final String mimeType = "application/TestType"; - final long systemCost = 456; - - mChooserLogger.logChooserActivityShown(isWorkProfile, mimeType, systemCost); - - ArgumentCaptor<LogMaker> eventCaptor = ArgumentCaptor.forClass(LogMaker.class); - verify(mMetricsLogger).write(eventCaptor.capture()); - LogMaker event = eventCaptor.getValue(); - - assertThat(event.getCategory()).isEqualTo(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN); - assertThat(event.getSubtype()).isEqualTo(MetricsEvent.MANAGED_PROFILE); - assertThat(event.getTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE)).isEqualTo(mimeType); - assertThat(event.getTaggedData(MetricsEvent.FIELD_TIME_TO_APP_TARGETS)) - .isEqualTo(systemCost); - } - - @Test - public void testLogShareStarted() { - final String packageName = "com.test.foo"; - final String mimeType = "text/plain"; - final int appProvidedDirectTargets = 123; - final int appProvidedAppTargets = 456; - final boolean workProfile = true; - final int previewType = ContentPreviewType.CONTENT_PREVIEW_FILE; - final String intentAction = Intent.ACTION_SENDTO; - final int numCustomActions = 3; - final boolean modifyShareProvided = true; - - mChooserLogger.logShareStarted( - packageName, - mimeType, - appProvidedDirectTargets, - appProvidedAppTargets, - workProfile, - previewType, - intentAction, - numCustomActions, - modifyShareProvided); - - verify(mFrameworkLog).write( - eq(FrameworkStatsLog.SHARESHEET_STARTED), - eq(SharesheetStartedEvent.SHARE_STARTED.getId()), - eq(packageName), - /* instanceId=*/ gt(0), - eq(mimeType), - eq(appProvidedDirectTargets), - eq(appProvidedAppTargets), - eq(workProfile), - eq(FrameworkStatsLog.SHARESHEET_STARTED__PREVIEW_TYPE__CONTENT_PREVIEW_FILE), - eq(FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_SENDTO), - /* custom actions provided */ eq(numCustomActions), - /* reselection action provided */ eq(modifyShareProvided)); - } - - @Test - public void testLogShareTargetSelected() { - final int targetType = EventLogImpl.SELECTION_TYPE_SERVICE; - final String packageName = "com.test.foo"; - final int positionPicked = 123; - final int directTargetAlsoRanked = -1; - final int callerTargetCount = 0; - final boolean isPinned = true; - final boolean isSuccessfullySelected = true; - final long selectionCost = 456; - - mChooserLogger.logShareTargetSelected( - targetType, - packageName, - positionPicked, - directTargetAlsoRanked, - callerTargetCount, - /* directTargetHashed= */ null, - isPinned, - isSuccessfullySelected, - selectionCost); - - verify(mFrameworkLog).write( - eq(FrameworkStatsLog.RANKING_SELECTED), - eq(SharesheetTargetSelectedEvent.SHARESHEET_SERVICE_TARGET_SELECTED.getId()), - eq(packageName), - /* instanceId=*/ gt(0), - eq(positionPicked), - eq(isPinned)); - - ArgumentCaptor<LogMaker> eventCaptor = ArgumentCaptor.forClass(LogMaker.class); - verify(mMetricsLogger).write(eventCaptor.capture()); - LogMaker event = eventCaptor.getValue(); - assertThat(event.getCategory()).isEqualTo( - MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET); - assertThat(event.getSubtype()).isEqualTo(positionPicked); - } - - @Test - public void testLogActionSelected() { - mChooserLogger.logActionSelected(EventLogImpl.SELECTION_TYPE_COPY); - - verify(mFrameworkLog).write( - eq(FrameworkStatsLog.RANKING_SELECTED), - eq(SharesheetTargetSelectedEvent.SHARESHEET_COPY_TARGET_SELECTED.getId()), - eq(""), - /* instanceId=*/ gt(0), - eq(-1), - eq(false)); - - ArgumentCaptor<LogMaker> eventCaptor = ArgumentCaptor.forClass(LogMaker.class); - verify(mMetricsLogger).write(eventCaptor.capture()); - LogMaker event = eventCaptor.getValue(); - assertThat(event.getCategory()).isEqualTo( - MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SYSTEM_TARGET); - assertThat(event.getSubtype()).isEqualTo(1); - } - - @Test - public void testLogCustomActionSelected() { - final int position = 4; - mChooserLogger.logCustomActionSelected(position); - - verify(mFrameworkLog).write( - eq(FrameworkStatsLog.RANKING_SELECTED), - eq(SharesheetTargetSelectedEvent.SHARESHEET_CUSTOM_ACTION_SELECTED.getId()), - any(), anyInt(), eq(position), eq(false)); - } - - @Test - public void testLogDirectShareTargetReceived() { - final int category = MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER; - final int latency = 123; - - mChooserLogger.logDirectShareTargetReceived(category, latency); - - ArgumentCaptor<LogMaker> eventCaptor = ArgumentCaptor.forClass(LogMaker.class); - verify(mMetricsLogger).write(eventCaptor.capture()); - LogMaker event = eventCaptor.getValue(); - assertThat(event.getCategory()).isEqualTo(category); - assertThat(event.getSubtype()).isEqualTo(latency); - } - - @Test - public void testLogActionShareWithPreview() { - final int previewType = ContentPreviewType.CONTENT_PREVIEW_TEXT; - - mChooserLogger.logActionShareWithPreview(previewType); - - ArgumentCaptor<LogMaker> eventCaptor = ArgumentCaptor.forClass(LogMaker.class); - verify(mMetricsLogger).write(eventCaptor.capture()); - LogMaker event = eventCaptor.getValue(); - assertThat(event.getCategory()).isEqualTo(MetricsEvent.ACTION_SHARE_WITH_PREVIEW); - assertThat(event.getSubtype()).isEqualTo(previewType); - } - - @Test - public void testLogSharesheetTriggered() { - mChooserLogger.logSharesheetTriggered(); - verify(mUiEventLog).logWithInstanceId( - eq(SharesheetStandardEvent.SHARESHEET_TRIGGERED), eq(0), isNull(), any()); - } - - @Test - public void testLogSharesheetAppLoadComplete() { - mChooserLogger.logSharesheetAppLoadComplete(); - verify(mUiEventLog).logWithInstanceId( - eq(SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE), eq(0), isNull(), any()); - } - - @Test - public void testLogSharesheetDirectLoadComplete() { - mChooserLogger.logSharesheetDirectLoadComplete(); - verify(mUiEventLog).logWithInstanceId( - eq(SharesheetStandardEvent.SHARESHEET_DIRECT_LOAD_COMPLETE), - eq(0), - isNull(), - any()); - } - - @Test - public void testLogSharesheetDirectLoadTimeout() { - mChooserLogger.logSharesheetDirectLoadTimeout(); - verify(mUiEventLog).logWithInstanceId( - eq(SharesheetStandardEvent.SHARESHEET_DIRECT_LOAD_TIMEOUT), eq(0), isNull(), any()); - } - - @Test - public void testLogSharesheetProfileChanged() { - mChooserLogger.logSharesheetProfileChanged(); - verify(mUiEventLog).logWithInstanceId( - eq(SharesheetStandardEvent.SHARESHEET_PROFILE_CHANGED), eq(0), isNull(), any()); - } - - @Test - public void testLogSharesheetExpansionChanged_collapsed() { - mChooserLogger.logSharesheetExpansionChanged(/* isCollapsed=*/ true); - verify(mUiEventLog).logWithInstanceId( - eq(SharesheetStandardEvent.SHARESHEET_COLLAPSED), eq(0), isNull(), any()); - } - - @Test - public void testLogSharesheetExpansionChanged_expanded() { - mChooserLogger.logSharesheetExpansionChanged(/* isCollapsed=*/ false); - verify(mUiEventLog).logWithInstanceId( - eq(SharesheetStandardEvent.SHARESHEET_EXPANDED), eq(0), isNull(), any()); - } - - @Test - public void testLogSharesheetAppShareRankingTimeout() { - mChooserLogger.logSharesheetAppShareRankingTimeout(); - verify(mUiEventLog).logWithInstanceId( - eq(SharesheetStandardEvent.SHARESHEET_APP_SHARE_RANKING_TIMEOUT), - eq(0), - isNull(), - any()); - } - - @Test - public void testLogSharesheetEmptyDirectShareRow() { - mChooserLogger.logSharesheetEmptyDirectShareRow(); - verify(mUiEventLog).logWithInstanceId( - eq(SharesheetStandardEvent.SHARESHEET_EMPTY_DIRECT_SHARE_ROW), - eq(0), - isNull(), - any()); - } - - @Test - public void testDifferentLoggerInstancesUseDifferentInstanceIds() { - ArgumentCaptor<Integer> idIntCaptor = ArgumentCaptor.forClass(Integer.class); - EventLogImpl chooserLogger2 = - new EventLogImpl(mUiEventLog, mFrameworkLog, mMetricsLogger, - mSequence.newInstanceId()); - - final int targetType = EventLogImpl.SELECTION_TYPE_COPY; - final String packageName = "com.test.foo"; - final int positionPicked = 123; - final int directTargetAlsoRanked = -1; - final int callerTargetCount = 0; - final boolean isPinned = true; - final boolean isSuccessfullySelected = true; - final long selectionCost = 456; - - mChooserLogger.logShareTargetSelected( - targetType, - packageName, - positionPicked, - directTargetAlsoRanked, - callerTargetCount, - /* directTargetHashed= */ null, - isPinned, - isSuccessfullySelected, - selectionCost); - - chooserLogger2.logShareTargetSelected( - targetType, - packageName, - positionPicked, - directTargetAlsoRanked, - callerTargetCount, - /* directTargetHashed= */ null, - isPinned, - isSuccessfullySelected, - selectionCost); - - verify(mFrameworkLog, times(2)).write( - anyInt(), anyInt(), anyString(), idIntCaptor.capture(), anyInt(), anyBoolean()); - - int id1 = idIntCaptor.getAllValues().get(0); - int id2 = idIntCaptor.getAllValues().get(1); - - assertThat(id1).isGreaterThan(0); - assertThat(id2).isGreaterThan(0); - assertThat(id1).isNotEqualTo(id2); - } - - @Test - public void testUiAndFrameworkEventsUseSameInstanceIdForSameLoggerInstance() { - ArgumentCaptor<Integer> idIntCaptor = ArgumentCaptor.forClass(Integer.class); - ArgumentCaptor<InstanceId> idObjectCaptor = ArgumentCaptor.forClass(InstanceId.class); - - final int targetType = EventLogImpl.SELECTION_TYPE_COPY; - final String packageName = "com.test.foo"; - final int positionPicked = 123; - final int directTargetAlsoRanked = -1; - final int callerTargetCount = 0; - final boolean isPinned = true; - final boolean isSuccessfullySelected = true; - final long selectionCost = 456; - - mChooserLogger.logShareTargetSelected( - targetType, - packageName, - positionPicked, - directTargetAlsoRanked, - callerTargetCount, - /* directTargetHashed= */ null, - isPinned, - isSuccessfullySelected, - selectionCost); - - verify(mFrameworkLog).write( - anyInt(), anyInt(), anyString(), idIntCaptor.capture(), anyInt(), anyBoolean()); - - mChooserLogger.logSharesheetTriggered(); - verify(mUiEventLog).logWithInstanceId( - any(UiEventEnum.class), anyInt(), any(), idObjectCaptor.capture()); - - assertThat(idIntCaptor.getValue()).isGreaterThan(0); - assertThat(idObjectCaptor.getValue().getId()).isEqualTo(idIntCaptor.getValue()); - } - - @Test - public void testTargetSelectionCategories() { - assertThat(EventLogImpl.getTargetSelectionCategory( - EventLogImpl.SELECTION_TYPE_SERVICE)) - .isEqualTo(MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET); - assertThat(EventLogImpl.getTargetSelectionCategory( - EventLogImpl.SELECTION_TYPE_APP)) - .isEqualTo(MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET); - assertThat(EventLogImpl.getTargetSelectionCategory( - EventLogImpl.SELECTION_TYPE_STANDARD)) - .isEqualTo(MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET); - assertThat(EventLogImpl.getTargetSelectionCategory( - EventLogImpl.SELECTION_TYPE_COPY)).isEqualTo(0); - assertThat(EventLogImpl.getTargetSelectionCategory( - EventLogImpl.SELECTION_TYPE_NEARBY)).isEqualTo(0); - assertThat(EventLogImpl.getTargetSelectionCategory( - EventLogImpl.SELECTION_TYPE_EDIT)).isEqualTo(0); - } -} diff --git a/java/tests/src/com/android/intentresolver/logging/FakeEventLog.kt b/java/tests/src/com/android/intentresolver/logging/FakeEventLog.kt deleted file mode 100644 index 9ed47db6..00000000 --- a/java/tests/src/com/android/intentresolver/logging/FakeEventLog.kt +++ /dev/null @@ -1,197 +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.logging - -import android.net.Uri -import android.util.HashedStringCache -import android.util.Log -import com.android.internal.logging.InstanceId -import javax.inject.Inject - -private const val TAG = "EventLog" -private const val LOG = true - -/** A fake EventLog. */ -class FakeEventLog @Inject constructor(private val instanceId: InstanceId) : EventLog { - - var chooserActivityShown: ChooserActivityShown? = null - var actionSelected: ActionSelected? = null - var customActionSelected: CustomActionSelected? = null - var actionShareWithPreview: ActionShareWithPreview? = null - val shareTargetSelected: MutableList<ShareTargetSelected> = mutableListOf() - - private fun log(message: () -> Any?) { - if (LOG) { - Log.d(TAG, "[%04x] ".format(instanceId.id) + message()) - } - } - - override fun logChooserActivityShown( - isWorkProfile: Boolean, - targetMimeType: String?, - systemCost: Long - ) { - chooserActivityShown = ChooserActivityShown(isWorkProfile, targetMimeType, systemCost) - log { chooserActivityShown } - } - - override fun logShareStarted( - packageName: String?, - mimeType: String?, - appProvidedDirect: Int, - appProvidedApp: Int, - isWorkprofile: Boolean, - previewType: Int, - intent: String?, - customActionCount: Int, - modifyShareActionProvided: Boolean - ) { - log { - ShareStarted( - packageName, - mimeType, - appProvidedDirect, - appProvidedApp, - isWorkprofile, - previewType, - intent, - customActionCount, - modifyShareActionProvided - ) - } - } - - override fun logCustomActionSelected(positionPicked: Int) { - customActionSelected = CustomActionSelected(positionPicked) - log { "logCustomActionSelected(positionPicked=$positionPicked)" } - } - - override fun logShareTargetSelected( - targetType: Int, - packageName: String?, - positionPicked: Int, - directTargetAlsoRanked: Int, - numCallerProvided: Int, - directTargetHashed: HashedStringCache.HashResult?, - isPinned: Boolean, - successfullySelected: Boolean, - selectionCost: Long - ) { - shareTargetSelected.add( - ShareTargetSelected( - targetType, - packageName, - positionPicked, - directTargetAlsoRanked, - numCallerProvided, - directTargetHashed, - isPinned, - successfullySelected, - selectionCost - ) - ) - log { shareTargetSelected.last() } - shareTargetSelected.limitSize(10) - } - - private fun MutableList<*>.limitSize(n: Int) { - while (size > n) { - removeFirst() - } - } - - override fun logDirectShareTargetReceived(category: Int, latency: Int) { - log { "logDirectShareTargetReceived(category=$category, latency=$latency)" } - } - - override fun logActionShareWithPreview(previewType: Int) { - actionShareWithPreview = ActionShareWithPreview(previewType) - log { actionShareWithPreview } - } - - override fun logActionSelected(targetType: Int) { - actionSelected = ActionSelected(targetType) - log { actionSelected } - } - - override fun logContentPreviewWarning(uri: Uri?) { - log { "logContentPreviewWarning(uri=$uri)" } - } - - override fun logSharesheetTriggered() { - log { "logSharesheetTriggered()" } - } - - override fun logSharesheetAppLoadComplete() { - log { "logSharesheetAppLoadComplete()" } - } - - override fun logSharesheetDirectLoadComplete() { - log { "logSharesheetAppLoadComplete()" } - } - - override fun logSharesheetDirectLoadTimeout() { - log { "logSharesheetDirectLoadTimeout()" } - } - - override fun logSharesheetProfileChanged() { - log { "logSharesheetProfileChanged()" } - } - - override fun logSharesheetExpansionChanged(isCollapsed: Boolean) { - log { "logSharesheetExpansionChanged(isCollapsed=$isCollapsed)" } - } - - override fun logSharesheetAppShareRankingTimeout() { - log { "logSharesheetAppShareRankingTimeout()" } - } - - override fun logSharesheetEmptyDirectShareRow() { - log { "logSharesheetEmptyDirectShareRow()" } - } - - data class ActionSelected(val targetType: Int) - data class CustomActionSelected(val positionPicked: Int) - data class ActionShareWithPreview(val previewType: Int) - data class ChooserActivityShown( - val isWorkProfile: Boolean, - val targetMimeType: String?, - val systemCost: Long - ) - data class ShareStarted( - val packageName: String?, - val mimeType: String?, - val appProvidedDirect: Int, - val appProvidedApp: Int, - val isWorkprofile: Boolean, - val previewType: Int, - val intent: String?, - val customActionCount: Int, - val modifyShareActionProvided: Boolean - ) - data class ShareTargetSelected( - val targetType: Int, - val packageName: String?, - val positionPicked: Int, - val directTargetAlsoRanked: Int, - val numCallerProvided: Int, - val directTargetHashed: HashedStringCache.HashResult?, - val pinned: Boolean, - val successfullySelected: Boolean, - val selectionCost: Long - ) -} diff --git a/java/tests/src/com/android/intentresolver/logging/FakeFrameworkStatsLogger.kt b/java/tests/src/com/android/intentresolver/logging/FakeFrameworkStatsLogger.kt deleted file mode 100644 index dcf8d23f..00000000 --- a/java/tests/src/com/android/intentresolver/logging/FakeFrameworkStatsLogger.kt +++ /dev/null @@ -1,95 +0,0 @@ -package com.android.intentresolver.logging -/* - * 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. - */ - -import com.android.internal.util.FrameworkStatsLog - -internal data class ShareSheetStarted( - val frameworkEventId: Int = FrameworkStatsLog.SHARESHEET_STARTED, - val appEventId: Int, - val packageName: String?, - val instanceId: Int, - val mimeType: String?, - val numAppProvidedDirectTargets: Int, - val numAppProvidedAppTargets: Int, - val isWorkProfile: Boolean, - val previewType: Int, - val intentType: Int, - val numCustomActions: Int, - val modifyShareActionProvided: Boolean -) - -internal data class RankingSelected( - val frameworkEventId: Int = FrameworkStatsLog.RANKING_SELECTED, - val appEventId: Int, - val packageName: String?, - val instanceId: Int, - val positionPicked: Int, - val isPinned: Boolean -) - -internal class FakeFrameworkStatsLogger : FrameworkStatsLogger { - var shareSheetStarted: ShareSheetStarted? = null - var rankingSelected: RankingSelected? = null - override fun write( - frameworkEventId: Int, - appEventId: Int, - packageName: String?, - instanceId: Int, - mimeType: String?, - numAppProvidedDirectTargets: Int, - numAppProvidedAppTargets: Int, - isWorkProfile: Boolean, - previewType: Int, - intentType: Int, - numCustomActions: Int, - modifyShareActionProvided: Boolean - ) { - shareSheetStarted = - ShareSheetStarted( - frameworkEventId, - appEventId, - packageName, - instanceId, - mimeType, - numAppProvidedDirectTargets, - numAppProvidedAppTargets, - isWorkProfile, - previewType, - intentType, - numCustomActions, - modifyShareActionProvided - ) - } - override fun write( - frameworkEventId: Int, - appEventId: Int, - packageName: String?, - instanceId: Int, - positionPicked: Int, - isPinned: Boolean - ) { - rankingSelected = - RankingSelected( - frameworkEventId, - appEventId, - packageName, - instanceId, - positionPicked, - isPinned - ) - } -} diff --git a/java/tests/src/com/android/intentresolver/logging/TestEventLogModule.kt b/java/tests/src/com/android/intentresolver/logging/TestEventLogModule.kt deleted file mode 100644 index cd808af4..00000000 --- a/java/tests/src/com/android/intentresolver/logging/TestEventLogModule.kt +++ /dev/null @@ -1,39 +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.logging - -import com.android.internal.logging.InstanceId -import com.android.internal.logging.InstanceIdSequence -import dagger.Binds -import dagger.Module -import dagger.Provides -import dagger.hilt.android.components.ActivityComponent -import dagger.hilt.android.scopes.ActivityScoped -import dagger.hilt.testing.TestInstallIn - -/** Binds a [FakeEventLog] as [EventLog] in tests. */ -@Module -@TestInstallIn(components = [ActivityComponent::class], replaces = [EventLogModule::class]) -interface TestEventLogModule { - - @Binds @ActivityScoped fun fakeEventLog(impl: FakeEventLog): EventLog - - companion object { - @Provides - fun instanceId(sequence: InstanceIdSequence): InstanceId = sequence.newInstanceId() - } -} diff --git a/java/tests/src/com/android/intentresolver/model/AbstractResolverComparatorTest.java b/java/tests/src/com/android/intentresolver/model/AbstractResolverComparatorTest.java deleted file mode 100644 index 2140a67d..00000000 --- a/java/tests/src/com/android/intentresolver/model/AbstractResolverComparatorTest.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) 2019 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.model; - -import static junit.framework.Assert.assertEquals; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.ResolveInfo; -import android.os.Message; - -import androidx.test.InstrumentationRegistry; - -import com.android.intentresolver.ResolvedComponentInfo; -import com.android.intentresolver.chooser.TargetInfo; - -import com.google.android.collect.Lists; - -import org.junit.Test; - -import java.util.List; - -public class AbstractResolverComparatorTest { - - @Test - public void testPinned() { - ResolvedComponentInfo r1 = createResolvedComponentInfo( - new ComponentName("package", "class")); - r1.setPinned(true); - - ResolvedComponentInfo r2 = createResolvedComponentInfo( - new ComponentName("zackage", "zlass")); - - Context context = InstrumentationRegistry.getTargetContext(); - AbstractResolverComparator comparator = getTestComparator(context, null); - - assertEquals("Pinned ranks over unpinned", -1, comparator.compare(r1, r2)); - assertEquals("Unpinned ranks under pinned", 1, comparator.compare(r2, r1)); - } - - @Test - public void testBothPinned() { - ResolvedComponentInfo r1 = createResolvedComponentInfo( - new ComponentName("package", "class")); - r1.setPinned(true); - - ResolvedComponentInfo r2 = createResolvedComponentInfo( - new ComponentName("zackage", "zlass")); - r2.setPinned(true); - - Context context = InstrumentationRegistry.getTargetContext(); - AbstractResolverComparator comparator = getTestComparator(context, null); - - assertEquals("Both pinned should rank alphabetically", -1, comparator.compare(r1, r2)); - } - - @Test - public void testPromoteToFirst() { - ComponentName promoteToFirst = new ComponentName("promoted-package", "class"); - ResolvedComponentInfo r1 = createResolvedComponentInfo(promoteToFirst); - - ResolvedComponentInfo r2 = createResolvedComponentInfo( - new ComponentName("package", "class")); - - Context context = InstrumentationRegistry.getTargetContext(); - AbstractResolverComparator comparator = getTestComparator(context, promoteToFirst); - - assertEquals("PromoteToFirst ranks over non-cemented", -1, comparator.compare(r1, r2)); - assertEquals("Non-cemented ranks under PromoteToFirst", 1, comparator.compare(r2, r1)); - } - - @Test - public void testPromoteToFirstOverPinned() { - ComponentName cementedComponent = new ComponentName("promoted-package", "class"); - ResolvedComponentInfo r1 = createResolvedComponentInfo(cementedComponent); - - ResolvedComponentInfo r2 = createResolvedComponentInfo( - new ComponentName("package", "class")); - r2.setPinned(true); - - Context context = InstrumentationRegistry.getTargetContext(); - AbstractResolverComparator comparator = getTestComparator(context, cementedComponent); - - assertEquals("PromoteToFirst ranks over pinned", -1, comparator.compare(r1, r2)); - assertEquals("Pinned ranks under PromoteToFirst", 1, comparator.compare(r2, r1)); - } - - private ResolvedComponentInfo createResolvedComponentInfo(ComponentName component) { - ResolveInfo info = new ResolveInfo(); - info.activityInfo = new ActivityInfo(); - info.activityInfo.packageName = component.getPackageName(); - info.activityInfo.name = component.getClassName(); - return new ResolvedComponentInfo(component, new Intent(), info); - } - - private AbstractResolverComparator getTestComparator( - Context context, ComponentName promoteToFirst) { - Intent intent = new Intent(); - - AbstractResolverComparator testComparator = - new AbstractResolverComparator(context, intent, - Lists.newArrayList(context.getUser()), promoteToFirst) { - - @Override - 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 - public void doCompute(List<ResolvedComponentInfo> targets) {} - - @Override - public float getScore(TargetInfo targetInfo) { - return 0; - } - - @Override - public void handleResultMessage(Message message) {} - }; - return testComparator; - } - -} diff --git a/java/tests/src/com/android/intentresolver/shortcuts/ScopedAppTargetListCallbackTest.kt b/java/tests/src/com/android/intentresolver/shortcuts/ScopedAppTargetListCallbackTest.kt deleted file mode 100644 index c81e88ab..00000000 --- a/java/tests/src/com/android/intentresolver/shortcuts/ScopedAppTargetListCallbackTest.kt +++ /dev/null @@ -1,71 +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.shortcuts - -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.cancel -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.UnconfinedTestDispatcher -import org.junit.Test - -@OptIn(ExperimentalCoroutinesApi::class) -class ScopedAppTargetListCallbackTest { - - @Test - fun test_consumerInvocations_onlyInvokedWhileScopeIsActive() { - val scope = TestScope(UnconfinedTestDispatcher()) - var counter = 0 - val testSubject = ScopedAppTargetListCallback(scope) { counter++ }.toConsumer() - - testSubject.accept(ArrayList()) - - assertThat(counter).isEqualTo(1) - - scope.cancel() - testSubject.accept(ArrayList()) - - assertThat(counter).isEqualTo(1) - } - - @Test - fun test_appPredictorCallbackInvocations_onlyInvokedWhileScopeIsActive() { - val scope = TestScope(UnconfinedTestDispatcher()) - var counter = 0 - val testSubject = ScopedAppTargetListCallback(scope) { counter++ }.toAppPredictorCallback() - - testSubject.onTargetsAvailable(ArrayList()) - - assertThat(counter).isEqualTo(1) - - scope.cancel() - testSubject.onTargetsAvailable(ArrayList()) - - assertThat(counter).isEqualTo(1) - } - - @Test - fun test_createdWithClosedScope_noCallbackInvocations() { - val scope = TestScope(UnconfinedTestDispatcher()).apply { cancel() } - var counter = 0 - val testSubject = ScopedAppTargetListCallback(scope) { counter++ }.toConsumer() - - testSubject.accept(ArrayList()) - - assertThat(counter).isEqualTo(0) - } -} diff --git a/java/tests/src/com/android/intentresolver/shortcuts/ShortcutLoaderTest.kt b/java/tests/src/com/android/intentresolver/shortcuts/ShortcutLoaderTest.kt deleted file mode 100644 index 43d0df79..00000000 --- a/java/tests/src/com/android/intentresolver/shortcuts/ShortcutLoaderTest.kt +++ /dev/null @@ -1,490 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.intentresolver.shortcuts - -import android.app.prediction.AppPredictor -import android.content.ComponentName -import android.content.Context -import android.content.IntentFilter -import android.content.pm.ApplicationInfo -import android.content.pm.PackageManager -import android.content.pm.PackageManager.ApplicationInfoFlags -import android.content.pm.ShortcutManager -import android.os.UserHandle -import android.os.UserManager -import androidx.test.filters.SmallTest -import com.android.intentresolver.any -import com.android.intentresolver.argumentCaptor -import com.android.intentresolver.capture -import com.android.intentresolver.chooser.DisplayResolveInfo -import com.android.intentresolver.createAppTarget -import com.android.intentresolver.createShareShortcutInfo -import com.android.intentresolver.createShortcutInfo -import com.android.intentresolver.mock -import com.android.intentresolver.whenever -import java.util.function.Consumer -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestCoroutineScheduler -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.UnconfinedTestDispatcher -import kotlinx.coroutines.test.runTest -import org.junit.Assert.assertArrayEquals -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue -import org.junit.Test -import org.mockito.Mockito.anyInt -import org.mockito.Mockito.atLeastOnce -import org.mockito.Mockito.never -import org.mockito.Mockito.times -import org.mockito.Mockito.verify - -@OptIn(ExperimentalCoroutinesApi::class) -@SmallTest -class ShortcutLoaderTest { - private val appInfo = - ApplicationInfo().apply { - enabled = true - flags = 0 - } - private val pm = - mock<PackageManager> { - whenever(getApplicationInfo(any(), any<ApplicationInfoFlags>())).thenReturn(appInfo) - } - private val userManager = - mock<UserManager> { - whenever(isUserRunning(any<UserHandle>())).thenReturn(true) - whenever(isUserUnlocked(any<UserHandle>())).thenReturn(true) - whenever(isQuietModeEnabled(any<UserHandle>())).thenReturn(false) - } - private val context = - mock<Context> { - whenever(packageManager).thenReturn(pm) - whenever(createContextAsUser(any(), anyInt())).thenReturn(this) - whenever(getSystemService(Context.USER_SERVICE)).thenReturn(userManager) - } - private val scheduler = TestCoroutineScheduler() - private val dispatcher = UnconfinedTestDispatcher(scheduler) - private val scope = TestScope(dispatcher) - private val intentFilter = mock<IntentFilter>() - private val appPredictor = mock<ShortcutLoader.AppPredictorProxy>() - private val callback = mock<Consumer<ShortcutLoader.Result>>() - private val componentName = ComponentName("pkg", "Class") - private val appTarget = - mock<DisplayResolveInfo> { whenever(resolvedComponentName).thenReturn(componentName) } - private val appTargets = arrayOf(appTarget) - private val matchingShortcutInfo = createShortcutInfo("id-0", componentName, 1) - - @Test - fun test_loadShortcutsWithAppPredictor_resultIntegrity() = - scope.runTest { - val testSubject = - ShortcutLoader( - context, - backgroundScope, - appPredictor, - UserHandle.of(0), - true, - intentFilter, - dispatcher, - callback - ) - - testSubject.updateAppTargets(appTargets) - - val matchingAppTarget = createAppTarget(matchingShortcutInfo) - val shortcuts = - listOf( - matchingAppTarget, - // an AppTarget that does not belong to any resolved application; should be - // ignored - createAppTarget( - createShortcutInfo("id-1", ComponentName("mismatching.pkg", "Class"), 1) - ) - ) - val appPredictorCallbackCaptor = argumentCaptor<AppPredictor.Callback>() - verify(appPredictor, atLeastOnce()) - .registerPredictionUpdates(any(), capture(appPredictorCallbackCaptor)) - appPredictorCallbackCaptor.value.onTargetsAvailable(shortcuts) - - val resultCaptor = argumentCaptor<ShortcutLoader.Result>() - verify(callback, times(1)).accept(capture(resultCaptor)) - - val result = resultCaptor.value - assertTrue("An app predictor result is expected", result.isFromAppPredictor) - assertArrayEquals( - "Wrong input app targets in the result", - appTargets, - result.appTargets - ) - assertEquals("Wrong shortcut count", 1, result.shortcutsByApp.size) - assertEquals("Wrong app target", appTarget, result.shortcutsByApp[0].appTarget) - for (shortcut in result.shortcutsByApp[0].shortcuts) { - assertEquals( - "Wrong AppTarget in the cache", - matchingAppTarget, - result.directShareAppTargetCache[shortcut] - ) - assertEquals( - "Wrong ShortcutInfo in the cache", - matchingShortcutInfo, - result.directShareShortcutInfoCache[shortcut] - ) - } - } - - @Test - fun test_loadShortcutsWithShortcutManager_resultIntegrity() = - scope.runTest { - val shortcutManagerResult = - listOf( - ShortcutManager.ShareShortcutInfo(matchingShortcutInfo, componentName), - // mismatching shortcut - createShareShortcutInfo("id-1", ComponentName("mismatching.pkg", "Class"), 1) - ) - val shortcutManager = - mock<ShortcutManager> { - whenever(getShareTargets(intentFilter)).thenReturn(shortcutManagerResult) - } - whenever(context.getSystemService(Context.SHORTCUT_SERVICE)).thenReturn(shortcutManager) - val testSubject = - ShortcutLoader( - context, - backgroundScope, - null, - UserHandle.of(0), - true, - intentFilter, - dispatcher, - callback - ) - - testSubject.updateAppTargets(appTargets) - - val resultCaptor = argumentCaptor<ShortcutLoader.Result>() - verify(callback, times(1)).accept(capture(resultCaptor)) - - val result = resultCaptor.value - assertFalse("An ShortcutManager result is expected", result.isFromAppPredictor) - assertArrayEquals( - "Wrong input app targets in the result", - appTargets, - result.appTargets - ) - assertEquals("Wrong shortcut count", 1, result.shortcutsByApp.size) - assertEquals("Wrong app target", appTarget, result.shortcutsByApp[0].appTarget) - for (shortcut in result.shortcutsByApp[0].shortcuts) { - assertTrue( - "AppTargets are not expected the cache of a ShortcutManager result", - result.directShareAppTargetCache.isEmpty() - ) - assertEquals( - "Wrong ShortcutInfo in the cache", - matchingShortcutInfo, - result.directShareShortcutInfoCache[shortcut] - ) - } - } - - @Test - fun test_appPredictorReturnsEmptyList_fallbackToShortcutManager() = - scope.runTest { - val shortcutManagerResult = - listOf( - ShortcutManager.ShareShortcutInfo(matchingShortcutInfo, componentName), - // mismatching shortcut - createShareShortcutInfo("id-1", ComponentName("mismatching.pkg", "Class"), 1) - ) - val shortcutManager = - mock<ShortcutManager> { - whenever(getShareTargets(intentFilter)).thenReturn(shortcutManagerResult) - } - whenever(context.getSystemService(Context.SHORTCUT_SERVICE)).thenReturn(shortcutManager) - val testSubject = - ShortcutLoader( - context, - backgroundScope, - appPredictor, - UserHandle.of(0), - true, - intentFilter, - dispatcher, - callback - ) - - testSubject.updateAppTargets(appTargets) - - verify(appPredictor, times(1)).requestPredictionUpdate() - val appPredictorCallbackCaptor = argumentCaptor<AppPredictor.Callback>() - verify(appPredictor, times(1)) - .registerPredictionUpdates(any(), capture(appPredictorCallbackCaptor)) - appPredictorCallbackCaptor.value.onTargetsAvailable(emptyList()) - - val resultCaptor = argumentCaptor<ShortcutLoader.Result>() - verify(callback, times(1)).accept(capture(resultCaptor)) - - val result = resultCaptor.value - assertFalse("An ShortcutManager result is expected", result.isFromAppPredictor) - assertArrayEquals( - "Wrong input app targets in the result", - appTargets, - result.appTargets - ) - assertEquals("Wrong shortcut count", 1, result.shortcutsByApp.size) - assertEquals("Wrong app target", appTarget, result.shortcutsByApp[0].appTarget) - for (shortcut in result.shortcutsByApp[0].shortcuts) { - assertTrue( - "AppTargets are not expected the cache of a ShortcutManager result", - result.directShareAppTargetCache.isEmpty() - ) - assertEquals( - "Wrong ShortcutInfo in the cache", - matchingShortcutInfo, - result.directShareShortcutInfoCache[shortcut] - ) - } - } - - @Test - fun test_appPredictor_requestPredictionUpdateFailure_fallbackToShortcutManager() = - scope.runTest { - val shortcutManagerResult = - listOf( - ShortcutManager.ShareShortcutInfo(matchingShortcutInfo, componentName), - // mismatching shortcut - createShareShortcutInfo("id-1", ComponentName("mismatching.pkg", "Class"), 1) - ) - val shortcutManager = - mock<ShortcutManager> { - whenever(getShareTargets(intentFilter)).thenReturn(shortcutManagerResult) - } - whenever(context.getSystemService(Context.SHORTCUT_SERVICE)).thenReturn(shortcutManager) - whenever(appPredictor.requestPredictionUpdate()) - .thenThrow(IllegalStateException("Test exception")) - val testSubject = - ShortcutLoader( - context, - backgroundScope, - appPredictor, - UserHandle.of(0), - true, - intentFilter, - dispatcher, - callback - ) - - testSubject.updateAppTargets(appTargets) - - verify(appPredictor, times(1)).requestPredictionUpdate() - - val resultCaptor = argumentCaptor<ShortcutLoader.Result>() - verify(callback, times(1)).accept(capture(resultCaptor)) - - val result = resultCaptor.value - assertFalse("An ShortcutManager result is expected", result.isFromAppPredictor) - assertArrayEquals( - "Wrong input app targets in the result", - appTargets, - result.appTargets - ) - assertEquals("Wrong shortcut count", 1, result.shortcutsByApp.size) - assertEquals("Wrong app target", appTarget, result.shortcutsByApp[0].appTarget) - for (shortcut in result.shortcutsByApp[0].shortcuts) { - assertTrue( - "AppTargets are not expected the cache of a ShortcutManager result", - result.directShareAppTargetCache.isEmpty() - ) - assertEquals( - "Wrong ShortcutInfo in the cache", - matchingShortcutInfo, - result.directShareShortcutInfoCache[shortcut] - ) - } - } - - @Test - fun test_ShortcutLoader_shortcutsRequestedIndependentlyFromAppTargets() = - scope.runTest { - ShortcutLoader( - context, - backgroundScope, - appPredictor, - UserHandle.of(0), - true, - intentFilter, - dispatcher, - callback - ) - - verify(appPredictor, times(1)).requestPredictionUpdate() - verify(callback, never()).accept(any()) - } - - @Test - fun test_ShortcutLoader_noResultsWithoutAppTargets() = - scope.runTest { - val shortcutManagerResult = - listOf( - ShortcutManager.ShareShortcutInfo(matchingShortcutInfo, componentName), - // mismatching shortcut - createShareShortcutInfo("id-1", ComponentName("mismatching.pkg", "Class"), 1) - ) - val shortcutManager = - mock<ShortcutManager> { - whenever(getShareTargets(intentFilter)).thenReturn(shortcutManagerResult) - } - whenever(context.getSystemService(Context.SHORTCUT_SERVICE)).thenReturn(shortcutManager) - val testSubject = - ShortcutLoader( - context, - backgroundScope, - null, - UserHandle.of(0), - true, - intentFilter, - dispatcher, - callback - ) - - verify(shortcutManager, times(1)).getShareTargets(any()) - verify(callback, never()).accept(any()) - - testSubject.reset() - - verify(shortcutManager, times(2)).getShareTargets(any()) - verify(callback, never()).accept(any()) - - testSubject.updateAppTargets(appTargets) - - verify(shortcutManager, times(2)).getShareTargets(any()) - verify(callback, times(1)).accept(any()) - } - - @Test - fun test_OnScopeCancellation_unsubscribeFromAppPredictor() { - scope.runTest { - ShortcutLoader( - context, - backgroundScope, - appPredictor, - UserHandle.of(0), - true, - intentFilter, - dispatcher, - callback - ) - - verify(appPredictor, never()).unregisterPredictionUpdates(any()) - } - - verify(appPredictor, times(1)).unregisterPredictionUpdates(any()) - } - - @Test - fun test_workProfileNotRunning_doNotCallServices() { - testDisabledWorkProfileDoNotCallSystem(isUserRunning = false) - } - - @Test - fun test_workProfileLocked_doNotCallServices() { - testDisabledWorkProfileDoNotCallSystem(isUserUnlocked = false) - } - - @Test - fun test_workProfileQuiteModeEnabled_doNotCallServices() { - testDisabledWorkProfileDoNotCallSystem(isQuietModeEnabled = true) - } - - @Test - fun test_mainProfileNotRunning_callServicesAnyway() { - testAlwaysCallSystemForMainProfile(isUserRunning = false) - } - - @Test - fun test_mainProfileLocked_callServicesAnyway() { - testAlwaysCallSystemForMainProfile(isUserUnlocked = false) - } - - @Test - fun test_mainProfileQuiteModeEnabled_callServicesAnyway() { - testAlwaysCallSystemForMainProfile(isQuietModeEnabled = true) - } - - private fun testDisabledWorkProfileDoNotCallSystem( - isUserRunning: Boolean = true, - isUserUnlocked: Boolean = true, - isQuietModeEnabled: Boolean = false - ) = - scope.runTest { - val userHandle = UserHandle.of(10) - with(userManager) { - whenever(isUserRunning(userHandle)).thenReturn(isUserRunning) - whenever(isUserUnlocked(userHandle)).thenReturn(isUserUnlocked) - whenever(isQuietModeEnabled(userHandle)).thenReturn(isQuietModeEnabled) - } - whenever(context.getSystemService(Context.USER_SERVICE)).thenReturn(userManager) - val appPredictor = mock<ShortcutLoader.AppPredictorProxy>() - val callback = mock<Consumer<ShortcutLoader.Result>>() - val testSubject = - ShortcutLoader( - context, - backgroundScope, - appPredictor, - userHandle, - false, - intentFilter, - dispatcher, - callback - ) - - testSubject.updateAppTargets(arrayOf<DisplayResolveInfo>(mock())) - - verify(appPredictor, never()).requestPredictionUpdate() - } - - private fun testAlwaysCallSystemForMainProfile( - isUserRunning: Boolean = true, - isUserUnlocked: Boolean = true, - isQuietModeEnabled: Boolean = false - ) = - scope.runTest { - val userHandle = UserHandle.of(10) - with(userManager) { - whenever(isUserRunning(userHandle)).thenReturn(isUserRunning) - whenever(isUserUnlocked(userHandle)).thenReturn(isUserUnlocked) - whenever(isQuietModeEnabled(userHandle)).thenReturn(isQuietModeEnabled) - } - whenever(context.getSystemService(Context.USER_SERVICE)).thenReturn(userManager) - val appPredictor = mock<ShortcutLoader.AppPredictorProxy>() - val callback = mock<Consumer<ShortcutLoader.Result>>() - val testSubject = - ShortcutLoader( - context, - backgroundScope, - appPredictor, - userHandle, - true, - intentFilter, - dispatcher, - callback - ) - - testSubject.updateAppTargets(arrayOf<DisplayResolveInfo>(mock())) - - verify(appPredictor, times(1)).requestPredictionUpdate() - } -} diff --git a/java/tests/src/com/android/intentresolver/shortcuts/ShortcutToChooserTargetConverterTest.kt b/java/tests/src/com/android/intentresolver/shortcuts/ShortcutToChooserTargetConverterTest.kt deleted file mode 100644 index e0de005d..00000000 --- a/java/tests/src/com/android/intentresolver/shortcuts/ShortcutToChooserTargetConverterTest.kt +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.intentresolver.shortcuts - -import android.app.prediction.AppTarget -import android.content.ComponentName -import android.content.Intent -import android.content.pm.ShortcutInfo -import android.content.pm.ShortcutManager.ShareShortcutInfo -import android.service.chooser.ChooserTarget -import com.android.intentresolver.createAppTarget -import com.android.intentresolver.createShareShortcutInfo -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNotNull -import org.junit.Test - -private const val PACKAGE = "org.package" - -class ShortcutToChooserTargetConverterTest { - private val testSubject = ShortcutToChooserTargetConverter() - private val ranks = arrayOf(3 ,7, 1 ,3) - private val shortcuts = ranks - .foldIndexed(ArrayList<ShareShortcutInfo>(ranks.size)) { i, acc, rank -> - val id = i + 1 - acc.add( - createShareShortcutInfo( - id = "id-$i", - componentName = ComponentName(PACKAGE, "Class$id"), - rank, - ) - ) - acc - } - - @Test - fun testConvertToChooserTarget_predictionService() { - val appTargets = shortcuts.map { createAppTarget(it.shortcutInfo) } - val expectedOrderAllShortcuts = intArrayOf(0, 1, 2, 3) - val expectedScoreAllShortcuts = floatArrayOf(1.0f, 0.99f, 0.98f, 0.97f) - val appTargetCache = HashMap<ChooserTarget, AppTarget>() - val shortcutInfoCache = HashMap<ChooserTarget, ShortcutInfo>() - - var chooserTargets = testSubject.convertToChooserTarget( - shortcuts, - shortcuts, - appTargets, - appTargetCache, - shortcutInfoCache, - ) - - assertCorrectShortcutToChooserTargetConversion( - shortcuts, - chooserTargets, - expectedOrderAllShortcuts, - expectedScoreAllShortcuts, - ) - assertAppTargetCache(chooserTargets, appTargetCache) - assertShortcutInfoCache(chooserTargets, shortcutInfoCache) - - val subset = shortcuts.subList(1, shortcuts.size) - val expectedOrderSubset = intArrayOf(1, 2, 3) - val expectedScoreSubset = floatArrayOf(0.99f, 0.98f, 0.97f) - appTargetCache.clear() - shortcutInfoCache.clear() - - chooserTargets = testSubject.convertToChooserTarget( - subset, - shortcuts, - appTargets, - appTargetCache, - shortcutInfoCache, - ) - - assertCorrectShortcutToChooserTargetConversion( - shortcuts, - chooserTargets, - expectedOrderSubset, - expectedScoreSubset, - ) - assertAppTargetCache(chooserTargets, appTargetCache) - assertShortcutInfoCache(chooserTargets, shortcutInfoCache) - } - - @Test - fun testConvertToChooserTarget_shortcutManager() { - val testSubject = ShortcutToChooserTargetConverter() - val expectedOrderAllShortcuts = intArrayOf(2, 0, 3, 1) - val expectedScoreAllShortcuts = floatArrayOf(1.0f, 0.99f, 0.99f, 0.98f) - val shortcutInfoCache = HashMap<ChooserTarget, ShortcutInfo>() - - var chooserTargets = testSubject.convertToChooserTarget( - shortcuts, - shortcuts, - null, - null, - shortcutInfoCache, - ) - - assertCorrectShortcutToChooserTargetConversion( - shortcuts, chooserTargets, - expectedOrderAllShortcuts, expectedScoreAllShortcuts - ) - assertShortcutInfoCache(chooserTargets, shortcutInfoCache) - - val subset: MutableList<ShareShortcutInfo> = java.util.ArrayList() - subset.add(shortcuts[1]) - subset.add(shortcuts[2]) - subset.add(shortcuts[3]) - val expectedOrderSubset = intArrayOf(2, 3, 1) - val expectedScoreSubset = floatArrayOf(1.0f, 0.99f, 0.98f) - shortcutInfoCache.clear() - - chooserTargets = testSubject.convertToChooserTarget( - subset, - shortcuts, - null, - null, - shortcutInfoCache, - ) - - assertCorrectShortcutToChooserTargetConversion( - shortcuts, chooserTargets, - expectedOrderSubset, expectedScoreSubset - ) - assertShortcutInfoCache(chooserTargets, shortcutInfoCache) - } - - private fun assertCorrectShortcutToChooserTargetConversion( - shortcuts: List<ShareShortcutInfo>, - chooserTargets: List<ChooserTarget>, - expectedOrder: IntArray, - expectedScores: FloatArray, - ) { - assertEquals("Unexpected ChooserTarget count", expectedOrder.size, chooserTargets.size) - for (i in chooserTargets.indices) { - val ct = chooserTargets[i] - val si = shortcuts[expectedOrder[i]].shortcutInfo - val cn = shortcuts[expectedOrder[i]].targetComponent - assertEquals(si.id, ct.intentExtras.getString(Intent.EXTRA_SHORTCUT_ID)) - assertEquals(si.label, ct.title) - assertEquals(expectedScores[i], ct.score) - assertEquals(cn, ct.componentName) - } - } - - private fun assertAppTargetCache( - chooserTargets: List<ChooserTarget>, cache: Map<ChooserTarget, AppTarget> - ) { - for (ct in chooserTargets) { - val target = cache[ct] - assertNotNull("AppTarget is missing", target) - } - } - - private fun assertShortcutInfoCache( - chooserTargets: List<ChooserTarget>, cache: Map<ChooserTarget, ShortcutInfo> - ) { - for (ct in chooserTargets) { - val si = cache[ct] - assertNotNull("AppTarget is missing", si) - } - } -} diff --git a/java/tests/src/com/android/intentresolver/util/TestExecutor.kt b/java/tests/src/com/android/intentresolver/util/TestExecutor.kt deleted file mode 100644 index 214b9707..00000000 --- a/java/tests/src/com/android/intentresolver/util/TestExecutor.kt +++ /dev/null @@ -1,40 +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 java.util.concurrent.Executor - -class TestExecutor(private val immediate: Boolean = false) : Executor { - private var pendingCommands = ArrayDeque<Runnable>() - - val pendingCommandCount: Int - get() = pendingCommands.size - - override fun execute(command: Runnable) { - if (immediate) { - command.run() - } else { - pendingCommands.add(command) - } - } - - fun runUntilIdle() { - while (pendingCommands.isNotEmpty()) { - pendingCommands.removeFirst().run() - } - } -} diff --git a/java/tests/src/com/android/intentresolver/util/UriFiltersTest.kt b/java/tests/src/com/android/intentresolver/util/UriFiltersTest.kt deleted file mode 100644 index 18218064..00000000 --- a/java/tests/src/com/android/intentresolver/util/UriFiltersTest.kt +++ /dev/null @@ -1,95 +0,0 @@ -package com.android.intentresolver.util - -import android.app.PendingIntent -import android.content.IIntentReceiver -import android.content.IIntentSender -import android.content.Intent -import android.graphics.Bitmap -import android.graphics.drawable.Icon -import android.net.Uri -import android.os.Binder -import android.os.Bundle -import android.os.IBinder -import android.os.UserHandle -import android.service.chooser.ChooserAction -import androidx.test.ext.junit.runners.AndroidJUnit4 -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class UriFiltersTest { - - @Test - fun uri_ownedByCurrentUser_noUserId() { - val uri = Uri.parse("content://media/images/12345") - assertTrue("Uri without userId should always return true", uri.ownedByCurrentUser) - } - - @Test - fun uri_ownedByCurrentUser_selfUserId() { - val uri = Uri.parse("content://${UserHandle.myUserId()}@media/images/12345") - assertTrue("Uri with own userId should return true", uri.ownedByCurrentUser) - } - - @Test - fun uri_ownedByCurrentUser_otherUserId() { - val otherUserId = UserHandle.myUserId() + 10 - val uri = Uri.parse("content://${otherUserId}@media/images/12345") - assertFalse("Uri with other userId should return false", uri.ownedByCurrentUser) - } - - @Test - fun chooserAction_hasValidIcon_bitmap() = - smallBitmap().use { - val icon = Icon.createWithBitmap(it) - val action = actionWithIcon(icon) - assertTrue("No uri, assumed valid", hasValidIcon(action)) - } - - @Test - fun chooserAction_hasValidIcon_uri() { - val icon = Icon.createWithContentUri("content://provider/content/12345") - assertTrue("No userId in uri, uri is valid", hasValidIcon(actionWithIcon(icon))) - } - @Test - fun chooserAction_hasValidIcon_uri_unowned() { - val userId = UserHandle.myUserId() + 10 - val icon = Icon.createWithContentUri("content://${userId}@provider/content/12345") - assertFalse("uri userId references a different user", hasValidIcon(actionWithIcon(icon))) - } - - private fun smallBitmap() = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888) - - private fun mockAction(): PendingIntent { - return PendingIntent( - object : IIntentSender { - override fun asBinder(): IBinder = Binder() - override fun send( - code: Int, - intent: Intent?, - resolvedType: String?, - whitelistToken: IBinder?, - finishedReceiver: IIntentReceiver?, - requiredPermission: String?, - options: Bundle? - ) { - /* empty */ - } - } - ) - } - - private fun actionWithIcon(icon: Icon): ChooserAction { - return ChooserAction.Builder(icon, "", mockAction()).build() - } - - /** Unconditionally recycles the [Bitmap] after running the given block */ - private fun Bitmap.use(block: (Bitmap) -> Unit) = - try { - block(this) - } finally { - recycle() - } -} diff --git a/java/tests/src/com/android/intentresolver/v2/ChooserActionFactoryTest.kt b/java/tests/src/com/android/intentresolver/v2/ChooserActionFactoryTest.kt deleted file mode 100644 index a1a9bc92..00000000 --- a/java/tests/src/com/android/intentresolver/v2/ChooserActionFactoryTest.kt +++ /dev/null @@ -1,232 +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.v2 - -import android.app.Activity -import android.app.PendingIntent -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Context.RECEIVER_EXPORTED -import android.content.Intent -import android.content.IntentFilter -import android.content.res.Resources -import android.graphics.drawable.Icon -import android.service.chooser.ChooserAction -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import com.android.intentresolver.ChooserRequestParameters -import com.android.intentresolver.logging.EventLog -import com.android.intentresolver.mock -import com.android.intentresolver.whenever -import com.google.common.collect.ImmutableList -import com.google.common.truth.Truth.assertThat -import java.util.Optional -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit -import java.util.function.Consumer -import org.junit.After -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.eq -import org.mockito.Mockito - -@RunWith(AndroidJUnit4::class) -class ChooserActionFactoryTest { - private val context = InstrumentationRegistry.getInstrumentation().context - - private val logger = mock<EventLog>() - private val actionLabel = "Action label" - private val modifyShareLabel = "Modify share" - private val testAction = "com.android.intentresolver.testaction" - private val countdown = CountDownLatch(1) - private val testReceiver: BroadcastReceiver = - object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - // Just doing at most a single countdown per test. - countdown.countDown() - } - } - private val resultConsumer = - object : Consumer<Int> { - var latestReturn = Integer.MIN_VALUE - - override fun accept(resultCode: Int) { - latestReturn = resultCode - } - } - - @Before - fun setup() { - context.registerReceiver(testReceiver, IntentFilter(testAction), RECEIVER_EXPORTED) - } - - @After - fun teardown() { - context.unregisterReceiver(testReceiver) - } - - @Test - fun testCreateCustomActions() { - val factory = createFactory() - - val customActions = factory.createCustomActions() - - assertThat(customActions.size).isEqualTo(1) - assertThat(customActions[0].label).isEqualTo(actionLabel) - - // click it - customActions[0].onClicked.run() - - Mockito.verify(logger).logCustomActionSelected(eq(0)) - assertEquals(Activity.RESULT_OK, resultConsumer.latestReturn) - // Verify the pending intent has been called - assertTrue("Timed out waiting for broadcast", countdown.await(2500, TimeUnit.MILLISECONDS)) - } - - @Test - fun testNoModifyShareAction() { - val factory = createFactory(includeModifyShare = false) - - assertThat(factory.modifyShareAction).isNull() - } - - @Test - fun testModifyShareAction() { - val factory = createFactory(includeModifyShare = true) - - val action = factory.modifyShareAction ?: error("Modify share action should not be null") - action.onClicked.run() - - Mockito.verify(logger).logActionSelected(eq(EventLog.SELECTION_TYPE_MODIFY_SHARE)) - assertEquals(Activity.RESULT_OK, resultConsumer.latestReturn) - // Verify the pending intent has been called - assertTrue("Timed out waiting for broadcast", countdown.await(2500, TimeUnit.MILLISECONDS)) - } - - @Test - fun nonSendAction_noCopyRunnable() { - val targetIntent = - Intent(Intent.ACTION_SEND_MULTIPLE).apply { - putExtra(Intent.EXTRA_TEXT, "Text to show") - } - - val chooserRequest = - mock<ChooserRequestParameters> { - whenever(this.targetIntent).thenReturn(targetIntent) - whenever(chooserActions).thenReturn(ImmutableList.of()) - } - val testSubject = - ChooserActionFactory( - context, - chooserRequest, - Optional.empty(), - logger, - {}, - { null }, - mock(), - {}, - ) - assertThat(testSubject.copyButtonRunnable).isNull() - } - - @Test - fun sendActionNoText_noCopyRunnable() { - val targetIntent = Intent(Intent.ACTION_SEND) - - val chooserRequest = - mock<ChooserRequestParameters> { - whenever(this.targetIntent).thenReturn(targetIntent) - whenever(chooserActions).thenReturn(ImmutableList.of()) - } - val testSubject = - ChooserActionFactory( - context, - chooserRequest, - Optional.empty(), - logger, - {}, - { null }, - mock(), - {}, - ) - assertThat(testSubject.copyButtonRunnable).isNull() - } - - @Test - fun sendActionWithText_nonNullCopyRunnable() { - val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_TEXT, "Text") } - - val chooserRequest = - mock<ChooserRequestParameters> { - whenever(this.targetIntent).thenReturn(targetIntent) - whenever(chooserActions).thenReturn(ImmutableList.of()) - } - val testSubject = - ChooserActionFactory( - context, - chooserRequest, - Optional.empty(), - logger, - {}, - { null }, - mock(), - {}, - ) - assertThat(testSubject.copyButtonRunnable).isNotNull() - } - - private fun createFactory(includeModifyShare: Boolean = false): ChooserActionFactory { - val testPendingIntent = - PendingIntent.getBroadcast(context, 0, Intent(testAction), PendingIntent.FLAG_IMMUTABLE) - val targetIntent = Intent() - val action = - ChooserAction.Builder( - Icon.createWithResource("", Resources.ID_NULL), - actionLabel, - testPendingIntent - ) - .build() - val chooserRequest = mock<ChooserRequestParameters>() - whenever(chooserRequest.targetIntent).thenReturn(targetIntent) - whenever(chooserRequest.chooserActions).thenReturn(ImmutableList.of(action)) - - if (includeModifyShare) { - val modifyShare = - ChooserAction.Builder( - Icon.createWithResource("", Resources.ID_NULL), - modifyShareLabel, - testPendingIntent - ) - .build() - whenever(chooserRequest.modifyShareAction).thenReturn(modifyShare) - } - - return ChooserActionFactory( - context, - chooserRequest, - Optional.empty(), - logger, - {}, - { null }, - mock(), - resultConsumer - ) - } -} diff --git a/java/tests/src/com/android/intentresolver/v2/ChooserActivityOverrideData.java b/java/tests/src/com/android/intentresolver/v2/ChooserActivityOverrideData.java deleted file mode 100644 index 32eabbed..00000000 --- a/java/tests/src/com/android/intentresolver/v2/ChooserActivityOverrideData.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2021 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; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.database.Cursor; -import android.os.UserHandle; - -import com.android.intentresolver.AnnotatedUserHandles; -import com.android.intentresolver.WorkProfileAvailabilityManager; -import com.android.intentresolver.chooser.TargetInfo; -import com.android.intentresolver.contentpreview.ImageLoader; -import com.android.intentresolver.emptystate.CrossProfileIntentsChecker; -import com.android.intentresolver.shortcuts.ShortcutLoader; - -import java.util.function.Consumer; -import java.util.function.Function; - -import kotlin.jvm.functions.Function2; - -/** - * Singleton providing overrides to be applied by any {@code IChooserWrapper} used in testing. - * We cannot directly mock the activity created since instrumentation creates it, so instead we use - * this singleton to modify behavior. - */ -public class ChooserActivityOverrideData { - private static ChooserActivityOverrideData sInstance = null; - - public static ChooserActivityOverrideData getInstance() { - if (sInstance == null) { - sInstance = new ChooserActivityOverrideData(); - } - return sInstance; - } - - @SuppressWarnings("Since15") - public Function<PackageManager, PackageManager> createPackageManager; - public Function<TargetInfo, Boolean> onSafelyStartInternalCallback; - public Function<TargetInfo, Boolean> onSafelyStartCallback; - public Function2<UserHandle, Consumer<ShortcutLoader.Result>, ShortcutLoader> - shortcutLoaderFactory = (userHandle, callback) -> null; - public ChooserActivity.ChooserListController resolverListController; - public ChooserActivity.ChooserListController workResolverListController; - public Boolean isVoiceInteraction; - public Cursor resolverCursor; - public boolean resolverForceException; - public ImageLoader imageLoader; - public int alternateProfileSetting; - public Resources resources; - public AnnotatedUserHandles annotatedUserHandles; - public boolean hasCrossProfileIntents; - public boolean isQuietModeEnabled; - public Integer myUserId; - public WorkProfileAvailabilityManager mWorkProfileAvailability; - public CrossProfileIntentsChecker mCrossProfileIntentsChecker; - public PackageManager packageManager; - - public void reset() { - onSafelyStartInternalCallback = null; - isVoiceInteraction = null; - createPackageManager = null; - imageLoader = null; - resolverCursor = null; - resolverForceException = false; - resolverListController = mock(ChooserActivity.ChooserListController.class); - workResolverListController = mock(ChooserActivity.ChooserListController.class); - alternateProfileSetting = 0; - resources = null; - annotatedUserHandles = AnnotatedUserHandles.newBuilder() - .setUserIdOfCallingApp(1234) // Must be non-negative. - .setUserHandleSharesheetLaunchedAs(UserHandle.SYSTEM) - .setPersonalProfileUserHandle(UserHandle.SYSTEM) - .build(); - hasCrossProfileIntents = true; - isQuietModeEnabled = false; - myUserId = null; - packageManager = null; - mWorkProfileAvailability = new WorkProfileAvailabilityManager(null, null, null) { - @Override - public boolean isQuietModeEnabled() { - return isQuietModeEnabled; - } - - @Override - public boolean isWorkProfileUserUnlocked() { - return true; - } - - @Override - public void requestQuietModeEnabled(boolean enabled) { - isQuietModeEnabled = enabled; - } - - @Override - public void markWorkProfileEnabledBroadcastReceived() {} - - @Override - public boolean isWaitingToEnableWorkProfile() { - return false; - } - }; - shortcutLoaderFactory = ((userHandle, resultConsumer) -> null); - - mCrossProfileIntentsChecker = mock(CrossProfileIntentsChecker.class); - when(mCrossProfileIntentsChecker.hasCrossProfileIntents(any(), anyInt(), anyInt())) - .thenAnswer(invocation -> hasCrossProfileIntents); - } - - private ChooserActivityOverrideData() {} -} - diff --git a/java/tests/src/com/android/intentresolver/v2/ChooserWrapperActivity.java b/java/tests/src/com/android/intentresolver/v2/ChooserWrapperActivity.java deleted file mode 100644 index 5572bb24..00000000 --- a/java/tests/src/com/android/intentresolver/v2/ChooserWrapperActivity.java +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Copyright (C) 2008 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; - -import android.annotation.Nullable; -import android.app.prediction.AppPredictor; -import android.app.usage.UsageStatsManager; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.res.Resources; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.os.UserHandle; - -import androidx.lifecycle.ViewModelProvider; - -import com.android.intentresolver.ChooserListAdapter; -import com.android.intentresolver.ChooserRequestParameters; -import com.android.intentresolver.IChooserWrapper; -import com.android.intentresolver.ResolverListController; -import com.android.intentresolver.TestContentPreviewViewModel; -import com.android.intentresolver.chooser.DisplayResolveInfo; -import com.android.intentresolver.chooser.TargetInfo; -import com.android.intentresolver.emptystate.CrossProfileIntentsChecker; -import com.android.intentresolver.grid.ChooserGridAdapter; -import com.android.intentresolver.icons.TargetDataLoader; -import com.android.intentresolver.shortcuts.ShortcutLoader; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; - -import java.util.List; -import java.util.function.Consumer; - -/** - * Simple wrapper around chooser activity to be able to initiate it under test. For more - * information, see {@code com.android.internal.app.ChooserWrapperActivity}. - */ -public class ChooserWrapperActivity extends ChooserActivity implements IChooserWrapper { - static final ChooserActivityOverrideData sOverrides = ChooserActivityOverrideData.getInstance(); - private UsageStatsManager mUsm; - - public ChooserWrapperActivity() { - super(); - mLogic = new TestChooserActivityLogic( - "ChooserWrapper", - () -> this, - this::onWorkProfileStatusUpdated, - () -> mTargetDataLoader, - this::onPreinitialization, - sOverrides - ); - } - - // ResolverActivity (the base class of ChooserActivity) inspects the launched-from UID at - // onCreate and needs to see some non-negative value in the test. - @Override - public int getLaunchedFromUid() { - return 1234; - } - - @Override - public ChooserListAdapter createChooserListAdapter( - Context context, - List<Intent> payloadIntents, - Intent[] initialIntents, - List<ResolveInfo> rList, - boolean filterLastUsed, - ResolverListController resolverListController, - UserHandle userHandle, - Intent targetIntent, - ChooserRequestParameters chooserRequest, - int maxTargetsPerRow, - TargetDataLoader targetDataLoader) { - PackageManager packageManager = - sOverrides.packageManager == null ? context.getPackageManager() - : sOverrides.packageManager; - return new ChooserListAdapter( - context, - payloadIntents, - initialIntents, - rList, - filterLastUsed, - createListController(userHandle), - userHandle, - targetIntent, - this, - packageManager, - getEventLog(), - chooserRequest, - maxTargetsPerRow, - userHandle, - targetDataLoader); - } - - @Override - public ChooserListAdapter getAdapter() { - return mChooserMultiProfilePagerAdapter.getActiveListAdapter(); - } - - @Override - public ChooserListAdapter getPersonalListAdapter() { - return ((ChooserGridAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(0)) - .getListAdapter(); - } - - @Override - public ChooserListAdapter getWorkListAdapter() { - if (mMultiProfilePagerAdapter.getInactiveListAdapter() == null) { - return null; - } - return ((ChooserGridAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(1)) - .getListAdapter(); - } - - @Override - public boolean getIsSelected() { - return mIsSuccessfullySelected; - } - - @Override - public UsageStatsManager getUsageStatsManager() { - if (mUsm == null) { - mUsm = getSystemService(UsageStatsManager.class); - } - return mUsm; - } - - @Override - public boolean isVoiceInteraction() { - if (sOverrides.isVoiceInteraction != null) { - return sOverrides.isVoiceInteraction; - } - return super.isVoiceInteraction(); - } - - @Override - protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() { - if (sOverrides.mCrossProfileIntentsChecker != null) { - return sOverrides.mCrossProfileIntentsChecker; - } - return super.createCrossProfileIntentsChecker(); - } - - @Override - public void safelyStartActivityInternal(TargetInfo cti, UserHandle user, - @Nullable Bundle options) { - if (sOverrides.onSafelyStartInternalCallback != null - && sOverrides.onSafelyStartInternalCallback.apply(cti)) { - return; - } - super.safelyStartActivityInternal(cti, user, options); - } - - @Override - protected ChooserListController createListController(UserHandle userHandle) { - if (userHandle == UserHandle.SYSTEM) { - return sOverrides.resolverListController; - } - return sOverrides.workResolverListController; - } - - @Override - public PackageManager getPackageManager() { - if (sOverrides.createPackageManager != null) { - return sOverrides.createPackageManager.apply(super.getPackageManager()); - } - return super.getPackageManager(); - } - - @Override - public Resources getResources() { - if (sOverrides.resources != null) { - return sOverrides.resources; - } - return super.getResources(); - } - - @Override - protected ViewModelProvider.Factory createPreviewViewModelFactory() { - return TestContentPreviewViewModel.Companion.wrap( - super.createPreviewViewModelFactory(), - sOverrides.imageLoader); - } - - @Override - public Cursor queryResolver(ContentResolver resolver, Uri uri) { - if (sOverrides.resolverCursor != null) { - return sOverrides.resolverCursor; - } - - if (sOverrides.resolverForceException) { - throw new SecurityException("Test exception handling"); - } - - return super.queryResolver(resolver, uri); - } - - @Override - protected boolean isWorkProfile() { - if (sOverrides.alternateProfileSetting != 0) { - return sOverrides.alternateProfileSetting == MetricsEvent.MANAGED_PROFILE; - } - return super.isWorkProfile(); - } - - @Override - public DisplayResolveInfo createTestDisplayResolveInfo( - Intent originalIntent, - ResolveInfo pri, - CharSequence pLabel, - CharSequence pInfo, - Intent replacementIntent) { - return DisplayResolveInfo.newDisplayResolveInfo( - originalIntent, - pri, - pLabel, - pInfo, - replacementIntent); - } - - @Override - public UserHandle getCurrentUserHandle() { - return mMultiProfilePagerAdapter.getCurrentUserHandle(); - } - - @Override - public Context createContextAsUser(UserHandle user, int flags) { - // return the current context as a work profile doesn't really exist in these tests - return this; - } - - @Override - protected ShortcutLoader createShortcutLoader( - Context context, - AppPredictor appPredictor, - UserHandle userHandle, - IntentFilter targetIntentFilter, - Consumer<ShortcutLoader.Result> callback) { - ShortcutLoader shortcutLoader = - sOverrides.shortcutLoaderFactory.invoke(userHandle, callback); - if (shortcutLoader != null) { - return shortcutLoader; - } - return super.createShortcutLoader( - context, appPredictor, userHandle, targetIntentFilter, callback); - } -} diff --git a/java/tests/src/com/android/intentresolver/v2/MultiProfilePagerAdapterTest.kt b/java/tests/src/com/android/intentresolver/v2/MultiProfilePagerAdapterTest.kt deleted file mode 100644 index f5dc0935..00000000 --- a/java/tests/src/com/android/intentresolver/v2/MultiProfilePagerAdapterTest.kt +++ /dev/null @@ -1,285 +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.v2 - -import android.os.UserHandle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.ListView -import androidx.test.platform.app.InstrumentationRegistry -import com.android.intentresolver.MultiProfilePagerAdapter.PROFILE_PERSONAL -import com.android.intentresolver.MultiProfilePagerAdapter.PROFILE_WORK -import com.android.intentresolver.R -import com.android.intentresolver.ResolverListAdapter -import com.android.intentresolver.emptystate.EmptyStateProvider -import com.android.intentresolver.mock -import com.android.intentresolver.whenever -import com.google.common.collect.ImmutableList -import com.google.common.truth.Truth.assertThat -import java.util.Optional -import java.util.function.Supplier -import org.junit.Test - -class MultiProfilePagerAdapterTest { - private val PERSONAL_USER_HANDLE = UserHandle.of(10) - private val WORK_USER_HANDLE = UserHandle.of(20) - - private val context = InstrumentationRegistry.getInstrumentation().getContext() - private val inflater = Supplier { - LayoutInflater.from(context).inflate(R.layout.resolver_list_per_profile, null, false) - as ViewGroup - } - - @Test - fun testSinglePageProfileAdapter() { - val personalListAdapter = - mock<ResolverListAdapter> { whenever(getUserHandle()).thenReturn(PERSONAL_USER_HANDLE) } - val pagerAdapter = - MultiProfilePagerAdapter( - { listAdapter: ResolverListAdapter -> listAdapter }, - { listView: ListView, bindAdapter: ResolverListAdapter -> - listView.setAdapter(bindAdapter) - }, - ImmutableList.of(personalListAdapter), - object : EmptyStateProvider {}, - { false }, - PROFILE_PERSONAL, - null, - null, - inflater, - { Optional.empty() } - ) - assertThat(pagerAdapter.count).isEqualTo(1) - assertThat(pagerAdapter.currentPage).isEqualTo(PROFILE_PERSONAL) - assertThat(pagerAdapter.currentUserHandle).isEqualTo(PERSONAL_USER_HANDLE) - assertThat(pagerAdapter.getAdapterForIndex(0)).isSameInstanceAs(personalListAdapter) - assertThat(pagerAdapter.activeListAdapter).isSameInstanceAs(personalListAdapter) - assertThat(pagerAdapter.inactiveListAdapter).isNull() - assertThat(pagerAdapter.personalListAdapter).isSameInstanceAs(personalListAdapter) - assertThat(pagerAdapter.workListAdapter).isNull() - assertThat(pagerAdapter.itemCount).isEqualTo(1) - // TODO: consider covering some of the package-private methods (and making them public?). - // TODO: consider exercising responsibilities as an implementation of a ViewPager adapter. - } - - @Test - fun testTwoProfilePagerAdapter() { - val personalListAdapter = - mock<ResolverListAdapter> { whenever(getUserHandle()).thenReturn(PERSONAL_USER_HANDLE) } - val workListAdapter = - mock<ResolverListAdapter> { whenever(getUserHandle()).thenReturn(WORK_USER_HANDLE) } - val pagerAdapter = - MultiProfilePagerAdapter( - { listAdapter: ResolverListAdapter -> listAdapter }, - { listView: ListView, bindAdapter: ResolverListAdapter -> - listView.setAdapter(bindAdapter) - }, - ImmutableList.of(personalListAdapter, workListAdapter), - object : EmptyStateProvider {}, - { false }, - PROFILE_PERSONAL, - WORK_USER_HANDLE, // TODO: why does this test pass even if this is null? - null, - inflater, - { Optional.empty() } - ) - assertThat(pagerAdapter.count).isEqualTo(2) - assertThat(pagerAdapter.currentPage).isEqualTo(PROFILE_PERSONAL) - assertThat(pagerAdapter.currentUserHandle).isEqualTo(PERSONAL_USER_HANDLE) - assertThat(pagerAdapter.getAdapterForIndex(0)).isSameInstanceAs(personalListAdapter) - assertThat(pagerAdapter.getAdapterForIndex(1)).isSameInstanceAs(workListAdapter) - assertThat(pagerAdapter.activeListAdapter).isSameInstanceAs(personalListAdapter) - assertThat(pagerAdapter.inactiveListAdapter).isSameInstanceAs(workListAdapter) - assertThat(pagerAdapter.personalListAdapter).isSameInstanceAs(personalListAdapter) - assertThat(pagerAdapter.workListAdapter).isSameInstanceAs(workListAdapter) - assertThat(pagerAdapter.itemCount).isEqualTo(2) - // TODO: consider covering some of the package-private methods (and making them public?). - // TODO: consider exercising responsibilities as an implementation of a ViewPager adapter; - // especially matching profiles to ListViews? - // TODO: test ProfileSelectedListener (and getters for "current" state) as the selected - // page changes. Currently there's no API to change the selected page directly; that's - // only possible through manipulation of the bound ViewPager. - } - - @Test - fun testTwoProfilePagerAdapter_workIsDefault() { - val personalListAdapter = - mock<ResolverListAdapter> { whenever(getUserHandle()).thenReturn(PERSONAL_USER_HANDLE) } - val workListAdapter = - mock<ResolverListAdapter> { whenever(getUserHandle()).thenReturn(WORK_USER_HANDLE) } - val pagerAdapter = - MultiProfilePagerAdapter( - { listAdapter: ResolverListAdapter -> listAdapter }, - { listView: ListView, bindAdapter: ResolverListAdapter -> - listView.setAdapter(bindAdapter) - }, - ImmutableList.of(personalListAdapter, workListAdapter), - object : EmptyStateProvider {}, - { false }, - PROFILE_WORK, // <-- This test specifically requests we start on work profile. - WORK_USER_HANDLE, // TODO: why does this test pass even if this is null? - null, - inflater, - { Optional.empty() } - ) - assertThat(pagerAdapter.count).isEqualTo(2) - assertThat(pagerAdapter.currentPage).isEqualTo(PROFILE_WORK) - assertThat(pagerAdapter.currentUserHandle).isEqualTo(WORK_USER_HANDLE) - assertThat(pagerAdapter.getAdapterForIndex(0)).isSameInstanceAs(personalListAdapter) - assertThat(pagerAdapter.getAdapterForIndex(1)).isSameInstanceAs(workListAdapter) - assertThat(pagerAdapter.activeListAdapter).isSameInstanceAs(workListAdapter) - assertThat(pagerAdapter.inactiveListAdapter).isSameInstanceAs(personalListAdapter) - assertThat(pagerAdapter.personalListAdapter).isSameInstanceAs(personalListAdapter) - assertThat(pagerAdapter.workListAdapter).isSameInstanceAs(workListAdapter) - assertThat(pagerAdapter.itemCount).isEqualTo(2) - // TODO: consider covering some of the package-private methods (and making them public?). - // TODO: test ProfileSelectedListener (and getters for "current" state) as the selected - // page changes. Currently there's no API to change the selected page directly; that's - // only possible through manipulation of the bound ViewPager. - } - - @Test - fun testBottomPaddingDelegate_default() { - val personalListAdapter = - mock<ResolverListAdapter> { whenever(getUserHandle()).thenReturn(PERSONAL_USER_HANDLE) } - val pagerAdapter = - MultiProfilePagerAdapter( - { listAdapter: ResolverListAdapter -> listAdapter }, - { listView: ListView, bindAdapter: ResolverListAdapter -> - listView.setAdapter(bindAdapter) - }, - ImmutableList.of(personalListAdapter), - object : EmptyStateProvider {}, - { false }, - PROFILE_PERSONAL, - null, - null, - inflater, - { Optional.empty() } - ) - val container = - pagerAdapter - .getActiveEmptyStateView() - .requireViewById<View>(com.android.internal.R.id.resolver_empty_state_container) - container.setPadding(1, 2, 3, 4) - pagerAdapter.setupContainerPadding() - assertThat(container.paddingLeft).isEqualTo(1) - assertThat(container.paddingTop).isEqualTo(2) - assertThat(container.paddingRight).isEqualTo(3) - assertThat(container.paddingBottom).isEqualTo(4) - } - - @Test - fun testBottomPaddingDelegate_override() { - val personalListAdapter = - mock<ResolverListAdapter> { whenever(getUserHandle()).thenReturn(PERSONAL_USER_HANDLE) } - val pagerAdapter = - MultiProfilePagerAdapter( - { listAdapter: ResolverListAdapter -> listAdapter }, - { listView: ListView, bindAdapter: ResolverListAdapter -> - listView.setAdapter(bindAdapter) - }, - ImmutableList.of(personalListAdapter), - object : EmptyStateProvider {}, - { false }, - PROFILE_PERSONAL, - null, - null, - inflater, - { Optional.of(42) } - ) - val container = - pagerAdapter - .getActiveEmptyStateView() - .requireViewById<View>(com.android.internal.R.id.resolver_empty_state_container) - container.setPadding(1, 2, 3, 4) - pagerAdapter.setupContainerPadding() - assertThat(container.paddingLeft).isEqualTo(1) - assertThat(container.paddingTop).isEqualTo(2) - assertThat(container.paddingRight).isEqualTo(3) - assertThat(container.paddingBottom).isEqualTo(42) - } - - @Test - fun testPresumedQuietModeEmptyStateForWorkProfile_whenQuiet() { - // TODO: this is "presumed" because the conditions to determine whether we "should" show an - // empty state aren't enforced to align with the conditions when we actually *would* -- I - // believe `shouldShowEmptyStateScreen` should be implemented in terms of the provider? - val personalListAdapter = - mock<ResolverListAdapter> { - whenever(getUserHandle()).thenReturn(PERSONAL_USER_HANDLE) - whenever(getUnfilteredCount()).thenReturn(1) - } - val workListAdapter = - mock<ResolverListAdapter> { - whenever(getUserHandle()).thenReturn(WORK_USER_HANDLE) - whenever(getUnfilteredCount()).thenReturn(1) - } - val pagerAdapter = - MultiProfilePagerAdapter( - { listAdapter: ResolverListAdapter -> listAdapter }, - { listView: ListView, bindAdapter: ResolverListAdapter -> - listView.setAdapter(bindAdapter) - }, - ImmutableList.of(personalListAdapter, workListAdapter), - object : EmptyStateProvider {}, - { true }, // <-- Work mode is quiet. - PROFILE_WORK, - WORK_USER_HANDLE, - null, - inflater, - { Optional.empty() } - ) - assertThat(pagerAdapter.shouldShowEmptyStateScreen(workListAdapter)).isTrue() - assertThat(pagerAdapter.shouldShowEmptyStateScreen(personalListAdapter)).isFalse() - } - - @Test - fun testPresumedQuietModeEmptyStateForWorkProfile_notWhenNotQuiet() { - // TODO: this is "presumed" because the conditions to determine whether we "should" show an - // empty state aren't enforced to align with the conditions when we actually *would* -- I - // believe `shouldShowEmptyStateScreen` should be implemented in terms of the provider? - val personalListAdapter = - mock<ResolverListAdapter> { - whenever(getUserHandle()).thenReturn(PERSONAL_USER_HANDLE) - whenever(getUnfilteredCount()).thenReturn(1) - } - val workListAdapter = - mock<ResolverListAdapter> { - whenever(getUserHandle()).thenReturn(WORK_USER_HANDLE) - whenever(getUnfilteredCount()).thenReturn(1) - } - val pagerAdapter = - MultiProfilePagerAdapter( - { listAdapter: ResolverListAdapter -> listAdapter }, - { listView: ListView, bindAdapter: ResolverListAdapter -> - listView.setAdapter(bindAdapter) - }, - ImmutableList.of(personalListAdapter, workListAdapter), - object : EmptyStateProvider {}, - { false }, // <-- Work mode is not quiet. - PROFILE_WORK, - WORK_USER_HANDLE, - null, - inflater, - { Optional.empty() } - ) - assertThat(pagerAdapter.shouldShowEmptyStateScreen(workListAdapter)).isFalse() - assertThat(pagerAdapter.shouldShowEmptyStateScreen(personalListAdapter)).isFalse() - } -} diff --git a/java/tests/src/com/android/intentresolver/v2/ResolverActivityTest.java b/java/tests/src/com/android/intentresolver/v2/ResolverActivityTest.java deleted file mode 100644 index f0911833..00000000 --- a/java/tests/src/com/android/intentresolver/v2/ResolverActivityTest.java +++ /dev/null @@ -1,1105 +0,0 @@ -/* - * Copyright (C) 2016 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; - -import static androidx.test.espresso.Espresso.onView; -import static androidx.test.espresso.action.ViewActions.click; -import static androidx.test.espresso.action.ViewActions.swipeUp; -import static androidx.test.espresso.assertion.ViewAssertions.matches; -import static androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed; -import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; -import static androidx.test.espresso.matcher.ViewMatchers.isEnabled; -import static androidx.test.espresso.matcher.ViewMatchers.withId; -import static androidx.test.espresso.matcher.ViewMatchers.withText; -import static com.android.intentresolver.MatcherUtils.first; -import static com.android.intentresolver.v2.ResolverWrapperActivity.sOverrides; -import static org.hamcrest.CoreMatchers.allOf; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.fail; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -import android.content.Intent; -import android.content.pm.ResolveInfo; -import android.net.Uri; -import android.os.RemoteException; -import android.os.UserHandle; -import android.text.TextUtils; -import android.view.View; -import android.widget.RelativeLayout; -import android.widget.TextView; - -import androidx.test.InstrumentationRegistry; -import androidx.test.espresso.Espresso; -import androidx.test.espresso.NoMatchingViewException; -import androidx.test.rule.ActivityTestRule; -import androidx.test.runner.AndroidJUnit4; - -import com.android.intentresolver.AnnotatedUserHandles; -import com.android.intentresolver.R; -import com.android.intentresolver.ResolvedComponentInfo; -import com.android.intentresolver.ResolverDataProvider; -import com.android.intentresolver.widget.ResolverDrawerLayout; -import com.google.android.collect.Lists; - -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mockito; - -import java.util.ArrayList; -import java.util.List; - -/** - * Resolver activity instrumentation tests - */ -@RunWith(AndroidJUnit4.class) -public class ResolverActivityTest { - - private static final UserHandle PERSONAL_USER_HANDLE = androidx.test.platform.app - .InstrumentationRegistry.getInstrumentation().getTargetContext().getUser(); - private static final UserHandle WORK_PROFILE_USER_HANDLE = UserHandle.of(10); - private static final UserHandle CLONE_PROFILE_USER_HANDLE = UserHandle.of(11); - - protected Intent getConcreteIntentForLaunch(Intent clientIntent) { - clientIntent.setClass( - androidx.test.platform.app.InstrumentationRegistry.getInstrumentation().getTargetContext(), - ResolverWrapperActivity.class); - return clientIntent; - } - - @Rule - public ActivityTestRule<ResolverWrapperActivity> mActivityRule = - new ActivityTestRule<>(ResolverWrapperActivity.class, false, false); - - @Before - public void setup() { - // TODO: use the other form of `adoptShellPermissionIdentity()` where we explicitly list the - // permissions we require (which we'll read from the manifest at runtime). - androidx.test.platform.app.InstrumentationRegistry - .getInstrumentation() - .getUiAutomation() - .adoptShellPermissionIdentity(); - - sOverrides.reset(); - } - - @Test - public void twoOptionsAndUserSelectsOne() throws InterruptedException { - Intent sendIntent = createSendImageIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2, - PERSONAL_USER_HANDLE); - - setupResolverControllers(resolvedComponentInfos); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - Espresso.registerIdlingResources(activity.getLabelIdlingResource()); - waitForIdle(); - - assertThat(activity.getAdapter().getCount(), is(2)); - - ResolveInfo[] chosen = new ResolveInfo[1]; - sOverrides.onSafelyStartInternalCallback = result -> { - chosen[0] = result.first.getResolveInfo(); - return true; - }; - - ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0); - onView(withText(toChoose.activityInfo.name)) - .perform(click()); - onView(withId(com.android.internal.R.id.button_once)) - .perform(click()); - waitForIdle(); - assertThat(chosen[0], is(toChoose)); - } - - @Ignore // Failing - b/144929805 - @Test - public void setMaxHeight() throws Exception { - Intent sendIntent = createSendImageIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2, - PERSONAL_USER_HANDLE); - - setupResolverControllers(resolvedComponentInfos); - waitForIdle(); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - final View viewPager = activity.findViewById(com.android.internal.R.id.profile_pager); - final int initialResolverHeight = viewPager.getHeight(); - - activity.runOnUiThread(() -> { - ResolverDrawerLayout layout = (ResolverDrawerLayout) - activity.findViewById( - com.android.internal.R.id.contentPanel); - ((ResolverDrawerLayout.LayoutParams) viewPager.getLayoutParams()).maxHeight - = initialResolverHeight - 1; - // Force a relayout - layout.invalidate(); - layout.requestLayout(); - }); - waitForIdle(); - assertThat("Drawer should be capped at maxHeight", - viewPager.getHeight() == (initialResolverHeight - 1)); - - activity.runOnUiThread(() -> { - ResolverDrawerLayout layout = (ResolverDrawerLayout) - activity.findViewById( - com.android.internal.R.id.contentPanel); - ((ResolverDrawerLayout.LayoutParams) viewPager.getLayoutParams()).maxHeight - = initialResolverHeight + 1; - // Force a relayout - layout.invalidate(); - layout.requestLayout(); - }); - waitForIdle(); - assertThat("Drawer should not change height if its height is less than maxHeight", - viewPager.getHeight() == initialResolverHeight); - } - - @Ignore // Failing - b/144929805 - @Test - public void setShowAtTopToTrue() throws Exception { - Intent sendIntent = createSendImageIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2, - PERSONAL_USER_HANDLE); - - setupResolverControllers(resolvedComponentInfos); - waitForIdle(); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - final View viewPager = activity.findViewById(com.android.internal.R.id.profile_pager); - final View divider = activity.findViewById(com.android.internal.R.id.divider); - final RelativeLayout profileView = - (RelativeLayout) activity.findViewById(com.android.internal.R.id.profile_button) - .getParent(); - assertThat("Drawer should show at bottom by default", - profileView.getBottom() + divider.getHeight() == viewPager.getTop() - && profileView.getTop() > 0); - - activity.runOnUiThread(() -> { - ResolverDrawerLayout layout = (ResolverDrawerLayout) - activity.findViewById( - com.android.internal.R.id.contentPanel); - layout.setShowAtTop(true); - }); - waitForIdle(); - assertThat("Drawer should show at top with new attribute", - profileView.getBottom() + divider.getHeight() == viewPager.getTop() - && profileView.getTop() == 0); - } - - @Test - public void hasLastChosenActivity() throws Exception { - Intent sendIntent = createSendImageIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2, - PERSONAL_USER_HANDLE); - ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0); - - setupResolverControllers(resolvedComponentInfos); - when(sOverrides.resolverListController.getLastChosen()) - .thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0)); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - waitForIdle(); - - // The other entry is filtered to the last used slot - assertThat(activity.getAdapter().getCount(), is(1)); - assertThat(activity.getAdapter().getPlaceholderCount(), is(1)); - - ResolveInfo[] chosen = new ResolveInfo[1]; - sOverrides.onSafelyStartInternalCallback = result -> { - chosen[0] = result.first.getResolveInfo(); - return true; - }; - - onView(withId(com.android.internal.R.id.button_once)).perform(click()); - waitForIdle(); - assertThat(chosen[0], is(toChoose)); - } - - @Test - public void hasOtherProfileOneOption() throws Exception { - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10, - PERSONAL_USER_HANDLE); - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4, - WORK_PROFILE_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - - ResolveInfo toChoose = personalResolvedComponentInfos.get(1).getResolveInfoAt(0); - Intent sendIntent = createSendImageIntent(); - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - Espresso.registerIdlingResources(activity.getLabelIdlingResource()); - waitForIdle(); - - // The other entry is filtered to the last used slot - assertThat(activity.getAdapter().getCount(), is(1)); - - ResolveInfo[] chosen = new ResolveInfo[1]; - sOverrides.onSafelyStartInternalCallback = result -> { - chosen[0] = result.first.getResolveInfo(); - return true; - }; - // Make a stable copy of the components as the original list may be modified - List<ResolvedComponentInfo> stableCopy = - createResolvedComponentsForTestWithOtherProfile(2, /* userId= */ 10, - PERSONAL_USER_HANDLE); - // We pick the first one as there is another one in the work profile side - onView(first(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))) - .perform(click()); - onView(withId(com.android.internal.R.id.button_once)) - .perform(click()); - waitForIdle(); - assertThat(chosen[0], is(toChoose)); - } - - @Test - public void hasOtherProfileTwoOptionsAndUserSelectsOne() throws Exception { - Intent sendIntent = createSendImageIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE); - ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0); - - setupResolverControllers(resolvedComponentInfos); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - Espresso.registerIdlingResources(activity.getLabelIdlingResource()); - waitForIdle(); - - // The other entry is filtered to the other profile slot - assertThat(activity.getAdapter().getCount(), is(2)); - - ResolveInfo[] chosen = new ResolveInfo[1]; - sOverrides.onSafelyStartInternalCallback = result -> { - chosen[0] = result.first.getResolveInfo(); - return true; - }; - - // Confirm that the button bar is disabled by default - onView(withId(com.android.internal.R.id.button_once)).check(matches(not(isEnabled()))); - - // Make a stable copy of the components as the original list may be modified - List<ResolvedComponentInfo> stableCopy = - createResolvedComponentsForTestWithOtherProfile(2, PERSONAL_USER_HANDLE); - - onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)) - .perform(click()); - onView(withId(com.android.internal.R.id.button_once)).perform(click()); - waitForIdle(); - assertThat(chosen[0], is(toChoose)); - } - - - @Test - public void hasLastChosenActivityAndOtherProfile() throws Exception { - // In this case we prefer the other profile and don't display anything about the last - // chosen activity. - Intent sendIntent = createSendImageIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE); - ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0); - - setupResolverControllers(resolvedComponentInfos); - when(sOverrides.resolverListController.getLastChosen()) - .thenReturn(resolvedComponentInfos.get(1).getResolveInfoAt(0)); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - Espresso.registerIdlingResources(activity.getLabelIdlingResource()); - waitForIdle(); - - // The other entry is filtered to the other profile slot - assertThat(activity.getAdapter().getCount(), is(2)); - - ResolveInfo[] chosen = new ResolveInfo[1]; - sOverrides.onSafelyStartInternalCallback = result -> { - chosen[0] = result.first.getResolveInfo(); - return true; - }; - - // Confirm that the button bar is disabled by default - onView(withId(com.android.internal.R.id.button_once)).check(matches(not(isEnabled()))); - - // Make a stable copy of the components as the original list may be modified - List<ResolvedComponentInfo> stableCopy = - createResolvedComponentsForTestWithOtherProfile(2, PERSONAL_USER_HANDLE); - - onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)) - .perform(click()); - onView(withId(com.android.internal.R.id.button_once)).perform(click()); - waitForIdle(); - assertThat(chosen[0], is(toChoose)); - } - - @Test - public void testWorkTab_displayedWhenWorkProfileUserAvailable() { - Intent sendIntent = createSendImageIntent(); - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - - mActivityRule.launchActivity(sendIntent); - waitForIdle(); - - onView(withId(com.android.internal.R.id.tabs)).check(matches(isDisplayed())); - } - - @Test - public void testWorkTab_hiddenWhenWorkProfileUserNotAvailable() { - Intent sendIntent = createSendImageIntent(); - - mActivityRule.launchActivity(sendIntent); - waitForIdle(); - - onView(withId(com.android.internal.R.id.tabs)).check(matches(not(isDisplayed()))); - } - - @Test - public void testWorkTab_workTabListPopulatedBeforeGoingToTab() throws InterruptedException { - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId = */ 10, - PERSONAL_USER_HANDLE); - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4, - WORK_PROFILE_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, - new ArrayList<>(workResolvedComponentInfos)); - Intent sendIntent = createSendImageIntent(); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - waitForIdle(); - - assertThat(activity.getCurrentUserHandle().getIdentifier(), is(0)); - // The work list adapter must be populated in advance before tapping the other tab - assertThat(activity.getWorkListAdapter().getCount(), is(4)); - } - - @Test - public void testWorkTab_workTabUsesExpectedAdapter() { - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10, - PERSONAL_USER_HANDLE); - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4, - WORK_PROFILE_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - waitForIdle(); - onView(withText(R.string.resolver_work_tab)).perform(click()); - - assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10)); - assertThat(activity.getWorkListAdapter().getCount(), is(4)); - } - - @Test - public void testWorkTab_personalTabUsesExpectedAdapter() { - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE); - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4, - WORK_PROFILE_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - waitForIdle(); - onView(withText(R.string.resolver_work_tab)).perform(click()); - - assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10)); - assertThat(activity.getPersonalListAdapter().getCount(), is(2)); - } - - @Test - public void testWorkTab_workProfileHasExpectedNumberOfTargets() throws InterruptedException { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10, - PERSONAL_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4, - WORK_PROFILE_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - waitForIdle(); - - onView(withText(R.string.resolver_work_tab)) - .perform(click()); - waitForIdle(); - assertThat(activity.getWorkListAdapter().getCount(), is(4)); - } - - @Test - public void testWorkTab_selectingWorkTabAppOpensAppInWorkProfile() throws InterruptedException { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10, - PERSONAL_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4, - WORK_PROFILE_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - ResolveInfo[] chosen = new ResolveInfo[1]; - sOverrides.onSafelyStartInternalCallback = result -> { - chosen[0] = result.first.getResolveInfo(); - return true; - }; - - mActivityRule.launchActivity(sendIntent); - waitForIdle(); - onView(withText(R.string.resolver_work_tab)) - .perform(click()); - waitForIdle(); - onView(first(allOf(withText(workResolvedComponentInfos.get(0) - .getResolveInfoAt(0).activityInfo.applicationInfo.name), isCompletelyDisplayed()))) - .perform(click()); - onView(withId(com.android.internal.R.id.button_once)) - .perform(click()); - - waitForIdle(); - assertThat(chosen[0], is(workResolvedComponentInfos.get(0).getResolveInfoAt(0))); - } - - @Test - public void testWorkTab_noPersonalApps_workTabHasExpectedNumberOfTargets() - throws InterruptedException { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(1, PERSONAL_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4, - WORK_PROFILE_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - waitForIdle(); - onView(withText(R.string.resolver_work_tab)) - .perform(click()); - - waitForIdle(); - assertThat(activity.getWorkListAdapter().getCount(), is(4)); - } - - @Test - public void testWorkTab_headerIsVisibleInPersonalTab() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(1, PERSONAL_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4, - WORK_PROFILE_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createOpenWebsiteIntent(); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - waitForIdle(); - TextView headerText = activity.findViewById(com.android.internal.R.id.title); - String initialText = headerText.getText().toString(); - assertFalse("Header text is empty.", initialText.isEmpty()); - assertThat(headerText.getVisibility(), is(View.VISIBLE)); - } - - @Test - public void testWorkTab_switchTabs_headerStaysSame() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(1, PERSONAL_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4, - WORK_PROFILE_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createOpenWebsiteIntent(); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - waitForIdle(); - TextView headerText = activity.findViewById(com.android.internal.R.id.title); - String initialText = headerText.getText().toString(); - onView(withText(R.string.resolver_work_tab)) - .perform(click()); - - waitForIdle(); - String currentText = headerText.getText().toString(); - assertThat(headerText.getVisibility(), is(View.VISIBLE)); - assertThat(String.format("Header text is not the same when switching tabs, personal profile" - + " header was %s but work profile header is %s", initialText, currentText), - TextUtils.equals(initialText, currentText)); - } - - @Test - public void testWorkTab_noPersonalApps_canStartWorkApps() - throws InterruptedException { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId= */ 10, - PERSONAL_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4, - WORK_PROFILE_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - ResolveInfo[] chosen = new ResolveInfo[1]; - sOverrides.onSafelyStartInternalCallback = result -> { - chosen[0] = result.first.getResolveInfo(); - return true; - }; - - mActivityRule.launchActivity(sendIntent); - waitForIdle(); - onView(withText(R.string.resolver_work_tab)) - .perform(click()); - waitForIdle(); - onView(first(allOf( - withText(workResolvedComponentInfos.get(0) - .getResolveInfoAt(0).activityInfo.applicationInfo.name), - isDisplayed()))) - .perform(click()); - onView(withId(com.android.internal.R.id.button_once)) - .perform(click()); - waitForIdle(); - - assertThat(chosen[0], is(workResolvedComponentInfos.get(0).getResolveInfoAt(0))); - } - - @Test - public void testWorkTab_crossProfileIntentsDisabled_personalToWork_emptyStateShown() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - int workProfileTargets = 4; - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10, - PERSONAL_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(workProfileTargets, WORK_PROFILE_USER_HANDLE); - sOverrides.hasCrossProfileIntents = false; - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - sendIntent.setType("TestType"); - - mActivityRule.launchActivity(sendIntent); - waitForIdle(); - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - onView(withId(com.android.internal.R.id.contentPanel)) - .perform(swipeUp()); - - onView(withText(R.string.resolver_cross_profile_blocked)) - .check(matches(isDisplayed())); - } - - @Test - public void testWorkTab_workProfileDisabled_emptyStateShown() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - int workProfileTargets = 4; - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10, - PERSONAL_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(workProfileTargets, WORK_PROFILE_USER_HANDLE); - sOverrides.isQuietModeEnabled = true; - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - sendIntent.setType("TestType"); - - mActivityRule.launchActivity(sendIntent); - waitForIdle(); - onView(withId(com.android.internal.R.id.contentPanel)) - .perform(swipeUp()); - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - - onView(withText(R.string.resolver_turn_on_work_apps)) - .check(matches(isDisplayed())); - } - - @Test - public void testWorkTab_noWorkAppsAvailable_emptyStateShown() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(3, PERSONAL_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(0, WORK_PROFILE_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - sendIntent.setType("TestType"); - - mActivityRule.launchActivity(sendIntent); - waitForIdle(); - onView(withId(com.android.internal.R.id.contentPanel)) - .perform(swipeUp()); - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - - onView(withText(R.string.resolver_no_work_apps_available)) - .check(matches(isDisplayed())); - } - - @Test - public void testWorkTab_xProfileOff_noAppsAvailable_workOff_xProfileOffEmptyStateShown() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(3, PERSONAL_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(0, WORK_PROFILE_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - sendIntent.setType("TestType"); - sOverrides.isQuietModeEnabled = true; - sOverrides.hasCrossProfileIntents = false; - - mActivityRule.launchActivity(sendIntent); - waitForIdle(); - onView(withId(com.android.internal.R.id.contentPanel)) - .perform(swipeUp()); - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - - onView(withText(R.string.resolver_cross_profile_blocked)) - .check(matches(isDisplayed())); - } - - @Test - public void testMiniResolver() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(1, PERSONAL_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(1, WORK_PROFILE_USER_HANDLE); - // Personal profile only has a browser - personalResolvedComponentInfos.get(0).getResolveInfoAt(0).handleAllWebDataURI = true; - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - sendIntent.setType("TestType"); - - mActivityRule.launchActivity(sendIntent); - waitForIdle(); - onView(withId(com.android.internal.R.id.open_cross_profile)).check(matches(isDisplayed())); - } - - @Test - public void testMiniResolver_noCurrentProfileTarget() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(0, PERSONAL_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(1, WORK_PROFILE_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - sendIntent.setType("TestType"); - - mActivityRule.launchActivity(sendIntent); - waitForIdle(); - - // Need to ensure mini resolver doesn't trigger here. - assertNotMiniResolver(); - } - - private void assertNotMiniResolver() { - try { - onView(withId(com.android.internal.R.id.open_cross_profile)) - .check(matches(isDisplayed())); - } catch (NoMatchingViewException e) { - return; - } - fail("Mini resolver present but shouldn't be"); - } - - @Test - public void testWorkTab_noAppsAvailable_workOff_noAppsAvailableEmptyStateShown() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(3, PERSONAL_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(0, WORK_PROFILE_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - sendIntent.setType("TestType"); - sOverrides.isQuietModeEnabled = true; - - mActivityRule.launchActivity(sendIntent); - waitForIdle(); - onView(withId(com.android.internal.R.id.contentPanel)) - .perform(swipeUp()); - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - - onView(withText(R.string.resolver_no_work_apps_available)) - .check(matches(isDisplayed())); - } - - @Test - public void testWorkTab_onePersonalTarget_emptyStateOnWorkTarget_doesNotAutoLaunch() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - int workProfileTargets = 4; - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10, - PERSONAL_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(workProfileTargets, WORK_PROFILE_USER_HANDLE); - sOverrides.hasCrossProfileIntents = false; - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - sendIntent.setType("TestType"); - ResolveInfo[] chosen = new ResolveInfo[1]; - sOverrides.onSafelyStartInternalCallback = result -> { - chosen[0] = result.first.getResolveInfo(); - return true; - }; - - mActivityRule.launchActivity(sendIntent); - waitForIdle(); - - assertNull(chosen[0]); - } - - @Test - public void testLayoutWithDefault_withWorkTab_neverShown() throws RemoteException { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - - // In this case we prefer the other profile and don't display anything about the last - // chosen activity. - Intent sendIntent = createSendImageIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = - createResolvedComponentsForTest(2, PERSONAL_USER_HANDLE); - - setupResolverControllers(resolvedComponentInfos); - when(sOverrides.resolverListController.getLastChosen()) - .thenReturn(resolvedComponentInfos.get(1).getResolveInfoAt(0)); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - Espresso.registerIdlingResources(activity.getLabelIdlingResource()); - waitForIdle(); - - // The other entry is filtered to the last used slot - assertThat(activity.getAdapter().hasFilteredItem(), is(false)); - assertThat(activity.getAdapter().getCount(), is(2)); - assertThat(activity.getAdapter().getPlaceholderCount(), is(2)); - } - - @Test - public void testClonedProfilePresent_personalAdapterIsSetWithPersonalProfile() { - // enable cloneProfile - markOtherProfileAvailability(/* workAvailable= */ false, /* cloneAvailable= */ true); - List<ResolvedComponentInfo> resolvedComponentInfos = - createResolvedComponentsWithCloneProfileForTest( - 3, - PERSONAL_USER_HANDLE, - CLONE_PROFILE_USER_HANDLE); - setupResolverControllers(resolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - waitForIdle(); - - assertThat(activity.getCurrentUserHandle(), is(PERSONAL_USER_HANDLE)); - assertThat(activity.getAdapter().getCount(), is(3)); - } - - @Test - public void testClonedProfilePresent_personalTabUsesExpectedAdapter() { - // enable cloneProfile - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ true); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsWithCloneProfileForTest( - 3, - PERSONAL_USER_HANDLE, - CLONE_PROFILE_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4, - WORK_PROFILE_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - waitForIdle(); - - assertThat(activity.getCurrentUserHandle(), is(PERSONAL_USER_HANDLE)); - assertThat(activity.getAdapter().getCount(), is(3)); - } - - @Test - public void testClonedProfilePresent_layoutWithDefault_neverShown() throws Exception { - // enable cloneProfile - markOtherProfileAvailability(/* workAvailable= */ false, /* cloneAvailable= */ true); - Intent sendIntent = createSendImageIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = - createResolvedComponentsWithCloneProfileForTest( - 2, - PERSONAL_USER_HANDLE, - CLONE_PROFILE_USER_HANDLE); - - setupResolverControllers(resolvedComponentInfos); - when(sOverrides.resolverListController.getLastChosen()) - .thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0)); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - Espresso.registerIdlingResources(activity.getLabelIdlingResource()); - waitForIdle(); - - assertThat(activity.getAdapter().hasFilteredItem(), is(false)); - assertThat(activity.getAdapter().getCount(), is(2)); - assertThat(activity.getAdapter().getPlaceholderCount(), is(2)); - } - - @Test - public void testClonedProfilePresent_alwaysButtonDisabled() throws Exception { - // enable cloneProfile - markOtherProfileAvailability(/* workAvailable= */ false, /* cloneAvailable= */ true); - Intent sendIntent = createSendImageIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = - createResolvedComponentsWithCloneProfileForTest( - 3, - PERSONAL_USER_HANDLE, - CLONE_PROFILE_USER_HANDLE); - - setupResolverControllers(resolvedComponentInfos); - when(sOverrides.resolverListController.getLastChosen()) - .thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0)); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - waitForIdle(); - - // Confirm that the button bar is disabled by default - onView(withId(com.android.internal.R.id.button_once)).check(matches(not(isEnabled()))); - onView(withId(com.android.internal.R.id.button_always)).check(matches(not(isEnabled()))); - - // Make a stable copy of the components as the original list may be modified - List<ResolvedComponentInfo> stableCopy = - createResolvedComponentsForTestWithOtherProfile(2, PERSONAL_USER_HANDLE); - - onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)) - .perform(click()); - - onView(withId(com.android.internal.R.id.button_once)).check(matches(isEnabled())); - onView(withId(com.android.internal.R.id.button_always)).check(matches(not(isEnabled()))); - } - - @Test - public void testClonedProfilePresent_personalProfileActivityIsStartedInCorrectUser() - throws Exception { - // enable cloneProfile - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ true); - - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsWithCloneProfileForTest( - 3, - PERSONAL_USER_HANDLE, - CLONE_PROFILE_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(3, WORK_PROFILE_USER_HANDLE); - sOverrides.hasCrossProfileIntents = false; - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - sendIntent.setType("TestType"); - final UserHandle[] selectedActivityUserHandle = new UserHandle[1]; - sOverrides.onSafelyStartInternalCallback = result -> { - selectedActivityUserHandle[0] = result.second; - return true; - }; - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - waitForIdle(); - onView(first(allOf(withText(personalResolvedComponentInfos.get(0) - .getResolveInfoAt(0).activityInfo.applicationInfo.name), isCompletelyDisplayed()))) - .perform(click()); - onView(withId(com.android.internal.R.id.button_once)) - .perform(click()); - waitForIdle(); - - assertThat(selectedActivityUserHandle[0], is(activity.getAdapter().getUserHandle())); - } - - @Test - public void testClonedProfilePresent_workProfileActivityIsStartedInCorrectUser() - throws Exception { - // enable cloneProfile - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ true); - - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsWithCloneProfileForTest( - 3, - PERSONAL_USER_HANDLE, - CLONE_PROFILE_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(3, WORK_PROFILE_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - sendIntent.setType("TestType"); - final UserHandle[] selectedActivityUserHandle = new UserHandle[1]; - sOverrides.onSafelyStartInternalCallback = result -> { - selectedActivityUserHandle[0] = result.second; - return true; - }; - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - waitForIdle(); - onView(withText(R.string.resolver_work_tab)) - .perform(click()); - waitForIdle(); - onView(first(allOf(withText(workResolvedComponentInfos.get(0) - .getResolveInfoAt(0).activityInfo.applicationInfo.name), isCompletelyDisplayed()))) - .perform(click()); - onView(withId(com.android.internal.R.id.button_once)) - .perform(click()); - waitForIdle(); - - assertThat(selectedActivityUserHandle[0], is(activity.getAdapter().getUserHandle())); - } - - @Test - public void testClonedProfilePresent_personalProfileResolverComparatorHasCorrectUsers() - throws Exception { - // enable cloneProfile - markOtherProfileAvailability(/* workAvailable= */ false, /* cloneAvailable= */ true); - List<ResolvedComponentInfo> resolvedComponentInfos = - createResolvedComponentsWithCloneProfileForTest( - 3, - PERSONAL_USER_HANDLE, - CLONE_PROFILE_USER_HANDLE); - setupResolverControllers(resolvedComponentInfos); - Intent sendIntent = createSendImageIntent(); - - final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); - waitForIdle(); - List<UserHandle> result = activity - .getResolverRankerServiceUserHandleList(PERSONAL_USER_HANDLE); - - assertThat(result.containsAll( - Lists.newArrayList(PERSONAL_USER_HANDLE, CLONE_PROFILE_USER_HANDLE)), is(true)); - } - - private Intent createSendImageIntent() { - Intent sendIntent = new Intent(); - sendIntent.setAction(Intent.ACTION_SEND); - sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending"); - sendIntent.setType("image/jpeg"); - return sendIntent; - } - - private Intent createOpenWebsiteIntent() { - Intent sendIntent = new Intent(); - sendIntent.setAction(Intent.ACTION_VIEW); - sendIntent.setData(Uri.parse("https://google.com")); - return sendIntent; - } - - private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults, - UserHandle resolvedForUser) { - List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); - for (int i = 0; i < numberOfResults; i++) { - infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, resolvedForUser)); - } - return infoList; - } - - private List<ResolvedComponentInfo> createResolvedComponentsWithCloneProfileForTest( - int numberOfResults, - UserHandle resolvedForPersonalUser, - UserHandle resolvedForClonedUser) { - List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); - for (int i = 0; i < 1; i++) { - infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, - resolvedForPersonalUser)); - } - for (int i = 1; i < numberOfResults; i++) { - infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, - resolvedForClonedUser)); - } - return infoList; - } - - private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile( - int numberOfResults, - UserHandle resolvedForUser) { - List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); - for (int i = 0; i < numberOfResults; i++) { - if (i == 0) { - infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, - resolvedForUser)); - } else { - infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, resolvedForUser)); - } - } - return infoList; - } - - private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile( - int numberOfResults, int userId, UserHandle resolvedForUser) { - List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); - for (int i = 0; i < numberOfResults; i++) { - if (i == 0) { - infoList.add( - ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId, - resolvedForUser)); - } else { - infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, resolvedForUser)); - } - } - return infoList; - } - - private void waitForIdle() { - InstrumentationRegistry.getInstrumentation().waitForIdleSync(); - } - - private void markOtherProfileAvailability(boolean workAvailable, boolean cloneAvailable) { - AnnotatedUserHandles.Builder handles = AnnotatedUserHandles.newBuilder(); - handles - .setUserIdOfCallingApp(1234) // Must be non-negative. - .setUserHandleSharesheetLaunchedAs(PERSONAL_USER_HANDLE) - .setPersonalProfileUserHandle(PERSONAL_USER_HANDLE); - if (workAvailable) { - handles.setWorkProfileUserHandle(WORK_PROFILE_USER_HANDLE); - } - if (cloneAvailable) { - handles.setCloneProfileUserHandle(CLONE_PROFILE_USER_HANDLE); - } - sOverrides.annotatedUserHandles = handles.build(); - } - - private void setupResolverControllers( - List<ResolvedComponentInfo> personalResolvedComponentInfos) { - setupResolverControllers(personalResolvedComponentInfos, new ArrayList<>()); - } - - private void setupResolverControllers( - List<ResolvedComponentInfo> personalResolvedComponentInfos, - List<ResolvedComponentInfo> workResolvedComponentInfos) { - when(sOverrides.resolverListController.getResolversForIntentAsUser( - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - eq(UserHandle.SYSTEM))) - .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); - when(sOverrides.workResolverListController.getResolversForIntentAsUser( - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - eq(UserHandle.SYSTEM))) - .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); - when(sOverrides.workResolverListController.getResolversForIntentAsUser( - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - eq(UserHandle.of(10)))) - .thenReturn(new ArrayList<>(workResolvedComponentInfos)); - } -} diff --git a/java/tests/src/com/android/intentresolver/v2/ResolverWrapperActivity.java b/java/tests/src/com/android/intentresolver/v2/ResolverWrapperActivity.java deleted file mode 100644 index 92b73d92..00000000 --- a/java/tests/src/com/android/intentresolver/v2/ResolverWrapperActivity.java +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright (C) 2017 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; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import android.annotation.Nullable; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.os.UserHandle; -import android.util.Pair; - -import androidx.annotation.NonNull; -import androidx.test.espresso.idling.CountingIdlingResource; - -import com.android.intentresolver.AnnotatedUserHandles; -import com.android.intentresolver.ResolverListAdapter; -import com.android.intentresolver.ResolverListController; -import com.android.intentresolver.WorkProfileAvailabilityManager; -import com.android.intentresolver.chooser.DisplayResolveInfo; -import com.android.intentresolver.chooser.SelectableTargetInfo; -import com.android.intentresolver.chooser.TargetInfo; -import com.android.intentresolver.emptystate.CrossProfileIntentsChecker; -import com.android.intentresolver.icons.LabelInfo; -import com.android.intentresolver.icons.TargetDataLoader; - -import kotlin.Unit; - -import java.util.List; -import java.util.function.Consumer; -import java.util.function.Function; - -/* - * Simple wrapper around chooser activity to be able to initiate it under test - */ -public class ResolverWrapperActivity extends ResolverActivity { - static final OverrideData sOverrides = new OverrideData(); - - private final CountingIdlingResource mLabelIdlingResource = - new CountingIdlingResource("LoadLabelTask"); - - public ResolverWrapperActivity() { - super(/* isIntentPicker= */ true); - mLogic = new TestResolverActivityLogic( - "ResolverWrapper", - () -> this, - () -> { - onWorkProfileStatusUpdated(); - return Unit.INSTANCE; - }, - sOverrides - ); - } - - public CountingIdlingResource getLabelIdlingResource() { - return mLabelIdlingResource; - } - - @Override - public ResolverListAdapter createResolverListAdapter( - Context context, - List<Intent> payloadIntents, - Intent[] initialIntents, - List<ResolveInfo> rList, - boolean filterLastUsed, - UserHandle userHandle, - TargetDataLoader targetDataLoader) { - return new ResolverListAdapter( - context, - payloadIntents, - initialIntents, - rList, - filterLastUsed, - createListController(userHandle), - userHandle, - payloadIntents.get(0), // TODO: extract upstream - this, - userHandle, - new TargetDataLoaderWrapper(targetDataLoader, mLabelIdlingResource)); - } - - @Override - protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() { - if (sOverrides.mCrossProfileIntentsChecker != null) { - return sOverrides.mCrossProfileIntentsChecker; - } - return super.createCrossProfileIntentsChecker(); - } - - ResolverListAdapter getAdapter() { - return mMultiProfilePagerAdapter.getActiveListAdapter(); - } - - ResolverListAdapter getPersonalListAdapter() { - return ((ResolverListAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(0)); - } - - ResolverListAdapter getWorkListAdapter() { - if (mMultiProfilePagerAdapter.getInactiveListAdapter() == null) { - return null; - } - return ((ResolverListAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(1)); - } - - @Override - public boolean isVoiceInteraction() { - if (sOverrides.isVoiceInteraction != null) { - return sOverrides.isVoiceInteraction; - } - return super.isVoiceInteraction(); - } - - @Override - public void safelyStartActivityInternal(TargetInfo cti, UserHandle user, - @Nullable Bundle options) { - if (sOverrides.onSafelyStartInternalCallback != null - && sOverrides.onSafelyStartInternalCallback.apply(new Pair<>(cti, user))) { - return; - } - super.safelyStartActivityInternal(cti, user, options); - } - - @Override - protected ResolverListController createListController(UserHandle userHandle) { - if (userHandle == UserHandle.SYSTEM) { - return sOverrides.resolverListController; - } - return sOverrides.workResolverListController; - } - - @Override - public PackageManager getPackageManager() { - if (sOverrides.createPackageManager != null) { - return sOverrides.createPackageManager.apply(super.getPackageManager()); - } - return super.getPackageManager(); - } - - protected UserHandle getCurrentUserHandle() { - return mMultiProfilePagerAdapter.getCurrentUserHandle(); - } - - @Override - public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) { - super.startActivityAsUser(intent, options, user); - } - - @Override - protected List<UserHandle> getResolverRankerServiceUserHandleListInternal(UserHandle - userHandle) { - return super.getResolverRankerServiceUserHandleListInternal(userHandle); - } - - /** - * We cannot directly mock the activity created since instrumentation creates it. - * <p> - * Instead, we use static instances of this object to modify behavior. - */ - public static class OverrideData { - @SuppressWarnings("Since15") - public Function<PackageManager, PackageManager> createPackageManager; - public Function<Pair<TargetInfo, UserHandle>, Boolean> onSafelyStartInternalCallback; - public ResolverListController resolverListController; - public ResolverListController workResolverListController; - public Boolean isVoiceInteraction; - public AnnotatedUserHandles annotatedUserHandles; - public Integer myUserId; - public boolean hasCrossProfileIntents; - public boolean isQuietModeEnabled; - public WorkProfileAvailabilityManager mWorkProfileAvailability; - public CrossProfileIntentsChecker mCrossProfileIntentsChecker; - - public void reset() { - onSafelyStartInternalCallback = null; - isVoiceInteraction = null; - createPackageManager = null; - resolverListController = mock(ResolverListController.class); - workResolverListController = mock(ResolverListController.class); - annotatedUserHandles = AnnotatedUserHandles.newBuilder() - .setUserIdOfCallingApp(1234) // Must be non-negative. - .setUserHandleSharesheetLaunchedAs(UserHandle.SYSTEM) - .setPersonalProfileUserHandle(UserHandle.SYSTEM) - .build(); - myUserId = null; - hasCrossProfileIntents = true; - isQuietModeEnabled = false; - - mWorkProfileAvailability = new WorkProfileAvailabilityManager(null, null, null) { - @Override - public boolean isQuietModeEnabled() { - return isQuietModeEnabled; - } - - @Override - public boolean isWorkProfileUserUnlocked() { - return true; - } - - @Override - public void requestQuietModeEnabled(boolean enabled) { - isQuietModeEnabled = enabled; - } - - @Override - public void markWorkProfileEnabledBroadcastReceived() {} - - @Override - public boolean isWaitingToEnableWorkProfile() { - return false; - } - }; - - mCrossProfileIntentsChecker = mock(CrossProfileIntentsChecker.class); - when(mCrossProfileIntentsChecker.hasCrossProfileIntents(any(), anyInt(), anyInt())) - .thenAnswer(invocation -> hasCrossProfileIntents); - } - } - - private static class TargetDataLoaderWrapper extends TargetDataLoader { - private final TargetDataLoader mTargetDataLoader; - private final CountingIdlingResource mLabelIdlingResource; - - private TargetDataLoaderWrapper( - TargetDataLoader targetDataLoader, CountingIdlingResource labelIdlingResource) { - mTargetDataLoader = targetDataLoader; - mLabelIdlingResource = labelIdlingResource; - } - - @Override - public void loadAppTargetIcon( - @NonNull DisplayResolveInfo info, - @NonNull UserHandle userHandle, - @NonNull Consumer<Drawable> callback) { - mTargetDataLoader.loadAppTargetIcon(info, userHandle, callback); - } - - @Override - public void loadDirectShareIcon( - @NonNull SelectableTargetInfo info, - @NonNull UserHandle userHandle, - @NonNull Consumer<Drawable> callback) { - mTargetDataLoader.loadDirectShareIcon(info, userHandle, callback); - } - - @Override - public void loadLabel( - @NonNull DisplayResolveInfo info, - @NonNull Consumer<LabelInfo> callback) { - mLabelIdlingResource.increment(); - mTargetDataLoader.loadLabel( - info, - (result) -> { - mLabelIdlingResource.decrement(); - callback.accept(result); - }); - } - - @Override - public void getOrLoadLabel(@NonNull DisplayResolveInfo info) { - mTargetDataLoader.getOrLoadLabel(info); - } - } -} diff --git a/java/tests/src/com/android/intentresolver/v2/TestChooserActivityLogic.kt b/java/tests/src/com/android/intentresolver/v2/TestChooserActivityLogic.kt deleted file mode 100644 index 198b9236..00000000 --- a/java/tests/src/com/android/intentresolver/v2/TestChooserActivityLogic.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.android.intentresolver.v2 - -import androidx.activity.ComponentActivity -import com.android.intentresolver.AnnotatedUserHandles -import com.android.intentresolver.WorkProfileAvailabilityManager -import com.android.intentresolver.icons.TargetDataLoader - -/** Activity logic for use when testing [ChooserActivity]. */ -class TestChooserActivityLogic( - tag: String, - activityProvider: () -> ComponentActivity, - onWorkProfileStatusUpdated: () -> Unit, - targetDataLoaderProvider: () -> TargetDataLoader, - onPreinitialization: () -> Unit, - private val overrideData: ChooserActivityOverrideData, -) : - ChooserActivityLogic( - tag, - activityProvider, - onWorkProfileStatusUpdated, - targetDataLoaderProvider, - onPreinitialization, - ) { - - override val annotatedUserHandles: AnnotatedUserHandles? by lazy { - overrideData.annotatedUserHandles - } - - override val workProfileAvailabilityManager: WorkProfileAvailabilityManager by lazy { - overrideData.mWorkProfileAvailability ?: super.workProfileAvailabilityManager - } -} diff --git a/java/tests/src/com/android/intentresolver/v2/TestResolverActivityLogic.kt b/java/tests/src/com/android/intentresolver/v2/TestResolverActivityLogic.kt deleted file mode 100644 index 7581043e..00000000 --- a/java/tests/src/com/android/intentresolver/v2/TestResolverActivityLogic.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.android.intentresolver.v2 - -import androidx.activity.ComponentActivity -import com.android.intentresolver.AnnotatedUserHandles -import com.android.intentresolver.WorkProfileAvailabilityManager - -/** Activity logic for use when testing [ResolverActivity]. */ -class TestResolverActivityLogic( - tag: String, - activityProvider: () -> ComponentActivity, - onWorkProfileStatusUpdated: () -> Unit, - private val overrideData: ResolverWrapperActivity.OverrideData, -) : ResolverActivityLogic(tag, activityProvider, onWorkProfileStatusUpdated) { - - override val annotatedUserHandles: AnnotatedUserHandles? by lazy { - overrideData.annotatedUserHandles - } - - override val workProfileAvailabilityManager: WorkProfileAvailabilityManager by lazy { - overrideData.mWorkProfileAvailability ?: super.workProfileAvailabilityManager - } -} diff --git a/java/tests/src/com/android/intentresolver/v2/UnbundledChooserActivityTest.java b/java/tests/src/com/android/intentresolver/v2/UnbundledChooserActivityTest.java deleted file mode 100644 index 5245f655..00000000 --- a/java/tests/src/com/android/intentresolver/v2/UnbundledChooserActivityTest.java +++ /dev/null @@ -1,3147 +0,0 @@ -/* - * Copyright (C) 2016 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; - -import static android.app.Activity.RESULT_OK; - -import static androidx.test.espresso.Espresso.onView; -import static androidx.test.espresso.action.ViewActions.click; -import static androidx.test.espresso.action.ViewActions.longClick; -import static androidx.test.espresso.action.ViewActions.swipeUp; -import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist; -import static androidx.test.espresso.assertion.ViewAssertions.matches; -import static androidx.test.espresso.matcher.ViewMatchers.hasSibling; -import static androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed; -import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; -import static androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility; -import static androidx.test.espresso.matcher.ViewMatchers.withId; -import static androidx.test.espresso.matcher.ViewMatchers.withText; - -import static com.android.intentresolver.ChooserActivity.TARGET_TYPE_CHOOSER_TARGET; -import static com.android.intentresolver.ChooserActivity.TARGET_TYPE_DEFAULT; -import static com.android.intentresolver.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE; -import static com.android.intentresolver.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER; -import static com.android.intentresolver.ChooserListAdapter.CALLER_TARGET_SCORE_BOOST; -import static com.android.intentresolver.ChooserListAdapter.SHORTCUT_TARGET_SCORE_BOOST; -import static com.android.intentresolver.MatcherUtils.first; - -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assertWithMessage; - -import static junit.framework.Assert.assertNull; - -import static org.hamcrest.CoreMatchers.allOf; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.app.PendingIntent; -import android.app.usage.UsageStatsManager; -import android.content.BroadcastReceiver; -import android.content.ClipData; -import android.content.ClipDescription; -import android.content.ClipboardManager; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.pm.ShortcutInfo; -import android.content.pm.ShortcutManager.ShareShortcutInfo; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Rect; -import android.graphics.Typeface; -import android.graphics.drawable.Icon; -import android.net.Uri; -import android.os.Bundle; -import android.os.UserHandle; -import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; -import android.provider.DeviceConfig; -import android.service.chooser.ChooserAction; -import android.service.chooser.ChooserTarget; -import android.text.Spannable; -import android.text.SpannableStringBuilder; -import android.text.Spanned; -import android.text.style.BackgroundColorSpan; -import android.text.style.ForegroundColorSpan; -import android.text.style.StyleSpan; -import android.text.style.UnderlineSpan; -import android.util.Pair; -import android.util.SparseArray; -import android.view.View; -import android.view.WindowManager; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.recyclerview.widget.GridLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import androidx.test.espresso.contrib.RecyclerViewActions; -import androidx.test.espresso.matcher.BoundedDiagnosingMatcher; -import androidx.test.espresso.matcher.ViewMatchers; -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.rule.ActivityTestRule; - -import com.android.intentresolver.AnnotatedUserHandles; -import com.android.intentresolver.ChooserListAdapter; -import com.android.intentresolver.Flags; -import com.android.intentresolver.IChooserWrapper; -import com.android.intentresolver.R; -import com.android.intentresolver.ResolvedComponentInfo; -import com.android.intentresolver.ResolverDataProvider; -import com.android.intentresolver.TestContentProvider; -import com.android.intentresolver.TestPreviewImageLoader; -import com.android.intentresolver.chooser.DisplayResolveInfo; -import com.android.intentresolver.contentpreview.ImageLoader; -import com.android.intentresolver.logging.EventLog; -import com.android.intentresolver.logging.FakeEventLog; -import com.android.intentresolver.shortcuts.ShortcutLoader; -import com.android.intentresolver.v2.platform.ImageEditor; -import com.android.intentresolver.v2.platform.ImageEditorModule; -import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; - -import dagger.hilt.android.testing.BindValue; -import dagger.hilt.android.testing.HiltAndroidRule; -import dagger.hilt.android.testing.HiltAndroidTest; -import dagger.hilt.android.testing.UninstallModules; - -import org.hamcrest.Description; -import org.hamcrest.Matcher; -import org.hamcrest.Matchers; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; -import java.util.function.Function; - -/** - * Instrumentation tests for ChooserActivity. - * <p> - * Legacy test suite migrated from framework CoreTests. - */ -@RunWith(Parameterized.class) -@HiltAndroidTest -@UninstallModules(ImageEditorModule.class) -public class UnbundledChooserActivityTest { - - private static FakeEventLog getEventLog(ChooserWrapperActivity activity) { - return (FakeEventLog) activity.mEventLog; - } - - private static final UserHandle PERSONAL_USER_HANDLE = InstrumentationRegistry - .getInstrumentation().getTargetContext().getUser(); - private static final UserHandle WORK_PROFILE_USER_HANDLE = UserHandle.of(10); - private static final UserHandle CLONE_PROFILE_USER_HANDLE = UserHandle.of(11); - - private static final Function<PackageManager, PackageManager> DEFAULT_PM = pm -> pm; - private static final Function<PackageManager, PackageManager> NO_APP_PREDICTION_SERVICE_PM = - pm -> { - PackageManager mock = Mockito.spy(pm); - when(mock.getAppPredictionServicePackageName()).thenReturn(null); - return mock; - }; - - @Parameterized.Parameters - public static Collection packageManagers() { - return Arrays.asList(new Object[][] { - // Default PackageManager - { DEFAULT_PM }, - // No App Prediction Service - { NO_APP_PREDICTION_SERVICE_PM} - }); - } - - private static final String TEST_MIME_TYPE = "application/TestType"; - - private static final int CONTENT_PREVIEW_IMAGE = 1; - private static final int CONTENT_PREVIEW_FILE = 2; - private static final int CONTENT_PREVIEW_TEXT = 3; - - @Rule(order = 0) - public CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); - - @Rule(order = 1) - public HiltAndroidRule mHiltAndroidRule = new HiltAndroidRule(this); - - @Rule(order = 2) - public ActivityTestRule<ChooserWrapperActivity> mActivityRule = - new ActivityTestRule<>(ChooserWrapperActivity.class, false, false); - - @Before - public void setUp() { - // TODO: use the other form of `adoptShellPermissionIdentity()` where we explicitly list the - // permissions we require (which we'll read from the manifest at runtime). - InstrumentationRegistry - .getInstrumentation() - .getUiAutomation() - .adoptShellPermissionIdentity(); - - cleanOverrideData(); - mHiltAndroidRule.inject(); - } - - private final Function<PackageManager, PackageManager> mPackageManagerOverride; - - /** An arbitrary pre-installed activity that handles this type of intent. */ - @BindValue - @ImageEditor - final Optional<ComponentName> mImageEditor = Optional.ofNullable( - ComponentName.unflattenFromString("com.google.android.apps.messaging/" - + ".ui.conversationlist.ShareIntentActivity")); - - public UnbundledChooserActivityTest( - Function<PackageManager, PackageManager> packageManagerOverride) { - mPackageManagerOverride = packageManagerOverride; - } - - private void setDeviceConfigProperty( - @NonNull String propertyName, - @NonNull String value) { - // TODO: consider running with {@link #runWithShellPermissionIdentity()} to more narrowly - // request WRITE_DEVICE_CONFIG permissions if we get rid of the broad grant we currently - // configure in {@link #setup()}. - // TODO: is it really appropriate that this is always set with makeDefault=true? - boolean valueWasSet = DeviceConfig.setProperty( - DeviceConfig.NAMESPACE_SYSTEMUI, - propertyName, - value, - true /* makeDefault */); - if (!valueWasSet) { - throw new IllegalStateException( - "Could not set " + propertyName + " to " + value); - } - } - - public void cleanOverrideData() { - ChooserActivityOverrideData.getInstance().reset(); - ChooserActivityOverrideData.getInstance().createPackageManager = mPackageManagerOverride; - - setDeviceConfigProperty( - SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI, - Boolean.toString(true)); - } - - @Test - public void customTitle() throws InterruptedException { - Intent viewIntent = createViewTextIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - final IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity( - Intent.createChooser(viewIntent, "chooser test")); - - waitForIdle(); - assertThat(activity.getAdapter().getCount(), is(2)); - assertThat(activity.getAdapter().getServiceTargetCount(), is(0)); - onView(withId(android.R.id.title)).check(matches(withText("chooser test"))); - } - - @Test - public void customTitleIgnoredForSendIntents() throws InterruptedException { - Intent sendIntent = createSendTextIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "chooser test")); - waitForIdle(); - onView(withId(android.R.id.title)) - .check(matches(withText(R.string.whichSendApplication))); - } - - @Test - public void emptyTitle() throws InterruptedException { - Intent sendIntent = createSendTextIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - onView(withId(android.R.id.title)) - .check(matches(withText(R.string.whichSendApplication))); - } - - @Test - public void test_shareRichTextWithRichTitle_richTextAndRichTitleDisplayed() { - CharSequence title = new SpannableStringBuilder() - .append("Rich", new UnderlineSpan(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE) - .append( - "Title", - new ForegroundColorSpan(Color.RED), - Spannable.SPAN_INCLUSIVE_EXCLUSIVE); - CharSequence sharedText = new SpannableStringBuilder() - .append( - "Rich", - new BackgroundColorSpan(Color.YELLOW), - Spanned.SPAN_INCLUSIVE_EXCLUSIVE) - .append( - "Text", - new StyleSpan(Typeface.ITALIC), - Spanned.SPAN_INCLUSIVE_EXCLUSIVE); - Intent sendIntent = createSendTextIntent(); - sendIntent.putExtra(Intent.EXTRA_TEXT, sharedText); - sendIntent.putExtra(Intent.EXTRA_TITLE, title); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - setupResolverControllers(resolvedComponentInfos); - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - onView(withId(com.android.internal.R.id.content_preview_title)) - .check((view, e) -> { - assertThat(view).isInstanceOf(TextView.class); - CharSequence text = ((TextView) view).getText(); - assertThat(text).isInstanceOf(Spanned.class); - Spanned spanned = (Spanned) text; - assertThat(spanned.getSpans(0, spanned.length(), Object.class)) - .hasLength(2); - assertThat(spanned.getSpans(0, 4, UnderlineSpan.class)).hasLength(1); - assertThat(spanned.getSpans(4, spanned.length(), ForegroundColorSpan.class)) - .hasLength(1); - }); - - onView(withId(com.android.internal.R.id.content_preview_text)) - .check((view, e) -> { - assertThat(view).isInstanceOf(TextView.class); - CharSequence text = ((TextView) view).getText(); - assertThat(text).isInstanceOf(Spanned.class); - Spanned spanned = (Spanned) text; - assertThat(spanned.getSpans(0, spanned.length(), Object.class)) - .hasLength(2); - assertThat(spanned.getSpans(0, 4, BackgroundColorSpan.class)).hasLength(1); - assertThat(spanned.getSpans(4, spanned.length(), StyleSpan.class)).hasLength(1); - }); - } - - @Test - public void emptyPreviewTitleAndThumbnail() throws InterruptedException { - Intent sendIntent = createSendTextIntentWithPreview(null, null); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - onView(withId(com.android.internal.R.id.content_preview_title)) - .check(matches(not(isDisplayed()))); - onView(withId(com.android.internal.R.id.content_preview_thumbnail)) - .check(matches(not(isDisplayed()))); - } - - @Test - public void visiblePreviewTitleWithoutThumbnail() throws InterruptedException { - String previewTitle = "My Content Preview Title"; - Intent sendIntent = createSendTextIntentWithPreview(previewTitle, null); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - onView(withId(com.android.internal.R.id.content_preview_title)) - .check(matches(isDisplayed())); - onView(withId(com.android.internal.R.id.content_preview_title)) - .check(matches(withText(previewTitle))); - onView(withId(com.android.internal.R.id.content_preview_thumbnail)) - .check(matches(not(isDisplayed()))); - } - - @Test - public void visiblePreviewTitleWithInvalidThumbnail() throws InterruptedException { - String previewTitle = "My Content Preview Title"; - Intent sendIntent = createSendTextIntentWithPreview(previewTitle, - Uri.parse("tel:(+49)12345789")); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - onView(withId(com.android.internal.R.id.content_preview_title)) - .check(matches(isDisplayed())); - onView(withId(com.android.internal.R.id.content_preview_thumbnail)) - .check(matches(not(isDisplayed()))); - } - - @Test - public void visiblePreviewTitleAndThumbnail() throws InterruptedException { - String previewTitle = "My Content Preview Title"; - Uri uri = Uri.parse( - "android.resource://com.android.frameworks.coretests/" - + com.android.intentresolver.tests.R.drawable.test320x240); - Intent sendIntent = createSendTextIntentWithPreview(previewTitle, uri); - ChooserActivityOverrideData.getInstance().imageLoader = - createImageLoader(uri, createBitmap()); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - onView(withId(com.android.internal.R.id.content_preview_title)) - .check(matches(isDisplayed())); - onView(withId(com.android.internal.R.id.content_preview_thumbnail)) - .check(matches(isDisplayed())); - } - - @Test @Ignore - public void twoOptionsAndUserSelectsOne() throws InterruptedException { - Intent sendIntent = createSendTextIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - assertThat(activity.getAdapter().getCount(), is(2)); - onView(withId(com.android.internal.R.id.profile_button)).check(doesNotExist()); - - ResolveInfo[] chosen = new ResolveInfo[1]; - ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { - chosen[0] = targetInfo.getResolveInfo(); - return true; - }; - - ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0); - onView(withText(toChoose.activityInfo.name)) - .perform(click()); - waitForIdle(); - assertThat(chosen[0], is(toChoose)); - } - - @Test @Ignore - public void fourOptionsStackedIntoOneTarget() throws InterruptedException { - Intent sendIntent = createSendTextIntent(); - - // create just enough targets to ensure the a-z list should be shown - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(1); - - // next create 4 targets in a single app that should be stacked into a single target - String packageName = "xxx.yyy"; - String appName = "aaa"; - ComponentName cn = new ComponentName(packageName, appName); - Intent intent = new Intent("fakeIntent"); - List<ResolvedComponentInfo> infosToStack = new ArrayList<>(); - for (int i = 0; i < 4; i++) { - ResolveInfo resolveInfo = ResolverDataProvider.createResolveInfo(i, - UserHandle.USER_CURRENT, PERSONAL_USER_HANDLE); - resolveInfo.activityInfo.applicationInfo.name = appName; - resolveInfo.activityInfo.applicationInfo.packageName = packageName; - resolveInfo.activityInfo.packageName = packageName; - resolveInfo.activityInfo.name = "ccc" + i; - infosToStack.add(new ResolvedComponentInfo(cn, intent, resolveInfo)); - } - resolvedComponentInfos.addAll(infosToStack); - - setupResolverControllers(resolvedComponentInfos); - - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - // expect 1 unique targets + 1 group + 4 ranked app targets - assertThat(activity.getAdapter().getCount(), is(6)); - - ResolveInfo[] chosen = new ResolveInfo[1]; - ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { - chosen[0] = targetInfo.getResolveInfo(); - return true; - }; - - onView(allOf(withText(appName), hasSibling(withText("")))).perform(click()); - waitForIdle(); - - // clicking will launch a dialog to choose the activity within the app - onView(withText(appName)).check(matches(isDisplayed())); - int i = 0; - for (ResolvedComponentInfo rci: infosToStack) { - onView(withText("ccc" + i)).check(matches(isDisplayed())); - ++i; - } - } - - @Test @Ignore - public void updateChooserCountsAndModelAfterUserSelection() throws InterruptedException { - Intent sendIntent = createSendTextIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - UsageStatsManager usm = activity.getUsageStatsManager(); - verify(ChooserActivityOverrideData.getInstance().resolverListController, times(1)) - .topK(any(List.class), anyInt()); - assertThat(activity.getIsSelected(), is(false)); - ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { - return true; - }; - ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0); - DisplayResolveInfo testDri = - activity.createTestDisplayResolveInfo( - sendIntent, toChoose, "testLabel", "testInfo", sendIntent); - onView(withText(toChoose.activityInfo.name)) - .perform(click()); - waitForIdle(); - verify(ChooserActivityOverrideData.getInstance().resolverListController, times(1)) - .updateChooserCounts(Mockito.anyString(), any(UserHandle.class), - Mockito.anyString()); - verify(ChooserActivityOverrideData.getInstance().resolverListController, times(1)) - .updateModel(testDri); - assertThat(activity.getIsSelected(), is(true)); - } - - @Ignore // b/148158199 - @Test - public void noResultsFromPackageManager() { - setupResolverControllers(null); - Intent sendIntent = createSendTextIntent(); - final ChooserActivity activity = - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - final IChooserWrapper wrapper = (IChooserWrapper) activity; - - waitForIdle(); - assertThat(activity.isFinishing(), is(false)); - - onView(withId(android.R.id.empty)).check(matches(isDisplayed())); - onView(withId(com.android.internal.R.id.profile_pager)).check(matches(not(isDisplayed()))); - InstrumentationRegistry.getInstrumentation().runOnMainSync( - () -> wrapper.getAdapter().handlePackagesChanged() - ); - // backward compatibility. looks like we finish when data is empty after package change - assertThat(activity.isFinishing(), is(true)); - } - - @Test - public void autoLaunchSingleResult() throws InterruptedException { - ResolveInfo[] chosen = new ResolveInfo[1]; - ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { - chosen[0] = targetInfo.getResolveInfo(); - return true; - }; - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(1); - setupResolverControllers(resolvedComponentInfos); - - Intent sendIntent = createSendTextIntent(); - final ChooserActivity activity = - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - assertThat(chosen[0], is(resolvedComponentInfos.get(0).getResolveInfoAt(0))); - assertThat(activity.isFinishing(), is(true)); - } - - @Test @Ignore - public void hasOtherProfileOneOption() { - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10); - List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - - ResolveInfo toChoose = personalResolvedComponentInfos.get(1).getResolveInfoAt(0); - Intent sendIntent = createSendTextIntent(); - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - // The other entry is filtered to the other profile slot - assertThat(activity.getAdapter().getCount(), is(1)); - - ResolveInfo[] chosen = new ResolveInfo[1]; - ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { - chosen[0] = targetInfo.getResolveInfo(); - return true; - }; - - // Make a stable copy of the components as the original list may be modified - List<ResolvedComponentInfo> stableCopy = - createResolvedComponentsForTestWithOtherProfile(2, /* userId= */ 10); - waitForIdle(); - - onView(first(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))) - .perform(click()); - waitForIdle(); - assertThat(chosen[0], is(toChoose)); - } - - @Test @Ignore - public void hasOtherProfileTwoOptionsAndUserSelectsOne() throws Exception { - Intent sendIntent = createSendTextIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3); - ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0); - - setupResolverControllers(resolvedComponentInfos); - when(ChooserActivityOverrideData.getInstance().resolverListController.getLastChosen()) - .thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0)); - - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - // The other entry is filtered to the other profile slot - assertThat(activity.getAdapter().getCount(), is(2)); - - ResolveInfo[] chosen = new ResolveInfo[1]; - ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { - chosen[0] = targetInfo.getResolveInfo(); - return true; - }; - - // Make a stable copy of the components as the original list may be modified - List<ResolvedComponentInfo> stableCopy = - createResolvedComponentsForTestWithOtherProfile(3); - onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)) - .perform(click()); - waitForIdle(); - assertThat(chosen[0], is(toChoose)); - } - - @Test @Ignore - public void hasLastChosenActivityAndOtherProfile() throws Exception { - Intent sendIntent = createSendTextIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3); - ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0); - - setupResolverControllers(resolvedComponentInfos); - - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - // The other entry is filtered to the last used slot - assertThat(activity.getAdapter().getCount(), is(2)); - - ResolveInfo[] chosen = new ResolveInfo[1]; - ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { - chosen[0] = targetInfo.getResolveInfo(); - return true; - }; - - // Make a stable copy of the components as the original list may be modified - List<ResolvedComponentInfo> stableCopy = - createResolvedComponentsForTestWithOtherProfile(3); - onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)) - .perform(click()); - waitForIdle(); - assertThat(chosen[0], is(toChoose)); - } - - @Test - @Ignore("b/285309527") - public void testFilePlusTextSharing_ExcludeText() { - Uri uri = createTestContentProviderUri(null, "image/png"); - Intent sendIntent = createSendImageIntent(uri); - ChooserActivityOverrideData.getInstance().imageLoader = - createImageLoader(uri, createBitmap()); - sendIntent.putExtra(Intent.EXTRA_TEXT, "https://google.com/search?q=google"); - - List<ResolvedComponentInfo> resolvedComponentInfos = Arrays.asList( - ResolverDataProvider.createResolvedComponentInfo( - new ComponentName("org.imageviewer", "ImageTarget"), - sendIntent, PERSONAL_USER_HANDLE), - ResolverDataProvider.createResolvedComponentInfo( - new ComponentName("org.textviewer", "UriTarget"), - new Intent("VIEW_TEXT"), PERSONAL_USER_HANDLE) - ); - - setupResolverControllers(resolvedComponentInfos); - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - onView(withId(R.id.include_text_action)) - .check(matches(isDisplayed())) - .perform(click()); - waitForIdle(); - - onView(withId(R.id.content_preview_text)).check(matches(withText("File only"))); - - AtomicReference<Intent> launchedIntentRef = new AtomicReference<>(); - ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { - launchedIntentRef.set(targetInfo.getTargetIntent()); - return true; - }; - - onView(withText(resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.name)) - .perform(click()); - waitForIdle(); - assertThat(launchedIntentRef.get().hasExtra(Intent.EXTRA_TEXT)).isFalse(); - } - - @Test - @Ignore("b/285309527") - public void testFilePlusTextSharing_RemoveAndAddBackText() { - Uri uri = createTestContentProviderUri("application/pdf", "image/png"); - Intent sendIntent = createSendImageIntent(uri); - ChooserActivityOverrideData.getInstance().imageLoader = - createImageLoader(uri, createBitmap()); - final String text = "https://google.com/search?q=google"; - sendIntent.putExtra(Intent.EXTRA_TEXT, text); - - List<ResolvedComponentInfo> resolvedComponentInfos = Arrays.asList( - ResolverDataProvider.createResolvedComponentInfo( - new ComponentName("org.imageviewer", "ImageTarget"), - sendIntent, PERSONAL_USER_HANDLE), - ResolverDataProvider.createResolvedComponentInfo( - new ComponentName("org.textviewer", "UriTarget"), - new Intent("VIEW_TEXT"), PERSONAL_USER_HANDLE) - ); - - setupResolverControllers(resolvedComponentInfos); - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - onView(withId(R.id.include_text_action)) - .check(matches(isDisplayed())) - .perform(click()); - waitForIdle(); - onView(withId(R.id.content_preview_text)).check(matches(withText("File only"))); - - onView(withId(R.id.include_text_action)) - .perform(click()); - waitForIdle(); - - onView(withId(R.id.content_preview_text)).check(matches(withText(text))); - - AtomicReference<Intent> launchedIntentRef = new AtomicReference<>(); - ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { - launchedIntentRef.set(targetInfo.getTargetIntent()); - return true; - }; - - onView(withText(resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.name)) - .perform(click()); - waitForIdle(); - assertThat(launchedIntentRef.get().getStringExtra(Intent.EXTRA_TEXT)).isEqualTo(text); - } - - @Test - @Ignore("b/285309527") - public void testFilePlusTextSharing_TextExclusionDoesNotAffectAlternativeIntent() { - Uri uri = createTestContentProviderUri("image/png", null); - Intent sendIntent = createSendImageIntent(uri); - ChooserActivityOverrideData.getInstance().imageLoader = - createImageLoader(uri, createBitmap()); - sendIntent.putExtra(Intent.EXTRA_TEXT, "https://google.com/search?q=google"); - - Intent alternativeIntent = createSendTextIntent(); - final String text = "alternative intent"; - alternativeIntent.putExtra(Intent.EXTRA_TEXT, text); - - List<ResolvedComponentInfo> resolvedComponentInfos = Arrays.asList( - ResolverDataProvider.createResolvedComponentInfo( - new ComponentName("org.imageviewer", "ImageTarget"), - sendIntent, PERSONAL_USER_HANDLE), - ResolverDataProvider.createResolvedComponentInfo( - new ComponentName("org.textviewer", "UriTarget"), - alternativeIntent, PERSONAL_USER_HANDLE) - ); - - setupResolverControllers(resolvedComponentInfos); - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - onView(withId(R.id.include_text_action)) - .check(matches(isDisplayed())) - .perform(click()); - waitForIdle(); - - AtomicReference<Intent> launchedIntentRef = new AtomicReference<>(); - ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { - launchedIntentRef.set(targetInfo.getTargetIntent()); - return true; - }; - - onView(withText(resolvedComponentInfos.get(1).getResolveInfoAt(0).activityInfo.name)) - .perform(click()); - waitForIdle(); - assertThat(launchedIntentRef.get().getStringExtra(Intent.EXTRA_TEXT)).isEqualTo(text); - } - - @Test - @Ignore("b/285309527") - public void testImagePlusTextSharing_failedThumbnailAndExcludedText_textChanges() { - Uri uri = createTestContentProviderUri("image/png", null); - Intent sendIntent = createSendImageIntent(uri); - ChooserActivityOverrideData.getInstance().imageLoader = - new TestPreviewImageLoader(Collections.emptyMap()); - sendIntent.putExtra(Intent.EXTRA_TEXT, "https://google.com/search?q=google"); - - List<ResolvedComponentInfo> resolvedComponentInfos = Arrays.asList( - ResolverDataProvider.createResolvedComponentInfo( - new ComponentName("org.imageviewer", "ImageTarget"), - sendIntent, PERSONAL_USER_HANDLE), - ResolverDataProvider.createResolvedComponentInfo( - new ComponentName("org.textviewer", "UriTarget"), - new Intent("VIEW_TEXT"), PERSONAL_USER_HANDLE) - ); - - setupResolverControllers(resolvedComponentInfos); - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - onView(withId(R.id.include_text_action)) - .check(matches(isDisplayed())) - .perform(click()); - waitForIdle(); - - onView(withId(R.id.image_view)) - .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE))); - onView(withId(R.id.content_preview_text)) - .check(matches(allOf(isDisplayed(), withText("Image only")))); - } - - @Test - public void copyTextToClipboard() { - Intent sendIntent = createSendTextIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - - final ChooserActivity activity = - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - onView(withId(R.id.copy)).check(matches(isDisplayed())); - onView(withId(R.id.copy)).perform(click()); - ClipboardManager clipboard = (ClipboardManager) activity.getSystemService( - Context.CLIPBOARD_SERVICE); - ClipData clipData = clipboard.getPrimaryClip(); - assertThat(clipData).isNotNull(); - assertThat(clipData.getItemAt(0).getText()).isEqualTo("testing intent sending"); - - ClipDescription clipDescription = clipData.getDescription(); - assertThat("text/plain", is(clipDescription.getMimeType(0))); - - assertEquals(mActivityRule.getActivityResult().getResultCode(), RESULT_OK); - } - - @Test - public void copyTextToClipboardLogging() { - Intent sendIntent = createSendTextIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - - ChooserWrapperActivity activity = - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - onView(withId(R.id.copy)).check(matches(isDisplayed())); - onView(withId(R.id.copy)).perform(click()); - FakeEventLog eventLog = getEventLog(activity); - assertThat(eventLog.getActionSelected()) - .isEqualTo(new FakeEventLog.ActionSelected( - /* targetType = */ EventLog.SELECTION_TYPE_COPY)); - } - - @Test - @Ignore - public void testNearbyShareLogging() throws Exception { - Intent sendIntent = createSendTextIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - onView(withId(com.android.internal.R.id.chooser_nearby_button)) - .check(matches(isDisplayed())); - onView(withId(com.android.internal.R.id.chooser_nearby_button)).perform(click()); - - // TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events. - } - - @Test @Ignore - public void testEditImageLogs() { - - Uri uri = createTestContentProviderUri("image/png", null); - Intent sendIntent = createSendImageIntent(uri); - ChooserActivityOverrideData.getInstance().imageLoader = - createImageLoader(uri, createBitmap()); - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - onView(withId(com.android.internal.R.id.chooser_edit_button)).check(matches(isDisplayed())); - onView(withId(com.android.internal.R.id.chooser_edit_button)).perform(click()); - - // TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events. - } - - - @Test - public void oneVisibleImagePreview() { - Uri uri = createTestContentProviderUri("image/png", null); - - ArrayList<Uri> uris = new ArrayList<>(); - uris.add(uri); - - Intent sendIntent = createSendUriIntentWithPreview(uris); - ChooserActivityOverrideData.getInstance().imageLoader = - createImageLoader(uri, createWideBitmap()); - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - onView(withId(R.id.scrollable_image_preview)) - .check((view, exception) -> { - if (exception != null) { - throw exception; - } - RecyclerView recyclerView = (RecyclerView) view; - assertThat(recyclerView.getAdapter().getItemCount(), is(1)); - assertThat(recyclerView.getChildCount(), is(1)); - View imageView = recyclerView.getChildAt(0); - Rect rect = new Rect(); - boolean isPartiallyVisible = imageView.getGlobalVisibleRect(rect); - assertThat( - "image preview view is not fully visible", - isPartiallyVisible - && rect.width() == imageView.getWidth() - && rect.height() == imageView.getHeight()); - }); - } - - @Test - public void allThumbnailsFailedToLoad_hidePreview() { - Uri uri = createTestContentProviderUri("image/jpg", null); - - ArrayList<Uri> uris = new ArrayList<>(); - uris.add(uri); - uris.add(uri); - - Intent sendIntent = createSendUriIntentWithPreview(uris); - ChooserActivityOverrideData.getInstance().imageLoader = - new TestPreviewImageLoader(Collections.emptyMap()); - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - onView(withId(R.id.scrollable_image_preview)) - .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE))); - } - - @Test(timeout = 4_000) - public void testSlowUriMetadata_fallbackToFilePreview() { - Uri uri = createTestContentProviderUri( - "application/pdf", "image/png", /*streamTypeTimeout=*/8_000); - ArrayList<Uri> uris = new ArrayList<>(1); - uris.add(uri); - Intent sendIntent = createSendUriIntentWithPreview(uris); - ChooserActivityOverrideData.getInstance().imageLoader = - createImageLoader(uri, createBitmap()); - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - // The preview type resolution is expected to timeout and default to file preview, otherwise - // the test should timeout. - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed())); - onView(withId(R.id.content_preview_filename)).check(matches(withText("image.png"))); - onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed())); - } - - @Test(timeout = 4_000) - public void testSendManyFilesWithSmallMetadataDelayAndOneImage_fallbackToFilePreviewUi() { - Uri fileUri = createTestContentProviderUri( - "application/pdf", "application/pdf", /*streamTypeTimeout=*/300); - Uri imageUri = createTestContentProviderUri("application/pdf", "image/png"); - ArrayList<Uri> uris = new ArrayList<>(50); - for (int i = 0; i < 49; i++) { - uris.add(fileUri); - } - uris.add(imageUri); - Intent sendIntent = createSendUriIntentWithPreview(uris); - ChooserActivityOverrideData.getInstance().imageLoader = - createImageLoader(imageUri, createBitmap()); - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - setupResolverControllers(resolvedComponentInfos); - // The preview type resolution is expected to timeout and default to file preview, otherwise - // the test should timeout. - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - - waitForIdle(); - - onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed())); - onView(withId(R.id.content_preview_filename)).check(matches(withText("image.png"))); - onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed())); - } - - @Test - public void testManyVisibleImagePreview_ScrollableImagePreview() { - Uri uri = createTestContentProviderUri("image/png", null); - - ArrayList<Uri> uris = new ArrayList<>(); - uris.add(uri); - uris.add(uri); - uris.add(uri); - uris.add(uri); - uris.add(uri); - uris.add(uri); - uris.add(uri); - uris.add(uri); - uris.add(uri); - uris.add(uri); - - Intent sendIntent = createSendUriIntentWithPreview(uris); - ChooserActivityOverrideData.getInstance().imageLoader = - createImageLoader(uri, createBitmap()); - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - onView(withId(R.id.scrollable_image_preview)) - .perform(RecyclerViewActions.scrollToLastPosition()) - .check((view, exception) -> { - if (exception != null) { - throw exception; - } - RecyclerView recyclerView = (RecyclerView) view; - assertThat(recyclerView.getAdapter().getItemCount(), is(uris.size())); - }); - } - - @Test(timeout = 4_000) - public void testPartiallyLoadedMetadata_previewIsShownForTheLoadedPart() { - Uri imgOneUri = createTestContentProviderUri("image/png", null); - Uri imgTwoUri = createTestContentProviderUri("image/png", null) - .buildUpon() - .path("image-2.png") - .build(); - Uri docUri = createTestContentProviderUri("application/pdf", "image/png", 8_000); - ArrayList<Uri> uris = new ArrayList<>(2); - // two large previews to fill the screen and be presented right away and one - // document that would be delayed by the URI metadata reading - uris.add(imgOneUri); - uris.add(imgTwoUri); - uris.add(docUri); - - Intent sendIntent = createSendUriIntentWithPreview(uris); - Map<Uri, Bitmap> bitmaps = new HashMap<>(); - bitmaps.put(imgOneUri, createWideBitmap(Color.RED)); - bitmaps.put(imgTwoUri, createWideBitmap(Color.GREEN)); - bitmaps.put(docUri, createWideBitmap(Color.BLUE)); - ChooserActivityOverrideData.getInstance().imageLoader = - new TestPreviewImageLoader(bitmaps); - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - setupResolverControllers(resolvedComponentInfos); - - // the preview type is expected to be resolved quickly based on the first provided URI - // metadata. If, instead, it is dependent on the third URI metadata, the test should either - // timeout or (more probably due to inner timeout) default to file preview type; anyway the - // test will fail. - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - onView(withId(R.id.scrollable_image_preview)) - .check((view, exception) -> { - if (exception != null) { - throw exception; - } - RecyclerView recyclerView = (RecyclerView) view; - assertThat(recyclerView.getChildCount()).isAtLeast(1); - // the first view is a preview - View imageView = recyclerView.getChildAt(0).findViewById(R.id.image); - assertThat(imageView).isNotNull(); - }) - .perform(RecyclerViewActions.scrollToLastPosition()) - .check((view, exception) -> { - if (exception != null) { - throw exception; - } - RecyclerView recyclerView = (RecyclerView) view; - assertThat(recyclerView.getChildCount()).isAtLeast(1); - // check that the last view is a loading indicator - View loadingIndicator = - recyclerView.getChildAt(recyclerView.getChildCount() - 1); - assertThat(loadingIndicator).isNotNull(); - }); - waitForIdle(); - } - - @Test - public void testImageAndTextPreview() { - final Uri uri = createTestContentProviderUri("image/png", null); - final String sharedText = "text-" + System.currentTimeMillis(); - - ArrayList<Uri> uris = new ArrayList<>(); - uris.add(uri); - - Intent sendIntent = createSendUriIntentWithPreview(uris); - sendIntent.putExtra(Intent.EXTRA_TEXT, sharedText); - ChooserActivityOverrideData.getInstance().imageLoader = - createImageLoader(uri, createBitmap()); - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - onView(withText(sharedText)) - .check(matches(isDisplayed())); - } - - @Test - public void test_shareImageWithRichText_RichTextIsDisplayed() { - final Uri uri = createTestContentProviderUri("image/png", null); - final CharSequence sharedText = new SpannableStringBuilder() - .append( - "text-", - new StyleSpan(Typeface.BOLD_ITALIC), - Spannable.SPAN_INCLUSIVE_EXCLUSIVE) - .append( - Long.toString(System.currentTimeMillis()), - new ForegroundColorSpan(Color.RED), - Spanned.SPAN_INCLUSIVE_EXCLUSIVE); - - ArrayList<Uri> uris = new ArrayList<>(); - uris.add(uri); - - Intent sendIntent = createSendUriIntentWithPreview(uris); - sendIntent.putExtra(Intent.EXTRA_TEXT, sharedText); - ChooserActivityOverrideData.getInstance().imageLoader = - createImageLoader(uri, createBitmap()); - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - onView(withText(sharedText.toString())) - .check(matches(isDisplayed())) - .check((view, e) -> { - if (e != null) { - throw e; - } - assertThat(view).isInstanceOf(TextView.class); - CharSequence text = ((TextView) view).getText(); - assertThat(text).isInstanceOf(Spanned.class); - Spanned spanned = (Spanned) text; - Object[] spans = spanned.getSpans(0, text.length(), Object.class); - assertThat(spans).hasLength(2); - assertThat(spanned.getSpans(0, 5, StyleSpan.class)).hasLength(1); - assertThat(spanned.getSpans(5, text.length(), ForegroundColorSpan.class)) - .hasLength(1); - }); - } - - @Test - public void testTextPreviewWhenTextIsSharedWithMultipleImages() { - final Uri uri = createTestContentProviderUri("image/png", null); - final String sharedText = "text-" + System.currentTimeMillis(); - - ArrayList<Uri> uris = new ArrayList<>(); - uris.add(uri); - uris.add(uri); - - Intent sendIntent = createSendUriIntentWithPreview(uris); - sendIntent.putExtra(Intent.EXTRA_TEXT, sharedText); - ChooserActivityOverrideData.getInstance().imageLoader = - createImageLoader(uri, createBitmap()); - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - when( - ChooserActivityOverrideData - .getInstance() - .resolverListController - .getResolversForIntentAsUser( - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - Mockito.any(UserHandle.class))) - .thenReturn(resolvedComponentInfos); - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - onView(withText(sharedText)).check(matches(isDisplayed())); - } - - @Test - public void testOnCreateLogging() { - Intent sendIntent = createSendTextIntent(); - sendIntent.setType(TEST_MIME_TYPE); - - ChooserWrapperActivity activity = - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "logger test")); - waitForIdle(); - - FakeEventLog eventLog = getEventLog(activity); - FakeEventLog.ChooserActivityShown event = eventLog.getChooserActivityShown(); - assertThat(event).isNotNull(); - assertThat(event.isWorkProfile()).isFalse(); - assertThat(event.getTargetMimeType()).isEqualTo(TEST_MIME_TYPE); - } - - @Test - public void testOnCreateLoggingFromWorkProfile() { - Intent sendIntent = createSendTextIntent(); - sendIntent.setType(TEST_MIME_TYPE); - ChooserActivityOverrideData.getInstance().alternateProfileSetting = - MetricsEvent.MANAGED_PROFILE; - - ChooserWrapperActivity activity = - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "logger test")); - waitForIdle(); - - FakeEventLog eventLog = getEventLog(activity); - FakeEventLog.ChooserActivityShown event = eventLog.getChooserActivityShown(); - assertThat(event).isNotNull(); - assertThat(event.isWorkProfile()).isTrue(); - assertThat(event.getTargetMimeType()).isEqualTo(TEST_MIME_TYPE); - } - - @Test - public void testEmptyPreviewLogging() { - Intent sendIntent = createSendTextIntentWithPreview(null, null); - - ChooserWrapperActivity activity = - mActivityRule.launchActivity(Intent.createChooser(sendIntent, - "empty preview logger test")); - waitForIdle(); - - FakeEventLog eventLog = getEventLog(activity); - FakeEventLog.ChooserActivityShown event = eventLog.getChooserActivityShown(); - assertThat(event).isNotNull(); - assertThat(event.isWorkProfile()).isFalse(); - assertThat(event.getTargetMimeType()).isNull(); - } - - @Test - public void testTitlePreviewLogging() { - Intent sendIntent = createSendTextIntentWithPreview("TestTitle", null); - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - - ChooserWrapperActivity activity = - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - FakeEventLog eventLog = getEventLog(activity); - assertThat(eventLog.getActionShareWithPreview()) - .isEqualTo(new FakeEventLog.ActionShareWithPreview( - /* previewType = */ CONTENT_PREVIEW_TEXT)); - } - - @Test - public void testImagePreviewLogging() { - Uri uri = createTestContentProviderUri("image/png", null); - - ArrayList<Uri> uris = new ArrayList<>(); - uris.add(uri); - - Intent sendIntent = createSendUriIntentWithPreview(uris); - ChooserActivityOverrideData.getInstance().imageLoader = - createImageLoader(uri, createBitmap()); - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - - ChooserWrapperActivity activity = - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - FakeEventLog eventLog = getEventLog(activity); - assertThat(eventLog.getActionShareWithPreview()) - .isEqualTo(new FakeEventLog.ActionShareWithPreview( - /* previewType = */ CONTENT_PREVIEW_IMAGE)); - } - - @Test - public void oneVisibleFilePreview() throws InterruptedException { - Uri uri = Uri.parse("content://com.android.frameworks.coretests/app.pdf"); - - ArrayList<Uri> uris = new ArrayList<>(); - uris.add(uri); - - Intent sendIntent = createSendUriIntentWithPreview(uris); - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed())); - onView(withId(R.id.content_preview_filename)).check(matches(withText("app.pdf"))); - onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed())); - } - - - @Test - public void moreThanOneVisibleFilePreview() throws InterruptedException { - Uri uri = Uri.parse("content://com.android.frameworks.coretests/app.pdf"); - - ArrayList<Uri> uris = new ArrayList<>(); - uris.add(uri); - uris.add(uri); - uris.add(uri); - - Intent sendIntent = createSendUriIntentWithPreview(uris); - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed())); - onView(withId(R.id.content_preview_filename)).check(matches(withText("app.pdf"))); - onView(withId(R.id.content_preview_more_files)).check(matches(isDisplayed())); - onView(withId(R.id.content_preview_more_files)).check(matches(withText("+ 2 more files"))); - onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed())); - } - - @Test - public void contentProviderThrowSecurityException() throws InterruptedException { - Uri uri = Uri.parse("content://com.android.frameworks.coretests/app.pdf"); - - ArrayList<Uri> uris = new ArrayList<>(); - uris.add(uri); - - Intent sendIntent = createSendUriIntentWithPreview(uris); - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - setupResolverControllers(resolvedComponentInfos); - - ChooserActivityOverrideData.getInstance().resolverForceException = true; - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed())); - onView(withId(R.id.content_preview_filename)).check(matches(withText("app.pdf"))); - onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed())); - } - - @Test - public void contentProviderReturnsNoColumns() throws InterruptedException { - Uri uri = Uri.parse("content://com.android.frameworks.coretests/app.pdf"); - - ArrayList<Uri> uris = new ArrayList<>(); - uris.add(uri); - uris.add(uri); - - Intent sendIntent = createSendUriIntentWithPreview(uris); - - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - setupResolverControllers(resolvedComponentInfos); - - Cursor cursor = mock(Cursor.class); - when(cursor.getCount()).thenReturn(1); - Mockito.doNothing().when(cursor).close(); - when(cursor.moveToFirst()).thenReturn(true); - when(cursor.getColumnIndex(Mockito.anyString())).thenReturn(-1); - - ChooserActivityOverrideData.getInstance().resolverCursor = cursor; - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed())); - onView(withId(R.id.content_preview_filename)).check(matches(withText("app.pdf"))); - onView(withId(R.id.content_preview_more_files)).check(matches(isDisplayed())); - onView(withId(R.id.content_preview_more_files)).check(matches(withText("+ 1 more file"))); - onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed())); - } - - @Test - public void testGetBaseScore() { - final float testBaseScore = 0.89f; - - Intent sendIntent = createSendTextIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - - when( - ChooserActivityOverrideData - .getInstance() - .resolverListController - .getScore(Mockito.isA(DisplayResolveInfo.class))) - .thenReturn(testBaseScore); - - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - final DisplayResolveInfo testDri = - activity.createTestDisplayResolveInfo( - sendIntent, - ResolverDataProvider.createResolveInfo(3, 0, PERSONAL_USER_HANDLE), - "testLabel", - "testInfo", - sendIntent); - final ChooserListAdapter adapter = activity.getAdapter(); - - assertThat(adapter.getBaseScore(null, 0), is(CALLER_TARGET_SCORE_BOOST)); - assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_DEFAULT), is(testBaseScore)); - assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_CHOOSER_TARGET), is(testBaseScore)); - assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE), - is(testBaseScore * SHORTCUT_TARGET_SCORE_BOOST)); - assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER), - is(testBaseScore * SHORTCUT_TARGET_SCORE_BOOST)); - } - - // This test is too long and too slow and should not be taken as an example for future tests. - @Test - public void testDirectTargetSelectionLogging() { - Intent sendIntent = createSendTextIntent(); - // We need app targets for direct targets to get displayed - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - setupResolverControllers(resolvedComponentInfos); - - // create test shortcut loader factory, remember loaders and their callbacks - SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders = - createShortcutLoaderFactory(); - - // Start activity - ChooserWrapperActivity activity = - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - // verify that ShortcutLoader was queried - ArgumentCaptor<DisplayResolveInfo[]> appTargets = - ArgumentCaptor.forClass(DisplayResolveInfo[].class); - verify(shortcutLoaders.get(0).first, times(1)).updateAppTargets(appTargets.capture()); - - // send shortcuts - assertThat( - "Wrong number of app targets", - appTargets.getValue().length, - is(resolvedComponentInfos.size())); - List<ChooserTarget> serviceTargets = createDirectShareTargets(1, ""); - ShortcutLoader.Result result = new ShortcutLoader.Result( - true, - appTargets.getValue(), - new ShortcutLoader.ShortcutResultInfo[] { - new ShortcutLoader.ShortcutResultInfo( - appTargets.getValue()[0], - serviceTargets - ) - }, - new HashMap<>(), - new HashMap<>() - ); - activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result)); - waitForIdle(); - - final ChooserListAdapter activeAdapter = activity.getAdapter(); - assertThat( - "Chooser should have 3 targets (2 apps, 1 direct)", - activeAdapter.getCount(), - is(3)); - assertThat( - "Chooser should have exactly one selectable direct target", - activeAdapter.getSelectableServiceTargetCount(), - is(1)); - assertThat( - "The resolver info must match the resolver info used to create the target", - activeAdapter.getItem(0).getResolveInfo(), - is(resolvedComponentInfos.get(0).getResolveInfoAt(0))); - - // Click on the direct target - String name = serviceTargets.get(0).getTitle().toString(); - onView(withText(name)) - .perform(click()); - waitForIdle(); - - FakeEventLog eventLog = getEventLog(activity); - assertThat(eventLog.getShareTargetSelected()).hasSize(1); - FakeEventLog.ShareTargetSelected call = eventLog.getShareTargetSelected().get(0); - assertThat(call.getTargetType()).isEqualTo(EventLog.SELECTION_TYPE_SERVICE); - assertThat(call.getDirectTargetAlsoRanked()).isEqualTo(-1); - var hashResult = call.getDirectTargetHashed(); - var hash = hashResult == null ? "" : hashResult.hashedString; - assertWithMessage("Hash is not predictable but must be obfuscated") - .that(hash).isNotEqualTo(name); - } - - // This test is too long and too slow and should not be taken as an example for future tests. - @Test - public void testDirectTargetLoggingWithRankedAppTarget() { - Intent sendIntent = createSendTextIntent(); - // We need app targets for direct targets to get displayed - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - setupResolverControllers(resolvedComponentInfos); - - // create test shortcut loader factory, remember loaders and their callbacks - SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders = - createShortcutLoaderFactory(); - - // Start activity - ChooserWrapperActivity activity = - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - // verify that ShortcutLoader was queried - ArgumentCaptor<DisplayResolveInfo[]> appTargets = - ArgumentCaptor.forClass(DisplayResolveInfo[].class); - verify(shortcutLoaders.get(0).first, times(1)).updateAppTargets(appTargets.capture()); - - // send shortcuts - assertThat( - "Wrong number of app targets", - appTargets.getValue().length, - is(resolvedComponentInfos.size())); - List<ChooserTarget> serviceTargets = createDirectShareTargets( - 1, - resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName); - ShortcutLoader.Result result = new ShortcutLoader.Result( - true, - appTargets.getValue(), - new ShortcutLoader.ShortcutResultInfo[] { - new ShortcutLoader.ShortcutResultInfo( - appTargets.getValue()[0], - serviceTargets - ) - }, - new HashMap<>(), - new HashMap<>() - ); - activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result)); - waitForIdle(); - - final ChooserListAdapter activeAdapter = activity.getAdapter(); - assertThat( - "Chooser should have 3 targets (2 apps, 1 direct)", - activeAdapter.getCount(), - is(3)); - assertThat( - "Chooser should have exactly one selectable direct target", - activeAdapter.getSelectableServiceTargetCount(), - is(1)); - assertThat( - "The resolver info must match the resolver info used to create the target", - activeAdapter.getItem(0).getResolveInfo(), - is(resolvedComponentInfos.get(0).getResolveInfoAt(0))); - - // Click on the direct target - String name = serviceTargets.get(0).getTitle().toString(); - onView(withText(name)) - .perform(click()); - waitForIdle(); - - FakeEventLog eventLog = getEventLog(activity); - assertThat(eventLog.getShareTargetSelected()).hasSize(1); - FakeEventLog.ShareTargetSelected call = eventLog.getShareTargetSelected().get(0); - - assertThat(call.getTargetType()).isEqualTo(EventLog.SELECTION_TYPE_SERVICE); - assertThat(call.getDirectTargetAlsoRanked()).isEqualTo(0); - } - - @Test - public void testShortcutTargetWithApplyAppLimits() { - // Set up resources - Resources resources = Mockito.spy( - InstrumentationRegistry.getInstrumentation().getContext().getResources()); - ChooserActivityOverrideData.getInstance().resources = resources; - doReturn(1).when(resources).getInteger(R.integer.config_maxShortcutTargetsPerApp); - Intent sendIntent = createSendTextIntent(); - // We need app targets for direct targets to get displayed - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - setupResolverControllers(resolvedComponentInfos); - - // create test shortcut loader factory, remember loaders and their callbacks - SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders = - createShortcutLoaderFactory(); - - // Start activity - final IChooserWrapper activity = (IChooserWrapper) mActivityRule - .launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - // verify that ShortcutLoader was queried - ArgumentCaptor<DisplayResolveInfo[]> appTargets = - ArgumentCaptor.forClass(DisplayResolveInfo[].class); - verify(shortcutLoaders.get(0).first, times(1)).updateAppTargets(appTargets.capture()); - - // send shortcuts - assertThat( - "Wrong number of app targets", - appTargets.getValue().length, - is(resolvedComponentInfos.size())); - List<ChooserTarget> serviceTargets = createDirectShareTargets( - 2, - resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName); - ShortcutLoader.Result result = new ShortcutLoader.Result( - true, - appTargets.getValue(), - new ShortcutLoader.ShortcutResultInfo[] { - new ShortcutLoader.ShortcutResultInfo( - appTargets.getValue()[0], - serviceTargets - ) - }, - new HashMap<>(), - new HashMap<>() - ); - activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result)); - waitForIdle(); - - final ChooserListAdapter activeAdapter = activity.getAdapter(); - assertThat( - "Chooser should have 3 targets (2 apps, 1 direct)", - activeAdapter.getCount(), - is(3)); - assertThat( - "Chooser should have exactly one selectable direct target", - activeAdapter.getSelectableServiceTargetCount(), - is(1)); - assertThat( - "The resolver info must match the resolver info used to create the target", - activeAdapter.getItem(0).getResolveInfo(), - is(resolvedComponentInfos.get(0).getResolveInfoAt(0))); - assertThat( - "The display label must match", - activeAdapter.getItem(0).getDisplayLabel(), - is("testTitle0")); - } - - @Test - public void testShortcutTargetWithoutApplyAppLimits() { - setDeviceConfigProperty( - SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI, - Boolean.toString(false)); - // Set up resources - Resources resources = Mockito.spy( - InstrumentationRegistry.getInstrumentation().getContext().getResources()); - ChooserActivityOverrideData.getInstance().resources = resources; - doReturn(1).when(resources).getInteger(R.integer.config_maxShortcutTargetsPerApp); - Intent sendIntent = createSendTextIntent(); - // We need app targets for direct targets to get displayed - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - setupResolverControllers(resolvedComponentInfos); - - // create test shortcut loader factory, remember loaders and their callbacks - SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders = - createShortcutLoaderFactory(); - - // Start activity - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - // verify that ShortcutLoader was queried - ArgumentCaptor<DisplayResolveInfo[]> appTargets = - ArgumentCaptor.forClass(DisplayResolveInfo[].class); - verify(shortcutLoaders.get(0).first, times(1)).updateAppTargets(appTargets.capture()); - - // send shortcuts - assertThat( - "Wrong number of app targets", - appTargets.getValue().length, - is(resolvedComponentInfos.size())); - List<ChooserTarget> serviceTargets = createDirectShareTargets( - 2, - resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName); - ShortcutLoader.Result result = new ShortcutLoader.Result( - true, - appTargets.getValue(), - new ShortcutLoader.ShortcutResultInfo[] { - new ShortcutLoader.ShortcutResultInfo( - appTargets.getValue()[0], - serviceTargets - ) - }, - new HashMap<>(), - new HashMap<>() - ); - activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result)); - waitForIdle(); - - final ChooserListAdapter activeAdapter = activity.getAdapter(); - assertThat( - "Chooser should have 4 targets (2 apps, 2 direct)", - activeAdapter.getCount(), - is(4)); - assertThat( - "Chooser should have exactly two selectable direct target", - activeAdapter.getSelectableServiceTargetCount(), - is(2)); - assertThat( - "The resolver info must match the resolver info used to create the target", - activeAdapter.getItem(0).getResolveInfo(), - is(resolvedComponentInfos.get(0).getResolveInfoAt(0))); - assertThat( - "The display label must match", - activeAdapter.getItem(0).getDisplayLabel(), - is("testTitle0")); - assertThat( - "The display label must match", - activeAdapter.getItem(1).getDisplayLabel(), - is("testTitle1")); - } - - @Test - public void testLaunchWithCallerProvidedTarget() { - setDeviceConfigProperty( - SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI, - Boolean.toString(false)); - // Set up resources - Resources resources = Mockito.spy( - InstrumentationRegistry.getInstrumentation().getContext().getResources()); - ChooserActivityOverrideData.getInstance().resources = resources; - doReturn(1).when(resources).getInteger(R.integer.config_maxShortcutTargetsPerApp); - - // We need app targets for direct targets to get displayed - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - setupResolverControllers(resolvedComponentInfos, resolvedComponentInfos); - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - - // set caller-provided target - Intent chooserIntent = Intent.createChooser(createSendTextIntent(), null); - String callerTargetLabel = "Caller Target"; - ChooserTarget[] targets = new ChooserTarget[] { - new ChooserTarget( - callerTargetLabel, - Icon.createWithBitmap(createBitmap()), - 0.1f, - resolvedComponentInfos.get(0).name, - new Bundle()) - }; - chooserIntent.putExtra(Intent.EXTRA_CHOOSER_TARGETS, targets); - - // create test shortcut loader factory, remember loaders and their callbacks - SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders = - createShortcutLoaderFactory(); - - // Start activity - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(chooserIntent); - waitForIdle(); - - // verify that ShortcutLoader was queried - ArgumentCaptor<DisplayResolveInfo[]> appTargets = - ArgumentCaptor.forClass(DisplayResolveInfo[].class); - verify(shortcutLoaders.get(0).first, times(1)).updateAppTargets(appTargets.capture()); - - // send shortcuts - assertThat( - "Wrong number of app targets", - appTargets.getValue().length, - is(resolvedComponentInfos.size())); - ShortcutLoader.Result result = new ShortcutLoader.Result( - true, - appTargets.getValue(), - new ShortcutLoader.ShortcutResultInfo[0], - new HashMap<>(), - new HashMap<>()); - activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result)); - waitForIdle(); - - final ChooserListAdapter activeAdapter = activity.getAdapter(); - assertThat( - "Chooser should have 3 targets (2 apps, 1 direct)", - activeAdapter.getCount(), - is(3)); - assertThat( - "Chooser should have exactly two selectable direct target", - activeAdapter.getSelectableServiceTargetCount(), - is(1)); - assertThat( - "The display label must match", - activeAdapter.getItem(0).getDisplayLabel(), - is(callerTargetLabel)); - - // Switch to work profile and ensure that the target *doesn't* show up there. - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - - for (int i = 0; i < activity.getWorkListAdapter().getCount(); i++) { - assertThat( - "Chooser target should not show up in opposite profile", - activity.getWorkListAdapter().getItem(i).getDisplayLabel(), - not(callerTargetLabel)); - } - } - - @Test - public void testLaunchWithCustomAction() throws InterruptedException { - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - setupResolverControllers(resolvedComponentInfos); - - Context testContext = InstrumentationRegistry.getInstrumentation().getContext(); - final String customActionLabel = "Custom Action"; - final String testAction = "test-broadcast-receiver-action"; - Intent chooserIntent = Intent.createChooser(createSendTextIntent(), null); - chooserIntent.putExtra( - Intent.EXTRA_CHOOSER_CUSTOM_ACTIONS, - new ChooserAction[] { - new ChooserAction.Builder( - Icon.createWithResource("", Resources.ID_NULL), - customActionLabel, - PendingIntent.getBroadcast( - testContext, - 123, - new Intent(testAction), - PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT)) - .build() - }); - // Start activity - mActivityRule.launchActivity(chooserIntent); - waitForIdle(); - - final CountDownLatch broadcastInvoked = new CountDownLatch(1); - BroadcastReceiver testReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - broadcastInvoked.countDown(); - } - }; - testContext.registerReceiver(testReceiver, new IntentFilter(testAction), - Context.RECEIVER_EXPORTED); - - try { - onView(withText(customActionLabel)).perform(click()); - assertTrue("Timeout waiting for broadcast", - broadcastInvoked.await(5000, TimeUnit.MILLISECONDS)); - } finally { - testContext.unregisterReceiver(testReceiver); - } - } - - @Test - public void testLaunchWithShareModification() throws InterruptedException { - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - setupResolverControllers(resolvedComponentInfos); - - Context testContext = InstrumentationRegistry.getInstrumentation().getContext(); - final String modifyShareAction = "test-broadcast-receiver-action"; - Intent chooserIntent = Intent.createChooser(createSendTextIntent(), null); - String label = "modify share"; - PendingIntent pendingIntent = PendingIntent.getBroadcast( - testContext, - 123, - new Intent(modifyShareAction), - PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT); - ChooserAction action = new ChooserAction.Builder(Icon.createWithBitmap( - createBitmap()), label, pendingIntent).build(); - chooserIntent.putExtra( - Intent.EXTRA_CHOOSER_MODIFY_SHARE_ACTION, - action); - // Start activity - mActivityRule.launchActivity(chooserIntent); - waitForIdle(); - - final CountDownLatch broadcastInvoked = new CountDownLatch(1); - BroadcastReceiver testReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - broadcastInvoked.countDown(); - } - }; - testContext.registerReceiver(testReceiver, new IntentFilter(modifyShareAction), - Context.RECEIVER_EXPORTED); - - try { - onView(withText(label)).perform(click()); - assertTrue("Timeout waiting for broadcast", - broadcastInvoked.await(5000, TimeUnit.MILLISECONDS)); - - } finally { - testContext.unregisterReceiver(testReceiver); - } - } - - @Test - public void testUpdateMaxTargetsPerRow_columnCountIsUpdated() throws InterruptedException { - updateMaxTargetsPerRowResource(/* targetsPerRow= */ 4); - givenAppTargets(/* appCount= */ 16); - Intent sendIntent = createSendTextIntent(); - final ChooserActivity activity = - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - - updateMaxTargetsPerRowResource(/* targetsPerRow= */ 6); - InstrumentationRegistry.getInstrumentation() - .runOnMainSync(() -> activity.onConfigurationChanged( - InstrumentationRegistry.getInstrumentation() - .getContext().getResources().getConfiguration())); - - waitForIdle(); - onView(withId(com.android.internal.R.id.resolver_list)) - .check(matches(withGridColumnCount(6))); - } - - // This test is too long and too slow and should not be taken as an example for future tests. - @Test @Ignore - public void testDirectTargetLoggingWithAppTargetNotRankedPortrait() - throws InterruptedException { - testDirectTargetLoggingWithAppTargetNotRanked(Configuration.ORIENTATION_PORTRAIT, 4); - } - - @Test @Ignore - public void testDirectTargetLoggingWithAppTargetNotRankedLandscape() - throws InterruptedException { - testDirectTargetLoggingWithAppTargetNotRanked(Configuration.ORIENTATION_LANDSCAPE, 8); - } - - private void testDirectTargetLoggingWithAppTargetNotRanked( - int orientation, int appTargetsExpected) { - Configuration configuration = - new Configuration(InstrumentationRegistry.getInstrumentation().getContext() - .getResources().getConfiguration()); - configuration.orientation = orientation; - - Resources resources = Mockito.spy( - InstrumentationRegistry.getInstrumentation().getContext().getResources()); - ChooserActivityOverrideData.getInstance().resources = resources; - doReturn(configuration).when(resources).getConfiguration(); - - Intent sendIntent = createSendTextIntent(); - // We need app targets for direct targets to get displayed - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(15); - setupResolverControllers(resolvedComponentInfos); - - // Create direct share target - List<ChooserTarget> serviceTargets = createDirectShareTargets(1, - resolvedComponentInfos.get(14).getResolveInfoAt(0).activityInfo.packageName); - ResolveInfo ri = ResolverDataProvider.createResolveInfo(16, 0, PERSONAL_USER_HANDLE); - - // Start activity - ChooserWrapperActivity activity = - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - // Insert the direct share target - Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos = new HashMap<>(); - directShareToShortcutInfos.put(serviceTargets.get(0), null); - InstrumentationRegistry.getInstrumentation().runOnMainSync( - () -> activity.getAdapter().addServiceResults( - activity.createTestDisplayResolveInfo(sendIntent, - ri, - "testLabel", - "testInfo", - sendIntent), - serviceTargets, - TARGET_TYPE_CHOOSER_TARGET, - directShareToShortcutInfos, - /* directShareToAppTargets */ null) - ); - - assertThat( - String.format("Chooser should have %d targets (%d apps, 1 direct, 15 A-Z)", - appTargetsExpected + 16, appTargetsExpected), - activity.getAdapter().getCount(), is(appTargetsExpected + 16)); - assertThat("Chooser should have exactly one selectable direct target", - activity.getAdapter().getSelectableServiceTargetCount(), is(1)); - assertThat("The resolver info must match the resolver info used to create the target", - activity.getAdapter().getItem(0).getResolveInfo(), is(ri)); - - // Click on the direct target - String name = serviceTargets.get(0).getTitle().toString(); - onView(withText(name)) - .perform(click()); - waitForIdle(); - - FakeEventLog eventLog = getEventLog(activity); - var invocations = eventLog.getShareTargetSelected(); - assertWithMessage("Only one ShareTargetSelected event logged") - .that(invocations).hasSize(1); - FakeEventLog.ShareTargetSelected call = invocations.get(0); - assertWithMessage("targetType should be SELECTION_TYPE_SERVICE") - .that(call.getTargetType()).isEqualTo(EventLog.SELECTION_TYPE_SERVICE); - assertWithMessage( - "The packages shouldn't match for app target and direct target") - .that(call.getDirectTargetAlsoRanked()).isEqualTo(-1); - } - - @Test - public void testWorkTab_displayedWhenWorkProfileUserAvailable() { - Intent sendIntent = createSendTextIntent(); - sendIntent.setType(TEST_MIME_TYPE); - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); - waitForIdle(); - - onView(withId(android.R.id.tabs)).check(matches(isDisplayed())); - } - - @Test - public void testWorkTab_hiddenWhenWorkProfileUserNotAvailable() { - Intent sendIntent = createSendTextIntent(); - sendIntent.setType(TEST_MIME_TYPE); - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); - waitForIdle(); - - onView(withId(android.R.id.tabs)).check(matches(not(isDisplayed()))); - } - - @Test - public void testWorkTab_eachTabUsesExpectedAdapter() { - int personalProfileTargets = 3; - int otherProfileTargets = 1; - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile( - personalProfileTargets + otherProfileTargets, /* userID */ 10); - int workProfileTargets = 4; - List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest( - workProfileTargets); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendTextIntent(); - sendIntent.setType(TEST_MIME_TYPE); - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); - waitForIdle(); - - assertThat(activity.getCurrentUserHandle().getIdentifier(), is(0)); - onView(withText(R.string.resolver_work_tab)).perform(click()); - assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10)); - assertThat(activity.getPersonalListAdapter().getCount(), is(personalProfileTargets)); - assertThat(activity.getWorkListAdapter().getCount(), is(workProfileTargets)); - } - - @Test - public void testWorkTab_workProfileHasExpectedNumberOfTargets() throws InterruptedException { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - int workProfileTargets = 4; - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(workProfileTargets); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendTextIntent(); - sendIntent.setType(TEST_MIME_TYPE); - - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); - waitForIdle(); - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - - assertThat(activity.getWorkListAdapter().getCount(), is(workProfileTargets)); - } - - @Test @Ignore - public void testWorkTab_selectingWorkTabAppOpensAppInWorkProfile() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); - int workProfileTargets = 4; - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(workProfileTargets); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendTextIntent(); - sendIntent.setType(TEST_MIME_TYPE); - ResolveInfo[] chosen = new ResolveInfo[1]; - ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { - chosen[0] = targetInfo.getResolveInfo(); - return true; - }; - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); - waitForIdle(); - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - - onView(first(allOf( - withText(workResolvedComponentInfos.get(0) - .getResolveInfoAt(0).activityInfo.applicationInfo.name), - isDisplayed()))) - .perform(click()); - waitForIdle(); - assertThat(chosen[0], is(workResolvedComponentInfos.get(0).getResolveInfoAt(0))); - } - - @Test - public void testWorkTab_crossProfileIntentsDisabled_personalToWork_emptyStateShown() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - int workProfileTargets = 4; - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(workProfileTargets); - ChooserActivityOverrideData.getInstance().hasCrossProfileIntents = false; - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendTextIntent(); - sendIntent.setType(TEST_MIME_TYPE); - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); - waitForIdle(); - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - onView(withId(com.android.internal.R.id.contentPanel)) - .perform(swipeUp()); - - onView(withText(R.string.resolver_cross_profile_blocked)) - .check(matches(isDisplayed())); - } - - @Test - public void testWorkTab_workProfileDisabled_emptyStateShown() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - int workProfileTargets = 4; - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(workProfileTargets); - ChooserActivityOverrideData.getInstance().isQuietModeEnabled = true; - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendTextIntent(); - sendIntent.setType(TEST_MIME_TYPE); - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); - waitForIdle(); - onView(withId(com.android.internal.R.id.contentPanel)) - .perform(swipeUp()); - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - - onView(withText(R.string.resolver_turn_on_work_apps)) - .check(matches(isDisplayed())); - } - - @Test - public void testWorkTab_noWorkAppsAvailable_emptyStateShown() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(3); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(0); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendTextIntent(); - sendIntent.setType(TEST_MIME_TYPE); - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); - waitForIdle(); - onView(withId(com.android.internal.R.id.contentPanel)) - .perform(swipeUp()); - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - - onView(withText(R.string.resolver_no_work_apps_available)) - .check(matches(isDisplayed())); - } - - @Test - @RequiresFlagsEnabled(Flags.FLAG_SCROLLABLE_PREVIEW) - public void testWorkTab_previewIsScrollable() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(300); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(3); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - - Uri uri = createTestContentProviderUri("image/png", null); - - ArrayList<Uri> uris = new ArrayList<>(); - uris.add(uri); - - Intent sendIntent = createSendUriIntentWithPreview(uris); - ChooserActivityOverrideData.getInstance().imageLoader = - createImageLoader(uri, createWideBitmap()); - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "Scrollable preview test")); - waitForIdle(); - - onView(withId(com.android.intentresolver.R.id.scrollable_image_preview)) - .check(matches(isDisplayed())); - - onView(withId(com.android.internal.R.id.contentPanel)).perform(swipeUp()); - waitForIdle(); - - onView(withId(com.android.intentresolver.R.id.chooser_headline_row_container)) - .check(matches(isCompletelyDisplayed())); - onView(withId(com.android.intentresolver.R.id.headline)) - .check(matches(isDisplayed())); - onView(withId(com.android.intentresolver.R.id.scrollable_image_preview)) - .check(matches(not(isDisplayed()))); - } - - @Ignore // b/220067877 - @Test - public void testWorkTab_xProfileOff_noAppsAvailable_workOff_xProfileOffEmptyStateShown() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(3); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(0); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - ChooserActivityOverrideData.getInstance().isQuietModeEnabled = true; - ChooserActivityOverrideData.getInstance().hasCrossProfileIntents = false; - Intent sendIntent = createSendTextIntent(); - sendIntent.setType(TEST_MIME_TYPE); - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); - waitForIdle(); - onView(withId(com.android.internal.R.id.contentPanel)) - .perform(swipeUp()); - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - - onView(withText(R.string.resolver_cross_profile_blocked)) - .check(matches(isDisplayed())); - } - - @Test - public void testWorkTab_noAppsAvailable_workOff_noAppsAvailableEmptyStateShown() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(3); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(0); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - ChooserActivityOverrideData.getInstance().isQuietModeEnabled = true; - Intent sendIntent = createSendTextIntent(); - sendIntent.setType(TEST_MIME_TYPE); - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); - waitForIdle(); - onView(withId(com.android.internal.R.id.contentPanel)) - .perform(swipeUp()); - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - - onView(withText(R.string.resolver_no_work_apps_available)) - .check(matches(isDisplayed())); - } - - @Test @Ignore("b/222124533") - public void testAppTargetLogging() throws InterruptedException { - Intent sendIntent = createSendTextIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - // TODO(b/222124533): other test cases use a timeout to make sure that the UI is fully - // populated; without one, this test flakes. Ideally we should address the need for a - // timeout everywhere instead of introducing one to fix this particular test. - - assertThat(activity.getAdapter().getCount(), is(2)); - onView(withId(com.android.internal.R.id.profile_button)).check(doesNotExist()); - - ResolveInfo[] chosen = new ResolveInfo[1]; - ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { - chosen[0] = targetInfo.getResolveInfo(); - return true; - }; - - ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0); - onView(withText(toChoose.activityInfo.name)) - .perform(click()); - waitForIdle(); - - // TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events. - } - - @Test - public void testDirectTargetLogging() { - Intent sendIntent = createSendTextIntent(); - // We need app targets for direct targets to get displayed - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - setupResolverControllers(resolvedComponentInfos); - - // create test shortcut loader factory, remember loaders and their callbacks - SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders = - new SparseArray<>(); - ChooserActivityOverrideData.getInstance().shortcutLoaderFactory = - (userHandle, callback) -> { - Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>> pair = - new Pair<>(mock(ShortcutLoader.class), callback); - shortcutLoaders.put(userHandle.getIdentifier(), pair); - return pair.first; - }; - - // Start activity - ChooserWrapperActivity activity = - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - // verify that ShortcutLoader was queried - ArgumentCaptor<DisplayResolveInfo[]> appTargets = - ArgumentCaptor.forClass(DisplayResolveInfo[].class); - verify(shortcutLoaders.get(0).first, times(1)) - .updateAppTargets(appTargets.capture()); - - // send shortcuts - assertThat( - "Wrong number of app targets", - appTargets.getValue().length, - is(resolvedComponentInfos.size())); - List<ChooserTarget> serviceTargets = createDirectShareTargets(1, - resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName); - ShortcutLoader.Result result = new ShortcutLoader.Result( - // TODO: test another value as well - false, - appTargets.getValue(), - new ShortcutLoader.ShortcutResultInfo[] { - new ShortcutLoader.ShortcutResultInfo( - appTargets.getValue()[0], - serviceTargets - ) - }, - new HashMap<>(), - new HashMap<>() - ); - activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result)); - waitForIdle(); - - assertThat("Chooser should have 3 targets (2 apps, 1 direct)", - activity.getAdapter().getCount(), is(3)); - assertThat("Chooser should have exactly one selectable direct target", - activity.getAdapter().getSelectableServiceTargetCount(), is(1)); - assertThat( - "The resolver info must match the resolver info used to create the target", - activity.getAdapter().getItem(0).getResolveInfo(), - is(resolvedComponentInfos.get(0).getResolveInfoAt(0))); - - // Click on the direct target - String name = serviceTargets.get(0).getTitle().toString(); - onView(withText(name)) - .perform(click()); - waitForIdle(); - - FakeEventLog eventLog = getEventLog(activity); - assertThat(eventLog.getShareTargetSelected()).hasSize(1); - FakeEventLog.ShareTargetSelected call = eventLog.getShareTargetSelected().get(0); - assertThat(call.getTargetType()).isEqualTo(EventLog.SELECTION_TYPE_SERVICE); - } - - @Test - public void testDirectTargetPinningDialog() { - Intent sendIntent = createSendTextIntent(); - // We need app targets for direct targets to get displayed - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - setupResolverControllers(resolvedComponentInfos); - - // create test shortcut loader factory, remember loaders and their callbacks - SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders = - new SparseArray<>(); - ChooserActivityOverrideData.getInstance().shortcutLoaderFactory = - (userHandle, callback) -> { - Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>> pair = - new Pair<>(mock(ShortcutLoader.class), callback); - shortcutLoaders.put(userHandle.getIdentifier(), pair); - return pair.first; - }; - - // Start activity - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - // verify that ShortcutLoader was queried - ArgumentCaptor<DisplayResolveInfo[]> appTargets = - ArgumentCaptor.forClass(DisplayResolveInfo[].class); - verify(shortcutLoaders.get(0).first, times(1)) - .updateAppTargets(appTargets.capture()); - - // send shortcuts - List<ChooserTarget> serviceTargets = createDirectShareTargets( - 1, - resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName); - ShortcutLoader.Result result = new ShortcutLoader.Result( - // TODO: test another value as well - false, - appTargets.getValue(), - new ShortcutLoader.ShortcutResultInfo[] { - new ShortcutLoader.ShortcutResultInfo( - appTargets.getValue()[0], - serviceTargets - ) - }, - new HashMap<>(), - new HashMap<>() - ); - activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result)); - waitForIdle(); - - // Long-click on the direct target - String name = serviceTargets.get(0).getTitle().toString(); - onView(withText(name)).perform(longClick()); - waitForIdle(); - - onView(withId(R.id.chooser_dialog_content)).check(matches(isDisplayed())); - } - - @Test @Ignore - public void testEmptyDirectRowLogging() throws InterruptedException { - Intent sendIntent = createSendTextIntent(); - // We need app targets for direct targets to get displayed - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - setupResolverControllers(resolvedComponentInfos); - - // Start activity - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - - // Thread.sleep shouldn't be a thing in an integration test but it's - // necessary here because of the way the code is structured - Thread.sleep(3000); - - assertThat("Chooser should have 2 app targets", - activity.getAdapter().getCount(), is(2)); - assertThat("Chooser should have no direct targets", - activity.getAdapter().getSelectableServiceTargetCount(), is(0)); - - // TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events. - } - - @Ignore // b/220067877 - @Test - public void testCopyTextToClipboardLogging() throws Exception { - Intent sendIntent = createSendTextIntent(); - List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); - - setupResolverControllers(resolvedComponentInfos); - - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); - waitForIdle(); - - onView(withId(com.android.internal.R.id.chooser_copy_button)).check(matches(isDisplayed())); - onView(withId(com.android.internal.R.id.chooser_copy_button)).perform(click()); - - // TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events. - } - - @Test @Ignore("b/222124533") - public void testSwitchProfileLogging() throws InterruptedException { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - int workProfileTargets = 4; - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(workProfileTargets); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendTextIntent(); - sendIntent.setType(TEST_MIME_TYPE); - - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); - waitForIdle(); - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - onView(withText(R.string.resolver_personal_tab)).perform(click()); - waitForIdle(); - - // TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events. - } - - @Test - public void testWorkTab_onePersonalTarget_emptyStateOnWorkTarget_doesNotAutoLaunch() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - int workProfileTargets = 4; - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(workProfileTargets); - ChooserActivityOverrideData.getInstance().hasCrossProfileIntents = false; - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendTextIntent(); - ResolveInfo[] chosen = new ResolveInfo[1]; - ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { - chosen[0] = targetInfo.getResolveInfo(); - return true; - }; - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "Test")); - waitForIdle(); - - assertNull(chosen[0]); - } - - @Test - public void testOneInitialIntent_noAutolaunch() { - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(1); - setupResolverControllers(personalResolvedComponentInfos); - Intent chooserIntent = createChooserIntent(createSendTextIntent(), - new Intent[] {new Intent("action.fake")}); - ResolveInfo[] chosen = new ResolveInfo[1]; - ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { - chosen[0] = targetInfo.getResolveInfo(); - return true; - }; - ChooserActivityOverrideData.getInstance().packageManager = mock(PackageManager.class); - ResolveInfo ri = createFakeResolveInfo(); - when( - ChooserActivityOverrideData - .getInstance().packageManager - .resolveActivity(any(Intent.class), any())) - .thenReturn(ri); - waitForIdle(); - - IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(chooserIntent); - waitForIdle(); - - assertNull(chosen[0]); - assertThat(activity - .getPersonalListAdapter().getCallerTargetCount(), is(1)); - } - - @Test - public void testWorkTab_withInitialIntents_workTabDoesNotIncludePersonalInitialIntents() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - int workProfileTargets = 1; - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(workProfileTargets); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent[] initialIntents = { - new Intent("action.fake1"), - new Intent("action.fake2") - }; - Intent chooserIntent = createChooserIntent(createSendTextIntent(), initialIntents); - ChooserActivityOverrideData.getInstance().packageManager = mock(PackageManager.class); - when( - ChooserActivityOverrideData - .getInstance() - .packageManager - .resolveActivity(any(Intent.class), any())) - .thenReturn(createFakeResolveInfo()); - waitForIdle(); - - IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(chooserIntent); - waitForIdle(); - - assertThat(activity.getPersonalListAdapter().getCallerTargetCount(), is(2)); - assertThat(activity.getWorkListAdapter().getCallerTargetCount(), is(0)); - } - - @Test - public void testWorkTab_xProfileIntentsDisabled_personalToWork_nonSendIntent_emptyStateShown() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - int workProfileTargets = 4; - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(workProfileTargets); - ChooserActivityOverrideData.getInstance().hasCrossProfileIntents = false; - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent[] initialIntents = { - new Intent("action.fake1"), - new Intent("action.fake2") - }; - Intent chooserIntent = createChooserIntent(new Intent(), initialIntents); - ChooserActivityOverrideData.getInstance().packageManager = mock(PackageManager.class); - when( - ChooserActivityOverrideData - .getInstance() - .packageManager - .resolveActivity(any(Intent.class), any())) - .thenReturn(createFakeResolveInfo()); - - mActivityRule.launchActivity(chooserIntent); - waitForIdle(); - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - onView(withId(com.android.internal.R.id.contentPanel)) - .perform(swipeUp()); - - onView(withText(R.string.resolver_cross_profile_blocked)) - .check(matches(isDisplayed())); - } - - @Test - public void testWorkTab_noWorkAppsAvailable_nonSendIntent_emptyStateShown() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(3); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(0); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent[] initialIntents = { - new Intent("action.fake1"), - new Intent("action.fake2") - }; - Intent chooserIntent = createChooserIntent(new Intent(), initialIntents); - ChooserActivityOverrideData.getInstance().packageManager = mock(PackageManager.class); - when( - ChooserActivityOverrideData - .getInstance() - .packageManager - .resolveActivity(any(Intent.class), any())) - .thenReturn(createFakeResolveInfo()); - - mActivityRule.launchActivity(chooserIntent); - waitForIdle(); - onView(withId(com.android.internal.R.id.contentPanel)) - .perform(swipeUp()); - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - - onView(withText(R.string.resolver_no_work_apps_available)) - .check(matches(isDisplayed())); - } - - @Test - public void testDeduplicateCallerTargetRankedTarget() { - // Create 4 ranked app targets. - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(4); - setupResolverControllers(personalResolvedComponentInfos); - // Create caller target which is duplicate with one of app targets - Intent chooserIntent = createChooserIntent(createSendTextIntent(), - new Intent[] {new Intent("action.fake")}); - ChooserActivityOverrideData.getInstance().packageManager = mock(PackageManager.class); - ResolveInfo ri = ResolverDataProvider.createResolveInfo(0, - UserHandle.USER_CURRENT, PERSONAL_USER_HANDLE); - when( - ChooserActivityOverrideData - .getInstance() - .packageManager - .resolveActivity(any(Intent.class), any())) - .thenReturn(ri); - waitForIdle(); - - IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(chooserIntent); - waitForIdle(); - - // Total 4 targets (1 caller target, 3 ranked targets) - assertThat(activity.getAdapter().getCount(), is(4)); - assertThat(activity.getAdapter().getCallerTargetCount(), is(1)); - assertThat(activity.getAdapter().getRankedTargetCount(), is(3)); - } - - @Test - public void test_query_shortcut_loader_for_the_selected_tab() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(3); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - ShortcutLoader personalProfileShortcutLoader = mock(ShortcutLoader.class); - ShortcutLoader workProfileShortcutLoader = mock(ShortcutLoader.class); - final SparseArray<ShortcutLoader> shortcutLoaders = new SparseArray<>(); - shortcutLoaders.put(0, personalProfileShortcutLoader); - shortcutLoaders.put(10, workProfileShortcutLoader); - ChooserActivityOverrideData.getInstance().shortcutLoaderFactory = - (userHandle, callback) -> shortcutLoaders.get(userHandle.getIdentifier(), null); - Intent sendIntent = createSendTextIntent(); - sendIntent.setType(TEST_MIME_TYPE); - - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); - waitForIdle(); - onView(withId(com.android.internal.R.id.contentPanel)) - .perform(swipeUp()); - waitForIdle(); - - verify(personalProfileShortcutLoader, times(1)).updateAppTargets(any()); - - onView(withText(R.string.resolver_work_tab)).perform(click()); - waitForIdle(); - - verify(workProfileShortcutLoader, times(1)).updateAppTargets(any()); - } - - @Test - public void testClonedProfilePresent_personalAdapterIsSetWithPersonalProfile() { - // enable cloneProfile - markOtherProfileAvailability(/* workAvailable= */ false, /* cloneAvailable= */ true); - List<ResolvedComponentInfo> resolvedComponentInfos = - createResolvedComponentsWithCloneProfileForTest( - 3, - PERSONAL_USER_HANDLE, - CLONE_PROFILE_USER_HANDLE); - setupResolverControllers(resolvedComponentInfos); - Intent sendIntent = createSendTextIntent(); - - final IChooserWrapper activity = (IChooserWrapper) mActivityRule - .launchActivity(Intent.createChooser(sendIntent, "personalProfileTest")); - waitForIdle(); - - assertThat(activity.getPersonalListAdapter().getUserHandle(), is(PERSONAL_USER_HANDLE)); - assertThat(activity.getAdapter().getCount(), is(3)); - } - - @Test - public void testClonedProfilePresent_personalTabUsesExpectedAdapter() { - markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ true); - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTest(3); - List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest( - 4); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - Intent sendIntent = createSendTextIntent(); - sendIntent.setType(TEST_MIME_TYPE); - - - final IChooserWrapper activity = (IChooserWrapper) - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "multi tab test")); - waitForIdle(); - - assertThat(activity.getCurrentUserHandle(), is(PERSONAL_USER_HANDLE)); - } - - private Intent createChooserIntent(Intent intent, Intent[] initialIntents) { - Intent chooserIntent = new Intent(); - chooserIntent.setAction(Intent.ACTION_CHOOSER); - chooserIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending"); - chooserIntent.putExtra(Intent.EXTRA_TITLE, "some title"); - chooserIntent.putExtra(Intent.EXTRA_INTENT, intent); - chooserIntent.setType("text/plain"); - if (initialIntents != null) { - chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, initialIntents); - } - return chooserIntent; - } - - /* This is a "test of a test" to make sure that our inherited test class - * is successfully configured to operate on the unbundled-equivalent - * ChooserWrapperActivity. - * - * TODO: remove after unbundling is complete. - */ - @Test - public void testWrapperActivityHasExpectedConcreteType() { - final ChooserActivity activity = mActivityRule.launchActivity( - Intent.createChooser(new Intent("ACTION_FOO"), "foo")); - waitForIdle(); - assertThat(activity).isInstanceOf(ChooserWrapperActivity.class); - } - - private ResolveInfo createFakeResolveInfo() { - ResolveInfo ri = new ResolveInfo(); - ri.activityInfo = new ActivityInfo(); - ri.activityInfo.name = "FakeActivityName"; - ri.activityInfo.packageName = "fake.package.name"; - ri.activityInfo.applicationInfo = new ApplicationInfo(); - ri.activityInfo.applicationInfo.packageName = "fake.package.name"; - ri.userHandle = UserHandle.CURRENT; - return ri; - } - - private Intent createSendTextIntent() { - Intent sendIntent = new Intent(); - sendIntent.setAction(Intent.ACTION_SEND); - sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending"); - sendIntent.setType("text/plain"); - return sendIntent; - } - - private Intent createSendImageIntent(Uri imageThumbnail) { - Intent sendIntent = new Intent(); - sendIntent.setAction(Intent.ACTION_SEND); - sendIntent.putExtra(Intent.EXTRA_STREAM, imageThumbnail); - sendIntent.setType("image/png"); - if (imageThumbnail != null) { - ClipData.Item clipItem = new ClipData.Item(imageThumbnail); - sendIntent.setClipData(new ClipData("Clip Label", new String[]{"image/png"}, clipItem)); - } - - return sendIntent; - } - - private Uri createTestContentProviderUri( - @Nullable String mimeType, @Nullable String streamType) { - return createTestContentProviderUri(mimeType, streamType, 0); - } - - private Uri createTestContentProviderUri( - @Nullable String mimeType, @Nullable String streamType, long streamTypeTimeout) { - String packageName = - InstrumentationRegistry.getInstrumentation().getContext().getPackageName(); - Uri.Builder builder = Uri.parse("content://" + packageName + "/image.png") - .buildUpon(); - if (mimeType != null) { - builder.appendQueryParameter(TestContentProvider.PARAM_MIME_TYPE, mimeType); - } - if (streamType != null) { - builder.appendQueryParameter(TestContentProvider.PARAM_STREAM_TYPE, streamType); - } - if (streamTypeTimeout > 0) { - builder.appendQueryParameter( - TestContentProvider.PARAM_STREAM_TYPE_TIMEOUT, - Long.toString(streamTypeTimeout)); - } - return builder.build(); - } - - private Intent createSendTextIntentWithPreview(String title, Uri imageThumbnail) { - Intent sendIntent = new Intent(); - sendIntent.setAction(Intent.ACTION_SEND); - sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending"); - sendIntent.putExtra(Intent.EXTRA_TITLE, title); - if (imageThumbnail != null) { - ClipData.Item clipItem = new ClipData.Item(imageThumbnail); - sendIntent.setClipData(new ClipData("Clip Label", new String[]{"image/png"}, clipItem)); - } - - return sendIntent; - } - - private Intent createSendUriIntentWithPreview(ArrayList<Uri> uris) { - Intent sendIntent = new Intent(); - - if (uris.size() > 1) { - sendIntent.setAction(Intent.ACTION_SEND_MULTIPLE); - sendIntent.putExtra(Intent.EXTRA_STREAM, uris); - } else { - sendIntent.setAction(Intent.ACTION_SEND); - sendIntent.putExtra(Intent.EXTRA_STREAM, uris.get(0)); - } - - return sendIntent; - } - - private Intent createViewTextIntent() { - Intent viewIntent = new Intent(); - viewIntent.setAction(Intent.ACTION_VIEW); - viewIntent.putExtra(Intent.EXTRA_TEXT, "testing intent viewing"); - return viewIntent; - } - - private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults) { - List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); - for (int i = 0; i < numberOfResults; i++) { - infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, PERSONAL_USER_HANDLE)); - } - return infoList; - } - - private List<ResolvedComponentInfo> createResolvedComponentsWithCloneProfileForTest( - int numberOfResults, - UserHandle resolvedForPersonalUser, - UserHandle resolvedForClonedUser) { - List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); - for (int i = 0; i < 1; i++) { - infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, - resolvedForPersonalUser)); - } - for (int i = 1; i < numberOfResults; i++) { - infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, - resolvedForClonedUser)); - } - return infoList; - } - - private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile( - int numberOfResults) { - List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); - for (int i = 0; i < numberOfResults; i++) { - if (i == 0) { - infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, - PERSONAL_USER_HANDLE)); - } else { - infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, - PERSONAL_USER_HANDLE)); - } - } - return infoList; - } - - private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile( - int numberOfResults, int userId) { - List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); - for (int i = 0; i < numberOfResults; i++) { - if (i == 0) { - infoList.add( - ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId, - PERSONAL_USER_HANDLE)); - } else { - infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, - PERSONAL_USER_HANDLE)); - } - } - return infoList; - } - - private List<ResolvedComponentInfo> createResolvedComponentsForTestWithUserId( - int numberOfResults, int userId) { - List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); - for (int i = 0; i < numberOfResults; i++) { - infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId, - PERSONAL_USER_HANDLE)); - } - return infoList; - } - - private List<ChooserTarget> createDirectShareTargets(int numberOfResults, String packageName) { - Icon icon = Icon.createWithBitmap(createBitmap()); - String testTitle = "testTitle"; - List<ChooserTarget> targets = new ArrayList<>(); - for (int i = 0; i < numberOfResults; i++) { - ComponentName componentName; - if (packageName.isEmpty()) { - componentName = ResolverDataProvider.createComponentName(i); - } else { - componentName = new ComponentName(packageName, packageName + ".class"); - } - ChooserTarget tempTarget = new ChooserTarget( - testTitle + i, - icon, - (float) (1 - ((i + 1) / 10.0)), - componentName, - null); - targets.add(tempTarget); - } - return targets; - } - - private void waitForIdle() { - InstrumentationRegistry.getInstrumentation().waitForIdleSync(); - } - - private Bitmap createBitmap() { - return createBitmap(200, 200); - } - - private Bitmap createWideBitmap() { - return createWideBitmap(Color.RED); - } - - private Bitmap createWideBitmap(int bgColor) { - WindowManager windowManager = InstrumentationRegistry.getInstrumentation() - .getTargetContext() - .getSystemService(WindowManager.class); - int width = 3000; - if (windowManager != null) { - Rect bounds = windowManager.getMaximumWindowMetrics().getBounds(); - width = bounds.width() + 200; - } - return createBitmap(width, 100, bgColor); - } - - private Bitmap createBitmap(int width, int height) { - return createBitmap(width, height, Color.RED); - } - - private Bitmap createBitmap(int width, int height, int bgColor) { - Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - - Paint paint = new Paint(); - paint.setColor(bgColor); - paint.setStyle(Paint.Style.FILL); - canvas.drawPaint(paint); - - paint.setColor(Color.WHITE); - paint.setAntiAlias(true); - paint.setTextSize(14.f); - paint.setTextAlign(Paint.Align.CENTER); - canvas.drawText("Hi!", (width / 2.f), (height / 2.f), paint); - - return bitmap; - } - - private List<ShareShortcutInfo> createShortcuts(Context context) { - Intent testIntent = new Intent("TestIntent"); - - List<ShareShortcutInfo> shortcuts = new ArrayList<>(); - shortcuts.add(new ShareShortcutInfo( - new ShortcutInfo.Builder(context, "shortcut1") - .setIntent(testIntent).setShortLabel("label1").setRank(3).build(), // 0 2 - new ComponentName("package1", "class1"))); - shortcuts.add(new ShareShortcutInfo( - new ShortcutInfo.Builder(context, "shortcut2") - .setIntent(testIntent).setShortLabel("label2").setRank(7).build(), // 1 3 - new ComponentName("package2", "class2"))); - shortcuts.add(new ShareShortcutInfo( - new ShortcutInfo.Builder(context, "shortcut3") - .setIntent(testIntent).setShortLabel("label3").setRank(1).build(), // 2 0 - new ComponentName("package3", "class3"))); - shortcuts.add(new ShareShortcutInfo( - new ShortcutInfo.Builder(context, "shortcut4") - .setIntent(testIntent).setShortLabel("label4").setRank(3).build(), // 3 2 - new ComponentName("package4", "class4"))); - - return shortcuts; - } - - private void markOtherProfileAvailability(boolean workAvailable, boolean cloneAvailable) { - AnnotatedUserHandles.Builder handles = AnnotatedUserHandles.newBuilder(); - handles - .setUserIdOfCallingApp(1234) // Must be non-negative. - .setUserHandleSharesheetLaunchedAs(PERSONAL_USER_HANDLE) - .setPersonalProfileUserHandle(PERSONAL_USER_HANDLE); - if (workAvailable) { - handles.setWorkProfileUserHandle(WORK_PROFILE_USER_HANDLE); - } - if (cloneAvailable) { - handles.setCloneProfileUserHandle(CLONE_PROFILE_USER_HANDLE); - } - ChooserWrapperActivity.sOverrides.annotatedUserHandles = handles.build(); - } - - private void setupResolverControllers( - List<ResolvedComponentInfo> personalResolvedComponentInfos) { - setupResolverControllers(personalResolvedComponentInfos, new ArrayList<>()); - } - - private void setupResolverControllers( - List<ResolvedComponentInfo> personalResolvedComponentInfos, - List<ResolvedComponentInfo> workResolvedComponentInfos) { - when( - ChooserActivityOverrideData - .getInstance() - .resolverListController - .getResolversForIntentAsUser( - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - eq(UserHandle.SYSTEM))) - .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); - when( - ChooserActivityOverrideData - .getInstance() - .workResolverListController - .getResolversForIntentAsUser( - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - eq(UserHandle.SYSTEM))) - .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); - when( - ChooserActivityOverrideData - .getInstance() - .workResolverListController - .getResolversForIntentAsUser( - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - eq(UserHandle.of(10)))) - .thenReturn(new ArrayList<>(workResolvedComponentInfos)); - } - - private static GridRecyclerSpanCountMatcher withGridColumnCount(int columnCount) { - return new GridRecyclerSpanCountMatcher(Matchers.is(columnCount)); - } - - private static class GridRecyclerSpanCountMatcher extends - BoundedDiagnosingMatcher<View, RecyclerView> { - - private final Matcher<Integer> mIntegerMatcher; - - private GridRecyclerSpanCountMatcher(Matcher<Integer> integerMatcher) { - super(RecyclerView.class); - this.mIntegerMatcher = integerMatcher; - } - - @Override - protected void describeMoreTo(Description description) { - description.appendText("RecyclerView grid layout span count to match: "); - this.mIntegerMatcher.describeTo(description); - } - - @Override - protected boolean matchesSafely(RecyclerView view, Description mismatchDescription) { - int spanCount = ((GridLayoutManager) view.getLayoutManager()).getSpanCount(); - if (this.mIntegerMatcher.matches(spanCount)) { - return true; - } else { - mismatchDescription.appendText("RecyclerView grid layout span count was ") - .appendValue(spanCount); - return false; - } - } - } - - private void givenAppTargets(int appCount) { - List<ResolvedComponentInfo> resolvedComponentInfos = - createResolvedComponentsForTest(appCount); - setupResolverControllers(resolvedComponentInfos); - } - - private void updateMaxTargetsPerRowResource(int targetsPerRow) { - Resources resources = Mockito.spy( - InstrumentationRegistry.getInstrumentation().getContext().getResources()); - ChooserActivityOverrideData.getInstance().resources = resources; - doReturn(targetsPerRow).when(resources).getInteger( - R.integer.config_chooser_max_targets_per_row); - } - - private SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> - createShortcutLoaderFactory() { - SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders = - new SparseArray<>(); - ChooserActivityOverrideData.getInstance().shortcutLoaderFactory = - (userHandle, callback) -> { - Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>> pair = - new Pair<>(mock(ShortcutLoader.class), callback); - shortcutLoaders.put(userHandle.getIdentifier(), pair); - return pair.first; - }; - return shortcutLoaders; - } - - private static ImageLoader createImageLoader(Uri uri, Bitmap bitmap) { - return new TestPreviewImageLoader(Collections.singletonMap(uri, bitmap)); - } -} diff --git a/java/tests/src/com/android/intentresolver/v2/UnbundledChooserActivityWorkProfileTest.java b/java/tests/src/com/android/intentresolver/v2/UnbundledChooserActivityWorkProfileTest.java deleted file mode 100644 index e4ec1776..00000000 --- a/java/tests/src/com/android/intentresolver/v2/UnbundledChooserActivityWorkProfileTest.java +++ /dev/null @@ -1,481 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.intentresolver.v2; - -import static android.testing.PollingCheck.waitFor; -import static androidx.test.espresso.Espresso.onView; -import static androidx.test.espresso.action.ViewActions.click; -import static androidx.test.espresso.action.ViewActions.swipeUp; -import static androidx.test.espresso.assertion.ViewAssertions.matches; -import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; -import static androidx.test.espresso.matcher.ViewMatchers.isSelected; -import static androidx.test.espresso.matcher.ViewMatchers.withId; -import static androidx.test.espresso.matcher.ViewMatchers.withText; -import static com.android.intentresolver.v2.ChooserWrapperActivity.sOverrides; -import static com.android.intentresolver.v2.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.NO_BLOCKER; -import static com.android.intentresolver.v2.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.PERSONAL_PROFILE_ACCESS_BLOCKER; -import static com.android.intentresolver.v2.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.PERSONAL_PROFILE_SHARE_BLOCKER; -import static com.android.intentresolver.v2.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.WORK_PROFILE_ACCESS_BLOCKER; -import static com.android.intentresolver.v2.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.WORK_PROFILE_SHARE_BLOCKER; -import static com.android.intentresolver.v2.UnbundledChooserActivityWorkProfileTest.TestCase.Tab.PERSONAL; -import static com.android.intentresolver.v2.UnbundledChooserActivityWorkProfileTest.TestCase.Tab.WORK; -import static org.hamcrest.CoreMatchers.not; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -import android.companion.DeviceFilter; -import android.content.Intent; -import android.os.UserHandle; - -import androidx.test.InstrumentationRegistry; -import androidx.test.espresso.NoMatchingViewException; -import androidx.test.rule.ActivityTestRule; - -import com.android.intentresolver.AnnotatedUserHandles; -import com.android.intentresolver.R; -import com.android.intentresolver.ResolvedComponentInfo; -import com.android.intentresolver.ResolverDataProvider; -import com.android.intentresolver.v2.UnbundledChooserActivityWorkProfileTest.TestCase.Tab; - -import junit.framework.AssertionFailedError; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.mockito.Mockito; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; - -import dagger.hilt.android.testing.HiltAndroidRule; -import dagger.hilt.android.testing.HiltAndroidTest; - -@DeviceFilter.MediumType -@RunWith(Parameterized.class) -@HiltAndroidTest -public class UnbundledChooserActivityWorkProfileTest { - - private static final UserHandle PERSONAL_USER_HANDLE = InstrumentationRegistry - .getInstrumentation().getTargetContext().getUser(); - private static final UserHandle WORK_USER_HANDLE = UserHandle.of(10); - - @Rule(order = 0) - public HiltAndroidRule mHiltAndroidRule = new HiltAndroidRule(this); - - @Rule(order = 1) - public ActivityTestRule<ChooserWrapperActivity> mActivityRule = - new ActivityTestRule<>(ChooserWrapperActivity.class, false, - false); - private final TestCase mTestCase; - - public UnbundledChooserActivityWorkProfileTest(TestCase testCase) { - mTestCase = testCase; - } - - @Before - public void cleanOverrideData() { - // TODO: use the other form of `adoptShellPermissionIdentity()` where we explicitly list the - // permissions we require (which we'll read from the manifest at runtime). - InstrumentationRegistry - .getInstrumentation() - .getUiAutomation() - .adoptShellPermissionIdentity(); - - sOverrides.reset(); - } - - @Test - public void testBlocker() { - setUpPersonalAndWorkComponentInfos(); - sOverrides.hasCrossProfileIntents = mTestCase.hasCrossProfileIntents(); - - launchActivity(mTestCase.getIsSendAction()); - switchToTab(mTestCase.getTab()); - - switch (mTestCase.getExpectedBlocker()) { - case NO_BLOCKER: - assertNoBlockerDisplayed(); - break; - case PERSONAL_PROFILE_SHARE_BLOCKER: - assertCantSharePersonalAppsBlockerDisplayed(); - break; - case WORK_PROFILE_SHARE_BLOCKER: - assertCantShareWorkAppsBlockerDisplayed(); - break; - case PERSONAL_PROFILE_ACCESS_BLOCKER: - assertCantAccessPersonalAppsBlockerDisplayed(); - break; - case WORK_PROFILE_ACCESS_BLOCKER: - assertCantAccessWorkAppsBlockerDisplayed(); - break; - } - } - - @Parameterized.Parameters(name = "{0}") - public static Collection tests() { - return Arrays.asList( - new TestCase( - /* isSendAction= */ true, - /* hasCrossProfileIntents= */ true, - /* myUserHandle= */ WORK_USER_HANDLE, - /* tab= */ WORK, - /* expectedBlocker= */ NO_BLOCKER - ), - new TestCase( - /* isSendAction= */ true, - /* hasCrossProfileIntents= */ false, - /* myUserHandle= */ WORK_USER_HANDLE, - /* tab= */ WORK, - /* expectedBlocker= */ NO_BLOCKER - ), - new TestCase( - /* isSendAction= */ true, - /* hasCrossProfileIntents= */ true, - /* myUserHandle= */ PERSONAL_USER_HANDLE, - /* tab= */ WORK, - /* expectedBlocker= */ NO_BLOCKER - ), - new TestCase( - /* isSendAction= */ true, - /* hasCrossProfileIntents= */ false, - /* myUserHandle= */ PERSONAL_USER_HANDLE, - /* tab= */ WORK, - /* expectedBlocker= */ WORK_PROFILE_SHARE_BLOCKER - ), - new TestCase( - /* isSendAction= */ true, - /* hasCrossProfileIntents= */ true, - /* myUserHandle= */ WORK_USER_HANDLE, - /* tab= */ PERSONAL, - /* expectedBlocker= */ NO_BLOCKER - ), - new TestCase( - /* isSendAction= */ true, - /* hasCrossProfileIntents= */ false, - /* myUserHandle= */ WORK_USER_HANDLE, - /* tab= */ PERSONAL, - /* expectedBlocker= */ PERSONAL_PROFILE_SHARE_BLOCKER - ), - new TestCase( - /* isSendAction= */ true, - /* hasCrossProfileIntents= */ true, - /* myUserHandle= */ PERSONAL_USER_HANDLE, - /* tab= */ PERSONAL, - /* expectedBlocker= */ NO_BLOCKER - ), - new TestCase( - /* isSendAction= */ true, - /* hasCrossProfileIntents= */ false, - /* myUserHandle= */ PERSONAL_USER_HANDLE, - /* tab= */ PERSONAL, - /* expectedBlocker= */ NO_BLOCKER - ), - new TestCase( - /* isSendAction= */ false, - /* hasCrossProfileIntents= */ true, - /* myUserHandle= */ WORK_USER_HANDLE, - /* tab= */ WORK, - /* expectedBlocker= */ NO_BLOCKER - ), - new TestCase( - /* isSendAction= */ false, - /* hasCrossProfileIntents= */ false, - /* myUserHandle= */ WORK_USER_HANDLE, - /* tab= */ WORK, - /* expectedBlocker= */ NO_BLOCKER - ), - new TestCase( - /* isSendAction= */ false, - /* hasCrossProfileIntents= */ true, - /* myUserHandle= */ PERSONAL_USER_HANDLE, - /* tab= */ WORK, - /* expectedBlocker= */ NO_BLOCKER - ), - new TestCase( - /* isSendAction= */ false, - /* hasCrossProfileIntents= */ false, - /* myUserHandle= */ PERSONAL_USER_HANDLE, - /* tab= */ WORK, - /* expectedBlocker= */ WORK_PROFILE_ACCESS_BLOCKER - ), - new TestCase( - /* isSendAction= */ false, - /* hasCrossProfileIntents= */ true, - /* myUserHandle= */ WORK_USER_HANDLE, - /* tab= */ PERSONAL, - /* expectedBlocker= */ NO_BLOCKER - ), - new TestCase( - /* isSendAction= */ false, - /* hasCrossProfileIntents= */ false, - /* myUserHandle= */ WORK_USER_HANDLE, - /* tab= */ PERSONAL, - /* expectedBlocker= */ PERSONAL_PROFILE_ACCESS_BLOCKER - ), - new TestCase( - /* isSendAction= */ false, - /* hasCrossProfileIntents= */ true, - /* myUserHandle= */ PERSONAL_USER_HANDLE, - /* tab= */ PERSONAL, - /* expectedBlocker= */ NO_BLOCKER - ), - new TestCase( - /* isSendAction= */ false, - /* hasCrossProfileIntents= */ false, - /* myUserHandle= */ PERSONAL_USER_HANDLE, - /* tab= */ PERSONAL, - /* expectedBlocker= */ NO_BLOCKER - ) - ); - } - - private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile( - int numberOfResults, int userId, UserHandle resolvedForUser) { - List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); - for (int i = 0; i < numberOfResults; i++) { - infoList.add( - ResolverDataProvider - .createResolvedComponentInfoWithOtherId(i, userId, resolvedForUser)); - } - return infoList; - } - - private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults, - UserHandle resolvedForUser) { - List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); - for (int i = 0; i < numberOfResults; i++) { - infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, resolvedForUser)); - } - return infoList; - } - - private void setUpPersonalAndWorkComponentInfos() { - ChooserWrapperActivity.sOverrides.annotatedUserHandles = AnnotatedUserHandles.newBuilder() - .setUserIdOfCallingApp(1234) // Must be non-negative. - .setUserHandleSharesheetLaunchedAs(mTestCase.getMyUserHandle()) - .setPersonalProfileUserHandle(PERSONAL_USER_HANDLE) - .setWorkProfileUserHandle(WORK_USER_HANDLE) - .build(); - int workProfileTargets = 4; - List<ResolvedComponentInfo> personalResolvedComponentInfos = - createResolvedComponentsForTestWithOtherProfile(3, - /* userId */ WORK_USER_HANDLE.getIdentifier(), PERSONAL_USER_HANDLE); - List<ResolvedComponentInfo> workResolvedComponentInfos = - createResolvedComponentsForTest(workProfileTargets, WORK_USER_HANDLE); - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); - } - - private void setupResolverControllers( - List<ResolvedComponentInfo> personalResolvedComponentInfos, - List<ResolvedComponentInfo> workResolvedComponentInfos) { - when(sOverrides.resolverListController.getResolversForIntentAsUser( - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - eq(UserHandle.SYSTEM))) - .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); - when(sOverrides.workResolverListController.getResolversForIntentAsUser( - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - eq(UserHandle.SYSTEM))) - .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); - when(sOverrides.workResolverListController.getResolversForIntentAsUser( - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class), - eq(WORK_USER_HANDLE))) - .thenReturn(new ArrayList<>(workResolvedComponentInfos)); - } - - private void waitForIdle() { - InstrumentationRegistry.getInstrumentation().waitForIdleSync(); - } - - private void assertCantAccessWorkAppsBlockerDisplayed() { - onView(withText(R.string.resolver_cross_profile_blocked)) - .check(matches(isDisplayed())); - onView(withText(R.string.resolver_cant_access_work_apps_explanation)) - .check(matches(isDisplayed())); - } - - private void assertCantAccessPersonalAppsBlockerDisplayed() { - onView(withText(R.string.resolver_cross_profile_blocked)) - .check(matches(isDisplayed())); - onView(withText(R.string.resolver_cant_access_personal_apps_explanation)) - .check(matches(isDisplayed())); - } - - private void assertCantShareWorkAppsBlockerDisplayed() { - onView(withText(R.string.resolver_cross_profile_blocked)) - .check(matches(isDisplayed())); - onView(withText(R.string.resolver_cant_share_with_work_apps_explanation)) - .check(matches(isDisplayed())); - } - - private void assertCantSharePersonalAppsBlockerDisplayed() { - onView(withText(R.string.resolver_cross_profile_blocked)) - .check(matches(isDisplayed())); - onView(withText(R.string.resolver_cant_share_with_personal_apps_explanation)) - .check(matches(isDisplayed())); - } - - private void assertNoBlockerDisplayed() { - try { - onView(withText(R.string.resolver_cross_profile_blocked)) - .check(matches(not(isDisplayed()))); - } catch (NoMatchingViewException ignored) { - } - } - - private void switchToTab(Tab tab) { - final int stringId = tab == Tab.WORK ? R.string.resolver_work_tab - : R.string.resolver_personal_tab; - - waitFor(() -> { - onView(withText(stringId)).perform(click()); - waitForIdle(); - - try { - onView(withText(stringId)).check(matches(isSelected())); - return true; - } catch (AssertionFailedError e) { - return false; - } - }); - - onView(withId(com.android.internal.R.id.contentPanel)) - .perform(swipeUp()); - waitForIdle(); - } - - private Intent createTextIntent(boolean isSendAction) { - Intent sendIntent = new Intent(); - if (isSendAction) { - sendIntent.setAction(Intent.ACTION_SEND); - } - sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending"); - sendIntent.setType("text/plain"); - return sendIntent; - } - - private void launchActivity(boolean isSendAction) { - Intent sendIntent = createTextIntent(isSendAction); - mActivityRule.launchActivity(Intent.createChooser(sendIntent, "Test")); - waitForIdle(); - } - - public static class TestCase { - private final boolean mIsSendAction; - private final boolean mHasCrossProfileIntents; - private final UserHandle mMyUserHandle; - private final Tab mTab; - private final ExpectedBlocker mExpectedBlocker; - - public enum ExpectedBlocker { - NO_BLOCKER, - PERSONAL_PROFILE_SHARE_BLOCKER, - WORK_PROFILE_SHARE_BLOCKER, - PERSONAL_PROFILE_ACCESS_BLOCKER, - WORK_PROFILE_ACCESS_BLOCKER - } - - public enum Tab { - WORK, - PERSONAL - } - - public TestCase(boolean isSendAction, boolean hasCrossProfileIntents, - UserHandle myUserHandle, Tab tab, ExpectedBlocker expectedBlocker) { - mIsSendAction = isSendAction; - mHasCrossProfileIntents = hasCrossProfileIntents; - mMyUserHandle = myUserHandle; - mTab = tab; - mExpectedBlocker = expectedBlocker; - } - - public boolean getIsSendAction() { - return mIsSendAction; - } - - public boolean hasCrossProfileIntents() { - return mHasCrossProfileIntents; - } - - public UserHandle getMyUserHandle() { - return mMyUserHandle; - } - - public Tab getTab() { - return mTab; - } - - public ExpectedBlocker getExpectedBlocker() { - return mExpectedBlocker; - } - - @Override - public String toString() { - StringBuilder result = new StringBuilder("test"); - - if (mTab == WORK) { - result.append("WorkTab_"); - } else { - result.append("PersonalTab_"); - } - - if (mIsSendAction) { - result.append("sendAction_"); - } else { - result.append("notSendAction_"); - } - - if (mHasCrossProfileIntents) { - result.append("hasCrossProfileIntents_"); - } else { - result.append("doesNotHaveCrossProfileIntents_"); - } - - if (mMyUserHandle.equals(PERSONAL_USER_HANDLE)) { - result.append("myUserIsPersonal_"); - } else { - result.append("myUserIsWork_"); - } - - if (mExpectedBlocker == ExpectedBlocker.NO_BLOCKER) { - result.append("thenNoBlocker"); - } else if (mExpectedBlocker == PERSONAL_PROFILE_ACCESS_BLOCKER) { - result.append("thenAccessBlockerOnPersonalProfile"); - } else if (mExpectedBlocker == PERSONAL_PROFILE_SHARE_BLOCKER) { - result.append("thenShareBlockerOnPersonalProfile"); - } else if (mExpectedBlocker == WORK_PROFILE_ACCESS_BLOCKER) { - result.append("thenAccessBlockerOnWorkProfile"); - } else if (mExpectedBlocker == WORK_PROFILE_SHARE_BLOCKER) { - result.append("thenShareBlockerOnWorkProfile"); - } - - return result.toString(); - } - } -} diff --git a/java/tests/src/com/android/intentresolver/v2/coroutines/Flow.kt b/java/tests/src/com/android/intentresolver/v2/coroutines/Flow.kt deleted file mode 100644 index a5677d94..00000000 --- a/java/tests/src/com/android/intentresolver/v2/coroutines/Flow.kt +++ /dev/null @@ -1,89 +0,0 @@ -@file:Suppress("OPT_IN_USAGE") - -package com.android.intentresolver.v2.coroutines - -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.EmptyCoroutineContext -import kotlin.properties.ReadOnlyProperty -import kotlin.reflect.KProperty -import kotlinx.coroutines.CoroutineStart -import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.launch -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.runCurrent - -/** - * Collect [flow] in a new [Job] and return a getter for the last collected value. - * - * ``` - * fun myTest() = runTest { - * // ... - * val actual by collectLastValue(underTest.flow) - * assertThat(actual).isEqualTo(expected) - * } - * ``` - */ -fun <T> TestScope.collectLastValue( - flow: Flow<T>, - context: CoroutineContext = EmptyCoroutineContext, - start: CoroutineStart = CoroutineStart.DEFAULT, -): FlowValue<T?> { - val values by - collectValues( - flow = flow, - context = context, - start = start, - ) - return FlowValueImpl { values.lastOrNull() } -} - -/** - * Collect [flow] in a new [Job] and return a getter for the collection of values collected. - * - * ``` - * fun myTest() = runTest { - * // ... - * val values by collectValues(underTest.flow) - * assertThat(values).isEqualTo(listOf(expected1, expected2, ...)) - * } - * ``` - */ -fun <T> TestScope.collectValues( - flow: Flow<T>, - context: CoroutineContext = EmptyCoroutineContext, - start: CoroutineStart = CoroutineStart.DEFAULT, -): FlowValue<List<T>> { - val values = mutableListOf<T>() - backgroundScope.launch(context, start) { flow.collect(values::add) } - return FlowValueImpl { - runCurrent() - values.toList() - } -} - -/** @see collectLastValue */ -interface FlowValue<T> : ReadOnlyProperty<Any?, T> { - operator fun invoke(): T -} - -private class FlowValueImpl<T>(private val block: () -> T) : FlowValue<T> { - override operator fun invoke(): T = block() - override fun getValue(thisRef: Any?, property: KProperty<*>): T = invoke() -} diff --git a/java/tests/src/com/android/intentresolver/v2/data/repository/UserRepositoryImplTest.kt b/java/tests/src/com/android/intentresolver/v2/data/repository/UserRepositoryImplTest.kt deleted file mode 100644 index 4f514db5..00000000 --- a/java/tests/src/com/android/intentresolver/v2/data/repository/UserRepositoryImplTest.kt +++ /dev/null @@ -1,222 +0,0 @@ -package com.android.intentresolver.v2.data.repository - -import android.content.Intent -import android.content.pm.UserInfo -import android.os.UserHandle -import android.os.UserHandle.SYSTEM -import android.os.UserHandle.USER_SYSTEM -import android.os.UserManager -import com.android.intentresolver.mock -import com.android.intentresolver.v2.coroutines.collectLastValue -import com.android.intentresolver.v2.data.model.User -import com.android.intentresolver.v2.data.model.User.Role -import com.android.intentresolver.v2.platform.FakeUserManager -import com.android.intentresolver.v2.platform.FakeUserManager.ProfileType -import com.android.intentresolver.whenever -import com.google.common.truth.Truth.assertThat -import com.google.common.truth.Truth.assertWithMessage -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.runTest -import org.junit.Test -import org.mockito.Mockito -import org.mockito.Mockito.doReturn - -internal class UserRepositoryImplTest { - private val userManager = FakeUserManager() - private val userState = userManager.state - - @Test - fun initialization() = runTest { - val repo = createUserRepository(userManager) - val users by collectLastValue(repo.users) - - assertWithMessage("collectLastValue(repo.users)").that(users).isNotNull() - assertThat(users) - .containsExactly( - userState.primaryUserHandle, - User(userState.primaryUserHandle.identifier, Role.PERSONAL) - ) - } - - @Test - fun createProfile() = runTest { - val repo = createUserRepository(userManager) - val users by collectLastValue(repo.users) - - assertWithMessage("collectLastValue(repo.users)").that(users).isNotNull() - assertThat(users!!.values.filter { it.role.type == User.Type.PROFILE }).isEmpty() - - val profile = userState.createProfile(ProfileType.WORK) - assertThat(users).containsEntry(profile, User(profile.identifier, Role.WORK)) - } - - @Test - fun removeProfile() = runTest { - val repo = createUserRepository(userManager) - val users by collectLastValue(repo.users) - - assertWithMessage("collectLastValue(repo.users)").that(users).isNotNull() - val work = userState.createProfile(ProfileType.WORK) - assertThat(users).containsEntry(work, User(work.identifier, Role.WORK)) - - userState.removeProfile(work) - assertThat(users).doesNotContainEntry(work, User(work.identifier, Role.WORK)) - } - - @Test - fun isAvailable() = runTest { - val repo = createUserRepository(userManager) - val work = userState.createProfile(ProfileType.WORK) - - val available by collectLastValue(repo.isAvailable(work)) - assertThat(available).isTrue() - - userState.setQuietMode(work, true) - assertThat(available).isFalse() - - userState.setQuietMode(work, false) - assertThat(available).isTrue() - } - - @Test - fun requestState() = runTest { - val repo = createUserRepository(userManager) - val work = userState.createProfile(ProfileType.WORK) - - val available by collectLastValue(repo.isAvailable(work)) - assertThat(available).isTrue() - - repo.requestState(work, false) - assertThat(available).isFalse() - - repo.requestState(work, true) - assertThat(available).isTrue() - } - - @Test(expected = IllegalArgumentException::class) - fun requestState_invalidForFullUser() = runTest { - val repo = createUserRepository(userManager) - val primaryUser = User(userState.primaryUserHandle.identifier, Role.PERSONAL) - repo.requestState(primaryUser, available = false) - } - - /** - * This and all the 'recovers_from_*' tests below all configure a static event flow instead of - * using [FakeUserManager]. These tests verify that a invalid broadcast causes the flow to - * reinitialize with the user profile group. - */ - @Test - fun recovers_from_invalid_profile_added_event() = runTest { - val userManager = - mockUserManager(validUser = USER_SYSTEM, invalidUser = UserHandle.USER_NULL) - val events = - flowOf( - UserRepositoryImpl.UserEvent( - Intent.ACTION_PROFILE_ADDED, - UserHandle.of(UserHandle.USER_NULL) - ) - ) - val repo = - UserRepositoryImpl( - profileParent = SYSTEM, - userManager = userManager, - userEvents = events, - scope = backgroundScope, - backgroundDispatcher = Dispatchers.Unconfined - ) - val users by collectLastValue(repo.users) - - assertWithMessage("collectLastValue(repo.users)").that(users).isNotNull() - assertThat(users).containsExactly(SYSTEM, User(USER_SYSTEM, Role.PERSONAL)) - } - - @Test - fun recovers_from_invalid_profile_removed_event() = runTest { - val userManager = - mockUserManager(validUser = USER_SYSTEM, invalidUser = UserHandle.USER_NULL) - val events = - flowOf( - UserRepositoryImpl.UserEvent( - Intent.ACTION_PROFILE_REMOVED, - UserHandle.of(UserHandle.USER_NULL) - ) - ) - val repo = - UserRepositoryImpl( - profileParent = SYSTEM, - userManager = userManager, - userEvents = events, - scope = backgroundScope, - backgroundDispatcher = Dispatchers.Unconfined - ) - val users by collectLastValue(repo.users) - - assertWithMessage("collectLastValue(repo.users)").that(users).isNotNull() - assertThat(users).containsExactly(SYSTEM, User(USER_SYSTEM, Role.PERSONAL)) - } - - @Test - fun recovers_from_invalid_profile_available_event() = runTest { - val userManager = - mockUserManager(validUser = USER_SYSTEM, invalidUser = UserHandle.USER_NULL) - val events = - flowOf( - UserRepositoryImpl.UserEvent( - Intent.ACTION_PROFILE_AVAILABLE, - UserHandle.of(UserHandle.USER_NULL) - ) - ) - val repo = - UserRepositoryImpl(SYSTEM, userManager, events, backgroundScope, Dispatchers.Unconfined) - val users by collectLastValue(repo.users) - - assertWithMessage("collectLastValue(repo.users)").that(users).isNotNull() - assertThat(users).containsExactly(SYSTEM, User(USER_SYSTEM, Role.PERSONAL)) - } - - @Test - fun recovers_from_unknown_event() = runTest { - val userManager = - mockUserManager(validUser = USER_SYSTEM, invalidUser = UserHandle.USER_NULL) - val events = - flowOf( - UserRepositoryImpl.UserEvent("UNKNOWN_EVENT", UserHandle.of(UserHandle.USER_NULL)) - ) - val repo = - UserRepositoryImpl( - profileParent = SYSTEM, - userManager = userManager, - userEvents = events, - scope = backgroundScope, - backgroundDispatcher = Dispatchers.Unconfined - ) - val users by collectLastValue(repo.users) - - assertWithMessage("collectLastValue(repo.users)").that(users).isNotNull() - assertThat(users).containsExactly(SYSTEM, User(USER_SYSTEM, Role.PERSONAL)) - } -} - -@Suppress("SameParameterValue", "DEPRECATION") -private fun mockUserManager(validUser: Int, invalidUser: Int) = - mock<UserManager> { - val info = UserInfo(validUser, "", "", UserInfo.FLAG_FULL) - doReturn(listOf(info)).whenever(this).getEnabledProfiles(Mockito.anyInt()) - - doReturn(info).whenever(this).getUserInfo(Mockito.eq(validUser)) - - doReturn(listOf<UserInfo>()).whenever(this).getEnabledProfiles(Mockito.eq(invalidUser)) - - doReturn(null).whenever(this).getUserInfo(Mockito.eq(invalidUser)) - } - -private fun TestScope.createUserRepository(userManager: FakeUserManager) = - UserRepositoryImpl( - profileParent = userManager.state.primaryUserHandle, - userManager = userManager, - userEvents = userManager.state.userEvents, - scope = backgroundScope, - backgroundDispatcher = Dispatchers.Unconfined - ) diff --git a/java/tests/src/com/android/intentresolver/v2/emptystate/EmptyStateUiHelperTest.kt b/java/tests/src/com/android/intentresolver/v2/emptystate/EmptyStateUiHelperTest.kt deleted file mode 100644 index 696dd1fd..00000000 --- a/java/tests/src/com/android/intentresolver/v2/emptystate/EmptyStateUiHelperTest.kt +++ /dev/null @@ -1,228 +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.v2.emptystate - -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.FrameLayout -import android.widget.TextView -import androidx.test.platform.app.InstrumentationRegistry -import com.android.intentresolver.any -import com.android.intentresolver.emptystate.EmptyState -import com.android.intentresolver.mock -import com.google.common.truth.Truth.assertThat -import java.util.Optional -import java.util.function.Supplier -import org.junit.Before -import org.junit.Test -import org.mockito.Mockito.never -import org.mockito.Mockito.verify - -class EmptyStateUiHelperTest { - private val context = InstrumentationRegistry.getInstrumentation().getContext() - - var shouldOverrideContainerPadding = false - val containerPaddingSupplier = - Supplier<Optional<Int>> { - Optional.ofNullable(if (shouldOverrideContainerPadding) 42 else null) - } - - lateinit var rootContainer: ViewGroup - lateinit var mainListView: View // Visible when no empty state is showing. - lateinit var emptyStateTitleView: TextView - lateinit var emptyStateSubtitleView: TextView - lateinit var emptyStateButtonView: View - lateinit var emptyStateProgressView: View - lateinit var emptyStateDefaultTextView: View - lateinit var emptyStateContainerView: View - lateinit var emptyStateRootView: View - lateinit var emptyStateUiHelper: EmptyStateUiHelper - - @Before - fun setup() { - rootContainer = FrameLayout(context) - LayoutInflater.from(context) - .inflate( - com.android.intentresolver.R.layout.resolver_list_per_profile, - rootContainer, - true - ) - mainListView = rootContainer.requireViewById(com.android.internal.R.id.resolver_list) - emptyStateRootView = - rootContainer.requireViewById(com.android.internal.R.id.resolver_empty_state) - emptyStateTitleView = - rootContainer.requireViewById(com.android.internal.R.id.resolver_empty_state_title) - emptyStateSubtitleView = - rootContainer.requireViewById(com.android.internal.R.id.resolver_empty_state_subtitle) - emptyStateButtonView = - rootContainer.requireViewById(com.android.internal.R.id.resolver_empty_state_button) - emptyStateProgressView = - rootContainer.requireViewById(com.android.internal.R.id.resolver_empty_state_progress) - emptyStateDefaultTextView = rootContainer.requireViewById(com.android.internal.R.id.empty) - emptyStateContainerView = - rootContainer.requireViewById(com.android.internal.R.id.resolver_empty_state_container) - emptyStateUiHelper = - EmptyStateUiHelper( - rootContainer, - com.android.internal.R.id.resolver_list, - containerPaddingSupplier - ) - } - - @Test - fun testResetViewVisibilities() { - // First set each view's visibility to differ from the expected "reset" state so we can then - // assert that they're all reset afterward. - // TODO: for historic reasons "reset" doesn't cover `emptyStateContainerView`; should it? - emptyStateRootView.visibility = View.GONE - emptyStateTitleView.visibility = View.GONE - emptyStateSubtitleView.visibility = View.GONE - emptyStateButtonView.visibility = View.VISIBLE - emptyStateProgressView.visibility = View.VISIBLE - emptyStateDefaultTextView.visibility = View.VISIBLE - - emptyStateUiHelper.resetViewVisibilities() - - assertThat(emptyStateRootView.visibility).isEqualTo(View.VISIBLE) - assertThat(emptyStateTitleView.visibility).isEqualTo(View.VISIBLE) - assertThat(emptyStateSubtitleView.visibility).isEqualTo(View.VISIBLE) - assertThat(emptyStateButtonView.visibility).isEqualTo(View.INVISIBLE) - assertThat(emptyStateProgressView.visibility).isEqualTo(View.GONE) - assertThat(emptyStateDefaultTextView.visibility).isEqualTo(View.GONE) - } - - @Test - fun testShowSpinner() { - emptyStateTitleView.visibility = View.VISIBLE - emptyStateButtonView.visibility = View.VISIBLE - emptyStateProgressView.visibility = View.GONE - emptyStateDefaultTextView.visibility = View.VISIBLE - - emptyStateUiHelper.showSpinner() - - // TODO: should this cover any other views? Subtitle? - assertThat(emptyStateTitleView.visibility).isEqualTo(View.INVISIBLE) - assertThat(emptyStateButtonView.visibility).isEqualTo(View.INVISIBLE) - assertThat(emptyStateProgressView.visibility).isEqualTo(View.VISIBLE) - assertThat(emptyStateDefaultTextView.visibility).isEqualTo(View.GONE) - } - - @Test - fun testHide() { - emptyStateRootView.visibility = View.VISIBLE - mainListView.visibility = View.GONE - - emptyStateUiHelper.hide() - - assertThat(emptyStateRootView.visibility).isEqualTo(View.GONE) - assertThat(mainListView.visibility).isEqualTo(View.VISIBLE) - } - - @Test - fun testBottomPaddingDelegate_default() { - shouldOverrideContainerPadding = false - emptyStateContainerView.setPadding(1, 2, 3, 4) - - emptyStateUiHelper.setupContainerPadding() - - assertThat(emptyStateContainerView.paddingLeft).isEqualTo(1) - assertThat(emptyStateContainerView.paddingTop).isEqualTo(2) - assertThat(emptyStateContainerView.paddingRight).isEqualTo(3) - assertThat(emptyStateContainerView.paddingBottom).isEqualTo(4) - } - - @Test - fun testBottomPaddingDelegate_override() { - shouldOverrideContainerPadding = true // Set bottom padding to 42. - emptyStateContainerView.setPadding(1, 2, 3, 4) - - emptyStateUiHelper.setupContainerPadding() - - assertThat(emptyStateContainerView.paddingLeft).isEqualTo(1) - assertThat(emptyStateContainerView.paddingTop).isEqualTo(2) - assertThat(emptyStateContainerView.paddingRight).isEqualTo(3) - assertThat(emptyStateContainerView.paddingBottom).isEqualTo(42) - } - - @Test - fun testShowEmptyState_noOnClickHandler() { - mainListView.visibility = View.VISIBLE - - // Note: an `EmptyState.ClickListener` isn't invoked directly by the UI helper; it has to be - // built into the "on-click handler" that's injected to implement the button-press. We won't - // display the button without a click "handler," even if it *does* have a `ClickListener`. - val clickListener = mock<EmptyState.ClickListener>() - - val emptyState = - object : EmptyState { - override fun getTitle() = "Test title" - override fun getSubtitle() = "Test subtitle" - - override fun getButtonClickListener() = clickListener - } - emptyStateUiHelper.showEmptyState(emptyState, null) - - assertThat(mainListView.visibility).isEqualTo(View.GONE) - assertThat(emptyStateRootView.visibility).isEqualTo(View.VISIBLE) - assertThat(emptyStateTitleView.visibility).isEqualTo(View.VISIBLE) - assertThat(emptyStateSubtitleView.visibility).isEqualTo(View.VISIBLE) - assertThat(emptyStateButtonView.visibility).isEqualTo(View.GONE) - assertThat(emptyStateProgressView.visibility).isEqualTo(View.GONE) - assertThat(emptyStateDefaultTextView.visibility).isEqualTo(View.GONE) - - assertThat(emptyStateTitleView.text).isEqualTo("Test title") - assertThat(emptyStateSubtitleView.text).isEqualTo("Test subtitle") - - verify(clickListener, never()).onClick(any()) - } - - @Test - fun testShowEmptyState_withOnClickHandlerAndClickListener() { - mainListView.visibility = View.VISIBLE - - val clickListener = mock<EmptyState.ClickListener>() - val onClickHandler = mock<View.OnClickListener>() - - val emptyState = - object : EmptyState { - override fun getTitle() = "Test title" - override fun getSubtitle() = "Test subtitle" - - override fun getButtonClickListener() = clickListener - } - emptyStateUiHelper.showEmptyState(emptyState, onClickHandler) - - assertThat(mainListView.visibility).isEqualTo(View.GONE) - assertThat(emptyStateRootView.visibility).isEqualTo(View.VISIBLE) - assertThat(emptyStateTitleView.visibility).isEqualTo(View.VISIBLE) - assertThat(emptyStateSubtitleView.visibility).isEqualTo(View.VISIBLE) - assertThat(emptyStateButtonView.visibility).isEqualTo(View.VISIBLE) // Now shown. - assertThat(emptyStateProgressView.visibility).isEqualTo(View.GONE) - assertThat(emptyStateDefaultTextView.visibility).isEqualTo(View.GONE) - - assertThat(emptyStateTitleView.text).isEqualTo("Test title") - assertThat(emptyStateSubtitleView.text).isEqualTo("Test subtitle") - - emptyStateButtonView.performClick() - - verify(onClickHandler).onClick(emptyStateButtonView) - // The test didn't explicitly configure its `OnClickListener` to relay the click event on - // to the `EmptyState.ClickListener`, so it still won't have fired here. - verify(clickListener, never()).onClick(any()) - } -} diff --git a/java/tests/src/com/android/intentresolver/v2/listcontroller/ChooserRequestFilteredComponentsTest.kt b/java/tests/src/com/android/intentresolver/v2/listcontroller/ChooserRequestFilteredComponentsTest.kt deleted file mode 100644 index 59494bed..00000000 --- a/java/tests/src/com/android/intentresolver/v2/listcontroller/ChooserRequestFilteredComponentsTest.kt +++ /dev/null @@ -1,61 +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.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 deleted file mode 100644 index ce40567e..00000000 --- a/java/tests/src/com/android/intentresolver/v2/listcontroller/FakeResolverComparator.kt +++ /dev/null @@ -1,83 +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.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 deleted file mode 100644 index 396505e6..00000000 --- a/java/tests/src/com/android/intentresolver/v2/listcontroller/FilterableComponentsTest.kt +++ /dev/null @@ -1,77 +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.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 deleted file mode 100644 index 09f6d373..00000000 --- a/java/tests/src/com/android/intentresolver/v2/listcontroller/IntentResolverTest.kt +++ /dev/null @@ -1,499 +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.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 deleted file mode 100644 index ce5e52b1..00000000 --- a/java/tests/src/com/android/intentresolver/v2/listcontroller/LastChosenManagerTest.kt +++ /dev/null @@ -1,111 +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.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 deleted file mode 100644 index 112342ad..00000000 --- a/java/tests/src/com/android/intentresolver/v2/listcontroller/PinnableComponentsTest.kt +++ /dev/null @@ -1,74 +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.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 deleted file mode 100644 index 26f0199e..00000000 --- a/java/tests/src/com/android/intentresolver/v2/listcontroller/ResolveListDeduperTest.kt +++ /dev/null @@ -1,125 +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.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 deleted file mode 100644 index 9786b801..00000000 --- a/java/tests/src/com/android/intentresolver/v2/listcontroller/ResolvedComponentFilteringTest.kt +++ /dev/null @@ -1,309 +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.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 deleted file mode 100644 index 39b328ee..00000000 --- a/java/tests/src/com/android/intentresolver/v2/listcontroller/ResolvedComponentSortingTest.kt +++ /dev/null @@ -1,197 +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.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 deleted file mode 100644 index 9d6394fa..00000000 --- a/java/tests/src/com/android/intentresolver/v2/listcontroller/SharedPreferencesPinnedComponentsTest.kt +++ /dev/null @@ -1,63 +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.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() - } -} diff --git a/java/tests/src/com/android/intentresolver/v2/platform/FakeSecureSettings.kt b/java/tests/src/com/android/intentresolver/v2/platform/FakeSecureSettings.kt deleted file mode 100644 index 4e279623..00000000 --- a/java/tests/src/com/android/intentresolver/v2/platform/FakeSecureSettings.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.android.intentresolver.v2.platform - -/** - * Creates a SecureSettings instance with predefined values: - * - * val settings = fakeSecureSettings { - * putString("stringValue", "example") - * putInt("intValue", 42) - * } - */ -fun fakeSecureSettings(block: FakeSecureSettings.Builder.() -> Unit): SecureSettings { - return FakeSecureSettings.Builder().apply(block).build() -} - -/** An in memory implementation of [SecureSettings]. */ -class FakeSecureSettings private constructor(private val map: Map<String, String>) : - SecureSettings { - - override fun getString(name: String): String? = map[name] - override fun getInt(name: String): Int? = getString(name)?.toIntOrNull() - override fun getLong(name: String): Long? = getString(name)?.toLongOrNull() - override fun getFloat(name: String): Float? = getString(name)?.toFloatOrNull() - - class Builder { - private val map = mutableMapOf<String, String>() - - fun putString(name: String, value: String) { - map[name] = value - } - fun putInt(name: String, value: Int) { - map[name] = value.toString() - } - fun putLong(name: String, value: Long) { - map[name] = value.toString() - } - fun putFloat(name: String, value: Float) { - map[name] = value.toString() - } - - fun build(): SecureSettings { - return FakeSecureSettings(map.toMap()) - } - } -} diff --git a/java/tests/src/com/android/intentresolver/v2/platform/FakeSecureSettingsTest.kt b/java/tests/src/com/android/intentresolver/v2/platform/FakeSecureSettingsTest.kt deleted file mode 100644 index 04c7093d..00000000 --- a/java/tests/src/com/android/intentresolver/v2/platform/FakeSecureSettingsTest.kt +++ /dev/null @@ -1,61 +0,0 @@ -package com.android.intentresolver.v2.platform - -import com.google.common.truth.Truth.assertThat - -class FakeSecureSettingsTest { - - private val secureSettings = fakeSecureSettings { - putInt(intKey, intVal) - putString(stringKey, stringVal) - putFloat(floatKey, floatVal) - putLong(longKey, longVal) - } - - fun testExpectedValues_returned() { - assertThat(secureSettings.getInt(intKey)).isEqualTo(intVal) - assertThat(secureSettings.getString(stringKey)).isEqualTo(stringVal) - assertThat(secureSettings.getFloat(floatKey)).isEqualTo(floatVal) - assertThat(secureSettings.getLong(longKey)).isEqualTo(longVal) - } - - fun testUndefinedValues_returnNull() { - assertThat(secureSettings.getInt("unknown")).isNull() - assertThat(secureSettings.getString("unknown")).isNull() - assertThat(secureSettings.getFloat("unknown")).isNull() - assertThat(secureSettings.getLong("unknown")).isNull() - } - - /** - * FakeSecureSettings models the real secure settings by storing values in String form. The - * value is returned if/when it can be parsed from the string value, otherwise null. - */ - fun testMismatchedTypes() { - assertThat(secureSettings.getString(intKey)).isEqualTo(intVal.toString()) - assertThat(secureSettings.getString(floatKey)).isEqualTo(floatVal.toString()) - assertThat(secureSettings.getString(longKey)).isEqualTo(longVal.toString()) - - assertThat(secureSettings.getInt(stringKey)).isNull() - assertThat(secureSettings.getLong(stringKey)).isNull() - assertThat(secureSettings.getFloat(stringKey)).isNull() - - assertThat(secureSettings.getInt(longKey)).isNull() - assertThat(secureSettings.getFloat(longKey)).isNull() // TODO: verify Long.MAX > Float.MAX ? - - assertThat(secureSettings.getLong(floatKey)).isNull() // TODO: or is Float.MAX > Long.MAX? - assertThat(secureSettings.getInt(floatKey)).isNull() - } - - companion object Data { - const val intKey = "int" - const val intVal = Int.MAX_VALUE - - const val stringKey = "string" - const val stringVal = "String" - - const val floatKey = "float" - const val floatVal = Float.MAX_VALUE - - const val longKey = "long" - const val longVal = Long.MAX_VALUE - } -} diff --git a/java/tests/src/com/android/intentresolver/v2/platform/FakeUserManager.kt b/java/tests/src/com/android/intentresolver/v2/platform/FakeUserManager.kt deleted file mode 100644 index 370e5a00..00000000 --- a/java/tests/src/com/android/intentresolver/v2/platform/FakeUserManager.kt +++ /dev/null @@ -1,239 +0,0 @@ -package com.android.intentresolver.v2.platform - -import android.content.Context -import android.content.Intent.ACTION_MANAGED_PROFILE_AVAILABLE -import android.content.Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE -import android.content.Intent.ACTION_PROFILE_ADDED -import android.content.Intent.ACTION_PROFILE_AVAILABLE -import android.content.Intent.ACTION_PROFILE_REMOVED -import android.content.Intent.ACTION_PROFILE_UNAVAILABLE -import android.content.pm.UserInfo -import android.content.pm.UserInfo.FLAG_FULL -import android.content.pm.UserInfo.FLAG_INITIALIZED -import android.content.pm.UserInfo.FLAG_PROFILE -import android.content.pm.UserInfo.NO_PROFILE_GROUP_ID -import android.os.IUserManager -import android.os.UserHandle -import android.os.UserManager -import androidx.annotation.NonNull -import com.android.intentresolver.THROWS_EXCEPTION -import com.android.intentresolver.mock -import com.android.intentresolver.v2.data.repository.UserRepositoryImpl.UserEvent -import com.android.intentresolver.v2.platform.FakeUserManager.State -import com.android.intentresolver.whenever -import kotlin.random.Random -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.consumeAsFlow -import org.mockito.Mockito.RETURNS_SELF -import org.mockito.Mockito.doAnswer -import org.mockito.Mockito.doReturn -import org.mockito.Mockito.withSettings - -/** - * A stand-in for [UserManager] to support testing of data layer components which depend on it. - * - * This fake targets system applications which need to interact with any or all of the current - * user's associated profiles (as reported by [getEnabledProfiles]). Support for manipulating - * non-profile (full) secondary users (switching active foreground user, adding or removing users) - * is not included. - * - * Upon creation [FakeUserManager] contains a single primary (full) user with a randomized ID. This - * is available from [FakeUserManager.state] using [primaryUserHandle][State.primaryUserHandle] or - * [getPrimaryUser][State.getPrimaryUser]. - * - * To make state changes, use functions available from [FakeUserManager.state]: - * * [createProfile][State.createProfile] - * * [removeProfile][State.removeProfile] - * * [setQuietMode][State.setQuietMode] - * - * Any functionality not explicitly overridden here is guaranteed to throw an exception when - * accessed (access to the real system service is prevented). - */ -class FakeUserManager(val state: State = State()) : - UserManager(/* context = */ mockContext(), /* service = */ mockService()) { - - enum class ProfileType { - WORK, - CLONE, - PRIVATE - } - - override fun getProfileParent(userHandle: UserHandle): UserHandle? { - return state.getUserOrNull(userHandle)?.let { user -> - if (user.isProfile) { - state.getUserOrNull(UserHandle.of(user.profileGroupId))?.userHandle - } else { - null - } - } - } - - override fun getUserInfo(userId: Int): UserInfo? { - return state.getUserOrNull(UserHandle.of(userId)) - } - - @Suppress("OVERRIDE_DEPRECATION") - override fun getEnabledProfiles(userId: Int): List<UserInfo> { - val user = state.users.single { it.id == userId } - return state.users.filter { other -> - user.id == other.id || user.profileGroupId == other.profileGroupId - } - } - - override fun requestQuietModeEnabled( - enableQuietMode: Boolean, - @NonNull userHandle: UserHandle - ): Boolean { - state.setQuietMode(userHandle, enableQuietMode) - return true - } - - override fun isQuietModeEnabled(userHandle: UserHandle): Boolean { - return state.getUser(userHandle).isQuietModeEnabled - } - - override fun toString(): String { - return "FakeUserManager(state=$state)" - } - - class State { - private val eventChannel = Channel<UserEvent>() - private val userInfoMap: MutableMap<UserHandle, UserInfo> = mutableMapOf() - - /** The id of the primary/full/system user, which is automatically created. */ - val primaryUserHandle: UserHandle - - /** - * Retrieves the primary user. The value returned changes, but the values are immutable. - * - * Do not cache this value in tests, between operations. - */ - fun getPrimaryUser(): UserInfo = getUser(primaryUserHandle) - - private var nextUserId: Int = 100 + Random.nextInt(0, 900) - - /** - * A flow of [UserEvent] which emulates those normally generated from system broadcasts. - * - * Events are produced by calls to [createPrimaryUser], [createProfile], [removeProfile]. - */ - val userEvents: Flow<UserEvent> - - val users: List<UserInfo> - get() = userInfoMap.values.toList() - - val userHandles: List<UserHandle> - get() = userInfoMap.keys.toList() - - init { - primaryUserHandle = createPrimaryUser(allocateNextId()) - userEvents = eventChannel.consumeAsFlow() - } - - private fun allocateNextId() = nextUserId++ - - private fun createPrimaryUser(id: Int): UserHandle { - val userInfo = - UserInfo(id, "", "", FLAG_INITIALIZED or FLAG_FULL, USER_TYPE_FULL_SYSTEM) - userInfoMap[userInfo.userHandle] = userInfo - return userInfo.userHandle - } - - fun getUserOrNull(handle: UserHandle): UserInfo? = userInfoMap[handle] - - fun getUser(handle: UserHandle): UserInfo = - requireNotNull(getUserOrNull(handle)) { - "Expected userInfoMap to contain an entry for $handle" - } - - fun setQuietMode(user: UserHandle, quietMode: Boolean) { - userInfoMap[user]?.also { - it.flags = - if (quietMode) { - it.flags or UserInfo.FLAG_QUIET_MODE - } else { - it.flags and UserInfo.FLAG_QUIET_MODE.inv() - } - val actions = mutableListOf<String>() - if (quietMode) { - actions += ACTION_PROFILE_UNAVAILABLE - if (it.isManagedProfile) { - actions += ACTION_MANAGED_PROFILE_UNAVAILABLE - } - } else { - actions += ACTION_PROFILE_AVAILABLE - if (it.isManagedProfile) { - actions += ACTION_MANAGED_PROFILE_AVAILABLE - } - } - actions.forEach { action -> - eventChannel.trySend(UserEvent(action, user, quietMode)) - } - } - } - - fun createProfile(type: ProfileType, parent: UserHandle = primaryUserHandle): UserHandle { - val parentUser = getUser(parent) - require(!parentUser.isProfile) { "Parent user cannot be a profile" } - - // Ensure the parent user has a valid profileGroupId - if (parentUser.profileGroupId == NO_PROFILE_GROUP_ID) { - parentUser.profileGroupId = parentUser.id - } - val id = allocateNextId() - val userInfo = - UserInfo(id, "", "", FLAG_INITIALIZED or FLAG_PROFILE, type.toUserType()).apply { - profileGroupId = parentUser.profileGroupId - } - userInfoMap[userInfo.userHandle] = userInfo - eventChannel.trySend(UserEvent(ACTION_PROFILE_ADDED, userInfo.userHandle)) - return userInfo.userHandle - } - - fun removeProfile(handle: UserHandle): Boolean { - return userInfoMap[handle]?.let { user -> - require(user.isProfile) { "Only profiles can be removed" } - userInfoMap.remove(user.userHandle) - eventChannel.trySend(UserEvent(ACTION_PROFILE_REMOVED, user.userHandle)) - return true - } - ?: false - } - - override fun toString() = buildString { - append("State(nextUserId=$nextUserId, userInfoMap=[") - userInfoMap.entries.forEach { - append("UserHandle[${it.key.identifier}] = ${it.value.debugString},") - } - append("])") - } - } -} - -/** A safe mock of [Context] which throws on any unstubbed method call. */ -private fun mockContext(user: UserHandle = UserHandle.SYSTEM): Context { - return mock<Context>(withSettings().defaultAnswer(THROWS_EXCEPTION)) { - doAnswer(RETURNS_SELF).whenever(this).applicationContext - doReturn(user).whenever(this).user - doReturn(user.identifier).whenever(this).userId - } -} - -private fun FakeUserManager.ProfileType.toUserType(): String { - return when (this) { - FakeUserManager.ProfileType.WORK -> UserManager.USER_TYPE_PROFILE_MANAGED - FakeUserManager.ProfileType.CLONE -> UserManager.USER_TYPE_PROFILE_CLONE - FakeUserManager.ProfileType.PRIVATE -> UserManager.USER_TYPE_PROFILE_PRIVATE - } -} - -/** A safe mock of [IUserManager] which throws on any unstubbed method call. */ -fun mockService(): IUserManager { - return mock<IUserManager>(withSettings().defaultAnswer(THROWS_EXCEPTION)) -} - -val UserInfo.debugString: String - get() = - "UserInfo(id=$id, profileGroupId=$profileGroupId, name=$name, " + - "type=$userType, flags=${UserInfo.flagsToString(flags)})" diff --git a/java/tests/src/com/android/intentresolver/v2/platform/FakeUserManagerTest.kt b/java/tests/src/com/android/intentresolver/v2/platform/FakeUserManagerTest.kt deleted file mode 100644 index a2239192..00000000 --- a/java/tests/src/com/android/intentresolver/v2/platform/FakeUserManagerTest.kt +++ /dev/null @@ -1,128 +0,0 @@ -package com.android.intentresolver.v2.platform - -import android.content.pm.UserInfo -import android.content.pm.UserInfo.NO_PROFILE_GROUP_ID -import android.os.UserHandle -import android.os.UserManager -import com.android.intentresolver.v2.platform.FakeUserManager.ProfileType -import com.google.common.truth.Correspondence -import com.google.common.truth.Truth.assertThat -import com.google.common.truth.Truth.assertWithMessage -import org.junit.Assert.assertTrue -import org.junit.Test - -class FakeUserManagerTest { - private val userManager = FakeUserManager() - private val state = userManager.state - - @Test - fun initialState() { - val personal = userManager.getEnabledProfiles(state.primaryUserHandle.identifier).single() - - assertThat(personal.id).isEqualTo(state.primaryUserHandle.identifier) - assertThat(personal.userType).isEqualTo(UserManager.USER_TYPE_FULL_SYSTEM) - assertThat(personal.flags and UserInfo.FLAG_FULL).isEqualTo(UserInfo.FLAG_FULL) - } - - @Test - fun getProfileParent() { - val workHandle = state.createProfile(ProfileType.WORK) - - assertThat(userManager.getProfileParent(state.primaryUserHandle)).isNull() - assertThat(userManager.getProfileParent(workHandle)).isEqualTo(state.primaryUserHandle) - assertThat(userManager.getProfileParent(UserHandle.of(-1))).isNull() - } - - @Test - fun getUserInfo() { - val personalUser = - requireNotNull(userManager.getUserInfo(state.primaryUserHandle.identifier)) { - "Expected getUserInfo to return non-null" - } - assertTrue(userInfoAreEqual.apply(personalUser, state.getPrimaryUser())) - - val workHandle = state.createProfile(ProfileType.WORK) - - val workUser = - requireNotNull(userManager.getUserInfo(workHandle.identifier)) { - "Expected getUserInfo to return non-null" - } - assertTrue( - userInfoAreEqual.apply(workUser, userManager.getUserInfo(workHandle.identifier)!!) - ) - } - - @Test - fun getEnabledProfiles_usingParentId() { - val personal = state.primaryUserHandle - val work = state.createProfile(ProfileType.WORK) - val private = state.createProfile(ProfileType.PRIVATE) - - val enabledProfiles = userManager.getEnabledProfiles(personal.identifier) - - assertWithMessage("enabledProfiles: List<UserInfo>") - .that(enabledProfiles) - .comparingElementsUsing(userInfoEquality) - .displayingDiffsPairedBy { it.id } - .containsExactly(state.getPrimaryUser(), state.getUser(work), state.getUser(private)) - } - - @Test - fun getEnabledProfiles_usingProfileId() { - val clone = state.createProfile(ProfileType.CLONE) - - val enabledProfiles = userManager.getEnabledProfiles(clone.identifier) - - assertWithMessage("getEnabledProfiles(clone.identifier)") - .that(enabledProfiles) - .comparingElementsUsing(userInfoEquality) - .displayingDiffsPairedBy { it.id } - .containsExactly(state.getPrimaryUser(), state.getUser(clone)) - } - - @Test - fun getUserOrNull() { - val personal = state.getPrimaryUser() - - assertThat(state.getUserOrNull(personal.userHandle)).isEqualTo(personal) - assertThat(state.getUserOrNull(UserHandle.of(personal.id - 1))).isNull() - } - - @Test - fun createProfile() { - // Order dependent: profile creation modifies the primary user - val workHandle = state.createProfile(ProfileType.WORK) - - val primaryUser = state.getPrimaryUser() - val workUser = state.getUser(workHandle) - - assertThat(primaryUser.profileGroupId).isNotEqualTo(NO_PROFILE_GROUP_ID) - assertThat(workUser.profileGroupId).isEqualTo(primaryUser.profileGroupId) - } - - @Test - fun removeProfile() { - val personal = state.getPrimaryUser() - val work = state.createProfile(ProfileType.WORK) - val private = state.createProfile(ProfileType.PRIVATE) - - state.removeProfile(private) - assertThat(state.userHandles).containsExactly(personal.userHandle, work) - } - - @Test(expected = IllegalArgumentException::class) - fun removeProfile_primaryNotAllowed() { - state.removeProfile(state.primaryUserHandle) - } -} - -private val userInfoAreEqual = - Correspondence.BinaryPredicate<UserInfo, UserInfo> { actual, expected -> - actual.id == expected.id && - actual.profileGroupId == expected.profileGroupId && - actual.userType == expected.userType && - actual.flags == expected.flags - } - -val userInfoEquality: Correspondence<UserInfo, UserInfo> = - Correspondence.from(userInfoAreEqual, "==") diff --git a/java/tests/src/com/android/intentresolver/v2/platform/NearbyShareModuleTest.kt b/java/tests/src/com/android/intentresolver/v2/platform/NearbyShareModuleTest.kt deleted file mode 100644 index fd5c8b3f..00000000 --- a/java/tests/src/com/android/intentresolver/v2/platform/NearbyShareModuleTest.kt +++ /dev/null @@ -1,83 +0,0 @@ -package com.android.intentresolver.v2.platform - -import android.content.ComponentName -import android.content.Context -import android.content.res.Configuration -import android.provider.Settings -import android.testing.TestableResources - -import androidx.test.platform.app.InstrumentationRegistry - -import com.android.intentresolver.R - -import com.google.common.truth.Truth8.assertThat - -import org.junit.Before -import org.junit.Test - -class NearbyShareModuleTest { - - lateinit var context: Context - - /** Create Resources with overridden values. */ - private fun Context.fakeResources( - config: Configuration? = null, - block: TestableResources.() -> Unit - ) = - TestableResources(resources) - .apply { config?.let { overrideConfiguration(it) } } - .apply(block) - .resources - - @Before - fun setup() { - val instr = InstrumentationRegistry.getInstrumentation() - context = instr.context - } - - @Test - fun valueIsAbsent_whenUnset() { - val secureSettings = fakeSecureSettings {} - val resources = - context.fakeResources { addOverride(R.string.config_defaultNearbySharingComponent, "") } - - val componentName = NearbyShareModule.nearbyShareComponent(resources, secureSettings) - assertThat(componentName).isEmpty() - } - - @Test - fun defaultValue_readFromResources() { - val secureSettings = fakeSecureSettings {} - val resources = - context.fakeResources { - addOverride( - R.string.config_defaultNearbySharingComponent, - "com.example/.ComponentName" - ) - } - - val nearbyShareComponent = NearbyShareModule.nearbyShareComponent(resources, secureSettings) - - assertThat(nearbyShareComponent).hasValue( - ComponentName.unflattenFromString("com.example/.ComponentName")) - } - - @Test - fun secureSettings_overridesDefault() { - val secureSettings = fakeSecureSettings { - putString(Settings.Secure.NEARBY_SHARING_COMPONENT, "com.example/.BComponent") - } - val resources = - context.fakeResources { - addOverride( - R.string.config_defaultNearbySharingComponent, - "com.example/.AComponent" - ) - } - - val nearbyShareComponent = NearbyShareModule.nearbyShareComponent(resources, secureSettings) - - assertThat(nearbyShareComponent).hasValue( - ComponentName.unflattenFromString("com.example/.BComponent")) - } -} diff --git a/java/tests/src/com/android/intentresolver/widget/BatchPreviewLoaderTest.kt b/java/tests/src/com/android/intentresolver/widget/BatchPreviewLoaderTest.kt deleted file mode 100644 index 4f4223c0..00000000 --- a/java/tests/src/com/android/intentresolver/widget/BatchPreviewLoaderTest.kt +++ /dev/null @@ -1,211 +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.widget - -import android.graphics.Bitmap -import android.net.Uri -import com.android.intentresolver.captureMany -import com.android.intentresolver.mock -import com.android.intentresolver.widget.ScrollableImagePreviewView.BatchPreviewLoader -import com.android.intentresolver.widget.ScrollableImagePreviewView.Preview -import com.android.intentresolver.widget.ScrollableImagePreviewView.PreviewType -import com.android.intentresolver.withArgCaptor -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.cancel -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.asFlow -import kotlinx.coroutines.launch -import kotlinx.coroutines.test.UnconfinedTestDispatcher -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.mockito.Mockito.atLeast -import org.mockito.Mockito.times -import org.mockito.Mockito.verify - -@OptIn(ExperimentalCoroutinesApi::class) -class BatchPreviewLoaderTest { - private val dispatcher = UnconfinedTestDispatcher() - private val testScope = CoroutineScope(dispatcher) - private val onCompletion = mock<() -> Unit>() - private val onUpdate = mock<(List<Preview>) -> Unit>() - - @Before - fun setup() { - Dispatchers.setMain(dispatcher) - } - - @After - fun cleanup() { - testScope.cancel() - Dispatchers.resetMain() - } - - @Test - fun test_allImagesWithinViewPort_oneUpdate() { - val imageLoader = TestImageLoader(testScope) - val uriOne = createUri(1) - val uriTwo = createUri(2) - imageLoader.setUriLoadingOrder(succeed(uriTwo), succeed(uriOne)) - val testSubject = - BatchPreviewLoader( - imageLoader, - previews(uriOne, uriTwo), - totalItemCount = 2, - onUpdate, - onCompletion - ) - testSubject.loadAspectRatios(200) { _, _, _ -> 100 } - dispatcher.scheduler.advanceUntilIdle() - - verify(onCompletion, times(1)).invoke() - val list = withArgCaptor { verify(onUpdate, times(1)).invoke(capture()) }.map { it.uri } - assertThat(list).containsExactly(uriOne, uriTwo).inOrder() - } - - @Test - fun test_allImagesWithinViewPortOneFailed_failedPreviewIsNotUpdated() { - val imageLoader = TestImageLoader(testScope) - val uriOne = createUri(1) - val uriTwo = createUri(2) - val uriThree = createUri(3) - imageLoader.setUriLoadingOrder(succeed(uriThree), fail(uriTwo), succeed(uriOne)) - val testSubject = - BatchPreviewLoader( - imageLoader, - previews(uriOne, uriTwo, uriThree), - totalItemCount = 3, - onUpdate, - onCompletion - ) - testSubject.loadAspectRatios(200) { _, _, _ -> 100 } - dispatcher.scheduler.advanceUntilIdle() - - verify(onCompletion, times(1)).invoke() - val list = withArgCaptor { verify(onUpdate, times(1)).invoke(capture()) }.map { it.uri } - assertThat(list).containsExactly(uriOne, uriThree).inOrder() - } - - @Test - fun test_imagesLoadedNotInOrder_updatedInOrder() { - val imageLoader = TestImageLoader(testScope) - val uris = Array(10) { createUri(it) } - val loadingOrder = - Array(uris.size) { i -> - val uriIdx = - when { - i % 2 == 1 -> i - 1 - i % 2 == 0 && i < uris.size - 1 -> i + 1 - else -> i - } - succeed(uris[uriIdx]) - } - imageLoader.setUriLoadingOrder(*loadingOrder) - val testSubject = - BatchPreviewLoader(imageLoader, previews(*uris), uris.size, onUpdate, onCompletion) - testSubject.loadAspectRatios(200) { _, _, _ -> 100 } - dispatcher.scheduler.advanceUntilIdle() - - verify(onCompletion, times(1)).invoke() - val list = - captureMany { verify(onUpdate, atLeast(1)).invoke(capture()) } - .fold(ArrayList<Preview>()) { acc, update -> acc.apply { addAll(update) } } - .map { it.uri } - assertThat(list).containsExactly(*uris).inOrder() - } - - @Test - fun test_imagesLoadedNotInOrderSomeFailed_updatedInOrder() { - val imageLoader = TestImageLoader(testScope) - val uris = Array(10) { createUri(it) } - val loadingOrder = - Array(uris.size) { i -> - val uriIdx = - when { - i % 2 == 1 -> i - 1 - i % 2 == 0 && i < uris.size - 1 -> i + 1 - else -> i - } - if (uriIdx % 2 == 0) fail(uris[uriIdx]) else succeed(uris[uriIdx]) - } - val expectedUris = Array(uris.size / 2) { createUri(it * 2 + 1) } - imageLoader.setUriLoadingOrder(*loadingOrder) - val testSubject = - BatchPreviewLoader(imageLoader, previews(*uris), uris.size, onUpdate, onCompletion) - testSubject.loadAspectRatios(200) { _, _, _ -> 100 } - dispatcher.scheduler.advanceUntilIdle() - - verify(onCompletion, times(1)).invoke() - val list = - captureMany { verify(onUpdate, atLeast(1)).invoke(capture()) } - .fold(ArrayList<Preview>()) { acc, update -> acc.apply { addAll(update) } } - .map { it.uri } - assertThat(list).containsExactly(*expectedUris).inOrder() - } - - private fun createUri(idx: Int): Uri = Uri.parse("content://org.pkg.app/image-$idx.png") - - private fun fail(uri: Uri) = uri to false - private fun succeed(uri: Uri) = uri to true - private fun previews(vararg uris: Uri) = - uris - .fold(ArrayList<Preview>(uris.size)) { acc, uri -> - acc.apply { add(Preview(PreviewType.Image, uri, editAction = null)) } - } - .asFlow() -} - -private class TestImageLoader(scope: CoroutineScope) : suspend (Uri, Boolean) -> Bitmap? { - private val loadingOrder = ArrayDeque<Pair<Uri, Boolean>>() - private val pendingRequests = LinkedHashMap<Uri, CompletableDeferred<Bitmap?>>() - private val flow = MutableSharedFlow<Unit>(replay = 1) - private val bitmap by lazy { Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888) } - - init { - scope.launch { - flow.collect { - while (true) { - val (nextUri, isLoaded) = loadingOrder.firstOrNull() ?: break - val deferred = pendingRequests.remove(nextUri) ?: break - loadingOrder.removeFirst() - deferred.complete(if (isLoaded) bitmap else null) - } - if (loadingOrder.isEmpty()) { - pendingRequests.forEach { (uri, deferred) -> deferred.complete(bitmap) } - pendingRequests.clear() - } - } - } - } - - fun setUriLoadingOrder(vararg uris: Pair<Uri, Boolean>) { - loadingOrder.clear() - loadingOrder.addAll(uris) - } - - override suspend fun invoke(uri: Uri, cache: Boolean): Bitmap? { - val deferred = pendingRequests.getOrPut(uri) { CompletableDeferred() } - flow.tryEmit(Unit) - return deferred.await() - } -} |