blob: b7b54c8c74b9b37a942096a6d4b797c8a0f22d4f [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;
Felipe Leme790be042019-03-25 09:52:19 -070057import java.util.Set;
Sergey Volnov6e049012020-01-10 18:10:48 +000058import java.util.concurrent.Executor;
59import java.util.function.Consumer;
Felipe Leme1dfa9a02018-10-17 17:24:37 -070060
Felipe Lemeecb08be2018-11-27 15:48:47 -080061/**
Felipe Lemef0d44c62019-03-26 16:36:58 -070062 * <p>The {@link ContentCaptureManager} provides additional ways for for apps to
63 * integrate with the content capture subsystem.
64 *
65 * <p>Content capture provides real-time, continuous capture of application activity, display and
66 * events to an intelligence service that is provided by the Android system. The intelligence
67 * service then uses that info to mediate and speed user journey through different apps. For
68 * example, when the user receives a restaurant address in a chat app and switchs to a map app
69 * to search for that restaurant, the intelligence service could offer an autofill dialog to
70 * let the user automatically select its address.
71 *
72 * <p>Content capture was designed with two major concerns in mind: privacy and performance.
73 *
74 * <ul>
75 * <li><b>Privacy:</b> the intelligence service is a trusted component provided that is provided
76 * by the device manufacturer and that cannot be changed by the user (although the user can
77 * globaly disable content capture using the Android Settings app). This service can only use the
78 * data for in-device machine learning, which is enforced both by process isolation and
79 * <a href="https://source.android.com/compatibility/cdd">CDD requirements</a>.
80 * <li><b>Performance:</b> content capture is highly optimized to minimize its impact in the app
81 * jankiness and overall device system health. For example, its only enabled on apps (or even
82 * specific activities from an app) that were explicitly whitelisted by the intelligence service,
83 * and it buffers the events so they are sent in a batch to the service (see
84 * {@link #isContentCaptureEnabled()} for other cases when its disabled).
85 * </ul>
86 *
87 * <p>In fact, before using this manager, the app developer should check if it's available. Example:
Felipe Leme052c9422019-04-19 16:01:29 -070088 * <pre><code>
Felipe Lemef0d44c62019-03-26 16:36:58 -070089 * ContentCaptureManager mgr = context.getSystemService(ContentCaptureManager.class);
90 * if (mgr != null && mgr.isContentCaptureEnabled()) {
91 * // ...
92 * }
Felipe Leme052c9422019-04-19 16:01:29 -070093 * </code></pre>
Felipe Lemef0d44c62019-03-26 16:36:58 -070094 *
95 * <p>App developers usually don't need to explicitly interact with content capture, except when the
96 * app:
97 *
98 * <ul>
99 * <li>Can define a contextual {@link android.content.LocusId} to identify unique state (such as a
100 * conversation between 2 chat users).
101 * <li>Can have multiple view hierarchies with different contextual meaning (for example, a
102 * browser app with multiple tabs, each representing a different URL).
103 * <li>Contains custom views (that extend View directly and are not provided by the standard
104 * Android SDK.
105 * <li>Contains views that provide their own virtual hierarchy (like a web browser that render the
106 * HTML elements using a Canvas).
107 * </ul>
108 *
109 * <p>The main integration point with content capture is the {@link ContentCaptureSession}. A "main"
110 * session is automatically created by the Android System when content capture is enabled for the
111 * activity and its used by the standard Android views to notify the content capture service of
112 * events such as views being added, views been removed, and text changed by user input. The session
113 * could have a {@link ContentCaptureContext} to provide more contextual info about it, such as
114 * the locus associated with the view hierarchy (see {@link android.content.LocusId} for more info
115 * about locus). By default, the main session doesn't have a {@code ContentCaptureContext}, but you
116 * can change it after its created. Example:
117 *
118 * <pre><code>
119 * protected void onCreate(Bundle savedInstanceState) {
120 * // Initialize view structure
121 * ContentCaptureSession session = rootView.getContentCaptureSession();
122 * if (session != null) {
123 * session.setContentCaptureContext(ContentCaptureContext.forLocusId("chat_UserA_UserB"));
124 * }
125 * }
126 * </code></pre>
127 *
128 * <p>If your activity contains view hierarchies with a different contextual meaning, you should
129 * created child sessions for each view hierarchy root. For example, if your activity is a browser,
130 * you could use the main session for the main URL being rendered, then child sessions for each
131 * {@code IFRAME}:
132 *
133 * <pre><code>
134 * ContentCaptureSession mMainSession;
135 *
136 * protected void onCreate(Bundle savedInstanceState) {
137 * // Initialize view structure...
138 * mMainSession = rootView.getContentCaptureSession();
139 * if (mMainSession != null) {
140 * mMainSession.setContentCaptureContext(
141 * ContentCaptureContext.forLocusId("https://example.com"));
142 * }
143 * }
144 *
145 * private void loadIFrame(View iframeRootView, String url) {
146 * if (mMainSession != null) {
147 * ContentCaptureSession iFrameSession = mMainSession.newChild(
148 * ContentCaptureContext.forLocusId(url));
149 * }
150 * iframeRootView.setContentCaptureSession(iFrameSession);
151 * }
152 * // Load iframe...
153 * }
154 * </code></pre>
155 *
156 * <p>If your activity has custom views (i.e., views that extend {@link View} directly and provide
157 * just one logical view, not a virtual tree hiearchy) and it provides content that's relevant for
158 * content capture (as of {@link android.os.Build.VERSION_CODES#Q Android Q}, the only relevant
159 * content is text), then your view implementation should:
160 *
161 * <ul>
162 * <li>Set it as important for content capture.
163 * <li>Fill {@link ViewStructure} used for content capture.
164 * <li>Notify the {@link ContentCaptureSession} when the text is changed by user input.
165 * </ul>
166 *
167 * <p>Here's an example of the relevant methods for an {@code EditText}-like view:
168 *
169 * <pre><code>
170 * public class MyEditText extends View {
171 *
172 * public MyEditText(...) {
173 * if (getImportantForContentCapture() == IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) {
174 * setImportantForContentCapture(IMPORTANT_FOR_CONTENT_CAPTURE_YES);
175 * }
176 * }
177 *
178 * public void onProvideContentCaptureStructure(@NonNull ViewStructure structure, int flags) {
179 * super.onProvideContentCaptureStructure(structure, flags);
180 *
181 * structure.setText(getText(), getSelectionStart(), getSelectionEnd());
182 * structure.setHint(getHint());
183 * structure.setInputType(getInputType());
184 * // set other properties like setTextIdEntry(), setTextLines(), setTextStyle(),
185 * // setMinTextEms(), setMaxTextEms(), setMaxTextLength()
186 * }
187 *
188 * private void onTextChanged() {
189 * if (isLaidOut() && isImportantForContentCapture() && isTextEditable()) {
190 * ContentCaptureManager mgr = mContext.getSystemService(ContentCaptureManager.class);
191 * if (cm != null && cm.isContentCaptureEnabled()) {
192 * ContentCaptureSession session = getContentCaptureSession();
193 * if (session != null) {
194 * session.notifyViewTextChanged(getAutofillId(), getText());
195 * }
196 * }
197 * }
198 * </code></pre>
199 *
200 * <p>If your view provides its own virtual hierarchy (for example, if it's a browser that draws
201 * the HTML using {@link Canvas} or native libraries in a different render process), then the view
202 * is also responsible to notify the session when the virtual elements appear and disappear - see
Felipe Lemed7217622019-05-13 10:11:36 -0700203 * {@link View#onProvideContentCaptureStructure(ViewStructure, int)} for more info.
Felipe Lemeecb08be2018-11-27 15:48:47 -0800204 */
205@SystemService(Context.CONTENT_CAPTURE_MANAGER_SERVICE)
206public final class ContentCaptureManager {
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700207
Felipe Leme749b8892018-12-03 16:30:30 -0800208 private static final String TAG = ContentCaptureManager.class.getSimpleName();
Felipe Lemee348dc32018-11-05 12:35:29 -0800209
Sergey Volnov43929442020-01-28 15:36:59 +0000210 /** Error happened during the data sharing session. */
211 public static final int DATA_SHARE_ERROR_UNKNOWN = 1;
212
213 /** Request has been rejected, because a concurrent data share sessions is in progress. */
214 public static final int DATA_SHARE_ERROR_CONCURRENT_REQUEST = 2;
215
216 /** Request has been interrupted because of data share session timeout. */
217 public static final int DATA_SHARE_ERROR_TIMEOUT_INTERRUPTED = 3;
218
Felipe Lemebb0c2a22019-01-25 17:29:29 -0800219 /** @hide */
Felipe Lemef8b87782019-03-20 14:31:18 -0700220 public static final int RESULT_CODE_OK = 0;
221 /** @hide */
Felipe Lemebb0c2a22019-01-25 17:29:29 -0800222 public static final int RESULT_CODE_TRUE = 1;
223 /** @hide */
224 public static final int RESULT_CODE_FALSE = 2;
225 /** @hide */
Felipe Lemef8b87782019-03-20 14:31:18 -0700226 public static final int RESULT_CODE_SECURITY_EXCEPTION = -1;
Felipe Lemebb0c2a22019-01-25 17:29:29 -0800227
Perumaal Saddabba2019-01-04 16:43:35 -0800228 /**
229 * Timeout for calls to system_server.
230 */
231 private static final int SYNC_CALLS_TIMEOUT_MS = 5000;
232
Felipe Leme14ef4612019-02-07 12:24:38 -0800233 /**
234 * DeviceConfig property used by {@code com.android.server.SystemServer} on start to decide
Felipe Lemef0d44c62019-03-26 16:36:58 -0700235 * whether the content capture service should be created or not
Felipe Leme14ef4612019-02-07 12:24:38 -0800236 *
237 * <p>By default it should *NOT* be set (or set to {@code "default"}, so the decision is based
238 * on whether the OEM provides an implementation for the service), but it can be overridden to:
239 *
240 * <ul>
241 * <li>Provide a "kill switch" so OEMs can disable it remotely in case of emergency (when
242 * it's set to {@code "false"}).
243 * <li>Enable the CTS tests to be run on AOSP builds (when it's set to {@code "true"}).
244 * </ul>
245 *
246 * @hide
247 */
248 @TestApi
249 public static final String DEVICE_CONFIG_PROPERTY_SERVICE_EXPLICITLY_ENABLED =
250 "service_explicitly_enabled";
251
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800252 /**
253 * Maximum number of events that are buffered before sent to the app.
254 *
255 * @hide
256 */
257 @TestApi
258 public static final String DEVICE_CONFIG_PROPERTY_MAX_BUFFER_SIZE = "max_buffer_size";
259
260 /**
261 * Frequency (in ms) of buffer flushes when no events are received.
262 *
263 * @hide
264 */
265 @TestApi
266 public static final String DEVICE_CONFIG_PROPERTY_IDLE_FLUSH_FREQUENCY = "idle_flush_frequency";
267
268 /**
269 * Frequency (in ms) of buffer flushes when no events are received and the last one was a
270 * text change event.
271 *
272 * @hide
273 */
274 @TestApi
275 public static final String DEVICE_CONFIG_PROPERTY_TEXT_CHANGE_FLUSH_FREQUENCY =
276 "text_change_flush_frequency";
277
278 /**
279 * Size of events that are logging on {@code dump}.
280 *
281 * <p>Set it to {@code 0} or less to disable history.
282 *
283 * @hide
284 */
285 @TestApi
286 public static final String DEVICE_CONFIG_PROPERTY_LOG_HISTORY_SIZE = "log_history_size";
287
288 /**
289 * Sets the logging level for {@code logcat} statements.
290 *
291 * <p>Valid values are: {@link #LOGGING_LEVEL_OFF}, {@value #LOGGING_LEVEL_DEBUG}, and
292 * {@link #LOGGING_LEVEL_VERBOSE}.
293 *
294 * @hide
295 */
296 @TestApi
297 public static final String DEVICE_CONFIG_PROPERTY_LOGGING_LEVEL = "logging_level";
298
Felipe Lemee764fa22019-02-21 16:45:35 -0800299 /**
300 * Sets how long (in ms) the service is bound while idle.
301 *
302 * <p>Use {@code 0} to keep it permanently bound.
303 *
304 * @hide
305 */
306 public static final String DEVICE_CONFIG_PROPERTY_IDLE_UNBIND_TIMEOUT = "idle_unbind_timeout";
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800307
308 /** @hide */
309 @TestApi
310 public static final int LOGGING_LEVEL_OFF = 0;
311
312 /** @hide */
313 @TestApi
314 public static final int LOGGING_LEVEL_DEBUG = 1;
315
316 /** @hide */
317 @TestApi
318 public static final int LOGGING_LEVEL_VERBOSE = 2;
319
320 /** @hide */
321 @IntDef(flag = false, value = {
322 LOGGING_LEVEL_OFF,
323 LOGGING_LEVEL_DEBUG,
324 LOGGING_LEVEL_VERBOSE
325 })
326 @Retention(RetentionPolicy.SOURCE)
327 public @interface LoggingLevel {}
328
Felipe Leme326f15a2019-02-19 09:42:24 -0800329
330 /** @hide */
331 public static final int DEFAULT_MAX_BUFFER_SIZE = 100;
332 /** @hide */
333 public static final int DEFAULT_IDLE_FLUSHING_FREQUENCY_MS = 5_000;
334 /** @hide */
335 public static final int DEFAULT_TEXT_CHANGE_FLUSHING_FREQUENCY_MS = 1_000;
336 /** @hide */
337 public static final int DEFAULT_LOG_HISTORY_SIZE = 10;
338
Adam He6079d152019-01-10 11:37:17 -0800339 private final Object mLock = new Object();
340
Felipe Lemeb18e3172018-11-27 10:33:41 -0800341 @NonNull
Felipe Lemee348dc32018-11-05 12:35:29 -0800342 private final Context mContext;
343
Felipe Lemed49d52c2019-02-15 09:48:20 -0800344 @NonNull
Felipe Leme749b8892018-12-03 16:30:30 -0800345 private final IContentCaptureManager mService;
Felipe Lemee348dc32018-11-05 12:35:29 -0800346
Felipe Leme326f15a2019-02-19 09:42:24 -0800347 @NonNull
348 final ContentCaptureOptions mOptions;
349
Adam He6079d152019-01-10 11:37:17 -0800350 // Flags used for starting session.
351 @GuardedBy("mLock")
352 private int mFlags;
353
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800354 // TODO(b/119220549): use UI Thread directly (as calls are one-way) or a shared thread / handler
Felipe Lemeb18e3172018-11-27 10:33:41 -0800355 // held at the Application level
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800356 @NonNull
Felipe Leme88eae3b2018-11-07 15:11:56 -0800357 private final Handler mHandler;
358
Adam He6079d152019-01-10 11:37:17 -0800359 @GuardedBy("mLock")
Felipe Leme87a9dc92018-12-18 14:28:07 -0800360 private MainContentCaptureSession mMainSession;
Felipe Leme4017b202018-12-10 12:13:31 -0800361
Felipe Lemee348dc32018-11-05 12:35:29 -0800362 /** @hide */
Felipe Leme41e9eb02019-04-17 13:57:59 -0700363 public interface ContentCaptureClient {
364 /**
365 * Gets the component name of the client.
366 */
367 @NonNull
368 ComponentName contentCaptureClientGetComponentName();
369 }
370
371 /** @hide */
Felipe Leme749b8892018-12-03 16:30:30 -0800372 public ContentCaptureManager(@NonNull Context context,
Felipe Leme326f15a2019-02-19 09:42:24 -0800373 @NonNull IContentCaptureManager service, @NonNull ContentCaptureOptions options) {
Felipe Lemed49d52c2019-02-15 09:48:20 -0800374 mContext = Preconditions.checkNotNull(context, "context cannot be null");
375 mService = Preconditions.checkNotNull(service, "service cannot be null");
Felipe Leme326f15a2019-02-19 09:42:24 -0800376 mOptions = Preconditions.checkNotNull(options, "options cannot be null");
Felipe Lemee348dc32018-11-05 12:35:29 -0800377
Felipe Leme326f15a2019-02-19 09:42:24 -0800378 ContentCaptureHelper.setLoggingLevel(mOptions.loggingLevel);
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800379
380 if (sVerbose) Log.v(TAG, "Constructor for " + context.getPackageName());
381
Felipe Leme34ccedf2019-01-17 13:42:35 -0800382 // TODO(b/119220549): we might not even need a handler, as the IPCs are oneway. But if we
383 // do, then we should optimize it to run the tests after the Choreographer finishes the most
384 // important steps of the frame.
385 mHandler = Handler.createAsync(Looper.getMainLooper());
Felipe Leme88eae3b2018-11-07 15:11:56 -0800386 }
387
Felipe Leme7a534082018-11-05 15:03:04 -0800388 /**
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800389 * Gets the main session associated with the context.
Felipe Leme88eae3b2018-11-07 15:11:56 -0800390 *
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800391 * <p>By default there's just one (associated with the activity lifecycle), but apps could
Felipe Leme87a9dc92018-12-18 14:28:07 -0800392 * explicitly add more using
393 * {@link ContentCaptureSession#createContentCaptureSession(ContentCaptureContext)}.
Felipe Leme88eae3b2018-11-07 15:11:56 -0800394 *
395 * @hide
396 */
397 @NonNull
Felipe Leme87a9dc92018-12-18 14:28:07 -0800398 @UiThread
399 public MainContentCaptureSession getMainContentCaptureSession() {
Adam He6079d152019-01-10 11:37:17 -0800400 synchronized (mLock) {
401 if (mMainSession == null) {
Felipe Leme609991d2019-01-30 16:27:24 -0800402 mMainSession = new MainContentCaptureSession(mContext, this, mHandler, mService);
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800403 if (sVerbose) Log.v(TAG, "getMainContentCaptureSession(): created " + mMainSession);
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800404 }
Adam He6079d152019-01-10 11:37:17 -0800405 return mMainSession;
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800406 }
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800407 }
408
409 /** @hide */
Felipe Leme3fe6e922019-02-04 17:52:27 -0800410 @UiThread
Felipe Lemeb0da18f2019-02-22 15:10:02 -0800411 public void onActivityCreated(@NonNull IBinder applicationToken,
Adam He43c06992019-04-15 15:41:49 -0700412 @NonNull ComponentName activityComponent) {
Felipe Leme5001b3b2019-03-20 18:25:28 -0700413 if (mOptions.lite) return;
Adam He6079d152019-01-10 11:37:17 -0800414 synchronized (mLock) {
Adam He6079d152019-01-10 11:37:17 -0800415 getMainContentCaptureSession().start(applicationToken, activityComponent, mFlags);
416 }
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800417 }
418
419 /** @hide */
Felipe Leme3fe6e922019-02-04 17:52:27 -0800420 @UiThread
Felipe Lemeb0da18f2019-02-22 15:10:02 -0800421 public void onActivityResumed() {
Felipe Leme5001b3b2019-03-20 18:25:28 -0700422 if (mOptions.lite) return;
Adam He42ec1d82019-09-26 12:24:09 -0700423 getMainContentCaptureSession().notifySessionResumed();
Felipe Lemeb0da18f2019-02-22 15:10:02 -0800424 }
425
426 /** @hide */
427 @UiThread
428 public void onActivityPaused() {
Felipe Leme5001b3b2019-03-20 18:25:28 -0700429 if (mOptions.lite) return;
Adam He42ec1d82019-09-26 12:24:09 -0700430 getMainContentCaptureSession().notifySessionPaused();
Felipe Lemeb0da18f2019-02-22 15:10:02 -0800431 }
432
433 /** @hide */
434 @UiThread
435 public void onActivityDestroyed() {
Felipe Leme5001b3b2019-03-20 18:25:28 -0700436 if (mOptions.lite) return;
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800437 getMainContentCaptureSession().destroy();
Felipe Leme88eae3b2018-11-07 15:11:56 -0800438 }
439
440 /**
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800441 * Flushes the content of all sessions.
Felipe Leme88eae3b2018-11-07 15:11:56 -0800442 *
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800443 * <p>Typically called by {@code Activity} when it's paused / resumed.
Felipe Leme88eae3b2018-11-07 15:11:56 -0800444 *
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800445 * @hide
Felipe Leme88eae3b2018-11-07 15:11:56 -0800446 */
Felipe Leme3fe6e922019-02-04 17:52:27 -0800447 @UiThread
Felipe Leme1af85ea2019-01-16 13:23:40 -0800448 public void flush(@FlushReason int reason) {
Felipe Leme5001b3b2019-03-20 18:25:28 -0700449 if (mOptions.lite) return;
Felipe Leme1af85ea2019-01-16 13:23:40 -0800450 getMainContentCaptureSession().flush(reason);
Felipe Leme88eae3b2018-11-07 15:11:56 -0800451 }
452
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700453 /**
Felipe Lemeecb08be2018-11-27 15:48:47 -0800454 * Returns the component name of the system service that is consuming the captured events for
455 * the current user.
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700456 */
457 @Nullable
Felipe Lemeecb08be2018-11-27 15:48:47 -0800458 public ComponentName getServiceComponentName() {
Felipe Leme5001b3b2019-03-20 18:25:28 -0700459 if (!isContentCaptureEnabled() && !mOptions.lite) return null;
Felipe Leme91ddeca2019-01-24 18:01:58 -0800460
Perumaal Saddabba2019-01-04 16:43:35 -0800461 final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
Perumaal Saddabba2019-01-04 16:43:35 -0800462 try {
Felipe Lemeeffa5f42019-01-28 14:59:46 -0800463 mService.getServiceComponentName(resultReceiver);
Perumaal Saddabba2019-01-04 16:43:35 -0800464 return resultReceiver.getParcelableResult();
465 } catch (RemoteException e) {
Perumaal Saddabba2019-01-04 16:43:35 -0800466 throw e.rethrowFromSystemServer();
467 }
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700468 }
469
470 /**
Felipe Lemef8b87782019-03-20 14:31:18 -0700471 * Gets the (optional) intent used to launch the service-specific settings.
472 *
473 * <p>This method is static because it's called by Settings, which might not be whitelisted
474 * for content capture (in which case the ContentCaptureManager on its context would be null).
475 *
476 * @hide
477 */
Felipe Leme5001b3b2019-03-20 18:25:28 -0700478 // TODO: use "lite" options as it's done by activities from the content capture service
Felipe Lemef8b87782019-03-20 14:31:18 -0700479 @Nullable
480 public static ComponentName getServiceSettingsComponentName() {
481 final IBinder binder = ServiceManager
482 .checkService(Context.CONTENT_CAPTURE_MANAGER_SERVICE);
483 if (binder == null) return null;
484
485 final IContentCaptureManager service = IContentCaptureManager.Stub.asInterface(binder);
486 final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
487 try {
488 service.getServiceSettingsActivity(resultReceiver);
489 final int resultCode = resultReceiver.getIntResult();
490 if (resultCode == RESULT_CODE_SECURITY_EXCEPTION) {
491 throw new SecurityException(resultReceiver.getStringResult());
492 }
493 return resultReceiver.getParcelableResult();
494 } catch (RemoteException e) {
495 throw e.rethrowFromSystemServer();
496 }
497 }
498
499 /**
Felipe Lemee348dc32018-11-05 12:35:29 -0800500 * Checks whether content capture is enabled for this activity.
Felipe Leme91ddeca2019-01-24 18:01:58 -0800501 *
502 * <p>There are many reasons it could be disabled, such as:
503 * <ul>
504 * <li>App itself disabled content capture through {@link #setContentCaptureEnabled(boolean)}.
Felipe Lemef0d44c62019-03-26 16:36:58 -0700505 * <li>Intelligence service did not whitelist content capture for this activity's package.
506 * <li>Intelligence service did not whitelist content capture for this specific activity.
507 * <li>Intelligence service disabled content capture globally.
508 * <li>User disabled content capture globally through the Android Settings app.
509 * <li>Device manufacturer (OEM) disabled content capture globally.
510 * <li>Transient errors, such as intelligence service package being updated.
Felipe Leme91ddeca2019-01-24 18:01:58 -0800511 * </ul>
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700512 */
513 public boolean isContentCaptureEnabled() {
Felipe Leme5001b3b2019-03-20 18:25:28 -0700514 if (mOptions.lite) return false;
515
Felipe Leme609991d2019-01-30 16:27:24 -0800516 final MainContentCaptureSession mainSession;
Adam He6079d152019-01-10 11:37:17 -0800517 synchronized (mLock) {
Felipe Leme609991d2019-01-30 16:27:24 -0800518 mainSession = mMainSession;
Adam He6079d152019-01-10 11:37:17 -0800519 }
Felipe Leme609991d2019-01-30 16:27:24 -0800520 // The main session is only set when the activity starts, so we need to return true until
521 // then.
522 if (mainSession != null && mainSession.isDisabled()) return false;
523
524 return true;
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700525 }
526
527 /**
Felipe Leme790be042019-03-25 09:52:19 -0700528 * Gets the list of conditions for when content capture should be allowed.
529 *
530 * <p>This method is typically used by web browsers so they don't generate unnecessary content
531 * capture events for websites the content capture service is not interested on.
532 *
533 * @return list of conditions, or {@code null} if the service didn't set any restriction
Felipe Lemef0d44c62019-03-26 16:36:58 -0700534 * (in which case content capture events should always be generated). If the list is empty,
535 * then it should not generate any event at all.
Felipe Leme790be042019-03-25 09:52:19 -0700536 */
537 @Nullable
538 public Set<ContentCaptureCondition> getContentCaptureConditions() {
Felipe Lemea8d33c22019-03-25 16:36:09 -0700539 // NOTE: we could cache the conditions on ContentCaptureOptions, but then it would be stick
540 // to the lifetime of the app. OTOH, by dynamically calling the server every time, we allow
541 // the service to fine tune how long-lived apps (like browsers) are whitelisted.
542 if (!isContentCaptureEnabled() && !mOptions.lite) return null;
543
Felipe Lemeafbba9f2019-03-26 14:02:25 -0700544 final SyncResultReceiver resultReceiver = syncRun(
545 (r) -> mService.getContentCaptureConditions(mContext.getPackageName(), r));
546
547 final ArrayList<ContentCaptureCondition> result = resultReceiver
548 .getParcelableListResult();
549 return toSet(result);
Felipe Leme790be042019-03-25 09:52:19 -0700550 }
551
552 /**
Felipe Leme284ad1c2018-11-15 18:16:12 -0800553 * Called by apps to explicitly enable or disable content capture.
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700554 *
555 * <p><b>Note: </b> this call is not persisted accross reboots, so apps should typically call
556 * it on {@link android.app.Activity#onCreate(android.os.Bundle, android.os.PersistableBundle)}.
557 */
Felipe Leme6b3a55c2018-11-13 17:14:03 -0800558 public void setContentCaptureEnabled(boolean enabled) {
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800559 if (sDebug) {
Felipe Lemebb0c2a22019-01-25 17:29:29 -0800560 Log.d(TAG, "setContentCaptureEnabled(): setting to " + enabled + " for " + mContext);
561 }
562
Adam He6c0afca2019-04-15 13:54:11 -0700563 MainContentCaptureSession mainSession;
Adam He6079d152019-01-10 11:37:17 -0800564 synchronized (mLock) {
Adam He6c0afca2019-04-15 13:54:11 -0700565 if (enabled) {
566 mFlags &= ~ContentCaptureContext.FLAG_DISABLED_BY_APP;
567 } else {
568 mFlags |= ContentCaptureContext.FLAG_DISABLED_BY_APP;
569 }
570 mainSession = mMainSession;
571 }
572 if (mainSession != null) {
573 mainSession.setDisabled(!enabled);
Adam He6079d152019-01-10 11:37:17 -0800574 }
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800575 }
576
577 /**
Adam He43c06992019-04-15 15:41:49 -0700578 * Called by apps to update flag secure when window attributes change.
579 *
580 * @hide
581 */
582 public void updateWindowAttributes(@NonNull WindowManager.LayoutParams params) {
583 if (sDebug) {
584 Log.d(TAG, "updateWindowAttributes(): window flags=" + params.flags);
585 }
586 final boolean flagSecureEnabled =
587 (params.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0;
588
589 MainContentCaptureSession mainSession;
590 synchronized (mLock) {
591 if (flagSecureEnabled) {
592 mFlags |= ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE;
593 } else {
594 mFlags &= ~ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE;
595 }
596 mainSession = mMainSession;
597 }
598 if (mainSession != null) {
599 mainSession.setDisabled(flagSecureEnabled);
600 }
601 }
602
603 /**
Felipe Lemef0d44c62019-03-26 16:36:58 -0700604 * Gets whether content capture is enabled for the given user.
Felipe Leme91ddeca2019-01-24 18:01:58 -0800605 *
Felipe Lemef0d44c62019-03-26 16:36:58 -0700606 * <p>This method is typically used by the content capture service settings page, so it can
Felipe Leme91ddeca2019-01-24 18:01:58 -0800607 * provide a toggle to enable / disable it.
608 *
Felipe Lemef0d44c62019-03-26 16:36:58 -0700609 * @throws SecurityException if caller is not the app that owns the content capture service
Felipe Lemebb0c2a22019-01-25 17:29:29 -0800610 * associated with the user.
611 *
Felipe Leme91ddeca2019-01-24 18:01:58 -0800612 * @hide
613 */
614 @SystemApi
Felipe Leme19652c02019-02-04 13:01:29 -0800615 @TestApi
Felipe Leme91ddeca2019-01-24 18:01:58 -0800616 public boolean isContentCaptureFeatureEnabled() {
Felipe Lemeafbba9f2019-03-26 14:02:25 -0700617 final SyncResultReceiver resultReceiver = syncRun(
618 (r) -> mService.isContentCaptureFeatureEnabled(r));
619 final int resultCode = resultReceiver.getIntResult();
Felipe Lemebb0c2a22019-01-25 17:29:29 -0800620 switch (resultCode) {
621 case RESULT_CODE_TRUE:
622 return true;
623 case RESULT_CODE_FALSE:
624 return false;
Felipe Lemebb0c2a22019-01-25 17:29:29 -0800625 default:
Felipe Leme72e83d82019-02-13 14:51:17 -0800626 Log.wtf(TAG, "received invalid result: " + resultCode);
627 return false;
Felipe Lemebb0c2a22019-01-25 17:29:29 -0800628 }
Felipe Leme91ddeca2019-01-24 18:01:58 -0800629 }
630
631 /**
Felipe Leme4439ee62019-05-08 10:27:49 -0700632 * Called by the app to request the content capture service to remove content capture data
633 * associated with some context.
Felipe Lemeaa5088e2018-12-10 14:53:58 -0800634 *
635 * @param request object specifying what user data should be removed.
636 */
Felipe Leme4439ee62019-05-08 10:27:49 -0700637 public void removeData(@NonNull DataRemovalRequest request) {
Adam He3d0409b2019-01-15 14:22:04 -0800638 Preconditions.checkNotNull(request);
639
640 try {
Felipe Leme4439ee62019-05-08 10:27:49 -0700641 mService.removeData(request);
Adam He3d0409b2019-01-15 14:22:04 -0800642 } catch (RemoteException e) {
643 e.rethrowFromSystemServer();
644 }
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700645 }
646
Felipe Lemeafbba9f2019-03-26 14:02:25 -0700647 /**
Sergey Volnov6e049012020-01-10 18:10:48 +0000648 * Called by the app to request data sharing via writing to a file.
649 *
650 * <p>The ContentCaptureService app will receive a read-only file descriptor pointing to the
651 * same file and will be able to read data being shared from it.
652 *
653 * <p>Note: using this API doesn't guarantee the app staying alive and is "best-effort".
654 * Starting a foreground service would minimize the chances of the app getting killed during the
655 * file sharing session.
656 *
657 * @param request object specifying details of the data being shared.
658 */
659 public void shareData(@NonNull DataShareRequest request,
660 @NonNull @CallbackExecutor Executor executor,
661 @NonNull DataShareWriteAdapter dataShareWriteAdapter) {
662 Preconditions.checkNotNull(request);
663 Preconditions.checkNotNull(dataShareWriteAdapter);
664 Preconditions.checkNotNull(executor);
665
666 try {
Sergey Volnov43929442020-01-28 15:36:59 +0000667 mService.shareData(request,
668 new DataShareAdapterDelegate(executor, dataShareWriteAdapter));
Sergey Volnov6e049012020-01-10 18:10:48 +0000669 } catch (RemoteException e) {
670 e.rethrowFromSystemServer();
671 }
672 }
673
674 /**
Felipe Lemeafbba9f2019-03-26 14:02:25 -0700675 * Runs a sync method in the service, properly handling exceptions.
676 *
677 * @throws SecurityException if caller is not allowed to execute the method.
678 */
679 @NonNull
680 private SyncResultReceiver syncRun(@NonNull MyRunnable r) {
681 final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
682 try {
683 r.run(resultReceiver);
684 final int resultCode = resultReceiver.getIntResult();
685 if (resultCode == RESULT_CODE_SECURITY_EXCEPTION) {
686 throw new SecurityException(resultReceiver.getStringResult());
687 }
688 return resultReceiver;
689 } catch (RemoteException e) {
690 throw e.rethrowFromSystemServer();
691 }
692 }
693
Felipe Lemee348dc32018-11-05 12:35:29 -0800694 /** @hide */
695 public void dump(String prefix, PrintWriter pw) {
Felipe Lemed49d52c2019-02-15 09:48:20 -0800696 pw.print(prefix); pw.println("ContentCaptureManager");
697 final String prefix2 = prefix + " ";
Adam He6079d152019-01-10 11:37:17 -0800698 synchronized (mLock) {
Felipe Lemed49d52c2019-02-15 09:48:20 -0800699 pw.print(prefix2); pw.print("isContentCaptureEnabled(): ");
Felipe Leme609991d2019-01-30 16:27:24 -0800700 pw.println(isContentCaptureEnabled());
Felipe Leme326f15a2019-02-19 09:42:24 -0800701 pw.print(prefix2); pw.print("Debug: "); pw.print(sDebug);
Felipe Lemed32d8f6f2019-02-15 10:25:33 -0800702 pw.print(" Verbose: "); pw.println(sVerbose);
Felipe Leme326f15a2019-02-19 09:42:24 -0800703 pw.print(prefix2); pw.print("Context: "); pw.println(mContext);
704 pw.print(prefix2); pw.print("User: "); pw.println(mContext.getUserId());
705 pw.print(prefix2); pw.print("Service: "); pw.println(mService);
706 pw.print(prefix2); pw.print("Flags: "); pw.println(mFlags);
707 pw.print(prefix2); pw.print("Options: "); mOptions.dumpShort(pw); pw.println();
Adam He6079d152019-01-10 11:37:17 -0800708 if (mMainSession != null) {
Felipe Lemed49d52c2019-02-15 09:48:20 -0800709 final String prefix3 = prefix2 + " ";
710 pw.print(prefix2); pw.println("Main session:");
711 mMainSession.dump(prefix3, pw);
Adam He6079d152019-01-10 11:37:17 -0800712 } else {
Felipe Lemed49d52c2019-02-15 09:48:20 -0800713 pw.print(prefix2); pw.println("No sessions");
Adam He6079d152019-01-10 11:37:17 -0800714 }
Felipe Lemee348dc32018-11-05 12:35:29 -0800715 }
716 }
Felipe Lemeafbba9f2019-03-26 14:02:25 -0700717
718 private interface MyRunnable {
719 void run(@NonNull SyncResultReceiver receiver) throws RemoteException;
720 }
Sergey Volnov6e049012020-01-10 18:10:48 +0000721
722 private static class DataShareAdapterDelegate extends IDataShareWriteAdapter.Stub {
723
724 private final WeakReference<DataShareWriteAdapter> mAdapterReference;
725 private final WeakReference<Executor> mExecutorReference;
726
Sergey Volnov43929442020-01-28 15:36:59 +0000727 private DataShareAdapterDelegate(Executor executor, DataShareWriteAdapter adapter) {
Sergey Volnov6e049012020-01-10 18:10:48 +0000728 Preconditions.checkNotNull(executor);
729 Preconditions.checkNotNull(adapter);
730
731 mExecutorReference = new WeakReference<>(executor);
732 mAdapterReference = new WeakReference<>(adapter);
733 }
734
735 @Override
736 public void write(ParcelFileDescriptor destination)
737 throws RemoteException {
Sergey Volnov43929442020-01-28 15:36:59 +0000738 executeAdapterMethodLocked(adapter -> adapter.onWrite(destination), "onWrite");
Sergey Volnov6e049012020-01-10 18:10:48 +0000739 }
740
741 @Override
742 public void error(int errorCode) throws RemoteException {
743 executeAdapterMethodLocked(adapter -> adapter.onError(errorCode), "onError");
744 }
745
746 @Override
747 public void rejected() throws RemoteException {
748 executeAdapterMethodLocked(DataShareWriteAdapter::onRejected, "onRejected");
749 }
750
751 private void executeAdapterMethodLocked(Consumer<DataShareWriteAdapter> adapterFn,
752 String methodName) {
753 DataShareWriteAdapter adapter = mAdapterReference.get();
754 Executor executor = mExecutorReference.get();
755
756 if (adapter == null || executor == null) {
757 Slog.w(TAG, "Can't execute " + methodName + "(), references have been GC'ed");
758 return;
759 }
760
761 final long identity = Binder.clearCallingIdentity();
762 try {
763 executor.execute(() -> adapterFn.accept(adapter));
764 } finally {
765 Binder.restoreCallingIdentity(identity);
766 }
767 }
768 }
Felipe Leme1dfa9a02018-10-17 17:24:37 -0700769}