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