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