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