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.Insets; 37 import android.graphics.Rect; 38 import android.util.Log; 39 import android.util.imetracing.ImeTracing; 40 import android.util.proto.ProtoOutputStream; 41 import android.view.InsetsState.InternalInsetsType; 42 import android.view.SurfaceControl.Transaction; 43 import android.view.WindowInsets.Type.InsetsType; 44 45 import com.android.internal.annotations.VisibleForTesting; 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 * Indicates if we have the pending animation. When we have the control, we need to play the 96 * animation if the requested visibility is different from the current state. But if we haven't 97 * had a leash yet, we will set this flag, and play the animation once we get the leash. 98 */ 99 private boolean mIsAnimationPending; 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 */ setControl(@ullable InsetsSourceControl control, @InsetsType int[] showTypes, @InsetsType int[] hideTypes)118 public void setControl(@Nullable InsetsSourceControl control, 119 @InsetsType int[] showTypes, @InsetsType int[] hideTypes) { 120 if (mType == ITYPE_IME) { 121 ImeTracing.getInstance().triggerClientDump("InsetsSourceConsumer#setControl", 122 mController.getHost().getInputMethodManager(), null /* icProto */); 123 } 124 if (Objects.equals(mSourceControl, control)) { 125 if (mSourceControl != null && mSourceControl != control) { 126 mSourceControl.release(SurfaceControl::release); 127 mSourceControl = control; 128 } 129 return; 130 } 131 SurfaceControl oldLeash = mSourceControl != null ? mSourceControl.getLeash() : null; 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 // We are gaining control, and need to run an animation since previous state 157 // didn't match 158 final boolean requestedVisible = isRequestedVisibleAwaitingControl(); 159 final boolean needAnimation = requestedVisible != mState.getSource(mType).isVisible(); 160 if (control.getLeash() != null && (needAnimation || mIsAnimationPending)) { 161 if (DEBUG) Log.d(TAG, String.format("Gaining control in %s, requestedVisible: %b", 162 mController.getHost().getRootViewTitle(), requestedVisible)); 163 if (requestedVisible) { 164 showTypes[0] |= toPublicType(getType()); 165 } else { 166 hideTypes[0] |= toPublicType(getType()); 167 } 168 mIsAnimationPending = false; 169 } else { 170 if (needAnimation) { 171 // We need animation but we haven't had a leash yet. Set this flag that when we 172 // get the leash we can play the deferred animation. 173 mIsAnimationPending = true; 174 } 175 // We are gaining control, but don't need to run an animation. 176 // However make sure that the leash visibility is still up to date. 177 if (applyLocalVisibilityOverride()) { 178 mController.notifyVisibilityChanged(); 179 } 180 181 // If we have a new leash, make sure visibility is up-to-date, even though we 182 // didn't want to run an animation above. 183 SurfaceControl newLeash = mSourceControl.getLeash(); 184 if (oldLeash == null || newLeash == null || !oldLeash.isSameSurface(newLeash)) { 185 applyHiddenToControl(); 186 } 187 188 // Remove the surface that owned by last control when it lost. 189 if (!requestedVisible && !mIsAnimationPending && lastControl == null) { 190 removeSurface(); 191 } 192 } 193 } 194 if (lastControl != null) { 195 lastControl.release(SurfaceControl::release); 196 } 197 } 198 199 @VisibleForTesting getControl()200 public InsetsSourceControl getControl() { 201 return mSourceControl; 202 } 203 204 /** 205 * Determines if the consumer will be shown after control is available. 206 * Note: for system bars this method is same as {@link #isRequestedVisible()}. 207 * 208 * @return {@code true} if consumer has a pending show. 209 */ isRequestedVisibleAwaitingControl()210 protected boolean isRequestedVisibleAwaitingControl() { 211 return isRequestedVisible(); 212 } 213 getType()214 int getType() { 215 return mType; 216 } 217 218 @VisibleForTesting show(boolean fromIme)219 public void show(boolean fromIme) { 220 if (DEBUG) Log.d(TAG, String.format("Call show() for type: %s fromIme: %b ", 221 InsetsState.typeToString(mType), fromIme)); 222 setRequestedVisible(true); 223 } 224 225 @VisibleForTesting hide()226 public void hide() { 227 if (DEBUG) Log.d(TAG, String.format("Call hide for %s on %s", 228 InsetsState.typeToString(mType), mController.getHost().getRootViewTitle())); 229 setRequestedVisible(false); 230 } 231 hide(boolean animationFinished, @AnimationType int animationType)232 void hide(boolean animationFinished, @AnimationType int animationType) { 233 hide(); 234 } 235 236 /** 237 * Called when current window gains focus 238 */ onWindowFocusGained(boolean hasViewFocus)239 public void onWindowFocusGained(boolean hasViewFocus) { 240 mHasWindowFocus = true; 241 mHasViewFocusWhenWindowFocusGain = hasViewFocus; 242 } 243 244 /** 245 * Called when current window loses focus. 246 */ onWindowFocusLost()247 public void onWindowFocusLost() { 248 mHasWindowFocus = false; 249 } 250 hasViewFocusWhenWindowFocusGain()251 boolean hasViewFocusWhenWindowFocusGain() { 252 return mHasViewFocusWhenWindowFocusGain; 253 } 254 applyLocalVisibilityOverride()255 boolean applyLocalVisibilityOverride() { 256 final InsetsSource source = mState.peekSource(mType); 257 final boolean isVisible = source != null ? source.isVisible() : getDefaultVisibility(mType); 258 final boolean hasControl = mSourceControl != null; 259 260 if (mType == ITYPE_IME) { 261 ImeTracing.getInstance().triggerClientDump( 262 "InsetsSourceConsumer#applyLocalVisibilityOverride", 263 mController.getHost().getInputMethodManager(), null /* icProto */); 264 } 265 266 // We still need to let the legacy app know the visibility change even if we don't have the 267 // control. If we don't have the source, we don't change the requested visibility for making 268 // the callback behavior compatible. 269 mController.updateCompatSysUiVisibility( 270 mType, (hasControl || source == null) ? mRequestedVisible : isVisible, hasControl); 271 272 // If we don't have control, we are not able to change the visibility. 273 if (!hasControl) { 274 if (DEBUG) Log.d(TAG, "applyLocalVisibilityOverride: No control in " 275 + mController.getHost().getRootViewTitle() 276 + " requestedVisible " + mRequestedVisible); 277 return false; 278 } 279 if (isVisible == mRequestedVisible) { 280 return false; 281 } 282 if (DEBUG) Log.d(TAG, String.format("applyLocalVisibilityOverride: %s requestedVisible: %b", 283 mController.getHost().getRootViewTitle(), mRequestedVisible)); 284 mState.getSource(mType).setVisible(mRequestedVisible); 285 return true; 286 } 287 288 @VisibleForTesting isRequestedVisible()289 public boolean isRequestedVisible() { 290 return mRequestedVisible; 291 } 292 293 /** 294 * Request to show current window type. 295 * 296 * @param fromController {@code true} if request is coming from controller. 297 * (e.g. in IME case, controller is 298 * {@link android.inputmethodservice.InputMethodService}). 299 * @return @see {@link ShowResult}. 300 */ 301 @VisibleForTesting requestShow(boolean fromController)302 public @ShowResult int requestShow(boolean fromController) { 303 return ShowResult.SHOW_IMMEDIATELY; 304 } 305 306 /** 307 * Reports that this source's perceptibility has changed 308 * 309 * @param perceptible true if the source is perceptible, false otherwise. 310 * @see InsetsAnimationControlCallbacks#reportPerceptible 311 */ onPerceptible(boolean perceptible)312 public void onPerceptible(boolean perceptible) { 313 } 314 315 /** 316 * Notify listeners that window is now hidden. 317 */ notifyHidden()318 void notifyHidden() { 319 // no-op for types that always return ShowResult#SHOW_IMMEDIATELY. 320 } 321 322 /** 323 * Remove surface on which this consumer type is drawn. 324 */ removeSurface()325 public void removeSurface() { 326 // no-op for types that always return ShowResult#SHOW_IMMEDIATELY. 327 } 328 329 @VisibleForTesting(visibility = PACKAGE) updateSource(InsetsSource newSource, @AnimationType int animationType)330 public void updateSource(InsetsSource newSource, @AnimationType int animationType) { 331 InsetsSource source = mState.peekSource(mType); 332 if (source == null || animationType == ANIMATION_TYPE_NONE 333 || source.getFrame().equals(newSource.getFrame())) { 334 mPendingFrame = null; 335 mPendingVisibleFrame = null; 336 mState.addSource(newSource); 337 return; 338 } 339 340 // Frame is changing while animating. Keep note of the new frame but keep existing frame 341 // until animation is finished. 342 newSource = new InsetsSource(newSource); 343 mPendingFrame = new Rect(newSource.getFrame()); 344 mPendingVisibleFrame = newSource.getVisibleFrame() != null 345 ? new Rect(newSource.getVisibleFrame()) 346 : null; 347 newSource.setFrame(source.getFrame()); 348 newSource.setVisibleFrame(source.getVisibleFrame()); 349 mState.addSource(newSource); 350 if (DEBUG) Log.d(TAG, "updateSource: " + newSource); 351 } 352 353 @VisibleForTesting(visibility = PACKAGE) notifyAnimationFinished()354 public boolean notifyAnimationFinished() { 355 if (mPendingFrame != null) { 356 InsetsSource source = mState.getSource(mType); 357 source.setFrame(mPendingFrame); 358 source.setVisibleFrame(mPendingVisibleFrame); 359 mPendingFrame = null; 360 mPendingVisibleFrame = null; 361 return true; 362 } 363 return false; 364 } 365 366 /** 367 * Sets requested visibility from the client, regardless of whether we are able to control it at 368 * the moment. 369 */ setRequestedVisible(boolean requestedVisible)370 protected void setRequestedVisible(boolean requestedVisible) { 371 if (mRequestedVisible != requestedVisible) { 372 mRequestedVisible = requestedVisible; 373 374 // We need an animation later if the leash of a real control (which has an insets hint) 375 // is not ready. The !mIsAnimationPending check is in case that the requested visibility 376 // is changed twice before playing the animation -- we don't need an animation in this 377 // case. 378 mIsAnimationPending = !mIsAnimationPending 379 && mSourceControl != null 380 && mSourceControl.getLeash() == null 381 && !Insets.NONE.equals(mSourceControl.getInsetsHint()); 382 383 mController.onRequestedVisibilityChanged(this); 384 if (DEBUG) Log.d(TAG, "setRequestedVisible: " + requestedVisible); 385 } 386 if (applyLocalVisibilityOverride()) { 387 mController.notifyVisibilityChanged(); 388 } 389 } 390 applyHiddenToControl()391 private void applyHiddenToControl() { 392 if (mSourceControl == null || mSourceControl.getLeash() == null) { 393 return; 394 } 395 396 final Transaction t = mTransactionSupplier.get(); 397 if (DEBUG) Log.d(TAG, "applyHiddenToControl: " + mRequestedVisible); 398 if (mRequestedVisible) { 399 t.show(mSourceControl.getLeash()); 400 } else { 401 t.hide(mSourceControl.getLeash()); 402 } 403 t.apply(); 404 onPerceptible(mRequestedVisible); 405 } 406 dumpDebug(ProtoOutputStream proto, long fieldId)407 void dumpDebug(ProtoOutputStream proto, long fieldId) { 408 final long token = proto.start(fieldId); 409 proto.write(INTERNAL_INSETS_TYPE, InsetsState.typeToString(mType)); 410 proto.write(HAS_WINDOW_FOCUS, mHasWindowFocus); 411 proto.write(IS_REQUESTED_VISIBLE, mRequestedVisible); 412 if (mSourceControl != null) { 413 mSourceControl.dumpDebug(proto, SOURCE_CONTROL); 414 } 415 if (mPendingFrame != null) { 416 mPendingFrame.dumpDebug(proto, PENDING_FRAME); 417 } 418 if (mPendingVisibleFrame != null) { 419 mPendingVisibleFrame.dumpDebug(proto, PENDING_VISIBLE_FRAME); 420 } 421 proto.end(token); 422 } 423 } 424