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