1 /*
2  * Copyright (C) 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  *      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 android.inputmethodservice;
18 
19 import static android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR;
20 import static android.view.WindowInsets.Type.captionBar;
21 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
22 
23 import android.animation.ValueAnimator;
24 import android.annotation.FloatRange;
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.app.StatusBarManager;
28 import android.graphics.Color;
29 import android.graphics.Insets;
30 import android.graphics.Rect;
31 import android.graphics.Region;
32 import android.inputmethodservice.navigationbar.NavigationBarFrame;
33 import android.inputmethodservice.navigationbar.NavigationBarView;
34 import android.view.Gravity;
35 import android.view.LayoutInflater;
36 import android.view.View;
37 import android.view.ViewGroup;
38 import android.view.ViewParent;
39 import android.view.ViewTreeObserver;
40 import android.view.Window;
41 import android.view.WindowInsets;
42 import android.view.WindowInsetsController.Appearance;
43 import android.view.animation.Interpolator;
44 import android.view.animation.PathInterpolator;
45 import android.widget.FrameLayout;
46 
47 import com.android.internal.inputmethod.InputMethodNavButtonFlags;
48 
49 import java.util.Objects;
50 
51 /**
52  * This class hides details behind {@link InputMethodService#canImeRenderGesturalNavButtons()} from
53  * {@link InputMethodService}.
54  *
55  * <p>All the package-private methods are no-op when
56  * {@link InputMethodService#canImeRenderGesturalNavButtons()} returns {@code false}.</p>
57  */
58 final class NavigationBarController {
59 
60     private interface Callback {
61 
updateInsets(@onNull InputMethodService.Insets originalInsets)62         default void updateInsets(@NonNull InputMethodService.Insets originalInsets) {
63         }
64 
updateTouchableInsets(@onNull InputMethodService.Insets originalInsets, @NonNull ViewTreeObserver.InternalInsetsInfo dest)65         default void updateTouchableInsets(@NonNull InputMethodService.Insets originalInsets,
66                 @NonNull ViewTreeObserver.InternalInsetsInfo dest) {
67         }
68 
onSoftInputWindowCreated(@onNull SoftInputWindow softInputWindow)69         default void onSoftInputWindowCreated(@NonNull SoftInputWindow softInputWindow) {
70         }
71 
onViewInitialized()72         default void onViewInitialized() {
73         }
74 
onWindowShown()75         default void onWindowShown() {
76         }
77 
onDestroy()78         default void onDestroy() {
79         }
80 
onNavButtonFlagsChanged(@nputMethodNavButtonFlags int navButtonFlags)81         default void onNavButtonFlagsChanged(@InputMethodNavButtonFlags int navButtonFlags) {
82         }
83 
isShown()84         default boolean isShown() {
85             return false;
86         }
87 
toDebugString()88         default String toDebugString() {
89             return "No-op implementation";
90         }
91 
92         Callback NOOP = new Callback() {
93         };
94     }
95 
96     private final Callback mImpl;
97 
NavigationBarController(@onNull InputMethodService inputMethodService)98     NavigationBarController(@NonNull InputMethodService inputMethodService) {
99         mImpl = InputMethodService.canImeRenderGesturalNavButtons()
100                 ? new Impl(inputMethodService) : Callback.NOOP;
101     }
102 
103     /**
104      * Update the given insets to be at least as big as the IME navigation bar, when visible.
105      *
106      * @param originalInsets the insets to check and modify to include the IME navigation bar.
107      */
updateInsets(@onNull InputMethodService.Insets originalInsets)108     void updateInsets(@NonNull InputMethodService.Insets originalInsets) {
109         mImpl.updateInsets(originalInsets);
110     }
111 
updateTouchableInsets(@onNull InputMethodService.Insets originalInsets, @NonNull ViewTreeObserver.InternalInsetsInfo dest)112     void updateTouchableInsets(@NonNull InputMethodService.Insets originalInsets,
113             @NonNull ViewTreeObserver.InternalInsetsInfo dest) {
114         mImpl.updateTouchableInsets(originalInsets, dest);
115     }
116 
onSoftInputWindowCreated(@onNull SoftInputWindow softInputWindow)117     void onSoftInputWindowCreated(@NonNull SoftInputWindow softInputWindow) {
118         mImpl.onSoftInputWindowCreated(softInputWindow);
119     }
120 
onViewInitialized()121     void onViewInitialized() {
122         mImpl.onViewInitialized();
123     }
124 
onWindowShown()125     void onWindowShown() {
126         mImpl.onWindowShown();
127     }
128 
onDestroy()129     void onDestroy() {
130         mImpl.onDestroy();
131     }
132 
onNavButtonFlagsChanged(@nputMethodNavButtonFlags int navButtonFlags)133     void onNavButtonFlagsChanged(@InputMethodNavButtonFlags int navButtonFlags) {
134         mImpl.onNavButtonFlagsChanged(navButtonFlags);
135     }
136 
137     /**
138      * Returns whether the IME navigation bar is currently shown.
139      */
isShown()140     boolean isShown() {
141         return mImpl.isShown();
142     }
143 
toDebugString()144     String toDebugString() {
145         return mImpl.toDebugString();
146     }
147 
148     private static final class Impl implements Callback, Window.DecorCallback {
149         private static final int DEFAULT_COLOR_ADAPT_TRANSITION_TIME = 1700;
150 
151         // Copied from com.android.systemui.animation.Interpolators#LEGACY_DECELERATE
152         private static final Interpolator LEGACY_DECELERATE =
153                 new PathInterpolator(0f, 0f, 0.2f, 1f);
154 
155         @NonNull
156         private final InputMethodService mService;
157 
158         private boolean mDestroyed = false;
159 
160         private boolean mImeDrawsImeNavBar;
161 
162         @Nullable
163         private NavigationBarFrame mNavigationBarFrame;
164         @Nullable
165         Insets mLastInsets;
166 
167         private boolean mShouldShowImeSwitcherWhenImeIsShown;
168 
169         @Appearance
170         private int mAppearance;
171 
172         @FloatRange(from = 0.0f, to = 1.0f)
173         private float mDarkIntensity;
174 
175         @Nullable
176         private ValueAnimator mTintAnimator;
177 
178         private boolean mDrawLegacyNavigationBarBackground;
179 
180         private final Rect mTempRect = new Rect();
181         private final int[] mTempPos = new int[2];
182 
Impl(@onNull InputMethodService inputMethodService)183         Impl(@NonNull InputMethodService inputMethodService) {
184             mService = inputMethodService;
185         }
186 
187         @Nullable
getSystemInsets()188         private Insets getSystemInsets() {
189             if (mService.mWindow == null) {
190                 return null;
191             }
192             final View decorView = mService.mWindow.getWindow().getDecorView();
193             if (decorView == null) {
194                 return null;
195             }
196             final WindowInsets windowInsets = decorView.getRootWindowInsets();
197             if (windowInsets == null) {
198                 return null;
199             }
200             final Insets stableBarInsets =
201                     windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars());
202             return Insets.min(windowInsets.getInsets(WindowInsets.Type.systemBars()
203                     | WindowInsets.Type.displayCutout()), stableBarInsets);
204         }
205 
installNavigationBarFrameIfNecessary()206         private void installNavigationBarFrameIfNecessary() {
207             if (!mImeDrawsImeNavBar) {
208                 return;
209             }
210             if (mNavigationBarFrame != null) {
211                 return;
212             }
213             final View rawDecorView = mService.mWindow.getWindow().getDecorView();
214             if (!(rawDecorView instanceof ViewGroup)) {
215                 return;
216             }
217             final ViewGroup decorView = (ViewGroup) rawDecorView;
218             mNavigationBarFrame = decorView.findViewByPredicate(
219                     NavigationBarFrame.class::isInstance);
220             final Insets systemInsets = getSystemInsets();
221             if (mNavigationBarFrame == null) {
222                 mNavigationBarFrame = new NavigationBarFrame(mService);
223                 LayoutInflater.from(mService).inflate(
224                         com.android.internal.R.layout.input_method_navigation_bar,
225                         mNavigationBarFrame);
226                 if (systemInsets != null) {
227                     decorView.addView(mNavigationBarFrame, new FrameLayout.LayoutParams(
228                             ViewGroup.LayoutParams.MATCH_PARENT,
229                             systemInsets.bottom, Gravity.BOTTOM));
230                     mLastInsets = systemInsets;
231                 } else {
232                     decorView.addView(mNavigationBarFrame);
233                 }
234                 final NavigationBarView navigationBarView = mNavigationBarFrame.findViewByPredicate(
235                         NavigationBarView.class::isInstance);
236                 if (navigationBarView != null) {
237                     // TODO(b/213337792): Support InputMethodService#setBackDisposition().
238                     // TODO(b/213337792): Set NAVIGATION_HINT_IME_SHOWN only when necessary.
239                     final int hints = StatusBarManager.NAVIGATION_HINT_BACK_ALT
240                             | (mShouldShowImeSwitcherWhenImeIsShown
241                                     ? StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN
242                                     : 0);
243                     navigationBarView.setNavigationIconHints(hints);
244                 }
245             } else {
246                 mNavigationBarFrame.setLayoutParams(new FrameLayout.LayoutParams(
247                         ViewGroup.LayoutParams.MATCH_PARENT, systemInsets.bottom, Gravity.BOTTOM));
248                 mLastInsets = systemInsets;
249             }
250 
251             if (mDrawLegacyNavigationBarBackground) {
252                 mNavigationBarFrame.setBackgroundColor(Color.BLACK);
253             } else {
254                 mNavigationBarFrame.setBackground(null);
255             }
256 
257             setIconTintInternal(calculateTargetDarkIntensity(mAppearance,
258                     mDrawLegacyNavigationBarBackground));
259 
260             if (ENABLE_HIDE_IME_CAPTION_BAR) {
261                 mNavigationBarFrame.setOnApplyWindowInsetsListener((view, insets) -> {
262                     if (mNavigationBarFrame != null) {
263                         boolean visible = insets.isVisible(captionBar());
264                         mNavigationBarFrame.setVisibility(visible ? View.VISIBLE : View.GONE);
265                     }
266                     return view.onApplyWindowInsets(insets);
267                 });
268             }
269         }
270 
uninstallNavigationBarFrameIfNecessary()271         private void uninstallNavigationBarFrameIfNecessary() {
272             if (mNavigationBarFrame == null) {
273                 return;
274             }
275             final ViewParent parent = mNavigationBarFrame.getParent();
276             if (parent instanceof ViewGroup) {
277                 ((ViewGroup) parent).removeView(mNavigationBarFrame);
278             }
279             if (ENABLE_HIDE_IME_CAPTION_BAR) {
280                 mNavigationBarFrame.setOnApplyWindowInsetsListener(null);
281             }
282             mNavigationBarFrame = null;
283         }
284 
285         @Override
updateInsets(@onNull InputMethodService.Insets originalInsets)286         public void updateInsets(@NonNull InputMethodService.Insets originalInsets) {
287             if (!mImeDrawsImeNavBar || mNavigationBarFrame == null
288                     || mNavigationBarFrame.getVisibility() != View.VISIBLE
289                     || mService.isFullscreenMode()) {
290                 return;
291             }
292 
293             final int[] loc = new int[2];
294             mNavigationBarFrame.getLocationInWindow(loc);
295             if (originalInsets.contentTopInsets > loc[1]) {
296                 originalInsets.contentTopInsets = loc[1];
297             }
298             if (originalInsets.visibleTopInsets > loc[1]) {
299                 originalInsets.visibleTopInsets = loc[1];
300             }
301         }
302 
303         @Override
updateTouchableInsets(@onNull InputMethodService.Insets originalInsets, @NonNull ViewTreeObserver.InternalInsetsInfo dest)304         public void updateTouchableInsets(@NonNull InputMethodService.Insets originalInsets,
305                 @NonNull ViewTreeObserver.InternalInsetsInfo dest) {
306             if (!mImeDrawsImeNavBar || mNavigationBarFrame == null) {
307                 return;
308             }
309 
310             final Insets systemInsets = getSystemInsets();
311             if (systemInsets != null) {
312                 final Window window = mService.mWindow.getWindow();
313                 final View decor = window.getDecorView();
314 
315                 // If the extract view is shown, everything is touchable, so no need to update
316                 // touchable insets, but we still update normal insets below.
317                 if (!mService.isExtractViewShown()) {
318                     Region touchableRegion = null;
319                     final View inputFrame = mService.mInputFrame;
320                     switch (originalInsets.touchableInsets) {
321                         case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME:
322                             if (inputFrame.getVisibility() == View.VISIBLE) {
323                                 inputFrame.getLocationInWindow(mTempPos);
324                                 mTempRect.set(mTempPos[0], mTempPos[1],
325                                         mTempPos[0] + inputFrame.getWidth(),
326                                         mTempPos[1] + inputFrame.getHeight());
327                                 touchableRegion = new Region(mTempRect);
328                             }
329                             break;
330                         case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT:
331                             if (inputFrame.getVisibility() == View.VISIBLE) {
332                                 inputFrame.getLocationInWindow(mTempPos);
333                                 mTempRect.set(mTempPos[0], originalInsets.contentTopInsets,
334                                         mTempPos[0] + inputFrame.getWidth(),
335                                         mTempPos[1] + inputFrame.getHeight());
336                                 touchableRegion = new Region(mTempRect);
337                             }
338                             break;
339                         case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE:
340                             if (inputFrame.getVisibility() == View.VISIBLE) {
341                                 inputFrame.getLocationInWindow(mTempPos);
342                                 mTempRect.set(mTempPos[0], originalInsets.visibleTopInsets,
343                                         mTempPos[0] + inputFrame.getWidth(),
344                                         mTempPos[1] + inputFrame.getHeight());
345                                 touchableRegion = new Region(mTempRect);
346                             }
347                             break;
348                         case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION:
349                             touchableRegion = new Region();
350                             touchableRegion.set(originalInsets.touchableRegion);
351                             break;
352                     }
353                     // Hereafter "mTempRect" means a navigation bar rect.
354                     mTempRect.set(decor.getLeft(), decor.getBottom() - systemInsets.bottom,
355                             decor.getRight(), decor.getBottom());
356                     if (touchableRegion == null) {
357                         touchableRegion = new Region(mTempRect);
358                     } else {
359                         touchableRegion.union(mTempRect);
360                     }
361 
362                     dest.touchableRegion.set(touchableRegion);
363                     dest.setTouchableInsets(
364                             ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
365                 }
366 
367                 // TODO(b/215443343): See if we can use View#OnLayoutChangeListener().
368                 // TODO(b/215443343): See if we can replace DecorView#mNavigationColorViewState.view
369                 boolean zOrderChanged = false;
370                 if (decor instanceof ViewGroup) {
371                     ViewGroup decorGroup = (ViewGroup) decor;
372                     final View navbarBackgroundView = window.getNavigationBarBackgroundView();
373                     zOrderChanged = navbarBackgroundView != null
374                             && decorGroup.indexOfChild(navbarBackgroundView)
375                             > decorGroup.indexOfChild(mNavigationBarFrame);
376                 }
377                 final boolean insetChanged = !Objects.equals(systemInsets, mLastInsets);
378                 if (zOrderChanged || insetChanged) {
379                     scheduleRelayout();
380                 }
381             }
382         }
383 
scheduleRelayout()384         private void scheduleRelayout() {
385             // Capture the current frame object in case the object is replaced or cleared later.
386             final NavigationBarFrame frame = mNavigationBarFrame;
387             frame.post(() -> {
388                 if (mDestroyed) {
389                     return;
390                 }
391                 if (!frame.isAttachedToWindow()) {
392                     return;
393                 }
394                 final Window window = mService.mWindow.getWindow();
395                 if (window == null) {
396                     return;
397                 }
398                 final View decor = window.peekDecorView();
399                 if (decor == null) {
400                     return;
401                 }
402                 if (!(decor instanceof ViewGroup)) {
403                     return;
404                 }
405                 final ViewGroup decorGroup = (ViewGroup) decor;
406                 final Insets currentSystemInsets = getSystemInsets();
407                 if (!Objects.equals(currentSystemInsets, mLastInsets)) {
408                     frame.setLayoutParams(new FrameLayout.LayoutParams(
409                             ViewGroup.LayoutParams.MATCH_PARENT,
410                             currentSystemInsets.bottom, Gravity.BOTTOM));
411                     mLastInsets = currentSystemInsets;
412                 }
413                 final View navbarBackgroundView =
414                         window.getNavigationBarBackgroundView();
415                 if (navbarBackgroundView != null
416                         && decorGroup.indexOfChild(navbarBackgroundView)
417                         > decorGroup.indexOfChild(frame)) {
418                     decorGroup.bringChildToFront(frame);
419                 }
420             });
421         }
422 
423         @Override
onSoftInputWindowCreated(@onNull SoftInputWindow softInputWindow)424         public void onSoftInputWindowCreated(@NonNull SoftInputWindow softInputWindow) {
425             final Window window = softInputWindow.getWindow();
426             mAppearance = window.getSystemBarAppearance();
427             window.setDecorCallback(this);
428         }
429 
430         @Override
onViewInitialized()431         public void onViewInitialized() {
432             if (mDestroyed) {
433                 return;
434             }
435             installNavigationBarFrameIfNecessary();
436         }
437 
438         @Override
onDestroy()439         public void onDestroy() {
440             if (mDestroyed) {
441                 return;
442             }
443             if (mTintAnimator != null) {
444                 mTintAnimator.cancel();
445                 mTintAnimator = null;
446             }
447             mDestroyed = true;
448         }
449 
450         @Override
onWindowShown()451         public void onWindowShown() {
452             if (mDestroyed || !mImeDrawsImeNavBar || mNavigationBarFrame == null) {
453                 return;
454             }
455             final Insets systemInsets = getSystemInsets();
456             if (systemInsets != null) {
457                 if (!Objects.equals(systemInsets, mLastInsets)) {
458                     mNavigationBarFrame.setLayoutParams(new NavigationBarFrame.LayoutParams(
459                             ViewGroup.LayoutParams.MATCH_PARENT,
460                             systemInsets.bottom, Gravity.BOTTOM));
461                     mLastInsets = systemInsets;
462                 }
463                 final Window window = mService.mWindow.getWindow();
464                 View rawDecorView = window.getDecorView();
465                 if (rawDecorView instanceof ViewGroup) {
466                     final ViewGroup decor = (ViewGroup) rawDecorView;
467                     final View navbarBackgroundView = window.getNavigationBarBackgroundView();
468                     if (navbarBackgroundView != null
469                             && decor.indexOfChild(navbarBackgroundView)
470                             > decor.indexOfChild(mNavigationBarFrame)) {
471                         decor.bringChildToFront(mNavigationBarFrame);
472                     }
473                 }
474                 if (!ENABLE_HIDE_IME_CAPTION_BAR) {
475                     mNavigationBarFrame.setVisibility(View.VISIBLE);
476                 }
477             }
478         }
479 
480         @Override
onNavButtonFlagsChanged(@nputMethodNavButtonFlags int navButtonFlags)481         public void onNavButtonFlagsChanged(@InputMethodNavButtonFlags int navButtonFlags) {
482             if (mDestroyed) {
483                 return;
484             }
485 
486             final boolean imeDrawsImeNavBar =
487                     (navButtonFlags & InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR) != 0;
488             final boolean shouldShowImeSwitcherWhenImeIsShown =
489                     (navButtonFlags & InputMethodNavButtonFlags.SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN)
490                     != 0;
491 
492             mImeDrawsImeNavBar = imeDrawsImeNavBar;
493             final boolean prevShouldShowImeSwitcherWhenImeIsShown =
494                     mShouldShowImeSwitcherWhenImeIsShown;
495             mShouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherWhenImeIsShown;
496 
497             if (ENABLE_HIDE_IME_CAPTION_BAR) {
498                 mService.mWindow.getWindow().getDecorView().getWindowInsetsController()
499                         .setImeCaptionBarInsetsHeight(getImeCaptionBarHeight());
500             }
501 
502             if (imeDrawsImeNavBar) {
503                 installNavigationBarFrameIfNecessary();
504                 if (mNavigationBarFrame == null) {
505                     return;
506                 }
507                 if (mShouldShowImeSwitcherWhenImeIsShown
508                         == prevShouldShowImeSwitcherWhenImeIsShown) {
509                     return;
510                 }
511                 final NavigationBarView navigationBarView = mNavigationBarFrame.findViewByPredicate(
512                         NavigationBarView.class::isInstance);
513                 if (navigationBarView == null) {
514                     return;
515                 }
516                 final int hints = StatusBarManager.NAVIGATION_HINT_BACK_ALT
517                         | (shouldShowImeSwitcherWhenImeIsShown
518                                 ? StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN : 0);
519                 navigationBarView.setNavigationIconHints(hints);
520             } else {
521                 uninstallNavigationBarFrameIfNecessary();
522             }
523         }
524 
525         @Override
onSystemBarAppearanceChanged(@ppearance int appearance)526         public void onSystemBarAppearanceChanged(@Appearance int appearance) {
527             if (mDestroyed) {
528                 return;
529             }
530 
531             mAppearance = appearance;
532 
533             if (mNavigationBarFrame == null) {
534                 return;
535             }
536 
537             final float targetDarkIntensity = calculateTargetDarkIntensity(mAppearance,
538                     mDrawLegacyNavigationBarBackground);
539 
540             if (mTintAnimator != null) {
541                 mTintAnimator.cancel();
542             }
543             mTintAnimator = ValueAnimator.ofFloat(mDarkIntensity, targetDarkIntensity);
544             mTintAnimator.addUpdateListener(
545                     animation -> setIconTintInternal((Float) animation.getAnimatedValue()));
546             mTintAnimator.setDuration(DEFAULT_COLOR_ADAPT_TRANSITION_TIME);
547             mTintAnimator.setStartDelay(0);
548             mTintAnimator.setInterpolator(LEGACY_DECELERATE);
549             mTintAnimator.start();
550         }
551 
setIconTintInternal(float darkIntensity)552         private void setIconTintInternal(float darkIntensity) {
553             mDarkIntensity = darkIntensity;
554             if (mNavigationBarFrame == null) {
555                 return;
556             }
557             final NavigationBarView navigationBarView =
558                     mNavigationBarFrame.findViewByPredicate(NavigationBarView.class::isInstance);
559             if (navigationBarView == null) {
560                 return;
561             }
562             navigationBarView.setDarkIntensity(darkIntensity);
563         }
564 
565         @FloatRange(from = 0.0f, to = 1.0f)
calculateTargetDarkIntensity(@ppearance int appearance, boolean drawLegacyNavigationBarBackground)566         private static float calculateTargetDarkIntensity(@Appearance int appearance,
567                 boolean drawLegacyNavigationBarBackground) {
568             final boolean lightNavBar = !drawLegacyNavigationBarBackground
569                     && (appearance & APPEARANCE_LIGHT_NAVIGATION_BARS) != 0;
570             return lightNavBar ? 1.0f : 0.0f;
571         }
572 
573         @Override
onDrawLegacyNavigationBarBackgroundChanged( boolean drawLegacyNavigationBarBackground)574         public boolean onDrawLegacyNavigationBarBackgroundChanged(
575                 boolean drawLegacyNavigationBarBackground) {
576             if (mDestroyed) {
577                 return false;
578             }
579 
580             if (drawLegacyNavigationBarBackground != mDrawLegacyNavigationBarBackground) {
581                 mDrawLegacyNavigationBarBackground = drawLegacyNavigationBarBackground;
582                 if (mNavigationBarFrame != null) {
583                     if (mDrawLegacyNavigationBarBackground) {
584                         mNavigationBarFrame.setBackgroundColor(Color.BLACK);
585                     } else {
586                         mNavigationBarFrame.setBackground(null);
587                     }
588                     scheduleRelayout();
589                 }
590                 onSystemBarAppearanceChanged(mAppearance);
591             }
592             return drawLegacyNavigationBarBackground;
593         }
594 
595         /**
596          * Returns the height of the IME caption bar if this should be shown, or {@code 0} instead.
597          */
getImeCaptionBarHeight()598         private int getImeCaptionBarHeight() {
599             return mImeDrawsImeNavBar
600                     ? mService.getResources().getDimensionPixelSize(
601                             com.android.internal.R.dimen.navigation_bar_frame_height)
602                     : 0;
603         }
604 
605         @Override
isShown()606         public boolean isShown() {
607             return mNavigationBarFrame != null
608                     && mNavigationBarFrame.getVisibility() == View.VISIBLE;
609         }
610 
611         @Override
toDebugString()612         public String toDebugString() {
613             return "{mImeDrawsImeNavBar=" + mImeDrawsImeNavBar
614                     + " mNavigationBarFrame=" + mNavigationBarFrame
615                     + " mShouldShowImeSwitcherWhenImeIsShown="
616                     + mShouldShowImeSwitcherWhenImeIsShown
617                     + " mAppearance=0x" + Integer.toHexString(mAppearance)
618                     + " mDarkIntensity=" + mDarkIntensity
619                     + " mDrawLegacyNavigationBarBackground=" + mDrawLegacyNavigationBarBackground
620                     + "}";
621         }
622     }
623 }
624