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