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