summaryrefslogtreecommitdiff
path: root/java/tests/src
diff options
context:
space:
mode:
author Xin Li <delphij@google.com> 2023-03-13 23:11:31 -0700
committer Xin Li <delphij@google.com> 2023-03-13 23:11:31 -0700
commit7b21dc4a35cae1218308a2f04fc61d6247faa17b (patch)
tree1448e0c76772f25db02c8931f588e0d32673d1d4 /java/tests/src
parentcc64c57aa426bf71e88dc073b8197748fd720856 (diff)
parent1606e219c8db1c233713f9dc2546225533718eca (diff)
Merge Android 13 QPR2
Bug: 273316506 Merged-In: Ia56e92ed5358ca66185f5011abd139392ee73785 Change-Id: Ib152678de052bf41ad0716401561c7e505614fe5
Diffstat (limited to 'java/tests/src')
-rw-r--r--java/tests/src/com/android/intentresolver/ChooserActivityLoggerFake.java134
-rw-r--r--java/tests/src/com/android/intentresolver/ChooserActivityLoggerTest.java406
-rw-r--r--java/tests/src/com/android/intentresolver/ChooserActivityOverrideData.java60
-rw-r--r--java/tests/src/com/android/intentresolver/ChooserListAdapterTest.kt155
-rw-r--r--java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java136
-rw-r--r--java/tests/src/com/android/intentresolver/IChooserWrapper.java6
-rw-r--r--java/tests/src/com/android/intentresolver/MockitoKotlinHelpers.kt146
-rw-r--r--java/tests/src/com/android/intentresolver/ResolverActivityTest.java858
-rw-r--r--java/tests/src/com/android/intentresolver/ResolverDataProvider.java18
-rw-r--r--java/tests/src/com/android/intentresolver/ResolverWrapperActivity.java227
-rw-r--r--java/tests/src/com/android/intentresolver/ResolverWrapperAdapter.java84
-rw-r--r--java/tests/src/com/android/intentresolver/ShortcutSelectionLogicTest.kt308
-rw-r--r--java/tests/src/com/android/intentresolver/TargetPresentationGetterTest.kt204
-rw-r--r--java/tests/src/com/android/intentresolver/TestApplication.kt27
-rw-r--r--java/tests/src/com/android/intentresolver/TestHelpers.kt71
-rw-r--r--java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java1598
-rw-r--r--java/tests/src/com/android/intentresolver/UnbundledChooserActivityWorkProfileTest.java467
-rw-r--r--java/tests/src/com/android/intentresolver/chooser/TargetInfoTest.kt192
-rw-r--r--java/tests/src/com/android/intentresolver/model/AbstractResolverComparatorTest.java107
-rw-r--r--java/tests/src/com/android/intentresolver/shortcuts/ShortcutLoaderTest.kt329
-rw-r--r--java/tests/src/com/android/intentresolver/shortcuts/ShortcutToChooserTargetConverterTest.kt177
21 files changed, 4450 insertions, 1260 deletions
diff --git a/java/tests/src/com/android/intentresolver/ChooserActivityLoggerFake.java b/java/tests/src/com/android/intentresolver/ChooserActivityLoggerFake.java
deleted file mode 100644
index e4146cc5..00000000
--- a/java/tests/src/com/android/intentresolver/ChooserActivityLoggerFake.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.intentresolver;
-
-import com.android.internal.logging.InstanceId;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.util.FrameworkStatsLog;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class ChooserActivityLoggerFake implements ChooserActivityLogger {
- static class CallRecord {
- // shared fields between all logs
- public int atomId;
- public String packageName;
- public InstanceId instanceId;
-
- // generic log field
- public UiEventLogger.UiEventEnum event;
-
- // share started fields
- public String mimeType;
- public int appProvidedDirect;
- public int appProvidedApp;
- public boolean isWorkprofile;
- public int previewType;
- public String intent;
-
- // share completed fields
- public int targetType;
- public int positionPicked;
- public boolean isPinned;
-
- CallRecord(int atomId, UiEventLogger.UiEventEnum eventId,
- String packageName, InstanceId instanceId) {
- this.atomId = atomId;
- this.packageName = packageName;
- this.instanceId = instanceId;
- this.event = eventId;
- }
-
- CallRecord(int atomId, String packageName, InstanceId instanceId, String mimeType,
- int appProvidedDirect, int appProvidedApp, boolean isWorkprofile, int previewType,
- String intent) {
- this.atomId = atomId;
- this.packageName = packageName;
- this.instanceId = instanceId;
- this.mimeType = mimeType;
- this.appProvidedDirect = appProvidedDirect;
- this.appProvidedApp = appProvidedApp;
- this.isWorkprofile = isWorkprofile;
- this.previewType = previewType;
- this.intent = intent;
- }
-
- CallRecord(int atomId, String packageName, InstanceId instanceId, int targetType,
- int positionPicked, boolean isPinned) {
- this.atomId = atomId;
- this.packageName = packageName;
- this.instanceId = instanceId;
- this.targetType = targetType;
- this.positionPicked = positionPicked;
- this.isPinned = isPinned;
- }
-
- }
- private List<CallRecord> mCalls = new ArrayList<>();
-
- public int numCalls() {
- return mCalls.size();
- }
-
- List<CallRecord> getCalls() {
- return mCalls;
- }
-
- CallRecord get(int index) {
- return mCalls.get(index);
- }
-
- UiEventLogger.UiEventEnum event(int index) {
- return mCalls.get(index).event;
- }
-
- public void removeCallsForUiEventsOfType(int uiEventType) {
- mCalls.removeIf(
- call ->
- (call.atomId == FrameworkStatsLog.UI_EVENT_REPORTED)
- && (call.event.getId() == uiEventType));
- }
-
- @Override
- public void logShareStarted(int eventId, String packageName, String mimeType,
- int appProvidedDirect, int appProvidedApp, boolean isWorkprofile, int previewType,
- String intent) {
- mCalls.add(new CallRecord(FrameworkStatsLog.SHARESHEET_STARTED, packageName,
- getInstanceId(), mimeType, appProvidedDirect, appProvidedApp, isWorkprofile,
- previewType, intent));
- }
-
- @Override
- public void logShareTargetSelected(int targetType, String packageName, int positionPicked,
- boolean isPinned) {
- mCalls.add(new CallRecord(FrameworkStatsLog.RANKING_SELECTED, packageName, getInstanceId(),
- SharesheetTargetSelectedEvent.fromTargetType(targetType).getId(), positionPicked,
- isPinned));
- }
-
- @Override
- public void log(UiEventLogger.UiEventEnum event, InstanceId instanceId) {
- mCalls.add(new CallRecord(FrameworkStatsLog.UI_EVENT_REPORTED,
- event, "", instanceId));
- }
-
- @Override
- public InstanceId getInstanceId() {
- return InstanceId.fakeInstanceId(-1);
- }
-}
diff --git a/java/tests/src/com/android/intentresolver/ChooserActivityLoggerTest.java b/java/tests/src/com/android/intentresolver/ChooserActivityLoggerTest.java
new file mode 100644
index 00000000..705a3228
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/ChooserActivityLoggerTest.java
@@ -0,0 +1,406 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.intentresolver;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.AdditionalMatchers.gt;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.content.Intent;
+import android.metrics.LogMaker;
+
+import com.android.intentresolver.ChooserActivityLogger.FrameworkStatsLogger;
+import com.android.intentresolver.ChooserActivityLogger.SharesheetStandardEvent;
+import com.android.intentresolver.ChooserActivityLogger.SharesheetStartedEvent;
+import com.android.intentresolver.ChooserActivityLogger.SharesheetTargetSelectedEvent;
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.logging.UiEventLogger.UiEventEnum;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.FrameworkStatsLog;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public final class ChooserActivityLoggerTest {
+ @Mock private UiEventLogger mUiEventLog;
+ @Mock private FrameworkStatsLogger mFrameworkLog;
+ @Mock private MetricsLogger mMetricsLogger;
+
+ private ChooserActivityLogger mChooserLogger;
+
+ @Before
+ public void setUp() {
+ //Mockito.reset(mUiEventLog, mFrameworkLog, mMetricsLogger);
+ mChooserLogger = new ChooserActivityLogger(mUiEventLog, mFrameworkLog, mMetricsLogger);
+ }
+
+ @After
+ public void tearDown() {
+ verifyNoMoreInteractions(mUiEventLog);
+ verifyNoMoreInteractions(mFrameworkLog);
+ verifyNoMoreInteractions(mMetricsLogger);
+ }
+
+ @Test
+ public void testLogChooserActivityShown_personalProfile() {
+ final boolean isWorkProfile = false;
+ final String mimeType = "application/TestType";
+ final long systemCost = 456;
+
+ mChooserLogger.logChooserActivityShown(isWorkProfile, mimeType, systemCost);
+
+ ArgumentCaptor<LogMaker> eventCaptor = ArgumentCaptor.forClass(LogMaker.class);
+ verify(mMetricsLogger).write(eventCaptor.capture());
+ LogMaker event = eventCaptor.getValue();
+
+ assertThat(event.getCategory()).isEqualTo(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN);
+ assertThat(event.getSubtype()).isEqualTo(MetricsEvent.PARENT_PROFILE);
+ assertThat(event.getTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE)).isEqualTo(mimeType);
+ assertThat(event.getTaggedData(MetricsEvent.FIELD_TIME_TO_APP_TARGETS))
+ .isEqualTo(systemCost);
+ }
+
+ @Test
+ public void testLogChooserActivityShown_workProfile() {
+ final boolean isWorkProfile = true;
+ final String mimeType = "application/TestType";
+ final long systemCost = 456;
+
+ mChooserLogger.logChooserActivityShown(isWorkProfile, mimeType, systemCost);
+
+ ArgumentCaptor<LogMaker> eventCaptor = ArgumentCaptor.forClass(LogMaker.class);
+ verify(mMetricsLogger).write(eventCaptor.capture());
+ LogMaker event = eventCaptor.getValue();
+
+ assertThat(event.getCategory()).isEqualTo(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN);
+ assertThat(event.getSubtype()).isEqualTo(MetricsEvent.MANAGED_PROFILE);
+ assertThat(event.getTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE)).isEqualTo(mimeType);
+ assertThat(event.getTaggedData(MetricsEvent.FIELD_TIME_TO_APP_TARGETS))
+ .isEqualTo(systemCost);
+ }
+
+ @Test
+ public void testLogShareStarted() {
+ final int eventId = -1; // Passed-in eventId is unused. TODO: remove from method signature.
+ final String packageName = "com.test.foo";
+ final String mimeType = "text/plain";
+ final int appProvidedDirectTargets = 123;
+ final int appProvidedAppTargets = 456;
+ final boolean workProfile = true;
+ final int previewType = ChooserContentPreviewUi.CONTENT_PREVIEW_FILE;
+ final String intentAction = Intent.ACTION_SENDTO;
+
+ mChooserLogger.logShareStarted(
+ eventId,
+ packageName,
+ mimeType,
+ appProvidedDirectTargets,
+ appProvidedAppTargets,
+ workProfile,
+ previewType,
+ intentAction);
+
+ verify(mFrameworkLog).write(
+ eq(FrameworkStatsLog.SHARESHEET_STARTED),
+ eq(SharesheetStartedEvent.SHARE_STARTED.getId()),
+ eq(packageName),
+ /* instanceId=*/ gt(0),
+ eq(mimeType),
+ eq(appProvidedDirectTargets),
+ eq(appProvidedAppTargets),
+ eq(workProfile),
+ eq(FrameworkStatsLog.SHARESHEET_STARTED__PREVIEW_TYPE__CONTENT_PREVIEW_FILE),
+ eq(FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_SENDTO));
+ }
+
+ @Test
+ public void testLogShareTargetSelected() {
+ final int targetType = ChooserActivityLogger.SELECTION_TYPE_SERVICE;
+ final String packageName = "com.test.foo";
+ final int positionPicked = 123;
+ final int directTargetAlsoRanked = -1;
+ final int callerTargetCount = 0;
+ final boolean isPinned = true;
+ final boolean isSuccessfullySelected = true;
+ final long selectionCost = 456;
+
+ mChooserLogger.logShareTargetSelected(
+ targetType,
+ packageName,
+ positionPicked,
+ directTargetAlsoRanked,
+ callerTargetCount,
+ /* directTargetHashed= */ null,
+ isPinned,
+ isSuccessfullySelected,
+ selectionCost);
+
+ verify(mFrameworkLog).write(
+ eq(FrameworkStatsLog.RANKING_SELECTED),
+ eq(SharesheetTargetSelectedEvent.SHARESHEET_SERVICE_TARGET_SELECTED.getId()),
+ eq(packageName),
+ /* instanceId=*/ gt(0),
+ eq(positionPicked),
+ eq(isPinned));
+
+ ArgumentCaptor<LogMaker> eventCaptor = ArgumentCaptor.forClass(LogMaker.class);
+ verify(mMetricsLogger).write(eventCaptor.capture());
+ LogMaker event = eventCaptor.getValue();
+ assertThat(event.getCategory()).isEqualTo(
+ MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET);
+ assertThat(event.getSubtype()).isEqualTo(positionPicked);
+ }
+
+ @Test
+ public void testLogActionSelected() {
+ mChooserLogger.logActionSelected(ChooserActivityLogger.SELECTION_TYPE_COPY);
+
+ verify(mFrameworkLog).write(
+ eq(FrameworkStatsLog.RANKING_SELECTED),
+ eq(SharesheetTargetSelectedEvent.SHARESHEET_COPY_TARGET_SELECTED.getId()),
+ eq(""),
+ /* instanceId=*/ gt(0),
+ eq(-1),
+ eq(false));
+
+ ArgumentCaptor<LogMaker> eventCaptor = ArgumentCaptor.forClass(LogMaker.class);
+ verify(mMetricsLogger).write(eventCaptor.capture());
+ LogMaker event = eventCaptor.getValue();
+ assertThat(event.getCategory()).isEqualTo(
+ MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SYSTEM_TARGET);
+ assertThat(event.getSubtype()).isEqualTo(1);
+ }
+
+ @Test
+ public void testLogDirectShareTargetReceived() {
+ final int category = MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER;
+ final int latency = 123;
+
+ mChooserLogger.logDirectShareTargetReceived(category, latency);
+
+ ArgumentCaptor<LogMaker> eventCaptor = ArgumentCaptor.forClass(LogMaker.class);
+ verify(mMetricsLogger).write(eventCaptor.capture());
+ LogMaker event = eventCaptor.getValue();
+ assertThat(event.getCategory()).isEqualTo(category);
+ assertThat(event.getSubtype()).isEqualTo(latency);
+ }
+
+ @Test
+ public void testLogActionShareWithPreview() {
+ final int previewType = ChooserContentPreviewUi.CONTENT_PREVIEW_TEXT;
+
+ mChooserLogger.logActionShareWithPreview(previewType);
+
+ ArgumentCaptor<LogMaker> eventCaptor = ArgumentCaptor.forClass(LogMaker.class);
+ verify(mMetricsLogger).write(eventCaptor.capture());
+ LogMaker event = eventCaptor.getValue();
+ assertThat(event.getCategory()).isEqualTo(MetricsEvent.ACTION_SHARE_WITH_PREVIEW);
+ assertThat(event.getSubtype()).isEqualTo(previewType);
+ }
+
+ @Test
+ public void testLogSharesheetTriggered() {
+ mChooserLogger.logSharesheetTriggered();
+ verify(mUiEventLog).logWithInstanceId(
+ eq(SharesheetStandardEvent.SHARESHEET_TRIGGERED), eq(0), isNull(), any());
+ }
+
+ @Test
+ public void testLogSharesheetAppLoadComplete() {
+ mChooserLogger.logSharesheetAppLoadComplete();
+ verify(mUiEventLog).logWithInstanceId(
+ eq(SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE), eq(0), isNull(), any());
+ }
+
+ @Test
+ public void testLogSharesheetDirectLoadComplete() {
+ mChooserLogger.logSharesheetDirectLoadComplete();
+ verify(mUiEventLog).logWithInstanceId(
+ eq(SharesheetStandardEvent.SHARESHEET_DIRECT_LOAD_COMPLETE),
+ eq(0),
+ isNull(),
+ any());
+ }
+
+ @Test
+ public void testLogSharesheetDirectLoadTimeout() {
+ mChooserLogger.logSharesheetDirectLoadTimeout();
+ verify(mUiEventLog).logWithInstanceId(
+ eq(SharesheetStandardEvent.SHARESHEET_DIRECT_LOAD_TIMEOUT), eq(0), isNull(), any());
+ }
+
+ @Test
+ public void testLogSharesheetProfileChanged() {
+ mChooserLogger.logSharesheetProfileChanged();
+ verify(mUiEventLog).logWithInstanceId(
+ eq(SharesheetStandardEvent.SHARESHEET_PROFILE_CHANGED), eq(0), isNull(), any());
+ }
+
+ @Test
+ public void testLogSharesheetExpansionChanged_collapsed() {
+ mChooserLogger.logSharesheetExpansionChanged(/* isCollapsed=*/ true);
+ verify(mUiEventLog).logWithInstanceId(
+ eq(SharesheetStandardEvent.SHARESHEET_COLLAPSED), eq(0), isNull(), any());
+ }
+
+ @Test
+ public void testLogSharesheetExpansionChanged_expanded() {
+ mChooserLogger.logSharesheetExpansionChanged(/* isCollapsed=*/ false);
+ verify(mUiEventLog).logWithInstanceId(
+ eq(SharesheetStandardEvent.SHARESHEET_EXPANDED), eq(0), isNull(), any());
+ }
+
+ @Test
+ public void testLogSharesheetAppShareRankingTimeout() {
+ mChooserLogger.logSharesheetAppShareRankingTimeout();
+ verify(mUiEventLog).logWithInstanceId(
+ eq(SharesheetStandardEvent.SHARESHEET_APP_SHARE_RANKING_TIMEOUT),
+ eq(0),
+ isNull(),
+ any());
+ }
+
+ @Test
+ public void testLogSharesheetEmptyDirectShareRow() {
+ mChooserLogger.logSharesheetEmptyDirectShareRow();
+ verify(mUiEventLog).logWithInstanceId(
+ eq(SharesheetStandardEvent.SHARESHEET_EMPTY_DIRECT_SHARE_ROW),
+ eq(0),
+ isNull(),
+ any());
+ }
+
+ @Test
+ public void testDifferentLoggerInstancesUseDifferentInstanceIds() {
+ ArgumentCaptor<Integer> idIntCaptor = ArgumentCaptor.forClass(Integer.class);
+ ChooserActivityLogger chooserLogger2 =
+ new ChooserActivityLogger(mUiEventLog, mFrameworkLog, mMetricsLogger);
+
+ final int targetType = ChooserActivityLogger.SELECTION_TYPE_COPY;
+ final String packageName = "com.test.foo";
+ final int positionPicked = 123;
+ final int directTargetAlsoRanked = -1;
+ final int callerTargetCount = 0;
+ final boolean isPinned = true;
+ final boolean isSuccessfullySelected = true;
+ final long selectionCost = 456;
+
+ mChooserLogger.logShareTargetSelected(
+ targetType,
+ packageName,
+ positionPicked,
+ directTargetAlsoRanked,
+ callerTargetCount,
+ /* directTargetHashed= */ null,
+ isPinned,
+ isSuccessfullySelected,
+ selectionCost);
+
+ chooserLogger2.logShareTargetSelected(
+ targetType,
+ packageName,
+ positionPicked,
+ directTargetAlsoRanked,
+ callerTargetCount,
+ /* directTargetHashed= */ null,
+ isPinned,
+ isSuccessfullySelected,
+ selectionCost);
+
+ verify(mFrameworkLog, times(2)).write(
+ anyInt(), anyInt(), anyString(), idIntCaptor.capture(), anyInt(), anyBoolean());
+
+ int id1 = idIntCaptor.getAllValues().get(0);
+ int id2 = idIntCaptor.getAllValues().get(1);
+
+ assertThat(id1).isGreaterThan(0);
+ assertThat(id2).isGreaterThan(0);
+ assertThat(id1).isNotEqualTo(id2);
+ }
+
+ @Test
+ public void testUiAndFrameworkEventsUseSameInstanceIdForSameLoggerInstance() {
+ ArgumentCaptor<Integer> idIntCaptor = ArgumentCaptor.forClass(Integer.class);
+ ArgumentCaptor<InstanceId> idObjectCaptor = ArgumentCaptor.forClass(InstanceId.class);
+
+ final int targetType = ChooserActivityLogger.SELECTION_TYPE_COPY;
+ final String packageName = "com.test.foo";
+ final int positionPicked = 123;
+ final int directTargetAlsoRanked = -1;
+ final int callerTargetCount = 0;
+ final boolean isPinned = true;
+ final boolean isSuccessfullySelected = true;
+ final long selectionCost = 456;
+
+ mChooserLogger.logShareTargetSelected(
+ targetType,
+ packageName,
+ positionPicked,
+ directTargetAlsoRanked,
+ callerTargetCount,
+ /* directTargetHashed= */ null,
+ isPinned,
+ isSuccessfullySelected,
+ selectionCost);
+
+ verify(mFrameworkLog).write(
+ anyInt(), anyInt(), anyString(), idIntCaptor.capture(), anyInt(), anyBoolean());
+
+ mChooserLogger.logSharesheetTriggered();
+ verify(mUiEventLog).logWithInstanceId(
+ any(UiEventEnum.class), anyInt(), any(), idObjectCaptor.capture());
+
+ assertThat(idIntCaptor.getValue()).isGreaterThan(0);
+ assertThat(idObjectCaptor.getValue().getId()).isEqualTo(idIntCaptor.getValue());
+ }
+
+ @Test
+ public void testTargetSelectionCategories() {
+ assertThat(ChooserActivityLogger.getTargetSelectionCategory(
+ ChooserActivityLogger.SELECTION_TYPE_SERVICE))
+ .isEqualTo(MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET);
+ assertThat(ChooserActivityLogger.getTargetSelectionCategory(
+ ChooserActivityLogger.SELECTION_TYPE_APP))
+ .isEqualTo(MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET);
+ assertThat(ChooserActivityLogger.getTargetSelectionCategory(
+ ChooserActivityLogger.SELECTION_TYPE_STANDARD))
+ .isEqualTo(MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET);
+ assertThat(ChooserActivityLogger.getTargetSelectionCategory(
+ ChooserActivityLogger.SELECTION_TYPE_COPY)).isEqualTo(0);
+ assertThat(ChooserActivityLogger.getTargetSelectionCategory(
+ ChooserActivityLogger.SELECTION_TYPE_NEARBY)).isEqualTo(0);
+ assertThat(ChooserActivityLogger.getTargetSelectionCategory(
+ ChooserActivityLogger.SELECTION_TYPE_EDIT)).isEqualTo(0);
+ }
+}
diff --git a/java/tests/src/com/android/intentresolver/ChooserActivityOverrideData.java b/java/tests/src/com/android/intentresolver/ChooserActivityOverrideData.java
index 080f1e41..5df0d4a2 100644
--- a/java/tests/src/com/android/intentresolver/ChooserActivityOverrideData.java
+++ b/java/tests/src/com/android/intentresolver/ChooserActivityOverrideData.java
@@ -16,21 +16,28 @@
package com.android.intentresolver;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
-import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.os.UserHandle;
+import com.android.intentresolver.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
+import com.android.intentresolver.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
+import com.android.intentresolver.AbstractMultiProfilePagerAdapter.QuietModeManager;
import com.android.intentresolver.chooser.TargetInfo;
-import com.android.internal.logging.MetricsLogger;
+import com.android.intentresolver.shortcuts.ShortcutLoader;
-import java.util.List;
+import java.util.function.Consumer;
import java.util.function.Function;
+import kotlin.jvm.functions.Function2;
+
/**
* Singleton providing overrides to be applied by any {@code IChooserWrapper} used in testing.
* We cannot directly mock the activity created since instrumentation creates it, so instead we use
@@ -49,7 +56,8 @@ public class ChooserActivityOverrideData {
@SuppressWarnings("Since15")
public Function<PackageManager, PackageManager> createPackageManager;
public Function<TargetInfo, Boolean> onSafelyStartCallback;
- public Function<ChooserListAdapter, Void> onQueryDirectShareTargets;
+ public Function2<UserHandle, Consumer<ShortcutLoader.Result>, ShortcutLoader>
+ shortcutLoaderFactory = (userHandle, callback) -> null;
public ResolverListController resolverListController;
public ResolverListController workResolverListController;
public Boolean isVoiceInteraction;
@@ -57,21 +65,20 @@ public class ChooserActivityOverrideData {
public Cursor resolverCursor;
public boolean resolverForceException;
public Bitmap previewThumbnail;
- public MetricsLogger metricsLogger;
public ChooserActivityLogger chooserActivityLogger;
public int alternateProfileSetting;
public Resources resources;
public UserHandle workProfileUserHandle;
public boolean hasCrossProfileIntents;
public boolean isQuietModeEnabled;
- public boolean isWorkProfileUserRunning;
- public boolean isWorkProfileUserUnlocked;
- public AbstractMultiProfilePagerAdapter.Injector multiPagerAdapterInjector;
+ public Integer myUserId;
+ public QuietModeManager mQuietModeManager;
+ public MyUserIdProvider mMyUserIdProvider;
+ public CrossProfileIntentsChecker mCrossProfileIntentsChecker;
public PackageManager packageManager;
public void reset() {
onSafelyStartCallback = null;
- onQueryDirectShareTargets = null;
isVoiceInteraction = null;
createPackageManager = null;
previewThumbnail = null;
@@ -80,23 +87,15 @@ public class ChooserActivityOverrideData {
resolverForceException = false;
resolverListController = mock(ResolverListController.class);
workResolverListController = mock(ResolverListController.class);
- metricsLogger = mock(MetricsLogger.class);
- chooserActivityLogger = new ChooserActivityLoggerFake();
+ chooserActivityLogger = mock(ChooserActivityLogger.class);
alternateProfileSetting = 0;
resources = null;
workProfileUserHandle = null;
hasCrossProfileIntents = true;
isQuietModeEnabled = false;
- isWorkProfileUserRunning = true;
- isWorkProfileUserUnlocked = true;
+ myUserId = null;
packageManager = null;
- multiPagerAdapterInjector = new AbstractMultiProfilePagerAdapter.Injector() {
- @Override
- public boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId,
- int targetUserId) {
- return hasCrossProfileIntents;
- }
-
+ mQuietModeManager = new QuietModeManager() {
@Override
public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
return isQuietModeEnabled;
@@ -107,7 +106,28 @@ public class ChooserActivityOverrideData {
UserHandle workProfileUserHandle) {
isQuietModeEnabled = enabled;
}
+
+ @Override
+ public void markWorkProfileEnabledBroadcastReceived() {
+ }
+
+ @Override
+ public boolean isWaitingToEnableWorkProfile() {
+ return false;
+ }
};
+ shortcutLoaderFactory = ((userHandle, resultConsumer) -> null);
+
+ mMyUserIdProvider = new MyUserIdProvider() {
+ @Override
+ public int getMyUserId() {
+ return myUserId != null ? myUserId : UserHandle.myUserId();
+ }
+ };
+
+ mCrossProfileIntentsChecker = mock(CrossProfileIntentsChecker.class);
+ when(mCrossProfileIntentsChecker.hasCrossProfileIntents(any(), anyInt(), anyInt()))
+ .thenAnswer(invocation -> hasCrossProfileIntents);
}
private ChooserActivityOverrideData() {}
diff --git a/java/tests/src/com/android/intentresolver/ChooserListAdapterTest.kt b/java/tests/src/com/android/intentresolver/ChooserListAdapterTest.kt
new file mode 100644
index 00000000..58f6b733
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/ChooserListAdapterTest.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.intentresolver
+
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ResolveInfoFlags
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.intentresolver.ChooserListAdapter.LoadDirectShareIconTask
+import com.android.intentresolver.chooser.DisplayResolveInfo
+import com.android.intentresolver.chooser.SelectableTargetInfo
+import com.android.intentresolver.chooser.TargetInfo
+import com.android.internal.R
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidJUnit4::class)
+class ChooserListAdapterTest {
+ private val packageManager = mock<PackageManager> {
+ whenever(
+ resolveActivity(any(), any<ResolveInfoFlags>())
+ ).thenReturn(mock())
+ }
+ private val context = InstrumentationRegistry.getInstrumentation().getContext()
+ private val resolverListController = mock<ResolverListController>()
+ private val chooserActivityLogger = mock<ChooserActivityLogger>()
+
+ private fun createChooserListAdapter(
+ taskProvider: (TargetInfo?) -> LoadDirectShareIconTask
+ ) = object : ChooserListAdapter(
+ context,
+ emptyList(),
+ emptyArray(),
+ emptyList(),
+ false,
+ resolverListController,
+ null,
+ Intent(),
+ mock(),
+ packageManager,
+ chooserActivityLogger,
+ mock(),
+ 0
+ ) {
+ override fun createLoadDirectShareIconTask(
+ info: SelectableTargetInfo
+ ): LoadDirectShareIconTask = taskProvider(info)
+ }
+
+ @Before
+ fun setup() {
+ // ChooserListAdapter reads DeviceConfig and needs a permission for that.
+ InstrumentationRegistry
+ .getInstrumentation()
+ .getUiAutomation()
+ .adoptShellPermissionIdentity("android.permission.READ_DEVICE_CONFIG")
+ }
+
+ @Test
+ fun testDirectShareTargetLoadingIconIsStarted() {
+ val view = createView()
+ val viewHolder = ResolverListAdapter.ViewHolder(view)
+ view.tag = viewHolder
+ val targetInfo = createSelectableTargetInfo()
+ val iconTask = mock<LoadDirectShareIconTask>()
+ val testSubject = createChooserListAdapter { iconTask }
+ testSubject.onBindView(view, targetInfo, 0)
+
+ verify(iconTask, times(1)).loadIcon()
+ }
+
+ @Test
+ fun testOnlyOneTaskPerTarget() {
+ val view = createView()
+ val viewHolderOne = ResolverListAdapter.ViewHolder(view)
+ view.tag = viewHolderOne
+ val targetInfo = createSelectableTargetInfo()
+ val iconTaskOne = mock<LoadDirectShareIconTask>()
+ val testTaskProvider = mock<() -> LoadDirectShareIconTask> {
+ whenever(invoke()).thenReturn(iconTaskOne)
+ }
+ val testSubject = createChooserListAdapter { testTaskProvider.invoke() }
+ testSubject.onBindView(view, targetInfo, 0)
+
+ val viewHolderTwo = ResolverListAdapter.ViewHolder(view)
+ view.tag = viewHolderTwo
+ whenever(testTaskProvider()).thenReturn(mock())
+
+ testSubject.onBindView(view, targetInfo, 0)
+
+ verify(iconTaskOne, times(1)).loadIcon()
+ verify(testTaskProvider, times(1)).invoke()
+ }
+
+ private fun createSelectableTargetInfo(): TargetInfo =
+ SelectableTargetInfo.newSelectableTargetInfo(
+ /* sourceInfo = */ DisplayResolveInfo.newDisplayResolveInfo(
+ Intent(),
+ ResolverDataProvider.createResolveInfo(2, 0),
+ "label",
+ "extended info",
+ Intent(),
+ /* resolveInfoPresentationGetter= */ null
+ ),
+ /* backupResolveInfo = */ mock(),
+ /* resolvedIntent = */ Intent(),
+ /* chooserTarget = */ createChooserTarget(
+ "Target", 0.5f, ComponentName("pkg", "Class"), "id-1"
+ ),
+ /* modifiedScore = */ 1f,
+ /* shortcutInfo = */ createShortcutInfo("id-1", ComponentName("pkg", "Class"), 1),
+ /* appTarget */ null,
+ /* referrerFillInIntent = */ Intent()
+ )
+
+ private fun createView(): View {
+ val view = FrameLayout(context)
+ TextView(context).apply {
+ id = R.id.text1
+ view.addView(this)
+ }
+ TextView(context).apply {
+ id = R.id.text2
+ view.addView(this)
+ }
+ ImageView(context).apply {
+ id = R.id.icon
+ view.addView(this)
+ }
+ return view
+ }
+}
diff --git a/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java b/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java
index 0e9f010e..97de97f5 100644
--- a/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java
+++ b/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java
@@ -19,11 +19,13 @@ package com.android.intentresolver;
import static org.mockito.Mockito.when;
import android.annotation.Nullable;
+import android.app.prediction.AppPredictor;
import android.app.usage.UsageStatsManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
@@ -33,19 +35,18 @@ import android.net.Uri;
import android.os.UserHandle;
import android.util.Size;
-import com.android.intentresolver.AbstractMultiProfilePagerAdapter;
-import com.android.intentresolver.ChooserActivityLogger;
-import com.android.intentresolver.ChooserActivityOverrideData;
-import com.android.intentresolver.ChooserListAdapter;
-import com.android.intentresolver.IChooserWrapper;
-import com.android.intentresolver.ResolverListAdapter.ResolveInfoPresentationGetter;
-import com.android.intentresolver.ResolverListController;
+import com.android.intentresolver.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
+import com.android.intentresolver.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
+import com.android.intentresolver.AbstractMultiProfilePagerAdapter.QuietModeManager;
import com.android.intentresolver.chooser.DisplayResolveInfo;
+import com.android.intentresolver.chooser.NotSelectableTargetInfo;
import com.android.intentresolver.chooser.TargetInfo;
-import com.android.internal.logging.MetricsLogger;
+import com.android.intentresolver.grid.ChooserGridAdapter;
+import com.android.intentresolver.shortcuts.ShortcutLoader;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import java.util.List;
+import java.util.function.Consumer;
/**
* Simple wrapper around chooser activity to be able to initiate it under test. For more
@@ -64,25 +65,34 @@ public class ChooserWrapperActivity
}
@Override
- protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter(
- Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed) {
- AbstractMultiProfilePagerAdapter multiProfilePagerAdapter =
- super.createMultiProfilePagerAdapter(initialIntents, rList, filterLastUsed);
- multiProfilePagerAdapter.setInjector(sOverrides.multiPagerAdapterInjector);
- return multiProfilePagerAdapter;
- }
-
- @Override
- public ChooserListAdapter createChooserListAdapter(Context context, List<Intent> payloadIntents,
- Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed,
- ResolverListController resolverListController) {
+ public ChooserListAdapter createChooserListAdapter(
+ Context context,
+ List<Intent> payloadIntents,
+ Intent[] initialIntents,
+ List<ResolveInfo> rList,
+ boolean filterLastUsed,
+ ResolverListController resolverListController,
+ UserHandle userHandle,
+ Intent targetIntent,
+ ChooserRequestParameters chooserRequest,
+ int maxTargetsPerRow) {
PackageManager packageManager =
sOverrides.packageManager == null ? context.getPackageManager()
: sOverrides.packageManager;
- return new ChooserListAdapter(context, payloadIntents, initialIntents, rList,
- filterLastUsed, resolverListController,
- this, this, packageManager,
- getChooserActivityLogger());
+ return new ChooserListAdapter(
+ context,
+ payloadIntents,
+ initialIntents,
+ rList,
+ filterLastUsed,
+ resolverListController,
+ userHandle,
+ targetIntent,
+ this,
+ packageManager,
+ getChooserActivityLogger(),
+ chooserRequest,
+ maxTargetsPerRow);
}
@Override
@@ -119,7 +129,7 @@ public class ChooserWrapperActivity
@Override
protected TargetInfo getNearbySharingTarget(Intent originalIntent) {
- return new ChooserWrapperActivity.EmptyTargetInfo();
+ return NotSelectableTargetInfo.newEmptyTargetInfo();
}
@Override
@@ -139,6 +149,30 @@ public class ChooserWrapperActivity
}
@Override
+ protected MyUserIdProvider createMyUserIdProvider() {
+ if (sOverrides.mMyUserIdProvider != null) {
+ return sOverrides.mMyUserIdProvider;
+ }
+ return super.createMyUserIdProvider();
+ }
+
+ @Override
+ protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
+ if (sOverrides.mCrossProfileIntentsChecker != null) {
+ return sOverrides.mCrossProfileIntentsChecker;
+ }
+ return super.createCrossProfileIntentsChecker();
+ }
+
+ @Override
+ protected QuietModeManager createQuietModeManager() {
+ if (sOverrides.mQuietModeManager != null) {
+ return sOverrides.mQuietModeManager;
+ }
+ return super.createQuietModeManager();
+ }
+
+ @Override
public void safelyStartActivity(com.android.intentresolver.chooser.TargetInfo cti) {
if (sOverrides.onSafelyStartCallback != null
&& sOverrides.onSafelyStartCallback.apply(cti)) {
@@ -187,11 +221,6 @@ public class ChooserWrapperActivity
}
@Override
- protected MetricsLogger getMetricsLogger() {
- return sOverrides.metricsLogger;
- }
-
- @Override
public ChooserActivityLogger getChooserActivityLogger() {
return sOverrides.chooserActivityLogger;
}
@@ -220,8 +249,13 @@ public class ChooserWrapperActivity
@Override
public DisplayResolveInfo createTestDisplayResolveInfo(Intent originalIntent, ResolveInfo pri,
CharSequence pLabel, CharSequence pInfo, Intent replacementIntent,
- @Nullable ResolveInfoPresentationGetter resolveInfoPresentationGetter) {
- return new DisplayResolveInfo(originalIntent, pri, pLabel, pInfo, replacementIntent,
+ @Nullable TargetPresentationGetter resolveInfoPresentationGetter) {
+ return DisplayResolveInfo.newDisplayResolveInfo(
+ originalIntent,
+ pri,
+ pLabel,
+ pInfo,
+ replacementIntent,
resolveInfoPresentationGetter);
}
@@ -242,32 +276,18 @@ public class ChooserWrapperActivity
}
@Override
- protected void queryDirectShareTargets(ChooserListAdapter adapter,
- boolean skipAppPredictionService) {
- if (sOverrides.onQueryDirectShareTargets != null) {
- sOverrides.onQueryDirectShareTargets.apply(adapter);
- }
- super.queryDirectShareTargets(adapter, skipAppPredictionService);
- }
-
- @Override
- protected boolean isQuietModeEnabled(UserHandle userHandle) {
- return sOverrides.isQuietModeEnabled;
- }
-
- @Override
- protected boolean isUserRunning(UserHandle userHandle) {
- if (userHandle.equals(UserHandle.SYSTEM)) {
- return super.isUserRunning(userHandle);
- }
- return sOverrides.isWorkProfileUserRunning;
- }
-
- @Override
- protected boolean isUserUnlocked(UserHandle userHandle) {
- if (userHandle.equals(UserHandle.SYSTEM)) {
- return super.isUserUnlocked(userHandle);
+ protected ShortcutLoader createShortcutLoader(
+ Context context,
+ AppPredictor appPredictor,
+ UserHandle userHandle,
+ IntentFilter targetIntentFilter,
+ Consumer<ShortcutLoader.Result> callback) {
+ ShortcutLoader shortcutLoader =
+ sOverrides.shortcutLoaderFactory.invoke(userHandle, callback);
+ if (shortcutLoader != null) {
+ return shortcutLoader;
}
- return sOverrides.isWorkProfileUserUnlocked;
+ return super.createShortcutLoader(
+ context, appPredictor, userHandle, targetIntentFilter, callback);
}
}
diff --git a/java/tests/src/com/android/intentresolver/IChooserWrapper.java b/java/tests/src/com/android/intentresolver/IChooserWrapper.java
index f81cd023..af897a47 100644
--- a/java/tests/src/com/android/intentresolver/IChooserWrapper.java
+++ b/java/tests/src/com/android/intentresolver/IChooserWrapper.java
@@ -22,9 +22,10 @@ import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.os.UserHandle;
-import com.android.intentresolver.ResolverListAdapter.ResolveInfoPresentationGetter;
import com.android.intentresolver.chooser.DisplayResolveInfo;
+import java.util.concurrent.Executor;
+
/**
* Test-only extended API capabilities that an instrumented ChooserActivity subclass provides in
* order to expose the internals for override/inspection. Implementations should apply the overrides
@@ -38,7 +39,8 @@ public interface IChooserWrapper {
UsageStatsManager getUsageStatsManager();
DisplayResolveInfo createTestDisplayResolveInfo(Intent originalIntent, ResolveInfo pri,
CharSequence pLabel, CharSequence pInfo, Intent replacementIntent,
- @Nullable ResolveInfoPresentationGetter resolveInfoPresentationGetter);
+ @Nullable TargetPresentationGetter resolveInfoPresentationGetter);
UserHandle getCurrentUserHandle();
ChooserActivityLogger getChooserActivityLogger();
+ Executor getMainExecutor();
}
diff --git a/java/tests/src/com/android/intentresolver/MockitoKotlinHelpers.kt b/java/tests/src/com/android/intentresolver/MockitoKotlinHelpers.kt
new file mode 100644
index 00000000..159c6d6a
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/MockitoKotlinHelpers.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.intentresolver
+
+/**
+ * Kotlin versions of popular mockito methods that can return null in situations when Kotlin expects
+ * a non-null value. Kotlin will throw an IllegalStateException when this takes place ("x must not
+ * be null"). To fix this, we can use methods that modify the return type to be nullable. This
+ * causes Kotlin to skip the null checks.
+ * Cloned from frameworks/base/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
+ */
+
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatcher
+import org.mockito.Mockito
+import org.mockito.stubbing.OngoingStubbing
+
+/**
+ * Returns Mockito.eq() as nullable type to avoid java.lang.IllegalStateException when
+ * null is returned.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+fun <T> eq(obj: T): T = Mockito.eq<T>(obj)
+
+/**
+ * Returns Mockito.any() as nullable type to avoid java.lang.IllegalStateException when
+ * null is returned.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
+inline fun <reified T> any(): T = any(T::class.java)
+
+/**
+ * Returns Mockito.argThat() as nullable type to avoid java.lang.IllegalStateException when
+ * null is returned.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+fun <T> argThat(matcher: ArgumentMatcher<T>): T = Mockito.argThat(matcher)
+
+/**
+ * Kotlin type-inferred version of Mockito.nullable()
+ */
+inline fun <reified T> nullable(): T? = Mockito.nullable(T::class.java)
+
+/**
+ * Returns ArgumentCaptor.capture() as nullable type to avoid java.lang.IllegalStateException
+ * when null is returned.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+
+/**
+ * Helper function for creating an argumentCaptor in kotlin.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+inline fun <reified T : Any> argumentCaptor(): ArgumentCaptor<T> =
+ ArgumentCaptor.forClass(T::class.java)
+
+/**
+ * Helper function for creating new mocks, without the need to pass in a [Class] instance.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ *
+ * @param apply builder function to simplify stub configuration by improving type inference.
+ */
+inline fun <reified T : Any> mock(apply: T.() -> Unit = {}): T = Mockito.mock(T::class.java)
+ .apply(apply)
+
+/**
+ * Helper function for stubbing methods without the need to use backticks.
+ *
+ * @see Mockito.when
+ */
+fun <T> whenever(methodCall: T): OngoingStubbing<T> = Mockito.`when`(methodCall)
+
+/**
+ * A kotlin implemented wrapper of [ArgumentCaptor] which prevents the following exception when
+ * kotlin tests are mocking kotlin objects and the methods take non-null parameters:
+ *
+ * java.lang.NullPointerException: capture() must not be null
+ */
+class KotlinArgumentCaptor<T> constructor(clazz: Class<T>) {
+ private val wrapped: ArgumentCaptor<T> = ArgumentCaptor.forClass(clazz)
+ fun capture(): T = wrapped.capture()
+ val value: T
+ get() = wrapped.value
+ val allValues: List<T>
+ get() = wrapped.allValues
+}
+
+/**
+ * Helper function for creating an argumentCaptor in kotlin.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+inline fun <reified T : Any> kotlinArgumentCaptor(): KotlinArgumentCaptor<T> =
+ KotlinArgumentCaptor(T::class.java)
+
+/**
+ * Helper function for creating and using a single-use ArgumentCaptor in kotlin.
+ *
+ * val captor = argumentCaptor<Foo>()
+ * verify(...).someMethod(captor.capture())
+ * val captured = captor.value
+ *
+ * becomes:
+ *
+ * val captured = withArgCaptor<Foo> { verify(...).someMethod(capture()) }
+ *
+ * NOTE: this uses the KotlinArgumentCaptor to avoid the NullPointerException.
+ */
+inline fun <reified T : Any> withArgCaptor(block: KotlinArgumentCaptor<T>.() -> Unit): T =
+ kotlinArgumentCaptor<T>().apply { block() }.value
+
+/**
+ * Variant of [withArgCaptor] for capturing multiple arguments.
+ *
+ * val captor = argumentCaptor<Foo>()
+ * verify(...).someMethod(captor.capture())
+ * val captured: List<Foo> = captor.allValues
+ *
+ * becomes:
+ *
+ * val capturedList = captureMany<Foo> { verify(...).someMethod(capture()) }
+ */
+inline fun <reified T : Any> captureMany(block: KotlinArgumentCaptor<T>.() -> Unit): List<T> =
+ kotlinArgumentCaptor<T>().apply{ block() }.allValues
diff --git a/java/tests/src/com/android/intentresolver/ResolverActivityTest.java b/java/tests/src/com/android/intentresolver/ResolverActivityTest.java
new file mode 100644
index 00000000..62c16ff5
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/ResolverActivityTest.java
@@ -0,0 +1,858 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.intentresolver;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.action.ViewActions.swipeUp;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.isEnabled;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
+import static com.android.intentresolver.MatcherUtils.first;
+import static com.android.intentresolver.ResolverWrapperActivity.sOverrides;
+
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.fail;
+
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.espresso.Espresso;
+import androidx.test.espresso.NoMatchingViewException;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.intentresolver.ResolverActivity.ResolvedComponentInfo;
+import com.android.intentresolver.widget.ResolverDrawerLayout;
+import com.android.internal.R;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Resolver activity instrumentation tests
+ */
+@RunWith(AndroidJUnit4.class)
+public class ResolverActivityTest {
+ protected Intent getConcreteIntentForLaunch(Intent clientIntent) {
+ clientIntent.setClass(
+ androidx.test.platform.app.InstrumentationRegistry.getInstrumentation().getTargetContext(),
+ ResolverWrapperActivity.class);
+ return clientIntent;
+ }
+
+ @Rule
+ public ActivityTestRule<ResolverWrapperActivity> mActivityRule =
+ new ActivityTestRule<>(ResolverWrapperActivity.class, false, false);
+
+ @Before
+ public void setup() {
+ // TODO: use the other form of `adoptShellPermissionIdentity()` where we explicitly list the
+ // permissions we require (which we'll read from the manifest at runtime).
+ androidx.test.platform.app.InstrumentationRegistry
+ .getInstrumentation()
+ .getUiAutomation()
+ .adoptShellPermissionIdentity();
+
+ sOverrides.reset();
+ }
+
+ @Test
+ public void twoOptionsAndUserSelectsOne() throws InterruptedException {
+ Intent sendIntent = createSendImageIntent();
+ List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ Espresso.registerIdlingResources(activity.getAdapter().getLabelIdlingResource());
+ waitForIdle();
+
+ assertThat(activity.getAdapter().getCount(), is(2));
+
+ ResolveInfo[] chosen = new ResolveInfo[1];
+ sOverrides.onSafelyStartCallback = targetInfo -> {
+ chosen[0] = targetInfo.getResolveInfo();
+ return true;
+ };
+
+ ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
+ onView(withText(toChoose.activityInfo.name))
+ .perform(click());
+ onView(withId(R.id.button_once))
+ .perform(click());
+ waitForIdle();
+ assertThat(chosen[0], is(toChoose));
+ }
+
+ @Ignore // Failing - b/144929805
+ @Test
+ public void setMaxHeight() throws Exception {
+ Intent sendIntent = createSendImageIntent();
+ List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+ waitForIdle();
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ final View viewPager = activity.findViewById(R.id.profile_pager);
+ final int initialResolverHeight = viewPager.getHeight();
+
+ activity.runOnUiThread(() -> {
+ ResolverDrawerLayout layout = (ResolverDrawerLayout)
+ activity.findViewById(
+ R.id.contentPanel);
+ ((ResolverDrawerLayout.LayoutParams) viewPager.getLayoutParams()).maxHeight
+ = initialResolverHeight - 1;
+ // Force a relayout
+ layout.invalidate();
+ layout.requestLayout();
+ });
+ waitForIdle();
+ assertThat("Drawer should be capped at maxHeight",
+ viewPager.getHeight() == (initialResolverHeight - 1));
+
+ activity.runOnUiThread(() -> {
+ ResolverDrawerLayout layout = (ResolverDrawerLayout)
+ activity.findViewById(
+ R.id.contentPanel);
+ ((ResolverDrawerLayout.LayoutParams) viewPager.getLayoutParams()).maxHeight
+ = initialResolverHeight + 1;
+ // Force a relayout
+ layout.invalidate();
+ layout.requestLayout();
+ });
+ waitForIdle();
+ assertThat("Drawer should not change height if its height is less than maxHeight",
+ viewPager.getHeight() == initialResolverHeight);
+ }
+
+ @Ignore // Failing - b/144929805
+ @Test
+ public void setShowAtTopToTrue() throws Exception {
+ Intent sendIntent = createSendImageIntent();
+ List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+ waitForIdle();
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ final View viewPager = activity.findViewById(R.id.profile_pager);
+ final View divider = activity.findViewById(R.id.divider);
+ final RelativeLayout profileView =
+ (RelativeLayout) activity.findViewById(R.id.profile_button).getParent();
+ assertThat("Drawer should show at bottom by default",
+ profileView.getBottom() + divider.getHeight() == viewPager.getTop()
+ && profileView.getTop() > 0);
+
+ activity.runOnUiThread(() -> {
+ ResolverDrawerLayout layout = (ResolverDrawerLayout)
+ activity.findViewById(
+ R.id.contentPanel);
+ layout.setShowAtTop(true);
+ });
+ waitForIdle();
+ assertThat("Drawer should show at top with new attribute",
+ profileView.getBottom() + divider.getHeight() == viewPager.getTop()
+ && profileView.getTop() == 0);
+ }
+
+ @Test
+ public void hasLastChosenActivity() throws Exception {
+ Intent sendIntent = createSendImageIntent();
+ List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+ ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
+
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+ when(sOverrides.resolverListController.getLastChosen())
+ .thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0));
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+
+ // The other entry is filtered to the last used slot
+ assertThat(activity.getAdapter().getCount(), is(1));
+ assertThat(activity.getAdapter().getPlaceholderCount(), is(1));
+
+ ResolveInfo[] chosen = new ResolveInfo[1];
+ sOverrides.onSafelyStartCallback = targetInfo -> {
+ chosen[0] = targetInfo.getResolveInfo();
+ return true;
+ };
+
+ onView(withId(R.id.button_once)).perform(click());
+ waitForIdle();
+ assertThat(chosen[0], is(toChoose));
+ }
+
+ @Test
+ public void hasOtherProfileOneOption() throws Exception {
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ markWorkProfileUserAvailable();
+
+ ResolveInfo toChoose = personalResolvedComponentInfos.get(1).getResolveInfoAt(0);
+ Intent sendIntent = createSendImageIntent();
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ Espresso.registerIdlingResources(activity.getAdapter().getLabelIdlingResource());
+ waitForIdle();
+
+ // The other entry is filtered to the last used slot
+ assertThat(activity.getAdapter().getCount(), is(1));
+
+ ResolveInfo[] chosen = new ResolveInfo[1];
+ sOverrides.onSafelyStartCallback = targetInfo -> {
+ chosen[0] = targetInfo.getResolveInfo();
+ return true;
+ };
+ // Make a stable copy of the components as the original list may be modified
+ List<ResolvedComponentInfo> stableCopy =
+ createResolvedComponentsForTestWithOtherProfile(2, /* userId= */ 10);
+ // We pick the first one as there is another one in the work profile side
+ onView(first(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)))
+ .perform(click());
+ onView(withId(R.id.button_once))
+ .perform(click());
+ waitForIdle();
+ assertThat(chosen[0], is(toChoose));
+ }
+
+ @Test
+ public void hasOtherProfileTwoOptionsAndUserSelectsOne() throws Exception {
+ Intent sendIntent = createSendImageIntent();
+ List<ResolvedComponentInfo> resolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3);
+ ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
+
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ Espresso.registerIdlingResources(activity.getAdapter().getLabelIdlingResource());
+ waitForIdle();
+
+ // The other entry is filtered to the other profile slot
+ assertThat(activity.getAdapter().getCount(), is(2));
+
+ ResolveInfo[] chosen = new ResolveInfo[1];
+ sOverrides.onSafelyStartCallback = targetInfo -> {
+ chosen[0] = targetInfo.getResolveInfo();
+ return true;
+ };
+
+ // Confirm that the button bar is disabled by default
+ onView(withId(R.id.button_once)).check(matches(not(isEnabled())));
+
+ // Make a stable copy of the components as the original list may be modified
+ List<ResolvedComponentInfo> stableCopy =
+ createResolvedComponentsForTestWithOtherProfile(2);
+
+ onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
+ .perform(click());
+ onView(withId(R.id.button_once)).perform(click());
+ waitForIdle();
+ assertThat(chosen[0], is(toChoose));
+ }
+
+
+ @Test
+ public void hasLastChosenActivityAndOtherProfile() throws Exception {
+ // In this case we prefer the other profile and don't display anything about the last
+ // chosen activity.
+ Intent sendIntent = createSendImageIntent();
+ List<ResolvedComponentInfo> resolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3);
+ ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
+
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+ when(sOverrides.resolverListController.getLastChosen())
+ .thenReturn(resolvedComponentInfos.get(1).getResolveInfoAt(0));
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ Espresso.registerIdlingResources(activity.getAdapter().getLabelIdlingResource());
+ waitForIdle();
+
+ // The other entry is filtered to the other profile slot
+ assertThat(activity.getAdapter().getCount(), is(2));
+
+ ResolveInfo[] chosen = new ResolveInfo[1];
+ sOverrides.onSafelyStartCallback = targetInfo -> {
+ chosen[0] = targetInfo.getResolveInfo();
+ return true;
+ };
+
+ // Confirm that the button bar is disabled by default
+ onView(withId(R.id.button_once)).check(matches(not(isEnabled())));
+
+ // Make a stable copy of the components as the original list may be modified
+ List<ResolvedComponentInfo> stableCopy =
+ createResolvedComponentsForTestWithOtherProfile(2);
+
+ onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
+ .perform(click());
+ onView(withId(R.id.button_once)).perform(click());
+ waitForIdle();
+ assertThat(chosen[0], is(toChoose));
+ }
+
+ @Test
+ public void testWorkTab_displayedWhenWorkProfileUserAvailable() {
+ Intent sendIntent = createSendImageIntent();
+ markWorkProfileUserAvailable();
+
+ mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+
+ onView(withId(R.id.tabs)).check(matches(isDisplayed()));
+ }
+
+ @Test
+ public void testWorkTab_hiddenWhenWorkProfileUserNotAvailable() {
+ Intent sendIntent = createSendImageIntent();
+
+ mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+
+ onView(withId(R.id.tabs)).check(matches(not(isDisplayed())));
+ }
+
+ @Test
+ public void testWorkTab_workTabListPopulatedBeforeGoingToTab() throws InterruptedException {
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3, /* userId = */ 10);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ setupResolverControllers(personalResolvedComponentInfos,
+ new ArrayList<>(workResolvedComponentInfos));
+ Intent sendIntent = createSendImageIntent();
+ markWorkProfileUserAvailable();
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+
+ assertThat(activity.getCurrentUserHandle().getIdentifier(), is(0));
+ // The work list adapter must be populated in advance before tapping the other tab
+ assertThat(activity.getWorkListAdapter().getCount(), is(4));
+ }
+
+ @Test
+ public void testWorkTab_workTabUsesExpectedAdapter() {
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+ markWorkProfileUserAvailable();
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ onView(withText(R.string.resolver_work_tab)).perform(click());
+
+ assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10));
+ assertThat(activity.getWorkListAdapter().getCount(), is(4));
+ }
+
+ @Test
+ public void testWorkTab_personalTabUsesExpectedAdapter() {
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+ markWorkProfileUserAvailable();
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ onView(withText(R.string.resolver_work_tab)).perform(click());
+
+ assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10));
+ assertThat(activity.getPersonalListAdapter().getCount(), is(2));
+ }
+
+ @Test
+ public void testWorkTab_workProfileHasExpectedNumberOfTargets() throws InterruptedException {
+ markWorkProfileUserAvailable();
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+
+ onView(withText(R.string.resolver_work_tab))
+ .perform(click());
+ waitForIdle();
+ assertThat(activity.getWorkListAdapter().getCount(), is(4));
+ }
+
+ @Test
+ public void testWorkTab_selectingWorkTabAppOpensAppInWorkProfile() throws InterruptedException {
+ markWorkProfileUserAvailable();
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+ ResolveInfo[] chosen = new ResolveInfo[1];
+ sOverrides.onSafelyStartCallback = targetInfo -> {
+ chosen[0] = targetInfo.getResolveInfo();
+ return true;
+ };
+
+ mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ onView(withText(R.string.resolver_work_tab))
+ .perform(click());
+ waitForIdle();
+ onView(first(allOf(withText(workResolvedComponentInfos.get(0)
+ .getResolveInfoAt(0).activityInfo.applicationInfo.name), isCompletelyDisplayed())))
+ .perform(click());
+ onView(withId(R.id.button_once))
+ .perform(click());
+
+ waitForIdle();
+ assertThat(chosen[0], is(workResolvedComponentInfos.get(0).getResolveInfoAt(0)));
+ }
+
+ @Test
+ public void testWorkTab_noPersonalApps_workTabHasExpectedNumberOfTargets()
+ throws InterruptedException {
+ markWorkProfileUserAvailable();
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(1);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ onView(withText(R.string.resolver_work_tab))
+ .perform(click());
+
+ waitForIdle();
+ assertThat(activity.getWorkListAdapter().getCount(), is(4));
+ }
+
+ @Test
+ public void testWorkTab_headerIsVisibleInPersonalTab() {
+ markWorkProfileUserAvailable();
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(1);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createOpenWebsiteIntent();
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ TextView headerText = activity.findViewById(R.id.title);
+ String initialText = headerText.getText().toString();
+ assertFalse(initialText.isEmpty(), "Header text is empty.");
+ assertThat(headerText.getVisibility(), is(View.VISIBLE));
+ }
+
+ @Test
+ public void testWorkTab_switchTabs_headerStaysSame() {
+ markWorkProfileUserAvailable();
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(1);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createOpenWebsiteIntent();
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ TextView headerText = activity.findViewById(R.id.title);
+ String initialText = headerText.getText().toString();
+ onView(withText(R.string.resolver_work_tab))
+ .perform(click());
+
+ waitForIdle();
+ String currentText = headerText.getText().toString();
+ assertThat(headerText.getVisibility(), is(View.VISIBLE));
+ assertThat(String.format("Header text is not the same when switching tabs, personal profile"
+ + " header was %s but work profile header is %s", initialText, currentText),
+ TextUtils.equals(initialText, currentText));
+ }
+
+ @Test
+ public void testWorkTab_noPersonalApps_canStartWorkApps()
+ throws InterruptedException {
+ markWorkProfileUserAvailable();
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3, /* userId= */ 10);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+ ResolveInfo[] chosen = new ResolveInfo[1];
+ sOverrides.onSafelyStartCallback = targetInfo -> {
+ chosen[0] = targetInfo.getResolveInfo();
+ return true;
+ };
+
+ mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ onView(withText(R.string.resolver_work_tab))
+ .perform(click());
+ waitForIdle();
+ onView(first(allOf(
+ withText(workResolvedComponentInfos.get(0)
+ .getResolveInfoAt(0).activityInfo.applicationInfo.name),
+ isDisplayed())))
+ .perform(click());
+ onView(withId(R.id.button_once))
+ .perform(click());
+ waitForIdle();
+
+ assertThat(chosen[0], is(workResolvedComponentInfos.get(0).getResolveInfoAt(0)));
+ }
+
+ @Test
+ public void testWorkTab_crossProfileIntentsDisabled_personalToWork_emptyStateShown() {
+ markWorkProfileUserAvailable();
+ int workProfileTargets = 4;
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
+ List<ResolvedComponentInfo> workResolvedComponentInfos =
+ createResolvedComponentsForTest(workProfileTargets);
+ sOverrides.hasCrossProfileIntents = false;
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+ sendIntent.setType("TestType");
+
+ mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ onView(withText(R.string.resolver_work_tab)).perform(click());
+ waitForIdle();
+ onView(withId(R.id.contentPanel))
+ .perform(swipeUp());
+
+ onView(withText(R.string.resolver_cross_profile_blocked))
+ .check(matches(isDisplayed()));
+ }
+
+ @Test
+ public void testWorkTab_workProfileDisabled_emptyStateShown() {
+ markWorkProfileUserAvailable();
+ int workProfileTargets = 4;
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
+ List<ResolvedComponentInfo> workResolvedComponentInfos =
+ createResolvedComponentsForTest(workProfileTargets);
+ sOverrides.isQuietModeEnabled = true;
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+ sendIntent.setType("TestType");
+
+ mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ onView(withId(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() {
+ markWorkProfileUserAvailable();
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTest(3);
+ List<ResolvedComponentInfo> workResolvedComponentInfos =
+ createResolvedComponentsForTest(0);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+ sendIntent.setType("TestType");
+
+ mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ onView(withId(R.id.contentPanel))
+ .perform(swipeUp());
+ onView(withText(R.string.resolver_work_tab)).perform(click());
+ waitForIdle();
+
+ onView(withText(R.string.resolver_no_work_apps_available))
+ .check(matches(isDisplayed()));
+ }
+
+ @Test
+ public void testWorkTab_xProfileOff_noAppsAvailable_workOff_xProfileOffEmptyStateShown() {
+ markWorkProfileUserAvailable();
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTest(3);
+ List<ResolvedComponentInfo> workResolvedComponentInfos =
+ createResolvedComponentsForTest(0);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+ sendIntent.setType("TestType");
+ sOverrides.isQuietModeEnabled = true;
+ sOverrides.hasCrossProfileIntents = false;
+
+ mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ onView(withId(R.id.contentPanel))
+ .perform(swipeUp());
+ onView(withText(R.string.resolver_work_tab)).perform(click());
+ waitForIdle();
+
+ onView(withText(R.string.resolver_cross_profile_blocked))
+ .check(matches(isDisplayed()));
+ }
+
+ @Test
+ public void testMiniResolver() {
+ markWorkProfileUserAvailable();
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTest(1);
+ List<ResolvedComponentInfo> workResolvedComponentInfos =
+ createResolvedComponentsForTest(1);
+ // Personal profile only has a browser
+ personalResolvedComponentInfos.get(0).getResolveInfoAt(0).handleAllWebDataURI = true;
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+ sendIntent.setType("TestType");
+
+ mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ onView(withId(R.id.open_cross_profile)).check(matches(isDisplayed()));
+ }
+
+ @Test
+ public void testMiniResolver_noCurrentProfileTarget() {
+ markWorkProfileUserAvailable();
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTest(0);
+ List<ResolvedComponentInfo> workResolvedComponentInfos =
+ createResolvedComponentsForTest(1);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+ sendIntent.setType("TestType");
+
+ mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+
+ // Need to ensure mini resolver doesn't trigger here.
+ assertNotMiniResolver();
+ }
+
+ private void assertNotMiniResolver() {
+ try {
+ onView(withId(R.id.open_cross_profile)).check(matches(isDisplayed()));
+ } catch (NoMatchingViewException e) {
+ return;
+ }
+ fail("Mini resolver present but shouldn't be");
+ }
+
+ @Test
+ public void testWorkTab_noAppsAvailable_workOff_noAppsAvailableEmptyStateShown() {
+ markWorkProfileUserAvailable();
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTest(3);
+ List<ResolvedComponentInfo> workResolvedComponentInfos =
+ createResolvedComponentsForTest(0);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+ sendIntent.setType("TestType");
+ sOverrides.isQuietModeEnabled = true;
+
+ mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ onView(withId(R.id.contentPanel))
+ .perform(swipeUp());
+ onView(withText(R.string.resolver_work_tab)).perform(click());
+ waitForIdle();
+
+ onView(withText(R.string.resolver_no_work_apps_available))
+ .check(matches(isDisplayed()));
+ }
+
+ @Test
+ public void testWorkTab_onePersonalTarget_emptyStateOnWorkTarget_autolaunch() {
+ markWorkProfileUserAvailable();
+ int workProfileTargets = 4;
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10);
+ List<ResolvedComponentInfo> workResolvedComponentInfos =
+ createResolvedComponentsForTest(workProfileTargets);
+ sOverrides.hasCrossProfileIntents = false;
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+ sendIntent.setType("TestType");
+ ResolveInfo[] chosen = new ResolveInfo[1];
+ sOverrides.onSafelyStartCallback = targetInfo -> {
+ chosen[0] = targetInfo.getResolveInfo();
+ return true;
+ };
+
+ mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+
+ assertThat(chosen[0], is(personalResolvedComponentInfos.get(1).getResolveInfoAt(0)));
+ }
+
+ @Test
+ public void testLayoutWithDefault_withWorkTab_neverShown() throws RemoteException {
+ markWorkProfileUserAvailable();
+
+ // In this case we prefer the other profile and don't display anything about the last
+ // chosen activity.
+ Intent sendIntent = createSendImageIntent();
+ List<ResolvedComponentInfo> resolvedComponentInfos =
+ createResolvedComponentsForTest(2);
+
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+ when(sOverrides.resolverListController.getLastChosen())
+ .thenReturn(resolvedComponentInfos.get(1).getResolveInfoAt(0));
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ Espresso.registerIdlingResources(activity.getAdapter().getLabelIdlingResource());
+ waitForIdle();
+
+ // The other entry is filtered to the last used slot
+ assertThat(activity.getAdapter().hasFilteredItem(), is(false));
+ assertThat(activity.getAdapter().getCount(), is(2));
+ assertThat(activity.getAdapter().getPlaceholderCount(), is(2));
+ }
+
+ private Intent createSendImageIntent() {
+ Intent sendIntent = new Intent();
+ sendIntent.setAction(Intent.ACTION_SEND);
+ sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending");
+ sendIntent.setType("image/jpeg");
+ return sendIntent;
+ }
+
+ private Intent createOpenWebsiteIntent() {
+ Intent sendIntent = new Intent();
+ sendIntent.setAction(Intent.ACTION_VIEW);
+ sendIntent.setData(Uri.parse("https://google.com"));
+ return sendIntent;
+ }
+
+ private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults) {
+ List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+ for (int i = 0; i < numberOfResults; i++) {
+ infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+ }
+ return infoList;
+ }
+
+ private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
+ int numberOfResults) {
+ List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+ for (int i = 0; i < numberOfResults; i++) {
+ if (i == 0) {
+ infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i));
+ } else {
+ infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+ }
+ }
+ return infoList;
+ }
+
+ private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
+ int numberOfResults, int userId) {
+ List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+ for (int i = 0; i < numberOfResults; i++) {
+ if (i == 0) {
+ infoList.add(
+ ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId));
+ } else {
+ infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+ }
+ }
+ return infoList;
+ }
+
+ private void waitForIdle() {
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+
+ private void markWorkProfileUserAvailable() {
+ ResolverWrapperActivity.sOverrides.workProfileUserHandle = UserHandle.of(10);
+ }
+
+ private void setupResolverControllers(
+ List<ResolvedComponentInfo> personalResolvedComponentInfos,
+ List<ResolvedComponentInfo> workResolvedComponentInfos) {
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class)))
+ .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+ when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+ when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class),
+ eq(UserHandle.SYSTEM)))
+ .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+ }
+}
diff --git a/java/tests/src/com/android/intentresolver/ResolverDataProvider.java b/java/tests/src/com/android/intentresolver/ResolverDataProvider.java
index 33e7123f..fb928e09 100644
--- a/java/tests/src/com/android/intentresolver/ResolverDataProvider.java
+++ b/java/tests/src/com/android/intentresolver/ResolverDataProvider.java
@@ -32,7 +32,7 @@ import android.test.mock.MockResources;
/**
* Utility class used by resolver tests to create mock data
*/
-class ResolverDataProvider {
+public class ResolverDataProvider {
static private int USER_SOMEONE_ELSE = 10;
@@ -52,12 +52,12 @@ class ResolverDataProvider {
createResolverIntent(i), createResolveInfo(i, userId));
}
- static ComponentName createComponentName(int i) {
+ public static ComponentName createComponentName(int i) {
final String name = "component" + i;
return new ComponentName("foo.bar." + name, name);
}
- static ResolveInfo createResolveInfo(int i, int userId) {
+ public static ResolveInfo createResolveInfo(int i, int userId) {
final ResolveInfo resolveInfo = new ResolveInfo();
resolveInfo.activityInfo = createActivityInfo(i);
resolveInfo.targetUserId = userId;
@@ -93,11 +93,17 @@ class ResolverDataProvider {
public String setResolveInfoLabel;
}
+ /** Create a {@link PackageManagerMockedInfo} with all distinct labels. */
static PackageManagerMockedInfo createPackageManagerMockedInfo(boolean hasOverridePermission) {
- final String appLabel = "app_label";
- final String activityLabel = "activity_label";
- final String resolveInfoLabel = "resolve_info_label";
+ return createPackageManagerMockedInfo(
+ hasOverridePermission, "app_label", "activity_label", "resolve_info_label");
+ }
+ static PackageManagerMockedInfo createPackageManagerMockedInfo(
+ boolean hasOverridePermission,
+ String appLabel,
+ String activityLabel,
+ String resolveInfoLabel) {
MockContext ctx = new MockContext() {
@Override
public PackageManager getPackageManager() {
diff --git a/java/tests/src/com/android/intentresolver/ResolverWrapperActivity.java b/java/tests/src/com/android/intentresolver/ResolverWrapperActivity.java
new file mode 100644
index 00000000..239bffe0
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/ResolverWrapperActivity.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.intentresolver;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.usage.UsageStatsManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+import com.android.intentresolver.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
+import com.android.intentresolver.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
+import com.android.intentresolver.AbstractMultiProfilePagerAdapter.QuietModeManager;
+import com.android.intentresolver.chooser.TargetInfo;
+
+import java.util.List;
+import java.util.function.Function;
+
+/*
+ * Simple wrapper around chooser activity to be able to initiate it under test
+ */
+public class ResolverWrapperActivity extends ResolverActivity {
+ static final OverrideData sOverrides = new OverrideData();
+ private UsageStatsManager mUsm;
+
+ public ResolverWrapperActivity() {
+ super(/* isIntentPicker= */ true);
+ }
+
+ // ResolverActivity inspects the launched-from UID at onCreate and needs to see some
+ // non-negative value in the test.
+ @Override
+ public int getLaunchedFromUid() {
+ return 1234;
+ }
+
+ @Override
+ public ResolverListAdapter createResolverListAdapter(Context context,
+ List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList,
+ boolean filterLastUsed, UserHandle userHandle) {
+ return new ResolverWrapperAdapter(
+ context,
+ payloadIntents,
+ initialIntents,
+ rList,
+ filterLastUsed,
+ createListController(userHandle),
+ userHandle,
+ payloadIntents.get(0), // TODO: extract upstream
+ this);
+ }
+
+ @Override
+ protected MyUserIdProvider createMyUserIdProvider() {
+ if (sOverrides.mMyUserIdProvider != null) {
+ return sOverrides.mMyUserIdProvider;
+ }
+ return super.createMyUserIdProvider();
+ }
+
+ @Override
+ protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
+ if (sOverrides.mCrossProfileIntentsChecker != null) {
+ return sOverrides.mCrossProfileIntentsChecker;
+ }
+ return super.createCrossProfileIntentsChecker();
+ }
+
+ @Override
+ protected QuietModeManager createQuietModeManager() {
+ if (sOverrides.mQuietModeManager != null) {
+ return sOverrides.mQuietModeManager;
+ }
+ return super.createQuietModeManager();
+ }
+
+ ResolverWrapperAdapter getAdapter() {
+ return (ResolverWrapperAdapter) mMultiProfilePagerAdapter.getActiveListAdapter();
+ }
+
+ ResolverListAdapter getPersonalListAdapter() {
+ return ((ResolverListAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(0));
+ }
+
+ ResolverListAdapter getWorkListAdapter() {
+ if (mMultiProfilePagerAdapter.getInactiveListAdapter() == null) {
+ return null;
+ }
+ return ((ResolverListAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(1));
+ }
+
+ @Override
+ public boolean isVoiceInteraction() {
+ if (sOverrides.isVoiceInteraction != null) {
+ return sOverrides.isVoiceInteraction;
+ }
+ return super.isVoiceInteraction();
+ }
+
+ @Override
+ public void safelyStartActivity(TargetInfo cti) {
+ if (sOverrides.onSafelyStartCallback != null &&
+ sOverrides.onSafelyStartCallback.apply(cti)) {
+ return;
+ }
+ super.safelyStartActivity(cti);
+ }
+
+ @Override
+ protected ResolverListController createListController(UserHandle userHandle) {
+ if (userHandle == UserHandle.SYSTEM) {
+ when(sOverrides.resolverListController.getUserHandle()).thenReturn(UserHandle.SYSTEM);
+ return sOverrides.resolverListController;
+ }
+ when(sOverrides.workResolverListController.getUserHandle()).thenReturn(userHandle);
+ return sOverrides.workResolverListController;
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ if (sOverrides.createPackageManager != null) {
+ return sOverrides.createPackageManager.apply(super.getPackageManager());
+ }
+ return super.getPackageManager();
+ }
+
+ protected UserHandle getCurrentUserHandle() {
+ return mMultiProfilePagerAdapter.getCurrentUserHandle();
+ }
+
+ @Override
+ protected UserHandle getWorkProfileUserHandle() {
+ return sOverrides.workProfileUserHandle;
+ }
+
+ @Override
+ public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) {
+ super.startActivityAsUser(intent, options, user);
+ }
+
+ /**
+ * We cannot directly mock the activity created since instrumentation creates it.
+ * <p>
+ * Instead, we use static instances of this object to modify behavior.
+ */
+ static class OverrideData {
+ @SuppressWarnings("Since15")
+ public Function<PackageManager, PackageManager> createPackageManager;
+ public Function<TargetInfo, Boolean> onSafelyStartCallback;
+ public ResolverListController resolverListController;
+ public ResolverListController workResolverListController;
+ public Boolean isVoiceInteraction;
+ public UserHandle workProfileUserHandle;
+ public Integer myUserId;
+ public boolean hasCrossProfileIntents;
+ public boolean isQuietModeEnabled;
+ public QuietModeManager mQuietModeManager;
+ public MyUserIdProvider mMyUserIdProvider;
+ public CrossProfileIntentsChecker mCrossProfileIntentsChecker;
+
+ public void reset() {
+ onSafelyStartCallback = null;
+ isVoiceInteraction = null;
+ createPackageManager = null;
+ resolverListController = mock(ResolverListController.class);
+ workResolverListController = mock(ResolverListController.class);
+ workProfileUserHandle = null;
+ myUserId = null;
+ hasCrossProfileIntents = true;
+ isQuietModeEnabled = false;
+
+ mQuietModeManager = new QuietModeManager() {
+ @Override
+ public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
+ return isQuietModeEnabled;
+ }
+
+ @Override
+ public void requestQuietModeEnabled(boolean enabled,
+ UserHandle workProfileUserHandle) {
+ isQuietModeEnabled = enabled;
+ }
+
+ @Override
+ public void markWorkProfileEnabledBroadcastReceived() {
+ }
+
+ @Override
+ public boolean isWaitingToEnableWorkProfile() {
+ return false;
+ }
+ };
+
+ mMyUserIdProvider = new MyUserIdProvider() {
+ @Override
+ public int getMyUserId() {
+ return myUserId != null ? myUserId : UserHandle.myUserId();
+ }
+ };
+
+ mCrossProfileIntentsChecker = mock(CrossProfileIntentsChecker.class);
+ when(mCrossProfileIntentsChecker.hasCrossProfileIntents(any(), anyInt(), anyInt()))
+ .thenAnswer(invocation -> hasCrossProfileIntents);
+ }
+ }
+}
diff --git a/java/tests/src/com/android/intentresolver/ResolverWrapperAdapter.java b/java/tests/src/com/android/intentresolver/ResolverWrapperAdapter.java
new file mode 100644
index 00000000..a53b41d1
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/ResolverWrapperAdapter.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.intentresolver;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.os.UserHandle;
+
+import androidx.test.espresso.idling.CountingIdlingResource;
+
+import com.android.intentresolver.chooser.DisplayResolveInfo;
+
+import java.util.List;
+
+public class ResolverWrapperAdapter extends ResolverListAdapter {
+
+ private CountingIdlingResource mLabelIdlingResource =
+ new CountingIdlingResource("LoadLabelTask");
+
+ public ResolverWrapperAdapter(
+ Context context,
+ List<Intent> payloadIntents,
+ Intent[] initialIntents,
+ List<ResolveInfo> rList,
+ boolean filterLastUsed,
+ ResolverListController resolverListController,
+ UserHandle userHandle,
+ Intent targetIntent,
+ ResolverListCommunicator resolverListCommunicator) {
+ super(
+ context,
+ payloadIntents,
+ initialIntents,
+ rList,
+ filterLastUsed,
+ resolverListController,
+ userHandle,
+ targetIntent,
+ resolverListCommunicator,
+ false);
+ }
+
+ public CountingIdlingResource getLabelIdlingResource() {
+ return mLabelIdlingResource;
+ }
+
+ @Override
+ protected LoadLabelTask createLoadLabelTask(DisplayResolveInfo info) {
+ return new LoadLabelWrapperTask(info);
+ }
+
+ class LoadLabelWrapperTask extends LoadLabelTask {
+
+ protected LoadLabelWrapperTask(DisplayResolveInfo dri) {
+ super(dri);
+ }
+
+ @Override
+ protected void onPreExecute() {
+ mLabelIdlingResource.increment();
+ }
+
+ @Override
+ protected void onPostExecute(CharSequence[] result) {
+ super.onPostExecute(result);
+ mLabelIdlingResource.decrement();
+ }
+ }
+}
diff --git a/java/tests/src/com/android/intentresolver/ShortcutSelectionLogicTest.kt b/java/tests/src/com/android/intentresolver/ShortcutSelectionLogicTest.kt
new file mode 100644
index 00000000..a8d6f978
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/ShortcutSelectionLogicTest.kt
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.intentresolver
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ResolveInfo
+import android.content.pm.ShortcutInfo
+import android.service.chooser.ChooserTarget
+import com.android.intentresolver.chooser.DisplayResolveInfo
+import com.android.intentresolver.chooser.TargetInfo
+import androidx.test.filters.SmallTest
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+private const val PACKAGE_A = "package.a"
+private const val PACKAGE_B = "package.b"
+private const val CLASS_NAME = "./MainActivity"
+
+@SmallTest
+class ShortcutSelectionLogicTest {
+ private val packageTargets = HashMap<String, Array<ChooserTarget>>().apply {
+ arrayOf(PACKAGE_A, PACKAGE_B).forEach { pkg ->
+ // shortcuts in reverse priority order
+ val targets = Array(3) { i ->
+ createChooserTarget(
+ "Shortcut $i",
+ (i + 1).toFloat() / 10f,
+ ComponentName(pkg, CLASS_NAME),
+ pkg.shortcutId(i),
+ )
+ }
+ this[pkg] = targets
+ }
+ }
+
+ private val baseDisplayInfo = DisplayResolveInfo.newDisplayResolveInfo(
+ Intent(),
+ ResolverDataProvider.createResolveInfo(3, 0),
+ "label",
+ "extended info",
+ Intent(),
+ /* resolveInfoPresentationGetter= */ null)
+
+ private val otherBaseDisplayInfo = DisplayResolveInfo.newDisplayResolveInfo(
+ Intent(),
+ ResolverDataProvider.createResolveInfo(4, 0),
+ "label 2",
+ "extended info 2",
+ Intent(),
+ /* resolveInfoPresentationGetter= */ null)
+
+ private operator fun Map<String, Array<ChooserTarget>>.get(pkg: String, idx: Int) =
+ this[pkg]?.get(idx) ?: error("missing package $pkg")
+
+ @Test
+ fun testAddShortcuts_no_limits() {
+ val serviceResults = ArrayList<TargetInfo>()
+ val sc1 = packageTargets[PACKAGE_A, 0]
+ val sc2 = packageTargets[PACKAGE_A, 1]
+ val testSubject = ShortcutSelectionLogic(
+ /* maxShortcutTargetsPerApp = */ 1,
+ /* applySharingAppLimits = */ false
+ )
+
+ val isUpdated = testSubject.addServiceResults(
+ /* origTarget = */ baseDisplayInfo,
+ /* origTargetScore = */ 0.1f,
+ /* targets = */ listOf(sc1, sc2),
+ /* isShortcutResult = */ true,
+ /* directShareToShortcutInfos = */ emptyMap(),
+ /* directShareToAppTargets = */ emptyMap(),
+ /* userContext = */ mock(),
+ /* targetIntent = */ mock(),
+ /* refererFillInIntent = */ mock(),
+ /* maxRankedTargets = */ 4,
+ /* serviceTargets = */ serviceResults
+ )
+
+ assertTrue("Updates are expected", isUpdated)
+ assertShortcutsInOrder(
+ listOf(sc2, sc1),
+ serviceResults,
+ "Two shortcuts are expected as we do not apply per-app shortcut limit"
+ )
+ }
+
+ @Test
+ fun testAddShortcuts_same_package_with_per_package_limit() {
+ val serviceResults = ArrayList<TargetInfo>()
+ val sc1 = packageTargets[PACKAGE_A, 0]
+ val sc2 = packageTargets[PACKAGE_A, 1]
+ val testSubject = ShortcutSelectionLogic(
+ /* maxShortcutTargetsPerApp = */ 1,
+ /* applySharingAppLimits = */ true
+ )
+
+ val isUpdated = testSubject.addServiceResults(
+ /* origTarget = */ baseDisplayInfo,
+ /* origTargetScore = */ 0.1f,
+ /* targets = */ listOf(sc1, sc2),
+ /* isShortcutResult = */ true,
+ /* directShareToShortcutInfos = */ emptyMap(),
+ /* directShareToAppTargets = */ emptyMap(),
+ /* userContext = */ mock(),
+ /* targetIntent = */ mock(),
+ /* refererFillInIntent = */ mock(),
+ /* maxRankedTargets = */ 4,
+ /* serviceTargets = */ serviceResults
+ )
+
+ assertTrue("Updates are expected", isUpdated)
+ assertShortcutsInOrder(
+ listOf(sc2),
+ serviceResults,
+ "One shortcut is expected as we apply per-app shortcut limit"
+ )
+ }
+
+ @Test
+ fun testAddShortcuts_same_package_no_per_app_limit_with_target_limit() {
+ val serviceResults = ArrayList<TargetInfo>()
+ val sc1 = packageTargets[PACKAGE_A, 0]
+ val sc2 = packageTargets[PACKAGE_A, 1]
+ val testSubject = ShortcutSelectionLogic(
+ /* maxShortcutTargetsPerApp = */ 1,
+ /* applySharingAppLimits = */ false
+ )
+
+ val isUpdated = testSubject.addServiceResults(
+ /* origTarget = */ baseDisplayInfo,
+ /* origTargetScore = */ 0.1f,
+ /* targets = */ listOf(sc1, sc2),
+ /* isShortcutResult = */ true,
+ /* directShareToShortcutInfos = */ emptyMap(),
+ /* directShareToAppTargets = */ emptyMap(),
+ /* userContext = */ mock(),
+ /* targetIntent = */ mock(),
+ /* refererFillInIntent = */ mock(),
+ /* maxRankedTargets = */ 1,
+ /* serviceTargets = */ serviceResults
+ )
+
+ assertTrue("Updates are expected", isUpdated)
+ assertShortcutsInOrder(
+ listOf(sc2),
+ serviceResults,
+ "One shortcut is expected as we apply overall shortcut limit"
+ )
+ }
+
+ @Test
+ fun testAddShortcuts_different_packages_with_per_package_limit() {
+ val serviceResults = ArrayList<TargetInfo>()
+ val pkgAsc1 = packageTargets[PACKAGE_A, 0]
+ val pkgAsc2 = packageTargets[PACKAGE_A, 1]
+ val pkgBsc1 = packageTargets[PACKAGE_B, 0]
+ val pkgBsc2 = packageTargets[PACKAGE_B, 1]
+ val testSubject = ShortcutSelectionLogic(
+ /* maxShortcutTargetsPerApp = */ 1,
+ /* applySharingAppLimits = */ true
+ )
+
+ testSubject.addServiceResults(
+ /* origTarget = */ baseDisplayInfo,
+ /* origTargetScore = */ 0.1f,
+ /* targets = */ listOf(pkgAsc1, pkgAsc2),
+ /* isShortcutResult = */ true,
+ /* directShareToShortcutInfos = */ emptyMap(),
+ /* directShareToAppTargets = */ emptyMap(),
+ /* userContext = */ mock(),
+ /* targetIntent = */ mock(),
+ /* refererFillInIntent = */ mock(),
+ /* maxRankedTargets = */ 4,
+ /* serviceTargets = */ serviceResults
+ )
+ testSubject.addServiceResults(
+ /* origTarget = */ otherBaseDisplayInfo,
+ /* origTargetScore = */ 0.2f,
+ /* targets = */ listOf(pkgBsc1, pkgBsc2),
+ /* isShortcutResult = */ true,
+ /* directShareToShortcutInfos = */ emptyMap(),
+ /* directShareToAppTargets = */ emptyMap(),
+ /* userContext = */ mock(),
+ /* targetIntent = */ mock(),
+ /* refererFillInIntent = */ mock(),
+ /* maxRankedTargets = */ 4,
+ /* serviceTargets = */ serviceResults
+ )
+
+ assertShortcutsInOrder(
+ listOf(pkgBsc2, pkgAsc2),
+ serviceResults,
+ "Two shortcuts are expected as we apply per-app shortcut limit"
+ )
+ }
+
+ @Test
+ fun testAddShortcuts_pinned_shortcut() {
+ val serviceResults = ArrayList<TargetInfo>()
+ val sc1 = packageTargets[PACKAGE_A, 0]
+ val sc2 = packageTargets[PACKAGE_A, 1]
+ val testSubject = ShortcutSelectionLogic(
+ /* maxShortcutTargetsPerApp = */ 1,
+ /* applySharingAppLimits = */ false
+ )
+
+ val isUpdated = testSubject.addServiceResults(
+ /* origTarget = */ baseDisplayInfo,
+ /* origTargetScore = */ 0.1f,
+ /* targets = */ listOf(sc1, sc2),
+ /* isShortcutResult = */ true,
+ /* directShareToShortcutInfos = */ mapOf(
+ sc1 to createShortcutInfo(
+ PACKAGE_A.shortcutId(1),
+ sc1.componentName, 1).apply {
+ addFlags(ShortcutInfo.FLAG_PINNED)
+ }
+ ),
+ /* directShareToAppTargets = */ emptyMap(),
+ /* userContext = */ mock(),
+ /* targetIntent = */ mock(),
+ /* refererFillInIntent = */ mock(),
+ /* maxRankedTargets = */ 4,
+ /* serviceTargets = */ serviceResults
+ )
+
+ assertTrue("Updates are expected", isUpdated)
+ assertShortcutsInOrder(
+ listOf(sc1, sc2),
+ serviceResults,
+ "Two shortcuts are expected as we do not apply per-app shortcut limit"
+ )
+ }
+
+ @Test
+ fun test_available_caller_shortcuts_count_is_limited() {
+ val serviceResults = ArrayList<TargetInfo>()
+ val sc1 = packageTargets[PACKAGE_A, 0]
+ val sc2 = packageTargets[PACKAGE_A, 1]
+ val sc3 = packageTargets[PACKAGE_A, 2]
+ val testSubject = ShortcutSelectionLogic(
+ /* maxShortcutTargetsPerApp = */ 1,
+ /* applySharingAppLimits = */ true
+ )
+ val context = mock<Context> {
+ whenever(packageManager).thenReturn(mock())
+ }
+
+ testSubject.addServiceResults(
+ /* origTarget = */ baseDisplayInfo,
+ /* origTargetScore = */ 0f,
+ /* targets = */ listOf(sc1, sc2, sc3),
+ /* isShortcutResult = */ false,
+ /* directShareToShortcutInfos = */ emptyMap(),
+ /* directShareToAppTargets = */ emptyMap(),
+ /* userContext = */ context,
+ /* targetIntent = */ mock(),
+ /* refererFillInIntent = */ mock(),
+ /* maxRankedTargets = */ 4,
+ /* serviceTargets = */ serviceResults
+ )
+
+ assertShortcutsInOrder(
+ listOf(sc3, sc2),
+ serviceResults,
+ "At most two caller-provided shortcuts are allowed"
+ )
+ }
+
+ // TODO: consider renaming. Not all `ChooserTarget`s are "shortcuts" and many of our test cases
+ // add results with `isShortcutResult = false` and `directShareToShortcutInfos = emptyMap()`.
+ private fun assertShortcutsInOrder(
+ expected: List<ChooserTarget>, actual: List<TargetInfo>, msg: String? = ""
+ ) {
+ assertEquals(msg, expected.size, actual.size)
+ for (i in expected.indices) {
+ assertEquals(
+ "Unexpected item at position $i",
+ expected[i].componentName,
+ actual[i].chooserTargetComponentName
+ )
+ assertEquals(
+ "Unexpected item at position $i",
+ expected[i].title,
+ actual[i].displayLabel
+ )
+ }
+ }
+
+ private fun String.shortcutId(id: Int) = "$this.$id"
+}
diff --git a/java/tests/src/com/android/intentresolver/TargetPresentationGetterTest.kt b/java/tests/src/com/android/intentresolver/TargetPresentationGetterTest.kt
new file mode 100644
index 00000000..e62672a3
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/TargetPresentationGetterTest.kt
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.intentresolver
+
+import com.android.intentresolver.ResolverDataProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+/**
+ * Unit tests for the various implementations of {@link TargetPresentationGetter}.
+ * TODO: consider expanding to cover icon logic (not just labels/sublabels).
+ * TODO: these are conceptually "acceptance tests" that provide comprehensive coverage of the
+ * apparent variations in the legacy implementation. The tests probably don't have to be so
+ * exhaustive if we're able to impose a simpler design on the implementation.
+ */
+class TargetPresentationGetterTest {
+ fun makeResolveInfoPresentationGetter(
+ withSubstitutePermission: Boolean,
+ appLabel: String,
+ activityLabel: String,
+ resolveInfoLabel: String): TargetPresentationGetter {
+ val testPackageInfo = ResolverDataProvider.createPackageManagerMockedInfo(
+ withSubstitutePermission, appLabel, activityLabel, resolveInfoLabel)
+ val factory = TargetPresentationGetter.Factory(testPackageInfo.ctx, 100)
+ return factory.makePresentationGetter(testPackageInfo.resolveInfo)
+ }
+
+ fun makeActivityInfoPresentationGetter(
+ withSubstitutePermission: Boolean,
+ appLabel: String?,
+ activityLabel: String?): TargetPresentationGetter {
+ val testPackageInfo = ResolverDataProvider.createPackageManagerMockedInfo(
+ withSubstitutePermission, appLabel, activityLabel, "")
+ val factory = TargetPresentationGetter.Factory(testPackageInfo.ctx, 100)
+ return factory.makePresentationGetter(testPackageInfo.activityInfo)
+ }
+
+ @Test
+ fun testActivityInfoLabels_noSubstitutePermission_distinctRequestedLabelAndSublabel() {
+ val presentationGetter = makeActivityInfoPresentationGetter(
+ false, "app_label", "activity_label")
+ assertThat(presentationGetter.getLabel()).isEqualTo("app_label")
+ assertThat(presentationGetter.getSubLabel()).isEqualTo("activity_label")
+ }
+
+ @Test
+ fun testActivityInfoLabels_noSubstitutePermission_sameRequestedLabelAndSublabel() {
+ val presentationGetter = makeActivityInfoPresentationGetter(
+ false, "app_label", "app_label")
+ assertThat(presentationGetter.getLabel()).isEqualTo("app_label")
+ // Without the substitute permission, there's no logic to dedupe the labels.
+ // TODO: this matches our observations in the legacy code, but is it the right behavior? It
+ // seems like {@link ResolverListAdapter.ViewHolder#bindLabel()} has some logic to dedupe in
+ // the UI at least, but maybe that logic should be pulled back to the "presentation"?
+ assertThat(presentationGetter.getSubLabel()).isEqualTo("app_label")
+ }
+
+ @Test
+ fun testActivityInfoLabels_noSubstitutePermission_nullRequestedLabel() {
+ val presentationGetter = makeActivityInfoPresentationGetter(false, null, "activity_label")
+ assertThat(presentationGetter.getLabel()).isNull()
+ assertThat(presentationGetter.getSubLabel()).isEqualTo("activity_label")
+ }
+
+ @Test
+ fun testActivityInfoLabels_noSubstitutePermission_emptyRequestedLabel() {
+ val presentationGetter = makeActivityInfoPresentationGetter(false, "", "activity_label")
+ assertThat(presentationGetter.getLabel()).isEqualTo("")
+ assertThat(presentationGetter.getSubLabel()).isEqualTo("activity_label")
+ }
+
+ @Test
+ fun testActivityInfoLabels_noSubstitutePermission_emptyRequestedSublabel() {
+ val presentationGetter = makeActivityInfoPresentationGetter(false, "app_label", "")
+ assertThat(presentationGetter.getLabel()).isEqualTo("app_label")
+ // Without the substitute permission, empty sublabels are passed through as-is.
+ assertThat(presentationGetter.getSubLabel()).isEqualTo("")
+ }
+
+ @Test
+ fun testActivityInfoLabels_withSubstitutePermission_distinctRequestedLabelAndSublabel() {
+ val presentationGetter = makeActivityInfoPresentationGetter(
+ true, "app_label", "activity_label")
+ assertThat(presentationGetter.getLabel()).isEqualTo("activity_label")
+ // With the substitute permission, the same ("activity") label is requested as both the label
+ // and sublabel, even though the other value ("app_label") was distinct. Thus this behaves the
+ // same as a dupe.
+ assertThat(presentationGetter.getSubLabel()).isEqualTo(null)
+ }
+
+ @Test
+ fun testActivityInfoLabels_withSubstitutePermission_sameRequestedLabelAndSublabel() {
+ val presentationGetter = makeActivityInfoPresentationGetter(
+ true, "app_label", "app_label")
+ assertThat(presentationGetter.getLabel()).isEqualTo("app_label")
+ // With the substitute permission, duped sublabels get converted to nulls.
+ assertThat(presentationGetter.getSubLabel()).isNull()
+ }
+
+ @Test
+ fun testActivityInfoLabels_withSubstitutePermission_nullRequestedLabel() {
+ val presentationGetter = makeActivityInfoPresentationGetter(true, "app_label", null)
+ assertThat(presentationGetter.getLabel()).isEqualTo("app_label")
+ // With the substitute permission, null inputs are a special case that produces null outputs
+ // (i.e., they're not simply passed-through from the inputs).
+ assertThat(presentationGetter.getSubLabel()).isNull()
+ }
+
+ @Test
+ fun testActivityInfoLabels_withSubstitutePermission_emptyRequestedLabel() {
+ val presentationGetter = makeActivityInfoPresentationGetter(true, "app_label", "")
+ // Empty "labels" are taken as-is and (unlike nulls) don't prompt a fallback to the sublabel.
+ // Thus (as in the previous case with substitute permission & "distinct" labels), this is
+ // treated as a dupe.
+ assertThat(presentationGetter.getLabel()).isEqualTo("")
+ assertThat(presentationGetter.getSubLabel()).isNull()
+ }
+
+ @Test
+ fun testActivityInfoLabels_withSubstitutePermission_emptyRequestedSublabel() {
+ val presentationGetter = makeActivityInfoPresentationGetter(true, "", "activity_label")
+ assertThat(presentationGetter.getLabel()).isEqualTo("activity_label")
+ // With the substitute permission, empty sublabels get converted to nulls.
+ assertThat(presentationGetter.getSubLabel()).isNull()
+ }
+
+ @Test
+ fun testResolveInfoLabels_noSubstitutePermission_distinctRequestedLabelAndSublabel() {
+ val presentationGetter = makeResolveInfoPresentationGetter(
+ false, "app_label", "activity_label", "resolve_info_label")
+ assertThat(presentationGetter.getLabel()).isEqualTo("app_label")
+ assertThat(presentationGetter.getSubLabel()).isEqualTo("resolve_info_label")
+ }
+
+ @Test
+ fun testResolveInfoLabels_noSubstitutePermission_sameRequestedLabelAndSublabel() {
+ val presentationGetter = makeResolveInfoPresentationGetter(
+ false, "app_label", "activity_label", "app_label")
+ assertThat(presentationGetter.getLabel()).isEqualTo("app_label")
+ // Without the substitute permission, there's no logic to dedupe the labels.
+ // TODO: this matches our observations in the legacy code, but is it the right behavior? It
+ // seems like {@link ResolverListAdapter.ViewHolder#bindLabel()} has some logic to dedupe in
+ // the UI at least, but maybe that logic should be pulled back to the "presentation"?
+ assertThat(presentationGetter.getSubLabel()).isEqualTo("app_label")
+ }
+
+ @Test
+ fun testResolveInfoLabels_noSubstitutePermission_emptyRequestedSublabel() {
+ val presentationGetter = makeResolveInfoPresentationGetter(
+ false, "app_label", "activity_label", "")
+ assertThat(presentationGetter.getLabel()).isEqualTo("app_label")
+ // Without the substitute permission, empty sublabels are passed through as-is.
+ assertThat(presentationGetter.getSubLabel()).isEqualTo("")
+ }
+
+ @Test
+ fun testResolveInfoLabels_withSubstitutePermission_distinctRequestedLabelAndSublabel() {
+ val presentationGetter = makeResolveInfoPresentationGetter(
+ true, "app_label", "activity_label", "resolve_info_label")
+ assertThat(presentationGetter.getLabel()).isEqualTo("activity_label")
+ assertThat(presentationGetter.getSubLabel()).isEqualTo("resolve_info_label")
+ }
+
+ @Test
+ fun testResolveInfoLabels_withSubstitutePermission_sameRequestedLabelAndSublabel() {
+ val presentationGetter = makeResolveInfoPresentationGetter(
+ true, "app_label", "activity_label", "activity_label")
+ assertThat(presentationGetter.getLabel()).isEqualTo("activity_label")
+ // With the substitute permission, duped sublabels get converted to nulls.
+ assertThat(presentationGetter.getSubLabel()).isNull()
+ }
+
+ @Test
+ fun testResolveInfoLabels_withSubstitutePermission_emptyRequestedSublabel() {
+ val presentationGetter = makeResolveInfoPresentationGetter(
+ true, "app_label", "activity_label", "")
+ assertThat(presentationGetter.getLabel()).isEqualTo("activity_label")
+ // With the substitute permission, empty sublabels get converted to nulls.
+ assertThat(presentationGetter.getSubLabel()).isNull()
+ }
+
+ @Test
+ fun testResolveInfoLabels_withSubstitutePermission_emptyRequestedLabelAndSublabel() {
+ val presentationGetter = makeResolveInfoPresentationGetter(
+ true, "app_label", "", "")
+ assertThat(presentationGetter.getLabel()).isEqualTo("")
+ // With the substitute permission, empty sublabels get converted to nulls.
+ assertThat(presentationGetter.getSubLabel()).isNull()
+ }
+}
diff --git a/java/tests/src/com/android/intentresolver/TestApplication.kt b/java/tests/src/com/android/intentresolver/TestApplication.kt
new file mode 100644
index 00000000..849cfbab
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/TestApplication.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.intentresolver
+
+import android.app.Application
+import android.content.Context
+import android.os.UserHandle
+
+class TestApplication : Application() {
+
+ // return the current context as a work profile doesn't really exist in these tests
+ override fun createContextAsUser(user: UserHandle, flags: Int): Context = this
+} \ No newline at end of file
diff --git a/java/tests/src/com/android/intentresolver/TestHelpers.kt b/java/tests/src/com/android/intentresolver/TestHelpers.kt
new file mode 100644
index 00000000..5b583fef
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/TestHelpers.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.intentresolver
+
+import android.app.prediction.AppTarget
+import android.app.prediction.AppTargetId
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ShortcutInfo
+import android.content.pm.ShortcutManager.ShareShortcutInfo
+import android.os.Bundle
+import android.service.chooser.ChooserTarget
+import org.mockito.Mockito.`when` as whenever
+
+internal fun createShareShortcutInfo(
+ id: String,
+ componentName: ComponentName,
+ rank: Int
+): ShareShortcutInfo =
+ ShareShortcutInfo(
+ createShortcutInfo(id, componentName, rank),
+ componentName
+ )
+
+internal fun createShortcutInfo(
+ id: String,
+ componentName: ComponentName,
+ rank: Int
+): ShortcutInfo {
+ val context = mock<Context>()
+ whenever(context.packageName).thenReturn(componentName.packageName)
+ return ShortcutInfo.Builder(context, id)
+ .setShortLabel("Short Label $id")
+ .setLongLabel("Long Label $id")
+ .setActivity(componentName)
+ .setRank(rank)
+ .build()
+}
+
+internal fun createAppTarget(shortcutInfo: ShortcutInfo) =
+ AppTarget(
+ AppTargetId(shortcutInfo.id),
+ shortcutInfo,
+ shortcutInfo.activity?.className ?: error("missing activity info")
+ )
+
+fun createChooserTarget(
+ title: String, score: Float, componentName: ComponentName, shortcutId: String
+): ChooserTarget =
+ ChooserTarget(
+ title,
+ null,
+ score,
+ componentName,
+ Bundle().apply { putString(Intent.EXTRA_SHORTCUT_ID, shortcutId) }
+ )
diff --git a/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java b/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java
index b901fc1e..af2557ef 100644
--- a/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java
+++ b/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java
@@ -38,21 +38,18 @@ import static com.android.intentresolver.MatcherUtils.first;
import static com.google.common.truth.Truth.assertThat;
-import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.greaterThan;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -78,26 +75,29 @@ import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.Icon;
-import android.metrics.LogMaker;
import android.net.Uri;
+import android.os.Bundle;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.service.chooser.ChooserTarget;
+import android.util.HashedStringCache;
+import android.util.Pair;
+import android.util.SparseArray;
import android.view.View;
import androidx.annotation.CallSuper;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
import androidx.test.espresso.matcher.BoundedDiagnosingMatcher;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import com.android.intentresolver.ResolverActivity.ResolvedComponentInfo;
import com.android.intentresolver.chooser.DisplayResolveInfo;
+import com.android.intentresolver.shortcuts.ShortcutLoader;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
-import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.widget.GridLayoutManager;
-import com.android.internal.widget.RecyclerView;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
@@ -117,6 +117,7 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.function.Consumer;
import java.util.function.Function;
/**
@@ -130,7 +131,6 @@ import java.util.function.Function;
* TODO: this can simply be renamed to "ChooserActivityTest" if that's ever unambiguous (i.e., if
* there's no risk of confusion with the framework tests that currently share the same name).
*/
-@Ignore("investigate b/241944046 and re-enabled")
@RunWith(Parameterized.class)
public class UnbundledChooserActivityTest {
@@ -252,13 +252,31 @@ public class UnbundledChooserActivityTest {
mTestNum = testNum;
}
+ private void setDeviceConfigProperty(
+ @NonNull String propertyName,
+ @NonNull String value) {
+ // TODO: consider running with {@link #runWithShellPermissionIdentity()} to more narrowly
+ // request WRITE_DEVICE_CONFIG permissions if we get rid of the broad grant we currently
+ // configure in {@link #setup()}.
+ // TODO: is it really appropriate that this is always set with makeDefault=true?
+ boolean valueWasSet = DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ propertyName,
+ value,
+ true /* makeDefault */);
+ if (!valueWasSet) {
+ throw new IllegalStateException(
+ "Could not set " + propertyName + " to " + value);
+ }
+ }
+
public void cleanOverrideData() {
ChooserActivityOverrideData.getInstance().reset();
ChooserActivityOverrideData.getInstance().createPackageManager = mPackageManagerOverride;
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+
+ setDeviceConfigProperty(
SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI,
- Boolean.toString(true),
- true /* makeDefault*/);
+ Boolean.toString(true));
}
@Test
@@ -282,7 +300,7 @@ public class UnbundledChooserActivityTest {
waitForIdle();
assertThat(activity.getAdapter().getCount(), is(2));
assertThat(activity.getAdapter().getServiceTargetCount(), is(0));
- onView(withIdFromRuntimeResource("title")).check(matches(withText("chooser test")));
+ onView(withId(android.R.id.title)).check(matches(withText("chooser test")));
}
@Test
@@ -302,8 +320,8 @@ public class UnbundledChooserActivityTest {
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "chooser test"));
waitForIdle();
- onView(withIdFromRuntimeResource("title"))
- .check(matches(withTextFromRuntimeResource("whichSendApplication")));
+ onView(withId(android.R.id.title))
+ .check(matches(withText(com.android.internal.R.string.whichSendApplication)));
}
@Test
@@ -323,8 +341,8 @@ public class UnbundledChooserActivityTest {
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withIdFromRuntimeResource("title"))
- .check(matches(withTextFromRuntimeResource("whichSendApplication")));
+ onView(withId(android.R.id.title))
+ .check(matches(withText(com.android.internal.R.string.whichSendApplication)));
}
@Test
@@ -344,9 +362,9 @@ public class UnbundledChooserActivityTest {
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withIdFromRuntimeResource("content_preview_title"))
+ onView(withId(com.android.internal.R.id.content_preview_title))
.check(matches(not(isDisplayed())));
- onView(withIdFromRuntimeResource("content_preview_thumbnail"))
+ onView(withId(com.android.internal.R.id.content_preview_thumbnail))
.check(matches(not(isDisplayed())));
}
@@ -368,11 +386,11 @@ public class UnbundledChooserActivityTest {
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withIdFromRuntimeResource("content_preview_title"))
+ onView(withId(com.android.internal.R.id.content_preview_title))
.check(matches(isDisplayed()));
- onView(withIdFromRuntimeResource("content_preview_title"))
+ onView(withId(com.android.internal.R.id.content_preview_title))
.check(matches(withText(previewTitle)));
- onView(withIdFromRuntimeResource("content_preview_thumbnail"))
+ onView(withId(com.android.internal.R.id.content_preview_thumbnail))
.check(matches(not(isDisplayed())));
}
@@ -395,8 +413,9 @@ public class UnbundledChooserActivityTest {
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withIdFromRuntimeResource("content_preview_title")).check(matches(isDisplayed()));
- onView(withIdFromRuntimeResource("content_preview_thumbnail"))
+ 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())));
}
@@ -405,7 +424,7 @@ public class UnbundledChooserActivityTest {
String previewTitle = "My Content Preview Title";
Intent sendIntent = createSendTextIntentWithPreview(previewTitle,
Uri.parse("android.resource://com.android.frameworks.coretests/"
- + com.android.frameworks.coretests.R.drawable.test320x240));
+ + R.drawable.test320x240));
ChooserActivityOverrideData.getInstance().previewThumbnail = createBitmap();
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
@@ -421,8 +440,9 @@ public class UnbundledChooserActivityTest {
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withIdFromRuntimeResource("content_preview_title")).check(matches(isDisplayed()));
- onView(withIdFromRuntimeResource("content_preview_thumbnail"))
+ 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()));
}
@@ -447,7 +467,7 @@ public class UnbundledChooserActivityTest {
waitForIdle();
assertThat(activity.getAdapter().getCount(), is(2));
- onView(withIdFromRuntimeResource("profile_button")).check(doesNotExist());
+ onView(withId(com.android.internal.R.id.profile_button)).check(doesNotExist());
ResolveInfo[] chosen = new ResolveInfo[1];
ChooserActivityOverrideData.getInstance().onSafelyStartCallback = targetInfo -> {
@@ -580,8 +600,8 @@ public class UnbundledChooserActivityTest {
waitForIdle();
assertThat(activity.isFinishing(), is(false));
- onView(withIdFromRuntimeResource("empty")).check(matches(isDisplayed()));
- onView(withIdFromRuntimeResource("profile_pager")).check(matches(not(isDisplayed())));
+ 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()
);
@@ -619,9 +639,7 @@ public class UnbundledChooserActivityTest {
}
@Test @Ignore
- public void hasOtherProfileOneOption() throws Exception {
- // enable the work tab feature flag
- ResolverActivity.ENABLE_TABBED_VIEW = true;
+ public void hasOtherProfileOneOption() {
List<ResolvedComponentInfo> personalResolvedComponentInfos =
createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10);
List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
@@ -647,7 +665,6 @@ public class UnbundledChooserActivityTest {
List<ResolvedComponentInfo> stableCopy =
createResolvedComponentsForTestWithOtherProfile(2, /* userId= */ 10);
waitForIdle();
- Thread.sleep(((ChooserActivity) activity).mListViewUpdateDelayMs);
onView(first(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)))
.perform(click());
@@ -657,9 +674,6 @@ public class UnbundledChooserActivityTest {
@Test @Ignore
public void hasOtherProfileTwoOptionsAndUserSelectsOne() throws Exception {
- // enable the work tab feature flag
- ResolverActivity.ENABLE_TABBED_VIEW = true;
-
Intent sendIntent = createSendTextIntent();
List<ResolvedComponentInfo> resolvedComponentInfos =
createResolvedComponentsForTestWithOtherProfile(3);
@@ -697,9 +711,6 @@ public class UnbundledChooserActivityTest {
@Test @Ignore
public void hasLastChosenActivityAndOtherProfile() throws Exception {
- // enable the work tab feature flag
- ResolverActivity.ENABLE_TABBED_VIEW = true;
-
Intent sendIntent = createSendTextIntent();
List<ResolvedComponentInfo> resolvedComponentInfos =
createResolvedComponentsForTestWithOtherProfile(3);
@@ -748,8 +759,8 @@ public class UnbundledChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withIdFromRuntimeResource("chooser_copy_button")).check(matches(isDisplayed()));
- onView(withIdFromRuntimeResource("chooser_copy_button")).perform(click());
+ 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());
ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(
Context.CLIPBOARD_SERVICE);
ClipData clipData = clipboard.getPrimaryClip();
@@ -762,7 +773,7 @@ public class UnbundledChooserActivityTest {
}
@Test
- public void copyTextToClipboardLogging() throws Exception {
+ public void copyTextToClipboardLogging() {
Intent sendIntent = createSendTextIntent();
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
@@ -772,24 +783,17 @@ public class UnbundledChooserActivityTest {
Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
- MetricsLogger mockLogger = ChooserActivityOverrideData.getInstance().metricsLogger;
- ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class);
-
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+ final IChooserWrapper activity = (IChooserWrapper)
+ mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withIdFromRuntimeResource("chooser_copy_button")).check(matches(isDisplayed()));
- onView(withIdFromRuntimeResource("chooser_copy_button")).perform(click());
-
- verify(mockLogger, atLeastOnce()).write(logMakerCaptor.capture());
+ 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());
- // The last captured event is the selection of the target.
- assertThat(logMakerCaptor.getValue().getCategory(),
- is(MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SYSTEM_TARGET));
- assertThat(logMakerCaptor.getValue().getSubtype(), is(1));
+ ChooserActivityLogger logger = activity.getChooserActivityLogger();
+ verify(logger, times(1)).logActionSelected(eq(ChooserActivityLogger.SELECTION_TYPE_COPY));
}
-
@Test
@Ignore
public void testNearbyShareLogging() throws Exception {
@@ -806,52 +810,11 @@ public class UnbundledChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withIdFromRuntimeResource("chooser_nearby_button")).check(matches(isDisplayed()));
- onView(withIdFromRuntimeResource("chooser_nearby_button")).perform(click());
-
- ChooserActivityLoggerFake logger =
- (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
+ 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.
- logger.removeCallsForUiEventsOfType(
- ChooserActivityLogger.SharesheetStandardEvent
- .SHARESHEET_DIRECT_LOAD_COMPLETE.getId());
-
- // SHARESHEET_TRIGGERED:
- assertThat(logger.event(0).getId(),
- is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
-
- // SHARESHEET_STARTED:
- assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
- assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
- assertThat(logger.get(1).mimeType, is("text/plain"));
- assertThat(logger.get(1).packageName, is(
- InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageName()));
- assertThat(logger.get(1).appProvidedApp, is(0));
- assertThat(logger.get(1).appProvidedDirect, is(0));
- assertThat(logger.get(1).isWorkprofile, is(false));
- assertThat(logger.get(1).previewType, is(3));
-
- // SHARESHEET_APP_LOAD_COMPLETE:
- assertThat(logger.event(2).getId(),
- is(ChooserActivityLogger
- .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
-
- // Next are just artifacts of test set-up:
- assertThat(logger.event(3).getId(),
- is(ChooserActivityLogger
- .SharesheetStandardEvent.SHARESHEET_EMPTY_DIRECT_SHARE_ROW.getId()));
- assertThat(logger.event(4).getId(),
- is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_EXPANDED.getId()));
-
- // SHARESHEET_NEARBY_TARGET_SELECTED:
- assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
- assertThat(logger.get(5).targetType,
- is(ChooserActivityLogger
- .SharesheetTargetSelectedEvent.SHARESHEET_NEARBY_TARGET_SELECTED.getId()));
-
- // No more events.
- assertThat(logger.numCalls(), is(6));
}
@@ -860,7 +823,7 @@ public class UnbundledChooserActivityTest {
public void testEditImageLogs() throws Exception {
Intent sendIntent = createSendImageIntent(
Uri.parse("android.resource://com.android.frameworks.coretests/"
- + com.android.frameworks.coretests.R.drawable.test320x240));
+ + R.drawable.test320x240));
ChooserActivityOverrideData.getInstance().previewThumbnail = createBitmap();
ChooserActivityOverrideData.getInstance().isImageType = true;
@@ -877,59 +840,17 @@ public class UnbundledChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withIdFromRuntimeResource("chooser_edit_button")).check(matches(isDisplayed()));
- onView(withIdFromRuntimeResource("chooser_edit_button")).perform(click());
-
- ChooserActivityLoggerFake logger =
- (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
+ 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.
- logger.removeCallsForUiEventsOfType(
- ChooserActivityLogger.SharesheetStandardEvent
- .SHARESHEET_DIRECT_LOAD_COMPLETE.getId());
-
- // SHARESHEET_TRIGGERED:
- assertThat(logger.event(0).getId(),
- is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
-
- // SHARESHEET_STARTED:
- assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
- assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
- assertThat(logger.get(1).mimeType, is("image/png"));
- assertThat(logger.get(1).packageName, is(
- InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageName()));
- assertThat(logger.get(1).appProvidedApp, is(0));
- assertThat(logger.get(1).appProvidedDirect, is(0));
- assertThat(logger.get(1).isWorkprofile, is(false));
- assertThat(logger.get(1).previewType, is(1));
-
- // SHARESHEET_APP_LOAD_COMPLETE:
- assertThat(logger.event(2).getId(),
- is(ChooserActivityLogger
- .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
-
- // Next are just artifacts of test set-up:
- assertThat(logger.event(3).getId(),
- is(ChooserActivityLogger
- .SharesheetStandardEvent.SHARESHEET_EMPTY_DIRECT_SHARE_ROW.getId()));
- assertThat(logger.event(4).getId(),
- is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_EXPANDED.getId()));
-
- // SHARESHEET_EDIT_TARGET_SELECTED:
- assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
- assertThat(logger.get(5).targetType,
- is(ChooserActivityLogger
- .SharesheetTargetSelectedEvent.SHARESHEET_EDIT_TARGET_SELECTED.getId()));
-
- // No more events.
- assertThat(logger.numCalls(), is(6));
}
@Test
public void oneVisibleImagePreview() throws InterruptedException {
Uri uri = Uri.parse("android.resource://com.android.frameworks.coretests/"
- + com.android.frameworks.coretests.R.drawable.test320x240);
+ + R.drawable.test320x240);
ArrayList<Uri> uris = new ArrayList<>();
uris.add(uri);
@@ -952,20 +873,20 @@ public class UnbundledChooserActivityTest {
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withIdFromRuntimeResource("content_preview_image_1_large"))
+ onView(withId(com.android.internal.R.id.content_preview_image_1_large))
.check(matches(isDisplayed()));
- onView(withIdFromRuntimeResource("content_preview_image_2_large"))
+ onView(withId(com.android.internal.R.id.content_preview_image_2_large))
.check(matches(not(isDisplayed())));
- onView(withIdFromRuntimeResource("content_preview_image_2_small"))
+ onView(withId(com.android.internal.R.id.content_preview_image_2_small))
.check(matches(not(isDisplayed())));
- onView(withIdFromRuntimeResource("content_preview_image_3_small"))
+ onView(withId(com.android.internal.R.id.content_preview_image_3_small))
.check(matches(not(isDisplayed())));
}
@Test
public void twoVisibleImagePreview() throws InterruptedException {
Uri uri = Uri.parse("android.resource://com.android.frameworks.coretests/"
- + com.android.frameworks.coretests.R.drawable.test320x240);
+ + R.drawable.test320x240);
ArrayList<Uri> uris = new ArrayList<>();
uris.add(uri);
@@ -989,20 +910,20 @@ public class UnbundledChooserActivityTest {
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withIdFromRuntimeResource("content_preview_image_1_large"))
+ onView(withId(com.android.internal.R.id.content_preview_image_1_large))
.check(matches(isDisplayed()));
- onView(withIdFromRuntimeResource("content_preview_image_2_large"))
+ onView(withId(com.android.internal.R.id.content_preview_image_2_large))
.check(matches(isDisplayed()));
- onView(withIdFromRuntimeResource("content_preview_image_2_small"))
+ onView(withId(com.android.internal.R.id.content_preview_image_2_small))
.check(matches(not(isDisplayed())));
- onView(withIdFromRuntimeResource("content_preview_image_3_small"))
+ onView(withId(com.android.internal.R.id.content_preview_image_3_small))
.check(matches(not(isDisplayed())));
}
@Test
public void threeOrMoreVisibleImagePreview() throws InterruptedException {
Uri uri = Uri.parse("android.resource://com.android.frameworks.coretests/"
- + com.android.frameworks.coretests.R.drawable.test320x240);
+ + R.drawable.test320x240);
ArrayList<Uri> uris = new ArrayList<>();
uris.add(uri);
@@ -1029,13 +950,13 @@ public class UnbundledChooserActivityTest {
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withIdFromRuntimeResource("content_preview_image_1_large"))
+ onView(withId(com.android.internal.R.id.content_preview_image_1_large))
.check(matches(isDisplayed()));
- onView(withIdFromRuntimeResource("content_preview_image_2_large"))
+ onView(withId(com.android.internal.R.id.content_preview_image_2_large))
.check(matches(not(isDisplayed())));
- onView(withIdFromRuntimeResource("content_preview_image_2_small"))
+ onView(withId(com.android.internal.R.id.content_preview_image_2_small))
.check(matches(isDisplayed()));
- onView(withIdFromRuntimeResource("content_preview_image_3_small"))
+ onView(withId(com.android.internal.R.id.content_preview_image_3_small))
.check(matches(isDisplayed()));
}
@@ -1044,25 +965,12 @@ public class UnbundledChooserActivityTest {
Intent sendIntent = createSendTextIntent();
sendIntent.setType(TEST_MIME_TYPE);
- MetricsLogger mockLogger = ChooserActivityOverrideData.getInstance().metricsLogger;
- ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class);
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, "logger test"));
- waitForIdle();
- verify(mockLogger, atLeastOnce()).write(logMakerCaptor.capture());
- assertThat(logMakerCaptor.getAllValues().get(0).getCategory(),
- is(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN));
- assertThat(logMakerCaptor
- .getAllValues().get(0)
- .getTaggedData(MetricsEvent.FIELD_TIME_TO_APP_TARGETS),
- is(notNullValue()));
- assertThat(logMakerCaptor
- .getAllValues().get(0)
- .getTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE),
- is(TEST_MIME_TYPE));
- assertThat(logMakerCaptor
- .getAllValues().get(0)
- .getSubtype(),
- is(MetricsEvent.PARENT_PROFILE));
+ final IChooserWrapper activity = (IChooserWrapper)
+ mActivityRule.launchActivity(Intent.createChooser(sendIntent, "logger test"));
+ ChooserActivityLogger logger = activity.getChooserActivityLogger();
+ waitForIdle();
+
+ verify(logger).logChooserActivityShown(eq(false), eq(TEST_MIME_TYPE), anyLong());
}
@Test
@@ -1071,49 +979,32 @@ public class UnbundledChooserActivityTest {
sendIntent.setType(TEST_MIME_TYPE);
ChooserActivityOverrideData.getInstance().alternateProfileSetting =
MetricsEvent.MANAGED_PROFILE;
- MetricsLogger mockLogger = ChooserActivityOverrideData.getInstance().metricsLogger;
- ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class);
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, "logger test"));
- waitForIdle();
- verify(mockLogger, atLeastOnce()).write(logMakerCaptor.capture());
- assertThat(logMakerCaptor.getAllValues().get(0).getCategory(),
- is(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN));
- assertThat(logMakerCaptor
- .getAllValues().get(0)
- .getTaggedData(MetricsEvent.FIELD_TIME_TO_APP_TARGETS),
- is(notNullValue()));
- assertThat(logMakerCaptor
- .getAllValues().get(0)
- .getTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE),
- is(TEST_MIME_TYPE));
- assertThat(logMakerCaptor
- .getAllValues().get(0)
- .getSubtype(),
- is(MetricsEvent.MANAGED_PROFILE));
+
+ final IChooserWrapper activity = (IChooserWrapper)
+ mActivityRule.launchActivity(Intent.createChooser(sendIntent, "logger test"));
+ ChooserActivityLogger logger = activity.getChooserActivityLogger();
+ waitForIdle();
+
+ verify(logger).logChooserActivityShown(eq(true), eq(TEST_MIME_TYPE), anyLong());
}
@Test
public void testEmptyPreviewLogging() {
Intent sendIntent = createSendTextIntentWithPreview(null, null);
- MetricsLogger mockLogger = ChooserActivityOverrideData.getInstance().metricsLogger;
- ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class);
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, "empty preview logger test"));
+ final IChooserWrapper activity = (IChooserWrapper)
+ mActivityRule.launchActivity(
+ Intent.createChooser(sendIntent, "empty preview logger test"));
+ ChooserActivityLogger logger = activity.getChooserActivityLogger();
waitForIdle();
- verify(mockLogger, Mockito.times(1)).write(logMakerCaptor.capture());
- // First invocation is from onCreate
- assertThat(logMakerCaptor.getAllValues().get(0).getCategory(),
- is(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN));
+ verify(logger).logChooserActivityShown(eq(false), eq(null), anyLong());
}
@Test
public void testTitlePreviewLogging() {
Intent sendIntent = createSendTextIntentWithPreview("TestTitle", null);
- MetricsLogger mockLogger = ChooserActivityOverrideData.getInstance().metricsLogger;
- ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class);
-
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
when(ChooserActivityOverrideData.getInstance().resolverListController.getResolversForIntent(
@@ -1122,20 +1013,19 @@ public class UnbundledChooserActivityTest {
Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+ final IChooserWrapper activity = (IChooserWrapper)
+ mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
+
// Second invocation is from onCreate
- verify(mockLogger, Mockito.times(2)).write(logMakerCaptor.capture());
- assertThat(logMakerCaptor.getAllValues().get(0).getSubtype(),
- is(CONTENT_PREVIEW_TEXT));
- assertThat(logMakerCaptor.getAllValues().get(0).getCategory(),
- is(MetricsEvent.ACTION_SHARE_WITH_PREVIEW));
+ ChooserActivityLogger logger = activity.getChooserActivityLogger();
+ Mockito.verify(logger, times(1)).logActionShareWithPreview(eq(CONTENT_PREVIEW_TEXT));
}
@Test
public void testImagePreviewLogging() {
Uri uri = Uri.parse("android.resource://com.android.frameworks.coretests/"
- + com.android.frameworks.coretests.R.drawable.test320x240);
+ + R.drawable.test320x240);
ArrayList<Uri> uris = new ArrayList<>();
uris.add(uri);
@@ -1157,16 +1047,11 @@ public class UnbundledChooserActivityTest {
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
- MetricsLogger mockLogger = ChooserActivityOverrideData.getInstance().metricsLogger;
- ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class);
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+ final IChooserWrapper activity = (IChooserWrapper)
+ mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- verify(mockLogger, Mockito.times(2)).write(logMakerCaptor.capture());
- // First invocation is from onCreate
- assertThat(logMakerCaptor.getAllValues().get(0).getSubtype(),
- is(CONTENT_PREVIEW_IMAGE));
- assertThat(logMakerCaptor.getAllValues().get(0).getCategory(),
- is(MetricsEvent.ACTION_SHARE_WITH_PREVIEW));
+ ChooserActivityLogger logger = activity.getChooserActivityLogger();
+ Mockito.verify(logger, times(1)).logActionShareWithPreview(eq(CONTENT_PREVIEW_IMAGE));
}
@Test
@@ -1192,10 +1077,11 @@ public class UnbundledChooserActivityTest {
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withIdFromRuntimeResource("content_preview_filename")).check(matches(isDisplayed()));
- onView(withIdFromRuntimeResource("content_preview_filename"))
+ onView(withId(com.android.internal.R.id.content_preview_filename))
+ .check(matches(isDisplayed()));
+ onView(withId(com.android.internal.R.id.content_preview_filename))
.check(matches(withText("app.pdf")));
- onView(withIdFromRuntimeResource("content_preview_file_icon"))
+ onView(withId(com.android.internal.R.id.content_preview_file_icon))
.check(matches(isDisplayed()));
}
@@ -1225,11 +1111,11 @@ public class UnbundledChooserActivityTest {
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withIdFromRuntimeResource("content_preview_filename"))
+ onView(withId(com.android.internal.R.id.content_preview_filename))
.check(matches(isDisplayed()));
- onView(withIdFromRuntimeResource("content_preview_filename"))
+ onView(withId(com.android.internal.R.id.content_preview_filename))
.check(matches(withText("app.pdf + 2 files")));
- onView(withIdFromRuntimeResource("content_preview_file_icon"))
+ onView(withId(com.android.internal.R.id.content_preview_file_icon))
.check(matches(isDisplayed()));
}
@@ -1258,10 +1144,11 @@ public class UnbundledChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withIdFromRuntimeResource("content_preview_filename")).check(matches(isDisplayed()));
- onView(withIdFromRuntimeResource("content_preview_filename"))
+ onView(withId(com.android.internal.R.id.content_preview_filename))
+ .check(matches(isDisplayed()));
+ onView(withId(com.android.internal.R.id.content_preview_filename))
.check(matches(withText("app.pdf")));
- onView(withIdFromRuntimeResource("content_preview_file_icon"))
+ onView(withId(com.android.internal.R.id.content_preview_file_icon))
.check(matches(isDisplayed()));
}
@@ -1297,10 +1184,11 @@ public class UnbundledChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withIdFromRuntimeResource("content_preview_filename")).check(matches(isDisplayed()));
- onView(withIdFromRuntimeResource("content_preview_filename"))
+ onView(withId(com.android.internal.R.id.content_preview_filename))
+ .check(matches(isDisplayed()));
+ onView(withId(com.android.internal.R.id.content_preview_filename))
.check(matches(withText("app.pdf + 1 file")));
- onView(withIdFromRuntimeResource("content_preview_file_icon"))
+ onView(withId(com.android.internal.R.id.content_preview_file_icon))
.check(matches(isDisplayed()));
}
@@ -1347,9 +1235,11 @@ public class UnbundledChooserActivityTest {
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 testConvertToChooserTarget_predictionService() {
+ public void testDirectTargetSelectionLogging() {
Intent sendIntent = createSendTextIntent();
+ // We need app targets for direct targets to get displayed
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
when(
ChooserActivityOverrideData
@@ -1362,80 +1252,82 @@ public class UnbundledChooserActivityTest {
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
- final ChooserActivity activity =
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- waitForIdle();
-
- List<ShareShortcutInfo> shortcuts = createShortcuts(activity);
-
- int[] expectedOrderAllShortcuts = {0, 1, 2, 3};
- float[] expectedScoreAllShortcuts = {1.0f, 0.99f, 0.98f, 0.97f};
-
- List<ChooserTarget> chooserTargets = activity.convertToChooserTarget(shortcuts, shortcuts,
- null, TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
- assertCorrectShortcutToChooserTargetConversion(shortcuts, chooserTargets,
- expectedOrderAllShortcuts, expectedScoreAllShortcuts);
-
- List<ShareShortcutInfo> subset = new ArrayList<>();
- subset.add(shortcuts.get(1));
- subset.add(shortcuts.get(2));
- subset.add(shortcuts.get(3));
-
- int[] expectedOrderSubset = {1, 2, 3};
- float[] expectedScoreSubset = {0.99f, 0.98f, 0.97f};
+ // create test shortcut loader factory, remember loaders and their callbacks
+ SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders =
+ createShortcutLoaderFactory();
- chooserTargets = activity.convertToChooserTarget(subset, shortcuts, null,
- TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
- assertCorrectShortcutToChooserTargetConversion(shortcuts, chooserTargets,
- expectedOrderSubset, expectedScoreSubset);
- }
-
- @Test
- public void testConvertToChooserTarget_shortcutManager() {
- Intent sendIntent = createSendTextIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
- when(
- ChooserActivityOverrideData
- .getInstance()
- .resolverListController
- .getResolversForIntent(
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class)))
- .thenReturn(resolvedComponentInfos);
-
- final ChooserActivity activity =
+ // Start activity
+ final IChooserWrapper activity = (IChooserWrapper)
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- List<ShareShortcutInfo> shortcuts = createShortcuts(activity);
-
- int[] expectedOrderAllShortcuts = {2, 0, 3, 1};
- float[] expectedScoreAllShortcuts = {1.0f, 0.99f, 0.99f, 0.98f};
+ // verify that ShortcutLoader was queried
+ ArgumentCaptor<DisplayResolveInfo[]> appTargets =
+ ArgumentCaptor.forClass(DisplayResolveInfo[].class);
+ verify(shortcutLoaders.get(0).first, times(1)).queryShortcuts(appTargets.capture());
- List<ChooserTarget> chooserTargets = activity.convertToChooserTarget(shortcuts, shortcuts,
- null, TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER);
- assertCorrectShortcutToChooserTargetConversion(shortcuts, chooserTargets,
- expectedOrderAllShortcuts, expectedScoreAllShortcuts);
+ // send shortcuts
+ assertThat(
+ "Wrong number of app targets",
+ appTargets.getValue().length,
+ is(resolvedComponentInfos.size()));
+ List<ChooserTarget> serviceTargets = createDirectShareTargets(1, "");
+ ShortcutLoader.Result result = new ShortcutLoader.Result(
+ true,
+ appTargets.getValue(),
+ new ShortcutLoader.ShortcutResultInfo[] {
+ new ShortcutLoader.ShortcutResultInfo(
+ appTargets.getValue()[0],
+ serviceTargets
+ )
+ },
+ new HashMap<>(),
+ new HashMap<>()
+ );
+ activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result));
+ waitForIdle();
- List<ShareShortcutInfo> subset = new ArrayList<>();
- subset.add(shortcuts.get(1));
- subset.add(shortcuts.get(2));
- subset.add(shortcuts.get(3));
+ 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)));
- int[] expectedOrderSubset = {2, 3, 1};
- float[] expectedScoreSubset = {1.0f, 0.99f, 0.98f};
+ // Click on the direct target
+ String name = serviceTargets.get(0).getTitle().toString();
+ onView(withText(name))
+ .perform(click());
+ waitForIdle();
- chooserTargets = activity.convertToChooserTarget(subset, shortcuts, null,
- TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER);
- assertCorrectShortcutToChooserTargetConversion(shortcuts, chooserTargets,
- expectedOrderSubset, expectedScoreSubset);
+ ArgumentCaptor<HashedStringCache.HashResult> hashCaptor =
+ ArgumentCaptor.forClass(HashedStringCache.HashResult.class);
+ verify(activity.getChooserActivityLogger(), times(1)).logShareTargetSelected(
+ eq(ChooserActivityLogger.SELECTION_TYPE_SERVICE),
+ /* packageName= */ any(),
+ /* positionPicked= */ anyInt(),
+ /* directTargetAlsoRanked= */ eq(-1),
+ /* numCallerProvided= */ anyInt(),
+ /* directTargetHashed= */ hashCaptor.capture(),
+ /* isPinned= */ anyBoolean(),
+ /* successfullySelected= */ anyBoolean(),
+ /* selectionCost= */ anyLong());
+ String hashedName = hashCaptor.getValue().hashedString;
+ assertThat(
+ "Hash is not predictable but must be obfuscated",
+ hashedName, is(not(name)));
}
// This test is too long and too slow and should not be taken as an example for future tests.
- @Test @Ignore
- public void testDirectTargetSelectionLogging() throws InterruptedException {
+ @Test
+ public void testDirectTargetLoggingWithRankedAppTarget() {
Intent sendIntent = createSendTextIntent();
// We need app targets for direct targets to get displayed
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
@@ -1450,44 +1342,56 @@ public class UnbundledChooserActivityTest {
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
- // Set up resources
- MetricsLogger mockLogger = ChooserActivityOverrideData.getInstance().metricsLogger;
- ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class);
- // Create direct share target
- List<ChooserTarget> serviceTargets = createDirectShareTargets(1, "");
- ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0);
+ // create test shortcut loader factory, remember loaders and their callbacks
+ SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders =
+ createShortcutLoaderFactory();
// Start activity
final IChooserWrapper activity = (IChooserWrapper)
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+ waitForIdle();
- // Insert the direct share target
- Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos = new HashMap<>();
- directShareToShortcutInfos.put(serviceTargets.get(0), null);
- InstrumentationRegistry.getInstrumentation().runOnMainSync(
- () -> activity.getAdapter().addServiceResults(
- activity.createTestDisplayResolveInfo(sendIntent,
- ri,
- "testLabel",
- "testInfo",
- sendIntent,
- /* resolveInfoPresentationGetter */ null),
- serviceTargets,
- TARGET_TYPE_CHOOSER_TARGET,
- directShareToShortcutInfos)
- );
+ // verify that ShortcutLoader was queried
+ ArgumentCaptor<DisplayResolveInfo[]> appTargets =
+ ArgumentCaptor.forClass(DisplayResolveInfo[].class);
+ verify(shortcutLoaders.get(0).first, times(1)).queryShortcuts(appTargets.capture());
- // Thread.sleep shouldn't be a thing in an integration test but it's
- // necessary here because of the way the code is structured
- // TODO: restructure the tests b/129870719
- Thread.sleep(((ChooserActivity) activity).mListViewUpdateDelayMs);
+ // send shortcuts
+ assertThat(
+ "Wrong number of app targets",
+ appTargets.getValue().length,
+ is(resolvedComponentInfos.size()));
+ List<ChooserTarget> serviceTargets = createDirectShareTargets(
+ 1,
+ resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
+ ShortcutLoader.Result result = new ShortcutLoader.Result(
+ true,
+ appTargets.getValue(),
+ new ShortcutLoader.ShortcutResultInfo[] {
+ new ShortcutLoader.ShortcutResultInfo(
+ appTargets.getValue()[0],
+ serviceTargets
+ )
+ },
+ new HashMap<>(),
+ new HashMap<>()
+ );
+ activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result));
+ waitForIdle();
- 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(ri));
+ 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();
@@ -1495,24 +1399,29 @@ public class UnbundledChooserActivityTest {
.perform(click());
waitForIdle();
- // Currently we're seeing 3 invocations
- // 1. ChooserActivity.onCreate()
- // 2. ChooserActivity$ChooserRowAdapter.createContentPreviewView()
- // 3. ChooserActivity.startSelected -- which is the one we're after
- verify(mockLogger, Mockito.times(3)).write(logMakerCaptor.capture());
- assertThat(logMakerCaptor.getAllValues().get(2).getCategory(),
- is(MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET));
- String hashedName = (String) logMakerCaptor
- .getAllValues().get(2).getTaggedData(MetricsEvent.FIELD_HASHED_TARGET_NAME);
- assertThat("Hash is not predictable but must be obfuscated",
- hashedName, is(not(name)));
- assertThat("The packages shouldn't match for app target and direct target", logMakerCaptor
- .getAllValues().get(2).getTaggedData(MetricsEvent.FIELD_RANKED_POSITION), is(-1));
+ verify(activity.getChooserActivityLogger(), times(1)).logShareTargetSelected(
+ eq(ChooserActivityLogger.SELECTION_TYPE_SERVICE),
+ /* packageName= */ any(),
+ /* positionPicked= */ anyInt(),
+ /* directTargetAlsoRanked= */ eq(0),
+ /* numCallerProvided= */ anyInt(),
+ /* directTargetHashed= */ any(),
+ /* isPinned= */ anyBoolean(),
+ /* successfullySelected= */ anyBoolean(),
+ /* selectionCost= */ anyLong());
}
- // This test is too long and too slow and should not be taken as an example for future tests.
- @Test @Ignore
- public void testDirectTargetLoggingWithRankedAppTarget() throws InterruptedException {
+ @Test
+ public void testShortcutTargetWithApplyAppLimits() {
+ // Set up resources
+ ChooserActivityOverrideData.getInstance().resources = Mockito.spy(
+ InstrumentationRegistry.getInstrumentation().getContext().getResources());
+ when(
+ ChooserActivityOverrideData
+ .getInstance()
+ .resources
+ .getInteger(R.integer.config_maxShortcutTargetsPerApp))
+ .thenReturn(1);
Intent sendIntent = createSendTextIntent();
// We need app targets for direct targets to get displayed
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
@@ -1527,64 +1436,67 @@ public class UnbundledChooserActivityTest {
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
- // Set up resources
- MetricsLogger mockLogger = ChooserActivityOverrideData.getInstance().metricsLogger;
- ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class);
- // Create direct share target
- List<ChooserTarget> serviceTargets = createDirectShareTargets(1,
- resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
- ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0);
+ // create test shortcut loader factory, remember loaders and their callbacks
+ SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders =
+ createShortcutLoaderFactory();
// Start activity
- final IChooserWrapper activity = (IChooserWrapper)
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
-
- // Insert the direct share target
- Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos = new HashMap<>();
- directShareToShortcutInfos.put(serviceTargets.get(0), null);
- InstrumentationRegistry.getInstrumentation().runOnMainSync(
- () -> activity.getAdapter().addServiceResults(
- activity.createTestDisplayResolveInfo(sendIntent,
- ri,
- "testLabel",
- "testInfo",
- sendIntent,
- /* resolveInfoPresentationGetter */ null),
- serviceTargets,
- TARGET_TYPE_CHOOSER_TARGET,
- directShareToShortcutInfos)
- );
- // Thread.sleep shouldn't be a thing in an integration test but it's
- // necessary here because of the way the code is structured
- // TODO: restructure the tests b/129870719
- Thread.sleep(((ChooserActivity) activity).mListViewUpdateDelayMs);
+ final IChooserWrapper activity = (IChooserWrapper) mActivityRule
+ .launchActivity(Intent.createChooser(sendIntent, null));
+ 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(ri));
+ // verify that ShortcutLoader was queried
+ ArgumentCaptor<DisplayResolveInfo[]> appTargets =
+ ArgumentCaptor.forClass(DisplayResolveInfo[].class);
+ verify(shortcutLoaders.get(0).first, times(1)).queryShortcuts(appTargets.capture());
- // Click on the direct target
- String name = serviceTargets.get(0).getTitle().toString();
- onView(withText(name))
- .perform(click());
+ // send shortcuts
+ assertThat(
+ "Wrong number of app targets",
+ appTargets.getValue().length,
+ is(resolvedComponentInfos.size()));
+ List<ChooserTarget> serviceTargets = createDirectShareTargets(
+ 2,
+ resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
+ ShortcutLoader.Result result = new ShortcutLoader.Result(
+ true,
+ appTargets.getValue(),
+ new ShortcutLoader.ShortcutResultInfo[] {
+ new ShortcutLoader.ShortcutResultInfo(
+ appTargets.getValue()[0],
+ serviceTargets
+ )
+ },
+ new HashMap<>(),
+ new HashMap<>()
+ );
+ activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result));
waitForIdle();
- // Currently we're seeing 3 invocations
- // 1. ChooserActivity.onCreate()
- // 2. ChooserActivity$ChooserRowAdapter.createContentPreviewView()
- // 3. ChooserActivity.startSelected -- which is the one we're after
- verify(mockLogger, Mockito.times(3)).write(logMakerCaptor.capture());
- assertThat(logMakerCaptor.getAllValues().get(2).getCategory(),
- is(MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET));
- assertThat("The packages should match for app target and direct target", logMakerCaptor
- .getAllValues().get(2).getTaggedData(MetricsEvent.FIELD_RANKED_POSITION), is(0));
+ 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 @Ignore
- public void testShortcutTargetWithApplyAppLimits() throws InterruptedException {
+ @Test
+ public void testShortcutTargetWithoutApplyAppLimits() {
+ setDeviceConfigProperty(
+ SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI,
+ Boolean.toString(false));
// Set up resources
ChooserActivityOverrideData.getInstance().resources = Mockito.spy(
InstrumentationRegistry.getInstrumentation().getContext().getResources());
@@ -1592,8 +1504,7 @@ public class UnbundledChooserActivityTest {
ChooserActivityOverrideData
.getInstance()
.resources
- .getInteger(
- getRuntimeResourceId("config_maxShortcutTargetsPerApp", "integer")))
+ .getInteger(R.integer.config_maxShortcutTargetsPerApp))
.thenReturn(1);
Intent sendIntent = createSendTextIntent();
// We need app targets for direct targets to get displayed
@@ -1608,56 +1519,72 @@ public class UnbundledChooserActivityTest {
Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
- // Create direct share target
- List<ChooserTarget> serviceTargets = createDirectShareTargets(2,
- resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
- ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0);
+
+ // create test shortcut loader factory, remember loaders and their callbacks
+ SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders =
+ createShortcutLoaderFactory();
// Start activity
- final ChooserActivity activity =
+ final IChooserWrapper activity = (IChooserWrapper)
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- final IChooserWrapper wrapper = (IChooserWrapper) activity;
+ waitForIdle();
- // Insert the direct share target
- Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos = new HashMap<>();
- List<ShareShortcutInfo> shortcutInfos = createShortcuts(activity);
- directShareToShortcutInfos.put(serviceTargets.get(0),
- shortcutInfos.get(0).getShortcutInfo());
- directShareToShortcutInfos.put(serviceTargets.get(1),
- shortcutInfos.get(1).getShortcutInfo());
- InstrumentationRegistry.getInstrumentation().runOnMainSync(
- () -> wrapper.getAdapter().addServiceResults(
- wrapper.createTestDisplayResolveInfo(sendIntent,
- ri,
- "testLabel",
- "testInfo",
- sendIntent,
- /* resolveInfoPresentationGetter */ null),
- serviceTargets,
- TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE,
- directShareToShortcutInfos)
+ // verify that ShortcutLoader was queried
+ ArgumentCaptor<DisplayResolveInfo[]> appTargets =
+ ArgumentCaptor.forClass(DisplayResolveInfo[].class);
+ verify(shortcutLoaders.get(0).first, times(1)).queryShortcuts(appTargets.capture());
+
+ // send shortcuts
+ assertThat(
+ "Wrong number of app targets",
+ appTargets.getValue().length,
+ is(resolvedComponentInfos.size()));
+ List<ChooserTarget> serviceTargets = createDirectShareTargets(
+ 2,
+ resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
+ ShortcutLoader.Result result = new ShortcutLoader.Result(
+ true,
+ appTargets.getValue(),
+ new ShortcutLoader.ShortcutResultInfo[] {
+ new ShortcutLoader.ShortcutResultInfo(
+ appTargets.getValue()[0],
+ serviceTargets
+ )
+ },
+ new HashMap<>(),
+ new HashMap<>()
);
- // Thread.sleep shouldn't be a thing in an integration test but it's
- // necessary here because of the way the code is structured
- // TODO: restructure the tests b/129870719
- Thread.sleep(((ChooserActivity) activity).mListViewUpdateDelayMs);
+ activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result));
+ waitForIdle();
- assertThat("Chooser should have 3 targets (2 apps, 1 direct)",
- wrapper.getAdapter().getCount(), is(3));
- assertThat("Chooser should have exactly one selectable direct target",
- wrapper.getAdapter().getSelectableServiceTargetCount(), is(1));
- assertThat("The resolver info must match the resolver info used to create the target",
- wrapper.getAdapter().getItem(0).getResolveInfo(), is(ri));
- assertThat("The display label must match",
- wrapper.getAdapter().getItem(0).getDisplayLabel(), is("testTitle0"));
+ 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 @Ignore
- public void testShortcutTargetWithoutApplyAppLimits() throws InterruptedException {
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ @Test
+ public void testLaunchWithCallerProvidedTarget() {
+ setDeviceConfigProperty(
SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI,
- Boolean.toString(false),
- true /* makeDefault*/);
+ Boolean.toString(false));
// Set up resources
ChooserActivityOverrideData.getInstance().resources = Mockito.spy(
InstrumentationRegistry.getInstrumentation().getContext().getResources());
@@ -1665,10 +1592,9 @@ public class UnbundledChooserActivityTest {
ChooserActivityOverrideData
.getInstance()
.resources
- .getInteger(
- getRuntimeResourceId("config_maxShortcutTargetsPerApp", "integer")))
+ .getInteger(R.integer.config_maxShortcutTargetsPerApp))
.thenReturn(1);
- Intent sendIntent = createSendTextIntent();
+
// We need app targets for direct targets to get displayed
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
when(
@@ -1681,50 +1607,61 @@ public class UnbundledChooserActivityTest {
Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
- // Create direct share target
- List<ChooserTarget> serviceTargets = createDirectShareTargets(2,
- resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
- ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0);
+
+ // set caller-provided target
+ Intent chooserIntent = Intent.createChooser(createSendTextIntent(), null);
+ String callerTargetLabel = "Caller Target";
+ ChooserTarget[] targets = new ChooserTarget[] {
+ new ChooserTarget(
+ callerTargetLabel,
+ Icon.createWithBitmap(createBitmap()),
+ 0.1f,
+ resolvedComponentInfos.get(0).name,
+ new Bundle())
+ };
+ chooserIntent.putExtra(Intent.EXTRA_CHOOSER_TARGETS, targets);
+
+ // create test shortcut loader factory, remember loaders and their callbacks
+ SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders =
+ createShortcutLoaderFactory();
// Start activity
- final ChooserActivity activity =
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- final IChooserWrapper wrapper = (IChooserWrapper) activity;
+ final IChooserWrapper activity = (IChooserWrapper)
+ mActivityRule.launchActivity(chooserIntent);
+ waitForIdle();
- // Insert the direct share target
- Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos = new HashMap<>();
- List<ShareShortcutInfo> shortcutInfos = createShortcuts(activity);
- directShareToShortcutInfos.put(serviceTargets.get(0),
- shortcutInfos.get(0).getShortcutInfo());
- directShareToShortcutInfos.put(serviceTargets.get(1),
- shortcutInfos.get(1).getShortcutInfo());
- InstrumentationRegistry.getInstrumentation().runOnMainSync(
- () -> wrapper.getAdapter().addServiceResults(
- wrapper.createTestDisplayResolveInfo(sendIntent,
- ri,
- "testLabel",
- "testInfo",
- sendIntent,
- /* resolveInfoPresentationGetter */ null),
- serviceTargets,
- TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE,
- directShareToShortcutInfos)
- );
- // Thread.sleep shouldn't be a thing in an integration test but it's
- // necessary here because of the way the code is structured
- // TODO: restructure the tests b/129870719
- Thread.sleep(((ChooserActivity) activity).mListViewUpdateDelayMs);
+ // verify that ShortcutLoader was queried
+ ArgumentCaptor<DisplayResolveInfo[]> appTargets =
+ ArgumentCaptor.forClass(DisplayResolveInfo[].class);
+ verify(shortcutLoaders.get(0).first, times(1)).queryShortcuts(appTargets.capture());
- assertThat("Chooser should have 4 targets (2 apps, 2 direct)",
- wrapper.getAdapter().getCount(), is(4));
- assertThat("Chooser should have exactly two selectable direct target",
- wrapper.getAdapter().getSelectableServiceTargetCount(), is(2));
- assertThat("The resolver info must match the resolver info used to create the target",
- wrapper.getAdapter().getItem(0).getResolveInfo(), is(ri));
- assertThat("The display label must match",
- wrapper.getAdapter().getItem(0).getDisplayLabel(), is("testTitle0"));
- assertThat("The display label must match",
- wrapper.getAdapter().getItem(1).getDisplayLabel(), is("testTitle1"));
+ // 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));
}
@Test
@@ -1742,7 +1679,7 @@ public class UnbundledChooserActivityTest {
.getContext().getResources().getConfiguration()));
waitForIdle();
- onView(withIdFromRuntimeResource("resolver_list"))
+ onView(withId(com.android.internal.R.id.resolver_list))
.check(matches(withGridColumnCount(6)));
}
@@ -1760,8 +1697,7 @@ public class UnbundledChooserActivityTest {
}
private void testDirectTargetLoggingWithAppTargetNotRanked(
- int orientation, int appTargetsExpected
- ) throws InterruptedException {
+ int orientation, int appTargetsExpected) {
Configuration configuration =
new Configuration(InstrumentationRegistry.getInstrumentation().getContext()
.getResources().getConfiguration());
@@ -1790,18 +1726,14 @@ public class UnbundledChooserActivityTest {
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
- // Set up resources
- MetricsLogger mockLogger = ChooserActivityOverrideData.getInstance().metricsLogger;
- ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class);
// Create direct share target
List<ChooserTarget> serviceTargets = createDirectShareTargets(1,
resolvedComponentInfos.get(14).getResolveInfoAt(0).activityInfo.packageName);
ResolveInfo ri = ResolverDataProvider.createResolveInfo(16, 0);
// Start activity
- final IChooserWrapper activity = (IChooserWrapper)
+ final IChooserWrapper wrapper = (IChooserWrapper)
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
- final IChooserWrapper wrapper = (IChooserWrapper) activity;
// Insert the direct share target
Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos = new HashMap<>();
directShareToShortcutInfos.put(serviceTargets.get(0), null);
@@ -1815,12 +1747,9 @@ public class UnbundledChooserActivityTest {
/* resolveInfoPresentationGetter */ null),
serviceTargets,
TARGET_TYPE_CHOOSER_TARGET,
- directShareToShortcutInfos)
+ directShareToShortcutInfos,
+ /* directShareToAppTargets */ 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
- // TODO: restructure the tests b/129870719
- Thread.sleep(((ChooserActivity) activity).mListViewUpdateDelayMs);
assertThat(
String.format("Chooser should have %d targets (%d apps, 1 direct, 15 A-Z)",
@@ -1837,21 +1766,22 @@ public class UnbundledChooserActivityTest {
.perform(click());
waitForIdle();
- // Currently we're seeing 3 invocations
- // 1. ChooserActivity.onCreate()
- // 2. ChooserActivity$ChooserRowAdapter.createContentPreviewView()
- // 3. ChooserActivity.startSelected -- which is the one we're after
- verify(mockLogger, Mockito.times(3)).write(logMakerCaptor.capture());
- assertThat(logMakerCaptor.getAllValues().get(2).getCategory(),
- is(MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET));
- assertThat("The packages shouldn't match for app target and direct target", logMakerCaptor
- .getAllValues().get(2).getTaggedData(MetricsEvent.FIELD_RANKED_POSITION), is(-1));
+ ChooserActivityLogger logger = wrapper.getChooserActivityLogger();
+ verify(logger, times(1)).logShareTargetSelected(
+ eq(ChooserActivityLogger.SELECTION_TYPE_SERVICE),
+ /* packageName= */ any(),
+ /* positionPicked= */ anyInt(),
+ // The packages sholdn't match for app target and direct target:
+ /* directTargetAlsoRanked= */ eq(-1),
+ /* numCallerProvided= */ anyInt(),
+ /* directTargetHashed= */ any(),
+ /* isPinned= */ anyBoolean(),
+ /* successfullySelected= */ anyBoolean(),
+ /* selectionCost= */ anyLong());
}
@Test
public void testWorkTab_displayedWhenWorkProfileUserAvailable() {
- // enable the work tab feature flag
- ResolverActivity.ENABLE_TABBED_VIEW = true;
Intent sendIntent = createSendTextIntent();
sendIntent.setType(TEST_MIME_TYPE);
markWorkProfileUserAvailable();
@@ -1859,26 +1789,22 @@ public class UnbundledChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
waitForIdle();
- onView(withIdFromRuntimeResource("tabs")).check(matches(isDisplayed()));
+ onView(withId(android.R.id.tabs)).check(matches(isDisplayed()));
}
@Test
public void testWorkTab_hiddenWhenWorkProfileUserNotAvailable() {
- // enable the work tab feature flag
- ResolverActivity.ENABLE_TABBED_VIEW = true;
Intent sendIntent = createSendTextIntent();
sendIntent.setType(TEST_MIME_TYPE);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
waitForIdle();
- onView(withIdFromRuntimeResource("tabs")).check(matches(not(isDisplayed())));
+ onView(withId(android.R.id.tabs)).check(matches(not(isDisplayed())));
}
@Test
public void testWorkTab_eachTabUsesExpectedAdapter() {
- // enable the work tab feature flag
- ResolverActivity.ENABLE_TABBED_VIEW = true;
int personalProfileTargets = 3;
int otherProfileTargets = 1;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
@@ -1897,7 +1823,7 @@ public class UnbundledChooserActivityTest {
waitForIdle();
assertThat(activity.getCurrentUserHandle().getIdentifier(), is(0));
- onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
+ 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));
@@ -1905,8 +1831,6 @@ public class UnbundledChooserActivityTest {
@Test
public void testWorkTab_workProfileHasExpectedNumberOfTargets() throws InterruptedException {
- // enable the work tab feature flag
- ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
int workProfileTargets = 4;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
@@ -1920,16 +1844,14 @@ public class UnbundledChooserActivityTest {
final IChooserWrapper activity = (IChooserWrapper)
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
waitForIdle();
- onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
+ onView(withText(R.string.resolver_work_tab)).perform(click());
waitForIdle();
assertThat(activity.getWorkListAdapter().getCount(), is(workProfileTargets));
}
@Test @Ignore
- public void testWorkTab_selectingWorkTabAppOpensAppInWorkProfile() throws InterruptedException {
- // enable the work tab feature flag
- ResolverActivity.ENABLE_TABBED_VIEW = true;
+ public void testWorkTab_selectingWorkTabAppOpensAppInWorkProfile() {
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
@@ -1945,13 +1867,10 @@ public class UnbundledChooserActivityTest {
return true;
};
- final IChooserWrapper activity = (IChooserWrapper)
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+ mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
waitForIdle();
- onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
+ onView(withText(R.string.resolver_work_tab)).perform(click());
waitForIdle();
- // wait for the share sheet to expand
- Thread.sleep(((ChooserActivity) activity).mListViewUpdateDelayMs);
onView(first(allOf(
withText(workResolvedComponentInfos.get(0)
@@ -1964,8 +1883,6 @@ public class UnbundledChooserActivityTest {
@Test
public void testWorkTab_crossProfileIntentsDisabled_personalToWork_emptyStateShown() {
- // enable the work tab feature flag
- ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
int workProfileTargets = 4;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
@@ -1979,18 +1896,17 @@ public class UnbundledChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
waitForIdle();
- onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
+ onView(withText(R.string.resolver_work_tab)).perform(click());
waitForIdle();
- onView(withIdFromRuntimeResource("contentPanel"))
+ onView(withId(com.android.internal.R.id.contentPanel))
.perform(swipeUp());
- onView(withTextFromRuntimeResource("resolver_cross_profile_blocked"))
+ onView(withText(R.string.resolver_cross_profile_blocked))
.check(matches(isDisplayed()));
}
@Test
public void testWorkTab_workProfileDisabled_emptyStateShown() {
- // enable the work tab feature flag
markWorkProfileUserAvailable();
int workProfileTargets = 4;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
@@ -2002,22 +1918,19 @@ public class UnbundledChooserActivityTest {
Intent sendIntent = createSendTextIntent();
sendIntent.setType(TEST_MIME_TYPE);
- ResolverActivity.ENABLE_TABBED_VIEW = true;
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
waitForIdle();
- onView(withIdFromRuntimeResource("contentPanel"))
+ onView(withId(com.android.internal.R.id.contentPanel))
.perform(swipeUp());
- onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
+ onView(withText(R.string.resolver_work_tab)).perform(click());
waitForIdle();
- onView(withTextFromRuntimeResource("resolver_turn_on_work_apps"))
+ onView(withText(R.string.resolver_turn_on_work_apps))
.check(matches(isDisplayed()));
}
@Test
public void testWorkTab_noWorkAppsAvailable_emptyStateShown() {
- // enable the work tab feature flag
- ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
createResolvedComponentsForTest(3);
@@ -2029,20 +1942,18 @@ public class UnbundledChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
waitForIdle();
- onView(withIdFromRuntimeResource("contentPanel"))
+ onView(withId(com.android.internal.R.id.contentPanel))
.perform(swipeUp());
- onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
+ onView(withText(R.string.resolver_work_tab)).perform(click());
waitForIdle();
- onView(withTextFromRuntimeResource("resolver_no_work_apps_available"))
+ onView(withText(R.string.resolver_no_work_apps_available))
.check(matches(isDisplayed()));
}
@Ignore // b/220067877
@Test
public void testWorkTab_xProfileOff_noAppsAvailable_workOff_xProfileOffEmptyStateShown() {
- // enable the work tab feature flag
- ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
createResolvedComponentsForTest(3);
@@ -2056,19 +1967,17 @@ public class UnbundledChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
waitForIdle();
- onView(withIdFromRuntimeResource("contentPanel"))
+ onView(withId(com.android.internal.R.id.contentPanel))
.perform(swipeUp());
- onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
+ onView(withText(R.string.resolver_work_tab)).perform(click());
waitForIdle();
- onView(withTextFromRuntimeResource("resolver_cross_profile_blocked"))
+ onView(withText(R.string.resolver_cross_profile_blocked))
.check(matches(isDisplayed()));
}
@Test
public void testWorkTab_noAppsAvailable_workOff_noAppsAvailableEmptyStateShown() {
- // enable the work tab feature flag
- ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
createResolvedComponentsForTest(3);
@@ -2081,12 +1990,12 @@ public class UnbundledChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
waitForIdle();
- onView(withIdFromRuntimeResource("contentPanel"))
+ onView(withId(com.android.internal.R.id.contentPanel))
.perform(swipeUp());
- onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
+ onView(withText(R.string.resolver_work_tab)).perform(click());
waitForIdle();
- onView(withTextFromRuntimeResource("resolver_no_work_apps_available"))
+ onView(withText(R.string.resolver_no_work_apps_available))
.check(matches(isDisplayed()));
}
@@ -2115,7 +2024,7 @@ public class UnbundledChooserActivityTest {
// timeout everywhere instead of introducing one to fix this particular test.
assertThat(activity.getAdapter().getCount(), is(2));
- onView(withIdFromRuntimeResource("profile_button")).check(doesNotExist());
+ onView(withId(com.android.internal.R.id.profile_button)).check(doesNotExist());
ResolveInfo[] chosen = new ResolveInfo[1];
ChooserActivityOverrideData.getInstance().onSafelyStartCallback = targetInfo -> {
@@ -2128,53 +2037,11 @@ public class UnbundledChooserActivityTest {
.perform(click());
waitForIdle();
- ChooserActivityLoggerFake logger =
- (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
-
// TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events.
- logger.removeCallsForUiEventsOfType(
- ChooserActivityLogger.SharesheetStandardEvent
- .SHARESHEET_DIRECT_LOAD_COMPLETE.getId());
-
- // SHARESHEET_TRIGGERED:
- assertThat(logger.event(0).getId(),
- is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
-
- // SHARESHEET_STARTED:
- assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
- assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
- assertThat(logger.get(1).mimeType, is("text/plain"));
- assertThat(logger.get(1).packageName, is(
- InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageName()));
- assertThat(logger.get(1).appProvidedApp, is(0));
- assertThat(logger.get(1).appProvidedDirect, is(0));
- assertThat(logger.get(1).isWorkprofile, is(false));
- assertThat(logger.get(1).previewType, is(3));
-
- // SHARESHEET_APP_LOAD_COMPLETE:
- assertThat(logger.event(2).getId(),
- is(ChooserActivityLogger
- .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
-
- // Next are just artifacts of test set-up:
- assertThat(logger.event(3).getId(),
- is(ChooserActivityLogger
- .SharesheetStandardEvent.SHARESHEET_EMPTY_DIRECT_SHARE_ROW.getId()));
- assertThat(logger.event(4).getId(),
- is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_EXPANDED.getId()));
-
- // SHARESHEET_APP_TARGET_SELECTED:
- assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
- assertThat(logger.get(5).targetType,
- is(ChooserActivityLogger
- .SharesheetTargetSelectedEvent.SHARESHEET_APP_TARGET_SELECTED.getId()));
-
- // No more events.
- assertThat(logger.numCalls(), is(6));
}
- @Test @Ignore
- public void testDirectTargetLogging() throws InterruptedException {
+ @Test
+ public void testDirectTargetLogging() {
Intent sendIntent = createSendTextIntent();
// We need app targets for direct targets to get displayed
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
@@ -2189,41 +2056,59 @@ public class UnbundledChooserActivityTest {
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
- // Create direct share target
- List<ChooserTarget> serviceTargets = createDirectShareTargets(1,
- resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
- ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0);
+ // create test shortcut loader factory, remember loaders and their callbacks
+ SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders =
+ new SparseArray<>();
+ ChooserActivityOverrideData.getInstance().shortcutLoaderFactory =
+ (userHandle, callback) -> {
+ Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>> pair =
+ new Pair<>(mock(ShortcutLoader.class), callback);
+ shortcutLoaders.put(userHandle.getIdentifier(), pair);
+ return pair.first;
+ };
// Start activity
final IChooserWrapper activity = (IChooserWrapper)
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+ waitForIdle();
- // Insert the direct share target
- Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos = new HashMap<>();
- directShareToShortcutInfos.put(serviceTargets.get(0), null);
- InstrumentationRegistry.getInstrumentation().runOnMainSync(
- () -> activity.getAdapter().addServiceResults(
- activity.createTestDisplayResolveInfo(sendIntent,
- ri,
- "testLabel",
- "testInfo",
- sendIntent,
- /* resolveInfoPresentationGetter */ null),
- serviceTargets,
- TARGET_TYPE_CHOOSER_TARGET,
- directShareToShortcutInfos)
+ // verify that ShortcutLoader was queried
+ ArgumentCaptor<DisplayResolveInfo[]> appTargets =
+ ArgumentCaptor.forClass(DisplayResolveInfo[].class);
+ verify(shortcutLoaders.get(0).first, times(1))
+ .queryShortcuts(appTargets.capture());
+
+ // send shortcuts
+ assertThat(
+ "Wrong number of app targets",
+ appTargets.getValue().length,
+ is(resolvedComponentInfos.size()));
+ List<ChooserTarget> serviceTargets = createDirectShareTargets(1,
+ resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
+ ShortcutLoader.Result result = new ShortcutLoader.Result(
+ // TODO: test another value as well
+ false,
+ appTargets.getValue(),
+ new ShortcutLoader.ShortcutResultInfo[] {
+ new ShortcutLoader.ShortcutResultInfo(
+ appTargets.getValue()[0],
+ serviceTargets
+ )
+ },
+ new HashMap<>(),
+ new HashMap<>()
);
- // Thread.sleep shouldn't be a thing in an integration test but it's
- // necessary here because of the way the code is structured
- // TODO: restructure the tests b/129870719
- Thread.sleep(((ChooserActivity) activity).mListViewUpdateDelayMs);
+ 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(ri));
+ 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();
@@ -2231,34 +2116,18 @@ public class UnbundledChooserActivityTest {
.perform(click());
waitForIdle();
- ChooserActivityLoggerFake logger =
- (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
- assertThat(logger.numCalls(), is(6));
- // first one should be SHARESHEET_TRIGGERED uievent
- assertThat(logger.get(0).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
- assertThat(logger.get(0).event.getId(),
- is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
- // second one should be SHARESHEET_STARTED event
- assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
- assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
- assertThat(logger.get(1).mimeType, is("text/plain"));
- assertThat(logger.get(1).packageName, is(
- InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageName()));
- assertThat(logger.get(1).appProvidedApp, is(0));
- assertThat(logger.get(1).appProvidedDirect, is(0));
- assertThat(logger.get(1).isWorkprofile, is(false));
- assertThat(logger.get(1).previewType, is(3));
- // third one should be SHARESHEET_APP_LOAD_COMPLETE uievent
- assertThat(logger.get(2).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
- assertThat(logger.get(2).event.getId(),
- is(ChooserActivityLogger
- .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
- // fourth and fifth are just artifacts of test set-up
- // sixth one should be ranking atom with SHARESHEET_COPY_TARGET_SELECTED event
- assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
- assertThat(logger.get(5).targetType,
- is(ChooserActivityLogger
- .SharesheetTargetSelectedEvent.SHARESHEET_SERVICE_TARGET_SELECTED.getId()));
+ ChooserActivityLogger logger = activity.getChooserActivityLogger();
+ ArgumentCaptor<Integer> typeCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(logger, times(1)).logShareTargetSelected(
+ eq(ChooserActivityLogger.SELECTION_TYPE_SERVICE),
+ /* packageName= */ any(),
+ /* positionPicked= */ anyInt(),
+ /* directTargetAlsoRanked= */ anyInt(),
+ /* numCallerProvided= */ anyInt(),
+ /* directTargetHashed= */ any(),
+ /* isPinned= */ anyBoolean(),
+ /* successfullySelected= */ anyBoolean(),
+ /* selectionCost= */ anyLong());
}
@Test @Ignore
@@ -2290,44 +2159,7 @@ public class UnbundledChooserActivityTest {
assertThat("Chooser should have no direct targets",
activity.getAdapter().getSelectableServiceTargetCount(), is(0));
- ChooserActivityLoggerFake logger =
- (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
-
// TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events.
- logger.removeCallsForUiEventsOfType(
- ChooserActivityLogger.SharesheetStandardEvent
- .SHARESHEET_DIRECT_LOAD_COMPLETE.getId());
-
- // SHARESHEET_TRIGGERED:
- assertThat(logger.event(0).getId(),
- is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
-
- // SHARESHEET_STARTED:
- assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
- assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
- assertThat(logger.get(1).mimeType, is("text/plain"));
- assertThat(logger.get(1).packageName, is(
- InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageName()));
- assertThat(logger.get(1).appProvidedApp, is(0));
- assertThat(logger.get(1).appProvidedDirect, is(0));
- assertThat(logger.get(1).isWorkprofile, is(false));
- assertThat(logger.get(1).previewType, is(3));
-
- // SHARESHEET_APP_LOAD_COMPLETE:
- assertThat(logger.event(2).getId(),
- is(ChooserActivityLogger
- .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
-
- // SHARESHEET_EMPTY_DIRECT_SHARE_ROW:
- assertThat(logger.event(3).getId(),
- is(ChooserActivityLogger
- .SharesheetStandardEvent.SHARESHEET_EMPTY_DIRECT_SHARE_ROW.getId()));
-
- // Next is just an artifact of test set-up:
- assertThat(logger.event(4).getId(),
- is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_EXPANDED.getId()));
-
- assertThat(logger.numCalls(), is(5));
}
@Ignore // b/220067877
@@ -2351,58 +2183,14 @@ public class UnbundledChooserActivityTest {
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- onView(withIdFromRuntimeResource("chooser_copy_button")).check(matches(isDisplayed()));
- onView(withIdFromRuntimeResource("chooser_copy_button")).perform(click());
-
- ChooserActivityLoggerFake logger =
- (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
+ 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.
- logger.removeCallsForUiEventsOfType(
- ChooserActivityLogger.SharesheetStandardEvent
- .SHARESHEET_DIRECT_LOAD_COMPLETE.getId());
-
- // SHARESHEET_TRIGGERED:
- assertThat(logger.event(0).getId(),
- is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
-
- // SHARESHEET_STARTED:
- assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
- assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
- assertThat(logger.get(1).mimeType, is("text/plain"));
- assertThat(logger.get(1).packageName, is(
- InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageName()));
- assertThat(logger.get(1).appProvidedApp, is(0));
- assertThat(logger.get(1).appProvidedDirect, is(0));
- assertThat(logger.get(1).isWorkprofile, is(false));
- assertThat(logger.get(1).previewType, is(3));
-
- // SHARESHEET_APP_LOAD_COMPLETE:
- assertThat(logger.event(2).getId(),
- is(ChooserActivityLogger
- .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
-
- // Next are just artifacts of test set-up:
- assertThat(logger.event(3).getId(),
- is(ChooserActivityLogger
- .SharesheetStandardEvent.SHARESHEET_EMPTY_DIRECT_SHARE_ROW.getId()));
- assertThat(logger.event(4).getId(),
- is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_EXPANDED.getId()));
-
- // SHARESHEET_COPY_TARGET_SELECTED:
- assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
- assertThat(logger.get(5).targetType,
- is(ChooserActivityLogger
- .SharesheetTargetSelectedEvent.SHARESHEET_COPY_TARGET_SELECTED.getId()));
-
- // No more events.
- assertThat(logger.numCalls(), is(6));
}
@Test @Ignore("b/222124533")
public void testSwitchProfileLogging() throws InterruptedException {
- // enable the work tab feature flag
- ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
int workProfileTargets = 4;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
@@ -2416,134 +2204,16 @@ public class UnbundledChooserActivityTest {
final IChooserWrapper activity = (IChooserWrapper)
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
waitForIdle();
- onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
+ onView(withText(R.string.resolver_work_tab)).perform(click());
waitForIdle();
- onView(withTextFromRuntimeResource("resolver_personal_tab")).perform(click());
+ onView(withText(R.string.resolver_personal_tab)).perform(click());
waitForIdle();
- ChooserActivityLoggerFake logger =
- (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
-
// TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events.
- logger.removeCallsForUiEventsOfType(
- ChooserActivityLogger.SharesheetStandardEvent
- .SHARESHEET_DIRECT_LOAD_COMPLETE.getId());
-
- // SHARESHEET_TRIGGERED:
- assertThat(logger.event(0).getId(),
- is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
-
- // SHARESHEET_STARTED:
- assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
- assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
- assertThat(logger.get(1).mimeType, is(TEST_MIME_TYPE));
- assertThat(logger.get(1).packageName, is(
- InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageName()));
- assertThat(logger.get(1).appProvidedApp, is(0));
- assertThat(logger.get(1).appProvidedDirect, is(0));
- assertThat(logger.get(1).isWorkprofile, is(false));
- assertThat(logger.get(1).previewType, is(3));
-
- // SHARESHEET_APP_LOAD_COMPLETE:
- assertThat(logger.event(2).getId(),
- is(ChooserActivityLogger
- .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
-
- // Next is just an artifact of test set-up:
- assertThat(logger.event(3).getId(),
- is(ChooserActivityLogger
- .SharesheetStandardEvent.SHARESHEET_EMPTY_DIRECT_SHARE_ROW.getId()));
-
- // SHARESHEET_PROFILE_CHANGED:
- assertThat(logger.event(4).getId(),
- is(ChooserActivityLogger.SharesheetStandardEvent
- .SHARESHEET_PROFILE_CHANGED.getId()));
-
- // Repeat the loading steps in the new profile:
-
- // SHARESHEET_APP_LOAD_COMPLETE:
- assertThat(logger.event(5).getId(),
- is(ChooserActivityLogger
- .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
-
- // Next is again an artifact of test set-up:
- assertThat(logger.event(6).getId(),
- is(ChooserActivityLogger
- .SharesheetStandardEvent.SHARESHEET_EMPTY_DIRECT_SHARE_ROW.getId()));
-
- // SHARESHEET_PROFILE_CHANGED:
- assertThat(logger.event(7).getId(),
- is(ChooserActivityLogger.SharesheetStandardEvent
- .SHARESHEET_PROFILE_CHANGED.getId()));
-
- // No more events (this profile was already loaded).
- assertThat(logger.numCalls(), is(8));
- }
-
- @Test
- public void testAutolaunch_singleTarget_wifthWorkProfileAndTabbedViewOff_noAutolaunch() {
- ResolverActivity.ENABLE_TABBED_VIEW = false;
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10);
- when(
- ChooserActivityOverrideData
- .getInstance()
- .resolverListController
- .getResolversForIntent(
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class)))
- .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
- Intent sendIntent = createSendTextIntent();
- sendIntent.setType(TEST_MIME_TYPE);
- ResolveInfo[] chosen = new ResolveInfo[1];
- ChooserActivityOverrideData.getInstance().onSafelyStartCallback = targetInfo -> {
- chosen[0] = targetInfo.getResolveInfo();
- return true;
- };
- waitForIdle();
-
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
- waitForIdle();
-
- assertTrue(chosen[0] == null);
- }
-
- @Test
- public void testAutolaunch_singleTarget_noWorkProfile_autolaunch() {
- ResolverActivity.ENABLE_TABBED_VIEW = false;
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTest(1);
- when(
- ChooserActivityOverrideData
- .getInstance()
- .resolverListController
- .getResolversForIntent(
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.anyBoolean(),
- Mockito.isA(List.class)))
- .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
- Intent sendIntent = createSendTextIntent();
- sendIntent.setType(TEST_MIME_TYPE);
- ResolveInfo[] chosen = new ResolveInfo[1];
- ChooserActivityOverrideData.getInstance().onSafelyStartCallback = targetInfo -> {
- chosen[0] = targetInfo.getResolveInfo();
- return true;
- };
- waitForIdle();
-
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
- waitForIdle();
-
- assertThat(chosen[0], is(personalResolvedComponentInfos.get(0).getResolveInfoAt(0)));
}
@Test
public void testWorkTab_onePersonalTarget_emptyStateOnWorkTarget_autolaunch() {
- // enable the work tab feature flag
- ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
int workProfileTargets = 4;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
@@ -2559,7 +2229,7 @@ public class UnbundledChooserActivityTest {
return true;
};
- mActivityRule.launchActivity(sendIntent);
+ mActivityRule.launchActivity(Intent.createChooser(sendIntent, "Test"));
waitForIdle();
assertThat(chosen[0], is(personalResolvedComponentInfos.get(1).getResolveInfoAt(0)));
@@ -2591,7 +2261,7 @@ public class UnbundledChooserActivityTest {
when(
ChooserActivityOverrideData
.getInstance().packageManager
- .resolveActivity(any(Intent.class), anyInt()))
+ .resolveActivity(any(Intent.class), any()))
.thenReturn(ri);
waitForIdle();
@@ -2605,8 +2275,6 @@ public class UnbundledChooserActivityTest {
@Test
public void testWorkTab_withInitialIntents_workTabDoesNotIncludePersonalInitialIntents() {
- // enable the work tab feature flag
- ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
int workProfileTargets = 1;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
@@ -2624,7 +2292,7 @@ public class UnbundledChooserActivityTest {
ChooserActivityOverrideData
.getInstance()
.packageManager
- .resolveActivity(any(Intent.class), anyInt()))
+ .resolveActivity(any(Intent.class), any()))
.thenReturn(createFakeResolveInfo());
waitForIdle();
@@ -2637,8 +2305,6 @@ public class UnbundledChooserActivityTest {
@Test
public void testWorkTab_xProfileIntentsDisabled_personalToWork_nonSendIntent_emptyStateShown() {
- // enable the work tab feature flag
- ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
int workProfileTargets = 4;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
@@ -2657,24 +2323,22 @@ public class UnbundledChooserActivityTest {
ChooserActivityOverrideData
.getInstance()
.packageManager
- .resolveActivity(any(Intent.class), anyInt()))
+ .resolveActivity(any(Intent.class), any()))
.thenReturn(createFakeResolveInfo());
mActivityRule.launchActivity(chooserIntent);
waitForIdle();
- onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
+ onView(withText(R.string.resolver_work_tab)).perform(click());
waitForIdle();
- onView(withIdFromRuntimeResource("contentPanel"))
+ onView(withId(com.android.internal.R.id.contentPanel))
.perform(swipeUp());
- onView(withTextFromRuntimeResource("resolver_cross_profile_blocked"))
+ onView(withText(R.string.resolver_cross_profile_blocked))
.check(matches(isDisplayed()));
}
@Test
public void testWorkTab_noWorkAppsAvailable_nonSendIntent_emptyStateShown() {
- // enable the work tab feature flag
- ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
createResolvedComponentsForTest(3);
@@ -2691,17 +2355,17 @@ public class UnbundledChooserActivityTest {
ChooserActivityOverrideData
.getInstance()
.packageManager
- .resolveActivity(any(Intent.class), anyInt()))
+ .resolveActivity(any(Intent.class), any()))
.thenReturn(createFakeResolveInfo());
mActivityRule.launchActivity(chooserIntent);
waitForIdle();
- onView(withIdFromRuntimeResource("contentPanel"))
+ onView(withId(com.android.internal.R.id.contentPanel))
.perform(swipeUp());
- onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
+ onView(withText(R.string.resolver_work_tab)).perform(click());
waitForIdle();
- onView(withTextFromRuntimeResource("resolver_no_work_apps_available"))
+ onView(withText(R.string.resolver_no_work_apps_available))
.check(matches(isDisplayed()));
}
@@ -2726,7 +2390,7 @@ public class UnbundledChooserActivityTest {
ChooserActivityOverrideData
.getInstance()
.packageManager
- .resolveActivity(any(Intent.class), anyInt()))
+ .resolveActivity(any(Intent.class), any()))
.thenReturn(ri);
waitForIdle();
@@ -2740,150 +2404,35 @@ public class UnbundledChooserActivityTest {
}
@Test
- public void testWorkTab_selectingWorkTabWithPausedWorkProfile_directShareTargetsNotQueried() {
- // enable the work tab feature flag
- ResolverActivity.ENABLE_TABBED_VIEW = true;
- markWorkProfileUserAvailable();
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
- List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(3);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- ChooserActivityOverrideData.getInstance().isQuietModeEnabled = true;
- boolean[] isQueryDirectShareCalledOnWorkProfile = new boolean[] { false };
- ChooserActivityOverrideData.getInstance().onQueryDirectShareTargets =
- chooserListAdapter -> {
- isQueryDirectShareCalledOnWorkProfile[0] =
- (chooserListAdapter.getUserHandle().getIdentifier() == 10);
- return null;
- };
- Intent sendIntent = createSendTextIntent();
- sendIntent.setType(TEST_MIME_TYPE);
-
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
- waitForIdle();
- onView(withIdFromRuntimeResource("contentPanel"))
- .perform(swipeUp());
- onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
- waitForIdle();
-
- assertFalse("Direct share targets were queried on a paused work profile",
- isQueryDirectShareCalledOnWorkProfile[0]);
- }
-
- @Test
- public void testWorkTab_selectingWorkTabWithNotRunningWorkUser_directShareTargetsNotQueried() {
- // enable the work tab feature flag
- ResolverActivity.ENABLE_TABBED_VIEW = true;
- markWorkProfileUserAvailable();
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
- List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(3);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- ChooserActivityOverrideData.getInstance().isWorkProfileUserRunning = false;
- boolean[] isQueryDirectShareCalledOnWorkProfile = new boolean[] { false };
- ChooserActivityOverrideData.getInstance().onQueryDirectShareTargets =
- chooserListAdapter -> {
- isQueryDirectShareCalledOnWorkProfile[0] =
- (chooserListAdapter.getUserHandle().getIdentifier() == 10);
- return null;
- };
- Intent sendIntent = createSendTextIntent();
- sendIntent.setType(TEST_MIME_TYPE);
-
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
- waitForIdle();
- onView(withIdFromRuntimeResource("contentPanel"))
- .perform(swipeUp());
- onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
- waitForIdle();
-
- assertFalse("Direct share targets were queried on a locked work profile user",
- isQueryDirectShareCalledOnWorkProfile[0]);
- }
-
- @Test
- public void testWorkTab_workUserNotRunning_workTargetsShown() {
- // enable the work tab feature flag
- ResolverActivity.ENABLE_TABBED_VIEW = true;
- markWorkProfileUserAvailable();
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
- List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(3);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent sendIntent = createSendTextIntent();
- sendIntent.setType(TEST_MIME_TYPE);
- ChooserActivityOverrideData.getInstance().isWorkProfileUserRunning = false;
-
- final ChooserActivity activity =
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
- final IChooserWrapper wrapper = (IChooserWrapper) activity;
- waitForIdle();
- onView(withIdFromRuntimeResource("contentPanel")).perform(swipeUp());
- onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
- waitForIdle();
-
- assertEquals(3, wrapper.getWorkListAdapter().getCount());
- }
-
- @Test
- public void testWorkTab_selectingWorkTabWithLockedWorkUser_directShareTargetsNotQueried() {
- // enable the work tab feature flag
- ResolverActivity.ENABLE_TABBED_VIEW = true;
+ public void test_query_shortcut_loader_for_the_selected_tab() {
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
List<ResolvedComponentInfo> workResolvedComponentInfos =
createResolvedComponentsForTest(3);
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- ChooserActivityOverrideData.getInstance().isWorkProfileUserUnlocked = false;
- boolean[] isQueryDirectShareCalledOnWorkProfile = new boolean[] { false };
- ChooserActivityOverrideData.getInstance().onQueryDirectShareTargets =
- chooserListAdapter -> {
- isQueryDirectShareCalledOnWorkProfile[0] =
- (chooserListAdapter.getUserHandle().getIdentifier() == 10);
- return null;
- };
+ ShortcutLoader personalProfileShortcutLoader = mock(ShortcutLoader.class);
+ ShortcutLoader workProfileShortcutLoader = mock(ShortcutLoader.class);
+ final SparseArray<ShortcutLoader> shortcutLoaders = new SparseArray<>();
+ shortcutLoaders.put(0, personalProfileShortcutLoader);
+ shortcutLoaders.put(10, workProfileShortcutLoader);
+ ChooserActivityOverrideData.getInstance().shortcutLoaderFactory =
+ (userHandle, callback) -> shortcutLoaders.get(userHandle.getIdentifier(), null);
Intent sendIntent = createSendTextIntent();
sendIntent.setType(TEST_MIME_TYPE);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
waitForIdle();
- onView(withIdFromRuntimeResource("contentPanel"))
+ onView(withId(com.android.internal.R.id.contentPanel))
.perform(swipeUp());
- onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
waitForIdle();
- assertFalse("Direct share targets were queried on a locked work profile user",
- isQueryDirectShareCalledOnWorkProfile[0]);
- }
-
- @Test
- public void testWorkTab_workUserLocked_workTargetsShown() {
- // enable the work tab feature flag
- ResolverActivity.ENABLE_TABBED_VIEW = true;
- markWorkProfileUserAvailable();
- List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
- List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(3);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
- Intent sendIntent = createSendTextIntent();
- sendIntent.setType(TEST_MIME_TYPE);
- ChooserActivityOverrideData.getInstance().isWorkProfileUserUnlocked = false;
+ verify(personalProfileShortcutLoader, times(1)).queryShortcuts(any());
- final ChooserActivity activity =
- mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
- final IChooserWrapper wrapper = (IChooserWrapper) activity;
- waitForIdle();
- onView(withIdFromRuntimeResource("contentPanel"))
- .perform(swipeUp());
- onView(withTextFromRuntimeResource("resolver_work_tab")).perform(click());
+ onView(withText(R.string.resolver_work_tab)).perform(click());
waitForIdle();
- assertEquals(3, wrapper.getWorkListAdapter().getCount());
+ verify(workProfileShortcutLoader, times(1)).queryShortcuts(any());
}
private Intent createChooserIntent(Intent intent, Intent[] initialIntents) {
@@ -3092,21 +2641,6 @@ public class UnbundledChooserActivityTest {
return shortcuts;
}
- private void assertCorrectShortcutToChooserTargetConversion(List<ShareShortcutInfo> shortcuts,
- List<ChooserTarget> chooserTargets, int[] expectedOrder, float[] expectedScores) {
- assertEquals(expectedOrder.length, chooserTargets.size());
- for (int i = 0; i < chooserTargets.size(); i++) {
- ChooserTarget ct = chooserTargets.get(i);
- ShortcutInfo si = shortcuts.get(expectedOrder[i]).getShortcutInfo();
- ComponentName cn = shortcuts.get(expectedOrder[i]).getTargetComponent();
-
- assertEquals(si.getId(), ct.getIntentExtras().getString(Intent.EXTRA_SHORTCUT_ID));
- assertEquals(si.getShortLabel(), ct.getTitle());
- assertThat(Math.abs(expectedScores[i] - ct.getScore()) < 0.000001, is(true));
- assertEquals(cn.flattenToString(), ct.getComponentName().flattenToString());
- }
- }
-
private void markWorkProfileUserAvailable() {
ChooserActivityOverrideData.getInstance().workProfileUserHandle = UserHandle.of(10);
}
@@ -3147,14 +2681,6 @@ public class UnbundledChooserActivityTest {
.thenReturn(new ArrayList<>(personalResolvedComponentInfos));
}
- private Matcher<View> withIdFromRuntimeResource(String id) {
- return withId(getRuntimeResourceId(id, "id"));
- }
-
- private Matcher<View> withTextFromRuntimeResource(String id) {
- return withText(getRuntimeResourceId(id, "string"));
- }
-
private static GridRecyclerSpanCountMatcher withGridColumnCount(int columnCount) {
return new GridRecyclerSpanCountMatcher(Matchers.is(columnCount));
}
@@ -3214,25 +2740,17 @@ public class UnbundledChooserActivityTest {
.thenReturn(targetsPerRow);
}
- // ChooserWrapperActivity inherits from the framework ChooserActivity, so if the framework
- // resources have been updated since the framework was last built/pushed, the inherited behavior
- // (which is the focus of our testing) will still be implemented in terms of the old resource
- // IDs; then when we try to assert those IDs in tests (e.g. `onView(withText(R.string.foo))`),
- // the expected values won't match. The tests can instead call this method (with the same
- // general semantics as Resources#getIdentifier() e.g. `getRuntimeResourceId("foo", "string")`)
- // to refer to the resource by that name in the runtime chooser, regardless of whether the
- // framework code on the device is up-to-date.
- // TODO: is there a better way to do this? (Other than abandoning inheritance-based DI wrapper?)
- private int getRuntimeResourceId(String name, String defType) {
- int id = -1;
- if (ChooserActivityOverrideData.getInstance().resources != null) {
- id = ChooserActivityOverrideData.getInstance().resources.getIdentifier(
- name, defType, "android");
- } else {
- id = mActivityRule.getActivity().getResources().getIdentifier(name, defType, "android");
- }
- assertThat(id, greaterThan(0));
-
- return id;
+ private SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>>
+ createShortcutLoaderFactory() {
+ SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders =
+ new SparseArray<>();
+ ChooserActivityOverrideData.getInstance().shortcutLoaderFactory =
+ (userHandle, callback) -> {
+ Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>> pair =
+ new Pair<>(mock(ShortcutLoader.class), callback);
+ shortcutLoaders.put(userHandle.getIdentifier(), pair);
+ return pair.first;
+ };
+ return shortcutLoaders;
}
}
diff --git a/java/tests/src/com/android/intentresolver/UnbundledChooserActivityWorkProfileTest.java b/java/tests/src/com/android/intentresolver/UnbundledChooserActivityWorkProfileTest.java
new file mode 100644
index 00000000..f1febed2
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/UnbundledChooserActivityWorkProfileTest.java
@@ -0,0 +1,467 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.intentresolver;
+
+import static android.testing.PollingCheck.waitFor;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.action.ViewActions.swipeUp;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.isSelected;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
+import static com.android.intentresolver.ChooserWrapperActivity.sOverrides;
+import static com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.NO_BLOCKER;
+import static com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.PERSONAL_PROFILE_ACCESS_BLOCKER;
+import static com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.PERSONAL_PROFILE_SHARE_BLOCKER;
+import static com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.WORK_PROFILE_ACCESS_BLOCKER;
+import static com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.WORK_PROFILE_SHARE_BLOCKER;
+import static com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.Tab.PERSONAL;
+import static com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.Tab.WORK;
+
+import static org.hamcrest.CoreMatchers.not;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.companion.DeviceFilter;
+import android.content.Intent;
+import android.os.UserHandle;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.espresso.NoMatchingViewException;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.intentresolver.ResolverActivity.ResolvedComponentInfo;
+import com.android.intentresolver.UnbundledChooserActivityWorkProfileTest.TestCase.Tab;
+import com.android.internal.R;
+
+import junit.framework.AssertionFailedError;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+@DeviceFilter.MediumType
+@RunWith(Parameterized.class)
+public class UnbundledChooserActivityWorkProfileTest {
+
+ private static final UserHandle PERSONAL_USER_HANDLE = InstrumentationRegistry
+ .getInstrumentation().getTargetContext().getUser();
+ private static final UserHandle WORK_USER_HANDLE = UserHandle.of(10);
+
+ @Rule
+ public ActivityTestRule<ChooserWrapperActivity> mActivityRule =
+ new ActivityTestRule<>(ChooserWrapperActivity.class, false,
+ false);
+ private final TestCase mTestCase;
+
+ public UnbundledChooserActivityWorkProfileTest(TestCase testCase) {
+ mTestCase = testCase;
+ }
+
+ @Before
+ public void cleanOverrideData() {
+ // TODO: use the other form of `adoptShellPermissionIdentity()` where we explicitly list the
+ // permissions we require (which we'll read from the manifest at runtime).
+ InstrumentationRegistry
+ .getInstrumentation()
+ .getUiAutomation()
+ .adoptShellPermissionIdentity();
+
+ sOverrides.reset();
+ }
+
+ @Test
+ public void testBlocker() {
+ setUpPersonalAndWorkComponentInfos();
+ sOverrides.hasCrossProfileIntents = mTestCase.hasCrossProfileIntents();
+ sOverrides.myUserId = mTestCase.getMyUserHandle().getIdentifier();
+
+ launchActivity(mTestCase.getIsSendAction());
+ switchToTab(mTestCase.getTab());
+
+ switch (mTestCase.getExpectedBlocker()) {
+ case NO_BLOCKER:
+ assertNoBlockerDisplayed();
+ break;
+ case PERSONAL_PROFILE_SHARE_BLOCKER:
+ assertCantSharePersonalAppsBlockerDisplayed();
+ break;
+ case WORK_PROFILE_SHARE_BLOCKER:
+ assertCantShareWorkAppsBlockerDisplayed();
+ break;
+ case PERSONAL_PROFILE_ACCESS_BLOCKER:
+ assertCantAccessPersonalAppsBlockerDisplayed();
+ break;
+ case WORK_PROFILE_ACCESS_BLOCKER:
+ assertCantAccessWorkAppsBlockerDisplayed();
+ break;
+ }
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Collection tests() {
+ return Arrays.asList(
+ new TestCase(
+ /* isSendAction= */ true,
+ /* hasCrossProfileIntents= */ true,
+ /* myUserHandle= */ WORK_USER_HANDLE,
+ /* tab= */ WORK,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ true,
+ /* hasCrossProfileIntents= */ false,
+ /* myUserHandle= */ WORK_USER_HANDLE,
+ /* tab= */ WORK,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ true,
+ /* hasCrossProfileIntents= */ true,
+ /* myUserHandle= */ PERSONAL_USER_HANDLE,
+ /* tab= */ WORK,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ true,
+ /* hasCrossProfileIntents= */ false,
+ /* myUserHandle= */ PERSONAL_USER_HANDLE,
+ /* tab= */ WORK,
+ /* expectedBlocker= */ WORK_PROFILE_SHARE_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ true,
+ /* hasCrossProfileIntents= */ true,
+ /* myUserHandle= */ WORK_USER_HANDLE,
+ /* tab= */ PERSONAL,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ true,
+ /* hasCrossProfileIntents= */ false,
+ /* myUserHandle= */ WORK_USER_HANDLE,
+ /* tab= */ PERSONAL,
+ /* expectedBlocker= */ PERSONAL_PROFILE_SHARE_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ true,
+ /* hasCrossProfileIntents= */ true,
+ /* myUserHandle= */ PERSONAL_USER_HANDLE,
+ /* tab= */ PERSONAL,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ true,
+ /* hasCrossProfileIntents= */ false,
+ /* myUserHandle= */ PERSONAL_USER_HANDLE,
+ /* tab= */ PERSONAL,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ false,
+ /* hasCrossProfileIntents= */ true,
+ /* myUserHandle= */ WORK_USER_HANDLE,
+ /* tab= */ WORK,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ false,
+ /* hasCrossProfileIntents= */ false,
+ /* myUserHandle= */ WORK_USER_HANDLE,
+ /* tab= */ WORK,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ false,
+ /* hasCrossProfileIntents= */ true,
+ /* myUserHandle= */ PERSONAL_USER_HANDLE,
+ /* tab= */ WORK,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ false,
+ /* hasCrossProfileIntents= */ false,
+ /* myUserHandle= */ PERSONAL_USER_HANDLE,
+ /* tab= */ WORK,
+ /* expectedBlocker= */ WORK_PROFILE_ACCESS_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ false,
+ /* hasCrossProfileIntents= */ true,
+ /* myUserHandle= */ WORK_USER_HANDLE,
+ /* tab= */ PERSONAL,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ false,
+ /* hasCrossProfileIntents= */ false,
+ /* myUserHandle= */ WORK_USER_HANDLE,
+ /* tab= */ PERSONAL,
+ /* expectedBlocker= */ PERSONAL_PROFILE_ACCESS_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ false,
+ /* hasCrossProfileIntents= */ true,
+ /* myUserHandle= */ PERSONAL_USER_HANDLE,
+ /* tab= */ PERSONAL,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ false,
+ /* hasCrossProfileIntents= */ false,
+ /* myUserHandle= */ PERSONAL_USER_HANDLE,
+ /* tab= */ PERSONAL,
+ /* expectedBlocker= */ NO_BLOCKER
+ )
+ );
+ }
+
+ private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
+ int numberOfResults, int userId) {
+ List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+ for (int i = 0; i < numberOfResults; i++) {
+ infoList.add(
+ ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId));
+ }
+ return infoList;
+ }
+
+ private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults) {
+ List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+ for (int i = 0; i < numberOfResults; i++) {
+ infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+ }
+ return infoList;
+ }
+
+ private void setUpPersonalAndWorkComponentInfos() {
+ markWorkProfileUserAvailable();
+ int workProfileTargets = 4;
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3,
+ /* userId */ WORK_USER_HANDLE.getIdentifier());
+ List<ResolvedComponentInfo> workResolvedComponentInfos =
+ createResolvedComponentsForTest(workProfileTargets);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ }
+
+ private void setupResolverControllers(
+ List<ResolvedComponentInfo> personalResolvedComponentInfos,
+ List<ResolvedComponentInfo> workResolvedComponentInfos) {
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class)))
+ .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+ when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+ when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class),
+ eq(UserHandle.SYSTEM)))
+ .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+ }
+
+ private void waitForIdle() {
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+
+ private void markWorkProfileUserAvailable() {
+ ChooserWrapperActivity.sOverrides.workProfileUserHandle = WORK_USER_HANDLE;
+ }
+
+ private void assertCantAccessWorkAppsBlockerDisplayed() {
+ onView(withText(R.string.resolver_cross_profile_blocked))
+ .check(matches(isDisplayed()));
+ onView(withText(R.string.resolver_cant_access_work_apps_explanation))
+ .check(matches(isDisplayed()));
+ }
+
+ private void assertCantAccessPersonalAppsBlockerDisplayed() {
+ onView(withText(R.string.resolver_cross_profile_blocked))
+ .check(matches(isDisplayed()));
+ onView(withText(R.string.resolver_cant_access_personal_apps_explanation))
+ .check(matches(isDisplayed()));
+ }
+
+ private void assertCantShareWorkAppsBlockerDisplayed() {
+ onView(withText(R.string.resolver_cross_profile_blocked))
+ .check(matches(isDisplayed()));
+ onView(withText(R.string.resolver_cant_share_with_work_apps_explanation))
+ .check(matches(isDisplayed()));
+ }
+
+ private void assertCantSharePersonalAppsBlockerDisplayed() {
+ onView(withText(R.string.resolver_cross_profile_blocked))
+ .check(matches(isDisplayed()));
+ onView(withText(R.string.resolver_cant_share_with_personal_apps_explanation))
+ .check(matches(isDisplayed()));
+ }
+
+ private void assertNoBlockerDisplayed() {
+ try {
+ onView(withText(R.string.resolver_cross_profile_blocked))
+ .check(matches(not(isDisplayed())));
+ } catch (NoMatchingViewException ignored) {
+ }
+ }
+
+ private void switchToTab(Tab tab) {
+ final int stringId = tab == Tab.WORK ? R.string.resolver_work_tab
+ : R.string.resolver_personal_tab;
+
+ waitFor(() -> {
+ onView(withText(stringId)).perform(click());
+ waitForIdle();
+
+ try {
+ onView(withText(stringId)).check(matches(isSelected()));
+ return true;
+ } catch (AssertionFailedError e) {
+ return false;
+ }
+ });
+
+ onView(withId(R.id.contentPanel))
+ .perform(swipeUp());
+ waitForIdle();
+ }
+
+ private Intent createTextIntent(boolean isSendAction) {
+ Intent sendIntent = new Intent();
+ if (isSendAction) {
+ sendIntent.setAction(Intent.ACTION_SEND);
+ }
+ sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending");
+ sendIntent.setType("text/plain");
+ return sendIntent;
+ }
+
+ private void launchActivity(boolean isSendAction) {
+ Intent sendIntent = createTextIntent(isSendAction);
+ mActivityRule.launchActivity(Intent.createChooser(sendIntent, "Test"));
+ waitForIdle();
+ }
+
+ public static class TestCase {
+ private final boolean mIsSendAction;
+ private final boolean mHasCrossProfileIntents;
+ private final UserHandle mMyUserHandle;
+ private final Tab mTab;
+ private final ExpectedBlocker mExpectedBlocker;
+
+ public enum ExpectedBlocker {
+ NO_BLOCKER,
+ PERSONAL_PROFILE_SHARE_BLOCKER,
+ WORK_PROFILE_SHARE_BLOCKER,
+ PERSONAL_PROFILE_ACCESS_BLOCKER,
+ WORK_PROFILE_ACCESS_BLOCKER
+ }
+
+ public enum Tab {
+ WORK,
+ PERSONAL
+ }
+
+ public TestCase(boolean isSendAction, boolean hasCrossProfileIntents,
+ UserHandle myUserHandle, Tab tab, ExpectedBlocker expectedBlocker) {
+ mIsSendAction = isSendAction;
+ mHasCrossProfileIntents = hasCrossProfileIntents;
+ mMyUserHandle = myUserHandle;
+ mTab = tab;
+ mExpectedBlocker = expectedBlocker;
+ }
+
+ public boolean getIsSendAction() {
+ return mIsSendAction;
+ }
+
+ public boolean hasCrossProfileIntents() {
+ return mHasCrossProfileIntents;
+ }
+
+ public UserHandle getMyUserHandle() {
+ return mMyUserHandle;
+ }
+
+ public Tab getTab() {
+ return mTab;
+ }
+
+ public ExpectedBlocker getExpectedBlocker() {
+ return mExpectedBlocker;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder("test");
+
+ if (mTab == WORK) {
+ result.append("WorkTab_");
+ } else {
+ result.append("PersonalTab_");
+ }
+
+ if (mIsSendAction) {
+ result.append("sendAction_");
+ } else {
+ result.append("notSendAction_");
+ }
+
+ if (mHasCrossProfileIntents) {
+ result.append("hasCrossProfileIntents_");
+ } else {
+ result.append("doesNotHaveCrossProfileIntents_");
+ }
+
+ if (mMyUserHandle.equals(PERSONAL_USER_HANDLE)) {
+ result.append("myUserIsPersonal_");
+ } else {
+ result.append("myUserIsWork_");
+ }
+
+ if (mExpectedBlocker == ExpectedBlocker.NO_BLOCKER) {
+ result.append("thenNoBlocker");
+ } else if (mExpectedBlocker == PERSONAL_PROFILE_ACCESS_BLOCKER) {
+ result.append("thenAccessBlockerOnPersonalProfile");
+ } else if (mExpectedBlocker == PERSONAL_PROFILE_SHARE_BLOCKER) {
+ result.append("thenShareBlockerOnPersonalProfile");
+ } else if (mExpectedBlocker == WORK_PROFILE_ACCESS_BLOCKER) {
+ result.append("thenAccessBlockerOnWorkProfile");
+ } else if (mExpectedBlocker == WORK_PROFILE_SHARE_BLOCKER) {
+ result.append("thenShareBlockerOnWorkProfile");
+ }
+
+ return result.toString();
+ }
+ }
+}
diff --git a/java/tests/src/com/android/intentresolver/chooser/TargetInfoTest.kt b/java/tests/src/com/android/intentresolver/chooser/TargetInfoTest.kt
new file mode 100644
index 00000000..7c2b07a9
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/chooser/TargetInfoTest.kt
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.intentresolver.chooser
+
+import android.app.prediction.AppTarget
+import android.app.prediction.AppTargetId
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.ResolveInfo
+import android.os.UserHandle
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.intentresolver.createChooserTarget
+import com.android.intentresolver.createShortcutInfo
+import com.android.intentresolver.mock
+import com.android.intentresolver.ResolverDataProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class TargetInfoTest {
+ private val context = InstrumentationRegistry.getInstrumentation().getContext()
+
+ @Test
+ fun testNewEmptyTargetInfo() {
+ val info = NotSelectableTargetInfo.newEmptyTargetInfo()
+ assertThat(info.isEmptyTargetInfo()).isTrue()
+ assertThat(info.isChooserTargetInfo()).isTrue() // From legacy inheritance model.
+ assertThat(info.hasDisplayIcon()).isFalse()
+ assertThat(info.getDisplayIconHolder().getDisplayIcon()).isNull()
+ }
+
+ @Test
+ fun testNewPlaceholderTargetInfo() {
+ val info = NotSelectableTargetInfo.newPlaceHolderTargetInfo(context)
+ assertThat(info.isPlaceHolderTargetInfo()).isTrue()
+ assertThat(info.isChooserTargetInfo()).isTrue() // From legacy inheritance model.
+ assertThat(info.hasDisplayIcon()).isTrue()
+ // TODO: test infrastructure isn't set up to assert anything about the icon itself.
+ }
+
+ @Test
+ fun testNewSelectableTargetInfo() {
+ val resolvedIntent = Intent()
+ val baseDisplayInfo = DisplayResolveInfo.newDisplayResolveInfo(
+ resolvedIntent,
+ ResolverDataProvider.createResolveInfo(1, 0),
+ "label",
+ "extended info",
+ resolvedIntent,
+ /* resolveInfoPresentationGetter= */ null)
+ val chooserTarget = createChooserTarget(
+ "title", 0.3f, ResolverDataProvider.createComponentName(2), "test_shortcut_id")
+ val shortcutInfo = createShortcutInfo("id", ResolverDataProvider.createComponentName(3), 3)
+ val appTarget = AppTarget(
+ AppTargetId("id"),
+ chooserTarget.componentName.packageName,
+ chooserTarget.componentName.className,
+ UserHandle.CURRENT)
+
+ val targetInfo = SelectableTargetInfo.newSelectableTargetInfo(
+ baseDisplayInfo,
+ mock(),
+ resolvedIntent,
+ chooserTarget,
+ 0.1f,
+ shortcutInfo,
+ appTarget,
+ mock(),
+ )
+ assertThat(targetInfo.isSelectableTargetInfo).isTrue()
+ assertThat(targetInfo.isChooserTargetInfo).isTrue() // From legacy inheritance model.
+ assertThat(targetInfo.displayResolveInfo).isSameInstanceAs(baseDisplayInfo)
+ assertThat(targetInfo.chooserTargetComponentName).isEqualTo(chooserTarget.componentName)
+ assertThat(targetInfo.directShareShortcutId).isEqualTo(shortcutInfo.id)
+ assertThat(targetInfo.directShareShortcutInfo).isSameInstanceAs(shortcutInfo)
+ assertThat(targetInfo.directShareAppTarget).isSameInstanceAs(appTarget)
+ assertThat(targetInfo.resolvedIntent).isSameInstanceAs(resolvedIntent)
+ // TODO: make more meaningful assertions about the behavior of a selectable target.
+ }
+
+ @Test
+ fun test_SelectableTargetInfo_componentName_no_source_info() {
+ val chooserTarget = createChooserTarget(
+ "title", 0.3f, ResolverDataProvider.createComponentName(1), "test_shortcut_id")
+ val shortcutInfo = createShortcutInfo("id", ResolverDataProvider.createComponentName(2), 3)
+ val appTarget = AppTarget(
+ AppTargetId("id"),
+ chooserTarget.componentName.packageName,
+ chooserTarget.componentName.className,
+ UserHandle.CURRENT)
+ val pkgName = "org.package"
+ val className = "MainActivity"
+ val backupResolveInfo = ResolveInfo().apply {
+ activityInfo = ActivityInfo().apply {
+ packageName = pkgName
+ name = className
+ }
+ }
+
+ val targetInfo = SelectableTargetInfo.newSelectableTargetInfo(
+ null,
+ backupResolveInfo,
+ mock(),
+ chooserTarget,
+ 0.1f,
+ shortcutInfo,
+ appTarget,
+ mock(),
+ )
+ assertThat(targetInfo.resolvedComponentName).isEqualTo(ComponentName(pkgName, className))
+ }
+
+ @Test
+ fun testNewDisplayResolveInfo() {
+ val intent = Intent(Intent.ACTION_SEND)
+ intent.putExtra(Intent.EXTRA_TEXT, "testing intent sending")
+ intent.setType("text/plain")
+
+ val resolveInfo = ResolverDataProvider.createResolveInfo(3, 0)
+
+ val targetInfo = DisplayResolveInfo.newDisplayResolveInfo(
+ intent,
+ resolveInfo,
+ "label",
+ "extended info",
+ intent,
+ /* resolveInfoPresentationGetter= */ null)
+ assertThat(targetInfo.isDisplayResolveInfo()).isTrue()
+ assertThat(targetInfo.isMultiDisplayResolveInfo()).isFalse()
+ assertThat(targetInfo.isChooserTargetInfo()).isFalse()
+ }
+
+ @Test
+ fun testNewMultiDisplayResolveInfo() {
+ val intent = Intent(Intent.ACTION_SEND)
+ intent.putExtra(Intent.EXTRA_TEXT, "testing intent sending")
+ intent.setType("text/plain")
+
+ val resolveInfo = ResolverDataProvider.createResolveInfo(3, 0)
+ val firstTargetInfo = DisplayResolveInfo.newDisplayResolveInfo(
+ intent,
+ resolveInfo,
+ "label 1",
+ "extended info 1",
+ intent,
+ /* resolveInfoPresentationGetter= */ null)
+ val secondTargetInfo = DisplayResolveInfo.newDisplayResolveInfo(
+ intent,
+ resolveInfo,
+ "label 2",
+ "extended info 2",
+ intent,
+ /* resolveInfoPresentationGetter= */ null)
+
+ val multiTargetInfo = MultiDisplayResolveInfo.newMultiDisplayResolveInfo(
+ listOf(firstTargetInfo, secondTargetInfo))
+
+ assertThat(multiTargetInfo.isMultiDisplayResolveInfo()).isTrue()
+ assertThat(multiTargetInfo.isDisplayResolveInfo()).isTrue() // From legacy inheritance.
+ assertThat(multiTargetInfo.isChooserTargetInfo()).isFalse()
+
+ assertThat(multiTargetInfo.getExtendedInfo()).isNull()
+
+ assertThat(multiTargetInfo.getAllDisplayTargets())
+ .containsExactly(firstTargetInfo, secondTargetInfo)
+
+ assertThat(multiTargetInfo.hasSelected()).isFalse()
+ assertThat(multiTargetInfo.getSelectedTarget()).isNull()
+
+ multiTargetInfo.setSelected(1)
+
+ assertThat(multiTargetInfo.hasSelected()).isTrue()
+ assertThat(multiTargetInfo.getSelectedTarget()).isEqualTo(secondTargetInfo)
+
+ // TODO: consider exercising activity-start behavior.
+ // TODO: consider exercising DisplayResolveInfo base class behavior.
+ }
+}
diff --git a/java/tests/src/com/android/intentresolver/model/AbstractResolverComparatorTest.java b/java/tests/src/com/android/intentresolver/model/AbstractResolverComparatorTest.java
new file mode 100644
index 00000000..448718cd
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/model/AbstractResolverComparatorTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.intentresolver.model;
+
+import static junit.framework.Assert.assertEquals;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ResolveInfo;
+import android.os.Message;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.intentresolver.ResolverActivity;
+
+import org.junit.Test;
+
+import java.util.List;
+
+public class AbstractResolverComparatorTest {
+
+ @Test
+ public void testPinned() {
+ ResolverActivity.ResolvedComponentInfo r1 = new ResolverActivity.ResolvedComponentInfo(
+ new ComponentName("package", "class"), new Intent(), new ResolveInfo()
+ );
+ r1.setPinned(true);
+
+ ResolverActivity.ResolvedComponentInfo r2 = new ResolverActivity.ResolvedComponentInfo(
+ new ComponentName("zackage", "zlass"), new Intent(), new ResolveInfo()
+ );
+
+ Context context = InstrumentationRegistry.getTargetContext();
+ AbstractResolverComparator comparator = getTestComparator(context);
+
+ assertEquals("Pinned ranks over unpinned", -1, comparator.compare(r1, r2));
+ assertEquals("Unpinned ranks under pinned", 1, comparator.compare(r2, r1));
+ }
+
+
+ @Test
+ public void testBothPinned() {
+ ResolveInfo pmInfo1 = new ResolveInfo();
+ pmInfo1.activityInfo = new ActivityInfo();
+ pmInfo1.activityInfo.packageName = "aaa";
+
+ ResolverActivity.ResolvedComponentInfo r1 = new ResolverActivity.ResolvedComponentInfo(
+ new ComponentName("package", "class"), new Intent(), pmInfo1);
+ r1.setPinned(true);
+
+ ResolveInfo pmInfo2 = new ResolveInfo();
+ pmInfo2.activityInfo = new ActivityInfo();
+ pmInfo2.activityInfo.packageName = "zzz";
+ ResolverActivity.ResolvedComponentInfo r2 = new ResolverActivity.ResolvedComponentInfo(
+ new ComponentName("zackage", "zlass"), new Intent(), pmInfo2);
+ r2.setPinned(true);
+
+ Context context = InstrumentationRegistry.getTargetContext();
+ AbstractResolverComparator comparator = getTestComparator(context);
+
+ assertEquals("Both pinned should rank alphabetically", -1, comparator.compare(r1, r2));
+ }
+
+ private AbstractResolverComparator getTestComparator(Context context) {
+ Intent intent = new Intent();
+
+ AbstractResolverComparator testComparator =
+ new AbstractResolverComparator(context, intent) {
+
+ @Override
+ int compare(ResolveInfo lhs, ResolveInfo rhs) {
+ // Used for testing pinning, so we should never get here --- the overrides
+ // should determine the result instead.
+ return 1;
+ }
+
+ @Override
+ void doCompute(List<ResolverActivity.ResolvedComponentInfo> targets) {}
+
+ @Override
+ public float getScore(ComponentName name) {
+ return 0;
+ }
+
+ @Override
+ void handleResultMessage(Message message) {}
+ };
+ return testComparator;
+ }
+
+}
diff --git a/java/tests/src/com/android/intentresolver/shortcuts/ShortcutLoaderTest.kt b/java/tests/src/com/android/intentresolver/shortcuts/ShortcutLoaderTest.kt
new file mode 100644
index 00000000..5756a0cd
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/shortcuts/ShortcutLoaderTest.kt
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.intentresolver.shortcuts
+
+import android.app.prediction.AppPredictor
+import android.content.ComponentName
+import android.content.Context
+import android.content.IntentFilter
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ApplicationInfoFlags
+import android.content.pm.ShortcutManager
+import android.os.UserHandle
+import android.os.UserManager
+import androidx.test.filters.SmallTest
+import com.android.intentresolver.any
+import com.android.intentresolver.chooser.DisplayResolveInfo
+import com.android.intentresolver.createAppTarget
+import com.android.intentresolver.createShareShortcutInfo
+import com.android.intentresolver.createShortcutInfo
+import com.android.intentresolver.mock
+import com.android.intentresolver.whenever
+import org.junit.Assert.assertArrayEquals
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import java.util.concurrent.Executor
+import java.util.function.Consumer
+
+@SmallTest
+class ShortcutLoaderTest {
+ private val appInfo = ApplicationInfo().apply {
+ enabled = true
+ flags = 0
+ }
+ private val pm = mock<PackageManager> {
+ whenever(getApplicationInfo(any(), any<ApplicationInfoFlags>())).thenReturn(appInfo)
+ }
+ private val context = mock<Context> {
+ whenever(packageManager).thenReturn(pm)
+ whenever(createContextAsUser(any(), anyInt())).thenReturn(this)
+ }
+ private val executor = ImmediateExecutor()
+ private val intentFilter = mock<IntentFilter>()
+ private val appPredictor = mock<ShortcutLoader.AppPredictorProxy>()
+ private val callback = mock<Consumer<ShortcutLoader.Result>>()
+
+ @Test
+ fun test_app_predictor_result() {
+ val componentName = ComponentName("pkg", "Class")
+ val appTarget = mock<DisplayResolveInfo> {
+ whenever(resolvedComponentName).thenReturn(componentName)
+ }
+ val appTargets = arrayOf(appTarget)
+ val testSubject = ShortcutLoader(
+ context,
+ appPredictor,
+ UserHandle.of(0),
+ true,
+ intentFilter,
+ executor,
+ executor,
+ callback
+ )
+
+ testSubject.queryShortcuts(appTargets)
+
+ verify(appPredictor, times(1)).requestPredictionUpdate()
+ val appPredictorCallbackCaptor = ArgumentCaptor.forClass(AppPredictor.Callback::class.java)
+ verify(appPredictor, times(1))
+ .registerPredictionUpdates(any(), appPredictorCallbackCaptor.capture())
+
+ val matchingShortcutInfo = createShortcutInfo("id-0", componentName, 1)
+ val matchingAppTarget = createAppTarget(matchingShortcutInfo)
+ val shortcuts = listOf(
+ matchingAppTarget,
+ // mismatching shortcut
+ createAppTarget(
+ createShortcutInfo("id-1", ComponentName("mismatching.pkg", "Class"), 1)
+ )
+ )
+ appPredictorCallbackCaptor.value.onTargetsAvailable(shortcuts)
+
+ val resultCaptor = ArgumentCaptor.forClass(ShortcutLoader.Result::class.java)
+ verify(callback, times(1)).accept(resultCaptor.capture())
+
+ val result = resultCaptor.value
+ assertTrue("An app predictor result is expected", result.isFromAppPredictor)
+ assertArrayEquals("Wrong input app targets in the result", appTargets, result.appTargets)
+ assertEquals("Wrong shortcut count", 1, result.shortcutsByApp.size)
+ assertEquals("Wrong app target", appTarget, result.shortcutsByApp[0].appTarget)
+ for (shortcut in result.shortcutsByApp[0].shortcuts) {
+ assertEquals(
+ "Wrong AppTarget in the cache",
+ matchingAppTarget,
+ result.directShareAppTargetCache[shortcut]
+ )
+ assertEquals(
+ "Wrong ShortcutInfo in the cache",
+ matchingShortcutInfo,
+ result.directShareShortcutInfoCache[shortcut]
+ )
+ }
+ }
+
+ @Test
+ fun test_shortcut_manager_result() {
+ val componentName = ComponentName("pkg", "Class")
+ val appTarget = mock<DisplayResolveInfo> {
+ whenever(resolvedComponentName).thenReturn(componentName)
+ }
+ val appTargets = arrayOf(appTarget)
+ val matchingShortcutInfo = createShortcutInfo("id-0", componentName, 1)
+ val shortcutManagerResult = listOf(
+ ShortcutManager.ShareShortcutInfo(matchingShortcutInfo, componentName),
+ // mismatching shortcut
+ createShareShortcutInfo("id-1", ComponentName("mismatching.pkg", "Class"), 1)
+ )
+ val shortcutManager = mock<ShortcutManager> {
+ whenever(getShareTargets(intentFilter)).thenReturn(shortcutManagerResult)
+ }
+ whenever(context.getSystemService(Context.SHORTCUT_SERVICE)).thenReturn(shortcutManager)
+ val testSubject = ShortcutLoader(
+ context,
+ null,
+ UserHandle.of(0),
+ true,
+ intentFilter,
+ executor,
+ executor,
+ callback
+ )
+
+ testSubject.queryShortcuts(appTargets)
+
+ val resultCaptor = ArgumentCaptor.forClass(ShortcutLoader.Result::class.java)
+ verify(callback, times(1)).accept(resultCaptor.capture())
+
+ val result = resultCaptor.value
+ assertFalse("An ShortcutManager result is expected", result.isFromAppPredictor)
+ assertArrayEquals("Wrong input app targets in the result", appTargets, result.appTargets)
+ assertEquals("Wrong shortcut count", 1, result.shortcutsByApp.size)
+ assertEquals("Wrong app target", appTarget, result.shortcutsByApp[0].appTarget)
+ for (shortcut in result.shortcutsByApp[0].shortcuts) {
+ assertTrue(
+ "AppTargets are not expected the cache of a ShortcutManager result",
+ result.directShareAppTargetCache.isEmpty()
+ )
+ assertEquals(
+ "Wrong ShortcutInfo in the cache",
+ matchingShortcutInfo,
+ result.directShareShortcutInfoCache[shortcut]
+ )
+ }
+ }
+
+ @Test
+ fun test_fallback_to_shortcut_manager() {
+ val componentName = ComponentName("pkg", "Class")
+ val appTarget = mock<DisplayResolveInfo> {
+ whenever(resolvedComponentName).thenReturn(componentName)
+ }
+ val appTargets = arrayOf(appTarget)
+ val matchingShortcutInfo = createShortcutInfo("id-0", componentName, 1)
+ val shortcutManagerResult = listOf(
+ ShortcutManager.ShareShortcutInfo(matchingShortcutInfo, componentName),
+ // mismatching shortcut
+ createShareShortcutInfo("id-1", ComponentName("mismatching.pkg", "Class"), 1)
+ )
+ val shortcutManager = mock<ShortcutManager> {
+ whenever(getShareTargets(intentFilter)).thenReturn(shortcutManagerResult)
+ }
+ whenever(context.getSystemService(Context.SHORTCUT_SERVICE)).thenReturn(shortcutManager)
+ val testSubject = ShortcutLoader(
+ context,
+ appPredictor,
+ UserHandle.of(0),
+ true,
+ intentFilter,
+ executor,
+ executor,
+ callback
+ )
+
+ testSubject.queryShortcuts(appTargets)
+
+ verify(appPredictor, times(1)).requestPredictionUpdate()
+ val appPredictorCallbackCaptor = ArgumentCaptor.forClass(AppPredictor.Callback::class.java)
+ verify(appPredictor, times(1))
+ .registerPredictionUpdates(any(), appPredictorCallbackCaptor.capture())
+ appPredictorCallbackCaptor.value.onTargetsAvailable(emptyList())
+
+ val resultCaptor = ArgumentCaptor.forClass(ShortcutLoader.Result::class.java)
+ verify(callback, times(1)).accept(resultCaptor.capture())
+
+ val result = resultCaptor.value
+ assertFalse("An ShortcutManager result is expected", result.isFromAppPredictor)
+ assertArrayEquals("Wrong input app targets in the result", appTargets, result.appTargets)
+ assertEquals("Wrong shortcut count", 1, result.shortcutsByApp.size)
+ assertEquals("Wrong app target", appTarget, result.shortcutsByApp[0].appTarget)
+ for (shortcut in result.shortcutsByApp[0].shortcuts) {
+ assertTrue(
+ "AppTargets are not expected the cache of a ShortcutManager result",
+ result.directShareAppTargetCache.isEmpty()
+ )
+ assertEquals(
+ "Wrong ShortcutInfo in the cache",
+ matchingShortcutInfo,
+ result.directShareShortcutInfoCache[shortcut]
+ )
+ }
+ }
+
+ @Test
+ fun test_do_not_call_services_for_not_running_work_profile() {
+ testDisabledWorkProfileDoNotCallSystem(isUserRunning = false)
+ }
+
+ @Test
+ fun test_do_not_call_services_for_locked_work_profile() {
+ testDisabledWorkProfileDoNotCallSystem(isUserUnlocked = false)
+ }
+
+ @Test
+ fun test_do_not_call_services_if_quite_mode_is_enabled_for_work_profile() {
+ testDisabledWorkProfileDoNotCallSystem(isQuietModeEnabled = true)
+ }
+
+ @Test
+ fun test_call_services_for_not_running_main_profile() {
+ testAlwaysCallSystemForMainProfile(isUserRunning = false)
+ }
+
+ @Test
+ fun test_call_services_for_locked_main_profile() {
+ testAlwaysCallSystemForMainProfile(isUserUnlocked = false)
+ }
+
+ @Test
+ fun test_call_services_if_quite_mode_is_enabled_for_main_profile() {
+ testAlwaysCallSystemForMainProfile(isQuietModeEnabled = true)
+ }
+
+ private fun testDisabledWorkProfileDoNotCallSystem(
+ isUserRunning: Boolean = true,
+ isUserUnlocked: Boolean = true,
+ isQuietModeEnabled: Boolean = false
+ ) {
+ val userHandle = UserHandle.of(10)
+ val userManager = mock<UserManager> {
+ whenever(isUserRunning(userHandle)).thenReturn(isUserRunning)
+ whenever(isUserUnlocked(userHandle)).thenReturn(isUserUnlocked)
+ whenever(isQuietModeEnabled(userHandle)).thenReturn(isQuietModeEnabled)
+ }
+ whenever(context.getSystemService(Context.USER_SERVICE)).thenReturn(userManager);
+ val appPredictor = mock<ShortcutLoader.AppPredictorProxy>()
+ val callback = mock<Consumer<ShortcutLoader.Result>>()
+ val testSubject = ShortcutLoader(
+ context,
+ appPredictor,
+ userHandle,
+ false,
+ intentFilter,
+ executor,
+ executor,
+ callback
+ )
+
+ testSubject.queryShortcuts(arrayOf<DisplayResolveInfo>(mock()))
+
+ verify(appPredictor, never()).requestPredictionUpdate()
+ }
+
+ private fun testAlwaysCallSystemForMainProfile(
+ isUserRunning: Boolean = true,
+ isUserUnlocked: Boolean = true,
+ isQuietModeEnabled: Boolean = false
+ ) {
+ val userHandle = UserHandle.of(10)
+ val userManager = mock<UserManager> {
+ whenever(isUserRunning(userHandle)).thenReturn(isUserRunning)
+ whenever(isUserUnlocked(userHandle)).thenReturn(isUserUnlocked)
+ whenever(isQuietModeEnabled(userHandle)).thenReturn(isQuietModeEnabled)
+ }
+ whenever(context.getSystemService(Context.USER_SERVICE)).thenReturn(userManager);
+ val appPredictor = mock<ShortcutLoader.AppPredictorProxy>()
+ val callback = mock<Consumer<ShortcutLoader.Result>>()
+ val testSubject = ShortcutLoader(
+ context,
+ appPredictor,
+ userHandle,
+ true,
+ intentFilter,
+ executor,
+ executor,
+ callback
+ )
+
+ testSubject.queryShortcuts(arrayOf<DisplayResolveInfo>(mock()))
+
+ verify(appPredictor, times(1)).requestPredictionUpdate()
+ }
+}
+
+private class ImmediateExecutor : Executor {
+ override fun execute(r: Runnable) {
+ r.run()
+ }
+}
diff --git a/java/tests/src/com/android/intentresolver/shortcuts/ShortcutToChooserTargetConverterTest.kt b/java/tests/src/com/android/intentresolver/shortcuts/ShortcutToChooserTargetConverterTest.kt
new file mode 100644
index 00000000..e0de005d
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/shortcuts/ShortcutToChooserTargetConverterTest.kt
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.intentresolver.shortcuts
+
+import android.app.prediction.AppTarget
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.ShortcutInfo
+import android.content.pm.ShortcutManager.ShareShortcutInfo
+import android.service.chooser.ChooserTarget
+import com.android.intentresolver.createAppTarget
+import com.android.intentresolver.createShareShortcutInfo
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Test
+
+private const val PACKAGE = "org.package"
+
+class ShortcutToChooserTargetConverterTest {
+ private val testSubject = ShortcutToChooserTargetConverter()
+ private val ranks = arrayOf(3 ,7, 1 ,3)
+ private val shortcuts = ranks
+ .foldIndexed(ArrayList<ShareShortcutInfo>(ranks.size)) { i, acc, rank ->
+ val id = i + 1
+ acc.add(
+ createShareShortcutInfo(
+ id = "id-$i",
+ componentName = ComponentName(PACKAGE, "Class$id"),
+ rank,
+ )
+ )
+ acc
+ }
+
+ @Test
+ fun testConvertToChooserTarget_predictionService() {
+ val appTargets = shortcuts.map { createAppTarget(it.shortcutInfo) }
+ val expectedOrderAllShortcuts = intArrayOf(0, 1, 2, 3)
+ val expectedScoreAllShortcuts = floatArrayOf(1.0f, 0.99f, 0.98f, 0.97f)
+ val appTargetCache = HashMap<ChooserTarget, AppTarget>()
+ val shortcutInfoCache = HashMap<ChooserTarget, ShortcutInfo>()
+
+ var chooserTargets = testSubject.convertToChooserTarget(
+ shortcuts,
+ shortcuts,
+ appTargets,
+ appTargetCache,
+ shortcutInfoCache,
+ )
+
+ assertCorrectShortcutToChooserTargetConversion(
+ shortcuts,
+ chooserTargets,
+ expectedOrderAllShortcuts,
+ expectedScoreAllShortcuts,
+ )
+ assertAppTargetCache(chooserTargets, appTargetCache)
+ assertShortcutInfoCache(chooserTargets, shortcutInfoCache)
+
+ val subset = shortcuts.subList(1, shortcuts.size)
+ val expectedOrderSubset = intArrayOf(1, 2, 3)
+ val expectedScoreSubset = floatArrayOf(0.99f, 0.98f, 0.97f)
+ appTargetCache.clear()
+ shortcutInfoCache.clear()
+
+ chooserTargets = testSubject.convertToChooserTarget(
+ subset,
+ shortcuts,
+ appTargets,
+ appTargetCache,
+ shortcutInfoCache,
+ )
+
+ assertCorrectShortcutToChooserTargetConversion(
+ shortcuts,
+ chooserTargets,
+ expectedOrderSubset,
+ expectedScoreSubset,
+ )
+ assertAppTargetCache(chooserTargets, appTargetCache)
+ assertShortcutInfoCache(chooserTargets, shortcutInfoCache)
+ }
+
+ @Test
+ fun testConvertToChooserTarget_shortcutManager() {
+ val testSubject = ShortcutToChooserTargetConverter()
+ val expectedOrderAllShortcuts = intArrayOf(2, 0, 3, 1)
+ val expectedScoreAllShortcuts = floatArrayOf(1.0f, 0.99f, 0.99f, 0.98f)
+ val shortcutInfoCache = HashMap<ChooserTarget, ShortcutInfo>()
+
+ var chooserTargets = testSubject.convertToChooserTarget(
+ shortcuts,
+ shortcuts,
+ null,
+ null,
+ shortcutInfoCache,
+ )
+
+ assertCorrectShortcutToChooserTargetConversion(
+ shortcuts, chooserTargets,
+ expectedOrderAllShortcuts, expectedScoreAllShortcuts
+ )
+ assertShortcutInfoCache(chooserTargets, shortcutInfoCache)
+
+ val subset: MutableList<ShareShortcutInfo> = java.util.ArrayList()
+ subset.add(shortcuts[1])
+ subset.add(shortcuts[2])
+ subset.add(shortcuts[3])
+ val expectedOrderSubset = intArrayOf(2, 3, 1)
+ val expectedScoreSubset = floatArrayOf(1.0f, 0.99f, 0.98f)
+ shortcutInfoCache.clear()
+
+ chooserTargets = testSubject.convertToChooserTarget(
+ subset,
+ shortcuts,
+ null,
+ null,
+ shortcutInfoCache,
+ )
+
+ assertCorrectShortcutToChooserTargetConversion(
+ shortcuts, chooserTargets,
+ expectedOrderSubset, expectedScoreSubset
+ )
+ assertShortcutInfoCache(chooserTargets, shortcutInfoCache)
+ }
+
+ private fun assertCorrectShortcutToChooserTargetConversion(
+ shortcuts: List<ShareShortcutInfo>,
+ chooserTargets: List<ChooserTarget>,
+ expectedOrder: IntArray,
+ expectedScores: FloatArray,
+ ) {
+ assertEquals("Unexpected ChooserTarget count", expectedOrder.size, chooserTargets.size)
+ for (i in chooserTargets.indices) {
+ val ct = chooserTargets[i]
+ val si = shortcuts[expectedOrder[i]].shortcutInfo
+ val cn = shortcuts[expectedOrder[i]].targetComponent
+ assertEquals(si.id, ct.intentExtras.getString(Intent.EXTRA_SHORTCUT_ID))
+ assertEquals(si.label, ct.title)
+ assertEquals(expectedScores[i], ct.score)
+ assertEquals(cn, ct.componentName)
+ }
+ }
+
+ private fun assertAppTargetCache(
+ chooserTargets: List<ChooserTarget>, cache: Map<ChooserTarget, AppTarget>
+ ) {
+ for (ct in chooserTargets) {
+ val target = cache[ct]
+ assertNotNull("AppTarget is missing", target)
+ }
+ }
+
+ private fun assertShortcutInfoCache(
+ chooserTargets: List<ChooserTarget>, cache: Map<ChooserTarget, ShortcutInfo>
+ ) {
+ for (ct in chooserTargets) {
+ val si = cache[ct]
+ assertNotNull("AppTarget is missing", si)
+ }
+ }
+}