diff options
| author | 2018-11-16 17:57:15 +0800 | |
|---|---|---|
| committer | 2018-12-18 11:20:26 +0800 | |
| commit | 2ef71f753ca50398096cb2b6acd1353513aed32f (patch) | |
| tree | 5ad8bafe51551337540be1170baa483d164cd5ae | |
| parent | 6dd78526709392cd9ac82afbfb716f61f20a6206 (diff) | |
Add secondary launcher mechanism (1/3)
Add a new config value for recording the component name of secondary
launcher.
This secondary launcher with corresponding launch mode set in
AndroidManifest could be used on secondary displays that support system
decorations.
OEMs can easily replace their own secondary launcher by overlay it.
Bug: 118206886
Bug: 111363427
Test: atest RootActivityContainerTests
Test: atest ActivityManagerMultiDisplayTests
Change-Id: Iceab096fd369127231f2085313ee617c7cdea226
| -rw-r--r-- | api/current.txt | 1 | ||||
| -rw-r--r-- | core/java/android/content/Intent.java | 5 | ||||
| -rw-r--r-- | core/res/res/values/config.xml | 7 | ||||
| -rw-r--r-- | core/res/res/values/symbols.xml | 3 | ||||
| -rw-r--r-- | services/core/java/com/android/server/wm/ActivityRecord.java | 4 | ||||
| -rw-r--r-- | services/core/java/com/android/server/wm/ActivityTaskManagerService.java | 25 | ||||
| -rw-r--r-- | services/core/java/com/android/server/wm/RootActivityContainer.java | 129 | ||||
| -rw-r--r-- | services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java | 178 |
8 files changed, 307 insertions, 45 deletions
diff --git a/api/current.txt b/api/current.txt index 4db12eafc88c..a22fa59f4113 100644 --- a/api/current.txt +++ b/api/current.txt @@ -10268,6 +10268,7 @@ package android.content { field public static final java.lang.String CATEGORY_OPENABLE = "android.intent.category.OPENABLE"; field public static final java.lang.String CATEGORY_PREFERENCE = "android.intent.category.PREFERENCE"; field public static final java.lang.String CATEGORY_SAMPLE_CODE = "android.intent.category.SAMPLE_CODE"; + field public static final java.lang.String CATEGORY_SECONDARY_HOME = "android.intent.category.SECONDARY_HOME"; field public static final java.lang.String CATEGORY_SELECTED_ALTERNATIVE = "android.intent.category.SELECTED_ALTERNATIVE"; field public static final java.lang.String CATEGORY_TAB = "android.intent.category.TAB"; field public static final java.lang.String CATEGORY_TEST = "android.intent.category.TEST"; diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 2f0618cf64b3..d5c6c63243f6 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -4222,6 +4222,11 @@ public class Intent implements Parcelable, Cloneable { @SdkConstant(SdkConstantType.INTENT_CATEGORY) public static final String CATEGORY_HOME_MAIN = "android.intent.category.HOME_MAIN"; /** + * The home activity shown on secondary displays that support showing home activities. + */ + @SdkConstant(SdkConstantType.INTENT_CATEGORY) + public static final String CATEGORY_SECONDARY_HOME = "android.intent.category.SECONDARY_HOME"; + /** * This is the setup wizard activity, that is the first activity that is displayed * when the user sets up the device for the first time. * @hide diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index dd0b1ee83e14..7e567188a368 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3646,4 +3646,11 @@ <!-- Component name for the default module metadata provider on this device --> <string name="config_defaultModuleMetadataProvider">com.android.modulemetadata</string> + + <!-- This is the default launcher component to use on secondary displays that support system + decorations. + This launcher activity must support multiple instances and have corresponding launch mode + set in AndroidManifest. + {@see android.view.Display#FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS} --> + <string name="config_secondaryHomeComponent" translatable="false">com.android.launcher3/com.android.launcher3.SecondaryDisplayLauncher</string> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 87fdc1fb575b..907895b6a7b6 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3522,4 +3522,7 @@ <java-symbol type="dimen" name="rounded_corner_radius_bottom" /> <java-symbol type="string" name="config_defaultModuleMetadataProvider" /> + + <!-- For Secondary Launcher --> + <java-symbol type="string" name="config_secondaryHomeComponent" /> </resources> diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 5f00bcc26984..4d8440a899a3 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -42,6 +42,7 @@ import static android.app.WindowConfiguration.activityTypeToString; import static android.content.Intent.ACTION_MAIN; import static android.content.Intent.CATEGORY_HOME; import static android.content.Intent.CATEGORY_LAUNCHER; +import static android.content.Intent.CATEGORY_SECONDARY_HOME; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_NO_HISTORY; import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION; @@ -1178,7 +1179,8 @@ final class ActivityRecord extends ConfigurationContainer { private boolean isHomeIntent(Intent intent) { return ACTION_MAIN.equals(intent.getAction()) - && intent.hasCategory(CATEGORY_HOME) + && (intent.hasCategory(CATEGORY_HOME) + || intent.hasCategory(CATEGORY_SECONDARY_HOME)) && intent.getCategories().size() == 1 && intent.getData() == null && intent.getType() == null; diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 986115726efb..f662d0cc7f23 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -5475,6 +5475,31 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return intent; } + /** + * Return the intent set with {@link Intent#CATEGORY_SECONDARY_HOME} to resolve secondary home + * activities. + * + * @param preferredPackage Specify a preferred package name, otherwise use secondary home + * component defined in config_secondaryHomeComponent. + * @return the intent set with {@link Intent#CATEGORY_SECONDARY_HOME} + */ + Intent getSecondaryHomeIntent(String preferredPackage) { + final Intent intent = new Intent(mTopAction, mTopData != null ? Uri.parse(mTopData) : null); + if (preferredPackage == null) { + // Using the component stored in config if no package name. + final String secondaryHomeComponent = mContext.getResources().getString( + com.android.internal.R.string.config_secondaryHomeComponent); + intent.setComponent(ComponentName.unflattenFromString(secondaryHomeComponent)); + } else { + intent.setPackage(preferredPackage); + } + intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING); + if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) { + intent.addCategory(Intent.CATEGORY_SECONDARY_HOME); + } + return intent; + } + ApplicationInfo getAppInfoForUser(ApplicationInfo info, int userId) { if (info == null) return null; ApplicationInfo newInfo = new ApplicationInfo(info); diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java index d0144fdf670a..8ec97c5117f1 100644 --- a/services/core/java/com/android/server/wm/RootActivityContainer.java +++ b/services/core/java/com/android/server/wm/RootActivityContainer.java @@ -90,17 +90,18 @@ import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerInternal; import android.hardware.power.V1_0.PowerHint; -import android.os.Build; import android.os.FactoryTest; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; +import android.provider.Settings; import android.service.voice.IVoiceInteractionSession; import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.IntArray; +import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; @@ -110,6 +111,7 @@ import android.view.Display; import android.view.DisplayInfo; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.app.ResolverActivity; import com.android.server.LocalServices; import com.android.server.am.ActivityManagerService; import com.android.server.am.AppTimeTracker; @@ -346,35 +348,53 @@ class RootActivityContainer extends ConfigurationContainer } /** - * This starts home activity on displays that can have system decorations and only if the - * home activity can have multiple instances. + * This starts home activity on displays that can have system decorations based on displayId - + * Default display always use primary home component. + * For Secondary displays, the home activity must have category SECONDARY_HOME and then resolves + * according to the priorities listed below. + * - If default home is not set, always use the secondary home defined in the config. + * - Use currently selected primary home activity. + * - Use the activity in the same package as currently selected primary home activity. + * If there are multiple activities matched, use first one. + * - Use the secondary home defined in the config. */ boolean startHomeOnDisplay(int userId, String reason, int displayId) { - final Intent homeIntent = mService.getHomeIntent(); - final ActivityInfo aInfo = resolveHomeActivity(userId, homeIntent); + Intent homeIntent; + ActivityInfo aInfo; + if (displayId == DEFAULT_DISPLAY) { + homeIntent = mService.getHomeIntent(); + aInfo = resolveHomeActivity(userId, homeIntent); + } else { + Pair<ActivityInfo, Intent> info = resolveSecondaryHomeActivity(userId, displayId); + aInfo = info.first; + homeIntent = info.second; + } if (aInfo == null) { return false; } - if (!canStartHomeOnDisplay(aInfo, displayId, - false /* allowInstrumenting */)) { + if (!canStartHomeOnDisplay(aInfo, displayId, false /* allowInstrumenting */)) { return false; } + // Updates the home component of the intent. + homeIntent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name)); + homeIntent.setFlags(homeIntent.getFlags() | FLAG_ACTIVITY_NEW_TASK); // Update the reason for ANR debugging to verify if the user activity is the one that // actually launched. final String myReason = reason + ":" + userId + ":" + UserHandle.getUserId( - aInfo.applicationInfo.uid); + aInfo.applicationInfo.uid) + ":" + displayId; mService.getActivityStartController().startHomeActivity(homeIntent, aInfo, myReason, displayId); return true; } /** - * This resolves the home activity info and updates the home component of the given intent. + * This resolves the home activity info. * @return the home activity info if any. */ - private ActivityInfo resolveHomeActivity(int userId, Intent homeIntent) { + @VisibleForTesting + ActivityInfo resolveHomeActivity(int userId, Intent homeIntent) { final int flags = ActivityManagerService.STOCK_PM_FLAGS; final ComponentName comp = homeIntent.getComponent(); ActivityInfo aInfo = null; @@ -400,13 +420,82 @@ class RootActivityContainer extends ConfigurationContainer return null; } - homeIntent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name)); aInfo = new ActivityInfo(aInfo); aInfo.applicationInfo = mService.getAppInfoForUser(aInfo.applicationInfo, userId); - homeIntent.setFlags(homeIntent.getFlags() | FLAG_ACTIVITY_NEW_TASK); return aInfo; } + @VisibleForTesting + Pair<ActivityInfo, Intent> resolveSecondaryHomeActivity(int userId, int displayId) { + if (displayId == DEFAULT_DISPLAY) { + throw new IllegalArgumentException( + "resolveSecondaryHomeActivity: Should not be DEFAULT_DISPLAY"); + } + // Resolve activities in the same package as currently selected primary home activity. + Intent homeIntent = mService.getHomeIntent(); + ActivityInfo aInfo = resolveHomeActivity(userId, homeIntent); + if (aInfo != null) { + if (ResolverActivity.class.getName().equals(aInfo.name)) { + // Always fallback to secondary home component if default home is not set. + aInfo = null; + } else { + // Look for secondary home activities in the currently selected default home + // package. + homeIntent = mService.getSecondaryHomeIntent(aInfo.applicationInfo.packageName); + final List<ResolveInfo> resolutions = resolveActivities(userId, homeIntent); + final int size = resolutions.size(); + final String targetName = aInfo.name; + aInfo = null; + for (int i = 0; i < size; i++) { + ResolveInfo resolveInfo = resolutions.get(i); + // We need to traverse all resolutions to check if the currently selected + // default home activity is present. + if (resolveInfo.activityInfo.name.equals(targetName)) { + aInfo = resolveInfo.activityInfo; + break; + } + } + if (aInfo == null && size > 0) { + // First one is the best. + aInfo = resolutions.get(0).activityInfo; + } + } + } + + if (aInfo != null) { + if (!canStartHomeOnDisplay(aInfo, displayId, false /* allowInstrumenting */)) { + aInfo = null; + } + } + + // Fallback to secondary home component. + if (aInfo == null) { + homeIntent = mService.getSecondaryHomeIntent(null); + aInfo = resolveHomeActivity(userId, homeIntent); + } + return Pair.create(aInfo, homeIntent); + } + + /** + * Retrieve all activities that match the given intent. + * The list should already ordered from best to worst matched. + * {@link android.content.pm.PackageManager#queryIntentActivities} + */ + @VisibleForTesting + List<ResolveInfo> resolveActivities(int userId, Intent homeIntent) { + List<ResolveInfo> resolutions; + try { + final String resolvedType = + homeIntent.resolveTypeIfNeeded(mService.mContext.getContentResolver()); + resolutions = AppGlobals.getPackageManager().queryIntentActivities(homeIntent, + resolvedType, ActivityManagerService.STOCK_PM_FLAGS, userId).getList(); + + } catch (RemoteException e) { + resolutions = new ArrayList<>(); + } + return resolutions; + } + boolean resumeHomeActivity(ActivityRecord prev, String reason, int displayId) { if (!mService.isBooting() && !mService.isBooted()) { // Not ready yet! @@ -457,6 +546,14 @@ class RootActivityContainer extends ConfigurationContainer return true; } + final boolean deviceProvisioned = Settings.Global.getInt( + mService.mContext.getContentResolver(), + Settings.Global.DEVICE_PROVISIONED, 0) != 0; + if (displayId != DEFAULT_DISPLAY && displayId != INVALID_DISPLAY && !deviceProvisioned) { + // Can't launch home on secondary display before device is provisioned. + return false; + } + final ActivityDisplay display = getActivityDisplay(displayId); if (display == null || display.isRemoved() || !display.supportsSystemDecorations()) { // Can't launch home on display that doesn't support system decorations. @@ -464,13 +561,9 @@ class RootActivityContainer extends ConfigurationContainer } final boolean supportMultipleInstance = homeInfo.launchMode != LAUNCH_SINGLE_TASK - && homeInfo.launchMode != LAUNCH_SINGLE_INSTANCE - && homeInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.Q; + && homeInfo.launchMode != LAUNCH_SINGLE_INSTANCE; if (!supportMultipleInstance) { - // Can't launch home on other displays if it requested to be single instance. Also we - // don't allow home applications that target before Q to have multiple home activity - // instances because they may not be expected to have multiple home scenario and - // haven't explicitly request for single instance. + // Can't launch home on secondary displays if it requested to be single instance. return false; } diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java index 9b18388b5305..58302d6b8b75 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java @@ -23,8 +23,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE; -import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE; import static android.view.Display.DEFAULT_DISPLAY; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; @@ -36,7 +36,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.wm.ActivityDisplay.POSITION_TOP; import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_DESTROYING; import static com.android.server.wm.RootActivityContainer.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE; -import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -47,18 +47,27 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.contains; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.refEq; import android.app.ActivityOptions; +import android.content.ComponentName; +import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.ResolveInfo; import android.graphics.Rect; -import android.os.Build; import android.platform.test.annotations.Presubmit; +import android.util.Pair; + import androidx.test.filters.MediumTest; + +import com.android.internal.app.ResolverActivity; + import org.junit.Before; import org.junit.Test; import java.util.ArrayList; +import java.util.List; /** * Tests for the {@link ActivityStackSupervisor} class. @@ -385,31 +394,10 @@ public class RootActivityContainerTests extends ActivityTestsBase { } /** - * Tests home activities that targeted sdk before Q cannot start on secondary display. - */ - @Test - public void testStartHomeTargetSdkBeforeQ() throws Exception { - final TestActivityDisplay secondDisplay = spy(createNewActivityDisplay()); - mRootActivityContainer.addChild(secondDisplay, POSITION_TOP); - doReturn(true).when(secondDisplay).supportsSystemDecorations(); - - final ActivityInfo info = new ActivityInfo(); - info.launchMode = LAUNCH_MULTIPLE; - info.applicationInfo = new ApplicationInfo(); - info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.Q; - assertTrue(mRootActivityContainer.canStartHomeOnDisplay(info, secondDisplay.mDisplayId, - false /* allowInstrumenting */)); - - info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.P; - assertFalse(mRootActivityContainer.canStartHomeOnDisplay(info, secondDisplay.mDisplayId, - false /* allowInstrumenting */)); - } - - /** * Tests that home activities can be started on the displays that supports system decorations. */ - @Test - public void testStartHomeOnAllDisplays() { + // TODO (b/118206886): Will add it back once launcher's patch is merged into master. + private void testStartHomeOnAllDisplays() { // Create secondary displays. final TestActivityDisplay secondDisplay = spy(createNewActivityDisplay()); mRootActivityContainer.addChild(secondDisplay, POSITION_TOP); @@ -477,4 +465,142 @@ public class RootActivityContainerTests extends ActivityTestsBase { assertTrue(mRootActivityContainer.canStartHomeOnDisplay(info, DEFAULT_DISPLAY, true /* allowInstrumenting*/)); } + + /** + * Tests that secondary home should be selected if default home not set. + */ + @Test + public void testResolveSecondaryHomeActivityWhenDefaultHomeNotSet() { + final Intent defaultHomeIntent = mService.getHomeIntent(); + final ActivityInfo aInfoDefault = new ActivityInfo(); + aInfoDefault.name = ResolverActivity.class.getName(); + doReturn(aInfoDefault).when(mRootActivityContainer).resolveHomeActivity(anyInt(), + refEq(defaultHomeIntent)); + + final String secondaryHomeComponent = mService.mContext.getResources().getString( + com.android.internal.R.string.config_secondaryHomeComponent); + final ComponentName comp = ComponentName.unflattenFromString(secondaryHomeComponent); + final Intent secondaryHomeIntent = mService.getSecondaryHomeIntent(null); + final ActivityInfo aInfoSecondary = new ActivityInfo(); + aInfoSecondary.name = comp.getClassName(); + doReturn(aInfoSecondary).when(mRootActivityContainer).resolveHomeActivity(anyInt(), + refEq(secondaryHomeIntent)); + + // Should fallback to secondary home if default home not set. + final Pair<ActivityInfo, Intent> resolvedInfo = mRootActivityContainer + .resolveSecondaryHomeActivity(0 /* userId */, 1 /* displayId */); + + assertEquals(comp.getClassName(), resolvedInfo.first.name); + } + + /** + * Tests that secondary home should be selected if default home not support secondary displays + * or there is no matched activity in the same package as selected default home. + */ + @Test + public void testResolveSecondaryHomeActivityWhenDefaultHomeNotSupportMultiDisplay() { + final Intent defaultHomeIntent = mService.getHomeIntent(); + final ActivityInfo aInfoDefault = new ActivityInfo(); + aInfoDefault.name = "fakeHomeActivity"; + aInfoDefault.applicationInfo = new ApplicationInfo(); + aInfoDefault.applicationInfo.packageName = "fakeHomePackage"; + doReturn(aInfoDefault).when(mRootActivityContainer).resolveHomeActivity(anyInt(), + refEq(defaultHomeIntent)); + + final List<ResolveInfo> resolutions = new ArrayList<>(); + doReturn(resolutions).when(mRootActivityContainer).resolveActivities(anyInt(), any()); + + final String secondaryHomeComponent = mService.mContext.getResources().getString( + com.android.internal.R.string.config_secondaryHomeComponent); + final ComponentName comp = ComponentName.unflattenFromString(secondaryHomeComponent); + final Intent secondaryHomeIntent = mService.getSecondaryHomeIntent(null); + final ActivityInfo aInfoSecondary = new ActivityInfo(); + aInfoSecondary.name = comp.getClassName(); + doReturn(aInfoSecondary).when(mRootActivityContainer).resolveHomeActivity(anyInt(), + refEq(secondaryHomeIntent)); + + // Should fallback to secondary home if selected default home not support secondary displays + // or there is no matched activity in the same package as selected default home. + final Pair<ActivityInfo, Intent> resolvedInfo = mRootActivityContainer + .resolveSecondaryHomeActivity(0 /* userId */, 1 /* displayId */); + + assertEquals(comp.getClassName(), resolvedInfo.first.name); + } + + /** + * Tests that default home activity should be selected if it already support secondary displays. + */ + @Test + public void testResolveSecondaryHomeActivityWhenDefaultHomeSupportMultiDisplay() { + final Intent homeIntent = mService.getHomeIntent(); + final ActivityInfo aInfoDefault = new ActivityInfo(); + aInfoDefault.name = "fakeHomeActivity"; + aInfoDefault.applicationInfo = new ApplicationInfo(); + aInfoDefault.applicationInfo.packageName = "fakeHomePackage"; + doReturn(aInfoDefault).when(mRootActivityContainer).resolveHomeActivity(anyInt(), + refEq(homeIntent)); + + final List<ResolveInfo> resolutions = new ArrayList<>(); + final ResolveInfo infoFake1 = new ResolveInfo(); + infoFake1.activityInfo = new ActivityInfo(); + infoFake1.activityInfo.name = "fakeActivity1"; + infoFake1.activityInfo.applicationInfo = new ApplicationInfo(); + infoFake1.activityInfo.applicationInfo.packageName = "fakePackage1"; + final ResolveInfo infoFake2 = new ResolveInfo(); + infoFake2.activityInfo = aInfoDefault; + resolutions.add(infoFake1); + resolutions.add(infoFake2); + doReturn(resolutions).when(mRootActivityContainer).resolveActivities(anyInt(), any()); + + doReturn(true).when(mRootActivityContainer).canStartHomeOnDisplay( + any(), anyInt(), anyBoolean()); + + // Use default home activity if it support secondary displays. + final Pair<ActivityInfo, Intent> resolvedInfo = mRootActivityContainer + .resolveSecondaryHomeActivity(0 /* userId */, 1 /* displayId */); + + assertEquals(aInfoDefault.applicationInfo.packageName, + resolvedInfo.first.applicationInfo.packageName); + assertEquals(aInfoDefault.name, resolvedInfo.first.name); + } + + /** + * Tests that the first one that matches should be selected if there are multiple activities. + */ + @Test + public void testResolveSecondaryHomeActivityWhenOtherActivitySupportMultiDisplay() { + final Intent homeIntent = mService.getHomeIntent(); + final ActivityInfo aInfoDefault = new ActivityInfo(); + aInfoDefault.name = "fakeHomeActivity"; + aInfoDefault.applicationInfo = new ApplicationInfo(); + aInfoDefault.applicationInfo.packageName = "fakeHomePackage"; + doReturn(aInfoDefault).when(mRootActivityContainer).resolveHomeActivity(anyInt(), + refEq(homeIntent)); + + final List<ResolveInfo> resolutions = new ArrayList<>(); + final ResolveInfo infoFake1 = new ResolveInfo(); + infoFake1.activityInfo = new ActivityInfo(); + infoFake1.activityInfo.name = "fakeActivity1"; + infoFake1.activityInfo.applicationInfo = new ApplicationInfo(); + infoFake1.activityInfo.applicationInfo.packageName = "fakePackage1"; + final ResolveInfo infoFake2 = new ResolveInfo(); + infoFake2.activityInfo = new ActivityInfo(); + infoFake2.activityInfo.name = "fakeActivity2"; + infoFake2.activityInfo.applicationInfo = new ApplicationInfo(); + infoFake2.activityInfo.applicationInfo.packageName = "fakePackage2"; + resolutions.add(infoFake1); + resolutions.add(infoFake2); + doReturn(resolutions).when(mRootActivityContainer).resolveActivities(anyInt(), any()); + + doReturn(true).when(mRootActivityContainer).canStartHomeOnDisplay( + any(), anyInt(), anyBoolean()); + + // Use the first one of matched activities in the same package as selected default home. + final Pair<ActivityInfo, Intent> resolvedInfo = mRootActivityContainer + .resolveSecondaryHomeActivity(0 /* userId */, 1 /* displayId */); + + assertEquals(infoFake1.activityInfo.applicationInfo.packageName, + resolvedInfo.first.applicationInfo.packageName); + assertEquals(infoFake1.activityInfo.name, resolvedInfo.first.name); + } } |