• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.android.server.wm;
18 
19 import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
20 import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
21 import static android.view.InsetsState.ITYPE_IME;
22 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
23 import static android.view.InsetsState.ITYPE_STATUS_BAR;
24 
25 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
26 import static com.android.server.wm.InsetsSourceProviderProto.CAPTURED_LEASH;
27 import static com.android.server.wm.InsetsSourceProviderProto.CLIENT_VISIBLE;
28 import static com.android.server.wm.InsetsSourceProviderProto.CONTROL;
29 import static com.android.server.wm.InsetsSourceProviderProto.CONTROLLABLE;
30 import static com.android.server.wm.InsetsSourceProviderProto.CONTROL_TARGET;
31 import static com.android.server.wm.InsetsSourceProviderProto.FAKE_CONTROL;
32 import static com.android.server.wm.InsetsSourceProviderProto.FAKE_CONTROL_TARGET;
33 import static com.android.server.wm.InsetsSourceProviderProto.FRAME;
34 import static com.android.server.wm.InsetsSourceProviderProto.IME_OVERRIDDEN_FRAME;
35 import static com.android.server.wm.InsetsSourceProviderProto.IS_LEASH_READY_FOR_DISPATCHING;
36 import static com.android.server.wm.InsetsSourceProviderProto.PENDING_CONTROL_TARGET;
37 import static com.android.server.wm.InsetsSourceProviderProto.SEAMLESS_ROTATING;
38 import static com.android.server.wm.InsetsSourceProviderProto.SERVER_VISIBLE;
39 import static com.android.server.wm.InsetsSourceProviderProto.SOURCE;
40 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_INSETS_CONTROL;
41 import static com.android.server.wm.WindowManagerService.H.LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED;
42 
43 import android.annotation.NonNull;
44 import android.annotation.Nullable;
45 import android.graphics.Insets;
46 import android.graphics.Point;
47 import android.graphics.Rect;
48 import android.util.proto.ProtoOutputStream;
49 import android.view.InsetsSource;
50 import android.view.InsetsSourceControl;
51 import android.view.InsetsState;
52 import android.view.SurfaceControl;
53 import android.view.SurfaceControl.Transaction;
54 
55 import com.android.internal.annotations.VisibleForTesting;
56 import com.android.internal.protolog.common.ProtoLog;
57 import com.android.internal.util.function.TriConsumer;
58 import com.android.server.wm.SurfaceAnimator.AnimationType;
59 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
60 
61 import java.io.PrintWriter;
62 import java.util.function.Consumer;
63 
64 /**
65  * Controller for a specific inset source on the server. It's called provider as it provides the
66  * {@link InsetsSource} to the client that uses it in {@link android.view.InsetsSourceConsumer}.
67  */
68 class InsetsSourceProvider {
69 
70     protected final DisplayContent mDisplayContent;
71     protected final @NonNull InsetsSource mSource;
72     protected WindowState mWin;
73 
74     private final Rect mTmpRect = new Rect();
75     private final InsetsStateController mStateController;
76     private final InsetsSourceControl mFakeControl;
77     private @Nullable InsetsSourceControl mControl;
78     private @Nullable InsetsControlTarget mControlTarget;
79     private @Nullable InsetsControlTarget mPendingControlTarget;
80     private @Nullable InsetsControlTarget mFakeControlTarget;
81 
82     private @Nullable ControlAdapter mAdapter;
83     private TriConsumer<DisplayFrames, WindowState, Rect> mFrameProvider;
84     private TriConsumer<DisplayFrames, WindowState, Rect> mImeFrameProvider;
85     private final Rect mImeOverrideFrame = new Rect();
86     private boolean mIsLeashReadyForDispatching;
87     private final Rect mLastSourceFrame = new Rect();
88 
89     private final Consumer<Transaction> mSetLeashPositionConsumer = t -> {
90         if (mControl != null) {
91             final SurfaceControl leash = mControl.getLeash();
92             if (leash != null) {
93                 final Point position = mControl.getSurfacePosition();
94                 t.setPosition(leash, position.x, position.y);
95             }
96         }
97     };
98 
99     /** The visibility override from the current controlling window. */
100     private boolean mClientVisible;
101 
102     /**
103      * Whether the window is available and considered visible as in {@link WindowState#isVisible}.
104      */
105     private boolean mServerVisible;
106 
107     private boolean mSeamlessRotating;
108 
109     private final boolean mControllable;
110 
InsetsSourceProvider(InsetsSource source, InsetsStateController stateController, DisplayContent displayContent)111     InsetsSourceProvider(InsetsSource source, InsetsStateController stateController,
112             DisplayContent displayContent) {
113         mClientVisible = InsetsState.getDefaultVisibility(source.getType());
114         mSource = source;
115         mDisplayContent = displayContent;
116         mStateController = stateController;
117         mFakeControl = new InsetsSourceControl(
118                 source.getType(), null /* leash */, new Point(), Insets.NONE);
119 
120         switch (source.getType()) {
121             case ITYPE_STATUS_BAR:
122             case ITYPE_NAVIGATION_BAR:
123             case ITYPE_IME:
124             case ITYPE_CLIMATE_BAR:
125             case ITYPE_EXTRA_NAVIGATION_BAR:
126                 mControllable = true;
127                 break;
128             default:
129                 mControllable = false;
130         }
131     }
132 
getSource()133     InsetsSource getSource() {
134         return mSource;
135     }
136 
137     /**
138      * @return Whether the current flag configuration allows to control this source.
139      */
isControllable()140     boolean isControllable() {
141         return mControllable;
142     }
143 
144     /**
145      * Updates the window that currently backs this source.
146      *
147      * @param win The window that links to this source.
148      * @param frameProvider Based on display frame state and the window, calculates the resulting
149      *                      frame that should be reported to clients.
150      * @param imeFrameProvider Based on display frame state and the window, calculates the resulting
151      *                         frame that should be reported to IME.
152      */
setWindow(@ullable WindowState win, @Nullable TriConsumer<DisplayFrames, WindowState, Rect> frameProvider, @Nullable TriConsumer<DisplayFrames, WindowState, Rect> imeFrameProvider)153     void setWindow(@Nullable WindowState win,
154             @Nullable TriConsumer<DisplayFrames, WindowState, Rect> frameProvider,
155             @Nullable TriConsumer<DisplayFrames, WindowState, Rect> imeFrameProvider) {
156         if (mWin != null) {
157             if (mControllable) {
158                 mWin.setControllableInsetProvider(null);
159             }
160             // The window may be animating such that we can hand out the leash to the control
161             // target. Revoke the leash by cancelling the animation to correct the state.
162             // TODO: Ideally, we should wait for the animation to finish so previous window can
163             // animate-out as new one animates-in.
164             mWin.cancelAnimation();
165             mWin.mProvidedInsetsSources.remove(mSource.getType());
166         }
167         ProtoLog.d(WM_DEBUG_IME, "InsetsSource setWin %s", win);
168         mWin = win;
169         mFrameProvider = frameProvider;
170         mImeFrameProvider = imeFrameProvider;
171         if (win == null) {
172             setServerVisible(false);
173             mSource.setFrame(new Rect());
174             mSource.setVisibleFrame(null);
175         } else {
176             mWin.mProvidedInsetsSources.put(mSource.getType(), mSource);
177             if (mControllable) {
178                 mWin.setControllableInsetProvider(this);
179                 if (mPendingControlTarget != null) {
180                     updateControlForTarget(mPendingControlTarget, true /* force */);
181                     mPendingControlTarget = null;
182                 }
183             }
184         }
185     }
186 
187     /**
188      * @return Whether there is a window which backs this source.
189      */
hasWindow()190     boolean hasWindow() {
191         return mWin != null;
192     }
193 
194     /**
195      * The source frame can affect the layout of other windows, so this should be called once the
196      * window gets laid out.
197      */
updateSourceFrame()198     void updateSourceFrame() {
199         if (mWin == null || mWin.mGivenInsetsPending) {
200             // If the given insets are pending, they are not reliable for now. The source frame
201             // should be updated after the new given insets are sent to window manager.
202             return;
203         }
204 
205         // Make sure we set the valid source frame only when server visible is true, because the
206         // frame may not yet determined that server side doesn't think the window is ready to
207         // visible. (i.e. No surface, pending insets that were given during layout, etc..)
208         if (mServerVisible) {
209             mTmpRect.set(mWin.getFrame());
210             if (mFrameProvider != null) {
211                 mFrameProvider.accept(mWin.getDisplayContent().mDisplayFrames, mWin, mTmpRect);
212             } else {
213                 mTmpRect.inset(mWin.mGivenContentInsets);
214             }
215         } else {
216             mTmpRect.setEmpty();
217         }
218         mSource.setFrame(mTmpRect);
219 
220         if (mImeFrameProvider != null) {
221             mImeOverrideFrame.set(mWin.getFrame());
222             mImeFrameProvider.accept(mWin.getDisplayContent().mDisplayFrames, mWin,
223                     mImeOverrideFrame);
224         }
225 
226         if (mWin.mGivenVisibleInsets.left != 0 || mWin.mGivenVisibleInsets.top != 0
227                 || mWin.mGivenVisibleInsets.right != 0 || mWin.mGivenVisibleInsets.bottom != 0) {
228             mTmpRect.set(mWin.getFrame());
229             mTmpRect.inset(mWin.mGivenVisibleInsets);
230             mSource.setVisibleFrame(mTmpRect);
231         } else {
232             mSource.setVisibleFrame(null);
233         }
234     }
235 
236     /** @return A new source computed by the specified window frame in the given display frames. */
createSimulatedSource(DisplayFrames displayFrames, WindowFrames windowFrames)237     InsetsSource createSimulatedSource(DisplayFrames displayFrames, WindowFrames windowFrames) {
238         // Don't copy visible frame because it might not be calculated in the provided display
239         // frames and it is not significant for this usage.
240         final InsetsSource source = new InsetsSource(mSource.getType());
241         source.setVisible(mSource.isVisible());
242         mTmpRect.set(windowFrames.mFrame);
243         if (mFrameProvider != null) {
244             mFrameProvider.accept(displayFrames, mWin, mTmpRect);
245         }
246         source.setFrame(mTmpRect);
247         return source;
248     }
249 
250     /**
251      * Called when a layout pass has occurred.
252      */
onPostLayout()253     void onPostLayout() {
254         if (mWin == null) {
255             return;
256         }
257 
258         setServerVisible(mWin.wouldBeVisibleIfPolicyIgnored() && mWin.isVisibleByPolicy());
259         updateSourceFrame();
260         if (mControl != null) {
261             boolean changed = false;
262             final Point position = getWindowFrameSurfacePosition();
263             if (mControl.setSurfacePosition(position.x, position.y) && mControlTarget != null) {
264                 changed = true;
265                 if (mWin.getWindowFrames().didFrameSizeChange() && mWin.mWinAnimator.getShown()
266                         && mWin.okToDisplay()) {
267                     mWin.applyWithNextDraw(mSetLeashPositionConsumer);
268                 } else {
269                     mSetLeashPositionConsumer.accept(mWin.getPendingTransaction());
270                 }
271             }
272             if (mServerVisible && !mLastSourceFrame.equals(mSource.getFrame())) {
273                 final Insets insetsHint = mSource.calculateInsets(
274                         mWin.getBounds(), true /* ignoreVisibility */);
275                 if (!insetsHint.equals(mControl.getInsetsHint())) {
276                     changed = true;
277                     mControl.setInsetsHint(insetsHint);
278                 }
279                 mLastSourceFrame.set(mSource.getFrame());
280             }
281             if (changed) {
282                 mStateController.notifyControlChanged(mControlTarget);
283             }
284         }
285     }
286 
getWindowFrameSurfacePosition()287     private Point getWindowFrameSurfacePosition() {
288         final Rect frame = mWin.getFrame();
289         final Point position = new Point();
290         mWin.transformFrameToSurfacePosition(frame.left, frame.top, position);
291         return position;
292     }
293 
294     /**
295      * @see InsetsStateController#onControlFakeTargetChanged(int, InsetsControlTarget)
296      */
updateControlForFakeTarget(@ullable InsetsControlTarget fakeTarget)297     void updateControlForFakeTarget(@Nullable InsetsControlTarget fakeTarget) {
298         if (fakeTarget == mFakeControlTarget) {
299             return;
300         }
301         mFakeControlTarget = fakeTarget;
302     }
303 
updateControlForTarget(@ullable InsetsControlTarget target, boolean force)304     void updateControlForTarget(@Nullable InsetsControlTarget target, boolean force) {
305         if (mSeamlessRotating) {
306             // We are un-rotating the window against the display rotation. We don't want the target
307             // to control the window for now.
308             return;
309         }
310         if (target != null && target.getWindow() != null) {
311             // ime control target could be a different window.
312             // Refer WindowState#getImeControlTarget().
313             target = target.getWindow().getImeControlTarget();
314         }
315 
316         if (mWin != null && mWin.getSurfaceControl() == null) {
317             // if window doesn't have a surface, set it null and return.
318             setWindow(null, null, null);
319         }
320         if (mWin == null) {
321             mPendingControlTarget = target;
322             return;
323         }
324         if (target == mControlTarget && !force) {
325             return;
326         }
327         if (target == null) {
328             // Cancelling the animation will invoke onAnimationCancelled, resetting all the fields.
329             mWin.cancelAnimation();
330             setClientVisible(InsetsState.getDefaultVisibility(mSource.getType()));
331             return;
332         }
333         final Point surfacePosition = getWindowFrameSurfacePosition();
334         mAdapter = new ControlAdapter(surfacePosition);
335         if (getSource().getType() == ITYPE_IME) {
336             setClientVisible(target.getRequestedVisibility(mSource.getType()));
337         }
338         final Transaction t = mDisplayContent.getPendingTransaction();
339         mWin.startAnimation(t, mAdapter, !mClientVisible /* hidden */,
340                 ANIMATION_TYPE_INSETS_CONTROL);
341 
342         // The leash was just created. We cannot dispatch it until its surface transaction is
343         // applied. Otherwise, the client's operation to the leash might be overwritten by us.
344         mIsLeashReadyForDispatching = false;
345 
346         final SurfaceControl leash = mAdapter.mCapturedLeash;
347         mControlTarget = target;
348         updateVisibility();
349         mControl = new InsetsSourceControl(mSource.getType(), leash, surfacePosition,
350                 mSource.calculateInsets(mWin.getBounds(), true /* ignoreVisibility */));
351         ProtoLog.d(WM_DEBUG_IME,
352                 "InsetsSource Control %s for target %s", mControl, mControlTarget);
353     }
354 
startSeamlessRotation()355     void startSeamlessRotation() {
356       if (!mSeamlessRotating) {
357           mSeamlessRotating = true;
358           mWin.cancelAnimation();
359       }
360     }
361 
finishSeamlessRotation()362     void finishSeamlessRotation() {
363         mSeamlessRotating = false;
364     }
365 
updateClientVisibility(InsetsControlTarget caller)366     boolean updateClientVisibility(InsetsControlTarget caller) {
367         final boolean requestedVisible = caller.getRequestedVisibility(mSource.getType());
368         if (caller != mControlTarget || requestedVisible == mClientVisible) {
369             return false;
370         }
371         setClientVisible(requestedVisible);
372         return true;
373     }
374 
onSurfaceTransactionApplied()375     void onSurfaceTransactionApplied() {
376         mIsLeashReadyForDispatching = true;
377     }
378 
setClientVisible(boolean clientVisible)379     void setClientVisible(boolean clientVisible) {
380         if (mClientVisible == clientVisible) {
381             return;
382         }
383         mClientVisible = clientVisible;
384         mDisplayContent.mWmService.mH.obtainMessage(
385                 LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED, mDisplayContent).sendToTarget();
386         updateVisibility();
387     }
388 
389     @VisibleForTesting
setServerVisible(boolean serverVisible)390     void setServerVisible(boolean serverVisible) {
391         mServerVisible = serverVisible;
392         updateVisibility();
393     }
394 
updateVisibility()395     protected void updateVisibility() {
396         mSource.setVisible(mServerVisible && (isMirroredSource() || mClientVisible));
397         ProtoLog.d(WM_DEBUG_IME,
398                 "InsetsSource updateVisibility serverVisible: %s clientVisible: %s",
399                 mServerVisible, mClientVisible);
400     }
401 
isMirroredSource()402     private boolean isMirroredSource() {
403         if (mWin == null) {
404             return false;
405         }
406         final int[] provides = mWin.mAttrs.providesInsetsTypes;
407         if (provides == null) {
408             return false;
409         }
410         for (int i = 0; i < provides.length; i++) {
411             if (provides[i] == ITYPE_IME) {
412                 return true;
413             }
414         }
415         return false;
416     }
417 
getControl(InsetsControlTarget target)418     InsetsSourceControl getControl(InsetsControlTarget target) {
419         if (target == mControlTarget) {
420             if (!mIsLeashReadyForDispatching && mControl != null) {
421                 // The surface transaction of preparing leash is not applied yet. We don't send it
422                 // to the client in case that the client applies its transaction sooner than ours
423                 // that we could unexpectedly overwrite the surface state.
424                 return new InsetsSourceControl(mControl.getType(), null /* leash */,
425                         mControl.getSurfacePosition(), mControl.getInsetsHint());
426             }
427             return mControl;
428         }
429         if (target == mFakeControlTarget) {
430             return mFakeControl;
431         }
432         return null;
433     }
434 
getControlTarget()435     InsetsControlTarget getControlTarget() {
436         return mControlTarget;
437     }
438 
isClientVisible()439     boolean isClientVisible() {
440         return mClientVisible;
441     }
442 
443     /**
444      * @return Whether this provider uses a different frame to dispatch to the IME.
445      */
overridesImeFrame()446     boolean overridesImeFrame() {
447         return mImeFrameProvider != null;
448     }
449 
450     /**
451      * @return Rect to dispatch to the IME as frame. Only valid if {@link #overridesImeFrame()}
452      *         returns {@code true}.
453      */
getImeOverrideFrame()454     Rect getImeOverrideFrame() {
455         return mImeOverrideFrame;
456     }
457 
dump(PrintWriter pw, String prefix)458     public void dump(PrintWriter pw, String prefix) {
459         pw.println(prefix + getClass().getSimpleName());
460         prefix = prefix + "  ";
461         pw.print(prefix + "mSource="); mSource.dump("", pw);
462         if (mControl != null) {
463             pw.print(prefix + "mControl=");
464             mControl.dump("", pw);
465         }
466         pw.print(prefix);
467         pw.print("mIsLeashReadyForDispatching="); pw.print(mIsLeashReadyForDispatching);
468         pw.print(" mImeOverrideFrame="); pw.print(mImeOverrideFrame.toShortString());
469         pw.println();
470         if (mWin != null) {
471             pw.print(prefix + "mWin=");
472             pw.println(mWin);
473         }
474         if (mAdapter != null) {
475             pw.print(prefix + "mAdapter=");
476             mAdapter.dump(pw, "");
477         }
478         if (mControlTarget != null) {
479             pw.print(prefix + "mControlTarget=");
480             pw.println(mControlTarget.getWindow());
481         }
482         if (mPendingControlTarget != null) {
483             pw.print(prefix + "mPendingControlTarget=");
484             pw.println(mPendingControlTarget.getWindow());
485         }
486         if (mFakeControlTarget != null) {
487             pw.print(prefix + "mFakeControlTarget=");
488             pw.println(mFakeControlTarget.getWindow());
489         }
490     }
491 
dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel)492     void dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel) {
493         final long token = proto.start(fieldId);
494         mSource.dumpDebug(proto, SOURCE);
495         mTmpRect.dumpDebug(proto, FRAME);
496         mFakeControl.dumpDebug(proto, FAKE_CONTROL);
497         if (mControl != null) {
498             mControl.dumpDebug(proto, CONTROL);
499         }
500         if (mControlTarget != null && mControlTarget.getWindow() != null) {
501             mControlTarget.getWindow().dumpDebug(proto, CONTROL_TARGET, logLevel);
502         }
503         if (mPendingControlTarget != null && mPendingControlTarget.getWindow() != null) {
504             mPendingControlTarget.getWindow().dumpDebug(proto, PENDING_CONTROL_TARGET, logLevel);
505         }
506         if (mFakeControlTarget != null && mFakeControlTarget.getWindow() != null) {
507             mFakeControlTarget.getWindow().dumpDebug(proto, FAKE_CONTROL_TARGET, logLevel);
508         }
509         if (mAdapter != null && mAdapter.mCapturedLeash != null) {
510             mAdapter.mCapturedLeash.dumpDebug(proto, CAPTURED_LEASH);
511         }
512         mImeOverrideFrame.dumpDebug(proto, IME_OVERRIDDEN_FRAME);
513         proto.write(IS_LEASH_READY_FOR_DISPATCHING, mIsLeashReadyForDispatching);
514         proto.write(CLIENT_VISIBLE, mClientVisible);
515         proto.write(SERVER_VISIBLE, mServerVisible);
516         proto.write(SEAMLESS_ROTATING, mSeamlessRotating);
517         proto.write(CONTROLLABLE, mControllable);
518         proto.end(token);
519     }
520 
521     private class ControlAdapter implements AnimationAdapter {
522 
523         private final Point mSurfacePosition;
524         private SurfaceControl mCapturedLeash;
525 
ControlAdapter(Point surfacePosition)526         ControlAdapter(Point surfacePosition) {
527             mSurfacePosition = surfacePosition;
528         }
529 
530         @Override
getShowWallpaper()531         public boolean getShowWallpaper() {
532             return false;
533         }
534 
535         @Override
startAnimation(SurfaceControl animationLeash, Transaction t, @AnimationType int type, OnAnimationFinishedCallback finishCallback)536         public void startAnimation(SurfaceControl animationLeash, Transaction t,
537                 @AnimationType int type, OnAnimationFinishedCallback finishCallback) {
538             // TODO(b/166736352): Check if we still need to control the IME visibility here.
539             if (mSource.getType() == ITYPE_IME) {
540                 // TODO: use 0 alpha and remove t.hide() once b/138459974 is fixed.
541                 t.setAlpha(animationLeash, 1 /* alpha */);
542                 t.hide(animationLeash);
543             }
544             ProtoLog.i(WM_DEBUG_IME,
545                     "ControlAdapter startAnimation mSource: %s controlTarget: %s", mSource,
546                     mControlTarget);
547 
548             mCapturedLeash = animationLeash;
549             t.setPosition(mCapturedLeash, mSurfacePosition.x, mSurfacePosition.y);
550         }
551 
552         @Override
onAnimationCancelled(SurfaceControl animationLeash)553         public void onAnimationCancelled(SurfaceControl animationLeash) {
554             if (mAdapter == this) {
555                 mStateController.notifyControlRevoked(mControlTarget, InsetsSourceProvider.this);
556                 mControl = null;
557                 mControlTarget = null;
558                 mAdapter = null;
559                 setClientVisible(InsetsState.getDefaultVisibility(mSource.getType()));
560                 ProtoLog.i(WM_DEBUG_IME,
561                         "ControlAdapter onAnimationCancelled mSource: %s mControlTarget: %s",
562                         mSource, mControlTarget);
563             }
564         }
565 
566         @Override
getDurationHint()567         public long getDurationHint() {
568             return 0;
569         }
570 
571         @Override
getStatusBarTransitionsStartTime()572         public long getStatusBarTransitionsStartTime() {
573             return 0;
574         }
575 
576         @Override
dump(PrintWriter pw, String prefix)577         public void dump(PrintWriter pw, String prefix) {
578             pw.print(prefix + "ControlAdapter mCapturedLeash=");
579             pw.print(mCapturedLeash);
580             pw.println();
581         }
582 
583         @Override
dumpDebug(ProtoOutputStream proto)584         public void dumpDebug(ProtoOutputStream proto) {
585         }
586     }
587 }
588