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.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 20 import static android.view.InsetsAnimationControlImplProto.CURRENT_ALPHA; 21 import static android.view.InsetsAnimationControlImplProto.IS_CANCELLED; 22 import static android.view.InsetsAnimationControlImplProto.IS_FINISHED; 23 import static android.view.InsetsAnimationControlImplProto.PENDING_ALPHA; 24 import static android.view.InsetsAnimationControlImplProto.PENDING_FRACTION; 25 import static android.view.InsetsAnimationControlImplProto.PENDING_INSETS; 26 import static android.view.InsetsAnimationControlImplProto.SHOWN_ON_FINISH; 27 import static android.view.InsetsAnimationControlImplProto.TMP_MATRIX; 28 import static android.view.InsetsController.ANIMATION_TYPE_SHOW; 29 import static android.view.InsetsController.AnimationType; 30 import static android.view.InsetsController.DEBUG; 31 import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN; 32 import static android.view.InsetsController.LayoutInsetsDuringAnimation; 33 import static android.view.InsetsState.ISIDE_BOTTOM; 34 import static android.view.InsetsState.ISIDE_FLOATING; 35 import static android.view.InsetsState.ISIDE_LEFT; 36 import static android.view.InsetsState.ISIDE_RIGHT; 37 import static android.view.InsetsState.ISIDE_TOP; 38 import static android.view.InsetsState.ITYPE_IME; 39 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; 40 41 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; 42 43 import android.annotation.Nullable; 44 import android.content.res.CompatibilityInfo; 45 import android.graphics.Insets; 46 import android.graphics.Matrix; 47 import android.graphics.Point; 48 import android.graphics.Rect; 49 import android.util.ArraySet; 50 import android.util.Log; 51 import android.util.SparseArray; 52 import android.util.SparseIntArray; 53 import android.util.SparseSetArray; 54 import android.util.proto.ProtoOutputStream; 55 import android.view.InsetsState.InternalInsetsSide; 56 import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; 57 import android.view.WindowInsets.Type.InsetsType; 58 import android.view.WindowInsetsAnimation.Bounds; 59 import android.view.WindowManager.LayoutParams; 60 import android.view.animation.Interpolator; 61 62 import com.android.internal.annotations.VisibleForTesting; 63 64 import java.util.ArrayList; 65 import java.util.Objects; 66 67 /** 68 * Implements {@link WindowInsetsAnimationController} 69 * @hide 70 */ 71 @VisibleForTesting 72 public class InsetsAnimationControlImpl implements InternalInsetsAnimationController, 73 InsetsAnimationControlRunner { 74 75 private static final String TAG = "InsetsAnimationCtrlImpl"; 76 77 private final Rect mTmpFrame = new Rect(); 78 79 private final WindowInsetsAnimationControlListener mListener; 80 private final SparseArray<InsetsSourceControl> mControls; 81 private final SparseSetArray<InsetsSourceControl> mSideControlsMap = new SparseSetArray<>(); 82 83 /** @see WindowInsetsAnimationController#getHiddenStateInsets */ 84 private final Insets mHiddenInsets; 85 86 /** @see WindowInsetsAnimationController#getShownStateInsets */ 87 private final Insets mShownInsets; 88 private final Matrix mTmpMatrix = new Matrix(); 89 private final InsetsState mInitialInsetsState; 90 private final @AnimationType int mAnimationType; 91 private final @LayoutInsetsDuringAnimation int mLayoutInsetsDuringAnimation; 92 private final @InsetsType int mTypes; 93 private @InsetsType int mControllingTypes; 94 private final InsetsAnimationControlCallbacks mController; 95 private final WindowInsetsAnimation mAnimation; 96 /** @see WindowInsetsAnimationController#hasZeroInsetsIme */ 97 private final boolean mHasZeroInsetsIme; 98 private final CompatibilityInfo.Translator mTranslator; 99 private Insets mCurrentInsets; 100 private Insets mPendingInsets; 101 private float mPendingFraction; 102 private boolean mFinished; 103 private boolean mCancelled; 104 private boolean mShownOnFinish; 105 private float mCurrentAlpha = 1.0f; 106 private float mPendingAlpha = 1.0f; 107 @VisibleForTesting(visibility = PACKAGE) 108 private boolean mReadyDispatched; 109 private Boolean mPerceptible; 110 111 @VisibleForTesting InsetsAnimationControlImpl(SparseArray<InsetsSourceControl> controls, @Nullable Rect frame, InsetsState state, WindowInsetsAnimationControlListener listener, @InsetsType int types, InsetsAnimationControlCallbacks controller, long durationMs, Interpolator interpolator, @AnimationType int animationType, @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation, CompatibilityInfo.Translator translator)112 public InsetsAnimationControlImpl(SparseArray<InsetsSourceControl> controls, 113 @Nullable Rect frame, InsetsState state, WindowInsetsAnimationControlListener listener, 114 @InsetsType int types, InsetsAnimationControlCallbacks controller, long durationMs, 115 Interpolator interpolator, @AnimationType int animationType, 116 @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation, 117 CompatibilityInfo.Translator translator) { 118 mControls = controls; 119 mListener = listener; 120 mTypes = types; 121 mControllingTypes = types; 122 mController = controller; 123 mInitialInsetsState = new InsetsState(state, true /* copySources */); 124 if (frame != null) { 125 final SparseIntArray typeSideMap = new SparseIntArray(); 126 mCurrentInsets = getInsetsFromState(mInitialInsetsState, frame, null /* typeSideMap */); 127 mHiddenInsets = calculateInsets(mInitialInsetsState, frame, controls, false /* shown */, 128 null /* typeSideMap */); 129 mShownInsets = calculateInsets(mInitialInsetsState, frame, controls, true /* shown */, 130 typeSideMap); 131 mHasZeroInsetsIme = mShownInsets.bottom == 0 && controlsInternalType(ITYPE_IME); 132 if (mHasZeroInsetsIme) { 133 // IME has shownInsets of ZERO, and can't map to a side by default. 134 // Map zero insets IME to bottom, making it a special case of bottom insets. 135 typeSideMap.put(ITYPE_IME, ISIDE_BOTTOM); 136 } 137 buildSideControlsMap(typeSideMap, mSideControlsMap, controls); 138 } else { 139 // Passing a null frame indicates the caller wants to play the insets animation anyway, 140 // no matter the source provides insets to the frame or not. 141 mCurrentInsets = calculateInsets(mInitialInsetsState, controls, true /* shown */); 142 mHiddenInsets = calculateInsets(null, controls, false /* shown */); 143 mShownInsets = calculateInsets(null, controls, true /* shown */); 144 mHasZeroInsetsIme = mShownInsets.bottom == 0 && controlsInternalType(ITYPE_IME); 145 buildSideControlsMap(mSideControlsMap, controls); 146 } 147 mPendingInsets = mCurrentInsets; 148 149 mAnimation = new WindowInsetsAnimation(mTypes, interpolator, 150 durationMs); 151 mAnimation.setAlpha(getCurrentAlpha()); 152 mAnimationType = animationType; 153 mLayoutInsetsDuringAnimation = layoutInsetsDuringAnimation; 154 mTranslator = translator; 155 mController.startAnimation(this, listener, types, mAnimation, 156 new Bounds(mHiddenInsets, mShownInsets)); 157 } 158 calculatePerceptible(Insets currentInsets, float currentAlpha)159 private boolean calculatePerceptible(Insets currentInsets, float currentAlpha) { 160 return 100 * currentInsets.left >= 5 * (mShownInsets.left - mHiddenInsets.left) 161 && 100 * currentInsets.top >= 5 * (mShownInsets.top - mHiddenInsets.top) 162 && 100 * currentInsets.right >= 5 * (mShownInsets.right - mHiddenInsets.right) 163 && 100 * currentInsets.bottom >= 5 * (mShownInsets.bottom - mHiddenInsets.bottom) 164 && currentAlpha >= 0.5f; 165 } 166 167 @Override hasZeroInsetsIme()168 public boolean hasZeroInsetsIme() { 169 return mHasZeroInsetsIme; 170 } 171 172 @Override setReadyDispatched(boolean dispatched)173 public void setReadyDispatched(boolean dispatched) { 174 mReadyDispatched = dispatched; 175 } 176 177 @Override getHiddenStateInsets()178 public Insets getHiddenStateInsets() { 179 return mHiddenInsets; 180 } 181 182 @Override getShownStateInsets()183 public Insets getShownStateInsets() { 184 return mShownInsets; 185 } 186 187 @Override getCurrentInsets()188 public Insets getCurrentInsets() { 189 return mCurrentInsets; 190 } 191 192 @Override getCurrentAlpha()193 public float getCurrentAlpha() { 194 return mCurrentAlpha; 195 } 196 197 @Override getTypes()198 @InsetsType public int getTypes() { 199 return mTypes; 200 } 201 202 @Override getControllingTypes()203 public int getControllingTypes() { 204 return mControllingTypes; 205 } 206 207 @Override notifyControlRevoked(@nsetsType int types)208 public void notifyControlRevoked(@InsetsType int types) { 209 mControllingTypes &= ~types; 210 } 211 212 @Override updateSurfacePosition(SparseArray<InsetsSourceControl> controls)213 public void updateSurfacePosition(SparseArray<InsetsSourceControl> controls) { 214 for (int i = controls.size() - 1; i >= 0; i--) { 215 final InsetsSourceControl control = controls.valueAt(i); 216 final InsetsSourceControl c = mControls.get(control.getType()); 217 if (c == null) { 218 continue; 219 } 220 final Point position = control.getSurfacePosition(); 221 c.setSurfacePosition(position.x, position.y); 222 } 223 } 224 225 @Override getAnimationType()226 public @AnimationType int getAnimationType() { 227 return mAnimationType; 228 } 229 230 @Override setInsetsAndAlpha(Insets insets, float alpha, float fraction)231 public void setInsetsAndAlpha(Insets insets, float alpha, float fraction) { 232 setInsetsAndAlpha(insets, alpha, fraction, false /* allowWhenFinished */); 233 } 234 setInsetsAndAlpha(Insets insets, float alpha, float fraction, boolean allowWhenFinished)235 private void setInsetsAndAlpha(Insets insets, float alpha, float fraction, 236 boolean allowWhenFinished) { 237 if (!allowWhenFinished && mFinished) { 238 throw new IllegalStateException( 239 "Can't change insets on an animation that is finished."); 240 } 241 if (mCancelled) { 242 throw new IllegalStateException( 243 "Can't change insets on an animation that is cancelled."); 244 } 245 mPendingFraction = sanitize(fraction); 246 mPendingInsets = sanitize(insets); 247 mPendingAlpha = sanitize(alpha); 248 mController.scheduleApplyChangeInsets(this); 249 boolean perceptible = calculatePerceptible(mPendingInsets, mPendingAlpha); 250 if (mPerceptible == null || perceptible != mPerceptible) { 251 mController.reportPerceptible(mTypes, perceptible); 252 mPerceptible = perceptible; 253 } 254 } 255 256 @VisibleForTesting 257 /** 258 * @return Whether the finish callback of this animation should be invoked. 259 */ applyChangeInsets(@ullable InsetsState outState)260 public boolean applyChangeInsets(@Nullable InsetsState outState) { 261 if (mCancelled) { 262 if (DEBUG) Log.d(TAG, "applyChangeInsets canceled"); 263 return false; 264 } 265 final Insets offset = Insets.subtract(mShownInsets, mPendingInsets); 266 ArrayList<SurfaceParams> params = new ArrayList<>(); 267 updateLeashesForSide(ISIDE_LEFT, offset.left, mPendingInsets.left, params, outState, 268 mPendingAlpha); 269 updateLeashesForSide(ISIDE_TOP, offset.top, mPendingInsets.top, params, outState, 270 mPendingAlpha); 271 updateLeashesForSide(ISIDE_RIGHT, offset.right, mPendingInsets.right, params, outState, 272 mPendingAlpha); 273 updateLeashesForSide(ISIDE_BOTTOM, offset.bottom, mPendingInsets.bottom, params, outState, 274 mPendingAlpha); 275 276 mController.applySurfaceParams(params.toArray(new SurfaceParams[params.size()])); 277 mCurrentInsets = mPendingInsets; 278 mAnimation.setFraction(mPendingFraction); 279 mCurrentAlpha = mPendingAlpha; 280 mAnimation.setAlpha(mPendingAlpha); 281 if (mFinished) { 282 if (DEBUG) Log.d(TAG, String.format( 283 "notifyFinished shown: %s, currentAlpha: %f, currentInsets: %s", 284 mShownOnFinish, mCurrentAlpha, mCurrentInsets)); 285 mController.notifyFinished(this, mShownOnFinish); 286 releaseLeashes(); 287 if (DEBUG) Log.d(TAG, "Animation finished abruptly."); 288 } 289 return mFinished; 290 } 291 releaseLeashes()292 private void releaseLeashes() { 293 for (int i = mControls.size() - 1; i >= 0; i--) { 294 final InsetsSourceControl c = mControls.valueAt(i); 295 if (c == null) continue; 296 c.release(mController::releaseSurfaceControlFromRt); 297 } 298 } 299 300 @Override finish(boolean shown)301 public void finish(boolean shown) { 302 if (mCancelled || mFinished) { 303 if (DEBUG) Log.d(TAG, "Animation already canceled or finished, not notifying."); 304 return; 305 } 306 mShownOnFinish = shown; 307 mFinished = true; 308 setInsetsAndAlpha(shown ? mShownInsets : mHiddenInsets, mPendingAlpha, 1f /* fraction */, 309 true /* allowWhenFinished */); 310 311 if (DEBUG) Log.d(TAG, "notify control request finished for types: " + mTypes); 312 mListener.onFinished(this); 313 } 314 315 @Override 316 @VisibleForTesting getCurrentFraction()317 public float getCurrentFraction() { 318 return mAnimation.getFraction(); 319 } 320 321 @Override cancel()322 public void cancel() { 323 if (mFinished) { 324 return; 325 } 326 mPendingInsets = mLayoutInsetsDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN 327 ? mShownInsets : mHiddenInsets; 328 mPendingAlpha = 1f; 329 applyChangeInsets(null); 330 mCancelled = true; 331 mListener.onCancelled(mReadyDispatched ? this : null); 332 if (DEBUG) Log.d(TAG, "notify Control request cancelled for types: " + mTypes); 333 334 releaseLeashes(); 335 } 336 337 @Override isFinished()338 public boolean isFinished() { 339 return mFinished; 340 } 341 342 @Override isCancelled()343 public boolean isCancelled() { 344 return mCancelled; 345 } 346 347 @Override getAnimation()348 public WindowInsetsAnimation getAnimation() { 349 return mAnimation; 350 } 351 352 @Override dumpDebug(ProtoOutputStream proto, long fieldId)353 public void dumpDebug(ProtoOutputStream proto, long fieldId) { 354 final long token = proto.start(fieldId); 355 proto.write(IS_CANCELLED, mCancelled); 356 proto.write(IS_FINISHED, mFinished); 357 proto.write(TMP_MATRIX, Objects.toString(mTmpMatrix)); 358 proto.write(PENDING_INSETS, Objects.toString(mPendingInsets)); 359 proto.write(PENDING_FRACTION, mPendingFraction); 360 proto.write(SHOWN_ON_FINISH, mShownOnFinish); 361 proto.write(CURRENT_ALPHA, mCurrentAlpha); 362 proto.write(PENDING_ALPHA, mPendingAlpha); 363 proto.end(token); 364 } 365 getControls()366 SparseArray<InsetsSourceControl> getControls() { 367 return mControls; 368 } 369 getInsetsFromState(InsetsState state, Rect frame, @Nullable @InternalInsetsSide SparseIntArray typeSideMap)370 private Insets getInsetsFromState(InsetsState state, Rect frame, 371 @Nullable @InternalInsetsSide SparseIntArray typeSideMap) { 372 return state.calculateInsets(frame, null /* ignoringVisibilityState */, 373 false /* isScreenRound */, false /* alwaysConsumeSystemBars */, 374 LayoutParams.SOFT_INPUT_ADJUST_RESIZE /* legacySoftInputMode*/, 375 0 /* legacyWindowFlags */, 0 /* legacySystemUiFlags */, TYPE_APPLICATION, 376 WINDOWING_MODE_UNDEFINED, typeSideMap).getInsets(mTypes); 377 } 378 379 /** Computes the insets relative to the given frame. */ calculateInsets(InsetsState state, Rect frame, SparseArray<InsetsSourceControl> controls, boolean shown, @Nullable @InternalInsetsSide SparseIntArray typeSideMap)380 private Insets calculateInsets(InsetsState state, Rect frame, 381 SparseArray<InsetsSourceControl> controls, boolean shown, 382 @Nullable @InternalInsetsSide SparseIntArray typeSideMap) { 383 for (int i = controls.size() - 1; i >= 0; i--) { 384 final InsetsSourceControl control = controls.valueAt(i); 385 if (control == null) { 386 // control may be null if it got revoked. 387 continue; 388 } 389 state.getSource(control.getType()).setVisible(shown); 390 } 391 return getInsetsFromState(state, frame, typeSideMap); 392 } 393 394 /** Computes the insets from the insets hints of controls. */ calculateInsets(InsetsState state, SparseArray<InsetsSourceControl> controls, boolean shownOrCurrent)395 private Insets calculateInsets(InsetsState state, SparseArray<InsetsSourceControl> controls, 396 boolean shownOrCurrent) { 397 Insets insets = Insets.NONE; 398 if (!shownOrCurrent) { 399 return insets; 400 } 401 for (int i = controls.size() - 1; i >= 0; i--) { 402 final InsetsSourceControl control = controls.valueAt(i); 403 if (control == null) { 404 // control may be null if it got revoked. 405 continue; 406 } 407 if (state == null || state.getSource(control.getType()).isVisible()) { 408 insets = Insets.max(insets, control.getInsetsHint()); 409 } 410 } 411 return insets; 412 } 413 sanitize(Insets insets)414 private Insets sanitize(Insets insets) { 415 if (insets == null) { 416 insets = getCurrentInsets(); 417 } 418 if (hasZeroInsetsIme()) { 419 return insets; 420 } 421 return Insets.max(Insets.min(insets, mShownInsets), mHiddenInsets); 422 } 423 sanitize(float alpha)424 private static float sanitize(float alpha) { 425 return alpha >= 1 ? 1 : (alpha <= 0 ? 0 : alpha); 426 } 427 updateLeashesForSide(@nternalInsetsSide int side, int offset, int inset, ArrayList<SurfaceParams> surfaceParams, @Nullable InsetsState outState, float alpha)428 private void updateLeashesForSide(@InternalInsetsSide int side, int offset, int inset, 429 ArrayList<SurfaceParams> surfaceParams, @Nullable InsetsState outState, float alpha) { 430 final ArraySet<InsetsSourceControl> controls = mSideControlsMap.get(side); 431 if (controls == null) { 432 return; 433 } 434 // TODO: Implement behavior when inset spans over multiple types 435 for (int i = controls.size() - 1; i >= 0; i--) { 436 final InsetsSourceControl control = controls.valueAt(i); 437 final InsetsSource source = mInitialInsetsState.getSource(control.getType()); 438 final SurfaceControl leash = control.getLeash(); 439 440 mTmpMatrix.setTranslate(control.getSurfacePosition().x, control.getSurfacePosition().y); 441 mTmpFrame.set(source.getFrame()); 442 addTranslationToMatrix(side, offset, mTmpMatrix, mTmpFrame); 443 444 final boolean visible = mHasZeroInsetsIme && side == ISIDE_BOTTOM 445 ? (mAnimationType == ANIMATION_TYPE_SHOW || !mFinished) 446 : inset != 0; 447 448 if (outState != null) { 449 outState.getSource(source.getType()).setVisible(visible); 450 outState.getSource(source.getType()).setFrame(mTmpFrame); 451 } 452 453 // If the system is controlling the insets source, the leash can be null. 454 if (leash != null) { 455 SurfaceParams params = new SurfaceParams.Builder(leash) 456 .withAlpha(alpha) 457 .withMatrix(mTmpMatrix) 458 .withVisibility(visible) 459 .build(); 460 surfaceParams.add(params); 461 } 462 } 463 } 464 addTranslationToMatrix(@nternalInsetsSide int side, int offset, Matrix m, Rect frame)465 private void addTranslationToMatrix(@InternalInsetsSide int side, int offset, Matrix m, 466 Rect frame) { 467 final float surfaceOffset = mTranslator != null 468 ? mTranslator.translateLengthInAppWindowToScreen(offset) : offset; 469 switch (side) { 470 case ISIDE_LEFT: 471 m.postTranslate(-surfaceOffset, 0); 472 frame.offset(-offset, 0); 473 break; 474 case ISIDE_TOP: 475 m.postTranslate(0, -surfaceOffset); 476 frame.offset(0, -offset); 477 break; 478 case ISIDE_RIGHT: 479 m.postTranslate(surfaceOffset, 0); 480 frame.offset(offset, 0); 481 break; 482 case ISIDE_BOTTOM: 483 m.postTranslate(0, surfaceOffset); 484 frame.offset(0, offset); 485 break; 486 } 487 } 488 buildSideControlsMap(SparseIntArray typeSideMap, SparseSetArray<InsetsSourceControl> sideControlsMap, SparseArray<InsetsSourceControl> controls)489 private static void buildSideControlsMap(SparseIntArray typeSideMap, 490 SparseSetArray<InsetsSourceControl> sideControlsMap, 491 SparseArray<InsetsSourceControl> controls) { 492 for (int i = typeSideMap.size() - 1; i >= 0; i--) { 493 final int type = typeSideMap.keyAt(i); 494 final int side = typeSideMap.valueAt(i); 495 final InsetsSourceControl control = controls.get(type); 496 if (control == null) { 497 // If the types that we are controlling are less than the types that the system has, 498 // there can be some null controllers. 499 continue; 500 } 501 sideControlsMap.add(side, control); 502 } 503 } 504 buildSideControlsMap( SparseSetArray<InsetsSourceControl> sideControlsMap, SparseArray<InsetsSourceControl> controls)505 private static void buildSideControlsMap( 506 SparseSetArray<InsetsSourceControl> sideControlsMap, 507 SparseArray<InsetsSourceControl> controls) { 508 for (int i = controls.size() - 1; i >= 0; i--) { 509 final InsetsSourceControl control = controls.valueAt(i); 510 if (control == null) { 511 // control may be null if it got revoked. 512 continue; 513 } 514 @InternalInsetsSide int side = InsetsState.getInsetSide(control.getInsetsHint()); 515 if (side == ISIDE_FLOATING && control.getType() == ITYPE_IME) { 516 side = ISIDE_BOTTOM; 517 } 518 sideControlsMap.add(side, control); 519 } 520 } 521 } 522