blob: cee79439d1b3dfed34f1ce3703a059a5a2807937 [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 Leme4eecbe62019-02-11 17:50:17 -080018import static android.view.contentcapture.ContentCaptureEvent.TYPE_CONTEXT_UPDATED;
Felipe Leme87a9dc92018-12-18 14:28:07 -080019import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_FINISHED;
Felipe Lemeb0da18f2019-02-22 15:10:02 -080020import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_PAUSED;
21import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_RESUMED;
Felipe Leme87a9dc92018-12-18 14:28:07 -080022import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -080023import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_APPEARED;
24import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED;
25import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED;
Felipe Lemeb0da18f2019-02-22 15:10:02 -080026import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TREE_APPEARED;
27import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TREE_APPEARING;
Felipe Lemebe002d82019-01-23 10:22:32 -080028import static android.view.contentcapture.ContentCaptureHelper.getSanitizedString;
Felipe Lemed32d8f6f2019-02-15 10:25:33 -080029import static android.view.contentcapture.ContentCaptureHelper.sDebug;
30import static android.view.contentcapture.ContentCaptureHelper.sVerbose;
Adam He6c0afca2019-04-15 13:54:11 -070031import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_FALSE;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -080032
Felipe Lemeb63e0dd2018-12-18 11:56:42 -080033import android.annotation.NonNull;
34import android.annotation.Nullable;
Felipe Leme3fe6e922019-02-04 17:52:27 -080035import android.annotation.UiThread;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -080036import android.content.ComponentName;
37import android.content.Context;
38import android.content.pm.ParceledListSlice;
39import android.os.Bundle;
40import android.os.Handler;
41import android.os.IBinder;
42import android.os.IBinder.DeathRecipient;
43import android.os.RemoteException;
Felipe Leme1af85ea2019-01-16 13:23:40 -080044import android.util.LocalLog;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -080045import android.util.Log;
46import android.util.TimeUtils;
47import android.view.autofill.AutofillId;
48import android.view.contentcapture.ViewNode.ViewStructureImpl;
49
50import com.android.internal.os.IResultReceiver;
51
52import java.io.PrintWriter;
53import java.util.ArrayList;
54import java.util.Collections;
55import java.util.List;
56import java.util.concurrent.atomic.AtomicBoolean;
57
58/**
59 * Main session associated with a context.
60 *
61 * <p>This session is created when the activity starts and finished when it stops; clients can use
62 * it to create children activities.
63 *
Felipe Lemeb63e0dd2018-12-18 11:56:42 -080064 * @hide
65 */
Felipe Leme87a9dc92018-12-18 14:28:07 -080066public final class MainContentCaptureSession extends ContentCaptureSession {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -080067
Felipe Lemeaf1a0e72019-01-03 11:07:25 -080068 private static final String TAG = MainContentCaptureSession.class.getSimpleName();
69
Felipe Lemed32d8f6f2019-02-15 10:25:33 -080070 // For readability purposes...
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
Felipe Lemeb63e0dd2018-12-18 11:56:42 -080078 /**
79 * Name of the {@link IResultReceiver} extra used to pass the binder interface to the service.
80 * @hide
81 */
82 public static final String EXTRA_BINDER = "binder";
83
Adam He6c0afca2019-04-15 13:54:11 -070084 /**
85 * Name of the {@link IResultReceiver} extra used to pass the content capture enabled state.
86 * @hide
87 */
88 public static final String EXTRA_ENABLED_STATE = "enabled";
89
Felipe Lemeb63e0dd2018-12-18 11:56:42 -080090 @NonNull
Felipe Leme609991d2019-01-30 16:27:24 -080091 private final AtomicBoolean mDisabled = new AtomicBoolean(false);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -080092
93 @NonNull
94 private final Context mContext;
95
96 @NonNull
Felipe Leme609991d2019-01-30 16:27:24 -080097 private final ContentCaptureManager mManager;
98
99 @NonNull
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800100 private final Handler mHandler;
101
102 /**
103 * Interface to the system_server binder object - it's only used to start the session (and
104 * notify when the session is finished).
105 */
Felipe Lemed49d52c2019-02-15 09:48:20 -0800106 @NonNull
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800107 private final IContentCaptureManager mSystemServerInterface;
108
109 /**
110 * Direct interface to the service binder object - it's used to send the events, including the
111 * last ones (when the session is finished)
112 */
Felipe Lemed49d52c2019-02-15 09:48:20 -0800113 @NonNull
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800114 private IContentCaptureDirectManager mDirectServiceInterface;
115 @Nullable
116 private DeathRecipient mDirectServiceVulture;
117
Felipe Leme35ea7632019-01-29 16:13:30 -0800118 private int mState = UNKNOWN_STATE;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800119
120 @Nullable
121 private IBinder mApplicationToken;
122
123 @Nullable
124 private ComponentName mComponentName;
125
126 /**
127 * List of events held to be sent as a batch.
128 */
129 @Nullable
130 private ArrayList<ContentCaptureEvent> mEvents;
131
132 // Used just for debugging purposes (on dump)
133 private long mNextFlush;
134
Adam Heba9f64d2019-03-08 14:21:28 -0800135 /**
136 * Whether the next buffer flush is queued by a text changed event.
137 */
138 private boolean mNextFlushForTextChanged = false;
139
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800140 @Nullable
141 private final LocalLog mFlushHistory;
Felipe Leme1af85ea2019-01-16 13:23:40 -0800142
Felipe Leme518fb622019-03-08 15:35:04 -0800143 /**
144 * Binder object used to update the session state.
145 */
146 @NonNull
147 private final IResultReceiver.Stub mSessionStateReceiver;
148
Felipe Leme609991d2019-01-30 16:27:24 -0800149 protected MainContentCaptureSession(@NonNull Context context,
150 @NonNull ContentCaptureManager manager, @NonNull Handler handler,
Felipe Lemed49d52c2019-02-15 09:48:20 -0800151 @NonNull IContentCaptureManager systemServerInterface) {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800152 mContext = context;
Felipe Leme609991d2019-01-30 16:27:24 -0800153 mManager = manager;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800154 mHandler = handler;
155 mSystemServerInterface = systemServerInterface;
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800156
Felipe Leme326f15a2019-02-19 09:42:24 -0800157 final int logHistorySize = mManager.mOptions.logHistorySize;
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800158 mFlushHistory = logHistorySize > 0 ? new LocalLog(logHistorySize) : null;
Felipe Leme518fb622019-03-08 15:35:04 -0800159
160 mSessionStateReceiver = new IResultReceiver.Stub() {
161 @Override
162 public void send(int resultCode, Bundle resultData) {
163 final IBinder binder;
164 if (resultData != null) {
Adam He6c0afca2019-04-15 13:54:11 -0700165 // Change in content capture enabled.
166 final boolean hasEnabled = resultData.getBoolean(EXTRA_ENABLED_STATE);
167 if (hasEnabled) {
168 final boolean disabled = (resultCode == RESULT_CODE_FALSE);
169 mDisabled.set(disabled);
170 return;
171 }
Felipe Leme518fb622019-03-08 15:35:04 -0800172 binder = resultData.getBinder(EXTRA_BINDER);
173 if (binder == null) {
174 Log.wtf(TAG, "No " + EXTRA_BINDER + " extra result");
175 mHandler.post(() -> resetSession(
176 STATE_DISABLED | STATE_INTERNAL_ERROR));
177 return;
178 }
179 } else {
180 binder = null;
181 }
182 mHandler.post(() -> onSessionStarted(resultCode, binder));
183 }
184 };
185
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800186 }
187
Felipe Leme87a9dc92018-12-18 14:28:07 -0800188 @Override
Felipe Leme4bc0f6b2019-01-03 19:01:07 -0800189 MainContentCaptureSession getMainCaptureSession() {
190 return this;
191 }
192
193 @Override
Felipe Leme87a9dc92018-12-18 14:28:07 -0800194 ContentCaptureSession newChild(@NonNull ContentCaptureContext clientContext) {
195 final ContentCaptureSession child = new ChildContentCaptureSession(this, clientContext);
196 notifyChildSessionStarted(mId, child.mId, clientContext);
197 return child;
198 }
199
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800200 /**
201 * Starts this session.
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800202 */
Felipe Leme3fe6e922019-02-04 17:52:27 -0800203 @UiThread
204 void start(@NonNull IBinder token, @NonNull ComponentName component,
Adam He328c0e32019-01-03 15:19:22 -0800205 int flags) {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800206 if (!isContentCaptureEnabled()) return;
207
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800208 if (sVerbose) {
Felipe Leme3fe6e922019-02-04 17:52:27 -0800209 Log.v(TAG, "start(): token=" + token + ", comp="
210 + ComponentName.flattenToShortString(component));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800211 }
212
Felipe Leme3fe6e922019-02-04 17:52:27 -0800213 if (hasStarted()) {
Felipe Lemed65692c2019-01-16 12:10:50 -0800214 // TODO(b/122959591): make sure this is expected (and when), or use Log.w
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800215 if (sDebug) {
Felipe Lemed65692c2019-01-16 12:10:50 -0800216 Log.d(TAG, "ignoring handleStartSession(" + token + "/"
Felipe Leme3fe6e922019-02-04 17:52:27 -0800217 + ComponentName.flattenToShortString(component) + " while on state "
Felipe Lemed65692c2019-01-16 12:10:50 -0800218 + getStateAsString(mState));
219 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800220 return;
221 }
222 mState = STATE_WAITING_FOR_SERVER;
223 mApplicationToken = token;
Felipe Leme3fe6e922019-02-04 17:52:27 -0800224 mComponentName = component;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800225
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800226 if (sVerbose) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800227 Log.v(TAG, "handleStartSession(): token=" + token + ", act="
Felipe Lemed65692c2019-01-16 12:10:50 -0800228 + getDebugState() + ", id=" + mId);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800229 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800230
231 try {
Felipe Leme3fe6e922019-02-04 17:52:27 -0800232 mSystemServerInterface.startSession(mApplicationToken, component, mId, flags,
Felipe Leme518fb622019-03-08 15:35:04 -0800233 mSessionStateReceiver);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800234 } catch (RemoteException e) {
Felipe Leme3fe6e922019-02-04 17:52:27 -0800235 Log.w(TAG, "Error starting session for " + component.flattenToShortString() + ": " + e);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800236 }
237 }
238
Felipe Leme3fe6e922019-02-04 17:52:27 -0800239 @Override
240 void onDestroy() {
241 mHandler.removeMessages(MSG_FLUSH);
242 mHandler.post(() -> destroySession());
243 }
244
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800245 /**
246 * Callback from {@code system_server} after call to
Felipe Leme2f288432019-03-21 13:57:45 -0700247 * {@link IContentCaptureManager#startSession(IBinder, ComponentName, String, int,
248 * IResultReceiver)}.
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800249 *
250 * @param resultCode session state
251 * @param binder handle to {@code IContentCaptureDirectManager}
252 */
Felipe Leme3fe6e922019-02-04 17:52:27 -0800253 @UiThread
254 private void onSessionStarted(int resultCode, @Nullable IBinder binder) {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800255 if (binder != null) {
256 mDirectServiceInterface = IContentCaptureDirectManager.Stub.asInterface(binder);
257 mDirectServiceVulture = () -> {
Felipe Leme518fb622019-03-08 15:35:04 -0800258 Log.w(TAG, "Keeping session " + mId + " when service died");
259 mState = STATE_SERVICE_DIED;
260 mDisabled.set(true);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800261 };
262 try {
263 binder.linkToDeath(mDirectServiceVulture, 0);
264 } catch (RemoteException e) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800265 Log.w(TAG, "Failed to link to death on " + binder + ": " + e);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800266 }
267 }
Adam He328c0e32019-01-03 15:19:22 -0800268
Felipe Lemed65692c2019-01-16 12:10:50 -0800269 if ((resultCode & STATE_DISABLED) != 0) {
Felipe Leme3fe6e922019-02-04 17:52:27 -0800270 resetSession(resultCode);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800271 } else {
Felipe Lemed65692c2019-01-16 12:10:50 -0800272 mState = resultCode;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800273 mDisabled.set(false);
274 }
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800275 if (sVerbose) {
Felipe Lemed65692c2019-01-16 12:10:50 -0800276 Log.v(TAG, "handleSessionStarted() result: id=" + mId + " resultCode=" + resultCode
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800277 + ", state=" + getStateAsString(mState) + ", disabled=" + mDisabled.get()
Felipe Lemee127c9a2019-01-04 14:56:38 -0800278 + ", binder=" + binder + ", events=" + (mEvents == null ? 0 : mEvents.size()));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800279 }
280 }
281
Felipe Leme3fe6e922019-02-04 17:52:27 -0800282 @UiThread
283 private void sendEvent(@NonNull ContentCaptureEvent event) {
284 sendEvent(event, /* forceFlush= */ false);
Felipe Leme01297692019-01-29 18:16:23 -0800285 }
286
Felipe Leme3fe6e922019-02-04 17:52:27 -0800287 @UiThread
288 private void sendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) {
Felipe Leme1af85ea2019-01-16 13:23:40 -0800289 final int eventType = event.getType();
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800290 if (sVerbose) Log.v(TAG, "handleSendEvent(" + getDebugState() + "): " + event);
Felipe Leme4eecbe62019-02-11 17:50:17 -0800291 if (!hasStarted() && eventType != ContentCaptureEvent.TYPE_SESSION_STARTED
292 && eventType != ContentCaptureEvent.TYPE_CONTEXT_UPDATED) {
Felipe Lemed65692c2019-01-16 12:10:50 -0800293 // TODO(b/120494182): comment when this could happen (dialogs?)
294 Log.v(TAG, "handleSendEvent(" + getDebugState() + ", "
Felipe Leme1af85ea2019-01-16 13:23:40 -0800295 + ContentCaptureEvent.getTypeAsString(eventType)
Felipe Leme4eecbe62019-02-11 17:50:17 -0800296 + "): dropping because session not started yet");
Felipe Lemed65692c2019-01-16 12:10:50 -0800297 return;
298 }
Felipe Lemebe002d82019-01-23 10:22:32 -0800299 if (mDisabled.get()) {
300 // This happens when the event was queued in the handler before the sesison was ready,
301 // then handleSessionStarted() returned and set it as disabled - we need to drop it,
302 // otherwise it will keep triggering handleScheduleFlush()
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800303 if (sVerbose) Log.v(TAG, "handleSendEvent(): ignoring when disabled");
Felipe Lemebe002d82019-01-23 10:22:32 -0800304 return;
305 }
Felipe Leme326f15a2019-02-19 09:42:24 -0800306 final int maxBufferSize = mManager.mOptions.maxBufferSize;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800307 if (mEvents == null) {
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800308 if (sVerbose) {
Felipe Leme326f15a2019-02-19 09:42:24 -0800309 Log.v(TAG, "handleSendEvent(): creating buffer for " + maxBufferSize + " events");
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800310 }
Felipe Leme326f15a2019-02-19 09:42:24 -0800311 mEvents = new ArrayList<>(maxBufferSize);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800312 }
Adam Heac132652019-01-02 14:40:15 -0800313
Felipe Leme48f363c2019-01-17 10:53:46 -0800314 // Some type of events can be merged together
315 boolean addEvent = true;
316
Felipe Leme1af85ea2019-01-16 13:23:40 -0800317 if (!mEvents.isEmpty() && eventType == TYPE_VIEW_TEXT_CHANGED) {
Adam Heac132652019-01-02 14:40:15 -0800318 final ContentCaptureEvent lastEvent = mEvents.get(mEvents.size() - 1);
319
320 // TODO(b/121045053): check if flags match
321 if (lastEvent.getType() == TYPE_VIEW_TEXT_CHANGED
322 && lastEvent.getId().equals(event.getId())) {
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800323 if (sVerbose) {
Felipe Lemebe002d82019-01-23 10:22:32 -0800324 Log.v(TAG, "Buffering VIEW_TEXT_CHANGED event, updated text="
325 + getSanitizedString(event.getText()));
Adam Heac132652019-01-02 14:40:15 -0800326 }
Adam He0ce47192019-03-11 11:21:16 -0700327 lastEvent.mergeEvent(event);
Felipe Leme48f363c2019-01-17 10:53:46 -0800328 addEvent = false;
Adam Heac132652019-01-02 14:40:15 -0800329 }
Felipe Leme48f363c2019-01-17 10:53:46 -0800330 }
331
332 if (!mEvents.isEmpty() && eventType == TYPE_VIEW_DISAPPEARED) {
333 final ContentCaptureEvent lastEvent = mEvents.get(mEvents.size() - 1);
334 if (lastEvent.getType() == TYPE_VIEW_DISAPPEARED
Felipe Leme08054202019-03-28 11:29:25 -0700335 && event.getSessionId() == lastEvent.getSessionId()) {
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800336 if (sVerbose) {
Felipe Leme48f363c2019-01-17 10:53:46 -0800337 Log.v(TAG, "Buffering TYPE_VIEW_DISAPPEARED events for session "
338 + lastEvent.getSessionId());
339 }
Adam He0ce47192019-03-11 11:21:16 -0700340 lastEvent.mergeEvent(event);
Felipe Leme48f363c2019-01-17 10:53:46 -0800341 addEvent = false;
342 }
343 }
344
345 if (addEvent) {
Adam Heac132652019-01-02 14:40:15 -0800346 mEvents.add(event);
347 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800348
349 final int numberEvents = mEvents.size();
350
Felipe Leme326f15a2019-02-19 09:42:24 -0800351 final boolean bufferEvent = numberEvents < maxBufferSize;
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800352
353 if (bufferEvent && !forceFlush) {
Adam Heba9f64d2019-03-08 14:21:28 -0800354 final int flushReason;
355 if (eventType == TYPE_VIEW_TEXT_CHANGED) {
356 mNextFlushForTextChanged = true;
357 flushReason = FLUSH_REASON_TEXT_CHANGE_TIMEOUT;
358 } else {
359 if (mNextFlushForTextChanged) {
360 if (sVerbose) {
361 Log.i(TAG, "Not scheduling flush because next flush is for text changed");
362 }
363 return;
364 }
365
366 flushReason = FLUSH_REASON_IDLE_TIMEOUT;
367 }
368 scheduleFlush(flushReason, /* checkExisting= */ true);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800369 return;
370 }
371
Felipe Leme326f15a2019-02-19 09:42:24 -0800372 if (mState != STATE_ACTIVE && numberEvents >= maxBufferSize) {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800373 // Callback from startSession hasn't been called yet - typically happens on system
374 // apps that are started before the system service
Felipe Lemed65692c2019-01-16 12:10:50 -0800375 // TODO(b/122959591): try to ignore session while system is not ready / boot
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800376 // not complete instead. Similarly, the manager service should return right away
377 // when the user does not have a service set
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800378 if (sDebug) {
Felipe Lemed65692c2019-01-16 12:10:50 -0800379 Log.d(TAG, "Closing session for " + getDebugState()
380 + " after " + numberEvents + " delayed events");
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800381 }
Felipe Leme3fe6e922019-02-04 17:52:27 -0800382 resetSession(STATE_DISABLED | STATE_NO_RESPONSE);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800383 // TODO(b/111276913): blacklist activity / use special flag to indicate that
384 // when it's launched again
385 return;
386 }
Felipe Leme1af85ea2019-01-16 13:23:40 -0800387 final int flushReason;
388 switch (eventType) {
389 case ContentCaptureEvent.TYPE_SESSION_STARTED:
390 flushReason = FLUSH_REASON_SESSION_STARTED;
391 break;
392 case ContentCaptureEvent.TYPE_SESSION_FINISHED:
393 flushReason = FLUSH_REASON_SESSION_FINISHED;
394 break;
395 default:
396 flushReason = FLUSH_REASON_FULL;
397 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800398
Felipe Leme3fe6e922019-02-04 17:52:27 -0800399 flush(flushReason);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800400 }
401
Felipe Leme3fe6e922019-02-04 17:52:27 -0800402 @UiThread
403 private boolean hasStarted() {
Felipe Leme35ea7632019-01-29 16:13:30 -0800404 return mState != UNKNOWN_STATE;
Felipe Lemed65692c2019-01-16 12:10:50 -0800405 }
406
Felipe Leme3fe6e922019-02-04 17:52:27 -0800407 @UiThread
408 private void scheduleFlush(@FlushReason int reason, boolean checkExisting) {
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800409 if (sVerbose) {
Felipe Lemebe002d82019-01-23 10:22:32 -0800410 Log.v(TAG, "handleScheduleFlush(" + getDebugState(reason)
411 + ", checkExisting=" + checkExisting);
412 }
Felipe Leme3fe6e922019-02-04 17:52:27 -0800413 if (!hasStarted()) {
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800414 if (sVerbose) Log.v(TAG, "handleScheduleFlush(): session not started yet");
Felipe Lemebe002d82019-01-23 10:22:32 -0800415 return;
416 }
417
418 if (mDisabled.get()) {
419 // Should not be called on this state, as handleSendEvent checks.
420 // But we rather add one if check and log than re-schedule and keep the session alive...
421 Log.e(TAG, "handleScheduleFlush(" + getDebugState(reason) + "): should not be called "
422 + "when disabled. events=" + (mEvents == null ? null : mEvents.size()));
Felipe Lemed65692c2019-01-16 12:10:50 -0800423 return;
424 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800425 if (checkExisting && mHandler.hasMessages(MSG_FLUSH)) {
426 // "Renew" the flush message by removing the previous one
427 mHandler.removeMessages(MSG_FLUSH);
428 }
Adam Heba9f64d2019-03-08 14:21:28 -0800429
430 final int flushFrequencyMs;
Adam Hed93ed17d2019-06-18 16:51:57 -0700431 if (reason == FLUSH_REASON_TEXT_CHANGE_TIMEOUT) {
Adam Heba9f64d2019-03-08 14:21:28 -0800432 flushFrequencyMs = mManager.mOptions.textChangeFlushingFrequencyMs;
433 } else {
Adam Hed93ed17d2019-06-18 16:51:57 -0700434 if (reason != FLUSH_REASON_IDLE_TIMEOUT) {
435 if (sDebug) {
436 Log.d(TAG, "handleScheduleFlush(" + getDebugState(reason) + "): not a timeout "
437 + "reason because mDirectServiceInterface is not ready yet");
438 }
439 }
440 flushFrequencyMs = mManager.mOptions.idleFlushingFrequencyMs;
Adam Heba9f64d2019-03-08 14:21:28 -0800441 }
442
443 mNextFlush = System.currentTimeMillis() + flushFrequencyMs;
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800444 if (sVerbose) {
Felipe Lemebe002d82019-01-23 10:22:32 -0800445 Log.v(TAG, "handleScheduleFlush(): scheduled to flush in "
Adam Heba9f64d2019-03-08 14:21:28 -0800446 + flushFrequencyMs + "ms: " + TimeUtils.logTimeOfDay(mNextFlush));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800447 }
Felipe Leme3fe6e922019-02-04 17:52:27 -0800448 // Post using a Runnable directly to trim a few μs from PooledLambda.obtainMessage()
Adam Heba9f64d2019-03-08 14:21:28 -0800449 mHandler.postDelayed(() -> flushIfNeeded(reason), MSG_FLUSH, flushFrequencyMs);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800450 }
451
Felipe Leme3fe6e922019-02-04 17:52:27 -0800452 @UiThread
453 private void flushIfNeeded(@FlushReason int reason) {
Felipe Leme3d570a42019-01-28 13:04:34 -0800454 if (mEvents == null || mEvents.isEmpty()) {
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800455 if (sVerbose) Log.v(TAG, "Nothing to flush");
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800456 return;
457 }
Felipe Leme3fe6e922019-02-04 17:52:27 -0800458 flush(reason);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800459 }
460
Felipe Leme3fe6e922019-02-04 17:52:27 -0800461 @Override
462 @UiThread
463 void flush(@FlushReason int reason) {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800464 if (mEvents == null) return;
465
Felipe Lemebe002d82019-01-23 10:22:32 -0800466 if (mDisabled.get()) {
467 Log.e(TAG, "handleForceFlush(" + getDebugState(reason) + "): should not be when "
468 + "disabled");
469 return;
470 }
471
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800472 if (mDirectServiceInterface == null) {
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800473 if (sVerbose) {
Felipe Lemebe002d82019-01-23 10:22:32 -0800474 Log.v(TAG, "handleForceFlush(" + getDebugState(reason) + "): hold your horses, "
475 + "client not ready: " + mEvents);
Felipe Lemee127c9a2019-01-04 14:56:38 -0800476 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800477 if (!mHandler.hasMessages(MSG_FLUSH)) {
Felipe Leme3fe6e922019-02-04 17:52:27 -0800478 scheduleFlush(reason, /* checkExisting= */ false);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800479 }
480 return;
481 }
482
483 final int numberEvents = mEvents.size();
Felipe Lemed58c1ea2019-02-21 11:22:45 -0800484 final String reasonString = getFlushReasonAsString(reason);
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800485 if (sDebug) {
Felipe Lemebe002d82019-01-23 10:22:32 -0800486 Log.d(TAG, "Flushing " + numberEvents + " event(s) for " + getDebugState(reason));
Felipe Leme1af85ea2019-01-16 13:23:40 -0800487 }
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800488 if (mFlushHistory != null) {
489 // Logs reason, size, max size, idle timeout
490 final String logRecord = "r=" + reasonString + " s=" + numberEvents
Felipe Leme326f15a2019-02-19 09:42:24 -0800491 + " m=" + mManager.mOptions.maxBufferSize
492 + " i=" + mManager.mOptions.idleFlushingFrequencyMs;
Felipe Leme1af85ea2019-01-16 13:23:40 -0800493 mFlushHistory.log(logRecord);
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800494 }
495 try {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800496 mHandler.removeMessages(MSG_FLUSH);
497
Adam Heba9f64d2019-03-08 14:21:28 -0800498 if (reason == FLUSH_REASON_TEXT_CHANGE_TIMEOUT) {
499 mNextFlushForTextChanged = false;
500 }
501
Felipe Leme3fe6e922019-02-04 17:52:27 -0800502 final ParceledListSlice<ContentCaptureEvent> events = clearEvents();
Adam Heff2185342019-01-23 15:59:09 -0800503 mDirectServiceInterface.sendEvents(events, reason, mManager.mOptions);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800504 } catch (RemoteException e) {
Felipe Lemed65692c2019-01-16 12:10:50 -0800505 Log.w(TAG, "Error sending " + numberEvents + " for " + getDebugState()
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800506 + ": " + e);
507 }
508 }
509
Felipe Leme4eecbe62019-02-11 17:50:17 -0800510 @Override
511 public void updateContentCaptureContext(@Nullable ContentCaptureContext context) {
512 notifyContextUpdated(mId, context);
513 }
514
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800515 /**
516 * Resets the buffer and return a {@link ParceledListSlice} with the previous events.
517 */
518 @NonNull
Felipe Leme3fe6e922019-02-04 17:52:27 -0800519 @UiThread
520 private ParceledListSlice<ContentCaptureEvent> clearEvents() {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800521 // NOTE: we must save a reference to the current mEvents and then set it to to null,
522 // otherwise clearing it would clear it in the receiving side if the service is also local.
523 final List<ContentCaptureEvent> events = mEvents == null
524 ? Collections.emptyList()
525 : mEvents;
526 mEvents = null;
527 return new ParceledListSlice<>(events);
528 }
529
Felipe Leme3fe6e922019-02-04 17:52:27 -0800530 @UiThread
531 private void destroySession() {
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800532 if (sDebug) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800533 Log.d(TAG, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with "
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800534 + (mEvents == null ? 0 : mEvents.size()) + " event(s) for "
Felipe Lemed65692c2019-01-16 12:10:50 -0800535 + getDebugState());
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800536 }
537
538 try {
Felipe Lemef2aa0d22019-01-28 10:38:46 -0800539 mSystemServerInterface.finishSession(mId);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800540 } catch (RemoteException e) {
Felipe Lemeaf1a0e72019-01-03 11:07:25 -0800541 Log.e(TAG, "Error destroying system-service session " + mId + " for "
Felipe Lemed65692c2019-01-16 12:10:50 -0800542 + getDebugState() + ": " + e);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800543 }
544 }
545
Felipe Leme4bc0f6b2019-01-03 19:01:07 -0800546 // TODO(b/122454205): once we support multiple sessions, we might need to move some of these
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800547 // clearings out.
Felipe Leme3fe6e922019-02-04 17:52:27 -0800548 @UiThread
549 private void resetSession(int newState) {
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800550 if (sVerbose) {
Felipe Lemed65692c2019-01-16 12:10:50 -0800551 Log.v(TAG, "handleResetSession(" + getActivityName() + "): from "
552 + getStateAsString(mState) + " to " + getStateAsString(newState));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800553 }
Felipe Lemed65692c2019-01-16 12:10:50 -0800554 mState = newState;
555 mDisabled.set((newState & STATE_DISABLED) != 0);
Felipe Leme4bc0f6b2019-01-03 19:01:07 -0800556 // TODO(b/122454205): must reset children (which currently is owned by superclass)
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800557 mApplicationToken = null;
558 mComponentName = null;
559 mEvents = null;
560 if (mDirectServiceInterface != null) {
561 mDirectServiceInterface.asBinder().unlinkToDeath(mDirectServiceVulture, 0);
562 }
563 mDirectServiceInterface = null;
564 mHandler.removeMessages(MSG_FLUSH);
565 }
566
567 @Override
568 void internalNotifyViewAppeared(@NonNull ViewStructureImpl node) {
Felipe Leme87a9dc92018-12-18 14:28:07 -0800569 notifyViewAppeared(mId, node);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800570 }
571
572 @Override
573 void internalNotifyViewDisappeared(@NonNull AutofillId id) {
Felipe Leme87a9dc92018-12-18 14:28:07 -0800574 notifyViewDisappeared(mId, id);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800575 }
576
577 @Override
Felipe Leme21e8dcb2019-01-18 09:09:45 -0800578 void internalNotifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text) {
579 notifyViewTextChanged(mId, id, text);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800580 }
581
582 @Override
Felipe Lemeb0da18f2019-02-22 15:10:02 -0800583 public void internalNotifyViewTreeEvent(boolean started) {
584 notifyViewTreeEvent(mId, started);
Felipe Leme01297692019-01-29 18:16:23 -0800585 }
586
587 @Override
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800588 boolean isContentCaptureEnabled() {
Felipe Leme609991d2019-01-30 16:27:24 -0800589 return super.isContentCaptureEnabled() && mManager.isContentCaptureEnabled();
590 }
591
592 // Called by ContentCaptureManager.isContentCaptureEnabled
593 boolean isDisabled() {
594 return mDisabled.get();
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800595 }
596
Adam He6c0afca2019-04-15 13:54:11 -0700597 /**
Adam He43c06992019-04-15 15:41:49 -0700598 * Sets the disabled state of content capture.
Adam He6c0afca2019-04-15 13:54:11 -0700599 *
600 * @return whether disabled state was changed.
601 */
602 boolean setDisabled(boolean disabled) {
603 return mDisabled.compareAndSet(!disabled, disabled);
604 }
605
Felipe Leme4bc0f6b2019-01-03 19:01:07 -0800606 // TODO(b/122454205): refactor "notifyXXXX" methods below to a common "Buffer" object that is
Felipe Leme87a9dc92018-12-18 14:28:07 -0800607 // shared between ActivityContentCaptureSession and ChildContentCaptureSession objects. Such
608 // change should also get get rid of the "internalNotifyXXXX" methods above
Felipe Leme08054202019-03-28 11:29:25 -0700609 void notifyChildSessionStarted(int parentSessionId, int childSessionId,
610 @NonNull ContentCaptureContext clientContext) {
Felipe Leme3fe6e922019-02-04 17:52:27 -0800611 sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED)
612 .setParentSessionId(parentSessionId).setClientContext(clientContext),
613 FORCE_FLUSH);
Felipe Leme87a9dc92018-12-18 14:28:07 -0800614 }
615
Felipe Leme08054202019-03-28 11:29:25 -0700616 void notifyChildSessionFinished(int parentSessionId, int childSessionId) {
Felipe Leme3fe6e922019-02-04 17:52:27 -0800617 sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED)
618 .setParentSessionId(parentSessionId), FORCE_FLUSH);
Felipe Leme87a9dc92018-12-18 14:28:07 -0800619 }
620
Felipe Leme08054202019-03-28 11:29:25 -0700621 void notifyViewAppeared(int sessionId, @NonNull ViewStructureImpl node) {
Felipe Leme3fe6e922019-02-04 17:52:27 -0800622 sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED)
623 .setViewNode(node.mNode));
Felipe Leme87a9dc92018-12-18 14:28:07 -0800624 }
625
Felipe Leme544b39c2019-02-22 09:26:29 -0800626 /** Public because is also used by ViewRootImpl */
Felipe Leme08054202019-03-28 11:29:25 -0700627 public void notifyViewDisappeared(int sessionId, @NonNull AutofillId id) {
Felipe Leme544b39c2019-02-22 09:26:29 -0800628 sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED).setAutofillId(id));
Felipe Leme87a9dc92018-12-18 14:28:07 -0800629 }
630
Felipe Leme08054202019-03-28 11:29:25 -0700631 void notifyViewTextChanged(int sessionId, @NonNull AutofillId id, @Nullable CharSequence text) {
Felipe Leme3fe6e922019-02-04 17:52:27 -0800632 sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED).setAutofillId(id)
633 .setText(text));
Felipe Leme01297692019-01-29 18:16:23 -0800634 }
635
Felipe Leme544b39c2019-02-22 09:26:29 -0800636 /** Public because is also used by ViewRootImpl */
Felipe Leme08054202019-03-28 11:29:25 -0700637 public void notifyViewTreeEvent(int sessionId, boolean started) {
Felipe Lemeb0da18f2019-02-22 15:10:02 -0800638 final int type = started ? TYPE_VIEW_TREE_APPEARING : TYPE_VIEW_TREE_APPEARED;
Felipe Leme544b39c2019-02-22 09:26:29 -0800639 sendEvent(new ContentCaptureEvent(sessionId, type), FORCE_FLUSH);
Felipe Leme87a9dc92018-12-18 14:28:07 -0800640 }
641
Felipe Lemeb0da18f2019-02-22 15:10:02 -0800642 /** Public because is also used by ViewRootImpl */
643 public void notifySessionLifecycle(boolean started) {
644 final int type = started ? TYPE_SESSION_RESUMED : TYPE_SESSION_PAUSED;
645 sendEvent(new ContentCaptureEvent(mId, type), FORCE_FLUSH);
646 }
647
Felipe Leme08054202019-03-28 11:29:25 -0700648 void notifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) {
Felipe Leme4eecbe62019-02-11 17:50:17 -0800649 sendEvent(new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED)
650 .setClientContext(context));
651 }
652
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800653 @Override
654 void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
Felipe Lemed49d52c2019-02-15 09:48:20 -0800655 super.dump(prefix, pw);
656
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800657 pw.print(prefix); pw.print("mContext: "); pw.println(mContext);
658 pw.print(prefix); pw.print("user: "); pw.println(mContext.getUserId());
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800659 if (mDirectServiceInterface != null) {
660 pw.print(prefix); pw.print("mDirectServiceInterface: ");
661 pw.println(mDirectServiceInterface);
662 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800663 pw.print(prefix); pw.print("mDisabled: "); pw.println(mDisabled.get());
664 pw.print(prefix); pw.print("isEnabled(): "); pw.println(isContentCaptureEnabled());
Felipe Leme01b87492019-01-15 13:26:52 -0800665 pw.print(prefix); pw.print("state: "); pw.println(getStateAsString(mState));
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800666 if (mApplicationToken != null) {
667 pw.print(prefix); pw.print("app token: "); pw.println(mApplicationToken);
668 }
669 if (mComponentName != null) {
670 pw.print(prefix); pw.print("component name: ");
671 pw.println(mComponentName.flattenToShortString());
672 }
673 if (mEvents != null && !mEvents.isEmpty()) {
674 final int numberEvents = mEvents.size();
675 pw.print(prefix); pw.print("buffered events: "); pw.print(numberEvents);
Felipe Leme326f15a2019-02-19 09:42:24 -0800676 pw.print('/'); pw.println(mManager.mOptions.maxBufferSize);
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800677 if (sVerbose && numberEvents > 0) {
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800678 final String prefix3 = prefix + " ";
679 for (int i = 0; i < numberEvents; i++) {
680 final ContentCaptureEvent event = mEvents.get(i);
681 pw.print(prefix3); pw.print(i); pw.print(": "); event.dump(pw);
682 pw.println();
683 }
684 }
Adam Heba9f64d2019-03-08 14:21:28 -0800685 pw.print(prefix); pw.print("mNextFlushForTextChanged: ");
686 pw.println(mNextFlushForTextChanged);
Felipe Leme326f15a2019-02-19 09:42:24 -0800687 pw.print(prefix); pw.print("flush frequency: ");
Adam Heba9f64d2019-03-08 14:21:28 -0800688 if (mNextFlushForTextChanged) {
689 pw.println(mManager.mOptions.textChangeFlushingFrequencyMs);
690 } else {
691 pw.println(mManager.mOptions.idleFlushingFrequencyMs);
692 }
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800693 pw.print(prefix); pw.print("next flush: ");
Felipe Leme1af85ea2019-01-16 13:23:40 -0800694 TimeUtils.formatDuration(mNextFlush - System.currentTimeMillis(), pw);
695 pw.print(" ("); pw.print(TimeUtils.logTimeOfDay(mNextFlush)); pw.println(")");
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800696 }
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800697 if (mFlushHistory != null) {
698 pw.print(prefix); pw.println("flush history:");
699 mFlushHistory.reverseDump(/* fd= */ null, pw, /* args= */ null); pw.println();
700 } else {
701 pw.print(prefix); pw.println("not logging flush history");
702 }
703
704 super.dump(prefix, pw);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800705 }
706
707 /**
708 * Gets a string that can be used to identify the activity on logging statements.
709 */
Felipe Lemed65692c2019-01-16 12:10:50 -0800710 private String getActivityName() {
711 return mComponentName == null
712 ? "pkg:" + mContext.getPackageName()
713 : "act:" + mComponentName.flattenToShortString();
714 }
715
Felipe Lemebe002d82019-01-23 10:22:32 -0800716 @NonNull
Felipe Lemed65692c2019-01-16 12:10:50 -0800717 private String getDebugState() {
Felipe Lemebe002d82019-01-23 10:22:32 -0800718 return getActivityName() + " [state=" + getStateAsString(mState) + ", disabled="
719 + mDisabled.get() + "]";
720 }
721
722 @NonNull
723 private String getDebugState(@FlushReason int reason) {
Felipe Lemed58c1ea2019-02-21 11:22:45 -0800724 return getDebugState() + ", reason=" + getFlushReasonAsString(reason);
Felipe Lemeb63e0dd2018-12-18 11:56:42 -0800725 }
726}