blob: 48831daf75e8996e426a4fa21762e537314eeb48 [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 */
Felipe Leme749b8892018-12-03 16:30:30 -080016package android.view.contentcapture;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070017
Felipe Leme749b8892018-12-03 16:30:30 -080018import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_APPEARED;
19import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED;
20import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED;
Felipe Leme88eae3b2018-11-07 15:11:56 -080021
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;
Felipe Lemee348dc32018-11-05 12:35:29 -080026import android.annotation.SystemService;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070027import android.content.ComponentName;
28import android.content.Context;
Felipe Lemee348dc32018-11-05 12:35:29 -080029import android.os.Bundle;
Felipe Leme88eae3b2018-11-07 15:11:56 -080030import android.os.Handler;
31import android.os.HandlerThread;
Felipe Lemee348dc32018-11-05 12:35:29 -080032import android.os.IBinder;
33import android.os.RemoteException;
34import android.util.Log;
Felipe Leme88eae3b2018-11-07 15:11:56 -080035import android.view.View;
36import android.view.ViewStructure;
37import android.view.autofill.AutofillId;
Felipe Leme749b8892018-12-03 16:30:30 -080038import android.view.contentcapture.ContentCaptureEvent.EventType;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070039
Felipe Lemee348dc32018-11-05 12:35:29 -080040import com.android.internal.os.IResultReceiver;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070041import com.android.internal.util.Preconditions;
42
Felipe Lemee348dc32018-11-05 12:35:29 -080043import java.io.PrintWriter;
Felipe Leme88eae3b2018-11-07 15:11:56 -080044import java.util.ArrayList;
Felipe Leme749b8892018-12-03 16:30:30 -080045import java.util.UUID;
Felipe Lemeb18e3172018-11-27 10:33:41 -080046import java.util.concurrent.atomic.AtomicBoolean;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070047
Felipe Lemeb18e3172018-11-27 10:33:41 -080048/*
49 * NOTE: all methods in this class should return right away, or do the real work in a handler
50 * thread.
51 *
52 * Hence, the only field that must be thread-safe is mEnabled, which is called at the beginning
53 * of every method.
54 */
Felipe Lemeecb08be2018-11-27 15:48:47 -080055/**
56 * TODO(b/111276913): add javadocs / implement
57 */
58@SystemService(Context.CONTENT_CAPTURE_MANAGER_SERVICE)
59public final class ContentCaptureManager {
Felipe Leme1dfa9a02018-10-17 17:24:37 -070060
Felipe Leme749b8892018-12-03 16:30:30 -080061 private static final String TAG = ContentCaptureManager.class.getSimpleName();
Felipe Lemee348dc32018-11-05 12:35:29 -080062
Felipe Leme88eae3b2018-11-07 15:11:56 -080063 // TODO(b/111276913): define a way to dynamically set them(for example, using settings?)
Felipe Lemee348dc32018-11-05 12:35:29 -080064 private static final boolean VERBOSE = false;
Felipe Leme88eae3b2018-11-07 15:11:56 -080065 private static final boolean DEBUG = true; // STOPSHIP if not set to false
Felipe Lemee348dc32018-11-05 12:35:29 -080066
Felipe Leme1dfa9a02018-10-17 17:24:37 -070067 /**
68 * Used to indicate that a text change was caused by user input (for example, through IME).
69 */
70 //TODO(b/111276913): link to notifyTextChanged() method once available
71 public static final int FLAG_USER_INPUT = 0x1;
72
Felipe Lemee348dc32018-11-05 12:35:29 -080073 /**
74 * Initial state, when there is no session.
75 *
76 * @hide
77 */
78 public static final int STATE_UNKNOWN = 0;
79
80 /**
Felipe Lemea7bdb142018-11-05 16:29:29 -080081 * Service's startSession() was called, but server didn't confirm it was created yet.
Felipe Lemee348dc32018-11-05 12:35:29 -080082 *
83 * @hide
84 */
Felipe Lemea7bdb142018-11-05 16:29:29 -080085 public static final int STATE_WAITING_FOR_SERVER = 1;
Felipe Lemee348dc32018-11-05 12:35:29 -080086
87 /**
88 * Session is active.
89 *
90 * @hide
91 */
92 public static final int STATE_ACTIVE = 2;
93
Felipe Leme8e2e3412018-11-14 10:39:29 -080094 /**
95 * Session is disabled.
96 *
97 * @hide
98 */
99 public static final int STATE_DISABLED = 3;
100
Felipe Leme88eae3b2018-11-07 15:11:56 -0800101 private static final String BG_THREAD_NAME = "intel_svc_streamer_thread";
102
103 /**
Felipe Lemeb18e3172018-11-27 10:33:41 -0800104 * Maximum number of events that are buffered before sent to the app.
Felipe Leme88eae3b2018-11-07 15:11:56 -0800105 */
Felipe Lemeb18e3172018-11-27 10:33:41 -0800106 // TODO(b/111276913): use settings
107 private static final int MAX_BUFFER_SIZE = 100;
Felipe Leme88eae3b2018-11-07 15:11:56 -0800108
Felipe Lemeb18e3172018-11-27 10:33:41 -0800109 @NonNull
110 private final AtomicBoolean mDisabled = new AtomicBoolean();
111
112 @NonNull
Felipe Lemee348dc32018-11-05 12:35:29 -0800113 private final Context mContext;
114
115 @Nullable
Felipe Leme749b8892018-12-03 16:30:30 -0800116 private final IContentCaptureManager mService;
Felipe Lemee348dc32018-11-05 12:35:29 -0800117
Felipe Lemea7bdb142018-11-05 16:29:29 -0800118 @Nullable
Felipe Leme749b8892018-12-03 16:30:30 -0800119 private String mId;
Felipe Lemee348dc32018-11-05 12:35:29 -0800120
Felipe Lemee348dc32018-11-05 12:35:29 -0800121 private int mState = STATE_UNKNOWN;
122
Felipe Lemeb18e3172018-11-27 10:33:41 -0800123 @Nullable
Felipe Lemee348dc32018-11-05 12:35:29 -0800124 private IBinder mApplicationToken;
125
Felipe Lemeb18e3172018-11-27 10:33:41 -0800126 @Nullable
Felipe Lemee348dc32018-11-05 12:35:29 -0800127 private ComponentName mComponentName;
128
Felipe Leme88eae3b2018-11-07 15:11:56 -0800129 /**
130 * List of events held to be sent as a batch.
131 */
Felipe Lemeb18e3172018-11-27 10:33:41 -0800132 @Nullable
133 private ArrayList<ContentCaptureEvent> mEvents;
Felipe Leme88eae3b2018-11-07 15:11:56 -0800134
Felipe Lemeb18e3172018-11-27 10:33:41 -0800135 // TODO(b/111276913): use UI Thread directly (as calls are one-way) or a shared thread / handler
136 // held at the Application level
Felipe Leme88eae3b2018-11-07 15:11:56 -0800137 private final Handler mHandler;
138
Felipe Lemee348dc32018-11-05 12:35:29 -0800139 /** @hide */
Felipe Leme749b8892018-12-03 16:30:30 -0800140 public ContentCaptureManager(@NonNull Context context,
141 @Nullable IContentCaptureManager service) {
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700142 mContext = Preconditions.checkNotNull(context, "context cannot be null");
Felipe Lemeb18e3172018-11-27 10:33:41 -0800143 if (VERBOSE) {
144 Log.v(TAG, "Constructor for " + context.getPackageName());
145 }
Felipe Lemee348dc32018-11-05 12:35:29 -0800146 mService = service;
Felipe Leme88eae3b2018-11-07 15:11:56 -0800147 // TODO(b/111276913): use an existing bg thread instead...
148 final HandlerThread bgThread = new HandlerThread(BG_THREAD_NAME);
149 bgThread.start();
150 mHandler = Handler.createAsync(bgThread.getLooper());
Felipe Lemee348dc32018-11-05 12:35:29 -0800151 }
152
153 /** @hide */
154 public void onActivityCreated(@NonNull IBinder token, @NonNull ComponentName componentName) {
155 if (!isContentCaptureEnabled()) return;
156
Felipe Lemeecb08be2018-11-27 15:48:47 -0800157 mHandler.sendMessage(obtainMessage(ContentCaptureManager::handleStartSession, this,
Felipe Lemeb18e3172018-11-27 10:33:41 -0800158 token, componentName));
159 }
Felipe Lemee348dc32018-11-05 12:35:29 -0800160
Felipe Lemeb18e3172018-11-27 10:33:41 -0800161 private void handleStartSession(@NonNull IBinder token, @NonNull ComponentName componentName) {
162 if (mState != STATE_UNKNOWN) {
163 // TODO(b/111276913): revisit this scenario
164 Log.w(TAG, "ignoring handleStartSession(" + token + ") while on state "
165 + getStateAsString(mState));
166 return;
167 }
168 mState = STATE_WAITING_FOR_SERVER;
Felipe Leme749b8892018-12-03 16:30:30 -0800169 mId = UUID.randomUUID().toString();
Felipe Lemeb18e3172018-11-27 10:33:41 -0800170 mApplicationToken = token;
171 mComponentName = componentName;
Felipe Lemee348dc32018-11-05 12:35:29 -0800172
Felipe Lemeb18e3172018-11-27 10:33:41 -0800173 if (VERBOSE) {
174 Log.v(TAG, "handleStartSession(): token=" + token + ", act="
175 + getActivityDebugName() + ", id=" + mId);
176 }
177 final int flags = 0; // TODO(b/111276913): get proper flags
178
179 try {
180 mService.startSession(mContext.getUserId(), mApplicationToken, componentName,
181 mId, flags, new IResultReceiver.Stub() {
182 @Override
183 public void send(int resultCode, Bundle resultData) {
184 handleSessionStarted(resultCode);
185 }
186 });
187 } catch (RemoteException e) {
188 Log.w(TAG, "Error starting session for " + componentName.flattenToShortString() + ": "
189 + e);
Felipe Lemee348dc32018-11-05 12:35:29 -0800190 }
191 }
192
Felipe Leme39233ff2018-11-28 16:54:23 -0800193 private void handleSessionStarted(int resultCode) {
Felipe Lemeb18e3172018-11-27 10:33:41 -0800194 mState = resultCode;
195 mDisabled.set(mState == STATE_DISABLED);
196 if (VERBOSE) {
197 Log.v(TAG, "onActivityStarted() result: code=" + resultCode + ", id=" + mId
198 + ", state=" + getStateAsString(mState) + ", disabled=" + mDisabled.get());
199 }
Felipe Leme88eae3b2018-11-07 15:11:56 -0800200 }
201
Felipe Lemeb18e3172018-11-27 10:33:41 -0800202 private void handleSendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) {
203 if (mEvents == null) {
204 if (VERBOSE) {
205 Log.v(TAG, "Creating buffer for " + MAX_BUFFER_SIZE + " events");
Felipe Leme88eae3b2018-11-07 15:11:56 -0800206 }
Felipe Lemeb18e3172018-11-27 10:33:41 -0800207 mEvents = new ArrayList<>(MAX_BUFFER_SIZE);
208 }
209 mEvents.add(event);
210 final int numberEvents = mEvents.size();
211 if (numberEvents < MAX_BUFFER_SIZE && !forceFlush) {
212 // Buffering events, return right away...
213 return;
214 }
Felipe Leme88eae3b2018-11-07 15:11:56 -0800215
Felipe Lemeb18e3172018-11-27 10:33:41 -0800216 if (mState != STATE_ACTIVE) {
217 // Callback from startSession hasn't been called yet - typically happens on system
218 // apps that are started before the system service
219 // TODO(b/111276913): try to ignore session while system is not ready / boot
220 // not complete instead. Similarly, the manager service should return right away
221 // when the user does not have a service set
222 if (VERBOSE) {
223 Log.v(TAG, "Closing session for " + getActivityDebugName()
224 + " after " + numberEvents + " delayed events and state "
225 + getStateAsString(mState));
Felipe Leme88eae3b2018-11-07 15:11:56 -0800226 }
Felipe Lemeb18e3172018-11-27 10:33:41 -0800227 handleResetState();
228 // TODO(b/111276913): blacklist activity / use special flag to indicate that
229 // when it's launched again
230 return;
231 }
Felipe Leme88eae3b2018-11-07 15:11:56 -0800232
Felipe Lemeb18e3172018-11-27 10:33:41 -0800233 if (mId == null) {
234 // Sanity check - should not happen
235 Log.wtf(TAG, "null session id for " + getActivityDebugName());
236 return;
237 }
238
239 try {
240 if (DEBUG) {
241 Log.d(TAG, "Flushing " + numberEvents + " event(s) for " + getActivityDebugName());
Felipe Leme88eae3b2018-11-07 15:11:56 -0800242 }
Felipe Lemeb18e3172018-11-27 10:33:41 -0800243 mService.sendEvents(mContext.getUserId(), mId, mEvents);
244 // TODO(b/111276913): decide whether we should clear or set it to null, as each has
245 // its own advantages: clearing will save extra allocations while the session is
246 // active, while setting to null would save memory if there's no more event coming.
247 mEvents.clear();
248 } catch (RemoteException e) {
249 Log.w(TAG, "Error sending " + numberEvents + " for " + getActivityDebugName()
250 + ": " + e);
Felipe Leme88eae3b2018-11-07 15:11:56 -0800251 }
252 }
253
Felipe Leme7a534082018-11-05 15:03:04 -0800254 /**
255 * Used for intermediate events (i.e, other than created and destroyed).
256 *
257 * @hide
258 */
259 public void onActivityLifecycleEvent(@EventType int type) {
260 if (!isContentCaptureEnabled()) return;
Felipe Leme88eae3b2018-11-07 15:11:56 -0800261 if (VERBOSE) {
Felipe Lemeb18e3172018-11-27 10:33:41 -0800262 Log.v(TAG, "onActivityLifecycleEvent() for " + getActivityDebugName()
Felipe Leme88eae3b2018-11-07 15:11:56 -0800263 + ": " + ContentCaptureEvent.getTypeAsString(type));
Felipe Leme7a534082018-11-05 15:03:04 -0800264 }
Felipe Lemeecb08be2018-11-27 15:48:47 -0800265 mHandler.sendMessage(obtainMessage(ContentCaptureManager::handleSendEvent, this,
Felipe Lemeb18e3172018-11-27 10:33:41 -0800266 new ContentCaptureEvent(type), /* forceFlush= */ true));
Felipe Leme7a534082018-11-05 15:03:04 -0800267 }
268
Felipe Lemee348dc32018-11-05 12:35:29 -0800269 /** @hide */
270 public void onActivityDestroyed() {
271 if (!isContentCaptureEnabled()) return;
272
Felipe Lemeb18e3172018-11-27 10:33:41 -0800273 //TODO(b/111276913): check state (for example, how to handle if it's waiting for remote
274 // id) and send it to the cache of batched commands
275 if (VERBOSE) {
276 Log.v(TAG, "onActivityDestroyed(): state=" + getStateAsString(mState)
277 + ", mId=" + mId);
278 }
Felipe Lemee348dc32018-11-05 12:35:29 -0800279
Felipe Lemeecb08be2018-11-27 15:48:47 -0800280 mHandler.sendMessage(obtainMessage(ContentCaptureManager::handleFinishSession, this));
Felipe Lemeb18e3172018-11-27 10:33:41 -0800281 }
282
283 private void handleFinishSession() {
284 //TODO(b/111276913): right now both the ContentEvents and lifecycle sessions are sent
285 // to system_server, so it's ok to call both in sequence here. But once we split
286 // them so the events are sent directly to the service, we need to make sure they're
287 // sent in order.
288 try {
289 if (DEBUG) {
290 Log.d(TAG, "Finishing session " + mId + " with "
291 + (mEvents == null ? 0 : mEvents.size()) + " event(s) for "
292 + getActivityDebugName());
Felipe Lemee348dc32018-11-05 12:35:29 -0800293 }
294
Felipe Lemeb18e3172018-11-27 10:33:41 -0800295 mService.finishSession(mContext.getUserId(), mId, mEvents);
296 } catch (RemoteException e) {
297 Log.e(TAG, "Error finishing session " + mId + " for " + getActivityDebugName()
298 + ": " + e);
299 } finally {
300 handleResetState();
Felipe Lemee348dc32018-11-05 12:35:29 -0800301 }
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700302 }
303
Felipe Lemeb18e3172018-11-27 10:33:41 -0800304 private void handleResetState() {
Felipe Leme88eae3b2018-11-07 15:11:56 -0800305 mState = STATE_UNKNOWN;
306 mId = null;
307 mApplicationToken = null;
308 mComponentName = null;
Felipe Lemeb18e3172018-11-27 10:33:41 -0800309 mEvents = null;
Felipe Leme88eae3b2018-11-07 15:11:56 -0800310 }
311
312 /**
313 * Notifies the Intelligence Service that a node has been added to the view structure.
314 *
315 * <p>Typically called "manually" by views that handle their own virtual view hierarchy, or
316 * automatically by the Android System for views that return {@code true} on
317 * {@link View#onProvideContentCaptureStructure(ViewStructure, int)}.
318 *
319 * @param node node that has been added.
320 */
321 public void notifyViewAppeared(@NonNull ViewStructure node) {
322 Preconditions.checkNotNull(node);
323 if (!isContentCaptureEnabled()) return;
324
325 if (!(node instanceof ViewNode.ViewStructureImpl)) {
326 throw new IllegalArgumentException("Invalid node class: " + node.getClass());
327 }
Felipe Lemeb18e3172018-11-27 10:33:41 -0800328
Felipe Lemeecb08be2018-11-27 15:48:47 -0800329 mHandler.sendMessage(obtainMessage(ContentCaptureManager::handleSendEvent, this,
Felipe Lemeb18e3172018-11-27 10:33:41 -0800330 new ContentCaptureEvent(TYPE_VIEW_APPEARED)
331 .setViewNode(((ViewNode.ViewStructureImpl) node).mNode),
332 /* forceFlush= */ false));
Felipe Leme88eae3b2018-11-07 15:11:56 -0800333 }
334
335 /**
336 * Notifies the Intelligence Service that a node has been removed from the view structure.
337 *
338 * <p>Typically called "manually" by views that handle their own virtual view hierarchy, or
339 * automatically by the Android System for standard views.
340 *
341 * @param id id of the node that has been removed.
342 */
343 public void notifyViewDisappeared(@NonNull AutofillId id) {
344 Preconditions.checkNotNull(id);
345 if (!isContentCaptureEnabled()) return;
346
Felipe Lemeecb08be2018-11-27 15:48:47 -0800347 mHandler.sendMessage(obtainMessage(ContentCaptureManager::handleSendEvent, this,
Felipe Lemeb18e3172018-11-27 10:33:41 -0800348 new ContentCaptureEvent(TYPE_VIEW_DISAPPEARED).setAutofillId(id),
349 /* forceFlush= */ false));
Felipe Leme88eae3b2018-11-07 15:11:56 -0800350 }
351
352 /**
353 * Notifies the Intelligence Service that the value of a text node has been changed.
354 *
355 * @param id of the node.
356 * @param text new text.
357 * @param flags either {@code 0} or {@link #FLAG_USER_INPUT} when the value was explicitly
358 * changed by the user (for example, through the keyboard).
359 */
360 public void notifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text,
361 int flags) {
362 Preconditions.checkNotNull(id);
Felipe Lemeb18e3172018-11-27 10:33:41 -0800363
Felipe Leme88eae3b2018-11-07 15:11:56 -0800364 if (!isContentCaptureEnabled()) return;
365
Felipe Lemeecb08be2018-11-27 15:48:47 -0800366 mHandler.sendMessage(obtainMessage(ContentCaptureManager::handleSendEvent, this,
Felipe Lemeb18e3172018-11-27 10:33:41 -0800367 new ContentCaptureEvent(TYPE_VIEW_TEXT_CHANGED, flags).setAutofillId(id)
368 .setText(text), /* forceFlush= */ false));
Felipe Leme88eae3b2018-11-07 15:11:56 -0800369 }
370
371 /**
372 * Creates a {@link ViewStructure} for a "standard" view.
373 *
374 * @hide
375 */
376 @NonNull
377 public ViewStructure newViewStructure(@NonNull View view) {
378 return new ViewNode.ViewStructureImpl(view);
379 }
380
381 /**
382 * Creates a {@link ViewStructure} for a "virtual" view, so it can be passed to
383 * {@link #notifyViewAppeared(ViewStructure)} by the view managing the virtual view hierarchy.
384 *
385 * @param parentId id of the virtual view parent (it can be obtained by calling
386 * {@link ViewStructure#getAutofillId()} on the parent).
387 * @param virtualId id of the virtual child, relative to the parent.
388 *
389 * @return a new {@link ViewStructure} that can be used for Content Capture purposes.
390 */
391 @NonNull
392 public ViewStructure newVirtualViewStructure(@NonNull AutofillId parentId, int virtualId) {
393 return new ViewNode.ViewStructureImpl(parentId, virtualId);
394 }
395
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700396 /**
Felipe Lemeecb08be2018-11-27 15:48:47 -0800397 * Returns the component name of the system service that is consuming the captured events for
398 * the current user.
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700399 */
400 @Nullable
Felipe Lemeecb08be2018-11-27 15:48:47 -0800401 public ComponentName getServiceComponentName() {
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700402 //TODO(b/111276913): implement
403 return null;
404 }
405
406 /**
Felipe Lemee348dc32018-11-05 12:35:29 -0800407 * Checks whether content capture is enabled for this activity.
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700408 */
409 public boolean isContentCaptureEnabled() {
Felipe Lemeb18e3172018-11-27 10:33:41 -0800410 return mService != null && !mDisabled.get();
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700411 }
412
413 /**
Felipe Leme284ad1c2018-11-15 18:16:12 -0800414 * Called by apps to explicitly enable or disable content capture.
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700415 *
416 * <p><b>Note: </b> this call is not persisted accross reboots, so apps should typically call
417 * it on {@link android.app.Activity#onCreate(android.os.Bundle, android.os.PersistableBundle)}.
418 */
Felipe Leme6b3a55c2018-11-13 17:14:03 -0800419 public void setContentCaptureEnabled(boolean enabled) {
Felipe Lemee348dc32018-11-05 12:35:29 -0800420 //TODO(b/111276913): implement
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700421 }
422
Felipe Lemee348dc32018-11-05 12:35:29 -0800423 /** @hide */
424 public void dump(String prefix, PrintWriter pw) {
425 pw.print(prefix); pw.println("IntelligenceManager");
426 final String prefix2 = prefix + " ";
Felipe Lemeb18e3172018-11-27 10:33:41 -0800427 pw.print(prefix2); pw.print("mContext: "); pw.println(mContext);
428 pw.print(prefix2); pw.print("user: "); pw.println(mContext.getUserId());
429 if (mService != null) {
Felipe Lemee348dc32018-11-05 12:35:29 -0800430 pw.print(prefix2); pw.print("mService: "); pw.println(mService);
Felipe Lemeb18e3172018-11-27 10:33:41 -0800431 }
432 pw.print(prefix2); pw.print("mDisabled: "); pw.println(mDisabled.get());
433 pw.print(prefix2); pw.print("isEnabled(): "); pw.println(isContentCaptureEnabled());
434 if (mId != null) {
Felipe Lemea7bdb142018-11-05 16:29:29 -0800435 pw.print(prefix2); pw.print("id: "); pw.println(mId);
Felipe Lemeb18e3172018-11-27 10:33:41 -0800436 }
437 pw.print(prefix2); pw.print("state: "); pw.print(mState); pw.print(" (");
438 pw.print(getStateAsString(mState)); pw.println(")");
439 if (mApplicationToken != null) {
Felipe Leme88eae3b2018-11-07 15:11:56 -0800440 pw.print(prefix2); pw.print("app token: "); pw.println(mApplicationToken);
Felipe Lemeb18e3172018-11-27 10:33:41 -0800441 }
442 if (mComponentName != null) {
Felipe Leme88eae3b2018-11-07 15:11:56 -0800443 pw.print(prefix2); pw.print("component name: ");
Felipe Lemeb18e3172018-11-27 10:33:41 -0800444 pw.println(mComponentName.flattenToShortString());
445 }
446 if (mEvents != null) {
Felipe Leme88eae3b2018-11-07 15:11:56 -0800447 final int numberEvents = mEvents.size();
Felipe Lemeecb08be2018-11-27 15:48:47 -0800448 pw.print(prefix2); pw.print("buffered events: "); pw.print(numberEvents);
Felipe Lemeb18e3172018-11-27 10:33:41 -0800449 pw.print('/'); pw.println(MAX_BUFFER_SIZE);
450 if (VERBOSE && numberEvents > 0) {
451 final String prefix3 = prefix2 + " ";
Felipe Leme88eae3b2018-11-07 15:11:56 -0800452 for (int i = 0; i < numberEvents; i++) {
453 final ContentCaptureEvent event = mEvents.get(i);
Felipe Lemeb18e3172018-11-27 10:33:41 -0800454 pw.print(prefix3); pw.print(i); pw.print(": "); event.dump(pw);
455 pw.println();
Felipe Leme88eae3b2018-11-07 15:11:56 -0800456 }
Felipe Leme88eae3b2018-11-07 15:11:56 -0800457 }
Felipe Lemee348dc32018-11-05 12:35:29 -0800458 }
459 }
460
Felipe Leme88eae3b2018-11-07 15:11:56 -0800461 /**
462 * Gets a string that can be used to identify the activity on logging statements.
463 */
Felipe Lemeb18e3172018-11-27 10:33:41 -0800464 private String getActivityDebugName() {
Felipe Leme88eae3b2018-11-07 15:11:56 -0800465 return mComponentName == null ? mContext.getPackageName()
466 : mComponentName.flattenToShortString();
Felipe Lemee348dc32018-11-05 12:35:29 -0800467 }
468
469 @NonNull
470 private static String getStateAsString(int state) {
471 switch (state) {
472 case STATE_UNKNOWN:
473 return "UNKNOWN";
Felipe Lemea7bdb142018-11-05 16:29:29 -0800474 case STATE_WAITING_FOR_SERVER:
475 return "WAITING_FOR_SERVER";
Felipe Lemee348dc32018-11-05 12:35:29 -0800476 case STATE_ACTIVE:
477 return "ACTIVE";
Felipe Leme8e2e3412018-11-14 10:39:29 -0800478 case STATE_DISABLED:
479 return "DISABLED";
Felipe Lemee348dc32018-11-05 12:35:29 -0800480 default:
481 return "INVALID:" + state;
482 }
483 }
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700484}