blob: d076af52c52a591cbb66b7f50572e45d3c4785eb [file] [log] [blame]
Felipe Lemeb63e0dd2018-12-18 11:56:42 -08001/*
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 */
16package android.view.contentcapture;
17
Felipe Leme87a9dc92018-12-18 14:28:07 -080018import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_FINISHED;
19import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -080020import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_APPEARED;
21import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED;
22import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED;
23import static android.view.contentcapture.ContentCaptureManager.DEBUG;
24import static android.view.contentcapture.ContentCaptureManager.VERBOSE;
25
26import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
27
28import android.annotation.NonNull;
29import android.annotation.Nullable;
30import android.content.ComponentName;
31import android.content.Context;
32import android.content.pm.ParceledListSlice;
33import android.os.Bundle;
34import android.os.Handler;
35import android.os.IBinder;
36import android.os.IBinder.DeathRecipient;
37import android.os.RemoteException;
38import android.os.SystemClock;
39import android.util.Log;
40import android.util.TimeUtils;
41import android.view.autofill.AutofillId;
42import android.view.contentcapture.ViewNode.ViewStructureImpl;
43
44import com.android.internal.os.IResultReceiver;
45
46import java.io.PrintWriter;
47import java.util.ArrayList;
48import java.util.Collections;
49import java.util.List;
50import java.util.concurrent.atomic.AtomicBoolean;
51
52/**
53 * Main session associated with a context.
54 *
55 * <p>This session is created when the activity starts and finished when it stops; clients can use
56 * it to create children activities.
57 *
58 * <p><b>NOTE: all methods in this class should return right away, or do the real work in a handler
59 * thread. Hence, the only field that must be thread-safe is {@code mEnabled}, which is called at
60 * the beginning of every method.
61 *
62 * @hide
63 */
Felipe Leme87a9dc92018-12-18 14:28:07 -080064public final class MainContentCaptureSession extends ContentCaptureSession {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -080065
Felipe Lemeaf1a0e72019-01-03 11:07:25 -080066 private static final String TAG = MainContentCaptureSession.class.getSimpleName();
67
Felipe Lemeb63e0dd2018-12-18 11:56:42 -080068 /**
69 * Handler message used to flush the buffer.
70 */
71 private static final int MSG_FLUSH = 1;
72
73 /**
74 * Maximum number of events that are buffered before sent to the app.
75 */
76 // TODO(b/121044064): use settings
77 private static final int MAX_BUFFER_SIZE = 100;
78
79 /**
80 * Frequency the buffer is flushed if stale.
81 */
82 // TODO(b/121044064): use settings
83 private static final int FLUSHING_FREQUENCY_MS = 5_000;
84
85 /**
86 * Name of the {@link IResultReceiver} extra used to pass the binder interface to the service.
87 * @hide
88 */
89 public static final String EXTRA_BINDER = "binder";
90
91 @NonNull
92 private final AtomicBoolean mDisabled;
93
94 @NonNull
95 private final Context mContext;
96
97 @NonNull
98 private final Handler mHandler;
99
100 /**
101 * Interface to the system_server binder object - it's only used to start the session (and
102 * notify when the session is finished).
103 */
104 @Nullable
105 private final IContentCaptureManager mSystemServerInterface;
106
107 /**
108 * Direct interface to the service binder object - it's used to send the events, including the
109 * last ones (when the session is finished)
110 */
111 @Nullable
112 private IContentCaptureDirectManager mDirectServiceInterface;
113 @Nullable
114 private DeathRecipient mDirectServiceVulture;
115
116 private int mState = STATE_UNKNOWN;
117
118 @Nullable
119 private IBinder mApplicationToken;
120
121 @Nullable
122 private ComponentName mComponentName;
123
124 /**
125 * List of events held to be sent as a batch.
126 */
127 @Nullable
128 private ArrayList<ContentCaptureEvent> mEvents;
129
130 // Used just for debugging purposes (on dump)
131 private long mNextFlush;
132
Felipe Leme87a9dc92018-12-18 14:28:07 -0800133 /** @hide */
134 protected MainContentCaptureSession(@NonNull Context context, @NonNull Handler handler,
135 @Nullable IContentCaptureManager systemServerInterface,
136 @NonNull AtomicBoolean disabled) {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800137 mContext = context;
138 mHandler = handler;
139 mSystemServerInterface = systemServerInterface;
140 mDisabled = disabled;
141 }
142
Felipe Leme87a9dc92018-12-18 14:28:07 -0800143 @Override
144 ContentCaptureSession newChild(@NonNull ContentCaptureContext clientContext) {
145 final ContentCaptureSession child = new ChildContentCaptureSession(this, clientContext);
146 notifyChildSessionStarted(mId, child.mId, clientContext);
147 return child;
148 }
149
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800150 /**
151 * Starts this session.
152 *
153 * @hide
154 */
155 void start(@NonNull IBinder applicationToken, @NonNull ComponentName activityComponent) {
156 if (!isContentCaptureEnabled()) return;
157
158 if (VERBOSE) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800159 Log.v(TAG, "start(): token=" + applicationToken + ", comp="
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800160 + ComponentName.flattenToShortString(activityComponent));
161 }
162
Felipe Leme87a9dc92018-12-18 14:28:07 -0800163 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleStartSession, this,
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800164 applicationToken, activityComponent));
165 }
166
167 @Override
168 void flush() {
Felipe Leme87a9dc92018-12-18 14:28:07 -0800169 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleForceFlush, this));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800170 }
171
172 @Override
173 void onDestroy() {
174 mHandler.sendMessage(
Felipe Leme87a9dc92018-12-18 14:28:07 -0800175 obtainMessage(MainContentCaptureSession::handleDestroySession, this));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800176 }
177
178 private void handleStartSession(@NonNull IBinder token, @NonNull ComponentName componentName) {
179 if (mState != STATE_UNKNOWN) {
180 // TODO(b/111276913): revisit this scenario
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800181 Log.w(TAG, "ignoring handleStartSession(" + token + ") while on state "
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800182 + getStateAsString(mState));
183 return;
184 }
185 mState = STATE_WAITING_FOR_SERVER;
186 mApplicationToken = token;
187 mComponentName = componentName;
188
189 if (VERBOSE) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800190 Log.v(TAG, "handleStartSession(): token=" + token + ", act="
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800191 + getActivityDebugName() + ", id=" + mId);
192 }
193 final int flags = 0; // TODO(b/111276913): get proper flags
194
195 try {
Adam He6884fed2019-01-07 13:18:32 -0800196 if (mSystemServerInterface == null) return;
197
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800198 mSystemServerInterface.startSession(mContext.getUserId(), mApplicationToken,
Felipe Leme87a9dc92018-12-18 14:28:07 -0800199 componentName, mId, flags, new IResultReceiver.Stub() {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800200 @Override
201 public void send(int resultCode, Bundle resultData) {
202 IBinder binder = null;
203 if (resultData != null) {
204 binder = resultData.getBinder(EXTRA_BINDER);
205 if (binder == null) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800206 Log.wtf(TAG, "No " + EXTRA_BINDER + " extra result");
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800207 handleResetState();
208 return;
209 }
210 }
211 handleSessionStarted(resultCode, binder);
212 }
213 });
214 } catch (RemoteException e) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800215 Log.w(TAG, "Error starting session for " + componentName.flattenToShortString() + ": "
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800216 + e);
217 }
218 }
219
220 /**
221 * Callback from {@code system_server} after call to
222 * {@link IContentCaptureManager#startSession(int, IBinder, ComponentName, String,
Felipe Lemebef744c2019-01-03 11:07:25 -0800223 * int, IResultReceiver)}.
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800224 *
225 * @param resultCode session state
226 * @param binder handle to {@code IContentCaptureDirectManager}
227 */
228 private void handleSessionStarted(int resultCode, @Nullable IBinder binder) {
229 mState = resultCode;
230 if (binder != null) {
231 mDirectServiceInterface = IContentCaptureDirectManager.Stub.asInterface(binder);
232 mDirectServiceVulture = () -> {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800233 Log.w(TAG, "Destroying session " + mId + " because service died");
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800234 destroy();
235 };
236 try {
237 binder.linkToDeath(mDirectServiceVulture, 0);
238 } catch (RemoteException e) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800239 Log.w(TAG, "Failed to link to death on " + binder + ": " + e);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800240 }
241 }
242 if (resultCode == STATE_DISABLED || resultCode == STATE_DISABLED_DUPLICATED_ID) {
243 mDisabled.set(true);
244 handleResetSession(/* resetState= */ false);
245 } else {
246 mDisabled.set(false);
247 }
248 if (VERBOSE) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800249 Log.v(TAG, "handleSessionStarted() result: code=" + resultCode + ", id=" + mId
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800250 + ", state=" + getStateAsString(mState) + ", disabled=" + mDisabled.get()
251 + ", binder=" + binder);
252 }
253 }
254
255 private void handleSendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) {
256 if (mEvents == null) {
257 if (VERBOSE) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800258 Log.v(TAG, "Creating buffer for " + MAX_BUFFER_SIZE + " events");
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800259 }
260 mEvents = new ArrayList<>(MAX_BUFFER_SIZE);
261 }
Adam Heac132652019-01-02 14:40:15 -0800262
263 if (!mEvents.isEmpty() && event.getType() == TYPE_VIEW_TEXT_CHANGED) {
264 final ContentCaptureEvent lastEvent = mEvents.get(mEvents.size() - 1);
265
266 // TODO(b/121045053): check if flags match
267 if (lastEvent.getType() == TYPE_VIEW_TEXT_CHANGED
268 && lastEvent.getId().equals(event.getId())) {
269 if (VERBOSE) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800270 Log.v(TAG, "Buffering VIEW_TEXT_CHANGED event, updated text = "
Adam Heac132652019-01-02 14:40:15 -0800271 + event.getText());
272 }
273 lastEvent.setText(event.getText());
274 } else {
275 mEvents.add(event);
276 }
277 } else {
278 mEvents.add(event);
279 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800280
281 final int numberEvents = mEvents.size();
282
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800283 final boolean bufferEvent = numberEvents < MAX_BUFFER_SIZE;
284
285 if (bufferEvent && !forceFlush) {
286 handleScheduleFlush(/* checkExisting= */ true);
287 return;
288 }
289
290 if (mState != STATE_ACTIVE) {
291 // Callback from startSession hasn't been called yet - typically happens on system
292 // apps that are started before the system service
293 // TODO(b/111276913): try to ignore session while system is not ready / boot
294 // not complete instead. Similarly, the manager service should return right away
295 // when the user does not have a service set
296 if (VERBOSE) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800297 Log.v(TAG, "Closing session for " + getActivityDebugName()
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800298 + " after " + numberEvents + " delayed events and state "
299 + getStateAsString(mState));
300 }
301 handleResetState();
302 // TODO(b/111276913): blacklist activity / use special flag to indicate that
303 // when it's launched again
304 return;
305 }
306
307 handleForceFlush();
308 }
309
310 private void handleScheduleFlush(boolean checkExisting) {
311 if (checkExisting && mHandler.hasMessages(MSG_FLUSH)) {
312 // "Renew" the flush message by removing the previous one
313 mHandler.removeMessages(MSG_FLUSH);
314 }
315 mNextFlush = SystemClock.elapsedRealtime() + FLUSHING_FREQUENCY_MS;
316 if (VERBOSE) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800317 Log.v(TAG, "Scheduled to flush in " + FLUSHING_FREQUENCY_MS + "ms: " + mNextFlush);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800318 }
319 mHandler.sendMessageDelayed(
Felipe Leme87a9dc92018-12-18 14:28:07 -0800320 obtainMessage(MainContentCaptureSession::handleFlushIfNeeded, this)
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800321 .setWhat(MSG_FLUSH), FLUSHING_FREQUENCY_MS);
322 }
323
324 private void handleFlushIfNeeded() {
325 if (mEvents.isEmpty()) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800326 if (VERBOSE) Log.v(TAG, "Nothing to flush");
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800327 return;
328 }
329 handleForceFlush();
330 }
331
332 private void handleForceFlush() {
333 if (mEvents == null) return;
334
335 if (mDirectServiceInterface == null) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800336 if (DEBUG) Log.d(TAG, "handleForceFlush(): hold your horses, client not ready yet!");
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800337 if (!mHandler.hasMessages(MSG_FLUSH)) {
338 handleScheduleFlush(/* checkExisting= */ false);
339 }
340 return;
341 }
342
343 final int numberEvents = mEvents.size();
344 try {
345 if (DEBUG) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800346 Log.d(TAG, "Flushing " + numberEvents + " event(s) for " + getActivityDebugName());
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800347 }
348 mHandler.removeMessages(MSG_FLUSH);
349
350 final ParceledListSlice<ContentCaptureEvent> events = handleClearEvents();
Felipe Lemefc24bea2018-12-18 13:19:01 -0800351 mDirectServiceInterface.sendEvents(events);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800352 } catch (RemoteException e) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800353 Log.w(TAG, "Error sending " + numberEvents + " for " + getActivityDebugName()
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800354 + ": " + e);
355 }
356 }
357
358 /**
359 * Resets the buffer and return a {@link ParceledListSlice} with the previous events.
360 */
361 @NonNull
362 private ParceledListSlice<ContentCaptureEvent> handleClearEvents() {
363 // NOTE: we must save a reference to the current mEvents and then set it to to null,
364 // otherwise clearing it would clear it in the receiving side if the service is also local.
365 final List<ContentCaptureEvent> events = mEvents == null
366 ? Collections.emptyList()
367 : mEvents;
368 mEvents = null;
369 return new ParceledListSlice<>(events);
370 }
371
372 private void handleDestroySession() {
373 if (DEBUG) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800374 Log.d(TAG, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with "
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800375 + (mEvents == null ? 0 : mEvents.size()) + " event(s) for "
376 + getActivityDebugName());
377 }
378
379 try {
Adam He6884fed2019-01-07 13:18:32 -0800380 if (mSystemServerInterface == null) return;
381
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800382 mSystemServerInterface.finishSession(mContext.getUserId(), mId);
383 } catch (RemoteException e) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800384 Log.e(TAG, "Error destroying system-service session " + mId + " for "
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800385 + getActivityDebugName() + ": " + e);
386 }
387 }
388
389 private void handleResetState() {
390 handleResetSession(/* resetState= */ true);
391 }
392
Felipe Leme87a9dc92018-12-18 14:28:07 -0800393 // TODO(b/121033016): once we support multiple sessions, we might need to move some of these
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800394 // clearings out.
395 private void handleResetSession(boolean resetState) {
396 if (resetState) {
397 mState = STATE_UNKNOWN;
398 }
Felipe Leme87a9dc92018-12-18 14:28:07 -0800399
400 // TODO(b/121033016): must reset children (which currently is owned by superclass)
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800401 mApplicationToken = null;
402 mComponentName = null;
403 mEvents = null;
404 if (mDirectServiceInterface != null) {
405 mDirectServiceInterface.asBinder().unlinkToDeath(mDirectServiceVulture, 0);
406 }
407 mDirectServiceInterface = null;
408 mHandler.removeMessages(MSG_FLUSH);
409 }
410
411 @Override
412 void internalNotifyViewAppeared(@NonNull ViewStructureImpl node) {
Felipe Leme87a9dc92018-12-18 14:28:07 -0800413 notifyViewAppeared(mId, node);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800414 }
415
416 @Override
417 void internalNotifyViewDisappeared(@NonNull AutofillId id) {
Felipe Leme87a9dc92018-12-18 14:28:07 -0800418 notifyViewDisappeared(mId, id);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800419 }
420
421 @Override
422 void internalNotifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text,
423 int flags) {
Felipe Leme87a9dc92018-12-18 14:28:07 -0800424 notifyViewTextChanged(mId, id, text, flags);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800425 }
426
427 @Override
428 boolean isContentCaptureEnabled() {
Felipe Lemebef744c2019-01-03 11:07:25 -0800429 return super.isContentCaptureEnabled() && mSystemServerInterface != null
430 && !mDisabled.get();
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800431 }
432
Felipe Leme87a9dc92018-12-18 14:28:07 -0800433 // TODO(b/121033016): refactor "notifyXXXX" methods below to a common "Buffer" object that is
434 // shared between ActivityContentCaptureSession and ChildContentCaptureSession objects. Such
435 // change should also get get rid of the "internalNotifyXXXX" methods above
436 void notifyChildSessionStarted(@NonNull String parentSessionId,
437 @NonNull String childSessionId, @NonNull ContentCaptureContext clientContext) {
438 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
439 new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED)
440 .setParentSessionId(parentSessionId)
441 .setClientContext(clientContext),
442 /* forceFlush= */ false));
443 }
444
445 void notifyChildSessionFinished(@NonNull String parentSessionId,
446 @NonNull String childSessionId) {
447 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
448 new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED)
449 .setParentSessionId(parentSessionId), /* forceFlush= */ false));
450 }
451
452 void notifyViewAppeared(@NonNull String sessionId, @NonNull ViewStructureImpl node) {
453 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
454 new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED)
455 .setViewNode(node.mNode), /* forceFlush= */ false));
456 }
457
458 void notifyViewDisappeared(@NonNull String sessionId, @NonNull AutofillId id) {
459 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
460 new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED).setAutofillId(id),
461 /* forceFlush= */ false));
462 }
463
464 void notifyViewTextChanged(@NonNull String sessionId, @NonNull AutofillId id,
465 @Nullable CharSequence text, int flags) {
466 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
467 new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED, flags).setAutofillId(id)
468 .setText(text), /* forceFlush= */ false));
469 }
470
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800471 @Override
472 void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
473 pw.print(prefix); pw.print("id: "); pw.println(mId);
474 pw.print(prefix); pw.print("mContext: "); pw.println(mContext);
475 pw.print(prefix); pw.print("user: "); pw.println(mContext.getUserId());
476 if (mSystemServerInterface != null) {
477 pw.print(prefix); pw.print("mSystemServerInterface: ");
478 pw.println(mSystemServerInterface);
479 }
480 if (mDirectServiceInterface != null) {
481 pw.print(prefix); pw.print("mDirectServiceInterface: ");
482 pw.println(mDirectServiceInterface);
483 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800484 pw.print(prefix); pw.print("mDisabled: "); pw.println(mDisabled.get());
485 pw.print(prefix); pw.print("isEnabled(): "); pw.println(isContentCaptureEnabled());
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800486 pw.print(prefix); pw.print("state: "); pw.print(mState); pw.print(" (");
487 pw.print(getStateAsString(mState)); pw.println(")");
488 if (mApplicationToken != null) {
489 pw.print(prefix); pw.print("app token: "); pw.println(mApplicationToken);
490 }
491 if (mComponentName != null) {
492 pw.print(prefix); pw.print("component name: ");
493 pw.println(mComponentName.flattenToShortString());
494 }
495 if (mEvents != null && !mEvents.isEmpty()) {
496 final int numberEvents = mEvents.size();
497 pw.print(prefix); pw.print("buffered events: "); pw.print(numberEvents);
498 pw.print('/'); pw.println(MAX_BUFFER_SIZE);
499 if (VERBOSE && numberEvents > 0) {
500 final String prefix3 = prefix + " ";
501 for (int i = 0; i < numberEvents; i++) {
502 final ContentCaptureEvent event = mEvents.get(i);
503 pw.print(prefix3); pw.print(i); pw.print(": "); event.dump(pw);
504 pw.println();
505 }
506 }
507 pw.print(prefix); pw.print("flush frequency: "); pw.println(FLUSHING_FREQUENCY_MS);
508 pw.print(prefix); pw.print("next flush: ");
509 TimeUtils.formatDuration(mNextFlush - SystemClock.elapsedRealtime(), pw); pw.println();
510 }
Felipe Leme87a9dc92018-12-18 14:28:07 -0800511 super.dump(prefix, pw);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800512 }
513
514 /**
515 * Gets a string that can be used to identify the activity on logging statements.
516 */
517 private String getActivityDebugName() {
518 return mComponentName == null ? mContext.getPackageName()
519 : mComponentName.flattenToShortString();
520 }
521}