blob: 4986a6d5bd0d7f1930d467a316bab11e265e18cc [file] [log] [blame]
/*
* Copyright (C) 2018 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.server.wm;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
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 androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_DESTROYING;
import static com.android.server.wm.ActivityStackSupervisor.ON_TOP;
import android.app.ActivityManagerInternal;
import android.app.ActivityOptions;
import android.app.AppOpsManager;
import android.app.IApplicationThread;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
import android.os.Process;
import android.os.UserHandle;
import android.service.voice.IVoiceInteractionSession;
import android.testing.DexmakerShareClassLoaderRule;
import android.view.Display;
import android.view.DisplayInfo;
import com.android.internal.app.IVoiceInteractor;
import com.android.server.AttributeCache;
import com.android.server.ServiceThread;
import com.android.server.am.ActivityManagerService;
import com.android.server.am.PendingIntentController;
import com.android.server.appop.AppOpsService;
import com.android.server.firewall.IntentFirewall;
import com.android.server.policy.PermissionPolicyInternal;
import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.wm.TaskRecord.TaskRecordFactory;
import com.android.server.wm.utils.MockTracker;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.mockito.invocation.InvocationOnMock;
import java.io.File;
import java.util.List;
/**
* A base class to handle common operations in activity related unit tests.
*/
class ActivityTestsBase {
private static int sNextDisplayId = DEFAULT_DISPLAY + 1;
@Rule
public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
new DexmakerShareClassLoaderRule();
final Context mContext = getInstrumentation().getTargetContext();
final TestInjector mTestInjector = new TestInjector();
ActivityTaskManagerService mService;
RootActivityContainer mRootActivityContainer;
ActivityStackSupervisor mSupervisor;
private MockTracker mMockTracker;
// Default package name
static final String DEFAULT_COMPONENT_PACKAGE_NAME = "com.foo";
// Default base activity name
private static final String DEFAULT_COMPONENT_CLASS_NAME = ".BarActivity";
@BeforeClass
public static void setUpOnceBase() {
AttributeCache.init(getInstrumentation().getTargetContext());
}
@Before
public void setUpBase() {
mMockTracker = new MockTracker();
mTestInjector.setUp();
mService = new TestActivityTaskManagerService(mContext);
mSupervisor = mService.mStackSupervisor;
mRootActivityContainer = mService.mRootActivityContainer;
}
@After
public void tearDownBase() {
mTestInjector.tearDown();
if (mService != null) {
mService.setWindowManager(null);
mService = null;
}
if (sMockWindowManagerService != null) {
reset(sMockWindowManagerService);
}
mMockTracker.close();
mMockTracker = null;
}
/** Creates a {@link TestActivityDisplay}. */
TestActivityDisplay createNewActivityDisplay() {
return TestActivityDisplay.create(mSupervisor, sNextDisplayId++);
}
TestActivityDisplay createNewActivityDisplay(DisplayInfo info) {
return TestActivityDisplay.create(mSupervisor, sNextDisplayId++, info);
}
/** Creates and adds a {@link TestActivityDisplay} to supervisor at the given position. */
TestActivityDisplay addNewActivityDisplayAt(int position) {
final TestActivityDisplay display = createNewActivityDisplay();
mRootActivityContainer.addChild(display, position);
return display;
}
/** Creates and adds a {@link TestActivityDisplay} to supervisor at the given position. */
TestActivityDisplay addNewActivityDisplayAt(DisplayInfo info, int position) {
final TestActivityDisplay display = createNewActivityDisplay(info);
mRootActivityContainer.addChild(display, position);
return display;
}
/**
* Delegates task creation to {@link #TaskBuilder} to avoid the dependency of window hierarchy
* when starting activity in unit tests.
*/
void mockTaskRecordFactory() {
final TaskRecord task = new TaskBuilder(mSupervisor).setCreateStack(false).build();
final TaskRecordFactory factory = mock(TaskRecordFactory.class);
TaskRecord.setTaskRecordFactory(factory);
doReturn(task).when(factory).create(any() /* service */, anyInt() /* taskId */,
any() /* info */, any() /* intent */, any() /* voiceSession */,
any() /* voiceInteractor */);
}
/**
* Builder for creating new activities.
*/
protected static class ActivityBuilder {
// An id appended to the end of the component name to make it unique
private static int sCurrentActivityId = 0;
private final ActivityTaskManagerService mService;
private ComponentName mComponent;
private String mTargetActivity;
private TaskRecord mTaskRecord;
private int mUid;
private boolean mCreateTask;
private ActivityStack mStack;
private int mActivityFlags;
private int mLaunchMode;
ActivityBuilder(ActivityTaskManagerService service) {
mService = service;
}
ActivityBuilder setComponent(ComponentName component) {
mComponent = component;
return this;
}
ActivityBuilder setTargetActivity(String targetActivity) {
mTargetActivity = targetActivity;
return this;
}
static ComponentName getDefaultComponent() {
return ComponentName.createRelative(DEFAULT_COMPONENT_PACKAGE_NAME,
DEFAULT_COMPONENT_PACKAGE_NAME);
}
ActivityBuilder setTask(TaskRecord task) {
mTaskRecord = task;
return this;
}
ActivityBuilder setActivityFlags(int flags) {
mActivityFlags = flags;
return this;
}
ActivityBuilder setLaunchMode(int launchMode) {
mLaunchMode = launchMode;
return this;
}
ActivityBuilder setStack(ActivityStack stack) {
mStack = stack;
return this;
}
ActivityBuilder setCreateTask(boolean createTask) {
mCreateTask = createTask;
return this;
}
ActivityBuilder setUid(int uid) {
mUid = uid;
return this;
}
ActivityRecord build() {
if (mComponent == null) {
final int id = sCurrentActivityId++;
mComponent = ComponentName.createRelative(DEFAULT_COMPONENT_PACKAGE_NAME,
DEFAULT_COMPONENT_CLASS_NAME + id);
}
if (mCreateTask) {
mTaskRecord = new TaskBuilder(mService.mStackSupervisor)
.setComponent(mComponent)
.setStack(mStack).build();
}
Intent intent = new Intent();
intent.setComponent(mComponent);
final ActivityInfo aInfo = new ActivityInfo();
aInfo.applicationInfo = new ApplicationInfo();
aInfo.applicationInfo.packageName = mComponent.getPackageName();
aInfo.applicationInfo.uid = mUid;
aInfo.packageName = mComponent.getPackageName();
if (mTargetActivity != null) {
aInfo.targetActivity = mTargetActivity;
}
aInfo.flags |= mActivityFlags;
aInfo.launchMode = mLaunchMode;
final ActivityRecord activity = new ActivityRecord(mService, null /* caller */,
0 /* launchedFromPid */, 0, null, intent, null,
aInfo /*aInfo*/, new Configuration(), null /* resultTo */, null /* resultWho */,
0 /* reqCode */, false /*componentSpecified*/, false /* rootVoiceInteraction */,
mService.mStackSupervisor, null /* options */, null /* sourceRecord */);
spyOn(activity);
activity.mAppWindowToken = mock(AppWindowToken.class);
doCallRealMethod().when(activity.mAppWindowToken).getOrientationIgnoreVisibility();
doCallRealMethod().when(activity.mAppWindowToken)
.setOrientation(anyInt(), any(), any());
doCallRealMethod().when(activity.mAppWindowToken).setOrientation(anyInt());
doNothing().when(activity).removeWindowContainer();
doReturn(mock(Configuration.class)).when(activity.mAppWindowToken)
.getRequestedOverrideConfiguration();
if (mTaskRecord != null) {
mTaskRecord.addActivityToTop(activity);
}
final WindowProcessController wpc = new WindowProcessController(mService,
mService.mContext.getApplicationInfo(), "name", 12345,
UserHandle.getUserId(12345), mock(Object.class),
mock(WindowProcessListener.class));
wpc.setThread(mock(IApplicationThread.class));
activity.setProcess(wpc);
return activity;
}
}
/**
* Builder for creating new tasks.
*/
protected static class TaskBuilder {
// Default package name
static final String DEFAULT_PACKAGE = "com.bar";
private final ActivityStackSupervisor mSupervisor;
private ComponentName mComponent;
private String mPackage;
private int mFlags = 0;
// Task id 0 is reserved in ARC for the home app.
private int mTaskId = 1;
private int mUserId = 0;
private IVoiceInteractionSession mVoiceSession;
private boolean mCreateStack = true;
private ActivityStack mStack;
TaskBuilder(ActivityStackSupervisor supervisor) {
mSupervisor = supervisor;
}
TaskBuilder setComponent(ComponentName component) {
mComponent = component;
return this;
}
TaskBuilder setPackage(String packageName) {
mPackage = packageName;
return this;
}
/**
* Set to {@code true} by default, set to {@code false} to prevent the task from
* automatically creating a parent stack.
*/
TaskBuilder setCreateStack(boolean createStack) {
mCreateStack = createStack;
return this;
}
TaskBuilder setVoiceSession(IVoiceInteractionSession session) {
mVoiceSession = session;
return this;
}
TaskBuilder setFlags(int flags) {
mFlags = flags;
return this;
}
TaskBuilder setTaskId(int taskId) {
mTaskId = taskId;
return this;
}
TaskBuilder setUserId(int userId) {
mUserId = userId;
return this;
}
TaskBuilder setStack(ActivityStack stack) {
mStack = stack;
return this;
}
TaskRecord build() {
if (mStack == null && mCreateStack) {
mStack = mSupervisor.mRootActivityContainer.getDefaultDisplay().createStack(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
}
final ActivityInfo aInfo = new ActivityInfo();
aInfo.applicationInfo = new ApplicationInfo();
aInfo.applicationInfo.packageName = mPackage;
Intent intent = new Intent();
if (mComponent == null) {
mComponent = ComponentName.createRelative(DEFAULT_COMPONENT_PACKAGE_NAME,
DEFAULT_COMPONENT_CLASS_NAME);
}
intent.setComponent(mComponent);
intent.setFlags(mFlags);
final TestTaskRecord task = new TestTaskRecord(mSupervisor.mService, mTaskId, aInfo,
intent /*intent*/, mVoiceSession, null /*_voiceInteractor*/);
task.userId = mUserId;
if (mStack != null) {
mStack.moveToFront("test");
mStack.addTask(task, true, "creating test task");
task.setStack(mStack);
task.setTask();
mStack.getTaskStack().addChild(task.mTask, 0);
}
task.touchActiveTime();
return task;
}
private static class TestTaskRecord extends TaskRecord {
TestTaskRecord(ActivityTaskManagerService service, int taskId, ActivityInfo info,
Intent intent, IVoiceInteractionSession voiceSession,
IVoiceInteractor voiceInteractor) {
super(service, taskId, info, intent, voiceSession, voiceInteractor);
}
@Override
void createTask(boolean onTop, boolean showForAllUsers) {
setTask();
}
void setTask() {
Task mockTask = mock(Task.class);
mockTask.mTaskRecord = this;
doCallRealMethod().when(mockTask).onDescendantOrientationChanged(any(), any());
setTask(mock(Task.class));
}
}
}
protected class TestActivityTaskManagerService extends ActivityTaskManagerService {
private PackageManagerInternal mPmInternal;
private PermissionPolicyInternal mPermissionPolicyInternal;
// ActivityStackSupervisor may be created more than once while setting up AMS and ATMS.
// We keep the reference in order to prevent creating it twice.
ActivityStackSupervisor mTestStackSupervisor;
ActivityDisplay mDefaultDisplay;
AppOpsService mAppOpsService;
TestActivityTaskManagerService(Context context) {
super(context);
spyOn(this);
mUgmInternal = mock(UriGrantsManagerInternal.class);
mAppOpsService = mock(AppOpsService.class);
// Make sure permission checks aren't overridden.
doReturn(AppOpsManager.MODE_DEFAULT)
.when(mAppOpsService).noteOperation(anyInt(), anyInt(), anyString());
mSupportsMultiWindow = true;
mSupportsMultiDisplay = true;
mSupportsSplitScreenMultiWindow = true;
mSupportsFreeformWindowManagement = true;
mSupportsPictureInPicture = true;
final TestActivityManagerService am =
new TestActivityManagerService(mTestInjector, this);
spyOn(getLifecycleManager());
spyOn(getLockTaskController());
doReturn(mock(IPackageManager.class)).when(this).getPackageManager();
// allow background activity starts by default
doReturn(true).when(this).isBackgroundActivityStartsEnabled();
doNothing().when(this).updateCpuStats();
}
void setup(IntentFirewall intentFirewall, PendingIntentController intentController,
ActivityManagerInternal amInternal, WindowManagerService wm, Looper looper) {
mAmInternal = amInternal;
initialize(intentFirewall, intentController, looper);
initRootActivityContainerMocks(wm);
setWindowManager(wm);
createDefaultDisplay();
}
void initRootActivityContainerMocks(WindowManagerService wm) {
spyOn(mRootActivityContainer);
mRootActivityContainer.setWindowContainer(mock(RootWindowContainer.class));
mRootActivityContainer.mWindowManager = wm;
mRootActivityContainer.mDisplayManager =
(DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
doNothing().when(mRootActivityContainer).setWindowManager(any());
// Invoked during {@link ActivityStack} creation.
doNothing().when(mRootActivityContainer).updateUIDsPresentOnDisplay();
// Always keep things awake.
doReturn(true).when(mRootActivityContainer).hasAwakeDisplay();
// Called when moving activity to pinned stack.
doNothing().when(mRootActivityContainer).ensureActivitiesVisible(any(), anyInt(),
anyBoolean());
}
void createDefaultDisplay() {
// Create a default display and put a home stack on it so that we'll always have
// something focusable.
mDefaultDisplay = TestActivityDisplay.create(mStackSupervisor, DEFAULT_DISPLAY);
spyOn(mDefaultDisplay);
mRootActivityContainer.addChild(mDefaultDisplay, ActivityDisplay.POSITION_TOP);
mDefaultDisplay.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
final TaskRecord task = new TaskBuilder(mStackSupervisor)
.setStack(mDefaultDisplay.getHomeStack()).build();
new ActivityBuilder(this).setTask(task).build();
doReturn(mDefaultDisplay).when(mRootActivityContainer).getDefaultDisplay();
}
@Override
int handleIncomingUser(int callingPid, int callingUid, int userId, String name) {
return userId;
}
@Override
AppOpsService getAppOpsService() {
return mAppOpsService;
}
@Override
void updateCpuStats() {
}
@Override
void updateBatteryStats(ActivityRecord component, boolean resumed) {
}
@Override
void updateActivityUsageStats(ActivityRecord activity, int event) {
}
@Override
protected ActivityStackSupervisor createStackSupervisor() {
if (mTestStackSupervisor == null) {
mTestStackSupervisor = new TestActivityStackSupervisor(this, mH.getLooper());
}
return mTestStackSupervisor;
}
@Override
PackageManagerInternal getPackageManagerInternalLocked() {
if (mPmInternal == null) {
mPmInternal = mock(PackageManagerInternal.class);
doReturn(false)
.when(mPmInternal)
.isPermissionsReviewRequired(anyString(), anyInt());
}
return mPmInternal;
}
@Override
PermissionPolicyInternal getPermissionPolicyInternal() {
if (mPermissionPolicyInternal == null) {
mPermissionPolicyInternal = mock(PermissionPolicyInternal.class);
doReturn(true).when(mPermissionPolicyInternal).checkStartActivity(any(), anyInt(),
any());
}
return mPermissionPolicyInternal;
}
}
private static class TestInjector extends ActivityManagerService.Injector {
private ServiceThread mHandlerThread;
@Override
public Context getContext() {
return getInstrumentation().getTargetContext();
}
@Override
public AppOpsService getAppOpsService(File file, Handler handler) {
return null;
}
@Override
public Handler getUiHandler(ActivityManagerService service) {
return mHandlerThread.getThreadHandler();
}
@Override
public boolean isNetworkRestrictedForUid(int uid) {
return false;
}
void setUp() {
mHandlerThread = new ServiceThread("ActivityTestsThread",
Process.THREAD_PRIORITY_DEFAULT, true /* allowIo */);
mHandlerThread.start();
}
void tearDown() {
// Make sure there are no running messages and then quit the thread so the next test
// won't be affected.
mHandlerThread.getThreadHandler().runWithScissors(mHandlerThread::quit,
0 /* timeout */);
}
}
// TODO: Replace this with a mock object since we are no longer in AMS package.
/**
* An {@link ActivityManagerService} subclass which provides a test
* {@link ActivityStackSupervisor}.
*/
class TestActivityManagerService extends ActivityManagerService {
TestActivityManagerService(TestInjector testInjector, TestActivityTaskManagerService atm) {
super(testInjector, testInjector.mHandlerThread);
spyOn(this);
mWindowManager = prepareMockWindowManager();
mUgmInternal = mock(UriGrantsManagerInternal.class);
atm.setup(mIntentFirewall, mPendingIntentController, new LocalService(), mWindowManager,
testInjector.mHandlerThread.getLooper());
mActivityTaskManager = atm;
mAtmInternal = atm.mInternal;
doReturn(mock(IPackageManager.class)).when(this).getPackageManager();
PackageManagerInternal mockPackageManager = mock(PackageManagerInternal.class);
doReturn(mockPackageManager).when(this).getPackageManagerInternalLocked();
doReturn(null).when(mockPackageManager).getDefaultHomeActivity(anyInt());
doNothing().when(this).grantEphemeralAccessLocked(anyInt(), any(), anyInt(), anyInt());
}
}
/**
* An {@link ActivityStackSupervisor} which stubs out certain methods that depend on
* setup not available in the test environment. Also specifies an injector for
*/
protected class TestActivityStackSupervisor extends ActivityStackSupervisor {
private KeyguardController mKeyguardController;
TestActivityStackSupervisor(ActivityTaskManagerService service, Looper looper) {
super(service, looper);
spyOn(this);
mWindowManager = prepareMockWindowManager();
mKeyguardController = mock(KeyguardController.class);
// Do not schedule idle that may touch methods outside the scope of the test.
doNothing().when(this).scheduleIdleLocked();
doNothing().when(this).scheduleIdleTimeoutLocked(any());
// unit test version does not handle launch wake lock
doNothing().when(this).acquireLaunchWakelock();
doReturn(mKeyguardController).when(this).getKeyguardController();
mLaunchingActivityWakeLock = mock(PowerManager.WakeLock.class);
initialize();
}
@Override
public KeyguardController getKeyguardController() {
return mKeyguardController;
}
@Override
void setWindowManager(WindowManagerService wm) {
mWindowManager = wm;
}
}
protected static class TestActivityDisplay extends ActivityDisplay {
private final ActivityStackSupervisor mSupervisor;
static TestActivityDisplay create(ActivityStackSupervisor supervisor, int displayId) {
return create(supervisor, displayId, new DisplayInfo());
}
static TestActivityDisplay create(ActivityStackSupervisor supervisor, int displayId,
DisplayInfo info) {
if (displayId == DEFAULT_DISPLAY) {
return new TestActivityDisplay(supervisor,
supervisor.mRootActivityContainer.mDisplayManager.getDisplay(displayId));
}
final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId,
info, DEFAULT_DISPLAY_ADJUSTMENTS);
return new TestActivityDisplay(supervisor, display);
}
TestActivityDisplay(ActivityStackSupervisor supervisor, Display display) {
super(supervisor.mService.mRootActivityContainer, display);
// Normally this comes from display-properties as exposed by WM. Without that, just
// hard-code to FULLSCREEN for tests.
setWindowingMode(WINDOWING_MODE_FULLSCREEN);
mSupervisor = supervisor;
}
@SuppressWarnings("TypeParameterUnusedInFormals")
@Override
ActivityStack createStackUnchecked(int windowingMode, int activityType,
int stackId, boolean onTop) {
return new StackBuilder(mSupervisor.mRootActivityContainer).setDisplay(this)
.setWindowingMode(windowingMode).setActivityType(activityType)
.setStackId(stackId).setOnTop(onTop).setCreateActivity(false).build();
}
@Override
protected DisplayContent createDisplayContent() {
final DisplayContent displayContent = mock(DisplayContent.class);
DockedStackDividerController divider = mock(DockedStackDividerController.class);
doReturn(divider).when(displayContent).getDockedDividerController();
return displayContent;
}
void removeAllTasks() {
for (int i = 0; i < getChildCount(); i++) {
final ActivityStack stack = getChildAt(i);
for (TaskRecord task : (List<TaskRecord>) stack.getAllTasks()) {
stack.removeTask(task, "removeAllTasks", REMOVE_TASK_MODE_DESTROYING);
}
}
}
}
private static WindowManagerService sMockWindowManagerService;
private static WindowManagerService prepareMockWindowManager() {
if (sMockWindowManagerService == null) {
sMockWindowManagerService = mock(WindowManagerService.class);
}
sMockWindowManagerService.mRoot = mock(RootWindowContainer.class);
doAnswer((InvocationOnMock invocationOnMock) -> {
final Runnable runnable = invocationOnMock.<Runnable>getArgument(0);
if (runnable != null) {
runnable.run();
}
return null;
}).when(sMockWindowManagerService).inSurfaceTransaction(any());
return sMockWindowManagerService;
}
/**
* Overridden {@link ActivityStack} that tracks test metrics, such as the number of times a
* method is called. Note that its functionality depends on the implementations of the
* construction arguments.
*/
protected static class TestActivityStack
extends ActivityStack {
private int mOnActivityRemovedFromStackCount = 0;
static final int IS_TRANSLUCENT_UNSET = 0;
static final int IS_TRANSLUCENT_FALSE = 1;
static final int IS_TRANSLUCENT_TRUE = 2;
private int mIsTranslucent = IS_TRANSLUCENT_UNSET;
static final int SUPPORTS_SPLIT_SCREEN_UNSET = 0;
static final int SUPPORTS_SPLIT_SCREEN_FALSE = 1;
static final int SUPPORTS_SPLIT_SCREEN_TRUE = 2;
private int mSupportsSplitScreen = SUPPORTS_SPLIT_SCREEN_UNSET;
TestActivityStack(ActivityDisplay display, int stackId, ActivityStackSupervisor supervisor,
int windowingMode, int activityType, boolean onTop, boolean createActivity) {
super(display, stackId, supervisor, windowingMode, activityType, onTop);
if (createActivity) {
new ActivityBuilder(mService).setCreateTask(true).setStack(this).build();
if (onTop) {
// We move the task to front again in order to regain focus after activity
// added to the stack. Or {@link ActivityDisplay#mPreferredTopFocusableStack}
// could be other stacks (e.g. home stack).
moveToFront("createActivityStack");
} else {
moveToBack("createActivityStack", null);
}
}
}
@Override
void onActivityRemovedFromStack(ActivityRecord r) {
mOnActivityRemovedFromStackCount++;
super.onActivityRemovedFromStack(r);
}
// Returns the number of times {@link #onActivityRemovedFromStack} has been called
int onActivityRemovedFromStackInvocationCount() {
return mOnActivityRemovedFromStackCount;
}
@Override
protected void createTaskStack(int displayId, boolean onTop, Rect outBounds) {
mTaskStack = mock(TaskStack.class);
// Primary pinned stacks require a non-empty out bounds to be set or else all tasks
// will be moved to the full screen stack.
if (getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
outBounds.set(0, 0, 100, 100);
}
}
@Override
TaskStack getTaskStack() {
return mTaskStack;
}
void setIsTranslucent(boolean isTranslucent) {
mIsTranslucent = isTranslucent ? IS_TRANSLUCENT_TRUE : IS_TRANSLUCENT_FALSE;
}
@Override
boolean isStackTranslucent(ActivityRecord starting) {
switch (mIsTranslucent) {
case IS_TRANSLUCENT_TRUE:
return true;
case IS_TRANSLUCENT_FALSE:
return false;
case IS_TRANSLUCENT_UNSET:
default:
return super.isStackTranslucent(starting);
}
}
void setSupportsSplitScreen(boolean supportsSplitScreen) {
mSupportsSplitScreen = supportsSplitScreen
? SUPPORTS_SPLIT_SCREEN_TRUE : SUPPORTS_SPLIT_SCREEN_FALSE;
}
@Override
public boolean supportsSplitScreenWindowingMode() {
switch (mSupportsSplitScreen) {
case SUPPORTS_SPLIT_SCREEN_TRUE:
return true;
case SUPPORTS_SPLIT_SCREEN_FALSE:
return false;
case SUPPORTS_SPLIT_SCREEN_UNSET:
default:
return super.supportsSplitScreenWindowingMode();
}
}
@Override
void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
boolean newTask, boolean keepCurTransition,
ActivityOptions options) {
}
}
static class StackBuilder {
private final RootActivityContainer mRootActivityContainer;
private ActivityDisplay mDisplay;
private int mStackId = -1;
private int mWindowingMode = WINDOWING_MODE_FULLSCREEN;
private int mActivityType = ACTIVITY_TYPE_STANDARD;
private boolean mOnTop = true;
private boolean mCreateActivity = true;
StackBuilder(RootActivityContainer root) {
mRootActivityContainer = root;
mDisplay = mRootActivityContainer.getDefaultDisplay();
}
StackBuilder setWindowingMode(int windowingMode) {
mWindowingMode = windowingMode;
return this;
}
StackBuilder setActivityType(int activityType) {
mActivityType = activityType;
return this;
}
StackBuilder setStackId(int stackId) {
mStackId = stackId;
return this;
}
StackBuilder setDisplay(ActivityDisplay display) {
mDisplay = display;
return this;
}
StackBuilder setOnTop(boolean onTop) {
mOnTop = onTop;
return this;
}
StackBuilder setCreateActivity(boolean createActivity) {
mCreateActivity = createActivity;
return this;
}
@SuppressWarnings("TypeParameterUnusedInFormals")
ActivityStack build() {
final int stackId = mStackId >= 0 ? mStackId : mDisplay.getNextStackId();
if (mWindowingMode == WINDOWING_MODE_PINNED) {
return new ActivityStack(mDisplay, stackId, mRootActivityContainer.mStackSupervisor,
mWindowingMode, ACTIVITY_TYPE_STANDARD, mOnTop) {
@Override
Rect getDefaultPictureInPictureBounds(float aspectRatio) {
return new Rect(50, 50, 100, 100);
}
@Override
void createTaskStack(int displayId, boolean onTop, Rect outBounds) {
mTaskStack = mock(TaskStack.class);
}
};
} else {
return new TestActivityStack(mDisplay, stackId,
mRootActivityContainer.mStackSupervisor, mWindowingMode,
mActivityType, mOnTop, mCreateActivity);
}
}
}
}