blob: 0605fa3f39a2571351c6fa11678297d9d49dd4d6 [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 Leme01297692019-01-29 18:16:23 -080018import static android.view.contentcapture.ContentCaptureEvent.TYPE_INITIAL_VIEW_TREE_APPEARED;
19import static android.view.contentcapture.ContentCaptureEvent.TYPE_INITIAL_VIEW_TREE_APPEARING;
Felipe Leme87a9dc92018-12-18 14:28:07 -080020import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_FINISHED;
21import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -080022import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_APPEARED;
23import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED;
24import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED;
Felipe Lemebe002d82019-01-23 10:22:32 -080025import static android.view.contentcapture.ContentCaptureHelper.DEBUG;
26import static android.view.contentcapture.ContentCaptureHelper.VERBOSE;
27import static android.view.contentcapture.ContentCaptureHelper.getSanitizedString;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -080028
29import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
30
31import android.annotation.NonNull;
32import android.annotation.Nullable;
33import android.content.ComponentName;
34import android.content.Context;
35import android.content.pm.ParceledListSlice;
36import android.os.Bundle;
37import android.os.Handler;
38import android.os.IBinder;
39import android.os.IBinder.DeathRecipient;
40import android.os.RemoteException;
Felipe Leme1af85ea2019-01-16 13:23:40 -080041import android.util.LocalLog;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -080042import android.util.Log;
43import android.util.TimeUtils;
44import android.view.autofill.AutofillId;
45import android.view.contentcapture.ViewNode.ViewStructureImpl;
46
47import com.android.internal.os.IResultReceiver;
48
49import java.io.PrintWriter;
50import java.util.ArrayList;
51import java.util.Collections;
52import java.util.List;
53import java.util.concurrent.atomic.AtomicBoolean;
54
55/**
56 * Main session associated with a context.
57 *
58 * <p>This session is created when the activity starts and finished when it stops; clients can use
59 * it to create children activities.
60 *
61 * <p><b>NOTE: all methods in this class should return right away, or do the real work in a handler
62 * thread. Hence, the only field that must be thread-safe is {@code mEnabled}, which is called at
63 * the beginning of every method.
64 *
65 * @hide
66 */
Felipe Leme87a9dc92018-12-18 14:28:07 -080067public final class MainContentCaptureSession extends ContentCaptureSession {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -080068
Felipe Lemeaf1a0e72019-01-03 11:07:25 -080069 private static final String TAG = MainContentCaptureSession.class.getSimpleName();
70
Felipe Leme01297692019-01-29 18:16:23 -080071 private static final boolean FORCE_FLUSH = true;
72
Felipe Lemeb63e0dd2018-12-18 11:56:42 -080073 /**
74 * Handler message used to flush the buffer.
75 */
76 private static final int MSG_FLUSH = 1;
77
78 /**
79 * Maximum number of events that are buffered before sent to the app.
80 */
81 // TODO(b/121044064): use settings
82 private static final int MAX_BUFFER_SIZE = 100;
83
84 /**
85 * Frequency the buffer is flushed if stale.
86 */
87 // TODO(b/121044064): use settings
88 private static final int FLUSHING_FREQUENCY_MS = 5_000;
89
90 /**
91 * Name of the {@link IResultReceiver} extra used to pass the binder interface to the service.
92 * @hide
93 */
94 public static final String EXTRA_BINDER = "binder";
95
96 @NonNull
Felipe Leme609991d2019-01-30 16:27:24 -080097 private final AtomicBoolean mDisabled = new AtomicBoolean(false);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -080098
99 @NonNull
100 private final Context mContext;
101
102 @NonNull
Felipe Leme609991d2019-01-30 16:27:24 -0800103 private final ContentCaptureManager mManager;
104
105 @NonNull
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800106 private final Handler mHandler;
107
108 /**
109 * Interface to the system_server binder object - it's only used to start the session (and
110 * notify when the session is finished).
111 */
Felipe Leme609991d2019-01-30 16:27:24 -0800112 @Nullable // TODO(b/122959591): shoul never be null, we should make main session null instead
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800113 private final IContentCaptureManager mSystemServerInterface;
114
115 /**
116 * Direct interface to the service binder object - it's used to send the events, including the
117 * last ones (when the session is finished)
118 */
119 @Nullable
120 private IContentCaptureDirectManager mDirectServiceInterface;
121 @Nullable
122 private DeathRecipient mDirectServiceVulture;
123
Felipe Leme35ea7632019-01-29 16:13:30 -0800124 private int mState = UNKNOWN_STATE;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800125
126 @Nullable
127 private IBinder mApplicationToken;
128
129 @Nullable
130 private ComponentName mComponentName;
131
132 /**
133 * List of events held to be sent as a batch.
134 */
135 @Nullable
136 private ArrayList<ContentCaptureEvent> mEvents;
137
138 // Used just for debugging purposes (on dump)
139 private long mNextFlush;
140
Felipe Leme1af85ea2019-01-16 13:23:40 -0800141 // TODO(b/121044064): use settings to set size
142 private final LocalLog mFlushHistory = new LocalLog(10);
143
Felipe Leme87a9dc92018-12-18 14:28:07 -0800144 /** @hide */
Felipe Leme609991d2019-01-30 16:27:24 -0800145 protected MainContentCaptureSession(@NonNull Context context,
146 @NonNull ContentCaptureManager manager, @NonNull Handler handler,
147 @Nullable IContentCaptureManager systemServerInterface) {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800148 mContext = context;
Felipe Leme609991d2019-01-30 16:27:24 -0800149 mManager = manager;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800150 mHandler = handler;
151 mSystemServerInterface = systemServerInterface;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800152 }
153
Felipe Leme87a9dc92018-12-18 14:28:07 -0800154 @Override
Felipe Leme4bc0f6b2019-01-03 19:01:07 -0800155 MainContentCaptureSession getMainCaptureSession() {
156 return this;
157 }
158
159 @Override
Felipe Leme87a9dc92018-12-18 14:28:07 -0800160 ContentCaptureSession newChild(@NonNull ContentCaptureContext clientContext) {
161 final ContentCaptureSession child = new ChildContentCaptureSession(this, clientContext);
162 notifyChildSessionStarted(mId, child.mId, clientContext);
163 return child;
164 }
165
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800166 /**
167 * Starts this session.
168 *
169 * @hide
170 */
Adam He328c0e32019-01-03 15:19:22 -0800171 void start(@NonNull IBinder applicationToken, @NonNull ComponentName activityComponent,
172 int flags) {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800173 if (!isContentCaptureEnabled()) return;
174
175 if (VERBOSE) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800176 Log.v(TAG, "start(): token=" + applicationToken + ", comp="
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800177 + ComponentName.flattenToShortString(activityComponent));
178 }
179
Felipe Leme87a9dc92018-12-18 14:28:07 -0800180 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleStartSession, this,
Adam He328c0e32019-01-03 15:19:22 -0800181 applicationToken, activityComponent, flags));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800182 }
183
184 @Override
Felipe Leme1af85ea2019-01-16 13:23:40 -0800185 void flush(@FlushReason int reason) {
186 mHandler.sendMessage(
187 obtainMessage(MainContentCaptureSession::handleForceFlush, this, reason));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800188 }
189
190 @Override
191 void onDestroy() {
Felipe Lemee127c9a2019-01-04 14:56:38 -0800192 mHandler.removeMessages(MSG_FLUSH);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800193 mHandler.sendMessage(
Felipe Leme87a9dc92018-12-18 14:28:07 -0800194 obtainMessage(MainContentCaptureSession::handleDestroySession, this));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800195 }
196
Adam He328c0e32019-01-03 15:19:22 -0800197 private void handleStartSession(@NonNull IBinder token, @NonNull ComponentName componentName,
198 int flags) {
Felipe Lemed65692c2019-01-16 12:10:50 -0800199 if (handleHasStarted()) {
200 // TODO(b/122959591): make sure this is expected (and when), or use Log.w
201 if (DEBUG) {
202 Log.d(TAG, "ignoring handleStartSession(" + token + "/"
203 + ComponentName.flattenToShortString(componentName) + " while on state "
204 + getStateAsString(mState));
205 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800206 return;
207 }
208 mState = STATE_WAITING_FOR_SERVER;
209 mApplicationToken = token;
210 mComponentName = componentName;
211
212 if (VERBOSE) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800213 Log.v(TAG, "handleStartSession(): token=" + token + ", act="
Felipe Lemed65692c2019-01-16 12:10:50 -0800214 + getDebugState() + ", id=" + mId);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800215 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800216
217 try {
Adam He6884fed2019-01-07 13:18:32 -0800218 if (mSystemServerInterface == null) return;
219
Felipe Lemef2aa0d22019-01-28 10:38:46 -0800220 mSystemServerInterface.startSession(mApplicationToken, componentName, mId, flags,
221 new IResultReceiver.Stub() {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800222 @Override
223 public void send(int resultCode, Bundle resultData) {
224 IBinder binder = null;
225 if (resultData != null) {
226 binder = resultData.getBinder(EXTRA_BINDER);
227 if (binder == null) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800228 Log.wtf(TAG, "No " + EXTRA_BINDER + " extra result");
Felipe Lemed65692c2019-01-16 12:10:50 -0800229 handleResetSession(STATE_DISABLED | STATE_INTERNAL_ERROR);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800230 return;
231 }
232 }
233 handleSessionStarted(resultCode, binder);
234 }
235 });
236 } catch (RemoteException e) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800237 Log.w(TAG, "Error starting session for " + componentName.flattenToShortString() + ": "
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800238 + e);
239 }
240 }
241
242 /**
243 * Callback from {@code system_server} after call to
Felipe Leme609991d2019-01-30 16:27:24 -0800244 * {@link IContentCaptureManager#startSession(IBinder, ComponentName, String, int,
245 * IResultReceiver)}
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800246 *
247 * @param resultCode session state
248 * @param binder handle to {@code IContentCaptureDirectManager}
249 */
250 private void handleSessionStarted(int resultCode, @Nullable IBinder binder) {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800251 if (binder != null) {
252 mDirectServiceInterface = IContentCaptureDirectManager.Stub.asInterface(binder);
253 mDirectServiceVulture = () -> {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800254 Log.w(TAG, "Destroying session " + mId + " because service died");
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800255 destroy();
256 };
257 try {
258 binder.linkToDeath(mDirectServiceVulture, 0);
259 } catch (RemoteException e) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800260 Log.w(TAG, "Failed to link to death on " + binder + ": " + e);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800261 }
262 }
Adam He328c0e32019-01-03 15:19:22 -0800263
Felipe Lemed65692c2019-01-16 12:10:50 -0800264 if ((resultCode & STATE_DISABLED) != 0) {
265 handleResetSession(resultCode);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800266 } else {
Felipe Lemed65692c2019-01-16 12:10:50 -0800267 mState = resultCode;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800268 mDisabled.set(false);
269 }
270 if (VERBOSE) {
Felipe Lemed65692c2019-01-16 12:10:50 -0800271 Log.v(TAG, "handleSessionStarted() result: id=" + mId + " resultCode=" + resultCode
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800272 + ", state=" + getStateAsString(mState) + ", disabled=" + mDisabled.get()
Felipe Lemee127c9a2019-01-04 14:56:38 -0800273 + ", binder=" + binder + ", events=" + (mEvents == null ? 0 : mEvents.size()));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800274 }
275 }
276
Felipe Leme01297692019-01-29 18:16:23 -0800277 private void handleSendEvent(@NonNull ContentCaptureEvent event) {
278 handleSendEvent(event, /* forceFlush= */ false);
279 }
280
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800281 private void handleSendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) {
Felipe Leme1af85ea2019-01-16 13:23:40 -0800282 final int eventType = event.getType();
Felipe Lemebe002d82019-01-23 10:22:32 -0800283 if (VERBOSE) Log.v(TAG, "handleSendEvent(" + getDebugState() + "): " + event);
Felipe Leme1af85ea2019-01-16 13:23:40 -0800284 if (!handleHasStarted() && eventType != ContentCaptureEvent.TYPE_SESSION_STARTED) {
Felipe Lemed65692c2019-01-16 12:10:50 -0800285 // TODO(b/120494182): comment when this could happen (dialogs?)
286 Log.v(TAG, "handleSendEvent(" + getDebugState() + ", "
Felipe Leme1af85ea2019-01-16 13:23:40 -0800287 + ContentCaptureEvent.getTypeAsString(eventType)
Felipe Lemed65692c2019-01-16 12:10:50 -0800288 + "): session not started yet");
289 return;
290 }
Felipe Lemebe002d82019-01-23 10:22:32 -0800291 if (mDisabled.get()) {
292 // This happens when the event was queued in the handler before the sesison was ready,
293 // then handleSessionStarted() returned and set it as disabled - we need to drop it,
294 // otherwise it will keep triggering handleScheduleFlush()
295 if (VERBOSE) Log.v(TAG, "handleSendEvent(): ignoring when disabled");
296 return;
297 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800298 if (mEvents == null) {
299 if (VERBOSE) {
Felipe Lemebe002d82019-01-23 10:22:32 -0800300 Log.v(TAG, "handleSendEvent(): creating buffer for " + MAX_BUFFER_SIZE + " events");
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800301 }
302 mEvents = new ArrayList<>(MAX_BUFFER_SIZE);
303 }
Adam Heac132652019-01-02 14:40:15 -0800304
Felipe Leme48f363c2019-01-17 10:53:46 -0800305 // Some type of events can be merged together
306 boolean addEvent = true;
307
Felipe Leme1af85ea2019-01-16 13:23:40 -0800308 if (!mEvents.isEmpty() && eventType == TYPE_VIEW_TEXT_CHANGED) {
Adam Heac132652019-01-02 14:40:15 -0800309 final ContentCaptureEvent lastEvent = mEvents.get(mEvents.size() - 1);
310
311 // TODO(b/121045053): check if flags match
312 if (lastEvent.getType() == TYPE_VIEW_TEXT_CHANGED
313 && lastEvent.getId().equals(event.getId())) {
314 if (VERBOSE) {
Felipe Lemebe002d82019-01-23 10:22:32 -0800315 Log.v(TAG, "Buffering VIEW_TEXT_CHANGED event, updated text="
316 + getSanitizedString(event.getText()));
Adam Heac132652019-01-02 14:40:15 -0800317 }
318 lastEvent.setText(event.getText());
Felipe Leme48f363c2019-01-17 10:53:46 -0800319 addEvent = false;
Adam Heac132652019-01-02 14:40:15 -0800320 }
Felipe Leme48f363c2019-01-17 10:53:46 -0800321 }
322
323 if (!mEvents.isEmpty() && eventType == TYPE_VIEW_DISAPPEARED) {
324 final ContentCaptureEvent lastEvent = mEvents.get(mEvents.size() - 1);
325 if (lastEvent.getType() == TYPE_VIEW_DISAPPEARED
326 && event.getSessionId().equals(lastEvent.getSessionId())) {
327 if (VERBOSE) {
328 Log.v(TAG, "Buffering TYPE_VIEW_DISAPPEARED events for session "
329 + lastEvent.getSessionId());
330 }
331 lastEvent.addAutofillId(event.getId());
332 addEvent = false;
333 }
334 }
335
336 if (addEvent) {
Adam Heac132652019-01-02 14:40:15 -0800337 mEvents.add(event);
338 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800339
340 final int numberEvents = mEvents.size();
341
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800342 final boolean bufferEvent = numberEvents < MAX_BUFFER_SIZE;
343
344 if (bufferEvent && !forceFlush) {
Felipe Leme1af85ea2019-01-16 13:23:40 -0800345 handleScheduleFlush(FLUSH_REASON_IDLE_TIMEOUT, /* checkExisting= */ true);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800346 return;
347 }
348
Felipe Lemee127c9a2019-01-04 14:56:38 -0800349 if (mState != STATE_ACTIVE && numberEvents >= MAX_BUFFER_SIZE) {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800350 // Callback from startSession hasn't been called yet - typically happens on system
351 // apps that are started before the system service
Felipe Lemed65692c2019-01-16 12:10:50 -0800352 // TODO(b/122959591): try to ignore session while system is not ready / boot
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800353 // not complete instead. Similarly, the manager service should return right away
354 // when the user does not have a service set
Felipe Lemee127c9a2019-01-04 14:56:38 -0800355 if (DEBUG) {
Felipe Lemed65692c2019-01-16 12:10:50 -0800356 Log.d(TAG, "Closing session for " + getDebugState()
357 + " after " + numberEvents + " delayed events");
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800358 }
Felipe Lemed65692c2019-01-16 12:10:50 -0800359 handleResetSession(STATE_DISABLED | STATE_NO_RESPONSE);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800360 // TODO(b/111276913): blacklist activity / use special flag to indicate that
361 // when it's launched again
362 return;
363 }
Felipe Leme1af85ea2019-01-16 13:23:40 -0800364 final int flushReason;
365 switch (eventType) {
366 case ContentCaptureEvent.TYPE_SESSION_STARTED:
367 flushReason = FLUSH_REASON_SESSION_STARTED;
368 break;
369 case ContentCaptureEvent.TYPE_SESSION_FINISHED:
370 flushReason = FLUSH_REASON_SESSION_FINISHED;
371 break;
372 default:
373 flushReason = FLUSH_REASON_FULL;
374 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800375
Felipe Leme1af85ea2019-01-16 13:23:40 -0800376 handleForceFlush(flushReason);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800377 }
378
Felipe Lemed65692c2019-01-16 12:10:50 -0800379 private boolean handleHasStarted() {
Felipe Leme35ea7632019-01-29 16:13:30 -0800380 return mState != UNKNOWN_STATE;
Felipe Lemed65692c2019-01-16 12:10:50 -0800381 }
382
Felipe Leme1af85ea2019-01-16 13:23:40 -0800383 private void handleScheduleFlush(@FlushReason int reason, boolean checkExisting) {
Felipe Lemebe002d82019-01-23 10:22:32 -0800384 if (VERBOSE) {
385 Log.v(TAG, "handleScheduleFlush(" + getDebugState(reason)
386 + ", checkExisting=" + checkExisting);
387 }
Felipe Lemed65692c2019-01-16 12:10:50 -0800388 if (!handleHasStarted()) {
Felipe Lemebe002d82019-01-23 10:22:32 -0800389 if (VERBOSE) Log.v(TAG, "handleScheduleFlush(): session not started yet");
390 return;
391 }
392
393 if (mDisabled.get()) {
394 // Should not be called on this state, as handleSendEvent checks.
395 // But we rather add one if check and log than re-schedule and keep the session alive...
396 Log.e(TAG, "handleScheduleFlush(" + getDebugState(reason) + "): should not be called "
397 + "when disabled. events=" + (mEvents == null ? null : mEvents.size()));
Felipe Lemed65692c2019-01-16 12:10:50 -0800398 return;
399 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800400 if (checkExisting && mHandler.hasMessages(MSG_FLUSH)) {
401 // "Renew" the flush message by removing the previous one
402 mHandler.removeMessages(MSG_FLUSH);
403 }
Felipe Leme1af85ea2019-01-16 13:23:40 -0800404 mNextFlush = System.currentTimeMillis() + FLUSHING_FREQUENCY_MS;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800405 if (VERBOSE) {
Felipe Lemebe002d82019-01-23 10:22:32 -0800406 Log.v(TAG, "handleScheduleFlush(): scheduled to flush in "
Felipe Leme1af85ea2019-01-16 13:23:40 -0800407 + FLUSHING_FREQUENCY_MS + "ms: " + TimeUtils.logTimeOfDay(mNextFlush));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800408 }
409 mHandler.sendMessageDelayed(
Felipe Leme1af85ea2019-01-16 13:23:40 -0800410 obtainMessage(MainContentCaptureSession::handleFlushIfNeeded, this, reason)
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800411 .setWhat(MSG_FLUSH), FLUSHING_FREQUENCY_MS);
412 }
413
Felipe Leme1af85ea2019-01-16 13:23:40 -0800414 private void handleFlushIfNeeded(@FlushReason int reason) {
Felipe Leme3d570a42019-01-28 13:04:34 -0800415 if (mEvents == null || mEvents.isEmpty()) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800416 if (VERBOSE) Log.v(TAG, "Nothing to flush");
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800417 return;
418 }
Felipe Leme1af85ea2019-01-16 13:23:40 -0800419 handleForceFlush(reason);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800420 }
421
Felipe Leme1af85ea2019-01-16 13:23:40 -0800422 private void handleForceFlush(@FlushReason int reason) {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800423 if (mEvents == null) return;
424
Felipe Lemebe002d82019-01-23 10:22:32 -0800425 if (mDisabled.get()) {
426 Log.e(TAG, "handleForceFlush(" + getDebugState(reason) + "): should not be when "
427 + "disabled");
428 return;
429 }
430
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800431 if (mDirectServiceInterface == null) {
Felipe Lemee127c9a2019-01-04 14:56:38 -0800432 if (VERBOSE) {
Felipe Lemebe002d82019-01-23 10:22:32 -0800433 Log.v(TAG, "handleForceFlush(" + getDebugState(reason) + "): hold your horses, "
434 + "client not ready: " + mEvents);
Felipe Lemee127c9a2019-01-04 14:56:38 -0800435 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800436 if (!mHandler.hasMessages(MSG_FLUSH)) {
Felipe Leme1af85ea2019-01-16 13:23:40 -0800437 handleScheduleFlush(reason, /* checkExisting= */ false);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800438 }
439 return;
440 }
441
442 final int numberEvents = mEvents.size();
Felipe Leme1af85ea2019-01-16 13:23:40 -0800443 final String reasonString = getflushReasonAsString(reason);
444 if (DEBUG) {
Felipe Lemebe002d82019-01-23 10:22:32 -0800445 Log.d(TAG, "Flushing " + numberEvents + " event(s) for " + getDebugState(reason));
Felipe Leme1af85ea2019-01-16 13:23:40 -0800446 }
447 // Logs reason, size, max size, idle timeout
448 final String logRecord = "r=" + reasonString + " s=" + numberEvents
449 + " m=" + MAX_BUFFER_SIZE + " i=" + FLUSHING_FREQUENCY_MS;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800450 try {
Felipe Leme1af85ea2019-01-16 13:23:40 -0800451 mFlushHistory.log(logRecord);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800452 mHandler.removeMessages(MSG_FLUSH);
453
454 final ParceledListSlice<ContentCaptureEvent> events = handleClearEvents();
Felipe Lemefc24bea2018-12-18 13:19:01 -0800455 mDirectServiceInterface.sendEvents(events);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800456 } catch (RemoteException e) {
Felipe Lemed65692c2019-01-16 12:10:50 -0800457 Log.w(TAG, "Error sending " + numberEvents + " for " + getDebugState()
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800458 + ": " + e);
459 }
460 }
461
462 /**
463 * Resets the buffer and return a {@link ParceledListSlice} with the previous events.
464 */
465 @NonNull
466 private ParceledListSlice<ContentCaptureEvent> handleClearEvents() {
467 // NOTE: we must save a reference to the current mEvents and then set it to to null,
468 // otherwise clearing it would clear it in the receiving side if the service is also local.
469 final List<ContentCaptureEvent> events = mEvents == null
470 ? Collections.emptyList()
471 : mEvents;
472 mEvents = null;
473 return new ParceledListSlice<>(events);
474 }
475
476 private void handleDestroySession() {
477 if (DEBUG) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800478 Log.d(TAG, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with "
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800479 + (mEvents == null ? 0 : mEvents.size()) + " event(s) for "
Felipe Lemed65692c2019-01-16 12:10:50 -0800480 + getDebugState());
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800481 }
482
483 try {
Adam He6884fed2019-01-07 13:18:32 -0800484 if (mSystemServerInterface == null) return;
485
Felipe Lemef2aa0d22019-01-28 10:38:46 -0800486 mSystemServerInterface.finishSession(mId);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800487 } catch (RemoteException e) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800488 Log.e(TAG, "Error destroying system-service session " + mId + " for "
Felipe Lemed65692c2019-01-16 12:10:50 -0800489 + getDebugState() + ": " + e);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800490 }
491 }
492
Felipe Leme4bc0f6b2019-01-03 19:01:07 -0800493 // TODO(b/122454205): once we support multiple sessions, we might need to move some of these
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800494 // clearings out.
Felipe Lemed65692c2019-01-16 12:10:50 -0800495 private void handleResetSession(int newState) {
496 if (VERBOSE) {
497 Log.v(TAG, "handleResetSession(" + getActivityName() + "): from "
498 + getStateAsString(mState) + " to " + getStateAsString(newState));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800499 }
Felipe Lemed65692c2019-01-16 12:10:50 -0800500 mState = newState;
501 mDisabled.set((newState & STATE_DISABLED) != 0);
Felipe Leme4bc0f6b2019-01-03 19:01:07 -0800502 // TODO(b/122454205): must reset children (which currently is owned by superclass)
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800503 mApplicationToken = null;
504 mComponentName = null;
505 mEvents = null;
506 if (mDirectServiceInterface != null) {
507 mDirectServiceInterface.asBinder().unlinkToDeath(mDirectServiceVulture, 0);
508 }
509 mDirectServiceInterface = null;
510 mHandler.removeMessages(MSG_FLUSH);
511 }
512
513 @Override
514 void internalNotifyViewAppeared(@NonNull ViewStructureImpl node) {
Felipe Leme87a9dc92018-12-18 14:28:07 -0800515 notifyViewAppeared(mId, node);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800516 }
517
518 @Override
519 void internalNotifyViewDisappeared(@NonNull AutofillId id) {
Felipe Leme87a9dc92018-12-18 14:28:07 -0800520 notifyViewDisappeared(mId, id);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800521 }
522
523 @Override
Felipe Leme21e8dcb2019-01-18 09:09:45 -0800524 void internalNotifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text) {
525 notifyViewTextChanged(mId, id, text);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800526 }
527
528 @Override
Felipe Leme01297692019-01-29 18:16:23 -0800529 public void internalNotifyViewHierarchyEvent(boolean started) {
530 notifyInitialViewHierarchyEvent(mId, started);
531 }
532
533 @Override
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800534 boolean isContentCaptureEnabled() {
Felipe Leme609991d2019-01-30 16:27:24 -0800535 return super.isContentCaptureEnabled() && mManager.isContentCaptureEnabled();
536 }
537
538 // Called by ContentCaptureManager.isContentCaptureEnabled
539 boolean isDisabled() {
540 return mDisabled.get();
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800541 }
542
Felipe Leme4bc0f6b2019-01-03 19:01:07 -0800543 // TODO(b/122454205): refactor "notifyXXXX" methods below to a common "Buffer" object that is
Felipe Leme87a9dc92018-12-18 14:28:07 -0800544 // shared between ActivityContentCaptureSession and ChildContentCaptureSession objects. Such
545 // change should also get get rid of the "internalNotifyXXXX" methods above
546 void notifyChildSessionStarted(@NonNull String parentSessionId,
547 @NonNull String childSessionId, @NonNull ContentCaptureContext clientContext) {
548 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
549 new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED)
550 .setParentSessionId(parentSessionId)
551 .setClientContext(clientContext),
Felipe Leme01297692019-01-29 18:16:23 -0800552 FORCE_FLUSH));
Felipe Leme87a9dc92018-12-18 14:28:07 -0800553 }
554
555 void notifyChildSessionFinished(@NonNull String parentSessionId,
556 @NonNull String childSessionId) {
557 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
558 new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED)
Felipe Leme01297692019-01-29 18:16:23 -0800559 .setParentSessionId(parentSessionId), FORCE_FLUSH));
Felipe Leme87a9dc92018-12-18 14:28:07 -0800560 }
561
562 void notifyViewAppeared(@NonNull String sessionId, @NonNull ViewStructureImpl node) {
563 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
Felipe Leme01297692019-01-29 18:16:23 -0800564 new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED).setViewNode(node.mNode)));
Felipe Leme87a9dc92018-12-18 14:28:07 -0800565 }
566
567 void notifyViewDisappeared(@NonNull String sessionId, @NonNull AutofillId id) {
568 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
Felipe Leme01297692019-01-29 18:16:23 -0800569 new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED).setAutofillId(id)));
570 }
571
572 /** @hide */
573 public void notifyViewsDisappeared(@NonNull String sessionId,
574 @NonNull ArrayList<AutofillId> ids) {
575 final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED);
576 if (ids.size() == 1) {
577 event.setAutofillId(ids.get(0));
578 } else {
579 event.setAutofillIds(ids);
580 }
581
582 mHandler.sendMessage(
583 obtainMessage(MainContentCaptureSession::handleSendEvent, this, event));
Felipe Leme87a9dc92018-12-18 14:28:07 -0800584 }
585
586 void notifyViewTextChanged(@NonNull String sessionId, @NonNull AutofillId id,
Felipe Leme21e8dcb2019-01-18 09:09:45 -0800587 @Nullable CharSequence text) {
Felipe Leme87a9dc92018-12-18 14:28:07 -0800588 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
Felipe Leme21e8dcb2019-01-18 09:09:45 -0800589 new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED).setAutofillId(id)
Felipe Leme01297692019-01-29 18:16:23 -0800590 .setText(text)));
591 }
592
593 void notifyInitialViewHierarchyEvent(@NonNull String sessionId, boolean started) {
594 if (started) {
595 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
596 new ContentCaptureEvent(sessionId, TYPE_INITIAL_VIEW_TREE_APPEARING)));
597 } else {
598 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
599 new ContentCaptureEvent(sessionId, TYPE_INITIAL_VIEW_TREE_APPEARED),
600 FORCE_FLUSH));
601
602 }
Felipe Leme87a9dc92018-12-18 14:28:07 -0800603 }
604
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800605 @Override
606 void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800607 pw.print(prefix); pw.print("mContext: "); pw.println(mContext);
608 pw.print(prefix); pw.print("user: "); pw.println(mContext.getUserId());
609 if (mSystemServerInterface != null) {
610 pw.print(prefix); pw.print("mSystemServerInterface: ");
611 pw.println(mSystemServerInterface);
612 }
613 if (mDirectServiceInterface != null) {
614 pw.print(prefix); pw.print("mDirectServiceInterface: ");
615 pw.println(mDirectServiceInterface);
616 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800617 pw.print(prefix); pw.print("mDisabled: "); pw.println(mDisabled.get());
618 pw.print(prefix); pw.print("isEnabled(): "); pw.println(isContentCaptureEnabled());
Felipe Leme01b87492019-01-15 13:26:52 -0800619 pw.print(prefix); pw.print("state: "); pw.println(getStateAsString(mState));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800620 if (mApplicationToken != null) {
621 pw.print(prefix); pw.print("app token: "); pw.println(mApplicationToken);
622 }
623 if (mComponentName != null) {
624 pw.print(prefix); pw.print("component name: ");
625 pw.println(mComponentName.flattenToShortString());
626 }
627 if (mEvents != null && !mEvents.isEmpty()) {
628 final int numberEvents = mEvents.size();
629 pw.print(prefix); pw.print("buffered events: "); pw.print(numberEvents);
630 pw.print('/'); pw.println(MAX_BUFFER_SIZE);
631 if (VERBOSE && numberEvents > 0) {
632 final String prefix3 = prefix + " ";
633 for (int i = 0; i < numberEvents; i++) {
634 final ContentCaptureEvent event = mEvents.get(i);
635 pw.print(prefix3); pw.print(i); pw.print(": "); event.dump(pw);
636 pw.println();
637 }
638 }
639 pw.print(prefix); pw.print("flush frequency: "); pw.println(FLUSHING_FREQUENCY_MS);
640 pw.print(prefix); pw.print("next flush: ");
Felipe Leme1af85ea2019-01-16 13:23:40 -0800641 TimeUtils.formatDuration(mNextFlush - System.currentTimeMillis(), pw);
642 pw.print(" ("); pw.print(TimeUtils.logTimeOfDay(mNextFlush)); pw.println(")");
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800643 }
Felipe Leme1af85ea2019-01-16 13:23:40 -0800644 pw.print(prefix); pw.println("flush history:");
645 mFlushHistory.reverseDump(/* fd= */ null, pw, /* args= */ null); pw.println();
646
Felipe Leme87a9dc92018-12-18 14:28:07 -0800647 super.dump(prefix, pw);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800648 }
649
650 /**
651 * Gets a string that can be used to identify the activity on logging statements.
652 */
Felipe Lemed65692c2019-01-16 12:10:50 -0800653 private String getActivityName() {
654 return mComponentName == null
655 ? "pkg:" + mContext.getPackageName()
656 : "act:" + mComponentName.flattenToShortString();
657 }
658
Felipe Lemebe002d82019-01-23 10:22:32 -0800659 @NonNull
Felipe Lemed65692c2019-01-16 12:10:50 -0800660 private String getDebugState() {
Felipe Lemebe002d82019-01-23 10:22:32 -0800661 return getActivityName() + " [state=" + getStateAsString(mState) + ", disabled="
662 + mDisabled.get() + "]";
663 }
664
665 @NonNull
666 private String getDebugState(@FlushReason int reason) {
667 return getDebugState() + ", reason=" + getflushReasonAsString(reason);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800668 }
669}