blob: 5d1a06825a75432b64afe2b4c5ef1566fae08a25 [file] [log] [blame]
/*
* 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.server.wm;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
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.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.ActivityInterceptorCallback.FIRST_ORDERED_ID;
import static com.android.server.wm.ActivityInterceptorCallback.LAST_ORDERED_ID;
import static com.android.server.wm.ActivityRecord.State.PAUSED;
import static com.android.server.wm.ActivityRecord.State.PAUSING;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityRecord.State.STOPPING;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.IApplicationThread;
import android.app.PictureInPictureParams;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.EnterPipRequestedItem;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
import android.os.LocaleList;
import android.os.PowerManager;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.view.IDisplayWindowListener;
import androidx.test.filters.MediumTest;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.mockito.MockitoSession;
import java.util.ArrayList;
import java.util.function.Consumer;
/**
* Tests for the {@link ActivityTaskManagerService} class.
*
* Build/Install/Run:
* atest WmTests:ActivityTaskManagerServiceTests
*/
@Presubmit
@MediumTest
@RunWith(WindowTestRunner.class)
public class ActivityTaskManagerServiceTests extends WindowTestsBase {
private final ArgumentCaptor<ClientTransaction> mClientTransactionCaptor =
ArgumentCaptor.forClass(ClientTransaction.class);
private static final String DEFAULT_PACKAGE_NAME = "my.application.package";
private static final int DEFAULT_USER_ID = 100;
@Before
public void setUp() throws Exception {
setBooted(mAtm);
}
/** Verify that activity is finished correctly upon request. */
@Test
public void testActivityFinish() {
final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity();
assertTrue("Activity must be finished", mAtm.mActivityClientController.finishActivity(
activity.appToken, 0 /* resultCode */, null /* resultData */,
Activity.DONT_FINISH_TASK_WITH_ACTIVITY));
assertTrue(activity.finishing);
assertTrue("Duplicate activity finish request must also return 'true'",
mAtm.mActivityClientController.finishActivity(activity.appToken, 0 /* resultCode */,
null /* resultData */, Activity.DONT_FINISH_TASK_WITH_ACTIVITY));
}
@Test
public void testOnPictureInPictureRequested() throws RemoteException {
final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity();
final ClientLifecycleManager mockLifecycleManager = mock(ClientLifecycleManager.class);
doReturn(mockLifecycleManager).when(mAtm).getLifecycleManager();
doReturn(true).when(activity).checkEnterPictureInPictureState(anyString(), anyBoolean());
mAtm.mActivityClientController.requestPictureInPictureMode(activity);
verify(mockLifecycleManager).scheduleTransaction(mClientTransactionCaptor.capture());
final ClientTransaction transaction = mClientTransactionCaptor.getValue();
// Check that only an enter pip request item callback was scheduled.
assertEquals(1, transaction.getCallbacks().size());
assertTrue(transaction.getCallbacks().get(0) instanceof EnterPipRequestedItem);
// Check the activity lifecycle state remains unchanged.
assertNull(transaction.getLifecycleStateRequest());
}
@Test(expected = IllegalStateException.class)
public void testOnPictureInPictureRequested_cannotEnterPip() throws RemoteException {
final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity();
ClientLifecycleManager lifecycleManager = mAtm.getLifecycleManager();
doReturn(false).when(activity).inPinnedWindowingMode();
doReturn(false).when(activity).checkEnterPictureInPictureState(anyString(), anyBoolean());
mAtm.mActivityClientController.requestPictureInPictureMode(activity);
// Check enter no transactions with enter pip requests are made.
verify(lifecycleManager, times(0)).scheduleTransaction(any());
}
@Test(expected = IllegalStateException.class)
public void testOnPictureInPictureRequested_alreadyInPIPMode() throws RemoteException {
final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity();
ClientLifecycleManager lifecycleManager = mAtm.getLifecycleManager();
doReturn(true).when(activity).inPinnedWindowingMode();
mAtm.mActivityClientController.requestPictureInPictureMode(activity);
// Check that no transactions with enter pip requests are made.
verify(lifecycleManager, times(0)).scheduleTransaction(any());
}
@Test
public void testDisplayWindowListener() {
final ArrayList<Integer> added = new ArrayList<>();
final ArrayList<Integer> changed = new ArrayList<>();
final ArrayList<Integer> removed = new ArrayList<>();
IDisplayWindowListener listener = new IDisplayWindowListener.Stub() {
@Override
public void onDisplayAdded(int displayId) {
added.add(displayId);
}
@Override
public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
changed.add(displayId);
}
@Override
public void onDisplayRemoved(int displayId) {
removed.add(displayId);
}
@Override
public void onFixedRotationStarted(int displayId, int newRotation) {}
@Override
public void onFixedRotationFinished(int displayId) {}
};
int[] displayIds = mAtm.mWindowManager.registerDisplayWindowListener(listener);
for (int i = 0; i < displayIds.length; i++) {
added.add(displayIds[i]);
}
// Check that existing displays call added
assertEquals(mRootWindowContainer.getChildCount(), added.size());
assertEquals(0, changed.size());
assertEquals(0, removed.size());
added.clear();
// Check adding a display
DisplayContent newDisp1 = new TestDisplayContent.Builder(mAtm, 600, 800).build();
assertEquals(1, added.size());
assertEquals(0, changed.size());
assertEquals(0, removed.size());
added.clear();
// Check that changes are reported
Configuration c = new Configuration(newDisp1.getRequestedOverrideConfiguration());
c.windowConfiguration.setBounds(new Rect(0, 0, 1000, 1300));
newDisp1.onRequestedOverrideConfigurationChanged(c);
mAtm.mRootWindowContainer.ensureVisibilityAndConfig(null /* starting */,
newDisp1.mDisplayId, false /* markFrozenIfConfigChanged */,
false /* deferResume */);
assertEquals(0, added.size());
assertEquals(1, changed.size());
assertEquals(0, removed.size());
changed.clear();
// Check that removal is reported
newDisp1.remove();
assertEquals(0, added.size());
assertEquals(0, changed.size());
assertEquals(1, removed.size());
}
/*
a test to verify b/144045134 - ignore PIP mode request for destroyed activity.
mocks r.getParent() to return null to cause NPE inside enterPipRunnable#run() in
ActivityTaskMangerservice#enterPictureInPictureMode(), which rebooted the device.
It doesn't fully simulate the issue's reproduce steps, but this should suffice.
*/
@Test
public void testEnterPipModeWhenRecordParentChangesToNull() {
MockitoSession mockSession = mockitoSession()
.initMocks(this)
.mockStatic(ActivityRecord.class)
.startMocking();
ActivityRecord record = mock(ActivityRecord.class);
IBinder token = mock(IBinder.class);
PictureInPictureParams params = mock(PictureInPictureParams.class);
record.pictureInPictureArgs = params;
//mock operations in private method ensureValidPictureInPictureActivityParamsLocked()
when(ActivityRecord.forTokenLocked(token)).thenReturn(record);
doReturn(true).when(record).supportsPictureInPicture();
doReturn(false).when(params).hasSetAspectRatio();
//mock other operations
doReturn(true).when(record)
.checkEnterPictureInPictureState("enterPictureInPictureMode", false);
doReturn(false).when(record).inPinnedWindowingMode();
doReturn(false).when(mAtm).isKeyguardLocked();
//to simulate NPE
doReturn(null).when(record).getParent();
mAtm.mActivityClientController.enterPictureInPictureMode(token, params);
//if record's null parent is not handled gracefully, test will fail with NPE
mockSession.finishMocking();
}
@Test
public void testResumeNextActivityOnCrashedAppDied() {
mSupervisor.beginDeferResume();
final ActivityRecord homeActivity = new ActivityBuilder(mAtm)
.setTask(mRootWindowContainer.getDefaultTaskDisplayArea().getOrCreateRootHomeTask())
.build();
final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
activity.setState(RESUMED, "test");
mSupervisor.endDeferResume();
assertEquals(activity.app, mAtm.mInternal.getTopApp());
// Assume the activity is finishing and hidden because it was crashed.
activity.finishing = true;
activity.mVisibleRequested = false;
activity.setVisible(false);
activity.getTask().setPausingActivity(activity);
homeActivity.setState(PAUSED, "test");
// Even the visibility states are invisible, the next activity should be resumed because
// the crashed activity was pausing.
mAtm.mInternal.handleAppDied(activity.app, false /* restarting */,
null /* finishInstrumentationCallback */);
assertEquals(RESUMED, homeActivity.getState());
assertEquals(homeActivity.app, mAtm.mInternal.getTopApp());
}
@Test
public void testUpdateSleep() {
doCallRealMethod().when(mWm.mRoot).hasAwakeDisplay();
mSupervisor.mGoingToSleepWakeLock = mock(PowerManager.WakeLock.class);
final Task rootHomeTask = mWm.mRoot.getDefaultTaskDisplayArea().getOrCreateRootHomeTask();
final ActivityRecord homeActivity = new ActivityBuilder(mAtm).setTask(rootHomeTask).build();
final ActivityRecord topActivity = new ActivityBuilder(mAtm).setCreateTask(true).build();
topActivity.setState(RESUMED, "test");
final Consumer<ActivityRecord> assertTopNonSleeping = activity -> {
assertFalse(mAtm.mInternal.isSleeping());
assertEquals(ActivityManager.PROCESS_STATE_TOP, mAtm.mInternal.getTopProcessState());
assertEquals(activity.app, mAtm.mInternal.getTopApp());
};
assertTopNonSleeping.accept(topActivity);
// Sleep all displays.
mWm.mRoot.forAllDisplays(display -> doReturn(true).when(display).shouldSleep());
mAtm.updateSleepIfNeededLocked();
// Simulate holding sleep wake lock if it is acquired.
verify(mSupervisor.mGoingToSleepWakeLock).acquire();
doReturn(true).when(mSupervisor.mGoingToSleepWakeLock).isHeld();
assertEquals(PAUSING, topActivity.getState());
assertTrue(mAtm.mInternal.isSleeping());
assertEquals(ActivityManager.PROCESS_STATE_TOP_SLEEPING,
mAtm.mInternal.getTopProcessState());
// The top app should not change while sleeping.
assertEquals(topActivity.app, mAtm.mInternal.getTopApp());
mAtm.startLaunchPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY
| ActivityTaskManagerService.POWER_MODE_REASON_UNKNOWN_VISIBILITY);
assertEquals(ActivityManager.PROCESS_STATE_TOP, mAtm.mInternal.getTopProcessState());
// Because there is no unknown visibility record, the state will be restored if other
// reasons are all done.
mAtm.endLaunchPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY);
assertEquals(ActivityManager.PROCESS_STATE_TOP_SLEEPING,
mAtm.mInternal.getTopProcessState());
// If all activities are stopped, the sleep wake lock must be released.
final Task topRootTask = topActivity.getRootTask();
doReturn(true).when(rootHomeTask).goToSleepIfPossible(anyBoolean());
doReturn(true).when(topRootTask).goToSleepIfPossible(anyBoolean());
topActivity.setState(STOPPING, "test");
topActivity.activityStopped(null /* newIcicle */, null /* newPersistentState */,
null /* description */);
verify(mSupervisor.mGoingToSleepWakeLock).release();
// Move the current top to back, the top app should update to the next activity.
topRootTask.moveToBack("test", null /* self */);
assertEquals(homeActivity.app, mAtm.mInternal.getTopApp());
// Wake all displays.
mWm.mRoot.forAllDisplays(display -> doReturn(false).when(display).shouldSleep());
mAtm.updateSleepIfNeededLocked();
assertTopNonSleeping.accept(homeActivity);
}
@Test
public void testSupportsMultiWindow_resizable() {
final ActivityRecord activity = new ActivityBuilder(mAtm)
.setCreateTask(true)
.setResizeMode(RESIZE_MODE_RESIZEABLE)
.build();
final Task task = activity.getTask();
assertTrue(activity.supportsMultiWindow());
assertTrue(task.supportsMultiWindow());
}
@Test
public void testSupportsMultiWindow_nonResizable() {
final ActivityRecord activity = new ActivityBuilder(mAtm)
.setCreateTask(true)
.setResizeMode(RESIZE_MODE_UNRESIZEABLE)
.build();
final Task task = activity.getTask();
final TaskDisplayArea tda = task.getDisplayArea();
// Device config as not support.
mAtm.mSupportsNonResizableMultiWindow = -1;
assertFalse(activity.supportsMultiWindow());
assertFalse(task.supportsMultiWindow());
// Device config as always support.
mAtm.mSupportsNonResizableMultiWindow = 1;
assertTrue(activity.supportsMultiWindow());
assertTrue(task.supportsMultiWindow());
// The default config is relying on the screen size.
mAtm.mSupportsNonResizableMultiWindow = 0;
// Supports on large screen.
tda.getConfiguration().smallestScreenWidthDp = mAtm.mLargeScreenSmallestScreenWidthDp;
assertTrue(activity.supportsMultiWindow());
assertTrue(task.supportsMultiWindow());
// Not supports on small screen.
tda.getConfiguration().smallestScreenWidthDp = mAtm.mLargeScreenSmallestScreenWidthDp - 1;
assertFalse(activity.supportsMultiWindow());
assertFalse(task.supportsMultiWindow());
}
@Test
public void testSupportsMultiWindow_activityMinWidthHeight_largerThanSupport() {
final float density = mContext.getResources().getDisplayMetrics().density;
final ActivityInfo.WindowLayout windowLayout =
new ActivityInfo.WindowLayout(0, 0, 0, 0, 0,
// This is larger than the min dimensions device support in multi window,
// the activity will not be supported in multi window if the device respects
/* minWidth= */(int) (mAtm.mLargeScreenSmallestScreenWidthDp * density),
/* minHeight= */(int) (mAtm.mLargeScreenSmallestScreenWidthDp * density));
final ActivityRecord activity = new ActivityBuilder(mAtm)
.setCreateTask(true)
.setWindowLayout(windowLayout)
.setResizeMode(RESIZE_MODE_RESIZEABLE)
.build();
final Task task = activity.getTask();
final TaskDisplayArea tda = task.getDisplayArea();
// Ignore the activity min width/height for determine multi window eligibility.
mAtm.mRespectsActivityMinWidthHeightMultiWindow = -1;
assertTrue(activity.supportsMultiWindow());
assertTrue(task.supportsMultiWindow());
// Always check the activity min width/height.
mAtm.mRespectsActivityMinWidthHeightMultiWindow = 1;
assertFalse(activity.supportsMultiWindow());
assertFalse(task.supportsMultiWindow());
// The default config is relying on the screen size.
mAtm.mRespectsActivityMinWidthHeightMultiWindow = 0;
// Ignore on large screen.
tda.getConfiguration().smallestScreenWidthDp = mAtm.mLargeScreenSmallestScreenWidthDp;
assertTrue(activity.supportsMultiWindow());
assertTrue(task.supportsMultiWindow());
// Check on small screen.
tda.getConfiguration().smallestScreenWidthDp = mAtm.mLargeScreenSmallestScreenWidthDp - 1;
assertFalse(activity.supportsMultiWindow());
assertFalse(task.supportsMultiWindow());
}
@Test
public void testSupportsMultiWindow_landscape_checkActivityMinWidth() {
// This is smaller than the min dimensions device support in multi window,
// the activity will be supported in multi window
final float density = mContext.getResources().getDisplayMetrics().density;
final int supportedWidth = (int) (mAtm.mLargeScreenSmallestScreenWidthDp
* mAtm.mMinPercentageMultiWindowSupportWidth * density);
final ActivityInfo.WindowLayout windowLayout =
new ActivityInfo.WindowLayout(0, 0, 0, 0, 0,
/* minWidth= */ supportedWidth,
/* minHeight= */ 0);
final ActivityRecord activity = new ActivityBuilder(mAtm)
.setCreateTask(true)
.setWindowLayout(windowLayout)
.setResizeMode(RESIZE_MODE_RESIZEABLE)
.build();
final Task task = activity.getTask();
final TaskDisplayArea tda = task.getDisplayArea();
tda.getConfiguration().smallestScreenWidthDp = mAtm.mLargeScreenSmallestScreenWidthDp - 1;
tda.getConfiguration().screenWidthDp = mAtm.mLargeScreenSmallestScreenWidthDp - 1;
tda.getConfiguration().orientation = ORIENTATION_LANDSCAPE;
assertFalse(activity.supportsMultiWindow());
assertFalse(task.supportsMultiWindow());
tda.getConfiguration().screenWidthDp = (int) Math.ceil(
mAtm.mLargeScreenSmallestScreenWidthDp
/ mAtm.mMinPercentageMultiWindowSupportWidth);
assertTrue(activity.supportsMultiWindow());
assertTrue(task.supportsMultiWindow());
}
@Test
public void testSupportsMultiWindow_portrait_checkActivityMinHeight() {
// This is smaller than the min dimensions device support in multi window,
// the activity will be supported in multi window
final float density = mContext.getResources().getDisplayMetrics().density;
final int supportedHeight = (int) (mAtm.mLargeScreenSmallestScreenWidthDp
* mAtm.mMinPercentageMultiWindowSupportHeight * density);
final ActivityInfo.WindowLayout windowLayout =
new ActivityInfo.WindowLayout(0, 0, 0, 0, 0,
/* minWidth= */ 0,
/* minHeight= */ supportedHeight);
final ActivityRecord activity = new ActivityBuilder(mAtm)
.setCreateTask(true)
.setWindowLayout(windowLayout)
.setResizeMode(RESIZE_MODE_RESIZEABLE)
.build();
final Task task = activity.getTask();
final TaskDisplayArea tda = task.getDisplayArea();
tda.getConfiguration().smallestScreenWidthDp = mAtm.mLargeScreenSmallestScreenWidthDp - 1;
tda.getConfiguration().screenHeightDp = mAtm.mLargeScreenSmallestScreenWidthDp - 1;
tda.getConfiguration().orientation = ORIENTATION_PORTRAIT;
assertFalse(activity.supportsMultiWindow());
assertFalse(task.supportsMultiWindow());
tda.getConfiguration().screenHeightDp = (int) Math.ceil(
mAtm.mLargeScreenSmallestScreenWidthDp
/ mAtm.mMinPercentageMultiWindowSupportHeight);
assertTrue(activity.supportsMultiWindow());
assertTrue(task.supportsMultiWindow());
}
@Test
public void testPackageConfigUpdate_locales_successfullyApplied() {
Configuration config = mAtm.getGlobalConfiguration();
config.setLocales(LocaleList.forLanguageTags("en-XC"));
mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
mAtm.mInternal.createPackageConfigurationUpdater();
packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB")).commit();
WindowProcessController wpcAfterConfigChange = createWindowProcessController(
DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
wpcAfterConfigChange.getConfiguration().getLocales());
assertFalse(wpcAfterConfigChange.getConfiguration().isNightModeActive());
}
@Test
public void testPackageConfigUpdate_nightMode_successfullyApplied() {
Configuration config = mAtm.getGlobalConfiguration();
config.setLocales(LocaleList.forLanguageTags("en-XC"));
mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
mAtm.mInternal.createPackageConfigurationUpdater();
packageConfigUpdater.setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
WindowProcessController wpcAfterConfigChange = createWindowProcessController(
DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
assertTrue(wpcAfterConfigChange.getConfiguration().isNightModeActive());
assertEquals(LocaleList.forLanguageTags("en-XC"),
wpcAfterConfigChange.getConfiguration().getLocales());
}
@Test
public void testPackageConfigUpdate_multipleLocaleUpdates_successfullyApplied() {
Configuration config = mAtm.getGlobalConfiguration();
config.setLocales(LocaleList.forLanguageTags("en-XC"));
mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
WindowProcessController wpc = createWindowProcessController(
DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
mAtm.mProcessMap.put(Binder.getCallingPid(), wpc);
ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
mAtm.mInternal.createPackageConfigurationUpdater();
packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
.setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
wpcAfterConfigChange1.getConfiguration().getLocales());
assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
wpc.getConfiguration().getLocales());
packageConfigUpdater.setLocales(LocaleList.forLanguageTags("ja-XC,en-XC")).commit();
WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
assertEquals(LocaleList.forLanguageTags("ja-XC,en-XC"),
wpcAfterConfigChange2.getConfiguration().getLocales());
assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
assertEquals(LocaleList.forLanguageTags("ja-XC,en-XC"),
wpc.getConfiguration().getLocales());
}
@Test
public void testPackageConfigUpdate_multipleNightModeUpdates_successfullyApplied() {
Configuration config = mAtm.getGlobalConfiguration();
config.setLocales(LocaleList.forLanguageTags("en-XC"));
mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
mAtm.mInternal.createPackageConfigurationUpdater();
packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
.setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
wpcAfterConfigChange1.getConfiguration().getLocales());
assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
packageConfigUpdater.setNightMode(Configuration.UI_MODE_NIGHT_NO).commit();
WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
wpcAfterConfigChange2.getConfiguration().getLocales());
assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
}
@Test
public void testPackageConfigUpdate_onPackageUninstall_configShouldNotApply() {
Configuration config = mAtm.getGlobalConfiguration();
config.setLocales(LocaleList.forLanguageTags("en-XC"));
mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
mAtm.mInternal.createPackageConfigurationUpdater();
packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
.setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
wpcAfterConfigChange1.getConfiguration().getLocales());
assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
mAtm.mInternal.onPackageUninstalled(DEFAULT_PACKAGE_NAME);
WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
assertEquals(LocaleList.forLanguageTags("en-XC"),
wpcAfterConfigChange2.getConfiguration().getLocales());
assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
}
@Test
public void testPackageConfigUpdate_LocalesEmptyAndNightModeUndefined_configShouldNotApply() {
Configuration config = mAtm.getGlobalConfiguration();
config.setLocales(LocaleList.forLanguageTags("en-XC"));
mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
WindowProcessController wpc = createWindowProcessController(
DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
mAtm.mProcessMap.put(Binder.getCallingPid(), wpc);
ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
mAtm.mInternal.createPackageConfigurationUpdater();
packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
.setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
wpcAfterConfigChange1.getConfiguration().getLocales());
assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
wpc.getConfiguration().getLocales());
packageConfigUpdater.setLocales(LocaleList.getEmptyLocaleList())
.setNightMode(Configuration.UI_MODE_NIGHT_UNDEFINED).commit();
WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
assertEquals(LocaleList.forLanguageTags("en-XC"),
wpcAfterConfigChange2.getConfiguration().getLocales());
assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
assertEquals(LocaleList.forLanguageTags("en-XC"),
wpc.getConfiguration().getLocales());
}
@Test
public void testPackageConfigUpdate_WhenUserRemoved_configShouldNotApply() {
Configuration config = mAtm.getGlobalConfiguration();
config.setLocales(LocaleList.forLanguageTags("en-XC"));
mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
mAtm.mInternal.createPackageConfigurationUpdater();
packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
.setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
wpcAfterConfigChange1.getConfiguration().getLocales());
assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
mAtm.mInternal.removeUser(DEFAULT_USER_ID);
WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
assertEquals(LocaleList.forLanguageTags("en-XC"),
wpcAfterConfigChange2.getConfiguration().getLocales());
assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
}
@Test
public void testPackageConfigUpdate_setLocaleListToEmpty_doesNotOverlayLocaleListInWpc() {
Configuration config = mAtm.getGlobalConfiguration();
config.setLocales(LocaleList.forLanguageTags("en-XC"));
mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
mAtm.mInternal.createPackageConfigurationUpdater();
packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
.setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
wpcAfterConfigChange1.getConfiguration().getLocales());
assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
packageConfigUpdater.setLocales(LocaleList.getEmptyLocaleList()).commit();
WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
assertEquals(LocaleList.forLanguageTags("en-XC"),
wpcAfterConfigChange2.getConfiguration().getLocales());
assertTrue(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
}
@Test
public void testPackageConfigUpdate_resetNightMode_doesNotOverrideNightModeInWpc() {
Configuration config = mAtm.getGlobalConfiguration();
config.setLocales(LocaleList.forLanguageTags("en-XC"));
mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
mAtm.mInternal.createPackageConfigurationUpdater();
packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
.setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
wpcAfterConfigChange1.getConfiguration().getLocales());
assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
packageConfigUpdater.setNightMode(Configuration.UI_MODE_NIGHT_UNDEFINED).commit();
WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
wpcAfterConfigChange2.getConfiguration().getLocales());
assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
}
@Test
public void testPackageConfigUpdate_localesNotSet_localeConfigRetrievedNull() {
Configuration config = mAtm.getGlobalConfiguration();
config.setLocales(LocaleList.forLanguageTags("en-XC"));
mAtm.updateGlobalConfigurationLocked(config, true, true,
DEFAULT_USER_ID);
WindowProcessController wpc = createWindowProcessController(
DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
mAtm.mProcessMap.put(Binder.getCallingPid(), wpc);
mAtm.mInternal.onProcessAdded(wpc);
ActivityTaskManagerInternal.PackageConfig appSpecificConfig = mAtm.mInternal
.getApplicationConfig(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
// when no configuration is set we get a null object.
assertNull(appSpecificConfig);
ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
mAtm.mInternal.createPackageConfigurationUpdater(DEFAULT_PACKAGE_NAME,
DEFAULT_USER_ID);
packageConfigUpdater.setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
ActivityTaskManagerInternal.PackageConfig appSpecificConfig2 = mAtm.mInternal
.getApplicationConfig(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
assertNotNull(appSpecificConfig2);
assertNull(appSpecificConfig2.mLocales);
assertEquals(appSpecificConfig2.mNightMode.intValue(), Configuration.UI_MODE_NIGHT_YES);
}
@Test
public void testPackageConfigUpdate_appNotRunning_configSuccessfullyApplied() {
Configuration config = mAtm.getGlobalConfiguration();
config.setLocales(LocaleList.forLanguageTags("en-XC"));
mAtm.updateGlobalConfigurationLocked(config, true, true,
DEFAULT_USER_ID);
ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
mAtm.mInternal.createPackageConfigurationUpdater(DEFAULT_PACKAGE_NAME,
DEFAULT_USER_ID);
packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB")).commit();
// Verifies if the persisted app-specific configuration is same as the committed
// configuration.
ActivityTaskManagerInternal.PackageConfig appSpecificConfig = mAtm.mInternal
.getApplicationConfig(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
assertNotNull(appSpecificConfig);
assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB"), appSpecificConfig.mLocales);
// Verifies if the persisted configuration for an arbitrary app is applied correctly when
// a new WindowProcessController is created for it.
WindowProcessController wpcAfterConfigChange = createWindowProcessController(
DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
wpcAfterConfigChange.getConfiguration().getLocales());
}
@Test
public void testPackageConfigUpdate_appRunning_configSuccessfullyApplied() {
Configuration config = mAtm.getGlobalConfiguration();
config.setLocales(LocaleList.forLanguageTags("en-XC"));
mAtm.updateGlobalConfigurationLocked(config, true, true,
DEFAULT_USER_ID);
WindowProcessController wpc = createWindowProcessController(
DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
mAtm.mProcessMap.put(Binder.getCallingPid(), wpc);
mAtm.mInternal.onProcessAdded(wpc);
ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
mAtm.mInternal.createPackageConfigurationUpdater(DEFAULT_PACKAGE_NAME,
DEFAULT_USER_ID);
packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB")).commit();
ActivityTaskManagerInternal.PackageConfig appSpecificConfig = mAtm.mInternal
.getApplicationConfig(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
// Verifies if the persisted app-specific configuration is same as the committed
// configuration.
assertNotNull(appSpecificConfig);
assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB"), appSpecificConfig.mLocales);
// Verifies if the committed configuration is successfully applied to the required
// application while it is currently running.
assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
wpc.getConfiguration().getLocales());
}
private WindowProcessController createWindowProcessController(String packageName,
int userId) {
WindowProcessListener mMockListener = Mockito.mock(WindowProcessListener.class);
ApplicationInfo info = mock(ApplicationInfo.class);
info.packageName = packageName;
WindowProcessController wpc = new WindowProcessController(
mAtm, info, packageName, 0, userId, null, mMockListener);
wpc.setThread(mock(IApplicationThread.class));
return wpc;
}
@Test(expected = IllegalArgumentException.class)
public void testRegisterActivityStartInterceptor_IndexTooSmall() {
mAtm.mInternal.registerActivityStartInterceptor(FIRST_ORDERED_ID - 1,
new ActivityInterceptorCallback() {
@Nullable
@Override
public Intent intercept(ActivityInterceptorInfo info) {
return null;
}
});
}
@Test(expected = IllegalArgumentException.class)
public void testRegisterActivityStartInterceptor_IndexTooLarge() {
mAtm.mInternal.registerActivityStartInterceptor(LAST_ORDERED_ID + 1,
new ActivityInterceptorCallback() {
@Nullable
@Override
public Intent intercept(ActivityInterceptorInfo info) {
return null;
}
});
}
@Test(expected = IllegalArgumentException.class)
public void testRegisterActivityStartInterceptor_DuplicateId() {
mAtm.mInternal.registerActivityStartInterceptor(FIRST_ORDERED_ID,
new ActivityInterceptorCallback() {
@Nullable
@Override
public Intent intercept(ActivityInterceptorInfo info) {
return null;
}
});
mAtm.mInternal.registerActivityStartInterceptor(FIRST_ORDERED_ID,
new ActivityInterceptorCallback() {
@Nullable
@Override
public Intent intercept(ActivityInterceptorInfo info) {
return null;
}
});
}
@Test
public void testRegisterActivityStartInterceptor() {
assertEquals(0, mAtm.getActivityInterceptorCallbacks().size());
mAtm.mInternal.registerActivityStartInterceptor(FIRST_ORDERED_ID,
new ActivityInterceptorCallback() {
@Nullable
@Override
public Intent intercept(ActivityInterceptorInfo info) {
return null;
}
});
assertEquals(1, mAtm.getActivityInterceptorCallbacks().size());
assertTrue(mAtm.getActivityInterceptorCallbacks().contains(FIRST_ORDERED_ID));
}
}