Make setDefaultNightMode immediately apply
Currently setDefaultNightMode is just a simple setter,
meaning that it only takes effect in any components
which are created after the call. This is different
to setLocalNightMode() which applies the new night
mode immediately.
This CL syncs up the behavior between the two, so that
any AppCompatDelegates in the 'created' state have their
DayNight mode re-applied.
Also tidied up the method docs, since setDefaultNightMode
is now the preferred way to control DayNight.
BUG: 129365513
Test: updated current tests to parametize over both local/default
Test: ./gradlew appcompat:connectedCheck
Change-Id: I426c81567a3148afae5b9e4d44b6eeb7f4870ef1
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 e0d27bb..5b572ad 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,
@@ -464,22 +471,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>
@@ -492,15 +489,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);
@@ -513,14 +515,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()
@@ -532,7 +538,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");
@@ -596,4 +605,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 5d62ffa..04f9918 100644
--- a/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java
+++ b/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java
@@ -494,10 +494,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);
@@ -565,6 +569,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);
}
@@ -2124,15 +2131,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>