blob: cc0264ac0bc9493533dae2672130de019224e2cd [file] [log] [blame]
Felipe Leme1dfa9a02018-10-17 17:24:37 -07001/*
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 */
Felipe Leme749b8892018-12-03 16:30:30 -080016package android.view.contentcapture;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070017
Felipe Leme749b8892018-12-03 16:30:30 -080018import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_APPEARED;
19import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED;
20import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED;
Felipe Leme88eae3b2018-11-07 15:11:56 -080021
22import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
23
Felipe Leme1dfa9a02018-10-17 17:24:37 -070024import android.annotation.NonNull;
25import android.annotation.Nullable;
Felipe Lemee348dc32018-11-05 12:35:29 -080026import android.annotation.SystemService;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070027import android.content.ComponentName;
28import android.content.Context;
Felipe Lemee348dc32018-11-05 12:35:29 -080029import android.os.Bundle;
Felipe Leme88eae3b2018-11-07 15:11:56 -080030import android.os.Handler;
31import android.os.HandlerThread;
Felipe Lemee348dc32018-11-05 12:35:29 -080032import android.os.IBinder;
33import android.os.RemoteException;
Felipe Leme4017b202018-12-10 12:13:31 -080034import android.os.SystemClock;
Felipe Lemee348dc32018-11-05 12:35:29 -080035import android.util.Log;
Felipe Leme4017b202018-12-10 12:13:31 -080036import android.util.TimeUtils;
Felipe Leme88eae3b2018-11-07 15:11:56 -080037import android.view.View;
38import android.view.ViewStructure;
39import android.view.autofill.AutofillId;
Felipe Leme749b8892018-12-03 16:30:30 -080040import android.view.contentcapture.ContentCaptureEvent.EventType;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070041
Felipe Lemee348dc32018-11-05 12:35:29 -080042import com.android.internal.os.IResultReceiver;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070043import com.android.internal.util.Preconditions;
44
Felipe Lemee348dc32018-11-05 12:35:29 -080045import java.io.PrintWriter;
Felipe Leme88eae3b2018-11-07 15:11:56 -080046import java.util.ArrayList;
Felipe Leme749b8892018-12-03 16:30:30 -080047import java.util.UUID;
Felipe Lemeb18e3172018-11-27 10:33:41 -080048import java.util.concurrent.atomic.AtomicBoolean;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070049
Felipe Lemeb18e3172018-11-27 10:33:41 -080050/*
51 * NOTE: all methods in this class should return right away, or do the real work in a handler
52 * thread.
53 *
54 * Hence, the only field that must be thread-safe is mEnabled, which is called at the beginning
55 * of every method.
56 */
Felipe Lemeecb08be2018-11-27 15:48:47 -080057/**
58 * TODO(b/111276913): add javadocs / implement
59 */
60@SystemService(Context.CONTENT_CAPTURE_MANAGER_SERVICE)
61public final class ContentCaptureManager {
Felipe Leme1dfa9a02018-10-17 17:24:37 -070062
Felipe Leme749b8892018-12-03 16:30:30 -080063 private static final String TAG = ContentCaptureManager.class.getSimpleName();
Felipe Lemee348dc32018-11-05 12:35:29 -080064
Felipe Leme88eae3b2018-11-07 15:11:56 -080065 // TODO(b/111276913): define a way to dynamically set them(for example, using settings?)
Felipe Lemee348dc32018-11-05 12:35:29 -080066 private static final boolean VERBOSE = false;
Felipe Leme88eae3b2018-11-07 15:11:56 -080067 private static final boolean DEBUG = true; // STOPSHIP if not set to false
Felipe Lemee348dc32018-11-05 12:35:29 -080068
Felipe Leme1dfa9a02018-10-17 17:24:37 -070069 /**
70 * Used to indicate that a text change was caused by user input (for example, through IME).
71 */
72 //TODO(b/111276913): link to notifyTextChanged() method once available
73 public static final int FLAG_USER_INPUT = 0x1;
74
Felipe Lemee348dc32018-11-05 12:35:29 -080075 /**
76 * Initial state, when there is no session.
77 *
78 * @hide
79 */
80 public static final int STATE_UNKNOWN = 0;
81
82 /**
Felipe Lemea7bdb142018-11-05 16:29:29 -080083 * Service's startSession() was called, but server didn't confirm it was created yet.
Felipe Lemee348dc32018-11-05 12:35:29 -080084 *
85 * @hide
86 */
Felipe Lemea7bdb142018-11-05 16:29:29 -080087 public static final int STATE_WAITING_FOR_SERVER = 1;
Felipe Lemee348dc32018-11-05 12:35:29 -080088
89 /**
90 * Session is active.
91 *
92 * @hide
93 */
94 public static final int STATE_ACTIVE = 2;
95
Felipe Leme8e2e3412018-11-14 10:39:29 -080096 /**
97 * Session is disabled.
98 *
99 * @hide
100 */
101 public static final int STATE_DISABLED = 3;
102
Felipe Leme4017b202018-12-10 12:13:31 -0800103 /**
104 * Handler message used to flush the buffer.
105 */
106 private static final int MSG_FLUSH = 1;
107
108
Felipe Leme88eae3b2018-11-07 15:11:56 -0800109 private static final String BG_THREAD_NAME = "intel_svc_streamer_thread";
110
111 /**
Felipe Lemeb18e3172018-11-27 10:33:41 -0800112 * Maximum number of events that are buffered before sent to the app.
Felipe Leme88eae3b2018-11-07 15:11:56 -0800113 */
Felipe Lemeb18e3172018-11-27 10:33:41 -0800114 // TODO(b/111276913): use settings
115 private static final int MAX_BUFFER_SIZE = 100;
Felipe Leme88eae3b2018-11-07 15:11:56 -0800116
Felipe Leme4017b202018-12-10 12:13:31 -0800117 /**
118 * Frequency the buffer is flushed if stale.
119 */
120 // TODO(b/111276913): use settings
121 private static final int FLUSHING_FREQUENCY_MS = 5_000;
122
Felipe Lemeb18e3172018-11-27 10:33:41 -0800123 @NonNull
124 private final AtomicBoolean mDisabled = new AtomicBoolean();
125
126 @NonNull
Felipe Lemee348dc32018-11-05 12:35:29 -0800127 private final Context mContext;
128
129 @Nullable
Felipe Leme749b8892018-12-03 16:30:30 -0800130 private final IContentCaptureManager mService;
Felipe Lemee348dc32018-11-05 12:35:29 -0800131
Felipe Lemea7bdb142018-11-05 16:29:29 -0800132 @Nullable
Felipe Leme749b8892018-12-03 16:30:30 -0800133 private String mId;
Felipe Lemee348dc32018-11-05 12:35:29 -0800134
Felipe Lemee348dc32018-11-05 12:35:29 -0800135 private int mState = STATE_UNKNOWN;
136
Felipe Lemeb18e3172018-11-27 10:33:41 -0800137 @Nullable
Felipe Lemee348dc32018-11-05 12:35:29 -0800138 private IBinder mApplicationToken;
139
Felipe Lemeb18e3172018-11-27 10:33:41 -0800140 @Nullable
Felipe Lemee348dc32018-11-05 12:35:29 -0800141 private ComponentName mComponentName;
142
Felipe Leme88eae3b2018-11-07 15:11:56 -0800143 /**
144 * List of events held to be sent as a batch.
145 */
Felipe Lemeb18e3172018-11-27 10:33:41 -0800146 @Nullable
147 private ArrayList<ContentCaptureEvent> mEvents;
Felipe Leme88eae3b2018-11-07 15:11:56 -0800148
Felipe Lemeb18e3172018-11-27 10:33:41 -0800149 // TODO(b/111276913): use UI Thread directly (as calls are one-way) or a shared thread / handler
150 // held at the Application level
Felipe Leme88eae3b2018-11-07 15:11:56 -0800151 private final Handler mHandler;
152
Felipe Leme4017b202018-12-10 12:13:31 -0800153 // Used just for debugging purposes (on dump)
154 private long mNextFlush;
155
Felipe Lemee348dc32018-11-05 12:35:29 -0800156 /** @hide */
Felipe Leme749b8892018-12-03 16:30:30 -0800157 public ContentCaptureManager(@NonNull Context context,
158 @Nullable IContentCaptureManager service) {
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700159 mContext = Preconditions.checkNotNull(context, "context cannot be null");
Felipe Lemeb18e3172018-11-27 10:33:41 -0800160 if (VERBOSE) {
161 Log.v(TAG, "Constructor for " + context.getPackageName());
162 }
Felipe Lemee348dc32018-11-05 12:35:29 -0800163 mService = service;
Felipe Leme88eae3b2018-11-07 15:11:56 -0800164 // TODO(b/111276913): use an existing bg thread instead...
165 final HandlerThread bgThread = new HandlerThread(BG_THREAD_NAME);
166 bgThread.start();
167 mHandler = Handler.createAsync(bgThread.getLooper());
Felipe Lemee348dc32018-11-05 12:35:29 -0800168 }
169
170 /** @hide */
171 public void onActivityCreated(@NonNull IBinder token, @NonNull ComponentName componentName) {
172 if (!isContentCaptureEnabled()) return;
173
Felipe Lemeecb08be2018-11-27 15:48:47 -0800174 mHandler.sendMessage(obtainMessage(ContentCaptureManager::handleStartSession, this,
Felipe Lemeb18e3172018-11-27 10:33:41 -0800175 token, componentName));
176 }
Felipe Lemee348dc32018-11-05 12:35:29 -0800177
Felipe Lemeb18e3172018-11-27 10:33:41 -0800178 private void handleStartSession(@NonNull IBinder token, @NonNull ComponentName componentName) {
179 if (mState != STATE_UNKNOWN) {
180 // TODO(b/111276913): revisit this scenario
181 Log.w(TAG, "ignoring handleStartSession(" + token + ") while on state "
182 + getStateAsString(mState));
183 return;
184 }
185 mState = STATE_WAITING_FOR_SERVER;
Felipe Leme749b8892018-12-03 16:30:30 -0800186 mId = UUID.randomUUID().toString();
Felipe Lemeb18e3172018-11-27 10:33:41 -0800187 mApplicationToken = token;
188 mComponentName = componentName;
Felipe Lemee348dc32018-11-05 12:35:29 -0800189
Felipe Lemeb18e3172018-11-27 10:33:41 -0800190 if (VERBOSE) {
191 Log.v(TAG, "handleStartSession(): token=" + token + ", act="
192 + getActivityDebugName() + ", id=" + mId);
193 }
194 final int flags = 0; // TODO(b/111276913): get proper flags
195
196 try {
197 mService.startSession(mContext.getUserId(), mApplicationToken, componentName,
198 mId, flags, new IResultReceiver.Stub() {
199 @Override
200 public void send(int resultCode, Bundle resultData) {
201 handleSessionStarted(resultCode);
202 }
203 });
204 } catch (RemoteException e) {
205 Log.w(TAG, "Error starting session for " + componentName.flattenToShortString() + ": "
206 + e);
Felipe Lemee348dc32018-11-05 12:35:29 -0800207 }
208 }
209
Felipe Leme39233ff2018-11-28 16:54:23 -0800210 private void handleSessionStarted(int resultCode) {
Felipe Lemeb18e3172018-11-27 10:33:41 -0800211 mState = resultCode;
212 mDisabled.set(mState == STATE_DISABLED);
213 if (VERBOSE) {
214 Log.v(TAG, "onActivityStarted() result: code=" + resultCode + ", id=" + mId
215 + ", state=" + getStateAsString(mState) + ", disabled=" + mDisabled.get());
216 }
Felipe Leme88eae3b2018-11-07 15:11:56 -0800217 }
218
Felipe Lemeb18e3172018-11-27 10:33:41 -0800219 private void handleSendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) {
220 if (mEvents == null) {
221 if (VERBOSE) {
222 Log.v(TAG, "Creating buffer for " + MAX_BUFFER_SIZE + " events");
Felipe Leme88eae3b2018-11-07 15:11:56 -0800223 }
Felipe Lemeb18e3172018-11-27 10:33:41 -0800224 mEvents = new ArrayList<>(MAX_BUFFER_SIZE);
225 }
226 mEvents.add(event);
Felipe Leme4017b202018-12-10 12:13:31 -0800227
Felipe Lemeb18e3172018-11-27 10:33:41 -0800228 final int numberEvents = mEvents.size();
Felipe Leme4017b202018-12-10 12:13:31 -0800229
230 // TODO(b/120784831): need to optimize it so we buffer changes until a number of X are
231 // buffered (either total or per autofillid). For
232 // example, if the user typed "a", "b", "c" and the threshold is 3, we should buffer
233 // "a" and "b" then send "abc".
234 final boolean bufferEvent = numberEvents < MAX_BUFFER_SIZE;
235
236 if (bufferEvent && !forceFlush) {
237 handleScheduleFlush();
Felipe Lemeb18e3172018-11-27 10:33:41 -0800238 return;
239 }
Felipe Leme88eae3b2018-11-07 15:11:56 -0800240
Felipe Lemeb18e3172018-11-27 10:33:41 -0800241 if (mState != STATE_ACTIVE) {
242 // Callback from startSession hasn't been called yet - typically happens on system
243 // apps that are started before the system service
244 // TODO(b/111276913): try to ignore session while system is not ready / boot
245 // not complete instead. Similarly, the manager service should return right away
246 // when the user does not have a service set
247 if (VERBOSE) {
248 Log.v(TAG, "Closing session for " + getActivityDebugName()
249 + " after " + numberEvents + " delayed events and state "
250 + getStateAsString(mState));
Felipe Leme88eae3b2018-11-07 15:11:56 -0800251 }
Felipe Lemeb18e3172018-11-27 10:33:41 -0800252 handleResetState();
253 // TODO(b/111276913): blacklist activity / use special flag to indicate that
254 // when it's launched again
255 return;
256 }
Felipe Leme88eae3b2018-11-07 15:11:56 -0800257
Felipe Lemeb18e3172018-11-27 10:33:41 -0800258 if (mId == null) {
259 // Sanity check - should not happen
260 Log.wtf(TAG, "null session id for " + getActivityDebugName());
261 return;
262 }
263
Felipe Leme4017b202018-12-10 12:13:31 -0800264 handleForceFlush();
265 }
266
267 private void handleScheduleFlush() {
268 if (mHandler.hasMessages(MSG_FLUSH)) {
269 // "Renew" the flush message by removing the previous one
270 mHandler.removeMessages(MSG_FLUSH);
271 }
272 mNextFlush = SystemClock.elapsedRealtime() + FLUSHING_FREQUENCY_MS;
273 if (VERBOSE) {
274 Log.v(TAG, "Scheduled to flush in " + FLUSHING_FREQUENCY_MS + "ms: " + mNextFlush);
275 }
276 mHandler.sendMessageDelayed(
277 obtainMessage(ContentCaptureManager::handleFlushIfNeeded, this).setWhat(MSG_FLUSH),
278 FLUSHING_FREQUENCY_MS);
279 }
280
281 private void handleFlushIfNeeded() {
282 if (mEvents.isEmpty()) {
283 if (VERBOSE) Log.v(TAG, "Nothing to flush");
284 return;
285 }
286 handleForceFlush();
287 }
288
289 private void handleForceFlush() {
290 final int numberEvents = mEvents.size();
Felipe Lemeb18e3172018-11-27 10:33:41 -0800291 try {
292 if (DEBUG) {
293 Log.d(TAG, "Flushing " + numberEvents + " event(s) for " + getActivityDebugName());
Felipe Leme88eae3b2018-11-07 15:11:56 -0800294 }
Felipe Leme4017b202018-12-10 12:13:31 -0800295 mHandler.removeMessages(MSG_FLUSH);
Felipe Lemeb18e3172018-11-27 10:33:41 -0800296 mService.sendEvents(mContext.getUserId(), mId, mEvents);
297 // TODO(b/111276913): decide whether we should clear or set it to null, as each has
298 // its own advantages: clearing will save extra allocations while the session is
299 // active, while setting to null would save memory if there's no more event coming.
300 mEvents.clear();
301 } catch (RemoteException e) {
302 Log.w(TAG, "Error sending " + numberEvents + " for " + getActivityDebugName()
303 + ": " + e);
Felipe Leme88eae3b2018-11-07 15:11:56 -0800304 }
305 }
306
Felipe Leme7a534082018-11-05 15:03:04 -0800307 /**
308 * Used for intermediate events (i.e, other than created and destroyed).
309 *
310 * @hide
311 */
312 public void onActivityLifecycleEvent(@EventType int type) {
313 if (!isContentCaptureEnabled()) return;
Felipe Leme88eae3b2018-11-07 15:11:56 -0800314 if (VERBOSE) {
Felipe Lemeb18e3172018-11-27 10:33:41 -0800315 Log.v(TAG, "onActivityLifecycleEvent() for " + getActivityDebugName()
Felipe Leme88eae3b2018-11-07 15:11:56 -0800316 + ": " + ContentCaptureEvent.getTypeAsString(type));
Felipe Leme7a534082018-11-05 15:03:04 -0800317 }
Felipe Lemeecb08be2018-11-27 15:48:47 -0800318 mHandler.sendMessage(obtainMessage(ContentCaptureManager::handleSendEvent, this,
Felipe Lemeb18e3172018-11-27 10:33:41 -0800319 new ContentCaptureEvent(type), /* forceFlush= */ true));
Felipe Leme7a534082018-11-05 15:03:04 -0800320 }
321
Felipe Lemee348dc32018-11-05 12:35:29 -0800322 /** @hide */
323 public void onActivityDestroyed() {
324 if (!isContentCaptureEnabled()) return;
325
Felipe Lemeb18e3172018-11-27 10:33:41 -0800326 //TODO(b/111276913): check state (for example, how to handle if it's waiting for remote
327 // id) and send it to the cache of batched commands
328 if (VERBOSE) {
329 Log.v(TAG, "onActivityDestroyed(): state=" + getStateAsString(mState)
330 + ", mId=" + mId);
331 }
Felipe Lemee348dc32018-11-05 12:35:29 -0800332
Felipe Lemeecb08be2018-11-27 15:48:47 -0800333 mHandler.sendMessage(obtainMessage(ContentCaptureManager::handleFinishSession, this));
Felipe Lemeb18e3172018-11-27 10:33:41 -0800334 }
335
336 private void handleFinishSession() {
337 //TODO(b/111276913): right now both the ContentEvents and lifecycle sessions are sent
338 // to system_server, so it's ok to call both in sequence here. But once we split
339 // them so the events are sent directly to the service, we need to make sure they're
340 // sent in order.
341 try {
342 if (DEBUG) {
343 Log.d(TAG, "Finishing session " + mId + " with "
344 + (mEvents == null ? 0 : mEvents.size()) + " event(s) for "
345 + getActivityDebugName());
Felipe Lemee348dc32018-11-05 12:35:29 -0800346 }
347
Felipe Lemeb18e3172018-11-27 10:33:41 -0800348 mService.finishSession(mContext.getUserId(), mId, mEvents);
349 } catch (RemoteException e) {
350 Log.e(TAG, "Error finishing session " + mId + " for " + getActivityDebugName()
351 + ": " + e);
352 } finally {
353 handleResetState();
Felipe Lemee348dc32018-11-05 12:35:29 -0800354 }
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700355 }
356
Felipe Lemeb18e3172018-11-27 10:33:41 -0800357 private void handleResetState() {
Felipe Leme88eae3b2018-11-07 15:11:56 -0800358 mState = STATE_UNKNOWN;
359 mId = null;
360 mApplicationToken = null;
361 mComponentName = null;
Felipe Lemeb18e3172018-11-27 10:33:41 -0800362 mEvents = null;
Felipe Leme4017b202018-12-10 12:13:31 -0800363 mHandler.removeMessages(MSG_FLUSH);
Felipe Leme88eae3b2018-11-07 15:11:56 -0800364 }
365
366 /**
367 * Notifies the Intelligence Service that a node has been added to the view structure.
368 *
369 * <p>Typically called "manually" by views that handle their own virtual view hierarchy, or
370 * automatically by the Android System for views that return {@code true} on
371 * {@link View#onProvideContentCaptureStructure(ViewStructure, int)}.
372 *
373 * @param node node that has been added.
374 */
375 public void notifyViewAppeared(@NonNull ViewStructure node) {
376 Preconditions.checkNotNull(node);
377 if (!isContentCaptureEnabled()) return;
378
379 if (!(node instanceof ViewNode.ViewStructureImpl)) {
380 throw new IllegalArgumentException("Invalid node class: " + node.getClass());
381 }
Felipe Lemeb18e3172018-11-27 10:33:41 -0800382
Felipe Lemeecb08be2018-11-27 15:48:47 -0800383 mHandler.sendMessage(obtainMessage(ContentCaptureManager::handleSendEvent, this,
Felipe Lemeb18e3172018-11-27 10:33:41 -0800384 new ContentCaptureEvent(TYPE_VIEW_APPEARED)
385 .setViewNode(((ViewNode.ViewStructureImpl) node).mNode),
386 /* forceFlush= */ false));
Felipe Leme88eae3b2018-11-07 15:11:56 -0800387 }
388
389 /**
390 * Notifies the Intelligence Service that a node has been removed from the view structure.
391 *
392 * <p>Typically called "manually" by views that handle their own virtual view hierarchy, or
393 * automatically by the Android System for standard views.
394 *
395 * @param id id of the node that has been removed.
396 */
397 public void notifyViewDisappeared(@NonNull AutofillId id) {
398 Preconditions.checkNotNull(id);
399 if (!isContentCaptureEnabled()) return;
400
Felipe Lemeecb08be2018-11-27 15:48:47 -0800401 mHandler.sendMessage(obtainMessage(ContentCaptureManager::handleSendEvent, this,
Felipe Lemeb18e3172018-11-27 10:33:41 -0800402 new ContentCaptureEvent(TYPE_VIEW_DISAPPEARED).setAutofillId(id),
403 /* forceFlush= */ false));
Felipe Leme88eae3b2018-11-07 15:11:56 -0800404 }
405
406 /**
407 * Notifies the Intelligence Service that the value of a text node has been changed.
408 *
409 * @param id of the node.
410 * @param text new text.
411 * @param flags either {@code 0} or {@link #FLAG_USER_INPUT} when the value was explicitly
412 * changed by the user (for example, through the keyboard).
413 */
414 public void notifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text,
415 int flags) {
416 Preconditions.checkNotNull(id);
Felipe Lemeb18e3172018-11-27 10:33:41 -0800417
Felipe Leme88eae3b2018-11-07 15:11:56 -0800418 if (!isContentCaptureEnabled()) return;
419
Felipe Lemeecb08be2018-11-27 15:48:47 -0800420 mHandler.sendMessage(obtainMessage(ContentCaptureManager::handleSendEvent, this,
Felipe Lemeb18e3172018-11-27 10:33:41 -0800421 new ContentCaptureEvent(TYPE_VIEW_TEXT_CHANGED, flags).setAutofillId(id)
422 .setText(text), /* forceFlush= */ false));
Felipe Leme88eae3b2018-11-07 15:11:56 -0800423 }
424
425 /**
426 * Creates a {@link ViewStructure} for a "standard" view.
427 *
428 * @hide
429 */
430 @NonNull
431 public ViewStructure newViewStructure(@NonNull View view) {
432 return new ViewNode.ViewStructureImpl(view);
433 }
434
435 /**
436 * Creates a {@link ViewStructure} for a "virtual" view, so it can be passed to
437 * {@link #notifyViewAppeared(ViewStructure)} by the view managing the virtual view hierarchy.
438 *
439 * @param parentId id of the virtual view parent (it can be obtained by calling
440 * {@link ViewStructure#getAutofillId()} on the parent).
441 * @param virtualId id of the virtual child, relative to the parent.
442 *
443 * @return a new {@link ViewStructure} that can be used for Content Capture purposes.
444 */
445 @NonNull
446 public ViewStructure newVirtualViewStructure(@NonNull AutofillId parentId, int virtualId) {
447 return new ViewNode.ViewStructureImpl(parentId, virtualId);
448 }
449
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700450 /**
Felipe Lemeecb08be2018-11-27 15:48:47 -0800451 * Returns the component name of the system service that is consuming the captured events for
452 * the current user.
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700453 */
454 @Nullable
Felipe Lemeecb08be2018-11-27 15:48:47 -0800455 public ComponentName getServiceComponentName() {
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700456 //TODO(b/111276913): implement
457 return null;
458 }
459
460 /**
Felipe Lemee348dc32018-11-05 12:35:29 -0800461 * Checks whether content capture is enabled for this activity.
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700462 */
463 public boolean isContentCaptureEnabled() {
Felipe Lemeb18e3172018-11-27 10:33:41 -0800464 return mService != null && !mDisabled.get();
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700465 }
466
467 /**
Felipe Leme284ad1c2018-11-15 18:16:12 -0800468 * Called by apps to explicitly enable or disable content capture.
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700469 *
470 * <p><b>Note: </b> this call is not persisted accross reboots, so apps should typically call
471 * it on {@link android.app.Activity#onCreate(android.os.Bundle, android.os.PersistableBundle)}.
472 */
Felipe Leme6b3a55c2018-11-13 17:14:03 -0800473 public void setContentCaptureEnabled(boolean enabled) {
Felipe Lemee348dc32018-11-05 12:35:29 -0800474 //TODO(b/111276913): implement
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700475 }
476
Felipe Lemee348dc32018-11-05 12:35:29 -0800477 /** @hide */
478 public void dump(String prefix, PrintWriter pw) {
Felipe Lemeb718e032018-12-10 09:44:23 -0800479 pw.print(prefix); pw.println("ContentCaptureManager");
Felipe Lemee348dc32018-11-05 12:35:29 -0800480 final String prefix2 = prefix + " ";
Felipe Lemeb18e3172018-11-27 10:33:41 -0800481 pw.print(prefix2); pw.print("mContext: "); pw.println(mContext);
482 pw.print(prefix2); pw.print("user: "); pw.println(mContext.getUserId());
483 if (mService != null) {
Felipe Lemee348dc32018-11-05 12:35:29 -0800484 pw.print(prefix2); pw.print("mService: "); pw.println(mService);
Felipe Lemeb18e3172018-11-27 10:33:41 -0800485 }
486 pw.print(prefix2); pw.print("mDisabled: "); pw.println(mDisabled.get());
487 pw.print(prefix2); pw.print("isEnabled(): "); pw.println(isContentCaptureEnabled());
488 if (mId != null) {
Felipe Lemea7bdb142018-11-05 16:29:29 -0800489 pw.print(prefix2); pw.print("id: "); pw.println(mId);
Felipe Lemeb18e3172018-11-27 10:33:41 -0800490 }
491 pw.print(prefix2); pw.print("state: "); pw.print(mState); pw.print(" (");
492 pw.print(getStateAsString(mState)); pw.println(")");
493 if (mApplicationToken != null) {
Felipe Leme88eae3b2018-11-07 15:11:56 -0800494 pw.print(prefix2); pw.print("app token: "); pw.println(mApplicationToken);
Felipe Lemeb18e3172018-11-27 10:33:41 -0800495 }
496 if (mComponentName != null) {
Felipe Leme88eae3b2018-11-07 15:11:56 -0800497 pw.print(prefix2); pw.print("component name: ");
Felipe Lemeb18e3172018-11-27 10:33:41 -0800498 pw.println(mComponentName.flattenToShortString());
499 }
Felipe Leme4017b202018-12-10 12:13:31 -0800500 if (mEvents != null && !mEvents.isEmpty()) {
Felipe Leme88eae3b2018-11-07 15:11:56 -0800501 final int numberEvents = mEvents.size();
Felipe Lemeecb08be2018-11-27 15:48:47 -0800502 pw.print(prefix2); pw.print("buffered events: "); pw.print(numberEvents);
Felipe Lemeb18e3172018-11-27 10:33:41 -0800503 pw.print('/'); pw.println(MAX_BUFFER_SIZE);
504 if (VERBOSE && numberEvents > 0) {
505 final String prefix3 = prefix2 + " ";
Felipe Leme88eae3b2018-11-07 15:11:56 -0800506 for (int i = 0; i < numberEvents; i++) {
507 final ContentCaptureEvent event = mEvents.get(i);
Felipe Lemeb18e3172018-11-27 10:33:41 -0800508 pw.print(prefix3); pw.print(i); pw.print(": "); event.dump(pw);
509 pw.println();
Felipe Leme88eae3b2018-11-07 15:11:56 -0800510 }
Felipe Leme88eae3b2018-11-07 15:11:56 -0800511 }
Felipe Leme4017b202018-12-10 12:13:31 -0800512 pw.print(prefix2); pw.print("flush frequency: "); pw.println(FLUSHING_FREQUENCY_MS);
513 pw.print(prefix2); pw.print("next flush: ");
514 TimeUtils.formatDuration(mNextFlush - SystemClock.elapsedRealtime(), pw); pw.println();
Felipe Lemee348dc32018-11-05 12:35:29 -0800515 }
516 }
517
Felipe Leme88eae3b2018-11-07 15:11:56 -0800518 /**
519 * Gets a string that can be used to identify the activity on logging statements.
520 */
Felipe Lemeb18e3172018-11-27 10:33:41 -0800521 private String getActivityDebugName() {
Felipe Leme88eae3b2018-11-07 15:11:56 -0800522 return mComponentName == null ? mContext.getPackageName()
523 : mComponentName.flattenToShortString();
Felipe Lemee348dc32018-11-05 12:35:29 -0800524 }
525
526 @NonNull
527 private static String getStateAsString(int state) {
528 switch (state) {
529 case STATE_UNKNOWN:
530 return "UNKNOWN";
Felipe Lemea7bdb142018-11-05 16:29:29 -0800531 case STATE_WAITING_FOR_SERVER:
532 return "WAITING_FOR_SERVER";
Felipe Lemee348dc32018-11-05 12:35:29 -0800533 case STATE_ACTIVE:
534 return "ACTIVE";
Felipe Leme8e2e3412018-11-14 10:39:29 -0800535 case STATE_DISABLED:
536 return "DISABLED";
Felipe Lemee348dc32018-11-05 12:35:29 -0800537 default:
538 return "INVALID:" + state;
539 }
540 }
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700541}