1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.appcompat.app; 18 19 import static android.support.test.espresso.Espresso.onView; 20 import static android.support.test.espresso.assertion.ViewAssertions.matches; 21 import static android.support.test.espresso.matcher.ViewMatchers.withId; 22 import static android.support.test.espresso.matcher.ViewMatchers.withText; 23 24 import static androidx.appcompat.app.NightModeActivity.TOP_ACTIVITY; 25 import static androidx.appcompat.testutils.TestUtilsMatchers.isBackground; 26 27 import static org.junit.Assert.assertTrue; 28 29 import android.app.Instrumentation; 30 import android.support.test.InstrumentationRegistry; 31 import android.support.test.filters.LargeTest; 32 import android.support.test.rule.ActivityTestRule; 33 import android.support.test.runner.AndroidJUnit4; 34 35 import androidx.appcompat.test.R; 36 import androidx.core.content.ContextCompat; 37 import androidx.testutils.AppCompatActivityUtils; 38 import androidx.testutils.RecreatedAppCompatActivity; 39 40 import org.junit.Before; 41 import org.junit.Rule; 42 import org.junit.Test; 43 import org.junit.runner.RunWith; 44 45 import java.util.concurrent.CountDownLatch; 46 import java.util.concurrent.TimeUnit; 47 48 @LargeTest 49 @RunWith(AndroidJUnit4.class) 50 public class NightModeTestCase { 51 @Rule 52 public final ActivityTestRule<NightModeActivity> mActivityTestRule; 53 54 private static final String STRING_DAY = "DAY"; 55 private static final String STRING_NIGHT = "NIGHT"; 56 57 public NightModeTestCase() { 58 mActivityTestRule = new ActivityTestRule<>(NightModeActivity.class); 59 } 60 61 @Before 62 public void setup() { 63 // By default we'll set the night mode to NO, which allows us to make better 64 // assumptions in the test below 65 AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); 66 } 67 68 @Test 69 public void testLocalDayNightModeRecreatesActivity() throws Throwable { 70 // Verify first that we're in day mode 71 onView(withId(R.id.text_night_mode)).check(matches(withText(STRING_DAY))); 72 73 // Now force the local night mode to be yes (aka night mode) 74 setLocalNightModeAndWaitForRecreate( 75 mActivityTestRule.getActivity(), AppCompatDelegate.MODE_NIGHT_YES); 76 77 // Now check the text has changed, signifying that night resources are being used 78 onView(withId(R.id.text_night_mode)).check(matches(withText(STRING_NIGHT))); 79 } 80 81 @Test 82 public void testColorConvertedDrawableChangesWithNightMode() throws Throwable { 83 final NightModeActivity activity = mActivityTestRule.getActivity(); 84 final int dayColor = ContextCompat.getColor(activity, R.color.color_sky_day); 85 final int nightColor = ContextCompat.getColor(activity, R.color.color_sky_night); 86 87 // Loop through and switching from day to night and vice-versa multiple times. It needs 88 // to be looped since the issue is with drawable caching, therefore we need to prime the 89 // cache for the issue to happen 90 for (int i = 0; i < 5; i++) { 91 // First force it to not be night mode 92 setLocalNightModeAndWaitForRecreate(TOP_ACTIVITY, AppCompatDelegate.MODE_NIGHT_NO); 93 // ... and verify first that we're in day mode 94 onView(withId(R.id.view_background)).check(matches(isBackground(dayColor))); 95 96 // Now force the local night mode to be yes (aka night mode) 97 setLocalNightModeAndWaitForRecreate(TOP_ACTIVITY, AppCompatDelegate.MODE_NIGHT_YES); 98 // ... and verify first that we're in night mode 99 onView(withId(R.id.view_background)).check(matches(isBackground(nightColor))); 100 } 101 } 102 103 @Test 104 public void testNightModeAutoRecreatesOnTimeChange() throws Throwable { 105 // Create a fake TwilightManager and set it as the app instance 106 final FakeTwilightManager twilightManager = new FakeTwilightManager(); 107 TwilightManager.setInstance(twilightManager); 108 109 final NightModeActivity activity = mActivityTestRule.getActivity(); 110 111 // Verify that we're currently in day mode 112 onView(withId(R.id.text_night_mode)).check(matches(withText(STRING_DAY))); 113 114 // Set MODE_NIGHT_AUTO so that we will change to night mode automatically 115 final NightModeActivity newActivity = 116 setLocalNightModeAndWaitForRecreate(activity, AppCompatDelegate.MODE_NIGHT_AUTO); 117 final AppCompatDelegateImpl newDelegate = 118 (AppCompatDelegateImpl) newActivity.getDelegate(); 119 120 // Update the fake twilight manager to be in night and trigger a fake 'time' change 121 mActivityTestRule.runOnUiThread(new Runnable() { 122 @Override 123 public void run() { 124 twilightManager.setIsNight(true); 125 newDelegate.getAutoNightModeManager().dispatchTimeChanged(); 126 } 127 }); 128 129 RecreatedAppCompatActivity.sResumed = new CountDownLatch(1); 130 assertTrue(RecreatedAppCompatActivity.sResumed.await(1, TimeUnit.SECONDS)); 131 // At this point recreate that has been triggered by dispatchTimeChanged call 132 // has completed 133 134 // Check that the text has changed, signifying that night resources are being used 135 onView(withId(R.id.text_night_mode)).check(matches(withText(STRING_NIGHT))); 136 } 137 138 @Test 139 public void testNightModeAutoRecreatesOnResume() throws Throwable { 140 // Create a fake TwilightManager and set it as the app instance 141 final FakeTwilightManager twilightManager = new FakeTwilightManager(); 142 TwilightManager.setInstance(twilightManager); 143 144 NightModeActivity activity = mActivityTestRule.getActivity(); 145 146 // Set MODE_NIGHT_AUTO so that we will change to night mode automatically 147 activity = setLocalNightModeAndWaitForRecreate(activity, AppCompatDelegate.MODE_NIGHT_AUTO); 148 // Verify that we're currently in day mode 149 onView(withId(R.id.text_night_mode)).check(matches(withText(STRING_DAY))); 150 151 final NightModeActivity toTest = activity; 152 153 mActivityTestRule.runOnUiThread(new Runnable() { 154 @Override 155 public void run() { 156 final Instrumentation instrumentation = 157 InstrumentationRegistry.getInstrumentation(); 158 // Now fool the Activity into thinking that it has gone into the background 159 instrumentation.callActivityOnPause(toTest); 160 instrumentation.callActivityOnStop(toTest); 161 162 // Now update the twilight manager while the Activity is in the 'background' 163 twilightManager.setIsNight(true); 164 165 // Now tell the Activity that it has gone into the foreground again 166 instrumentation.callActivityOnStart(toTest); 167 instrumentation.callActivityOnResume(toTest); 168 } 169 }); 170 171 // finally check that the text has changed, signifying that night resources are being used 172 onView(withId(R.id.text_night_mode)).check(matches(withText(STRING_NIGHT))); 173 } 174 175 private static class FakeTwilightManager extends TwilightManager { 176 private boolean mIsNight; 177 178 FakeTwilightManager() { 179 super(null, null); 180 } 181 182 @Override 183 boolean isNight() { 184 return mIsNight; 185 } 186 187 void setIsNight(boolean night) { 188 mIsNight = night; 189 } 190 } 191 192 private NightModeActivity setLocalNightModeAndWaitForRecreate( 193 final NightModeActivity activity, 194 @AppCompatDelegate.NightMode final int nightMode) throws Throwable { 195 final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); 196 mActivityTestRule.runOnUiThread(new Runnable() { 197 @Override 198 public void run() { 199 activity.getDelegate().setLocalNightMode(nightMode); 200 } 201 }); 202 final NightModeActivity result = 203 AppCompatActivityUtils.recreateActivity(mActivityTestRule, activity); 204 AppCompatActivityUtils.waitForExecution(mActivityTestRule); 205 206 instrumentation.waitForIdleSync(); 207 return result; 208 } 209 } 210