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.HAS_WINDOW_FOCUS; 23 import static android.view.InsetsSourceConsumerProto.INTERNAL_INSETS_TYPE; 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.InsetsState.ITYPE_IME; 29 import static android.view.InsetsState.getDefaultVisibility; 30 import static android.view.InsetsState.toPublicType; 31 32 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; 33 34 import android.annotation.IntDef; 35 import android.annotation.Nullable; 36 import android.graphics.Rect; 37 import android.util.ArraySet; 38 import android.util.Log; 39 import android.util.proto.ProtoOutputStream; 40 import android.view.InsetsState.InternalInsetsType; 41 import android.view.SurfaceControl.Transaction; 42 import android.view.WindowInsets.Type.InsetsType; 43 44 import com.android.internal.annotations.VisibleForTesting; 45 import com.android.internal.inputmethod.ImeTracing; 46 47 import java.lang.annotation.Retention; 48 import java.lang.annotation.RetentionPolicy; 49 import java.util.Objects; 50 import java.util.function.Supplier; 51 52 /** 53 * Controls the visibility and animations of a single window insets source. 54 * @hide 55 */ 56 public class InsetsSourceConsumer { 57 58 @Retention(RetentionPolicy.SOURCE) 59 @IntDef(value = {ShowResult.SHOW_IMMEDIATELY, ShowResult.IME_SHOW_DELAYED, ShowResult.IME_SHOW_FAILED}) 60 @interface ShowResult { 61 /** 62 * Window type is ready to be shown, will be shown immidiately. 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 final InsetsController mController; 78 protected boolean mRequestedVisible; 79 protected final InsetsState mState; 80 protected final @InternalInsetsType int mType; 81 82 private static final String TAG = "InsetsSourceConsumer"; 83 private final Supplier<Transaction> mTransactionSupplier; 84 private @Nullable InsetsSourceControl mSourceControl; 85 private boolean mHasWindowFocus; 86 87 /** 88 * Whether the view has focus returned by {@link #onWindowFocusGained(boolean)}. 89 */ 90 private boolean mHasViewFocusWhenWindowFocusGain; 91 private Rect mPendingFrame; 92 private Rect mPendingVisibleFrame; 93 94 /** 95 * @param type The {@link InternalInsetsType} of the consumed insets. 96 * @param state The current {@link InsetsState} of the consumed insets. 97 * @param transactionSupplier The source of new {@link Transaction} instances. The supplier 98 * must provide *new* instances, which will be explicitly closed by this class. 99 * @param controller The {@link InsetsController} to use for insets interaction. 100 */ InsetsSourceConsumer(@nternalInsetsType int type, InsetsState state, Supplier<Transaction> transactionSupplier, InsetsController controller)101 public InsetsSourceConsumer(@InternalInsetsType int type, InsetsState state, 102 Supplier<Transaction> transactionSupplier, InsetsController controller) { 103 mType = type; 104 mState = state; 105 mTransactionSupplier = transactionSupplier; 106 mController = controller; 107 mRequestedVisible = getDefaultVisibility(type); 108 } 109 110 /** 111 * Updates the control delivered from the server. 112 113 * @param showTypes An integer array with a single entry that determines which types a show 114 * animation should be run after setting the control. 115 * @param hideTypes An integer array with a single entry that determines which types a hide 116 * animation should be run after setting the control. 117 * @return Whether the control has changed from the server 118 */ setControl(@ullable InsetsSourceControl control, @InsetsType int[] showTypes, @InsetsType int[] hideTypes)119 public boolean setControl(@Nullable InsetsSourceControl control, 120 @InsetsType int[] showTypes, @InsetsType int[] hideTypes) { 121 if (mType == ITYPE_IME) { 122 ImeTracing.getInstance().triggerClientDump("InsetsSourceConsumer#setControl", 123 mController.getHost().getInputMethodManager(), null /* icProto */); 124 } 125 if (Objects.equals(mSourceControl, control)) { 126 if (mSourceControl != null && mSourceControl != control) { 127 mSourceControl.release(SurfaceControl::release); 128 mSourceControl = control; 129 } 130 return false; 131 } 132 133 final InsetsSourceControl lastControl = mSourceControl; 134 mSourceControl = control; 135 if (control != null) { 136 if (DEBUG) Log.d(TAG, String.format("setControl -> %s on %s", 137 InsetsState.typeToString(control.getType()), 138 mController.getHost().getRootViewTitle())); 139 } 140 if (mSourceControl == null) { 141 // We are loosing control 142 mController.notifyControlRevoked(this); 143 144 // Check if we need to restore server visibility. 145 final InsetsSource source = mState.getSource(mType); 146 final boolean serverVisibility = 147 mController.getLastDispatchedState().getSourceOrDefaultVisibility(mType); 148 if (source.isVisible() != serverVisibility) { 149 source.setVisible(serverVisibility); 150 mController.notifyVisibilityChanged(); 151 } 152 153 // For updateCompatSysUiVisibility 154 applyLocalVisibilityOverride(); 155 } else { 156 final boolean requestedVisible = isRequestedVisibleAwaitingControl(); 157 final SurfaceControl oldLeash = lastControl != null ? lastControl.getLeash() : null; 158 final SurfaceControl newLeash = control.getLeash(); 159 if (newLeash != null && (oldLeash == null || !newLeash.isSameSurface(oldLeash)) 160 && requestedVisible != control.isInitiallyVisible()) { 161 // We are gaining leash, and need to run an animation since previous state 162 // didn't match. 163 if (DEBUG) Log.d(TAG, String.format("Gaining leash in %s, requestedVisible: %b", 164 mController.getHost().getRootViewTitle(), requestedVisible)); 165 if (requestedVisible) { 166 showTypes[0] |= toPublicType(getType()); 167 } else { 168 hideTypes[0] |= toPublicType(getType()); 169 } 170 } else { 171 // We are gaining control, but don't need to run an animation. 172 // However make sure that the leash visibility is still up to date. 173 if (applyLocalVisibilityOverride()) { 174 mController.notifyVisibilityChanged(); 175 } 176 177 // If we have a new leash, make sure visibility is up-to-date, even though we 178 // didn't want to run an animation above. 179 if (mController.getAnimationType(control.getType()) == ANIMATION_TYPE_NONE) { 180 applyRequestedVisibilityToControl(); 181 } 182 183 // Remove the surface that owned by last control when it lost. 184 if (!requestedVisible && lastControl == null) { 185 removeSurface(); 186 } 187 } 188 } 189 if (lastControl != null) { 190 lastControl.release(SurfaceControl::release); 191 } 192 return true; 193 } 194 195 @VisibleForTesting getControl()196 public InsetsSourceControl getControl() { 197 return mSourceControl; 198 } 199 200 /** 201 * Determines if the consumer will be shown after control is available. 202 * Note: for system bars this method is same as {@link #isRequestedVisible()}. 203 * 204 * @return {@code true} if consumer has a pending show. 205 */ isRequestedVisibleAwaitingControl()206 protected boolean isRequestedVisibleAwaitingControl() { 207 return isRequestedVisible(); 208 } 209 getType()210 int getType() { 211 return mType; 212 } 213 214 @VisibleForTesting show(boolean fromIme)215 public void show(boolean fromIme) { 216 if (DEBUG) Log.d(TAG, String.format("Call show() for type: %s fromIme: %b ", 217 InsetsState.typeToString(mType), fromIme)); 218 setRequestedVisible(true); 219 } 220 221 @VisibleForTesting hide()222 public void hide() { 223 if (DEBUG) Log.d(TAG, String.format("Call hide for %s on %s", 224 InsetsState.typeToString(mType), mController.getHost().getRootViewTitle())); 225 setRequestedVisible(false); 226 } 227 hide(boolean animationFinished, @AnimationType int animationType)228 void hide(boolean animationFinished, @AnimationType int animationType) { 229 hide(); 230 } 231 232 /** 233 * Called when current window gains focus 234 */ onWindowFocusGained(boolean hasViewFocus)235 public void onWindowFocusGained(boolean hasViewFocus) { 236 mHasWindowFocus = true; 237 mHasViewFocusWhenWindowFocusGain = hasViewFocus; 238 } 239 240 /** 241 * Called when current window loses focus. 242 */ onWindowFocusLost()243 public void onWindowFocusLost() { 244 mHasWindowFocus = false; 245 } 246 hasViewFocusWhenWindowFocusGain()247 boolean hasViewFocusWhenWindowFocusGain() { 248 return mHasViewFocusWhenWindowFocusGain; 249 } 250 applyLocalVisibilityOverride()251 boolean applyLocalVisibilityOverride() { 252 final InsetsSource source = mState.peekSource(mType); 253 final boolean isVisible = source != null ? source.isVisible() : getDefaultVisibility(mType); 254 final boolean hasControl = mSourceControl != null; 255 256 if (mType == ITYPE_IME) { 257 ImeTracing.getInstance().triggerClientDump( 258 "InsetsSourceConsumer#applyLocalVisibilityOverride", 259 mController.getHost().getInputMethodManager(), null /* icProto */); 260 } 261 262 updateCompatSysUiVisibility(hasControl, source, isVisible); 263 264 // If we don't have control, we are not able to change the visibility. 265 if (!hasControl) { 266 if (DEBUG) Log.d(TAG, "applyLocalVisibilityOverride: No control in " 267 + mController.getHost().getRootViewTitle() 268 + " requestedVisible " + mRequestedVisible); 269 return false; 270 } 271 if (isVisible == mRequestedVisible) { 272 return false; 273 } 274 if (DEBUG) Log.d(TAG, String.format("applyLocalVisibilityOverride: %s requestedVisible: %b", 275 mController.getHost().getRootViewTitle(), mRequestedVisible)); 276 mState.getSource(mType).setVisible(mRequestedVisible); 277 return true; 278 } 279 updateCompatSysUiVisibility(boolean hasControl, InsetsSource source, boolean visible)280 private void updateCompatSysUiVisibility(boolean hasControl, InsetsSource source, 281 boolean visible) { 282 final @InsetsType int publicType = InsetsState.toPublicType(mType); 283 if (publicType != WindowInsets.Type.statusBars() 284 && publicType != WindowInsets.Type.navigationBars()) { 285 // System UI visibility only controls status bars and navigation bars. 286 return; 287 } 288 final boolean compatVisible; 289 if (hasControl) { 290 compatVisible = mRequestedVisible; 291 } else if (source != null && !source.getFrame().isEmpty()) { 292 compatVisible = visible; 293 } else { 294 final ArraySet<Integer> types = InsetsState.toInternalType(publicType); 295 for (int i = types.size() - 1; i >= 0; i--) { 296 final InsetsSource s = mState.peekSource(types.valueAt(i)); 297 if (s != null && !s.getFrame().isEmpty()) { 298 // The compat system UI visibility would be updated by another consumer which 299 // handles the same public insets type. 300 return; 301 } 302 } 303 // No one provides the public type. Use the requested visibility for making the callback 304 // behavior compatible. 305 compatVisible = mRequestedVisible; 306 } 307 mController.updateCompatSysUiVisibility(mType, compatVisible, hasControl); 308 } 309 310 @VisibleForTesting isRequestedVisible()311 public boolean isRequestedVisible() { 312 return mRequestedVisible; 313 } 314 315 /** 316 * Request to show current window type. 317 * 318 * @param fromController {@code true} if request is coming from controller. 319 * (e.g. in IME case, controller is 320 * {@link android.inputmethodservice.InputMethodService}). 321 * @return @see {@link ShowResult}. 322 */ 323 @VisibleForTesting requestShow(boolean fromController)324 public @ShowResult int requestShow(boolean fromController) { 325 return ShowResult.SHOW_IMMEDIATELY; 326 } 327 328 /** 329 * Reports that this source's perceptibility has changed 330 * 331 * @param perceptible true if the source is perceptible, false otherwise. 332 * @see InsetsAnimationControlCallbacks#reportPerceptible 333 */ onPerceptible(boolean perceptible)334 public void onPerceptible(boolean perceptible) { 335 } 336 337 /** 338 * Notify listeners that window is now hidden. 339 */ notifyHidden()340 void notifyHidden() { 341 // no-op for types that always return ShowResult#SHOW_IMMEDIATELY. 342 } 343 344 /** 345 * Remove surface on which this consumer type is drawn. 346 */ removeSurface()347 public void removeSurface() { 348 // no-op for types that always return ShowResult#SHOW_IMMEDIATELY. 349 } 350 351 @VisibleForTesting(visibility = PACKAGE) updateSource(InsetsSource newSource, @AnimationType int animationType)352 public void updateSource(InsetsSource newSource, @AnimationType int animationType) { 353 InsetsSource source = mState.peekSource(mType); 354 if (source == null || animationType == ANIMATION_TYPE_NONE 355 || source.getFrame().equals(newSource.getFrame())) { 356 mPendingFrame = null; 357 mPendingVisibleFrame = null; 358 mState.addSource(newSource); 359 return; 360 } 361 362 // Frame is changing while animating. Keep note of the new frame but keep existing frame 363 // until animation is finished. 364 newSource = new InsetsSource(newSource); 365 mPendingFrame = new Rect(newSource.getFrame()); 366 mPendingVisibleFrame = newSource.getVisibleFrame() != null 367 ? new Rect(newSource.getVisibleFrame()) 368 : null; 369 newSource.setFrame(source.getFrame()); 370 newSource.setVisibleFrame(source.getVisibleFrame()); 371 mState.addSource(newSource); 372 if (DEBUG) Log.d(TAG, "updateSource: " + newSource); 373 } 374 375 @VisibleForTesting(visibility = PACKAGE) notifyAnimationFinished()376 public boolean notifyAnimationFinished() { 377 if (mPendingFrame != null) { 378 InsetsSource source = mState.getSource(mType); 379 source.setFrame(mPendingFrame); 380 source.setVisibleFrame(mPendingVisibleFrame); 381 mPendingFrame = null; 382 mPendingVisibleFrame = null; 383 return true; 384 } 385 return false; 386 } 387 388 /** 389 * Sets requested visibility from the client, regardless of whether we are able to control it at 390 * the moment. 391 */ setRequestedVisible(boolean requestedVisible)392 protected void setRequestedVisible(boolean requestedVisible) { 393 if (mRequestedVisible != requestedVisible) { 394 mRequestedVisible = requestedVisible; 395 mController.onRequestedVisibilityChanged(this); 396 if (DEBUG) Log.d(TAG, "setRequestedVisible: " + requestedVisible); 397 } 398 if (applyLocalVisibilityOverride()) { 399 mController.notifyVisibilityChanged(); 400 } 401 } 402 applyRequestedVisibilityToControl()403 private void applyRequestedVisibilityToControl() { 404 if (mSourceControl == null || mSourceControl.getLeash() == null) { 405 return; 406 } 407 408 try (Transaction t = mTransactionSupplier.get()) { 409 if (DEBUG) Log.d(TAG, "applyRequestedVisibilityToControl: " + mRequestedVisible); 410 if (mRequestedVisible) { 411 t.show(mSourceControl.getLeash()); 412 } else { 413 t.hide(mSourceControl.getLeash()); 414 } 415 // Ensure the alpha value is aligned with the actual requested visibility. 416 t.setAlpha(mSourceControl.getLeash(), mRequestedVisible ? 1 : 0); 417 t.apply(); 418 } 419 onPerceptible(mRequestedVisible); 420 } 421 dumpDebug(ProtoOutputStream proto, long fieldId)422 void dumpDebug(ProtoOutputStream proto, long fieldId) { 423 final long token = proto.start(fieldId); 424 proto.write(INTERNAL_INSETS_TYPE, InsetsState.typeToString(mType)); 425 proto.write(HAS_WINDOW_FOCUS, mHasWindowFocus); 426 proto.write(IS_REQUESTED_VISIBLE, mRequestedVisible); 427 if (mSourceControl != null) { 428 mSourceControl.dumpDebug(proto, SOURCE_CONTROL); 429 } 430 if (mPendingFrame != null) { 431 mPendingFrame.dumpDebug(proto, PENDING_FRAME); 432 } 433 if (mPendingVisibleFrame != null) { 434 mPendingVisibleFrame.dumpDebug(proto, PENDING_VISIBLE_FRAME); 435 } 436 proto.end(token); 437 } 438 } 439