*/
final AppOpsService mAppOpsService;
- /**
- * Current global configuration information. Contains general settings for the entire system,
- * also corresponds to the merged configuration of the default display.
- */
- Configuration mGlobalConfiguration = new Configuration();
-
/** Current sequencing integer of the configuration, for skipping old configurations. */
private int mConfigurationSeq;
/**
* Temp object used when global configuration is updated. It is also sent to outer world
- * instead of {@link #mGlobalConfiguration} because we don't trust anyone...
+ * instead of {@link #getGlobalConfiguration} because we don't trust anyone...
*/
private Configuration mTempGlobalConfig = new Configuration();
final boolean mPermissionReviewRequired;
+ /**
+ * Current global configuration information. Contains general settings for the entire system,
+ * also corresponds to the merged configuration of the default display.
+ */
+ Configuration getGlobalConfiguration() {
+ return mStackSupervisor.getConfiguration();
+ }
+
final class KillHandler extends Handler {
static final int KILL_PROCESS_GROUP_MSG = 4000;
callingPackage = r.info.getComponentName();
if (mInVrMode != vrMode) {
mInVrMode = vrMode;
- mShowDialogs = shouldShowDialogs(mGlobalConfiguration, mInVrMode);
+ mShowDialogs = shouldShowDialogs(getGlobalConfiguration(), mInVrMode);
if (r.app != null) {
ProcessRecord proc = r.app;
if (proc.vrThreadTid > 0) {
mTrackingAssociations = "1".equals(SystemProperties.get("debug.track-associations"));
- mGlobalConfiguration.setToDefaults();
- mGlobalConfiguration.setLocales(LocaleList.getDefault());
+ mTempGlobalConfig.setToDefaults();
+ mTempGlobalConfig.setLocales(LocaleList.getDefault());
+ mConfigurationSeq = mTempGlobalConfig.seq = 1;
- mConfigurationSeq = mGlobalConfiguration.seq = 1;
mProcessCpuTracker.init();
+ mStackSupervisor = new ActivityStackSupervisor(this);
+ mStackSupervisor.onConfigurationChanged(mTempGlobalConfig);
mCompatModePackages = new CompatModePackages(this, systemDir, mHandler);
mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler);
- mStackSupervisor = new ActivityStackSupervisor(this);
mActivityStarter = new ActivityStarter(this, mStackSupervisor);
mRecentTasks = new RecentTasks(this, mStackSupervisor);
}
final void showUnsupportedZoomDialogIfNeededLocked(ActivityRecord r) {
- if (mGlobalConfiguration.densityDpi != DisplayMetrics.DENSITY_DEVICE_STABLE
- && r.appInfo.requiresSmallestWidthDp > mGlobalConfiguration.smallestScreenWidthDp) {
+ final Configuration globalConfig = getGlobalConfiguration();
+ if (globalConfig.densityDpi != DisplayMetrics.DENSITY_DEVICE_STABLE
+ && r.appInfo.requiresSmallestWidthDp > globalConfig.smallestScreenWidthDp) {
final Message msg = Message.obtain();
msg.what = SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG;
msg.obj = r;
final long origId = Binder.clearCallingIdentity();
mWindowManager.setAppOrientation(r.appToken, requestedOrientation);
Configuration config = mWindowManager.updateOrientationFromAppTokens(
- mGlobalConfiguration, r.mayFreezeScreenLocked(r.app) ? r.appToken : null);
+ getGlobalConfiguration(), r.mayFreezeScreenLocked(r.app) ? r.appToken : null);
if (config != null) {
r.frozenBeforeDestroy = true;
if (!updateConfigurationLocked(config, r, false)) {
PackageManager.NOTIFY_PACKAGE_USE_INSTRUMENTATION);
}
if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Binding proc "
- + processName + " with config " + mGlobalConfiguration);
+ + processName + " with config " + getGlobalConfiguration());
ApplicationInfo appInfo = app.instrumentationInfo != null
? app.instrumentationInfo : app.info;
app.compat = compatibilityInfoForPackageLocked(appInfo);
app.instrumentationUiAutomationConnection, testMode,
mBinderTransactionTrackingEnabled, enableTrackAllocation,
isRestrictedBackupMode || !normalMode, app.persistent,
- new Configuration(mGlobalConfiguration), app.compat,
+ new Configuration(getGlobalConfiguration()), app.compat,
getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked(),
buildSerial);
}
}
- // Use the full screen as the context for the task thumbnail
- final Point displaySize = new Point();
- final TaskThumbnailInfo thumbnailInfo = new TaskThumbnailInfo();
- r.getStack().getDisplaySize(displaySize);
- thumbnailInfo.taskWidth = displaySize.x;
- thumbnailInfo.taskHeight = displaySize.y;
- thumbnailInfo.screenOrientation = mGlobalConfiguration.orientation;
-
TaskRecord task = new TaskRecord(this,
mStackSupervisor.getNextTaskIdForUserLocked(r.userId),
- ainfo, intent, description, thumbnailInfo);
+ ainfo, intent, description, new TaskThumbnailInfo());
int trimIdx = mRecentTasks.trimForTaskLocked(task, false);
if (trimIdx >= 0) {
// This happens before any activities are started, so we can change global configuration
// in-place.
updateConfigurationLocked(configuration, null, true);
- if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
- "Initial config: " + mGlobalConfiguration);
+ final Configuration globalConfig = getGlobalConfiguration();
+ if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Initial config: " + globalConfig);
// Load resources only after the current configuration has been set.
final Resources res = mContext.getResources();
com.android.internal.R.string.config_appsNotReportingCrashes));
mUserController.mUserSwitchUiEnabled = !res.getBoolean(
com.android.internal.R.bool.config_customUserSwitchUi);
- if ((mGlobalConfiguration.uiMode & UI_MODE_TYPE_TELEVISION)
- == UI_MODE_TYPE_TELEVISION) {
+ if ((globalConfig.uiMode & UI_MODE_TYPE_TELEVISION) == UI_MODE_TYPE_TELEVISION) {
mFullscreenThumbnailScale = (float) res
.getInteger(com.android.internal.R.integer.thumbnail_width_tv) /
- (float) mGlobalConfiguration.screenWidthDp;
+ (float) globalConfig.screenWidthDp;
} else {
mFullscreenThumbnailScale = res.getFraction(
com.android.internal.R.fraction.thumbnail_fullscreen_scale, 1, 1);
pw.println(" mHeavyWeightProcess: " + mHeavyWeightProcess);
}
if (dumpPackage == null) {
- pw.println(" mGlobalConfiguration: " + mGlobalConfiguration);
+ pw.println(" mGlobalConfiguration: " + getGlobalConfiguration());
}
if (dumpAll) {
pw.println(" mConfigWillChange: " + getFocusedStack().mConfigWillChange);
public ConfigurationInfo getDeviceConfigurationInfo() {
ConfigurationInfo config = new ConfigurationInfo();
synchronized (this) {
- config.reqTouchScreen = mGlobalConfiguration.touchscreen;
- config.reqKeyboardType = mGlobalConfiguration.keyboard;
- config.reqNavigation = mGlobalConfiguration.navigation;
- if (mGlobalConfiguration.navigation == Configuration.NAVIGATION_DPAD
- || mGlobalConfiguration.navigation == Configuration.NAVIGATION_TRACKBALL) {
+ final Configuration globalConfig = getGlobalConfiguration();
+ config.reqTouchScreen = globalConfig.touchscreen;
+ config.reqKeyboardType = globalConfig.keyboard;
+ config.reqNavigation = globalConfig.navigation;
+ if (globalConfig.navigation == Configuration.NAVIGATION_DPAD
+ || globalConfig.navigation == Configuration.NAVIGATION_TRACKBALL) {
config.reqInputFeatures |= ConfigurationInfo.INPUT_FEATURE_FIVE_WAY_NAV;
}
- if (mGlobalConfiguration.keyboard != Configuration.KEYBOARD_UNDEFINED
- && mGlobalConfiguration.keyboard != Configuration.KEYBOARD_NOKEYS) {
+ if (globalConfig.keyboard != Configuration.KEYBOARD_UNDEFINED
+ && globalConfig.keyboard != Configuration.KEYBOARD_NOKEYS) {
config.reqInputFeatures |= ConfigurationInfo.INPUT_FEATURE_HARD_KEYBOARD;
}
config.reqGlEsVersion = GL_ES_VERSION;
public Configuration getConfiguration() {
Configuration ci;
synchronized(this) {
- ci = new Configuration(mGlobalConfiguration);
+ ci = new Configuration(getGlobalConfiguration());
ci.userSetLocale = false;
}
return ci;
private void updateFontScaleIfNeeded(@UserIdInt int userId) {
final float scaleFactor = Settings.System.getFloatForUser(mContext.getContentResolver(),
FONT_SCALE, 1.0f, userId);
- if (mGlobalConfiguration.fontScale != scaleFactor) {
+ if (getGlobalConfiguration().fontScale != scaleFactor) {
final Configuration configuration = mWindowManager.computeNewConfiguration();
configuration.fontScale = scaleFactor;
synchronized (this) {
}
void updateUserConfigurationLocked() {
- final Configuration configuration = new Configuration(mGlobalConfiguration);
+ final Configuration configuration = new Configuration(getGlobalConfiguration());
final int currentUserId = mUserController.getCurrentUserIdLocked();
Settings.System.adjustConfigurationForUser(mContext.getContentResolver(), configuration,
currentUserId, Settings.System.canWrite(mContext));
/** Update default (global) configuration and notify listeners about changes. */
private int updateGlobalConfiguration(@NonNull Configuration values, boolean initLocale,
boolean persistent, int userId, boolean deferResume) {
- mTempGlobalConfig.setTo(mGlobalConfiguration);
+ mTempGlobalConfig.setTo(getGlobalConfiguration());
final int changes = mTempGlobalConfig.updateFrom(values);
if (changes == 0) {
return 0;
mConfigurationSeq = Math.max(++mConfigurationSeq, 1);
mTempGlobalConfig.seq = mConfigurationSeq;
- mGlobalConfiguration.setTo(mTempGlobalConfig);
+ // Update stored global config and notify everyone about the change.
+ mStackSupervisor.onConfigurationChanged(mTempGlobalConfig);
+
Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + mTempGlobalConfig);
// TODO(multi-display): Update UsageEvents#Event to include displayId.
mUsageStatsService.reportConfigurationChange(mTempGlobalConfig,
// We need another copy of global config because we're scheduling some calls instead of
// running them in place. We need to be sure that object we send will be handled unchanged.
- final Configuration configCopy = new Configuration(mGlobalConfiguration);
+ final Configuration configCopy = new Configuration(mTempGlobalConfig);
if (persistent && Settings.System.hasInterestingConfigurationChanges(changes)) {
Message msg = mHandler.obtainMessage(UPDATE_CONFIGURATION_MSG);
msg.obj = configCopy;
package com.android.server.am;
import static android.app.ActivityManager.StackId;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.content.pm.ActivityInfo.FLAG_ON_TOP_LAUNCHER;
long cpuTimeAtResume; // the cpu time of host process at the time of resuming activity
long pauseTime; // last time we started pausing the activity
long launchTickTime; // base time for launch tick messages
- Configuration configuration; // configuration activity was last running in
+ Configuration mLastReportedConfiguration; // configuration activity was last running in
// Overridden configuration by the activity task
- // WARNING: Reference points to {@link TaskRecord#mOverrideConfig}, so its internal state
- // should never be altered directly.
- Configuration taskConfigOverride;
+ // WARNING: Reference points to {@link TaskRecord#getMergedOverrideConfig}, so its internal
+ // state should never be altered directly.
+ Configuration mLastReportedOverrideConfiguration;
CompatibilityInfo compat;// last used compatibility mode
ActivityRecord resultTo; // who started this entry, so will get our reply
final String resultWho; // additional identifier for use by resultTo.
pw.print(" labelRes=0x"); pw.print(Integer.toHexString(labelRes));
pw.print(" icon=0x"); pw.print(Integer.toHexString(icon));
pw.print(" theme=0x"); pw.println(Integer.toHexString(theme));
- pw.print(prefix); pw.print("config="); pw.println(configuration);
- pw.print(prefix); pw.print("taskConfigOverride="); pw.println(taskConfigOverride);
+ pw.print(prefix); pw.print("mLastReportedConfiguration=");
+ pw.println(mLastReportedConfiguration);
+ pw.print(prefix); pw.print("mLastReportedOverrideConfiguration=");
+ pw.println(mLastReportedOverrideConfiguration);
if (resultTo != null || resultWho != null) {
pw.print(prefix); pw.print("resultTo="); pw.print(resultTo);
pw.print(" resultWho="); pw.print(resultWho);
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending new config to " + this + " " +
"reportToActivity=" + reportToActivity + " and config: " + config);
- app.thread.scheduleActivityConfigurationChanged(appToken, config, reportToActivity);
+ app.thread.scheduleActivityConfigurationChanged(appToken, new Configuration(config),
+ reportToActivity);
} catch (RemoteException e) {
// If process died, whatever.
}
resolvedType = _resolvedType;
componentSpecified = _componentSpecified;
rootVoiceInteraction = _rootVoiceInteraction;
- configuration = _configuration;
- taskConfigOverride = Configuration.EMPTY;
+ mLastReportedConfiguration = new Configuration(_configuration);
+ mLastReportedOverrideConfiguration = new Configuration();
resultTo = _resultTo;
resultWho = _resultWho;
requestCode = _reqCode;
import static android.content.pm.ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
-import static android.content.res.Configuration.SCREENLAYOUT_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ADD_REMOVE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
/**
* State and management of a single stack of activities.
*/
-final class ActivityStack {
+final class ActivityStack extends ConfigurationContainer {
private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStack" : TAG_AM;
private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE;
// How many activities have to be scheduled to stop to force a stop pass.
private static final int MAX_STOPPING_TO_FORCE = 3;
+ @Override
+ protected int getChildCount() {
+ return mTaskHistory.size();
+ }
+
+ @Override
+ protected ConfigurationContainer getChildAt(int index) {
+ return mTaskHistory.get(index);
+ }
+
+ @Override
+ protected ConfigurationContainer getParent() {
+ return mActivityContainer.mActivityDisplay;
+ }
+
enum ActivityState {
INITIALIZING,
RESUMED,
private final SparseArray<Rect> mTmpInsetBounds = new SparseArray<>();
private final Rect tempRect2 = new Rect();
+ /**
+ * Temp configs used in {@link #ensureActivityConfigurationLocked(ActivityRecord, int, boolean)}
+ */
+ private final Configuration mTmpGlobalConfig = new Configuration();
+ private final Configuration mTmpTaskConfig = new Configuration();
+
/** Run all ActivityStacks through this */
final ActivityStackSupervisor mStackSupervisor;
mTaskPositioner.setDisplay(activityDisplay.mDisplay);
mTaskPositioner.configure(mBounds);
}
+ onParentChanged();
if (mStackId == DOCKED_STACK_ID) {
// If we created a docked stack we want to resize it so it resizes all other stacks
mTaskPositioner.reset();
}
mWindowManager.detachStack(mStackId);
+ onParentChanged();
if (mStackId == DOCKED_STACK_ID) {
// If we removed a docked stack we want to resize it so it resizes all other stacks
// in the system to fullscreen.
boolean notUpdated = true;
if (mStackSupervisor.isFocusedStack(this)) {
Configuration config = mWindowManager.updateOrientationFromAppTokens(
- mService.mGlobalConfiguration,
+ mService.getGlobalConfiguration(),
next.mayFreezeScreenLocked(next.app) ? next.appToken : null);
if (config != null) {
next.frozenBeforeDestroy = true;
}
}
- mTmpConfigs.put(task.taskId, task.mOverrideConfig);
+ mTmpConfigs.put(task.taskId, task.getOverrideConfiguration());
mTmpBounds.put(task.taskId, task.mBounds);
if (tempTaskInsetBounds != null) {
mTmpInsetBounds.put(task.taskId, tempTaskInsetBounds);
// Short circuit: if the two configurations are equal (the common case), then there is
// nothing to do.
- final Configuration newConfig = mService.mGlobalConfiguration;
- r.task.sanitizeOverrideConfiguration(newConfig);
- final Configuration taskConfig = r.task.mOverrideConfig;
- if (r.configuration.equals(newConfig)
- && r.taskConfigOverride.equals(taskConfig)
+ final Configuration newGlobalConfig = mService.getGlobalConfiguration();
+ final Configuration newTaskMergedOverrideConfig = r.task.getMergedOverrideConfiguration();
+ if (r.mLastReportedConfiguration.equals(newGlobalConfig)
+ && r.mLastReportedOverrideConfiguration.equals(newTaskMergedOverrideConfig)
&& !r.forceNewConfig) {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Configuration unchanged in " + r);
// Okay we now are going to make this activity have the new config.
// But then we need to figure out how it needs to deal with that.
- final Configuration oldConfig = r.configuration;
- final Configuration oldTaskOverride = r.taskConfigOverride;
- r.configuration = newConfig;
- r.taskConfigOverride = taskConfig;
-
- int taskChanges = getTaskConfigurationChanges(r, taskConfig, oldTaskOverride);
- final int changes = oldConfig.diff(newConfig) | taskChanges;
+ mTmpGlobalConfig.setTo(r.mLastReportedConfiguration);
+ mTmpTaskConfig.setTo(r.mLastReportedOverrideConfiguration);
+ r.mLastReportedConfiguration.setTo(newGlobalConfig);
+ r.mLastReportedOverrideConfiguration.setTo(newTaskMergedOverrideConfig);
+
+ int taskChanges = getTaskConfigurationChanges(r, newTaskMergedOverrideConfig,
+ mTmpTaskConfig);
+ final int changes = mTmpGlobalConfig.diff(newGlobalConfig) | taskChanges;
if (changes == 0 && !r.forceNewConfig) {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Configuration no differences in " + r);
// There are no significant differences, so we won't relaunch but should still deliver
// the new configuration to the client process.
- r.scheduleConfigurationChanged(taskConfig, true);
+ r.scheduleConfigurationChanged(newTaskMergedOverrideConfig, true);
return true;
}
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Checking to restart " + r.info.name + ": changed=0x"
+ Integer.toHexString(changes) + ", handles=0x"
- + Integer.toHexString(r.info.getRealConfigChanged()) + ", newConfig=" + newConfig
- + ", taskConfig=" + taskConfig);
+ + Integer.toHexString(r.info.getRealConfigChanged())
+ + ", newGlobalConfig=" + newGlobalConfig
+ + ", newTaskMergedOverrideConfig=" + newTaskMergedOverrideConfig);
if ((changes&(~r.info.getRealConfigChanged())) != 0 || r.forceNewConfig) {
// Aha, the activity isn't handling the change, so DIE DIE DIE.
// NOTE: We only forward the task override configuration as the system level configuration
// changes is always sent to all processes when they happen so it can just use whatever
// system level configuration it last got.
- r.scheduleConfigurationChanged(taskConfig, true);
+ r.scheduleConfigurationChanged(newTaskMergedOverrideConfig, true);
r.stopFreezingScreenLocked(false);
return true;
// configuration task diff, so the diff stays as small as possible.
if (Configuration.EMPTY.equals(oldTaskOverride)
&& !Configuration.EMPTY.equals(taskConfig)) {
- oldTaskOverride = record.task.extractOverrideConfig(record.configuration);
+ oldTaskOverride = record.task.extractOverrideConfig(record.mLastReportedConfiguration);
}
// Conversely, do the same when going the other direction.
if (Configuration.EMPTY.equals(taskConfig)
&& !Configuration.EMPTY.equals(oldTaskOverride)) {
- taskConfig = record.task.extractOverrideConfig(record.configuration);
+ taskConfig = record.task.extractOverrideConfig(record.mLastReportedConfiguration);
}
// Determine what has changed. May be nothing, if this is a config
r.forceNewConfig = false;
mStackSupervisor.activityRelaunchingLocked(r);
r.app.thread.scheduleRelaunchActivity(r.appToken, results, newIntents, changes,
- !andResume, new Configuration(mService.mGlobalConfiguration),
- new Configuration(r.task.mOverrideConfig), preserveWindow);
+ !andResume, new Configuration(mService.getGlobalConfiguration()),
+ new Configuration(r.task.getMergedOverrideConfiguration()), preserveWindow);
// Note: don't need to call pauseIfSleepingLocked() here, because
// the caller will only pass in 'andResume' if this activity is
// currently resumed, which implies we aren't sleeping.
mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken,
r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
(r.info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0, r.userId, r.info.configChanges,
- task.voiceSession != null, r.mLaunchTaskBehind, bounds, task.mOverrideConfig,
- task.mResizeMode, r.isAlwaysFocusable(), task.isHomeTask(),
- r.appInfo.targetSdkVersion, r.mRotationAnimationHint, task.isOnTopLauncher());
- r.taskConfigOverride = task.mOverrideConfig;
+ task.voiceSession != null, r.mLaunchTaskBehind, bounds,
+ task.getOverrideConfiguration(), task.mResizeMode, r.isAlwaysFocusable(),
+ task.isHomeTask(), r.appInfo.targetSdkVersion, r.mRotationAnimationHint,
+ task.isOnTopLauncher());
+ r.mLastReportedOverrideConfiguration.setTo(task.getMergedOverrideConfiguration());
}
void moveToFrontAndResumeStateIfNeeded(
private void setAppTask(ActivityRecord r, TaskRecord task) {
final Rect bounds = task.updateOverrideConfigurationFromLaunchBounds();
- mWindowManager.setAppTask(r.appToken, task.taskId, mStackId, bounds, task.mOverrideConfig,
- task.mResizeMode, task.isHomeTask(), task.isOnTopLauncher());
- r.taskConfigOverride = task.mOverrideConfig;
+ mWindowManager.setAppTask(r.appToken, task.taskId, mStackId, bounds,
+ task.getOverrideConfiguration(), task.mResizeMode, task.isHomeTask(),
+ task.isOnTopLauncher());
+ r.mLastReportedOverrideConfiguration.setTo(task.getMergedOverrideConfiguration());
}
public int getStackId() {
import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_WHITELISTED;
import static com.android.server.wm.AppTransition.TRANSIT_DOCK_TASK_FROM_RECENTS;
-public final class ActivityStackSupervisor implements DisplayListener {
+public final class ActivityStackSupervisor extends ConfigurationContainer
+ implements DisplayListener {
private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStackSupervisor" : TAG_AM;
private static final String TAG_CONTAINERS = TAG + POSTFIX_CONTAINERS;
private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS;
private final ResizeDockedStackTimeout mResizeDockedStackTimeout;
+ @Override
+ protected int getChildCount() {
+ return mActivityDisplays.size();
+ }
+
+ @Override
+ protected ConfigurationContainer getChildAt(int index) {
+ return mActivityDisplays.valueAt(index);
+ }
+
+ @Override
+ protected ConfigurationContainer getParent() {
+ return null;
+ }
+
static class FindTaskResult {
ActivityRecord r;
boolean matchedByRootAffinity;
// just restarting it anyway.
if (checkConfig) {
Configuration config = mWindowManager.updateOrientationFromAppTokens(
- mService.mGlobalConfiguration,
+ mService.getGlobalConfiguration(),
r.mayFreezeScreenLocked(app) ? r.appToken : null);
// Deferring resume here because we're going to launch new activity shortly.
// We don't want to perform a redundant launch of the same record while ensuring
app.pendingUiClean = true;
}
app.forceProcessStateUpTo(mService.mTopProcessState);
+ // Because we could be starting an Activity in the system process this may not go across
+ // a Binder interface which would create a new Configuration. Consequently we have to
+ // always create a new Configuration here.
app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
System.identityHashCode(r), r.info,
- new Configuration(mService.mGlobalConfiguration),
- new Configuration(task.mOverrideConfig), r.compat, r.launchedFromPackage,
- task.voiceInteractor, app.repProcState, r.icicle, r.persistentState, results,
- newIntents, !andResume, mService.isNextTransitionForward(), profilerInfo);
+ new Configuration(mService.getGlobalConfiguration()),
+ new Configuration(task.getMergedOverrideConfiguration()), r.compat,
+ r.launchedFromPackage, task.voiceInteractor, app.repProcState, r.icicle,
+ r.persistentState, results, newIntents, !andResume,
+ mService.isNextTransitionForward(), profilerInfo);
if ((app.info.privateFlags&ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) {
// This may be a heavy-weight process! Note that the package
// We'll update with whatever configuration it now says
// it used to launch.
if (config != null) {
- r.configuration = config;
+ r.mLastReportedConfiguration.setTo(config);
}
// We are now idle. If someone is waiting for a thumbnail from
// WM resizeTask must be done after the task is moved to the correct stack,
// because Task's setBounds() also updates dim layer's bounds, but that has
// dependency on the stack.
- mWindowManager.resizeTask(task.taskId, task.mBounds, task.mOverrideConfig,
- false /* relayout */, false /* forced */);
+ mWindowManager.resizeTask(task.taskId, task.mBounds,
+ task.getOverrideConfiguration(), false /* relayout */,
+ false /* forced */);
}
}
}
}
}
}
- mWindowManager.resizeTask(task.taskId, task.mBounds, task.mOverrideConfig, kept, forced);
+ mWindowManager.resizeTask(task.taskId, task.mBounds, task.getOverrideConfiguration(), kept,
+ forced);
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
return kept;
task.updateOverrideConfigurationForStack(stack);
mWindowManager.positionTaskInStack(
- taskId, stackId, position, task.mBounds, task.mOverrideConfig);
+ taskId, stackId, position, task.mBounds, task.getOverrideConfiguration());
stack.positionTask(task, position);
// The task might have already been running and its visibility needs to be synchronized with
// the visibility of the stack / windows.
/** Exactly one of these classes per Display in the system. Capable of holding zero or more
* attached {@link ActivityStack}s */
- class ActivityDisplay {
+ class ActivityDisplay extends ConfigurationContainer {
/** Actual Display this object tracks. */
int mDisplayId;
Display mDisplay;
public String toString() {
return "ActivityDisplay={" + mDisplayId + " numStacks=" + mStacks.size() + "}";
}
+
+ @Override
+ protected int getChildCount() {
+ return mStacks.size();
+ }
+
+ @Override
+ protected ConfigurationContainer getChildAt(int index) {
+ return mStacks.get(index);
+ }
+
+ @Override
+ protected ConfigurationContainer getParent() {
+ return ActivityStackSupervisor.this;
+ }
}
class VirtualActivityDisplay extends ActivityDisplay {
}
ActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage,
- intent, resolvedType, aInfo, mService.mGlobalConfiguration, resultRecord, resultWho,
- requestCode, componentSpecified, voiceSession != null, mSupervisor, container,
- options, sourceRecord);
+ intent, resolvedType, aInfo, mService.getGlobalConfiguration(), resultRecord,
+ resultWho, requestCode, componentSpecified, voiceSession != null, mSupervisor,
+ container, options, sourceRecord);
if (outActivity != null) {
outActivity[0] = r;
}
stack = container.mStack;
}
stack.mConfigWillChange = globalConfig != null
- && mService.mGlobalConfiguration.diff(globalConfig) != 0;
+ && mService.getGlobalConfiguration().diff(globalConfig) != 0;
if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Starting activity when config will change = " + stack.mConfigWillChange);
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
}
public CompatibilityInfo compatibilityInfoForPackageLocked(ApplicationInfo ai) {
- CompatibilityInfo ci = new CompatibilityInfo(ai, mService.mGlobalConfiguration.screenLayout,
- mService.mGlobalConfiguration.smallestScreenWidthDp,
+ final Configuration globalConfig = mService.getGlobalConfiguration();
+ CompatibilityInfo ci = new CompatibilityInfo(ai, globalConfig.screenLayout,
+ globalConfig.smallestScreenWidthDp,
(getPackageFlags(ai.packageName)&COMPAT_FLAG_ENABLED) != 0);
//Slog.i(TAG, "*********** COMPAT FOR PKG " + ai.packageName + ": " + ci);
return ci;
}
public int computeCompatModeLocked(ApplicationInfo ai) {
- boolean enabled = (getPackageFlags(ai.packageName)&COMPAT_FLAG_ENABLED) != 0;
- CompatibilityInfo info = new CompatibilityInfo(ai,
- mService.mGlobalConfiguration.screenLayout,
- mService.mGlobalConfiguration.smallestScreenWidthDp, enabled);
+ final boolean enabled = (getPackageFlags(ai.packageName)&COMPAT_FLAG_ENABLED) != 0;
+ final Configuration globalConfig = mService.getGlobalConfiguration();
+ final CompatibilityInfo info = new CompatibilityInfo(ai, globalConfig.screenLayout,
+ globalConfig.smallestScreenWidthDp, enabled);
if (info.alwaysSupportsScreen()) {
return ActivityManager.COMPAT_MODE_NEVER;
}
out.startTag(null, "compat-packages");
final IPackageManager pm = AppGlobals.getPackageManager();
- final int screenLayout = mService.mGlobalConfiguration.screenLayout;
- final int smallestScreenWidthDp = mService.mGlobalConfiguration.smallestScreenWidthDp;
+ final Configuration globalConfig = mService.getGlobalConfiguration();
+ final int screenLayout = globalConfig.screenLayout;
+ final int smallestScreenWidthDp = globalConfig.smallestScreenWidthDp;
final Iterator<Map.Entry<String, Integer>> it = pkgs.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Integer> entry = it.next();
--- /dev/null
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.am;
+
+import android.content.res.Configuration;
+
+/**
+ * Contains common logic for classes that have override configurations and are organized in a
+ * hierarchy.
+ */
+abstract class ConfigurationContainer<E extends ConfigurationContainer> {
+
+ /** Contains override configuration settings applied to this configuration container. */
+ private Configuration mOverrideConfiguration = new Configuration();
+
+ /**
+ * Contains full configuration applied to this configuration container. Corresponds to full
+ * parent's config with applied {@link #mOverrideConfiguration}.
+ */
+ private Configuration mFullConfiguration = new Configuration();
+
+ /**
+ * Contains merged override configuration settings from the top of the hierarchy down to this
+ * particular instance. It is different from {@link #mFullConfiguration} because it starts from
+ * topmost container's override config instead of global config.
+ */
+ private Configuration mMergedOverrideConfiguration = new Configuration();
+
+ /**
+ * Returns full configuration applied to this configuration container.
+ * This method should be used for getting settings applied in each particular level of the
+ * hierarchy.
+ */
+ Configuration getConfiguration() {
+ return mFullConfiguration;
+ }
+
+ /**
+ * Notify that parent config changed and we need to update full configuration.
+ * @see #mFullConfiguration
+ */
+ void onConfigurationChanged(Configuration newParentConfig) {
+ mFullConfiguration.setTo(newParentConfig);
+ mFullConfiguration.updateFrom(mOverrideConfiguration);
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ final ConfigurationContainer child = getChildAt(i);
+ child.onConfigurationChanged(mFullConfiguration);
+ }
+ }
+
+ /** Returns override configuration applied to this configuration container. */
+ Configuration getOverrideConfiguration() {
+ return mOverrideConfiguration;
+ }
+
+ /**
+ * Update override configuration and recalculate full config.
+ * @see #mOverrideConfiguration
+ * @see #mFullConfiguration
+ */
+ void onOverrideConfigurationChanged(Configuration overrideConfiguration) {
+ mOverrideConfiguration.setTo(overrideConfiguration);
+ // Update full configuration of this container and all its children.
+ final ConfigurationContainer parent = getParent();
+ onConfigurationChanged(parent != null ? parent.getConfiguration() : Configuration.EMPTY);
+ // Update merged override config of this container and all its children.
+ onMergedOverrideConfigurationChanged();
+ }
+
+ /**
+ * Get merged override configuration from the top of the hierarchy down to this particular
+ * instance. This should be reported to client as override config.
+ */
+ Configuration getMergedOverrideConfiguration() {
+ return mMergedOverrideConfiguration;
+ }
+
+ /**
+ * Update merged override configuration based on corresponding parent's config and notify all
+ * its children. If there is no parent, merged override configuration will set equal to current
+ * override config.
+ * @see #mMergedOverrideConfiguration
+ */
+ private void onMergedOverrideConfigurationChanged() {
+ final ConfigurationContainer parent = getParent();
+ if (parent != null) {
+ mMergedOverrideConfiguration.setTo(parent.getMergedOverrideConfiguration());
+ mMergedOverrideConfiguration.updateFrom(mOverrideConfiguration);
+ } else {
+ mMergedOverrideConfiguration.setTo(mOverrideConfiguration);
+ }
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ final ConfigurationContainer child = getChildAt(i);
+ child.onMergedOverrideConfigurationChanged();
+ }
+ }
+
+ /**
+ * Must be called when new parent for the container was set.
+ */
+ void onParentChanged() {
+ final ConfigurationContainer parent = getParent();
+ // Update full configuration of this container and all its children.
+ onConfigurationChanged(parent != null ? parent.mFullConfiguration : Configuration.EMPTY);
+ // Update merged override configuration of this container and all its children.
+ onMergedOverrideConfigurationChanged();
+ }
+
+ abstract protected int getChildCount();
+
+ abstract protected E getChildAt(int index);
+
+ abstract protected ConfigurationContainer getParent();
+}
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
import java.util.Objects;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.content.pm.ActivityInfo.RESIZE_MODE_CROP_WINDOWS;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
-import static android.content.res.Configuration.SCREENLAYOUT_LONG_MASK;
-import static android.content.res.Configuration.SCREENLAYOUT_SIZE_MASK;
import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ADD_REMOVE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK;
import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
import static com.android.server.am.ActivityRecord.STARTING_WINDOW_SHOWN;
-final class TaskRecord {
+final class TaskRecord extends ConfigurationContainer {
private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskRecord" : TAG_AM;
private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE;
private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS;
// This number will be assigned when we evaluate OOM scores for all visible tasks.
int mLayerRank = -1;
- /** Contains configurations settings that are different from the parent's configuration. */
- Configuration mOverrideConfig = Configuration.EMPTY;
+ /** Helper object used for updating override configuration. */
+ private Configuration mTmpConfig = new Configuration();
TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent,
IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor) {
return mStack;
}
+ /** Must be used for setting parent stack because it performs configuration updates. */
void setStack(ActivityStack stack) {
mStack = stack;
+ onParentChanged();
}
/**
return mStack != null ? mStack.mStackId : INVALID_STACK_ID;
}
+ @Override
+ protected int getChildCount() {
+ return 0;
+ }
+
+ @Override
+ protected ConfigurationContainer getChildAt(int index) {
+ return null;
+ }
+
+ @Override
+ protected ConfigurationContainer getParent() {
+ return mStack;
+ }
+
// Close up recents linked list.
void closeRecentsChain() {
if (mPrevAffiliate != null) {
* @return whether the thumbnail was set
*/
boolean setLastThumbnailLocked(Bitmap thumbnail) {
- final Configuration serviceConfig = mService.mGlobalConfiguration;
int taskWidth = 0;
int taskHeight = 0;
if (mBounds != null) {
} else {
Slog.e(TAG, "setLastThumbnailLocked() called on Task without stack");
}
- return setLastThumbnailLocked(thumbnail, taskWidth, taskHeight, serviceConfig.orientation);
+ // We need to provide the current orientation of the display on which this task resides,
+ // not the orientation of the task.
+ final int orientation =
+ getStack().mActivityContainer.mActivityDisplay.getConfiguration().orientation;
+ return setLastThumbnailLocked(thumbnail, taskWidth, taskHeight, orientation);
}
/**
if (Objects.equals(mBounds, bounds)) {
return false;
}
- final Configuration oldConfig = mOverrideConfig;
+ mTmpConfig.setTo(getOverrideConfiguration());
final boolean oldFullscreen = mFullscreen;
+ final Configuration newConfig = getOverrideConfiguration();
mFullscreen = bounds == null;
if (mFullscreen) {
mLastNonFullscreenBounds = mBounds;
}
mBounds = null;
- mOverrideConfig = Configuration.EMPTY;
+ newConfig.unset();
} else {
mTmpRect.set(bounds);
adjustForMinimalTaskDimensions(mTmpRect);
if (mStack == null || StackId.persistTaskBounds(mStack.mStackId)) {
mLastNonFullscreenBounds = mBounds;
}
- mOverrideConfig = calculateOverrideConfig(mTmpRect, insetBounds,
+ calculateOverrideConfig(newConfig, mTmpRect, insetBounds,
mTmpRect.right != bounds.right, mTmpRect.bottom != bounds.bottom);
}
+ onOverrideConfigurationChanged(newConfig);
if (mFullscreen != oldFullscreen) {
mService.mStackSupervisor.scheduleReportMultiWindowModeChanged(this);
}
- return !mOverrideConfig.equals(oldConfig);
+ return !mTmpConfig.equals(newConfig);
}
private void subtractNonDecorInsets(Rect inOutBounds, Rect inInsetBounds,
inOutBounds.inset(leftInset, topInset, rightInset, bottomInset);
}
- private Configuration calculateOverrideConfig(Rect bounds, Rect insetBounds,
- boolean overrideWidth, boolean overrideHeight) {
+ /** Clears passed config and fills it with new override values. */
+ private Configuration calculateOverrideConfig(Configuration config, Rect bounds,
+ Rect insetBounds, boolean overrideWidth, boolean overrideHeight) {
mTmpNonDecorBounds.set(bounds);
mTmpStableBounds.set(bounds);
subtractNonDecorInsets(
mTmpStableBounds, insetBounds != null ? insetBounds : bounds,
overrideWidth, overrideHeight);
- // For calculating screenWidthDp, screenWidthDp, we use the stable inset screen area,
+ // For calculating screenWidthDp, screenHeightDp, we use the stable inset screen area,
// i.e. the screen area without the system bars.
- final Configuration serviceConfig = mService.mGlobalConfiguration;
- final Configuration config = new Configuration(Configuration.EMPTY);
- // TODO(multidisplay): Update Dp to that of display stack is on.
- final float density = serviceConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ // Additionally task dimensions should not be bigger than its parents dimensions.
+ final Configuration parentConfig = getParent().getConfiguration();
+ config.unset();
+ final float density = parentConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
config.screenWidthDp =
- Math.min((int)(mTmpStableBounds.width() / density), serviceConfig.screenWidthDp);
+ Math.min((int)(mTmpStableBounds.width() / density), parentConfig.screenWidthDp);
config.screenHeightDp =
- Math.min((int)(mTmpStableBounds.height() / density), serviceConfig.screenHeightDp);
+ Math.min((int)(mTmpStableBounds.height() / density), parentConfig.screenHeightDp);
// TODO: Orientation?
config.orientation = (config.screenWidthDp <= config.screenHeightDp)
// never go away in Honeycomb.
final int compatScreenWidthDp = (int)(mTmpNonDecorBounds.width() / density);
final int compatScreenHeightDp = (int)(mTmpNonDecorBounds.height() / density);
- final int sl = Configuration.resetScreenLayout(serviceConfig.screenLayout);
+ // We're only overriding LONG, SIZE and COMPAT parts of screenLayout, so we start override
+ // calculation with partial default.
+ final int sl = Configuration.SCREENLAYOUT_LONG_YES | Configuration.SCREENLAYOUT_SIZE_XLARGE;
final int longSize = Math.max(compatScreenHeightDp, compatScreenWidthDp);
- final int shortSize = Math.min(compatScreenHeightDp, compatScreenWidthDp);;
+ final int shortSize = Math.min(compatScreenHeightDp, compatScreenWidthDp);
config.screenLayout = Configuration.reduceScreenLayout(sl, longSize, shortSize);
config.smallestScreenWidthDp = mService.mWindowManager.getSmallestWidthForTaskBounds(
* {@param config}.
*/
Configuration extractOverrideConfig(Configuration config) {
- final Configuration extracted = new Configuration(Configuration.EMPTY);
+ final Configuration extracted = new Configuration();
extracted.screenWidthDp = config.screenWidthDp;
extracted.screenHeightDp = config.screenHeightDp;
extracted.smallestScreenWidthDp = config.smallestScreenWidthDp;
extracted.orientation = config.orientation;
- extracted.screenLayout = config.screenLayout;
+ // We're only overriding LONG, SIZE and COMPAT parts of screenLayout.
+ extracted.screenLayout = config.screenLayout & (Configuration.SCREENLAYOUT_LONG_MASK
+ | Configuration.SCREENLAYOUT_SIZE_MASK | Configuration.SCREENLAYOUT_COMPAT_NEEDED);
return extracted;
}
return bounds;
}
- /**
- * Update fields that are not overridden for task from global configuration.
- *
- * @param globalConfig global configuration to update from.
- */
- void sanitizeOverrideConfiguration(Configuration globalConfig) {
- // If it's fullscreen, the override config should be empty and we should leave it alone.
- if (mFullscreen) {
- return;
- }
-
- // screenLayout field is set in #calculateOverrideConfig but only part of it is really
- // overridden - aspect ratio and size. Other flags (like layout direction) can be updated
- // separately in global config and they also must be updated in override config.
- int overrideScreenLayout = mOverrideConfig.screenLayout;
- int newScreenLayout = globalConfig.screenLayout;
- newScreenLayout = (newScreenLayout & ~SCREENLAYOUT_LONG_MASK)
- | (overrideScreenLayout & SCREENLAYOUT_LONG_MASK);
- newScreenLayout = (newScreenLayout & ~SCREENLAYOUT_SIZE_MASK)
- | (overrideScreenLayout & SCREENLAYOUT_SIZE_MASK);
- mOverrideConfig.screenLayout = newScreenLayout;
- }
-
static Rect validateBounds(Rect bounds) {
if (bounds != null && bounds.isEmpty()) {
Slog.wtf(TAG, "Received strange task bounds: " + bounds, new Throwable());
stack.addTask(this, toTop);
}
- void positionTaskInStack(TaskStack stack, int position, Rect bounds, Configuration config) {
+ void positionTaskInStack(TaskStack stack, int position, Rect bounds,
+ Configuration overrideConfig) {
if (mStack != null && stack != mStack) {
if (DEBUG_STACK) Slog.i(TAG, "positionTaskInStack: removing taskId=" + mTaskId
+ " from stack=" + mStack);
mStack.removeChild(this);
}
stack.positionTask(this, position, showForAllUsers());
- resizeLocked(bounds, config, false /* force */);
+ resizeLocked(bounds, overrideConfig, false /* force */);
for (int activityNdx = mChildren.size() - 1; activityNdx >= 0; --activityNdx) {
mChildren.get(activityNdx).notifyMovedInStack();
}
public void positionTaskInStack(int taskId, int stackId, int position, Rect bounds,
- Configuration config) {
+ Configuration overrideConfig) {
synchronized (mWindowMap) {
if (DEBUG_STACK) Slog.i(TAG_WM, "positionTaskInStack: positioning taskId=" + taskId
+ " in stackId=" + stackId + " at " + position);
"positionTaskInStack: could not find stackId=" + stackId);
return;
}
- task.positionTaskInStack(stack, position, bounds, config);
+ task.positionTaskInStack(stack, position, bounds, overrideConfig);
final DisplayContent displayContent = stack.getDisplayContent();
displayContent.setLayoutNeeded();
mWindowPlacerLocked.performSurfacePlacement();
*/
public int getSmallestWidthForTaskBounds(Rect bounds) {
synchronized (mWindowMap) {
+ // TODO(multi-display): Use correct display content here
return getDefaultDisplayContentLocked().getDockedDividerController()
.getSmallestWidthDpForBounds(bounds);
}
--- /dev/null
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.am;
+
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.content.res.Configuration;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test class for {@link ConfigurationContainer}. Mostly duplicates configuration tests from
+ * {@link com.android.server.wm.WindowContainerTests}.
+ *
+ * Build: mmma -j32 frameworks/base/services/tests/servicestests
+ * Install: adb install -r out/target/product/$TARGET_PRODUCT/data/app/FrameworksServicesTests/FrameworksServicesTests.apk
+ * Run: adb shell am instrument -w -e class com.android.server.am.ConfigurationContainerTests com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class ConfigurationContainerTests {
+
+ @Test
+ public void testConfigurationInit() throws Exception {
+ // Check root container initial config.
+ final TestConfigurationContainer root = new TestConfigurationContainer();
+ assertEquals(Configuration.EMPTY, root.getOverrideConfiguration());
+ assertEquals(Configuration.EMPTY, root.getMergedOverrideConfiguration());
+ assertEquals(Configuration.EMPTY, root.getConfiguration());
+
+ // Check child initial config.
+ final TestConfigurationContainer child1 = root.addChild();
+ assertEquals(Configuration.EMPTY, child1.getOverrideConfiguration());
+ assertEquals(Configuration.EMPTY, child1.getMergedOverrideConfiguration());
+ assertEquals(Configuration.EMPTY, child1.getConfiguration());
+
+ // Check child initial config if root has overrides.
+ final Configuration rootOverrideConfig = new Configuration();
+ rootOverrideConfig.fontScale = 1.3f;
+ root.onOverrideConfigurationChanged(rootOverrideConfig);
+ final TestConfigurationContainer child2 = root.addChild();
+ assertEquals(Configuration.EMPTY, child2.getOverrideConfiguration());
+ assertEquals(rootOverrideConfig, child2.getMergedOverrideConfiguration());
+ assertEquals(rootOverrideConfig, child2.getConfiguration());
+
+ // Check child initial config if root has parent config set.
+ final Configuration rootParentConfig = new Configuration();
+ rootParentConfig.fontScale = 0.8f;
+ rootParentConfig.orientation = SCREEN_ORIENTATION_LANDSCAPE;
+ root.onConfigurationChanged(rootParentConfig);
+ final Configuration rootFullConfig = new Configuration(rootParentConfig);
+ rootFullConfig.updateFrom(rootOverrideConfig);
+
+ final TestConfigurationContainer child3 = root.addChild();
+ assertEquals(Configuration.EMPTY, child3.getOverrideConfiguration());
+ assertEquals(rootOverrideConfig, child3.getMergedOverrideConfiguration());
+ assertEquals(rootFullConfig, child3.getConfiguration());
+ }
+
+ @Test
+ public void testConfigurationChangeOnAddRemove() throws Exception {
+ // Init root's config.
+ final TestConfigurationContainer root = new TestConfigurationContainer();
+ final Configuration rootOverrideConfig = new Configuration();
+ rootOverrideConfig.fontScale = 1.3f;
+ root.onOverrideConfigurationChanged(rootOverrideConfig);
+
+ // Init child's config.
+ final TestConfigurationContainer child = root.addChild();
+ final Configuration childOverrideConfig = new Configuration();
+ childOverrideConfig.densityDpi = 320;
+ child.onOverrideConfigurationChanged(childOverrideConfig);
+
+ // Check configuration update when child is removed from parent.
+ root.removeChild(child);
+ assertEquals(childOverrideConfig, child.getOverrideConfiguration());
+ assertEquals(childOverrideConfig, child.getMergedOverrideConfiguration());
+ assertEquals(childOverrideConfig, child.getConfiguration());
+
+ // It may be paranoia... but let's check if parent's config didn't change after removal.
+ assertEquals(rootOverrideConfig, root.getOverrideConfiguration());
+ assertEquals(rootOverrideConfig, root.getMergedOverrideConfiguration());
+ assertEquals(rootOverrideConfig, root.getConfiguration());
+
+ // Check configuration update when child is added to parent.
+ final Configuration mergedOverrideConfig = new Configuration(root.getConfiguration());
+ mergedOverrideConfig.updateFrom(childOverrideConfig);
+ root.addChild(child);
+ assertEquals(childOverrideConfig, child.getOverrideConfiguration());
+ assertEquals(mergedOverrideConfig, child.getMergedOverrideConfiguration());
+ assertEquals(mergedOverrideConfig, child.getConfiguration());
+ }
+
+ @Test
+ public void testConfigurationChangePropagation() throws Exception {
+ // Builds 3-level vertical hierarchy with one configuration container on each level.
+ // In addition to different overrides on each level, everyone in hierarchy will have one
+ // common overridden value - orientation;
+
+ // Init root's config.
+ final TestConfigurationContainer root = new TestConfigurationContainer();
+ final Configuration rootOverrideConfig = new Configuration();
+ rootOverrideConfig.fontScale = 1.3f;
+ rootOverrideConfig.orientation = SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
+ root.onOverrideConfigurationChanged(rootOverrideConfig);
+
+ // Init children.
+ final TestConfigurationContainer child1 = root.addChild();
+ final Configuration childOverrideConfig1 = new Configuration();
+ childOverrideConfig1.densityDpi = 320;
+ childOverrideConfig1.orientation = SCREEN_ORIENTATION_LANDSCAPE;
+ child1.onOverrideConfigurationChanged(childOverrideConfig1);
+
+ final TestConfigurationContainer child2 = child1.addChild();
+ final Configuration childOverrideConfig2 = new Configuration();
+ childOverrideConfig2.screenWidthDp = 150;
+ childOverrideConfig2.orientation = SCREEN_ORIENTATION_PORTRAIT;
+ child2.onOverrideConfigurationChanged(childOverrideConfig2);
+
+ // Check configuration on all levels when root override is updated.
+ rootOverrideConfig.smallestScreenWidthDp = 200;
+ root.onOverrideConfigurationChanged(rootOverrideConfig);
+
+ final Configuration mergedOverrideConfig1 = new Configuration(rootOverrideConfig);
+ mergedOverrideConfig1.updateFrom(childOverrideConfig1);
+ final Configuration mergedConfig1 = new Configuration(mergedOverrideConfig1);
+
+ final Configuration mergedOverrideConfig2 = new Configuration(mergedOverrideConfig1);
+ mergedOverrideConfig2.updateFrom(childOverrideConfig2);
+ final Configuration mergedConfig2 = new Configuration(mergedOverrideConfig2);
+
+ assertEquals(rootOverrideConfig, root.getOverrideConfiguration());
+ assertEquals(rootOverrideConfig, root.getMergedOverrideConfiguration());
+ assertEquals(rootOverrideConfig, root.getConfiguration());
+
+ assertEquals(childOverrideConfig1, child1.getOverrideConfiguration());
+ assertEquals(mergedOverrideConfig1, child1.getMergedOverrideConfiguration());
+ assertEquals(mergedConfig1, child1.getConfiguration());
+
+ assertEquals(childOverrideConfig2, child2.getOverrideConfiguration());
+ assertEquals(mergedOverrideConfig2, child2.getMergedOverrideConfiguration());
+ assertEquals(mergedConfig2, child2.getConfiguration());
+
+ // Check configuration on all levels when root parent config is updated.
+ final Configuration rootParentConfig = new Configuration();
+ rootParentConfig.screenHeightDp = 100;
+ rootParentConfig.orientation = SCREEN_ORIENTATION_REVERSE_PORTRAIT;
+ root.onConfigurationChanged(rootParentConfig);
+ final Configuration mergedRootConfig = new Configuration(rootParentConfig);
+ mergedRootConfig.updateFrom(rootOverrideConfig);
+
+ mergedConfig1.setTo(mergedRootConfig);
+ mergedConfig1.updateFrom(mergedOverrideConfig1);
+
+ mergedConfig2.setTo(mergedConfig1);
+ mergedConfig2.updateFrom(mergedOverrideConfig2);
+
+ assertEquals(rootOverrideConfig, root.getOverrideConfiguration());
+ assertEquals(rootOverrideConfig, root.getMergedOverrideConfiguration());
+ assertEquals(mergedRootConfig, root.getConfiguration());
+
+ assertEquals(childOverrideConfig1, child1.getOverrideConfiguration());
+ assertEquals(mergedOverrideConfig1, child1.getMergedOverrideConfiguration());
+ assertEquals(mergedConfig1, child1.getConfiguration());
+
+ assertEquals(childOverrideConfig2, child2.getOverrideConfiguration());
+ assertEquals(mergedOverrideConfig2, child2.getMergedOverrideConfiguration());
+ assertEquals(mergedConfig2, child2.getConfiguration());
+ }
+
+ /**
+ * Contains minimal implementation of {@link ConfigurationContainer}'s abstract behavior needed
+ * for testing.
+ */
+ private class TestConfigurationContainer
+ extends ConfigurationContainer<TestConfigurationContainer> {
+ private List<TestConfigurationContainer> mChildren = new ArrayList<>();
+ private TestConfigurationContainer mParent;
+
+ TestConfigurationContainer addChild(TestConfigurationContainer childContainer) {
+ childContainer.mParent = this;
+ childContainer.onParentChanged();
+ mChildren.add(childContainer);
+ return childContainer;
+ }
+
+ TestConfigurationContainer addChild() {
+ return addChild(new TestConfigurationContainer());
+ }
+
+ void removeChild(TestConfigurationContainer child) {
+ child.mParent = null;
+ child.onParentChanged();
+ }
+
+ @Override
+ protected int getChildCount() {
+ return mChildren.size();
+ }
+
+ @Override
+ protected TestConfigurationContainer getChildAt(int index) {
+ return mChildren.get(index);
+ }
+
+ @Override
+ protected ConfigurationContainer getParent() {
+ return mParent;
+ }
+ }
+}