blob: 71b47f8f83a5ad794b5210e652c78556a991ff10 [file] [log] [blame]
Felipe Leme1dfa9a02018-10-17 17:24:37 -07001/*
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.intelligence;
17
Felipe Leme88eae3b2018-11-07 15:11:56 -080018import static android.view.intelligence.ContentCaptureEvent.TYPE_VIEW_APPEARED;
19import static android.view.intelligence.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED;
20import static android.view.intelligence.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED;
21
22import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
23
Felipe Leme1dfa9a02018-10-17 17:24:37 -070024import android.annotation.NonNull;
25import android.annotation.Nullable;
26import android.annotation.SystemApi;
Felipe Lemee348dc32018-11-05 12:35:29 -080027import android.annotation.SystemService;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070028import android.content.ComponentName;
29import android.content.Context;
Felipe Lemee348dc32018-11-05 12:35:29 -080030import android.os.Bundle;
Felipe Leme88eae3b2018-11-07 15:11:56 -080031import android.os.Handler;
32import android.os.HandlerThread;
Felipe Lemee348dc32018-11-05 12:35:29 -080033import android.os.IBinder;
34import android.os.RemoteException;
Felipe Lemea7bdb142018-11-05 16:29:29 -080035import android.service.intelligence.InteractionSessionId;
Felipe Lemee348dc32018-11-05 12:35:29 -080036import android.util.Log;
Felipe Leme88eae3b2018-11-07 15:11:56 -080037import android.view.View;
38import android.view.ViewStructure;
39import android.view.autofill.AutofillId;
Felipe Leme7a534082018-11-05 15:03:04 -080040import android.view.intelligence.ContentCaptureEvent.EventType;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070041
Felipe Lemee348dc32018-11-05 12:35:29 -080042import com.android.internal.annotations.GuardedBy;
43import com.android.internal.os.IResultReceiver;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070044import com.android.internal.util.Preconditions;
45
Felipe Lemee348dc32018-11-05 12:35:29 -080046import java.io.PrintWriter;
Felipe Leme88eae3b2018-11-07 15:11:56 -080047import java.util.ArrayList;
Felipe Leme6b3a55c2018-11-13 17:14:03 -080048import java.util.List;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070049import java.util.Set;
50
51/**
Felipe Lemee348dc32018-11-05 12:35:29 -080052 * TODO(b/111276913): add javadocs / implement
Felipe Leme1dfa9a02018-10-17 17:24:37 -070053 */
Felipe Lemee348dc32018-11-05 12:35:29 -080054@SystemService(Context.INTELLIGENCE_MANAGER_SERVICE)
Felipe Leme1dfa9a02018-10-17 17:24:37 -070055public final class IntelligenceManager {
56
Felipe Lemee348dc32018-11-05 12:35:29 -080057 private static final String TAG = "IntelligenceManager";
58
Felipe Leme88eae3b2018-11-07 15:11:56 -080059 // TODO(b/111276913): define a way to dynamically set them(for example, using settings?)
Felipe Lemee348dc32018-11-05 12:35:29 -080060 private static final boolean VERBOSE = false;
Felipe Leme88eae3b2018-11-07 15:11:56 -080061 private static final boolean DEBUG = true; // STOPSHIP if not set to false
Felipe Lemee348dc32018-11-05 12:35:29 -080062
Felipe Leme1dfa9a02018-10-17 17:24:37 -070063 /**
64 * Used to indicate that a text change was caused by user input (for example, through IME).
65 */
66 //TODO(b/111276913): link to notifyTextChanged() method once available
67 public static final int FLAG_USER_INPUT = 0x1;
68
Felipe Lemee348dc32018-11-05 12:35:29 -080069 /**
70 * Initial state, when there is no session.
71 *
72 * @hide
73 */
74 public static final int STATE_UNKNOWN = 0;
75
76 /**
Felipe Lemea7bdb142018-11-05 16:29:29 -080077 * Service's startSession() was called, but server didn't confirm it was created yet.
Felipe Lemee348dc32018-11-05 12:35:29 -080078 *
79 * @hide
80 */
Felipe Lemea7bdb142018-11-05 16:29:29 -080081 public static final int STATE_WAITING_FOR_SERVER = 1;
Felipe Lemee348dc32018-11-05 12:35:29 -080082
83 /**
84 * Session is active.
85 *
86 * @hide
87 */
88 public static final int STATE_ACTIVE = 2;
89
Felipe Leme88eae3b2018-11-07 15:11:56 -080090 private static final String BG_THREAD_NAME = "intel_svc_streamer_thread";
91
92 /**
93 * Maximum number of events that are delayed for an app.
94 *
95 * <p>If the session is not started after the limit is reached, it's discarded.
96 */
97 private static final int MAX_DELAYED_SIZE = 20;
98
Felipe Lemee348dc32018-11-05 12:35:29 -080099 private final Context mContext;
100
101 @Nullable
102 private final IIntelligenceManager mService;
103
104 private final Object mLock = new Object();
105
Felipe Lemea7bdb142018-11-05 16:29:29 -0800106 @Nullable
Felipe Lemee348dc32018-11-05 12:35:29 -0800107 @GuardedBy("mLock")
Felipe Lemea7bdb142018-11-05 16:29:29 -0800108 private InteractionSessionId mId;
Felipe Lemee348dc32018-11-05 12:35:29 -0800109
110 @GuardedBy("mLock")
111 private int mState = STATE_UNKNOWN;
112
113 @GuardedBy("mLock")
114 private IBinder mApplicationToken;
115
116 // TODO(b/111276913): replace by an interface name implemented by Activity, similar to
117 // AutofillClient
118 @GuardedBy("mLock")
119 private ComponentName mComponentName;
120
Felipe Leme88eae3b2018-11-07 15:11:56 -0800121 // TODO(b/111276913): create using maximum batch size as capacity
122 /**
123 * List of events held to be sent as a batch.
124 */
125 @GuardedBy("mLock")
126 private final ArrayList<ContentCaptureEvent> mEvents = new ArrayList<>();
127
128 private final Handler mHandler;
129
Felipe Lemee348dc32018-11-05 12:35:29 -0800130 /** @hide */
131 public IntelligenceManager(@NonNull Context context, @Nullable IIntelligenceManager service) {
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700132 mContext = Preconditions.checkNotNull(context, "context cannot be null");
Felipe Lemee348dc32018-11-05 12:35:29 -0800133 mService = service;
Felipe Leme88eae3b2018-11-07 15:11:56 -0800134
135 // TODO(b/111276913): use an existing bg thread instead...
136 final HandlerThread bgThread = new HandlerThread(BG_THREAD_NAME);
137 bgThread.start();
138 mHandler = Handler.createAsync(bgThread.getLooper());
Felipe Lemee348dc32018-11-05 12:35:29 -0800139 }
140
141 /** @hide */
142 public void onActivityCreated(@NonNull IBinder token, @NonNull ComponentName componentName) {
143 if (!isContentCaptureEnabled()) return;
144
145 synchronized (mLock) {
146 if (mState != STATE_UNKNOWN) {
Felipe Leme88eae3b2018-11-07 15:11:56 -0800147 // TODO(b/111276913): revisit this scenario
Felipe Lemee348dc32018-11-05 12:35:29 -0800148 Log.w(TAG, "ignoring onActivityStarted(" + token + ") while on state "
Felipe Leme88eae3b2018-11-07 15:11:56 -0800149 + getStateAsString(mState));
Felipe Lemee348dc32018-11-05 12:35:29 -0800150 return;
151 }
Felipe Lemea7bdb142018-11-05 16:29:29 -0800152 mState = STATE_WAITING_FOR_SERVER;
153 mId = new InteractionSessionId();
Felipe Lemee348dc32018-11-05 12:35:29 -0800154 mApplicationToken = token;
155 mComponentName = componentName;
156
157 if (VERBOSE) {
Felipe Leme88eae3b2018-11-07 15:11:56 -0800158 Log.v(TAG, "onActivityCreated(): token=" + token + ", act="
159 + getActivityDebugNameLocked() + ", id=" + mId);
Felipe Lemee348dc32018-11-05 12:35:29 -0800160 }
161 final int flags = 0; // TODO(b/111276913): get proper flags
162
163 try {
164 mService.startSession(mContext.getUserId(), mApplicationToken, componentName,
Felipe Lemea7bdb142018-11-05 16:29:29 -0800165 mId, flags, new IResultReceiver.Stub() {
Felipe Lemee348dc32018-11-05 12:35:29 -0800166 @Override
167 public void send(int resultCode, Bundle resultData)
168 throws RemoteException {
169 synchronized (mLock) {
170 if (resultCode > 0) {
Felipe Lemee348dc32018-11-05 12:35:29 -0800171 mState = STATE_ACTIVE;
172 } else {
173 // TODO(b/111276913): handle other cases like disabled by
174 // service
Felipe Leme88eae3b2018-11-07 15:11:56 -0800175 resetStateLocked();
Felipe Lemee348dc32018-11-05 12:35:29 -0800176 }
177 if (VERBOSE) {
178 Log.v(TAG, "onActivityStarted() result: code=" + resultCode
Felipe Lemea7bdb142018-11-05 16:29:29 -0800179 + ", id=" + mId
Felipe Leme88eae3b2018-11-07 15:11:56 -0800180 + ", state=" + getStateAsString(mState));
Felipe Lemee348dc32018-11-05 12:35:29 -0800181 }
182 }
183 }
184 });
185 } catch (RemoteException e) {
186 throw e.rethrowFromSystemServer();
187 }
188 }
189 }
190
Felipe Leme88eae3b2018-11-07 15:11:56 -0800191 //TODO(b/111276913): should buffer event (and call service on handler thread), instead of
192 // calling right away
193 private void sendEvent(@NonNull ContentCaptureEvent event) {
194 mHandler.sendMessage(obtainMessage(IntelligenceManager::handleSendEvent, this, event));
195 }
196
197 private void handleSendEvent(@NonNull ContentCaptureEvent event) {
198
199 synchronized (mLock) {
200 mEvents.add(event);
201 final int numberEvents = mEvents.size();
202 if (mState != STATE_ACTIVE) {
203 if (numberEvents >= MAX_DELAYED_SIZE) {
204 // Typically happens on system apps that are started before the system service
205 // is ready (like com.android.settings/.FallbackHome)
206 //TODO(b/111276913): try to ignore session while system is not ready / boot
207 // not complete instead.
208 Log.w(TAG, "Closing session for " + getActivityDebugNameLocked()
209 + " after " + numberEvents + " delayed events");
210 // TODO(b/111276913): blacklist activity / use special flag to indicate that
211 // when it's launched again
212 resetStateLocked();
213 return;
214 }
215
216 if (VERBOSE) {
217 Log.v(TAG, "Delaying " + numberEvents + " events for "
218 + getActivityDebugNameLocked() + " while on state "
219 + getStateAsString(mState));
220 }
221 return;
222 }
223
224 if (mId == null) {
225 // Sanity check - should not happen
226 Log.wtf(TAG, "null session id for " + mComponentName);
227 return;
228 }
229
230 //TODO(b/111276913): right now we're sending sending right away (unless not ready), but
231 // we should hold the events and flush later.
232 try {
233 if (DEBUG) {
234 Log.d(TAG, "Sending " + numberEvents + " event(s) for "
235 + getActivityDebugNameLocked());
236 }
237 mService.sendEvents(mContext.getUserId(), mId, mEvents);
238 mEvents.clear();
239 } catch (RemoteException e) {
240 throw e.rethrowFromSystemServer();
241 }
242 }
243 }
244
Felipe Leme7a534082018-11-05 15:03:04 -0800245 /**
246 * Used for intermediate events (i.e, other than created and destroyed).
247 *
248 * @hide
249 */
250 public void onActivityLifecycleEvent(@EventType int type) {
251 if (!isContentCaptureEnabled()) return;
Felipe Leme88eae3b2018-11-07 15:11:56 -0800252 if (VERBOSE) {
253 Log.v(TAG, "onActivityLifecycleEvent() for " + getActivityDebugNameLocked()
254 + ": " + ContentCaptureEvent.getTypeAsString(type));
Felipe Leme7a534082018-11-05 15:03:04 -0800255 }
Felipe Leme88eae3b2018-11-07 15:11:56 -0800256 sendEvent(new ContentCaptureEvent(type));
Felipe Leme7a534082018-11-05 15:03:04 -0800257 }
258
Felipe Lemee348dc32018-11-05 12:35:29 -0800259 /** @hide */
260 public void onActivityDestroyed() {
261 if (!isContentCaptureEnabled()) return;
262
263 synchronized (mLock) {
264 //TODO(b/111276913): check state (for example, how to handle if it's waiting for remote
265 // id) and send it to the cache of batched commands
266
267 if (VERBOSE) {
Felipe Leme88eae3b2018-11-07 15:11:56 -0800268 Log.v(TAG, "onActivityDestroyed(): state=" + getStateAsString(mState)
Felipe Lemea7bdb142018-11-05 16:29:29 -0800269 + ", mId=" + mId);
Felipe Lemee348dc32018-11-05 12:35:29 -0800270 }
271
272 try {
Felipe Lemea7bdb142018-11-05 16:29:29 -0800273 mService.finishSession(mContext.getUserId(), mId);
Felipe Leme88eae3b2018-11-07 15:11:56 -0800274 resetStateLocked();
Felipe Lemee348dc32018-11-05 12:35:29 -0800275 } catch (RemoteException e) {
276 throw e.rethrowFromSystemServer();
277 }
278 }
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700279 }
280
Felipe Leme88eae3b2018-11-07 15:11:56 -0800281 @GuardedBy("mLock")
282 private void resetStateLocked() {
283 mState = STATE_UNKNOWN;
284 mId = null;
285 mApplicationToken = null;
286 mComponentName = null;
287 mEvents.clear();
288 }
289
290 /**
291 * Notifies the Intelligence Service that a node has been added to the view structure.
292 *
293 * <p>Typically called "manually" by views that handle their own virtual view hierarchy, or
294 * automatically by the Android System for views that return {@code true} on
295 * {@link View#onProvideContentCaptureStructure(ViewStructure, int)}.
296 *
297 * @param node node that has been added.
298 */
299 public void notifyViewAppeared(@NonNull ViewStructure node) {
300 Preconditions.checkNotNull(node);
301 if (!isContentCaptureEnabled()) return;
302
303 if (!(node instanceof ViewNode.ViewStructureImpl)) {
304 throw new IllegalArgumentException("Invalid node class: " + node.getClass());
305 }
306 sendEvent(new ContentCaptureEvent(TYPE_VIEW_APPEARED)
307 .setViewNode(((ViewNode.ViewStructureImpl) node).mNode));
308 }
309
310 /**
311 * Notifies the Intelligence Service that a node has been removed from the view structure.
312 *
313 * <p>Typically called "manually" by views that handle their own virtual view hierarchy, or
314 * automatically by the Android System for standard views.
315 *
316 * @param id id of the node that has been removed.
317 */
318 public void notifyViewDisappeared(@NonNull AutofillId id) {
319 Preconditions.checkNotNull(id);
320 if (!isContentCaptureEnabled()) return;
321
322 sendEvent(new ContentCaptureEvent(TYPE_VIEW_DISAPPEARED).setAutofillId(id));
323 }
324
325 /**
326 * Notifies the Intelligence Service that the value of a text node has been changed.
327 *
328 * @param id of the node.
329 * @param text new text.
330 * @param flags either {@code 0} or {@link #FLAG_USER_INPUT} when the value was explicitly
331 * changed by the user (for example, through the keyboard).
332 */
333 public void notifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text,
334 int flags) {
335 Preconditions.checkNotNull(id);
336 if (!isContentCaptureEnabled()) return;
337
338 sendEvent(new ContentCaptureEvent(TYPE_VIEW_TEXT_CHANGED, flags).setAutofillId(id)
339 .setText(text));
340 }
341
342 /**
343 * Creates a {@link ViewStructure} for a "standard" view.
344 *
345 * @hide
346 */
347 @NonNull
348 public ViewStructure newViewStructure(@NonNull View view) {
349 return new ViewNode.ViewStructureImpl(view);
350 }
351
352 /**
353 * Creates a {@link ViewStructure} for a "virtual" view, so it can be passed to
354 * {@link #notifyViewAppeared(ViewStructure)} by the view managing the virtual view hierarchy.
355 *
356 * @param parentId id of the virtual view parent (it can be obtained by calling
357 * {@link ViewStructure#getAutofillId()} on the parent).
358 * @param virtualId id of the virtual child, relative to the parent.
359 *
360 * @return a new {@link ViewStructure} that can be used for Content Capture purposes.
361 */
362 @NonNull
363 public ViewStructure newVirtualViewStructure(@NonNull AutofillId parentId, int virtualId) {
364 return new ViewNode.ViewStructureImpl(parentId, virtualId);
365 }
366
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700367 /**
Felipe Lemef783fa02018-11-05 14:57:32 -0800368 * Returns the component name of the {@code android.service.intelligence.IntelligenceService}
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700369 * that is enabled for the current user.
370 */
371 @Nullable
372 public ComponentName getIntelligenceServiceComponentName() {
373 //TODO(b/111276913): implement
374 return null;
375 }
376
377 /**
Felipe Lemee348dc32018-11-05 12:35:29 -0800378 * Checks whether content capture is enabled for this activity.
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700379 */
380 public boolean isContentCaptureEnabled() {
Felipe Lemee348dc32018-11-05 12:35:29 -0800381 //TODO(b/111276913): properly implement by checking if it was explicitly disabled by
382 // service, or if service is not set
383 // (and probably renamign to isEnabledLocked()
384 return mService != null;
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700385 }
386
387 /**
Felipe Leme6b3a55c2018-11-13 17:14:03 -0800388 * Called by apps to explicitly enabled or disable content capture.
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700389 *
390 * <p><b>Note: </b> this call is not persisted accross reboots, so apps should typically call
391 * it on {@link android.app.Activity#onCreate(android.os.Bundle, android.os.PersistableBundle)}.
392 */
Felipe Leme6b3a55c2018-11-13 17:14:03 -0800393 public void setContentCaptureEnabled(boolean enabled) {
Felipe Lemee348dc32018-11-05 12:35:29 -0800394 //TODO(b/111276913): implement
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700395 }
396
397 /**
398 * Called by the the service {@link android.service.intelligence.IntelligenceService}
399 * to define whether content capture should be enabled for activities with such
400 * {@link android.content.ComponentName}.
401 *
402 * <p>Useful to blacklist a particular activity.
403 *
404 * @throws UnsupportedOperationException if not called by the UID that owns the
405 * {@link android.service.intelligence.IntelligenceService} associated with the
406 * current user.
407 *
408 * @hide
409 */
410 @SystemApi
411 public void setActivityContentCaptureEnabled(@NonNull ComponentName activity,
412 boolean enabled) {
413 //TODO(b/111276913): implement
414 }
415
416 /**
417 * Called by the the service {@link android.service.intelligence.IntelligenceService}
Felipe Leme6b3a55c2018-11-13 17:14:03 -0800418 * to explicitly limit content capture to the given packages and activities.
419 *
420 * <p>When the whitelist is set, it overrides the values passed to
421 * {@link #setActivityContentCaptureEnabled(ComponentName, boolean)}
422 * and {@link #setPackageContentCaptureEnabled(String, boolean)}.
423 *
424 * <p>To reset the whitelist, call it passing {@code null} to both arguments.
425 *
426 * <p>Useful when the service wants to restrict content capture to a category of apps, like
427 * chat apps. For example, if the service wants to support view captures on all activities of
428 * app {@code ChatApp1} and just activities {@code act1} and {@code act2} of {@code ChatApp2},
429 * it would call: {@code setContentCaptureWhitelist(Arrays.asList("ChatApp1"),
430 * Arrays.asList(new ComponentName("ChatApp2", "act1"),
431 * new ComponentName("ChatApp2", "act2")));}
432 *
433 * @throws UnsupportedOperationException if not called by the UID that owns the
434 * {@link android.service.intelligence.IntelligenceService} associated with the
435 * current user.
436 *
437 * @hide
438 */
439 @SystemApi
440 public void setContentCaptureWhitelist(@Nullable List<String> packages,
441 @Nullable List<ComponentName> activities) {
442 //TODO(b/111276913): implement
443 }
444
445 /**
446 * Called by the the service {@link android.service.intelligence.IntelligenceService}
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700447 * to define whether content capture should be enabled for activities of the app with such
448 * {@code packageName}.
449 *
450 * <p>Useful to blacklist any activity from a particular app.
451 *
452 * @throws UnsupportedOperationException if not called by the UID that owns the
453 * {@link android.service.intelligence.IntelligenceService} associated with the
454 * current user.
455 *
456 * @hide
457 */
458 @SystemApi
459 public void setPackageContentCaptureEnabled(@NonNull String packageName, boolean enabled) {
460 //TODO(b/111276913): implement
461 }
462
463 /**
464 * Gets the activities where content capture was disabled by
465 * {@link #setActivityContentCaptureEnabled(ComponentName, boolean)}.
466 *
467 * @throws UnsupportedOperationException if not called by the UID that owns the
468 * {@link android.service.intelligence.IntelligenceService} associated with the
469 * current user.
470 *
471 * @hide
472 */
473 @SystemApi
474 @NonNull
475 public Set<ComponentName> getContentCaptureDisabledActivities() {
476 //TODO(b/111276913): implement
477 return null;
478 }
479
480 /**
481 * Gets the apps where content capture was disabled by
482 * {@link #setPackageContentCaptureEnabled(String, boolean)}.
483 *
484 * @throws UnsupportedOperationException if not called by the UID that owns the
485 * {@link android.service.intelligence.IntelligenceService} associated with the
486 * current user.
487 *
488 * @hide
489 */
490 @SystemApi
491 @NonNull
492 public Set<String> getContentCaptureDisabledPackages() {
493 //TODO(b/111276913): implement
494 return null;
495 }
Felipe Lemee348dc32018-11-05 12:35:29 -0800496
497 /** @hide */
498 public void dump(String prefix, PrintWriter pw) {
499 pw.print(prefix); pw.println("IntelligenceManager");
500 final String prefix2 = prefix + " ";
501 synchronized (mLock) {
502 pw.print(prefix2); pw.print("mContext: "); pw.println(mContext);
503 pw.print(prefix2); pw.print("mService: "); pw.println(mService);
504 pw.print(prefix2); pw.print("user: "); pw.println(mContext.getUserId());
505 pw.print(prefix2); pw.print("enabled: "); pw.println(isContentCaptureEnabled());
Felipe Lemea7bdb142018-11-05 16:29:29 -0800506 pw.print(prefix2); pw.print("id: "); pw.println(mId);
507 pw.print(prefix2); pw.print("state: "); pw.print(mState); pw.print(" (");
Felipe Leme88eae3b2018-11-07 15:11:56 -0800508 pw.print(getStateAsString(mState)); pw.println(")");
509 pw.print(prefix2); pw.print("app token: "); pw.println(mApplicationToken);
510 pw.print(prefix2); pw.print("component name: ");
511 pw.println(mComponentName == null ? "null" : mComponentName.flattenToShortString());
512 final int numberEvents = mEvents.size();
513 pw.print(prefix2); pw.print("batched events: "); pw.println(numberEvents);
514 if (numberEvents > 0) {
515 for (int i = 0; i < numberEvents; i++) {
516 final ContentCaptureEvent event = mEvents.get(i);
517 pw.println(i); pw.print(": "); event.dump(pw); pw.println();
518 }
519
520 }
Felipe Lemee348dc32018-11-05 12:35:29 -0800521 }
522 }
523
Felipe Leme88eae3b2018-11-07 15:11:56 -0800524 /**
525 * Gets a string that can be used to identify the activity on logging statements.
526 */
Felipe Lemee348dc32018-11-05 12:35:29 -0800527 @GuardedBy("mLock")
Felipe Leme88eae3b2018-11-07 15:11:56 -0800528 private String getActivityDebugNameLocked() {
529 return mComponentName == null ? mContext.getPackageName()
530 : mComponentName.flattenToShortString();
Felipe Lemee348dc32018-11-05 12:35:29 -0800531 }
532
533 @NonNull
534 private static String getStateAsString(int state) {
535 switch (state) {
536 case STATE_UNKNOWN:
537 return "UNKNOWN";
Felipe Lemea7bdb142018-11-05 16:29:29 -0800538 case STATE_WAITING_FOR_SERVER:
539 return "WAITING_FOR_SERVER";
Felipe Lemee348dc32018-11-05 12:35:29 -0800540 case STATE_ACTIVE:
541 return "ACTIVE";
542 default:
543 return "INVALID:" + state;
544 }
545 }
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700546}