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>