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 com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_REMOTE_ANIMATIONS;
20 import static com.android.server.wm.AnimationAdapterProto.REMOTE;
21 import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
22 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
23 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
24 
25 import android.annotation.NonNull;
26 import android.graphics.Point;
27 import android.graphics.Rect;
28 import android.os.Binder;
29 import android.os.Handler;
30 import android.os.IBinder.DeathRecipient;
31 import android.os.RemoteException;
32 import android.os.SystemClock;
33 import android.util.Slog;
34 import android.util.proto.ProtoOutputStream;
35 import android.view.IRemoteAnimationFinishedCallback;
36 import android.view.RemoteAnimationAdapter;
37 import android.view.RemoteAnimationTarget;
38 import android.view.SurfaceControl;
39 import android.view.SurfaceControl.Transaction;
40 import android.view.WindowManager;
41 
42 import com.android.internal.annotations.VisibleForTesting;
43 import com.android.internal.protolog.ProtoLogImpl;
44 import com.android.internal.protolog.common.ProtoLog;
45 import com.android.internal.util.FastPrintWriter;
46 import com.android.server.wm.SurfaceAnimator.AnimationType;
47 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
48 
49 import java.io.PrintWriter;
50 import java.io.StringWriter;
51 import java.util.ArrayList;
52 
53 /**
54  * Helper class to run app animations in a remote process.
55  */
56 class RemoteAnimationController implements DeathRecipient {
57     private static final String TAG = TAG_WITH_CLASS_NAME
58                     ? "RemoteAnimationController" : TAG_WM;
59     private static final long TIMEOUT_MS = 10000;
60 
61     private final WindowManagerService mService;
62     private final DisplayContent mDisplayContent;
63     private final RemoteAnimationAdapter mRemoteAnimationAdapter;
64     private final ArrayList<RemoteAnimationRecord> mPendingAnimations = new ArrayList<>();
65     private final ArrayList<WallpaperAnimationAdapter> mPendingWallpaperAnimations =
66             new ArrayList<>();
67     @VisibleForTesting
68     final ArrayList<NonAppWindowAnimationAdapter> mPendingNonAppAnimations = new ArrayList<>();
69     private final Handler mHandler;
70     private final Runnable mTimeoutRunnable = () -> cancelAnimation("timeoutRunnable");
71 
72     private FinishedCallback mFinishedCallback;
73     private boolean mCanceled;
74     private boolean mLinkedToDeathOfRunner;
75 
RemoteAnimationController(WindowManagerService service, DisplayContent displayContent, RemoteAnimationAdapter remoteAnimationAdapter, Handler handler)76     RemoteAnimationController(WindowManagerService service, DisplayContent displayContent,
77             RemoteAnimationAdapter remoteAnimationAdapter, Handler handler) {
78         mService = service;
79         mDisplayContent = displayContent;
80         mRemoteAnimationAdapter = remoteAnimationAdapter;
81         mHandler = handler;
82     }
83 
84     /**
85      * Creates an animation record for each individual {@link WindowContainer}.
86      *
87      * @param windowContainer The windows to animate.
88      * @param position The position app bounds relative to its parent.
89      * @param localBounds The bounds of the app relative to its parent.
90      * @param endBounds The end bounds after the transition, in screen coordinates.
91      * @param startBounds The start bounds before the transition, in screen coordinates.
92      * @return The record representing animation(s) to run on the app.
93      */
createRemoteAnimationRecord(WindowContainer windowContainer, Point position, Rect localBounds, Rect endBounds, Rect startBounds)94     RemoteAnimationRecord createRemoteAnimationRecord(WindowContainer windowContainer,
95             Point position, Rect localBounds, Rect endBounds, Rect startBounds) {
96         ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createAnimationAdapter(): container=%s",
97                 windowContainer);
98         final RemoteAnimationRecord adapters = new RemoteAnimationRecord(windowContainer, position,
99                 localBounds, endBounds, startBounds);
100         mPendingAnimations.add(adapters);
101         return adapters;
102     }
103 
104     /**
105      * Called when the transition is ready to be started, and all leashes have been set up.
106      */
goodToGo(@indowManager.TransitionOldType int transit)107     void goodToGo(@WindowManager.TransitionOldType int transit) {
108         ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "goodToGo()");
109         if (mCanceled) {
110             ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS,
111                     "goodToGo(): Animation canceled already");
112             onAnimationFinished();
113             invokeAnimationCancelled();
114             return;
115         }
116 
117         // Scale the timeout with the animator scale the controlling app is using.
118         mHandler.postDelayed(mTimeoutRunnable,
119                 (long) (TIMEOUT_MS * mService.getCurrentAnimatorScale()));
120         mFinishedCallback = new FinishedCallback(this);
121 
122         // Create the app targets
123         final RemoteAnimationTarget[] appTargets = createAppAnimations();
124         if (appTargets.length == 0 && !AppTransition.isKeyguardOccludeTransitOld(transit)) {
125             // Keyguard occlude transition can be executed before the occluding activity becomes
126             // visible. Even in this case, KeyguardService expects to receive binder call, so we
127             // don't cancel remote animation.
128             ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS,
129                     "goodToGo(): No apps to animate, mPendingAnimations=%d",
130                     mPendingAnimations.size());
131             onAnimationFinished();
132             invokeAnimationCancelled();
133             return;
134         }
135 
136         // Create the remote wallpaper animation targets (if any)
137         final RemoteAnimationTarget[] wallpaperTargets = createWallpaperAnimations();
138 
139         // Create the remote non app animation targets (if any)
140         final RemoteAnimationTarget[] nonAppTargets = createNonAppWindowAnimations(transit);
141 
142         mService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
143             try {
144                 linkToDeathOfRunner();
145                 ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "goodToGo(): onAnimationStart,"
146                                 + " transit=%s, apps=%d, wallpapers=%d, nonApps=%d",
147                         AppTransition.appTransitionOldToString(transit), appTargets.length,
148                         wallpaperTargets.length, nonAppTargets.length);
149                 mRemoteAnimationAdapter.getRunner().onAnimationStart(transit, appTargets,
150                         wallpaperTargets, nonAppTargets, mFinishedCallback);
151             } catch (RemoteException e) {
152                 Slog.e(TAG, "Failed to start remote animation", e);
153                 onAnimationFinished();
154             }
155             if (ProtoLogImpl.isEnabled(WM_DEBUG_REMOTE_ANIMATIONS)) {
156                 ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation(): Notify animation start:");
157                 writeStartDebugStatement();
158             }
159         });
160         setRunningRemoteAnimation(true);
161     }
162 
cancelAnimation(String reason)163     void cancelAnimation(String reason) {
164         ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "cancelAnimation(): reason=%s", reason);
165         synchronized (mService.getWindowManagerLock()) {
166             if (mCanceled) {
167                 return;
168             }
169             mCanceled = true;
170         }
171         onAnimationFinished();
172         invokeAnimationCancelled();
173     }
174 
writeStartDebugStatement()175     private void writeStartDebugStatement() {
176         ProtoLog.i(WM_DEBUG_REMOTE_ANIMATIONS, "Starting remote animation");
177         final StringWriter sw = new StringWriter();
178         final FastPrintWriter pw = new FastPrintWriter(sw);
179         for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
180             mPendingAnimations.get(i).mAdapter.dump(pw, "");
181         }
182         pw.close();
183         ProtoLog.i(WM_DEBUG_REMOTE_ANIMATIONS, "%s", sw.toString());
184     }
185 
createAppAnimations()186     private RemoteAnimationTarget[] createAppAnimations() {
187         ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createAppAnimations()");
188         final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
189         for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
190             final RemoteAnimationRecord wrappers = mPendingAnimations.get(i);
191             final RemoteAnimationTarget target = wrappers.createRemoteAnimationTarget();
192             if (target != null) {
193                 ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\tAdd container=%s",
194                         wrappers.mWindowContainer);
195                 targets.add(target);
196             } else {
197                 ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\tRemove container=%s",
198                         wrappers.mWindowContainer);
199 
200                 // We can't really start an animation but we still need to make sure to finish the
201                 // pending animation that was started by SurfaceAnimator
202                 if (wrappers.mAdapter != null
203                         && wrappers.mAdapter.mCapturedFinishCallback != null) {
204                     wrappers.mAdapter.mCapturedFinishCallback
205                             .onAnimationFinished(wrappers.mAdapter.mAnimationType,
206                                     wrappers.mAdapter);
207                 }
208                 if (wrappers.mThumbnailAdapter != null
209                         && wrappers.mThumbnailAdapter.mCapturedFinishCallback != null) {
210                     wrappers.mThumbnailAdapter.mCapturedFinishCallback
211                             .onAnimationFinished(wrappers.mThumbnailAdapter.mAnimationType,
212                                     wrappers.mThumbnailAdapter);
213                 }
214                 mPendingAnimations.remove(i);
215             }
216         }
217         return targets.toArray(new RemoteAnimationTarget[targets.size()]);
218     }
219 
createWallpaperAnimations()220     private RemoteAnimationTarget[] createWallpaperAnimations() {
221         ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createWallpaperAnimations()");
222         return WallpaperAnimationAdapter.startWallpaperAnimations(mDisplayContent,
223                 mRemoteAnimationAdapter.getDuration(),
224                 mRemoteAnimationAdapter.getStatusBarTransitionDelay(),
225                 adapter -> {
226                     synchronized (mService.mGlobalLock) {
227                         // If the wallpaper animation is canceled, continue with the app animation
228                         mPendingWallpaperAnimations.remove(adapter);
229                     }
230                 }, mPendingWallpaperAnimations);
231     }
232 
233     private RemoteAnimationTarget[] createNonAppWindowAnimations(
234             @WindowManager.TransitionOldType int transit) {
235         ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createNonAppWindowAnimations()");
236         return NonAppWindowAnimationAdapter.startNonAppWindowAnimations(mService,
237                 mDisplayContent,
238                 transit,
239                 mRemoteAnimationAdapter.getDuration(),
240                 mRemoteAnimationAdapter.getStatusBarTransitionDelay(),
241                 mPendingNonAppAnimations);
242     }
243 
244     private void onAnimationFinished() {
245         ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "onAnimationFinished(): mPendingAnimations=%d",
246                 mPendingAnimations.size());
247         mHandler.removeCallbacks(mTimeoutRunnable);
248         synchronized (mService.mGlobalLock) {
249             unlinkToDeathOfRunner();
250             releaseFinishedCallback();
251             mService.openSurfaceTransaction();
252             try {
253                 ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS,
254                         "onAnimationFinished(): Notify animation finished:");
255                 for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
256                     final RemoteAnimationRecord adapters = mPendingAnimations.get(i);
257                     if (adapters.mAdapter != null) {
258                         adapters.mAdapter.mCapturedFinishCallback
259                                 .onAnimationFinished(adapters.mAdapter.mAnimationType,
260                                         adapters.mAdapter);
261                     }
262                     if (adapters.mThumbnailAdapter != null) {
263                         adapters.mThumbnailAdapter.mCapturedFinishCallback
264                                 .onAnimationFinished(adapters.mThumbnailAdapter.mAnimationType,
265                                         adapters.mThumbnailAdapter);
266                     }
267                     mPendingAnimations.remove(i);
268                     ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\tcontainer=%s",
269                             adapters.mWindowContainer);
270                 }
271 
272                 for (int i = mPendingWallpaperAnimations.size() - 1; i >= 0; i--) {
273                     final WallpaperAnimationAdapter adapter = mPendingWallpaperAnimations.get(i);
274                     adapter.getLeashFinishedCallback().onAnimationFinished(
275                             adapter.getLastAnimationType(), adapter);
276                     mPendingWallpaperAnimations.remove(i);
277                     ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\twallpaper=%s", adapter.getToken());
278                 }
279 
280                 for (int i = mPendingNonAppAnimations.size() - 1; i >= 0; i--) {
281                     final NonAppWindowAnimationAdapter adapter = mPendingNonAppAnimations.get(i);
282                     adapter.getLeashFinishedCallback().onAnimationFinished(
283                             adapter.getLastAnimationType(), adapter);
284                     mPendingNonAppAnimations.remove(i);
285                     ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\tnonApp=%s",
286                             adapter.getWindowContainer());
287                 }
288             } catch (Exception e) {
289                 Slog.e(TAG, "Failed to finish remote animation", e);
290                 throw e;
291             } finally {
292                 mService.closeSurfaceTransaction("RemoteAnimationController#finished");
293             }
294         }
295         setRunningRemoteAnimation(false);
296         ProtoLog.i(WM_DEBUG_REMOTE_ANIMATIONS, "Finishing remote animation");
297     }
298 
299     private void invokeAnimationCancelled() {
300         try {
301             mRemoteAnimationAdapter.getRunner().onAnimationCancelled();
302         } catch (RemoteException e) {
303             Slog.e(TAG, "Failed to notify cancel", e);
304         }
305     }
306 
307     private void releaseFinishedCallback() {
308         if (mFinishedCallback != null) {
309             mFinishedCallback.release();
310             mFinishedCallback = null;
311         }
312     }
313 
314     private void setRunningRemoteAnimation(boolean running) {
315         final int pid = mRemoteAnimationAdapter.getCallingPid();
316         final int uid = mRemoteAnimationAdapter.getCallingUid();
317 
318         if (pid == 0) {
319             throw new RuntimeException("Calling pid of remote animation was null");
320         }
321         final WindowProcessController wpc = mService.mAtmService.getProcessController(pid, uid);
322         if (wpc == null) {
323             Slog.w(TAG, "Unable to find process with pid=" + pid + " uid=" + uid);
324             return;
325         }
326         wpc.setRunningRemoteAnimation(running);
327     }
328 
329     private void linkToDeathOfRunner() throws RemoteException {
330         if (!mLinkedToDeathOfRunner) {
331             mRemoteAnimationAdapter.getRunner().asBinder().linkToDeath(this, 0);
332             mLinkedToDeathOfRunner = true;
333         }
334     }
335 
336     private void unlinkToDeathOfRunner() {
337         if (mLinkedToDeathOfRunner) {
338             mRemoteAnimationAdapter.getRunner().asBinder().unlinkToDeath(this, 0);
339             mLinkedToDeathOfRunner = false;
340         }
341     }
342 
343     @Override
344     public void binderDied() {
345         cancelAnimation("binderDied");
346     }
347 
348     private static final class FinishedCallback extends IRemoteAnimationFinishedCallback.Stub {
349 
350         RemoteAnimationController mOuter;
351 
352         FinishedCallback(RemoteAnimationController outer) {
353             mOuter = outer;
354         }
355 
356         @Override
357         public void onAnimationFinished() throws RemoteException {
358             ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "app-onAnimationFinished(): mOuter=%s", mOuter);
359             final long token = Binder.clearCallingIdentity();
360             try {
361                 if (mOuter != null) {
362                     mOuter.onAnimationFinished();
363 
364                     // In case the client holds on to the finish callback, make sure we don't leak
365                     // RemoteAnimationController which in turn would leak the runner on the client.
366                     mOuter = null;
367                 }
368             } finally {
369                 Binder.restoreCallingIdentity(token);
370             }
371         }
372 
373         /**
374          * Marks this callback as not be used anymore by releasing the reference to the outer class
375          * to prevent memory leak.
376          */
377         void release() {
378             ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "app-release(): mOuter=%s", mOuter);
379             mOuter = null;
380         }
381     };
382 
383     /**
384      * Contains information about a remote-animation for one WindowContainer. This keeps track of,
385      * potentially, multiple animating surfaces (AdapterWrappers) associated with one
386      * Window/Transition. For example, a change transition has an adapter controller for the
387      * main window and an adapter controlling the start-state snapshot.
388      * <p>
389      * This can be thought of as a bridge between the information that the remote animator sees (via
390      * {@link RemoteAnimationTarget}) and what the server sees (the
391      * {@link RemoteAnimationAdapterWrapper}(s) interfacing with the moving surfaces).
392      */
393     public class RemoteAnimationRecord {
394         RemoteAnimationAdapterWrapper mAdapter;
395         RemoteAnimationAdapterWrapper mThumbnailAdapter = null;
396         RemoteAnimationTarget mTarget;
397         final WindowContainer mWindowContainer;
398         final Rect mStartBounds;
399         private @RemoteAnimationTarget.Mode int mMode = RemoteAnimationTarget.MODE_CHANGING;
400 
401         RemoteAnimationRecord(WindowContainer windowContainer, Point endPos, Rect localBounds,
402                 Rect endBounds, Rect startBounds) {
403             mWindowContainer = windowContainer;
404             if (startBounds != null) {
405                 mStartBounds = new Rect(startBounds);
406                 mAdapter = new RemoteAnimationAdapterWrapper(this, endPos, localBounds, endBounds,
407                         mStartBounds);
408                 if (mRemoteAnimationAdapter.getChangeNeedsSnapshot()) {
409                     final Rect thumbnailLocalBounds = new Rect(startBounds);
410                     thumbnailLocalBounds.offsetTo(0, 0);
411                     // Snapshot is located at (0,0) of the animation leash. It doesn't have size
412                     // change, so the startBounds is its end bounds, and no start bounds for it.
413                     mThumbnailAdapter = new RemoteAnimationAdapterWrapper(this, new Point(0, 0),
414                             thumbnailLocalBounds, startBounds, new Rect());
415                 }
416             } else {
417                 mAdapter = new RemoteAnimationAdapterWrapper(this, endPos, localBounds, endBounds,
418                         new Rect());
419                 mStartBounds = null;
420             }
421         }
422 
423         RemoteAnimationTarget createRemoteAnimationTarget() {
424             if (mAdapter == null
425                     || mAdapter.mCapturedFinishCallback == null
426                     || mAdapter.mCapturedLeash == null) {
427                 return null;
428             }
429             mTarget = mWindowContainer.createRemoteAnimationTarget(this);
430             return mTarget;
431         }
432 
433         void setMode(@RemoteAnimationTarget.Mode int mode) {
434             mMode = mode;
435         }
436 
437         int getMode() {
438             return mMode;
439         }
440 
441         /** Whether its parent is also an animation target in the same transition. */
442         boolean hasAnimatingParent() {
443             // mOpeningApps and mClosingApps are only activities, so only need to check
444             // mChangingContainers.
445             for (int i = mDisplayContent.mChangingContainers.size() - 1; i >= 0; i--) {
446                 if (mWindowContainer.isDescendantOf(
447                         mDisplayContent.mChangingContainers.valueAt(i))) {
448                     return true;
449                 }
450             }
451             return false;
452         }
453     }
454 
455     class RemoteAnimationAdapterWrapper implements AnimationAdapter {
456         private final RemoteAnimationRecord mRecord;
457         SurfaceControl mCapturedLeash;
458         private OnAnimationFinishedCallback mCapturedFinishCallback;
459         private @AnimationType int mAnimationType;
460         final Point mPosition = new Point();
461         final Rect mLocalBounds;
462         final Rect mEndBounds = new Rect();
463         final Rect mStartBounds = new Rect();
464 
465         RemoteAnimationAdapterWrapper(RemoteAnimationRecord record, Point position,
466                 Rect localBounds, Rect endBounds, Rect startBounds) {
467             mRecord = record;
468             mPosition.set(position.x, position.y);
469             mLocalBounds = localBounds;
470             mEndBounds.set(endBounds);
471             mStartBounds.set(startBounds);
472         }
473 
474         @Override
475         public boolean getShowWallpaper() {
476             return false;
477         }
478 
479         @Override
480         public void startAnimation(SurfaceControl animationLeash, Transaction t,
481                 @AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) {
482             ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation");
483 
484             if (mStartBounds.isEmpty()) {
485                 // Restore position and stack crop until client has a chance to modify it.
486                 t.setPosition(animationLeash, mPosition.x, mPosition.y);
487                 t.setWindowCrop(animationLeash, mEndBounds.width(), mEndBounds.height());
488             } else {
489                 // Offset the change animation leash to the relative start position in parent.
490                 // (mPosition) is the relative end position in parent container.
491                 // (mStartBounds - mEndBounds) is the position difference between start and end.
492                 // (mPosition + mStartBounds - mEndBounds) will be the relative start position.
493                 t.setPosition(animationLeash, mPosition.x + mStartBounds.left - mEndBounds.left,
494                         mPosition.y + mStartBounds.top - mEndBounds.top);
495                 t.setWindowCrop(animationLeash, mStartBounds.width(), mStartBounds.height());
496             }
497             mCapturedLeash = animationLeash;
498             mCapturedFinishCallback = finishCallback;
499             mAnimationType = type;
500         }
501 
502         @Override
503         public void onAnimationCancelled(SurfaceControl animationLeash) {
504             if (mRecord.mAdapter == this) {
505                 mRecord.mAdapter = null;
506             } else {
507                 mRecord.mThumbnailAdapter = null;
508             }
509             if (mRecord.mAdapter == null && mRecord.mThumbnailAdapter == null) {
510                 mPendingAnimations.remove(mRecord);
511             }
512             if (mPendingAnimations.isEmpty()) {
513                 cancelAnimation("allAppAnimationsCanceled");
514             }
515         }
516 
517         @Override
518         public long getDurationHint() {
519             return mRemoteAnimationAdapter.getDuration();
520         }
521 
522         @Override
523         public long getStatusBarTransitionsStartTime() {
524             return SystemClock.uptimeMillis()
525                     + mRemoteAnimationAdapter.getStatusBarTransitionDelay();
526         }
527 
528         @Override
529         public void dump(PrintWriter pw, String prefix) {
530             pw.print(prefix); pw.print("container="); pw.println(mRecord.mWindowContainer);
531             if (mRecord.mTarget != null) {
532                 pw.print(prefix); pw.println("Target:");
533                 mRecord.mTarget.dump(pw, prefix + "  ");
534             } else {
535                 pw.print(prefix); pw.println("Target: null");
536             }
537         }
538 
539         @Override
540         public void dumpDebug(ProtoOutputStream proto) {
541             final long token = proto.start(REMOTE);
542             if (mRecord.mTarget != null) {
543                 mRecord.mTarget.dumpDebug(proto, TARGET);
544             }
545             proto.end(token);
546         }
547     }
548 }
549