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 WindowInsetsAnimationController, 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 public 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 getHiddenStateInsets()173 public Insets getHiddenStateInsets() { 174 return mHiddenInsets; 175 } 176 177 @Override getShownStateInsets()178 public Insets getShownStateInsets() { 179 return mShownInsets; 180 } 181 182 @Override getCurrentInsets()183 public Insets getCurrentInsets() { 184 return mCurrentInsets; 185 } 186 187 @Override getCurrentAlpha()188 public float getCurrentAlpha() { 189 return mCurrentAlpha; 190 } 191 192 @Override getTypes()193 @InsetsType public int getTypes() { 194 return mTypes; 195 } 196 197 @Override getControllingTypes()198 public int getControllingTypes() { 199 return mControllingTypes; 200 } 201 202 @Override notifyControlRevoked(@nsetsType int types)203 public void notifyControlRevoked(@InsetsType int types) { 204 mControllingTypes &= ~types; 205 } 206 207 @Override updateSurfacePosition(SparseArray<InsetsSourceControl> controls)208 public void updateSurfacePosition(SparseArray<InsetsSourceControl> controls) { 209 for (int i = controls.size() - 1; i >= 0; i--) { 210 final InsetsSourceControl control = controls.valueAt(i); 211 final InsetsSourceControl c = mControls.get(control.getType()); 212 if (c == null) { 213 continue; 214 } 215 final Point position = control.getSurfacePosition(); 216 c.setSurfacePosition(position.x, position.y); 217 } 218 } 219 220 @Override getAnimationType()221 public @AnimationType int getAnimationType() { 222 return mAnimationType; 223 } 224 225 @Override setInsetsAndAlpha(Insets insets, float alpha, float fraction)226 public void setInsetsAndAlpha(Insets insets, float alpha, float fraction) { 227 setInsetsAndAlpha(insets, alpha, fraction, false /* allowWhenFinished */); 228 } 229 setInsetsAndAlpha(Insets insets, float alpha, float fraction, boolean allowWhenFinished)230 private void setInsetsAndAlpha(Insets insets, float alpha, float fraction, 231 boolean allowWhenFinished) { 232 if (!allowWhenFinished && mFinished) { 233 throw new IllegalStateException( 234 "Can't change insets on an animation that is finished."); 235 } 236 if (mCancelled) { 237 throw new IllegalStateException( 238 "Can't change insets on an animation that is cancelled."); 239 } 240 mPendingFraction = sanitize(fraction); 241 mPendingInsets = sanitize(insets); 242 mPendingAlpha = sanitize(alpha); 243 mController.scheduleApplyChangeInsets(this); 244 boolean perceptible = calculatePerceptible(mPendingInsets, mPendingAlpha); 245 if (mPerceptible == null || perceptible != mPerceptible) { 246 mController.reportPerceptible(mTypes, perceptible); 247 mPerceptible = perceptible; 248 } 249 } 250 251 @VisibleForTesting 252 /** 253 * @return Whether the finish callback of this animation should be invoked. 254 */ applyChangeInsets(@ullable InsetsState outState)255 public boolean applyChangeInsets(@Nullable InsetsState outState) { 256 if (mCancelled) { 257 if (DEBUG) Log.d(TAG, "applyChangeInsets canceled"); 258 return false; 259 } 260 final Insets offset = Insets.subtract(mShownInsets, mPendingInsets); 261 ArrayList<SurfaceParams> params = new ArrayList<>(); 262 updateLeashesForSide(ISIDE_LEFT, offset.left, mPendingInsets.left, params, outState, 263 mPendingAlpha); 264 updateLeashesForSide(ISIDE_TOP, offset.top, mPendingInsets.top, params, outState, 265 mPendingAlpha); 266 updateLeashesForSide(ISIDE_RIGHT, offset.right, mPendingInsets.right, params, outState, 267 mPendingAlpha); 268 updateLeashesForSide(ISIDE_BOTTOM, offset.bottom, mPendingInsets.bottom, params, outState, 269 mPendingAlpha); 270 271 mController.applySurfaceParams(params.toArray(new SurfaceParams[params.size()])); 272 mCurrentInsets = mPendingInsets; 273 mAnimation.setFraction(mPendingFraction); 274 mCurrentAlpha = mPendingAlpha; 275 mAnimation.setAlpha(mPendingAlpha); 276 if (mFinished) { 277 if (DEBUG) Log.d(TAG, String.format( 278 "notifyFinished shown: %s, currentAlpha: %f, currentInsets: %s", 279 mShownOnFinish, mCurrentAlpha, mCurrentInsets)); 280 mController.notifyFinished(this, mShownOnFinish); 281 releaseLeashes(); 282 } 283 if (DEBUG) Log.d(TAG, "Animation finished abruptly."); 284 return mFinished; 285 } 286 releaseLeashes()287 private void releaseLeashes() { 288 for (int i = mControls.size() - 1; i >= 0; i--) { 289 final InsetsSourceControl c = mControls.valueAt(i); 290 if (c == null) continue; 291 c.release(mController::releaseSurfaceControlFromRt); 292 } 293 } 294 295 @Override finish(boolean shown)296 public void finish(boolean shown) { 297 if (mCancelled || mFinished) { 298 if (DEBUG) Log.d(TAG, "Animation already canceled or finished, not notifying."); 299 return; 300 } 301 mShownOnFinish = shown; 302 mFinished = true; 303 setInsetsAndAlpha(shown ? mShownInsets : mHiddenInsets, mPendingAlpha, 1f /* fraction */, 304 true /* allowWhenFinished */); 305 306 if (DEBUG) Log.d(TAG, "notify control request finished for types: " + mTypes); 307 mListener.onFinished(this); 308 } 309 310 @Override 311 @VisibleForTesting getCurrentFraction()312 public float getCurrentFraction() { 313 return mAnimation.getFraction(); 314 } 315 316 @Override cancel()317 public void cancel() { 318 if (mFinished) { 319 return; 320 } 321 mPendingInsets = mLayoutInsetsDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN 322 ? mShownInsets : mHiddenInsets; 323 mPendingAlpha = 1f; 324 applyChangeInsets(null); 325 mCancelled = true; 326 mListener.onCancelled(mReadyDispatched ? this : null); 327 if (DEBUG) Log.d(TAG, "notify Control request cancelled for types: " + mTypes); 328 329 releaseLeashes(); 330 } 331 332 @Override isFinished()333 public boolean isFinished() { 334 return mFinished; 335 } 336 337 @Override isCancelled()338 public boolean isCancelled() { 339 return mCancelled; 340 } 341 342 @Override getAnimation()343 public WindowInsetsAnimation getAnimation() { 344 return mAnimation; 345 } 346 347 @Override dumpDebug(ProtoOutputStream proto, long fieldId)348 public void dumpDebug(ProtoOutputStream proto, long fieldId) { 349 final long token = proto.start(fieldId); 350 proto.write(IS_CANCELLED, mCancelled); 351 proto.write(IS_FINISHED, mFinished); 352 proto.write(TMP_MATRIX, Objects.toString(mTmpMatrix)); 353 proto.write(PENDING_INSETS, Objects.toString(mPendingInsets)); 354 proto.write(PENDING_FRACTION, mPendingFraction); 355 proto.write(SHOWN_ON_FINISH, mShownOnFinish); 356 proto.write(CURRENT_ALPHA, mCurrentAlpha); 357 proto.write(PENDING_ALPHA, mPendingAlpha); 358 proto.end(token); 359 } 360 getControls()361 SparseArray<InsetsSourceControl> getControls() { 362 return mControls; 363 } 364 getInsetsFromState(InsetsState state, Rect frame, @Nullable @InternalInsetsSide SparseIntArray typeSideMap)365 private Insets getInsetsFromState(InsetsState state, Rect frame, 366 @Nullable @InternalInsetsSide SparseIntArray typeSideMap) { 367 return state.calculateInsets(frame, null /* ignoringVisibilityState */, 368 false /* isScreenRound */, false /* alwaysConsumeSystemBars */, 369 LayoutParams.SOFT_INPUT_ADJUST_RESIZE /* legacySoftInputMode*/, 370 0 /* legacyWindowFlags */, 0 /* legacySystemUiFlags */, TYPE_APPLICATION, 371 WINDOWING_MODE_UNDEFINED, typeSideMap).getInsets(mTypes); 372 } 373 374 /** Computes the insets relative to the given frame. */ calculateInsets(InsetsState state, Rect frame, SparseArray<InsetsSourceControl> controls, boolean shown, @Nullable @InternalInsetsSide SparseIntArray typeSideMap)375 private Insets calculateInsets(InsetsState state, Rect frame, 376 SparseArray<InsetsSourceControl> controls, boolean shown, 377 @Nullable @InternalInsetsSide SparseIntArray typeSideMap) { 378 for (int i = controls.size() - 1; i >= 0; i--) { 379 final InsetsSourceControl control = controls.valueAt(i); 380 if (control == null) { 381 // control may be null if it got revoked. 382 continue; 383 } 384 state.getSource(control.getType()).setVisible(shown); 385 } 386 return getInsetsFromState(state, frame, typeSideMap); 387 } 388 389 /** Computes the insets from the insets hints of controls. */ calculateInsets(InsetsState state, SparseArray<InsetsSourceControl> controls, boolean shownOrCurrent)390 private Insets calculateInsets(InsetsState state, SparseArray<InsetsSourceControl> controls, 391 boolean shownOrCurrent) { 392 Insets insets = Insets.NONE; 393 if (!shownOrCurrent) { 394 return insets; 395 } 396 for (int i = controls.size() - 1; i >= 0; i--) { 397 final InsetsSourceControl control = controls.valueAt(i); 398 if (control == null) { 399 // control may be null if it got revoked. 400 continue; 401 } 402 if (state == null || state.getSource(control.getType()).isVisible()) { 403 insets = Insets.max(insets, control.getInsetsHint()); 404 } 405 } 406 return insets; 407 } 408 sanitize(Insets insets)409 private Insets sanitize(Insets insets) { 410 if (insets == null) { 411 insets = getCurrentInsets(); 412 } 413 if (hasZeroInsetsIme()) { 414 return insets; 415 } 416 return Insets.max(Insets.min(insets, mShownInsets), mHiddenInsets); 417 } 418 sanitize(float alpha)419 private static float sanitize(float alpha) { 420 return alpha >= 1 ? 1 : (alpha <= 0 ? 0 : alpha); 421 } 422 updateLeashesForSide(@nternalInsetsSide int side, int offset, int inset, ArrayList<SurfaceParams> surfaceParams, @Nullable InsetsState outState, float alpha)423 private void updateLeashesForSide(@InternalInsetsSide int side, int offset, int inset, 424 ArrayList<SurfaceParams> surfaceParams, @Nullable InsetsState outState, float alpha) { 425 final ArraySet<InsetsSourceControl> controls = mSideControlsMap.get(side); 426 if (controls == null) { 427 return; 428 } 429 // TODO: Implement behavior when inset spans over multiple types 430 for (int i = controls.size() - 1; i >= 0; i--) { 431 final InsetsSourceControl control = controls.valueAt(i); 432 final InsetsSource source = mInitialInsetsState.getSource(control.getType()); 433 final SurfaceControl leash = control.getLeash(); 434 435 mTmpMatrix.setTranslate(control.getSurfacePosition().x, control.getSurfacePosition().y); 436 mTmpFrame.set(source.getFrame()); 437 addTranslationToMatrix(side, offset, mTmpMatrix, mTmpFrame); 438 439 final boolean visible = mHasZeroInsetsIme && side == ISIDE_BOTTOM 440 ? (mAnimationType == ANIMATION_TYPE_SHOW || !mFinished) 441 : inset != 0; 442 443 if (outState != null) { 444 outState.getSource(source.getType()).setVisible(visible); 445 outState.getSource(source.getType()).setFrame(mTmpFrame); 446 } 447 448 // If the system is controlling the insets source, the leash can be null. 449 if (leash != null) { 450 SurfaceParams params = new SurfaceParams.Builder(leash) 451 .withAlpha(alpha) 452 .withMatrix(mTmpMatrix) 453 .withVisibility(visible) 454 .build(); 455 surfaceParams.add(params); 456 } 457 } 458 } 459 addTranslationToMatrix(@nternalInsetsSide int side, int offset, Matrix m, Rect frame)460 private void addTranslationToMatrix(@InternalInsetsSide int side, int offset, Matrix m, 461 Rect frame) { 462 final float surfaceOffset = mTranslator != null 463 ? mTranslator.translateLengthInAppWindowToScreen(offset) : offset; 464 switch (side) { 465 case ISIDE_LEFT: 466 m.postTranslate(-surfaceOffset, 0); 467 frame.offset(-offset, 0); 468 break; 469 case ISIDE_TOP: 470 m.postTranslate(0, -surfaceOffset); 471 frame.offset(0, -offset); 472 break; 473 case ISIDE_RIGHT: 474 m.postTranslate(surfaceOffset, 0); 475 frame.offset(offset, 0); 476 break; 477 case ISIDE_BOTTOM: 478 m.postTranslate(0, surfaceOffset); 479 frame.offset(0, offset); 480 break; 481 } 482 } 483 buildSideControlsMap(SparseIntArray typeSideMap, SparseSetArray<InsetsSourceControl> sideControlsMap, SparseArray<InsetsSourceControl> controls)484 private static void buildSideControlsMap(SparseIntArray typeSideMap, 485 SparseSetArray<InsetsSourceControl> sideControlsMap, 486 SparseArray<InsetsSourceControl> controls) { 487 for (int i = typeSideMap.size() - 1; i >= 0; i--) { 488 final int type = typeSideMap.keyAt(i); 489 final int side = typeSideMap.valueAt(i); 490 final InsetsSourceControl control = controls.get(type); 491 if (control == null) { 492 // If the types that we are controlling are less than the types that the system has, 493 // there can be some null controllers. 494 continue; 495 } 496 sideControlsMap.add(side, control); 497 } 498 } 499 buildSideControlsMap( SparseSetArray<InsetsSourceControl> sideControlsMap, SparseArray<InsetsSourceControl> controls)500 private static void buildSideControlsMap( 501 SparseSetArray<InsetsSourceControl> sideControlsMap, 502 SparseArray<InsetsSourceControl> controls) { 503 for (int i = controls.size() - 1; i >= 0; i--) { 504 final InsetsSourceControl control = controls.valueAt(i); 505 if (control == null) { 506 // control may be null if it got revoked. 507 continue; 508 } 509 @InternalInsetsSide int side = InsetsState.getInsetSide(control.getInsetsHint()); 510 if (side == ISIDE_FLOATING && control.getType() == ITYPE_IME) { 511 side = ISIDE_BOTTOM; 512 } 513 sideControlsMap.add(side, control); 514 } 515 } 516 } 517