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