blob: 034c8fae0843ee952bd58d5febf32f1271b96774 [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;
Felipe Lemebe002d82019-01-23 10:22:32 -080023import static android.view.contentcapture.ContentCaptureHelper.DEBUG;
24import static android.view.contentcapture.ContentCaptureHelper.VERBOSE;
25import static android.view.contentcapture.ContentCaptureHelper.getSanitizedString;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -080026
27import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
28
29import android.annotation.NonNull;
30import android.annotation.Nullable;
31import android.content.ComponentName;
32import android.content.Context;
33import android.content.pm.ParceledListSlice;
34import android.os.Bundle;
35import android.os.Handler;
36import android.os.IBinder;
37import android.os.IBinder.DeathRecipient;
38import android.os.RemoteException;
Felipe Leme1af85ea2019-01-16 13:23:40 -080039import android.util.LocalLog;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -080040import android.util.Log;
41import android.util.TimeUtils;
42import android.view.autofill.AutofillId;
43import android.view.contentcapture.ViewNode.ViewStructureImpl;
44
45import com.android.internal.os.IResultReceiver;
46
47import java.io.PrintWriter;
48import java.util.ArrayList;
49import java.util.Collections;
50import java.util.List;
51import java.util.concurrent.atomic.AtomicBoolean;
52
53/**
54 * Main session associated with a context.
55 *
56 * <p>This session is created when the activity starts and finished when it stops; clients can use
57 * it to create children activities.
58 *
59 * <p><b>NOTE: all methods in this class should return right away, or do the real work in a handler
60 * thread. Hence, the only field that must be thread-safe is {@code mEnabled}, which is called at
61 * the beginning of every method.
62 *
63 * @hide
64 */
Felipe Leme87a9dc92018-12-18 14:28:07 -080065public final class MainContentCaptureSession extends ContentCaptureSession {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -080066
Felipe Lemeaf1a0e72019-01-03 11:07:25 -080067 private static final String TAG = MainContentCaptureSession.class.getSimpleName();
68
Felipe Lemeb63e0dd2018-12-18 11:56:42 -080069 /**
70 * Handler message used to flush the buffer.
71 */
72 private static final int MSG_FLUSH = 1;
73
74 /**
75 * Maximum number of events that are buffered before sent to the app.
76 */
77 // TODO(b/121044064): use settings
78 private static final int MAX_BUFFER_SIZE = 100;
79
80 /**
81 * Frequency the buffer is flushed if stale.
82 */
83 // TODO(b/121044064): use settings
84 private static final int FLUSHING_FREQUENCY_MS = 5_000;
85
86 /**
87 * Name of the {@link IResultReceiver} extra used to pass the binder interface to the service.
88 * @hide
89 */
90 public static final String EXTRA_BINDER = "binder";
91
92 @NonNull
Felipe Leme609991d2019-01-30 16:27:24 -080093 private final AtomicBoolean mDisabled = new AtomicBoolean(false);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -080094
95 @NonNull
96 private final Context mContext;
97
98 @NonNull
Felipe Leme609991d2019-01-30 16:27:24 -080099 private final ContentCaptureManager mManager;
100
101 @NonNull
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800102 private final Handler mHandler;
103
104 /**
105 * Interface to the system_server binder object - it's only used to start the session (and
106 * notify when the session is finished).
107 */
Felipe Leme609991d2019-01-30 16:27:24 -0800108 @Nullable // TODO(b/122959591): shoul never be null, we should make main session null instead
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800109 private final IContentCaptureManager mSystemServerInterface;
110
111 /**
112 * Direct interface to the service binder object - it's used to send the events, including the
113 * last ones (when the session is finished)
114 */
115 @Nullable
116 private IContentCaptureDirectManager mDirectServiceInterface;
117 @Nullable
118 private DeathRecipient mDirectServiceVulture;
119
Felipe Leme35ea7632019-01-29 16:13:30 -0800120 private int mState = UNKNOWN_STATE;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800121
122 @Nullable
123 private IBinder mApplicationToken;
124
125 @Nullable
126 private ComponentName mComponentName;
127
128 /**
129 * List of events held to be sent as a batch.
130 */
131 @Nullable
132 private ArrayList<ContentCaptureEvent> mEvents;
133
134 // Used just for debugging purposes (on dump)
135 private long mNextFlush;
136
Felipe Leme1af85ea2019-01-16 13:23:40 -0800137 // TODO(b/121044064): use settings to set size
138 private final LocalLog mFlushHistory = new LocalLog(10);
139
Felipe Leme87a9dc92018-12-18 14:28:07 -0800140 /** @hide */
Felipe Leme609991d2019-01-30 16:27:24 -0800141 protected MainContentCaptureSession(@NonNull Context context,
142 @NonNull ContentCaptureManager manager, @NonNull Handler handler,
143 @Nullable IContentCaptureManager systemServerInterface) {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800144 mContext = context;
Felipe Leme609991d2019-01-30 16:27:24 -0800145 mManager = manager;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800146 mHandler = handler;
147 mSystemServerInterface = systemServerInterface;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800148 }
149
Felipe Leme87a9dc92018-12-18 14:28:07 -0800150 @Override
Felipe Leme4bc0f6b2019-01-03 19:01:07 -0800151 MainContentCaptureSession getMainCaptureSession() {
152 return this;
153 }
154
155 @Override
Felipe Leme87a9dc92018-12-18 14:28:07 -0800156 ContentCaptureSession newChild(@NonNull ContentCaptureContext clientContext) {
157 final ContentCaptureSession child = new ChildContentCaptureSession(this, clientContext);
158 notifyChildSessionStarted(mId, child.mId, clientContext);
159 return child;
160 }
161
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800162 /**
163 * Starts this session.
164 *
165 * @hide
166 */
Adam He328c0e32019-01-03 15:19:22 -0800167 void start(@NonNull IBinder applicationToken, @NonNull ComponentName activityComponent,
168 int flags) {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800169 if (!isContentCaptureEnabled()) return;
170
171 if (VERBOSE) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800172 Log.v(TAG, "start(): token=" + applicationToken + ", comp="
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800173 + ComponentName.flattenToShortString(activityComponent));
174 }
175
Felipe Leme87a9dc92018-12-18 14:28:07 -0800176 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleStartSession, this,
Adam He328c0e32019-01-03 15:19:22 -0800177 applicationToken, activityComponent, flags));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800178 }
179
180 @Override
Felipe Leme1af85ea2019-01-16 13:23:40 -0800181 void flush(@FlushReason int reason) {
182 mHandler.sendMessage(
183 obtainMessage(MainContentCaptureSession::handleForceFlush, this, reason));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800184 }
185
186 @Override
187 void onDestroy() {
Felipe Lemee127c9a2019-01-04 14:56:38 -0800188 mHandler.removeMessages(MSG_FLUSH);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800189 mHandler.sendMessage(
Felipe Leme87a9dc92018-12-18 14:28:07 -0800190 obtainMessage(MainContentCaptureSession::handleDestroySession, this));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800191 }
192
Adam He328c0e32019-01-03 15:19:22 -0800193 private void handleStartSession(@NonNull IBinder token, @NonNull ComponentName componentName,
194 int flags) {
Felipe Lemed65692c2019-01-16 12:10:50 -0800195 if (handleHasStarted()) {
196 // TODO(b/122959591): make sure this is expected (and when), or use Log.w
197 if (DEBUG) {
198 Log.d(TAG, "ignoring handleStartSession(" + token + "/"
199 + ComponentName.flattenToShortString(componentName) + " while on state "
200 + getStateAsString(mState));
201 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800202 return;
203 }
204 mState = STATE_WAITING_FOR_SERVER;
205 mApplicationToken = token;
206 mComponentName = componentName;
207
208 if (VERBOSE) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800209 Log.v(TAG, "handleStartSession(): token=" + token + ", act="
Felipe Lemed65692c2019-01-16 12:10:50 -0800210 + getDebugState() + ", id=" + mId);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800211 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800212
213 try {
Adam He6884fed2019-01-07 13:18:32 -0800214 if (mSystemServerInterface == null) return;
215
Felipe Lemef2aa0d22019-01-28 10:38:46 -0800216 mSystemServerInterface.startSession(mApplicationToken, componentName, mId, flags,
217 new IResultReceiver.Stub() {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800218 @Override
219 public void send(int resultCode, Bundle resultData) {
220 IBinder binder = null;
221 if (resultData != null) {
222 binder = resultData.getBinder(EXTRA_BINDER);
223 if (binder == null) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800224 Log.wtf(TAG, "No " + EXTRA_BINDER + " extra result");
Felipe Lemed65692c2019-01-16 12:10:50 -0800225 handleResetSession(STATE_DISABLED | STATE_INTERNAL_ERROR);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800226 return;
227 }
228 }
229 handleSessionStarted(resultCode, binder);
230 }
231 });
232 } catch (RemoteException e) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800233 Log.w(TAG, "Error starting session for " + componentName.flattenToShortString() + ": "
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800234 + e);
235 }
236 }
237
238 /**
239 * Callback from {@code system_server} after call to
Felipe Leme609991d2019-01-30 16:27:24 -0800240 * {@link IContentCaptureManager#startSession(IBinder, ComponentName, String, int,
241 * IResultReceiver)}
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800242 *
243 * @param resultCode session state
244 * @param binder handle to {@code IContentCaptureDirectManager}
245 */
246 private void handleSessionStarted(int resultCode, @Nullable IBinder binder) {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800247 if (binder != null) {
248 mDirectServiceInterface = IContentCaptureDirectManager.Stub.asInterface(binder);
249 mDirectServiceVulture = () -> {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800250 Log.w(TAG, "Destroying session " + mId + " because service died");
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800251 destroy();
252 };
253 try {
254 binder.linkToDeath(mDirectServiceVulture, 0);
255 } catch (RemoteException e) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800256 Log.w(TAG, "Failed to link to death on " + binder + ": " + e);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800257 }
258 }
Adam He328c0e32019-01-03 15:19:22 -0800259
Felipe Lemed65692c2019-01-16 12:10:50 -0800260 if ((resultCode & STATE_DISABLED) != 0) {
261 handleResetSession(resultCode);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800262 } else {
Felipe Lemed65692c2019-01-16 12:10:50 -0800263 mState = resultCode;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800264 mDisabled.set(false);
265 }
266 if (VERBOSE) {
Felipe Lemed65692c2019-01-16 12:10:50 -0800267 Log.v(TAG, "handleSessionStarted() result: id=" + mId + " resultCode=" + resultCode
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800268 + ", state=" + getStateAsString(mState) + ", disabled=" + mDisabled.get()
Felipe Lemee127c9a2019-01-04 14:56:38 -0800269 + ", binder=" + binder + ", events=" + (mEvents == null ? 0 : mEvents.size()));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800270 }
271 }
272
273 private void handleSendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) {
Felipe Leme1af85ea2019-01-16 13:23:40 -0800274 final int eventType = event.getType();
Felipe Lemebe002d82019-01-23 10:22:32 -0800275 if (VERBOSE) Log.v(TAG, "handleSendEvent(" + getDebugState() + "): " + event);
Felipe Leme1af85ea2019-01-16 13:23:40 -0800276 if (!handleHasStarted() && eventType != ContentCaptureEvent.TYPE_SESSION_STARTED) {
Felipe Lemed65692c2019-01-16 12:10:50 -0800277 // TODO(b/120494182): comment when this could happen (dialogs?)
278 Log.v(TAG, "handleSendEvent(" + getDebugState() + ", "
Felipe Leme1af85ea2019-01-16 13:23:40 -0800279 + ContentCaptureEvent.getTypeAsString(eventType)
Felipe Lemed65692c2019-01-16 12:10:50 -0800280 + "): session not started yet");
281 return;
282 }
Felipe Lemebe002d82019-01-23 10:22:32 -0800283 if (mDisabled.get()) {
284 // This happens when the event was queued in the handler before the sesison was ready,
285 // then handleSessionStarted() returned and set it as disabled - we need to drop it,
286 // otherwise it will keep triggering handleScheduleFlush()
287 if (VERBOSE) Log.v(TAG, "handleSendEvent(): ignoring when disabled");
288 return;
289 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800290 if (mEvents == null) {
291 if (VERBOSE) {
Felipe Lemebe002d82019-01-23 10:22:32 -0800292 Log.v(TAG, "handleSendEvent(): creating buffer for " + MAX_BUFFER_SIZE + " events");
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800293 }
294 mEvents = new ArrayList<>(MAX_BUFFER_SIZE);
295 }
Adam Heac132652019-01-02 14:40:15 -0800296
Felipe Leme48f363c2019-01-17 10:53:46 -0800297 // Some type of events can be merged together
298 boolean addEvent = true;
299
Felipe Leme1af85ea2019-01-16 13:23:40 -0800300 if (!mEvents.isEmpty() && eventType == TYPE_VIEW_TEXT_CHANGED) {
Adam Heac132652019-01-02 14:40:15 -0800301 final ContentCaptureEvent lastEvent = mEvents.get(mEvents.size() - 1);
302
303 // TODO(b/121045053): check if flags match
304 if (lastEvent.getType() == TYPE_VIEW_TEXT_CHANGED
305 && lastEvent.getId().equals(event.getId())) {
306 if (VERBOSE) {
Felipe Lemebe002d82019-01-23 10:22:32 -0800307 Log.v(TAG, "Buffering VIEW_TEXT_CHANGED event, updated text="
308 + getSanitizedString(event.getText()));
Adam Heac132652019-01-02 14:40:15 -0800309 }
310 lastEvent.setText(event.getText());
Felipe Leme48f363c2019-01-17 10:53:46 -0800311 addEvent = false;
Adam Heac132652019-01-02 14:40:15 -0800312 }
Felipe Leme48f363c2019-01-17 10:53:46 -0800313 }
314
315 if (!mEvents.isEmpty() && eventType == TYPE_VIEW_DISAPPEARED) {
316 final ContentCaptureEvent lastEvent = mEvents.get(mEvents.size() - 1);
317 if (lastEvent.getType() == TYPE_VIEW_DISAPPEARED
318 && event.getSessionId().equals(lastEvent.getSessionId())) {
319 if (VERBOSE) {
320 Log.v(TAG, "Buffering TYPE_VIEW_DISAPPEARED events for session "
321 + lastEvent.getSessionId());
322 }
323 lastEvent.addAutofillId(event.getId());
324 addEvent = false;
325 }
326 }
327
328 if (addEvent) {
Adam Heac132652019-01-02 14:40:15 -0800329 mEvents.add(event);
330 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800331
332 final int numberEvents = mEvents.size();
333
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800334 final boolean bufferEvent = numberEvents < MAX_BUFFER_SIZE;
335
336 if (bufferEvent && !forceFlush) {
Felipe Leme1af85ea2019-01-16 13:23:40 -0800337 handleScheduleFlush(FLUSH_REASON_IDLE_TIMEOUT, /* checkExisting= */ true);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800338 return;
339 }
340
Felipe Lemee127c9a2019-01-04 14:56:38 -0800341 if (mState != STATE_ACTIVE && numberEvents >= MAX_BUFFER_SIZE) {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800342 // Callback from startSession hasn't been called yet - typically happens on system
343 // apps that are started before the system service
Felipe Lemed65692c2019-01-16 12:10:50 -0800344 // TODO(b/122959591): try to ignore session while system is not ready / boot
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800345 // not complete instead. Similarly, the manager service should return right away
346 // when the user does not have a service set
Felipe Lemee127c9a2019-01-04 14:56:38 -0800347 if (DEBUG) {
Felipe Lemed65692c2019-01-16 12:10:50 -0800348 Log.d(TAG, "Closing session for " + getDebugState()
349 + " after " + numberEvents + " delayed events");
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800350 }
Felipe Lemed65692c2019-01-16 12:10:50 -0800351 handleResetSession(STATE_DISABLED | STATE_NO_RESPONSE);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800352 // TODO(b/111276913): blacklist activity / use special flag to indicate that
353 // when it's launched again
354 return;
355 }
Felipe Leme1af85ea2019-01-16 13:23:40 -0800356 final int flushReason;
357 switch (eventType) {
358 case ContentCaptureEvent.TYPE_SESSION_STARTED:
359 flushReason = FLUSH_REASON_SESSION_STARTED;
360 break;
361 case ContentCaptureEvent.TYPE_SESSION_FINISHED:
362 flushReason = FLUSH_REASON_SESSION_FINISHED;
363 break;
364 default:
365 flushReason = FLUSH_REASON_FULL;
366 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800367
Felipe Leme1af85ea2019-01-16 13:23:40 -0800368 handleForceFlush(flushReason);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800369 }
370
Felipe Lemed65692c2019-01-16 12:10:50 -0800371 private boolean handleHasStarted() {
Felipe Leme35ea7632019-01-29 16:13:30 -0800372 return mState != UNKNOWN_STATE;
Felipe Lemed65692c2019-01-16 12:10:50 -0800373 }
374
Felipe Leme1af85ea2019-01-16 13:23:40 -0800375 private void handleScheduleFlush(@FlushReason int reason, boolean checkExisting) {
Felipe Lemebe002d82019-01-23 10:22:32 -0800376 if (VERBOSE) {
377 Log.v(TAG, "handleScheduleFlush(" + getDebugState(reason)
378 + ", checkExisting=" + checkExisting);
379 }
Felipe Lemed65692c2019-01-16 12:10:50 -0800380 if (!handleHasStarted()) {
Felipe Lemebe002d82019-01-23 10:22:32 -0800381 if (VERBOSE) Log.v(TAG, "handleScheduleFlush(): session not started yet");
382 return;
383 }
384
385 if (mDisabled.get()) {
386 // Should not be called on this state, as handleSendEvent checks.
387 // But we rather add one if check and log than re-schedule and keep the session alive...
388 Log.e(TAG, "handleScheduleFlush(" + getDebugState(reason) + "): should not be called "
389 + "when disabled. events=" + (mEvents == null ? null : mEvents.size()));
Felipe Lemed65692c2019-01-16 12:10:50 -0800390 return;
391 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800392 if (checkExisting && mHandler.hasMessages(MSG_FLUSH)) {
393 // "Renew" the flush message by removing the previous one
394 mHandler.removeMessages(MSG_FLUSH);
395 }
Felipe Leme1af85ea2019-01-16 13:23:40 -0800396 mNextFlush = System.currentTimeMillis() + FLUSHING_FREQUENCY_MS;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800397 if (VERBOSE) {
Felipe Lemebe002d82019-01-23 10:22:32 -0800398 Log.v(TAG, "handleScheduleFlush(): scheduled to flush in "
Felipe Leme1af85ea2019-01-16 13:23:40 -0800399 + FLUSHING_FREQUENCY_MS + "ms: " + TimeUtils.logTimeOfDay(mNextFlush));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800400 }
401 mHandler.sendMessageDelayed(
Felipe Leme1af85ea2019-01-16 13:23:40 -0800402 obtainMessage(MainContentCaptureSession::handleFlushIfNeeded, this, reason)
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800403 .setWhat(MSG_FLUSH), FLUSHING_FREQUENCY_MS);
404 }
405
Felipe Leme1af85ea2019-01-16 13:23:40 -0800406 private void handleFlushIfNeeded(@FlushReason int reason) {
Felipe Leme3d570a42019-01-28 13:04:34 -0800407 if (mEvents == null || mEvents.isEmpty()) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800408 if (VERBOSE) Log.v(TAG, "Nothing to flush");
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800409 return;
410 }
Felipe Leme1af85ea2019-01-16 13:23:40 -0800411 handleForceFlush(reason);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800412 }
413
Felipe Leme1af85ea2019-01-16 13:23:40 -0800414 private void handleForceFlush(@FlushReason int reason) {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800415 if (mEvents == null) return;
416
Felipe Lemebe002d82019-01-23 10:22:32 -0800417 if (mDisabled.get()) {
418 Log.e(TAG, "handleForceFlush(" + getDebugState(reason) + "): should not be when "
419 + "disabled");
420 return;
421 }
422
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800423 if (mDirectServiceInterface == null) {
Felipe Lemee127c9a2019-01-04 14:56:38 -0800424 if (VERBOSE) {
Felipe Lemebe002d82019-01-23 10:22:32 -0800425 Log.v(TAG, "handleForceFlush(" + getDebugState(reason) + "): hold your horses, "
426 + "client not ready: " + mEvents);
Felipe Lemee127c9a2019-01-04 14:56:38 -0800427 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800428 if (!mHandler.hasMessages(MSG_FLUSH)) {
Felipe Leme1af85ea2019-01-16 13:23:40 -0800429 handleScheduleFlush(reason, /* checkExisting= */ false);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800430 }
431 return;
432 }
433
434 final int numberEvents = mEvents.size();
Felipe Leme1af85ea2019-01-16 13:23:40 -0800435 final String reasonString = getflushReasonAsString(reason);
436 if (DEBUG) {
Felipe Lemebe002d82019-01-23 10:22:32 -0800437 Log.d(TAG, "Flushing " + numberEvents + " event(s) for " + getDebugState(reason));
Felipe Leme1af85ea2019-01-16 13:23:40 -0800438 }
439 // Logs reason, size, max size, idle timeout
440 final String logRecord = "r=" + reasonString + " s=" + numberEvents
441 + " m=" + MAX_BUFFER_SIZE + " i=" + FLUSHING_FREQUENCY_MS;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800442 try {
Felipe Leme1af85ea2019-01-16 13:23:40 -0800443 mFlushHistory.log(logRecord);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800444 mHandler.removeMessages(MSG_FLUSH);
445
446 final ParceledListSlice<ContentCaptureEvent> events = handleClearEvents();
Felipe Lemefc24bea2018-12-18 13:19:01 -0800447 mDirectServiceInterface.sendEvents(events);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800448 } catch (RemoteException e) {
Felipe Lemed65692c2019-01-16 12:10:50 -0800449 Log.w(TAG, "Error sending " + numberEvents + " for " + getDebugState()
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800450 + ": " + e);
451 }
452 }
453
454 /**
455 * Resets the buffer and return a {@link ParceledListSlice} with the previous events.
456 */
457 @NonNull
458 private ParceledListSlice<ContentCaptureEvent> handleClearEvents() {
459 // NOTE: we must save a reference to the current mEvents and then set it to to null,
460 // otherwise clearing it would clear it in the receiving side if the service is also local.
461 final List<ContentCaptureEvent> events = mEvents == null
462 ? Collections.emptyList()
463 : mEvents;
464 mEvents = null;
465 return new ParceledListSlice<>(events);
466 }
467
468 private void handleDestroySession() {
469 if (DEBUG) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800470 Log.d(TAG, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with "
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800471 + (mEvents == null ? 0 : mEvents.size()) + " event(s) for "
Felipe Lemed65692c2019-01-16 12:10:50 -0800472 + getDebugState());
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800473 }
474
475 try {
Adam He6884fed2019-01-07 13:18:32 -0800476 if (mSystemServerInterface == null) return;
477
Felipe Lemef2aa0d22019-01-28 10:38:46 -0800478 mSystemServerInterface.finishSession(mId);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800479 } catch (RemoteException e) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800480 Log.e(TAG, "Error destroying system-service session " + mId + " for "
Felipe Lemed65692c2019-01-16 12:10:50 -0800481 + getDebugState() + ": " + e);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800482 }
483 }
484
Felipe Leme4bc0f6b2019-01-03 19:01:07 -0800485 // TODO(b/122454205): once we support multiple sessions, we might need to move some of these
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800486 // clearings out.
Felipe Lemed65692c2019-01-16 12:10:50 -0800487 private void handleResetSession(int newState) {
488 if (VERBOSE) {
489 Log.v(TAG, "handleResetSession(" + getActivityName() + "): from "
490 + getStateAsString(mState) + " to " + getStateAsString(newState));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800491 }
Felipe Lemed65692c2019-01-16 12:10:50 -0800492 mState = newState;
493 mDisabled.set((newState & STATE_DISABLED) != 0);
Felipe Leme4bc0f6b2019-01-03 19:01:07 -0800494 // TODO(b/122454205): must reset children (which currently is owned by superclass)
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800495 mApplicationToken = null;
496 mComponentName = null;
497 mEvents = null;
498 if (mDirectServiceInterface != null) {
499 mDirectServiceInterface.asBinder().unlinkToDeath(mDirectServiceVulture, 0);
500 }
501 mDirectServiceInterface = null;
502 mHandler.removeMessages(MSG_FLUSH);
503 }
504
505 @Override
506 void internalNotifyViewAppeared(@NonNull ViewStructureImpl node) {
Felipe Leme87a9dc92018-12-18 14:28:07 -0800507 notifyViewAppeared(mId, node);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800508 }
509
510 @Override
511 void internalNotifyViewDisappeared(@NonNull AutofillId id) {
Felipe Leme87a9dc92018-12-18 14:28:07 -0800512 notifyViewDisappeared(mId, id);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800513 }
514
515 @Override
Felipe Leme21e8dcb2019-01-18 09:09:45 -0800516 void internalNotifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text) {
517 notifyViewTextChanged(mId, id, text);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800518 }
519
520 @Override
521 boolean isContentCaptureEnabled() {
Felipe Leme609991d2019-01-30 16:27:24 -0800522 return super.isContentCaptureEnabled() && mManager.isContentCaptureEnabled();
523 }
524
525 // Called by ContentCaptureManager.isContentCaptureEnabled
526 boolean isDisabled() {
527 return mDisabled.get();
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800528 }
529
Felipe Leme4bc0f6b2019-01-03 19:01:07 -0800530 // TODO(b/122454205): refactor "notifyXXXX" methods below to a common "Buffer" object that is
Felipe Leme87a9dc92018-12-18 14:28:07 -0800531 // shared between ActivityContentCaptureSession and ChildContentCaptureSession objects. Such
532 // change should also get get rid of the "internalNotifyXXXX" methods above
533 void notifyChildSessionStarted(@NonNull String parentSessionId,
534 @NonNull String childSessionId, @NonNull ContentCaptureContext clientContext) {
535 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
536 new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED)
537 .setParentSessionId(parentSessionId)
538 .setClientContext(clientContext),
Felipe Lemee127c9a2019-01-04 14:56:38 -0800539 /* forceFlush= */ true));
Felipe Leme87a9dc92018-12-18 14:28:07 -0800540 }
541
542 void notifyChildSessionFinished(@NonNull String parentSessionId,
543 @NonNull String childSessionId) {
544 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
545 new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED)
Felipe Lemee127c9a2019-01-04 14:56:38 -0800546 .setParentSessionId(parentSessionId), /* forceFlush= */ true));
Felipe Leme87a9dc92018-12-18 14:28:07 -0800547 }
548
549 void notifyViewAppeared(@NonNull String sessionId, @NonNull ViewStructureImpl node) {
550 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
551 new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED)
552 .setViewNode(node.mNode), /* forceFlush= */ false));
553 }
554
555 void notifyViewDisappeared(@NonNull String sessionId, @NonNull AutofillId id) {
556 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
557 new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED).setAutofillId(id),
558 /* forceFlush= */ false));
559 }
560
561 void notifyViewTextChanged(@NonNull String sessionId, @NonNull AutofillId id,
Felipe Leme21e8dcb2019-01-18 09:09:45 -0800562 @Nullable CharSequence text) {
Felipe Leme87a9dc92018-12-18 14:28:07 -0800563 mHandler.sendMessage(obtainMessage(MainContentCaptureSession::handleSendEvent, this,
Felipe Leme21e8dcb2019-01-18 09:09:45 -0800564 new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED).setAutofillId(id)
Felipe Leme87a9dc92018-12-18 14:28:07 -0800565 .setText(text), /* forceFlush= */ false));
566 }
567
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800568 @Override
569 void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800570 pw.print(prefix); pw.print("mContext: "); pw.println(mContext);
571 pw.print(prefix); pw.print("user: "); pw.println(mContext.getUserId());
572 if (mSystemServerInterface != null) {
573 pw.print(prefix); pw.print("mSystemServerInterface: ");
574 pw.println(mSystemServerInterface);
575 }
576 if (mDirectServiceInterface != null) {
577 pw.print(prefix); pw.print("mDirectServiceInterface: ");
578 pw.println(mDirectServiceInterface);
579 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800580 pw.print(prefix); pw.print("mDisabled: "); pw.println(mDisabled.get());
581 pw.print(prefix); pw.print("isEnabled(): "); pw.println(isContentCaptureEnabled());
Felipe Leme01b87492019-01-15 13:26:52 -0800582 pw.print(prefix); pw.print("state: "); pw.println(getStateAsString(mState));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800583 if (mApplicationToken != null) {
584 pw.print(prefix); pw.print("app token: "); pw.println(mApplicationToken);
585 }
586 if (mComponentName != null) {
587 pw.print(prefix); pw.print("component name: ");
588 pw.println(mComponentName.flattenToShortString());
589 }
590 if (mEvents != null && !mEvents.isEmpty()) {
591 final int numberEvents = mEvents.size();
592 pw.print(prefix); pw.print("buffered events: "); pw.print(numberEvents);
593 pw.print('/'); pw.println(MAX_BUFFER_SIZE);
594 if (VERBOSE && numberEvents > 0) {
595 final String prefix3 = prefix + " ";
596 for (int i = 0; i < numberEvents; i++) {
597 final ContentCaptureEvent event = mEvents.get(i);
598 pw.print(prefix3); pw.print(i); pw.print(": "); event.dump(pw);
599 pw.println();
600 }
601 }
602 pw.print(prefix); pw.print("flush frequency: "); pw.println(FLUSHING_FREQUENCY_MS);
603 pw.print(prefix); pw.print("next flush: ");
Felipe Leme1af85ea2019-01-16 13:23:40 -0800604 TimeUtils.formatDuration(mNextFlush - System.currentTimeMillis(), pw);
605 pw.print(" ("); pw.print(TimeUtils.logTimeOfDay(mNextFlush)); pw.println(")");
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800606 }
Felipe Leme1af85ea2019-01-16 13:23:40 -0800607 pw.print(prefix); pw.println("flush history:");
608 mFlushHistory.reverseDump(/* fd= */ null, pw, /* args= */ null); pw.println();
609
Felipe Leme87a9dc92018-12-18 14:28:07 -0800610 super.dump(prefix, pw);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800611 }
612
613 /**
614 * Gets a string that can be used to identify the activity on logging statements.
615 */
Felipe Lemed65692c2019-01-16 12:10:50 -0800616 private String getActivityName() {
617 return mComponentName == null
618 ? "pkg:" + mContext.getPackageName()
619 : "act:" + mComponentName.flattenToShortString();
620 }
621
Felipe Lemebe002d82019-01-23 10:22:32 -0800622 @NonNull
Felipe Lemed65692c2019-01-16 12:10:50 -0800623 private String getDebugState() {
Felipe Lemebe002d82019-01-23 10:22:32 -0800624 return getActivityName() + " [state=" + getStateAsString(mState) + ", disabled="
625 + mDisabled.get() + "]";
626 }
627
628 @NonNull
629 private String getDebugState(@FlushReason int reason) {
630 return getDebugState() + ", reason=" + getflushReasonAsString(reason);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800631 }
632}