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_SHOW; 20 import static android.view.InsetsController.AnimationType; 21 import static android.view.InsetsController.DEBUG; 22 import static android.view.InsetsState.ISIDE_BOTTOM; 23 import static android.view.InsetsState.ISIDE_LEFT; 24 import static android.view.InsetsState.ISIDE_RIGHT; 25 import static android.view.InsetsState.ISIDE_TOP; 26 import static android.view.InsetsState.ITYPE_IME; 27 28 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; 29 30 import android.annotation.Nullable; 31 import android.graphics.Insets; 32 import android.graphics.Matrix; 33 import android.graphics.Rect; 34 import android.util.ArraySet; 35 import android.util.Log; 36 import android.util.SparseArray; 37 import android.util.SparseIntArray; 38 import android.util.SparseSetArray; 39 import android.view.InsetsState.InternalInsetsSide; 40 import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; 41 import android.view.WindowInsets.Type.InsetsType; 42 import android.view.WindowInsetsAnimation.Bounds; 43 import android.view.WindowManager.LayoutParams; 44 import android.view.animation.Interpolator; 45 46 import com.android.internal.annotations.VisibleForTesting; 47 48 import java.util.ArrayList; 49 50 /** 51 * Implements {@link WindowInsetsAnimationController} 52 * @hide 53 */ 54 @VisibleForTesting 55 public class InsetsAnimationControlImpl implements WindowInsetsAnimationController, 56 InsetsAnimationControlRunner { 57 58 private static final String TAG = "InsetsAnimationCtrlImpl"; 59 60 private final Rect mTmpFrame = new Rect(); 61 62 private final WindowInsetsAnimationControlListener mListener; 63 private final SparseArray<InsetsSourceControl> mControls; 64 private final SparseIntArray mTypeSideMap = new SparseIntArray(); 65 private final SparseSetArray<InsetsSourceControl> mSideSourceMap = new SparseSetArray<>(); 66 67 /** @see WindowInsetsAnimationController#getHiddenStateInsets */ 68 private final Insets mHiddenInsets; 69 70 /** @see WindowInsetsAnimationController#getShownStateInsets */ 71 private final Insets mShownInsets; 72 private final Matrix mTmpMatrix = new Matrix(); 73 private final InsetsState mInitialInsetsState; 74 private final @AnimationType int mAnimationType; 75 private final @InsetsType int mTypes; 76 private final InsetsAnimationControlCallbacks mController; 77 private final WindowInsetsAnimation mAnimation; 78 /** @see WindowInsetsAnimationController#hasZeroInsetsIme */ 79 private final boolean mHasZeroInsetsIme; 80 private Insets mCurrentInsets; 81 private Insets mPendingInsets; 82 private float mPendingFraction; 83 private boolean mFinished; 84 private boolean mCancelled; 85 private boolean mShownOnFinish; 86 private float mCurrentAlpha = 1.0f; 87 private float mPendingAlpha = 1.0f; 88 @VisibleForTesting(visibility = PACKAGE) 89 public boolean mReadyDispatched; 90 private Boolean mPerceptible; 91 92 @VisibleForTesting InsetsAnimationControlImpl(SparseArray<InsetsSourceControl> controls, Rect frame, InsetsState state, WindowInsetsAnimationControlListener listener, @InsetsType int types, InsetsAnimationControlCallbacks controller, long durationMs, Interpolator interpolator, @AnimationType int animationType)93 public InsetsAnimationControlImpl(SparseArray<InsetsSourceControl> controls, Rect frame, 94 InsetsState state, WindowInsetsAnimationControlListener listener, 95 @InsetsType int types, 96 InsetsAnimationControlCallbacks controller, long durationMs, Interpolator interpolator, 97 @AnimationType int animationType) { 98 mControls = controls; 99 mListener = listener; 100 mTypes = types; 101 mController = controller; 102 mInitialInsetsState = new InsetsState(state, true /* copySources */); 103 mCurrentInsets = getInsetsFromState(mInitialInsetsState, frame, null /* typeSideMap */); 104 mPendingInsets = mCurrentInsets; 105 mHiddenInsets = calculateInsets(mInitialInsetsState, frame, controls, false /* shown */, 106 null /* typeSideMap */); 107 mShownInsets = calculateInsets(mInitialInsetsState, frame, controls, true /* shown */, 108 mTypeSideMap); 109 mHasZeroInsetsIme = mShownInsets.bottom == 0 && controlsInternalType(ITYPE_IME); 110 if (mHasZeroInsetsIme) { 111 // IME has shownInsets of ZERO, and can't map to a side by default. 112 // Map zero insets IME to bottom, making it a special case of bottom insets. 113 mTypeSideMap.put(ITYPE_IME, ISIDE_BOTTOM); 114 } 115 buildTypeSourcesMap(mTypeSideMap, mSideSourceMap, mControls); 116 117 mAnimation = new WindowInsetsAnimation(mTypes, interpolator, 118 durationMs); 119 mAnimation.setAlpha(getCurrentAlpha()); 120 mAnimationType = animationType; 121 mController.startAnimation(this, listener, types, mAnimation, 122 new Bounds(mHiddenInsets, mShownInsets)); 123 } 124 calculatePerceptible(Insets currentInsets, float currentAlpha)125 private boolean calculatePerceptible(Insets currentInsets, float currentAlpha) { 126 return 100 * currentInsets.left >= 5 * (mShownInsets.left - mHiddenInsets.left) 127 && 100 * currentInsets.top >= 5 * (mShownInsets.top - mHiddenInsets.top) 128 && 100 * currentInsets.right >= 5 * (mShownInsets.right - mHiddenInsets.right) 129 && 100 * currentInsets.bottom >= 5 * (mShownInsets.bottom - mHiddenInsets.bottom) 130 && currentAlpha >= 0.5f; 131 } 132 133 @Override hasZeroInsetsIme()134 public boolean hasZeroInsetsIme() { 135 return mHasZeroInsetsIme; 136 } 137 138 @Override getHiddenStateInsets()139 public Insets getHiddenStateInsets() { 140 return mHiddenInsets; 141 } 142 143 @Override getShownStateInsets()144 public Insets getShownStateInsets() { 145 return mShownInsets; 146 } 147 148 @Override getCurrentInsets()149 public Insets getCurrentInsets() { 150 return mCurrentInsets; 151 } 152 153 @Override getCurrentAlpha()154 public float getCurrentAlpha() { 155 return mCurrentAlpha; 156 } 157 158 @Override getTypes()159 @InsetsType public int getTypes() { 160 return mTypes; 161 } 162 163 @Override getAnimationType()164 public @AnimationType int getAnimationType() { 165 return mAnimationType; 166 } 167 168 @Override setInsetsAndAlpha(Insets insets, float alpha, float fraction)169 public void setInsetsAndAlpha(Insets insets, float alpha, float fraction) { 170 setInsetsAndAlpha(insets, alpha, fraction, false /* allowWhenFinished */); 171 } 172 setInsetsAndAlpha(Insets insets, float alpha, float fraction, boolean allowWhenFinished)173 private void setInsetsAndAlpha(Insets insets, float alpha, float fraction, 174 boolean allowWhenFinished) { 175 if (!allowWhenFinished && mFinished) { 176 throw new IllegalStateException( 177 "Can't change insets on an animation that is finished."); 178 } 179 if (mCancelled) { 180 throw new IllegalStateException( 181 "Can't change insets on an animation that is cancelled."); 182 } 183 mPendingFraction = sanitize(fraction); 184 mPendingInsets = sanitize(insets); 185 mPendingAlpha = sanitize(alpha); 186 mController.scheduleApplyChangeInsets(this); 187 boolean perceptible = calculatePerceptible(mPendingInsets, mPendingAlpha); 188 if (mPerceptible == null || perceptible != mPerceptible) { 189 mController.reportPerceptible(mTypes, perceptible); 190 mPerceptible = perceptible; 191 } 192 } 193 194 @VisibleForTesting 195 /** 196 * @return Whether the finish callback of this animation should be invoked. 197 */ applyChangeInsets(InsetsState state)198 public boolean applyChangeInsets(InsetsState state) { 199 if (mCancelled) { 200 if (DEBUG) Log.d(TAG, "applyChangeInsets canceled"); 201 return false; 202 } 203 final Insets offset = Insets.subtract(mShownInsets, mPendingInsets); 204 ArrayList<SurfaceParams> params = new ArrayList<>(); 205 updateLeashesForSide(ISIDE_LEFT, offset.left, mShownInsets.left, mPendingInsets.left, 206 params, state, mPendingAlpha); 207 updateLeashesForSide(ISIDE_TOP, offset.top, mShownInsets.top, mPendingInsets.top, params, 208 state, mPendingAlpha); 209 updateLeashesForSide(ISIDE_RIGHT, offset.right, mShownInsets.right, mPendingInsets.right, 210 params, state, mPendingAlpha); 211 updateLeashesForSide(ISIDE_BOTTOM, offset.bottom, mShownInsets.bottom, 212 mPendingInsets.bottom, params, state, mPendingAlpha); 213 214 mController.applySurfaceParams(params.toArray(new SurfaceParams[params.size()])); 215 mCurrentInsets = mPendingInsets; 216 mAnimation.setFraction(mPendingFraction); 217 mCurrentAlpha = mPendingAlpha; 218 mAnimation.setAlpha(mPendingAlpha); 219 if (mFinished) { 220 if (DEBUG) Log.d(TAG, String.format( 221 "notifyFinished shown: %s, currentAlpha: %f, currentInsets: %s", 222 mShownOnFinish, mCurrentAlpha, mCurrentInsets)); 223 mController.notifyFinished(this, mShownOnFinish); 224 releaseLeashes(); 225 } 226 if (DEBUG) Log.d(TAG, "Animation finished abruptly."); 227 return mFinished; 228 } 229 releaseLeashes()230 private void releaseLeashes() { 231 for (int i = mControls.size() - 1; i >= 0; i--) { 232 final InsetsSourceControl c = mControls.valueAt(i); 233 if (c == null) continue; 234 c.release(mController::releaseSurfaceControlFromRt); 235 } 236 } 237 238 @Override finish(boolean shown)239 public void finish(boolean shown) { 240 if (mCancelled || mFinished) { 241 if (DEBUG) Log.d(TAG, "Animation already canceled or finished, not notifying."); 242 return; 243 } 244 mShownOnFinish = shown; 245 mFinished = true; 246 setInsetsAndAlpha(shown ? mShownInsets : mHiddenInsets, 1f /* alpha */, 1f /* fraction */, 247 true /* allowWhenFinished */); 248 249 if (DEBUG) Log.d(TAG, "notify control request finished for types: " + mTypes); 250 mListener.onFinished(this); 251 } 252 253 @Override 254 @VisibleForTesting getCurrentFraction()255 public float getCurrentFraction() { 256 return mAnimation.getFraction(); 257 } 258 259 @Override cancel()260 public void cancel() { 261 if (mFinished) { 262 return; 263 } 264 mCancelled = true; 265 mListener.onCancelled(mReadyDispatched ? this : null); 266 if (DEBUG) Log.d(TAG, "notify Control request cancelled for types: " + mTypes); 267 268 releaseLeashes(); 269 } 270 271 @Override isFinished()272 public boolean isFinished() { 273 return mFinished; 274 } 275 276 @Override isCancelled()277 public boolean isCancelled() { 278 return mCancelled; 279 } 280 281 @Override getAnimation()282 public WindowInsetsAnimation getAnimation() { 283 return mAnimation; 284 } 285 getListener()286 WindowInsetsAnimationControlListener getListener() { 287 return mListener; 288 } 289 getControls()290 SparseArray<InsetsSourceControl> getControls() { 291 return mControls; 292 } 293 calculateInsets(InsetsState state, Rect frame, SparseArray<InsetsSourceControl> controls, boolean shown, @Nullable @InternalInsetsSide SparseIntArray typeSideMap)294 private Insets calculateInsets(InsetsState state, Rect frame, 295 SparseArray<InsetsSourceControl> controls, boolean shown, 296 @Nullable @InternalInsetsSide SparseIntArray typeSideMap) { 297 for (int i = controls.size() - 1; i >= 0; i--) { 298 // control may be null if it got revoked. 299 if (controls.valueAt(i) == null) continue; 300 state.getSource(controls.valueAt(i).getType()).setVisible(shown); 301 } 302 return getInsetsFromState(state, frame, typeSideMap); 303 } 304 getInsetsFromState(InsetsState state, Rect frame, @Nullable @InternalInsetsSide SparseIntArray typeSideMap)305 private Insets getInsetsFromState(InsetsState state, Rect frame, 306 @Nullable @InternalInsetsSide SparseIntArray typeSideMap) { 307 return state.calculateInsets(frame, null /* ignoringVisibilityState */, 308 false /* isScreenRound */, 309 false /* alwaysConsumeSystemBars */, null /* displayCutout */, 310 LayoutParams.SOFT_INPUT_ADJUST_RESIZE /* legacySoftInputMode*/, 311 0 /* legacyWindowFlags */, 0 /* legacySystemUiFlags */, typeSideMap) 312 .getInsets(mTypes); 313 } 314 sanitize(Insets insets)315 private Insets sanitize(Insets insets) { 316 if (insets == null) { 317 insets = getCurrentInsets(); 318 } 319 if (hasZeroInsetsIme()) { 320 return insets; 321 } 322 return Insets.max(Insets.min(insets, mShownInsets), mHiddenInsets); 323 } 324 sanitize(float alpha)325 private static float sanitize(float alpha) { 326 return alpha >= 1 ? 1 : (alpha <= 0 ? 0 : alpha); 327 } 328 updateLeashesForSide(@nternalInsetsSide int side, int offset, int maxInset, int inset, ArrayList<SurfaceParams> surfaceParams, InsetsState state, Float alpha)329 private void updateLeashesForSide(@InternalInsetsSide int side, int offset, int maxInset, 330 int inset, ArrayList<SurfaceParams> surfaceParams, InsetsState state, Float alpha) { 331 ArraySet<InsetsSourceControl> items = mSideSourceMap.get(side); 332 if (items == null) { 333 return; 334 } 335 // TODO: Implement behavior when inset spans over multiple types 336 for (int i = items.size() - 1; i >= 0; i--) { 337 final InsetsSourceControl control = items.valueAt(i); 338 final InsetsSource source = mInitialInsetsState.getSource(control.getType()); 339 final SurfaceControl leash = control.getLeash(); 340 341 mTmpMatrix.setTranslate(control.getSurfacePosition().x, control.getSurfacePosition().y); 342 mTmpFrame.set(source.getFrame()); 343 addTranslationToMatrix(side, offset, mTmpMatrix, mTmpFrame); 344 345 final boolean visible = mHasZeroInsetsIme && side == ISIDE_BOTTOM 346 ? (mAnimationType == ANIMATION_TYPE_SHOW ? true : !mFinished) 347 : inset != 0; 348 349 state.getSource(source.getType()).setVisible(visible); 350 state.getSource(source.getType()).setFrame(mTmpFrame); 351 352 // If the system is controlling the insets source, the leash can be null. 353 if (leash != null) { 354 SurfaceParams params = new SurfaceParams.Builder(leash) 355 .withAlpha(alpha) 356 .withMatrix(mTmpMatrix) 357 .withVisibility(visible) 358 .build(); 359 surfaceParams.add(params); 360 } 361 } 362 } 363 addTranslationToMatrix(@nternalInsetsSide int side, int inset, Matrix m, Rect frame)364 private void addTranslationToMatrix(@InternalInsetsSide int side, int inset, Matrix m, 365 Rect frame) { 366 switch (side) { 367 case ISIDE_LEFT: 368 m.postTranslate(-inset, 0); 369 frame.offset(-inset, 0); 370 break; 371 case ISIDE_TOP: 372 m.postTranslate(0, -inset); 373 frame.offset(0, -inset); 374 break; 375 case ISIDE_RIGHT: 376 m.postTranslate(inset, 0); 377 frame.offset(inset, 0); 378 break; 379 case ISIDE_BOTTOM: 380 m.postTranslate(0, inset); 381 frame.offset(0, inset); 382 break; 383 } 384 } 385 buildTypeSourcesMap(SparseIntArray typeSideMap, SparseSetArray<InsetsSourceControl> sideSourcesMap, SparseArray<InsetsSourceControl> controls)386 private static void buildTypeSourcesMap(SparseIntArray typeSideMap, 387 SparseSetArray<InsetsSourceControl> sideSourcesMap, 388 SparseArray<InsetsSourceControl> controls) { 389 for (int i = typeSideMap.size() - 1; i >= 0; i--) { 390 final int type = typeSideMap.keyAt(i); 391 final int side = typeSideMap.valueAt(i); 392 final InsetsSourceControl control = controls.get(type); 393 if (control == null) { 394 // If the types that we are controlling are less than the types that the system has, 395 // there can be some null controllers. 396 continue; 397 } 398 sideSourcesMap.add(side, control); 399 } 400 } 401 } 402