Make DayNight honor configChanges correctly

Currently the way AppCompat DayNight works with
configChanges="uiMode" is incorrect, and different
to the framework. This CL fixes that and moves to an
equivalent behavior.

DayNight now updates the Resources configuration with the
new night uiMode configuration, and calls
Activity.onConfigurationChanged() when changed.

BUG: 134974370
Test: ./gradlew appcompat:cC

Change-Id: I00c086521b9ec0bb4cc5b9836731a0fa7ba548e8
diff --git a/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeActivity.java b/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeActivity.java
index b9faf73..e5230bd 100644
--- a/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeActivity.java
+++ b/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeActivity.java
@@ -16,11 +16,15 @@
 
 package androidx.appcompat.app;
 
+import android.content.res.Configuration;
+
+import androidx.annotation.NonNull;
 import androidx.appcompat.test.R;
 import androidx.appcompat.testutils.BaseTestActivity;
 
 public class NightModeActivity extends BaseTestActivity {
     private int mLastNightModeChange = Integer.MIN_VALUE;
+    private Configuration mLastConfigurationChange;
 
     @Override
     protected int getContentViewLayoutResId() {
@@ -32,6 +36,18 @@
         mLastNightModeChange = mode;
     }
 
+    @Override
+    public void onConfigurationChanged(@NonNull Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        mLastConfigurationChange = newConfig;
+    }
+
+    Configuration getLastConfigurationChangeAndClear() {
+        final Configuration config = mLastConfigurationChange;
+        mLastConfigurationChange = null;
+        return config;
+    }
+
     int getLastNightModeAndReset() {
         final int mode = mLastNightModeChange;
         mLastNightModeChange = Integer.MIN_VALUE;
diff --git a/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeTestCase.java b/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeTestCase.java
index ac5cd5c..0550ec5 100644
--- a/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeTestCase.java
+++ b/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeTestCase.java
@@ -32,6 +32,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 
 import android.app.Instrumentation;
 import android.content.res.Configuration;
@@ -310,6 +311,21 @@
         });
     }
 
+    @Test
+    public void testOnConfigurationChangeNotCalled() throws Throwable {
+        NightModeActivity activity = mActivityTestRule.getActivity();
+        // Set local night mode to YES
+        setNightModeAndWait(mActivityTestRule, MODE_NIGHT_YES, mSetMode);
+        // Assert that onConfigurationChange was not called on the original activity
+        assertNull(activity.getLastConfigurationChangeAndClear());
+
+        activity = mActivityTestRule.getActivity();
+        // Set local night mode back to NO
+        setNightModeAndWait(mActivityTestRule, MODE_NIGHT_NO, mSetMode);
+        // Assert that onConfigurationChange was not called
+        assertNull(activity.getLastConfigurationChangeAndClear());
+    }
+
     @After
     public void cleanup() throws Throwable {
         // Reset the default night mode
diff --git a/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeUiModeConfigChangesTestCase.java b/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeUiModeConfigChangesTestCase.java
index 246f842..0badb08 100644
--- a/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeUiModeConfigChangesTestCase.java
+++ b/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeUiModeConfigChangesTestCase.java
@@ -22,6 +22,7 @@
 import static androidx.appcompat.testutils.NightModeUtils.setNightModeAndWait;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 
 import android.content.res.Configuration;
 
@@ -68,23 +69,39 @@
     }
 
     @Test
-    public void testResourcesNotUpdated() throws Throwable {
-        final int defaultNightMode = mActivityTestRule.getActivity()
-                .getResources().getConfiguration().uiMode
-                & Configuration.UI_MODE_NIGHT_MASK;
-
+    public void testOnConfigurationChangeCalled() throws Throwable {
         // Set local night mode to YES
         setNightModeAndWait(mActivityTestRule, MODE_NIGHT_YES, mSetMode);
 
-        // Assert that the Activity did not get updated
-        assertConfigurationNightModeEquals(defaultNightMode,
+        // Assert that the onConfigurationChange was called with a new correct config
+        Configuration lastConfig = mActivityTestRule.getActivity()
+                .getLastConfigurationChangeAndClear();
+        assertNotNull(lastConfig);
+        assertConfigurationNightModeEquals(Configuration.UI_MODE_NIGHT_YES, lastConfig);
+
+        // Set local night mode back to NO
+        setNightModeAndWait(mActivityTestRule, MODE_NIGHT_NO, mSetMode);
+
+        // Assert that the onConfigurationChange was again called with a new correct config
+        lastConfig = mActivityTestRule.getActivity().getLastConfigurationChangeAndClear();
+        assertNotNull(lastConfig);
+        assertConfigurationNightModeEquals(Configuration.UI_MODE_NIGHT_NO, lastConfig);
+    }
+
+    @Test
+    public void testResourcesUpdated() throws Throwable {
+        // Set local night mode to YES
+        setNightModeAndWait(mActivityTestRule, MODE_NIGHT_YES, mSetMode);
+
+        // Assert that the Activity resources configuration was updated
+        assertConfigurationNightModeEquals(Configuration.UI_MODE_NIGHT_YES,
                 mActivityTestRule.getActivity().getResources().getConfiguration());
 
         // Set local night mode back to NO
         setNightModeAndWait(mActivityTestRule, MODE_NIGHT_NO, mSetMode);
 
-        // Assert that the Activity did not get updated
-        assertConfigurationNightModeEquals(defaultNightMode,
+        // Assert that the Activity resources configuration was updated
+        assertConfigurationNightModeEquals(Configuration.UI_MODE_NIGHT_NO,
                 mActivityTestRule.getActivity().getResources().getConfiguration());
     }
 
diff --git a/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java b/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java
index 86ae4f33..ec681f1 100644
--- a/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java
+++ b/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java
@@ -2247,8 +2247,10 @@
 
         final boolean activityHandlingUiMode = isActivityManifestHandlingUiMode();
 
-        if (newNightMode != applicationNightMode && !activityHandlingUiMode
-                && Build.VERSION.SDK_INT >= 17 && !mBaseContextAttached
+        if (newNightMode != applicationNightMode
+                && !activityHandlingUiMode
+                && Build.VERSION.SDK_INT >= 17
+                && !mBaseContextAttached
                 && mHost instanceof android.view.ContextThemeWrapper) {
             // If we're here then we can try and apply an override configuration on the Context.
             final Configuration conf = new Configuration();
@@ -2271,48 +2273,51 @@
             }
         }
 
-        if (!handled && !activityHandlingUiMode) {
-            final int currentNightMode = mContext.getResources().getConfiguration().uiMode
-                    & Configuration.UI_MODE_NIGHT_MASK;
-            if (currentNightMode != newNightMode) {
-                if (allowRecreation && mBaseContextAttached
-                        && (Build.VERSION.SDK_INT >= 17 || mCreated)
-                        && mHost instanceof Activity) {
-                    // If we're an attached Activity, we can recreate to apply
-                    // The SDK_INT check above is because applyOverrideConfiguration only exists on
-                    // API 17+, so we don't want to get into an loop of infinite recreations.
-                    // On < API 17 we need to use updateConfiguration before we're 'created'
-                    if (DEBUG) {
-                        Log.d(TAG, "updateForNightMode. Recreating Activity");
-                    }
-                    ActivityCompat.recreate((Activity) mHost);
-                    handled = true;
-                }
-                if (!handled) {
-                    // Else we need to use the updateConfiguration path
-                    if (DEBUG) {
-                        Log.d(TAG, "updateForNightMode. Updating resources config");
-                    }
-                    updateResourcesConfigurationForNightMode(newNightMode);
-                    handled = true;
-                }
-            } else {
-                if (DEBUG) {
-                    Log.d(TAG, "updateForNightMode. Skipping. Night mode: " + mode);
-                }
+        final int currentNightMode = mContext.getResources().getConfiguration().uiMode
+                & Configuration.UI_MODE_NIGHT_MASK;
+
+        if (!handled
+                && currentNightMode != newNightMode
+                && allowRecreation
+                && !activityHandlingUiMode
+                && mBaseContextAttached
+                && (Build.VERSION.SDK_INT >= 17 || mCreated)
+                && mHost instanceof Activity) {
+            // If we're an attached Activity, we can recreate to apply
+            // The SDK_INT check above is because applyOverrideConfiguration only exists on
+            // API 17+, so we don't want to get into an loop of infinite recreations.
+            // On < API 17 we need to use updateConfiguration before we're 'created'
+            if (DEBUG) {
+                Log.d(TAG, "updateForNightMode. Recreating Activity");
             }
+            ActivityCompat.recreate((Activity) mHost);
+            handled = true;
+        }
+
+        if (!handled && currentNightMode != newNightMode) {
+            // Else we need to use the updateConfiguration path
+            if (DEBUG) {
+                Log.d(TAG, "updateForNightMode. Updating resources config");
+            }
+            updateResourcesConfigurationForNightMode(newNightMode, activityHandlingUiMode);
+            handled = true;
+        }
+
+        if (DEBUG && !handled) {
+            Log.d(TAG, "updateForNightMode. Skipping. Night mode: " + mode);
         }
 
         // Notify the activity of the night mode. We only notify if we handled the change,
         // or the Activity is set to handle uiMode changes
-        if ((handled || activityHandlingUiMode) && mHost instanceof AppCompatActivity) {
+        if (handled && mHost instanceof AppCompatActivity) {
             ((AppCompatActivity) mHost).onNightModeChanged(mode);
         }
 
         return handled;
     }
 
-    private void updateResourcesConfigurationForNightMode(final int uiModeNightModeValue) {
+    private void updateResourcesConfigurationForNightMode(
+            final int uiModeNightModeValue, final boolean callOnConfigChange) {
         // If the Activity is not set to handle uiMode config changes we will
         // update the Resources with a new Configuration with an updated UI Mode
         final Resources res = mContext.getResources();
@@ -2340,6 +2345,10 @@
                 mContext.getTheme().applyStyle(mThemeResId, true);
             }
         }
+
+        if (callOnConfigChange && mHost instanceof Activity) {
+            ((Activity) mHost).onConfigurationChanged(conf);
+        }
     }
 
     /**