Merge "Fix the system gender in ActivityRecord objects" into main
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionBackupHelper.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionBackupHelper.java
index d494be5..e91de37 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionBackupHelper.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionBackupHelper.java
@@ -123,8 +123,7 @@
      * Returns the system-gender to be backed up as a data-blob.
      */
     public byte[] getSystemBackupPayload(int userId) {
-        int gender = mGrammaticalGenderService.getSystemGrammaticalGender(mAttributionSource,
-                userId);
+        int gender = mGrammaticalGenderService.getSystemGrammaticalGender(userId);
         return intToByteArray(gender);
     }
 
@@ -167,7 +166,7 @@
         BackupManager.dataChanged(SYSTEM_BACKUP_PACKAGE_KEY);
     }
 
-    private byte[] convertToByteArray(HashMap<String, Integer> pkgGenderInfo) {
+    private static byte[] convertToByteArray(HashMap<String, Integer> pkgGenderInfo) {
         try (final ByteArrayOutputStream out = new ByteArrayOutputStream();
              final ObjectOutputStream objStream = new ObjectOutputStream(out)) {
             objStream.writeObject(pkgGenderInfo);
@@ -178,22 +177,22 @@
         }
     }
 
-    private byte[] intToByteArray(final int gender) {
+    private static byte[] intToByteArray(final int gender) {
         ByteBuffer bb = ByteBuffer.allocate(4);
         bb.putInt(gender);
         return bb.array();
     }
 
-    private int convertByteArrayToInt(byte[] intBytes) {
+    private static int convertByteArrayToInt(byte[] intBytes) {
         ByteBuffer byteBuffer = ByteBuffer.wrap(intBytes);
         return byteBuffer.getInt();
     }
 
-    private HashMap<String, Integer> readFromByteArray(byte[] payload) {
+    private static HashMap<String, Integer> readFromByteArray(byte[] payload) {
         HashMap<String, Integer> data = new HashMap<>();
 
-        try (ByteArrayInputStream byteIn = new ByteArrayInputStream(payload);
-             ObjectInputStream in = new ObjectInputStream(byteIn)) {
+        try (var byteIn = new ByteArrayInputStream(payload);
+                var in = new ObjectInputStream(byteIn)) {
             data = (HashMap<String, Integer>) in.readObject();
         } catch (IOException | ClassNotFoundException e) {
             Log.e(TAG, "cannot convert payload to HashMap.", e);
@@ -205,10 +204,10 @@
     private void cleanStagedDataForOldEntries() {
         for (int i = 0; i < mCache.size(); i++) {
             int userId = mCache.keyAt(i);
-            StagedData stagedData = mCache.get(userId);
+            StagedData stagedData = mCache.valueAt(userId);
             if (stagedData.mCreationTimeMillis
                     < mClock.millis() - STAGE_DATA_RETENTION_PERIOD.toMillis()) {
-                mCache.remove(userId);
+                mCache.removeAt(i--);
             }
         }
     }
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
index 2816d08..7eb971c 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
@@ -16,7 +16,6 @@
 
 package com.android.server.grammaticalinflection;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.res.Configuration;
 
@@ -41,7 +40,7 @@
     public abstract void stageAndApplyRestoredPayload(byte[] payload, int userId);
 
     /**
-     * Get the current system grammatical gender of privileged application.
+     * Get the current system grammatical gender for the particular user.
      *
      * @return the value of grammatical gender
      *
@@ -50,18 +49,25 @@
     public abstract @Configuration.GrammaticalGender int getSystemGrammaticalGender(int userId);
 
     /**
-     * Retrieve the system grammatical gender.
+     * Get the final merged value of the global grammatical gender, user- or devsettings-set.
      *
      * @return the value of grammatical gender
      *
      */
-    public abstract @Configuration.GrammaticalGender int retrieveSystemGrammaticalGender(
-            @NonNull Configuration configuration);
+    public abstract @Configuration.GrammaticalGender int mergedFinalSystemGrammaticalGender();
+
+    /**
+     * Get the grammatical gender from developer settings global override.
+     *
+     * @return the value of grammatical gender
+     */
+    public abstract
+            @Configuration.GrammaticalGender int getGrammaticalGenderFromDeveloperSettings();
 
     /**
      * Whether the package can get the system grammatical gender or not.
      */
-    public abstract boolean canGetSystemGrammaticalGender(int uid, @Nullable String packageName);
+    public abstract boolean canGetSystemGrammaticalGender(int uid);
 
 
     /**
@@ -74,4 +80,3 @@
      */
     public abstract void applyRestoredSystemPayload(byte[] payload, int userId);
 }
-
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index 93a71b9..e242164 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -17,7 +17,6 @@
 package com.android.server.grammaticalinflection;
 
 import static android.app.Flags.systemTermsOfAddressEnabled;
-import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
 
 import static com.android.server.grammaticalinflection.GrammaticalInflectionUtils.checkSystemGrammaticalGenderPermission;
 
@@ -43,6 +42,7 @@
 import android.util.SparseIntArray;
 import android.util.Xml;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.XmlUtils;
 import com.android.modules.utils.TypedXmlPullParser;
@@ -126,7 +126,7 @@
 
         @Override
         public void setSystemWideGrammaticalGender(int grammaticalGender, int userId) {
-            isCallerAllowed();
+            enforceCallerPermissions();
             GrammaticalInflectionService.this.setSystemWideGrammaticalGender(grammaticalGender,
                     userId);
         }
@@ -138,18 +138,16 @@
                         + " does not have READ_SYSTEM_GRAMMATICAL_GENDER permission.");
             }
             return checkSystemTermsOfAddressIsEnabled()
-                    ? GrammaticalInflectionService.this.getSystemGrammaticalGender(
-                    attributionSource, userId)
-                    : GRAMMATICAL_GENDER_NOT_SPECIFIED;
+                    ? GrammaticalInflectionService.this.getSystemGrammaticalGender(userId)
+                    : Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
         }
 
         @Override
         public int peekSystemGrammaticalGenderByUserId(AttributionSource attributionSource,
                 int userId) {
             return canGetSystemGrammaticalGender(attributionSource)
-                    ? GrammaticalInflectionService.this.getSystemGrammaticalGender(
-                    attributionSource, userId)
-                    : GRAMMATICAL_GENDER_NOT_SPECIFIED;
+                    ? GrammaticalInflectionService.this.getSystemGrammaticalGender(userId)
+                    : Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
         }
 
         @Override
@@ -163,11 +161,10 @@
 
     private final class GrammaticalInflectionManagerInternalImpl
             extends GrammaticalInflectionManagerInternal {
-
         @Override
         @Nullable
         public byte[] getBackupPayload(int userId) {
-            isCallerAllowed();
+            enforceCallerPermissions();
             return mBackupHelper.getBackupPayload(userId);
         }
 
@@ -179,7 +176,7 @@
         @Override
         @Nullable
         public byte[] getSystemBackupPayload(int userId) {
-            isCallerAllowed();
+            enforceCallerPermissions();
             return mBackupHelper.getSystemBackupPayload(userId);
         }
 
@@ -191,30 +188,35 @@
         @Override
         public int getSystemGrammaticalGender(int userId) {
             return checkSystemTermsOfAddressIsEnabled()
-                    ? GrammaticalInflectionService.this.getSystemGrammaticalGender(
-                    mContext.getAttributionSource(), userId)
-                    : GRAMMATICAL_GENDER_NOT_SPECIFIED;
+                    ? GrammaticalInflectionService.this.getSystemGrammaticalGender(userId)
+                    : Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
         }
 
         @Override
-        public int retrieveSystemGrammaticalGender(Configuration configuration) {
+        public int mergedFinalSystemGrammaticalGender() {
             int systemGrammaticalGender = getSystemGrammaticalGender(mContext.getUserId());
             // Retrieve the grammatical gender from system property, set it into
             // configuration which will get updated later if the grammatical gender raw value of
             // current configuration is {@link Configuration#GRAMMATICAL_GENDER_UNDEFINED}.
-            if (configuration.getGrammaticalGenderRaw()
-                    == Configuration.GRAMMATICAL_GENDER_UNDEFINED
-                    || systemGrammaticalGender <= Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED) {
-                systemGrammaticalGender = SystemProperties.getInt(GRAMMATICAL_GENDER_PROPERTY,
-                        Configuration.GRAMMATICAL_GENDER_UNDEFINED);
+            if (systemGrammaticalGender == Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED) {
+                systemGrammaticalGender = getGrammaticalGenderFromDeveloperSettings();
             }
-            return systemGrammaticalGender;
+            return systemGrammaticalGender == Configuration.GRAMMATICAL_GENDER_UNDEFINED
+                    ? Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED : systemGrammaticalGender;
         }
 
         @Override
-        public boolean canGetSystemGrammaticalGender(int uid, String packageName) {
-            AttributionSource attributionSource = new AttributionSource.Builder(
-                    uid).setPackageName(packageName).build();
+        public int getGrammaticalGenderFromDeveloperSettings() {
+            return SystemProperties.getInt(GRAMMATICAL_GENDER_PROPERTY,
+                    Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED);
+        }
+
+        @Override
+        public boolean canGetSystemGrammaticalGender(int uid) {
+            if (uid == Process.SYSTEM_UID) {
+                return true;
+            }
+            var attributionSource = new AttributionSource.Builder(uid).build();
             return GrammaticalInflectionService.this.canGetSystemGrammaticalGender(
                     attributionSource);
         }
@@ -225,7 +227,7 @@
                 mActivityTaskManagerInternal.getApplicationConfig(appPackageName, userId);
 
         if (appConfig == null || appConfig.mGrammaticalGender == null) {
-            return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+            return Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
         } else {
             return appConfig.mGrammaticalGender;
         }
@@ -239,9 +241,10 @@
                         userId);
 
         if (!SystemProperties.getBoolean(GRAMMATICAL_INFLECTION_ENABLED, true)) {
-            if (preValue != GRAMMATICAL_GENDER_NOT_SPECIFIED) {
+            if (preValue != Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED) {
                 Log.d(TAG, "Clearing the user's grammatical gender setting");
-                updater.setGrammaticalGender(GRAMMATICAL_GENDER_NOT_SPECIFIED).commit();
+                updater.setGrammaticalGender(
+                        Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED).commit();
             }
             return;
         }
@@ -250,49 +253,48 @@
         FrameworkStatsLog.write(FrameworkStatsLog.APPLICATION_GRAMMATICAL_INFLECTION_CHANGED,
                 FrameworkStatsLog.APPLICATION_GRAMMATICAL_INFLECTION_CHANGED__SOURCE_ID__OTHERS,
                 uid,
-                gender != GRAMMATICAL_GENDER_NOT_SPECIFIED,
-                preValue != GRAMMATICAL_GENDER_NOT_SPECIFIED);
+                gender != Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED,
+                preValue != Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED);
 
         updater.setGrammaticalGender(gender).commit();
     }
 
     protected void setSystemWideGrammaticalGender(int grammaticalGender, int userId) {
-        Trace.beginSection("GrammaticalInflectionService.setSystemWideGrammaticalGender");
-        if (!GrammaticalInflectionManager.VALID_GRAMMATICAL_GENDER_VALUES.contains(
-                grammaticalGender)) {
-            throw new IllegalArgumentException("Unknown grammatical gender");
-        }
-
-        if (!checkSystemTermsOfAddressIsEnabled()) {
-            if (grammaticalGender == GRAMMATICAL_GENDER_NOT_SPECIFIED) {
-                return;
+        try {
+            if (!checkSystemTermsOfAddressIsEnabled()) {
+                return; // Nothing to do, and the flag can't get flipped at the runtime.
             }
-            Log.d(TAG, "Clearing the system grammatical gender setting");
-            grammaticalGender = GRAMMATICAL_GENDER_NOT_SPECIFIED;
-        }
 
-        synchronized (mLock) {
+            Trace.beginSection("GrammaticalInflectionService.setSystemWideGrammaticalGender");
+            if (!GrammaticalInflectionManager.VALID_GRAMMATICAL_GENDER_VALUES.contains(
+                    grammaticalGender)) {
+                throw new IllegalArgumentException("Unknown grammatical gender");
+            }
+
             final File file = getGrammaticalGenderFile(userId);
-            final AtomicFile atomicFile = new AtomicFile(file);
-            FileOutputStream stream = null;
-            try {
-                stream = atomicFile.startWrite();
-                stream.write(toXmlByteArray(grammaticalGender, stream));
-                atomicFile.finishWrite(stream);
-                mGrammaticalGenderCache.put(userId, grammaticalGender);
-            } catch (IOException e) {
-                Log.e(TAG, "Failed to write file " + atomicFile, e);
-                if (stream != null) {
-                    atomicFile.failWrite(stream);
+            synchronized (mLock) {
+                final AtomicFile atomicFile = new AtomicFile(file);
+                FileOutputStream stream = null;
+                try {
+                    stream = atomicFile.startWrite();
+                    stream.write(toXmlByteArray(grammaticalGender, stream));
+                    atomicFile.finishWrite(stream);
+                    mGrammaticalGenderCache.put(userId, grammaticalGender);
+                } catch (IOException e) {
+                    Log.e(TAG, "Failed to write file " + atomicFile, e);
+                    if (stream != null) {
+                        atomicFile.failWrite(stream);
+                    }
+                    throw new RuntimeException(e);
                 }
-                throw new RuntimeException(e);
             }
+            updateConfiguration(grammaticalGender, userId);
+        } finally {
+            Trace.endSection();
         }
-        updateConfiguration(grammaticalGender, userId);
-        Trace.endSection();
     }
 
-    private void updateConfiguration(int grammaticalGender, int userId) {
+    private static void updateConfiguration(int grammaticalGender, int userId) {
         try {
             Configuration config = new Configuration();
             int preValue = config.getGrammaticalGender();
@@ -301,54 +303,47 @@
             FrameworkStatsLog.write(FrameworkStatsLog.SYSTEM_GRAMMATICAL_INFLECTION_CHANGED,
                     FrameworkStatsLog.SYSTEM_GRAMMATICAL_INFLECTION_CHANGED__SOURCE_ID__SYSTEM,
                     userId,
-                    grammaticalGender != GRAMMATICAL_GENDER_NOT_SPECIFIED,
-                    preValue != GRAMMATICAL_GENDER_NOT_SPECIFIED);
+                    grammaticalGender != Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED,
+                    preValue != Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED);
             GrammaticalInflectionBackupHelper.notifyBackupManager();
         } catch (RemoteException e) {
             Log.w(TAG, "Can not update configuration", e);
         }
     }
 
-    public int getSystemGrammaticalGender(AttributionSource attributionSource, int userId) {
-        String packageName = attributionSource.getPackageName();
-        if (packageName == null) {
-            Log.d(TAG, "Package name is null.");
-            return GRAMMATICAL_GENDER_NOT_SPECIFIED;
-        }
-
+    /**
+     * Returns the system global grammatical gender value for the requested user.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+    public int getSystemGrammaticalGender(int userId) {
         synchronized (mLock) {
             int grammaticalGender = mGrammaticalGenderCache.get(userId);
-            return grammaticalGender < 0 ? GRAMMATICAL_GENDER_NOT_SPECIFIED : grammaticalGender;
+            return grammaticalGender < 0
+                    ? Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED : grammaticalGender;
         }
     }
 
-    private File getGrammaticalGenderFile(int userId) {
+    private static File getGrammaticalGenderFile(int userId) {
         final File dir = new File(Environment.getDataSystemCeDirectory(userId),
                 TAG_GRAMMATICAL_INFLECTION);
         return new File(dir, USER_SETTINGS_FILE_NAME);
     }
 
-    private byte[] toXmlByteArray(int grammaticalGender, FileOutputStream fileStream) {
-
-        try {
-            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-            TypedXmlSerializer out = Xml.resolveSerializer(fileStream);
-            out.setOutput(outputStream, StandardCharsets.UTF_8.name());
-            out.startDocument(/* encoding= */ null, /* standalone= */ true);
-            out.startTag(null, TAG_GRAMMATICAL_INFLECTION);
-            out.attributeInt(null, ATTR_NAME, grammaticalGender);
-            out.endTag(null, TAG_GRAMMATICAL_INFLECTION);
-            out.endDocument();
-
-            return outputStream.toByteArray();
-        } catch (IOException e) {
-            return null;
-        }
+    private static byte[] toXmlByteArray(int grammaticalGender, FileOutputStream fileStream)
+            throws IOException {
+        var outputStream = new ByteArrayOutputStream();
+        TypedXmlSerializer out = Xml.resolveSerializer(fileStream);
+        out.setOutput(outputStream, StandardCharsets.UTF_8.name());
+        out.startDocument(/* encoding= */ null, /* standalone= */ true);
+        out.startTag(null, TAG_GRAMMATICAL_INFLECTION);
+        out.attributeInt(null, ATTR_NAME, grammaticalGender);
+        out.endTag(null, TAG_GRAMMATICAL_INFLECTION);
+        out.endDocument();
+        return outputStream.toByteArray();
     }
 
-    private int getGrammaticalGenderFromXml(TypedXmlPullParser parser)
+    private static int getGrammaticalGenderFromXml(TypedXmlPullParser parser)
             throws IOException, XmlPullParserException {
-
         XmlUtils.nextElement(parser);
         while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
             String tagName = parser.getName();
@@ -359,20 +354,20 @@
             }
         }
 
-        return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+        return Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
     }
 
-    private void isCallerAllowed() {
+    private void enforceCallerPermissions() {
         int callingUid = Binder.getCallingUid();
         if (callingUid != Process.SYSTEM_UID && callingUid != Process.SHELL_UID
                 && callingUid != Process.ROOT_UID) {
             mContext.enforceCallingOrSelfPermission(
                     android.Manifest.permission.CHANGE_CONFIGURATION,
-                    "Caller must be system, shell, root or has CHANGE_CONFIGURATION permission.");
+                    "Caller must be system, shell, root or hold CHANGE_CONFIGURATION permission.");
         }
     }
 
-    private boolean checkSystemTermsOfAddressIsEnabled() {
+    private static boolean checkSystemTermsOfAddressIsEnabled() {
         if (!systemTermsOfAddressEnabled()) {
             Log.d(TAG, "The flag must be enabled to allow calling the API.");
             return false;
@@ -387,25 +382,31 @@
 
     @Override
     public void onUserUnlocked(TargetUser user) {
+        if (!checkSystemTermsOfAddressIsEnabled()) {
+            return;
+        }
         IoThread.getHandler().post(() -> {
-            int userId = user.getUserIdentifier();
+            final int userId = user.getUserIdentifier();
             final File file = getGrammaticalGenderFile(userId);
+            final int grammaticalGender;
             synchronized (mLock) {
                 if (!file.exists()) {
                     Log.d(TAG, "User " + userId + " doesn't have the grammatical gender file.");
                     return;
                 }
-                if (mGrammaticalGenderCache.indexOfKey(userId) < 0) {
-                    try (FileInputStream in = new FileInputStream(file)) {
-                        final TypedXmlPullParser parser = Xml.resolvePullParser(in);
-                        int grammaticalGender = getGrammaticalGenderFromXml(parser);
-                        mGrammaticalGenderCache.put(userId, grammaticalGender);
-                        updateConfiguration(grammaticalGender, userId);
-                    } catch (IOException | XmlPullParserException e) {
-                        Log.e(TAG, "Failed to parse XML configuration from " + file, e);
-                    }
+                if (mGrammaticalGenderCache.indexOfKey(userId) >= 0) {
+                    return;
+                }
+                try (FileInputStream in = new FileInputStream(file)) {
+                    final TypedXmlPullParser parser = Xml.resolvePullParser(in);
+                    grammaticalGender = getGrammaticalGenderFromXml(parser);
+                    mGrammaticalGenderCache.put(userId, grammaticalGender);
+                } catch (IOException | XmlPullParserException e) {
+                    Log.e(TAG, "Failed to parse XML configuration from " + file, e);
+                    return;
                 }
             }
+            updateConfiguration(grammaticalGender, userId);
         });
     }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 7d057a9..3503516 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -9492,6 +9492,12 @@
         return false;
     }
 
+    @Override
+    protected boolean setOverrideGender(Configuration requestsTmpConfig, int gender) {
+        return WindowProcessController.applyConfigGenderOverride(
+                requestsTmpConfig, gender, mAtmService.mGrammaticalManagerInternal, getUid());
+    }
+
     @VisibleForTesting
     @Override
     Rect getAnimationBounds(int appRootTaskClipMode) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index fc05d17..f3e1dfb 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -949,7 +949,7 @@
         }
 
         configuration.setGrammaticalGender(
-                mGrammaticalManagerInternal.retrieveSystemGrammaticalGender(configuration));
+                mGrammaticalManagerInternal.mergedFinalSystemGrammaticalGender());
 
         synchronized (mGlobalLock) {
             mForceResizableActivities = forceResizable;
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 31754bf..efd5202 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -534,8 +534,8 @@
                 nightMode);
         boolean newLocalesSet = (locales != null) && setOverrideLocales(mRequestsTmpConfig,
                 locales);
-        boolean newGenderSet = (gender != null) && setOverrideGender(mRequestsTmpConfig,
-                gender);
+        boolean newGenderSet = setOverrideGender(mRequestsTmpConfig,
+                gender == null ? Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED : gender);
         if (newNightModeSet || newLocalesSet || newGenderSet) {
             onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);
         }
@@ -577,14 +577,11 @@
      *
      * @return true if the grammatical gender has been changed.
      */
-    private boolean setOverrideGender(Configuration requestsTmpConfig,
+    protected boolean setOverrideGender(Configuration requestsTmpConfig,
             @Configuration.GrammaticalGender int gender) {
-        if (mRequestedOverrideConfiguration.getGrammaticalGender() == gender) {
-            return false;
-        } else {
-            requestsTmpConfig.setGrammaticalGender(gender);
-            return true;
-        }
+        // Noop, only ActivityRecord and WindowProcessController have enough knowledge about the
+        // app to apply gender correctly.
+        return false;
     }
 
     public boolean isActivityTypeDream() {
diff --git a/services/core/java/com/android/server/wm/PackageConfigPersister.java b/services/core/java/com/android/server/wm/PackageConfigPersister.java
index 23127ac..9d597ea 100644
--- a/services/core/java/com/android/server/wm/PackageConfigPersister.java
+++ b/services/core/java/com/android/server/wm/PackageConfigPersister.java
@@ -169,6 +169,8 @@
                         LocaleOverlayHelper.combineLocalesIfOverlayExists(
                         modifiedRecord.mLocales, mAtm.getGlobalConfiguration().getLocales()),
                         modifiedRecord.mGrammaticalGender);
+            } else {
+                container.applyAppSpecificConfig(null, null, null);
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index fd1b5be..1c00fbb 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -84,6 +84,7 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.Watchdog;
+import com.android.server.grammaticalinflection.GrammaticalInflectionManagerInternal;
 import com.android.server.wm.ActivityTaskManagerService.HotPath;
 
 import java.io.IOException;
@@ -324,8 +325,6 @@
      */
     private volatile int mActivityStateFlags = ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER;
 
-    private final boolean mCanUseSystemGrammaticalGender;
-
     public WindowProcessController(@NonNull ActivityTaskManagerService atm,
             @NonNull ApplicationInfo info, String name, int uid, int userId, Object owner,
             @NonNull WindowProcessListener listener) {
@@ -349,9 +348,6 @@
         mUseFifoUiScheduling = com.android.window.flags.Flags.fifoPriorityForMajorUiProcesses()
                 && (isSysUiPackage || mAtm.isCallerRecents(uid));
 
-        mCanUseSystemGrammaticalGender = mAtm.mGrammaticalManagerInternal != null
-                && mAtm.mGrammaticalManagerInternal.canGetSystemGrammaticalGender(mUid,
-                mInfo.packageName);
         onConfigurationChanged(atm.getGlobalConfiguration());
         mAtm.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, mInfo.packageName);
     }
@@ -1612,11 +1608,6 @@
             return;
         }
 
-        if (mCanUseSystemGrammaticalGender) {
-            config.setGrammaticalGender(
-                    mAtm.mGrammaticalManagerInternal.getSystemGrammaticalGender(mUserId));
-        }
-
         if (mPauseConfigurationDispatchCount > 0) {
             mHasPendingConfigurationChange = true;
             return;
@@ -2139,4 +2130,34 @@
     void dumpDebug(ProtoOutputStream proto, long fieldId) {
         mListener.dumpDebug(proto, fieldId);
     }
+
+    @Override
+    protected boolean setOverrideGender(Configuration requestsTmpConfig, int gender) {
+        return applyConfigGenderOverride(requestsTmpConfig, gender,
+                mAtm.mGrammaticalManagerInternal, mUid);
+    }
+
+    static boolean applyConfigGenderOverride(@NonNull Configuration overrideConfig,
+            @Configuration.GrammaticalGender int override,
+            GrammaticalInflectionManagerInternal service, int uid) {
+        final boolean canGetSystemValue = service != null
+                && service.canGetSystemGrammaticalGender(uid);
+
+        // The priority here is as follows:
+        // - app-specific override if set
+        // - system value if allowed to see it
+        // - global configuration otherwise
+        final int targetValue = (override != Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED)
+                ? override
+                : canGetSystemValue
+                        ? Configuration.GRAMMATICAL_GENDER_UNDEFINED
+                        : service != null
+                                ? service.getGrammaticalGenderFromDeveloperSettings()
+                                : Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
+        if (overrideConfig.getGrammaticalGenderRaw() == targetValue) {
+            return false;
+        }
+        overrideConfig.setGrammaticalGender(targetValue);
+        return true;
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/grammaticalinflection/GrammaticalInflectionBackupTest.java b/services/tests/servicestests/src/com/android/server/grammaticalinflection/GrammaticalInflectionBackupTest.java
index af6f6f2..608b306 100644
--- a/services/tests/servicestests/src/com/android/server/grammaticalinflection/GrammaticalInflectionBackupTest.java
+++ b/services/tests/servicestests/src/com/android/server/grammaticalinflection/GrammaticalInflectionBackupTest.java
@@ -112,7 +112,7 @@
     public void testSystemBackupPayload_returnsGender()
             throws IOException, ClassNotFoundException {
         doReturn(Configuration.GRAMMATICAL_GENDER_MASCULINE).when(mGrammaticalInflectionService)
-                .getSystemGrammaticalGender(any(), eq(DEFAULT_USER_ID));
+                .getSystemGrammaticalGender(eq(DEFAULT_USER_ID));
 
         int gender = convertByteArrayToInt(mBackupHelper.getSystemBackupPayload(DEFAULT_USER_ID));
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 1da5001..b0a3374 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -525,10 +525,13 @@
 
         // The activity shouldn't start relaunching since it doesn't have any desk resources.
         assertFalse(activity.isRelaunching());
+        // The activity configuration ui mode should match.
+        final var activityConfig = activity.getConfiguration();
+        assertEquals(newConfig.uiMode, activityConfig.uiMode);
 
         // The configuration change is still sent to the activity, even if it doesn't relaunch.
         final ActivityConfigurationChangeItem expected =
-                ActivityConfigurationChangeItem.obtain(activity.token, newConfig,
+                ActivityConfigurationChangeItem.obtain(activity.token, activityConfig,
                         activity.getActivityWindowInfo());
         verify(mClientLifecycleManager).scheduleTransactionItem(
                 eq(activity.app.getThread()), eq(expected));
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 87f26e5..c45c86c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -56,6 +56,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManagerInternal;
+import android.content.res.Configuration;
 import android.database.ContentObserver;
 import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.display.DisplayManagerGlobal;
@@ -86,6 +87,7 @@
 import com.android.server.display.DisplayControl;
 import com.android.server.display.color.ColorDisplayService;
 import com.android.server.firewall.IntentFirewall;
+import com.android.server.grammaticalinflection.GrammaticalInflectionManagerInternal;
 import com.android.server.input.InputManagerService;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.UserManagerService;
@@ -208,6 +210,11 @@
         setUpLocalServices();
         setUpActivityTaskManagerService();
         setUpWindowManagerService();
+
+        // We never load the system settings in the tests, thus need to setup the grammatical
+        // gender configuration explicitly.
+        mAtmService.getGlobalConfiguration().setGrammaticalGender(
+                Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED);
     }
 
     private void setUpSystemCore() {
@@ -337,6 +344,18 @@
         };
         when(umi.isUserVisible(anyInt())).thenAnswer(isUserVisibleAnswer);
         when(umi.isUserVisible(anyInt(), anyInt())).thenAnswer(isUserVisibleAnswer);
+
+        final var gimi = mock(
+                GrammaticalInflectionManagerInternal.class, withSettings().stubOnly());
+        doReturn(Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED).when(
+                gimi).getGrammaticalGenderFromDeveloperSettings();
+        doReturn(Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED).when(
+                gimi).getSystemGrammaticalGender(anyInt());
+        doReturn(Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED).when(
+                gimi).mergedFinalSystemGrammaticalGender();
+        doReturn(false).when(gimi).canGetSystemGrammaticalGender(anyInt());
+        doReturn(gimi).when(
+                () -> LocalServices.getService(GrammaticalInflectionManagerInternal.class));
     }
 
     private void setUpActivityTaskManagerService() {
@@ -475,6 +494,7 @@
         LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
         LocalServices.removeServiceForTest(UserManagerInternal.class);
         LocalServices.removeServiceForTest(ImeTargetVisibilityPolicy.class);
+        LocalServices.removeServiceForTest(GrammaticalInflectionManagerInternal.class);
     }
 
     Description getDescription() {