ViewRootImpl 在 performTraversals 时会调用 dispatchApplyInsets 方法
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;
...
Rect frame = mWinFrame;
if (mFirst) {
...
dispatchApplyInsets(host);
} else {
...
}
...
}
ViewRootImpl::dispatchApplyInsets 定义如下
public void dispatchApplyInsets(View host) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "dispatchApplyInsets");
mApplyInsetsRequested = false;
WindowInsets insets = getWindowInsets(true /* forceConstruct */);
if (!shouldDispatchCutout()) {
// Window is either not laid out in cutout or the status bar inset takes care of
// clearing the cutout, so we don't need to dispatch the cutout to the hierarchy.
insets = insets.consumeDisplayCutout();
}
host.dispatchApplyWindowInsets(insets);//1
mAttachInfo.delayNotifyContentCaptureInsetsEvent(insets.getInsets(Type.all()));
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
注释 1 处的 host,其实就是在调用 ViewRootImpl::setView 时传递进来的 View 对象,通常来说,是一个 ViewGroup 对象
ViewGroup::dispatchApplyWindowInsets
@Override
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
insets = super.dispatchApplyWindowInsets(insets);//1
if (insets.isConsumed()) {
return insets;
}
if (View.sBrokenInsetsDispatch) {
return brokenDispatchApplyWindowInsets(insets);
} else {
return newDispatchApplyWindowInsets(insets);
}
}
注释 1 处先执行 View 的 dispatchApplyWindowInsets 方法
View::dispatchApplyWindowInsets
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
try {
mPrivateFlags3 |= PFLAG3_APPLYING_INSETS;
if (mListenerInfo != null && mListenerInfo.mOnApplyWindowInsetsListener != null) {//1
return mListenerInfo.mOnApplyWindowInsetsListener.onApplyWindowInsets(this, insets);//2
} else {
return onApplyWindowInsets(insets);//3
}
} finally {
mPrivateFlags3 &= ~PFLAG3_APPLYING_INSETS;
}
}
在注释 1 处判断,是否存在 mOnApplyWindowInsetsListener,该 Listener 可以通过 View:: setOnApplyWindowInsetsListener 来设置,如果设置过 mOnApplyWindowInsetsListener 则走注释 2 的代码,即调用 mOnApplyWindowInsetsListener 的 onApplyWindowInsets 方法;若未设置 mOnApplyWindowInsetsListener,则走注释 3 的代码,即继续执行 View::onApplyWindowInsets 的逻辑。
注意注释 2 和注释 3 是互斥的,如果调用 View::setOnApplyWindowInsetsListener 设置了 listener,则不会走注释 3 的逻辑。
先来看不设置 listener 的情况,即注释 2 处的代码
View::onApplyWindowInsets
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
if ((mPrivateFlags4 & PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS) != 0
&& (mViewFlags & FITS_SYSTEM_WINDOWS) != 0) {
return onApplyFrameworkOptionalFitSystemWindows(insets);//1
}
if ((mPrivateFlags3 & PFLAG3_FITTING_SYSTEM_WINDOWS) == 0) {
// We weren't called from within a direct call to fitSystemWindows,
// call into it as a fallback in case we're in a class that overrides it
// and has logic to perform.
if (fitSystemWindows(insets.getSystemWindowInsetsAsRect())) {
return insets.consumeSystemWindowInsets();//2
}
} else {
// We were called from within a direct call to fitSystemWindows.
if (fitSystemWindowsInt(insets.getSystemWindowInsetsAsRect())) {
return insets.consumeSystemWindowInsets();//3
}
}
return insets;
}
View::onApplyWindowInsets 根据两种情况来讨论
注释 1 所在分支是第一种情况,DecorView 满足 (mPrivateFlags4 & PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS) != 0,参考下面代码
PhoneWindow::installDecor
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeFrameworkOptionalFitsSystemWindows();
...
}
}
View::makeFrameworkOptionalFitsSystemWindows
public void makeFrameworkOptionalFitsSystemWindows() {
mPrivateFlags4 |= PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS;
}
(mViewFlags & FITS_SYSTEM_WINDOWS) != 0 可以通过View::setFitsSystemWindows 控制
View::setFitsSystemWindows
public void setFitsSystemWindows(boolean fitSystemWindows) {
setFlags(fitSystemWindows ? FITS_SYSTEM_WINDOWS : 0, FITS_SYSTEM_WINDOWS);
}
Android S 上 DecorView 默认不具备 FITS_SYSTEM_WINDOWS Flag
分支 1 不满足以后,第二个 if 语句用于判断是使用 fitSystemWindows 还是使用 fitSystemWindowsInt
View
protected boolean fitSystemWindows(Rect insets) {
if ((mPrivateFlags3 & PFLAG3_APPLYING_INSETS) == 0) {
if (insets == null) {
// Null insets by definition have already been consumed.
// This call cannot apply insets since there are none to apply,
// so return false.
return false;
}
// If we're not in the process of dispatching the newer apply insets call,
// that means we're not in the compatibility path. Dispatch into the newer
// apply insets path and take things from there.
try {
mPrivateFlags3 |= PFLAG3_FITTING_SYSTEM_WINDOWS;
return dispatchApplyWindowInsets(new WindowInsets(insets)).isConsumed();
} finally {
mPrivateFlags3 &= ~PFLAG3_FITTING_SYSTEM_WINDOWS;
}
} else {
// We're being called from the newer apply insets path.
// Perform the standard fallback behavior.
return fitSystemWindowsInt(insets);
}
}
private boolean fitSystemWindowsInt(Rect insets) {
if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) {//1
Rect localInsets = sThreadLocal.get();
boolean res = computeFitSystemWindows(insets, localInsets);//2
applyInsets(localInsets);//3
return res;
}
return false;
}
fitSystemWindows 是老方法,在 SDK 20 以后就已经不推荐使用了,主要研究 fitSystemWindowsInt
在注释 1 处,判断当前 View 是否设置了 android:fitsSystemWindows=”true” 如果设置了,则在注释 2 处计算 Insets,并在注释 3 处应用该 Insets
View::computeFitSystemWindows
protected boolean computeFitSystemWindows(Rect inoutInsets, Rect outLocalInsets) {
WindowInsets innerInsets = computeSystemWindowInsets(new WindowInsets(inoutInsets),
outLocalInsets);//1
inoutInsets.set(innerInsets.getSystemWindowInsetsAsRect());
return innerInsets.isSystemWindowInsetsConsumed();
}
computeFitSystemWindows 调用 computeSystemWindowInsets 计算 insets
public WindowInsets computeSystemWindowInsets(WindowInsets in, Rect outLocalInsets) {
boolean isOptionalFitSystemWindows = (mViewFlags & OPTIONAL_FITS_SYSTEM_WINDOWS) != 0
|| (mPrivateFlags4 & PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS) != 0;//1
if (isOptionalFitSystemWindows && mAttachInfo != null) {
OnContentApplyWindowInsetsListener listener =
mAttachInfo.mContentOnApplyWindowInsetsListener;//2
if (listener == null) {
// The application wants to take care of fitting system window for
// the content.
outLocalInsets.setEmpty();
return in;
}
Pair<Insets, WindowInsets> result = listener.onContentApplyWindowInsets(this, in);//3
outLocalInsets.set(result.first.toRect());
return result.second;
} else {
outLocalInsets.set(in.getSystemWindowInsetsAsRect());
return in.consumeSystemWindowInsets().inset(outLocalInsets);
}
}
注释 1 的判断是针对 DecorView 的,DecorView 带有 PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS
注释 2 的 mContentOnApplyWindowInsetsListener,是 PhoneWindow 根据mDecorFitsSystemWindows 调用过判断而给 ViewRootImpl 设置的监听器,用户可以通过调用 PhoneWindow::setDecorFitsSystemWindows 手动设置该值。
PhoneWindow
@Override
public void setDecorFitsSystemWindows(boolean decorFitsSystemWindows) {
mDecorFitsSystemWindows = decorFitsSystemWindows;
applyDecorFitsSystemWindows();
}
private void applyDecorFitsSystemWindows() {
ViewRootImpl impl = getViewRootImplOrNull();
if (impl != null) {
impl.setOnContentApplyWindowInsetsListener(mDecorFitsSystemWindows
? sDefaultContentInsetsApplier
: null);
}
}
注释 3 在 mContentOnApplyWindowInsetsListener 不为 null 的前提下,根据 ContentOnApplyWindowInsetsListener::onContentApplyWindowInsets 计算 insets
对于 DecorView 来说,这里调用的是下面的方法
/**
* @see Window#setDecorFitsSystemWindows
*/
private static final OnContentApplyWindowInsetsListener sDefaultContentInsetsApplier =
(view, insets) -> {
if ((view.getWindowSystemUiVisibility() & SYSTEM_UI_LAYOUT_FLAGS) != 0) {
return new Pair<>(Insets.NONE, insets);
}
Insets insetsToApply = insets.getSystemWindowInsets();
return new Pair<>(insetsToApply,
insets.inset(insetsToApply).consumeSystemWindowInsets());
};
对于 computeSystemWindowInsets 方法来说,它的 outLocalInsets 参数指应用的 insets 矩形,返回值用于标记该 insets 是否被消费过,如果 in.consumeSystemWindowInsets() 则说明该 insets 被消费了
回到 computeFitSystemWindows 方法
protected boolean computeFitSystemWindows(Rect inoutInsets, Rect outLocalInsets) {
WindowInsets innerInsets = computeSystemWindowInsets(new WindowInsets(inoutInsets),
outLocalInsets);//1
inoutInsets.set(innerInsets.getSystemWindowInsetsAsRect());
return innerInsets.isSystemWindowInsetsConsumed();//2
}
在注释 2 处根据当前 insets 是否被消费过,返回 true/false
View::fitSystemWindowsInt
private boolean fitSystemWindowsInt(Rect insets) {
if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) {//1
Rect localInsets = sThreadLocal.get();
boolean res = computeFitSystemWindows(insets, localInsets);//2
applyInsets(localInsets);//3
return res;
}
return false;
}
回到 View::fitSystemWindowsInt 的注释 3,在这里将应用 Insets
View::applyInsets
private void applyInsets(Rect insets) {
mUserPaddingStart = UNDEFINED_PADDING;
mUserPaddingEnd = UNDEFINED_PADDING;
mUserPaddingLeftInitial = insets.left;
mUserPaddingRightInitial = insets.right;
internalSetPadding(insets.left, insets.top, insets.right, insets.bottom);
}
在这里将通过 internalSetPadding 将 insets 以 padding 的形式给 View 设置上
后续,回到 View::onApplyWindowInsets
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
if ((mPrivateFlags4 & PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS) != 0
&& (mViewFlags & FITS_SYSTEM_WINDOWS) != 0) {
return onApplyFrameworkOptionalFitSystemWindows(insets);//1
}
if ((mPrivateFlags3 & PFLAG3_FITTING_SYSTEM_WINDOWS) == 0) {
// We weren't called from within a direct call to fitSystemWindows,
// call into it as a fallback in case we're in a class that overrides it
// and has logic to perform.
if (fitSystemWindows(insets.getSystemWindowInsetsAsRect())) {
return insets.consumeSystemWindowInsets();//2
}
} else {
// We were called from within a direct call to fitSystemWindows.
if (fitSystemWindowsInt(insets.getSystemWindowInsetsAsRect())) {
return insets.consumeSystemWindowInsets();//3
}
}
return insets;
}
若 View::fitSystemWindowsInt 的注释 2 返回 true (即被消耗),则也会让 View::onApplyWindowInsets 的参数也变为被消耗的 Insets (注释 2、注释 3)
回到 ViewGroup::dispatchApplyWindowInsets
@Override
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
insets = super.dispatchApplyWindowInsets(insets);//1
if (insets.isConsumed()) {//2
return insets;
}
if (View.sBrokenInsetsDispatch) {//3
return brokenDispatchApplyWindowInsets(insets);
} else {
return newDispatchApplyWindowInsets(insets);//4
}
}
若 insets 在注释 2 处判断为被消耗,则不会进行分发 WindowInsets,否则会继续执行注释 3 的逻辑判断是否中断分发, sBrokenInsetsDispatch 为 true 的条件如下
sBrokenInsetsDispatch = targetSdkVersion < Build.VERSION_CODES.R;
在 Android S 系统中,该值为 false,所以继续看注释 4 处的逻辑
ViewGroup::newDispatchApplyWindowInsets
private WindowInsets newDispatchApplyWindowInsets(WindowInsets insets) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
getChildAt(i).dispatchApplyWindowInsets(insets);//1
}
return insets;
}
在注释 1 处,开始调用子 View 的 dispatchApplyWindowInsets 方法,让 WindowInsets 继续向子分发
对于低于 R 版本的系统来说,替代 newDispatchApplyWindowInsets 的是 brokenDispatchApplyWindowInsets
ViewGroup::brokenDispatchApplyWindowInsets
private WindowInsets brokenDispatchApplyWindowInsets(WindowInsets insets) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
insets = getChildAt(i).dispatchApplyWindowInsets(insets);
if (insets.isConsumed()) {
break;
}
}
return insets;
}
如果 app targetSdkVersion < 30 ,如果某个节点消费了 Insets,所有没遍历到的节点都不会收到 WindowInsets 的分发;
当 app 运行在 Android 11 以上版本的设备上且 targetSdkVersion >= 30,如果某个节点消费了 Insets,该节点的所有子节点不会收到 WindowInsets 分发。
区别在于一个节点消费了 WindowInsets 分发,其兄弟节点似乎否还能收到分发。
表示为单个类型 insets 的具体信息,例如 Insets 大小、是否可见等信息
InsetsState 持有当前系统 insets 的状态信息,其内部存有一个 InsetsSource 类型的数组 mSources
android.view.InsetsState#mSources
private final InsetsSource[] mSources = new InsetsSource[SIZE];
该数组用于保存当前屏幕中所有类型的 insets
当一个 Window 在客户端被添加、更新的时候,ViewRootImpl 将会调用 relayoutWindow 方法,从 WMS 获取当前 Window 的 InsetsState 信息
android.view.ViewRootImpl#relayoutWindow
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
boolean insetsPending) throws RemoteException {
float appScale = mAttachInfo.mApplicationScale;
boolean restore = false;
if (params != null && mTranslator != null) {
restore = true;
params.backup();
mTranslator.translateWindowLayout(params);
}
if (params != null) {
if (DBG) Log.d(mTag, "WindowLayout in layoutWindow:" + params);
if (mOrigWindowType != params.type) {
// For compatibility with old apps, don't crash here.
if (mTargetSdkVersion < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
Slog.w(mTag, "Window type can not be changed after "
+ "the window is added; ignoring change of " + mView);
params.type = mOrigWindowType;
}
}
}
long frameNumber = -1;
if (mSurface.isValid()) {
frameNumber = mSurface.getNextFrameNumber();
}
int relayoutResult = mWindowSession.relayout(mWindow, params,
(int) (mView.getMeasuredWidth() * appScale + 0.5f),
(int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
mTempControls, mSurfaceSize);//1
mPendingBackDropFrame.set(mTmpFrames.backdropFrame);
if (mSurfaceControl.isValid()) {
if (!useBLAST()) {
mSurface.copyFrom(mSurfaceControl);
} else {
final Surface blastSurface = getOrCreateBLASTSurface();
// If blastSurface == null that means it hasn't changed since the last time we
// called. In this situation, avoid calling transferFrom as we would then
// inc the generation ID and cause EGL resources to be recreated.
if (blastSurface != null) {
mSurface.transferFrom(blastSurface);
}
}
if (mAttachInfo.mThreadedRenderer != null) {
if (HardwareRenderer.isWebViewOverlaysEnabled()) {
addPrepareSurfaceControlForWebviewCallback();
addASurfaceTransactionCallback();
}
mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl);
}
} else {
destroySurface();
}
mPendingAlwaysConsumeSystemBars =
(relayoutResult & WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS) != 0;
if (restore) {
params.restore();
}
if (mTranslator != null) {
mTranslator.translateRectInScreenToAppWindow(mTmpFrames.frame);
mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
}
setFrame(mTmpFrames.frame);//2
mWillMove = false;
mWillResize = false;
mInsetsController.onStateChanged(mTempInsets);//3
mInsetsController.onControlsChanged(mTempControls);//4
return relayoutResult;
}
在注释 1 处,调用 IWindowSession::relayout 方法,IWindowSession 是 ViewRootImpl 与 WindowMangerService 的代理,从注释 1 处得到该当前 Window 的框架大小 mTmpFrames、以及 Insets 情况(mTempInsets、mTempControls),并在注释 2 、注释 3 、注释 4 将这些属性同步给 ViewRootImpl
注释 3、4 中的 mInsetsController 是 InsetsController 类型的实例,它实现了 WindowInsetsController 接口
WindowInsetsController 是客户端用于控制 Window 的管理器,它在客户端的实现类是 InsetsController,每个 Window 会有一个 ViewRootImpl,每个 ViewRootImpl 都将持有一个 InsetsController 实例,客户端程序可以通过 View::getWindowInsetsController 得到该实例,对当前 Window 的 insets 进行控制
android.view.View#getWindowInsetsController
/**
* Retrieves the single {@link WindowInsetsController} of the window this view is attached to.
*
* @return The {@link WindowInsetsController} or {@code null} if the view is neither attached to
* a window nor a view tree with a decor.
* @see Window#getInsetsController()
*/
public @Nullable WindowInsetsController getWindowInsetsController() {
if (mAttachInfo != null) {
return mAttachInfo.mViewRootImpl.getInsetsController();
}
ViewParent parent = getParent();
if (parent instanceof View) {
return ((View) parent).getWindowInsetsController();
} else if (parent instanceof ViewRootImpl) {
// Between WindowManager.addView() and the first traversal AttachInfo isn't set yet.
return ((ViewRootImpl) parent).getInsetsController();
}
return null;
}
用于控制单独 InsetsSource 的控制器,被 InsetsController 所持有,同样也是在 ViewRootImpl:: relayoutWindow 时从 WMS 中获得
对单一 InsetsSource 的消费者,其内部持有 InsetsSourceControl,可以控制其leash的可见性和动画。
InsetsSourceControl 持有 InsetsSourceConsumer 的创造器,会在 InsetsController::getSourceConsumer 时创建实例
android.view.InsetsController#getSourceConsumer
public InsetsController(Host host) {
this(host, (controller, type) -> {
if (type == ITYPE_IME) {
return new ImeInsetsSourceConsumer(controller.mState, Transaction::new, controller);
} else {
return new InsetsSourceConsumer(type, controller.mState, Transaction::new,
controller);
}
}, host.getHandler());//1
}
@VisibleForTesting
public @NonNull InsetsSourceConsumer getSourceConsumer(@InternalInsetsType int type) {
InsetsSourceConsumer controller = mSourceConsumers.get(type);
if (controller != null) {
return controller;
}
controller = mConsumerCreator.apply(this, type);//2
mSourceConsumers.put(type, controller);
return controller;
}
注释 2 处实际调用的是注释 1 的箭头函数
负责在服务端管理全局 insets 状态信息,InsetsStateController 中的 mState 代表当前屏幕的所以类型的 Insets 状态信息
InsetsStateController 还持有不同类型的 InsetsSourceProvider,通常 DisplayContent、InsetsPolicy 等模块会通过 InsetsStateController 访问某一类型的 InsetsSourceProvider 实例
负责在服务端根据 WindowState 配置、生成特定类型的 InsetsSource
com.android.server.wm.InsetsSourceProvider#getSource
/**
* Updates the window that currently backs this source.
*
* @param win The window that links to this source.
* @param frameProvider Based on display frame state and the window, calculates the resulting
* frame that should be reported to clients.
* @param imeFrameProvider Based on display frame state and the window, calculates the resulting
* frame that should be reported to IME.
*/
void setWindow(@Nullable WindowState win,
@Nullable TriConsumer<DisplayFrames, WindowState, Rect> frameProvider,
@Nullable TriConsumer<DisplayFrames, WindowState, Rect> imeFrameProvider) {
if (mWin != null) {
if (mControllable) {
mWin.setControllableInsetProvider(null);
}
// The window may be animating such that we can hand out the leash to the control
// target. Revoke the leash by cancelling the animation to correct the state.
// TODO: Ideally, we should wait for the animation to finish so previous window can
// animate-out as new one animates-in.
mWin.cancelAnimation();
mWin.mProvidedInsetsSources.remove(mSource.getType());
}
ProtoLog.d(WM_DEBUG_IME, "InsetsSource setWin %s", win);
mWin = win;
mFrameProvider = frameProvider;
mImeFrameProvider = imeFrameProvider;
if (win == null) {
setServerVisible(false);
mSource.setFrame(new Rect());
mSource.setVisibleFrame(null);
} else {
mWin.mProvidedInsetsSources.put(mSource.getType(), mSource);
if (mControllable) {
mWin.setControllableInsetProvider(this);
if (mPendingControlTarget != null) {
updateControlForTarget(mPendingControlTarget, true /* force */);
mPendingControlTarget = null;
}
}
}
}
InsetsSource getSource() {
return mSource;
}
被 DisplayContent 创建并持有,InsetsPolicy 负责管理不同类型 Inests 的 InsetsControlTarget 实例,同时 InsetsPolicy 还持有 InsetsStateController 类型实例,也提供了一些 Inests 相关行为策略
DisplayContent 对应一块屏幕的内容,一块屏幕对应一个 DisplayContent,一个 DisplayContent 下持有且仅一个 InsetsStateController 实例与 InsetsPolicy 实例,
InsetsControlTarget 是一个接口,代表可以控制 Insets 状态的对象,WindowState 实现了该接口,由于 WindowState 是每个 Window 在 WMS 中的状态描述,所以每个 Window 都具有控制 Insets 的能力,不同类型的 Insets 可能被不同的 Window 控制,可以通过 dump InsetsStateController 的信息查看当前 Insets 被谁控制
com.android.server.wm.InsetsStateController#dump
void dump(String prefix, PrintWriter pw) {
pw.println(prefix + "WindowInsetsStateController");
prefix = prefix + " ";
mState.dump(prefix, pw);
pw.println(prefix + "Control map:");
for (int i = mTypeControlTargetMap.size() - 1; i >= 0; i--) {
pw.print(prefix + " ");
pw.println(InsetsState.typeToString(mTypeControlTargetMap.keyAt(i)) + " -> "
+ mTypeControlTargetMap.valueAt(i));
}
pw.println(prefix + "InsetsSourceProviders:");
for (int i = mProviders.size() - 1; i >= 0; i--) {
mProviders.valueAt(i).dump(pw, prefix + " ");
}
}