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.systemui.wm; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.ValueAnimator; 22 import android.annotation.IntDef; 23 import android.content.Context; 24 import android.content.res.Configuration; 25 import android.graphics.Point; 26 import android.graphics.Rect; 27 import android.os.RemoteException; 28 import android.os.ServiceManager; 29 import android.util.Slog; 30 import android.util.SparseArray; 31 import android.view.IDisplayWindowInsetsController; 32 import android.view.IWindowManager; 33 import android.view.InsetsSource; 34 import android.view.InsetsSourceControl; 35 import android.view.InsetsState; 36 import android.view.Surface; 37 import android.view.SurfaceControl; 38 import android.view.WindowInsets; 39 import android.view.animation.Interpolator; 40 import android.view.animation.PathInterpolator; 41 42 import androidx.annotation.BinderThread; 43 import androidx.annotation.VisibleForTesting; 44 45 import com.android.internal.view.IInputMethodManager; 46 import com.android.systemui.TransactionPool; 47 import com.android.systemui.dagger.qualifiers.Main; 48 49 import java.util.ArrayList; 50 import java.util.concurrent.Executor; 51 52 import javax.inject.Inject; 53 import javax.inject.Singleton; 54 55 /** 56 * Manages IME control at the display-level. This occurs when IME comes up in multi-window mode. 57 */ 58 @Singleton 59 public class DisplayImeController implements DisplayController.OnDisplaysChangedListener { 60 private static final String TAG = "DisplayImeController"; 61 62 private static final boolean DEBUG = false; 63 64 // NOTE: All these constants came from InsetsController. 65 public static final int ANIMATION_DURATION_SHOW_MS = 275; 66 public static final int ANIMATION_DURATION_HIDE_MS = 340; 67 public static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f); 68 private static final int DIRECTION_NONE = 0; 69 private static final int DIRECTION_SHOW = 1; 70 private static final int DIRECTION_HIDE = 2; 71 private static final int FLOATING_IME_BOTTOM_INSET = -80; 72 73 protected final IWindowManager mWmService; 74 protected final Executor mMainExecutor; 75 final TransactionPool mTransactionPool; 76 final DisplayController mDisplayController; 77 78 final SparseArray<PerDisplay> mImePerDisplay = new SparseArray<>(); 79 80 final ArrayList<ImePositionProcessor> mPositionProcessors = new ArrayList<>(); 81 82 @Inject DisplayImeController(IWindowManager wmService, DisplayController displayController, @Main Executor mainExecutor, TransactionPool transactionPool)83 public DisplayImeController(IWindowManager wmService, DisplayController displayController, 84 @Main Executor mainExecutor, TransactionPool transactionPool) { 85 mWmService = wmService; 86 mMainExecutor = mainExecutor; 87 mTransactionPool = transactionPool; 88 mDisplayController = displayController; 89 displayController.addDisplayWindowListener(this); 90 } 91 92 @Override onDisplayAdded(int displayId)93 public void onDisplayAdded(int displayId) { 94 // Add's a system-ui window-manager specifically for ime. This type is special because 95 // WM will defer IME inset handling to it in multi-window scenarious. 96 PerDisplay pd = new PerDisplay(displayId, 97 mDisplayController.getDisplayLayout(displayId).rotation()); 98 pd.register(); 99 mImePerDisplay.put(displayId, pd); 100 } 101 102 @Override onDisplayConfigurationChanged(int displayId, Configuration newConfig)103 public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { 104 PerDisplay pd = mImePerDisplay.get(displayId); 105 if (pd == null) { 106 return; 107 } 108 if (mDisplayController.getDisplayLayout(displayId).rotation() 109 != pd.mRotation && isImeShowing(displayId)) { 110 pd.startAnimation(true, false /* forceRestart */); 111 } 112 } 113 114 @Override onDisplayRemoved(int displayId)115 public void onDisplayRemoved(int displayId) { 116 try { 117 mWmService.setDisplayWindowInsetsController(displayId, null); 118 } catch (RemoteException e) { 119 Slog.w(TAG, "Unable to remove insets controller on display " + displayId); 120 } 121 mImePerDisplay.remove(displayId); 122 } 123 isImeShowing(int displayId)124 private boolean isImeShowing(int displayId) { 125 PerDisplay pd = mImePerDisplay.get(displayId); 126 if (pd == null) { 127 return false; 128 } 129 final InsetsSource imeSource = pd.mInsetsState.getSource(InsetsState.ITYPE_IME); 130 return imeSource != null && pd.mImeSourceControl != null && imeSource.isVisible(); 131 } 132 dispatchPositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t)133 private void dispatchPositionChanged(int displayId, int imeTop, 134 SurfaceControl.Transaction t) { 135 synchronized (mPositionProcessors) { 136 for (ImePositionProcessor pp : mPositionProcessors) { 137 pp.onImePositionChanged(displayId, imeTop, t); 138 } 139 } 140 } 141 142 @ImePositionProcessor.ImeAnimationFlags dispatchStartPositioning(int displayId, int hiddenTop, int shownTop, boolean show, boolean isFloating, SurfaceControl.Transaction t)143 private int dispatchStartPositioning(int displayId, int hiddenTop, int shownTop, 144 boolean show, boolean isFloating, SurfaceControl.Transaction t) { 145 synchronized (mPositionProcessors) { 146 int flags = 0; 147 for (ImePositionProcessor pp : mPositionProcessors) { 148 flags |= pp.onImeStartPositioning( 149 displayId, hiddenTop, shownTop, show, isFloating, t); 150 } 151 return flags; 152 } 153 } 154 dispatchEndPositioning(int displayId, boolean cancel, SurfaceControl.Transaction t)155 private void dispatchEndPositioning(int displayId, boolean cancel, 156 SurfaceControl.Transaction t) { 157 synchronized (mPositionProcessors) { 158 for (ImePositionProcessor pp : mPositionProcessors) { 159 pp.onImeEndPositioning(displayId, cancel, t); 160 } 161 } 162 } 163 164 /** 165 * Adds an {@link ImePositionProcessor} to be called during ime position updates. 166 */ addPositionProcessor(ImePositionProcessor processor)167 public void addPositionProcessor(ImePositionProcessor processor) { 168 synchronized (mPositionProcessors) { 169 if (mPositionProcessors.contains(processor)) { 170 return; 171 } 172 mPositionProcessors.add(processor); 173 } 174 } 175 176 /** 177 * Removes an {@link ImePositionProcessor} to be called during ime position updates. 178 */ removePositionProcessor(ImePositionProcessor processor)179 public void removePositionProcessor(ImePositionProcessor processor) { 180 synchronized (mPositionProcessors) { 181 mPositionProcessors.remove(processor); 182 } 183 } 184 185 /** An implementation of {@link IDisplayWindowInsetsController} for a given display id. */ 186 public class PerDisplay { 187 final int mDisplayId; 188 final InsetsState mInsetsState = new InsetsState(); 189 protected final DisplayWindowInsetsControllerImpl mInsetsControllerImpl = 190 new DisplayWindowInsetsControllerImpl(); 191 InsetsSourceControl mImeSourceControl = null; 192 int mAnimationDirection = DIRECTION_NONE; 193 ValueAnimator mAnimation = null; 194 int mRotation = Surface.ROTATION_0; 195 boolean mImeShowing = false; 196 final Rect mImeFrame = new Rect(); 197 boolean mAnimateAlpha = true; 198 PerDisplay(int displayId, int initialRotation)199 PerDisplay(int displayId, int initialRotation) { 200 mDisplayId = displayId; 201 mRotation = initialRotation; 202 } 203 register()204 public void register() { 205 try { 206 mWmService.setDisplayWindowInsetsController(mDisplayId, mInsetsControllerImpl); 207 } catch (RemoteException e) { 208 Slog.w(TAG, "Unable to set insets controller on display " + mDisplayId); 209 } 210 } 211 insetsChanged(InsetsState insetsState)212 public void insetsChanged(InsetsState insetsState) { 213 if (mInsetsState.equals(insetsState)) { 214 return; 215 } 216 217 mImeShowing = insetsState.getSourceOrDefaultVisibility(InsetsState.ITYPE_IME); 218 219 final InsetsSource newSource = insetsState.getSource(InsetsState.ITYPE_IME); 220 final Rect newFrame = newSource.getFrame(); 221 final Rect oldFrame = mInsetsState.getSource(InsetsState.ITYPE_IME).getFrame(); 222 223 mInsetsState.set(insetsState, true /* copySources */); 224 if (mImeShowing && !newFrame.equals(oldFrame) && newSource.isVisible()) { 225 if (DEBUG) Slog.d(TAG, "insetsChanged when IME showing, restart animation"); 226 startAnimation(mImeShowing, true /* forceRestart */); 227 } 228 } 229 insetsControlChanged(InsetsState insetsState, InsetsSourceControl[] activeControls)230 public void insetsControlChanged(InsetsState insetsState, 231 InsetsSourceControl[] activeControls) { 232 insetsChanged(insetsState); 233 if (activeControls != null) { 234 for (InsetsSourceControl activeControl : activeControls) { 235 if (activeControl == null) { 236 continue; 237 } 238 if (activeControl.getType() == InsetsState.ITYPE_IME) { 239 final Point lastSurfacePosition = mImeSourceControl != null 240 ? mImeSourceControl.getSurfacePosition() : null; 241 final boolean positionChanged = 242 !activeControl.getSurfacePosition().equals(lastSurfacePosition); 243 final boolean leashChanged = 244 !haveSameLeash(mImeSourceControl, activeControl); 245 mImeSourceControl = activeControl; 246 if (mAnimation != null) { 247 if (positionChanged) { 248 startAnimation(mImeShowing, true /* forceRestart */); 249 } 250 } else { 251 if (leashChanged) { 252 applyVisibilityToLeash(); 253 } 254 if (!mImeShowing) { 255 removeImeSurface(); 256 } 257 } 258 } 259 } 260 } 261 } 262 applyVisibilityToLeash()263 private void applyVisibilityToLeash() { 264 SurfaceControl leash = mImeSourceControl.getLeash(); 265 if (leash != null) { 266 SurfaceControl.Transaction t = mTransactionPool.acquire(); 267 if (mImeShowing) { 268 t.show(leash); 269 } else { 270 t.hide(leash); 271 } 272 t.apply(); 273 mTransactionPool.release(t); 274 } 275 } 276 showInsets(int types, boolean fromIme)277 public void showInsets(int types, boolean fromIme) { 278 if ((types & WindowInsets.Type.ime()) == 0) { 279 return; 280 } 281 if (DEBUG) Slog.d(TAG, "Got showInsets for ime"); 282 startAnimation(true /* show */, false /* forceRestart */); 283 } 284 hideInsets(int types, boolean fromIme)285 public void hideInsets(int types, boolean fromIme) { 286 if ((types & WindowInsets.Type.ime()) == 0) { 287 return; 288 } 289 if (DEBUG) Slog.d(TAG, "Got hideInsets for ime"); 290 startAnimation(false /* show */, false /* forceRestart */); 291 } 292 topFocusedWindowChanged(String packageName)293 public void topFocusedWindowChanged(String packageName) { 294 // no-op 295 } 296 297 /** 298 * Sends the local visibility state back to window manager. Needed for legacy adjustForIme. 299 */ setVisibleDirectly(boolean visible)300 private void setVisibleDirectly(boolean visible) { 301 mInsetsState.getSource(InsetsState.ITYPE_IME).setVisible(visible); 302 try { 303 mWmService.modifyDisplayWindowInsets(mDisplayId, mInsetsState); 304 } catch (RemoteException e) { 305 } 306 } 307 imeTop(float surfaceOffset)308 private int imeTop(float surfaceOffset) { 309 return mImeFrame.top + (int) surfaceOffset; 310 } 311 calcIsFloating(InsetsSource imeSource)312 private boolean calcIsFloating(InsetsSource imeSource) { 313 final Rect frame = imeSource.getFrame(); 314 if (frame.height() == 0) { 315 return true; 316 } 317 // Some Floating Input Methods will still report a frame, but the frame is actually 318 // a nav-bar inset created by WM and not part of the IME (despite being reported as 319 // an IME inset). For now, we assume that no non-floating IME will be <= this nav bar 320 // frame height so any reported frame that is <= nav-bar frame height is assumed to 321 // be floating. 322 return frame.height() <= mDisplayController.getDisplayLayout(mDisplayId) 323 .navBarFrameHeight(); 324 } 325 startAnimation(final boolean show, final boolean forceRestart)326 private void startAnimation(final boolean show, final boolean forceRestart) { 327 final InsetsSource imeSource = mInsetsState.getSource(InsetsState.ITYPE_IME); 328 if (imeSource == null || mImeSourceControl == null) { 329 return; 330 } 331 final Rect newFrame = imeSource.getFrame(); 332 final boolean isFloating = calcIsFloating(imeSource) && show; 333 if (isFloating) { 334 // This is a "floating" or "expanded" IME, so to get animations, just 335 // pretend the ime has some size just below the screen. 336 mImeFrame.set(newFrame); 337 final int floatingInset = (int) ( 338 mDisplayController.getDisplayLayout(mDisplayId).density() 339 * FLOATING_IME_BOTTOM_INSET); 340 mImeFrame.bottom -= floatingInset; 341 } else if (newFrame.height() != 0) { 342 // Don't set a new frame if it's empty and hiding -- this maintains continuity 343 mImeFrame.set(newFrame); 344 } 345 if (DEBUG) { 346 Slog.d(TAG, "Run startAnim show:" + show + " was:" 347 + (mAnimationDirection == DIRECTION_SHOW ? "SHOW" 348 : (mAnimationDirection == DIRECTION_HIDE ? "HIDE" : "NONE"))); 349 } 350 if (!forceRestart && (mAnimationDirection == DIRECTION_SHOW && show) 351 || (mAnimationDirection == DIRECTION_HIDE && !show)) { 352 return; 353 } 354 boolean seek = false; 355 float seekValue = 0; 356 if (mAnimation != null) { 357 if (mAnimation.isRunning()) { 358 seekValue = (float) mAnimation.getAnimatedValue(); 359 seek = true; 360 } 361 mAnimation.cancel(); 362 } 363 final float defaultY = mImeSourceControl.getSurfacePosition().y; 364 final float x = mImeSourceControl.getSurfacePosition().x; 365 final float hiddenY = defaultY + mImeFrame.height(); 366 final float shownY = defaultY; 367 final float startY = show ? hiddenY : shownY; 368 final float endY = show ? shownY : hiddenY; 369 if (mAnimationDirection == DIRECTION_NONE && mImeShowing && show) { 370 // IME is already showing, so set seek to end 371 seekValue = shownY; 372 seek = true; 373 } 374 mAnimationDirection = show ? DIRECTION_SHOW : DIRECTION_HIDE; 375 mImeShowing = show; 376 mAnimation = ValueAnimator.ofFloat(startY, endY); 377 mAnimation.setDuration( 378 show ? ANIMATION_DURATION_SHOW_MS : ANIMATION_DURATION_HIDE_MS); 379 if (seek) { 380 mAnimation.setCurrentFraction((seekValue - startY) / (endY - startY)); 381 } 382 383 mAnimation.addUpdateListener(animation -> { 384 SurfaceControl.Transaction t = mTransactionPool.acquire(); 385 float value = (float) animation.getAnimatedValue(); 386 t.setPosition(mImeSourceControl.getLeash(), x, value); 387 final float alpha = (mAnimateAlpha || isFloating) 388 ? (value - hiddenY) / (shownY - hiddenY) : 1.f; 389 t.setAlpha(mImeSourceControl.getLeash(), alpha); 390 dispatchPositionChanged(mDisplayId, imeTop(value), t); 391 t.apply(); 392 mTransactionPool.release(t); 393 }); 394 mAnimation.setInterpolator(INTERPOLATOR); 395 mAnimation.addListener(new AnimatorListenerAdapter() { 396 private boolean mCancelled = false; 397 @Override 398 public void onAnimationStart(Animator animation) { 399 SurfaceControl.Transaction t = mTransactionPool.acquire(); 400 t.setPosition(mImeSourceControl.getLeash(), x, startY); 401 if (DEBUG) { 402 Slog.d(TAG, "onAnimationStart d:" + mDisplayId + " top:" 403 + imeTop(hiddenY) + "->" + imeTop(shownY) 404 + " showing:" + (mAnimationDirection == DIRECTION_SHOW)); 405 } 406 int flags = dispatchStartPositioning(mDisplayId, imeTop(hiddenY), 407 imeTop(shownY), mAnimationDirection == DIRECTION_SHOW, isFloating, t); 408 mAnimateAlpha = (flags & ImePositionProcessor.IME_ANIMATION_NO_ALPHA) == 0; 409 final float alpha = (mAnimateAlpha || isFloating) 410 ? (startY - hiddenY) / (shownY - hiddenY) 411 : 1.f; 412 t.setAlpha(mImeSourceControl.getLeash(), alpha); 413 if (mAnimationDirection == DIRECTION_SHOW) { 414 t.show(mImeSourceControl.getLeash()); 415 } 416 t.apply(); 417 mTransactionPool.release(t); 418 } 419 @Override 420 public void onAnimationCancel(Animator animation) { 421 mCancelled = true; 422 } 423 @Override 424 public void onAnimationEnd(Animator animation) { 425 if (DEBUG) Slog.d(TAG, "onAnimationEnd " + mCancelled); 426 SurfaceControl.Transaction t = mTransactionPool.acquire(); 427 if (!mCancelled) { 428 t.setPosition(mImeSourceControl.getLeash(), x, endY); 429 t.setAlpha(mImeSourceControl.getLeash(), 1.f); 430 } 431 dispatchEndPositioning(mDisplayId, mCancelled, t); 432 if (mAnimationDirection == DIRECTION_HIDE && !mCancelled) { 433 t.hide(mImeSourceControl.getLeash()); 434 removeImeSurface(); 435 } 436 t.apply(); 437 mTransactionPool.release(t); 438 439 mAnimationDirection = DIRECTION_NONE; 440 mAnimation = null; 441 } 442 }); 443 if (!show) { 444 // When going away, queue up insets change first, otherwise any bounds changes 445 // can have a "flicker" of ime-provided insets. 446 setVisibleDirectly(false /* visible */); 447 } 448 mAnimation.start(); 449 if (show) { 450 // When showing away, queue up insets change last, otherwise any bounds changes 451 // can have a "flicker" of ime-provided insets. 452 setVisibleDirectly(true /* visible */); 453 } 454 } 455 456 @VisibleForTesting 457 @BinderThread 458 public class DisplayWindowInsetsControllerImpl 459 extends IDisplayWindowInsetsController.Stub { 460 @Override topFocusedWindowChanged(String packageName)461 public void topFocusedWindowChanged(String packageName) throws RemoteException { 462 mMainExecutor.execute(() -> { 463 PerDisplay.this.topFocusedWindowChanged(packageName); 464 }); 465 } 466 467 @Override insetsChanged(InsetsState insetsState)468 public void insetsChanged(InsetsState insetsState) throws RemoteException { 469 mMainExecutor.execute(() -> { 470 PerDisplay.this.insetsChanged(insetsState); 471 }); 472 } 473 474 @Override insetsControlChanged(InsetsState insetsState, InsetsSourceControl[] activeControls)475 public void insetsControlChanged(InsetsState insetsState, 476 InsetsSourceControl[] activeControls) throws RemoteException { 477 mMainExecutor.execute(() -> { 478 PerDisplay.this.insetsControlChanged(insetsState, activeControls); 479 }); 480 } 481 482 @Override showInsets(int types, boolean fromIme)483 public void showInsets(int types, boolean fromIme) throws RemoteException { 484 mMainExecutor.execute(() -> { 485 PerDisplay.this.showInsets(types, fromIme); 486 }); 487 } 488 489 @Override hideInsets(int types, boolean fromIme)490 public void hideInsets(int types, boolean fromIme) throws RemoteException { 491 mMainExecutor.execute(() -> { 492 PerDisplay.this.hideInsets(types, fromIme); 493 }); 494 } 495 } 496 } 497 removeImeSurface()498 void removeImeSurface() { 499 final IInputMethodManager imms = getImms(); 500 if (imms != null) { 501 try { 502 // Remove the IME surface to make the insets invisible for 503 // non-client controlled insets. 504 imms.removeImeSurface(); 505 } catch (RemoteException e) { 506 Slog.e(TAG, "Failed to remove IME surface.", e); 507 } 508 } 509 } 510 511 /** 512 * Allows other things to synchronize with the ime position 513 */ 514 public interface ImePositionProcessor { 515 /** 516 * Indicates that ime shouldn't animate alpha. It will always be opaque. Used when stuff 517 * behind the IME shouldn't be visible (for example during split-screen adjustment where 518 * there is nothing behind the ime). 519 */ 520 int IME_ANIMATION_NO_ALPHA = 1; 521 522 /** @hide */ 523 @IntDef(prefix = { "IME_ANIMATION_" }, value = { 524 IME_ANIMATION_NO_ALPHA, 525 }) 526 @interface ImeAnimationFlags {} 527 528 /** 529 * Called when the IME position is starting to animate. 530 * 531 * @param hiddenTop The y position of the top of the IME surface when it is hidden. 532 * @param shownTop The y position of the top of the IME surface when it is shown. 533 * @param showing {@code true} when we are animating from hidden to shown, {@code false} 534 * when animating from shown to hidden. 535 * @param isFloating {@code true} when the ime is a floating ime (doesn't inset). 536 * @return flags that may alter how ime itself is animated (eg. no-alpha). 537 */ 538 @ImeAnimationFlags onImeStartPositioning(int displayId, int hiddenTop, int shownTop, boolean showing, boolean isFloating, SurfaceControl.Transaction t)539 default int onImeStartPositioning(int displayId, int hiddenTop, int shownTop, 540 boolean showing, boolean isFloating, SurfaceControl.Transaction t) { 541 return 0; 542 } 543 544 /** 545 * Called when the ime position changed. This is expected to be a synchronous call on the 546 * animation thread. Operations can be added to the transaction to be applied in sync. 547 * 548 * @param imeTop The current y position of the top of the IME surface. 549 */ onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t)550 default void onImePositionChanged(int displayId, int imeTop, 551 SurfaceControl.Transaction t) {} 552 553 /** 554 * Called when the IME position is done animating. 555 * 556 * @param cancel {@code true} if this was cancelled. This implies another start is coming. 557 */ onImeEndPositioning(int displayId, boolean cancel, SurfaceControl.Transaction t)558 default void onImeEndPositioning(int displayId, boolean cancel, 559 SurfaceControl.Transaction t) {} 560 } 561 getImms()562 public IInputMethodManager getImms() { 563 return IInputMethodManager.Stub.asInterface( 564 ServiceManager.getService(Context.INPUT_METHOD_SERVICE)); 565 } 566 haveSameLeash(InsetsSourceControl a, InsetsSourceControl b)567 private static boolean haveSameLeash(InsetsSourceControl a, InsetsSourceControl b) { 568 if (a == b) { 569 return true; 570 } 571 if (a == null || b == null) { 572 return false; 573 } 574 if (a.getLeash() == b.getLeash()) { 575 return true; 576 } 577 if (a.getLeash() == null || b.getLeash() == null) { 578 return false; 579 } 580 return a.getLeash().isSameSurface(b.getLeash()); 581 } 582 } 583