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