OSDN Git Service

Use ConfigurationContainer for holding configs in AM hierarchy
authorAndrii Kulian <akulian@google.com>
Thu, 13 Oct 2016 04:58:25 +0000 (21:58 -0700)
committerAndrii Kulian <akulian@google.com>
Thu, 13 Oct 2016 04:58:25 +0000 (21:58 -0700)
Extract configuration holding and handling into separate class
and use it both in hierarchy in AM.

Change-Id: I19ca20152f7473af2c4a7bbedeff69422b1454eb
Test: ConfigurationContainerTests
Test: Existing and manual tests still pass.

services/core/java/com/android/server/am/ActivityManagerService.java
services/core/java/com/android/server/am/ActivityRecord.java
services/core/java/com/android/server/am/ActivityStack.java
services/core/java/com/android/server/am/ActivityStackSupervisor.java
services/core/java/com/android/server/am/ActivityStarter.java
services/core/java/com/android/server/am/CompatModePackages.java
services/core/java/com/android/server/am/ConfigurationContainer.java [new file with mode: 0644]
services/core/java/com/android/server/am/TaskRecord.java
services/core/java/com/android/server/wm/Task.java
services/core/java/com/android/server/wm/WindowManagerService.java
services/tests/servicestests/src/com/android/server/am/ConfigurationContainerTests.java [new file with mode: 0644]

index e85fa25..70c4fb2 100644 (file)
@@ -1121,18 +1121,12 @@ public final class ActivityManagerService extends ActivityManagerNative
      */
     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();
 
@@ -1582,6 +1576,14 @@ public final class ActivityManagerService extends ActivityManagerNative
 
     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;
 
@@ -2329,7 +2331,7 @@ public final class ActivityManagerService extends ActivityManagerNative
                     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) {
@@ -2705,15 +2707,16 @@ public final class ActivityManagerService extends ActivityManagerNative
 
         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);
 
@@ -3145,8 +3148,9 @@ public final class ActivityManagerService extends ActivityManagerNative
     }
 
     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;
@@ -4757,7 +4761,7 @@ public final class ActivityManagerService extends ActivityManagerNative
             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)) {
@@ -6544,7 +6548,7 @@ public final class ActivityManagerService extends ActivityManagerNative
                                  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);
@@ -6569,7 +6573,7 @@ public final class ActivityManagerService extends ActivityManagerNative
                     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);
@@ -9325,17 +9329,9 @@ public final class ActivityManagerService extends ActivityManagerNative
                     }
                 }
 
-                // 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) {
@@ -13170,8 +13166,8 @@ public final class ActivityManagerService extends ActivityManagerNative
             // 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();
@@ -13186,11 +13182,10 @@ public final class ActivityManagerService extends ActivityManagerNative
                     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);
@@ -14785,7 +14780,7 @@ public final class ActivityManagerService extends ActivityManagerNative
             pw.println("  mHeavyWeightProcess: " + mHeavyWeightProcess);
         }
         if (dumpPackage == null) {
-            pw.println("  mGlobalConfiguration: " + mGlobalConfiguration);
+            pw.println("  mGlobalConfiguration: " + getGlobalConfiguration());
         }
         if (dumpAll) {
             pw.println("  mConfigWillChange: " + getFocusedStack().mConfigWillChange);
@@ -18768,15 +18763,16 @@ public final class ActivityManagerService extends ActivityManagerNative
     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;
@@ -18800,7 +18796,7 @@ public final class ActivityManagerService extends ActivityManagerNative
     public Configuration getConfiguration() {
         Configuration ci;
         synchronized(this) {
-            ci = new Configuration(mGlobalConfiguration);
+            ci = new Configuration(getGlobalConfiguration());
             ci.userSetLocale = false;
         }
         return ci;
@@ -18858,7 +18854,7 @@ public final class ActivityManagerService extends ActivityManagerNative
     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) {
@@ -18918,7 +18914,7 @@ public final class ActivityManagerService extends ActivityManagerNative
     }
 
     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));
@@ -18989,7 +18985,7 @@ public final class ActivityManagerService extends ActivityManagerNative
     /** 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;
@@ -19019,7 +19015,9 @@ public final class ActivityManagerService extends ActivityManagerNative
         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,
@@ -19041,7 +19039,7 @@ public final class ActivityManagerService extends ActivityManagerNative
 
         // 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;
index 40d6d14..9317605 100644 (file)
@@ -17,7 +17,6 @@
 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;
@@ -150,11 +149,11 @@ final class ActivityRecord {
     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.
@@ -279,8 +278,10 @@ final class ActivityRecord {
                 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);
@@ -457,7 +458,8 @@ final class ActivityRecord {
             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.
         }
@@ -614,8 +616,8 @@ final class ActivityRecord {
         resolvedType = _resolvedType;
         componentSpecified = _componentSpecified;
         rootVoiceInteraction = _rootVoiceInteraction;
-        configuration = _configuration;
-        taskConfigOverride = Configuration.EMPTY;
+        mLastReportedConfiguration = new Configuration(_configuration);
+        mLastReportedOverrideConfiguration = new Configuration();
         resultTo = _resultTo;
         resultWho = _resultWho;
         requestCode = _reqCode;
index 5dda02d..8b843e8 100644 (file)
@@ -28,7 +28,6 @@ import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
 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;
@@ -143,7 +142,7 @@ import java.util.Set;
 /**
  * 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;
@@ -200,6 +199,21 @@ final class ActivityStack {
     // 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,
@@ -337,6 +351,12 @@ final class ActivityStack {
     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;
 
@@ -466,6 +486,7 @@ final class ActivityStack {
             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
@@ -482,6 +503,7 @@ final class ActivityStack {
             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.
@@ -2529,7 +2551,7 @@ final class ActivityStack {
             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;
@@ -4582,7 +4604,7 @@ final class ActivityStack {
                 }
             }
 
-            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);
@@ -4655,11 +4677,10 @@ final class ActivityStack {
 
         // 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);
@@ -4676,19 +4697,20 @@ final class ActivityStack {
 
         // 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;
         }
 
@@ -4711,8 +4733,9 @@ final class ActivityStack {
         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.
@@ -4759,7 +4782,7 @@ final class ActivityStack {
         // 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;
@@ -4772,13 +4795,13 @@ final class ActivityStack {
         // 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
@@ -4843,8 +4866,8 @@ final class ActivityStack {
             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.
@@ -5314,10 +5337,11 @@ final class ActivityStack {
         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(
@@ -5368,9 +5392,10 @@ final class ActivityStack {
 
     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() {
index e1fe0d2..b98bc48 100644 (file)
@@ -178,7 +178,8 @@ import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_PINNABLE;
 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;
@@ -412,6 +413,21 @@ public final class ActivityStackSupervisor implements DisplayListener {
 
     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;
@@ -1182,7 +1198,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
         // 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
@@ -1272,12 +1288,16 @@ public final class ActivityStackSupervisor implements DisplayListener {
                 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
@@ -1599,7 +1619,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
             // 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
@@ -1865,8 +1885,9 @@ public final class ActivityStackSupervisor implements DisplayListener {
                     // 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 */);
                 }
             }
         }
@@ -2247,7 +2268,8 @@ public final class ActivityStackSupervisor implements DisplayListener {
                 }
             }
         }
-        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;
@@ -2589,7 +2611,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
         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.
@@ -4230,7 +4252,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
 
     /** 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;
@@ -4290,6 +4312,21 @@ public final class ActivityStackSupervisor implements DisplayListener {
         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 {
index 5c32f50..a834af7 100644 (file)
@@ -470,9 +470,9 @@ class ActivityStarter {
         }
 
         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;
         }
@@ -782,7 +782,7 @@ class ActivityStarter {
                 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);
 
index 0b282ed..32790fc 100644 (file)
@@ -38,6 +38,7 @@ import android.app.AppGlobals;
 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;
@@ -197,18 +198,19 @@ public final class CompatModePackages {
     }
 
     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;
         }
@@ -408,8 +410,9 @@ public final class CompatModePackages {
             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();
diff --git a/services/core/java/com/android/server/am/ConfigurationContainer.java b/services/core/java/com/android/server/am/ConfigurationContainer.java
new file mode 100644 (file)
index 0000000..30f5309
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * 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();
+}
index 4d7c4d3..af0e926 100644 (file)
@@ -56,6 +56,8 @@ import java.io.File;
 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;
@@ -74,8 +76,6 @@ import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
 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;
@@ -93,7 +93,7 @@ import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
 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;
@@ -271,8 +271,8 @@ final class TaskRecord {
     // 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) {
@@ -531,8 +531,10 @@ final class TaskRecord {
         return mStack;
     }
 
+    /** Must be used for setting parent stack because it performs configuration updates. */
     void setStack(ActivityStack stack) {
         mStack = stack;
+        onParentChanged();
     }
 
     /**
@@ -542,6 +544,21 @@ final class TaskRecord {
         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) {
@@ -591,7 +608,6 @@ final class TaskRecord {
      * @return whether the thumbnail was set
      */
     boolean setLastThumbnailLocked(Bitmap thumbnail) {
-        final Configuration serviceConfig = mService.mGlobalConfiguration;
         int taskWidth = 0;
         int taskHeight = 0;
         if (mBounds != null) {
@@ -607,7 +623,11 @@ final class TaskRecord {
         } 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);
     }
 
     /**
@@ -1487,8 +1507,9 @@ final class TaskRecord {
         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) {
@@ -1496,7 +1517,7 @@ final class TaskRecord {
                 mLastNonFullscreenBounds = mBounds;
             }
             mBounds = null;
-            mOverrideConfig = Configuration.EMPTY;
+            newConfig.unset();
         } else {
             mTmpRect.set(bounds);
             adjustForMinimalTaskDimensions(mTmpRect);
@@ -1508,15 +1529,16 @@ final class TaskRecord {
             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,
@@ -1541,8 +1563,9 @@ final class TaskRecord {
         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(
@@ -1552,16 +1575,16 @@ final class TaskRecord {
                 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)
@@ -1573,9 +1596,11 @@ final class TaskRecord {
         // 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(
@@ -1589,12 +1614,14 @@ final class TaskRecord {
      * {@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;
     }
 
@@ -1607,29 +1634,6 @@ final class TaskRecord {
         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());
index f6598c1..82f6790 100644 (file)
@@ -164,7 +164,8 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
         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);
@@ -172,7 +173,7 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
             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();
index c899490..14fad4e 100644 (file)
@@ -3974,7 +3974,7 @@ public class WindowManagerService extends IWindowManager.Stub
     }
 
     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);
@@ -3990,7 +3990,7 @@ public class WindowManagerService extends IWindowManager.Stub
                         "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();
@@ -9405,6 +9405,7 @@ public class WindowManagerService extends IWindowManager.Stub
      */
     public int getSmallestWidthForTaskBounds(Rect bounds) {
         synchronized (mWindowMap) {
+            // TODO(multi-display): Use correct display content here
             return getDefaultDisplayContentLocked().getDockedDividerController()
                     .getSmallestWidthDpForBounds(bounds);
         }
diff --git a/services/tests/servicestests/src/com/android/server/am/ConfigurationContainerTests.java b/services/tests/servicestests/src/com/android/server/am/ConfigurationContainerTests.java
new file mode 100644 (file)
index 0000000..92c442e
--- /dev/null
@@ -0,0 +1,238 @@
+/*
+ * 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;
+        }
+    }
+}