1 /* 2 * Copyright (C) 2019 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 com.android.wm.shell.common; 18 19 import static android.view.EventLogTags.IMF_IME_REMOTE_ANIM_CANCEL; 20 import static android.view.EventLogTags.IMF_IME_REMOTE_ANIM_END; 21 import static android.view.EventLogTags.IMF_IME_REMOTE_ANIM_START; 22 import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY; 23 24 import android.animation.Animator; 25 import android.animation.AnimatorListenerAdapter; 26 import android.animation.ValueAnimator; 27 import android.annotation.IntDef; 28 import android.annotation.NonNull; 29 import android.annotation.Nullable; 30 import android.content.ComponentName; 31 import android.content.res.Configuration; 32 import android.graphics.Point; 33 import android.graphics.Rect; 34 import android.os.RemoteException; 35 import android.util.EventLog; 36 import android.util.Slog; 37 import android.util.SparseArray; 38 import android.view.IDisplayWindowInsetsController; 39 import android.view.IWindowManager; 40 import android.view.InsetsSource; 41 import android.view.InsetsSourceControl; 42 import android.view.InsetsState; 43 import android.view.Surface; 44 import android.view.SurfaceControl; 45 import android.view.WindowInsets; 46 import android.view.WindowInsets.Type.InsetsType; 47 import android.view.animation.Interpolator; 48 import android.view.animation.PathInterpolator; 49 import android.view.inputmethod.ImeTracker; 50 import android.view.inputmethod.InputMethodManagerGlobal; 51 52 import androidx.annotation.VisibleForTesting; 53 54 import com.android.internal.inputmethod.SoftInputShowHideReason; 55 import com.android.wm.shell.shared.TransactionPool; 56 import com.android.wm.shell.sysui.ShellInit; 57 58 import java.util.ArrayList; 59 import java.util.Objects; 60 import java.util.concurrent.Executor; 61 62 /** 63 * Manages IME control at the display-level. This occurs when IME comes up in multi-window mode. 64 */ 65 public class DisplayImeController implements DisplayController.OnDisplaysChangedListener { 66 private static final String TAG = "DisplayImeController"; 67 68 private static final boolean DEBUG = false; 69 70 // NOTE: All these constants came from InsetsController. 71 public static final int ANIMATION_DURATION_SHOW_MS = 275; 72 public static final int ANIMATION_DURATION_HIDE_MS = 340; 73 public static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f); 74 private static final int DIRECTION_NONE = 0; 75 private static final int DIRECTION_SHOW = 1; 76 private static final int DIRECTION_HIDE = 2; 77 private static final int FLOATING_IME_BOTTOM_INSET = -80; 78 79 protected final IWindowManager mWmService; 80 protected final Executor mMainExecutor; 81 private final TransactionPool mTransactionPool; 82 private final DisplayController mDisplayController; 83 private final DisplayInsetsController mDisplayInsetsController; 84 private final SparseArray<PerDisplay> mImePerDisplay = new SparseArray<>(); 85 private final ArrayList<ImePositionProcessor> mPositionProcessors = new ArrayList<>(); 86 87 DisplayImeController(IWindowManager wmService, ShellInit shellInit, DisplayController displayController, DisplayInsetsController displayInsetsController, TransactionPool transactionPool, Executor mainExecutor)88 public DisplayImeController(IWindowManager wmService, 89 ShellInit shellInit, 90 DisplayController displayController, 91 DisplayInsetsController displayInsetsController, 92 TransactionPool transactionPool, 93 Executor mainExecutor) { 94 mWmService = wmService; 95 mDisplayController = displayController; 96 mDisplayInsetsController = displayInsetsController; 97 mMainExecutor = mainExecutor; 98 mTransactionPool = transactionPool; 99 shellInit.addInitCallback(this::onInit, this); 100 } 101 102 /** 103 * Starts monitor displays changes and set insets controller for each displays. 104 */ onInit()105 public void onInit() { 106 mDisplayController.addDisplayWindowListener(this); 107 } 108 109 @Override onDisplayAdded(int displayId)110 public void onDisplayAdded(int displayId) { 111 // Add's a system-ui window-manager specifically for ime. This type is special because 112 // WM will defer IME inset handling to it in multi-window scenarious. 113 PerDisplay pd = new PerDisplay(displayId, 114 mDisplayController.getDisplayLayout(displayId).rotation()); 115 pd.register(); 116 mImePerDisplay.put(displayId, pd); 117 } 118 119 @Override onDisplayConfigurationChanged(int displayId, Configuration newConfig)120 public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { 121 PerDisplay pd = mImePerDisplay.get(displayId); 122 if (pd == null) { 123 return; 124 } 125 if (mDisplayController.getDisplayLayout(displayId).rotation() 126 != pd.mRotation && isImeShowing(displayId)) { 127 pd.startAnimation(true, false /* forceRestart */, 128 SoftInputShowHideReason.DISPLAY_CONFIGURATION_CHANGED); 129 } 130 } 131 132 @Override onDisplayRemoved(int displayId)133 public void onDisplayRemoved(int displayId) { 134 PerDisplay pd = mImePerDisplay.get(displayId); 135 if (pd == null) { 136 return; 137 } 138 pd.unregister(); 139 mImePerDisplay.remove(displayId); 140 } 141 isImeShowing(int displayId)142 private boolean isImeShowing(int displayId) { 143 PerDisplay pd = mImePerDisplay.get(displayId); 144 if (pd == null) { 145 return false; 146 } 147 final InsetsSource imeSource = pd.mInsetsState.peekSource(InsetsSource.ID_IME); 148 return imeSource != null && pd.mImeSourceControl != null && imeSource.isVisible(); 149 } 150 dispatchPositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t)151 private void dispatchPositionChanged(int displayId, int imeTop, 152 SurfaceControl.Transaction t) { 153 synchronized (mPositionProcessors) { 154 for (ImePositionProcessor pp : mPositionProcessors) { 155 pp.onImePositionChanged(displayId, imeTop, t); 156 } 157 } 158 } 159 dispatchImeRequested(int displayId, boolean isRequested)160 private void dispatchImeRequested(int displayId, boolean isRequested) { 161 synchronized (mPositionProcessors) { 162 for (ImePositionProcessor pp : mPositionProcessors) { 163 pp.onImeRequested(displayId, isRequested); 164 } 165 } 166 } 167 168 @ImePositionProcessor.ImeAnimationFlags dispatchStartPositioning(int displayId, int hiddenTop, int shownTop, boolean show, boolean isFloating, SurfaceControl.Transaction t)169 private int dispatchStartPositioning(int displayId, int hiddenTop, int shownTop, 170 boolean show, boolean isFloating, SurfaceControl.Transaction t) { 171 synchronized (mPositionProcessors) { 172 int flags = 0; 173 for (ImePositionProcessor pp : mPositionProcessors) { 174 flags |= pp.onImeStartPositioning( 175 displayId, hiddenTop, shownTop, show, isFloating, t); 176 } 177 return flags; 178 } 179 } 180 dispatchEndPositioning(int displayId, boolean cancel, SurfaceControl.Transaction t)181 private void dispatchEndPositioning(int displayId, boolean cancel, 182 SurfaceControl.Transaction t) { 183 synchronized (mPositionProcessors) { 184 for (ImePositionProcessor pp : mPositionProcessors) { 185 pp.onImeEndPositioning(displayId, cancel, t); 186 } 187 } 188 } 189 dispatchImeControlTargetChanged(int displayId, boolean controlling)190 private void dispatchImeControlTargetChanged(int displayId, boolean controlling) { 191 synchronized (mPositionProcessors) { 192 for (ImePositionProcessor pp : mPositionProcessors) { 193 pp.onImeControlTargetChanged(displayId, controlling); 194 } 195 } 196 } 197 dispatchVisibilityChanged(int displayId, boolean isShowing)198 private void dispatchVisibilityChanged(int displayId, boolean isShowing) { 199 synchronized (mPositionProcessors) { 200 for (ImePositionProcessor pp : mPositionProcessors) { 201 pp.onImeVisibilityChanged(displayId, isShowing); 202 } 203 } 204 } 205 206 /** 207 * Adds an {@link ImePositionProcessor} to be called during ime position updates. 208 */ addPositionProcessor(ImePositionProcessor processor)209 public void addPositionProcessor(ImePositionProcessor processor) { 210 synchronized (mPositionProcessors) { 211 if (mPositionProcessors.contains(processor)) { 212 return; 213 } 214 mPositionProcessors.add(processor); 215 } 216 } 217 218 /** 219 * Removes an {@link ImePositionProcessor} to be called during ime position updates. 220 */ removePositionProcessor(ImePositionProcessor processor)221 public void removePositionProcessor(ImePositionProcessor processor) { 222 synchronized (mPositionProcessors) { 223 mPositionProcessors.remove(processor); 224 } 225 } 226 227 /** Hides the IME for Bubbles when the device is locked. */ hideImeForBubblesWhenLocked(int displayId)228 public void hideImeForBubblesWhenLocked(int displayId) { 229 PerDisplay pd = mImePerDisplay.get(displayId); 230 InsetsSourceControl imeSourceControl = pd.getImeSourceControl(); 231 if (imeSourceControl != null) { 232 ImeTracker.Token imeStatsToken = imeSourceControl.getImeStatsToken(); 233 if (imeStatsToken != null) { 234 pd.setImeInputTargetRequestedVisibility(false, imeStatsToken); 235 } 236 } 237 } 238 239 /** An implementation of {@link IDisplayWindowInsetsController} for a given display id. */ 240 public class PerDisplay implements DisplayInsetsController.OnInsetsChangedListener { 241 final int mDisplayId; 242 final InsetsState mInsetsState = new InsetsState(); 243 boolean mImeRequestedVisible = 244 (WindowInsets.Type.defaultVisible() & WindowInsets.Type.ime()) != 0; 245 InsetsSourceControl mImeSourceControl = null; 246 int mAnimationDirection = DIRECTION_NONE; 247 ValueAnimator mAnimation = null; 248 int mRotation = Surface.ROTATION_0; 249 boolean mImeShowing = false; 250 final Rect mImeFrame = new Rect(); 251 boolean mAnimateAlpha = true; 252 PerDisplay(int displayId, int initialRotation)253 public PerDisplay(int displayId, int initialRotation) { 254 mDisplayId = displayId; 255 mRotation = initialRotation; 256 } 257 register()258 public void register() { 259 mDisplayInsetsController.addInsetsChangedListener(mDisplayId, this); 260 } 261 unregister()262 public void unregister() { 263 mDisplayInsetsController.removeInsetsChangedListener(mDisplayId, this); 264 } 265 266 @Override insetsChanged(InsetsState insetsState)267 public void insetsChanged(InsetsState insetsState) { 268 if (mInsetsState.equals(insetsState)) { 269 return; 270 } 271 272 if (!android.view.inputmethod.Flags.refactorInsetsController()) { 273 updateImeVisibility(insetsState.isSourceOrDefaultVisible(InsetsSource.ID_IME, 274 WindowInsets.Type.ime())); 275 } 276 277 final InsetsSource newSource = insetsState.peekSource(InsetsSource.ID_IME); 278 final Rect newFrame = newSource != null ? newSource.getFrame() : null; 279 final boolean newSourceVisible = newSource != null && newSource.isVisible(); 280 final InsetsSource oldSource = mInsetsState.peekSource(InsetsSource.ID_IME); 281 final Rect oldFrame = oldSource != null ? oldSource.getFrame() : null; 282 283 mInsetsState.set(insetsState, true /* copySources */); 284 if (mImeShowing && !Objects.equals(oldFrame, newFrame) && newSourceVisible) { 285 if (DEBUG) Slog.d(TAG, "insetsChanged when IME showing, restart animation"); 286 startAnimation(mImeShowing, true /* forceRestart */, 287 SoftInputShowHideReason.DISPLAY_INSETS_CHANGED); 288 } 289 } 290 291 @Override 292 @VisibleForTesting insetsControlChanged(InsetsState insetsState, InsetsSourceControl[] activeControls)293 public void insetsControlChanged(InsetsState insetsState, 294 InsetsSourceControl[] activeControls) { 295 insetsChanged(insetsState); 296 InsetsSourceControl imeSourceControl = null; 297 if (activeControls != null) { 298 for (InsetsSourceControl activeControl : activeControls) { 299 if (activeControl == null) { 300 continue; 301 } 302 if (activeControl.getType() == WindowInsets.Type.ime()) { 303 imeSourceControl = activeControl; 304 } 305 } 306 } 307 308 final boolean hadImeSourceControl = mImeSourceControl != null; 309 final boolean hasImeSourceControl = imeSourceControl != null; 310 if (hadImeSourceControl != hasImeSourceControl) { 311 dispatchImeControlTargetChanged(mDisplayId, hasImeSourceControl); 312 } 313 final boolean hasImeLeash = hasImeSourceControl && imeSourceControl.getLeash() != null; 314 315 boolean pendingImeStartAnimation = false; 316 boolean positionChanged = false; 317 if (hasImeLeash) { 318 if (mAnimation != null) { 319 final Point lastSurfacePosition = hadImeSourceControl 320 ? mImeSourceControl.getSurfacePosition() : null; 321 positionChanged = !imeSourceControl.getSurfacePosition().equals( 322 lastSurfacePosition); 323 } else { 324 if (!haveSameLeash(mImeSourceControl, imeSourceControl)) { 325 if (android.view.inputmethod.Flags.refactorInsetsController()) { 326 pendingImeStartAnimation = true; 327 // The starting point for the IME should be it's previous state 328 // (whether it is initiallyVisible or not) 329 updateImeVisibility(imeSourceControl.isInitiallyVisible()); 330 } 331 applyVisibilityToLeash(imeSourceControl); 332 } 333 if (!android.view.inputmethod.Flags.refactorInsetsController()) { 334 if (!mImeShowing) { 335 removeImeSurface(mDisplayId); 336 } 337 } 338 } 339 } else { 340 if (!android.view.inputmethod.Flags.refactorInsetsController() 341 && mAnimation != null) { 342 // we don't want to cancel the hide animation, when the control is lost, but 343 // continue the bar to slide to the end (even without visible IME) 344 mAnimation.cancel(); 345 } else if (android.view.inputmethod.Flags.refactorInsetsController() && mImeShowing 346 && mAnimation == null) { 347 // There is no leash, so the IME cannot be in a showing state 348 updateImeVisibility(false); 349 } 350 } 351 352 // Make mImeSourceControl point to the new control before starting the animation. 353 if (hadImeSourceControl && mImeSourceControl != imeSourceControl) { 354 mImeSourceControl.release(SurfaceControl::release); 355 if (android.view.inputmethod.Flags.refactorInsetsController() 356 && !hasImeLeash && mAnimation != null) { 357 // In case of losing the leash, the animation should be cancelled. 358 mAnimation.cancel(); 359 } 360 } 361 mImeSourceControl = imeSourceControl; 362 363 if (positionChanged) { 364 if (android.view.inputmethod.Flags.refactorInsetsController()) { 365 // For showing the IME, the leash has to be available first. Hiding 366 // the IME happens directly via {@link #hideInsets} (triggered by 367 // setImeInputTargetRequestedVisibility) while the leash is not gone 368 // yet. 369 pendingImeStartAnimation = true; 370 } else { 371 startAnimation(mImeShowing, true /* forceRestart */, 372 SoftInputShowHideReason.DISPLAY_CONTROLS_CHANGED); 373 } 374 } 375 376 if (android.view.inputmethod.Flags.refactorInsetsController()) { 377 if (pendingImeStartAnimation) { 378 startAnimation(mImeRequestedVisible, true /* forceRestart */); 379 } 380 } 381 } 382 applyVisibilityToLeash(InsetsSourceControl imeSourceControl)383 private void applyVisibilityToLeash(InsetsSourceControl imeSourceControl) { 384 SurfaceControl leash = imeSourceControl.getLeash(); 385 if (leash != null) { 386 SurfaceControl.Transaction t = mTransactionPool.acquire(); 387 if (mImeShowing) { 388 t.show(leash); 389 } else { 390 t.hide(leash); 391 } 392 t.apply(); 393 mTransactionPool.release(t); 394 } 395 } 396 397 @Override showInsets(@nsetsType int types, boolean fromIme, @Nullable ImeTracker.Token statsToken)398 public void showInsets(@InsetsType int types, boolean fromIme, 399 @Nullable ImeTracker.Token statsToken) { 400 if ((types & WindowInsets.Type.ime()) == 0) { 401 return; 402 } 403 if (DEBUG) Slog.d(TAG, "Got showInsets for ime"); 404 startAnimation(true /* show */, false /* forceRestart */, statsToken); 405 } 406 407 @Override hideInsets(@nsetsType int types, boolean fromIme, @Nullable ImeTracker.Token statsToken)408 public void hideInsets(@InsetsType int types, boolean fromIme, 409 @Nullable ImeTracker.Token statsToken) { 410 if ((types & WindowInsets.Type.ime()) == 0) { 411 return; 412 } 413 if (DEBUG) Slog.d(TAG, "Got hideInsets for ime"); 414 startAnimation(false /* show */, false /* forceRestart */, statsToken); 415 } 416 417 @Override topFocusedWindowChanged(ComponentName component, int requestedVisibleTypes)418 public void topFocusedWindowChanged(ComponentName component, int requestedVisibleTypes) { 419 // Do nothing 420 } 421 422 @Override 423 // TODO(b/335404678): pass control target setImeInputTargetRequestedVisibility(boolean visible, @NonNull ImeTracker.Token statsToken)424 public void setImeInputTargetRequestedVisibility(boolean visible, 425 @NonNull ImeTracker.Token statsToken) { 426 if (android.view.inputmethod.Flags.refactorInsetsController()) { 427 ImeTracker.forLogging().onProgress(statsToken, 428 ImeTracker.PHASE_WM_DISPLAY_IME_CONTROLLER_SET_IME_REQUESTED_VISIBLE); 429 mImeRequestedVisible = visible; 430 dispatchImeRequested(mDisplayId, mImeRequestedVisible); 431 432 // In the case that the IME becomes visible, but we have the control with leash 433 // already (e.g., when focussing an editText in activity B, while and editText in 434 // activity A is focussed), we will not get a call of #insetsControlChanged, and 435 // therefore have to start the show animation from here 436 if (visible || mImeShowing) { 437 // only start the animation if we're either already showing or becoming visible. 438 // otherwise starting another hide animation causes flickers. 439 startAnimation(mImeRequestedVisible /* show */, false /* forceRestart */, 440 statsToken); 441 } 442 443 boolean hideAnimOngoing; 444 boolean reportVisible; 445 if (android.view.inputmethod.Flags.reportAnimatingInsetsTypes()) { 446 hideAnimOngoing = false; 447 reportVisible = mImeRequestedVisible; 448 } else { 449 // In case of a hide, the statsToken should not been send yet (as the animation 450 // is still ongoing). It will be sent at the end of the animation. 451 hideAnimOngoing = !mImeRequestedVisible && mAnimation != null; 452 reportVisible = mImeRequestedVisible || mAnimation != null; 453 } 454 setVisibleDirectly(reportVisible, hideAnimOngoing ? null : statsToken); 455 } 456 } 457 458 /** 459 * Sends the local visibility state back to window manager. Needed for legacy adjustForIme. 460 */ setVisibleDirectly(boolean visible, @Nullable ImeTracker.Token statsToken)461 private void setVisibleDirectly(boolean visible, @Nullable ImeTracker.Token statsToken) { 462 mInsetsState.setSourceVisible(InsetsSource.ID_IME, visible); 463 int visibleTypes = visible ? WindowInsets.Type.ime() : 0; 464 try { 465 mWmService.updateDisplayWindowRequestedVisibleTypes(mDisplayId, 466 visibleTypes, WindowInsets.Type.ime(), statsToken); 467 } catch (RemoteException e) { 468 } 469 } 470 setAnimating(boolean imeAnimationOngoing, @Nullable ImeTracker.Token statsToken)471 private void setAnimating(boolean imeAnimationOngoing, 472 @Nullable ImeTracker.Token statsToken) { 473 int animatingTypes = imeAnimationOngoing ? WindowInsets.Type.ime() : 0; 474 try { 475 mWmService.updateDisplayWindowAnimatingTypes(mDisplayId, animatingTypes, 476 statsToken); 477 } catch (RemoteException e) { 478 } 479 } 480 imeTop(float surfaceOffset, float surfacePositionY)481 private int imeTop(float surfaceOffset, float surfacePositionY) { 482 // surfaceOffset is already offset by the surface's top inset, so we need to subtract 483 // the top inset so that the return value is in screen coordinates. 484 return mImeFrame.top + (int) (surfaceOffset - surfacePositionY); 485 } 486 calcIsFloating(InsetsSource imeSource)487 private boolean calcIsFloating(InsetsSource imeSource) { 488 final Rect frame = imeSource.getFrame(); 489 if (frame.height() == 0) { 490 return true; 491 } 492 // Some Floating Input Methods will still report a frame, but the frame is actually 493 // a nav-bar inset created by WM and not part of the IME (despite being reported as 494 // an IME inset). For now, we assume that no non-floating IME will be <= this nav bar 495 // frame height so any reported frame that is <= nav-bar frame height is assumed to 496 // be floating. 497 return frame.height() <= mDisplayController.getDisplayLayout(mDisplayId) 498 .navBarFrameHeight(); 499 } 500 startAnimation(final boolean show, final boolean forceRestart)501 private void startAnimation(final boolean show, final boolean forceRestart) { 502 final var imeSource = mInsetsState.peekSource(InsetsSource.ID_IME); 503 if (imeSource == null || mImeSourceControl == null) { 504 return; 505 } 506 // TODO(b/353463205): For hide: this still has the statsToken from the previous show 507 // request 508 final var statsToken = mImeSourceControl.getImeStatsToken(); 509 510 startAnimation(show, forceRestart, statsToken); 511 } 512 startAnimation(final boolean show, final boolean forceRestart, @SoftInputShowHideReason int reason)513 private void startAnimation(final boolean show, final boolean forceRestart, 514 @SoftInputShowHideReason int reason) { 515 final var imeSource = mInsetsState.peekSource(InsetsSource.ID_IME); 516 if (imeSource == null || mImeSourceControl == null) { 517 return; 518 } 519 final ImeTracker.Token statsToken; 520 if (android.view.inputmethod.Flags.refactorInsetsController() 521 && mImeSourceControl.getImeStatsToken() != null) { 522 statsToken = mImeSourceControl.getImeStatsToken(); 523 } else { 524 statsToken = ImeTracker.forLogging().onStart( 525 show ? ImeTracker.TYPE_SHOW : ImeTracker.TYPE_HIDE, 526 ImeTracker.ORIGIN_WM_SHELL, reason, false /* fromUser */); 527 } 528 startAnimation(show, forceRestart, statsToken); 529 } 530 startAnimation(final boolean show, final boolean forceRestart, @NonNull final ImeTracker.Token statsToken)531 private void startAnimation(final boolean show, final boolean forceRestart, 532 @NonNull final ImeTracker.Token statsToken) { 533 if (mImeSourceControl == null || mImeSourceControl.getLeash() == null) { 534 if (DEBUG) Slog.d(TAG, "No leash available, not starting the animation."); 535 return; 536 } 537 if (android.view.inputmethod.Flags.refactorInsetsController()) { 538 if (!mImeRequestedVisible && show) { 539 // we have a control with leash, but the IME was not requested visible before, 540 // therefore aborting the show animation. 541 Slog.e(TAG, "IME was not requested visible, not starting the show animation."); 542 // TODO(b/353463205) fail statsToken here 543 return; 544 } 545 } 546 final InsetsSource imeSource = mInsetsState.peekSource(InsetsSource.ID_IME); 547 if (imeSource == null) { 548 ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE); 549 return; 550 } 551 final Rect newFrame = imeSource.getFrame(); 552 final boolean isFloating = calcIsFloating(imeSource) && show; 553 if (isFloating) { 554 // This is a "floating" or "expanded" IME, so to get animations, just 555 // pretend the ime has some size just below the screen. 556 mImeFrame.set(newFrame); 557 final int floatingInset = (int) (mDisplayController.getDisplayLayout(mDisplayId) 558 .density() * FLOATING_IME_BOTTOM_INSET); 559 mImeFrame.bottom -= floatingInset; 560 } else if (newFrame.height() != 0) { 561 // Don't set a new frame if it's empty and hiding -- this maintains continuity 562 mImeFrame.set(newFrame); 563 } 564 if (DEBUG) { 565 Slog.d(TAG, "Run startAnim show:" + show + " was:" 566 + (mAnimationDirection == DIRECTION_SHOW ? "SHOW" 567 : (mAnimationDirection == DIRECTION_HIDE ? "HIDE" : "NONE"))); 568 } 569 if ((!forceRestart && (mAnimationDirection == DIRECTION_SHOW && show)) 570 || (mAnimationDirection == DIRECTION_HIDE && !show)) { 571 ImeTracker.forLogging().onCancelled( 572 statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE); 573 return; 574 } 575 boolean seek = false; 576 float seekValue = 0; 577 if (mAnimation != null) { 578 if (mAnimation.isRunning()) { 579 seekValue = (float) mAnimation.getAnimatedValue(); 580 seek = true; 581 } 582 mAnimation.cancel(); 583 } 584 final InsetsSourceControl animatingControl = new InsetsSourceControl(mImeSourceControl); 585 final SurfaceControl animatingLeash = animatingControl.getLeash(); 586 final float defaultY = animatingControl.getSurfacePosition().y; 587 final float x = animatingControl.getSurfacePosition().x; 588 final float hiddenY = defaultY + mImeFrame.height(); 589 final float shownY = defaultY; 590 final float startY = show ? hiddenY : shownY; 591 final float endY = show ? shownY : hiddenY; 592 if (mAnimationDirection == DIRECTION_NONE && mImeShowing && show) { 593 // IME is already showing, so set seek to end 594 seekValue = shownY; 595 seek = true; 596 } 597 mAnimationDirection = show ? DIRECTION_SHOW : DIRECTION_HIDE; 598 updateImeVisibility(show); 599 mAnimation = ValueAnimator.ofFloat(startY, endY); 600 mAnimation.setDuration( 601 show ? ANIMATION_DURATION_SHOW_MS : ANIMATION_DURATION_HIDE_MS); 602 if (seek) { 603 mAnimation.setCurrentFraction((seekValue - startY) / (endY - startY)); 604 } else { 605 // In some cases the value in onAnimationStart is zero, therefore setting it 606 // explicitly to startY 607 mAnimation.setCurrentFraction(0); 608 } 609 610 mAnimation.addUpdateListener(animation -> { 611 SurfaceControl.Transaction t = mTransactionPool.acquire(); 612 float value = (float) animation.getAnimatedValue(); 613 t.setPosition(animatingLeash, x, value); 614 final float alpha = (mAnimateAlpha || isFloating) 615 ? (value - hiddenY) / (shownY - hiddenY) : 1f; 616 t.setAlpha(animatingLeash, alpha); 617 dispatchPositionChanged(mDisplayId, imeTop(value, defaultY), t); 618 t.apply(); 619 mTransactionPool.release(t); 620 }); 621 mAnimation.setInterpolator(INTERPOLATOR); 622 ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE); 623 mAnimation.addListener(new AnimatorListenerAdapter() { 624 private boolean mCancelled = false; 625 @NonNull 626 private final ImeTracker.Token mStatsToken = statsToken; 627 628 @Override 629 public void onAnimationStart(Animator animation) { 630 ValueAnimator valueAnimator = (ValueAnimator) animation; 631 float value = (float) valueAnimator.getAnimatedValue(); 632 SurfaceControl.Transaction t = mTransactionPool.acquire(); 633 t.setPosition(animatingLeash, x, value); 634 if (DEBUG) { 635 Slog.d(TAG, "onAnimationStart d:" + mDisplayId + " top:" 636 + imeTop(hiddenY, defaultY) + "->" + imeTop(shownY, defaultY) 637 + " showing:" + (mAnimationDirection == DIRECTION_SHOW)); 638 } 639 if (android.view.inputmethod.Flags.reportAnimatingInsetsTypes()) { 640 // Updating the animatingTypes when starting the animation is not the 641 // trigger to show the IME. Thus, not sending the statsToken here. 642 setAnimating(true /* imeAnimationOngoing */, null /* statsToken */); 643 } 644 int flags = dispatchStartPositioning(mDisplayId, imeTop(hiddenY, defaultY), 645 imeTop(shownY, defaultY), mAnimationDirection == DIRECTION_SHOW, 646 isFloating, t); 647 mAnimateAlpha = (flags & ImePositionProcessor.IME_ANIMATION_NO_ALPHA) == 0; 648 final float alpha = (mAnimateAlpha || isFloating) 649 ? (value - hiddenY) / (shownY - hiddenY) 650 : 1.f; 651 t.setAlpha(animatingLeash, alpha); 652 if (mAnimationDirection == DIRECTION_SHOW) { 653 ImeTracker.forLogging().onProgress(mStatsToken, 654 ImeTracker.PHASE_WM_ANIMATION_RUNNING); 655 t.show(animatingLeash); 656 } 657 if (DEBUG_IME_VISIBILITY) { 658 EventLog.writeEvent(IMF_IME_REMOTE_ANIM_START, 659 mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE, 660 mDisplayId, mAnimationDirection, alpha, value, endY, 661 Objects.toString(animatingLeash), 662 Objects.toString(animatingControl.getInsetsHint()), 663 Objects.toString(animatingControl.getSurfacePosition()), 664 Objects.toString(mImeFrame)); 665 } 666 t.apply(); 667 mTransactionPool.release(t); 668 } 669 670 @Override 671 public void onAnimationCancel(Animator animation) { 672 mCancelled = true; 673 if (DEBUG_IME_VISIBILITY) { 674 EventLog.writeEvent(IMF_IME_REMOTE_ANIM_CANCEL, 675 mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE, 676 mDisplayId, 677 Objects.toString(animatingControl.getInsetsHint())); 678 } 679 } 680 681 @Override 682 public void onAnimationEnd(Animator animation) { 683 if (DEBUG) Slog.d(TAG, "onAnimationEnd " + mCancelled); 684 SurfaceControl.Transaction t = mTransactionPool.acquire(); 685 if (!mCancelled) { 686 t.setPosition(animatingLeash, x, endY); 687 t.setAlpha(animatingLeash, 1.f); 688 } 689 if (!android.view.inputmethod.Flags.refactorInsetsController()) { 690 dispatchEndPositioning(mDisplayId, mCancelled, t); 691 } else if (android.view.inputmethod.Flags.reportAnimatingInsetsTypes()) { 692 setAnimating(false /* imeAnimationOngoing */, 693 mAnimationDirection == DIRECTION_HIDE ? statsToken : null); 694 } 695 if (mAnimationDirection == DIRECTION_HIDE && !mCancelled) { 696 ImeTracker.forLogging().onProgress(mStatsToken, 697 ImeTracker.PHASE_WM_ANIMATION_RUNNING); 698 t.hide(animatingLeash); 699 if (!android.view.inputmethod.Flags.refactorInsetsController()) { 700 removeImeSurface(mDisplayId); 701 } 702 if (android.view.inputmethod.Flags.refactorInsetsController()) { 703 // Updating the client visibility will not hide the IME, unless it is 704 // not animating anymore. Thus, not sending a statsToken here, but 705 // only later when we're updating the animatingTypes. 706 setVisibleDirectly(false /* visible */, 707 !android.view.inputmethod.Flags.reportAnimatingInsetsTypes() 708 ? statsToken : null); 709 } 710 if (!android.view.inputmethod.Flags.refactorInsetsController()) { 711 ImeTracker.forLogging().onHidden(mStatsToken); 712 } 713 } else if (mAnimationDirection == DIRECTION_SHOW && !mCancelled) { 714 ImeTracker.forLogging().onShown(mStatsToken); 715 } else if (mCancelled) { 716 ImeTracker.forLogging().onCancelled(mStatsToken, 717 ImeTracker.PHASE_WM_ANIMATION_RUNNING); 718 } 719 if (android.view.inputmethod.Flags.refactorInsetsController()) { 720 // In split screen, we also set {@link 721 // WindowContainer#mExcludeInsetsTypes} but this should only happen after 722 // the IME client visibility was set. Otherwise the insets will we 723 // dispatched too early, and we get a flicker. Thus, only dispatching it 724 // after reporting that the IME is hidden to system server. 725 dispatchEndPositioning(mDisplayId, mCancelled, t); 726 } 727 if (DEBUG_IME_VISIBILITY) { 728 EventLog.writeEvent(IMF_IME_REMOTE_ANIM_END, 729 mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE, 730 mDisplayId, mAnimationDirection, endY, 731 Objects.toString(animatingLeash), 732 Objects.toString(animatingControl.getInsetsHint()), 733 Objects.toString(animatingControl.getSurfacePosition()), 734 Objects.toString(mImeFrame)); 735 } 736 t.apply(); 737 mTransactionPool.release(t); 738 739 mAnimationDirection = DIRECTION_NONE; 740 mAnimation = null; 741 animatingControl.release(SurfaceControl::release); 742 } 743 }); 744 if (!android.view.inputmethod.Flags.refactorInsetsController() && !show) { 745 // When going away, queue up insets change first, otherwise any bounds changes 746 // can have a "flicker" of ime-provided insets. 747 setVisibleDirectly(false /* visible */, null /* statsToken */); 748 } 749 mAnimation.start(); 750 if (!android.view.inputmethod.Flags.refactorInsetsController() && show) { 751 // When showing away, queue up insets change last, otherwise any bounds changes 752 // can have a "flicker" of ime-provided insets. 753 setVisibleDirectly(true /* visible */, null /* statsToken */); 754 } 755 } 756 updateImeVisibility(boolean isShowing)757 private void updateImeVisibility(boolean isShowing) { 758 if (mImeShowing != isShowing) { 759 mImeShowing = isShowing; 760 dispatchVisibilityChanged(mDisplayId, isShowing); 761 } 762 } 763 764 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) getImeSourceControl()765 public InsetsSourceControl getImeSourceControl() { 766 return mImeSourceControl; 767 } 768 } 769 removeImeSurface(int displayId)770 void removeImeSurface(int displayId) { 771 // Remove the IME surface to make the insets invisible for 772 // non-client controlled insets. 773 InputMethodManagerGlobal.removeImeSurface(displayId, 774 e -> Slog.e(TAG, "Failed to remove IME surface.", e)); 775 } 776 777 /** 778 * Allows other things to synchronize with the ime position 779 */ 780 public interface ImePositionProcessor { 781 782 /** Default animation flags. */ 783 int IME_ANIMATION_DEFAULT = 0; 784 785 /** 786 * Indicates that ime shouldn't animate alpha. It will always be opaque. Used when stuff 787 * behind the IME shouldn't be visible (for example during split-screen adjustment where 788 * there is nothing behind the ime). 789 */ 790 int IME_ANIMATION_NO_ALPHA = 1; 791 792 /** @hide */ 793 @IntDef(prefix = {"IME_ANIMATION_"}, value = { 794 IME_ANIMATION_DEFAULT, 795 IME_ANIMATION_NO_ALPHA, 796 }) 797 @interface ImeAnimationFlags { 798 } 799 800 /** 801 * Called when the IME was requested by an app 802 * 803 * @param isRequested {@code true} if the IME was requested to be visible 804 */ onImeRequested(int displayId, boolean isRequested)805 default void onImeRequested(int displayId, boolean isRequested) { 806 } 807 808 /** 809 * Called when the IME position is starting to animate. 810 * 811 * @param hiddenTop The y position of the top of the IME surface when it is hidden. 812 * @param shownTop The y position of the top of the IME surface when it is shown. 813 * @param showing {@code true} when we are animating from hidden to shown, {@code false} 814 * when animating from shown to hidden. 815 * @param isFloating {@code true} when the ime is a floating ime (doesn't inset). 816 * @return flags that may alter how ime itself is animated (eg. no-alpha). 817 */ 818 @ImeAnimationFlags onImeStartPositioning(int displayId, int hiddenTop, int shownTop, boolean showing, boolean isFloating, SurfaceControl.Transaction t)819 default int onImeStartPositioning(int displayId, int hiddenTop, int shownTop, 820 boolean showing, boolean isFloating, SurfaceControl.Transaction t) { 821 return IME_ANIMATION_DEFAULT; 822 } 823 824 /** 825 * Called when the ime position changed. This is expected to be a synchronous call on the 826 * animation thread. Operations can be added to the transaction to be applied in sync. 827 * 828 * @param imeTop The current y position of the top of the IME surface. 829 */ onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t)830 default void onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t) { 831 } 832 833 /** 834 * Called when the IME position is done animating. 835 * 836 * @param cancel {@code true} if this was cancelled. This implies another start is coming. 837 */ onImeEndPositioning(int displayId, boolean cancel, SurfaceControl.Transaction t)838 default void onImeEndPositioning(int displayId, boolean cancel, 839 SurfaceControl.Transaction t) { 840 } 841 842 /** 843 * Called when the IME control target changed. So that the processor can restore its 844 * adjusted layout when the IME insets is not controlling by the current controller anymore. 845 * 846 * @param controlling indicates whether the current controller is controlling IME insets. 847 */ onImeControlTargetChanged(int displayId, boolean controlling)848 default void onImeControlTargetChanged(int displayId, boolean controlling) { 849 } 850 851 /** 852 * Called when the IME visibility changed. 853 * 854 * @param isShowing {@code true} if the IME is shown. 855 */ onImeVisibilityChanged(int displayId, boolean isShowing)856 default void onImeVisibilityChanged(int displayId, boolean isShowing) { 857 858 } 859 } 860 haveSameLeash(InsetsSourceControl a, InsetsSourceControl b)861 private static boolean haveSameLeash(InsetsSourceControl a, InsetsSourceControl b) { 862 if (a == b) { 863 return true; 864 } 865 if (a == null || b == null) { 866 return false; 867 } 868 if (a.getLeash() == b.getLeash()) { 869 return true; 870 } 871 if (a.getLeash() == null || b.getLeash() == null) { 872 return false; 873 } 874 return a.getLeash().isSameSurface(b.getLeash()); 875 } 876 } 877