Merge "Make setDefaultNightMode immediately apply" into androidx-master-dev
diff --git a/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeOrientationConfigChangesTestCase.java b/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeOrientationConfigChangesTestCase.java
index a845616..0de4fea 100644
--- a/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeOrientationConfigChangesTestCase.java
+++ b/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeOrientationConfigChangesTestCase.java
@@ -16,8 +16,11 @@
 
 package androidx.appcompat.app;
 
+import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO;
+import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES;
 import static androidx.appcompat.testutils.NightModeUtils.assertConfigurationNightModeEquals;
-import static androidx.appcompat.testutils.NightModeUtils.setLocalNightModeAndWaitForDestroy;
+import static androidx.appcompat.testutils.NightModeUtils.setNightModeAndWait;
+import static androidx.appcompat.testutils.NightModeUtils.setNightModeAndWaitForDestroy;
 import static androidx.appcompat.testutils.TestUtilsActions.rotateScreenOrientation;
 import static androidx.test.espresso.Espresso.onView;
 import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
@@ -27,22 +30,35 @@
 import android.app.Activity;
 import android.content.res.Configuration;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.appcompat.testutils.NightModeUtils.NightSetMode;
 import androidx.test.filters.LargeTest;
 import androidx.test.rule.ActivityTestRule;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
 
 @LargeTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
 public class NightModeOrientationConfigChangesTestCase {
+    @Parameterized.Parameters
+    public static Collection<NightSetMode> data() {
+        return Arrays.asList(NightSetMode.DEFAULT, NightSetMode.LOCAL);
+    }
+
+    private final NightSetMode mSetMode;
+
     @Rule
     public final ActivityTestRule<NightModeOrientationConfigChangesActivity> mActivityTestRule;
 
-    public NightModeOrientationConfigChangesTestCase() {
+    public NightModeOrientationConfigChangesTestCase(NightSetMode setMode) {
+        mSetMode = setMode;
         mActivityTestRule = new ActivityTestRule<>(NightModeOrientationConfigChangesActivity.class);
     }
 
@@ -56,7 +72,7 @@
     @Test
     public void testRotateDoesNotRecreateActivity() throws Throwable {
         // Set local night mode to YES
-        setLocalNightModeAndWaitForDestroy(mActivityTestRule, AppCompatDelegate.MODE_NIGHT_YES);
+        setNightModeAndWaitForDestroy(mActivityTestRule, MODE_NIGHT_YES, mSetMode);
 
         final Activity activity = mActivityTestRule.getActivity();
 
@@ -70,4 +86,10 @@
         // And assert that we have the same Activity, and thus was not recreated
         assertSame(activity, mActivityTestRule.getActivity());
     }
+
+    @After
+    public void cleanup() throws Throwable {
+        // Reset the default night mode
+        setNightModeAndWait(mActivityTestRule, MODE_NIGHT_NO, NightSetMode.DEFAULT);
+    }
 }
diff --git a/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeTestCase.java b/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeTestCase.java
index 02b45bf..fa63b18 100644
--- a/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeTestCase.java
+++ b/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeTestCase.java
@@ -16,8 +16,12 @@
 
 package androidx.appcompat.app;
 
+import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM;
+import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO;
+import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES;
 import static androidx.appcompat.testutils.NightModeUtils.assertConfigurationNightModeEquals;
-import static androidx.appcompat.testutils.NightModeUtils.setLocalNightModeAndWait;
+import static androidx.appcompat.testutils.NightModeUtils.setNightModeAndWait;
+import static androidx.appcompat.testutils.NightModeUtils.setNightModeAndWaitForDestroy;
 import static androidx.appcompat.testutils.TestUtilsMatchers.isBackground;
 import static androidx.test.espresso.Espresso.onView;
 import static androidx.test.espresso.assertion.ViewAssertions.matches;
@@ -32,30 +36,43 @@
 import android.webkit.WebView;
 
 import androidx.appcompat.test.R;
+import androidx.appcompat.testutils.NightModeUtils.NightSetMode;
 import androidx.core.content.ContextCompat;
 import androidx.lifecycle.Lifecycle;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.concurrent.CountDownLatch;
 
 @LargeTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
 public class NightModeTestCase {
-    @Rule
-    public final ActivityTestRule<NightModeActivity> mActivityTestRule;
 
     private static final String STRING_DAY = "DAY";
     private static final String STRING_NIGHT = "NIGHT";
 
-    public NightModeTestCase() {
+    @Parameterized.Parameters
+    public static Collection<NightSetMode> data() {
+        return Arrays.asList(NightSetMode.DEFAULT, NightSetMode.LOCAL);
+    }
+
+    private final NightSetMode mSetMode;
+
+    @Rule
+    public final ActivityTestRule<NightModeActivity> mActivityTestRule;
+
+    public NightModeTestCase(NightSetMode setMode) {
+        mSetMode = setMode;
         mActivityTestRule = new ActivityTestRule<>(NightModeActivity.class);
     }
 
@@ -68,14 +85,19 @@
 
     @Test
     public void testLocalDayNightModeRecreatesActivity() throws Throwable {
+        if (mSetMode != NightSetMode.LOCAL) {
+            // This test is only applicable when using setLocalNightMode
+            return;
+        }
+
         // Verify first that we're in day mode
         onView(withId(R.id.text_night_mode)).check(matches(withText(STRING_DAY)));
 
         // Now force the local night mode to be yes (aka night mode)
-        setLocalNightModeAndWaitForRecreate(AppCompatDelegate.MODE_NIGHT_YES);
+        setNightModeAndWaitForDestroy(mActivityTestRule, MODE_NIGHT_YES, mSetMode);
 
         // Assert that the new local night mode is returned
-        assertEquals(AppCompatDelegate.MODE_NIGHT_YES,
+        assertEquals(MODE_NIGHT_YES,
                 mActivityTestRule.getActivity().getDelegate().getLocalNightMode());
 
         // Now check the text has changed, signifying that night resources are being used
@@ -89,14 +111,14 @@
                 .check(matches(withText(STRING_DAY)));
 
         // Now force the local night mode to be yes (aka night mode)
-        setLocalNightModeAndWaitForRecreate(AppCompatDelegate.MODE_NIGHT_YES);
+        setNightModeAndWaitForDestroy(mActivityTestRule, MODE_NIGHT_YES, mSetMode);
 
         // Now check the text has changed, signifying that night resources are being used
         onView(withId(R.id.text_night_mode))
                 .check(matches(withText(STRING_NIGHT)));
 
         // Now force the local night mode to be FOLLOW_SYSTEM, which should go back to DAY
-        setLocalNightModeAndWaitForRecreate(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
+        setNightModeAndWaitForDestroy(mActivityTestRule, MODE_NIGHT_FOLLOW_SYSTEM, mSetMode);
 
         // Now check the text has changed, signifying that night resources are being used
         onView(withId(R.id.text_night_mode))
@@ -114,11 +136,11 @@
         // cache for the issue to happen
         for (int i = 0; i < 5; i++) {
             // First force it to be night mode and assert the color
-            setLocalNightModeAndWaitForRecreate(AppCompatDelegate.MODE_NIGHT_YES);
+            setNightModeAndWaitForDestroy(mActivityTestRule, MODE_NIGHT_YES, mSetMode);
             onView(withId(R.id.view_background)).check(matches(isBackground(nightColor)));
 
             // Now force the local night mode to be no (aka day mode) and assert the color
-            setLocalNightModeAndWaitForRecreate(AppCompatDelegate.MODE_NIGHT_NO);
+            setNightModeAndWaitForDestroy(mActivityTestRule, MODE_NIGHT_NO, mSetMode);
             onView(withId(R.id.view_background)).check(matches(isBackground(dayColor)));
         }
     }
@@ -133,7 +155,7 @@
         onView(withId(R.id.text_night_mode)).check(matches(withText(STRING_DAY)));
 
         // Set MODE_NIGHT_AUTO so that we will change to night mode automatically
-        setLocalNightModeAndWait(mActivityTestRule, AppCompatDelegate.MODE_NIGHT_AUTO_TIME);
+        setNightModeAndWait(mActivityTestRule, AppCompatDelegate.MODE_NIGHT_AUTO_TIME, mSetMode);
         final AppCompatDelegateImpl newDelegate =
                 (AppCompatDelegateImpl) mActivityTestRule.getActivity().getDelegate();
 
@@ -160,7 +182,7 @@
         TwilightManager.setInstance(twilightManager);
 
         // Set MODE_NIGHT_AUTO_TIME so that we will change to night mode automatically
-        setLocalNightModeAndWait(mActivityTestRule, AppCompatDelegate.MODE_NIGHT_AUTO_TIME);
+        setNightModeAndWait(mActivityTestRule, AppCompatDelegate.MODE_NIGHT_AUTO_TIME, mSetMode);
 
         // Verify that we're currently in day mode
         onView(withId(R.id.text_night_mode)).check(matches(withText(STRING_DAY)));
@@ -196,16 +218,15 @@
     @Test
     public void testOnNightModeChangedCalled() throws Throwable {
         // Set local night mode to YES
-        setLocalNightModeAndWait(mActivityTestRule, AppCompatDelegate.MODE_NIGHT_YES);
+        setNightModeAndWait(mActivityTestRule, MODE_NIGHT_YES, mSetMode);
         // Assert that the Activity received a new value
-        assertEquals(AppCompatDelegate.MODE_NIGHT_YES,
-                mActivityTestRule.getActivity().getLastNightModeAndReset());
+        assertEquals(MODE_NIGHT_YES, mActivityTestRule.getActivity().getLastNightModeAndReset());
     }
 
     @Test
     public void testDialogDoesNotOverrideActivityConfiguration() throws Throwable {
         // Set Activity local night mode to YES
-        setLocalNightModeAndWaitForRecreate(AppCompatDelegate.MODE_NIGHT_YES);
+        setNightModeAndWaitForDestroy(mActivityTestRule, MODE_NIGHT_YES, mSetMode);
 
         // Assert that the uiMode is as expected
         assertConfigurationNightModeEquals(Configuration.UI_MODE_NIGHT_YES,
@@ -228,7 +249,7 @@
     @Test
     public void testLoadingWebViewMaintainsConfiguration() throws Throwable {
         // Set night mode and wait for the new Activity
-        setLocalNightModeAndWaitForRecreate(AppCompatDelegate.MODE_NIGHT_YES);
+        setNightModeAndWaitForDestroy(mActivityTestRule, MODE_NIGHT_YES, mSetMode);
 
         // Assert that the context still has a night themed configuration
         assertConfigurationNightModeEquals(
@@ -249,6 +270,12 @@
                 mActivityTestRule.getActivity().getResources().getConfiguration());
     }
 
+    @After
+    public void cleanup() throws Throwable {
+        // Reset the default night mode
+        setNightModeAndWait(mActivityTestRule, MODE_NIGHT_NO, NightSetMode.DEFAULT);
+    }
+
     private static class FakeTwilightManager extends TwilightManager {
         private boolean mIsNight;
 
@@ -265,16 +292,4 @@
             mIsNight = night;
         }
     }
-
-    private void setLocalNightModeAndWaitForRecreate(
-            @AppCompatDelegate.NightMode final int nightMode) throws Throwable {
-        final NightModeActivity activity = mActivityTestRule.getActivity();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                activity.getDelegate().setLocalNightMode(nightMode);
-            }
-        });
-        waitUntilState(activity, mActivityTestRule, Lifecycle.State.DESTROYED);
-    }
 }
diff --git a/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeUiModeConfigChangesTestCase.java b/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeUiModeConfigChangesTestCase.java
index bed3af3..c37e1b02 100644
--- a/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeUiModeConfigChangesTestCase.java
+++ b/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeUiModeConfigChangesTestCase.java
@@ -16,37 +16,52 @@
 
 package androidx.appcompat.app;
 
+import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO;
+import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES;
 import static androidx.appcompat.testutils.NightModeUtils.assertConfigurationNightModeEquals;
-import static androidx.appcompat.testutils.NightModeUtils.setLocalNightModeAndWait;
+import static androidx.appcompat.testutils.NightModeUtils.setNightModeAndWait;
 
 import static org.junit.Assert.assertEquals;
 
 import android.content.res.Configuration;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.appcompat.testutils.NightModeUtils.NightSetMode;
 import androidx.test.filters.LargeTest;
 import androidx.test.rule.ActivityTestRule;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
 
 @LargeTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
 public class NightModeUiModeConfigChangesTestCase {
+    @Parameterized.Parameters
+    public static Collection<NightSetMode> data() {
+        return Arrays.asList(NightSetMode.DEFAULT, NightSetMode.LOCAL);
+    }
+
+    private final NightSetMode mSetMode;
+
     @Rule
     public final ActivityTestRule<NightModeUiModeConfigChangesActivity> mActivityTestRule;
 
-    public NightModeUiModeConfigChangesTestCase() {
+    public NightModeUiModeConfigChangesTestCase(NightSetMode setMode) {
+        mSetMode = setMode;
         mActivityTestRule = new ActivityTestRule<>(NightModeUiModeConfigChangesActivity.class);
     }
 
     @Before
     public void setup() {
         // By default we'll set the night mode to NO, which allows us to make better
-        // assumptions in the test below
-        AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
+        // assumptions in the tests below
+        AppCompatDelegate.setDefaultNightMode(MODE_NIGHT_NO);
     }
 
     @Test
@@ -56,14 +71,14 @@
                 & Configuration.UI_MODE_NIGHT_MASK;
 
         // Set local night mode to YES
-        setLocalNightModeAndWait(mActivityTestRule, AppCompatDelegate.MODE_NIGHT_YES);
+        setNightModeAndWait(mActivityTestRule, MODE_NIGHT_YES, mSetMode);
 
         // Assert that the Activity did not get updated
         assertConfigurationNightModeEquals(defaultNightMode,
                 mActivityTestRule.getActivity().getResources().getConfiguration());
 
         // Set local night mode back to NO
-        setLocalNightModeAndWait(mActivityTestRule, AppCompatDelegate.MODE_NIGHT_NO);
+        setNightModeAndWait(mActivityTestRule, MODE_NIGHT_NO, mSetMode);
 
         // Assert that the Activity did not get updated
         assertConfigurationNightModeEquals(defaultNightMode,
@@ -73,15 +88,19 @@
     @Test
     public void testOnNightModeChangedCalled() throws Throwable {
         // Set local night mode to YES
-        setLocalNightModeAndWait(mActivityTestRule, AppCompatDelegate.MODE_NIGHT_YES);
+        setNightModeAndWait(mActivityTestRule, MODE_NIGHT_YES, mSetMode);
         // Assert that the Activity received a new value
-        assertEquals(AppCompatDelegate.MODE_NIGHT_YES,
-                mActivityTestRule.getActivity().getLastNightModeAndReset());
+        assertEquals(MODE_NIGHT_YES, mActivityTestRule.getActivity().getLastNightModeAndReset());
 
         // Set local night mode to NO
-        setLocalNightModeAndWait(mActivityTestRule, AppCompatDelegate.MODE_NIGHT_NO);
+        setNightModeAndWait(mActivityTestRule, MODE_NIGHT_NO, mSetMode);
         // Assert that the Activity received a new value
-        assertEquals(AppCompatDelegate.MODE_NIGHT_NO,
-                mActivityTestRule.getActivity().getLastNightModeAndReset());
+        assertEquals(MODE_NIGHT_NO, mActivityTestRule.getActivity().getLastNightModeAndReset());
+    }
+
+    @After
+    public void cleanup() throws Throwable {
+        // Reset the default night mode
+        setNightModeAndWait(mActivityTestRule, MODE_NIGHT_NO, NightSetMode.DEFAULT);
     }
 }
diff --git a/appcompat/src/androidTest/java/androidx/appcompat/testutils/NightModeUtils.java b/appcompat/src/androidTest/java/androidx/appcompat/testutils/NightModeUtils.java
index 6d7ef15..88dc3f2 100644
--- a/appcompat/src/androidTest/java/androidx/appcompat/testutils/NightModeUtils.java
+++ b/appcompat/src/androidTest/java/androidx/appcompat/testutils/NightModeUtils.java
@@ -24,6 +24,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.app.AppCompatDelegate;
 import androidx.appcompat.app.AppCompatDelegate.NightMode;
 import androidx.lifecycle.Lifecycle;
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -32,6 +33,18 @@
 
 public class NightModeUtils {
 
+    public enum NightSetMode {
+        /**
+         * Set the night mode using {@link AppCompatDelegate#setDefaultNightMode(int)}
+         */
+        DEFAULT,
+
+        /**
+         * Set the night mode using {@link AppCompatDelegate#setLocalNightMode(int)}
+         */
+        LOCAL
+    }
+
     public static void assertConfigurationNightModeEquals(int expectedNightMode,
             @NonNull Context context) {
         assertConfigurationNightModeEquals(expectedNightMode,
@@ -43,37 +56,53 @@
         assertEquals(expectedNightMode, configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK);
     }
 
-    public static <T extends AppCompatActivity> void setLocalNightModeAndWait(
-            final ActivityTestRule<T> activityRule, @NightMode final int nightMode
+    public static <T extends AppCompatActivity> void setNightModeAndWait(
+            final ActivityTestRule<T> activityRule,
+            @NightMode final int nightMode,
+            final NightSetMode setMode
     ) throws Throwable {
-        setLocalNightModeAndWait(activityRule.getActivity(), activityRule, nightMode);
+        setNightModeAndWait(activityRule.getActivity(), activityRule, nightMode, setMode);
     }
 
-    public static <T extends AppCompatActivity> void setLocalNightModeAndWait(
+    public static <T extends AppCompatActivity> void setNightModeAndWait(
             final AppCompatActivity activity,
             final ActivityTestRule<T> activityRule,
-            @NightMode final int nightMode
+            @NightMode final int nightMode,
+            final NightSetMode setMode
     ) throws Throwable {
         final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
         activityRule.runOnUiThread(new Runnable() {
             @Override
             public void run() {
-                activity.getDelegate().setLocalNightMode(nightMode);
+                setNightMode(nightMode, activity, setMode);
             }
         });
         instrumentation.waitForIdleSync();
     }
 
-    public static <T extends AppCompatActivity> void setLocalNightModeAndWaitForDestroy(
-            final ActivityTestRule<T> activityRule, @NightMode final int nightMode
+    public static <T extends AppCompatActivity> void setNightModeAndWaitForDestroy(
+            final ActivityTestRule<T> activityRule,
+            @NightMode final int nightMode,
+            final NightSetMode setMode
     ) throws Throwable {
         final T activity = activityRule.getActivity();
         activityRule.runOnUiThread(new Runnable() {
             @Override
             public void run() {
-                activity.getDelegate().setLocalNightMode(nightMode);
+                setNightMode(nightMode, activity, setMode);
             }
         });
         LifecycleOwnerUtils.waitUntilState(activity, activityRule, Lifecycle.State.DESTROYED);
     }
+
+    private static void setNightMode(
+            @NightMode final int nightMode,
+            final AppCompatActivity activity,
+            final NightSetMode setMode) {
+        if (setMode == NightSetMode.DEFAULT) {
+            AppCompatDelegate.setDefaultNightMode(nightMode);
+        } else {
+            activity.getDelegate().setLocalNightMode(nightMode);
+        }
+    }
 }
diff --git a/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegate.java b/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegate.java
index 48b9d14..1353953 100644
--- a/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegate.java
+++ b/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegate.java
@@ -40,11 +40,14 @@
 import androidx.appcompat.view.ActionMode;
 import androidx.appcompat.widget.Toolbar;
 import androidx.appcompat.widget.VectorEnabledTintResources;
+import androidx.collection.ArraySet;
 import androidx.core.view.WindowCompat;
 import androidx.fragment.app.FragmentActivity;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.util.Iterator;
 
 /**
  * This class represents a delegate which you can use to extend AppCompat's support to any
@@ -159,6 +162,10 @@
     @NightMode
     private static int sDefaultNightMode = MODE_NIGHT_UNSPECIFIED;
 
+    private static final ArraySet<WeakReference<AppCompatDelegate>> sActiveDelegates =
+            new ArraySet<>();
+    private static final Object sActiveDelegatesLock = new Object();
+
     /** @hide */
     @RestrictTo(LIBRARY_GROUP_PREFIX)
     @IntDef({MODE_NIGHT_NO, MODE_NIGHT_YES, MODE_NIGHT_AUTO_TIME, MODE_NIGHT_FOLLOW_SYSTEM,
@@ -476,22 +483,12 @@
     public abstract void onSaveInstanceState(Bundle outState);
 
     /**
-     * Allow AppCompat to apply the {@code night} and {@code notnight} resource qualifiers.
+     * Applies the currently selected night mode to this delegate's host component.
      *
-     * <p>Doing this enables the
+     * <p>This enables the
      * {@link
      * androidx.appcompat.R.style#Theme_AppCompat_DayNight Theme.AppCompat.DayNight}
-     * family of themes to work, using the computed twilight to automatically select a dark or
-     * light theme.</p>
-     *
-     * <p>You can override the night mode using {@link #setLocalNightMode(int)}.</p>
-     *
-     * <p>This only works on devices running
-     * {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH ICE_CREAM_SANDWICH} and above.</p>
-     *
-     * <p>If this is called after the host component has been created, the component will either be
-     * automatically recreated or its {@link Configuration} updated. Which one depends on how
-     * the component is setup (via {@code android:configChanges} or similar).</p>
+     * family of themes to work, using the specified mode.</p>
      *
      * <p>You can be notified when the night changes by overriding the
      * {@link AppCompatActivity#onNightModeChanged(int)} method.</p>
@@ -504,15 +501,20 @@
     public abstract boolean applyDayNight();
 
     /**
-     * Override the night mode used for this delegate's host component. This method only takes
-     * effect for those situations where {@link #applyDayNight()} works.
+     * Override the night mode used for this delegate's host component.
      *
-     * <p>As this will call {@link #applyDayNight()}, the host component might be
-     * recreated automatically.</p>
+     * <p>When setting an mode to be used across an entire app, the
+     * {@link #setDefaultNightMode(int)} method is preferred.</p>
+     *
+     * <p>If this is called after the host component has been created, a {@code uiMode}
+     * configuration change will occur, which may result in the component being recreated.</p>
      *
      * <p>It is not recommended to use this method on a delegate attached to a {@link Dialog}.
      * Dialogs use the host Activity as their context, resulting in the dialog's night mode
      * overriding the Activity's night mode.
+     *
+     * @see #getLocalNightMode()
+     * @see #setDefaultNightMode(int)
      */
     public abstract void setLocalNightMode(@NightMode int mode);
 
@@ -525,14 +527,18 @@
     }
 
     /**
-     * Sets the default night mode. This is used across all activities/dialogs but can be overridden
-     * locally via {@link #setLocalNightMode(int)}.
+     * Sets the default night mode. This is the default value used for all components, but can
+     * be overridden locally via {@link #setLocalNightMode(int)}.
      *
-     * <p>This method only takes effect for those situations where {@link #applyDayNight()} works.
-     * Defaults to {@link #MODE_NIGHT_FOLLOW_SYSTEM}.</p>
+     * <p>This is the primary method to control the DayNight functionality, since it allows
+     * the delegates to avoid unnecessary recreations when possible.</p>
      *
-     * <p>This only takes effect for components which are created after the call. Any components
-     * which are already open will not be updated.</p>
+     * <p>If this method is called after any host components with attached
+     * {@link AppCompatDelegate}s have been 'started', a {@code uiMode} configuration change
+     * will occur in each. This may result in those components being recreated, depending
+     * on their manifest configuration.</p>
+     *
+     * <p>Defaults to {@link #MODE_NIGHT_FOLLOW_SYSTEM}.</p>
      *
      * @see #setLocalNightMode(int)
      * @see #getDefaultNightMode()
@@ -544,7 +550,10 @@
             case MODE_NIGHT_FOLLOW_SYSTEM:
             case MODE_NIGHT_AUTO_TIME:
             case MODE_NIGHT_AUTO_BATTERY:
-                sDefaultNightMode = mode;
+                if (sDefaultNightMode != mode) {
+                    sDefaultNightMode = mode;
+                    applyDayNightToActiveDelegates();
+                }
                 break;
             default:
                 Log.d(TAG, "setDefaultNightMode() called with an unknown mode");
@@ -608,4 +617,46 @@
     public static boolean isCompatVectorFromResourcesEnabled() {
         return VectorEnabledTintResources.isCompatVectorFromResourcesEnabled();
     }
+
+    static void markStarted(@NonNull AppCompatDelegate delegate) {
+        synchronized (sActiveDelegatesLock) {
+            // Remove any existing records pointing to the delegate.
+            // There should not be any, but we'll make sure
+            removeDelegateFromActives(delegate);
+            // Add a new record to the set
+            sActiveDelegates.add(new WeakReference<>(delegate));
+        }
+    }
+
+    static void markStopped(@NonNull AppCompatDelegate delegate) {
+        synchronized (sActiveDelegatesLock) {
+            // Remove any WeakRef records pointing to the delegate in the set
+            removeDelegateFromActives(delegate);
+        }
+    }
+
+    private static void removeDelegateFromActives(@NonNull AppCompatDelegate toRemove) {
+        synchronized (sActiveDelegatesLock) {
+            final Iterator<WeakReference<AppCompatDelegate>> i = sActiveDelegates.iterator();
+            while (i.hasNext()) {
+                final AppCompatDelegate delegate = i.next().get();
+                if (delegate == toRemove || delegate == null) {
+                    // If the delegate is the one to remove, or it is null (because of the WeakRef),
+                    // remove it from the set
+                    i.remove();
+                }
+            }
+        }
+    }
+
+    private static void applyDayNightToActiveDelegates() {
+        synchronized (sActiveDelegatesLock) {
+            for (WeakReference<AppCompatDelegate> activeDelegate : sActiveDelegates) {
+                final AppCompatDelegate delegate = activeDelegate.get();
+                if (delegate != null) {
+                    delegate.applyDayNight();
+                }
+            }
+        }
+    }
 }
diff --git a/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java b/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java
index b36e7c8..06ca972 100644
--- a/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java
+++ b/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java
@@ -498,10 +498,14 @@
         // This will apply day/night if the time has changed, it will also call through to
         // setupAutoNightModeIfNeeded()
         applyDayNight();
+
+        markStarted(this);
     }
 
     @Override
     public void onStop() {
+        markStopped(this);
+
         ActionBar ab = getSupportActionBar();
         if (ab != null) {
             ab.setShowHideAnimationEnabled(false);
@@ -569,6 +573,9 @@
 
     @Override
     public void onDestroy() {
+        // There are cases where onStop is not called on all API levels. We make sure here.
+        markStopped(this);
+
         if (mInvalidatePanelMenuPosted) {
             mWindow.getDecorView().removeCallbacks(mInvalidatePanelMenuRunnable);
         }
@@ -2128,15 +2135,26 @@
     }
 
     private boolean applyDayNight(final boolean recreateIfNeeded) {
+        if (mIsDestroyed) {
+            // If we're destroyed, ignore the call
+            return false;
+        }
+
         @NightMode final int nightMode = calculateNightMode();
         @ApplyableNightMode final int modeToApply = mapNightMode(nightMode);
         final boolean applied = updateForNightMode(modeToApply, recreateIfNeeded);
 
         if (nightMode == MODE_NIGHT_AUTO_TIME) {
-            // If we're already been started, we may need to setup auto mode again
             getAutoTimeNightModeManager().setup();
-        } else if (nightMode == MODE_NIGHT_AUTO_BATTERY) {
+        } else if (mAutoTimeNightModeManager != null) {
+            // Make sure we clean up the existing manager
+            mAutoTimeNightModeManager.cleanup();
+        }
+        if (nightMode == MODE_NIGHT_AUTO_BATTERY) {
             getAutoBatteryNightModeManager().setup();
+        } else if (mAutoBatteryNightModeManager != null) {
+            // Make sure we clean up the existing manager
+            mAutoBatteryNightModeManager.cleanup();
         }
 
         return applied;
diff --git a/samples/Support7Demos/src/main/AndroidManifest.xml b/samples/Support7Demos/src/main/AndroidManifest.xml
index 3014c77..769c2fe 100644
--- a/samples/Support7Demos/src/main/AndroidManifest.xml
+++ b/samples/Support7Demos/src/main/AndroidManifest.xml
@@ -376,8 +376,8 @@
             </intent-filter>
         </activity>
 
-        <activity android:name=".app.AppCompatNightModeActivity"
-                  android:label="@string/mode_night_activity_title"
+        <activity android:name=".app.AppCompatDefaultNightModeActivity"
+                  android:label="@string/mode_night_activity_title_default"
                   android:theme="@style/Theme.AppCompat.DayNight">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -385,6 +385,15 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".app.AppCompatLocalNightModeActivity"
+            android:label="@string/mode_night_activity_title_local"
+            android:theme="@style/Theme.AppCompat.DayNight">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.example.android.supportv7.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
         <activity android:name=".app.AppCompatNightModeDialog"
                   android:label="@string/mode_night_dialog_title"
                   android:theme="@style/Theme.AppCompat">
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/app/AppCompatNightModeActivity.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/app/AppCompatDefaultNightModeActivity.java
similarity index 71%
copy from samples/Support7Demos/src/main/java/com/example/android/supportv7/app/AppCompatNightModeActivity.java
copy to samples/Support7Demos/src/main/java/com/example/android/supportv7/app/AppCompatDefaultNightModeActivity.java
index 30a24ef..9b3ffe9 100644
--- a/samples/Support7Demos/src/main/java/com/example/android/supportv7/app/AppCompatNightModeActivity.java
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/app/AppCompatDefaultNightModeActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright 2019 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.
@@ -29,7 +29,7 @@
 /**
  * This demonstrates idiomatic usage of AppCompatActivity with Theme.AppCompat.DayNight
  */
-public class AppCompatNightModeActivity extends AppCompatActivity {
+public class AppCompatDefaultNightModeActivity extends AppCompatActivity {
 
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -38,22 +38,22 @@
     }
 
     public void setModeNightFollowSystem(View view) {
-        getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
+        AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
     }
 
     public void setModeNightNo(View view) {
-        getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_NO);
+        AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
     }
 
     public void setModeNightYes(View view) {
-        getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES);
+        AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
     }
 
     public void setModeNightAutoTime(View view) {
-        getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_TIME);
+        AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_TIME);
     }
 
     public void setModeNightAutoBattery(View view) {
-        getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY);
+        AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY);
     }
-}
\ No newline at end of file
+}
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/app/AppCompatNightModeActivity.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/app/AppCompatLocalNightModeActivity.java
similarity index 96%
rename from samples/Support7Demos/src/main/java/com/example/android/supportv7/app/AppCompatNightModeActivity.java
rename to samples/Support7Demos/src/main/java/com/example/android/supportv7/app/AppCompatLocalNightModeActivity.java
index 30a24ef..140adab 100644
--- a/samples/Support7Demos/src/main/java/com/example/android/supportv7/app/AppCompatNightModeActivity.java
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/app/AppCompatLocalNightModeActivity.java
@@ -29,7 +29,7 @@
 /**
  * This demonstrates idiomatic usage of AppCompatActivity with Theme.AppCompat.DayNight
  */
-public class AppCompatNightModeActivity extends AppCompatActivity {
+public class AppCompatLocalNightModeActivity extends AppCompatActivity {
 
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -56,4 +56,4 @@
     public void setModeNightAutoBattery(View view) {
         getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY);
     }
-}
\ No newline at end of file
+}
diff --git a/samples/Support7Demos/src/main/res/values/strings.xml b/samples/Support7Demos/src/main/res/values/strings.xml
index c9db2ab..cb961ec 100644
--- a/samples/Support7Demos/src/main/res/values/strings.xml
+++ b/samples/Support7Demos/src/main/res/values/strings.xml
@@ -222,7 +222,8 @@
     <string name="mode_night_no">MODE_NIGHT_NO</string>
     <string name="mode_night_auto_time">MODE_NIGHT_AUTO_TIME</string>
     <string name="mode_night_auto_battery">MODE_NIGHT_AUTO_BATTERY</string>
-    <string name="mode_night_activity_title">AppCompat/DayNight/Activity Usage</string>
+    <string name="mode_night_activity_title_local">AppCompat/DayNight/Activity Usage (local)</string>
+    <string name="mode_night_activity_title_default">AppCompat/DayNight/Activity Usage (default)</string>
     <string name="mode_night_dialog_title">AppCompat/DayNight/Dialog Usage</string>
     <string name="mode_night_alertdialog_title">AppCompat/DayNight/AlertDialog Usage</string>