blob: 103d7e6dc25663db6b60cfc7f00d624bc3e56661 [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;
Felipe Leme1af85ea2019-01-16 13:23:40 -080038import android.util.LocalLog;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -080039import 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 Leme1af85ea2019-01-16 13:23:40 -0800134 // TODO(b/121044064): use settings to set size
135 private final LocalLog mFlushHistory = new LocalLog(10);
136
Felipe Leme87a9dc92018-12-18 14:28:07 -0800137 /** @hide */
138 protected MainContentCaptureSession(@NonNull Context context, @NonNull Handler handler,
139 @Nullable IContentCaptureManager systemServerInterface,
Felipe Leme01b87492019-01-15 13:26:52 -0800140 @NonNull boolean disabled) {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800141 mContext = context;
142 mHandler = handler;
143 mSystemServerInterface = systemServerInterface;
Felipe Leme01b87492019-01-15 13:26:52 -0800144 mDisabled = new AtomicBoolean(disabled);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800145 }
146
Felipe Leme87a9dc92018-12-18 14:28:07 -0800147 @Override
Felipe Leme4bc0f6b2019-01-03 19:01:07 -0800148 MainContentCaptureSession getMainCaptureSession() {
149 return this;
150 }
151
152 @Override
Felipe Leme87a9dc92018-12-18 14:28:07 -0800153 ContentCaptureSession newChild(@NonNull ContentCaptureContext clientContext) {
154 final ContentCaptureSession child = new ChildContentCaptureSession(this, clientContext);
155 notifyChildSessionStarted(mId, child.mId, clientContext);
156 return child;
157 }
158
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800159 /**
160 * Starts this session.
161 *
162 * @hide
163 */
Adam He328c0e32019-01-03 15:19:22 -0800164 void start(@NonNull IBinder applicationToken, @NonNull ComponentName activityComponent,
165 int flags) {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800166 if (!isContentCaptureEnabled()) return;
167
168 if (VERBOSE) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800169 Log.v(TAG, "start(): token=" + applicationToken + ", comp="
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800170 + ComponentName.flattenToShortString(activityComponent));
171 }
172
Felipe Leme87a9dc92018-12-18 14:28:07 -0800173 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleStartSession, this,
Adam He328c0e32019-01-03 15:19:22 -0800174 applicationToken, activityComponent, flags));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800175 }
176
177 @Override
Felipe Leme1af85ea2019-01-16 13:23:40 -0800178 void flush(@FlushReason int reason) {
179 mHandler.sendMessage(
180 obtainMessage(MainContentCaptureSession::handleForceFlush, this, reason));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800181 }
182
183 @Override
184 void onDestroy() {
Felipe Lemee127c9a2019-01-04 14:56:38 -0800185 mHandler.removeMessages(MSG_FLUSH);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800186 mHandler.sendMessage(
Felipe Leme87a9dc92018-12-18 14:28:07 -0800187 obtainMessage(MainContentCaptureSession::handleDestroySession, this));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800188 }
189
Adam He328c0e32019-01-03 15:19:22 -0800190 private void handleStartSession(@NonNull IBinder token, @NonNull ComponentName componentName,
191 int flags) {
Felipe Lemed65692c2019-01-16 12:10:50 -0800192 if (handleHasStarted()) {
193 // TODO(b/122959591): make sure this is expected (and when), or use Log.w
194 if (DEBUG) {
195 Log.d(TAG, "ignoring handleStartSession(" + token + "/"
196 + ComponentName.flattenToShortString(componentName) + " while on state "
197 + getStateAsString(mState));
198 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800199 return;
200 }
201 mState = STATE_WAITING_FOR_SERVER;
202 mApplicationToken = token;
203 mComponentName = componentName;
204
205 if (VERBOSE) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800206 Log.v(TAG, "handleStartSession(): token=" + token + ", act="
Felipe Lemed65692c2019-01-16 12:10:50 -0800207 + getDebugState() + ", id=" + mId);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800208 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800209
210 try {
Adam He6884fed2019-01-07 13:18:32 -0800211 if (mSystemServerInterface == null) return;
212
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800213 mSystemServerInterface.startSession(mContext.getUserId(), mApplicationToken,
Felipe Leme87a9dc92018-12-18 14:28:07 -0800214 componentName, mId, flags, new IResultReceiver.Stub() {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800215 @Override
216 public void send(int resultCode, Bundle resultData) {
217 IBinder binder = null;
218 if (resultData != null) {
219 binder = resultData.getBinder(EXTRA_BINDER);
220 if (binder == null) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800221 Log.wtf(TAG, "No " + EXTRA_BINDER + " extra result");
Felipe Lemed65692c2019-01-16 12:10:50 -0800222 handleResetSession(STATE_DISABLED | STATE_INTERNAL_ERROR);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800223 return;
224 }
225 }
226 handleSessionStarted(resultCode, binder);
227 }
228 });
229 } catch (RemoteException e) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800230 Log.w(TAG, "Error starting session for " + componentName.flattenToShortString() + ": "
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800231 + e);
232 }
233 }
234
235 /**
236 * Callback from {@code system_server} after call to
237 * {@link IContentCaptureManager#startSession(int, IBinder, ComponentName, String,
Felipe Lemebef744c2019-01-03 11:07:25 -0800238 * int, IResultReceiver)}.
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800239 *
240 * @param resultCode session state
241 * @param binder handle to {@code IContentCaptureDirectManager}
242 */
243 private void handleSessionStarted(int resultCode, @Nullable IBinder binder) {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800244 if (binder != null) {
245 mDirectServiceInterface = IContentCaptureDirectManager.Stub.asInterface(binder);
246 mDirectServiceVulture = () -> {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800247 Log.w(TAG, "Destroying session " + mId + " because service died");
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800248 destroy();
249 };
250 try {
251 binder.linkToDeath(mDirectServiceVulture, 0);
252 } catch (RemoteException e) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800253 Log.w(TAG, "Failed to link to death on " + binder + ": " + e);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800254 }
255 }
Adam He328c0e32019-01-03 15:19:22 -0800256
Felipe Lemed65692c2019-01-16 12:10:50 -0800257 if ((resultCode & STATE_DISABLED) != 0) {
258 handleResetSession(resultCode);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800259 } else {
Felipe Lemed65692c2019-01-16 12:10:50 -0800260 mState = resultCode;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800261 mDisabled.set(false);
262 }
263 if (VERBOSE) {
Felipe Lemed65692c2019-01-16 12:10:50 -0800264 Log.v(TAG, "handleSessionStarted() result: id=" + mId + " resultCode=" + resultCode
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800265 + ", state=" + getStateAsString(mState) + ", disabled=" + mDisabled.get()
Felipe Lemee127c9a2019-01-04 14:56:38 -0800266 + ", binder=" + binder + ", events=" + (mEvents == null ? 0 : mEvents.size()));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800267 }
268 }
269
270 private void handleSendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) {
Felipe Leme1af85ea2019-01-16 13:23:40 -0800271 final int eventType = event.getType();
272 if (!handleHasStarted() && eventType != ContentCaptureEvent.TYPE_SESSION_STARTED) {
Felipe Lemed65692c2019-01-16 12:10:50 -0800273 // TODO(b/120494182): comment when this could happen (dialogs?)
274 Log.v(TAG, "handleSendEvent(" + getDebugState() + ", "
Felipe Leme1af85ea2019-01-16 13:23:40 -0800275 + ContentCaptureEvent.getTypeAsString(eventType)
Felipe Lemed65692c2019-01-16 12:10:50 -0800276 + "): session not started yet");
277 return;
278 }
Felipe Leme1af85ea2019-01-16 13:23:40 -0800279 if (VERBOSE) Log.v(TAG, "handleSendEvent(" + getDebugState() + "): " + event);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800280 if (mEvents == null) {
281 if (VERBOSE) {
Felipe Lemed65692c2019-01-16 12:10:50 -0800282 Log.v(TAG, "handleSendEvent(" + getDebugState() + ", "
Felipe Leme1af85ea2019-01-16 13:23:40 -0800283 + ContentCaptureEvent.getTypeAsString(eventType)
284 + "): creating buffer for " + MAX_BUFFER_SIZE + " events");
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800285 }
286 mEvents = new ArrayList<>(MAX_BUFFER_SIZE);
287 }
Adam Heac132652019-01-02 14:40:15 -0800288
Felipe Leme48f363c2019-01-17 10:53:46 -0800289 // Some type of events can be merged together
290 boolean addEvent = true;
291
Felipe Leme1af85ea2019-01-16 13:23:40 -0800292 if (!mEvents.isEmpty() && eventType == TYPE_VIEW_TEXT_CHANGED) {
Adam Heac132652019-01-02 14:40:15 -0800293 final ContentCaptureEvent lastEvent = mEvents.get(mEvents.size() - 1);
294
295 // TODO(b/121045053): check if flags match
296 if (lastEvent.getType() == TYPE_VIEW_TEXT_CHANGED
297 && lastEvent.getId().equals(event.getId())) {
298 if (VERBOSE) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800299 Log.v(TAG, "Buffering VIEW_TEXT_CHANGED event, updated text = "
Adam Heac132652019-01-02 14:40:15 -0800300 + event.getText());
301 }
302 lastEvent.setText(event.getText());
Felipe Leme48f363c2019-01-17 10:53:46 -0800303 addEvent = false;
Adam Heac132652019-01-02 14:40:15 -0800304 }
Felipe Leme48f363c2019-01-17 10:53:46 -0800305 }
306
307 if (!mEvents.isEmpty() && eventType == TYPE_VIEW_DISAPPEARED) {
308 final ContentCaptureEvent lastEvent = mEvents.get(mEvents.size() - 1);
309 if (lastEvent.getType() == TYPE_VIEW_DISAPPEARED
310 && event.getSessionId().equals(lastEvent.getSessionId())) {
311 if (VERBOSE) {
312 Log.v(TAG, "Buffering TYPE_VIEW_DISAPPEARED events for session "
313 + lastEvent.getSessionId());
314 }
315 lastEvent.addAutofillId(event.getId());
316 addEvent = false;
317 }
318 }
319
320 if (addEvent) {
Adam Heac132652019-01-02 14:40:15 -0800321 mEvents.add(event);
322 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800323
324 final int numberEvents = mEvents.size();
325
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800326 final boolean bufferEvent = numberEvents < MAX_BUFFER_SIZE;
327
328 if (bufferEvent && !forceFlush) {
Felipe Leme1af85ea2019-01-16 13:23:40 -0800329 handleScheduleFlush(FLUSH_REASON_IDLE_TIMEOUT, /* checkExisting= */ true);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800330 return;
331 }
332
Felipe Lemee127c9a2019-01-04 14:56:38 -0800333 if (mState != STATE_ACTIVE && numberEvents >= MAX_BUFFER_SIZE) {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800334 // Callback from startSession hasn't been called yet - typically happens on system
335 // apps that are started before the system service
Felipe Lemed65692c2019-01-16 12:10:50 -0800336 // TODO(b/122959591): try to ignore session while system is not ready / boot
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800337 // not complete instead. Similarly, the manager service should return right away
338 // when the user does not have a service set
Felipe Lemee127c9a2019-01-04 14:56:38 -0800339 if (DEBUG) {
Felipe Lemed65692c2019-01-16 12:10:50 -0800340 Log.d(TAG, "Closing session for " + getDebugState()
341 + " after " + numberEvents + " delayed events");
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800342 }
Felipe Lemed65692c2019-01-16 12:10:50 -0800343 handleResetSession(STATE_DISABLED | STATE_NO_RESPONSE);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800344 // TODO(b/111276913): blacklist activity / use special flag to indicate that
345 // when it's launched again
346 return;
347 }
Felipe Leme1af85ea2019-01-16 13:23:40 -0800348 final int flushReason;
349 switch (eventType) {
350 case ContentCaptureEvent.TYPE_SESSION_STARTED:
351 flushReason = FLUSH_REASON_SESSION_STARTED;
352 break;
353 case ContentCaptureEvent.TYPE_SESSION_FINISHED:
354 flushReason = FLUSH_REASON_SESSION_FINISHED;
355 break;
356 default:
357 flushReason = FLUSH_REASON_FULL;
358 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800359
Felipe Leme1af85ea2019-01-16 13:23:40 -0800360 handleForceFlush(flushReason);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800361 }
362
Felipe Lemed65692c2019-01-16 12:10:50 -0800363 private boolean handleHasStarted() {
364 return mState != UNKNWON_STATE;
365 }
366
Felipe Leme1af85ea2019-01-16 13:23:40 -0800367 private void handleScheduleFlush(@FlushReason int reason, boolean checkExisting) {
Felipe Lemed65692c2019-01-16 12:10:50 -0800368 if (!handleHasStarted()) {
369 Log.v(TAG, "handleScheduleFlush(" + getDebugState() + "): session not started yet");
370 return;
371 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800372 if (checkExisting && mHandler.hasMessages(MSG_FLUSH)) {
373 // "Renew" the flush message by removing the previous one
374 mHandler.removeMessages(MSG_FLUSH);
375 }
Felipe Leme1af85ea2019-01-16 13:23:40 -0800376 mNextFlush = System.currentTimeMillis() + FLUSHING_FREQUENCY_MS;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800377 if (VERBOSE) {
Felipe Leme1af85ea2019-01-16 13:23:40 -0800378 Log.v(TAG, "handleScheduleFlush(" + getDebugState()
379 + ", reason=" + getflushReasonAsString(reason) + "): scheduled to flush in "
380 + FLUSHING_FREQUENCY_MS + "ms: " + TimeUtils.logTimeOfDay(mNextFlush));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800381 }
382 mHandler.sendMessageDelayed(
Felipe Leme1af85ea2019-01-16 13:23:40 -0800383 obtainMessage(MainContentCaptureSession::handleFlushIfNeeded, this, reason)
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800384 .setWhat(MSG_FLUSH), FLUSHING_FREQUENCY_MS);
385 }
386
Felipe Leme1af85ea2019-01-16 13:23:40 -0800387 private void handleFlushIfNeeded(@FlushReason int reason) {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800388 if (mEvents.isEmpty()) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800389 if (VERBOSE) Log.v(TAG, "Nothing to flush");
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800390 return;
391 }
Felipe Leme1af85ea2019-01-16 13:23:40 -0800392 handleForceFlush(reason);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800393 }
394
Felipe Leme1af85ea2019-01-16 13:23:40 -0800395 private void handleForceFlush(@FlushReason int reason) {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800396 if (mEvents == null) return;
397
398 if (mDirectServiceInterface == null) {
Felipe Lemee127c9a2019-01-04 14:56:38 -0800399 if (VERBOSE) {
Felipe Lemed65692c2019-01-16 12:10:50 -0800400 Log.v(TAG, "handleForceFlush(" + getDebugState()
Felipe Leme1af85ea2019-01-16 13:23:40 -0800401 + ", reason=" + getflushReasonAsString(reason)
Felipe Lemed65692c2019-01-16 12:10:50 -0800402 + "): hold your horses, client not ready: " + mEvents);
Felipe Lemee127c9a2019-01-04 14:56:38 -0800403 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800404 if (!mHandler.hasMessages(MSG_FLUSH)) {
Felipe Leme1af85ea2019-01-16 13:23:40 -0800405 handleScheduleFlush(reason, /* checkExisting= */ false);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800406 }
407 return;
408 }
409
410 final int numberEvents = mEvents.size();
Felipe Leme1af85ea2019-01-16 13:23:40 -0800411 final String reasonString = getflushReasonAsString(reason);
412 if (DEBUG) {
413 Log.d(TAG, "Flushing " + numberEvents + " event(s) for " + getDebugState()
414 + ". Reason: " + reasonString);
415 }
416 // Logs reason, size, max size, idle timeout
417 final String logRecord = "r=" + reasonString + " s=" + numberEvents
418 + " m=" + MAX_BUFFER_SIZE + " i=" + FLUSHING_FREQUENCY_MS;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800419 try {
Felipe Leme1af85ea2019-01-16 13:23:40 -0800420 mFlushHistory.log(logRecord);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800421 mHandler.removeMessages(MSG_FLUSH);
422
423 final ParceledListSlice<ContentCaptureEvent> events = handleClearEvents();
Felipe Lemefc24bea2018-12-18 13:19:01 -0800424 mDirectServiceInterface.sendEvents(events);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800425 } catch (RemoteException e) {
Felipe Lemed65692c2019-01-16 12:10:50 -0800426 Log.w(TAG, "Error sending " + numberEvents + " for " + getDebugState()
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800427 + ": " + e);
428 }
429 }
430
431 /**
432 * Resets the buffer and return a {@link ParceledListSlice} with the previous events.
433 */
434 @NonNull
435 private ParceledListSlice<ContentCaptureEvent> handleClearEvents() {
436 // NOTE: we must save a reference to the current mEvents and then set it to to null,
437 // otherwise clearing it would clear it in the receiving side if the service is also local.
438 final List<ContentCaptureEvent> events = mEvents == null
439 ? Collections.emptyList()
440 : mEvents;
441 mEvents = null;
442 return new ParceledListSlice<>(events);
443 }
444
445 private void handleDestroySession() {
446 if (DEBUG) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800447 Log.d(TAG, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with "
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800448 + (mEvents == null ? 0 : mEvents.size()) + " event(s) for "
Felipe Lemed65692c2019-01-16 12:10:50 -0800449 + getDebugState());
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800450 }
451
452 try {
Adam He6884fed2019-01-07 13:18:32 -0800453 if (mSystemServerInterface == null) return;
454
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800455 mSystemServerInterface.finishSession(mContext.getUserId(), mId);
456 } catch (RemoteException e) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800457 Log.e(TAG, "Error destroying system-service session " + mId + " for "
Felipe Lemed65692c2019-01-16 12:10:50 -0800458 + getDebugState() + ": " + e);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800459 }
460 }
461
Felipe Leme4bc0f6b2019-01-03 19:01:07 -0800462 // TODO(b/122454205): once we support multiple sessions, we might need to move some of these
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800463 // clearings out.
Felipe Lemed65692c2019-01-16 12:10:50 -0800464 private void handleResetSession(int newState) {
465 if (VERBOSE) {
466 Log.v(TAG, "handleResetSession(" + getActivityName() + "): from "
467 + getStateAsString(mState) + " to " + getStateAsString(newState));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800468 }
Felipe Lemed65692c2019-01-16 12:10:50 -0800469 mState = newState;
470 mDisabled.set((newState & STATE_DISABLED) != 0);
Felipe Leme4bc0f6b2019-01-03 19:01:07 -0800471 // TODO(b/122454205): must reset children (which currently is owned by superclass)
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800472 mApplicationToken = null;
473 mComponentName = null;
474 mEvents = null;
475 if (mDirectServiceInterface != null) {
476 mDirectServiceInterface.asBinder().unlinkToDeath(mDirectServiceVulture, 0);
477 }
478 mDirectServiceInterface = null;
479 mHandler.removeMessages(MSG_FLUSH);
480 }
481
482 @Override
483 void internalNotifyViewAppeared(@NonNull ViewStructureImpl node) {
Felipe Leme87a9dc92018-12-18 14:28:07 -0800484 notifyViewAppeared(mId, node);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800485 }
486
487 @Override
488 void internalNotifyViewDisappeared(@NonNull AutofillId id) {
Felipe Leme87a9dc92018-12-18 14:28:07 -0800489 notifyViewDisappeared(mId, id);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800490 }
491
492 @Override
Felipe Leme21e8dcb2019-01-18 09:09:45 -0800493 void internalNotifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text) {
494 notifyViewTextChanged(mId, id, text);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800495 }
496
497 @Override
498 boolean isContentCaptureEnabled() {
Felipe Lemebef744c2019-01-03 11:07:25 -0800499 return super.isContentCaptureEnabled() && mSystemServerInterface != null
500 && !mDisabled.get();
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800501 }
502
Felipe Leme4bc0f6b2019-01-03 19:01:07 -0800503 // TODO(b/122454205): refactor "notifyXXXX" methods below to a common "Buffer" object that is
Felipe Leme87a9dc92018-12-18 14:28:07 -0800504 // shared between ActivityContentCaptureSession and ChildContentCaptureSession objects. Such
505 // change should also get get rid of the "internalNotifyXXXX" methods above
506 void notifyChildSessionStarted(@NonNull String parentSessionId,
507 @NonNull String childSessionId, @NonNull ContentCaptureContext clientContext) {
508 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
509 new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED)
510 .setParentSessionId(parentSessionId)
511 .setClientContext(clientContext),
Felipe Lemee127c9a2019-01-04 14:56:38 -0800512 /* forceFlush= */ true));
Felipe Leme87a9dc92018-12-18 14:28:07 -0800513 }
514
515 void notifyChildSessionFinished(@NonNull String parentSessionId,
516 @NonNull String childSessionId) {
517 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
518 new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED)
Felipe Lemee127c9a2019-01-04 14:56:38 -0800519 .setParentSessionId(parentSessionId), /* forceFlush= */ true));
Felipe Leme87a9dc92018-12-18 14:28:07 -0800520 }
521
522 void notifyViewAppeared(@NonNull String sessionId, @NonNull ViewStructureImpl node) {
523 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
524 new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED)
525 .setViewNode(node.mNode), /* forceFlush= */ false));
526 }
527
528 void notifyViewDisappeared(@NonNull String sessionId, @NonNull AutofillId id) {
529 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
530 new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED).setAutofillId(id),
531 /* forceFlush= */ false));
532 }
533
534 void notifyViewTextChanged(@NonNull String sessionId, @NonNull AutofillId id,
Felipe Leme21e8dcb2019-01-18 09:09:45 -0800535 @Nullable CharSequence text) {
Felipe Leme87a9dc92018-12-18 14:28:07 -0800536 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
Felipe Leme21e8dcb2019-01-18 09:09:45 -0800537 new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED).setAutofillId(id)
Felipe Leme87a9dc92018-12-18 14:28:07 -0800538 .setText(text), /* forceFlush= */ false));
539 }
540
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800541 @Override
542 void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800543 pw.print(prefix); pw.print("mContext: "); pw.println(mContext);
544 pw.print(prefix); pw.print("user: "); pw.println(mContext.getUserId());
545 if (mSystemServerInterface != null) {
546 pw.print(prefix); pw.print("mSystemServerInterface: ");
547 pw.println(mSystemServerInterface);
548 }
549 if (mDirectServiceInterface != null) {
550 pw.print(prefix); pw.print("mDirectServiceInterface: ");
551 pw.println(mDirectServiceInterface);
552 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800553 pw.print(prefix); pw.print("mDisabled: "); pw.println(mDisabled.get());
554 pw.print(prefix); pw.print("isEnabled(): "); pw.println(isContentCaptureEnabled());
Felipe Leme01b87492019-01-15 13:26:52 -0800555 pw.print(prefix); pw.print("state: "); pw.println(getStateAsString(mState));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800556 if (mApplicationToken != null) {
557 pw.print(prefix); pw.print("app token: "); pw.println(mApplicationToken);
558 }
559 if (mComponentName != null) {
560 pw.print(prefix); pw.print("component name: ");
561 pw.println(mComponentName.flattenToShortString());
562 }
563 if (mEvents != null && !mEvents.isEmpty()) {
564 final int numberEvents = mEvents.size();
565 pw.print(prefix); pw.print("buffered events: "); pw.print(numberEvents);
566 pw.print('/'); pw.println(MAX_BUFFER_SIZE);
567 if (VERBOSE && numberEvents > 0) {
568 final String prefix3 = prefix + " ";
569 for (int i = 0; i < numberEvents; i++) {
570 final ContentCaptureEvent event = mEvents.get(i);
571 pw.print(prefix3); pw.print(i); pw.print(": "); event.dump(pw);
572 pw.println();
573 }
574 }
575 pw.print(prefix); pw.print("flush frequency: "); pw.println(FLUSHING_FREQUENCY_MS);
576 pw.print(prefix); pw.print("next flush: ");
Felipe Leme1af85ea2019-01-16 13:23:40 -0800577 TimeUtils.formatDuration(mNextFlush - System.currentTimeMillis(), pw);
578 pw.print(" ("); pw.print(TimeUtils.logTimeOfDay(mNextFlush)); pw.println(")");
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800579 }
Felipe Leme1af85ea2019-01-16 13:23:40 -0800580 pw.print(prefix); pw.println("flush history:");
581 mFlushHistory.reverseDump(/* fd= */ null, pw, /* args= */ null); pw.println();
582
Felipe Leme87a9dc92018-12-18 14:28:07 -0800583 super.dump(prefix, pw);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800584 }
585
586 /**
587 * Gets a string that can be used to identify the activity on logging statements.
588 */
Felipe Lemed65692c2019-01-16 12:10:50 -0800589 private String getActivityName() {
590 return mComponentName == null
591 ? "pkg:" + mContext.getPackageName()
592 : "act:" + mComponentName.flattenToShortString();
593 }
594
595 private String getDebugState() {
596 return getActivityName() + " (state=" + getStateAsString(mState) + ")";
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800597 }
598}