/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.intentresolver; import static android.app.Activity.RESULT_OK; import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.action.ViewActions.longClick; import static androidx.test.espresso.action.ViewActions.swipeUp; import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist; import static androidx.test.espresso.assertion.ViewAssertions.matches; import static androidx.test.espresso.matcher.ViewMatchers.hasSibling; import static androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed; import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; import static androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withText; import static com.android.intentresolver.ChooserActivity.TARGET_TYPE_CHOOSER_TARGET; import static com.android.intentresolver.ChooserActivity.TARGET_TYPE_DEFAULT; import static com.android.intentresolver.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE; import static com.android.intentresolver.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER; import static com.android.intentresolver.ChooserListAdapter.CALLER_TARGET_SCORE_BOOST; import static com.android.intentresolver.ChooserListAdapter.SHORTCUT_TARGET_SCORE_BOOST; import static com.android.intentresolver.MatcherUtils.first; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static junit.framework.Assert.assertNull; import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.PendingIntent; import android.app.usage.UsageStatsManager; import android.content.BroadcastReceiver; import android.content.ClipData; import android.content.ClipDescription; import android.content.ClipboardManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager.ShareShortcutInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Bundle; import android.os.UserHandle; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.provider.DeviceConfig; import android.provider.Settings; import android.service.chooser.ChooserAction; import android.service.chooser.ChooserTarget; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.style.BackgroundColorSpan; import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; import android.text.style.UnderlineSpan; import android.util.Pair; import android.util.SparseArray; import android.view.View; import android.view.WindowManager; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.test.espresso.contrib.RecyclerViewActions; import androidx.test.espresso.matcher.BoundedDiagnosingMatcher; import androidx.test.espresso.matcher.ViewMatchers; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.rule.ActivityTestRule; import com.android.intentresolver.chooser.DisplayResolveInfo; import com.android.intentresolver.contentpreview.FakeThumbnailLoader; import com.android.intentresolver.contentpreview.ImageLoader; import com.android.intentresolver.contentpreview.ImageLoaderModule; import com.android.intentresolver.contentpreview.PreviewCacheSize; import com.android.intentresolver.contentpreview.PreviewMaxConcurrency; import com.android.intentresolver.contentpreview.ThumbnailLoader; import com.android.intentresolver.data.repository.FakeUserRepository; import com.android.intentresolver.data.repository.UserRepository; import com.android.intentresolver.data.repository.UserRepositoryModule; import com.android.intentresolver.ext.RecyclerViewExt; import com.android.intentresolver.inject.ApplicationUser; import com.android.intentresolver.inject.PackageManagerModule; import com.android.intentresolver.inject.ProfileParent; import com.android.intentresolver.logging.EventLog; import com.android.intentresolver.logging.FakeEventLog; import com.android.intentresolver.platform.AppPredictionAvailable; import com.android.intentresolver.platform.AppPredictionModule; import com.android.intentresolver.platform.GlobalSettings; import com.android.intentresolver.platform.ImageEditor; import com.android.intentresolver.platform.ImageEditorModule; import com.android.intentresolver.shared.model.User; import com.android.intentresolver.shortcuts.ShortcutLoader; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import dagger.hilt.android.qualifiers.ApplicationContext; import dagger.hilt.android.testing.BindValue; import dagger.hilt.android.testing.HiltAndroidRule; import dagger.hilt.android.testing.HiltAndroidTest; import dagger.hilt.android.testing.UninstallModules; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import javax.inject.Inject; /** * Instrumentation tests for ChooserActivity. *

* Legacy test suite migrated from framework CoreTests. */ @SuppressWarnings("OptionalUsedAsFieldOrParameterType") @RunWith(Parameterized.class) @HiltAndroidTest @UninstallModules({ AppPredictionModule.class, ImageEditorModule.class, PackageManagerModule.class, ImageLoaderModule.class, UserRepositoryModule.class, }) public class ChooserActivityTest { private static FakeEventLog getEventLog(ChooserWrapperActivity activity) { return (FakeEventLog) activity.mEventLog; } private static final UserHandle PERSONAL_USER_HANDLE = InstrumentationRegistry .getInstrumentation().getTargetContext().getUser(); private static final User PERSONAL_USER = new User(PERSONAL_USER_HANDLE.getIdentifier(), User.Role.PERSONAL); private static final UserHandle WORK_PROFILE_USER_HANDLE = UserHandle.of(10); private static final User WORK_USER = new User(WORK_PROFILE_USER_HANDLE.getIdentifier(), User.Role.WORK); private static final UserHandle CLONE_PROFILE_USER_HANDLE = UserHandle.of(11); private static final User CLONE_USER = new User(CLONE_PROFILE_USER_HANDLE.getIdentifier(), User.Role.CLONE); @Parameters(name = "appPrediction={0}") public static Iterable parameters() { return Arrays.asList( /* appPredictionAvailable = */ true, /* appPredictionAvailable = */ false ); } private static final String TEST_MIME_TYPE = "application/TestType"; private static final int CONTENT_PREVIEW_IMAGE = 1; private static final int CONTENT_PREVIEW_FILE = 2; private static final int CONTENT_PREVIEW_TEXT = 3; @Rule(order = 0) public CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); @Rule(order = 1) public HiltAndroidRule mHiltAndroidRule = new HiltAndroidRule(this); @Rule(order = 2) public ActivityTestRule mActivityRule = new ActivityTestRule<>(ChooserWrapperActivity.class, false, false); @Inject @ApplicationContext Context mContext; @Inject GlobalSettings mGlobalSettings; /** An arbitrary pre-installed activity that handles this type of intent. */ @BindValue @ImageEditor final Optional mImageEditor = Optional.ofNullable( ComponentName.unflattenFromString("com.google.android.apps.messaging/" + ".ui.conversationlist.ShareIntentActivity")); /** Whether an AppPredictionService is available for use. */ @BindValue @AppPredictionAvailable final boolean mAppPredictionAvailable; @BindValue PackageManager mPackageManager; /** "launchedAs" */ @BindValue @ApplicationUser UserHandle mApplicationUser = PERSONAL_USER_HANDLE; @BindValue @ProfileParent UserHandle mProfileParent = PERSONAL_USER_HANDLE; private final FakeUserRepository mFakeUserRepo = new FakeUserRepository(List.of(PERSONAL_USER)); @BindValue final UserRepository mUserRepository = mFakeUserRepo; private final FakeImageLoader mFakeImageLoader = new FakeImageLoader(); @BindValue final ImageLoader mImageLoader = mFakeImageLoader; @BindValue @PreviewCacheSize int mPreviewCacheSize = 16; @BindValue @PreviewMaxConcurrency int mPreviewMaxConcurrency = 4; @BindValue ThumbnailLoader mThumbnailLoader = new FakeThumbnailLoader(); @Before public void setUp() { // TODO: use the other form of `adoptShellPermissionIdentity()` where we explicitly list the // permissions we require (which we'll read from the manifest at runtime). InstrumentationRegistry .getInstrumentation() .getUiAutomation() .adoptShellPermissionIdentity(); cleanOverrideData(); // Assign @Inject fields mHiltAndroidRule.inject(); // Populate @BindValue dependencies using injected values. These fields contribute // values to the dependency graph at activity launch time. This allows replacing // arbitrary bindings per-test case if needed. mPackageManager = mContext.getPackageManager(); // TODO: inject image loader in the prod code and remove this override ChooserActivityOverrideData.getInstance().imageLoader = mFakeImageLoader; } public ChooserActivityTest(boolean appPredictionAvailable) { mAppPredictionAvailable = appPredictionAvailable; } private void setDeviceConfigProperty( @NonNull String propertyName, @NonNull String value) { // TODO: consider running with {@link #runWithShellPermissionIdentity()} to more narrowly // request WRITE_DEVICE_CONFIG permissions if we get rid of the broad grant we currently // configure in {@link #setup()}. // TODO: is it really appropriate that this is always set with makeDefault=true? boolean valueWasSet = DeviceConfig.setProperty( DeviceConfig.NAMESPACE_SYSTEMUI, propertyName, value, true /* makeDefault */); if (!valueWasSet) { throw new IllegalStateException( "Could not set " + propertyName + " to " + value); } } public void cleanOverrideData() { ChooserActivityOverrideData.getInstance().reset(); setDeviceConfigProperty( SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI, Boolean.toString(true)); } private static PackageManager createFakePackageManager(ResolveInfo resolveInfo) { PackageManager packageManager = mock(PackageManager.class); when(packageManager.resolveActivity(any(Intent.class), any())).thenReturn(resolveInfo); return packageManager; } @Test public void customTitle() throws InterruptedException { Intent viewIntent = createViewTextIntent(); List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); final IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity( Intent.createChooser(viewIntent, "chooser test")); waitForIdle(); assertThat(activity.getAdapter().getCount(), is(2)); assertThat(activity.getAdapter().getServiceTargetCount(), is(0)); onView(withId(android.R.id.title)).check(matches(withText("chooser test"))); } @Test public void customTitleIgnoredForSendIntents() throws InterruptedException { Intent sendIntent = createSendTextIntent(); List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, "chooser test")); waitForIdle(); onView(withId(android.R.id.title)) .check(matches(withText(R.string.whichSendApplication))); } @Test public void emptyTitle() throws InterruptedException { Intent sendIntent = createSendTextIntent(); List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); onView(withId(android.R.id.title)) .check(matches(withText(R.string.whichSendApplication))); } @Test public void test_shareRichTextWithRichTitle_richTextAndRichTitleDisplayed() { CharSequence title = new SpannableStringBuilder() .append("Rich", new UnderlineSpan(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE) .append( "Title", new ForegroundColorSpan(Color.RED), Spannable.SPAN_INCLUSIVE_EXCLUSIVE); CharSequence sharedText = new SpannableStringBuilder() .append( "Rich", new BackgroundColorSpan(Color.YELLOW), Spanned.SPAN_INCLUSIVE_EXCLUSIVE) .append( "Text", new StyleSpan(Typeface.ITALIC), Spanned.SPAN_INCLUSIVE_EXCLUSIVE); Intent sendIntent = createSendTextIntent(); sendIntent.putExtra(Intent.EXTRA_TEXT, sharedText); sendIntent.putExtra(Intent.EXTRA_TITLE, title); List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); onView(withId(com.android.internal.R.id.content_preview_title)) .check((view, e) -> { assertThat(view).isInstanceOf(TextView.class); CharSequence text = ((TextView) view).getText(); assertThat(text).isInstanceOf(Spanned.class); Spanned spanned = (Spanned) text; assertThat(spanned.getSpans(0, spanned.length(), Object.class)) .hasLength(2); assertThat(spanned.getSpans(0, 4, UnderlineSpan.class)).hasLength(1); assertThat(spanned.getSpans(4, spanned.length(), ForegroundColorSpan.class)) .hasLength(1); }); onView(withId(com.android.internal.R.id.content_preview_text)) .check((view, e) -> { assertThat(view).isInstanceOf(TextView.class); CharSequence text = ((TextView) view).getText(); assertThat(text).isInstanceOf(Spanned.class); Spanned spanned = (Spanned) text; assertThat(spanned.getSpans(0, spanned.length(), Object.class)) .hasLength(2); assertThat(spanned.getSpans(0, 4, BackgroundColorSpan.class)).hasLength(1); assertThat(spanned.getSpans(4, spanned.length(), StyleSpan.class)).hasLength(1); }); } @Test public void emptyPreviewTitleAndThumbnail() throws InterruptedException { Intent sendIntent = createSendTextIntentWithPreview(null, null); List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); onView(withId(com.android.internal.R.id.content_preview_title)) .check(matches(not(isDisplayed()))); onView(withId(com.android.internal.R.id.content_preview_thumbnail)) .check(matches(not(isDisplayed()))); } @Test public void visiblePreviewTitleWithoutThumbnail() throws InterruptedException { String previewTitle = "My Content Preview Title"; Intent sendIntent = createSendTextIntentWithPreview(previewTitle, null); List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); onView(withId(com.android.internal.R.id.content_preview_title)) .check(matches(isDisplayed())); onView(withId(com.android.internal.R.id.content_preview_title)) .check(matches(withText(previewTitle))); onView(withId(com.android.internal.R.id.content_preview_thumbnail)) .check(matches(not(isDisplayed()))); } @Test public void visiblePreviewTitleWithInvalidThumbnail() throws InterruptedException { String previewTitle = "My Content Preview Title"; Intent sendIntent = createSendTextIntentWithPreview(previewTitle, Uri.parse("tel:(+49)12345789")); List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); onView(withId(com.android.internal.R.id.content_preview_title)) .check(matches(isDisplayed())); onView(withId(com.android.internal.R.id.content_preview_thumbnail)) .check(matches(not(isDisplayed()))); } @Test public void visiblePreviewTitleAndThumbnail() { String previewTitle = "My Content Preview Title"; Uri uri = Uri.parse( "android.resource://com.android.frameworks.coretests/" + com.android.intentresolver.tests.R.drawable.test320x240); Intent sendIntent = createSendTextIntentWithPreview(previewTitle, uri); mFakeImageLoader.setBitmap(uri, createBitmap()); List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); onView(withId(com.android.internal.R.id.content_preview_title)) .check(matches(isDisplayed())); onView(withId(com.android.internal.R.id.content_preview_thumbnail)) .check(matches(isDisplayed())); } @Test @Ignore public void twoOptionsAndUserSelectsOne() throws InterruptedException { Intent sendIntent = createSendTextIntent(); List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); final IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); assertThat(activity.getAdapter().getCount(), is(2)); onView(withId(com.android.internal.R.id.profile_button)).check(doesNotExist()); ResolveInfo[] chosen = new ResolveInfo[1]; ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { chosen[0] = targetInfo.getResolveInfo(); return true; }; ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0); onView(withText(toChoose.activityInfo.name)) .perform(click()); waitForIdle(); assertThat(chosen[0], is(toChoose)); } @Test @Ignore public void fourOptionsStackedIntoOneTarget() throws InterruptedException { Intent sendIntent = createSendTextIntent(); // create just enough targets to ensure the a-z list should be shown List resolvedComponentInfos = createResolvedComponentsForTest(1); // next create 4 targets in a single app that should be stacked into a single target String packageName = "xxx.yyy"; String appName = "aaa"; ComponentName cn = new ComponentName(packageName, appName); Intent intent = new Intent("fakeIntent"); List infosToStack = new ArrayList<>(); for (int i = 0; i < 4; i++) { ResolveInfo resolveInfo = ResolverDataProvider.createResolveInfo(i, UserHandle.USER_CURRENT, PERSONAL_USER_HANDLE); resolveInfo.activityInfo.applicationInfo.name = appName; resolveInfo.activityInfo.applicationInfo.packageName = packageName; resolveInfo.activityInfo.packageName = packageName; resolveInfo.activityInfo.name = "ccc" + i; infosToStack.add(new ResolvedComponentInfo(cn, intent, resolveInfo)); } resolvedComponentInfos.addAll(infosToStack); setupResolverControllers(resolvedComponentInfos); final IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); // expect 1 unique targets + 1 group + 4 ranked app targets assertThat(activity.getAdapter().getCount(), is(6)); ResolveInfo[] chosen = new ResolveInfo[1]; ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { chosen[0] = targetInfo.getResolveInfo(); return true; }; onView(allOf(withText(appName), hasSibling(withText("")))).perform(click()); waitForIdle(); // clicking will launch a dialog to choose the activity within the app onView(withText(appName)).check(matches(isDisplayed())); int i = 0; for (ResolvedComponentInfo rci: infosToStack) { onView(withText("ccc" + i)).check(matches(isDisplayed())); ++i; } } @Test @Ignore public void updateChooserCountsAndModelAfterUserSelection() throws InterruptedException { Intent sendIntent = createSendTextIntent(); List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); final IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); UsageStatsManager usm = activity.getUsageStatsManager(); verify(ChooserActivityOverrideData.getInstance().resolverListController, times(1)) .topK(any(List.class), anyInt()); assertThat(activity.getIsSelected(), is(false)); ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { return true; }; ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0); DisplayResolveInfo testDri = activity.createTestDisplayResolveInfo( sendIntent, toChoose, "testLabel", "testInfo", sendIntent); onView(withText(toChoose.activityInfo.name)) .perform(click()); waitForIdle(); verify(ChooserActivityOverrideData.getInstance().resolverListController, times(1)) .updateChooserCounts(Mockito.anyString(), any(UserHandle.class), Mockito.anyString()); verify(ChooserActivityOverrideData.getInstance().resolverListController, times(1)) .updateModel(testDri); assertThat(activity.getIsSelected(), is(true)); } @Ignore // b/148158199 @Test public void noResultsFromPackageManager() { setupResolverControllers(null); Intent sendIntent = createSendTextIntent(); final ChooserActivity activity = mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); final IChooserWrapper wrapper = (IChooserWrapper) activity; waitForIdle(); assertThat(activity.isFinishing(), is(false)); onView(withId(android.R.id.empty)).check(matches(isDisplayed())); onView(withId(com.android.internal.R.id.profile_pager)).check(matches(not(isDisplayed()))); InstrumentationRegistry.getInstrumentation().runOnMainSync( () -> wrapper.getAdapter().handlePackagesChanged() ); // backward compatibility. looks like we finish when data is empty after package change assertThat(activity.isFinishing(), is(true)); } @Test public void autoLaunchSingleResult() throws InterruptedException { ResolveInfo[] chosen = new ResolveInfo[1]; ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { chosen[0] = targetInfo.getResolveInfo(); return true; }; List resolvedComponentInfos = createResolvedComponentsForTest(1); setupResolverControllers(resolvedComponentInfos); Intent sendIntent = createSendTextIntent(); final ChooserActivity activity = mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); assertThat(chosen[0], is(resolvedComponentInfos.get(0).getResolveInfoAt(0))); assertThat(activity.isFinishing(), is(true)); } @Test @Ignore public void hasOtherProfileOneOption() { List personalResolvedComponentInfos = createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10); List workResolvedComponentInfos = createResolvedComponentsForTest(4); setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); ResolveInfo toChoose = personalResolvedComponentInfos.get(1).getResolveInfoAt(0); Intent sendIntent = createSendTextIntent(); final IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); // The other entry is filtered to the other profile slot assertThat(activity.getAdapter().getCount(), is(1)); ResolveInfo[] chosen = new ResolveInfo[1]; ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { chosen[0] = targetInfo.getResolveInfo(); return true; }; // Make a stable copy of the components as the original list may be modified List stableCopy = createResolvedComponentsForTestWithOtherProfile(2, /* userId= */ 10); waitForIdle(); onView(first(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))) .perform(click()); waitForIdle(); assertThat(chosen[0], is(toChoose)); } @Test @Ignore public void hasOtherProfileTwoOptionsAndUserSelectsOne() throws Exception { Intent sendIntent = createSendTextIntent(); List resolvedComponentInfos = createResolvedComponentsForTestWithOtherProfile(3); ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0); setupResolverControllers(resolvedComponentInfos); when(ChooserActivityOverrideData.getInstance().resolverListController.getLastChosen()) .thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0)); final IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); // The other entry is filtered to the other profile slot assertThat(activity.getAdapter().getCount(), is(2)); ResolveInfo[] chosen = new ResolveInfo[1]; ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { chosen[0] = targetInfo.getResolveInfo(); return true; }; // Make a stable copy of the components as the original list may be modified List stableCopy = createResolvedComponentsForTestWithOtherProfile(3); onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)) .perform(click()); waitForIdle(); assertThat(chosen[0], is(toChoose)); } @Test @Ignore public void hasLastChosenActivityAndOtherProfile() throws Exception { Intent sendIntent = createSendTextIntent(); List resolvedComponentInfos = createResolvedComponentsForTestWithOtherProfile(3); ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0); setupResolverControllers(resolvedComponentInfos); final IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); // The other entry is filtered to the last used slot assertThat(activity.getAdapter().getCount(), is(2)); ResolveInfo[] chosen = new ResolveInfo[1]; ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { chosen[0] = targetInfo.getResolveInfo(); return true; }; // Make a stable copy of the components as the original list may be modified List stableCopy = createResolvedComponentsForTestWithOtherProfile(3); onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)) .perform(click()); waitForIdle(); assertThat(chosen[0], is(toChoose)); } @Test @Ignore("b/285309527") public void testFilePlusTextSharing_ExcludeText() { Uri uri = createTestContentProviderUri(null, "image/png"); Intent sendIntent = createSendImageIntent(uri); mFakeImageLoader.setBitmap(uri, createBitmap()); sendIntent.putExtra(Intent.EXTRA_TEXT, "https://google.com/search?q=google"); List resolvedComponentInfos = Arrays.asList( ResolverDataProvider.createResolvedComponentInfo( new ComponentName("org.imageviewer", "ImageTarget"), sendIntent, PERSONAL_USER_HANDLE), ResolverDataProvider.createResolvedComponentInfo( new ComponentName("org.textviewer", "UriTarget"), new Intent("VIEW_TEXT"), PERSONAL_USER_HANDLE) ); setupResolverControllers(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); onView(withId(R.id.include_text_action)) .check(matches(isDisplayed())) .perform(click()); waitForIdle(); onView(withId(R.id.content_preview_text)).check(matches(withText("File only"))); AtomicReference launchedIntentRef = new AtomicReference<>(); ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { launchedIntentRef.set(targetInfo.getTargetIntent()); return true; }; onView(withText(resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.name)) .perform(click()); waitForIdle(); assertThat(launchedIntentRef.get().hasExtra(Intent.EXTRA_TEXT)).isFalse(); } @Test @Ignore("b/285309527") public void testFilePlusTextSharing_RemoveAndAddBackText() { Uri uri = createTestContentProviderUri("application/pdf", "image/png"); Intent sendIntent = createSendImageIntent(uri); mFakeImageLoader.setBitmap(uri, createBitmap()); final String text = "https://google.com/search?q=google"; sendIntent.putExtra(Intent.EXTRA_TEXT, text); List resolvedComponentInfos = Arrays.asList( ResolverDataProvider.createResolvedComponentInfo( new ComponentName("org.imageviewer", "ImageTarget"), sendIntent, PERSONAL_USER_HANDLE), ResolverDataProvider.createResolvedComponentInfo( new ComponentName("org.textviewer", "UriTarget"), new Intent("VIEW_TEXT"), PERSONAL_USER_HANDLE) ); setupResolverControllers(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); onView(withId(R.id.include_text_action)) .check(matches(isDisplayed())) .perform(click()); waitForIdle(); onView(withId(R.id.content_preview_text)).check(matches(withText("File only"))); onView(withId(R.id.include_text_action)) .perform(click()); waitForIdle(); onView(withId(R.id.content_preview_text)).check(matches(withText(text))); AtomicReference launchedIntentRef = new AtomicReference<>(); ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { launchedIntentRef.set(targetInfo.getTargetIntent()); return true; }; onView(withText(resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.name)) .perform(click()); waitForIdle(); assertThat(launchedIntentRef.get().getStringExtra(Intent.EXTRA_TEXT)).isEqualTo(text); } @Test @Ignore("b/285309527") public void testFilePlusTextSharing_TextExclusionDoesNotAffectAlternativeIntent() { Uri uri = createTestContentProviderUri("image/png", null); Intent sendIntent = createSendImageIntent(uri); mFakeImageLoader.setBitmap(uri, createBitmap()); sendIntent.putExtra(Intent.EXTRA_TEXT, "https://google.com/search?q=google"); Intent alternativeIntent = createSendTextIntent(); final String text = "alternative intent"; alternativeIntent.putExtra(Intent.EXTRA_TEXT, text); List resolvedComponentInfos = Arrays.asList( ResolverDataProvider.createResolvedComponentInfo( new ComponentName("org.imageviewer", "ImageTarget"), sendIntent, PERSONAL_USER_HANDLE), ResolverDataProvider.createResolvedComponentInfo( new ComponentName("org.textviewer", "UriTarget"), alternativeIntent, PERSONAL_USER_HANDLE) ); setupResolverControllers(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); onView(withId(R.id.include_text_action)) .check(matches(isDisplayed())) .perform(click()); waitForIdle(); AtomicReference launchedIntentRef = new AtomicReference<>(); ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { launchedIntentRef.set(targetInfo.getTargetIntent()); return true; }; onView(withText(resolvedComponentInfos.get(1).getResolveInfoAt(0).activityInfo.name)) .perform(click()); waitForIdle(); assertThat(launchedIntentRef.get().getStringExtra(Intent.EXTRA_TEXT)).isEqualTo(text); } @Test @Ignore("b/285309527") public void testImagePlusTextSharing_failedThumbnailAndExcludedText_textChanges() { Uri uri = createTestContentProviderUri("image/png", null); Intent sendIntent = createSendImageIntent(uri); sendIntent.putExtra(Intent.EXTRA_TEXT, "https://google.com/search?q=google"); List resolvedComponentInfos = Arrays.asList( ResolverDataProvider.createResolvedComponentInfo( new ComponentName("org.imageviewer", "ImageTarget"), sendIntent, PERSONAL_USER_HANDLE), ResolverDataProvider.createResolvedComponentInfo( new ComponentName("org.textviewer", "UriTarget"), new Intent("VIEW_TEXT"), PERSONAL_USER_HANDLE) ); setupResolverControllers(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); onView(withId(R.id.include_text_action)) .check(matches(isDisplayed())) .perform(click()); waitForIdle(); onView(withId(R.id.image_view)) .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE))); onView(withId(R.id.content_preview_text)) .check(matches(allOf(isDisplayed(), withText("Image only")))); } @Test public void copyTextToClipboard() { Intent sendIntent = createSendTextIntent(); List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); final ChooserActivity activity = mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); onView(withId(R.id.copy)).check(matches(isDisplayed())); onView(withId(R.id.copy)).perform(click()); ClipboardManager clipboard = (ClipboardManager) activity.getSystemService( Context.CLIPBOARD_SERVICE); ClipData clipData = clipboard.getPrimaryClip(); assertThat(clipData).isNotNull(); assertThat(clipData.getItemAt(0).getText()).isEqualTo("testing intent sending"); ClipDescription clipDescription = clipData.getDescription(); assertThat("text/plain", is(clipDescription.getMimeType(0))); assertEquals(mActivityRule.getActivityResult().getResultCode(), RESULT_OK); } @Test public void copyTextToClipboardLogging() { Intent sendIntent = createSendTextIntent(); List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); ChooserWrapperActivity activity = mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); onView(withId(R.id.copy)).check(matches(isDisplayed())); onView(withId(R.id.copy)).perform(click()); FakeEventLog eventLog = getEventLog(activity); assertThat(eventLog.getActionSelected()) .isEqualTo(new FakeEventLog.ActionSelected( /* targetType = */ EventLog.SELECTION_TYPE_COPY)); } @Test @Ignore public void testNearbyShareLogging() throws Exception { Intent sendIntent = createSendTextIntent(); List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); onView(withId(com.android.internal.R.id.chooser_nearby_button)) .check(matches(isDisplayed())); onView(withId(com.android.internal.R.id.chooser_nearby_button)).perform(click()); // TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events. } @Test @Ignore public void testEditImageLogs() { Uri uri = createTestContentProviderUri("image/png", null); Intent sendIntent = createSendImageIntent(uri); mFakeImageLoader.setBitmap(uri, createBitmap()); List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); onView(withId(com.android.internal.R.id.chooser_edit_button)).check(matches(isDisplayed())); onView(withId(com.android.internal.R.id.chooser_edit_button)).perform(click()); // TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events. } @Test public void oneVisibleImagePreview() { Uri uri = createTestContentProviderUri("image/png", null); ArrayList uris = new ArrayList<>(); uris.add(uri); Intent sendIntent = createSendUriIntentWithPreview(uris); mFakeImageLoader.setBitmap(uri, createWideBitmap()); List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); onView(withId(R.id.scrollable_image_preview)) .check((view, exception) -> { if (exception != null) { throw exception; } RecyclerView recyclerView = (RecyclerView) view; RecyclerViewExt.endAnimations(recyclerView); assertThat("recyclerView adapter item count", recyclerView.getAdapter().getItemCount(), is(1)); assertThat("recyclerView child view count", recyclerView.getChildCount(), is(1)); View imageView = recyclerView.getChildAt(0); Rect rect = new Rect(); boolean isPartiallyVisible = imageView.getGlobalVisibleRect(rect); assertThat( "image preview view is not fully visible", isPartiallyVisible && rect.width() == imageView.getWidth() && rect.height() == imageView.getHeight()); }); } @Test public void allThumbnailsFailedToLoad_hidePreview() { Uri uri = createTestContentProviderUri("image/jpg", null); ArrayList uris = new ArrayList<>(); uris.add(uri); uris.add(uri); Intent sendIntent = createSendUriIntentWithPreview(uris); List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); onView(withId(R.id.scrollable_image_preview)) .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE))); } @Test(timeout = 4_000) public void testSlowUriMetadata_fallbackToFilePreview() { Uri uri = createTestContentProviderUri( "application/pdf", "image/png", /*streamTypeTimeout=*/8_000); ArrayList uris = new ArrayList<>(1); uris.add(uri); Intent sendIntent = createSendUriIntentWithPreview(uris); mFakeImageLoader.setBitmap(uri, createBitmap()); List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); // The preview type resolution is expected to timeout and default to file preview, otherwise // the test should timeout. mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed())); onView(withId(R.id.content_preview_filename)).check(matches(withText("image.png"))); onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed())); } @Test(timeout = 4_000) public void testSendManyFilesWithSmallMetadataDelayAndOneImage_fallbackToFilePreviewUi() { Uri fileUri = createTestContentProviderUri( "application/pdf", "application/pdf", /*streamTypeTimeout=*/300); Uri imageUri = createTestContentProviderUri("application/pdf", "image/png"); ArrayList uris = new ArrayList<>(50); for (int i = 0; i < 49; i++) { uris.add(fileUri); } uris.add(imageUri); Intent sendIntent = createSendUriIntentWithPreview(uris); mFakeImageLoader.setBitmap(imageUri, createBitmap()); List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); // The preview type resolution is expected to timeout and default to file preview, otherwise // the test should timeout. mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed())); onView(withId(R.id.content_preview_filename)).check(matches(withText("image.png"))); onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed())); } @Test public void testManyVisibleImagePreview_ScrollableImagePreview() { Uri uri = createTestContentProviderUri("image/png", null); ArrayList uris = new ArrayList<>(); uris.add(uri); uris.add(uri); uris.add(uri); uris.add(uri); uris.add(uri); uris.add(uri); uris.add(uri); uris.add(uri); uris.add(uri); uris.add(uri); Intent sendIntent = createSendUriIntentWithPreview(uris); mFakeImageLoader.setBitmap(uri, createBitmap()); List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); onView(withId(R.id.scrollable_image_preview)) .perform(RecyclerViewActions.scrollToLastPosition()) .check((view, exception) -> { if (exception != null) { throw exception; } RecyclerView recyclerView = (RecyclerView) view; assertThat(recyclerView.getAdapter().getItemCount(), is(uris.size())); }); } @Test(timeout = 4_000) public void testPartiallyLoadedMetadata_previewIsShownForTheLoadedPart() { Uri imgOneUri = createTestContentProviderUri("image/png", null); Uri imgTwoUri = createTestContentProviderUri("image/png", null) .buildUpon() .path("image-2.png") .build(); Uri docUri = createTestContentProviderUri("application/pdf", "image/png", 8_000); ArrayList uris = new ArrayList<>(2); // two large previews to fill the screen and be presented right away and one // document that would be delayed by the URI metadata reading uris.add(imgOneUri); uris.add(imgTwoUri); uris.add(docUri); Intent sendIntent = createSendUriIntentWithPreview(uris); mFakeImageLoader.setBitmap(imgOneUri, createWideBitmap(Color.RED)); mFakeImageLoader.setBitmap(imgTwoUri, createWideBitmap(Color.GREEN)); mFakeImageLoader.setBitmap(docUri, createWideBitmap(Color.BLUE)); List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); // the preview type is expected to be resolved quickly based on the first provided URI // metadata. If, instead, it is dependent on the third URI metadata, the test should either // timeout or (more probably due to inner timeout) default to file preview type; anyway the // test will fail. mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); onView(withId(R.id.scrollable_image_preview)) .check((view, exception) -> { if (exception != null) { throw exception; } RecyclerView recyclerView = (RecyclerView) view; RecyclerViewExt.endAnimations(recyclerView); assertThat(recyclerView.getChildCount()).isAtLeast(1); // the first view is a preview View imageView = recyclerView.getChildAt(0).findViewById(R.id.image); assertThat(imageView).isNotNull(); }) .perform(RecyclerViewActions.scrollToLastPosition()) .check((view, exception) -> { if (exception != null) { throw exception; } RecyclerView recyclerView = (RecyclerView) view; assertThat(recyclerView.getChildCount()).isAtLeast(1); // check that the last view is a loading indicator View loadingIndicator = recyclerView.getChildAt(recyclerView.getChildCount() - 1); assertThat(loadingIndicator).isNotNull(); }); waitForIdle(); } @Test public void testImageAndTextPreview() { final Uri uri = createTestContentProviderUri("image/png", null); final String sharedText = "text-" + System.currentTimeMillis(); ArrayList uris = new ArrayList<>(); uris.add(uri); Intent sendIntent = createSendUriIntentWithPreview(uris); sendIntent.putExtra(Intent.EXTRA_TEXT, sharedText); mFakeImageLoader.setBitmap(uri, createBitmap()); List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); onView(withText(sharedText)) .check(matches(isDisplayed())); } @Test public void test_shareImageWithRichText_RichTextIsDisplayed() { final Uri uri = createTestContentProviderUri("image/png", null); final CharSequence sharedText = new SpannableStringBuilder() .append( "text-", new StyleSpan(Typeface.BOLD_ITALIC), Spannable.SPAN_INCLUSIVE_EXCLUSIVE) .append( Long.toString(System.currentTimeMillis()), new ForegroundColorSpan(Color.RED), Spanned.SPAN_INCLUSIVE_EXCLUSIVE); ArrayList uris = new ArrayList<>(); uris.add(uri); Intent sendIntent = createSendUriIntentWithPreview(uris); sendIntent.putExtra(Intent.EXTRA_TEXT, sharedText); mFakeImageLoader.setBitmap(uri, createBitmap()); List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); onView(withText(sharedText.toString())) .check(matches(isDisplayed())) .check((view, e) -> { if (e != null) { throw e; } assertThat(view).isInstanceOf(TextView.class); CharSequence text = ((TextView) view).getText(); assertThat(text).isInstanceOf(Spanned.class); Spanned spanned = (Spanned) text; Object[] spans = spanned.getSpans(0, text.length(), Object.class); assertThat(spans).hasLength(2); assertThat(spanned.getSpans(0, 5, StyleSpan.class)).hasLength(1); assertThat(spanned.getSpans(5, text.length(), ForegroundColorSpan.class)) .hasLength(1); }); } @Test public void testTextPreviewWhenTextIsSharedWithMultipleImages() { final Uri uri = createTestContentProviderUri("image/png", null); final String sharedText = "text-" + System.currentTimeMillis(); ArrayList uris = new ArrayList<>(); uris.add(uri); uris.add(uri); Intent sendIntent = createSendUriIntentWithPreview(uris); sendIntent.putExtra(Intent.EXTRA_TEXT, sharedText); mFakeImageLoader.setBitmap(uri, createBitmap()); List resolvedComponentInfos = createResolvedComponentsForTest(2); when( ChooserActivityOverrideData .getInstance() .resolverListController .getResolversForIntentAsUser( Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.isA(List.class), Mockito.any(UserHandle.class))) .thenReturn(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); onView(withText(sharedText)).check(matches(isDisplayed())); } @Test public void testOnCreateLogging() { Intent sendIntent = createSendTextIntent(); sendIntent.setType(TEST_MIME_TYPE); ChooserWrapperActivity activity = mActivityRule.launchActivity(Intent.createChooser(sendIntent, "logger test")); waitForIdle(); FakeEventLog eventLog = getEventLog(activity); FakeEventLog.ChooserActivityShown event = eventLog.getChooserActivityShown(); assertThat(event).isNotNull(); assertThat(event.isWorkProfile()).isFalse(); assertThat(event.getTargetMimeType()).isEqualTo(TEST_MIME_TYPE); } @Test public void testOnCreateLoggingFromWorkProfile() { Intent sendIntent = createSendTextIntent(); sendIntent.setType(TEST_MIME_TYPE); // Launch as work user. mFakeUserRepo.addUser(WORK_USER, true); mApplicationUser = WORK_PROFILE_USER_HANDLE; ChooserWrapperActivity activity = mActivityRule.launchActivity(Intent.createChooser(sendIntent, "logger test")); waitForIdle(); FakeEventLog eventLog = getEventLog(activity); FakeEventLog.ChooserActivityShown event = eventLog.getChooserActivityShown(); assertThat(event).isNotNull(); assertThat(event.isWorkProfile()).isTrue(); assertThat(event.getTargetMimeType()).isEqualTo(TEST_MIME_TYPE); } @Test public void testEmptyPreviewLogging() { Intent sendIntent = createSendTextIntentWithPreview(null, null); ChooserWrapperActivity activity = mActivityRule.launchActivity(Intent.createChooser(sendIntent, "empty preview logger test")); waitForIdle(); FakeEventLog eventLog = getEventLog(activity); FakeEventLog.ChooserActivityShown event = eventLog.getChooserActivityShown(); assertThat(event).isNotNull(); assertThat(event.isWorkProfile()).isFalse(); assertThat(event.getTargetMimeType()).isNull(); } @Test public void testTitlePreviewLogging() { Intent sendIntent = createSendTextIntentWithPreview("TestTitle", null); List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); ChooserWrapperActivity activity = mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); FakeEventLog eventLog = getEventLog(activity); assertThat(eventLog.getActionShareWithPreview()) .isEqualTo(new FakeEventLog.ActionShareWithPreview( /* previewType = */ CONTENT_PREVIEW_TEXT)); } @Test public void testImagePreviewLogging() { Uri uri = createTestContentProviderUri("image/png", null); ArrayList uris = new ArrayList<>(); uris.add(uri); Intent sendIntent = createSendUriIntentWithPreview(uris); mFakeImageLoader.setBitmap(uri, createBitmap()); List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); ChooserWrapperActivity activity = mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); FakeEventLog eventLog = getEventLog(activity); assertThat(eventLog.getActionShareWithPreview()) .isEqualTo(new FakeEventLog.ActionShareWithPreview( /* previewType = */ CONTENT_PREVIEW_IMAGE)); } @Test public void oneVisibleFilePreview() throws InterruptedException { Uri uri = Uri.parse("content://com.android.frameworks.coretests/app.pdf"); ArrayList uris = new ArrayList<>(); uris.add(uri); Intent sendIntent = createSendUriIntentWithPreview(uris); List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed())); onView(withId(R.id.content_preview_filename)).check(matches(withText("app.pdf"))); onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed())); } @Test public void moreThanOneVisibleFilePreview() throws InterruptedException { Uri uri = Uri.parse("content://com.android.frameworks.coretests/app.pdf"); ArrayList uris = new ArrayList<>(); uris.add(uri); uris.add(uri); uris.add(uri); Intent sendIntent = createSendUriIntentWithPreview(uris); List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed())); onView(withId(R.id.content_preview_filename)).check(matches(withText("app.pdf"))); onView(withId(R.id.content_preview_more_files)).check(matches(isDisplayed())); onView(withId(R.id.content_preview_more_files)).check(matches(withText("+ 2 more files"))); onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed())); } @Test public void contentProviderThrowSecurityException() throws InterruptedException { Uri uri = Uri.parse("content://com.android.frameworks.coretests/app.pdf"); ArrayList uris = new ArrayList<>(); uris.add(uri); Intent sendIntent = createSendUriIntentWithPreview(uris); List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); ChooserActivityOverrideData.getInstance().resolverForceException = true; mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed())); onView(withId(R.id.content_preview_filename)).check(matches(withText("app.pdf"))); onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed())); } @Test public void contentProviderReturnsNoColumns() throws InterruptedException { Uri uri = Uri.parse("content://com.android.frameworks.coretests/app.pdf"); ArrayList uris = new ArrayList<>(); uris.add(uri); uris.add(uri); Intent sendIntent = createSendUriIntentWithPreview(uris); List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); Cursor cursor = mock(Cursor.class); when(cursor.getCount()).thenReturn(1); Mockito.doNothing().when(cursor).close(); when(cursor.moveToFirst()).thenReturn(true); when(cursor.getColumnIndex(Mockito.anyString())).thenReturn(-1); ChooserActivityOverrideData.getInstance().resolverCursor = cursor; mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); onView(withId(R.id.content_preview_filename)).check(matches(isDisplayed())); onView(withId(R.id.content_preview_filename)).check(matches(withText("app.pdf"))); onView(withId(R.id.content_preview_more_files)).check(matches(isDisplayed())); onView(withId(R.id.content_preview_more_files)).check(matches(withText("+ 1 more file"))); onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed())); } @Test public void testGetBaseScore() { final float testBaseScore = 0.89f; Intent sendIntent = createSendTextIntent(); List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); when( ChooserActivityOverrideData .getInstance() .resolverListController .getScore(Mockito.isA(DisplayResolveInfo.class))) .thenReturn(testBaseScore); final IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); final DisplayResolveInfo testDri = activity.createTestDisplayResolveInfo( sendIntent, ResolverDataProvider.createResolveInfo(3, 0, PERSONAL_USER_HANDLE), "testLabel", "testInfo", sendIntent); final ChooserListAdapter adapter = activity.getAdapter(); assertThat(adapter.getBaseScore(null, 0), is(CALLER_TARGET_SCORE_BOOST)); assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_DEFAULT), is(testBaseScore)); assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_CHOOSER_TARGET), is(testBaseScore)); assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE), is(testBaseScore * SHORTCUT_TARGET_SCORE_BOOST)); assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER), is(testBaseScore * SHORTCUT_TARGET_SCORE_BOOST)); } // This test is too long and too slow and should not be taken as an example for future tests. @Test public void testDirectTargetSelectionLogging() { Intent sendIntent = createSendTextIntent(); // We need app targets for direct targets to get displayed List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); // create test shortcut loader factory, remember loaders and their callbacks SparseArray>> shortcutLoaders = createShortcutLoaderFactory(); // Start activity ChooserWrapperActivity activity = mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); // verify that ShortcutLoader was queried ArgumentCaptor appTargets = ArgumentCaptor.forClass(DisplayResolveInfo[].class); verify(shortcutLoaders.get(0).first, times(1)).updateAppTargets(appTargets.capture()); // send shortcuts assertThat( "Wrong number of app targets", appTargets.getValue().length, is(resolvedComponentInfos.size())); List serviceTargets = createDirectShareTargets(1, ""); ShortcutLoader.Result result = new ShortcutLoader.Result( true, appTargets.getValue(), new ShortcutLoader.ShortcutResultInfo[] { new ShortcutLoader.ShortcutResultInfo( appTargets.getValue()[0], serviceTargets ) }, new HashMap<>(), new HashMap<>() ); activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result)); waitForIdle(); final ChooserListAdapter activeAdapter = activity.getAdapter(); assertThat( "Chooser should have 3 targets (2 apps, 1 direct)", activeAdapter.getCount(), is(3)); assertThat( "Chooser should have exactly one selectable direct target", activeAdapter.getSelectableServiceTargetCount(), is(1)); assertThat( "The resolver info must match the resolver info used to create the target", activeAdapter.getItem(0).getResolveInfo(), is(resolvedComponentInfos.get(0).getResolveInfoAt(0))); // Click on the direct target String name = serviceTargets.get(0).getTitle().toString(); onView(withText(name)) .perform(click()); waitForIdle(); FakeEventLog eventLog = getEventLog(activity); assertThat(eventLog.getShareTargetSelected()).hasSize(1); FakeEventLog.ShareTargetSelected call = eventLog.getShareTargetSelected().get(0); assertThat(call.getTargetType()).isEqualTo(EventLog.SELECTION_TYPE_SERVICE); assertThat(call.getDirectTargetAlsoRanked()).isEqualTo(-1); var hashResult = call.getDirectTargetHashed(); var hash = hashResult == null ? "" : hashResult.hashedString; assertWithMessage("Hash is not predictable but must be obfuscated") .that(hash).isNotEqualTo(name); } // This test is too long and too slow and should not be taken as an example for future tests. @Test public void testDirectTargetLoggingWithRankedAppTarget() { Intent sendIntent = createSendTextIntent(); // We need app targets for direct targets to get displayed List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); // create test shortcut loader factory, remember loaders and their callbacks SparseArray>> shortcutLoaders = createShortcutLoaderFactory(); // Start activity ChooserWrapperActivity activity = mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); // verify that ShortcutLoader was queried ArgumentCaptor appTargets = ArgumentCaptor.forClass(DisplayResolveInfo[].class); verify(shortcutLoaders.get(0).first, times(1)).updateAppTargets(appTargets.capture()); // send shortcuts assertThat( "Wrong number of app targets", appTargets.getValue().length, is(resolvedComponentInfos.size())); List serviceTargets = createDirectShareTargets( 1, resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName); ShortcutLoader.Result result = new ShortcutLoader.Result( true, appTargets.getValue(), new ShortcutLoader.ShortcutResultInfo[] { new ShortcutLoader.ShortcutResultInfo( appTargets.getValue()[0], serviceTargets ) }, new HashMap<>(), new HashMap<>() ); activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result)); waitForIdle(); final ChooserListAdapter activeAdapter = activity.getAdapter(); assertThat( "Chooser should have 3 targets (2 apps, 1 direct)", activeAdapter.getCount(), is(3)); assertThat( "Chooser should have exactly one selectable direct target", activeAdapter.getSelectableServiceTargetCount(), is(1)); assertThat( "The resolver info must match the resolver info used to create the target", activeAdapter.getItem(0).getResolveInfo(), is(resolvedComponentInfos.get(0).getResolveInfoAt(0))); // Click on the direct target String name = serviceTargets.get(0).getTitle().toString(); onView(withText(name)) .perform(click()); waitForIdle(); FakeEventLog eventLog = getEventLog(activity); assertThat(eventLog.getShareTargetSelected()).hasSize(1); FakeEventLog.ShareTargetSelected call = eventLog.getShareTargetSelected().get(0); assertThat(call.getTargetType()).isEqualTo(EventLog.SELECTION_TYPE_SERVICE); assertThat(call.getDirectTargetAlsoRanked()).isEqualTo(0); } @Test public void testShortcutTargetWithApplyAppLimits() { // Set up resources Resources resources = Mockito.spy( InstrumentationRegistry.getInstrumentation().getContext().getResources()); ChooserActivityOverrideData.getInstance().resources = resources; doReturn(1).when(resources).getInteger(R.integer.config_maxShortcutTargetsPerApp); Intent sendIntent = createSendTextIntent(); // We need app targets for direct targets to get displayed List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); // create test shortcut loader factory, remember loaders and their callbacks SparseArray>> shortcutLoaders = createShortcutLoaderFactory(); // Start activity final IChooserWrapper activity = (IChooserWrapper) mActivityRule .launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); // verify that ShortcutLoader was queried ArgumentCaptor appTargets = ArgumentCaptor.forClass(DisplayResolveInfo[].class); verify(shortcutLoaders.get(0).first, times(1)).updateAppTargets(appTargets.capture()); // send shortcuts assertThat( "Wrong number of app targets", appTargets.getValue().length, is(resolvedComponentInfos.size())); List serviceTargets = createDirectShareTargets( 2, resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName); ShortcutLoader.Result result = new ShortcutLoader.Result( true, appTargets.getValue(), new ShortcutLoader.ShortcutResultInfo[] { new ShortcutLoader.ShortcutResultInfo( appTargets.getValue()[0], serviceTargets ) }, new HashMap<>(), new HashMap<>() ); activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result)); waitForIdle(); final ChooserListAdapter activeAdapter = activity.getAdapter(); assertThat( "Chooser should have 3 targets (2 apps, 1 direct)", activeAdapter.getCount(), is(3)); assertThat( "Chooser should have exactly one selectable direct target", activeAdapter.getSelectableServiceTargetCount(), is(1)); assertThat( "The resolver info must match the resolver info used to create the target", activeAdapter.getItem(0).getResolveInfo(), is(resolvedComponentInfos.get(0).getResolveInfoAt(0))); assertThat( "The display label must match", activeAdapter.getItem(0).getDisplayLabel(), is("testTitle0")); } @Test public void testShortcutTargetWithoutApplyAppLimits() { setDeviceConfigProperty( SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI, Boolean.toString(false)); // Set up resources Resources resources = Mockito.spy( InstrumentationRegistry.getInstrumentation().getContext().getResources()); ChooserActivityOverrideData.getInstance().resources = resources; doReturn(1).when(resources).getInteger(R.integer.config_maxShortcutTargetsPerApp); Intent sendIntent = createSendTextIntent(); // We need app targets for direct targets to get displayed List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); // create test shortcut loader factory, remember loaders and their callbacks SparseArray>> shortcutLoaders = createShortcutLoaderFactory(); // Start activity final IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); // verify that ShortcutLoader was queried ArgumentCaptor appTargets = ArgumentCaptor.forClass(DisplayResolveInfo[].class); verify(shortcutLoaders.get(0).first, times(1)).updateAppTargets(appTargets.capture()); // send shortcuts assertThat( "Wrong number of app targets", appTargets.getValue().length, is(resolvedComponentInfos.size())); List serviceTargets = createDirectShareTargets( 2, resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName); ShortcutLoader.Result result = new ShortcutLoader.Result( true, appTargets.getValue(), new ShortcutLoader.ShortcutResultInfo[] { new ShortcutLoader.ShortcutResultInfo( appTargets.getValue()[0], serviceTargets ) }, new HashMap<>(), new HashMap<>() ); activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result)); waitForIdle(); final ChooserListAdapter activeAdapter = activity.getAdapter(); assertThat( "Chooser should have 4 targets (2 apps, 2 direct)", activeAdapter.getCount(), is(4)); assertThat( "Chooser should have exactly two selectable direct target", activeAdapter.getSelectableServiceTargetCount(), is(2)); assertThat( "The resolver info must match the resolver info used to create the target", activeAdapter.getItem(0).getResolveInfo(), is(resolvedComponentInfos.get(0).getResolveInfoAt(0))); assertThat( "The display label must match", activeAdapter.getItem(0).getDisplayLabel(), is("testTitle0")); assertThat( "The display label must match", activeAdapter.getItem(1).getDisplayLabel(), is("testTitle1")); } @Test public void testLaunchWithCallerProvidedTarget() { setDeviceConfigProperty( SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI, Boolean.toString(false)); // Set up resources Resources resources = Mockito.spy( InstrumentationRegistry.getInstrumentation().getContext().getResources()); ChooserActivityOverrideData.getInstance().resources = resources; doReturn(1).when(resources).getInteger(R.integer.config_maxShortcutTargetsPerApp); // We need app targets for direct targets to get displayed List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos, resolvedComponentInfos); markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); // set caller-provided target Intent chooserIntent = Intent.createChooser(createSendTextIntent(), null); String callerTargetLabel = "Caller Target"; ChooserTarget[] targets = new ChooserTarget[] { new ChooserTarget( callerTargetLabel, Icon.createWithBitmap(createBitmap()), 0.1f, resolvedComponentInfos.get(0).name, new Bundle()) }; chooserIntent.putExtra(Intent.EXTRA_CHOOSER_TARGETS, targets); // create test shortcut loader factory, remember loaders and their callbacks SparseArray>> shortcutLoaders = createShortcutLoaderFactory(); // Start activity final IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(chooserIntent); waitForIdle(); // verify that ShortcutLoader was queried ArgumentCaptor appTargets = ArgumentCaptor.forClass(DisplayResolveInfo[].class); verify(shortcutLoaders.get(0).first, times(1)).updateAppTargets(appTargets.capture()); // send shortcuts assertThat( "Wrong number of app targets", appTargets.getValue().length, is(resolvedComponentInfos.size())); ShortcutLoader.Result result = new ShortcutLoader.Result( true, appTargets.getValue(), new ShortcutLoader.ShortcutResultInfo[0], new HashMap<>(), new HashMap<>()); activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result)); waitForIdle(); final ChooserListAdapter activeAdapter = activity.getAdapter(); assertThat( "Chooser should have 3 targets (2 apps, 1 direct)", activeAdapter.getCount(), is(3)); assertThat( "Chooser should have exactly two selectable direct target", activeAdapter.getSelectableServiceTargetCount(), is(1)); assertThat( "The display label must match", activeAdapter.getItem(0).getDisplayLabel(), is(callerTargetLabel)); // Switch to work profile and ensure that the target *doesn't* show up there. onView(withText(R.string.resolver_work_tab)).perform(click()); waitForIdle(); for (int i = 0; i < activity.getWorkListAdapter().getCount(); i++) { assertThat( "Chooser target should not show up in opposite profile", activity.getWorkListAdapter().getItem(i).getDisplayLabel(), not(callerTargetLabel)); } } @Test public void testLaunchWithCustomAction() throws InterruptedException { List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); Context testContext = InstrumentationRegistry.getInstrumentation().getContext(); final String customActionLabel = "Custom Action"; final String testAction = "test-broadcast-receiver-action"; Intent chooserIntent = Intent.createChooser(createSendTextIntent(), null); chooserIntent.putExtra( Intent.EXTRA_CHOOSER_CUSTOM_ACTIONS, new ChooserAction[] { new ChooserAction.Builder( Icon.createWithResource("", Resources.ID_NULL), customActionLabel, PendingIntent.getBroadcast( testContext, 123, new Intent(testAction), PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT)) .build() }); // Start activity mActivityRule.launchActivity(chooserIntent); waitForIdle(); final CountDownLatch broadcastInvoked = new CountDownLatch(1); BroadcastReceiver testReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { broadcastInvoked.countDown(); } }; testContext.registerReceiver(testReceiver, new IntentFilter(testAction), Context.RECEIVER_EXPORTED); try { onView(withText(customActionLabel)).perform(click()); assertTrue("Timeout waiting for broadcast", broadcastInvoked.await(5000, TimeUnit.MILLISECONDS)); } finally { testContext.unregisterReceiver(testReceiver); } } @Test public void testLaunchWithShareModification() throws InterruptedException { List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); Context testContext = InstrumentationRegistry.getInstrumentation().getContext(); final String modifyShareAction = "test-broadcast-receiver-action"; Intent chooserIntent = Intent.createChooser(createSendTextIntent(), null); String label = "modify share"; PendingIntent pendingIntent = PendingIntent.getBroadcast( testContext, 123, new Intent(modifyShareAction), PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT); ChooserAction action = new ChooserAction.Builder(Icon.createWithBitmap( createBitmap()), label, pendingIntent).build(); chooserIntent.putExtra( Intent.EXTRA_CHOOSER_MODIFY_SHARE_ACTION, action); // Start activity mActivityRule.launchActivity(chooserIntent); waitForIdle(); final CountDownLatch broadcastInvoked = new CountDownLatch(1); BroadcastReceiver testReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { broadcastInvoked.countDown(); } }; testContext.registerReceiver(testReceiver, new IntentFilter(modifyShareAction), Context.RECEIVER_EXPORTED); try { onView(withText(label)).perform(click()); assertTrue("Timeout waiting for broadcast", broadcastInvoked.await(5000, TimeUnit.MILLISECONDS)); } finally { testContext.unregisterReceiver(testReceiver); } } @Test public void testUpdateMaxTargetsPerRow_columnCountIsUpdated() throws InterruptedException { updateMaxTargetsPerRowResource(/* targetsPerRow= */ 4); givenAppTargets(/* appCount= */ 16); Intent sendIntent = createSendTextIntent(); final ChooserActivity activity = mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); updateMaxTargetsPerRowResource(/* targetsPerRow= */ 6); InstrumentationRegistry.getInstrumentation() .runOnMainSync(() -> activity.onConfigurationChanged( InstrumentationRegistry.getInstrumentation() .getContext().getResources().getConfiguration())); waitForIdle(); onView(withId(com.android.internal.R.id.resolver_list)) .check(matches(withGridColumnCount(6))); } // This test is too long and too slow and should not be taken as an example for future tests. @Test @Ignore public void testDirectTargetLoggingWithAppTargetNotRankedPortrait() throws InterruptedException { testDirectTargetLoggingWithAppTargetNotRanked(Configuration.ORIENTATION_PORTRAIT, 4); } @Test @Ignore public void testDirectTargetLoggingWithAppTargetNotRankedLandscape() throws InterruptedException { testDirectTargetLoggingWithAppTargetNotRanked(Configuration.ORIENTATION_LANDSCAPE, 8); } private void testDirectTargetLoggingWithAppTargetNotRanked( int orientation, int appTargetsExpected) { Configuration configuration = new Configuration(InstrumentationRegistry.getInstrumentation().getContext() .getResources().getConfiguration()); configuration.orientation = orientation; Resources resources = Mockito.spy( InstrumentationRegistry.getInstrumentation().getContext().getResources()); ChooserActivityOverrideData.getInstance().resources = resources; doReturn(configuration).when(resources).getConfiguration(); Intent sendIntent = createSendTextIntent(); // We need app targets for direct targets to get displayed List resolvedComponentInfos = createResolvedComponentsForTest(15); setupResolverControllers(resolvedComponentInfos); // Create direct share target List serviceTargets = createDirectShareTargets(1, resolvedComponentInfos.get(14).getResolveInfoAt(0).activityInfo.packageName); ResolveInfo ri = ResolverDataProvider.createResolveInfo(16, 0, PERSONAL_USER_HANDLE); // Start activity ChooserWrapperActivity activity = mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); // Insert the direct share target Map directShareToShortcutInfos = new HashMap<>(); directShareToShortcutInfos.put(serviceTargets.get(0), null); InstrumentationRegistry.getInstrumentation().runOnMainSync( () -> activity.getAdapter().addServiceResults( activity.createTestDisplayResolveInfo(sendIntent, ri, "testLabel", "testInfo", sendIntent), serviceTargets, TARGET_TYPE_CHOOSER_TARGET, directShareToShortcutInfos, /* directShareToAppTargets */ null) ); assertThat( String.format("Chooser should have %d targets (%d apps, 1 direct, 15 A-Z)", appTargetsExpected + 16, appTargetsExpected), activity.getAdapter().getCount(), is(appTargetsExpected + 16)); assertThat("Chooser should have exactly one selectable direct target", activity.getAdapter().getSelectableServiceTargetCount(), is(1)); assertThat("The resolver info must match the resolver info used to create the target", activity.getAdapter().getItem(0).getResolveInfo(), is(ri)); // Click on the direct target String name = serviceTargets.get(0).getTitle().toString(); onView(withText(name)) .perform(click()); waitForIdle(); FakeEventLog eventLog = getEventLog(activity); var invocations = eventLog.getShareTargetSelected(); assertWithMessage("Only one ShareTargetSelected event logged") .that(invocations).hasSize(1); FakeEventLog.ShareTargetSelected call = invocations.get(0); assertWithMessage("targetType should be SELECTION_TYPE_SERVICE") .that(call.getTargetType()).isEqualTo(EventLog.SELECTION_TYPE_SERVICE); assertWithMessage( "The packages shouldn't match for app target and direct target") .that(call.getDirectTargetAlsoRanked()).isEqualTo(-1); } @Test public void testWorkTab_displayedWhenWorkProfileUserAvailable() { Intent sendIntent = createSendTextIntent(); sendIntent.setType(TEST_MIME_TYPE); markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); waitForIdle(); onView(withId(android.R.id.tabs)).check(matches(isDisplayed())); } @Test public void testWorkTab_hiddenWhenWorkProfileUserNotAvailable() { Intent sendIntent = createSendTextIntent(); sendIntent.setType(TEST_MIME_TYPE); mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); waitForIdle(); onView(withId(android.R.id.tabs)).check(matches(not(isDisplayed()))); } @Test public void testWorkTab_eachTabUsesExpectedAdapter() { int personalProfileTargets = 3; int otherProfileTargets = 1; List personalResolvedComponentInfos = createResolvedComponentsForTestWithOtherProfile( personalProfileTargets + otherProfileTargets, /* userID */ 10); int workProfileTargets = 4; List workResolvedComponentInfos = createResolvedComponentsForTest( workProfileTargets); setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); Intent sendIntent = createSendTextIntent(); sendIntent.setType(TEST_MIME_TYPE); markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); final IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); waitForIdle(); assertThat(activity.getCurrentUserHandle().getIdentifier(), is(0)); onView(withText(R.string.resolver_work_tab)).perform(click()); assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10)); assertThat(activity.getPersonalListAdapter().getCount(), is(personalProfileTargets)); assertThat(activity.getWorkListAdapter().getCount(), is(workProfileTargets)); } @Test public void testWorkTab_workProfileHasExpectedNumberOfTargets() throws InterruptedException { markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); int workProfileTargets = 4; List personalResolvedComponentInfos = createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); List workResolvedComponentInfos = createResolvedComponentsForTest(workProfileTargets); setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); Intent sendIntent = createSendTextIntent(); sendIntent.setType(TEST_MIME_TYPE); final IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); waitForIdle(); onView(withText(R.string.resolver_work_tab)).perform(click()); waitForIdle(); assertThat(activity.getWorkListAdapter().getCount(), is(workProfileTargets)); } @Test @Ignore public void testWorkTab_selectingWorkTabAppOpensAppInWorkProfile() { markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); List personalResolvedComponentInfos = createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); int workProfileTargets = 4; List workResolvedComponentInfos = createResolvedComponentsForTest(workProfileTargets); setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); Intent sendIntent = createSendTextIntent(); sendIntent.setType(TEST_MIME_TYPE); ResolveInfo[] chosen = new ResolveInfo[1]; ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { chosen[0] = targetInfo.getResolveInfo(); return true; }; mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); waitForIdle(); onView(withText(R.string.resolver_work_tab)).perform(click()); waitForIdle(); onView(first(allOf( withText(workResolvedComponentInfos.get(0) .getResolveInfoAt(0).activityInfo.applicationInfo.name), isDisplayed()))) .perform(click()); waitForIdle(); assertThat(chosen[0], is(workResolvedComponentInfos.get(0).getResolveInfoAt(0))); } @Test public void testWorkTab_crossProfileIntentsDisabled_personalToWork_emptyStateShown() { markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); int workProfileTargets = 4; List personalResolvedComponentInfos = createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); List workResolvedComponentInfos = createResolvedComponentsForTest(workProfileTargets); ChooserActivityOverrideData.getInstance().hasCrossProfileIntents = false; setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); Intent sendIntent = createSendTextIntent(); sendIntent.setType(TEST_MIME_TYPE); mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); waitForIdle(); onView(withText(R.string.resolver_work_tab)).perform(click()); waitForIdle(); onView(withId(com.android.internal.R.id.contentPanel)) .perform(swipeUp()); onView(withText(R.string.resolver_cross_profile_blocked)) .check(matches(isDisplayed())); } @Test public void testWorkTab_workProfileDisabled_emptyStateShown() { markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); int workProfileTargets = 4; List personalResolvedComponentInfos = createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); List workResolvedComponentInfos = createResolvedComponentsForTest(workProfileTargets); mFakeUserRepo.updateState(WORK_USER, false); setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); Intent sendIntent = createSendTextIntent(); sendIntent.setType(TEST_MIME_TYPE); mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); waitForIdle(); onView(withId(com.android.internal.R.id.contentPanel)) .perform(swipeUp()); onView(withText(R.string.resolver_work_tab)).perform(click()); waitForIdle(); onView(withText(R.string.resolver_turn_on_work_apps)) .check(matches(isDisplayed())); } @Test public void testWorkTab_noWorkAppsAvailable_emptyStateShown() { markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); List personalResolvedComponentInfos = createResolvedComponentsForTest(3); List workResolvedComponentInfos = createResolvedComponentsForTest(0); setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); Intent sendIntent = createSendTextIntent(); sendIntent.setType(TEST_MIME_TYPE); mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); waitForIdle(); onView(withId(com.android.internal.R.id.contentPanel)) .perform(swipeUp()); onView(withText(R.string.resolver_work_tab)).perform(click()); waitForIdle(); onView(withText(R.string.resolver_no_work_apps_available)) .check(matches(isDisplayed())); } @Test public void testWorkTab_previewIsScrollable() { markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); List personalResolvedComponentInfos = createResolvedComponentsForTest(300); List workResolvedComponentInfos = createResolvedComponentsForTest(3); setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); Uri uri = createTestContentProviderUri("image/png", null); ArrayList uris = new ArrayList<>(); uris.add(uri); Intent sendIntent = createSendUriIntentWithPreview(uris); mFakeImageLoader.setBitmap(uri, createWideBitmap()); mActivityRule.launchActivity(Intent.createChooser(sendIntent, "Scrollable preview test")); waitForIdle(); onView(withId(com.android.intentresolver.R.id.scrollable_image_preview)) .check(matches(isDisplayed())); onView(withId(com.android.internal.R.id.contentPanel)).perform(swipeUp()); waitForIdle(); onView(withId(com.android.intentresolver.R.id.chooser_headline_row_container)) .check(matches(isCompletelyDisplayed())); onView(withId(com.android.intentresolver.R.id.headline)) .check(matches(isDisplayed())); onView(withId(com.android.intentresolver.R.id.scrollable_image_preview)) .check(matches(not(isDisplayed()))); } @Ignore // b/220067877 @Test public void testWorkTab_xProfileOff_noAppsAvailable_workOff_xProfileOffEmptyStateShown() { markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); List personalResolvedComponentInfos = createResolvedComponentsForTest(3); List workResolvedComponentInfos = createResolvedComponentsForTest(0); setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); mFakeUserRepo.updateState(WORK_USER, false); ChooserActivityOverrideData.getInstance().hasCrossProfileIntents = false; Intent sendIntent = createSendTextIntent(); sendIntent.setType(TEST_MIME_TYPE); mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); waitForIdle(); onView(withId(com.android.internal.R.id.contentPanel)) .perform(swipeUp()); onView(withText(R.string.resolver_work_tab)).perform(click()); waitForIdle(); onView(withText(R.string.resolver_cross_profile_blocked)) .check(matches(isDisplayed())); } @Test public void testWorkTab_noAppsAvailable_workOff_noAppsAvailableEmptyStateShown() { markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); List personalResolvedComponentInfos = createResolvedComponentsForTest(3); List workResolvedComponentInfos = createResolvedComponentsForTest(0); setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); mFakeUserRepo.updateState(WORK_USER, false); Intent sendIntent = createSendTextIntent(); sendIntent.setType(TEST_MIME_TYPE); mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); waitForIdle(); onView(withId(com.android.internal.R.id.contentPanel)) .perform(swipeUp()); onView(withText(R.string.resolver_work_tab)).perform(click()); waitForIdle(); onView(withText(R.string.resolver_no_work_apps_available)) .check(matches(isDisplayed())); } @Test @Ignore("b/222124533") public void testAppTargetLogging() throws InterruptedException { Intent sendIntent = createSendTextIntent(); List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); final IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); // TODO(b/222124533): other test cases use a timeout to make sure that the UI is fully // populated; without one, this test flakes. Ideally we should address the need for a // timeout everywhere instead of introducing one to fix this particular test. assertThat(activity.getAdapter().getCount(), is(2)); onView(withId(com.android.internal.R.id.profile_button)).check(doesNotExist()); ResolveInfo[] chosen = new ResolveInfo[1]; ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { chosen[0] = targetInfo.getResolveInfo(); return true; }; ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0); onView(withText(toChoose.activityInfo.name)) .perform(click()); waitForIdle(); // TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events. } @Test public void testDirectTargetLogging() { Intent sendIntent = createSendTextIntent(); // We need app targets for direct targets to get displayed List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); // create test shortcut loader factory, remember loaders and their callbacks SparseArray>> shortcutLoaders = new SparseArray<>(); ChooserActivityOverrideData.getInstance().shortcutLoaderFactory = (userHandle, callback) -> { Pair> pair = new Pair<>(mock(ShortcutLoader.class), callback); shortcutLoaders.put(userHandle.getIdentifier(), pair); return pair.first; }; // Start activity ChooserWrapperActivity activity = mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); // verify that ShortcutLoader was queried ArgumentCaptor appTargets = ArgumentCaptor.forClass(DisplayResolveInfo[].class); verify(shortcutLoaders.get(0).first, times(1)) .updateAppTargets(appTargets.capture()); // send shortcuts assertThat( "Wrong number of app targets", appTargets.getValue().length, is(resolvedComponentInfos.size())); List serviceTargets = createDirectShareTargets(1, resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName); ShortcutLoader.Result result = new ShortcutLoader.Result( // TODO: test another value as well false, appTargets.getValue(), new ShortcutLoader.ShortcutResultInfo[] { new ShortcutLoader.ShortcutResultInfo( appTargets.getValue()[0], serviceTargets ) }, new HashMap<>(), new HashMap<>() ); activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result)); waitForIdle(); assertThat("Chooser should have 3 targets (2 apps, 1 direct)", activity.getAdapter().getCount(), is(3)); assertThat("Chooser should have exactly one selectable direct target", activity.getAdapter().getSelectableServiceTargetCount(), is(1)); assertThat( "The resolver info must match the resolver info used to create the target", activity.getAdapter().getItem(0).getResolveInfo(), is(resolvedComponentInfos.get(0).getResolveInfoAt(0))); // Click on the direct target String name = serviceTargets.get(0).getTitle().toString(); onView(withText(name)) .perform(click()); waitForIdle(); FakeEventLog eventLog = getEventLog(activity); assertThat(eventLog.getShareTargetSelected()).hasSize(1); FakeEventLog.ShareTargetSelected call = eventLog.getShareTargetSelected().get(0); assertThat(call.getTargetType()).isEqualTo(EventLog.SELECTION_TYPE_SERVICE); } @Test public void testDirectTargetPinningDialog() { Intent sendIntent = createSendTextIntent(); // We need app targets for direct targets to get displayed List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); // create test shortcut loader factory, remember loaders and their callbacks SparseArray>> shortcutLoaders = new SparseArray<>(); ChooserActivityOverrideData.getInstance().shortcutLoaderFactory = (userHandle, callback) -> { Pair> pair = new Pair<>(mock(ShortcutLoader.class), callback); shortcutLoaders.put(userHandle.getIdentifier(), pair); return pair.first; }; // Start activity final IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); // verify that ShortcutLoader was queried ArgumentCaptor appTargets = ArgumentCaptor.forClass(DisplayResolveInfo[].class); verify(shortcutLoaders.get(0).first, times(1)) .updateAppTargets(appTargets.capture()); // send shortcuts List serviceTargets = createDirectShareTargets( 1, resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName); ShortcutLoader.Result result = new ShortcutLoader.Result( // TODO: test another value as well false, appTargets.getValue(), new ShortcutLoader.ShortcutResultInfo[] { new ShortcutLoader.ShortcutResultInfo( appTargets.getValue()[0], serviceTargets ) }, new HashMap<>(), new HashMap<>() ); activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result)); waitForIdle(); // Long-click on the direct target String name = serviceTargets.get(0).getTitle().toString(); onView(withText(name)).perform(longClick()); waitForIdle(); onView(withId(R.id.chooser_dialog_content)).check(matches(isDisplayed())); } @Test @Ignore public void testEmptyDirectRowLogging() throws InterruptedException { Intent sendIntent = createSendTextIntent(); // We need app targets for direct targets to get displayed List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); // Start activity final IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); // Thread.sleep shouldn't be a thing in an integration test but it's // necessary here because of the way the code is structured Thread.sleep(3000); assertThat("Chooser should have 2 app targets", activity.getAdapter().getCount(), is(2)); assertThat("Chooser should have no direct targets", activity.getAdapter().getSelectableServiceTargetCount(), is(0)); // TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events. } @Ignore // b/220067877 @Test public void testCopyTextToClipboardLogging() throws Exception { Intent sendIntent = createSendTextIntent(); List resolvedComponentInfos = createResolvedComponentsForTest(2); setupResolverControllers(resolvedComponentInfos); final IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); onView(withId(com.android.internal.R.id.chooser_copy_button)).check(matches(isDisplayed())); onView(withId(com.android.internal.R.id.chooser_copy_button)).perform(click()); // TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events. } @Test @Ignore("b/222124533") public void testSwitchProfileLogging() throws InterruptedException { markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); int workProfileTargets = 4; List personalResolvedComponentInfos = createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); List workResolvedComponentInfos = createResolvedComponentsForTest(workProfileTargets); setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); Intent sendIntent = createSendTextIntent(); sendIntent.setType(TEST_MIME_TYPE); final IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); waitForIdle(); onView(withText(R.string.resolver_work_tab)).perform(click()); waitForIdle(); onView(withText(R.string.resolver_personal_tab)).perform(click()); waitForIdle(); // TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events. } @Test public void testWorkTab_onePersonalTarget_emptyStateOnWorkTarget_doesNotAutoLaunch() { markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); int workProfileTargets = 4; List personalResolvedComponentInfos = createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10); List workResolvedComponentInfos = createResolvedComponentsForTest(workProfileTargets); ChooserActivityOverrideData.getInstance().hasCrossProfileIntents = false; setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); Intent sendIntent = createSendTextIntent(); ResolveInfo[] chosen = new ResolveInfo[1]; ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { chosen[0] = targetInfo.getResolveInfo(); return true; }; mActivityRule.launchActivity(Intent.createChooser(sendIntent, "Test")); waitForIdle(); assertNull(chosen[0]); } @Test public void testOneInitialIntent_noAutolaunch() { List personalResolvedComponentInfos = createResolvedComponentsForTest(1); setupResolverControllers(personalResolvedComponentInfos); Intent chooserIntent = createChooserIntent(createSendTextIntent(), new Intent[] {new Intent("action.fake")}); ResolveInfo[] chosen = new ResolveInfo[1]; ChooserActivityOverrideData.getInstance().onSafelyStartInternalCallback = targetInfo -> { chosen[0] = targetInfo.getResolveInfo(); return true; }; mPackageManager = createFakePackageManager(createFakeResolveInfo()); waitForIdle(); IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(chooserIntent); waitForIdle(); assertNull(chosen[0]); assertThat(activity .getPersonalListAdapter().getCallerTargetCount(), is(1)); } @Test public void testWorkTab_withInitialIntents_workTabDoesNotIncludePersonalInitialIntents() { markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); int workProfileTargets = 1; List personalResolvedComponentInfos = createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10); List workResolvedComponentInfos = createResolvedComponentsForTest(workProfileTargets); setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); Intent[] initialIntents = { new Intent("action.fake1"), new Intent("action.fake2") }; Intent chooserIntent = createChooserIntent(createSendTextIntent(), initialIntents); mPackageManager = createFakePackageManager(createFakeResolveInfo()); waitForIdle(); IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(chooserIntent); waitForIdle(); assertThat(activity.getPersonalListAdapter().getCallerTargetCount(), is(2)); assertThat(activity.getWorkListAdapter().getCallerTargetCount(), is(0)); } @Test public void testWorkTab_xProfileIntentsDisabled_personalToWork_nonSendIntent_emptyStateShown() { markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); int workProfileTargets = 4; List personalResolvedComponentInfos = createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); List workResolvedComponentInfos = createResolvedComponentsForTest(workProfileTargets); ChooserActivityOverrideData.getInstance().hasCrossProfileIntents = false; setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); Intent[] initialIntents = { new Intent("action.fake1"), new Intent("action.fake2") }; Intent chooserIntent = createChooserIntent(new Intent(), initialIntents); mPackageManager = createFakePackageManager(createFakeResolveInfo()); mActivityRule.launchActivity(chooserIntent); waitForIdle(); onView(withText(R.string.resolver_work_tab)).perform(click()); waitForIdle(); onView(withId(com.android.internal.R.id.contentPanel)) .perform(swipeUp()); onView(withText(R.string.resolver_cross_profile_blocked)) .check(matches(isDisplayed())); } @Test public void testWorkTab_noWorkAppsAvailable_nonSendIntent_emptyStateShown() { markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); List personalResolvedComponentInfos = createResolvedComponentsForTest(3); List workResolvedComponentInfos = createResolvedComponentsForTest(0); setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); Intent[] initialIntents = { new Intent("action.fake1"), new Intent("action.fake2") }; Intent chooserIntent = createChooserIntent(new Intent(), initialIntents); mPackageManager = createFakePackageManager(createFakeResolveInfo()); mActivityRule.launchActivity(chooserIntent); waitForIdle(); onView(withId(com.android.internal.R.id.contentPanel)) .perform(swipeUp()); onView(withText(R.string.resolver_work_tab)).perform(click()); waitForIdle(); onView(withText(R.string.resolver_no_work_apps_available)) .check(matches(isDisplayed())); } @Test public void testDeduplicateCallerTargetRankedTarget() { // Create 4 ranked app targets. List personalResolvedComponentInfos = createResolvedComponentsForTest(4); setupResolverControllers(personalResolvedComponentInfos); // Create caller target which is duplicate with one of app targets Intent chooserIntent = createChooserIntent(createSendTextIntent(), new Intent[] {new Intent("action.fake")}); mPackageManager = createFakePackageManager(ResolverDataProvider.createResolveInfo(0, UserHandle.USER_CURRENT, PERSONAL_USER_HANDLE)); waitForIdle(); IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(chooserIntent); waitForIdle(); // Total 4 targets (1 caller target, 3 ranked targets) assertThat(activity.getAdapter().getCount(), is(4)); assertThat(activity.getAdapter().getCallerTargetCount(), is(1)); assertThat(activity.getAdapter().getRankedTargetCount(), is(3)); } @Test public void test_query_shortcut_loader_for_the_selected_tab() { markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ false); List personalResolvedComponentInfos = createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); List workResolvedComponentInfos = createResolvedComponentsForTest(3); setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); ShortcutLoader personalProfileShortcutLoader = mock(ShortcutLoader.class); ShortcutLoader workProfileShortcutLoader = mock(ShortcutLoader.class); final SparseArray shortcutLoaders = new SparseArray<>(); shortcutLoaders.put(0, personalProfileShortcutLoader); shortcutLoaders.put(10, workProfileShortcutLoader); ChooserActivityOverrideData.getInstance().shortcutLoaderFactory = (userHandle, callback) -> shortcutLoaders.get(userHandle.getIdentifier(), null); Intent sendIntent = createSendTextIntent(); sendIntent.setType(TEST_MIME_TYPE); mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); waitForIdle(); onView(withId(com.android.internal.R.id.contentPanel)) .perform(swipeUp()); waitForIdle(); verify(personalProfileShortcutLoader, times(1)).updateAppTargets(any()); onView(withText(R.string.resolver_work_tab)).perform(click()); waitForIdle(); verify(workProfileShortcutLoader, times(1)).updateAppTargets(any()); } @Test public void testClonedProfilePresent_personalAdapterIsSetWithPersonalProfile() { // enable cloneProfile markOtherProfileAvailability(/* workAvailable= */ false, /* cloneAvailable= */ true); List resolvedComponentInfos = createResolvedComponentsWithCloneProfileForTest( 3, PERSONAL_USER_HANDLE, CLONE_PROFILE_USER_HANDLE); setupResolverControllers(resolvedComponentInfos); Intent sendIntent = createSendTextIntent(); final IChooserWrapper activity = (IChooserWrapper) mActivityRule .launchActivity(Intent.createChooser(sendIntent, "personalProfileTest")); waitForIdle(); assertThat(activity.getPersonalListAdapter().getUserHandle(), is(PERSONAL_USER_HANDLE)); assertThat(activity.getAdapter().getCount(), is(3)); } @Test public void testClonedProfilePresent_personalTabUsesExpectedAdapter() { markOtherProfileAvailability(/* workAvailable= */ true, /* cloneAvailable= */ true); List personalResolvedComponentInfos = createResolvedComponentsForTest(3); List 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)); } @Test public void chooserDisabledWhileDeviceFrpLocked() { mGlobalSettings.putBoolean(Settings.Global.SECURE_FRP_MODE, true); Intent viewIntent = createSendTextIntent(); ChooserWrapperActivity activity = mActivityRule.launchActivity( Intent.createChooser(viewIntent, "chooser test")); waitForIdle(); assertTrue(activity.isFinishing()); } private Intent createChooserIntent(Intent intent, Intent[] initialIntents) { Intent chooserIntent = new Intent(); chooserIntent.setAction(Intent.ACTION_CHOOSER); chooserIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending"); chooserIntent.putExtra(Intent.EXTRA_TITLE, "some title"); chooserIntent.putExtra(Intent.EXTRA_INTENT, intent); chooserIntent.setType("text/plain"); if (initialIntents != null) { chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, initialIntents); } return chooserIntent; } /* This is a "test of a test" to make sure that our inherited test class * is successfully configured to operate on the unbundled-equivalent * ChooserWrapperActivity. * * TODO: remove after unbundling is complete. */ @Test public void testWrapperActivityHasExpectedConcreteType() { final ChooserActivity activity = mActivityRule.launchActivity( Intent.createChooser(new Intent("ACTION_FOO"), "foo")); waitForIdle(); assertThat(activity).isInstanceOf(ChooserWrapperActivity.class); } private ResolveInfo createFakeResolveInfo() { ResolveInfo ri = new ResolveInfo(); ri.activityInfo = new ActivityInfo(); ri.activityInfo.name = "FakeActivityName"; ri.activityInfo.packageName = "fake.package.name"; ri.activityInfo.applicationInfo = new ApplicationInfo(); ri.activityInfo.applicationInfo.packageName = "fake.package.name"; ri.userHandle = UserHandle.CURRENT; return ri; } private Intent createSendTextIntent() { Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending"); sendIntent.setType("text/plain"); return sendIntent; } private Intent createSendImageIntent(Uri imageThumbnail) { Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); sendIntent.putExtra(Intent.EXTRA_STREAM, imageThumbnail); sendIntent.setType("image/png"); if (imageThumbnail != null) { ClipData.Item clipItem = new ClipData.Item(imageThumbnail); sendIntent.setClipData(new ClipData("Clip Label", new String[]{"image/png"}, clipItem)); } return sendIntent; } private Uri createTestContentProviderUri( @Nullable String mimeType, @Nullable String streamType) { return createTestContentProviderUri(mimeType, streamType, 0); } private Uri createTestContentProviderUri( @Nullable String mimeType, @Nullable String streamType, long streamTypeTimeout) { String packageName = InstrumentationRegistry.getInstrumentation().getContext().getPackageName(); Uri.Builder builder = Uri.parse("content://" + packageName + "/image.png") .buildUpon(); if (mimeType != null) { builder.appendQueryParameter(TestContentProvider.PARAM_MIME_TYPE, mimeType); } if (streamType != null) { builder.appendQueryParameter(TestContentProvider.PARAM_STREAM_TYPE, streamType); } if (streamTypeTimeout > 0) { builder.appendQueryParameter( TestContentProvider.PARAM_STREAM_TYPE_TIMEOUT, Long.toString(streamTypeTimeout)); } return builder.build(); } private Intent createSendTextIntentWithPreview(String title, Uri imageThumbnail) { Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending"); sendIntent.putExtra(Intent.EXTRA_TITLE, title); if (imageThumbnail != null) { ClipData.Item clipItem = new ClipData.Item(imageThumbnail); sendIntent.setClipData(new ClipData("Clip Label", new String[]{"image/png"}, clipItem)); } return sendIntent; } private Intent createSendUriIntentWithPreview(ArrayList uris) { Intent sendIntent = new Intent(); if (uris.size() > 1) { sendIntent.setAction(Intent.ACTION_SEND_MULTIPLE); sendIntent.putExtra(Intent.EXTRA_STREAM, uris); } else { sendIntent.setAction(Intent.ACTION_SEND); sendIntent.putExtra(Intent.EXTRA_STREAM, uris.get(0)); } return sendIntent; } private Intent createViewTextIntent() { Intent viewIntent = new Intent(); viewIntent.setAction(Intent.ACTION_VIEW); viewIntent.putExtra(Intent.EXTRA_TEXT, "testing intent viewing"); return viewIntent; } private List createResolvedComponentsForTest(int numberOfResults) { List infoList = new ArrayList<>(numberOfResults); for (int i = 0; i < numberOfResults; i++) { infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, PERSONAL_USER_HANDLE)); } return infoList; } private List createResolvedComponentsWithCloneProfileForTest( int numberOfResults, UserHandle resolvedForPersonalUser, UserHandle resolvedForClonedUser) { List 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 createResolvedComponentsForTestWithOtherProfile( int numberOfResults) { List infoList = new ArrayList<>(numberOfResults); for (int i = 0; i < numberOfResults; i++) { if (i == 0) { infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, PERSONAL_USER_HANDLE)); } else { infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, PERSONAL_USER_HANDLE)); } } return infoList; } private List createResolvedComponentsForTestWithOtherProfile( int numberOfResults, int userId) { List infoList = new ArrayList<>(numberOfResults); for (int i = 0; i < numberOfResults; i++) { if (i == 0) { infoList.add( ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId, PERSONAL_USER_HANDLE)); } else { infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, PERSONAL_USER_HANDLE)); } } return infoList; } private List createResolvedComponentsForTestWithUserId( int numberOfResults, int userId) { List infoList = new ArrayList<>(numberOfResults); for (int i = 0; i < numberOfResults; i++) { infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId, PERSONAL_USER_HANDLE)); } return infoList; } private List createDirectShareTargets(int numberOfResults, String packageName) { Icon icon = Icon.createWithBitmap(createBitmap()); String testTitle = "testTitle"; List targets = new ArrayList<>(); for (int i = 0; i < numberOfResults; i++) { ComponentName componentName; if (packageName.isEmpty()) { componentName = ResolverDataProvider.createComponentName(i); } else { componentName = new ComponentName(packageName, packageName + ".class"); } ChooserTarget tempTarget = new ChooserTarget( testTitle + i, icon, (float) (1 - ((i + 1) / 10.0)), componentName, null); targets.add(tempTarget); } return targets; } private void waitForIdle() { InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } private Bitmap createBitmap() { return createBitmap(200, 200); } private Bitmap createWideBitmap() { return createWideBitmap(Color.RED); } private Bitmap createWideBitmap(int bgColor) { WindowManager windowManager = InstrumentationRegistry.getInstrumentation() .getTargetContext() .getSystemService(WindowManager.class); int width = 3000; if (windowManager != null) { Rect bounds = windowManager.getMaximumWindowMetrics().getBounds(); width = bounds.width() + 200; } return createBitmap(width, 100, bgColor); } private Bitmap createBitmap(int width, int height) { return createBitmap(width, height, Color.RED); } private Bitmap createBitmap(int width, int height, int bgColor) { Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); Paint paint = new Paint(); paint.setColor(bgColor); paint.setStyle(Paint.Style.FILL); canvas.drawPaint(paint); paint.setColor(Color.WHITE); paint.setAntiAlias(true); paint.setTextSize(14.f); paint.setTextAlign(Paint.Align.CENTER); canvas.drawText("Hi!", (width / 2.f), (height / 2.f), paint); return bitmap; } private List createShortcuts(Context context) { Intent testIntent = new Intent("TestIntent"); List shortcuts = new ArrayList<>(); shortcuts.add(new ShareShortcutInfo( new ShortcutInfo.Builder(context, "shortcut1") .setIntent(testIntent).setShortLabel("label1").setRank(3).build(), // 0 2 new ComponentName("package1", "class1"))); shortcuts.add(new ShareShortcutInfo( new ShortcutInfo.Builder(context, "shortcut2") .setIntent(testIntent).setShortLabel("label2").setRank(7).build(), // 1 3 new ComponentName("package2", "class2"))); shortcuts.add(new ShareShortcutInfo( new ShortcutInfo.Builder(context, "shortcut3") .setIntent(testIntent).setShortLabel("label3").setRank(1).build(), // 2 0 new ComponentName("package3", "class3"))); shortcuts.add(new ShareShortcutInfo( new ShortcutInfo.Builder(context, "shortcut4") .setIntent(testIntent).setShortLabel("label4").setRank(3).build(), // 3 2 new ComponentName("package4", "class4"))); return shortcuts; } private void markOtherProfileAvailability(boolean workAvailable, boolean cloneAvailable) { if (workAvailable) { mFakeUserRepo.addUser(WORK_USER, /* available= */ true); } if (cloneAvailable) { mFakeUserRepo.addUser(CLONE_USER, /* available= */ true); } } private void setupResolverControllers( List personalResolvedComponentInfos) { setupResolverControllers(personalResolvedComponentInfos, new ArrayList<>()); } private void setupResolverControllers( List personalResolvedComponentInfos, List workResolvedComponentInfos) { when( ChooserActivityOverrideData .getInstance() .resolverListController .getResolversForIntentAsUser( Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.isA(List.class), eq(PERSONAL_USER_HANDLE))) .thenReturn(new ArrayList<>(personalResolvedComponentInfos)); when( ChooserActivityOverrideData .getInstance() .workResolverListController .getResolversForIntentAsUser( Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.isA(List.class), eq(WORK_PROFILE_USER_HANDLE))) .thenReturn(new ArrayList<>(workResolvedComponentInfos)); } private static GridRecyclerSpanCountMatcher withGridColumnCount(int columnCount) { return new GridRecyclerSpanCountMatcher(Matchers.is(columnCount)); } private static class GridRecyclerSpanCountMatcher extends BoundedDiagnosingMatcher { private final Matcher mIntegerMatcher; private GridRecyclerSpanCountMatcher(Matcher integerMatcher) { super(RecyclerView.class); this.mIntegerMatcher = integerMatcher; } @Override protected void describeMoreTo(Description description) { description.appendText("RecyclerView grid layout span count to match: "); this.mIntegerMatcher.describeTo(description); } @Override protected boolean matchesSafely(RecyclerView view, Description mismatchDescription) { int spanCount = ((GridLayoutManager) view.getLayoutManager()).getSpanCount(); if (this.mIntegerMatcher.matches(spanCount)) { return true; } else { mismatchDescription.appendText("RecyclerView grid layout span count was ") .appendValue(spanCount); return false; } } } private void givenAppTargets(int appCount) { List resolvedComponentInfos = createResolvedComponentsForTest(appCount); setupResolverControllers(resolvedComponentInfos); } private void updateMaxTargetsPerRowResource(int targetsPerRow) { Resources resources = Mockito.spy( InstrumentationRegistry.getInstrumentation().getContext().getResources()); ChooserActivityOverrideData.getInstance().resources = resources; doReturn(targetsPerRow).when(resources).getInteger( R.integer.config_chooser_max_targets_per_row); } private SparseArray>> createShortcutLoaderFactory() { SparseArray>> shortcutLoaders = new SparseArray<>(); ChooserActivityOverrideData.getInstance().shortcutLoaderFactory = (userHandle, callback) -> { Pair> pair = new Pair<>(mock(ShortcutLoader.class), callback); shortcutLoaders.put(userHandle.getIdentifier(), pair); return pair.first; }; return shortcutLoaders; } }