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 // We are loosing control 117 if (mSourceControl == null) { 118 mController.notifyControlRevoked(this); 119 120 // Restore server visibility. 121 mState.getSource(getType()).setVisible( 122 mController.getLastDispatchedState().getSource(getType()).isVisible()); 123 applyLocalVisibilityOverride(); 124 } else { 125 // We are gaining control, and need to run an animation since previous state 126 // didn't match 127 final boolean requestedVisible = isRequestedVisibleAwaitingControl(); 128 final boolean needAnimation = requestedVisible != mState.getSource(mType).isVisible(); 129 if (control.getLeash() != null && (needAnimation || mIsAnimationPending)) { 130 if (DEBUG) Log.d(TAG, String.format("Gaining control in %s, requestedVisible: %b", 131 mController.getHost().getRootViewTitle(), requestedVisible)); 132 if (requestedVisible) { 133 showTypes[0] |= toPublicType(getType()); 134 } else { 135 hideTypes[0] |= toPublicType(getType()); 136 } 137 mIsAnimationPending = false; 138 } else { 139 if (needAnimation) { 140 // We need animation but we haven't had a leash yet. Set this flag that when we 141 // get the leash we can play the deferred animation. 142 mIsAnimationPending = true; 143 } 144 // We are gaining control, but don't need to run an animation. 145 // However make sure that the leash visibility is still up to date. 146 if (applyLocalVisibilityOverride()) { 147 mController.notifyVisibilityChanged(); 148 } 149 150 // If we have a new leash, make sure visibility is up-to-date, even though we 151 // didn't want to run an animation above. 152 SurfaceControl newLeash = mSourceControl.getLeash(); 153 if (oldLeash == null || newLeash == null || !oldLeash.isSameSurface(newLeash)) { 154 applyHiddenToControl(); 155 } 156 if (!requestedVisible && !mIsAnimationPending) { 157 removeSurface(); 158 } 159 } 160 } 161 if (lastControl != null) { 162 lastControl.release(SurfaceControl::release); 163 } 164 } 165 166 @VisibleForTesting getControl()167 public InsetsSourceControl getControl() { 168 return mSourceControl; 169 } 170 171 /** 172 * Determines if the consumer will be shown after control is available. 173 * Note: for system bars this method is same as {@link #isRequestedVisible()}. 174 * 175 * @return {@code true} if consumer has a pending show. 176 */ isRequestedVisibleAwaitingControl()177 protected boolean isRequestedVisibleAwaitingControl() { 178 return isRequestedVisible(); 179 } 180 getType()181 int getType() { 182 return mType; 183 } 184 185 @VisibleForTesting show(boolean fromIme)186 public void show(boolean fromIme) { 187 if (DEBUG) Log.d(TAG, String.format("Call show() for type: %s fromIme: %b ", 188 InsetsState.typeToString(mType), fromIme)); 189 setRequestedVisible(true); 190 } 191 192 @VisibleForTesting hide()193 public void hide() { 194 if (DEBUG) Log.d(TAG, String.format("Call hide for %s on %s", 195 InsetsState.typeToString(mType), mController.getHost().getRootViewTitle())); 196 setRequestedVisible(false); 197 } 198 hide(boolean animationFinished, @AnimationType int animationType)199 void hide(boolean animationFinished, @AnimationType int animationType) { 200 hide(); 201 } 202 203 /** 204 * Called when current window gains focus 205 */ onWindowFocusGained()206 public void onWindowFocusGained() { 207 mHasWindowFocus = true; 208 } 209 210 /** 211 * Called when current window loses focus. 212 */ onWindowFocusLost()213 public void onWindowFocusLost() { 214 mHasWindowFocus = false; 215 } 216 hasWindowFocus()217 boolean hasWindowFocus() { 218 return mHasWindowFocus; 219 } 220 applyLocalVisibilityOverride()221 boolean applyLocalVisibilityOverride() { 222 final InsetsSource source = mState.peekSource(mType); 223 final boolean isVisible = source != null ? source.isVisible() : getDefaultVisibility(mType); 224 final boolean hasControl = mSourceControl != null; 225 226 // We still need to let the legacy app know the visibility change even if we don't have the 227 // control. If we don't have the source, we don't change the requested visibility for making 228 // the callback behavior compatible. 229 mController.updateCompatSysUiVisibility( 230 mType, (hasControl || source == null) ? mRequestedVisible : isVisible, hasControl); 231 232 // If we don't have control, we are not able to change the visibility. 233 if (!hasControl) { 234 if (DEBUG) Log.d(TAG, "applyLocalVisibilityOverride: No control in " 235 + mController.getHost().getRootViewTitle() 236 + " requestedVisible " + mRequestedVisible); 237 return false; 238 } 239 if (isVisible == mRequestedVisible) { 240 return false; 241 } 242 if (DEBUG) Log.d(TAG, String.format("applyLocalVisibilityOverride: %s requestedVisible: %b", 243 mController.getHost().getRootViewTitle(), mRequestedVisible)); 244 mState.getSource(mType).setVisible(mRequestedVisible); 245 return true; 246 } 247 248 @VisibleForTesting isRequestedVisible()249 public boolean isRequestedVisible() { 250 return mRequestedVisible; 251 } 252 253 /** 254 * Request to show current window type. 255 * 256 * @param fromController {@code true} if request is coming from controller. 257 * (e.g. in IME case, controller is 258 * {@link android.inputmethodservice.InputMethodService}). 259 * @return @see {@link ShowResult}. 260 */ 261 @VisibleForTesting requestShow(boolean fromController)262 public @ShowResult int requestShow(boolean fromController) { 263 return ShowResult.SHOW_IMMEDIATELY; 264 } 265 266 /** 267 * Reports that this source's perceptibility has changed 268 * 269 * @param perceptible true if the source is perceptible, false otherwise. 270 * @see InsetsAnimationControlCallbacks#reportPerceptible 271 */ onPerceptible(boolean perceptible)272 public void onPerceptible(boolean perceptible) { 273 } 274 275 /** 276 * Notify listeners that window is now hidden. 277 */ notifyHidden()278 void notifyHidden() { 279 // no-op for types that always return ShowResult#SHOW_IMMEDIATELY. 280 } 281 282 /** 283 * Remove surface on which this consumer type is drawn. 284 */ removeSurface()285 public void removeSurface() { 286 // no-op for types that always return ShowResult#SHOW_IMMEDIATELY. 287 } 288 289 @VisibleForTesting(visibility = PACKAGE) updateSource(InsetsSource newSource, @AnimationType int animationType)290 public void updateSource(InsetsSource newSource, @AnimationType int animationType) { 291 InsetsSource source = mState.peekSource(mType); 292 if (source == null || animationType == ANIMATION_TYPE_NONE 293 || source.getFrame().equals(newSource.getFrame())) { 294 mPendingFrame = null; 295 mPendingVisibleFrame = null; 296 mState.addSource(newSource); 297 return; 298 } 299 300 // Frame is changing while animating. Keep note of the new frame but keep existing frame 301 // until animation is finished. 302 newSource = new InsetsSource(newSource); 303 mPendingFrame = new Rect(newSource.getFrame()); 304 mPendingVisibleFrame = newSource.getVisibleFrame() != null 305 ? new Rect(newSource.getVisibleFrame()) 306 : null; 307 newSource.setFrame(source.getFrame()); 308 newSource.setVisibleFrame(source.getVisibleFrame()); 309 mState.addSource(newSource); 310 if (DEBUG) Log.d(TAG, "updateSource: " + newSource); 311 } 312 313 @VisibleForTesting(visibility = PACKAGE) notifyAnimationFinished()314 public boolean notifyAnimationFinished() { 315 if (mPendingFrame != null) { 316 InsetsSource source = mState.getSource(mType); 317 source.setFrame(mPendingFrame); 318 source.setVisibleFrame(mPendingVisibleFrame); 319 mPendingFrame = null; 320 mPendingVisibleFrame = null; 321 return true; 322 } 323 return false; 324 } 325 326 /** 327 * Sets requested visibility from the client, regardless of whether we are able to control it at 328 * the moment. 329 */ setRequestedVisible(boolean requestedVisible)330 protected void setRequestedVisible(boolean requestedVisible) { 331 if (mRequestedVisible != requestedVisible) { 332 mRequestedVisible = requestedVisible; 333 mIsAnimationPending = false; 334 if (DEBUG) Log.d(TAG, "setRequestedVisible: " + requestedVisible); 335 } 336 if (applyLocalVisibilityOverride()) { 337 mController.notifyVisibilityChanged(); 338 } 339 } 340 applyHiddenToControl()341 private void applyHiddenToControl() { 342 if (mSourceControl == null || mSourceControl.getLeash() == null) { 343 return; 344 } 345 346 final Transaction t = mTransactionSupplier.get(); 347 if (DEBUG) Log.d(TAG, "applyHiddenToControl: " + mRequestedVisible); 348 if (mRequestedVisible) { 349 t.show(mSourceControl.getLeash()); 350 } else { 351 t.hide(mSourceControl.getLeash()); 352 } 353 t.apply(); 354 onPerceptible(mRequestedVisible); 355 } 356 } 357