1 /* 2 * Copyright 2020 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.core.view; 18 19 import static android.os.Build.VERSION.SDK_INT; 20 21 import android.annotation.SuppressLint; 22 import android.inputmethodservice.InputMethodService; 23 import android.os.CancellationSignal; 24 import android.view.View; 25 import android.view.Window; 26 import android.view.WindowInsets; 27 import android.view.WindowInsetsAnimationControlListener; 28 import android.view.WindowInsetsAnimationController; 29 import android.view.WindowInsetsController; 30 import android.view.WindowManager; 31 import android.view.animation.Interpolator; 32 33 import androidx.annotation.IntDef; 34 import androidx.annotation.RequiresApi; 35 import androidx.annotation.RestrictTo; 36 import androidx.collection.SimpleArrayMap; 37 import androidx.core.graphics.Insets; 38 import androidx.core.view.WindowInsetsCompat.Type.InsetsType; 39 40 import org.jspecify.annotations.NonNull; 41 import org.jspecify.annotations.Nullable; 42 43 import java.lang.annotation.Retention; 44 import java.lang.annotation.RetentionPolicy; 45 import java.util.concurrent.TimeUnit; 46 47 /** 48 * Provide simple controls of windows that generate insets. 49 * 50 * For SDKs >= 30, this class is a simple wrapper around {@link WindowInsetsController}. For 51 * lower SDKs, this class aims to behave as close as possible to the original implementation. 52 */ 53 public final class WindowInsetsControllerCompat { 54 55 /** 56 * Option for {@link #setSystemBarsBehavior(int)}. System bars will be forcibly shown on any 57 * user interaction on the corresponding display if navigation bars are hidden by 58 * {@link #hide(int)} or 59 * {@link WindowInsetsAnimationControllerCompat#setInsetsAndAlpha(Insets, float, float)}. 60 * 61 * @deprecated This is not supported on Android {@link android.os.Build.VERSION_CODES#S} and 62 * later. Use {@link #BEHAVIOR_DEFAULT} or {@link #BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE} 63 * instead. 64 */ 65 @Deprecated 66 public static final int BEHAVIOR_SHOW_BARS_BY_TOUCH = 0; 67 68 /** 69 * The default option for {@link #setSystemBarsBehavior(int)}: Window would like to remain 70 * interactive when hiding navigation bars by calling {@link #hide(int)} or 71 * {@link WindowInsetsAnimationControllerCompat#setInsetsAndAlpha(Insets, float, float)}. 72 * 73 * <p>When system bars are hidden in this mode, they can be revealed with system gestures, such 74 * as swiping from the edge of the screen where the bar is hidden from.</p> 75 * 76 * <p>When the gesture navigation is enabled, the system gestures can be triggered regardless 77 * the visibility of system bars.</p> 78 */ 79 public static final int BEHAVIOR_DEFAULT = 1; 80 81 /** 82 * Option for {@link #setSystemBarsBehavior(int)}: Window would like to remain interactive 83 * when hiding navigation bars by calling {@link #hide(int)} or 84 * {@link WindowInsetsAnimationControllerCompat#setInsetsAndAlpha(Insets, float, float)}. 85 * <p> 86 * When system bars are hidden in this mode, they can be revealed with system 87 * gestures, such as swiping from the edge of the screen where the bar is hidden from. 88 * 89 * @deprecated Use {@link #BEHAVIOR_DEFAULT} instead. 90 */ 91 @Deprecated 92 public static final int BEHAVIOR_SHOW_BARS_BY_SWIPE = BEHAVIOR_DEFAULT; 93 94 /** 95 * Option for {@link #setSystemBarsBehavior(int)}: Window would like to remain 96 * interactive when hiding navigation bars by calling {@link #hide(int)} or 97 * {@link WindowInsetsAnimationControllerCompat#setInsetsAndAlpha(Insets, float, float)}. 98 * <p> 99 * When system bars are hidden in this mode, they can be revealed temporarily with system 100 * gestures, such as swiping from the edge of the screen where the bar is hidden from. These 101 * transient system bars will overlay app’s content, may have some degree of 102 * transparency, and will automatically hide after a short timeout. 103 */ 104 public static final int BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE = 2; 105 106 private final Impl mImpl; 107 108 /** 109 * This version fails to workaround 110 * <a href="https://issuetracker.google.com/issues/180881870"> 111 * https://issuetracker.google.com/issues/180881870 112 * </a>, but is present for backwards compatibility. 113 */ 114 @RequiresApi(30) 115 @Deprecated WindowInsetsControllerCompat(@onNull WindowInsetsController insetsController)116 private WindowInsetsControllerCompat(@NonNull WindowInsetsController insetsController) { 117 if (SDK_INT >= 35) { 118 mImpl = new Impl35(insetsController, 119 this, 120 new SoftwareKeyboardControllerCompat(insetsController)); 121 } else { 122 mImpl = new Impl30(insetsController, 123 this, 124 new SoftwareKeyboardControllerCompat(insetsController)); 125 } 126 } 127 WindowInsetsControllerCompat(@onNull Window window, @NonNull View view)128 public WindowInsetsControllerCompat(@NonNull Window window, @NonNull View view) { 129 SoftwareKeyboardControllerCompat softwareKeyboardControllerCompat = 130 new SoftwareKeyboardControllerCompat(view); 131 if (SDK_INT >= 35) { 132 mImpl = new Impl35(window, this, softwareKeyboardControllerCompat); 133 } else if (SDK_INT >= 30) { 134 mImpl = new Impl30(window, this, softwareKeyboardControllerCompat); 135 } else if (SDK_INT >= 26) { 136 mImpl = new Impl26(window, softwareKeyboardControllerCompat); 137 } else if (SDK_INT >= 23) { 138 mImpl = new Impl23(window, softwareKeyboardControllerCompat); 139 } else if (SDK_INT >= 20) { 140 mImpl = new Impl20(window, softwareKeyboardControllerCompat); 141 } else { 142 mImpl = new Impl(); 143 } 144 } 145 146 /** 147 * Wrap a {@link WindowInsetsController} into a {@link WindowInsetsControllerCompat} for 148 * compatibility purpose. 149 * 150 * @param insetsController The {@link WindowInsetsController} to wrap. 151 * @return The provided {@link WindowInsetsController} wrapped into a 152 * {@link WindowInsetsControllerCompat} 153 * @deprecated Use {@link WindowCompat#getInsetsController(Window, View)} instead 154 */ 155 @RequiresApi(30) 156 @Deprecated toWindowInsetsControllerCompat( @onNull WindowInsetsController insetsController)157 public static @NonNull WindowInsetsControllerCompat toWindowInsetsControllerCompat( 158 @NonNull WindowInsetsController insetsController) { 159 return new WindowInsetsControllerCompat(insetsController); 160 } 161 162 /** 163 * Determines the behavior of system bars when hiding them by calling {@link #hide}. 164 * 165 */ 166 @RestrictTo(RestrictTo.Scope.LIBRARY) 167 @Retention(RetentionPolicy.SOURCE) 168 @IntDef(value = {BEHAVIOR_DEFAULT, BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE}) 169 @interface Behavior { 170 } 171 172 /** 173 * Makes a set of windows that cause insets appear on screen. 174 * <p> 175 * Note that if the window currently doesn't have control over a certain type, it will apply the 176 * change as soon as the window gains control. The app can listen to the event by observing 177 * {@link View#onApplyWindowInsets} and checking visibility with {@link WindowInsets#isVisible}. 178 * 179 * @param types A bitmask of {@link WindowInsetsCompat.Type} specifying what windows the app 180 * would like to make appear on screen. 181 */ show(@nsetsType int types)182 public void show(@InsetsType int types) { 183 mImpl.show(types); 184 } 185 186 /** 187 * Makes a set of windows causing insets disappear. 188 * <p> 189 * Note that if the window currently doesn't have control over a certain type, it will apply the 190 * change as soon as the window gains control. The app can listen to the event by observing 191 * {@link View#onApplyWindowInsets} and checking visibility with {@link WindowInsets#isVisible}. 192 * 193 * @param types A bitmask of {@link WindowInsetsCompat.Type} specifying what windows the app 194 * would like to make disappear. 195 */ hide(@nsetsType int types)196 public void hide(@InsetsType int types) { 197 mImpl.hide(types); 198 } 199 200 201 /** 202 * Checks if the foreground of the status bar is set to light. 203 * <p> 204 * This method always returns false on API < 23. 205 * <p> 206 * If this value is being set in the theme (via {@link android.R.attr#windowLightStatusBar}), 207 * then the correct value will only be returned once attached to the window. 208 * <p> 209 * Once this method is called, modifying `systemUiVisibility` directly to change the 210 * appearance is undefined behavior. 211 * 212 * @return true if the foreground is light 213 * @see #setAppearanceLightStatusBars(boolean) 214 */ isAppearanceLightStatusBars()215 public boolean isAppearanceLightStatusBars() { 216 return mImpl.isAppearanceLightStatusBars(); 217 } 218 219 /** 220 * If true, changes the foreground color of the status bars to light so that the items on the 221 * bar can be read clearly. If false, reverts to the default appearance. 222 * <p> 223 * This method has no effect on API < 23. 224 * <p> 225 * Once this method is called, modifying `systemUiVisibility` directly to change the 226 * appearance is undefined behavior. 227 * 228 * @see #isAppearanceLightStatusBars() 229 */ setAppearanceLightStatusBars(boolean isLight)230 public void setAppearanceLightStatusBars(boolean isLight) { 231 mImpl.setAppearanceLightStatusBars(isLight); 232 } 233 234 /** 235 * Checks if the foreground of the navigation bar is set to light. 236 * <p> 237 * This method always returns false on API < 26. 238 * <p> 239 * If this value is being set in the theme (via 240 * {@link android.R.attr#windowLightNavigationBar}), 241 * then the correct value will only be returned once attached to the window. 242 * <p> 243 * Once this method is called, modifying `systemUiVisibility` directly to change the 244 * appearance is undefined behavior. 245 * 246 * @return true if the foreground is light 247 * @see #setAppearanceLightNavigationBars(boolean) 248 */ isAppearanceLightNavigationBars()249 public boolean isAppearanceLightNavigationBars() { 250 return mImpl.isAppearanceLightNavigationBars(); 251 } 252 253 /** 254 * If true, changes the foreground color of the navigation bars to light so that the items on 255 * the bar can be read clearly. If false, reverts to the default appearance. 256 * <p> 257 * This method has no effect on API < 26. 258 * <p> 259 * Once this method is called, modifying `systemUiVisibility` directly to change the 260 * appearance is undefined behavior. 261 * 262 * @see #isAppearanceLightNavigationBars() 263 */ setAppearanceLightNavigationBars(boolean isLight)264 public void setAppearanceLightNavigationBars(boolean isLight) { 265 mImpl.setAppearanceLightNavigationBars(isLight); 266 } 267 268 /** 269 * Lets the application control window inset animations in a frame-by-frame manner by 270 * modifying the position of the windows in the system causing insets directly using 271 * {@link WindowInsetsAnimationControllerCompat#setInsetsAndAlpha} in the controller provided 272 * by the given listener. 273 * <p> 274 * This method only works on API >= 30 since there is no way to control the window in the 275 * system on prior APIs. 276 * 277 * @param types The {@link WindowInsetsCompat.Type}s the application has 278 * requested to control. 279 * @param durationMillis Duration of animation in {@link TimeUnit#MILLISECONDS}, or -1 if 280 * the animation doesn't have a predetermined duration. This value 281 * will be passed to 282 * {@link WindowInsetsAnimationCompat#getDurationMillis()} 283 * @param interpolator The interpolator used for this animation, or {@code null } if 284 * this animation doesn't follow an interpolation curve. This value 285 * will be passed to 286 * {@link WindowInsetsAnimationCompat#getInterpolator()} and used 287 * to calculate 288 * {@link WindowInsetsAnimationCompat#getInterpolatedFraction()}. 289 * @param cancellationSignal A cancellation signal that the caller can use to cancel the 290 * request to obtain control, or once they have control, to cancel 291 * the control. 292 * @param listener The {@link WindowInsetsAnimationControlListener} that gets 293 * called when the windows are ready to be controlled, among other 294 * callbacks. 295 * @see WindowInsetsAnimationCompat#getFraction() 296 * @see WindowInsetsAnimationCompat#getInterpolatedFraction() 297 * @see WindowInsetsAnimationCompat#getInterpolator() 298 * @see WindowInsetsAnimationCompat#getDurationMillis() 299 */ controlWindowInsetsAnimation(@nsetsType int types, long durationMillis, @Nullable Interpolator interpolator, @Nullable CancellationSignal cancellationSignal, @NonNull WindowInsetsAnimationControlListenerCompat listener)300 public void controlWindowInsetsAnimation(@InsetsType int types, long durationMillis, 301 @Nullable Interpolator interpolator, 302 @Nullable CancellationSignal cancellationSignal, 303 @NonNull WindowInsetsAnimationControlListenerCompat listener) { 304 mImpl.controlWindowInsetsAnimation(types, 305 durationMillis, 306 interpolator, 307 cancellationSignal, 308 listener); 309 } 310 311 /** 312 * Controls the behavior of system bars. 313 * 314 * @param behavior Determines how the bars behave when being hidden by the application. 315 * @see #getSystemBarsBehavior 316 */ setSystemBarsBehavior(@ehavior int behavior)317 public void setSystemBarsBehavior(@Behavior int behavior) { 318 mImpl.setSystemBarsBehavior(behavior); 319 } 320 321 /** 322 * Retrieves the requested behavior of system bars. 323 * 324 * @return the system bar behavior controlled by this window. 325 * @see #setSystemBarsBehavior(int) 326 */ 327 @SuppressLint("WrongConstant") 328 @Behavior getSystemBarsBehavior()329 public int getSystemBarsBehavior() { 330 return mImpl.getSystemBarsBehavior(); 331 } 332 333 /** 334 * Adds a {@link WindowInsetsController.OnControllableInsetsChangedListener} to the window 335 * insets controller. 336 * 337 * @param listener The listener to add. 338 * @see WindowInsetsControllerCompat.OnControllableInsetsChangedListener 339 * @see #removeOnControllableInsetsChangedListener( 340 *WindowInsetsControllerCompat.OnControllableInsetsChangedListener) 341 */ addOnControllableInsetsChangedListener( WindowInsetsControllerCompat.@onNull OnControllableInsetsChangedListener listener)342 public void addOnControllableInsetsChangedListener( 343 WindowInsetsControllerCompat.@NonNull OnControllableInsetsChangedListener listener) { 344 mImpl.addOnControllableInsetsChangedListener(listener); 345 } 346 347 /** 348 * Removes a {@link WindowInsetsController.OnControllableInsetsChangedListener} from the 349 * window insets controller. 350 * 351 * @param listener The listener to remove. 352 * @see WindowInsetsControllerCompat.OnControllableInsetsChangedListener 353 * @see #addOnControllableInsetsChangedListener( 354 *WindowInsetsControllerCompat.OnControllableInsetsChangedListener) 355 */ removeOnControllableInsetsChangedListener( WindowInsetsControllerCompat.@onNull OnControllableInsetsChangedListener listener)356 public void removeOnControllableInsetsChangedListener( 357 WindowInsetsControllerCompat.@NonNull OnControllableInsetsChangedListener 358 listener) { 359 mImpl.removeOnControllableInsetsChangedListener(listener); 360 } 361 362 /** 363 * Listener to be notified when the set of controllable {@link WindowInsetsCompat.Type} 364 * controlled by a {@link WindowInsetsController} changes. 365 * <p> 366 * Once a {@link WindowInsetsCompat.Type} becomes controllable, the app will be able to 367 * control the window that is causing this type of insets by calling 368 * {@link #controlWindowInsetsAnimation}. 369 * <p> 370 * Note: When listening to cancellability of the {@link WindowInsets.Type#ime}, 371 * {@link #controlWindowInsetsAnimation} may still fail in case the {@link InputMethodService} 372 * decides to cancel the show request. This could happen when there is a hardware keyboard 373 * attached. 374 * 375 * @see #addOnControllableInsetsChangedListener( 376 *WindowInsetsControllerCompat.OnControllableInsetsChangedListener) 377 * @see #removeOnControllableInsetsChangedListener( 378 *WindowInsetsControllerCompat.OnControllableInsetsChangedListener) 379 */ 380 public interface OnControllableInsetsChangedListener { 381 382 /** 383 * Called when the set of controllable {@link WindowInsetsCompat.Type} changes. 384 * 385 * @param controller The controller for which the set of controllable 386 * {@link WindowInsetsCompat.Type}s 387 * are changing. 388 * @param typeMask Bitwise behavior type-mask of the {@link WindowInsetsCompat.Type}s 389 * the controller is currently able to control. 390 */ onControllableInsetsChanged(@onNull WindowInsetsControllerCompat controller, @InsetsType int typeMask)391 void onControllableInsetsChanged(@NonNull WindowInsetsControllerCompat controller, 392 @InsetsType int typeMask); 393 } 394 395 private static class Impl { 396 static final int KEY_BEHAVIOR = 356039078; 397 Impl()398 Impl() { 399 //private 400 } 401 show(int types)402 void show(int types) { 403 } 404 hide(int types)405 void hide(int types) { 406 } 407 controlWindowInsetsAnimation(int types, long durationMillis, Interpolator interpolator, CancellationSignal cancellationSignal, WindowInsetsAnimationControlListenerCompat listener)408 void controlWindowInsetsAnimation(int types, long durationMillis, 409 Interpolator interpolator, CancellationSignal cancellationSignal, 410 WindowInsetsAnimationControlListenerCompat listener) { 411 } 412 setSystemBarsBehavior(int behavior)413 void setSystemBarsBehavior(int behavior) { 414 } 415 getSystemBarsBehavior()416 int getSystemBarsBehavior() { 417 return BEHAVIOR_DEFAULT; 418 } 419 isAppearanceLightStatusBars()420 public boolean isAppearanceLightStatusBars() { 421 return false; 422 } 423 setAppearanceLightStatusBars(boolean isLight)424 public void setAppearanceLightStatusBars(boolean isLight) { 425 } 426 isAppearanceLightNavigationBars()427 public boolean isAppearanceLightNavigationBars() { 428 return false; 429 } 430 setAppearanceLightNavigationBars(boolean isLight)431 public void setAppearanceLightNavigationBars(boolean isLight) { 432 } 433 addOnControllableInsetsChangedListener( WindowInsetsControllerCompat.OnControllableInsetsChangedListener listener)434 void addOnControllableInsetsChangedListener( 435 WindowInsetsControllerCompat.OnControllableInsetsChangedListener listener) { 436 } 437 removeOnControllableInsetsChangedListener( WindowInsetsControllerCompat.@onNull OnControllableInsetsChangedListener listener)438 void removeOnControllableInsetsChangedListener( 439 WindowInsetsControllerCompat.@NonNull OnControllableInsetsChangedListener 440 listener) { 441 } 442 } 443 444 @RequiresApi(20) 445 private static class Impl20 extends Impl { 446 447 protected final @NonNull Window mWindow; 448 449 private final @NonNull SoftwareKeyboardControllerCompat mSoftwareKeyboardControllerCompat; 450 Impl20(@onNull Window window, @NonNull SoftwareKeyboardControllerCompat softwareKeyboardControllerCompat)451 Impl20(@NonNull Window window, 452 @NonNull SoftwareKeyboardControllerCompat softwareKeyboardControllerCompat) { 453 mWindow = window; 454 mSoftwareKeyboardControllerCompat = softwareKeyboardControllerCompat; 455 } 456 457 @Override show(int typeMask)458 void show(int typeMask) { 459 for (int i = WindowInsetsCompat.Type.FIRST; i <= WindowInsetsCompat.Type.LAST; 460 i = i << 1) { 461 if ((typeMask & i) == 0) { 462 continue; 463 } 464 showForType(i); 465 } 466 } 467 showForType(int type)468 private void showForType(int type) { 469 switch (type) { 470 case WindowInsetsCompat.Type.STATUS_BARS: 471 unsetSystemUiFlag(View.SYSTEM_UI_FLAG_FULLSCREEN); 472 unsetWindowFlag(WindowManager.LayoutParams.FLAG_FULLSCREEN); 473 return; 474 case WindowInsetsCompat.Type.NAVIGATION_BARS: 475 unsetSystemUiFlag(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); 476 return; 477 case WindowInsetsCompat.Type.IME: 478 mSoftwareKeyboardControllerCompat.show(); 479 } 480 } 481 482 @Override hide(int typeMask)483 void hide(int typeMask) { 484 for (int i = WindowInsetsCompat.Type.FIRST; i <= WindowInsetsCompat.Type.LAST; 485 i = i << 1) { 486 if ((typeMask & i) == 0) { 487 continue; 488 } 489 hideForType(i); 490 } 491 } 492 hideForType(int type)493 private void hideForType(int type) { 494 switch (type) { 495 case WindowInsetsCompat.Type.STATUS_BARS: 496 setSystemUiFlag(View.SYSTEM_UI_FLAG_FULLSCREEN); 497 return; 498 case WindowInsetsCompat.Type.NAVIGATION_BARS: 499 setSystemUiFlag(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); 500 return; 501 case WindowInsetsCompat.Type.IME: 502 mSoftwareKeyboardControllerCompat.hide(); 503 } 504 } 505 setSystemUiFlag(int systemUiFlag)506 protected void setSystemUiFlag(int systemUiFlag) { 507 View decorView = mWindow.getDecorView(); 508 decorView.setSystemUiVisibility( 509 decorView.getSystemUiVisibility() 510 | systemUiFlag); 511 } 512 unsetSystemUiFlag(int systemUiFlag)513 protected void unsetSystemUiFlag(int systemUiFlag) { 514 View decorView = mWindow.getDecorView(); 515 decorView.setSystemUiVisibility( 516 decorView.getSystemUiVisibility() 517 & ~systemUiFlag); 518 } 519 setWindowFlag(int windowFlag)520 protected void setWindowFlag(int windowFlag) { 521 mWindow.addFlags(windowFlag); 522 } 523 unsetWindowFlag(int windowFlag)524 protected void unsetWindowFlag(int windowFlag) { 525 mWindow.clearFlags(windowFlag); 526 } 527 528 @Override controlWindowInsetsAnimation(int types, long durationMillis, Interpolator interpolator, CancellationSignal cancellationSignal, WindowInsetsAnimationControlListenerCompat listener)529 void controlWindowInsetsAnimation(int types, long durationMillis, 530 Interpolator interpolator, CancellationSignal cancellationSignal, 531 WindowInsetsAnimationControlListenerCompat listener) { 532 } 533 534 @Override setSystemBarsBehavior(int behavior)535 void setSystemBarsBehavior(int behavior) { 536 mWindow.getDecorView().setTag(KEY_BEHAVIOR, behavior); 537 switch (behavior) { 538 case BEHAVIOR_DEFAULT: 539 unsetSystemUiFlag(View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); 540 setSystemUiFlag(View.SYSTEM_UI_FLAG_IMMERSIVE); 541 break; 542 case BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE: 543 unsetSystemUiFlag(View.SYSTEM_UI_FLAG_IMMERSIVE); 544 setSystemUiFlag(View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); 545 break; 546 case BEHAVIOR_SHOW_BARS_BY_TOUCH: 547 unsetSystemUiFlag(View.SYSTEM_UI_FLAG_IMMERSIVE 548 | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); 549 break; 550 } 551 } 552 553 @Override getSystemBarsBehavior()554 int getSystemBarsBehavior() { 555 final Object behaviorTag = mWindow.getDecorView().getTag(KEY_BEHAVIOR); 556 return behaviorTag != null ? (int) behaviorTag : BEHAVIOR_DEFAULT; 557 } 558 559 @Override addOnControllableInsetsChangedListener( WindowInsetsControllerCompat.OnControllableInsetsChangedListener listener)560 void addOnControllableInsetsChangedListener( 561 WindowInsetsControllerCompat.OnControllableInsetsChangedListener listener) { 562 } 563 564 @Override removeOnControllableInsetsChangedListener( WindowInsetsControllerCompat.@onNull OnControllableInsetsChangedListener listener)565 void removeOnControllableInsetsChangedListener( 566 WindowInsetsControllerCompat.@NonNull OnControllableInsetsChangedListener 567 listener) { 568 } 569 } 570 571 @RequiresApi(23) 572 private static class Impl23 extends Impl20 { 573 Impl23(@onNull Window window, @NonNull SoftwareKeyboardControllerCompat softwareKeyboardControllerCompat)574 Impl23(@NonNull Window window, 575 @NonNull SoftwareKeyboardControllerCompat softwareKeyboardControllerCompat) { 576 super(window, softwareKeyboardControllerCompat); 577 } 578 579 @Override isAppearanceLightStatusBars()580 public boolean isAppearanceLightStatusBars() { 581 return (mWindow.getDecorView().getSystemUiVisibility() 582 & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0; 583 } 584 585 @Override setAppearanceLightStatusBars(boolean isLight)586 public void setAppearanceLightStatusBars(boolean isLight) { 587 if (isLight) { 588 unsetWindowFlag(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 589 setWindowFlag(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 590 setSystemUiFlag(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); 591 } else { 592 unsetSystemUiFlag(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); 593 } 594 } 595 } 596 597 @RequiresApi(26) 598 private static class Impl26 extends Impl23 { 599 Impl26(@onNull Window window, @NonNull SoftwareKeyboardControllerCompat softwareKeyboardControllerCompat)600 Impl26(@NonNull Window window, 601 @NonNull SoftwareKeyboardControllerCompat softwareKeyboardControllerCompat) { 602 super(window, softwareKeyboardControllerCompat); 603 } 604 605 @Override isAppearanceLightNavigationBars()606 public boolean isAppearanceLightNavigationBars() { 607 return (mWindow.getDecorView().getSystemUiVisibility() 608 & View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) != 0; 609 } 610 611 @Override setAppearanceLightNavigationBars(boolean isLight)612 public void setAppearanceLightNavigationBars(boolean isLight) { 613 if (isLight) { 614 unsetWindowFlag(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); 615 setWindowFlag(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 616 setSystemUiFlag(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR); 617 } else { 618 unsetSystemUiFlag(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR); 619 } 620 } 621 } 622 623 @RequiresApi(30) 624 private static class Impl30 extends Impl { 625 626 final WindowInsetsControllerCompat mCompatController; 627 final WindowInsetsController mInsetsController; 628 final SoftwareKeyboardControllerCompat mSoftwareKeyboardControllerCompat; 629 private final SimpleArrayMap< 630 WindowInsetsControllerCompat.OnControllableInsetsChangedListener, 631 WindowInsetsController.OnControllableInsetsChangedListener> 632 mListeners = new SimpleArrayMap<>(); 633 634 protected Window mWindow; 635 Impl30(@onNull Window window, @NonNull WindowInsetsControllerCompat compatController, @NonNull SoftwareKeyboardControllerCompat softwareKeyboardControllerCompat)636 Impl30(@NonNull Window window, 637 @NonNull WindowInsetsControllerCompat compatController, 638 @NonNull SoftwareKeyboardControllerCompat softwareKeyboardControllerCompat) { 639 this(window.getInsetsController(), compatController, softwareKeyboardControllerCompat); 640 mWindow = window; 641 } 642 Impl30(@onNull WindowInsetsController insetsController, @NonNull WindowInsetsControllerCompat compatController, @NonNull SoftwareKeyboardControllerCompat softwareKeyboardControllerCompat)643 Impl30(@NonNull WindowInsetsController insetsController, 644 @NonNull WindowInsetsControllerCompat compatController, 645 @NonNull SoftwareKeyboardControllerCompat softwareKeyboardControllerCompat) { 646 mInsetsController = insetsController; 647 mCompatController = compatController; 648 mSoftwareKeyboardControllerCompat = softwareKeyboardControllerCompat; 649 } 650 651 @Override show(@nsetsType int types)652 void show(@InsetsType int types) { 653 if ((types & WindowInsetsCompat.Type.IME) != 0) { 654 mSoftwareKeyboardControllerCompat.show(); 655 } 656 mInsetsController.show(types & ~WindowInsetsCompat.Type.IME); 657 } 658 659 @Override hide(@nsetsType int types)660 void hide(@InsetsType int types) { 661 if ((types & WindowInsetsCompat.Type.IME) != 0) { 662 mSoftwareKeyboardControllerCompat.hide(); 663 } 664 mInsetsController.hide(types & ~WindowInsetsCompat.Type.IME); 665 } 666 667 @Override isAppearanceLightStatusBars()668 public boolean isAppearanceLightStatusBars() { 669 // This is a side-effectful workaround 670 // Because the mask is zero, this won't change the system bar appearance 671 // However, it "unlocks" reading the effective system bar appearance in the following 672 // call. Without this being "unlocked," the system bar appearance will always return 673 // nothing, even if it has been set in the theme or by the system ui flags before 674 // querying for it. 675 mInsetsController.setSystemBarsAppearance(0, 0); 676 return (mInsetsController.getSystemBarsAppearance() 677 & WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS) != 0; 678 } 679 680 @Override setAppearanceLightStatusBars(boolean isLight)681 public void setAppearanceLightStatusBars(boolean isLight) { 682 if (isLight) { 683 if (mWindow != null) { 684 setSystemUiFlag(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); 685 } 686 687 mInsetsController.setSystemBarsAppearance( 688 WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS, 689 WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS); 690 } else { 691 if (mWindow != null) { 692 unsetSystemUiFlag(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); 693 } 694 695 mInsetsController.setSystemBarsAppearance( 696 0, 697 WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS); 698 } 699 } 700 701 @Override isAppearanceLightNavigationBars()702 public boolean isAppearanceLightNavigationBars() { 703 // This is a side-effectful workaround 704 // Because the mask is zero, this won't change the system bar appearance 705 // However, it "unlocks" reading the effective system bar appearance in the following 706 // call. Without this being "unlocked," the system bar appearance will always return 707 // nothing, even if it has been set in the theme or by the system ui flags before 708 // querying for it. 709 mInsetsController.setSystemBarsAppearance(0, 0); 710 return (mInsetsController.getSystemBarsAppearance() 711 & WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS) != 0; 712 } 713 714 @Override setAppearanceLightNavigationBars(boolean isLight)715 public void setAppearanceLightNavigationBars(boolean isLight) { 716 if (isLight) { 717 if (mWindow != null) { 718 setSystemUiFlag(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR); 719 } 720 721 mInsetsController.setSystemBarsAppearance( 722 WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS, 723 WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS); 724 } else { 725 if (mWindow != null) { 726 unsetSystemUiFlag(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR); 727 } 728 729 mInsetsController.setSystemBarsAppearance( 730 0, 731 WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS); 732 } 733 } 734 735 @Override controlWindowInsetsAnimation(@nsetsType int types, long durationMillis, @Nullable Interpolator interpolator, @Nullable CancellationSignal cancellationSignal, final @NonNull WindowInsetsAnimationControlListenerCompat listener)736 void controlWindowInsetsAnimation(@InsetsType int types, long durationMillis, 737 @Nullable Interpolator interpolator, 738 @Nullable CancellationSignal cancellationSignal, 739 final @NonNull WindowInsetsAnimationControlListenerCompat listener) { 740 741 WindowInsetsAnimationControlListener fwListener = 742 new WindowInsetsAnimationControlListener() { 743 744 private WindowInsetsAnimationControllerCompat mCompatAnimController = null; 745 746 @Override 747 public void onReady(@NonNull WindowInsetsAnimationController controller, 748 int types) { 749 mCompatAnimController = 750 new WindowInsetsAnimationControllerCompat(controller); 751 listener.onReady(mCompatAnimController, types); 752 } 753 754 @Override 755 public void onFinished( 756 @NonNull WindowInsetsAnimationController controller) { 757 listener.onFinished(mCompatAnimController); 758 } 759 760 @Override 761 public void onCancelled( 762 @Nullable WindowInsetsAnimationController controller) { 763 listener.onCancelled(controller == null ? null : mCompatAnimController); 764 } 765 }; 766 767 mInsetsController.controlWindowInsetsAnimation(types, 768 durationMillis, 769 interpolator, 770 cancellationSignal, 771 fwListener); 772 } 773 774 /** 775 * Controls the behavior of system bars. 776 * 777 * @param behavior Determines how the bars behave when being hidden by the application. 778 * @see #getSystemBarsBehavior 779 */ 780 @Override setSystemBarsBehavior(@ehavior int behavior)781 void setSystemBarsBehavior(@Behavior int behavior) { 782 if (mWindow != null) { 783 // Use the legacy way to control the behavior as a workaround because API 30 has a 784 // bug that the behavior might be cleared unexpectedly after setting a LayoutParam 785 // to a window. 786 mWindow.getDecorView().setTag(KEY_BEHAVIOR, behavior); 787 switch (behavior) { 788 case BEHAVIOR_DEFAULT: 789 unsetSystemUiFlag(View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); 790 setSystemUiFlag(View.SYSTEM_UI_FLAG_IMMERSIVE); 791 break; 792 case BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE: 793 unsetSystemUiFlag(View.SYSTEM_UI_FLAG_IMMERSIVE); 794 setSystemUiFlag(View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); 795 break; 796 case BEHAVIOR_SHOW_BARS_BY_TOUCH: 797 unsetSystemUiFlag(View.SYSTEM_UI_FLAG_IMMERSIVE 798 | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); 799 break; 800 } 801 } else { 802 mInsetsController.setSystemBarsBehavior(behavior); 803 } 804 } 805 806 /** 807 * Retrieves the requested behavior of system bars. 808 * 809 * @return the system bar behavior controlled by this window. 810 * @see #setSystemBarsBehavior(int) 811 */ 812 @SuppressLint("WrongConstant") 813 @Override 814 @Behavior getSystemBarsBehavior()815 int getSystemBarsBehavior() { 816 if (mWindow != null) { 817 final Object behaviorTag = mWindow.getDecorView().getTag(KEY_BEHAVIOR); 818 return behaviorTag != null ? (int) behaviorTag : BEHAVIOR_DEFAULT; 819 } else { 820 return mInsetsController.getSystemBarsBehavior(); 821 } 822 } 823 824 @Override addOnControllableInsetsChangedListener( final WindowInsetsControllerCompat.@NonNull OnControllableInsetsChangedListener listener)825 void addOnControllableInsetsChangedListener( 826 final WindowInsetsControllerCompat.@NonNull OnControllableInsetsChangedListener 827 listener) { 828 829 if (mListeners.containsKey(listener)) { 830 // The listener has already been added. 831 return; 832 } 833 WindowInsetsController.OnControllableInsetsChangedListener 834 fwListener = (controller, typeMask) -> { 835 if (mInsetsController == controller) { 836 listener.onControllableInsetsChanged( 837 mCompatController, typeMask); 838 } 839 }; 840 mListeners.put(listener, fwListener); 841 mInsetsController.addOnControllableInsetsChangedListener(fwListener); 842 } 843 844 @Override removeOnControllableInsetsChangedListener( WindowInsetsControllerCompat.@onNull OnControllableInsetsChangedListener listener)845 void removeOnControllableInsetsChangedListener( 846 WindowInsetsControllerCompat.@NonNull OnControllableInsetsChangedListener 847 listener) { 848 WindowInsetsController.OnControllableInsetsChangedListener 849 fwListener = mListeners.remove(listener); 850 if (fwListener != null) { 851 mInsetsController.removeOnControllableInsetsChangedListener(fwListener); 852 } 853 } 854 unsetSystemUiFlag(int systemUiFlag)855 protected void unsetSystemUiFlag(int systemUiFlag) { 856 View decorView = mWindow.getDecorView(); 857 decorView.setSystemUiVisibility( 858 decorView.getSystemUiVisibility() 859 & ~systemUiFlag); 860 } 861 setSystemUiFlag(int systemUiFlag)862 protected void setSystemUiFlag(int systemUiFlag) { 863 View decorView = mWindow.getDecorView(); 864 decorView.setSystemUiVisibility( 865 decorView.getSystemUiVisibility() 866 | systemUiFlag); 867 } 868 } 869 870 @RequiresApi(31) 871 private static class Impl31 extends Impl30 { 872 Impl31(@onNull Window window, @NonNull WindowInsetsControllerCompat compatController, @NonNull SoftwareKeyboardControllerCompat softwareKeyboardControllerCompat)873 Impl31(@NonNull Window window, 874 @NonNull WindowInsetsControllerCompat compatController, 875 @NonNull SoftwareKeyboardControllerCompat softwareKeyboardControllerCompat) { 876 super(window, compatController, softwareKeyboardControllerCompat); 877 } 878 Impl31(@onNull WindowInsetsController insetsController, @NonNull WindowInsetsControllerCompat compatController, @NonNull SoftwareKeyboardControllerCompat softwareKeyboardControllerCompat)879 Impl31(@NonNull WindowInsetsController insetsController, 880 @NonNull WindowInsetsControllerCompat compatController, 881 @NonNull SoftwareKeyboardControllerCompat softwareKeyboardControllerCompat) { 882 super(insetsController, compatController, softwareKeyboardControllerCompat); 883 } 884 885 /** 886 * Controls the behavior of system bars. 887 * 888 * @param behavior Determines how the bars behave when being hidden by the application. 889 * @see #getSystemBarsBehavior 890 */ 891 @Override setSystemBarsBehavior(@ehavior int behavior)892 void setSystemBarsBehavior(@Behavior int behavior) { 893 mInsetsController.setSystemBarsBehavior(behavior); 894 } 895 896 /** 897 * Retrieves the requested behavior of system bars. 898 * 899 * @return the system bar behavior controlled by this window. 900 * @see #setSystemBarsBehavior(int) 901 */ 902 @SuppressLint("WrongConstant") 903 @Override 904 @Behavior getSystemBarsBehavior()905 int getSystemBarsBehavior() { 906 return mInsetsController.getSystemBarsBehavior(); 907 } 908 } 909 910 @RequiresApi(35) 911 private static class Impl35 extends Impl31 { 912 Impl35(@onNull Window window, @NonNull WindowInsetsControllerCompat compatController, @NonNull SoftwareKeyboardControllerCompat softwareKeyboardControllerCompat)913 Impl35(@NonNull Window window, 914 @NonNull WindowInsetsControllerCompat compatController, 915 @NonNull SoftwareKeyboardControllerCompat softwareKeyboardControllerCompat) { 916 super(window, compatController, softwareKeyboardControllerCompat); 917 } 918 Impl35(@onNull WindowInsetsController insetsController, @NonNull WindowInsetsControllerCompat compatController, @NonNull SoftwareKeyboardControllerCompat softwareKeyboardControllerCompat)919 Impl35(@NonNull WindowInsetsController insetsController, 920 @NonNull WindowInsetsControllerCompat compatController, 921 @NonNull SoftwareKeyboardControllerCompat softwareKeyboardControllerCompat) { 922 super(insetsController, compatController, softwareKeyboardControllerCompat); 923 } 924 925 @Override isAppearanceLightStatusBars()926 public boolean isAppearanceLightStatusBars() { 927 return (mInsetsController.getSystemBarsAppearance() 928 & WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS) != 0; 929 } 930 931 @Override isAppearanceLightNavigationBars()932 public boolean isAppearanceLightNavigationBars() { 933 return (mInsetsController.getSystemBarsAppearance() 934 & WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS) != 0; 935 } 936 937 } 938 } 939