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