1 /* 2 * Copyright 2022 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 * https://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 @file:Suppress("DEPRECATION") 18 19 package com.google.accompanist.systemuicontroller 20 21 import android.os.Build 22 import android.view.View 23 import android.view.Window 24 import androidx.activity.ComponentActivity 25 import androidx.compose.ui.graphics.Color 26 import androidx.core.view.ViewCompat 27 import androidx.core.view.WindowCompat 28 import androidx.core.view.WindowInsetsCompat 29 import androidx.core.view.WindowInsetsControllerCompat 30 import androidx.test.ext.junit.rules.ActivityScenarioRule 31 import androidx.test.ext.junit.runners.AndroidJUnit4 32 import androidx.test.filters.FlakyTest 33 import androidx.test.filters.SdkSuppress 34 import com.google.accompanist.internal.test.IgnoreOnRobolectric 35 import com.google.accompanist.internal.test.waitUntil 36 import com.google.accompanist.internal.test.withActivity 37 import com.google.common.truth.Truth.assertThat 38 import org.junit.Before 39 import org.junit.Rule 40 import org.junit.Test 41 import org.junit.experimental.categories.Category 42 import org.junit.runner.RunWith 43 44 @RunWith(AndroidJUnit4::class) 45 class ActivitySystemUiControllerTest { 46 @get:Rule 47 val rule = ActivityScenarioRule(ComponentActivity::class.java) 48 49 private lateinit var window: Window 50 private lateinit var contentView: View 51 52 @Before setupnull53 fun setup() { 54 window = rule.scenario.withActivity { it.window } 55 contentView = rule.scenario.withActivity { it.findViewById(android.R.id.content)!! } 56 57 if (Build.VERSION.SDK_INT >= 29) { 58 // On API 29+, the system can modify the bar colors to maintain contrast. 59 // We disable that here to make it simple to assert expected values 60 rule.scenario.onActivity { 61 window.apply { 62 isNavigationBarContrastEnforced = false 63 isStatusBarContrastEnforced = false 64 } 65 } 66 } 67 } 68 69 @Test statusBarColornull70 fun statusBarColor() { 71 rule.scenario.onActivity { 72 // Now create an AndroidSystemUiController() and set the status bar color 73 val controller = AndroidSystemUiController(contentView, window) 74 controller.setStatusBarColor(Color.Blue, darkIcons = false) 75 } 76 77 // Assert that the color was set 78 assertThat(Color(window.statusBarColor)).isEqualTo(Color.Blue) 79 } 80 81 @Test navigationBarColornull82 fun navigationBarColor() { 83 rule.scenario.onActivity { 84 // Now create an AndroidSystemUiController() and set the status bar color 85 val controller = AndroidSystemUiController(contentView, window) 86 controller.setNavigationBarColor(Color.Green, darkIcons = false) 87 } 88 89 assertThat(Color(window.navigationBarColor)).isEqualTo(Color.Green) 90 } 91 92 @Test systemBarColornull93 fun systemBarColor() { 94 // Now create an AndroidSystemUiController() and set the system bar colors 95 rule.scenario.onActivity { 96 val controller = AndroidSystemUiController(contentView, window) 97 controller.setSystemBarsColor(Color.Red, darkIcons = false) 98 } 99 100 // Assert that the colors were set 101 assertThat(Color(window.statusBarColor)).isEqualTo(Color.Red) 102 assertThat(Color(window.navigationBarColor)).isEqualTo(Color.Red) 103 } 104 105 @Test 106 @Category(IgnoreOnRobolectric::class) // Robolectric implements the new behavior from 23+ 107 @SdkSuppress(maxSdkVersion = 22) statusBarIcons_scrimnull108 fun statusBarIcons_scrim() { 109 // Now create an AndroidSystemUiController() and set the navigation bar with dark icons 110 rule.scenario.onActivity { 111 val controller = AndroidSystemUiController(contentView, window) 112 controller.setStatusBarColor(Color.White, darkIcons = true) { 113 // Here we can provide custom logic to 'darken' the color to maintain contrast. 114 // We return red just to assert below. 115 Color.Red 116 } 117 } 118 119 // Assert that the colors were set to our 'darkened' color 120 assertThat(Color(window.statusBarColor)).isEqualTo(Color.Red) 121 122 // Assert that the system couldn't apply the native light icons 123 rule.scenario.onActivity { 124 val windowInsetsController = WindowCompat.getInsetsController(window, contentView) 125 assertThat(windowInsetsController.isAppearanceLightStatusBars).isFalse() 126 } 127 } 128 129 @Test 130 @SdkSuppress(minSdkVersion = 23) statusBarIcons_nativenull131 fun statusBarIcons_native() { 132 // Now create an AndroidSystemUiController() and set the status bar with dark icons 133 rule.scenario.onActivity { 134 val controller = AndroidSystemUiController(contentView, window) 135 controller.setStatusBarColor(Color.White, darkIcons = true) { 136 // Here we can provide custom logic to 'darken' the color to maintain contrast. 137 // We return red just to assert below. 138 Color.Red 139 } 140 } 141 142 // Assert that the colors were darkened color is not used 143 assertThat(Color(window.statusBarColor)).isEqualTo(Color.White) 144 145 // Assert that the system applied the native light icons 146 rule.scenario.onActivity { 147 val windowInsetsController = WindowCompat.getInsetsController(window, contentView) 148 assertThat(windowInsetsController.isAppearanceLightStatusBars).isTrue() 149 } 150 } 151 152 @Test 153 @Category(IgnoreOnRobolectric::class) // Robolectric implements the new behavior from 25+ 154 @SdkSuppress(maxSdkVersion = 25) navigationBarIcons_scrimnull155 fun navigationBarIcons_scrim() { 156 // Now create an AndroidSystemUiController() and set the navigation bar with dark icons 157 rule.scenario.onActivity { 158 val controller = AndroidSystemUiController(contentView, window) 159 controller.setNavigationBarColor(Color.White, darkIcons = true) { 160 // Here we can provide custom logic to 'darken' the color to maintain contrast. 161 // We return red just to assert below. 162 Color.Red 163 } 164 } 165 166 // Assert that the colors were set to our 'darkened' color 167 assertThat(Color(window.navigationBarColor)).isEqualTo(Color.Red) 168 169 // Assert that the system couldn't apply the native light icons 170 rule.scenario.onActivity { 171 val windowInsetsController = WindowCompat.getInsetsController(window, contentView) 172 assertThat(windowInsetsController.isAppearanceLightNavigationBars).isFalse() 173 } 174 } 175 176 @Test 177 @SdkSuppress(minSdkVersion = 26) navigationBar_nativenull178 fun navigationBar_native() { 179 // Now create an AndroidSystemUiController() and set the navigation bar with dark icons 180 rule.scenario.onActivity { 181 val controller = AndroidSystemUiController(contentView, window) 182 controller.setNavigationBarColor(Color.White, darkIcons = true) { 183 // Here we can provide custom logic to 'darken' the color to maintain contrast. 184 // We return red just to assert below. 185 Color.Red 186 } 187 } 188 189 // Assert that the colors were darkened color is not used 190 assertThat(Color(window.navigationBarColor)).isEqualTo(Color.White) 191 192 // Assert that the system applied the native light icons 193 rule.scenario.onActivity { 194 val windowInsetsController = WindowCompat.getInsetsController(window, contentView) 195 assertThat(windowInsetsController.isAppearanceLightNavigationBars).isTrue() 196 } 197 } 198 199 @Test 200 @SdkSuppress(minSdkVersion = 29) navigationBar_contrastEnforcednull201 fun navigationBar_contrastEnforced() { 202 rule.scenario.onActivity { 203 // Now create an AndroidSystemUiController() 204 val controller = AndroidSystemUiController(contentView, window) 205 206 // Assert that the contrast is not enforced initially 207 assertThat(controller.isNavigationBarContrastEnforced).isFalse() 208 209 // and set the navigation bar with dark icons and enforce contrast 210 controller.setNavigationBarColor( 211 Color.Transparent, 212 darkIcons = true, 213 navigationBarContrastEnforced = true 214 ) { 215 // Here we can provide custom logic to 'darken' the color to maintain contrast. 216 // We return red just to assert below. 217 Color.Red 218 } 219 220 // Assert that the colors were darkened color is not used 221 assertThat(Color(window.navigationBarColor)).isEqualTo(Color.Transparent) 222 223 // Assert that the system applied the contrast enforced property 224 assertThat(window.isNavigationBarContrastEnforced).isTrue() 225 226 // Assert that the controller reflects that the contrast is enforced 227 assertThat(controller.isNavigationBarContrastEnforced).isTrue() 228 } 229 } 230 231 @Suppress("DEPRECATION") 232 @Test 233 @SdkSuppress(minSdkVersion = 30) // TODO: https://issuetracker.google.com/issues/189366125 systemBarsBehavior_showBarsByTouchnull234 fun systemBarsBehavior_showBarsByTouch() { 235 val controller = rule.scenario.withActivity { 236 AndroidSystemUiController(contentView, window) 237 } 238 239 rule.scenario.onActivity { 240 controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_BARS_BY_TOUCH 241 } 242 243 assertThat(WindowCompat.getInsetsController(window, contentView).systemBarsBehavior) 244 .isEqualTo(WindowInsetsControllerCompat.BEHAVIOR_SHOW_BARS_BY_TOUCH) 245 } 246 247 @Test 248 @SdkSuppress(minSdkVersion = 30) // TODO: https://issuetracker.google.com/issues/189366125 systemBarsBehavior_showBarsBySwipenull249 fun systemBarsBehavior_showBarsBySwipe() { 250 val controller = rule.scenario.withActivity { 251 AndroidSystemUiController(contentView, window) 252 } 253 254 rule.scenario.onActivity { 255 controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_DEFAULT 256 } 257 258 assertThat(WindowCompat.getInsetsController(window, contentView).systemBarsBehavior) 259 .isEqualTo(WindowInsetsControllerCompat.BEHAVIOR_DEFAULT) 260 } 261 262 @Test 263 @SdkSuppress(minSdkVersion = 30) // TODO: https://issuetracker.google.com/issues/189366125 systemBarsBehavior_showTransientBarsBySwipenull264 fun systemBarsBehavior_showTransientBarsBySwipe() { 265 val controller = rule.scenario.withActivity { 266 AndroidSystemUiController(contentView, window) 267 } 268 269 rule.scenario.onActivity { 270 controller.systemBarsBehavior = 271 WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE 272 } 273 274 assertThat(WindowCompat.getInsetsController(window, contentView).systemBarsBehavior) 275 .isEqualTo(WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE) 276 } 277 278 @Test 279 @FlakyTest(detail = "https://github.com/google/accompanist/issues/491") 280 @SdkSuppress(minSdkVersion = 23) // rootWindowInsets which work 281 @Category(IgnoreOnRobolectric::class) statusBarsVisibilitynull282 fun statusBarsVisibility() { 283 // Now create an AndroidSystemUiController() and set the system bar colors 284 val controller = rule.scenario.withActivity { 285 AndroidSystemUiController(contentView, window) 286 } 287 288 // First show the bars 289 rule.scenario.onActivity { 290 controller.isStatusBarVisible = true 291 } 292 waitUntil { isRootWindowTypeVisible(WindowInsetsCompat.Type.statusBars()) } 293 294 // Now hide the bars 295 rule.scenario.onActivity { 296 controller.isStatusBarVisible = false 297 } 298 waitUntil { !isRootWindowTypeVisible(WindowInsetsCompat.Type.statusBars()) } 299 } 300 301 @Test 302 @FlakyTest(detail = "https://github.com/google/accompanist/issues/491") 303 @SdkSuppress(minSdkVersion = 23) // rootWindowInsets which work 304 @Category(IgnoreOnRobolectric::class) navigationBarsVisibilitynull305 fun navigationBarsVisibility() { 306 // Now create an AndroidSystemUiController() and set the system bar colors 307 val controller = rule.scenario.withActivity { 308 AndroidSystemUiController(contentView, window) 309 } 310 311 // First show the bars 312 rule.scenario.onActivity { 313 controller.isNavigationBarVisible = true 314 } 315 waitUntil { isRootWindowTypeVisible(WindowInsetsCompat.Type.navigationBars()) } 316 317 // Now hide the bars 318 rule.scenario.onActivity { 319 controller.isNavigationBarVisible = false 320 } 321 waitUntil { !isRootWindowTypeVisible(WindowInsetsCompat.Type.navigationBars()) } 322 } 323 324 @Test 325 @Category(IgnoreOnRobolectric::class) 326 @FlakyTest(detail = "https://github.com/google/accompanist/issues/491") 327 @SdkSuppress(minSdkVersion = 23) // rootWindowInsets which work systemBarsVisibilitynull328 fun systemBarsVisibility() { 329 // Now create an AndroidSystemUiController() and set the system bar colors 330 val controller = rule.scenario.withActivity { 331 AndroidSystemUiController(contentView, window) 332 } 333 334 // First show the bars 335 rule.scenario.onActivity { 336 controller.isSystemBarsVisible = true 337 } 338 waitUntil { isRootWindowTypeVisible(WindowInsetsCompat.Type.navigationBars()) } 339 waitUntil { isRootWindowTypeVisible(WindowInsetsCompat.Type.statusBars()) } 340 341 // Now hide the bars 342 rule.scenario.onActivity { 343 controller.isSystemBarsVisible = false 344 } 345 waitUntil { !isRootWindowTypeVisible(WindowInsetsCompat.Type.navigationBars()) } 346 waitUntil { !isRootWindowTypeVisible(WindowInsetsCompat.Type.statusBars()) } 347 } 348 isRootWindowTypeVisiblenull349 private fun isRootWindowTypeVisible(type: Int): Boolean { 350 return rule.scenario.withActivity { 351 ViewCompat.getRootWindowInsets(contentView)!!.isVisible(type) 352 } 353 } 354 } 355