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.InsetsState.getDefaultVisibility; 23 import static android.view.InsetsState.toPublicType; 24 25 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; 26 27 import android.annotation.IntDef; 28 import android.annotation.Nullable; 29 import android.graphics.Rect; 30 import android.util.Log; 31 import android.view.InsetsState.InternalInsetsType; 32 import android.view.SurfaceControl.Transaction; 33 import android.view.WindowInsets.Type.InsetsType; 34 35 import com.android.internal.annotations.VisibleForTesting; 36 37 import java.lang.annotation.Retention; 38 import java.lang.annotation.RetentionPolicy; 39 import java.util.function.Supplier; 40 41 /** 42 * Controls the visibility and animations of a single window insets source. 43 * @hide 44 */ 45 public class InsetsSourceConsumer { 46 47 @Retention(RetentionPolicy.SOURCE) 48 @IntDef(value = {ShowResult.SHOW_IMMEDIATELY, ShowResult.IME_SHOW_DELAYED, ShowResult.IME_SHOW_FAILED}) 49 @interface ShowResult { 50 /** 51 * Window type is ready to be shown, will be shown immidiately. 52 */ 53 int SHOW_IMMEDIATELY = 0; 54 /** 55 * Result will be delayed. Window needs to be prepared or request is not from controller. 56 * Request will be delegated to controller and may or may not be shown. 57 */ 58 int IME_SHOW_DELAYED = 1; 59 /** 60 * Window will not be shown because one of the conditions couldn't be met. 61 * (e.g. in IME's case, when no editor is focused.) 62 */ 63 int IME_SHOW_FAILED = 2; 64 } 65 66 protected final InsetsController mController; 67 protected boolean mRequestedVisible; 68 protected final InsetsState mState; 69 protected final @InternalInsetsType int mType; 70 71 private static final String TAG = "InsetsSourceConsumer"; 72 private final Supplier<Transaction> mTransactionSupplier; 73 private @Nullable InsetsSourceControl mSourceControl; 74 private boolean mHasWindowFocus; 75 private Rect mPendingFrame; 76 private Rect mPendingVisibleFrame; 77 78 /** 79 * Indicates if we have the pending animation. When we have the control, we need to play the 80 * animation if the requested visibility is different from the current state. But if we haven't 81 * had a leash yet, we will set this flag, and play the animation once we get the leash. 82 */ 83 private boolean mIsAnimationPending; 84 InsetsSourceConsumer(@nternalInsetsType int type, InsetsState state, Supplier<Transaction> transactionSupplier, InsetsController controller)85 public InsetsSourceConsumer(@InternalInsetsType int type, InsetsState state, 86 Supplier<Transaction> transactionSupplier, InsetsController controller) { 87 mType = type; 88 mState = state; 89 mTransactionSupplier = transactionSupplier; 90 mController = controller; 91 mRequestedVisible = getDefaultVisibility(type); 92 } 93 94 /** 95 * Updates the control delivered from the server. 96 97 * @param showTypes An integer array with a single entry that determines which types a show 98 * animation should be run after setting the control. 99 * @param hideTypes An integer array with a single entry that determines which types a hide 100 * animation should be run after setting the control. 101 */ setControl(@ullable InsetsSourceControl control, @InsetsType int[] showTypes, @InsetsType int[] hideTypes)102 public void setControl(@Nullable InsetsSourceControl control, 103 @InsetsType int[] showTypes, @InsetsType int[] hideTypes) { 104 if (mSourceControl == control) { 105 return; 106 } 107 SurfaceControl oldLeash = mSourceControl != null ? mSourceControl.getLeash() : null; 108 109 final InsetsSourceControl lastControl = mSourceControl; 110 mSourceControl = control; 111 if (control != null) { 112 if (DEBUG) Log.d(TAG, String.format("setControl -> %s on %s", 113 InsetsState.typeToString(control.getType()), 114 mController.getHost().getRootViewTitle())); 115 } 116 if (mSourceControl == null) { 117 // We are loosing control 118 mController.notifyControlRevoked(this); 119 120 // Check if we need to restore server visibility. 121 final InsetsSource source = mState.getSource(mType); 122 final boolean serverVisibility = 123 mController.getLastDispatchedState().getSourceOrDefaultVisibility(mType); 124 if (source.isVisible() != serverVisibility) { 125 source.setVisible(serverVisibility); 126 mController.notifyVisibilityChanged(); 127 } 128 129 // For updateCompatSysUiVisibility 130 applyLocalVisibilityOverride(); 131 } else { 132 // We are gaining control, and need to run an animation since previous state 133 // didn't match 134 final boolean requestedVisible = isRequestedVisibleAwaitingControl(); 135 final boolean needAnimation = requestedVisible != mState.getSource(mType).isVisible(); 136 if (control.getLeash() != null && (needAnimation || mIsAnimationPending)) { 137 if (DEBUG) Log.d(TAG, String.format("Gaining control in %s, requestedVisible: %b", 138 mController.getHost().getRootViewTitle(), requestedVisible)); 139 if (requestedVisible) { 140 showTypes[0] |= toPublicType(getType()); 141 } else { 142 hideTypes[0] |= toPublicType(getType()); 143 } 144 mIsAnimationPending = false; 145 } else { 146 if (needAnimation) { 147 // We need animation but we haven't had a leash yet. Set this flag that when we 148 // get the leash we can play the deferred animation. 149 mIsAnimationPending = true; 150 } 151 // We are gaining control, but don't need to run an animation. 152 // However make sure that the leash visibility is still up to date. 153 if (applyLocalVisibilityOverride()) { 154 mController.notifyVisibilityChanged(); 155 } 156 157 // If we have a new leash, make sure visibility is up-to-date, even though we 158 // didn't want to run an animation above. 159 SurfaceControl newLeash = mSourceControl.getLeash(); 160 if (oldLeash == null || newLeash == null || !oldLeash.isSameSurface(newLeash)) { 161 applyHiddenToControl(); 162 } 163 if (!requestedVisible && !mIsAnimationPending) { 164 removeSurface(); 165 } 166 } 167 } 168 if (lastControl != null) { 169 lastControl.release(SurfaceControl::release); 170 } 171 } 172 173 @VisibleForTesting getControl()174 public InsetsSourceControl getControl() { 175 return mSourceControl; 176 } 177 178 /** 179 * Determines if the consumer will be shown after control is available. 180 * Note: for system bars this method is same as {@link #isRequestedVisible()}. 181 * 182 * @return {@code true} if consumer has a pending show. 183 */ isRequestedVisibleAwaitingControl()184 protected boolean isRequestedVisibleAwaitingControl() { 185 return isRequestedVisible(); 186 } 187 getType()188 int getType() { 189 return mType; 190 } 191 192 @VisibleForTesting show(boolean fromIme)193 public void show(boolean fromIme) { 194 if (DEBUG) Log.d(TAG, String.format("Call show() for type: %s fromIme: %b ", 195 InsetsState.typeToString(mType), fromIme)); 196 setRequestedVisible(true); 197 } 198 199 @VisibleForTesting hide()200 public void hide() { 201 if (DEBUG) Log.d(TAG, String.format("Call hide for %s on %s", 202 InsetsState.typeToString(mType), mController.getHost().getRootViewTitle())); 203 setRequestedVisible(false); 204 } 205 hide(boolean animationFinished, @AnimationType int animationType)206 void hide(boolean animationFinished, @AnimationType int animationType) { 207 hide(); 208 } 209 210 /** 211 * Called when current window gains focus 212 */ onWindowFocusGained()213 public void onWindowFocusGained() { 214 mHasWindowFocus = true; 215 } 216 217 /** 218 * Called when current window loses focus. 219 */ onWindowFocusLost()220 public void onWindowFocusLost() { 221 mHasWindowFocus = false; 222 } 223 hasWindowFocus()224 boolean hasWindowFocus() { 225 return mHasWindowFocus; 226 } 227 applyLocalVisibilityOverride()228 boolean applyLocalVisibilityOverride() { 229 final InsetsSource source = mState.peekSource(mType); 230 final boolean isVisible = source != null ? source.isVisible() : getDefaultVisibility(mType); 231 final boolean hasControl = mSourceControl != null; 232 233 // We still need to let the legacy app know the visibility change even if we don't have the 234 // control. If we don't have the source, we don't change the requested visibility for making 235 // the callback behavior compatible. 236 mController.updateCompatSysUiVisibility( 237 mType, (hasControl || source == null) ? mRequestedVisible : isVisible, hasControl); 238 239 // If we don't have control, we are not able to change the visibility. 240 if (!hasControl) { 241 if (DEBUG) Log.d(TAG, "applyLocalVisibilityOverride: No control in " 242 + mController.getHost().getRootViewTitle() 243 + " requestedVisible " + mRequestedVisible); 244 return false; 245 } 246 if (isVisible == mRequestedVisible) { 247 return false; 248 } 249 if (DEBUG) Log.d(TAG, String.format("applyLocalVisibilityOverride: %s requestedVisible: %b", 250 mController.getHost().getRootViewTitle(), mRequestedVisible)); 251 mState.getSource(mType).setVisible(mRequestedVisible); 252 return true; 253 } 254 255 @VisibleForTesting isRequestedVisible()256 public boolean isRequestedVisible() { 257 return mRequestedVisible; 258 } 259 260 /** 261 * Request to show current window type. 262 * 263 * @param fromController {@code true} if request is coming from controller. 264 * (e.g. in IME case, controller is 265 * {@link android.inputmethodservice.InputMethodService}). 266 * @return @see {@link ShowResult}. 267 */ 268 @VisibleForTesting requestShow(boolean fromController)269 public @ShowResult int requestShow(boolean fromController) { 270 return ShowResult.SHOW_IMMEDIATELY; 271 } 272 273 /** 274 * Reports that this source's perceptibility has changed 275 * 276 * @param perceptible true if the source is perceptible, false otherwise. 277 * @see InsetsAnimationControlCallbacks#reportPerceptible 278 */ onPerceptible(boolean perceptible)279 public void onPerceptible(boolean perceptible) { 280 } 281 282 /** 283 * Notify listeners that window is now hidden. 284 */ notifyHidden()285 void notifyHidden() { 286 // no-op for types that always return ShowResult#SHOW_IMMEDIATELY. 287 } 288 289 /** 290 * Remove surface on which this consumer type is drawn. 291 */ removeSurface()292 public void removeSurface() { 293 // no-op for types that always return ShowResult#SHOW_IMMEDIATELY. 294 } 295 296 @VisibleForTesting(visibility = PACKAGE) updateSource(InsetsSource newSource, @AnimationType int animationType)297 public void updateSource(InsetsSource newSource, @AnimationType int animationType) { 298 InsetsSource source = mState.peekSource(mType); 299 if (source == null || animationType == ANIMATION_TYPE_NONE 300 || source.getFrame().equals(newSource.getFrame())) { 301 mPendingFrame = null; 302 mPendingVisibleFrame = null; 303 mState.addSource(newSource); 304 return; 305 } 306 307 // Frame is changing while animating. Keep note of the new frame but keep existing frame 308 // until animation is finished. 309 newSource = new InsetsSource(newSource); 310 mPendingFrame = new Rect(newSource.getFrame()); 311 mPendingVisibleFrame = newSource.getVisibleFrame() != null 312 ? new Rect(newSource.getVisibleFrame()) 313 : null; 314 newSource.setFrame(source.getFrame()); 315 newSource.setVisibleFrame(source.getVisibleFrame()); 316 mState.addSource(newSource); 317 if (DEBUG) Log.d(TAG, "updateSource: " + newSource); 318 } 319 320 @VisibleForTesting(visibility = PACKAGE) notifyAnimationFinished()321 public boolean notifyAnimationFinished() { 322 if (mPendingFrame != null) { 323 InsetsSource source = mState.getSource(mType); 324 source.setFrame(mPendingFrame); 325 source.setVisibleFrame(mPendingVisibleFrame); 326 mPendingFrame = null; 327 mPendingVisibleFrame = null; 328 return true; 329 } 330 return false; 331 } 332 333 /** 334 * Sets requested visibility from the client, regardless of whether we are able to control it at 335 * the moment. 336 */ setRequestedVisible(boolean requestedVisible)337 protected void setRequestedVisible(boolean requestedVisible) { 338 if (mRequestedVisible != requestedVisible) { 339 mRequestedVisible = requestedVisible; 340 mIsAnimationPending = false; 341 if (DEBUG) Log.d(TAG, "setRequestedVisible: " + requestedVisible); 342 } 343 if (applyLocalVisibilityOverride()) { 344 mController.notifyVisibilityChanged(); 345 } 346 } 347 applyHiddenToControl()348 private void applyHiddenToControl() { 349 if (mSourceControl == null || mSourceControl.getLeash() == null) { 350 return; 351 } 352 353 final Transaction t = mTransactionSupplier.get(); 354 if (DEBUG) Log.d(TAG, "applyHiddenToControl: " + mRequestedVisible); 355 if (mRequestedVisible) { 356 t.show(mSourceControl.getLeash()); 357 } else { 358 t.hide(mSourceControl.getLeash()); 359 } 360 t.apply(); 361 onPerceptible(mRequestedVisible); 362 } 363 } 364