Enable Window Manager to store app-specific locales and apply them.

Test: atest WmTests:ActivityTaskManagerServiceTests

Bug: 194095029
Bug: 194369257
Bug: 194369420
Bug: 194369426
Bug: 194370141

Change-Id: I743a9cfb84f4b054caa03413f39671d3f43a81c4
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 5174a38..0ba77d8 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -30,6 +30,7 @@
 import android.content.res.CompatibilityInfo;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.LocaleList;
 import android.os.RemoteException;
 import android.service.voice.IVoiceInteractionSession;
 import android.util.IntArray;
@@ -611,6 +612,14 @@
         PackageConfigurationUpdater setNightMode(int nightMode);
 
         /**
+         * Sets the app-specific locales for the application referenced by this updater.
+         * This setting is persisted and will overlay on top of the system locales for
+         * the said application.
+         * @return the current {@link PackageConfigurationUpdater} updated with the provided locale.
+         */
+        PackageConfigurationUpdater setLocales(LocaleList locales);
+
+        /**
          * Commit changes.
          */
         void commit();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 081c618..ddd184e 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -947,7 +947,7 @@
         setRecentTasks(new RecentTasks(this, mTaskSupervisor));
         mVrController = new VrController(mGlobalLock);
         mKeyguardController = mTaskSupervisor.getKeyguardController();
-        mPackageConfigPersister = new PackageConfigPersister(mTaskSupervisor.mPersisterQueue);
+        mPackageConfigPersister = new PackageConfigPersister(mTaskSupervisor.mPersisterQueue, this);
     }
 
     public void onActivityManagerInternalAdded() {
@@ -6519,7 +6519,8 @@
     final class PackageConfigurationUpdaterImpl implements
             ActivityTaskManagerInternal.PackageConfigurationUpdater {
         private final int mPid;
-        private int mNightMode;
+        private Integer mNightMode;
+        private LocaleList mLocales;
 
         PackageConfigurationUpdaterImpl(int pid) {
             mPid = pid;
@@ -6532,6 +6533,13 @@
         }
 
         @Override
+        public ActivityTaskManagerInternal.PackageConfigurationUpdater
+                setLocales(LocaleList locales) {
+            mLocales = locales;
+            return this;
+        }
+
+        @Override
         public void commit() {
             synchronized (mGlobalLock) {
                 final long ident = Binder.clearCallingIdentity();
@@ -6541,8 +6549,10 @@
                         Slog.w(TAG, "Override application configuration: cannot find pid " + mPid);
                         return;
                     }
-                    wpc.setOverrideNightMode(mNightMode);
-                    wpc.updateNightModeForAllActivities(mNightMode);
+                    LocaleList localesOverride = LocaleOverlayHelper.combineLocalesIfOverlayExists(
+                            mLocales, getGlobalConfiguration().getLocales());
+                    wpc.applyAppSpecificConfig(mNightMode, localesOverride);
+                    wpc.updateAppSpecificSettingsForAllActivities(mNightMode, localesOverride);
                     mPackageConfigPersister.updateFromImpl(wpc.mName, wpc.mUserId, this);
                 } finally {
                     Binder.restoreCallingIdentity(ident);
@@ -6550,8 +6560,12 @@
             }
         }
 
-        int getNightMode() {
+        Integer getNightMode() {
             return mNightMode;
         }
+
+        LocaleList getLocales() {
+            return mLocales;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 6fafc02..eeb85c5 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -39,6 +39,7 @@
 import android.content.res.Configuration;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.os.LocaleList;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -512,7 +513,7 @@
         return mFullConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FREEFORM;
     }
 
-    /** Returns the activity type associated with the the configuration container. */
+    /** Returns the activity type associated with the configuration container. */
     /*@WindowConfiguration.ActivityType*/
     public int getActivityType() {
         return mFullConfiguration.windowConfiguration.getActivityType();
@@ -546,20 +547,48 @@
     }
 
     /**
+     * Applies app-specific nightMode and {@link LocaleList} on requested configuration.
+     * @return true if any of the requested configuration has been updated.
+     */
+    public boolean applyAppSpecificConfig(Integer nightMode, LocaleList locales) {
+        mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration());
+        boolean newNightModeSet = (nightMode != null) && setOverrideNightMode(mRequestsTmpConfig,
+                nightMode);
+        boolean newLocalesSet = (locales != null) && setOverrideLocales(mRequestsTmpConfig,
+                locales);
+        if (newNightModeSet || newLocalesSet) {
+            onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);
+        }
+        return newNightModeSet || newLocalesSet;
+    }
+
+    /**
      * Overrides the night mode applied to this ConfigurationContainer.
      * @return true if the nightMode has been changed.
      */
-    public boolean setOverrideNightMode(int nightMode) {
+    private boolean setOverrideNightMode(Configuration requestsTmpConfig, int nightMode) {
         final int currentUiMode = mRequestedOverrideConfiguration.uiMode;
         final int currentNightMode = currentUiMode & Configuration.UI_MODE_NIGHT_MASK;
         final int validNightMode = nightMode & Configuration.UI_MODE_NIGHT_MASK;
         if (currentNightMode == validNightMode) {
             return false;
         }
-        mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration());
-        mRequestsTmpConfig.uiMode = validNightMode
+        requestsTmpConfig.uiMode = validNightMode
                 | (currentUiMode & ~Configuration.UI_MODE_NIGHT_MASK);
-        onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);
+        return true;
+    }
+
+    /**
+     * Overrides the locales applied to this ConfigurationContainer.
+     * @return true if the LocaleList has been changed.
+     */
+    private boolean setOverrideLocales(Configuration requestsTmpConfig,
+            @NonNull LocaleList overrideLocales) {
+        if (mRequestedOverrideConfiguration.getLocales().equals(overrideLocales)) {
+            return false;
+        }
+        requestsTmpConfig.setLocales(overrideLocales);
+        requestsTmpConfig.userSetLocale = true;
         return true;
     }
 
diff --git a/services/core/java/com/android/server/wm/LocaleOverlayHelper.java b/services/core/java/com/android/server/wm/LocaleOverlayHelper.java
new file mode 100644
index 0000000..a1a01db
--- /dev/null
+++ b/services/core/java/com/android/server/wm/LocaleOverlayHelper.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.os.LocaleList;
+
+import java.util.Locale;
+
+/**
+ * Static utilities to overlay locales on top of another localeList.
+ *
+ * <p>This is used to overlay application-specific locales in
+ *  {@link com.android.server.wm.ActivityTaskManagerInternal.PackageConfigurationUpdater} on top of
+ *  system locales.
+ */
+final class LocaleOverlayHelper {
+
+    /**
+     * Combines the overlay locales and base locales.
+     * @return the combined {@link LocaleList} if the overlay locales is not empty/null else
+     * returns the empty/null LocaleList.
+     */
+    static LocaleList combineLocalesIfOverlayExists(LocaleList overlayLocales,
+            LocaleList baseLocales) {
+        if (overlayLocales == null || overlayLocales.isEmpty()) {
+            return overlayLocales;
+        }
+        return combineLocales(overlayLocales, baseLocales);
+    }
+
+    /**
+     * Creates a combined {@link LocaleList} by placing overlay locales before base locales and
+     * dropping duplicates from the base locales.
+     */
+    private static LocaleList combineLocales(LocaleList overlayLocales, LocaleList baseLocales) {
+        Locale[] combinedLocales = new Locale[overlayLocales.size() + baseLocales.size()];
+        for (int i = 0; i < overlayLocales.size(); i++) {
+            combinedLocales[i] = overlayLocales.get(i);
+        }
+        for (int i = 0; i < baseLocales.size(); i++) {
+            combinedLocales[i + overlayLocales.size()] = baseLocales.get(i);
+        }
+        // Constructor of {@link LocaleList} removes duplicates
+        return new LocaleList(combinedLocales);
+    }
+
+
+}
diff --git a/services/core/java/com/android/server/wm/PackageConfigPersister.java b/services/core/java/com/android/server/wm/PackageConfigPersister.java
index 1552a96..505c4beb 100644
--- a/services/core/java/com/android/server/wm/PackageConfigPersister.java
+++ b/services/core/java/com/android/server/wm/PackageConfigPersister.java
@@ -21,6 +21,7 @@
 
 import android.annotation.NonNull;
 import android.os.Environment;
+import android.os.LocaleList;
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -54,12 +55,14 @@
     private static final String TAG_CONFIG = "config";
     private static final String ATTR_PACKAGE_NAME = "package_name";
     private static final String ATTR_NIGHT_MODE = "night_mode";
+    private static final String ATTR_LOCALES = "locale_list";
 
     private static final String PACKAGE_DIRNAME = "package_configs";
     private static final String SUFFIX_FILE_NAME = "_config.xml";
 
     private final PersisterQueue mPersisterQueue;
     private final Object mLock = new Object();
+    private final ActivityTaskManagerService mAtm;
 
     @GuardedBy("mLock")
     private final SparseArray<HashMap<String, PackageConfigRecord>> mPendingWrite =
@@ -72,8 +75,9 @@
         return new File(Environment.getDataSystemCeDirectory(userId), PACKAGE_DIRNAME);
     }
 
-    PackageConfigPersister(PersisterQueue queue) {
+    PackageConfigPersister(PersisterQueue queue, ActivityTaskManagerService atm) {
         mPersisterQueue = queue;
+        mAtm = atm;
     }
 
     @GuardedBy("mLock")
@@ -100,7 +104,8 @@
                     final TypedXmlPullParser in = Xml.resolvePullParser(is);
                     int event;
                     String packageName = null;
-                    int nightMode = MODE_NIGHT_AUTO;
+                    Integer nightMode = null;
+                    LocaleList locales = null;
                     while (((event = in.next()) != XmlPullParser.END_DOCUMENT)
                             && event != XmlPullParser.END_TAG) {
                         final String name = in.getName();
@@ -120,6 +125,9 @@
                                         case ATTR_NIGHT_MODE:
                                             nightMode = Integer.parseInt(attrValue);
                                             break;
+                                        case ATTR_LOCALES:
+                                            locales = LocaleList.forLanguageTags(attrValue);
+                                            break;
                                     }
                                 }
                             }
@@ -130,6 +138,7 @@
                         final PackageConfigRecord initRecord =
                                 findRecordOrCreate(mModified, packageName, userId);
                         initRecord.mNightMode = nightMode;
+                        initRecord.mLocales = locales;
                         if (DEBUG) {
                             Slog.d(TAG, "loadPackages: load one package " + initRecord);
                         }
@@ -155,7 +164,9 @@
                         "updateConfigIfNeeded record " + container + " find? " + modifiedRecord);
             }
             if (modifiedRecord != null) {
-                container.setOverrideNightMode(modifiedRecord.mNightMode);
+                container.applyAppSpecificConfig(modifiedRecord.mNightMode,
+                        LocaleOverlayHelper.combineLocalesIfOverlayExists(
+                        modifiedRecord.mLocales, mAtm.getGlobalConfiguration().getLocales()));
             }
         }
     }
@@ -165,10 +176,16 @@
             ActivityTaskManagerService.PackageConfigurationUpdaterImpl impl) {
         synchronized (mLock) {
             PackageConfigRecord record = findRecordOrCreate(mModified, packageName, userId);
-            record.mNightMode = impl.getNightMode();
-
-            if (record.isResetNightMode()) {
-                removePackage(record.mName, record.mUserId);
+            if (impl.getNightMode() != null) {
+                record.mNightMode = impl.getNightMode();
+            }
+            if (impl.getLocales() != null) {
+                record.mLocales = impl.getLocales();
+            }
+            if ((record.mNightMode == null || record.isResetNightMode())
+                    && (record.mLocales == null || record.mLocales.isEmpty())) {
+                // if all values default to system settings, we can remove the package.
+                removePackage(packageName, userId);
             } else {
                 final PackageConfigRecord pendingRecord =
                         findRecord(mPendingWrite, record.mName, record.mUserId);
@@ -179,10 +196,11 @@
                 } else {
                     writeRecord = pendingRecord;
                 }
-                if (writeRecord.mNightMode == record.mNightMode) {
+
+                if (!updateNightMode(record, writeRecord) && !updateLocales(record, writeRecord)) {
                     return;
                 }
-                writeRecord.mNightMode = record.mNightMode;
+
                 if (DEBUG) {
                     Slog.d(TAG, "PackageConfigUpdater save config " + writeRecord);
                 }
@@ -191,6 +209,22 @@
         }
     }
 
+    private boolean updateNightMode(PackageConfigRecord record, PackageConfigRecord writeRecord) {
+        if (record.mNightMode == null || record.mNightMode.equals(writeRecord.mNightMode)) {
+            return false;
+        }
+        writeRecord.mNightMode = record.mNightMode;
+        return true;
+    }
+
+    private boolean updateLocales(PackageConfigRecord record, PackageConfigRecord writeRecord) {
+        if (record.mLocales == null || record.mLocales.equals(writeRecord.mLocales)) {
+            return false;
+        }
+        writeRecord.mLocales = record.mLocales;
+        return true;
+    }
+
     @GuardedBy("mLock")
     void removeUser(int userId) {
         synchronized (mLock) {
@@ -210,7 +244,7 @@
     @GuardedBy("mLock")
     void onPackageUninstall(String packageName) {
         synchronized (mLock) {
-            for (int i = mModified.size() - 1; i > 0; i--) {
+            for (int i = mModified.size() - 1; i >= 0; i--) {
                 final int userId = mModified.keyAt(i);
                 removePackage(packageName, userId);
             }
@@ -242,7 +276,8 @@
     static class PackageConfigRecord {
         final String mName;
         final int mUserId;
-        int mNightMode;
+        Integer mNightMode;
+        LocaleList mLocales;
 
         PackageConfigRecord(String name, int userId) {
             mName = name;
@@ -256,7 +291,7 @@
         @Override
         public String toString() {
             return "PackageConfigRecord package name: " + mName + " userId " + mUserId
-                    + " nightMode " + mNightMode;
+                    + " nightMode " + mNightMode + " locales " + mLocales;
         }
     }
 
@@ -369,7 +404,13 @@
             }
             xmlSerializer.startTag(null, TAG_CONFIG);
             xmlSerializer.attribute(null, ATTR_PACKAGE_NAME, mRecord.mName);
-            xmlSerializer.attributeInt(null, ATTR_NIGHT_MODE, mRecord.mNightMode);
+            if (mRecord.mNightMode != null) {
+                xmlSerializer.attributeInt(null, ATTR_NIGHT_MODE, mRecord.mNightMode);
+            }
+            if (mRecord.mLocales != null) {
+                xmlSerializer.attribute(null, ATTR_LOCALES, mRecord.mLocales
+                        .toLanguageTags());
+            }
             xmlSerializer.endTag(null, TAG_CONFIG);
             xmlSerializer.endDocument();
             xmlSerializer.flush();
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index cd29f0e..6eb2e8a 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -57,6 +57,7 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
+import android.os.LocaleList;
 import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
@@ -817,10 +818,13 @@
         return false;
     }
 
-    void updateNightModeForAllActivities(int nightMode) {
+    // TODO(b/199277065): Re-assess how app-specific locales are applied based on UXR
+    // TODO(b/199277729): Consider whether we need to add special casing for edge cases like
+    //  activity-embeddings etc.
+    void updateAppSpecificSettingsForAllActivities(Integer nightMode, LocaleList localesOverride) {
         for (int i = mActivities.size() - 1; i >= 0; --i) {
             final ActivityRecord r = mActivities.get(i);
-            if (r.setOverrideNightMode(nightMode) && r.mVisibleRequested) {
+            if (r.applyAppSpecificConfig(nightMode, localesOverride) && r.mVisibleRequested) {
                 r.ensureActivityConfiguration(0 /* globalChanges */, true /* preserveWindow */);
             }
         }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index b95d56b..509b460 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -43,13 +43,17 @@
 
 import android.app.Activity;
 import android.app.ActivityManager;
+import android.app.IApplicationThread;
 import android.app.PictureInPictureParams;
 import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.EnterPipRequestedItem;
 import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
 import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.os.Binder;
 import android.os.IBinder;
+import android.os.LocaleList;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
@@ -61,6 +65,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
 import org.mockito.MockitoSession;
 
 import java.util.ArrayList;
@@ -80,6 +85,9 @@
     private final ArgumentCaptor<ClientTransaction> mClientTransactionCaptor =
             ArgumentCaptor.forClass(ClientTransaction.class);
 
+    private static final String DEFAULT_PACKAGE_NAME = "my.application.package";
+    private static final int DEFAULT_USER_ID = 100;
+
     @Before
     public void setUp() throws Exception {
         setBooted(mAtm);
@@ -480,5 +488,269 @@
         assertTrue(activity.supportsMultiWindow());
         assertTrue(task.supportsMultiWindow());
     }
+
+    @Test
+    public void testPackageConfigUpdate_locales_successfullyApplied() {
+        Configuration config = mAtm.getGlobalConfiguration();
+        config.setLocales(LocaleList.forLanguageTags("en-XC"));
+        mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+        mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
+
+        ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+                mAtm.mInternal.createPackageConfigurationUpdater();
+        packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB")).commit();
+
+        WindowProcessController wpcAfterConfigChange = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+
+        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+                wpcAfterConfigChange.getConfiguration().getLocales());
+        assertFalse(wpcAfterConfigChange.getConfiguration().isNightModeActive());
+    }
+
+    @Test
+    public void testPackageConfigUpdate_nightMode_successfullyApplied() {
+        Configuration config = mAtm.getGlobalConfiguration();
+        config.setLocales(LocaleList.forLanguageTags("en-XC"));
+        mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+        mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
+        ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+                mAtm.mInternal.createPackageConfigurationUpdater();
+
+        packageConfigUpdater.setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+
+        WindowProcessController wpcAfterConfigChange = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+        assertTrue(wpcAfterConfigChange.getConfiguration().isNightModeActive());
+        assertEquals(LocaleList.forLanguageTags("en-XC"),
+                wpcAfterConfigChange.getConfiguration().getLocales());
+    }
+
+    @Test
+    public void testPackageConfigUpdate_multipleLocaleUpdates_successfullyApplied() {
+        Configuration config = mAtm.getGlobalConfiguration();
+        config.setLocales(LocaleList.forLanguageTags("en-XC"));
+        mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+        WindowProcessController wpc = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+        mAtm.mProcessMap.put(Binder.getCallingPid(), wpc);
+        ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+                mAtm.mInternal.createPackageConfigurationUpdater();
+
+        packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
+                .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+
+        WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+                wpcAfterConfigChange1.getConfiguration().getLocales());
+        assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
+        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+                wpc.getConfiguration().getLocales());
+
+        packageConfigUpdater.setLocales(LocaleList.forLanguageTags("ja-XC,en-XC")).commit();
+
+        WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+
+        assertEquals(LocaleList.forLanguageTags("ja-XC,en-XC"),
+                wpcAfterConfigChange2.getConfiguration().getLocales());
+        assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
+        assertEquals(LocaleList.forLanguageTags("ja-XC,en-XC"),
+                wpc.getConfiguration().getLocales());
+    }
+
+    @Test
+    public void testPackageConfigUpdate_multipleNightModeUpdates_successfullyApplied() {
+        Configuration config = mAtm.getGlobalConfiguration();
+        config.setLocales(LocaleList.forLanguageTags("en-XC"));
+        mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+        mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
+        ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+                mAtm.mInternal.createPackageConfigurationUpdater();
+
+        packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
+                .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+
+        WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+                wpcAfterConfigChange1.getConfiguration().getLocales());
+        assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
+
+        packageConfigUpdater.setNightMode(Configuration.UI_MODE_NIGHT_NO).commit();
+
+        WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+                wpcAfterConfigChange2.getConfiguration().getLocales());
+        assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
+    }
+
+    @Test
+    public void testPackageConfigUpdate_onPackageUninstall_configShouldNotApply() {
+        Configuration config = mAtm.getGlobalConfiguration();
+        config.setLocales(LocaleList.forLanguageTags("en-XC"));
+        mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+        mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
+        ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+                mAtm.mInternal.createPackageConfigurationUpdater();
+
+        packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
+                .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+
+        WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+                wpcAfterConfigChange1.getConfiguration().getLocales());
+        assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
+
+        mAtm.mInternal.onPackageUninstalled(DEFAULT_PACKAGE_NAME);
+
+        WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+        assertEquals(LocaleList.forLanguageTags("en-XC"),
+                wpcAfterConfigChange2.getConfiguration().getLocales());
+        assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
+    }
+
+    @Test
+    public void testPackageConfigUpdate_LocalesEmptyAndNightModeUndefined_configShouldNotApply() {
+        Configuration config = mAtm.getGlobalConfiguration();
+        config.setLocales(LocaleList.forLanguageTags("en-XC"));
+        mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+        WindowProcessController wpc = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+        mAtm.mProcessMap.put(Binder.getCallingPid(), wpc);
+        ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+                mAtm.mInternal.createPackageConfigurationUpdater();
+
+        packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
+                .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+        WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+
+        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+                wpcAfterConfigChange1.getConfiguration().getLocales());
+        assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
+        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+                wpc.getConfiguration().getLocales());
+
+        packageConfigUpdater.setLocales(LocaleList.getEmptyLocaleList())
+                .setNightMode(Configuration.UI_MODE_NIGHT_UNDEFINED).commit();
+
+        WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+        assertEquals(LocaleList.forLanguageTags("en-XC"),
+                wpcAfterConfigChange2.getConfiguration().getLocales());
+        assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
+        assertEquals(LocaleList.forLanguageTags("en-XC"),
+                wpc.getConfiguration().getLocales());
+    }
+
+    @Test
+    public void testPackageConfigUpdate_WhenUserRemoved_configShouldNotApply() {
+        Configuration config = mAtm.getGlobalConfiguration();
+        config.setLocales(LocaleList.forLanguageTags("en-XC"));
+        mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+        mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
+
+        ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+                mAtm.mInternal.createPackageConfigurationUpdater();
+
+        packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
+                .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+
+        WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+                wpcAfterConfigChange1.getConfiguration().getLocales());
+        assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
+
+        mAtm.mInternal.removeUser(DEFAULT_USER_ID);
+
+        WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+        assertEquals(LocaleList.forLanguageTags("en-XC"),
+                wpcAfterConfigChange2.getConfiguration().getLocales());
+        assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
+    }
+
+    @Test
+    public void testPackageConfigUpdate_setLocaleListToEmpty_doesNotOverlayLocaleListInWpc() {
+        Configuration config = mAtm.getGlobalConfiguration();
+        config.setLocales(LocaleList.forLanguageTags("en-XC"));
+        mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+        mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
+
+        ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+                mAtm.mInternal.createPackageConfigurationUpdater();
+
+        packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
+                .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+
+        WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+                wpcAfterConfigChange1.getConfiguration().getLocales());
+        assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
+
+        packageConfigUpdater.setLocales(LocaleList.getEmptyLocaleList()).commit();
+
+        WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+        assertEquals(LocaleList.forLanguageTags("en-XC"),
+                wpcAfterConfigChange2.getConfiguration().getLocales());
+        assertTrue(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
+    }
+
+    @Test
+    public void testPackageConfigUpdate_resetNightMode_doesNotOverrideNightModeInWpc() {
+        Configuration config = mAtm.getGlobalConfiguration();
+        config.setLocales(LocaleList.forLanguageTags("en-XC"));
+        mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+        mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
+
+        ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+                mAtm.mInternal.createPackageConfigurationUpdater();
+
+        packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
+                .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+
+        WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+                wpcAfterConfigChange1.getConfiguration().getLocales());
+        assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
+
+        packageConfigUpdater.setNightMode(Configuration.UI_MODE_NIGHT_UNDEFINED).commit();
+
+        WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+        assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+                wpcAfterConfigChange2.getConfiguration().getLocales());
+        assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
+    }
+
+    private WindowProcessController createWindowProcessController(String packageName,
+            int userId) {
+        WindowProcessListener mMockListener = Mockito.mock(WindowProcessListener.class);
+        ApplicationInfo info = mock(ApplicationInfo.class);
+        info.packageName = packageName;
+        WindowProcessController wpc = new WindowProcessController(
+                mAtm, info, packageName, 0, userId, null, mMockListener);
+        wpc.setThread(mock(IApplicationThread.class));
+        return wpc;
+    }
+
 }
 
+
+
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index d3f2d14..c56b614 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -34,6 +34,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -49,6 +50,7 @@
 import android.content.pm.ServiceInfo;
 import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.os.LocaleList;
 import android.platform.test.annotations.Presubmit;
 
 import org.junit.Before;
@@ -371,8 +373,9 @@
     public void testTopActivityUiModeChangeScheduleConfigChange() {
         final ActivityRecord activity = createActivityRecord(mWpc);
         activity.mVisibleRequested = true;
-        doReturn(true).when(activity).setOverrideNightMode(anyInt());
-        mWpc.updateNightModeForAllActivities(Configuration.UI_MODE_NIGHT_YES);
+        doReturn(true).when(activity).applyAppSpecificConfig(anyInt(), any());
+        mWpc.updateAppSpecificSettingsForAllActivities(Configuration.UI_MODE_NIGHT_YES,
+                LocaleList.forLanguageTags("en-XA"));
         verify(activity).ensureActivityConfiguration(anyInt(), anyBoolean());
     }