blob: 1d9018c1682c2d9fcbcba55b34e582641acac442 [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
Felipe Leme01b87492019-01-15 13:26:52 -080091 // TODO(b/111276913): make sure disabled state is in sync with manager's disabled
Felipe Lemeb63e0dd2018-12-18 11:56:42 -080092 @NonNull
93 private final AtomicBoolean mDisabled;
94
95 @NonNull
96 private final Context mContext;
97
98 @NonNull
99 private final Handler mHandler;
100
101 /**
102 * Interface to the system_server binder object - it's only used to start the session (and
103 * notify when the session is finished).
104 */
105 @Nullable
106 private final IContentCaptureManager mSystemServerInterface;
107
108 /**
109 * Direct interface to the service binder object - it's used to send the events, including the
110 * last ones (when the session is finished)
111 */
112 @Nullable
113 private IContentCaptureDirectManager mDirectServiceInterface;
114 @Nullable
115 private DeathRecipient mDirectServiceVulture;
116
Felipe Leme01b87492019-01-15 13:26:52 -0800117 private int mState = UNKNWON_STATE;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800118
119 @Nullable
120 private IBinder mApplicationToken;
121
122 @Nullable
123 private ComponentName mComponentName;
124
125 /**
126 * List of events held to be sent as a batch.
127 */
128 @Nullable
129 private ArrayList<ContentCaptureEvent> mEvents;
130
131 // Used just for debugging purposes (on dump)
132 private long mNextFlush;
133
Felipe Leme87a9dc92018-12-18 14:28:07 -0800134 /** @hide */
135 protected MainContentCaptureSession(@NonNull Context context, @NonNull Handler handler,
136 @Nullable IContentCaptureManager systemServerInterface,
Felipe Leme01b87492019-01-15 13:26:52 -0800137 @NonNull boolean disabled) {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800138 mContext = context;
139 mHandler = handler;
140 mSystemServerInterface = systemServerInterface;
Felipe Leme01b87492019-01-15 13:26:52 -0800141 mDisabled = new AtomicBoolean(disabled);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800142 }
143
Felipe Leme87a9dc92018-12-18 14:28:07 -0800144 @Override
Felipe Leme4bc0f6b2019-01-03 19:01:07 -0800145 MainContentCaptureSession getMainCaptureSession() {
146 return this;
147 }
148
149 @Override
Felipe Leme87a9dc92018-12-18 14:28:07 -0800150 ContentCaptureSession newChild(@NonNull ContentCaptureContext clientContext) {
151 final ContentCaptureSession child = new ChildContentCaptureSession(this, clientContext);
152 notifyChildSessionStarted(mId, child.mId, clientContext);
153 return child;
154 }
155
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800156 /**
157 * Starts this session.
158 *
159 * @hide
160 */
Adam He328c0e32019-01-03 15:19:22 -0800161 void start(@NonNull IBinder applicationToken, @NonNull ComponentName activityComponent,
162 int flags) {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800163 if (!isContentCaptureEnabled()) return;
164
165 if (VERBOSE) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800166 Log.v(TAG, "start(): token=" + applicationToken + ", comp="
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800167 + ComponentName.flattenToShortString(activityComponent));
168 }
169
Felipe Leme87a9dc92018-12-18 14:28:07 -0800170 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleStartSession, this,
Adam He328c0e32019-01-03 15:19:22 -0800171 applicationToken, activityComponent, flags));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800172 }
173
174 @Override
175 void flush() {
Felipe Leme87a9dc92018-12-18 14:28:07 -0800176 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleForceFlush, this));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800177 }
178
179 @Override
180 void onDestroy() {
Felipe Lemee127c9a2019-01-04 14:56:38 -0800181 mHandler.removeMessages(MSG_FLUSH);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800182 mHandler.sendMessage(
Felipe Leme87a9dc92018-12-18 14:28:07 -0800183 obtainMessage(MainContentCaptureSession::handleDestroySession, this));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800184 }
185
Adam He328c0e32019-01-03 15:19:22 -0800186 private void handleStartSession(@NonNull IBinder token, @NonNull ComponentName componentName,
187 int flags) {
Felipe Leme01b87492019-01-15 13:26:52 -0800188 if (mState != UNKNWON_STATE) {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800189 // TODO(b/111276913): revisit this scenario
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800190 Log.w(TAG, "ignoring handleStartSession(" + token + ") while on state "
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800191 + getStateAsString(mState));
192 return;
193 }
194 mState = STATE_WAITING_FOR_SERVER;
195 mApplicationToken = token;
196 mComponentName = componentName;
197
198 if (VERBOSE) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800199 Log.v(TAG, "handleStartSession(): token=" + token + ", act="
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800200 + getActivityDebugName() + ", id=" + mId);
201 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800202
203 try {
Adam He6884fed2019-01-07 13:18:32 -0800204 if (mSystemServerInterface == null) return;
205
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800206 mSystemServerInterface.startSession(mContext.getUserId(), mApplicationToken,
Felipe Leme87a9dc92018-12-18 14:28:07 -0800207 componentName, mId, flags, new IResultReceiver.Stub() {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800208 @Override
209 public void send(int resultCode, Bundle resultData) {
210 IBinder binder = null;
211 if (resultData != null) {
212 binder = resultData.getBinder(EXTRA_BINDER);
213 if (binder == null) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800214 Log.wtf(TAG, "No " + EXTRA_BINDER + " extra result");
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800215 handleResetState();
216 return;
217 }
218 }
219 handleSessionStarted(resultCode, binder);
220 }
221 });
222 } catch (RemoteException e) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800223 Log.w(TAG, "Error starting session for " + componentName.flattenToShortString() + ": "
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800224 + e);
225 }
226 }
227
228 /**
229 * Callback from {@code system_server} after call to
230 * {@link IContentCaptureManager#startSession(int, IBinder, ComponentName, String,
Felipe Lemebef744c2019-01-03 11:07:25 -0800231 * int, IResultReceiver)}.
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800232 *
233 * @param resultCode session state
234 * @param binder handle to {@code IContentCaptureDirectManager}
235 */
236 private void handleSessionStarted(int resultCode, @Nullable IBinder binder) {
237 mState = resultCode;
238 if (binder != null) {
239 mDirectServiceInterface = IContentCaptureDirectManager.Stub.asInterface(binder);
240 mDirectServiceVulture = () -> {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800241 Log.w(TAG, "Destroying session " + mId + " because service died");
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800242 destroy();
243 };
244 try {
245 binder.linkToDeath(mDirectServiceVulture, 0);
246 } catch (RemoteException e) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800247 Log.w(TAG, "Failed to link to death on " + binder + ": " + e);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800248 }
249 }
Adam He328c0e32019-01-03 15:19:22 -0800250
Felipe Leme01b87492019-01-15 13:26:52 -0800251 if ((mState & STATE_DISABLED) != 0) {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800252 mDisabled.set(true);
253 handleResetSession(/* resetState= */ false);
254 } else {
255 mDisabled.set(false);
256 }
257 if (VERBOSE) {
Felipe Leme01b87492019-01-15 13:26:52 -0800258 Log.v(TAG, "handleSessionStarted() result: id=" + mId
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800259 + ", state=" + getStateAsString(mState) + ", disabled=" + mDisabled.get()
Felipe Lemee127c9a2019-01-04 14:56:38 -0800260 + ", binder=" + binder + ", events=" + (mEvents == null ? 0 : mEvents.size()));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800261 }
262 }
263
264 private void handleSendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) {
265 if (mEvents == null) {
266 if (VERBOSE) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800267 Log.v(TAG, "Creating buffer for " + MAX_BUFFER_SIZE + " events");
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800268 }
269 mEvents = new ArrayList<>(MAX_BUFFER_SIZE);
270 }
Adam Heac132652019-01-02 14:40:15 -0800271
272 if (!mEvents.isEmpty() && event.getType() == TYPE_VIEW_TEXT_CHANGED) {
273 final ContentCaptureEvent lastEvent = mEvents.get(mEvents.size() - 1);
274
275 // TODO(b/121045053): check if flags match
276 if (lastEvent.getType() == TYPE_VIEW_TEXT_CHANGED
277 && lastEvent.getId().equals(event.getId())) {
278 if (VERBOSE) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800279 Log.v(TAG, "Buffering VIEW_TEXT_CHANGED event, updated text = "
Adam Heac132652019-01-02 14:40:15 -0800280 + event.getText());
281 }
282 lastEvent.setText(event.getText());
283 } else {
284 mEvents.add(event);
285 }
286 } else {
287 mEvents.add(event);
288 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800289
290 final int numberEvents = mEvents.size();
291
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800292 final boolean bufferEvent = numberEvents < MAX_BUFFER_SIZE;
293
294 if (bufferEvent && !forceFlush) {
295 handleScheduleFlush(/* checkExisting= */ true);
296 return;
297 }
298
Felipe Lemee127c9a2019-01-04 14:56:38 -0800299 if (mState != STATE_ACTIVE && numberEvents >= MAX_BUFFER_SIZE) {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800300 // Callback from startSession hasn't been called yet - typically happens on system
301 // apps that are started before the system service
302 // TODO(b/111276913): try to ignore session while system is not ready / boot
303 // not complete instead. Similarly, the manager service should return right away
304 // when the user does not have a service set
Felipe Lemee127c9a2019-01-04 14:56:38 -0800305 if (DEBUG) {
306 Log.d(TAG, "Closing session for " + getActivityDebugName()
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800307 + " after " + numberEvents + " delayed events and state "
308 + getStateAsString(mState));
309 }
310 handleResetState();
311 // TODO(b/111276913): blacklist activity / use special flag to indicate that
312 // when it's launched again
313 return;
314 }
315
316 handleForceFlush();
317 }
318
319 private void handleScheduleFlush(boolean checkExisting) {
320 if (checkExisting && mHandler.hasMessages(MSG_FLUSH)) {
321 // "Renew" the flush message by removing the previous one
322 mHandler.removeMessages(MSG_FLUSH);
323 }
324 mNextFlush = SystemClock.elapsedRealtime() + FLUSHING_FREQUENCY_MS;
325 if (VERBOSE) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800326 Log.v(TAG, "Scheduled to flush in " + FLUSHING_FREQUENCY_MS + "ms: " + mNextFlush);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800327 }
328 mHandler.sendMessageDelayed(
Felipe Leme87a9dc92018-12-18 14:28:07 -0800329 obtainMessage(MainContentCaptureSession::handleFlushIfNeeded, this)
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800330 .setWhat(MSG_FLUSH), FLUSHING_FREQUENCY_MS);
331 }
332
333 private void handleFlushIfNeeded() {
334 if (mEvents.isEmpty()) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800335 if (VERBOSE) Log.v(TAG, "Nothing to flush");
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800336 return;
337 }
338 handleForceFlush();
339 }
340
341 private void handleForceFlush() {
342 if (mEvents == null) return;
343
344 if (mDirectServiceInterface == null) {
Felipe Lemee127c9a2019-01-04 14:56:38 -0800345 if (VERBOSE) {
346 Log.v(TAG, "handleForceFlush(): hold your horses, client not ready: " + mEvents);
347 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800348 if (!mHandler.hasMessages(MSG_FLUSH)) {
349 handleScheduleFlush(/* checkExisting= */ false);
350 }
351 return;
352 }
353
354 final int numberEvents = mEvents.size();
355 try {
356 if (DEBUG) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800357 Log.d(TAG, "Flushing " + numberEvents + " event(s) for " + getActivityDebugName());
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800358 }
359 mHandler.removeMessages(MSG_FLUSH);
360
361 final ParceledListSlice<ContentCaptureEvent> events = handleClearEvents();
Felipe Lemefc24bea2018-12-18 13:19:01 -0800362 mDirectServiceInterface.sendEvents(events);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800363 } catch (RemoteException e) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800364 Log.w(TAG, "Error sending " + numberEvents + " for " + getActivityDebugName()
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800365 + ": " + e);
366 }
367 }
368
369 /**
370 * Resets the buffer and return a {@link ParceledListSlice} with the previous events.
371 */
372 @NonNull
373 private ParceledListSlice<ContentCaptureEvent> handleClearEvents() {
374 // NOTE: we must save a reference to the current mEvents and then set it to to null,
375 // otherwise clearing it would clear it in the receiving side if the service is also local.
376 final List<ContentCaptureEvent> events = mEvents == null
377 ? Collections.emptyList()
378 : mEvents;
379 mEvents = null;
380 return new ParceledListSlice<>(events);
381 }
382
383 private void handleDestroySession() {
384 if (DEBUG) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800385 Log.d(TAG, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with "
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800386 + (mEvents == null ? 0 : mEvents.size()) + " event(s) for "
387 + getActivityDebugName());
388 }
389
390 try {
Adam He6884fed2019-01-07 13:18:32 -0800391 if (mSystemServerInterface == null) return;
392
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800393 mSystemServerInterface.finishSession(mContext.getUserId(), mId);
394 } catch (RemoteException e) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800395 Log.e(TAG, "Error destroying system-service session " + mId + " for "
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800396 + getActivityDebugName() + ": " + e);
397 }
398 }
399
400 private void handleResetState() {
401 handleResetSession(/* resetState= */ true);
402 }
403
Felipe Leme4bc0f6b2019-01-03 19:01:07 -0800404 // TODO(b/122454205): once we support multiple sessions, we might need to move some of these
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800405 // clearings out.
406 private void handleResetSession(boolean resetState) {
407 if (resetState) {
Felipe Leme01b87492019-01-15 13:26:52 -0800408 mState = UNKNWON_STATE;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800409 }
Felipe Leme87a9dc92018-12-18 14:28:07 -0800410
Felipe Leme4bc0f6b2019-01-03 19:01:07 -0800411 // TODO(b/122454205): must reset children (which currently is owned by superclass)
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800412 mApplicationToken = null;
413 mComponentName = null;
414 mEvents = null;
415 if (mDirectServiceInterface != null) {
416 mDirectServiceInterface.asBinder().unlinkToDeath(mDirectServiceVulture, 0);
417 }
418 mDirectServiceInterface = null;
419 mHandler.removeMessages(MSG_FLUSH);
420 }
421
422 @Override
423 void internalNotifyViewAppeared(@NonNull ViewStructureImpl node) {
Felipe Leme87a9dc92018-12-18 14:28:07 -0800424 notifyViewAppeared(mId, node);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800425 }
426
427 @Override
428 void internalNotifyViewDisappeared(@NonNull AutofillId id) {
Felipe Leme87a9dc92018-12-18 14:28:07 -0800429 notifyViewDisappeared(mId, id);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800430 }
431
432 @Override
433 void internalNotifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text,
434 int flags) {
Felipe Leme87a9dc92018-12-18 14:28:07 -0800435 notifyViewTextChanged(mId, id, text, flags);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800436 }
437
438 @Override
439 boolean isContentCaptureEnabled() {
Felipe Lemebef744c2019-01-03 11:07:25 -0800440 return super.isContentCaptureEnabled() && mSystemServerInterface != null
441 && !mDisabled.get();
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800442 }
443
Felipe Leme4bc0f6b2019-01-03 19:01:07 -0800444 // TODO(b/122454205): refactor "notifyXXXX" methods below to a common "Buffer" object that is
Felipe Leme87a9dc92018-12-18 14:28:07 -0800445 // shared between ActivityContentCaptureSession and ChildContentCaptureSession objects. Such
446 // change should also get get rid of the "internalNotifyXXXX" methods above
447 void notifyChildSessionStarted(@NonNull String parentSessionId,
448 @NonNull String childSessionId, @NonNull ContentCaptureContext clientContext) {
449 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
450 new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED)
451 .setParentSessionId(parentSessionId)
452 .setClientContext(clientContext),
Felipe Lemee127c9a2019-01-04 14:56:38 -0800453 /* forceFlush= */ true));
Felipe Leme87a9dc92018-12-18 14:28:07 -0800454 }
455
456 void notifyChildSessionFinished(@NonNull String parentSessionId,
457 @NonNull String childSessionId) {
458 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
459 new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED)
Felipe Lemee127c9a2019-01-04 14:56:38 -0800460 .setParentSessionId(parentSessionId), /* forceFlush= */ true));
Felipe Leme87a9dc92018-12-18 14:28:07 -0800461 }
462
463 void notifyViewAppeared(@NonNull String sessionId, @NonNull ViewStructureImpl node) {
464 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
465 new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED)
466 .setViewNode(node.mNode), /* forceFlush= */ false));
467 }
468
469 void notifyViewDisappeared(@NonNull String sessionId, @NonNull AutofillId id) {
470 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
471 new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED).setAutofillId(id),
472 /* forceFlush= */ false));
473 }
474
475 void notifyViewTextChanged(@NonNull String sessionId, @NonNull AutofillId id,
476 @Nullable CharSequence text, int flags) {
477 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
478 new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED, flags).setAutofillId(id)
479 .setText(text), /* forceFlush= */ false));
480 }
481
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800482 @Override
483 void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
484 pw.print(prefix); pw.print("id: "); pw.println(mId);
485 pw.print(prefix); pw.print("mContext: "); pw.println(mContext);
486 pw.print(prefix); pw.print("user: "); pw.println(mContext.getUserId());
487 if (mSystemServerInterface != null) {
488 pw.print(prefix); pw.print("mSystemServerInterface: ");
489 pw.println(mSystemServerInterface);
490 }
491 if (mDirectServiceInterface != null) {
492 pw.print(prefix); pw.print("mDirectServiceInterface: ");
493 pw.println(mDirectServiceInterface);
494 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800495 pw.print(prefix); pw.print("mDisabled: "); pw.println(mDisabled.get());
496 pw.print(prefix); pw.print("isEnabled(): "); pw.println(isContentCaptureEnabled());
Felipe Leme01b87492019-01-15 13:26:52 -0800497 pw.print(prefix); pw.print("state: "); pw.println(getStateAsString(mState));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800498 if (mApplicationToken != null) {
499 pw.print(prefix); pw.print("app token: "); pw.println(mApplicationToken);
500 }
501 if (mComponentName != null) {
502 pw.print(prefix); pw.print("component name: ");
503 pw.println(mComponentName.flattenToShortString());
504 }
505 if (mEvents != null && !mEvents.isEmpty()) {
506 final int numberEvents = mEvents.size();
507 pw.print(prefix); pw.print("buffered events: "); pw.print(numberEvents);
508 pw.print('/'); pw.println(MAX_BUFFER_SIZE);
509 if (VERBOSE && numberEvents > 0) {
510 final String prefix3 = prefix + " ";
511 for (int i = 0; i < numberEvents; i++) {
512 final ContentCaptureEvent event = mEvents.get(i);
513 pw.print(prefix3); pw.print(i); pw.print(": "); event.dump(pw);
514 pw.println();
515 }
516 }
517 pw.print(prefix); pw.print("flush frequency: "); pw.println(FLUSHING_FREQUENCY_MS);
518 pw.print(prefix); pw.print("next flush: ");
519 TimeUtils.formatDuration(mNextFlush - SystemClock.elapsedRealtime(), pw); pw.println();
520 }
Felipe Leme87a9dc92018-12-18 14:28:07 -0800521 super.dump(prefix, pw);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800522 }
523
524 /**
525 * Gets a string that can be used to identify the activity on logging statements.
526 */
527 private String getActivityDebugName() {
528 return mComponentName == null ? mContext.getPackageName()
529 : mComponentName.flattenToShortString();
530 }
531}