summaryrefslogtreecommitdiff
path: root/java/tests
diff options
context:
space:
mode:
author Xin Li <delphij@google.com> 2023-08-14 15:42:50 -0700
committer Xin Li <delphij@google.com> 2023-08-14 15:42:50 -0700
commit80bfff1f0eef6db4e061d9892450737e110bad59 (patch)
treee586dfb61c90c1aec759f551cbac857895edaf83 /java/tests
parenta0c4477fdbbae9857ead667a5227c3d93d9d3167 (diff)
parent5e86e4846a4326b05fa747eaf7f1ba0fa89cd623 (diff)
Merge Android U (ab/10368041)
Bug: 291102124 Merged-In: If40fe5329a6f3835d1edaebdef2ff47a947a9943 Change-Id: Ide948ae0ca758570ba7dae69fdcaa6c066522a20
Diffstat (limited to 'java/tests')
-rw-r--r--java/tests/Android.bp2
-rw-r--r--java/tests/AndroidManifest.xml4
-rw-r--r--java/tests/src/com/android/intentresolver/AnnotatedUserHandlesTest.kt79
-rw-r--r--java/tests/src/com/android/intentresolver/ChooserActionFactoryTest.kt22
-rw-r--r--java/tests/src/com/android/intentresolver/ChooserActivityOverrideData.java24
-rw-r--r--java/tests/src/com/android/intentresolver/ChooserListAdapterTest.kt87
-rw-r--r--java/tests/src/com/android/intentresolver/ChooserRefinementManagerTest.kt225
-rw-r--r--java/tests/src/com/android/intentresolver/ChooserRequestParametersTest.kt88
-rw-r--r--java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java52
-rw-r--r--java/tests/src/com/android/intentresolver/ImagePreviewImageLoaderTest.kt101
-rw-r--r--java/tests/src/com/android/intentresolver/ResolverActivityTest.java475
-rw-r--r--java/tests/src/com/android/intentresolver/ResolverDataProvider.java48
-rw-r--r--java/tests/src/com/android/intentresolver/ResolverWrapperActivity.java131
-rw-r--r--java/tests/src/com/android/intentresolver/ResolverWrapperAdapter.java84
-rw-r--r--java/tests/src/com/android/intentresolver/ShortcutSelectionLogicTest.kt9
-rw-r--r--java/tests/src/com/android/intentresolver/TestContentPreviewViewModel.kt56
-rw-r--r--java/tests/src/com/android/intentresolver/TestContentProvider.kt55
-rw-r--r--java/tests/src/com/android/intentresolver/TestPreviewImageLoader.kt19
-rw-r--r--java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java571
-rw-r--r--java/tests/src/com/android/intentresolver/UnbundledChooserActivityWorkProfileTest.java19
-rw-r--r--java/tests/src/com/android/intentresolver/chooser/ImmutableTargetInfoTest.kt12
-rw-r--r--java/tests/src/com/android/intentresolver/chooser/TargetInfoTest.kt9
-rw-r--r--java/tests/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt233
-rw-r--r--java/tests/src/com/android/intentresolver/contentpreview/ContentPreviewUiTest.kt41
-rw-r--r--java/tests/src/com/android/intentresolver/contentpreview/HeadlineGeneratorImplTest.kt61
-rw-r--r--java/tests/src/com/android/intentresolver/contentpreview/ImagePreviewImageLoaderTest.kt366
-rw-r--r--java/tests/src/com/android/intentresolver/contentpreview/PreviewDataProviderTest.kt351
-rw-r--r--java/tests/src/com/android/intentresolver/model/AbstractResolverComparatorTest.java80
-rw-r--r--java/tests/src/com/android/intentresolver/shortcuts/ShortcutLoaderTest.kt230
-rw-r--r--java/tests/src/com/android/intentresolver/util/UriFiltersTest.kt95
-rw-r--r--java/tests/src/com/android/intentresolver/widget/BatchPreviewLoaderTest.kt215
31 files changed, 2935 insertions, 909 deletions
diff --git a/java/tests/Android.bp b/java/tests/Android.bp
index 4e835ec8..c381d0a8 100644
--- a/java/tests/Android.bp
+++ b/java/tests/Android.bp
@@ -29,12 +29,10 @@ android_test {
"androidx.lifecycle_lifecycle-runtime-ktx",
"truth-prebuilt",
"testables",
- "testng",
"kotlinx_coroutines_test",
],
test_suites: ["general-tests"],
sdk_version: "core_platform",
- platform_apis: true,
compile_multilib: "both",
dont_merge_manifests: true,
diff --git a/java/tests/AndroidManifest.xml b/java/tests/AndroidManifest.xml
index 306eccb9..05830c4c 100644
--- a/java/tests/AndroidManifest.xml
+++ b/java/tests/AndroidManifest.xml
@@ -29,6 +29,10 @@
<uses-library android:name="android.test.runner" />
<activity android:name="com.android.intentresolver.ChooserWrapperActivity" />
<activity android:name="com.android.intentresolver.ResolverWrapperActivity" />
+ <provider
+ android:authorities="com.android.intentresolver.tests"
+ android:name="com.android.intentresolver.TestContentProvider"
+ android:grantUriPermissions="true" />
</application>
<instrumentation android:name="android.testing.TestableInstrumentation"
diff --git a/java/tests/src/com/android/intentresolver/AnnotatedUserHandlesTest.kt b/java/tests/src/com/android/intentresolver/AnnotatedUserHandlesTest.kt
new file mode 100644
index 00000000..a17a560c
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/AnnotatedUserHandlesTest.kt
@@ -0,0 +1,79 @@
+/*
+ * 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
index af134fcd..d72c9aa6 100644
--- a/java/tests/src/com/android/intentresolver/ChooserActionFactoryTest.kt
+++ b/java/tests/src/com/android/intentresolver/ChooserActionFactoryTest.kt
@@ -29,7 +29,6 @@ import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.android.intentresolver.flags.FeatureFlagRepository
-import com.android.intentresolver.flags.Flags
import com.google.common.collect.ImmutableList
import com.google.common.truth.Truth.assertThat
import org.junit.After
@@ -50,6 +49,7 @@ class ChooserActionFactoryTest {
private val logger = mock<ChooserActivityLogger>()
private val flags = mock<FeatureFlagRepository>()
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() {
@@ -69,7 +69,6 @@ class ChooserActionFactoryTest {
@Before
fun setup() {
- whenever(flags.isEnabled(Flags.SHARESHEET_RESELECTION_ACTION)).thenReturn(true)
context.registerReceiver(testReceiver, IntentFilter(testAction))
}
@@ -104,18 +103,11 @@ class ChooserActionFactoryTest {
}
@Test
- fun testNoModifyShareAction_flagDisabled() {
- whenever(flags.isEnabled(Flags.SHARESHEET_RESELECTION_ACTION)).thenReturn(false)
- val factory = createFactory(includeModifyShare = true)
-
- assertThat(factory.modifyShareAction).isNull()
- }
-
- @Test
fun testModifyShareAction() {
val factory = createFactory(includeModifyShare = true)
- factory.modifyShareAction!!.run()
+ val action = factory.modifyShareAction ?: error("Modify share action should not be null")
+ action.onClicked.run()
Mockito.verify(logger).logActionSelected(
eq(ChooserActivityLogger.SELECTION_TYPE_MODIFY_SHARE))
@@ -137,13 +129,17 @@ class ChooserActionFactoryTest {
whenever(chooserRequest.chooserActions).thenReturn(ImmutableList.of(action))
if (includeModifyShare) {
- whenever(chooserRequest.modifyShareAction).thenReturn(testPendingIntent)
+ val modifyShare = ChooserAction.Builder(
+ Icon.createWithResource("", Resources.ID_NULL),
+ modifyShareLabel,
+ testPendingIntent
+ ).build()
+ whenever(chooserRequest.modifyShareAction).thenReturn(modifyShare)
}
return ChooserActionFactory(
context,
chooserRequest,
- flags,
mock<ChooserIntegratedDeviceComponents>(),
logger,
Consumer<Boolean>{},
diff --git a/java/tests/src/com/android/intentresolver/ChooserActivityOverrideData.java b/java/tests/src/com/android/intentresolver/ChooserActivityOverrideData.java
index f0c459e5..ce96ef63 100644
--- a/java/tests/src/com/android/intentresolver/ChooserActivityOverrideData.java
+++ b/java/tests/src/com/android/intentresolver/ChooserActivityOverrideData.java
@@ -24,12 +24,11 @@ import static org.mockito.Mockito.when;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.Cursor;
-import android.graphics.Bitmap;
import android.os.UserHandle;
import com.android.intentresolver.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
-import com.android.intentresolver.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
import com.android.intentresolver.chooser.TargetInfo;
+import com.android.intentresolver.contentpreview.ImageLoader;
import com.android.intentresolver.flags.FeatureFlagRepository;
import com.android.intentresolver.shortcuts.ShortcutLoader;
@@ -55,35 +54,35 @@ public class ChooserActivityOverrideData {
@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 boolean isImageType;
public Cursor resolverCursor;
public boolean resolverForceException;
- public Bitmap previewThumbnail;
+ public ImageLoader imageLoader;
public ChooserActivityLogger chooserActivityLogger;
public int alternateProfileSetting;
public Resources resources;
public UserHandle workProfileUserHandle;
+ public UserHandle cloneProfileUserHandle;
+ public UserHandle tabOwnerUserHandleForLaunch;
public boolean hasCrossProfileIntents;
public boolean isQuietModeEnabled;
public Integer myUserId;
public WorkProfileAvailabilityManager mWorkProfileAvailability;
- public MyUserIdProvider mMyUserIdProvider;
public CrossProfileIntentsChecker mCrossProfileIntentsChecker;
public PackageManager packageManager;
public FeatureFlagRepository featureFlagRepository;
public void reset() {
- onSafelyStartCallback = null;
+ onSafelyStartInternalCallback = null;
isVoiceInteraction = null;
createPackageManager = null;
- previewThumbnail = null;
- isImageType = false;
+ imageLoader = null;
resolverCursor = null;
resolverForceException = false;
resolverListController = mock(ChooserActivity.ChooserListController.class);
@@ -92,6 +91,8 @@ public class ChooserActivityOverrideData {
alternateProfileSetting = 0;
resources = null;
workProfileUserHandle = null;
+ cloneProfileUserHandle = null;
+ tabOwnerUserHandleForLaunch = null;
hasCrossProfileIntents = true;
isQuietModeEnabled = false;
myUserId = null;
@@ -122,13 +123,6 @@ public class ChooserActivityOverrideData {
};
shortcutLoaderFactory = ((userHandle, resultConsumer) -> null);
- mMyUserIdProvider = new MyUserIdProvider() {
- @Override
- public int getMyUserId() {
- return myUserId != null ? myUserId : UserHandle.myUserId();
- }
- };
-
mCrossProfileIntentsChecker = mock(CrossProfileIntentsChecker.class);
when(mCrossProfileIntentsChecker.hasCrossProfileIntents(any(), anyInt(), anyInt()))
.thenAnswer(invocation -> hasCrossProfileIntents);
diff --git a/java/tests/src/com/android/intentresolver/ChooserListAdapterTest.kt b/java/tests/src/com/android/intentresolver/ChooserListAdapterTest.kt
index 58f6b733..4612b430 100644
--- a/java/tests/src/com/android/intentresolver/ChooserListAdapterTest.kt
+++ b/java/tests/src/com/android/intentresolver/ChooserListAdapterTest.kt
@@ -20,16 +20,17 @@ import android.content.ComponentName
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.PackageManager.ResolveInfoFlags
+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.ChooserListAdapter.LoadDirectShareIconTask
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.internal.R
import org.junit.Before
import org.junit.Test
@@ -39,43 +40,43 @@ import org.mockito.Mockito.verify
@RunWith(AndroidJUnit4::class)
class ChooserListAdapterTest {
- private val packageManager = mock<PackageManager> {
- whenever(
- resolveActivity(any(), any<ResolveInfoFlags>())
- ).thenReturn(mock())
- }
- private val context = InstrumentationRegistry.getInstrumentation().getContext()
+ 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 chooserActivityLogger = mock<ChooserActivityLogger>()
+ private val mTargetDataLoader = mock<TargetDataLoader>()
- private fun createChooserListAdapter(
- taskProvider: (TargetInfo?) -> LoadDirectShareIconTask
- ) = object : ChooserListAdapter(
+ private val testSubject by lazy {
+ ChooserListAdapter(
context,
emptyList(),
emptyArray(),
emptyList(),
false,
resolverListController,
- null,
+ userHandle,
Intent(),
mock(),
packageManager,
chooserActivityLogger,
mock(),
- 0
- ) {
- override fun createLoadDirectShareIconTask(
- info: SelectableTargetInfo
- ): LoadDirectShareIconTask = taskProvider(info)
- }
+ 0,
+ null,
+ mTargetDataLoader
+ )
+ }
@Before
fun setup() {
// ChooserListAdapter reads DeviceConfig and needs a permission for that.
- InstrumentationRegistry
- .getInstrumentation()
- .getUiAutomation()
+ InstrumentationRegistry.getInstrumentation()
+ .uiAutomation
.adoptShellPermissionIdentity("android.permission.READ_DEVICE_CONFIG")
}
@@ -85,41 +86,56 @@ class ChooserListAdapterTest {
val viewHolder = ResolverListAdapter.ViewHolder(view)
view.tag = viewHolder
val targetInfo = createSelectableTargetInfo()
- val iconTask = mock<LoadDirectShareIconTask>()
- val testSubject = createChooserListAdapter { iconTask }
testSubject.onBindView(view, targetInfo, 0)
- verify(iconTask, times(1)).loadIcon()
+ verify(mTargetDataLoader, times(1)).loadDirectShareIcon(any(), any(), any())
}
@Test
- fun testOnlyOneTaskPerTarget() {
+ fun onBindView_DirectShareTargetIconAndLabelLoadedOnlyOnce() {
val view = createView()
val viewHolderOne = ResolverListAdapter.ViewHolder(view)
view.tag = viewHolderOne
val targetInfo = createSelectableTargetInfo()
- val iconTaskOne = mock<LoadDirectShareIconTask>()
- val testTaskProvider = mock<() -> LoadDirectShareIconTask> {
- whenever(invoke()).thenReturn(iconTaskOne)
- }
- val testSubject = createChooserListAdapter { testTaskProvider.invoke() }
testSubject.onBindView(view, targetInfo, 0)
val viewHolderTwo = ResolverListAdapter.ViewHolder(view)
view.tag = viewHolderTwo
- whenever(testTaskProvider()).thenReturn(mock())
testSubject.onBindView(view, targetInfo, 0)
- verify(iconTaskOne, times(1)).loadIcon()
- verify(testTaskProvider, times(1)).invoke()
+ 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(),
+ /* resolveInfoPresentationGetter= */ null
+ )
+ 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())
}
private fun createSelectableTargetInfo(): TargetInfo =
SelectableTargetInfo.newSelectableTargetInfo(
/* sourceInfo = */ DisplayResolveInfo.newDisplayResolveInfo(
Intent(),
- ResolverDataProvider.createResolveInfo(2, 0),
+ ResolverDataProvider.createResolveInfo(2, 0, userHandle),
"label",
"extended info",
Intent(),
@@ -128,7 +144,10 @@ class ChooserListAdapterTest {
/* backupResolveInfo = */ mock(),
/* resolvedIntent = */ Intent(),
/* chooserTarget = */ createChooserTarget(
- "Target", 0.5f, ComponentName("pkg", "Class"), "id-1"
+ "Target",
+ 0.5f,
+ ComponentName("pkg", "Class"),
+ "id-1"
),
/* modifiedScore = */ 1f,
/* shortcutInfo = */ createShortcutInfo("id-1", ComponentName("pkg", "Class"), 1),
diff --git a/java/tests/src/com/android/intentresolver/ChooserRefinementManagerTest.kt b/java/tests/src/com/android/intentresolver/ChooserRefinementManagerTest.kt
index 50c37c7f..bd355c86 100644
--- a/java/tests/src/com/android/intentresolver/ChooserRefinementManagerTest.kt
+++ b/java/tests/src/com/android/intentresolver/ChooserRefinementManagerTest.kt
@@ -16,46 +16,227 @@
package com.android.intentresolver
-import android.content.Context
+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
-import java.util.function.Consumer
-import org.junit.Assert.assertEquals
@RunWith(AndroidJUnit4::class)
+@UiThreadTest
class ChooserRefinementManagerTest {
- @Test
- fun testMaybeHandleSelection() {
- val intentSender = mock<IntentSender>()
- val refinementManager = ChooserRefinementManager(
- mock<Context>(),
- intentSender,
- Consumer<TargetInfo>{},
- Runnable{})
-
- val intents = listOf(Intent(Intent.ACTION_VIEW), Intent(Intent.ACTION_EDIT))
- val targetInfo = mock<TargetInfo>{
- whenever(allSourceIntents).thenReturn(intents)
+ 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()
+ }
+ }
+ }
}
- refinementManager.maybeHandleSelection(targetInfo)
+ /** 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(Looper.myLooper())
+ )
+ )
+ .isTrue()
val intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
- Mockito.verify(intentSender).sendIntent(
- any(), eq(0), intentCaptor.capture(), eq(null), eq(null))
+ Mockito.verify(intentSender)
+ .sendIntent(any(), eq(0), intentCaptor.capture(), eq(null), eq(null))
val intent = intentCaptor.value
- assertEquals(intents[0], intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java))
+ assertThat(intent?.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java))
+ .isEqualTo(exampleSourceIntents[0])
val alternates =
- intent.getParcelableArrayExtra(Intent.EXTRA_ALTERNATE_INTENTS, Intent::class.java)
- assertEquals(1, alternates?.size)
- assertEquals(intents[1], alternates?.get(0))
+ 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(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(Looper.myLooper())
+ )
+ )
+ .isFalse()
+ }
+
+ @Test
+ fun testMaybeHandleSelection_suspended() {
+ val targetInfo =
+ ImmutableTargetInfo.newBuilder()
+ .setAllSourceIntents(exampleSourceIntents)
+ .setIsSuspended(true)
+ .build()
+
+ assertThat(
+ refinementManager.maybeHandleSelection(
+ targetInfo,
+ intentSender,
+ application,
+ FakeHandler(Looper.myLooper())
+ )
+ )
+ .isFalse()
+ }
+
+ @Test
+ fun testMaybeHandleSelection_noIntentSender() {
+ assertThat(
+ refinementManager.maybeHandleSelection(
+ exampleTargetInfo,
+ /* IntentSender */ null,
+ application,
+ FakeHandler(Looper.myLooper())
+ )
+ )
+ .isFalse()
+ }
+
+ @Test
+ fun testConfigurationChangeDuringRefinement() {
+ assertThat(
+ refinementManager.maybeHandleSelection(
+ exampleTargetInfo,
+ intentSender,
+ application,
+ FakeHandler(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(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
new file mode 100644
index 00000000..331d1c21
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/ChooserRequestParametersTest.kt
@@ -0,0 +1,88 @@
+/*
+ * 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 {
+ val flags = TestFeatureFlagRepository(mapOf())
+
+ @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, flags)
+ 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, flags)
+ 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, flags)
+
+ 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
index d4ae666b..6ac6b6d3 100644
--- a/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java
+++ b/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java
@@ -29,14 +29,17 @@ 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.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
-import com.android.intentresolver.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
import com.android.intentresolver.chooser.DisplayResolveInfo;
import com.android.intentresolver.chooser.TargetInfo;
import com.android.intentresolver.flags.FeatureFlagRepository;
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;
@@ -70,7 +73,8 @@ public class ChooserWrapperActivity
UserHandle userHandle,
Intent targetIntent,
ChooserRequestParameters chooserRequest,
- int maxTargetsPerRow) {
+ int maxTargetsPerRow,
+ TargetDataLoader targetDataLoader) {
PackageManager packageManager =
sOverrides.packageManager == null ? context.getPackageManager()
: sOverrides.packageManager;
@@ -80,14 +84,16 @@ public class ChooserWrapperActivity
initialIntents,
rList,
filterLastUsed,
- resolverListController,
+ createListController(userHandle),
userHandle,
targetIntent,
this,
packageManager,
getChooserActivityLogger(),
chooserRequest,
- maxTargetsPerRow);
+ maxTargetsPerRow,
+ userHandle,
+ targetDataLoader);
}
@Override
@@ -142,14 +148,6 @@ public class ChooserWrapperActivity
}
@Override
- protected MyUserIdProvider createMyUserIdProvider() {
- if (sOverrides.mMyUserIdProvider != null) {
- return sOverrides.mMyUserIdProvider;
- }
- return super.createMyUserIdProvider();
- }
-
- @Override
protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
if (sOverrides.mCrossProfileIntentsChecker != null) {
return sOverrides.mCrossProfileIntentsChecker;
@@ -166,12 +164,13 @@ public class ChooserWrapperActivity
}
@Override
- public void safelyStartActivity(TargetInfo cti) {
- if (sOverrides.onSafelyStartCallback != null
- && sOverrides.onSafelyStartCallback.apply(cti)) {
+ public void safelyStartActivityInternal(TargetInfo cti, UserHandle user,
+ @Nullable Bundle options) {
+ if (sOverrides.onSafelyStartInternalCallback != null
+ && sOverrides.onSafelyStartInternalCallback.apply(cti)) {
return;
}
- super.safelyStartActivity(cti);
+ super.safelyStartActivityInternal(cti, user, options);
}
@Override
@@ -199,15 +198,10 @@ public class ChooserWrapperActivity
}
@Override
- protected ImageLoader createPreviewImageLoader() {
- return new TestPreviewImageLoader(
- super.createPreviewImageLoader(),
- () -> sOverrides.previewThumbnail);
- }
-
- @Override
- protected boolean isImageType(String mimeType) {
- return sOverrides.isImageType;
+ protected ViewModelProvider.Factory createPreviewViewModelFactory() {
+ return TestContentPreviewViewModel.Companion.wrap(
+ super.createPreviewViewModelFactory(),
+ sOverrides.imageLoader);
}
@Override
@@ -260,6 +254,14 @@ public class ChooserWrapperActivity
}
@Override
+ protected UserHandle getTabOwnerUserHandleForLaunch() {
+ if (sOverrides.tabOwnerUserHandleForLaunch == null) {
+ return super.getTabOwnerUserHandleForLaunch();
+ }
+ return sOverrides.tabOwnerUserHandleForLaunch;
+ }
+
+ @Override
public Context createContextAsUser(UserHandle user, int flags) {
// return the current context as a work profile doesn't really exist in these tests
return getApplicationContext();
diff --git a/java/tests/src/com/android/intentresolver/ImagePreviewImageLoaderTest.kt b/java/tests/src/com/android/intentresolver/ImagePreviewImageLoaderTest.kt
deleted file mode 100644
index f327e19e..00000000
--- a/java/tests/src/com/android/intentresolver/ImagePreviewImageLoaderTest.kt
+++ /dev/null
@@ -1,101 +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.ContentResolver
-import android.content.Context
-import android.content.res.Resources
-import android.net.Uri
-import android.util.Size
-import androidx.lifecycle.Lifecycle
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-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 org.junit.After
-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 contentResolver = mock<ContentResolver>()
- private val resources = mock<Resources> {
- whenever(getDimensionPixelSize(R.dimen.chooser_preview_image_max_dimen))
- .thenReturn(imageSize.width)
- }
- private val context = mock<Context> {
- whenever(this.resources).thenReturn(this@ImagePreviewImageLoaderTest.resources)
- whenever(this.contentResolver).thenReturn(this@ImagePreviewImageLoaderTest.contentResolver)
- }
- private val scheduler = TestCoroutineScheduler()
- private val lifecycleOwner = TestLifecycleOwner()
- private val dispatcher = UnconfinedTestDispatcher(scheduler)
- private val testSubject = ImagePreviewImageLoader(
- context, lifecycleOwner.lifecycle, 1, dispatcher
- )
-
- @Before
- fun setup() {
- Dispatchers.setMain(dispatcher)
- lifecycleOwner.state = Lifecycle.State.CREATED
- }
-
- @After
- fun cleanup() {
- lifecycleOwner.state = Lifecycle.State.DESTROYED
- Dispatchers.resetMain()
- }
-
- @Test
- fun test_prePopulate() = 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 test_invoke_return_cached_image() = runTest {
- testSubject(uriOne)
- testSubject(uriOne)
-
- verify(contentResolver, times(1)).loadThumbnail(any(), any(), anyOrNull())
- }
-
- @Test
- fun test_invoke_old_records_evicted_from_the_cache() = 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)
- }
-}
diff --git a/java/tests/src/com/android/intentresolver/ResolverActivityTest.java b/java/tests/src/com/android/intentresolver/ResolverActivityTest.java
index ae1b99f8..7233fd3d 100644
--- a/java/tests/src/com/android/intentresolver/ResolverActivityTest.java
+++ b/java/tests/src/com/android/intentresolver/ResolverActivityTest.java
@@ -33,10 +33,11 @@ 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 static org.testng.Assert.assertFalse;
-import static org.testng.Assert.fail;
import android.content.Intent;
import android.content.pm.ResolveInfo;
@@ -55,7 +56,8 @@ import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
import com.android.intentresolver.widget.ResolverDrawerLayout;
-import com.android.internal.R;
+
+import com.google.android.collect.Lists;
import org.junit.Before;
import org.junit.Ignore;
@@ -72,6 +74,9 @@ import java.util.List;
*/
@RunWith(AndroidJUnit4.class)
public class ResolverActivityTest {
+
+ private static final UserHandle PERSONAL_USER_HANDLE = androidx.test.platform.app
+ .InstrumentationRegistry.getInstrumentation().getTargetContext().getUser();
protected Intent getConcreteIntentForLaunch(Intent clientIntent) {
clientIntent.setClass(
androidx.test.platform.app.InstrumentationRegistry.getInstrumentation().getTargetContext(),
@@ -98,26 +103,27 @@ public class ResolverActivityTest {
@Test
public void twoOptionsAndUserSelectsOne() throws InterruptedException {
Intent sendIntent = createSendImageIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+ List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2,
+ PERSONAL_USER_HANDLE);
setupResolverControllers(resolvedComponentInfos);
final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- Espresso.registerIdlingResources(activity.getAdapter().getLabelIdlingResource());
+ Espresso.registerIdlingResources(activity.getLabelIdlingResource());
waitForIdle();
assertThat(activity.getAdapter().getCount(), is(2));
ResolveInfo[] chosen = new ResolveInfo[1];
- sOverrides.onSafelyStartCallback = targetInfo -> {
- chosen[0] = targetInfo.getResolveInfo();
+ 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(R.id.button_once))
+ onView(withId(com.android.internal.R.id.button_once))
.perform(click());
waitForIdle();
assertThat(chosen[0], is(toChoose));
@@ -127,19 +133,20 @@ public class ResolverActivityTest {
@Test
public void setMaxHeight() throws Exception {
Intent sendIntent = createSendImageIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+ List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2,
+ PERSONAL_USER_HANDLE);
setupResolverControllers(resolvedComponentInfos);
waitForIdle();
final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- final View viewPager = activity.findViewById(R.id.profile_pager);
+ final View viewPager = activity.findViewById(com.android.internal.R.id.profile_pager);
final int initialResolverHeight = viewPager.getHeight();
activity.runOnUiThread(() -> {
ResolverDrawerLayout layout = (ResolverDrawerLayout)
activity.findViewById(
- R.id.contentPanel);
+ com.android.internal.R.id.contentPanel);
((ResolverDrawerLayout.LayoutParams) viewPager.getLayoutParams()).maxHeight
= initialResolverHeight - 1;
// Force a relayout
@@ -153,7 +160,7 @@ public class ResolverActivityTest {
activity.runOnUiThread(() -> {
ResolverDrawerLayout layout = (ResolverDrawerLayout)
activity.findViewById(
- R.id.contentPanel);
+ com.android.internal.R.id.contentPanel);
((ResolverDrawerLayout.LayoutParams) viewPager.getLayoutParams()).maxHeight
= initialResolverHeight + 1;
// Force a relayout
@@ -169,16 +176,18 @@ public class ResolverActivityTest {
@Test
public void setShowAtTopToTrue() throws Exception {
Intent sendIntent = createSendImageIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+ List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2,
+ PERSONAL_USER_HANDLE);
setupResolverControllers(resolvedComponentInfos);
waitForIdle();
final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- final View viewPager = activity.findViewById(R.id.profile_pager);
- final View divider = activity.findViewById(R.id.divider);
+ 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(R.id.profile_button).getParent();
+ (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);
@@ -186,7 +195,7 @@ public class ResolverActivityTest {
activity.runOnUiThread(() -> {
ResolverDrawerLayout layout = (ResolverDrawerLayout)
activity.findViewById(
- R.id.contentPanel);
+ com.android.internal.R.id.contentPanel);
layout.setShowAtTop(true);
});
waitForIdle();
@@ -198,7 +207,8 @@ public class ResolverActivityTest {
@Test
public void hasLastChosenActivity() throws Exception {
Intent sendIntent = createSendImageIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+ List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2,
+ PERSONAL_USER_HANDLE);
ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
setupResolverControllers(resolvedComponentInfos);
@@ -213,12 +223,12 @@ public class ResolverActivityTest {
assertThat(activity.getAdapter().getPlaceholderCount(), is(1));
ResolveInfo[] chosen = new ResolveInfo[1];
- sOverrides.onSafelyStartCallback = targetInfo -> {
- chosen[0] = targetInfo.getResolveInfo();
+ sOverrides.onSafelyStartInternalCallback = result -> {
+ chosen[0] = result.first.getResolveInfo();
return true;
};
- onView(withId(R.id.button_once)).perform(click());
+ onView(withId(com.android.internal.R.id.button_once)).perform(click());
waitForIdle();
assertThat(chosen[0], is(toChoose));
}
@@ -226,32 +236,35 @@ public class ResolverActivityTest {
@Test
public void hasOtherProfileOneOption() throws Exception {
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10,
+ PERSONAL_USER_HANDLE);
markWorkProfileUserAvailable();
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+ sOverrides.workProfileUserHandle);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
ResolveInfo toChoose = personalResolvedComponentInfos.get(1).getResolveInfoAt(0);
Intent sendIntent = createSendImageIntent();
final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- Espresso.registerIdlingResources(activity.getAdapter().getLabelIdlingResource());
+ 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.onSafelyStartCallback = targetInfo -> {
- chosen[0] = targetInfo.getResolveInfo();
+ 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);
+ 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(R.id.button_once))
+ onView(withId(com.android.internal.R.id.button_once))
.perform(click());
waitForIdle();
assertThat(chosen[0], is(toChoose));
@@ -261,34 +274,34 @@ public class ResolverActivityTest {
public void hasOtherProfileTwoOptionsAndUserSelectsOne() throws Exception {
Intent sendIntent = createSendImageIntent();
List<ResolvedComponentInfo> resolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3);
+ createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE);
ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
setupResolverControllers(resolvedComponentInfos);
final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- Espresso.registerIdlingResources(activity.getAdapter().getLabelIdlingResource());
+ 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.onSafelyStartCallback = targetInfo -> {
- chosen[0] = targetInfo.getResolveInfo();
+ sOverrides.onSafelyStartInternalCallback = result -> {
+ chosen[0] = result.first.getResolveInfo();
return true;
};
// Confirm that the button bar is disabled by default
- onView(withId(R.id.button_once)).check(matches(not(isEnabled())));
+ 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);
+ createResolvedComponentsForTestWithOtherProfile(2, PERSONAL_USER_HANDLE);
onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
.perform(click());
- onView(withId(R.id.button_once)).perform(click());
+ onView(withId(com.android.internal.R.id.button_once)).perform(click());
waitForIdle();
assertThat(chosen[0], is(toChoose));
}
@@ -300,7 +313,7 @@ public class ResolverActivityTest {
// chosen activity.
Intent sendIntent = createSendImageIntent();
List<ResolvedComponentInfo> resolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3);
+ createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE);
ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
setupResolverControllers(resolvedComponentInfos);
@@ -308,28 +321,28 @@ public class ResolverActivityTest {
.thenReturn(resolvedComponentInfos.get(1).getResolveInfoAt(0));
final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- Espresso.registerIdlingResources(activity.getAdapter().getLabelIdlingResource());
+ 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.onSafelyStartCallback = targetInfo -> {
- chosen[0] = targetInfo.getResolveInfo();
+ sOverrides.onSafelyStartInternalCallback = result -> {
+ chosen[0] = result.first.getResolveInfo();
return true;
};
// Confirm that the button bar is disabled by default
- onView(withId(R.id.button_once)).check(matches(not(isEnabled())));
+ 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);
+ createResolvedComponentsForTestWithOtherProfile(2, PERSONAL_USER_HANDLE);
onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
.perform(click());
- onView(withId(R.id.button_once)).perform(click());
+ onView(withId(com.android.internal.R.id.button_once)).perform(click());
waitForIdle();
assertThat(chosen[0], is(toChoose));
}
@@ -342,7 +355,7 @@ public class ResolverActivityTest {
mActivityRule.launchActivity(sendIntent);
waitForIdle();
- onView(withId(R.id.tabs)).check(matches(isDisplayed()));
+ onView(withId(com.android.internal.R.id.tabs)).check(matches(isDisplayed()));
}
@Test
@@ -352,18 +365,20 @@ public class ResolverActivityTest {
mActivityRule.launchActivity(sendIntent);
waitForIdle();
- onView(withId(R.id.tabs)).check(matches(not(isDisplayed())));
+ 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);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ createResolvedComponentsForTestWithOtherProfile(3, /* userId = */ 10,
+ PERSONAL_USER_HANDLE);
+ markWorkProfileUserAvailable();
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+ sOverrides.workProfileUserHandle);
setupResolverControllers(personalResolvedComponentInfos,
new ArrayList<>(workResolvedComponentInfos));
Intent sendIntent = createSendImageIntent();
- markWorkProfileUserAvailable();
final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
waitForIdle();
@@ -376,8 +391,11 @@ public class ResolverActivityTest {
@Test
public void testWorkTab_workTabUsesExpectedAdapter() {
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
+ PERSONAL_USER_HANDLE);
+ markWorkProfileUserAvailable();
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+ sOverrides.workProfileUserHandle);
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
markWorkProfileUserAvailable();
@@ -393,11 +411,12 @@ public class ResolverActivityTest {
@Test
public void testWorkTab_personalTabUsesExpectedAdapter() {
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE);
+ markWorkProfileUserAvailable();
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+ sOverrides.workProfileUserHandle);
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
- markWorkProfileUserAvailable();
final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
waitForIdle();
@@ -411,8 +430,10 @@ public class ResolverActivityTest {
public void testWorkTab_workProfileHasExpectedNumberOfTargets() throws InterruptedException {
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
+ PERSONAL_USER_HANDLE);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+ sOverrides.workProfileUserHandle);
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
@@ -429,13 +450,15 @@ public class ResolverActivityTest {
public void testWorkTab_selectingWorkTabAppOpensAppInWorkProfile() throws InterruptedException {
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
+ PERSONAL_USER_HANDLE);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+ sOverrides.workProfileUserHandle);
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
ResolveInfo[] chosen = new ResolveInfo[1];
- sOverrides.onSafelyStartCallback = targetInfo -> {
- chosen[0] = targetInfo.getResolveInfo();
+ sOverrides.onSafelyStartInternalCallback = result -> {
+ chosen[0] = result.first.getResolveInfo();
return true;
};
@@ -447,7 +470,7 @@ public class ResolverActivityTest {
onView(first(allOf(withText(workResolvedComponentInfos.get(0)
.getResolveInfoAt(0).activityInfo.applicationInfo.name), isCompletelyDisplayed())))
.perform(click());
- onView(withId(R.id.button_once))
+ onView(withId(com.android.internal.R.id.button_once))
.perform(click());
waitForIdle();
@@ -459,8 +482,9 @@ public class ResolverActivityTest {
throws InterruptedException {
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(1);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ createResolvedComponentsForTestWithOtherProfile(1, PERSONAL_USER_HANDLE);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+ sOverrides.workProfileUserHandle);
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
@@ -477,16 +501,17 @@ public class ResolverActivityTest {
public void testWorkTab_headerIsVisibleInPersonalTab() {
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(1);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ createResolvedComponentsForTestWithOtherProfile(1, PERSONAL_USER_HANDLE);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+ sOverrides.workProfileUserHandle);
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createOpenWebsiteIntent();
final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
waitForIdle();
- TextView headerText = activity.findViewById(R.id.title);
+ TextView headerText = activity.findViewById(com.android.internal.R.id.title);
String initialText = headerText.getText().toString();
- assertFalse(initialText.isEmpty(), "Header text is empty.");
+ assertFalse("Header text is empty.", initialText.isEmpty());
assertThat(headerText.getVisibility(), is(View.VISIBLE));
}
@@ -494,14 +519,15 @@ public class ResolverActivityTest {
public void testWorkTab_switchTabs_headerStaysSame() {
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(1);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ createResolvedComponentsForTestWithOtherProfile(1, PERSONAL_USER_HANDLE);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+ sOverrides.workProfileUserHandle);
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createOpenWebsiteIntent();
final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
waitForIdle();
- TextView headerText = activity.findViewById(R.id.title);
+ TextView headerText = activity.findViewById(com.android.internal.R.id.title);
String initialText = headerText.getText().toString();
onView(withText(R.string.resolver_work_tab))
.perform(click());
@@ -519,13 +545,15 @@ public class ResolverActivityTest {
throws InterruptedException {
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId= */ 10);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ createResolvedComponentsForTestWithOtherProfile(3, /* userId= */ 10,
+ PERSONAL_USER_HANDLE);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+ sOverrides.workProfileUserHandle);
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
ResolveInfo[] chosen = new ResolveInfo[1];
- sOverrides.onSafelyStartCallback = targetInfo -> {
- chosen[0] = targetInfo.getResolveInfo();
+ sOverrides.onSafelyStartInternalCallback = result -> {
+ chosen[0] = result.first.getResolveInfo();
return true;
};
@@ -539,7 +567,7 @@ public class ResolverActivityTest {
.getResolveInfoAt(0).activityInfo.applicationInfo.name),
isDisplayed())))
.perform(click());
- onView(withId(R.id.button_once))
+ onView(withId(com.android.internal.R.id.button_once))
.perform(click());
waitForIdle();
@@ -551,9 +579,11 @@ public class ResolverActivityTest {
markWorkProfileUserAvailable();
int workProfileTargets = 4;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
+ createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
+ PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(workProfileTargets);
+ createResolvedComponentsForTest(workProfileTargets,
+ sOverrides.workProfileUserHandle);
sOverrides.hasCrossProfileIntents = false;
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
@@ -563,7 +593,7 @@ public class ResolverActivityTest {
waitForIdle();
onView(withText(R.string.resolver_work_tab)).perform(click());
waitForIdle();
- onView(withId(R.id.contentPanel))
+ onView(withId(com.android.internal.R.id.contentPanel))
.perform(swipeUp());
onView(withText(R.string.resolver_cross_profile_blocked))
@@ -575,9 +605,11 @@ public class ResolverActivityTest {
markWorkProfileUserAvailable();
int workProfileTargets = 4;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
+ createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
+ PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(workProfileTargets);
+ createResolvedComponentsForTest(workProfileTargets,
+ sOverrides.workProfileUserHandle);
sOverrides.isQuietModeEnabled = true;
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
@@ -585,7 +617,7 @@ public class ResolverActivityTest {
mActivityRule.launchActivity(sendIntent);
waitForIdle();
- onView(withId(R.id.contentPanel))
+ onView(withId(com.android.internal.R.id.contentPanel))
.perform(swipeUp());
onView(withText(R.string.resolver_work_tab)).perform(click());
waitForIdle();
@@ -598,16 +630,16 @@ public class ResolverActivityTest {
public void testWorkTab_noWorkAppsAvailable_emptyStateShown() {
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTest(3);
+ createResolvedComponentsForTest(3, PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(0);
+ createResolvedComponentsForTest(0, sOverrides.workProfileUserHandle);
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
sendIntent.setType("TestType");
mActivityRule.launchActivity(sendIntent);
waitForIdle();
- onView(withId(R.id.contentPanel))
+ onView(withId(com.android.internal.R.id.contentPanel))
.perform(swipeUp());
onView(withText(R.string.resolver_work_tab)).perform(click());
waitForIdle();
@@ -620,9 +652,9 @@ public class ResolverActivityTest {
public void testWorkTab_xProfileOff_noAppsAvailable_workOff_xProfileOffEmptyStateShown() {
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTest(3);
+ createResolvedComponentsForTest(3, PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(0);
+ createResolvedComponentsForTest(0, sOverrides.workProfileUserHandle);
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
sendIntent.setType("TestType");
@@ -631,7 +663,7 @@ public class ResolverActivityTest {
mActivityRule.launchActivity(sendIntent);
waitForIdle();
- onView(withId(R.id.contentPanel))
+ onView(withId(com.android.internal.R.id.contentPanel))
.perform(swipeUp());
onView(withText(R.string.resolver_work_tab)).perform(click());
waitForIdle();
@@ -644,9 +676,9 @@ public class ResolverActivityTest {
public void testMiniResolver() {
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTest(1);
+ createResolvedComponentsForTest(1, PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(1);
+ createResolvedComponentsForTest(1, sOverrides.workProfileUserHandle);
// Personal profile only has a browser
personalResolvedComponentInfos.get(0).getResolveInfoAt(0).handleAllWebDataURI = true;
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
@@ -655,16 +687,16 @@ public class ResolverActivityTest {
mActivityRule.launchActivity(sendIntent);
waitForIdle();
- onView(withId(R.id.open_cross_profile)).check(matches(isDisplayed()));
+ onView(withId(com.android.internal.R.id.open_cross_profile)).check(matches(isDisplayed()));
}
@Test
public void testMiniResolver_noCurrentProfileTarget() {
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTest(0);
+ createResolvedComponentsForTest(0, PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(1);
+ createResolvedComponentsForTest(1, sOverrides.workProfileUserHandle);
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
sendIntent.setType("TestType");
@@ -678,7 +710,8 @@ public class ResolverActivityTest {
private void assertNotMiniResolver() {
try {
- onView(withId(R.id.open_cross_profile)).check(matches(isDisplayed()));
+ onView(withId(com.android.internal.R.id.open_cross_profile))
+ .check(matches(isDisplayed()));
} catch (NoMatchingViewException e) {
return;
}
@@ -689,9 +722,9 @@ public class ResolverActivityTest {
public void testWorkTab_noAppsAvailable_workOff_noAppsAvailableEmptyStateShown() {
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTest(3);
+ createResolvedComponentsForTest(3, PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(0);
+ createResolvedComponentsForTest(0, sOverrides.workProfileUserHandle);
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
sendIntent.setType("TestType");
@@ -699,7 +732,7 @@ public class ResolverActivityTest {
mActivityRule.launchActivity(sendIntent);
waitForIdle();
- onView(withId(R.id.contentPanel))
+ onView(withId(com.android.internal.R.id.contentPanel))
.perform(swipeUp());
onView(withText(R.string.resolver_work_tab)).perform(click());
waitForIdle();
@@ -709,27 +742,29 @@ public class ResolverActivityTest {
}
@Test
- public void testWorkTab_onePersonalTarget_emptyStateOnWorkTarget_autolaunch() {
+ public void testWorkTab_onePersonalTarget_emptyStateOnWorkTarget_doesNotAutoLaunch() {
markWorkProfileUserAvailable();
int workProfileTargets = 4;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10);
+ createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10,
+ PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(workProfileTargets);
+ createResolvedComponentsForTest(workProfileTargets,
+ sOverrides.workProfileUserHandle);
sOverrides.hasCrossProfileIntents = false;
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
sendIntent.setType("TestType");
ResolveInfo[] chosen = new ResolveInfo[1];
- sOverrides.onSafelyStartCallback = targetInfo -> {
- chosen[0] = targetInfo.getResolveInfo();
+ sOverrides.onSafelyStartInternalCallback = result -> {
+ chosen[0] = result.first.getResolveInfo();
return true;
};
mActivityRule.launchActivity(sendIntent);
waitForIdle();
- assertThat(chosen[0], is(personalResolvedComponentInfos.get(1).getResolveInfoAt(0)));
+ assertNull(chosen[0]);
}
@Test
@@ -740,14 +775,14 @@ public class ResolverActivityTest {
// chosen activity.
Intent sendIntent = createSendImageIntent();
List<ResolvedComponentInfo> resolvedComponentInfos =
- createResolvedComponentsForTest(2);
+ 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.getAdapter().getLabelIdlingResource());
+ Espresso.registerIdlingResources(activity.getLabelIdlingResource());
waitForIdle();
// The other entry is filtered to the last used slot
@@ -756,6 +791,200 @@ public class ResolverActivityTest {
assertThat(activity.getAdapter().getPlaceholderCount(), is(2));
}
+ @Test
+ public void testClonedProfilePresent_personalAdapterIsSetWithPersonalProfile() {
+ // enable cloneProfile
+ markCloneProfileUserAvailable();
+ List<ResolvedComponentInfo> resolvedComponentInfos =
+ createResolvedComponentsWithCloneProfileForTest(
+ 3,
+ PERSONAL_USER_HANDLE,
+ sOverrides.cloneProfileUserHandle);
+ setupResolverControllers(resolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+
+ assertThat(activity.getCurrentUserHandle(), is(activity.getPersonalProfileUserHandle()));
+ assertThat(activity.getAdapter().getCount(), is(3));
+ }
+
+ @Test
+ public void testClonedProfilePresent_personalTabUsesExpectedAdapter() {
+ markWorkProfileUserAvailable();
+ // enable cloneProfile
+ markCloneProfileUserAvailable();
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsWithCloneProfileForTest(
+ 3,
+ PERSONAL_USER_HANDLE,
+ sOverrides.cloneProfileUserHandle);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+ sOverrides.workProfileUserHandle);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+
+ assertThat(activity.getCurrentUserHandle(), is(activity.getPersonalProfileUserHandle()));
+ assertThat(activity.getAdapter().getCount(), is(3));
+ }
+
+ @Test
+ public void testClonedProfilePresent_layoutWithDefault_neverShown() throws Exception {
+ // enable cloneProfile
+ markCloneProfileUserAvailable();
+ Intent sendIntent = createSendImageIntent();
+ List<ResolvedComponentInfo> resolvedComponentInfos =
+ createResolvedComponentsWithCloneProfileForTest(
+ 2,
+ PERSONAL_USER_HANDLE,
+ sOverrides.cloneProfileUserHandle);
+
+ 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
+ markCloneProfileUserAvailable();
+ Intent sendIntent = createSendImageIntent();
+ List<ResolvedComponentInfo> resolvedComponentInfos =
+ createResolvedComponentsWithCloneProfileForTest(
+ 3,
+ PERSONAL_USER_HANDLE,
+ sOverrides.cloneProfileUserHandle);
+
+ 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 {
+ markWorkProfileUserAvailable();
+ // enable cloneProfile
+ markCloneProfileUserAvailable();
+
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsWithCloneProfileForTest(
+ 3,
+ PERSONAL_USER_HANDLE,
+ sOverrides.cloneProfileUserHandle);
+ List<ResolvedComponentInfo> workResolvedComponentInfos =
+ createResolvedComponentsForTest(3, sOverrides.workProfileUserHandle);
+ 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 {
+ markWorkProfileUserAvailable();
+ // enable cloneProfile
+ markCloneProfileUserAvailable();
+
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsWithCloneProfileForTest(
+ 3,
+ PERSONAL_USER_HANDLE,
+ sOverrides.cloneProfileUserHandle);
+ List<ResolvedComponentInfo> workResolvedComponentInfos =
+ createResolvedComponentsForTest(3, sOverrides.workProfileUserHandle);
+ 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
+ markCloneProfileUserAvailable();
+ List<ResolvedComponentInfo> resolvedComponentInfos =
+ createResolvedComponentsWithCloneProfileForTest(
+ 3,
+ PERSONAL_USER_HANDLE,
+ sOverrides.cloneProfileUserHandle);
+ 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,
+ sOverrides.cloneProfileUserHandle)), is(true));
+ }
+
private Intent createSendImageIntent() {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
@@ -771,36 +1000,56 @@ public class ResolverActivityTest {
return sendIntent;
}
- private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults) {
+ 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));
+ 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) {
+ 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));
+ infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i,
+ resolvedForUser));
} else {
- infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+ infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, resolvedForUser));
}
}
return infoList;
}
private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
- int numberOfResults, int userId) {
+ 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));
+ ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId,
+ resolvedForUser));
} else {
- infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+ infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, resolvedForUser));
}
}
return infoList;
@@ -819,6 +1068,10 @@ public class ResolverActivityTest {
setupResolverControllers(personalResolvedComponentInfos, new ArrayList<>());
}
+ private void markCloneProfileUserAvailable() {
+ ResolverWrapperActivity.sOverrides.cloneProfileUserHandle = UserHandle.of(11);
+ }
+
private void setupResolverControllers(
List<ResolvedComponentInfo> personalResolvedComponentInfos,
List<ResolvedComponentInfo> workResolvedComponentInfos) {
diff --git a/java/tests/src/com/android/intentresolver/ResolverDataProvider.java b/java/tests/src/com/android/intentresolver/ResolverDataProvider.java
index b6b32b5a..688dd867 100644
--- a/java/tests/src/com/android/intentresolver/ResolverDataProvider.java
+++ b/java/tests/src/com/android/intentresolver/ResolverDataProvider.java
@@ -43,6 +43,14 @@ public class ResolverDataProvider {
createResolveInfo(i, UserHandle.USER_CURRENT));
}
+ 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(
@@ -51,6 +59,14 @@ public class ResolverDataProvider {
createResolveInfo(componentName, UserHandle.USER_CURRENT));
}
+ 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),
@@ -58,6 +74,14 @@ public class ResolverDataProvider {
createResolveInfo(i, USER_SOMEONE_ELSE));
}
+ 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),
@@ -65,6 +89,14 @@ public class ResolverDataProvider {
createResolveInfo(i, userId));
}
+ 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);
@@ -76,6 +108,13 @@ public class ResolverDataProvider {
resolveInfo.targetUserId = userId;
return resolveInfo;
}
+ public static ResolveInfo createResolveInfo(int i, int userId, UserHandle resolvedForUser) {
+ final ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.activityInfo = createActivityInfo(i);
+ resolveInfo.targetUserId = userId;
+ resolveInfo.userHandle = resolvedForUser;
+ return resolveInfo;
+ }
public static ResolveInfo createResolveInfo(ComponentName componentName, int userId) {
final ResolveInfo resolveInfo = new ResolveInfo();
@@ -84,6 +123,15 @@ public class ResolverDataProvider {
return resolveInfo;
}
+ public static ResolveInfo createResolveInfo(ComponentName componentName, int userId,
+ UserHandle resolvedForUser) {
+ final ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.activityInfo = createActivityInfo(componentName);
+ resolveInfo.targetUserId = userId;
+ resolveInfo.userHandle = resolvedForUser;
+ return resolveInfo;
+ }
+
static ActivityInfo createActivityInfo(int i) {
ActivityInfo ai = new ActivityInfo();
ai.name = "activity_name" + i;
diff --git a/java/tests/src/com/android/intentresolver/ResolverWrapperActivity.java b/java/tests/src/com/android/intentresolver/ResolverWrapperActivity.java
index d67b73af..401ede26 100644
--- a/java/tests/src/com/android/intentresolver/ResolverWrapperActivity.java
+++ b/java/tests/src/com/android/intentresolver/ResolverWrapperActivity.java
@@ -21,19 +21,27 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-import android.app.usage.UsageStatsManager;
+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.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
-import com.android.intentresolver.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
+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 java.util.List;
+import java.util.function.Consumer;
import java.util.function.Function;
/*
@@ -41,7 +49,9 @@ import java.util.function.Function;
*/
public class ResolverWrapperActivity extends ResolverActivity {
static final OverrideData sOverrides = new OverrideData();
- private UsageStatsManager mUsm;
+
+ private final CountingIdlingResource mLabelIdlingResource =
+ new CountingIdlingResource("LoadLabelTask");
public ResolverWrapperActivity() {
super(/* isIntentPicker= */ true);
@@ -54,11 +64,20 @@ public class ResolverWrapperActivity extends ResolverActivity {
return 1234;
}
+ public CountingIdlingResource getLabelIdlingResource() {
+ return mLabelIdlingResource;
+ }
+
@Override
- public ResolverListAdapter createResolverListAdapter(Context context,
- List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList,
- boolean filterLastUsed, UserHandle userHandle) {
- return new ResolverWrapperAdapter(
+ 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,
@@ -67,15 +86,9 @@ public class ResolverWrapperActivity extends ResolverActivity {
createListController(userHandle),
userHandle,
payloadIntents.get(0), // TODO: extract upstream
- this);
- }
-
- @Override
- protected MyUserIdProvider createMyUserIdProvider() {
- if (sOverrides.mMyUserIdProvider != null) {
- return sOverrides.mMyUserIdProvider;
- }
- return super.createMyUserIdProvider();
+ this,
+ userHandle,
+ new TargetDataLoaderWrapper(targetDataLoader, mLabelIdlingResource));
}
@Override
@@ -94,8 +107,8 @@ public class ResolverWrapperActivity extends ResolverActivity {
return super.createWorkProfileAvailabilityManager();
}
- ResolverWrapperAdapter getAdapter() {
- return (ResolverWrapperAdapter) mMultiProfilePagerAdapter.getActiveListAdapter();
+ ResolverListAdapter getAdapter() {
+ return mMultiProfilePagerAdapter.getActiveListAdapter();
}
ResolverListAdapter getPersonalListAdapter() {
@@ -118,12 +131,13 @@ public class ResolverWrapperActivity extends ResolverActivity {
}
@Override
- public void safelyStartActivity(TargetInfo cti) {
- if (sOverrides.onSafelyStartCallback != null &&
- sOverrides.onSafelyStartCallback.apply(cti)) {
+ public void safelyStartActivityInternal(TargetInfo cti, UserHandle user,
+ @Nullable Bundle options) {
+ if (sOverrides.onSafelyStartInternalCallback != null
+ && sOverrides.onSafelyStartInternalCallback.apply(new Pair<>(cti, user))) {
return;
}
- super.safelyStartActivity(cti);
+ super.safelyStartActivityInternal(cti, user, options);
}
@Override
@@ -152,10 +166,21 @@ public class ResolverWrapperActivity extends ResolverActivity {
}
@Override
+ protected UserHandle getCloneProfileUserHandle() {
+ return sOverrides.cloneProfileUserHandle;
+ }
+
+ @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>
@@ -164,25 +189,28 @@ public class ResolverWrapperActivity extends ResolverActivity {
static class OverrideData {
@SuppressWarnings("Since15")
public Function<PackageManager, PackageManager> createPackageManager;
- public Function<TargetInfo, Boolean> onSafelyStartCallback;
+ public Function<Pair<TargetInfo, UserHandle>, Boolean> onSafelyStartInternalCallback;
public ResolverListController resolverListController;
public ResolverListController workResolverListController;
public Boolean isVoiceInteraction;
public UserHandle workProfileUserHandle;
+ public UserHandle cloneProfileUserHandle;
+ public UserHandle tabOwnerUserHandleForLaunch;
public Integer myUserId;
public boolean hasCrossProfileIntents;
public boolean isQuietModeEnabled;
public WorkProfileAvailabilityManager mWorkProfileAvailability;
- public MyUserIdProvider mMyUserIdProvider;
public CrossProfileIntentsChecker mCrossProfileIntentsChecker;
public void reset() {
- onSafelyStartCallback = null;
+ onSafelyStartInternalCallback = null;
isVoiceInteraction = null;
createPackageManager = null;
resolverListController = mock(ResolverListController.class);
workResolverListController = mock(ResolverListController.class);
workProfileUserHandle = null;
+ cloneProfileUserHandle = null;
+ tabOwnerUserHandleForLaunch = null;
myUserId = null;
hasCrossProfileIntents = true;
isQuietModeEnabled = false;
@@ -212,16 +240,55 @@ public class ResolverWrapperActivity extends ResolverActivity {
}
};
- mMyUserIdProvider = new MyUserIdProvider() {
- @Override
- public int getMyUserId() {
- return myUserId != null ? myUserId : UserHandle.myUserId();
- }
- };
-
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<CharSequence[]> callback) {
+ mLabelIdlingResource.increment();
+ mTargetDataLoader.loadLabel(
+ info,
+ (result) -> {
+ mLabelIdlingResource.decrement();
+ callback.accept(result);
+ });
+ }
+
+ @NonNull
+ @Override
+ public TargetPresentationGetter createPresentationGetter(@NonNull ResolveInfo info) {
+ return mTargetDataLoader.createPresentationGetter(info);
+ }
+ }
}
diff --git a/java/tests/src/com/android/intentresolver/ResolverWrapperAdapter.java b/java/tests/src/com/android/intentresolver/ResolverWrapperAdapter.java
deleted file mode 100644
index a53b41d1..00000000
--- a/java/tests/src/com/android/intentresolver/ResolverWrapperAdapter.java
+++ /dev/null
@@ -1,84 +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;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ResolveInfo;
-import android.os.UserHandle;
-
-import androidx.test.espresso.idling.CountingIdlingResource;
-
-import com.android.intentresolver.chooser.DisplayResolveInfo;
-
-import java.util.List;
-
-public class ResolverWrapperAdapter extends ResolverListAdapter {
-
- private CountingIdlingResource mLabelIdlingResource =
- new CountingIdlingResource("LoadLabelTask");
-
- public ResolverWrapperAdapter(
- Context context,
- List<Intent> payloadIntents,
- Intent[] initialIntents,
- List<ResolveInfo> rList,
- boolean filterLastUsed,
- ResolverListController resolverListController,
- UserHandle userHandle,
- Intent targetIntent,
- ResolverListCommunicator resolverListCommunicator) {
- super(
- context,
- payloadIntents,
- initialIntents,
- rList,
- filterLastUsed,
- resolverListController,
- userHandle,
- targetIntent,
- resolverListCommunicator,
- false);
- }
-
- public CountingIdlingResource getLabelIdlingResource() {
- return mLabelIdlingResource;
- }
-
- @Override
- protected LoadLabelTask createLoadLabelTask(DisplayResolveInfo info) {
- return new LoadLabelWrapperTask(info);
- }
-
- class LoadLabelWrapperTask extends LoadLabelTask {
-
- protected LoadLabelWrapperTask(DisplayResolveInfo dri) {
- super(dri);
- }
-
- @Override
- protected void onPreExecute() {
- mLabelIdlingResource.increment();
- }
-
- @Override
- protected void onPostExecute(CharSequence[] result) {
- super.onPostExecute(result);
- mLabelIdlingResource.decrement();
- }
- }
-}
diff --git a/java/tests/src/com/android/intentresolver/ShortcutSelectionLogicTest.kt b/java/tests/src/com/android/intentresolver/ShortcutSelectionLogicTest.kt
index a8d6f978..9ddeed84 100644
--- a/java/tests/src/com/android/intentresolver/ShortcutSelectionLogicTest.kt
+++ b/java/tests/src/com/android/intentresolver/ShortcutSelectionLogicTest.kt
@@ -21,10 +21,12 @@ import android.content.Context
import android.content.Intent
import android.content.pm.ResolveInfo
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
@@ -35,6 +37,9 @@ 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
@@ -52,7 +57,7 @@ class ShortcutSelectionLogicTest {
private val baseDisplayInfo = DisplayResolveInfo.newDisplayResolveInfo(
Intent(),
- ResolverDataProvider.createResolveInfo(3, 0),
+ ResolverDataProvider.createResolveInfo(3, 0, PERSONAL_USER_HANDLE),
"label",
"extended info",
Intent(),
@@ -60,7 +65,7 @@ class ShortcutSelectionLogicTest {
private val otherBaseDisplayInfo = DisplayResolveInfo.newDisplayResolveInfo(
Intent(),
- ResolverDataProvider.createResolveInfo(4, 0),
+ ResolverDataProvider.createResolveInfo(4, 0, PERSONAL_USER_HANDLE),
"label 2",
"extended info 2",
Intent(),
diff --git a/java/tests/src/com/android/intentresolver/TestContentPreviewViewModel.kt b/java/tests/src/com/android/intentresolver/TestContentPreviewViewModel.kt
new file mode 100644
index 00000000..d239f612
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/TestContentPreviewViewModel.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.intentresolver
+
+import 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
new file mode 100644
index 00000000..b3b53baa
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/TestContentProvider.kt
@@ -0,0 +1,55 @@
+/*
+ * 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("mimeType")
+ }.getOrNull()
+
+ override fun getStreamTypes(uri: Uri, mimeTypeFilter: String): Array<String>?
+ = runCatching {
+ uri.getQueryParameter("streamType")?.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
+} \ No newline at end of file
diff --git a/java/tests/src/com/android/intentresolver/TestPreviewImageLoader.kt b/java/tests/src/com/android/intentresolver/TestPreviewImageLoader.kt
index cfe041dd..bf87ed8a 100644
--- a/java/tests/src/com/android/intentresolver/TestPreviewImageLoader.kt
+++ b/java/tests/src/com/android/intentresolver/TestPreviewImageLoader.kt
@@ -18,21 +18,16 @@ package com.android.intentresolver
import android.graphics.Bitmap
import android.net.Uri
+import androidx.lifecycle.Lifecycle
+import com.android.intentresolver.contentpreview.ImageLoader
import java.util.function.Consumer
-internal class TestPreviewImageLoader(
- private val imageLoader: ImageLoader,
- private val imageOverride: () -> Bitmap?
-) : ImageLoader {
- override fun loadImage(uri: Uri, callback: Consumer<Bitmap?>) {
- val override = imageOverride()
- if (override != null) {
- callback.accept(override)
- } else {
- imageLoader.loadImage(uri, callback)
- }
+internal class TestPreviewImageLoader(private val bitmaps: Map<Uri, Bitmap>) : ImageLoader {
+ override fun loadImage(callerLifecycle: Lifecycle, uri: Uri, callback: Consumer<Bitmap?>) {
+ callback.accept(bitmaps[uri])
}
- override suspend fun invoke(uri: Uri): Bitmap? = imageOverride() ?: imageLoader(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
index 9ffd02d4..3ddd4394 100644
--- a/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java
+++ b/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java
@@ -26,6 +26,7 @@ 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.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;
@@ -79,6 +80,7 @@ import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
+import android.graphics.Rect;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Bundle;
@@ -90,19 +92,21 @@ import android.util.HashedStringCache;
import android.util.Pair;
import android.util.SparseArray;
import android.view.View;
-import android.view.ViewGroup;
+import android.view.WindowManager;
import androidx.annotation.CallSuper;
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.flags.Flags;
+import com.android.intentresolver.contentpreview.ImageLoader;
import com.android.intentresolver.shortcuts.ShortcutLoader;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -125,6 +129,7 @@ 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;
@@ -155,6 +160,8 @@ public class UnbundledChooserActivityTest {
* --------
*/
+ private static final UserHandle PERSONAL_USER_HANDLE = InstrumentationRegistry
+ .getInstrumentation().getTargetContext().getUser();
private static final Function<PackageManager, PackageManager> DEFAULT_PM = pm -> pm;
private static final Function<PackageManager, PackageManager> NO_APP_PREDICTION_SERVICE_PM =
pm -> {
@@ -164,11 +171,7 @@ public class UnbundledChooserActivityTest {
};
private static final List<BooleanFlag> ALL_FLAGS =
- Arrays.asList(
- Flags.SHARESHEET_CUSTOM_ACTIONS,
- Flags.SHARESHEET_RESELECTION_ACTION,
- Flags.SHARESHEET_IMAGE_AND_TEXT_PREVIEW,
- Flags.SHARESHEET_SCROLLABLE_IMAGE_PREVIEW);
+ Arrays.asList();
private static final Map<BooleanFlag, Boolean> ALL_FLAGS_OFF =
createAllFlagsOverride(false);
@@ -177,11 +180,20 @@ public class UnbundledChooserActivityTest {
@Parameterized.Parameters
public static Collection packageManagers() {
+ if (ALL_FLAGS.isEmpty()) {
+ // No flags to toggle between, so just two configurations.
+ return Arrays.asList(new Object[][] {
+ // Default PackageManager and all flags off
+ { DEFAULT_PM, ALL_FLAGS_OFF},
+ // No App Prediction Service and all flags off
+ { NO_APP_PREDICTION_SERVICE_PM, ALL_FLAGS_OFF },
+ });
+ }
return Arrays.asList(new Object[][] {
// Default PackageManager and all flags off
- { DEFAULT_PM, ALL_FLAGS_OFF },
+ { DEFAULT_PM, ALL_FLAGS_OFF},
// Default PackageManager and all flags on
- { DEFAULT_PM, ALL_FLAGS_ON },
+ { DEFAULT_PM, ALL_FLAGS_ON},
// No App Prediction Service and all flags off
{ NO_APP_PREDICTION_SERVICE_PM, ALL_FLAGS_OFF },
// No App Prediction Service and all flags on
@@ -350,7 +362,7 @@ public class UnbundledChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "chooser test"));
waitForIdle();
onView(withId(android.R.id.title))
- .check(matches(withText(com.android.internal.R.string.whichSendApplication)));
+ .check(matches(withText(R.string.whichSendApplication)));
}
@Test
@@ -362,7 +374,7 @@ public class UnbundledChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
onView(withId(android.R.id.title))
- .check(matches(withText(com.android.internal.R.string.whichSendApplication)));
+ .check(matches(withText(R.string.whichSendApplication)));
}
@Test
@@ -415,10 +427,12 @@ public class UnbundledChooserActivityTest {
@Test
public void visiblePreviewTitleAndThumbnail() throws InterruptedException {
String previewTitle = "My Content Preview Title";
- Intent sendIntent = createSendTextIntentWithPreview(previewTitle,
- Uri.parse("android.resource://com.android.frameworks.coretests/"
- + R.drawable.test320x240));
- ChooserActivityOverrideData.getInstance().previewThumbnail = createBitmap();
+ Uri uri = Uri.parse(
+ "android.resource://com.android.frameworks.coretests/"
+ + R.drawable.test320x240);
+ Intent sendIntent = createSendTextIntentWithPreview(previewTitle, uri);
+ ChooserActivityOverrideData.getInstance().imageLoader =
+ createImageLoader(uri, createBitmap());
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
setupResolverControllers(resolvedComponentInfos);
@@ -445,7 +459,7 @@ public class UnbundledChooserActivityTest {
onView(withId(com.android.internal.R.id.profile_button)).check(doesNotExist());
ResolveInfo[] chosen = new ResolveInfo[1];
- ChooserActivityOverrideData.getInstance().onSafelyStartCallback = targetInfo -> {
+ ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
chosen[0] = targetInfo.getResolveInfo();
return true;
};
@@ -472,7 +486,7 @@ public class UnbundledChooserActivityTest {
List<ResolvedComponentInfo> infosToStack = new ArrayList<>();
for (int i = 0; i < 4; i++) {
ResolveInfo resolveInfo = ResolverDataProvider.createResolveInfo(i,
- UserHandle.USER_CURRENT);
+ UserHandle.USER_CURRENT, PERSONAL_USER_HANDLE);
resolveInfo.activityInfo.applicationInfo.name = appName;
resolveInfo.activityInfo.applicationInfo.packageName = packageName;
resolveInfo.activityInfo.packageName = packageName;
@@ -491,7 +505,7 @@ public class UnbundledChooserActivityTest {
assertThat(activity.getAdapter().getCount(), is(6));
ResolveInfo[] chosen = new ResolveInfo[1];
- ChooserActivityOverrideData.getInstance().onSafelyStartCallback = targetInfo -> {
+ ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
chosen[0] = targetInfo.getResolveInfo();
return true;
};
@@ -522,17 +536,21 @@ public class UnbundledChooserActivityTest {
verify(ChooserActivityOverrideData.getInstance().resolverListController, times(1))
.topK(any(List.class), anyInt());
assertThat(activity.getIsSelected(), is(false));
- ChooserActivityOverrideData.getInstance().onSafelyStartCallback = targetInfo -> {
+ ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
return true;
};
ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
+ DisplayResolveInfo testDri =
+ activity.createTestDisplayResolveInfo(sendIntent, toChoose, "testLabel", "testInfo",
+ sendIntent, /* resolveInfoPresentationGetter */ null);
onView(withText(toChoose.activityInfo.name))
.perform(click());
waitForIdle();
verify(ChooserActivityOverrideData.getInstance().resolverListController, times(1))
- .updateChooserCounts(Mockito.anyString(), anyInt(), Mockito.anyString());
+ .updateChooserCounts(Mockito.anyString(), any(UserHandle.class),
+ Mockito.anyString());
verify(ChooserActivityOverrideData.getInstance().resolverListController, times(1))
- .updateModel(toChoose.activityInfo.getComponentName());
+ .updateModel(testDri);
assertThat(activity.getIsSelected(), is(true));
}
@@ -560,7 +578,7 @@ public class UnbundledChooserActivityTest {
@Test
public void autoLaunchSingleResult() throws InterruptedException {
ResolveInfo[] chosen = new ResolveInfo[1];
- ChooserActivityOverrideData.getInstance().onSafelyStartCallback = targetInfo -> {
+ ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
chosen[0] = targetInfo.getResolveInfo();
return true;
};
@@ -595,7 +613,7 @@ public class UnbundledChooserActivityTest {
assertThat(activity.getAdapter().getCount(), is(1));
ResolveInfo[] chosen = new ResolveInfo[1];
- ChooserActivityOverrideData.getInstance().onSafelyStartCallback = targetInfo -> {
+ ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
chosen[0] = targetInfo.getResolveInfo();
return true;
};
@@ -630,7 +648,7 @@ public class UnbundledChooserActivityTest {
assertThat(activity.getAdapter().getCount(), is(2));
ResolveInfo[] chosen = new ResolveInfo[1];
- ChooserActivityOverrideData.getInstance().onSafelyStartCallback = targetInfo -> {
+ ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
chosen[0] = targetInfo.getResolveInfo();
return true;
};
@@ -661,7 +679,7 @@ public class UnbundledChooserActivityTest {
assertThat(activity.getAdapter().getCount(), is(2));
ResolveInfo[] chosen = new ResolveInfo[1];
- ChooserActivityOverrideData.getInstance().onSafelyStartCallback = targetInfo -> {
+ ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
chosen[0] = targetInfo.getResolveInfo();
return true;
};
@@ -676,24 +694,21 @@ public class UnbundledChooserActivityTest {
}
@Test
- @RequireFeatureFlags(
- flags = { Flags.SHARESHEET_IMAGE_AND_TEXT_PREVIEW_NAME },
- values = { true })
- public void testImagePlusTextSharing_ExcludeText() {
- Intent sendIntent = createSendImageIntent(
- Uri.parse("android.resource://com.android.frameworks.coretests/"
- + R.drawable.test320x240));
- ChooserActivityOverrideData.getInstance().previewThumbnail = createBitmap();
- ChooserActivityOverrideData.getInstance().isImageType = true;
+ @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),
+ sendIntent, PERSONAL_USER_HANDLE),
ResolverDataProvider.createResolvedComponentInfo(
new ComponentName("org.textviewer", "UriTarget"),
- new Intent("VIEW_TEXT"))
+ new Intent("VIEW_TEXT"), PERSONAL_USER_HANDLE)
);
setupResolverControllers(resolvedComponentInfos);
@@ -706,8 +721,10 @@ public class UnbundledChooserActivityTest {
.perform(click());
waitForIdle();
+ onView(withId(R.id.content_preview_text)).check(matches(withText("File only")));
+
AtomicReference<Intent> launchedIntentRef = new AtomicReference<>();
- ChooserActivityOverrideData.getInstance().onSafelyStartCallback = targetInfo -> {
+ ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
launchedIntentRef.set(targetInfo.getTargetIntent());
return true;
};
@@ -719,25 +736,22 @@ public class UnbundledChooserActivityTest {
}
@Test
- @RequireFeatureFlags(
- flags = { Flags.SHARESHEET_IMAGE_AND_TEXT_PREVIEW_NAME },
- values = { true })
- public void testImagePlusTextSharing_RemoveAndAddBackText() {
- Intent sendIntent = createSendImageIntent(
- Uri.parse("android.resource://com.android.frameworks.coretests/"
- + R.drawable.test320x240));
- ChooserActivityOverrideData.getInstance().previewThumbnail = createBitmap();
- ChooserActivityOverrideData.getInstance().isImageType = true;
+ @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),
+ sendIntent, PERSONAL_USER_HANDLE),
ResolverDataProvider.createResolvedComponentInfo(
new ComponentName("org.textviewer", "UriTarget"),
- new Intent("VIEW_TEXT"))
+ new Intent("VIEW_TEXT"), PERSONAL_USER_HANDLE)
);
setupResolverControllers(resolvedComponentInfos);
@@ -749,12 +763,16 @@ public class UnbundledChooserActivityTest {
.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().onSafelyStartCallback = targetInfo -> {
+ ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
launchedIntentRef.set(targetInfo.getTargetIntent());
return true;
};
@@ -766,15 +784,12 @@ public class UnbundledChooserActivityTest {
}
@Test
- @RequireFeatureFlags(
- flags = { Flags.SHARESHEET_IMAGE_AND_TEXT_PREVIEW_NAME },
- values = { true })
- public void testImagePlusTextSharing_TextExclusionDoesNotAffectAlternativeIntent() {
- Intent sendIntent = createSendImageIntent(
- Uri.parse("android.resource://com.android.frameworks.coretests/"
- + R.drawable.test320x240));
- ChooserActivityOverrideData.getInstance().previewThumbnail = createBitmap();
- ChooserActivityOverrideData.getInstance().isImageType = true;
+ @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();
@@ -784,10 +799,10 @@ public class UnbundledChooserActivityTest {
List<ResolvedComponentInfo> resolvedComponentInfos = Arrays.asList(
ResolverDataProvider.createResolvedComponentInfo(
new ComponentName("org.imageviewer", "ImageTarget"),
- sendIntent),
+ sendIntent, PERSONAL_USER_HANDLE),
ResolverDataProvider.createResolvedComponentInfo(
new ComponentName("org.textviewer", "UriTarget"),
- alternativeIntent)
+ alternativeIntent, PERSONAL_USER_HANDLE)
);
setupResolverControllers(resolvedComponentInfos);
@@ -801,7 +816,7 @@ public class UnbundledChooserActivityTest {
waitForIdle();
AtomicReference<Intent> launchedIntentRef = new AtomicReference<>();
- ChooserActivityOverrideData.getInstance().onSafelyStartCallback = targetInfo -> {
+ ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
launchedIntentRef.set(targetInfo.getTargetIntent());
return true;
};
@@ -813,6 +828,40 @@ public class UnbundledChooserActivityTest {
}
@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() throws Exception {
Intent sendIntent = createSendTextIntent();
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
@@ -823,8 +872,8 @@ public class UnbundledChooserActivityTest {
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());
+ 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();
@@ -847,8 +896,8 @@ public class UnbundledChooserActivityTest {
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());
+ onView(withId(R.id.copy)).check(matches(isDisplayed()));
+ onView(withId(R.id.copy)).perform(click());
ChooserActivityLogger logger = activity.getChooserActivityLogger();
verify(logger, times(1)).logActionSelected(eq(ChooserActivityLogger.SELECTION_TYPE_COPY));
@@ -876,13 +925,11 @@ public class UnbundledChooserActivityTest {
@Test @Ignore
- public void testEditImageLogs() throws Exception {
- Intent sendIntent = createSendImageIntent(
- Uri.parse("android.resource://com.android.frameworks.coretests/"
- + R.drawable.test320x240));
-
- ChooserActivityOverrideData.getInstance().previewThumbnail = createBitmap();
- ChooserActivityOverrideData.getInstance().isImageType = true;
+ public void testEditImageLogs() {
+ Uri uri = createTestContentProviderUri("image/png", null);
+ Intent sendIntent = createSendImageIntent(uri);
+ ChooserActivityOverrideData.getInstance().imageLoader =
+ createImageLoader(uri, createBitmap());
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
@@ -901,113 +948,63 @@ public class UnbundledChooserActivityTest {
@Test
public void oneVisibleImagePreview() {
- Uri uri = Uri.parse("android.resource://com.android.frameworks.coretests/"
- + R.drawable.test320x240);
+ Uri uri = createTestContentProviderUri("image/png", null);
ArrayList<Uri> uris = new ArrayList<>();
uris.add(uri);
Intent sendIntent = createSendUriIntentWithPreview(uris);
- ChooserActivityOverrideData.getInstance().previewThumbnail = createBitmap();
- ChooserActivityOverrideData.getInstance().isImageType = true;
+ ChooserActivityOverrideData.getInstance().imageLoader =
+ createImageLoader(uri, createWideBitmap());
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
setupResolverControllers(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withId(com.android.internal.R.id.content_preview_image_area))
+ onView(withId(R.id.scrollable_image_preview))
.check((view, exception) -> {
if (exception != null) {
throw exception;
}
- ViewGroup parent = (ViewGroup) view;
- ArrayList<View> visibleViews = new ArrayList<>();
- for (int i = 0, count = parent.getChildCount(); i < count; i++) {
- View child = parent.getChildAt(i);
- if (child.getVisibility() == View.VISIBLE) {
- visibleViews.add(child);
- }
- }
- assertThat(visibleViews.size(), is(1));
+ 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 fully visible",
- isDisplayed().matches(visibleViews.get(0)));
+ "image preview view is not fully visible",
+ isPartiallyVisible
+ && rect.width() == imageView.getWidth()
+ && rect.height() == imageView.getHeight());
});
}
@Test
- @RequireFeatureFlags(
- flags = { Flags.SHARESHEET_SCROLLABLE_IMAGE_PREVIEW_NAME },
- values = { false })
- public void twoVisibleImagePreview() {
- Uri uri = Uri.parse("android.resource://com.android.frameworks.coretests/"
- + R.drawable.test320x240);
+ 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().previewThumbnail = createBitmap();
- ChooserActivityOverrideData.getInstance().isImageType = true;
+ ChooserActivityOverrideData.getInstance().imageLoader =
+ new TestPreviewImageLoader(Collections.emptyMap());
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
setupResolverControllers(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withId(com.android.internal.R.id.content_preview_image_1_large))
- .check(matches(isDisplayed()));
- onView(withId(com.android.internal.R.id.content_preview_image_2_large))
- .check(matches(isDisplayed()));
- onView(withId(com.android.internal.R.id.content_preview_image_2_small))
- .check(matches(not(isDisplayed())));
- onView(withId(com.android.internal.R.id.content_preview_image_3_small))
- .check(matches(not(isDisplayed())));
- }
-
- @Test
- @RequireFeatureFlags(
- flags = { Flags.SHARESHEET_SCROLLABLE_IMAGE_PREVIEW_NAME },
- values = { false })
- public void threeOrMoreVisibleImagePreview() {
- Uri uri = Uri.parse("android.resource://com.android.frameworks.coretests/"
- + R.drawable.test320x240);
-
- ArrayList<Uri> uris = new ArrayList<>();
- uris.add(uri);
- uris.add(uri);
- uris.add(uri);
- uris.add(uri);
- uris.add(uri);
-
- Intent sendIntent = createSendUriIntentWithPreview(uris);
- ChooserActivityOverrideData.getInstance().previewThumbnail = createBitmap();
- ChooserActivityOverrideData.getInstance().isImageType = true;
-
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
-
- setupResolverControllers(resolvedComponentInfos);
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
- onView(withId(com.android.internal.R.id.content_preview_image_1_large))
- .check(matches(isDisplayed()));
- onView(withId(com.android.internal.R.id.content_preview_image_2_large))
- .check(matches(not(isDisplayed())));
- onView(withId(com.android.internal.R.id.content_preview_image_2_small))
- .check(matches(isDisplayed()));
- onView(withId(com.android.internal.R.id.content_preview_image_3_small))
- .check(matches(isDisplayed()));
+ onView(withId(R.id.scrollable_image_preview))
+ .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)));
}
@Test
- @RequireFeatureFlags(
- flags = { Flags.SHARESHEET_SCROLLABLE_IMAGE_PREVIEW_NAME },
- values = { true })
public void testManyVisibleImagePreview_ScrollableImagePreview() {
- Uri uri = Uri.parse("android.resource://com.android.frameworks.coretests/"
- + R.drawable.test320x240);
+ Uri uri = createTestContentProviderUri("image/png", null);
ArrayList<Uri> uris = new ArrayList<>();
uris.add(uri);
@@ -1022,15 +1019,15 @@ public class UnbundledChooserActivityTest {
uris.add(uri);
Intent sendIntent = createSendUriIntentWithPreview(uris);
- ChooserActivityOverrideData.getInstance().previewThumbnail = createBitmap();
- ChooserActivityOverrideData.getInstance().isImageType = true;
+ 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_image_area))
+ onView(withId(R.id.scrollable_image_preview))
.perform(RecyclerViewActions.scrollToLastPosition())
.check((view, exception) -> {
if (exception != null) {
@@ -1042,12 +1039,8 @@ public class UnbundledChooserActivityTest {
}
@Test
- @RequireFeatureFlags(
- flags = { Flags.SHARESHEET_IMAGE_AND_TEXT_PREVIEW_NAME },
- values = { true })
public void testImageAndTextPreview() {
- final Uri uri = Uri.parse("android.resource://com.android.frameworks.coretests/"
- + R.drawable.test320x240);
+ final Uri uri = createTestContentProviderUri("image/png", null);
final String sharedText = "text-" + System.currentTimeMillis();
ArrayList<Uri> uris = new ArrayList<>();
@@ -1055,8 +1048,8 @@ public class UnbundledChooserActivityTest {
Intent sendIntent = createSendUriIntentWithPreview(uris);
sendIntent.putExtra(Intent.EXTRA_TEXT, sharedText);
- ChooserActivityOverrideData.getInstance().previewThumbnail = createBitmap();
- ChooserActivityOverrideData.getInstance().isImageType = true;
+ ChooserActivityOverrideData.getInstance().imageLoader =
+ createImageLoader(uri, createBitmap());
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
@@ -1068,6 +1061,38 @@ public class UnbundledChooserActivityTest {
}
@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);
@@ -1127,15 +1152,14 @@ public class UnbundledChooserActivityTest {
@Test
public void testImagePreviewLogging() {
- Uri uri = Uri.parse("android.resource://com.android.frameworks.coretests/"
- + R.drawable.test320x240);
+ Uri uri = createTestContentProviderUri("image/png", null);
ArrayList<Uri> uris = new ArrayList<>();
uris.add(uri);
Intent sendIntent = createSendUriIntentWithPreview(uris);
- ChooserActivityOverrideData.getInstance().previewThumbnail = createBitmap();
- ChooserActivityOverrideData.getInstance().isImageType = true;
+ ChooserActivityOverrideData.getInstance().imageLoader =
+ createImageLoader(uri, createBitmap());
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
@@ -1162,12 +1186,9 @@ public class UnbundledChooserActivityTest {
setupResolverControllers(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withId(com.android.internal.R.id.content_preview_filename))
- .check(matches(isDisplayed()));
- onView(withId(com.android.internal.R.id.content_preview_filename))
- .check(matches(withText("app.pdf")));
- onView(withId(com.android.internal.R.id.content_preview_file_icon))
- .check(matches(isDisplayed()));
+ 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()));
}
@@ -1187,12 +1208,11 @@ public class UnbundledChooserActivityTest {
setupResolverControllers(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withId(com.android.internal.R.id.content_preview_filename))
- .check(matches(isDisplayed()));
- onView(withId(com.android.internal.R.id.content_preview_filename))
- .check(matches(withText("app.pdf + 2 files")));
- onView(withId(com.android.internal.R.id.content_preview_file_icon))
- .check(matches(isDisplayed()));
+ 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
@@ -1211,12 +1231,9 @@ public class UnbundledChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withId(com.android.internal.R.id.content_preview_filename))
- .check(matches(isDisplayed()));
- onView(withId(com.android.internal.R.id.content_preview_filename))
- .check(matches(withText("app.pdf")));
- onView(withId(com.android.internal.R.id.content_preview_file_icon))
- .check(matches(isDisplayed()));
+ 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
@@ -1242,12 +1259,11 @@ public class UnbundledChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withId(com.android.internal.R.id.content_preview_filename))
- .check(matches(isDisplayed()));
- onView(withId(com.android.internal.R.id.content_preview_filename))
- .check(matches(withText("app.pdf + 1 file")));
- onView(withId(com.android.internal.R.id.content_preview_file_icon))
- .check(matches(isDisplayed()));
+ 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
@@ -1271,8 +1287,12 @@ public class UnbundledChooserActivityTest {
waitForIdle();
final DisplayResolveInfo testDri =
- activity.createTestDisplayResolveInfo(sendIntent,
- ResolverDataProvider.createResolveInfo(3, 0), "testLabel", "testInfo", sendIntent,
+ activity.createTestDisplayResolveInfo(
+ sendIntent,
+ ResolverDataProvider.createResolveInfo(3, 0, PERSONAL_USER_HANDLE),
+ "testLabel",
+ "testInfo",
+ sendIntent,
/* resolveInfoPresentationGetter */ null);
final ChooserListAdapter adapter = activity.getAdapter();
@@ -1305,7 +1325,7 @@ public class UnbundledChooserActivityTest {
// verify that ShortcutLoader was queried
ArgumentCaptor<DisplayResolveInfo[]> appTargets =
ArgumentCaptor.forClass(DisplayResolveInfo[].class);
- verify(shortcutLoaders.get(0).first, times(1)).queryShortcuts(appTargets.capture());
+ verify(shortcutLoaders.get(0).first, times(1)).updateAppTargets(appTargets.capture());
// send shortcuts
assertThat(
@@ -1386,7 +1406,7 @@ public class UnbundledChooserActivityTest {
// verify that ShortcutLoader was queried
ArgumentCaptor<DisplayResolveInfo[]> appTargets =
ArgumentCaptor.forClass(DisplayResolveInfo[].class);
- verify(shortcutLoaders.get(0).first, times(1)).queryShortcuts(appTargets.capture());
+ verify(shortcutLoaders.get(0).first, times(1)).updateAppTargets(appTargets.capture());
// send shortcuts
assertThat(
@@ -1471,7 +1491,7 @@ public class UnbundledChooserActivityTest {
// verify that ShortcutLoader was queried
ArgumentCaptor<DisplayResolveInfo[]> appTargets =
ArgumentCaptor.forClass(DisplayResolveInfo[].class);
- verify(shortcutLoaders.get(0).first, times(1)).queryShortcuts(appTargets.capture());
+ verify(shortcutLoaders.get(0).first, times(1)).updateAppTargets(appTargets.capture());
// send shortcuts
assertThat(
@@ -1546,7 +1566,7 @@ public class UnbundledChooserActivityTest {
// verify that ShortcutLoader was queried
ArgumentCaptor<DisplayResolveInfo[]> appTargets =
ArgumentCaptor.forClass(DisplayResolveInfo[].class);
- verify(shortcutLoaders.get(0).first, times(1)).queryShortcuts(appTargets.capture());
+ verify(shortcutLoaders.get(0).first, times(1)).updateAppTargets(appTargets.capture());
// send shortcuts
assertThat(
@@ -1611,7 +1631,8 @@ public class UnbundledChooserActivityTest {
// We need app targets for direct targets to get displayed
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
- setupResolverControllers(resolvedComponentInfos);
+ setupResolverControllers(resolvedComponentInfos, resolvedComponentInfos);
+ markWorkProfileUserAvailable();
// set caller-provided target
Intent chooserIntent = Intent.createChooser(createSendTextIntent(), null);
@@ -1638,7 +1659,7 @@ public class UnbundledChooserActivityTest {
// verify that ShortcutLoader was queried
ArgumentCaptor<DisplayResolveInfo[]> appTargets =
ArgumentCaptor.forClass(DisplayResolveInfo[].class);
- verify(shortcutLoaders.get(0).first, times(1)).queryShortcuts(appTargets.capture());
+ verify(shortcutLoaders.get(0).first, times(1)).updateAppTargets(appTargets.capture());
// send shortcuts
assertThat(
@@ -1667,12 +1688,20 @@ public class UnbundledChooserActivityTest {
"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
- @RequireFeatureFlags(
- flags = { Flags.SHARESHEET_CUSTOM_ACTIONS_NAME },
- values = { true })
public void testLaunchWithCustomAction() throws InterruptedException {
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
setupResolverControllers(resolvedComponentInfos);
@@ -1716,9 +1745,6 @@ public class UnbundledChooserActivityTest {
}
@Test
- @RequireFeatureFlags(
- flags = { Flags.SHARESHEET_RESELECTION_ACTION_NAME },
- values = { true })
public void testLaunchWithShareModification() throws InterruptedException {
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
setupResolverControllers(resolvedComponentInfos);
@@ -1726,13 +1752,17 @@ public class UnbundledChooserActivityTest {
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,
- PendingIntent.getBroadcast(
- testContext,
- 123,
- new Intent(modifyShareAction),
- PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT));
+ action);
// Start activity
mActivityRule.launchActivity(chooserIntent);
waitForIdle();
@@ -1747,7 +1777,7 @@ public class UnbundledChooserActivityTest {
testContext.registerReceiver(testReceiver, new IntentFilter(modifyShareAction));
try {
- onView(withText(R.string.select_text)).perform(click());
+ onView(withText(label)).perform(click());
broadcastInvoked.await();
} finally {
testContext.unregisterReceiver(testReceiver);
@@ -1810,7 +1840,7 @@ public class UnbundledChooserActivityTest {
// Create direct share target
List<ChooserTarget> serviceTargets = createDirectShareTargets(1,
resolvedComponentInfos.get(14).getResolveInfoAt(0).activityInfo.packageName);
- ResolveInfo ri = ResolverDataProvider.createResolveInfo(16, 0);
+ ResolveInfo ri = ResolverDataProvider.createResolveInfo(16, 0, PERSONAL_USER_HANDLE);
// Start activity
final IChooserWrapper wrapper = (IChooserWrapper)
@@ -1943,7 +1973,7 @@ public class UnbundledChooserActivityTest {
Intent sendIntent = createSendTextIntent();
sendIntent.setType(TEST_MIME_TYPE);
ResolveInfo[] chosen = new ResolveInfo[1];
- ChooserActivityOverrideData.getInstance().onSafelyStartCallback = targetInfo -> {
+ ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
chosen[0] = targetInfo.getResolveInfo();
return true;
};
@@ -2099,7 +2129,7 @@ public class UnbundledChooserActivityTest {
onView(withId(com.android.internal.R.id.profile_button)).check(doesNotExist());
ResolveInfo[] chosen = new ResolveInfo[1];
- ChooserActivityOverrideData.getInstance().onSafelyStartCallback = targetInfo -> {
+ ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
chosen[0] = targetInfo.getResolveInfo();
return true;
};
@@ -2139,7 +2169,7 @@ public class UnbundledChooserActivityTest {
ArgumentCaptor<DisplayResolveInfo[]> appTargets =
ArgumentCaptor.forClass(DisplayResolveInfo[].class);
verify(shortcutLoaders.get(0).first, times(1))
- .queryShortcuts(appTargets.capture());
+ .updateAppTargets(appTargets.capture());
// send shortcuts
assertThat(
@@ -2220,7 +2250,7 @@ public class UnbundledChooserActivityTest {
ArgumentCaptor<DisplayResolveInfo[]> appTargets =
ArgumentCaptor.forClass(DisplayResolveInfo[].class);
verify(shortcutLoaders.get(0).first, times(1))
- .queryShortcuts(appTargets.capture());
+ .updateAppTargets(appTargets.capture());
// send shortcuts
List<ChooserTarget> serviceTargets = createDirectShareTargets(
@@ -2315,7 +2345,7 @@ public class UnbundledChooserActivityTest {
}
@Test
- public void testWorkTab_onePersonalTarget_emptyStateOnWorkTarget_autolaunch() {
+ public void testWorkTab_onePersonalTarget_emptyStateOnWorkTarget_doesNotAutoLaunch() {
markWorkProfileUserAvailable();
int workProfileTargets = 4;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
@@ -2326,7 +2356,7 @@ public class UnbundledChooserActivityTest {
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendTextIntent();
ResolveInfo[] chosen = new ResolveInfo[1];
- ChooserActivityOverrideData.getInstance().onSafelyStartCallback = targetInfo -> {
+ ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
chosen[0] = targetInfo.getResolveInfo();
return true;
};
@@ -2334,7 +2364,7 @@ public class UnbundledChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "Test"));
waitForIdle();
- assertThat(chosen[0], is(personalResolvedComponentInfos.get(1).getResolveInfoAt(0)));
+ assertNull(chosen[0]);
}
@Test
@@ -2345,7 +2375,7 @@ public class UnbundledChooserActivityTest {
Intent chooserIntent = createChooserIntent(createSendTextIntent(),
new Intent[] {new Intent("action.fake")});
ResolveInfo[] chosen = new ResolveInfo[1];
- ChooserActivityOverrideData.getInstance().onSafelyStartCallback = targetInfo -> {
+ ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> {
chosen[0] = targetInfo.getResolveInfo();
return true;
};
@@ -2473,7 +2503,7 @@ public class UnbundledChooserActivityTest {
new Intent[] {new Intent("action.fake")});
ChooserActivityOverrideData.getInstance().packageManager = mock(PackageManager.class);
ResolveInfo ri = ResolverDataProvider.createResolveInfo(0,
- UserHandle.USER_CURRENT);
+ UserHandle.USER_CURRENT, PERSONAL_USER_HANDLE);
when(
ChooserActivityOverrideData
.getInstance()
@@ -2515,12 +2545,52 @@ public class UnbundledChooserActivityTest {
.perform(swipeUp());
waitForIdle();
- verify(personalProfileShortcutLoader, times(1)).queryShortcuts(any());
+ verify(personalProfileShortcutLoader, times(1)).updateAppTargets(any());
onView(withText(R.string.resolver_work_tab)).perform(click());
waitForIdle();
- verify(workProfileShortcutLoader, times(1)).queryShortcuts(any());
+ verify(workProfileShortcutLoader, times(1)).updateAppTargets(any());
+ }
+
+ @Test
+ public void testClonedProfilePresent_personalAdapterIsSetWithPersonalProfile() {
+ // enable cloneProfile
+ markCloneProfileUserAvailable();
+ List<ResolvedComponentInfo> resolvedComponentInfos =
+ createResolvedComponentsWithCloneProfileForTest(
+ 3,
+ PERSONAL_USER_HANDLE,
+ ChooserActivityOverrideData.getInstance().cloneProfileUserHandle);
+ 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() {
+ markWorkProfileUserAvailable();
+ markCloneProfileUserAvailable();
+ 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) {
@@ -2557,6 +2627,7 @@ public class UnbundledChooserActivityTest {
ri.activityInfo.packageName = "fake.package.name";
ri.activityInfo.applicationInfo = new ApplicationInfo();
ri.activityInfo.applicationInfo.packageName = "fake.package.name";
+ ri.userHandle = UserHandle.CURRENT;
return ri;
}
@@ -2581,6 +2652,21 @@ public class UnbundledChooserActivityTest {
return sendIntent;
}
+ private Uri createTestContentProviderUri(
+ @Nullable String mimeType, @Nullable String streamType) {
+ String packageName =
+ InstrumentationRegistry.getInstrumentation().getContext().getPackageName();
+ Uri.Builder builder = Uri.parse("content://" + packageName + "/image.png")
+ .buildUpon();
+ if (mimeType != null) {
+ builder.appendQueryParameter("mimeType", mimeType);
+ }
+ if (streamType != null) {
+ builder.appendQueryParameter("streamType", streamType);
+ }
+ return builder.build();
+ }
+
private Intent createSendTextIntentWithPreview(String title, Uri imageThumbnail) {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
@@ -2618,7 +2704,23 @@ public class UnbundledChooserActivityTest {
private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults) {
List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
for (int i = 0; i < numberOfResults; i++) {
- infoList.add(ResolverDataProvider.createResolvedComponentInfo(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;
}
@@ -2628,9 +2730,11 @@ public class UnbundledChooserActivityTest {
List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
for (int i = 0; i < numberOfResults; i++) {
if (i == 0) {
- infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i));
+ infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i,
+ PERSONAL_USER_HANDLE));
} else {
- infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+ infoList.add(ResolverDataProvider.createResolvedComponentInfo(i,
+ PERSONAL_USER_HANDLE));
}
}
return infoList;
@@ -2642,9 +2746,11 @@ public class UnbundledChooserActivityTest {
for (int i = 0; i < numberOfResults; i++) {
if (i == 0) {
infoList.add(
- ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId));
+ ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId,
+ PERSONAL_USER_HANDLE));
} else {
- infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+ infoList.add(ResolverDataProvider.createResolvedComponentInfo(i,
+ PERSONAL_USER_HANDLE));
}
}
return infoList;
@@ -2654,7 +2760,8 @@ public class UnbundledChooserActivityTest {
int numberOfResults, int userId) {
List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
for (int i = 0; i < numberOfResults; i++) {
- infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId));
+ infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId,
+ PERSONAL_USER_HANDLE));
}
return infoList;
}
@@ -2686,8 +2793,22 @@ public class UnbundledChooserActivityTest {
}
private Bitmap createBitmap() {
- int width = 200;
- int height = 200;
+ return createBitmap(200, 200);
+ }
+
+ private Bitmap createWideBitmap() {
+ 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);
+ }
+
+ private Bitmap createBitmap(int width, int height) {
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
@@ -2733,6 +2854,10 @@ public class UnbundledChooserActivityTest {
ChooserActivityOverrideData.getInstance().workProfileUserHandle = UserHandle.of(10);
}
+ private void markCloneProfileUserAvailable() {
+ ChooserActivityOverrideData.getInstance().cloneProfileUserHandle = UserHandle.of(11);
+ }
+
private void setupResolverControllers(
List<ResolvedComponentInfo> personalResolvedComponentInfos) {
setupResolverControllers(personalResolvedComponentInfos, new ArrayList<>());
@@ -2839,4 +2964,8 @@ public class UnbundledChooserActivityTest {
};
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
index 87dc1b9d..92bccb7d 100644
--- a/java/tests/src/com/android/intentresolver/UnbundledChooserActivityWorkProfileTest.java
+++ b/java/tests/src/com/android/intentresolver/UnbundledChooserActivityWorkProfileTest.java
@@ -49,7 +49,6 @@ import androidx.test.espresso.NoMatchingViewException;
import androidx.test.rule.ActivityTestRule;
import com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.Tab;
-import com.android.internal.R;
import junit.framework.AssertionFailedError;
@@ -99,7 +98,7 @@ public class UnbundledChooserActivityWorkProfileTest {
public void testBlocker() {
setUpPersonalAndWorkComponentInfos();
sOverrides.hasCrossProfileIntents = mTestCase.hasCrossProfileIntents();
- sOverrides.myUserId = mTestCase.getMyUserHandle().getIdentifier();
+ sOverrides.tabOwnerUserHandleForLaunch = mTestCase.getMyUserHandle();
launchActivity(mTestCase.getIsSendAction());
switchToTab(mTestCase.getTab());
@@ -242,19 +241,21 @@ public class UnbundledChooserActivityWorkProfileTest {
}
private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
- int numberOfResults, int userId) {
+ 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));
+ ResolverDataProvider
+ .createResolvedComponentInfoWithOtherId(i, userId, resolvedForUser));
}
return infoList;
}
- private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults) {
+ 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));
+ infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, resolvedForUser));
}
return infoList;
}
@@ -264,9 +265,9 @@ public class UnbundledChooserActivityWorkProfileTest {
int workProfileTargets = 4;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
createResolvedComponentsForTestWithOtherProfile(3,
- /* userId */ WORK_USER_HANDLE.getIdentifier());
+ /* userId */ WORK_USER_HANDLE.getIdentifier(), PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(workProfileTargets);
+ createResolvedComponentsForTest(workProfileTargets, WORK_USER_HANDLE);
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
}
@@ -356,7 +357,7 @@ public class UnbundledChooserActivityWorkProfileTest {
}
});
- onView(withId(R.id.contentPanel))
+ onView(withId(com.android.internal.R.id.contentPanel))
.perform(swipeUp());
waitForIdle();
}
diff --git a/java/tests/src/com/android/intentresolver/chooser/ImmutableTargetInfoTest.kt b/java/tests/src/com/android/intentresolver/chooser/ImmutableTargetInfoTest.kt
index dff1e5ae..504cfd97 100644
--- a/java/tests/src/com/android/intentresolver/chooser/ImmutableTargetInfoTest.kt
+++ b/java/tests/src/com/android/intentresolver/chooser/ImmutableTargetInfoTest.kt
@@ -30,14 +30,18 @@ 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)
+ 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()
@@ -45,14 +49,14 @@ class ImmutableTargetInfoTest {
private val sourceIntent2 = Intent("source2")
private val displayTarget1 = DisplayResolveInfo.newDisplayResolveInfo(
Intent("display1"),
- ResolverDataProvider.createResolveInfo(2, 0),
+ ResolverDataProvider.createResolveInfo(2, 0, PERSONAL_USER_HANDLE),
"display1 label",
"display1 extended info",
Intent("display1_resolved"),
/* resolveInfoPresentationGetter= */ null)
private val displayTarget2 = DisplayResolveInfo.newDisplayResolveInfo(
Intent("display2"),
- ResolverDataProvider.createResolveInfo(3, 0),
+ ResolverDataProvider.createResolveInfo(3, 0, PERSONAL_USER_HANDLE),
"display2 label",
"display2 extended info",
Intent("display2_resolved"),
@@ -66,7 +70,7 @@ class ImmutableTargetInfoTest {
UserHandle.CURRENT)
private val displayResolveInfo = DisplayResolveInfo.newDisplayResolveInfo(
Intent("displayresolve"),
- ResolverDataProvider.createResolveInfo(5, 0),
+ ResolverDataProvider.createResolveInfo(5, 0, PERSONAL_USER_HANDLE),
"displayresolve label",
"displayresolve extended info",
Intent("display_resolved"),
diff --git a/java/tests/src/com/android/intentresolver/chooser/TargetInfoTest.kt b/java/tests/src/com/android/intentresolver/chooser/TargetInfoTest.kt
index 3d832cc9..f9d3dd96 100644
--- a/java/tests/src/com/android/intentresolver/chooser/TargetInfoTest.kt
+++ b/java/tests/src/com/android/intentresolver/chooser/TargetInfoTest.kt
@@ -41,6 +41,9 @@ 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
@@ -81,7 +84,7 @@ class TargetInfoTest {
val resolvedIntent = Intent()
val baseDisplayInfo = DisplayResolveInfo.newDisplayResolveInfo(
resolvedIntent,
- ResolverDataProvider.createResolveInfo(1, 0),
+ ResolverDataProvider.createResolveInfo(1, 0, PERSONAL_USER_HANDLE),
"label",
"extended info",
resolvedIntent,
@@ -190,7 +193,7 @@ class TargetInfoTest {
intent.putExtra(Intent.EXTRA_TEXT, "testing intent sending")
intent.setType("text/plain")
- val resolveInfo = ResolverDataProvider.createResolveInfo(3, 0)
+ val resolveInfo = ResolverDataProvider.createResolveInfo(3, 0, PERSONAL_USER_HANDLE)
val targetInfo = DisplayResolveInfo.newDisplayResolveInfo(
intent,
@@ -268,7 +271,7 @@ class TargetInfoTest {
intent.putExtra(Intent.EXTRA_TEXT, "testing intent sending")
intent.setType("text/plain")
- val resolveInfo = ResolverDataProvider.createResolveInfo(3, 0)
+ val resolveInfo = ResolverDataProvider.createResolveInfo(3, 0, PERSONAL_USER_HANDLE)
val firstTargetInfo = DisplayResolveInfo.newDisplayResolveInfo(
intent,
resolveInfo,
diff --git a/java/tests/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt b/java/tests/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt
index d870a8c2..9bfd2052 100644
--- a/java/tests/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt
+++ b/java/tests/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt
@@ -16,188 +16,131 @@
package com.android.intentresolver.contentpreview
-import android.content.ClipDescription
-import android.content.ContentInterface
import android.content.Intent
import android.graphics.Bitmap
import android.net.Uri
-import com.android.intentresolver.ImageLoader
-import com.android.intentresolver.TestFeatureFlagRepository
+import androidx.lifecycle.Lifecycle
+import com.android.intentresolver.any
import com.android.intentresolver.contentpreview.ChooserContentPreviewUi.ActionFactory
-import com.android.intentresolver.flags.Flags
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 org.junit.Test
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
-import java.util.function.Consumer
-private const val PROVIDER_NAME = "org.pkg.app"
class ChooserContentPreviewUiTest {
- private val contentResolver = mock<ContentInterface>()
- private val imageClassifier = ChooserContentPreviewUi.ImageMimeTypeClassifier { mimeType ->
- mimeType != null && ClipDescription.compareMimeTypes(mimeType, "image/*")
- }
- private val imageLoader = object : ImageLoader {
- override fun loadImage(uri: Uri, callback: Consumer<Bitmap?>) {
- callback.accept(null)
+ private val lifecycle = mock<Lifecycle>()
+ private val previewData = mock<PreviewDataProvider>()
+ private val headlineGenerator = mock<HeadlineGenerator>()
+ private val imageLoader =
+ object : ImageLoader {
+ override fun loadImage(
+ callerLifecycle: Lifecycle,
+ uri: Uri,
+ callback: Consumer<Bitmap?>,
+ ) {
+ callback.accept(null)
+ }
+ override fun prePopulate(uris: List<Uri>) = Unit
+ override suspend fun invoke(uri: Uri, caching: Boolean): Bitmap? = null
+ }
+ 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> {}
}
- override fun prePopulate(uris: List<Uri>) = Unit
- override suspend fun invoke(uri: Uri): Bitmap? = null
- }
- private val actionFactory = object : ActionFactory {
- override fun createCopyButton() = ActionRow.Action(label = "Copy", icon = null) {}
- override fun createEditButton(): ActionRow.Action? = null
- override fun createNearbyButton(): ActionRow.Action? = null
- override fun createCustomActions(): List<ActionRow.Action> = emptyList()
- override fun getModifyShareAction(): Runnable? = null
- override fun getExcludeSharedTextAction(): Consumer<Boolean> = Consumer<Boolean> {}
- }
private val transitionCallback = mock<ImagePreviewView.TransitionElementStatusCallback>()
- private val featureFlagRepository = TestFeatureFlagRepository(
- mapOf(
- Flags.SHARESHEET_SCROLLABLE_IMAGE_PREVIEW to true
- )
- )
@Test
- fun test_ChooserContentPreview_non_send_intent_action_to_text_preview() {
- val targetIntent = Intent(Intent.ACTION_VIEW)
- val testSubject = ChooserContentPreviewUi(
- targetIntent,
- contentResolver,
- imageClassifier,
- imageLoader,
- actionFactory,
- transitionCallback,
- featureFlagRepository
- )
+ fun test_textPreviewType_useTextPreviewUi() {
+ whenever(previewData.previewType).thenReturn(ContentPreviewType.CONTENT_PREVIEW_TEXT)
+ val testSubject =
+ ChooserContentPreviewUi(
+ lifecycle,
+ 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_ChooserContentPreview_text_mime_type_to_text_preview() {
- val targetIntent = Intent(Intent.ACTION_SEND).apply {
- type = "text/plain"
- putExtra(Intent.EXTRA_TEXT, "Text Extra")
- }
- val testSubject = ChooserContentPreviewUi(
- targetIntent,
- contentResolver,
- imageClassifier,
- imageLoader,
- actionFactory,
- transitionCallback,
- featureFlagRepository
- )
+ fun test_filePreviewType_useFilePreviewUi() {
+ whenever(previewData.previewType).thenReturn(ContentPreviewType.CONTENT_PREVIEW_FILE)
+ val testSubject =
+ ChooserContentPreviewUi(
+ lifecycle,
+ previewData,
+ Intent(Intent.ACTION_SEND),
+ imageLoader,
+ actionFactory,
+ transitionCallback,
+ headlineGenerator,
+ )
assertThat(testSubject.preferredContentPreview)
- .isEqualTo(ContentPreviewType.CONTENT_PREVIEW_TEXT)
+ .isEqualTo(ContentPreviewType.CONTENT_PREVIEW_FILE)
+ assertThat(testSubject.mContentPreviewUi).isInstanceOf(FileContentPreviewUi::class.java)
verify(transitionCallback, times(1)).onAllTransitionElementsReady()
}
@Test
- fun test_ChooserContentPreview_single_image_uri_to_image_preview() {
- val uri = Uri.parse("content://$PROVIDER_NAME/test.png")
- val targetIntent = Intent(Intent.ACTION_SEND).apply {
- putExtra(Intent.EXTRA_STREAM, uri)
- }
- whenever(contentResolver.getType(uri)).thenReturn("image/png")
- val testSubject = ChooserContentPreviewUi(
- targetIntent,
- contentResolver,
- imageClassifier,
- imageLoader,
- actionFactory,
- transitionCallback,
- featureFlagRepository
- )
- assertThat(testSubject.preferredContentPreview)
- .isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE)
- verify(transitionCallback, never()).onAllTransitionElementsReady()
- }
-
- @Test
- fun test_ChooserContentPreview_single_non_image_uri_to_file_preview() {
- val uri = Uri.parse("content://$PROVIDER_NAME/test.pdf")
- val targetIntent = Intent(Intent.ACTION_SEND).apply {
- putExtra(Intent.EXTRA_STREAM, uri)
- }
- whenever(contentResolver.getType(uri)).thenReturn("application/pdf")
- val testSubject = ChooserContentPreviewUi(
- targetIntent,
- contentResolver,
- imageClassifier,
- imageLoader,
- actionFactory,
- transitionCallback,
- featureFlagRepository
- )
- assertThat(testSubject.preferredContentPreview)
- .isEqualTo(ContentPreviewType.CONTENT_PREVIEW_FILE)
+ 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())
+ val testSubject =
+ ChooserContentPreviewUi(
+ lifecycle,
+ 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)).getFileMetadataForImagePreview(any(), any())
verify(transitionCallback, times(1)).onAllTransitionElementsReady()
}
@Test
- fun test_ChooserContentPreview_multiple_image_uri_to_image_preview() {
- val uri1 = Uri.parse("content://$PROVIDER_NAME/test.png")
- val uri2 = Uri.parse("content://$PROVIDER_NAME/test.jpg")
- val targetIntent = Intent(Intent.ACTION_SEND_MULTIPLE).apply {
- putExtra(
- Intent.EXTRA_STREAM,
- ArrayList<Uri>().apply {
- add(uri1)
- add(uri2)
- }
+ 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())
+ val testSubject =
+ ChooserContentPreviewUi(
+ lifecycle,
+ previewData,
+ Intent(Intent.ACTION_SEND),
+ imageLoader,
+ actionFactory,
+ transitionCallback,
+ headlineGenerator,
)
- }
- whenever(contentResolver.getType(uri1)).thenReturn("image/png")
- whenever(contentResolver.getType(uri2)).thenReturn("image/jpeg")
- val testSubject = ChooserContentPreviewUi(
- targetIntent,
- contentResolver,
- imageClassifier,
- imageLoader,
- actionFactory,
- transitionCallback,
- featureFlagRepository
- )
assertThat(testSubject.preferredContentPreview)
.isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE)
+ assertThat(testSubject.mContentPreviewUi).isInstanceOf(UnifiedContentPreviewUi::class.java)
+ verify(previewData, times(1)).getFileMetadataForImagePreview(any(), any())
verify(transitionCallback, never()).onAllTransitionElementsReady()
}
-
- @Test
- fun test_ChooserContentPreview_some_non_image_uri_to_file_preview() {
- val uri1 = Uri.parse("content://$PROVIDER_NAME/test.png")
- val uri2 = Uri.parse("content://$PROVIDER_NAME/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("image/png")
- whenever(contentResolver.getType(uri2)).thenReturn("application/pdf")
- val testSubject = ChooserContentPreviewUi(
- targetIntent,
- contentResolver,
- imageClassifier,
- imageLoader,
- actionFactory,
- transitionCallback,
- featureFlagRepository
- )
- assertThat(testSubject.preferredContentPreview)
- .isEqualTo(ContentPreviewType.CONTENT_PREVIEW_FILE)
- verify(transitionCallback, times(1)).onAllTransitionElementsReady()
- }
}
diff --git a/java/tests/src/com/android/intentresolver/contentpreview/ContentPreviewUiTest.kt b/java/tests/src/com/android/intentresolver/contentpreview/ContentPreviewUiTest.kt
new file mode 100644
index 00000000..6db53a9e
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/contentpreview/ContentPreviewUiTest.kt
@@ -0,0 +1,41 @@
+/*
+ * 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/HeadlineGeneratorImplTest.kt b/java/tests/src/com/android/intentresolver/contentpreview/HeadlineGeneratorImplTest.kt
new file mode 100644
index 00000000..a65280e5
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/contentpreview/HeadlineGeneratorImplTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.intentresolver.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
new file mode 100644
index 00000000..6e57c289
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/contentpreview/ImagePreviewImageLoaderTest.kt
@@ -0,0 +1,366 @@
+/*
+ * 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 com.android.intentresolver.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.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.state = Lifecycle.State.CREATED
+ // 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.state = Lifecycle.State.DESTROYED
+ 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.state = Lifecycle.State.DESTROYED
+ 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.state = Lifecycle.State.DESTROYED
+ 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()
+ latch.await()
+ 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
new file mode 100644
index 00000000..145b89ad
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/contentpreview/PreviewDataProviderTest.kt
@@ -0,0 +1,351 @@
+/*
+ * 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 androidx.lifecycle.Lifecycle
+import com.android.intentresolver.TestLifecycleOwner
+import com.android.intentresolver.mock
+import com.android.intentresolver.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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.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 lifecycleOwner = TestLifecycleOwner()
+ private val dispatcher = UnconfinedTestDispatcher()
+
+ @Before
+ fun setup() {
+ Dispatchers.setMain(dispatcher)
+ lifecycleOwner.state = Lifecycle.State.CREATED
+ }
+
+ @After
+ fun cleanup() {
+ lifecycleOwner.state = Lifecycle.State.DESTROYED
+ Dispatchers.resetMain()
+ }
+
+ @Test
+ fun test_nonSendIntentAction_resolvesToTextPreviewUiSynchronously() {
+ val targetIntent = Intent(Intent.ACTION_VIEW)
+ val testSubject =
+ PreviewDataProvider(targetIntent, contentResolver, mimeTypeClassifier, dispatcher)
+
+ 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(targetIntent, contentResolver, mimeTypeClassifier, dispatcher)
+
+ 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(targetIntent, contentResolver, mimeTypeClassifier, dispatcher)
+
+ 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(targetIntent, contentResolver, mimeTypeClassifier, dispatcher)
+
+ 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(targetIntent, contentResolver, mimeTypeClassifier, dispatcher)
+
+ 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(targetIntent, contentResolver, mimeTypeClassifier, dispatcher)
+
+ 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(targetIntent, contentResolver, mimeTypeClassifier, dispatcher)
+
+ 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(targetIntent, contentResolver, mimeTypeClassifier, dispatcher)
+
+ 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")
+ whenever(contentResolver.query(uri, METADATA_COLUMNS, null, null))
+ .thenReturn(MatrixCursor(columns).apply { addRow(values) })
+ val testSubject =
+ PreviewDataProvider(targetIntent, contentResolver, mimeTypeClassifier, dispatcher)
+
+ 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())
+ }
+
+ @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(targetIntent, contentResolver, mimeTypeClassifier, dispatcher)
+
+ 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(targetIntent, contentResolver, mimeTypeClassifier, dispatcher)
+
+ 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(targetIntent, contentResolver, mimeTypeClassifier, dispatcher)
+
+ 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(targetIntent, contentResolver, mimeTypeClassifier, dispatcher)
+
+ 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())
+ }
+}
diff --git a/java/tests/src/com/android/intentresolver/model/AbstractResolverComparatorTest.java b/java/tests/src/com/android/intentresolver/model/AbstractResolverComparatorTest.java
index 006f3b2d..5f0ead7b 100644
--- a/java/tests/src/com/android/intentresolver/model/AbstractResolverComparatorTest.java
+++ b/java/tests/src/com/android/intentresolver/model/AbstractResolverComparatorTest.java
@@ -28,6 +28,9 @@ 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;
@@ -37,51 +40,82 @@ public class AbstractResolverComparatorTest {
@Test
public void testPinned() {
- ResolvedComponentInfo r1 = new ResolvedComponentInfo(
- new ComponentName("package", "class"), new Intent(), new ResolveInfo()
- );
+ ResolvedComponentInfo r1 = createResolvedComponentInfo(
+ new ComponentName("package", "class"));
r1.setPinned(true);
- ResolvedComponentInfo r2 = new ResolvedComponentInfo(
- new ComponentName("zackage", "zlass"), new Intent(), new ResolveInfo()
- );
+ ResolvedComponentInfo r2 = createResolvedComponentInfo(
+ new ComponentName("zackage", "zlass"));
Context context = InstrumentationRegistry.getTargetContext();
- AbstractResolverComparator comparator = getTestComparator(context);
+ 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() {
- ResolveInfo pmInfo1 = new ResolveInfo();
- pmInfo1.activityInfo = new ActivityInfo();
- pmInfo1.activityInfo.packageName = "aaa";
-
- ResolvedComponentInfo r1 = new ResolvedComponentInfo(
- new ComponentName("package", "class"), new Intent(), pmInfo1);
+ ResolvedComponentInfo r1 = createResolvedComponentInfo(
+ new ComponentName("package", "class"));
r1.setPinned(true);
- ResolveInfo pmInfo2 = new ResolveInfo();
- pmInfo2.activityInfo = new ActivityInfo();
- pmInfo2.activityInfo.packageName = "zzz";
- ResolvedComponentInfo r2 = new ResolvedComponentInfo(
- new ComponentName("zackage", "zlass"), new Intent(), pmInfo2);
+ ResolvedComponentInfo r2 = createResolvedComponentInfo(
+ new ComponentName("zackage", "zlass"));
r2.setPinned(true);
Context context = InstrumentationRegistry.getTargetContext();
- AbstractResolverComparator comparator = getTestComparator(context);
+ AbstractResolverComparator comparator = getTestComparator(context, null);
assertEquals("Both pinned should rank alphabetically", -1, comparator.compare(r1, r2));
}
- private AbstractResolverComparator getTestComparator(Context context) {
+ @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) {
+ new AbstractResolverComparator(context, intent,
+ Lists.newArrayList(context.getUser()), promoteToFirst) {
@Override
int compare(ResolveInfo lhs, ResolveInfo rhs) {
@@ -94,7 +128,7 @@ public class AbstractResolverComparatorTest {
void doCompute(List<ResolvedComponentInfo> targets) {}
@Override
- public float getScore(ComponentName name) {
+ public float getScore(TargetInfo targetInfo) {
return 0;
}
diff --git a/java/tests/src/com/android/intentresolver/shortcuts/ShortcutLoaderTest.kt b/java/tests/src/com/android/intentresolver/shortcuts/ShortcutLoaderTest.kt
index 0c817cb2..742aac71 100644
--- a/java/tests/src/com/android/intentresolver/shortcuts/ShortcutLoaderTest.kt
+++ b/java/tests/src/com/android/intentresolver/shortcuts/ShortcutLoaderTest.kt
@@ -26,7 +26,9 @@ import android.content.pm.PackageManager.ApplicationInfoFlags
import android.content.pm.ShortcutManager
import android.os.UserHandle
import android.os.UserManager
+import androidx.lifecycle.Lifecycle
import androidx.test.filters.SmallTest
+import com.android.intentresolver.TestLifecycleOwner
import com.android.intentresolver.any
import com.android.intentresolver.argumentCaptor
import com.android.intentresolver.capture
@@ -36,19 +38,27 @@ import com.android.intentresolver.createShareShortcutInfo
import com.android.intentresolver.createShortcutInfo
import com.android.intentresolver.mock
import com.android.intentresolver.whenever
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.setMain
+import org.junit.After
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
+import org.junit.Before
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
-import java.util.concurrent.Executor
import java.util.function.Consumer
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
class ShortcutLoaderTest {
private val appInfo = ApplicationInfo().apply {
@@ -58,7 +68,7 @@ class ShortcutLoaderTest {
private val pm = mock<PackageManager> {
whenever(getApplicationInfo(any(), any<ApplicationInfoFlags>())).thenReturn(appInfo)
}
- val userManager = mock<UserManager> {
+ private val userManager = mock<UserManager> {
whenever(isUserRunning(any<UserHandle>())).thenReturn(true)
whenever(isUserUnlocked(any<UserHandle>())).thenReturn(true)
whenever(isQuietModeEnabled(any<UserHandle>())).thenReturn(false)
@@ -68,32 +78,46 @@ class ShortcutLoaderTest {
whenever(createContextAsUser(any(), anyInt())).thenReturn(this)
whenever(getSystemService(Context.USER_SERVICE)).thenReturn(userManager)
}
- private val executor = ImmediateExecutor()
+ private val scheduler = TestCoroutineScheduler()
+ private val dispatcher = UnconfinedTestDispatcher(scheduler)
+ private val lifecycleOwner = TestLifecycleOwner()
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)
+
+ @Before
+ fun setup() {
+ Dispatchers.setMain(dispatcher)
+ lifecycleOwner.state = Lifecycle.State.CREATED
+ }
+
+ @After
+ fun cleanup() {
+ lifecycleOwner.state = Lifecycle.State.DESTROYED
+ Dispatchers.resetMain()
+ }
@Test
- fun test_queryShortcuts_result_consistency_with_AppPredictor() {
- val componentName = ComponentName("pkg", "Class")
- val appTarget = mock<DisplayResolveInfo> {
- whenever(resolvedComponentName).thenReturn(componentName)
- }
- val appTargets = arrayOf(appTarget)
+ fun test_loadShortcutsWithAppPredictor_resultIntegrity() {
val testSubject = ShortcutLoader(
context,
+ lifecycleOwner.lifecycle,
appPredictor,
UserHandle.of(0),
true,
intentFilter,
- executor,
- executor,
+ dispatcher,
callback
)
- testSubject.queryShortcuts(appTargets)
+ testSubject.updateAppTargets(appTargets)
- val matchingShortcutInfo = createShortcutInfo("id-0", componentName, 1)
val matchingAppTarget = createAppTarget(matchingShortcutInfo)
val shortcuts = listOf(
matchingAppTarget,
@@ -130,13 +154,7 @@ class ShortcutLoaderTest {
}
@Test
- fun test_queryShortcuts_result_consistency_with_ShortcutManager() {
- val componentName = ComponentName("pkg", "Class")
- val appTarget = mock<DisplayResolveInfo> {
- whenever(resolvedComponentName).thenReturn(componentName)
- }
- val appTargets = arrayOf(appTarget)
- val matchingShortcutInfo = createShortcutInfo("id-0", componentName, 1)
+ fun test_loadShortcutsWithShortcutManager_resultIntegrity() {
val shortcutManagerResult = listOf(
ShortcutManager.ShareShortcutInfo(matchingShortcutInfo, componentName),
// mismatching shortcut
@@ -148,16 +166,16 @@ class ShortcutLoaderTest {
whenever(context.getSystemService(Context.SHORTCUT_SERVICE)).thenReturn(shortcutManager)
val testSubject = ShortcutLoader(
context,
+ lifecycleOwner.lifecycle,
null,
UserHandle.of(0),
true,
intentFilter,
- executor,
- executor,
+ dispatcher,
callback
)
- testSubject.queryShortcuts(appTargets)
+ testSubject.updateAppTargets(appTargets)
val resultCaptor = argumentCaptor<ShortcutLoader.Result>()
verify(callback, times(1)).accept(capture(resultCaptor))
@@ -181,13 +199,7 @@ class ShortcutLoaderTest {
}
@Test
- fun test_queryShortcuts_falls_back_to_ShortcutManager_on_empty_reply() {
- val componentName = ComponentName("pkg", "Class")
- val appTarget = mock<DisplayResolveInfo> {
- whenever(resolvedComponentName).thenReturn(componentName)
- }
- val appTargets = arrayOf(appTarget)
- val matchingShortcutInfo = createShortcutInfo("id-0", componentName, 1)
+ fun test_appPredictorReturnsEmptyList_fallbackToShortcutManager() {
val shortcutManagerResult = listOf(
ShortcutManager.ShareShortcutInfo(matchingShortcutInfo, componentName),
// mismatching shortcut
@@ -199,16 +211,16 @@ class ShortcutLoaderTest {
whenever(context.getSystemService(Context.SHORTCUT_SERVICE)).thenReturn(shortcutManager)
val testSubject = ShortcutLoader(
context,
+ lifecycleOwner.lifecycle,
appPredictor,
UserHandle.of(0),
true,
intentFilter,
- executor,
- executor,
+ dispatcher,
callback
)
- testSubject.queryShortcuts(appTargets)
+ testSubject.updateAppTargets(appTargets)
verify(appPredictor, times(1)).requestPredictionUpdate()
val appPredictorCallbackCaptor = argumentCaptor<AppPredictor.Callback>()
@@ -238,32 +250,154 @@ class ShortcutLoaderTest {
}
@Test
- fun test_queryShortcuts_do_not_call_services_for_not_running_work_profile() {
+ fun test_appPredictor_requestPredictionUpdateFailure_fallbackToShortcutManager() {
+ val shortcutManagerResult = listOf(
+ ShortcutManager.ShareShortcutInfo(matchingShortcutInfo, componentName),
+ // mismatching shortcut
+ createShareShortcutInfo("id-1", ComponentName("mismatching.pkg", "Class"), 1)
+ )
+ val shortcutManager = mock<ShortcutManager> {
+ whenever(getShareTargets(intentFilter)).thenReturn(shortcutManagerResult)
+ }
+ whenever(context.getSystemService(Context.SHORTCUT_SERVICE)).thenReturn(shortcutManager)
+ whenever(appPredictor.requestPredictionUpdate())
+ .thenThrow(IllegalStateException("Test exception"))
+ val testSubject = ShortcutLoader(
+ context,
+ lifecycleOwner.lifecycle,
+ 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() {
+ ShortcutLoader(
+ context,
+ lifecycleOwner.lifecycle,
+ appPredictor,
+ UserHandle.of(0),
+ true,
+ intentFilter,
+ dispatcher,
+ callback
+ )
+
+ verify(appPredictor, times(1)).requestPredictionUpdate()
+ verify(callback, never()).accept(any())
+ }
+
+ @Test
+ fun test_ShortcutLoader_noResultsWithoutAppTargets() {
+ 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,
+ lifecycleOwner.lifecycle,
+ 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_OnLifecycleDestroyed_unsubscribeFromAppPredictor() {
+ ShortcutLoader(
+ context,
+ lifecycleOwner.lifecycle,
+ appPredictor,
+ UserHandle.of(0),
+ true,
+ intentFilter,
+ dispatcher,
+ callback
+ )
+
+ verify(appPredictor, never()).unregisterPredictionUpdates(any())
+
+ lifecycleOwner.state = Lifecycle.State.DESTROYED
+
+ verify(appPredictor, times(1)).unregisterPredictionUpdates(any())
+ }
+
+ @Test
+ fun test_workProfileNotRunning_doNotCallServices() {
testDisabledWorkProfileDoNotCallSystem(isUserRunning = false)
}
@Test
- fun test_queryShortcuts_do_not_call_services_for_locked_work_profile() {
+ fun test_workProfileLocked_doNotCallServices() {
testDisabledWorkProfileDoNotCallSystem(isUserUnlocked = false)
}
@Test
- fun test_queryShortcuts_do_not_call_services_if_quite_mode_is_enabled_for_work_profile() {
+ fun test_workProfileQuiteModeEnabled_doNotCallServices() {
testDisabledWorkProfileDoNotCallSystem(isQuietModeEnabled = true)
}
@Test
- fun test_queryShortcuts_call_services_for_not_running_main_profile() {
+ fun test_mainProfileNotRunning_callServicesAnyway() {
testAlwaysCallSystemForMainProfile(isUserRunning = false)
}
@Test
- fun test_queryShortcuts_call_services_for_locked_main_profile() {
+ fun test_mainProfileLocked_callServicesAnyway() {
testAlwaysCallSystemForMainProfile(isUserUnlocked = false)
}
@Test
- fun test_queryShortcuts_call_services_if_quite_mode_is_enabled_for_main_profile() {
+ fun test_mainProfileQuiteModeEnabled_callServicesAnyway() {
testAlwaysCallSystemForMainProfile(isQuietModeEnabled = true)
}
@@ -283,16 +417,16 @@ class ShortcutLoaderTest {
val callback = mock<Consumer<ShortcutLoader.Result>>()
val testSubject = ShortcutLoader(
context,
+ lifecycleOwner.lifecycle,
appPredictor,
userHandle,
false,
intentFilter,
- executor,
- executor,
+ dispatcher,
callback
)
- testSubject.queryShortcuts(arrayOf<DisplayResolveInfo>(mock()))
+ testSubject.updateAppTargets(arrayOf<DisplayResolveInfo>(mock()))
verify(appPredictor, never()).requestPredictionUpdate()
}
@@ -313,23 +447,17 @@ class ShortcutLoaderTest {
val callback = mock<Consumer<ShortcutLoader.Result>>()
val testSubject = ShortcutLoader(
context,
+ lifecycleOwner.lifecycle,
appPredictor,
userHandle,
true,
intentFilter,
- executor,
- executor,
+ dispatcher,
callback
)
- testSubject.queryShortcuts(arrayOf<DisplayResolveInfo>(mock()))
+ testSubject.updateAppTargets(arrayOf<DisplayResolveInfo>(mock()))
verify(appPredictor, times(1)).requestPredictionUpdate()
}
}
-
-private class ImmediateExecutor : Executor {
- override fun execute(r: Runnable) {
- r.run()
- }
-}
diff --git a/java/tests/src/com/android/intentresolver/util/UriFiltersTest.kt b/java/tests/src/com/android/intentresolver/util/UriFiltersTest.kt
new file mode 100644
index 00000000..18218064
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/util/UriFiltersTest.kt
@@ -0,0 +1,95 @@
+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/widget/BatchPreviewLoaderTest.kt b/java/tests/src/com/android/intentresolver/widget/BatchPreviewLoaderTest.kt
new file mode 100644
index 00000000..e65cba5f
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/widget/BatchPreviewLoaderTest.kt
@@ -0,0 +1,215 @@
+/*
+ * 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.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 onReset = mock<(Int) -> 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),
+ 0,
+ onReset,
+ onUpdate,
+ onCompletion
+ )
+ testSubject.loadAspectRatios(200) { _, _, _ -> 100 }
+ dispatcher.scheduler.advanceUntilIdle()
+
+ verify(onCompletion, times(1)).invoke()
+ verify(onReset, times(1)).invoke(2)
+ 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),
+ 0,
+ onReset,
+ onUpdate,
+ onCompletion
+ )
+ testSubject.loadAspectRatios(200) { _, _, _ -> 100 }
+ dispatcher.scheduler.advanceUntilIdle()
+
+ verify(onCompletion, times(1)).invoke()
+ verify(onReset, times(1)).invoke(3)
+ 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), 0, onReset, onUpdate, onCompletion)
+ testSubject.loadAspectRatios(200) { _, _, _ -> 100 }
+ dispatcher.scheduler.advanceUntilIdle()
+
+ verify(onCompletion, times(1)).invoke()
+ verify(onReset, times(1)).invoke(uris.size)
+ 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), 0, onReset, onUpdate, onCompletion)
+ testSubject.loadAspectRatios(200) { _, _, _ -> 100 }
+ dispatcher.scheduler.advanceUntilIdle()
+
+ verify(onCompletion, times(1)).invoke()
+ verify(onReset, times(1)).invoke(uris.size)
+ 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)) }
+ }
+}
+
+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()
+ }
+}