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