1 /* 2 * Copyright (C) 2018 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.view; 18 19 import static android.view.InsetsState.TYPE_IME; 20 import static android.view.InsetsState.toPublicType; 21 import static android.view.WindowInsets.Type.all; 22 23 import android.animation.Animator; 24 import android.animation.AnimatorListenerAdapter; 25 import android.animation.ObjectAnimator; 26 import android.animation.TypeEvaluator; 27 import android.annotation.IntDef; 28 import android.annotation.NonNull; 29 import android.graphics.Insets; 30 import android.graphics.Rect; 31 import android.os.RemoteException; 32 import android.util.ArraySet; 33 import android.util.Log; 34 import android.util.Pair; 35 import android.util.Property; 36 import android.util.SparseArray; 37 import android.view.InsetsSourceConsumer.ShowResult; 38 import android.view.InsetsState.InternalInsetType; 39 import android.view.SurfaceControl.Transaction; 40 import android.view.WindowInsets.Type; 41 import android.view.WindowInsets.Type.InsetType; 42 import android.view.animation.Interpolator; 43 import android.view.animation.PathInterpolator; 44 45 import com.android.internal.annotations.VisibleForTesting; 46 47 import java.io.PrintWriter; 48 import java.util.ArrayList; 49 50 /** 51 * Implements {@link WindowInsetsController} on the client. 52 * @hide 53 */ 54 public class InsetsController implements WindowInsetsController { 55 56 private static final int ANIMATION_DURATION_SHOW_MS = 275; 57 private static final int ANIMATION_DURATION_HIDE_MS = 340; 58 private static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f); 59 private static final int DIRECTION_NONE = 0; 60 private static final int DIRECTION_SHOW = 1; 61 private static final int DIRECTION_HIDE = 2; 62 63 @IntDef ({DIRECTION_NONE, DIRECTION_SHOW, DIRECTION_HIDE}) 64 private @interface AnimationDirection{} 65 66 /** 67 * Translation animation evaluator. 68 */ 69 private static TypeEvaluator<Insets> sEvaluator = (fraction, startValue, endValue) -> Insets.of( 70 0, 71 (int) (startValue.top + fraction * (endValue.top - startValue.top)), 72 0, 73 (int) (startValue.bottom + fraction * (endValue.bottom - startValue.bottom))); 74 75 /** 76 * Linear animation property 77 */ 78 private static class InsetsProperty extends Property<WindowInsetsAnimationController, Insets> { InsetsProperty()79 InsetsProperty() { 80 super(Insets.class, "Insets"); 81 } 82 83 @Override get(WindowInsetsAnimationController object)84 public Insets get(WindowInsetsAnimationController object) { 85 return object.getCurrentInsets(); 86 } 87 @Override set(WindowInsetsAnimationController object, Insets value)88 public void set(WindowInsetsAnimationController object, Insets value) { 89 object.changeInsets(value); 90 } 91 } 92 93 private final String TAG = "InsetsControllerImpl"; 94 95 private final InsetsState mState = new InsetsState(); 96 private final InsetsState mTmpState = new InsetsState(); 97 98 private final Rect mFrame = new Rect(); 99 private final SparseArray<InsetsSourceConsumer> mSourceConsumers = new SparseArray<>(); 100 private final ViewRootImpl mViewRoot; 101 102 private final SparseArray<InsetsSourceControl> mTmpControlArray = new SparseArray<>(); 103 private final ArrayList<InsetsAnimationControlImpl> mAnimationControls = new ArrayList<>(); 104 private final ArrayList<InsetsAnimationControlImpl> mTmpFinishedControls = new ArrayList<>(); 105 private WindowInsets mLastInsets; 106 107 private boolean mAnimCallbackScheduled; 108 109 private final Runnable mAnimCallback; 110 111 private final Rect mLastLegacyContentInsets = new Rect(); 112 private final Rect mLastLegacyStableInsets = new Rect(); 113 private @AnimationDirection int mAnimationDirection; 114 115 private int mPendingTypesToShow; 116 117 private int mLastLegacySoftInputMode; 118 InsetsController(ViewRootImpl viewRoot)119 public InsetsController(ViewRootImpl viewRoot) { 120 mViewRoot = viewRoot; 121 mAnimCallback = () -> { 122 mAnimCallbackScheduled = false; 123 if (mAnimationControls.isEmpty()) { 124 return; 125 } 126 127 mTmpFinishedControls.clear(); 128 InsetsState state = new InsetsState(mState, true /* copySources */); 129 for (int i = mAnimationControls.size() - 1; i >= 0; i--) { 130 InsetsAnimationControlImpl control = mAnimationControls.get(i); 131 if (mAnimationControls.get(i).applyChangeInsets(state)) { 132 mTmpFinishedControls.add(control); 133 } 134 } 135 136 WindowInsets insets = state.calculateInsets(mFrame, mLastInsets.isRound(), 137 mLastInsets.shouldAlwaysConsumeSystemBars(), mLastInsets.getDisplayCutout(), 138 mLastLegacyContentInsets, mLastLegacyStableInsets, mLastLegacySoftInputMode, 139 null /* typeSideMap */); 140 mViewRoot.mView.dispatchWindowInsetsAnimationProgress(insets); 141 142 for (int i = mTmpFinishedControls.size() - 1; i >= 0; i--) { 143 dispatchAnimationFinished(mTmpFinishedControls.get(i).getAnimation()); 144 } 145 }; 146 } 147 148 @VisibleForTesting onFrameChanged(Rect frame)149 public void onFrameChanged(Rect frame) { 150 if (mFrame.equals(frame)) { 151 return; 152 } 153 mViewRoot.notifyInsetsChanged(); 154 mFrame.set(frame); 155 } 156 getState()157 public InsetsState getState() { 158 return mState; 159 } 160 onStateChanged(InsetsState state)161 boolean onStateChanged(InsetsState state) { 162 if (mState.equals(state)) { 163 return false; 164 } 165 mState.set(state); 166 mTmpState.set(state, true /* copySources */); 167 applyLocalVisibilityOverride(); 168 mViewRoot.notifyInsetsChanged(); 169 if (!mState.equals(mTmpState)) { 170 sendStateToWindowManager(); 171 } 172 return true; 173 } 174 175 /** 176 * @see InsetsState#calculateInsets 177 */ 178 @VisibleForTesting calculateInsets(boolean isScreenRound, boolean alwaysConsumeSystemBars, DisplayCutout cutout, Rect legacyContentInsets, Rect legacyStableInsets, int legacySoftInputMode)179 public WindowInsets calculateInsets(boolean isScreenRound, 180 boolean alwaysConsumeSystemBars, DisplayCutout cutout, Rect legacyContentInsets, 181 Rect legacyStableInsets, int legacySoftInputMode) { 182 mLastLegacyContentInsets.set(legacyContentInsets); 183 mLastLegacyStableInsets.set(legacyStableInsets); 184 mLastLegacySoftInputMode = legacySoftInputMode; 185 mLastInsets = mState.calculateInsets(mFrame, isScreenRound, alwaysConsumeSystemBars, cutout, 186 legacyContentInsets, legacyStableInsets, legacySoftInputMode, 187 null /* typeSideMap */); 188 return mLastInsets; 189 } 190 191 /** 192 * Called when the server has dispatched us a new set of inset controls. 193 */ onControlsChanged(InsetsSourceControl[] activeControls)194 public void onControlsChanged(InsetsSourceControl[] activeControls) { 195 if (activeControls != null) { 196 for (InsetsSourceControl activeControl : activeControls) { 197 if (activeControl != null) { 198 // TODO(b/122982984): Figure out why it can be null. 199 mTmpControlArray.put(activeControl.getType(), activeControl); 200 } 201 } 202 } 203 204 // Ensure to update all existing source consumers 205 for (int i = mSourceConsumers.size() - 1; i >= 0; i--) { 206 final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i); 207 final InsetsSourceControl control = mTmpControlArray.get(consumer.getType()); 208 209 // control may be null, but we still need to update the control to null if it got 210 // revoked. 211 consumer.setControl(control); 212 } 213 214 // Ensure to create source consumers if not available yet. 215 for (int i = mTmpControlArray.size() - 1; i >= 0; i--) { 216 final InsetsSourceControl control = mTmpControlArray.valueAt(i); 217 getSourceConsumer(control.getType()).setControl(control); 218 } 219 mTmpControlArray.clear(); 220 } 221 222 @Override show(@nsetType int types)223 public void show(@InsetType int types) { 224 show(types, false /* fromIme */); 225 } 226 show(@nsetType int types, boolean fromIme)227 private void show(@InsetType int types, boolean fromIme) { 228 // TODO: Support a ResultReceiver for IME. 229 // TODO(b/123718661): Make show() work for multi-session IME. 230 int typesReady = 0; 231 final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); 232 for (int i = internalTypes.size() - 1; i >= 0; i--) { 233 InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i)); 234 if (mAnimationDirection == DIRECTION_HIDE) { 235 // Only one animator (with multiple InsetType) can run at a time. 236 // previous one should be cancelled for simplicity. 237 cancelExistingAnimation(); 238 } else if (consumer.isVisible() 239 && (mAnimationDirection == DIRECTION_NONE 240 || mAnimationDirection == DIRECTION_HIDE)) { 241 // no-op: already shown or animating in (because window visibility is 242 // applied before starting animation). 243 // TODO: When we have more than one types: handle specific case when 244 // show animation is going on, but the current type is not becoming visible. 245 continue; 246 } 247 typesReady |= InsetsState.toPublicType(consumer.getType()); 248 } 249 applyAnimation(typesReady, true /* show */, fromIme); 250 } 251 252 @Override hide(@nsetType int types)253 public void hide(@InsetType int types) { 254 int typesReady = 0; 255 final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); 256 for (int i = internalTypes.size() - 1; i >= 0; i--) { 257 InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i)); 258 if (mAnimationDirection == DIRECTION_SHOW) { 259 cancelExistingAnimation(); 260 } else if (!consumer.isVisible() 261 && (mAnimationDirection == DIRECTION_NONE 262 || mAnimationDirection == DIRECTION_HIDE)) { 263 // no-op: already hidden or animating out. 264 continue; 265 } 266 typesReady |= InsetsState.toPublicType(consumer.getType()); 267 } 268 applyAnimation(typesReady, false /* show */, false /* fromIme */); 269 } 270 271 @Override controlWindowInsetsAnimation(@nsetType int types, WindowInsetsAnimationControlListener listener)272 public void controlWindowInsetsAnimation(@InsetType int types, 273 WindowInsetsAnimationControlListener listener) { 274 controlWindowInsetsAnimation(types, listener, false /* fromIme */); 275 } 276 controlWindowInsetsAnimation(@nsetType int types, WindowInsetsAnimationControlListener listener, boolean fromIme)277 private void controlWindowInsetsAnimation(@InsetType int types, 278 WindowInsetsAnimationControlListener listener, boolean fromIme) { 279 // If the frame of our window doesn't span the entire display, the control API makes very 280 // little sense, as we don't deal with negative insets. So just cancel immediately. 281 if (!mState.getDisplayFrame().equals(mFrame)) { 282 listener.onCancelled(); 283 return; 284 } 285 controlAnimationUnchecked(types, listener, mFrame, fromIme); 286 } 287 controlAnimationUnchecked(@nsetType int types, WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme)288 private void controlAnimationUnchecked(@InsetType int types, 289 WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme) { 290 if (types == 0) { 291 // nothing to animate. 292 return; 293 } 294 cancelExistingControllers(types); 295 296 final ArraySet<Integer> internalTypes = mState.toInternalType(types); 297 final SparseArray<InsetsSourceConsumer> consumers = new SparseArray<>(); 298 299 Pair<Integer, Boolean> typesReadyPair = collectConsumers(fromIme, internalTypes, consumers); 300 int typesReady = typesReadyPair.first; 301 boolean isReady = typesReadyPair.second; 302 if (!isReady) { 303 // IME isn't ready, all requested types would be shown once IME is ready. 304 mPendingTypesToShow = typesReady; 305 // TODO: listener for pending types. 306 return; 307 } 308 309 // pending types from previous request. 310 typesReady = collectPendingConsumers(typesReady, consumers); 311 312 if (typesReady == 0) { 313 listener.onCancelled(); 314 return; 315 } 316 317 final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(consumers, 318 frame, mState, listener, typesReady, 319 () -> new SyncRtSurfaceTransactionApplier(mViewRoot.mView), this); 320 mAnimationControls.add(controller); 321 } 322 323 /** 324 * @return Pair of (types ready to animate, is ready to animate). 325 */ collectConsumers(boolean fromIme, ArraySet<Integer> internalTypes, SparseArray<InsetsSourceConsumer> consumers)326 private Pair<Integer, Boolean> collectConsumers(boolean fromIme, 327 ArraySet<Integer> internalTypes, SparseArray<InsetsSourceConsumer> consumers) { 328 int typesReady = 0; 329 boolean isReady = true; 330 for (int i = internalTypes.size() - 1; i >= 0; i--) { 331 InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i)); 332 if (consumer.getControl() != null) { 333 if (!consumer.isVisible()) { 334 // Show request 335 switch(consumer.requestShow(fromIme)) { 336 case ShowResult.SHOW_IMMEDIATELY: 337 typesReady |= InsetsState.toPublicType(consumer.getType()); 338 break; 339 case ShowResult.SHOW_DELAYED: 340 isReady = false; 341 break; 342 case ShowResult.SHOW_FAILED: 343 // IME cannot be shown (since it didn't have focus), proceed 344 // with animation of other types. 345 if (mPendingTypesToShow != 0) { 346 // remove IME from pending because view no longer has focus. 347 mPendingTypesToShow &= ~InsetsState.toPublicType(TYPE_IME); 348 } 349 break; 350 } 351 } else { 352 // Hide request 353 // TODO: Move notifyHidden() to beginning of the hide animation 354 // (when visibility actually changes using hideDirectly()). 355 consumer.notifyHidden(); 356 typesReady |= InsetsState.toPublicType(consumer.getType()); 357 } 358 consumers.put(consumer.getType(), consumer); 359 } else { 360 // TODO: Let calling app know it's not possible, or wait 361 // TODO: Remove it from types 362 } 363 } 364 return new Pair<>(typesReady, isReady); 365 } 366 collectPendingConsumers(@nsetType int typesReady, SparseArray<InsetsSourceConsumer> consumers)367 private int collectPendingConsumers(@InsetType int typesReady, 368 SparseArray<InsetsSourceConsumer> consumers) { 369 if (mPendingTypesToShow != 0) { 370 typesReady |= mPendingTypesToShow; 371 final ArraySet<Integer> internalTypes = mState.toInternalType(mPendingTypesToShow); 372 for (int i = internalTypes.size() - 1; i >= 0; i--) { 373 InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i)); 374 consumers.put(consumer.getType(), consumer); 375 } 376 mPendingTypesToShow = 0; 377 } 378 return typesReady; 379 } 380 cancelExistingControllers(@nsetType int types)381 private void cancelExistingControllers(@InsetType int types) { 382 for (int i = mAnimationControls.size() - 1; i >= 0; i--) { 383 InsetsAnimationControlImpl control = mAnimationControls.get(i); 384 if ((control.getTypes() & types) != 0) { 385 cancelAnimation(control); 386 } 387 } 388 } 389 390 @VisibleForTesting notifyFinished(InsetsAnimationControlImpl controller, int shownTypes)391 public void notifyFinished(InsetsAnimationControlImpl controller, int shownTypes) { 392 mAnimationControls.remove(controller); 393 hideDirectly(controller.getTypes() & ~shownTypes); 394 showDirectly(controller.getTypes() & shownTypes); 395 } 396 notifyControlRevoked(InsetsSourceConsumer consumer)397 void notifyControlRevoked(InsetsSourceConsumer consumer) { 398 for (int i = mAnimationControls.size() - 1; i >= 0; i--) { 399 InsetsAnimationControlImpl control = mAnimationControls.get(i); 400 if ((control.getTypes() & toPublicType(consumer.getType())) != 0) { 401 cancelAnimation(control); 402 } 403 } 404 } 405 cancelAnimation(InsetsAnimationControlImpl control)406 private void cancelAnimation(InsetsAnimationControlImpl control) { 407 control.onCancelled(); 408 mAnimationControls.remove(control); 409 } 410 applyLocalVisibilityOverride()411 private void applyLocalVisibilityOverride() { 412 for (int i = mSourceConsumers.size() - 1; i >= 0; i--) { 413 final InsetsSourceConsumer controller = mSourceConsumers.valueAt(i); 414 controller.applyLocalVisibilityOverride(); 415 } 416 } 417 418 @VisibleForTesting getSourceConsumer(@nternalInsetType int type)419 public @NonNull InsetsSourceConsumer getSourceConsumer(@InternalInsetType int type) { 420 InsetsSourceConsumer controller = mSourceConsumers.get(type); 421 if (controller != null) { 422 return controller; 423 } 424 controller = createConsumerOfType(type); 425 mSourceConsumers.put(type, controller); 426 return controller; 427 } 428 429 @VisibleForTesting notifyVisibilityChanged()430 public void notifyVisibilityChanged() { 431 mViewRoot.notifyInsetsChanged(); 432 sendStateToWindowManager(); 433 } 434 435 /** 436 * Called when current window gains focus. 437 */ onWindowFocusGained()438 public void onWindowFocusGained() { 439 getSourceConsumer(TYPE_IME).onWindowFocusGained(); 440 } 441 442 /** 443 * Called when current window loses focus. 444 */ onWindowFocusLost()445 public void onWindowFocusLost() { 446 getSourceConsumer(TYPE_IME).onWindowFocusLost(); 447 } 448 getViewRoot()449 ViewRootImpl getViewRoot() { 450 return mViewRoot; 451 } 452 453 /** 454 * Used by {@link ImeInsetsSourceConsumer} when IME decides to be shown/hidden. 455 * @hide 456 */ 457 @VisibleForTesting applyImeVisibility(boolean setVisible)458 public void applyImeVisibility(boolean setVisible) { 459 if (setVisible) { 460 show(Type.IME, true /* fromIme */); 461 } else { 462 hide(Type.IME); 463 } 464 } 465 createConsumerOfType(int type)466 private InsetsSourceConsumer createConsumerOfType(int type) { 467 if (type == TYPE_IME) { 468 return new ImeInsetsSourceConsumer(mState, Transaction::new, this); 469 } else { 470 return new InsetsSourceConsumer(type, mState, Transaction::new, this); 471 } 472 } 473 474 /** 475 * Sends the local visibility state back to window manager. 476 */ sendStateToWindowManager()477 private void sendStateToWindowManager() { 478 InsetsState tmpState = new InsetsState(); 479 for (int i = mSourceConsumers.size() - 1; i >= 0; i--) { 480 final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i); 481 if (consumer.getControl() != null) { 482 tmpState.addSource(mState.getSource(consumer.getType())); 483 } 484 } 485 486 // TODO: Put this on a dispatcher thread. 487 try { 488 mViewRoot.mWindowSession.insetsModified(mViewRoot.mWindow, tmpState); 489 } catch (RemoteException e) { 490 Log.e(TAG, "Failed to call insetsModified", e); 491 } 492 } 493 applyAnimation(@nsetType final int types, boolean show, boolean fromIme)494 private void applyAnimation(@InsetType final int types, boolean show, boolean fromIme) { 495 if (types == 0) { 496 // nothing to animate. 497 return; 498 } 499 500 WindowInsetsAnimationControlListener listener = new WindowInsetsAnimationControlListener() { 501 502 private WindowInsetsAnimationController mController; 503 private ObjectAnimator mAnimator; 504 505 @Override 506 public void onReady(WindowInsetsAnimationController controller, int types) { 507 mController = controller; 508 if (show) { 509 showDirectly(types); 510 } else { 511 hideDirectly(types); 512 } 513 mAnimator = ObjectAnimator.ofObject( 514 controller, 515 new InsetsProperty(), 516 sEvaluator, 517 show ? controller.getHiddenStateInsets() : controller.getShownStateInsets(), 518 show ? controller.getShownStateInsets() : controller.getHiddenStateInsets() 519 ); 520 mAnimator.setDuration(show 521 ? ANIMATION_DURATION_SHOW_MS 522 : ANIMATION_DURATION_HIDE_MS); 523 mAnimator.setInterpolator(INTERPOLATOR); 524 mAnimator.addListener(new AnimatorListenerAdapter() { 525 526 @Override 527 public void onAnimationEnd(Animator animation) { 528 onAnimationFinish(); 529 } 530 }); 531 mAnimator.start(); 532 } 533 534 @Override 535 public void onCancelled() { 536 mAnimator.cancel(); 537 } 538 539 private void onAnimationFinish() { 540 mAnimationDirection = DIRECTION_NONE; 541 mController.finish(show ? types : 0); 542 } 543 }; 544 545 // Show/hide animations always need to be relative to the display frame, in order that shown 546 // and hidden state insets are correct. 547 controlAnimationUnchecked(types, listener, mState.getDisplayFrame(), fromIme); 548 } 549 hideDirectly(@nsetType int types)550 private void hideDirectly(@InsetType int types) { 551 final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); 552 for (int i = internalTypes.size() - 1; i >= 0; i--) { 553 getSourceConsumer(internalTypes.valueAt(i)).hide(); 554 } 555 } 556 showDirectly(@nsetType int types)557 private void showDirectly(@InsetType int types) { 558 final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); 559 for (int i = internalTypes.size() - 1; i >= 0; i--) { 560 getSourceConsumer(internalTypes.valueAt(i)).show(); 561 } 562 } 563 564 /** 565 * Cancel on-going animation to show/hide {@link InsetType}. 566 */ 567 @VisibleForTesting cancelExistingAnimation()568 public void cancelExistingAnimation() { 569 cancelExistingControllers(all()); 570 } 571 dump(String prefix, PrintWriter pw)572 void dump(String prefix, PrintWriter pw) { 573 pw.println(prefix); pw.println("InsetsController:"); 574 mState.dump(prefix + " ", pw); 575 } 576 577 @VisibleForTesting dispatchAnimationStarted(WindowInsetsAnimationListener.InsetsAnimation animation)578 public void dispatchAnimationStarted(WindowInsetsAnimationListener.InsetsAnimation animation) { 579 mViewRoot.mView.dispatchWindowInsetsAnimationStarted(animation); 580 } 581 582 @VisibleForTesting dispatchAnimationFinished(WindowInsetsAnimationListener.InsetsAnimation animation)583 public void dispatchAnimationFinished(WindowInsetsAnimationListener.InsetsAnimation animation) { 584 mViewRoot.mView.dispatchWindowInsetsAnimationFinished(animation); 585 } 586 587 @VisibleForTesting scheduleApplyChangeInsets()588 public void scheduleApplyChangeInsets() { 589 if (!mAnimCallbackScheduled) { 590 mViewRoot.mChoreographer.postCallback(Choreographer.CALLBACK_INSETS_ANIMATION, 591 mAnimCallback, null /* token*/); 592 mAnimCallbackScheduled = true; 593 } 594 } 595 } 596