Merge "Check preferred display for matching existing activity"

This commit is contained in:
TreeHugger Robot
2018-09-18 06:18:38 +00:00
committed by Android (Google) Code Review
9 changed files with 187 additions and 71 deletions

View File

@@ -126,11 +126,6 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack>
private final FindTaskResult mTmpFindTaskResult = new FindTaskResult();
@VisibleForTesting
ActivityDisplay(ActivityStackSupervisor supervisor, int displayId) {
this(supervisor, supervisor.mDisplayManager.getDisplay(displayId));
}
ActivityDisplay(ActivityStackSupervisor supervisor, Display display) {
mSupervisor = supervisor;
mDisplayId = display.getDisplayId();

View File

@@ -52,6 +52,7 @@ import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TOP;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONFIGURATION;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOCUS;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
@@ -1901,14 +1902,20 @@ class ActivityStarter {
// except... well, with SINGLE_TASK_LAUNCH it's not entirely clear. We'd like to have
// the same behavior as if a new instance was being started, which means not bringing it
// to the front if the caller is not itself in the front.
final ActivityStack focusStack = mSupervisor.getTopDisplayFocusedStack();
ActivityRecord curTop = (focusStack == null)
? null : focusStack.topRunningNonDelayedActivityLocked(mNotTop);
final boolean differentTopTask;
if (mPreferredDisplayId == mTargetStack.mDisplayId) {
final ActivityStack focusStack = mTargetStack.getDisplay().getFocusedStack();
final ActivityRecord curTop = (focusStack == null)
? null : focusStack.topRunningNonDelayedActivityLocked(mNotTop);
final TaskRecord topTask = curTop != null ? curTop.getTask() : null;
differentTopTask = topTask != null
&& (topTask != intentActivity.getTask() || topTask != focusStack.topTask());
} else {
// The existing task should always be different from those in other displays.
differentTopTask = true;
}
final TaskRecord topTask = curTop != null ? curTop.getTask() : null;
if (topTask != null
&& (topTask != intentActivity.getTask() || topTask != focusStack.topTask())
&& !mAvoidMoveToFront) {
if (differentTopTask && !mAvoidMoveToFront) {
mStartActivity.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
if (mSourceRecord == null || (mSourceStack.getTopActivity() != null &&
mSourceStack.getTopActivity().getTask() == mSourceRecord.getTask())) {

View File

@@ -69,7 +69,6 @@ import org.mockito.invocation.InvocationOnMock;
@Presubmit
@RunWith(AndroidJUnit4.class)
public class ActivityRecordTests extends ActivityTestsBase {
private ActivityTaskManagerService mService;
private TestActivityStack mStack;
private TaskRecord mTask;
private ActivityRecord mActivity;
@@ -79,10 +78,10 @@ public class ActivityRecordTests extends ActivityTestsBase {
public void setUp() throws Exception {
super.setUp();
mService = createActivityTaskManagerService();
mStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
setupActivityTaskManagerService();
mStack = mSupervisor.getDefaultDisplay().createStack(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
mTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build();
mTask = new TaskBuilder(mSupervisor).setStack(mStack).build();
mActivity = new ActivityBuilder(mService).setTask(mTask).build();
}

View File

@@ -64,8 +64,6 @@ import java.util.ArrayList;
@Presubmit
@RunWith(AndroidJUnit4.class)
public class ActivityStackSupervisorTests extends ActivityTestsBase {
private ActivityTaskManagerService mService;
private ActivityStackSupervisor mSupervisor;
private ActivityStack mFullscreenStack;
@Before
@@ -73,8 +71,7 @@ public class ActivityStackSupervisorTests extends ActivityTestsBase {
public void setUp() throws Exception {
super.setUp();
mService = createActivityTaskManagerService();
mSupervisor = mService.mStackSupervisor;
setupActivityTaskManagerService();
mFullscreenStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
}

View File

@@ -60,8 +60,6 @@ import org.junit.runner.RunWith;
@Presubmit
@RunWith(AndroidJUnit4.class)
public class ActivityStackTests extends ActivityTestsBase {
private ActivityTaskManagerService mService;
private ActivityStackSupervisor mSupervisor;
private ActivityDisplay mDefaultDisplay;
private ActivityStack mStack;
private TaskRecord mTask;
@@ -71,9 +69,8 @@ public class ActivityStackTests extends ActivityTestsBase {
public void setUp() throws Exception {
super.setUp();
mService = createActivityTaskManagerService();
mSupervisor = mService.mStackSupervisor;
mDefaultDisplay = mService.mStackSupervisor.getDefaultDisplay();
setupActivityTaskManagerService();
mDefaultDisplay = mSupervisor.getDefaultDisplay();
mStack = mDefaultDisplay.createStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD,
true /* onTop */);
mTask = new TaskBuilder(mSupervisor).setStack(mStack).build();

View File

@@ -32,26 +32,32 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
import static com.android.server.am.ActivityDisplay.POSITION_BOTTOM;
import static com.android.server.am.ActivityManagerService.ANIMATE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyObject;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyObject;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.app.ActivityOptions;
import android.app.IApplicationThread;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.WindowLayout;
@@ -65,28 +71,6 @@ import android.platform.test.annotations.Presubmit;
import android.service.voice.IVoiceInteractionSession;
import android.view.Gravity;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED;
import static com.android.server.am.ActivityManagerService.ANIMATE;
import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_DESTROYING;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyObject;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.times;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -106,7 +90,6 @@ import org.junit.runner.RunWith;
@Presubmit
@RunWith(AndroidJUnit4.class)
public class ActivityStarterTests extends ActivityTestsBase {
private ActivityTaskManagerService mService;
private ActivityStarter mStarter;
private ActivityStartController mController;
private ActivityMetricsLogger mActivityMetricsLogger;
@@ -130,7 +113,7 @@ public class ActivityStarterTests extends ActivityTestsBase {
@Override
public void setUp() throws Exception {
super.setUp();
mService = createActivityTaskManagerService();
setupActivityTaskManagerService();
mController = mock(ActivityStartController.class);
mActivityMetricsLogger = mock(ActivityMetricsLogger.class);
clearInvocations(mActivityMetricsLogger);
@@ -323,7 +306,22 @@ public class ActivityStarterTests extends ActivityTestsBase {
}
}
private ActivityStarter prepareStarter(int launchFlags) {
private ActivityStarter prepareStarter(@Intent.Flags int launchFlags) {
return prepareStarter(launchFlags, true /* mockGetLaunchStack */);
}
/**
* Creates a {@link ActivityStarter} with default parameters and necessary mocks.
*
* @param launchFlags The intent flags to launch activity.
* @param mockGetLaunchStack Whether to mock {@link ActivityStackSupervisor#getLaunchStack} for
* always launching to the testing stack. Set to false when allowing
* the activity can be launched to any stack that is decided by real
* implementation.
* @return A {@link ActivityStarter} with default setup.
*/
private ActivityStarter prepareStarter(@Intent.Flags int launchFlags,
boolean mockGetLaunchStack) {
// always allow test to start activity.
doReturn(true).when(mService.mStackSupervisor).checkStartAnyActivityPermission(
any(), any(), any(), anyInt(), anyInt(), anyInt(), any(),
@@ -343,11 +341,13 @@ public class ActivityStarterTests extends ActivityTestsBase {
// return task when created.
doReturn(task).when(factory).create(any(), anyInt(), any(), any(), any(), any());
// direct starter to use spy stack.
doReturn(stack).when(mService.mStackSupervisor)
.getLaunchStack(any(), any(), any(), anyBoolean());
doReturn(stack).when(mService.mStackSupervisor)
.getLaunchStack(any(), any(), any(), anyBoolean(), anyInt());
if (mockGetLaunchStack) {
// Direct starter to use spy stack.
doReturn(stack).when(mService.mStackSupervisor)
.getLaunchStack(any(), any(), any(), anyBoolean());
doReturn(stack).when(mService.mStackSupervisor)
.getLaunchStack(any(), any(), any(), anyBoolean(), anyInt());
}
// Set up mock package manager internal and make sure no unmocked methods are called
PackageManagerInternal mockPackageManager = mock(PackageManagerInternal.class,
@@ -546,4 +546,84 @@ public class ActivityStarterTests extends ActivityTestsBase {
eq(ActivityBuilder.getDefaultComponent().getPackageName()), anyInt(), anyBoolean(),
any(), eq(false));
}
/**
* This test ensures that when starting an existing single task activity on secondary display
* which is not the top focused display, it should deliver new intent to the activity and not
* create a new stack.
*/
@Test
public void testDeliverIntentToTopActivityOfNonTopDisplay() {
final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK,
false /* mockGetLaunchStack */);
// Create a secondary display at bottom.
final TestActivityDisplay secondaryDisplay = spy(addNewActivityDisplayAt(POSITION_BOTTOM));
final ActivityStack stack = secondaryDisplay.createStack(WINDOWING_MODE_FULLSCREEN,
ACTIVITY_TYPE_STANDARD, true /* onTop */);
// Create an activity record on the top of secondary display.
final ComponentName componentName = ComponentName.createRelative(
DEFAULT_COMPONENT_PACKAGE_NAME,
DEFAULT_COMPONENT_PACKAGE_NAME + ".ReusableActivity");
final TaskRecord taskRecord = new TaskBuilder(mSupervisor)
.setComponent(componentName)
.setStack(stack)
.build();
final ActivityRecord topActivityOnSecondaryDisplay = new ActivityBuilder(mService)
.setComponent(componentName)
.setLaunchMode(LAUNCH_SINGLE_TASK)
.setTask(taskRecord)
.build();
// Put an activity on default display as the top focused activity.
new ActivityBuilder(mService).setCreateTask(true).build();
// Start activity with the same intent as {@code topActivityOnSecondaryDisplay}
// on secondary display.
final ActivityOptions options = ActivityOptions.makeBasic()
.setLaunchDisplayId(secondaryDisplay.mDisplayId);
final int result = starter.setReason("testDeliverIntentToTopActivityOfNonTopDisplay")
.setIntent(topActivityOnSecondaryDisplay.intent)
.setActivityOptions(options.toBundle())
.execute();
// Ensure result is delivering intent to top.
assertEquals(START_DELIVERED_TO_TOP, result);
// Ensure secondary display only creates one stack.
verify(secondaryDisplay, times(1)).createStack(anyInt(), anyInt(), anyBoolean());
}
/**
* This test ensures that a reused top activity in the top focused stack is able to be
* reparented to another display.
*/
@Test
public void testReparentTopFocusedActivityToSecondaryDisplay() {
final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK,
false /* mockGetLaunchStack */);
// Create a secondary display at bottom.
final TestActivityDisplay secondaryDisplay = addNewActivityDisplayAt(POSITION_BOTTOM);
secondaryDisplay.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
true /* onTop */);
// Put an activity on default display as the top focused activity.
final ActivityRecord topActivity = new ActivityBuilder(mService)
.setCreateTask(true)
.setLaunchMode(LAUNCH_SINGLE_TASK)
.build();
// Start activity with the same intent as {@code topActivity} on secondary display.
final ActivityOptions options = ActivityOptions.makeBasic()
.setLaunchDisplayId(secondaryDisplay.mDisplayId);
starter.setReason("testReparentTopFocusedActivityToSecondaryDisplay")
.setIntent(topActivity.intent)
.setActivityOptions(options.toBundle())
.execute();
// Ensure the activity is moved to secondary display.
assertEquals(secondaryDisplay, topActivity.getDisplay());
}
}

View File

@@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_DESTROYING;
import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
@@ -56,11 +57,14 @@ import android.content.pm.IPackageManager;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.UserHandle;
import android.service.voice.IVoiceInteractionSession;
import android.testing.DexmakerShareClassLoaderRule;
import android.view.Display;
import android.view.DisplayInfo;
import androidx.test.InstrumentationRegistry;
@@ -86,6 +90,8 @@ import java.util.List;
public class ActivityTestsBase {
private static boolean sOneTimeSetupDone = false;
private static int sNextDisplayId = DEFAULT_DISPLAY + 1;
@Rule
public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
new DexmakerShareClassLoaderRule();
@@ -93,6 +99,9 @@ public class ActivityTestsBase {
private final Context mContext = InstrumentationRegistry.getContext();
private HandlerThread mHandlerThread;
ActivityTaskManagerService mService;
ActivityStackSupervisor mSupervisor;
// Default package name
static final String DEFAULT_COMPONENT_PACKAGE_NAME = "com.foo";
@@ -122,6 +131,11 @@ public class ActivityTestsBase {
return atm;
}
void setupActivityTaskManagerService() {
mService = createActivityTaskManagerService();
mSupervisor = mService.mStackSupervisor;
}
ActivityManagerService createActivityManagerService() {
final TestActivityTaskManagerService atm =
spy(new TestActivityTaskManagerService(mContext));
@@ -134,6 +148,18 @@ public class ActivityTestsBase {
return am;
}
/** Creates a {@link TestActivityDisplay}. */
TestActivityDisplay createNewActivityDisplay() {
return TestActivityDisplay.create(mSupervisor, sNextDisplayId++);
}
/** Creates and adds a {@link TestActivityDisplay} to supervisor at the given position. */
TestActivityDisplay addNewActivityDisplayAt(int position) {
final TestActivityDisplay display = createNewActivityDisplay();
mSupervisor.addChild(display, position);
return display;
}
void setupActivityManagerService(
TestActivityManagerService am, TestActivityTaskManagerService atm) {
atm.setActivityManagerService(am);
@@ -173,6 +199,7 @@ public class ActivityTestsBase {
private boolean mCreateTask;
private ActivityStack mStack;
private int mActivityFlags;
private int mLaunchMode;
ActivityBuilder(ActivityTaskManagerService service) {
mService = service;
@@ -198,6 +225,11 @@ public class ActivityTestsBase {
return this;
}
ActivityBuilder setLaunchMode(int launchMode) {
mLaunchMode = launchMode;
return this;
}
ActivityBuilder setStack(ActivityStack stack) {
mStack = stack;
return this;
@@ -233,6 +265,7 @@ public class ActivityTestsBase {
aInfo.applicationInfo.packageName = mComponent.getPackageName();
aInfo.applicationInfo.uid = mUid;
aInfo.flags |= mActivityFlags;
aInfo.launchMode = mLaunchMode;
final ActivityRecord activity = new ActivityRecord(mService, null /* caller */,
0 /* launchedFromPid */, 0, null, intent, null,
@@ -500,7 +533,7 @@ public class ActivityTestsBase {
@Override
public void initialize() {
super.initialize();
mDisplay = spy(new TestActivityDisplay(this, DEFAULT_DISPLAY));
mDisplay = spy(TestActivityDisplay.create(this, DEFAULT_DISPLAY));
addChild(mDisplay, ActivityDisplay.POSITION_TOP);
}
@@ -516,10 +549,20 @@ public class ActivityTestsBase {
}
protected static class TestActivityDisplay extends ActivityDisplay {
private final ActivityStackSupervisor mSupervisor;
TestActivityDisplay(ActivityStackSupervisor supervisor, int displayId) {
super(supervisor, displayId);
static TestActivityDisplay create(ActivityStackSupervisor supervisor, int displayId) {
if (displayId == DEFAULT_DISPLAY) {
return new TestActivityDisplay(supervisor,
supervisor.mDisplayManager.getDisplay(displayId));
}
final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId,
new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS);
return new TestActivityDisplay(supervisor, display);
}
TestActivityDisplay(ActivityStackSupervisor supervisor, Display display) {
super(supervisor, display);
// Normally this comes from display-properties as exposed by WM. Without that, just
// hard-code to FULLSCREEN for tests.
setWindowingMode(WINDOWING_MODE_FULLSCREEN);

View File

@@ -870,8 +870,8 @@ public class RecentTasksTest extends ActivityTestsBase {
@Override
public void initialize() {
super.initialize();
mDisplay = new TestActivityDisplay(this, DEFAULT_DISPLAY);
mOtherDisplay = new TestActivityDisplay(this, DEFAULT_DISPLAY);
mDisplay = TestActivityDisplay.create(this, DEFAULT_DISPLAY);
mOtherDisplay = TestActivityDisplay.create(this, DEFAULT_DISPLAY + 1);
addChild(mOtherDisplay, ActivityDisplay.POSITION_TOP);
addChild(mDisplay, ActivityDisplay.POSITION_TOP);
}

View File

@@ -51,7 +51,6 @@ import java.util.ArrayList;
public class RunningTasksTest extends ActivityTestsBase {
private Context mContext = InstrumentationRegistry.getContext();
private ActivityTaskManagerService mService;
private RunningTasks mRunningTasks;
@@ -60,21 +59,20 @@ public class RunningTasksTest extends ActivityTestsBase {
public void setUp() throws Exception {
super.setUp();
mService = createActivityTaskManagerService();
setupActivityTaskManagerService();
mRunningTasks = new RunningTasks();
}
@Test
public void testCollectTasksByLastActiveTime() throws Exception {
// Create a number of stacks with tasks (of incrementing active time)
final ActivityStackSupervisor supervisor = mService.mStackSupervisor;
final ArrayList<ActivityDisplay> displays = new ArrayList<>();
final ActivityDisplay display = new TestActivityDisplay(supervisor, DEFAULT_DISPLAY);
final ActivityDisplay display = TestActivityDisplay.create(mSupervisor, DEFAULT_DISPLAY);
displays.add(display);
final int numStacks = 2;
for (int stackIndex = 0; stackIndex < numStacks; stackIndex++) {
final ActivityStack stack = new TestActivityStack(display, stackIndex, supervisor,
final ActivityStack stack = new TestActivityStack(display, stackIndex, mSupervisor,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true);
display.addChild(stack, POSITION_BOTTOM);
}