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.InsetsController.ANIMATION_TYPE_NONE; 20 import static android.view.InsetsController.AnimationType; 21 import static android.view.InsetsController.DEBUG; 22 import static android.view.InsetsSourceConsumerProto.ANIMATION_STATE; 23 import static android.view.InsetsSourceConsumerProto.HAS_WINDOW_FOCUS; 24 import static android.view.InsetsSourceConsumerProto.IS_REQUESTED_VISIBLE; 25 import static android.view.InsetsSourceConsumerProto.PENDING_FRAME; 26 import static android.view.InsetsSourceConsumerProto.PENDING_VISIBLE_FRAME; 27 import static android.view.InsetsSourceConsumerProto.SOURCE_CONTROL; 28 import static android.view.InsetsSourceConsumerProto.TYPE_NUMBER; 29 30 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; 31 32 import android.annotation.IntDef; 33 import android.annotation.Nullable; 34 import android.graphics.Insets; 35 import android.graphics.Matrix; 36 import android.graphics.Point; 37 import android.graphics.Rect; 38 import android.os.IBinder; 39 import android.text.TextUtils; 40 import android.util.Log; 41 import android.util.proto.ProtoOutputStream; 42 import android.view.WindowInsets.Type.InsetsType; 43 import android.view.inputmethod.Flags; 44 import android.view.inputmethod.ImeTracker; 45 46 import com.android.internal.annotations.VisibleForTesting; 47 import com.android.internal.inputmethod.ImeTracing; 48 49 import java.lang.annotation.Retention; 50 import java.lang.annotation.RetentionPolicy; 51 import java.util.Objects; 52 53 /** 54 * Controls the visibility and animations of a single window insets source. 55 * @hide 56 */ 57 public class InsetsSourceConsumer { 58 59 @Retention(RetentionPolicy.SOURCE) 60 @IntDef(value = { 61 ShowResult.SHOW_IMMEDIATELY, 62 ShowResult.IME_SHOW_DELAYED, 63 ShowResult.IME_SHOW_FAILED 64 }) 65 @interface ShowResult { 66 /** 67 * Window type is ready to be shown, will be shown immediately. 68 */ 69 int SHOW_IMMEDIATELY = 0; 70 /** 71 * Result will be delayed. Window needs to be prepared or request is not from controller. 72 * Request will be delegated to controller and may or may not be shown. 73 */ 74 int IME_SHOW_DELAYED = 1; 75 /** 76 * Window will not be shown because one of the conditions couldn't be met. 77 * (e.g. in IME's case, when no editor is focused.) 78 */ 79 int IME_SHOW_FAILED = 2; 80 } 81 82 protected static final int ANIMATION_STATE_NONE = 0; 83 protected static final int ANIMATION_STATE_SHOW = 1; 84 protected static final int ANIMATION_STATE_HIDE = 2; 85 86 protected int mAnimationState = ANIMATION_STATE_NONE; 87 88 protected final InsetsController mController; 89 protected final InsetsState mState; 90 private int mId; 91 @InsetsType 92 private final int mType; 93 94 private static final String TAG = "InsetsSourceConsumer"; 95 @Nullable 96 private InsetsSourceControl mSourceControl; 97 private boolean mHasWindowFocus; 98 private InsetsAnimationControlRunner.SurfaceParamsApplier mSurfaceParamsApplier = 99 InsetsAnimationControlRunner.SurfaceParamsApplier.DEFAULT; 100 private final Matrix mTmpMatrix = new Matrix(); 101 102 /** 103 * Whether the view has focus returned by {@link #onWindowFocusGained(boolean)}. 104 */ 105 private boolean mHasViewFocusWhenWindowFocusGain; 106 private Rect mPendingFrame; 107 private Rect mPendingVisibleFrame; 108 109 /** 110 * @param id The ID of the consumed insets. 111 * @param type The {@link InsetsType} of the consumed insets. 112 * @param state The current {@link InsetsState} of the consumed insets. 113 * @param controller The {@link InsetsController} to use for insets interaction. 114 */ InsetsSourceConsumer(int id, @InsetsType int type, InsetsState state, InsetsController controller)115 public InsetsSourceConsumer(int id, @InsetsType int type, InsetsState state, 116 InsetsController controller) { 117 mId = id; 118 mType = type; 119 mState = state; 120 mController = controller; 121 } 122 123 /** 124 * Updates the control delivered from the server. 125 * 126 * @param showTypes An integer array with a single entry that determines which types a show 127 * animation should be run after setting the control. 128 * @param hideTypes An integer array with a single entry that determines which types a hide 129 * animation should be run after setting the control. 130 * @return Whether the control has changed from the server 131 */ setControl(@ullable InsetsSourceControl control, @InsetsType int[] showTypes, @InsetsType int[] hideTypes, @InsetsType int[] cancelTypes, @InsetsType int[] transientTypes)132 public boolean setControl(@Nullable InsetsSourceControl control, 133 @InsetsType int[] showTypes, 134 @InsetsType int[] hideTypes, 135 @InsetsType int[] cancelTypes, 136 @InsetsType int[] transientTypes) { 137 if (Objects.equals(mSourceControl, control)) { 138 if (mSourceControl != null && mSourceControl != control) { 139 mSourceControl.release(SurfaceControl::release); 140 mSourceControl = control; 141 } 142 return false; 143 } 144 145 final InsetsSourceControl lastControl = mSourceControl; 146 mSourceControl = control; 147 if (control != null) { 148 if (DEBUG) Log.d(TAG, String.format("setControl -> %s on %s", 149 WindowInsets.Type.toString(control.getType()), 150 mController.getHost().getRootViewTitle())); 151 } 152 if (mSourceControl == null) { 153 // We are loosing control 154 mController.notifyControlRevoked(this); 155 156 // Check if we need to restore server visibility. 157 final InsetsSource localSource = mState.peekSource(mId); 158 final InsetsSource serverSource = mController.getLastDispatchedState().peekSource(mId); 159 final boolean localVisible = localSource != null && localSource.isVisible(); 160 final boolean serverVisible = serverSource != null && serverSource.isVisible(); 161 if (localSource != null) { 162 localSource.setVisible(serverVisible); 163 } 164 if (localVisible != serverVisible) { 165 mController.notifyVisibilityChanged(); 166 } 167 168 // Reset the applier to the default one which has the most lightweight implementation. 169 setSurfaceParamsApplier(InsetsAnimationControlRunner.SurfaceParamsApplier.DEFAULT); 170 } else { 171 if (lastControl != null && !Insets.NONE.equals(lastControl.getInsetsHint()) 172 && InsetsSource.getInsetSide(lastControl.getInsetsHint()) 173 != InsetsSource.getInsetSide(control.getInsetsHint())) { 174 // The source has been moved to a different side. The coordinates are stale. 175 // Canceling existing animation if there is any. 176 cancelTypes[0] |= mType; 177 } 178 final boolean requestedVisible = isRequestedVisibleAwaitingControl(); 179 final SurfaceControl oldLeash = lastControl != null ? lastControl.getLeash() : null; 180 final SurfaceControl newLeash = control.getLeash(); 181 if (newLeash != null && (oldLeash == null || !newLeash.isSameSurface(oldLeash)) 182 && requestedVisible != control.isInitiallyVisible()) { 183 // We are gaining leash, and need to run an animation since previous state 184 // didn't match. 185 if (DEBUG) Log.d(TAG, String.format("Gaining leash in %s, requestedVisible: %b", 186 mController.getHost().getRootViewTitle(), requestedVisible)); 187 if (requestedVisible) { 188 showTypes[0] |= mType; 189 } else { 190 hideTypes[0] |= mType; 191 } 192 if (lastControl != null && lastControl.isFake()) { 193 transientTypes[0] |= mType; 194 } 195 } else { 196 // We are gaining control, but don't need to run an animation. 197 // However make sure that the leash visibility is still up to date. 198 if (applyLocalVisibilityOverride()) { 199 mController.notifyVisibilityChanged(); 200 } 201 202 // If there is no animation controlling the leash, make sure the visibility and the 203 // position is up-to-date. 204 if (!mController.hasSurfaceAnimation(mType)) { 205 applyRequestedVisibilityAndPositionToControl(); 206 } 207 208 // Remove the surface that owned by last control when it lost. 209 if (!requestedVisible && lastControl == null) { 210 removeSurface(); 211 } 212 } 213 } 214 if (lastControl != null) { 215 lastControl.release(SurfaceControl::release); 216 } 217 return true; 218 } 219 220 @VisibleForTesting(visibility = PACKAGE) getControl()221 public InsetsSourceControl getControl() { 222 return mSourceControl; 223 } 224 225 /** 226 * Determines if the consumer will be shown after control is available. 227 * 228 * @return {@code true} if consumer has a pending show. 229 */ isRequestedVisibleAwaitingControl()230 protected boolean isRequestedVisibleAwaitingControl() { 231 return (mController.getRequestedVisibleTypes() & mType) != 0; 232 } 233 getId()234 int getId() { 235 return mId; 236 } 237 setId(int id)238 void setId(int id) { 239 mId = id; 240 } 241 getType()242 @InsetsType int getType() { 243 return mType; 244 } 245 246 /** 247 * Sets the SurfaceParamsApplier that the latest animation runner is using. The leash owned by 248 * this class is always applied by the applier, so that the transaction order can always be 249 * aligned with the calling sequence. 250 */ setSurfaceParamsApplier(InsetsAnimationControlRunner.SurfaceParamsApplier applier)251 void setSurfaceParamsApplier(InsetsAnimationControlRunner.SurfaceParamsApplier applier) { 252 mSurfaceParamsApplier = applier; 253 } 254 255 /** 256 * Called right after the animation is started or finished. 257 */ 258 @VisibleForTesting(visibility = PACKAGE) onAnimationStateChanged(boolean running)259 public boolean onAnimationStateChanged(boolean running) { 260 boolean insetsChanged = false; 261 if (!running && mPendingFrame != null) { 262 final InsetsSource source = mState.peekSource(mId); 263 if (source != null) { 264 source.setFrame(mPendingFrame); 265 source.setVisibleFrame(mPendingVisibleFrame); 266 insetsChanged = true; 267 } 268 mPendingFrame = null; 269 mPendingVisibleFrame = null; 270 } 271 272 final boolean showRequested = isShowRequested(); 273 final boolean cancelledForNewAnimation; 274 if (Flags.refactorInsetsController()) { 275 cancelledForNewAnimation = 276 (mController.getCancelledForNewAnimationTypes() & mType) != 0; 277 } else { 278 cancelledForNewAnimation = (!running && showRequested) 279 ? mAnimationState == ANIMATION_STATE_HIDE 280 : mAnimationState == ANIMATION_STATE_SHOW; 281 } 282 283 mAnimationState = running 284 ? (showRequested ? ANIMATION_STATE_SHOW : ANIMATION_STATE_HIDE) 285 : ANIMATION_STATE_NONE; 286 287 // We apply the visibility override after the animation is started. We don't do this before 288 // that because we need to know the initial insets state while creating the animation. 289 // We also need to apply the override after the animation is finished because the requested 290 // visibility can be set when finishing the user animation. 291 // If the animation is cancelled because we are going to play a new animation with an 292 // opposite direction, don't apply it now but after the new animation is started. 293 if (!cancelledForNewAnimation) { 294 insetsChanged |= applyLocalVisibilityOverride(); 295 } 296 return insetsChanged; 297 } 298 isShowRequested()299 protected boolean isShowRequested() { 300 return (mController.getRequestedVisibleTypes() & getType()) != 0; 301 } 302 303 /** 304 * Called when current window gains focus 305 */ onWindowFocusGained(boolean hasViewFocus)306 public void onWindowFocusGained(boolean hasViewFocus) { 307 mHasWindowFocus = true; 308 mHasViewFocusWhenWindowFocusGain = hasViewFocus; 309 } 310 311 /** 312 * Called when current window loses focus. 313 */ onWindowFocusLost()314 public void onWindowFocusLost() { 315 mHasWindowFocus = false; 316 } 317 hasViewFocusWhenWindowFocusGain()318 boolean hasViewFocusWhenWindowFocusGain() { 319 return mHasViewFocusWhenWindowFocusGain; 320 } 321 322 @VisibleForTesting(visibility = PACKAGE) applyLocalVisibilityOverride()323 public boolean applyLocalVisibilityOverride() { 324 if (Flags.refactorInsetsController()) { 325 if (mType == WindowInsets.Type.ime()) { 326 ImeTracing.getInstance().triggerClientDump( 327 "ImeInsetsSourceConsumer#applyLocalVisibilityOverride", 328 mController.getHost().getInputMethodManager(), null /* icProto */); 329 } 330 } 331 final InsetsSource source = mState.peekSource(mId); 332 if (source == null) { 333 return false; 334 } 335 final boolean requestedVisible = (mController.getRequestedVisibleTypes() & mType) != 0; 336 337 // If we don't have control or the leash (in case of the IME), we enforce the 338 // visibility to be hidden, as otherwise we would let the app know too early. 339 if (mSourceControl == null) { 340 if (DEBUG) { 341 Log.d(TAG, TextUtils.formatSimple( 342 "applyLocalVisibilityOverride: No control in %s for type %s, " 343 + "requestedVisible=%s", 344 mController.getHost().getRootViewTitle(), 345 WindowInsets.Type.toString(mType), requestedVisible)); 346 } 347 return false; 348 } 349 if (Flags.refactorInsetsController()) { 350 // TODO(b/323136120) add a flag to the control, to define whether a leash is 351 // needed and make it generic for all types 352 if (mId == InsetsSource.ID_IME && mSourceControl.getLeash() == null) { 353 if (DEBUG) { 354 Log.d(TAG, TextUtils.formatSimple( 355 "applyLocalVisibilityOverride: Set the source visibility to false, as" 356 + " there is no leash yet for type %s in %s", 357 WindowInsets.Type.toString(mType), 358 mController.getHost().getRootViewTitle())); 359 } 360 boolean wasVisible = source.isVisible(); 361 source.setVisible(false); 362 // only if it was visible before and is now hidden, we want to notify about the 363 // changed state 364 return wasVisible; 365 } 366 } 367 if (source.isVisible() == requestedVisible) { 368 return false; 369 } 370 if (DEBUG) Log.d(TAG, String.format("applyLocalVisibilityOverride: %s requestedVisible: %b", 371 mController.getHost().getRootViewTitle(), requestedVisible)); 372 source.setVisible(requestedVisible); 373 return true; 374 } 375 376 /** 377 * Request to show current window type. 378 * 379 * @param fromController {@code true} if request is coming from controller. 380 * (e.g. in IME case, controller is 381 * {@link android.inputmethodservice.InputMethodService}). 382 * @param statsToken the token tracking the current IME request or {@code null} otherwise. 383 * 384 * @implNote The {@code statsToken} is ignored here, and only handled in 385 * {@link ImeInsetsSourceConsumer} for IME animations only. 386 * 387 * @return @see {@link ShowResult}. 388 */ 389 @VisibleForTesting(visibility = PACKAGE) 390 @ShowResult requestShow(boolean fromController, @Nullable ImeTracker.Token statsToken)391 public int requestShow(boolean fromController, @Nullable ImeTracker.Token statsToken) { 392 return ShowResult.SHOW_IMMEDIATELY; 393 } 394 requestHide(boolean fromController, @Nullable ImeTracker.Token statsToken)395 void requestHide(boolean fromController, @Nullable ImeTracker.Token statsToken) { 396 // no-op for types that always return ShowResult#SHOW_IMMEDIATELY. 397 } 398 399 /** 400 * Reports that this source's perceptibility has changed 401 * 402 * @param perceptible true if the source is perceptible, false otherwise. 403 * @see InsetsAnimationControlCallbacks#reportPerceptible 404 */ onPerceptible(boolean perceptible)405 public void onPerceptible(boolean perceptible) { 406 if (Flags.refactorInsetsController()) { 407 if (mType == WindowInsets.Type.ime()) { 408 final IBinder window = mController.getHost().getWindowToken(); 409 if (window != null) { 410 mController.getHost().getInputMethodManager().reportPerceptible(window, 411 perceptible); 412 } 413 } 414 } 415 } 416 417 /** 418 * Remove surface on which this consumer type is drawn. 419 */ removeSurface()420 public void removeSurface() { 421 // no-op for types that always return ShowResult#SHOW_IMMEDIATELY. 422 if (Flags.refactorInsetsController()) { 423 if (mType == WindowInsets.Type.ime()) { 424 final IBinder window = mController.getHost().getWindowToken(); 425 if (window != null) { 426 mController.getHost().getInputMethodManager().removeImeSurface(window); 427 } 428 } 429 } 430 } 431 432 @VisibleForTesting(visibility = PACKAGE) updateSource(InsetsSource newSource, @AnimationType int animationType)433 public void updateSource(InsetsSource newSource, @AnimationType int animationType) { 434 InsetsSource source = mState.peekSource(mId); 435 if (source == null || animationType == ANIMATION_TYPE_NONE 436 || source.getFrame().equals(newSource.getFrame())) { 437 mPendingFrame = null; 438 mPendingVisibleFrame = null; 439 mState.addSource(newSource); 440 return; 441 } 442 443 // Frame is changing while animating. Keep note of the new frame but keep existing frame 444 // until animation is finished. 445 mPendingFrame = new Rect(newSource.getFrame()); 446 mPendingVisibleFrame = newSource.getVisibleFrame() != null 447 ? new Rect(newSource.getVisibleFrame()) 448 : null; 449 newSource.setFrame(source.getFrame()); 450 newSource.setVisibleFrame(source.getVisibleFrame()); 451 mState.addSource(newSource); 452 if (DEBUG) Log.d(TAG, "updateSource: " + newSource); 453 } 454 applyRequestedVisibilityAndPositionToControl()455 private void applyRequestedVisibilityAndPositionToControl() { 456 if (mSourceControl == null) { 457 return; 458 } 459 final SurfaceControl leash = mSourceControl.getLeash(); 460 if (leash == null) { 461 return; 462 } 463 464 final boolean visible = (mController.getRequestedVisibleTypes() & mType) != 0; 465 final Point surfacePosition = mSourceControl.getSurfacePosition(); 466 467 if (DEBUG) Log.d(TAG, "applyRequestedVisibilityAndPositionToControl: visible=" + visible 468 + " position=" + surfacePosition); 469 470 mTmpMatrix.setTranslate(surfacePosition.x, surfacePosition.y); 471 mSurfaceParamsApplier.applySurfaceParams( 472 new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(leash) 473 .withVisibility(visible) 474 .withAlpha(visible ? 1 : 0) 475 .withMatrix(mTmpMatrix) 476 .build()); 477 478 onPerceptible(visible); 479 } 480 dumpDebug(ProtoOutputStream proto, long fieldId)481 void dumpDebug(ProtoOutputStream proto, long fieldId) { 482 final long token = proto.start(fieldId); 483 proto.write(HAS_WINDOW_FOCUS, mHasWindowFocus); 484 proto.write(IS_REQUESTED_VISIBLE, isShowRequested()); 485 if (mSourceControl != null) { 486 mSourceControl.dumpDebug(proto, SOURCE_CONTROL); 487 } 488 if (mPendingFrame != null) { 489 mPendingFrame.dumpDebug(proto, PENDING_FRAME); 490 } 491 if (mPendingVisibleFrame != null) { 492 mPendingVisibleFrame.dumpDebug(proto, PENDING_VISIBLE_FRAME); 493 } 494 proto.write(ANIMATION_STATE, mAnimationState); 495 proto.write(TYPE_NUMBER, mType); 496 proto.end(token); 497 } 498 } 499