blob: 4519aefe65feb74f27ec1353ba9e596fd01a2ccd [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 Lemed32d8f6f2019-02-15 10:25:33 -080018import static android.view.contentcapture.ContentCaptureHelper.sDebug;
19import static android.view.contentcapture.ContentCaptureHelper.sVerbose;
Felipe Lemea8d33c22019-03-25 16:36:09 -070020import static android.view.contentcapture.ContentCaptureHelper.toSet;
Felipe Lemebe002d82019-01-23 10:22:32 -080021
Sergey Volnov6e049012020-01-10 18:10:48 +000022import android.annotation.CallbackExecutor;
Felipe Lemed32d8f6f2019-02-15 10:25:33 -080023import android.annotation.IntDef;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070024import android.annotation.NonNull;
25import android.annotation.Nullable;
Felipe Leme91ddeca2019-01-24 18:01:58 -080026import android.annotation.SystemApi;
Felipe Lemee348dc32018-11-05 12:35:29 -080027import android.annotation.SystemService;
Felipe Leme19652c02019-02-04 13:01:29 -080028import android.annotation.TestApi;
Felipe Leme87a9dc92018-12-18 14:28:07 -080029import android.annotation.UiThread;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070030import android.content.ComponentName;
Felipe Leme326f15a2019-02-19 09:42:24 -080031import android.content.ContentCaptureOptions;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070032import android.content.Context;
Felipe Lemef0d44c62019-03-26 16:36:58 -070033import android.graphics.Canvas;
Sergey Volnov6e049012020-01-10 18:10:48 +000034import android.os.Binder;
Felipe Leme88eae3b2018-11-07 15:11:56 -080035import android.os.Handler;
Felipe Lemee348dc32018-11-05 12:35:29 -080036import android.os.IBinder;
Felipe Leme34ccedf2019-01-17 13:42:35 -080037import android.os.Looper;
Sergey Volnov6e049012020-01-10 18:10:48 +000038import android.os.ParcelFileDescriptor;
Perumaal Saddabba2019-01-04 16:43:35 -080039import android.os.RemoteException;
Felipe Lemef8b87782019-03-20 14:31:18 -070040import android.os.ServiceManager;
Felipe Lemee348dc32018-11-05 12:35:29 -080041import android.util.Log;
Sergey Volnov6e049012020-01-10 18:10:48 +000042import android.util.Slog;
Felipe Lemef0d44c62019-03-26 16:36:58 -070043import android.view.View;
44import android.view.ViewStructure;
Adam He43c06992019-04-15 15:41:49 -070045import android.view.WindowManager;
Felipe Leme1af85ea2019-01-16 13:23:40 -080046import android.view.contentcapture.ContentCaptureSession.FlushReason;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070047
Adam He6079d152019-01-10 11:37:17 -080048import com.android.internal.annotations.GuardedBy;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070049import com.android.internal.util.Preconditions;
Perumaal Saddabba2019-01-04 16:43:35 -080050import com.android.internal.util.SyncResultReceiver;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070051
Felipe Lemee348dc32018-11-05 12:35:29 -080052import java.io.PrintWriter;
Felipe Lemed32d8f6f2019-02-15 10:25:33 -080053import java.lang.annotation.Retention;
54import java.lang.annotation.RetentionPolicy;
Sergey Volnov6e049012020-01-10 18:10:48 +000055import java.lang.ref.WeakReference;
Felipe Lemea8d33c22019-03-25 16:36:09 -070056import java.util.ArrayList;
Sergey Volnov5532c772020-02-12 18:19:41 +000057import java.util.HashMap;
58import java.util.Map;
Felipe Leme790be042019-03-25 09:52:19 -070059import java.util.Set;
Sergey Volnov6e049012020-01-10 18:10:48 +000060import java.util.concurrent.Executor;
61import java.util.function.Consumer;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070062
Felipe Lemeecb08be2018-11-27 15:48:47 -080063/**
Felipe Lemef0d44c62019-03-26 16:36:58 -070064 * <p>The {@link ContentCaptureManager} provides additional ways for for apps to
65 * integrate with the content capture subsystem.
66 *
67 * <p>Content capture provides real-time, continuous capture of application activity, display and
68 * events to an intelligence service that is provided by the Android system. The intelligence
69 * service then uses that info to mediate and speed user journey through different apps. For
70 * example, when the user receives a restaurant address in a chat app and switchs to a map app
71 * to search for that restaurant, the intelligence service could offer an autofill dialog to
72 * let the user automatically select its address.
73 *
74 * <p>Content capture was designed with two major concerns in mind: privacy and performance.
75 *
76 * <ul>
77 * <li><b>Privacy:</b> the intelligence service is a trusted component provided that is provided
78 * by the device manufacturer and that cannot be changed by the user (although the user can
79 * globaly disable content capture using the Android Settings app). This service can only use the
80 * data for in-device machine learning, which is enforced both by process isolation and
81 * <a href="https://source.android.com/compatibility/cdd">CDD requirements</a>.
82 * <li><b>Performance:</b> content capture is highly optimized to minimize its impact in the app
83 * jankiness and overall device system health. For example, its only enabled on apps (or even
84 * specific activities from an app) that were explicitly whitelisted by the intelligence service,
85 * and it buffers the events so they are sent in a batch to the service (see
86 * {@link #isContentCaptureEnabled()} for other cases when its disabled).
87 * </ul>
88 *
89 * <p>In fact, before using this manager, the app developer should check if it's available. Example:
Felipe Leme052c9422019-04-19 16:01:29 -070090 * <pre><code>
Felipe Lemef0d44c62019-03-26 16:36:58 -070091 * ContentCaptureManager mgr = context.getSystemService(ContentCaptureManager.class);
92 * if (mgr != null && mgr.isContentCaptureEnabled()) {
93 * // ...
94 * }
Felipe Leme052c9422019-04-19 16:01:29 -070095 * </code></pre>
Felipe Lemef0d44c62019-03-26 16:36:58 -070096 *
97 * <p>App developers usually don't need to explicitly interact with content capture, except when the
98 * app:
99 *
100 * <ul>
101 * <li>Can define a contextual {@link android.content.LocusId} to identify unique state (such as a
102 * conversation between 2 chat users).
103 * <li>Can have multiple view hierarchies with different contextual meaning (for example, a
104 * browser app with multiple tabs, each representing a different URL).
105 * <li>Contains custom views (that extend View directly and are not provided by the standard
106 * Android SDK.
107 * <li>Contains views that provide their own virtual hierarchy (like a web browser that render the
108 * HTML elements using a Canvas).
109 * </ul>
110 *
111 * <p>The main integration point with content capture is the {@link ContentCaptureSession}. A "main"
112 * session is automatically created by the Android System when content capture is enabled for the
113 * activity and its used by the standard Android views to notify the content capture service of
114 * events such as views being added, views been removed, and text changed by user input. The session
115 * could have a {@link ContentCaptureContext} to provide more contextual info about it, such as
116 * the locus associated with the view hierarchy (see {@link android.content.LocusId} for more info
117 * about locus). By default, the main session doesn't have a {@code ContentCaptureContext}, but you
118 * can change it after its created. Example:
119 *
120 * <pre><code>
121 * protected void onCreate(Bundle savedInstanceState) {
122 * // Initialize view structure
123 * ContentCaptureSession session = rootView.getContentCaptureSession();
124 * if (session != null) {
125 * session.setContentCaptureContext(ContentCaptureContext.forLocusId("chat_UserA_UserB"));
126 * }
127 * }
128 * </code></pre>
129 *
130 * <p>If your activity contains view hierarchies with a different contextual meaning, you should
131 * created child sessions for each view hierarchy root. For example, if your activity is a browser,
132 * you could use the main session for the main URL being rendered, then child sessions for each
133 * {@code IFRAME}:
134 *
135 * <pre><code>
136 * ContentCaptureSession mMainSession;
137 *
138 * protected void onCreate(Bundle savedInstanceState) {
139 * // Initialize view structure...
140 * mMainSession = rootView.getContentCaptureSession();
141 * if (mMainSession != null) {
142 * mMainSession.setContentCaptureContext(
143 * ContentCaptureContext.forLocusId("https://example.com"));
144 * }
145 * }
146 *
147 * private void loadIFrame(View iframeRootView, String url) {
148 * if (mMainSession != null) {
149 * ContentCaptureSession iFrameSession = mMainSession.newChild(
150 * ContentCaptureContext.forLocusId(url));
151 * }
152 * iframeRootView.setContentCaptureSession(iFrameSession);
153 * }
154 * // Load iframe...
155 * }
156 * </code></pre>
157 *
158 * <p>If your activity has custom views (i.e., views that extend {@link View} directly and provide
159 * just one logical view, not a virtual tree hiearchy) and it provides content that's relevant for
160 * content capture (as of {@link android.os.Build.VERSION_CODES#Q Android Q}, the only relevant
161 * content is text), then your view implementation should:
162 *
163 * <ul>
164 * <li>Set it as important for content capture.
165 * <li>Fill {@link ViewStructure} used for content capture.
166 * <li>Notify the {@link ContentCaptureSession} when the text is changed by user input.
167 * </ul>
168 *
169 * <p>Here's an example of the relevant methods for an {@code EditText}-like view:
170 *
171 * <pre><code>
172 * public class MyEditText extends View {
173 *
174 * public MyEditText(...) {
175 * if (getImportantForContentCapture() == IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) {
176 * setImportantForContentCapture(IMPORTANT_FOR_CONTENT_CAPTURE_YES);
177 * }
178 * }
179 *
180 * public void onProvideContentCaptureStructure(@NonNull ViewStructure structure, int flags) {
181 * super.onProvideContentCaptureStructure(structure, flags);
182 *
183 * structure.setText(getText(), getSelectionStart(), getSelectionEnd());
184 * structure.setHint(getHint());
185 * structure.setInputType(getInputType());
186 * // set other properties like setTextIdEntry(), setTextLines(), setTextStyle(),
187 * // setMinTextEms(), setMaxTextEms(), setMaxTextLength()
188 * }
189 *
190 * private void onTextChanged() {
191 * if (isLaidOut() && isImportantForContentCapture() && isTextEditable()) {
192 * ContentCaptureManager mgr = mContext.getSystemService(ContentCaptureManager.class);
193 * if (cm != null && cm.isContentCaptureEnabled()) {
194 * ContentCaptureSession session = getContentCaptureSession();
195 * if (session != null) {
196 * session.notifyViewTextChanged(getAutofillId(), getText());
197 * }
198 * }
199 * }
200 * </code></pre>
201 *
202 * <p>If your view provides its own virtual hierarchy (for example, if it's a browser that draws
203 * the HTML using {@link Canvas} or native libraries in a different render process), then the view
204 * is also responsible to notify the session when the virtual elements appear and disappear - see
Felipe Lemed7217622019-05-13 10:11:36 -0700205 * {@link View#onProvideContentCaptureStructure(ViewStructure, int)} for more info.
Felipe Lemeecb08be2018-11-27 15:48:47 -0800206 */
207@SystemService(Context.CONTENT_CAPTURE_MANAGER_SERVICE)
208public final class ContentCaptureManager {
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700209
Felipe Leme749b8892018-12-03 16:30:30 -0800210 private static final String TAG = ContentCaptureManager.class.getSimpleName();
Felipe Lemee348dc32018-11-05 12:35:29 -0800211
Sergey Volnov43929442020-01-28 15:36:59 +0000212 /** Error happened during the data sharing session. */
213 public static final int DATA_SHARE_ERROR_UNKNOWN = 1;
214
215 /** Request has been rejected, because a concurrent data share sessions is in progress. */
216 public static final int DATA_SHARE_ERROR_CONCURRENT_REQUEST = 2;
217
218 /** Request has been interrupted because of data share session timeout. */
219 public static final int DATA_SHARE_ERROR_TIMEOUT_INTERRUPTED = 3;
220
Felipe Lemebb0c2a22019-01-25 17:29:29 -0800221 /** @hide */
Yara Hassane41b29e2020-03-09 18:35:59 +0000222 @IntDef(flag = false, value = {
223 DATA_SHARE_ERROR_UNKNOWN,
224 DATA_SHARE_ERROR_CONCURRENT_REQUEST,
225 DATA_SHARE_ERROR_TIMEOUT_INTERRUPTED
226 })
227 @Retention(RetentionPolicy.SOURCE)
228 public @interface DataShareError {}
229
230 /** @hide */
Felipe Lemef8b87782019-03-20 14:31:18 -0700231 public static final int RESULT_CODE_OK = 0;
232 /** @hide */
Felipe Lemebb0c2a22019-01-25 17:29:29 -0800233 public static final int RESULT_CODE_TRUE = 1;
234 /** @hide */
235 public static final int RESULT_CODE_FALSE = 2;
236 /** @hide */
Felipe Lemef8b87782019-03-20 14:31:18 -0700237 public static final int RESULT_CODE_SECURITY_EXCEPTION = -1;
Felipe Lemebb0c2a22019-01-25 17:29:29 -0800238
Perumaal Saddabba2019-01-04 16:43:35 -0800239 /**
Mihir Patel6f33b102020-03-24 15:24:18 -0700240 * ID used to indicate that a session does not exist
241 * @hide
242 */
243 @SystemApi
244 public static final int NO_SESSION_ID = 0;
245
246 /**
Perumaal Saddabba2019-01-04 16:43:35 -0800247 * Timeout for calls to system_server.
248 */
249 private static final int SYNC_CALLS_TIMEOUT_MS = 5000;
250
Felipe Leme14ef4612019-02-07 12:24:38 -0800251 /**
252 * DeviceConfig property used by {@code com.android.server.SystemServer} on start to decide
Felipe Lemef0d44c62019-03-26 16:36:58 -0700253 * whether the content capture service should be created or not
Felipe Leme14ef4612019-02-07 12:24:38 -0800254 *
255 * <p>By default it should *NOT* be set (or set to {@code "default"}, so the decision is based
256 * on whether the OEM provides an implementation for the service), but it can be overridden to:
257 *
258 * <ul>
259 * <li>Provide a "kill switch" so OEMs can disable it remotely in case of emergency (when
260 * it's set to {@code "false"}).
261 * <li>Enable the CTS tests to be run on AOSP builds (when it's set to {@code "true"}).
262 * </ul>
263 *
264 * @hide
265 */
266 @TestApi
267 public static final String DEVICE_CONFIG_PROPERTY_SERVICE_EXPLICITLY_ENABLED =
268 "service_explicitly_enabled";
269
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800270 /**
271 * Maximum number of events that are buffered before sent to the app.
272 *
273 * @hide
274 */
275 @TestApi
276 public static final String DEVICE_CONFIG_PROPERTY_MAX_BUFFER_SIZE = "max_buffer_size";
277
278 /**
279 * Frequency (in ms) of buffer flushes when no events are received.
280 *
281 * @hide
282 */
283 @TestApi
284 public static final String DEVICE_CONFIG_PROPERTY_IDLE_FLUSH_FREQUENCY = "idle_flush_frequency";
285
286 /**
287 * Frequency (in ms) of buffer flushes when no events are received and the last one was a
288 * text change event.
289 *
290 * @hide
291 */
292 @TestApi
293 public static final String DEVICE_CONFIG_PROPERTY_TEXT_CHANGE_FLUSH_FREQUENCY =
294 "text_change_flush_frequency";
295
296 /**
297 * Size of events that are logging on {@code dump}.
298 *
299 * <p>Set it to {@code 0} or less to disable history.
300 *
301 * @hide
302 */
303 @TestApi
304 public static final String DEVICE_CONFIG_PROPERTY_LOG_HISTORY_SIZE = "log_history_size";
305
306 /**
307 * Sets the logging level for {@code logcat} statements.
308 *
309 * <p>Valid values are: {@link #LOGGING_LEVEL_OFF}, {@value #LOGGING_LEVEL_DEBUG}, and
310 * {@link #LOGGING_LEVEL_VERBOSE}.
311 *
312 * @hide
313 */
314 @TestApi
315 public static final String DEVICE_CONFIG_PROPERTY_LOGGING_LEVEL = "logging_level";
316
Felipe Lemee764fa22019-02-21 16:45:35 -0800317 /**
318 * Sets how long (in ms) the service is bound while idle.
319 *
320 * <p>Use {@code 0} to keep it permanently bound.
321 *
322 * @hide
323 */
324 public static final String DEVICE_CONFIG_PROPERTY_IDLE_UNBIND_TIMEOUT = "idle_unbind_timeout";
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800325
326 /** @hide */
327 @TestApi
328 public static final int LOGGING_LEVEL_OFF = 0;
329
330 /** @hide */
331 @TestApi
332 public static final int LOGGING_LEVEL_DEBUG = 1;
333
334 /** @hide */
335 @TestApi
336 public static final int LOGGING_LEVEL_VERBOSE = 2;
337
338 /** @hide */
339 @IntDef(flag = false, value = {
340 LOGGING_LEVEL_OFF,
341 LOGGING_LEVEL_DEBUG,
342 LOGGING_LEVEL_VERBOSE
343 })
344 @Retention(RetentionPolicy.SOURCE)
345 public @interface LoggingLevel {}
346
Felipe Leme326f15a2019-02-19 09:42:24 -0800347
348 /** @hide */
349 public static final int DEFAULT_MAX_BUFFER_SIZE = 100;
350 /** @hide */
351 public static final int DEFAULT_IDLE_FLUSHING_FREQUENCY_MS = 5_000;
352 /** @hide */
353 public static final int DEFAULT_TEXT_CHANGE_FLUSHING_FREQUENCY_MS = 1_000;
354 /** @hide */
355 public static final int DEFAULT_LOG_HISTORY_SIZE = 10;
356
Adam He6079d152019-01-10 11:37:17 -0800357 private final Object mLock = new Object();
358
Felipe Lemeb18e3172018-11-27 10:33:41 -0800359 @NonNull
Felipe Lemee348dc32018-11-05 12:35:29 -0800360 private final Context mContext;
361
Felipe Lemed49d52c2019-02-15 09:48:20 -0800362 @NonNull
Felipe Leme749b8892018-12-03 16:30:30 -0800363 private final IContentCaptureManager mService;
Felipe Lemee348dc32018-11-05 12:35:29 -0800364
Sergey Volnov5532c772020-02-12 18:19:41 +0000365 @GuardedBy("mLock")
366 private final LocalDataShareAdapterResourceManager mDataShareAdapterResourceManager;
367
Felipe Leme326f15a2019-02-19 09:42:24 -0800368 @NonNull
369 final ContentCaptureOptions mOptions;
370
Adam He6079d152019-01-10 11:37:17 -0800371 // Flags used for starting session.
372 @GuardedBy("mLock")
373 private int mFlags;
374
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800375 // TODO(b/119220549): use UI Thread directly (as calls are one-way) or a shared thread / handler
Felipe Lemeb18e3172018-11-27 10:33:41 -0800376 // held at the Application level
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800377 @NonNull
Felipe Leme88eae3b2018-11-07 15:11:56 -0800378 private final Handler mHandler;
379
Adam He6079d152019-01-10 11:37:17 -0800380 @GuardedBy("mLock")
Felipe Leme87a9dc92018-12-18 14:28:07 -0800381 private MainContentCaptureSession mMainSession;
Felipe Leme4017b202018-12-10 12:13:31 -0800382
Felipe Lemee348dc32018-11-05 12:35:29 -0800383 /** @hide */
Felipe Leme41e9eb02019-04-17 13:57:59 -0700384 public interface ContentCaptureClient {
385 /**
386 * Gets the component name of the client.
387 */
388 @NonNull
389 ComponentName contentCaptureClientGetComponentName();
390 }
391
392 /** @hide */
Felipe Leme749b8892018-12-03 16:30:30 -0800393 public ContentCaptureManager(@NonNull Context context,
Felipe Leme326f15a2019-02-19 09:42:24 -0800394 @NonNull IContentCaptureManager service, @NonNull ContentCaptureOptions options) {
Felipe Lemed49d52c2019-02-15 09:48:20 -0800395 mContext = Preconditions.checkNotNull(context, "context cannot be null");
396 mService = Preconditions.checkNotNull(service, "service cannot be null");
Felipe Leme326f15a2019-02-19 09:42:24 -0800397 mOptions = Preconditions.checkNotNull(options, "options cannot be null");
Felipe Lemee348dc32018-11-05 12:35:29 -0800398
Felipe Leme326f15a2019-02-19 09:42:24 -0800399 ContentCaptureHelper.setLoggingLevel(mOptions.loggingLevel);
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800400
401 if (sVerbose) Log.v(TAG, "Constructor for " + context.getPackageName());
402
Felipe Leme34ccedf2019-01-17 13:42:35 -0800403 // TODO(b/119220549): we might not even need a handler, as the IPCs are oneway. But if we
404 // do, then we should optimize it to run the tests after the Choreographer finishes the most
405 // important steps of the frame.
406 mHandler = Handler.createAsync(Looper.getMainLooper());
Sergey Volnov5532c772020-02-12 18:19:41 +0000407
408 mDataShareAdapterResourceManager = new LocalDataShareAdapterResourceManager();
Felipe Leme88eae3b2018-11-07 15:11:56 -0800409 }
410
Felipe Leme7a534082018-11-05 15:03:04 -0800411 /**
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800412 * Gets the main session associated with the context.
Felipe Leme88eae3b2018-11-07 15:11:56 -0800413 *
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800414 * <p>By default there's just one (associated with the activity lifecycle), but apps could
Felipe Leme87a9dc92018-12-18 14:28:07 -0800415 * explicitly add more using
416 * {@link ContentCaptureSession#createContentCaptureSession(ContentCaptureContext)}.
Felipe Leme88eae3b2018-11-07 15:11:56 -0800417 *
418 * @hide
419 */
420 @NonNull
Felipe Leme87a9dc92018-12-18 14:28:07 -0800421 @UiThread
422 public MainContentCaptureSession getMainContentCaptureSession() {
Adam He6079d152019-01-10 11:37:17 -0800423 synchronized (mLock) {
424 if (mMainSession == null) {
Felipe Leme609991d2019-01-30 16:27:24 -0800425 mMainSession = new MainContentCaptureSession(mContext, this, mHandler, mService);
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800426 if (sVerbose) Log.v(TAG, "getMainContentCaptureSession(): created " + mMainSession);
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800427 }
Adam He6079d152019-01-10 11:37:17 -0800428 return mMainSession;
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800429 }
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800430 }
431
432 /** @hide */
Felipe Leme3fe6e922019-02-04 17:52:27 -0800433 @UiThread
Felipe Lemeb0da18f2019-02-22 15:10:02 -0800434 public void onActivityCreated(@NonNull IBinder applicationToken,
Adam He43c06992019-04-15 15:41:49 -0700435 @NonNull ComponentName activityComponent) {
Felipe Leme5001b3b2019-03-20 18:25:28 -0700436 if (mOptions.lite) return;
Adam He6079d152019-01-10 11:37:17 -0800437 synchronized (mLock) {
Adam He6079d152019-01-10 11:37:17 -0800438 getMainContentCaptureSession().start(applicationToken, activityComponent, mFlags);
439 }
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800440 }
441
442 /** @hide */
Felipe Leme3fe6e922019-02-04 17:52:27 -0800443 @UiThread
Felipe Lemeb0da18f2019-02-22 15:10:02 -0800444 public void onActivityResumed() {
Felipe Leme5001b3b2019-03-20 18:25:28 -0700445 if (mOptions.lite) return;
Adam He42ec1d82019-09-26 12:24:09 -0700446 getMainContentCaptureSession().notifySessionResumed();
Felipe Lemeb0da18f2019-02-22 15:10:02 -0800447 }
448
449 /** @hide */
450 @UiThread
451 public void onActivityPaused() {
Felipe Leme5001b3b2019-03-20 18:25:28 -0700452 if (mOptions.lite) return;
Adam He42ec1d82019-09-26 12:24:09 -0700453 getMainContentCaptureSession().notifySessionPaused();
Felipe Lemeb0da18f2019-02-22 15:10:02 -0800454 }
455
456 /** @hide */
457 @UiThread
458 public void onActivityDestroyed() {
Felipe Leme5001b3b2019-03-20 18:25:28 -0700459 if (mOptions.lite) return;
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800460 getMainContentCaptureSession().destroy();
Felipe Leme88eae3b2018-11-07 15:11:56 -0800461 }
462
463 /**
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800464 * Flushes the content of all sessions.
Felipe Leme88eae3b2018-11-07 15:11:56 -0800465 *
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800466 * <p>Typically called by {@code Activity} when it's paused / resumed.
Felipe Leme88eae3b2018-11-07 15:11:56 -0800467 *
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800468 * @hide
Felipe Leme88eae3b2018-11-07 15:11:56 -0800469 */
Felipe Leme3fe6e922019-02-04 17:52:27 -0800470 @UiThread
Felipe Leme1af85ea2019-01-16 13:23:40 -0800471 public void flush(@FlushReason int reason) {
Felipe Leme5001b3b2019-03-20 18:25:28 -0700472 if (mOptions.lite) return;
Felipe Leme1af85ea2019-01-16 13:23:40 -0800473 getMainContentCaptureSession().flush(reason);
Felipe Leme88eae3b2018-11-07 15:11:56 -0800474 }
475
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700476 /**
Felipe Lemeecb08be2018-11-27 15:48:47 -0800477 * Returns the component name of the system service that is consuming the captured events for
478 * the current user.
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700479 */
480 @Nullable
Felipe Lemeecb08be2018-11-27 15:48:47 -0800481 public ComponentName getServiceComponentName() {
Felipe Leme5001b3b2019-03-20 18:25:28 -0700482 if (!isContentCaptureEnabled() && !mOptions.lite) return null;
Felipe Leme91ddeca2019-01-24 18:01:58 -0800483
Perumaal Saddabba2019-01-04 16:43:35 -0800484 final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
Perumaal Saddabba2019-01-04 16:43:35 -0800485 try {
Felipe Lemeeffa5f42019-01-28 14:59:46 -0800486 mService.getServiceComponentName(resultReceiver);
Perumaal Saddabba2019-01-04 16:43:35 -0800487 return resultReceiver.getParcelableResult();
488 } catch (RemoteException e) {
Perumaal Saddabba2019-01-04 16:43:35 -0800489 throw e.rethrowFromSystemServer();
490 }
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700491 }
492
493 /**
Felipe Lemef8b87782019-03-20 14:31:18 -0700494 * Gets the (optional) intent used to launch the service-specific settings.
495 *
496 * <p>This method is static because it's called by Settings, which might not be whitelisted
497 * for content capture (in which case the ContentCaptureManager on its context would be null).
498 *
499 * @hide
500 */
Felipe Leme5001b3b2019-03-20 18:25:28 -0700501 // TODO: use "lite" options as it's done by activities from the content capture service
Felipe Lemef8b87782019-03-20 14:31:18 -0700502 @Nullable
503 public static ComponentName getServiceSettingsComponentName() {
504 final IBinder binder = ServiceManager
505 .checkService(Context.CONTENT_CAPTURE_MANAGER_SERVICE);
506 if (binder == null) return null;
507
508 final IContentCaptureManager service = IContentCaptureManager.Stub.asInterface(binder);
509 final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
510 try {
511 service.getServiceSettingsActivity(resultReceiver);
512 final int resultCode = resultReceiver.getIntResult();
513 if (resultCode == RESULT_CODE_SECURITY_EXCEPTION) {
514 throw new SecurityException(resultReceiver.getStringResult());
515 }
516 return resultReceiver.getParcelableResult();
517 } catch (RemoteException e) {
518 throw e.rethrowFromSystemServer();
519 }
520 }
521
522 /**
Felipe Lemee348dc32018-11-05 12:35:29 -0800523 * Checks whether content capture is enabled for this activity.
Felipe Leme91ddeca2019-01-24 18:01:58 -0800524 *
525 * <p>There are many reasons it could be disabled, such as:
526 * <ul>
527 * <li>App itself disabled content capture through {@link #setContentCaptureEnabled(boolean)}.
Felipe Lemef0d44c62019-03-26 16:36:58 -0700528 * <li>Intelligence service did not whitelist content capture for this activity's package.
529 * <li>Intelligence service did not whitelist content capture for this specific activity.
530 * <li>Intelligence service disabled content capture globally.
531 * <li>User disabled content capture globally through the Android Settings app.
532 * <li>Device manufacturer (OEM) disabled content capture globally.
533 * <li>Transient errors, such as intelligence service package being updated.
Felipe Leme91ddeca2019-01-24 18:01:58 -0800534 * </ul>
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700535 */
536 public boolean isContentCaptureEnabled() {
Felipe Leme5001b3b2019-03-20 18:25:28 -0700537 if (mOptions.lite) return false;
538
Felipe Leme609991d2019-01-30 16:27:24 -0800539 final MainContentCaptureSession mainSession;
Adam He6079d152019-01-10 11:37:17 -0800540 synchronized (mLock) {
Felipe Leme609991d2019-01-30 16:27:24 -0800541 mainSession = mMainSession;
Adam He6079d152019-01-10 11:37:17 -0800542 }
Felipe Leme609991d2019-01-30 16:27:24 -0800543 // The main session is only set when the activity starts, so we need to return true until
544 // then.
545 if (mainSession != null && mainSession.isDisabled()) return false;
546
547 return true;
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700548 }
549
550 /**
Felipe Leme790be042019-03-25 09:52:19 -0700551 * Gets the list of conditions for when content capture should be allowed.
552 *
553 * <p>This method is typically used by web browsers so they don't generate unnecessary content
554 * capture events for websites the content capture service is not interested on.
555 *
556 * @return list of conditions, or {@code null} if the service didn't set any restriction
Felipe Lemef0d44c62019-03-26 16:36:58 -0700557 * (in which case content capture events should always be generated). If the list is empty,
558 * then it should not generate any event at all.
Felipe Leme790be042019-03-25 09:52:19 -0700559 */
560 @Nullable
561 public Set<ContentCaptureCondition> getContentCaptureConditions() {
Felipe Lemea8d33c22019-03-25 16:36:09 -0700562 // NOTE: we could cache the conditions on ContentCaptureOptions, but then it would be stick
563 // to the lifetime of the app. OTOH, by dynamically calling the server every time, we allow
564 // the service to fine tune how long-lived apps (like browsers) are whitelisted.
565 if (!isContentCaptureEnabled() && !mOptions.lite) return null;
566
Felipe Lemeafbba9f2019-03-26 14:02:25 -0700567 final SyncResultReceiver resultReceiver = syncRun(
568 (r) -> mService.getContentCaptureConditions(mContext.getPackageName(), r));
569
570 final ArrayList<ContentCaptureCondition> result = resultReceiver
571 .getParcelableListResult();
572 return toSet(result);
Felipe Leme790be042019-03-25 09:52:19 -0700573 }
574
575 /**
Felipe Leme284ad1c2018-11-15 18:16:12 -0800576 * Called by apps to explicitly enable or disable content capture.
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700577 *
578 * <p><b>Note: </b> this call is not persisted accross reboots, so apps should typically call
579 * it on {@link android.app.Activity#onCreate(android.os.Bundle, android.os.PersistableBundle)}.
580 */
Felipe Leme6b3a55c2018-11-13 17:14:03 -0800581 public void setContentCaptureEnabled(boolean enabled) {
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800582 if (sDebug) {
Felipe Lemebb0c2a22019-01-25 17:29:29 -0800583 Log.d(TAG, "setContentCaptureEnabled(): setting to " + enabled + " for " + mContext);
584 }
585
Adam He6c0afca2019-04-15 13:54:11 -0700586 MainContentCaptureSession mainSession;
Adam He6079d152019-01-10 11:37:17 -0800587 synchronized (mLock) {
Adam He6c0afca2019-04-15 13:54:11 -0700588 if (enabled) {
589 mFlags &= ~ContentCaptureContext.FLAG_DISABLED_BY_APP;
590 } else {
591 mFlags |= ContentCaptureContext.FLAG_DISABLED_BY_APP;
592 }
593 mainSession = mMainSession;
594 }
595 if (mainSession != null) {
596 mainSession.setDisabled(!enabled);
Adam He6079d152019-01-10 11:37:17 -0800597 }
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800598 }
599
600 /**
Adam He43c06992019-04-15 15:41:49 -0700601 * Called by apps to update flag secure when window attributes change.
602 *
603 * @hide
604 */
605 public void updateWindowAttributes(@NonNull WindowManager.LayoutParams params) {
606 if (sDebug) {
607 Log.d(TAG, "updateWindowAttributes(): window flags=" + params.flags);
608 }
609 final boolean flagSecureEnabled =
610 (params.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0;
611
612 MainContentCaptureSession mainSession;
613 synchronized (mLock) {
614 if (flagSecureEnabled) {
615 mFlags |= ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE;
616 } else {
617 mFlags &= ~ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE;
618 }
619 mainSession = mMainSession;
620 }
621 if (mainSession != null) {
622 mainSession.setDisabled(flagSecureEnabled);
623 }
624 }
625
626 /**
Felipe Lemef0d44c62019-03-26 16:36:58 -0700627 * Gets whether content capture is enabled for the given user.
Felipe Leme91ddeca2019-01-24 18:01:58 -0800628 *
Felipe Lemef0d44c62019-03-26 16:36:58 -0700629 * <p>This method is typically used by the content capture service settings page, so it can
Felipe Leme91ddeca2019-01-24 18:01:58 -0800630 * provide a toggle to enable / disable it.
631 *
Felipe Lemef0d44c62019-03-26 16:36:58 -0700632 * @throws SecurityException if caller is not the app that owns the content capture service
Felipe Lemebb0c2a22019-01-25 17:29:29 -0800633 * associated with the user.
634 *
Felipe Leme91ddeca2019-01-24 18:01:58 -0800635 * @hide
636 */
637 @SystemApi
Felipe Leme19652c02019-02-04 13:01:29 -0800638 @TestApi
Felipe Leme91ddeca2019-01-24 18:01:58 -0800639 public boolean isContentCaptureFeatureEnabled() {
Felipe Lemeafbba9f2019-03-26 14:02:25 -0700640 final SyncResultReceiver resultReceiver = syncRun(
641 (r) -> mService.isContentCaptureFeatureEnabled(r));
642 final int resultCode = resultReceiver.getIntResult();
Felipe Lemebb0c2a22019-01-25 17:29:29 -0800643 switch (resultCode) {
644 case RESULT_CODE_TRUE:
645 return true;
646 case RESULT_CODE_FALSE:
647 return false;
Felipe Lemebb0c2a22019-01-25 17:29:29 -0800648 default:
Felipe Leme72e83d82019-02-13 14:51:17 -0800649 Log.wtf(TAG, "received invalid result: " + resultCode);
650 return false;
Felipe Lemebb0c2a22019-01-25 17:29:29 -0800651 }
Felipe Leme91ddeca2019-01-24 18:01:58 -0800652 }
653
654 /**
Felipe Leme4439ee62019-05-08 10:27:49 -0700655 * Called by the app to request the content capture service to remove content capture data
656 * associated with some context.
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800657 *
658 * @param request object specifying what user data should be removed.
659 */
Felipe Leme4439ee62019-05-08 10:27:49 -0700660 public void removeData(@NonNull DataRemovalRequest request) {
Adam He3d0409b2019-01-15 14:22:04 -0800661 Preconditions.checkNotNull(request);
662
663 try {
Felipe Leme4439ee62019-05-08 10:27:49 -0700664 mService.removeData(request);
Adam He3d0409b2019-01-15 14:22:04 -0800665 } catch (RemoteException e) {
666 e.rethrowFromSystemServer();
667 }
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700668 }
669
Felipe Lemeafbba9f2019-03-26 14:02:25 -0700670 /**
Sergey Volnov6e049012020-01-10 18:10:48 +0000671 * Called by the app to request data sharing via writing to a file.
672 *
673 * <p>The ContentCaptureService app will receive a read-only file descriptor pointing to the
674 * same file and will be able to read data being shared from it.
675 *
676 * <p>Note: using this API doesn't guarantee the app staying alive and is "best-effort".
677 * Starting a foreground service would minimize the chances of the app getting killed during the
678 * file sharing session.
679 *
680 * @param request object specifying details of the data being shared.
681 */
682 public void shareData(@NonNull DataShareRequest request,
683 @NonNull @CallbackExecutor Executor executor,
684 @NonNull DataShareWriteAdapter dataShareWriteAdapter) {
685 Preconditions.checkNotNull(request);
686 Preconditions.checkNotNull(dataShareWriteAdapter);
687 Preconditions.checkNotNull(executor);
688
689 try {
Sergey Volnov43929442020-01-28 15:36:59 +0000690 mService.shareData(request,
Sergey Volnov5532c772020-02-12 18:19:41 +0000691 new DataShareAdapterDelegate(executor, dataShareWriteAdapter,
692 mDataShareAdapterResourceManager));
Sergey Volnov6e049012020-01-10 18:10:48 +0000693 } catch (RemoteException e) {
694 e.rethrowFromSystemServer();
695 }
696 }
697
698 /**
Felipe Lemeafbba9f2019-03-26 14:02:25 -0700699 * Runs a sync method in the service, properly handling exceptions.
700 *
701 * @throws SecurityException if caller is not allowed to execute the method.
702 */
703 @NonNull
704 private SyncResultReceiver syncRun(@NonNull MyRunnable r) {
705 final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
706 try {
707 r.run(resultReceiver);
708 final int resultCode = resultReceiver.getIntResult();
709 if (resultCode == RESULT_CODE_SECURITY_EXCEPTION) {
710 throw new SecurityException(resultReceiver.getStringResult());
711 }
712 return resultReceiver;
713 } catch (RemoteException e) {
714 throw e.rethrowFromSystemServer();
715 }
716 }
717
Felipe Lemee348dc32018-11-05 12:35:29 -0800718 /** @hide */
719 public void dump(String prefix, PrintWriter pw) {
Felipe Lemed49d52c2019-02-15 09:48:20 -0800720 pw.print(prefix); pw.println("ContentCaptureManager");
721 final String prefix2 = prefix + " ";
Adam He6079d152019-01-10 11:37:17 -0800722 synchronized (mLock) {
Felipe Lemed49d52c2019-02-15 09:48:20 -0800723 pw.print(prefix2); pw.print("isContentCaptureEnabled(): ");
Felipe Leme609991d2019-01-30 16:27:24 -0800724 pw.println(isContentCaptureEnabled());
Felipe Leme326f15a2019-02-19 09:42:24 -0800725 pw.print(prefix2); pw.print("Debug: "); pw.print(sDebug);
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800726 pw.print(" Verbose: "); pw.println(sVerbose);
Felipe Leme326f15a2019-02-19 09:42:24 -0800727 pw.print(prefix2); pw.print("Context: "); pw.println(mContext);
728 pw.print(prefix2); pw.print("User: "); pw.println(mContext.getUserId());
729 pw.print(prefix2); pw.print("Service: "); pw.println(mService);
730 pw.print(prefix2); pw.print("Flags: "); pw.println(mFlags);
731 pw.print(prefix2); pw.print("Options: "); mOptions.dumpShort(pw); pw.println();
Adam He6079d152019-01-10 11:37:17 -0800732 if (mMainSession != null) {
Felipe Lemed49d52c2019-02-15 09:48:20 -0800733 final String prefix3 = prefix2 + " ";
734 pw.print(prefix2); pw.println("Main session:");
735 mMainSession.dump(prefix3, pw);
Adam He6079d152019-01-10 11:37:17 -0800736 } else {
Felipe Lemed49d52c2019-02-15 09:48:20 -0800737 pw.print(prefix2); pw.println("No sessions");
Adam He6079d152019-01-10 11:37:17 -0800738 }
Felipe Lemee348dc32018-11-05 12:35:29 -0800739 }
740 }
Felipe Lemeafbba9f2019-03-26 14:02:25 -0700741
742 private interface MyRunnable {
743 void run(@NonNull SyncResultReceiver receiver) throws RemoteException;
744 }
Sergey Volnov6e049012020-01-10 18:10:48 +0000745
746 private static class DataShareAdapterDelegate extends IDataShareWriteAdapter.Stub {
747
Sergey Volnov5532c772020-02-12 18:19:41 +0000748 private final WeakReference<LocalDataShareAdapterResourceManager> mResourceManagerReference;
Sergey Volnov6e049012020-01-10 18:10:48 +0000749
Sergey Volnov5532c772020-02-12 18:19:41 +0000750 private DataShareAdapterDelegate(Executor executor, DataShareWriteAdapter adapter,
751 LocalDataShareAdapterResourceManager resourceManager) {
Sergey Volnov6e049012020-01-10 18:10:48 +0000752 Preconditions.checkNotNull(executor);
753 Preconditions.checkNotNull(adapter);
Sergey Volnov5532c772020-02-12 18:19:41 +0000754 Preconditions.checkNotNull(resourceManager);
Sergey Volnov6e049012020-01-10 18:10:48 +0000755
Sergey Volnov5532c772020-02-12 18:19:41 +0000756 resourceManager.initializeForDelegate(this, adapter, executor);
757 mResourceManagerReference = new WeakReference<>(resourceManager);
Sergey Volnov6e049012020-01-10 18:10:48 +0000758 }
759
760 @Override
761 public void write(ParcelFileDescriptor destination)
762 throws RemoteException {
Sergey Volnov43929442020-01-28 15:36:59 +0000763 executeAdapterMethodLocked(adapter -> adapter.onWrite(destination), "onWrite");
Sergey Volnov5532c772020-02-12 18:19:41 +0000764
765 // Client app and Service successfully connected, so this object would be kept alive
766 // until the session has finished.
767 clearHardReferences();
Sergey Volnov6e049012020-01-10 18:10:48 +0000768 }
769
770 @Override
771 public void error(int errorCode) throws RemoteException {
772 executeAdapterMethodLocked(adapter -> adapter.onError(errorCode), "onError");
Sergey Volnov5532c772020-02-12 18:19:41 +0000773 clearHardReferences();
Sergey Volnov6e049012020-01-10 18:10:48 +0000774 }
775
776 @Override
777 public void rejected() throws RemoteException {
778 executeAdapterMethodLocked(DataShareWriteAdapter::onRejected, "onRejected");
Sergey Volnov5532c772020-02-12 18:19:41 +0000779 clearHardReferences();
Sergey Volnov6e049012020-01-10 18:10:48 +0000780 }
781
782 private void executeAdapterMethodLocked(Consumer<DataShareWriteAdapter> adapterFn,
783 String methodName) {
Sergey Volnov5532c772020-02-12 18:19:41 +0000784 LocalDataShareAdapterResourceManager resourceManager = mResourceManagerReference.get();
785 if (resourceManager == null) {
786 Slog.w(TAG, "Can't execute " + methodName + "(), resource manager has been GC'ed");
787 return;
788 }
789
790 DataShareWriteAdapter adapter = resourceManager.getAdapter(this);
791 Executor executor = resourceManager.getExecutor(this);
Sergey Volnov6e049012020-01-10 18:10:48 +0000792
793 if (adapter == null || executor == null) {
Sergey Volnov5532c772020-02-12 18:19:41 +0000794 Slog.w(TAG, "Can't execute " + methodName + "(), references are null");
Sergey Volnov6e049012020-01-10 18:10:48 +0000795 return;
796 }
797
798 final long identity = Binder.clearCallingIdentity();
799 try {
800 executor.execute(() -> adapterFn.accept(adapter));
801 } finally {
802 Binder.restoreCallingIdentity(identity);
803 }
804 }
Sergey Volnov5532c772020-02-12 18:19:41 +0000805
806 private void clearHardReferences() {
807 LocalDataShareAdapterResourceManager resourceManager = mResourceManagerReference.get();
808 if (resourceManager == null) {
809 Slog.w(TAG, "Can't clear references, resource manager has been GC'ed");
810 return;
811 }
812
813 resourceManager.clearHardReferences(this);
814 }
815 }
816
817 /**
818 * Wrapper class making sure dependencies on the current application stay in the application
819 * context.
820 */
821 private static class LocalDataShareAdapterResourceManager {
822
823 // Keeping hard references to the remote objects in the current process (static context)
824 // to prevent them to be gc'ed during the lifetime of the application. This is an
825 // artifact of only operating with weak references remotely: there has to be at least 1
826 // hard reference in order for this to not be killed.
827 private Map<DataShareAdapterDelegate, DataShareWriteAdapter> mWriteAdapterHardReferences =
828 new HashMap<>();
829 private Map<DataShareAdapterDelegate, Executor> mExecutorHardReferences =
830 new HashMap<>();
831
832 void initializeForDelegate(DataShareAdapterDelegate delegate, DataShareWriteAdapter adapter,
833 Executor executor) {
834 mWriteAdapterHardReferences.put(delegate, adapter);
Sergey Volnov6c74f7c2020-04-09 21:18:48 +0100835 mExecutorHardReferences.put(delegate, executor);
Sergey Volnov5532c772020-02-12 18:19:41 +0000836 }
837
838 Executor getExecutor(DataShareAdapterDelegate delegate) {
839 return mExecutorHardReferences.get(delegate);
840 }
841
842 DataShareWriteAdapter getAdapter(DataShareAdapterDelegate delegate) {
843 return mWriteAdapterHardReferences.get(delegate);
844 }
845
846 void clearHardReferences(DataShareAdapterDelegate delegate) {
847 mWriteAdapterHardReferences.remove(delegate);
848 mExecutorHardReferences.remove(delegate);
849 }
Sergey Volnov6e049012020-01-10 18:10:48 +0000850 }
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700851}