1 /*
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  */
16 package android.view.contentcapture;
17 
18 import static android.view.contentcapture.ContentCaptureHelper.sDebug;
19 import static android.view.contentcapture.ContentCaptureHelper.sVerbose;
20 import static android.view.contentcapture.ContentCaptureHelper.toSet;
21 import static android.view.contentcapture.flags.Flags.runOnBackgroundThreadEnabled;
22 
23 import android.annotation.CallbackExecutor;
24 import android.annotation.IntDef;
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.annotation.RequiresPermission;
28 import android.annotation.SystemApi;
29 import android.annotation.SystemService;
30 import android.annotation.TestApi;
31 import android.annotation.UiThread;
32 import android.annotation.UserIdInt;
33 import android.app.Activity;
34 import android.app.Service;
35 import android.content.ComponentName;
36 import android.content.ContentCaptureOptions;
37 import android.content.Context;
38 import android.graphics.Canvas;
39 import android.os.Binder;
40 import android.os.Handler;
41 import android.os.IBinder;
42 import android.os.Looper;
43 import android.os.ParcelFileDescriptor;
44 import android.os.RemoteException;
45 import android.os.ServiceManager;
46 import android.util.Dumpable;
47 import android.util.Log;
48 import android.util.Slog;
49 import android.view.View;
50 import android.view.ViewStructure;
51 import android.view.WindowManager;
52 import android.view.contentcapture.ContentCaptureSession.FlushReason;
53 
54 import com.android.internal.annotations.GuardedBy;
55 import com.android.internal.annotations.VisibleForTesting;
56 import com.android.internal.os.BackgroundThread;
57 import com.android.internal.util.RingBuffer;
58 import com.android.internal.util.SyncResultReceiver;
59 
60 import java.io.PrintWriter;
61 import java.lang.annotation.Retention;
62 import java.lang.annotation.RetentionPolicy;
63 import java.lang.ref.WeakReference;
64 import java.util.ArrayList;
65 import java.util.Collections;
66 import java.util.HashMap;
67 import java.util.List;
68 import java.util.Map;
69 import java.util.Objects;
70 import java.util.Set;
71 import java.util.concurrent.Executor;
72 import java.util.function.Consumer;
73 
74 /**
75  * <p>Provides additional ways for apps to integrate with the content capture subsystem.
76  *
77  * <p>Content capture provides real-time, continuous capture of application activity, display and
78  * events to an intelligence service that is provided by the Android system. The intelligence
79  * service then uses that info to mediate and speed user journey through different apps. For
80  * example, when the user receives a restaurant address in a chat app and switches to a map app
81  * to search for that restaurant, the intelligence service could offer an autofill dialog to
82  * let the user automatically select its address.
83  *
84  * <p>Content capture was designed with two major concerns in mind: privacy and performance.
85  *
86  * <ul>
87  *   <li><b>Privacy:</b> the intelligence service is a trusted component provided that is provided
88  *   by the device manufacturer and that cannot be changed by the user (although the user can
89  *   globaly disable content capture using the Android Settings app). This service can only use the
90  *   data for in-device machine learning, which is enforced both by process isolation and
91  *   <a href="https://source.android.com/compatibility/cdd">CDD requirements</a>.
92  *   <li><b>Performance:</b> content capture is highly optimized to minimize its impact in the app
93  *   jankiness and overall device system health. For example, its only enabled on apps (or even
94  *   specific activities from an app) that were explicitly allowlisted by the intelligence service,
95  *   and it buffers the events so they are sent in a batch to the service (see
96  *   {@link #isContentCaptureEnabled()} for other cases when its disabled).
97  * </ul>
98  *
99  * <p>In fact, before using this manager, the app developer should check if it's available. Example:
100  * <pre><code>
101  *  ContentCaptureManager mgr = context.getSystemService(ContentCaptureManager.class);
102  *  if (mgr != null && mgr.isContentCaptureEnabled()) {
103  *    // ...
104  *  }
105  *  </code></pre>
106  *
107  * <p>App developers usually don't need to explicitly interact with content capture, except when the
108  * app:
109  *
110  * <ul>
111  *   <li>Can define a contextual {@link android.content.LocusId} to identify unique state (such as a
112  *   conversation between 2 chat users).
113  *   <li>Can have multiple view hierarchies with different contextual meaning (for example, a
114  *   browser app with multiple tabs, each representing a different URL).
115  *   <li>Contains custom views (that extend View directly and are not provided by the standard
116  *   Android SDK.
117  *   <li>Contains views that provide their own virtual hierarchy (like a web browser that render the
118  *   HTML elements using a Canvas).
119  * </ul>
120  *
121  * <p>The main integration point with content capture is the {@link ContentCaptureSession}. A "main"
122  * session is automatically created by the Android System when content capture is enabled for the
123  * activity and its used by the standard Android views to notify the content capture service of
124  * events such as views being added, views been removed, and text changed by user input. The session
125  * could have a {@link ContentCaptureContext} to provide more contextual info about it, such as
126  * the locus associated with the view hierarchy (see {@link android.content.LocusId} for more info
127  * about locus). By default, the main session doesn't have a {@code ContentCaptureContext}, but you
128  * can change it after its created. Example:
129  *
130  * <pre><code>
131  * protected void onCreate(Bundle savedInstanceState) {
132  *   // Initialize view structure
133  *   ContentCaptureSession session = rootView.getContentCaptureSession();
134  *   if (session != null) {
135  *     session.setContentCaptureContext(ContentCaptureContext.forLocusId("chat_UserA_UserB"));
136  *   }
137  * }
138  * </code></pre>
139  *
140  * <p>If your activity contains view hierarchies with a different contextual meaning, you should
141  * created child sessions for each view hierarchy root. For example, if your activity is a browser,
142  * you could use the main session for the main URL being rendered, then child sessions for each
143  * {@code IFRAME}:
144  *
145  * <pre><code>
146  * ContentCaptureSession mMainSession;
147  *
148  * protected void onCreate(Bundle savedInstanceState) {
149  *    // Initialize view structure...
150  *    mMainSession = rootView.getContentCaptureSession();
151  *    if (mMainSession != null) {
152  *      mMainSession.setContentCaptureContext(
153  *          ContentCaptureContext.forLocusId("https://example.com"));
154  *    }
155  * }
156  *
157  * private void loadIFrame(View iframeRootView, String url) {
158  *   if (mMainSession != null) {
159  *      ContentCaptureSession iFrameSession = mMainSession.newChild(
160  *          ContentCaptureContext.forLocusId(url));
161  *      }
162  *      iframeRootView.setContentCaptureSession(iFrameSession);
163  *   }
164  *   // Load iframe...
165  * }
166  * </code></pre>
167  *
168  * <p>If your activity has custom views (i.e., views that extend {@link View} directly and provide
169  * just one logical view, not a virtual tree hiearchy) and it provides content that's relevant for
170  * content capture (as of {@link android.os.Build.VERSION_CODES#Q Android Q}, the only relevant
171  * content is text), then your view implementation should:
172  *
173  * <ul>
174  *   <li>Set it as important for content capture.
175  *   <li>Fill {@link ViewStructure} used for content capture.
176  *   <li>Notify the {@link ContentCaptureSession} when the text is changed by user input.
177  * </ul>
178  *
179  * <p>Here's an example of the relevant methods for an {@code EditText}-like view:
180  *
181  * <pre><code>
182  * public class MyEditText extends View {
183  *
184  * public MyEditText(...) {
185  *   if (getImportantForContentCapture() == IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) {
186  *     setImportantForContentCapture(IMPORTANT_FOR_CONTENT_CAPTURE_YES);
187  *   }
188  * }
189  *
190  * public void onProvideContentCaptureStructure(@NonNull ViewStructure structure, int flags) {
191  *   super.onProvideContentCaptureStructure(structure, flags);
192  *
193  *   structure.setText(getText(), getSelectionStart(), getSelectionEnd());
194  *   structure.setHint(getHint());
195  *   structure.setInputType(getInputType());
196  *   // set other properties like setTextIdEntry(), setTextLines(), setTextStyle(),
197  *   // setMinTextEms(), setMaxTextEms(), setMaxTextLength()
198  * }
199  *
200  * private void onTextChanged() {
201  *   if (isLaidOut() && isImportantForContentCapture() && isTextEditable()) {
202  *     ContentCaptureManager mgr = mContext.getSystemService(ContentCaptureManager.class);
203  *     if (cm != null && cm.isContentCaptureEnabled()) {
204  *        ContentCaptureSession session = getContentCaptureSession();
205  *        if (session != null) {
206  *          session.notifyViewTextChanged(getAutofillId(), getText());
207  *        }
208  *   }
209  * }
210  * </code></pre>
211  *
212  * <p>If your view provides its own virtual hierarchy (for example, if it's a browser that draws
213  * the HTML using {@link Canvas} or native libraries in a different render process), then the view
214  * is also responsible to notify the session when the virtual elements appear and disappear - see
215  * {@link View#onProvideContentCaptureStructure(ViewStructure, int)} for more info.
216  */
217 @SystemService(Context.CONTENT_CAPTURE_MANAGER_SERVICE)
218 public final class ContentCaptureManager {
219 
220     private static final String TAG = ContentCaptureManager.class.getSimpleName();
221 
222     /** @hide */
223     public static final boolean DEBUG = false;
224 
225     /** @hide */
226     @TestApi
227     public static final String DUMPABLE_NAME = "ContentCaptureManager";
228 
229     /** Error happened during the data sharing session. */
230     public static final int DATA_SHARE_ERROR_UNKNOWN = 1;
231 
232     /** Request has been rejected, because a concurrent data share sessions is in progress. */
233     public static final int DATA_SHARE_ERROR_CONCURRENT_REQUEST = 2;
234 
235     /** Request has been interrupted because of data share session timeout. */
236     public static final int DATA_SHARE_ERROR_TIMEOUT_INTERRUPTED = 3;
237 
238     /** @hide */
239     @IntDef(flag = false, value = {
240             DATA_SHARE_ERROR_UNKNOWN,
241             DATA_SHARE_ERROR_CONCURRENT_REQUEST,
242             DATA_SHARE_ERROR_TIMEOUT_INTERRUPTED
243     })
244     @Retention(RetentionPolicy.SOURCE)
245     public @interface DataShareError {}
246 
247     /** @hide */
248     public static final int RESULT_CODE_OK = 0;
249     /** @hide */
250     public static final int RESULT_CODE_TRUE = 1;
251     /** @hide */
252     public static final int RESULT_CODE_FALSE = 2;
253     /** @hide */
254     public static final int RESULT_CODE_SECURITY_EXCEPTION = -1;
255 
256     /**
257      * ID used to indicate that a session does not exist
258      * @hide
259      */
260     @SystemApi
261     public static final int NO_SESSION_ID = 0;
262 
263     /**
264      * Timeout for calls to system_server.
265      */
266     private static final int SYNC_CALLS_TIMEOUT_MS = 5000;
267 
268     /**
269      * DeviceConfig property used by {@code com.android.server.SystemServer} on start to decide
270      * whether the content capture service should be created or not
271      *
272      * <p>By default it should *NOT* be set (or set to {@code "default"}, so the decision is based
273      * on whether the OEM provides an implementation for the service), but it can be overridden to:
274      *
275      * <ul>
276      *   <li>Provide a "kill switch" so OEMs can disable it remotely in case of emergency (when
277      *   it's set to {@code "false"}).
278      *   <li>Enable the CTS tests to be run on AOSP builds (when it's set to {@code "true"}).
279      * </ul>
280      *
281      * @hide
282      */
283     @TestApi
284     public static final String DEVICE_CONFIG_PROPERTY_SERVICE_EXPLICITLY_ENABLED =
285             "service_explicitly_enabled";
286 
287     /**
288      * Device config property used by {@code android.widget.AbsListView} to determine whether or
289      * not it should report the positions of its children to Content Capture.
290      *
291      * @hide
292      */
293     public static final String DEVICE_CONFIG_PROPERTY_REPORT_LIST_VIEW_CHILDREN =
294             "report_list_view_children";
295 
296     /**
297      * Maximum number of events that are buffered before sent to the app.
298      *
299      * @hide
300      */
301     @TestApi
302     public static final String DEVICE_CONFIG_PROPERTY_MAX_BUFFER_SIZE = "max_buffer_size";
303 
304     /**
305      * Frequency (in ms) of buffer flushes when no events are received.
306      *
307      * @hide
308      */
309     @TestApi
310     public static final String DEVICE_CONFIG_PROPERTY_IDLE_FLUSH_FREQUENCY = "idle_flush_frequency";
311 
312     /**
313      * Frequency (in ms) of buffer flushes when no events are received and the last one was a
314      * text change event.
315      *
316      * @hide
317      */
318     @TestApi
319     public static final String DEVICE_CONFIG_PROPERTY_TEXT_CHANGE_FLUSH_FREQUENCY =
320             "text_change_flush_frequency";
321 
322     /**
323      * Size of events that are logging on {@code dump}.
324      *
325      * <p>Set it to {@code 0} or less to disable history.
326      *
327      * @hide
328      */
329     @TestApi
330     public static final String DEVICE_CONFIG_PROPERTY_LOG_HISTORY_SIZE = "log_history_size";
331 
332     /**
333      * Sets the logging level for {@code logcat} statements.
334      *
335      * <p>Valid values are: {@link #LOGGING_LEVEL_OFF}, {@value #LOGGING_LEVEL_DEBUG}, and
336      * {@link #LOGGING_LEVEL_VERBOSE}.
337      *
338      * @hide
339      */
340     @TestApi
341     public static final String DEVICE_CONFIG_PROPERTY_LOGGING_LEVEL = "logging_level";
342 
343     /**
344      * Sets how long (in ms) the service is bound while idle.
345      *
346      * <p>Use {@code 0} to keep it permanently bound.
347      *
348      * @hide
349      */
350     public static final String DEVICE_CONFIG_PROPERTY_IDLE_UNBIND_TIMEOUT = "idle_unbind_timeout";
351 
352     /**
353      * Sets to disable flush when receiving a VIEW_TREE_APPEARING event.
354      *
355      * @hide
356      */
357     public static final String DEVICE_CONFIG_PROPERTY_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING =
358             "disable_flush_for_view_tree_appearing";
359 
360     /**
361      * Enables the content protection receiver.
362      *
363      * @hide
364      */
365     public static final String DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER =
366             "enable_content_protection_receiver";
367 
368     /**
369      * Whether AssistContent snapshot should be sent on activity start.
370      *
371      * @hide
372      */
373     public static final String DEVICE_CONFIG_ENABLE_ACTIVITY_START_ASSIST_CONTENT =
374             "enable_activity_start_assist_content";
375 
376     /**
377      * Sets the size of the in-memory ring buffer for the content protection flow.
378      *
379      * @hide
380      */
381     public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE =
382             "content_protection_buffer_size";
383 
384     /**
385      * Sets the config for content protection required groups.
386      *
387      * @hide
388      */
389     public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG =
390             "content_protection_required_groups_config";
391 
392     /**
393      * Sets the config for content protection optional groups.
394      *
395      * @hide
396      */
397     public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG =
398             "content_protection_optional_groups_config";
399 
400     /**
401      * Sets the threshold for content protection optional groups.
402      *
403      * @hide
404      */
405     public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD =
406             "content_protection_optional_groups_threshold";
407 
408     /**
409      * Sets the initial delay for fetching content protection allowlist in milliseconds.
410      *
411      * @hide
412      */
413     public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_DELAY_MS =
414             "content_protection_allowlist_delay_ms";
415 
416     /**
417      * Sets the timeout for fetching content protection allowlist in milliseconds.
418      *
419      * @hide
420      */
421     public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_TIMEOUT_MS =
422             "content_protection_allowlist_timeout_ms";
423 
424     /**
425      * Sets the auto disconnect timeout for the content protection service in milliseconds.
426      *
427      * @hide
428      */
429     // Unit can't be in the name in order to pass the checkstyle hook, line would be too long.
430     public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_AUTO_DISCONNECT_TIMEOUT =
431             "content_protection_auto_disconnect_timeout_ms";
432 
433     /** @hide */
434     @TestApi
435     public static final int LOGGING_LEVEL_OFF = 0;
436 
437     /** @hide */
438     @TestApi
439     public static final int LOGGING_LEVEL_DEBUG = 1;
440 
441     /** @hide */
442     @TestApi
443     public static final int LOGGING_LEVEL_VERBOSE = 2;
444 
445     /** @hide */
446     @IntDef(flag = false, value = {
447             LOGGING_LEVEL_OFF,
448             LOGGING_LEVEL_DEBUG,
449             LOGGING_LEVEL_VERBOSE
450     })
451     @Retention(RetentionPolicy.SOURCE)
452     public @interface LoggingLevel {}
453 
454 
455     /** @hide */
456     public static final int DEFAULT_MAX_BUFFER_SIZE = 500; // Enough for typical busy screen.
457     /** @hide */
458     public static final int DEFAULT_IDLE_FLUSHING_FREQUENCY_MS = 5_000;
459     /** @hide */
460     public static final int DEFAULT_TEXT_CHANGE_FLUSHING_FREQUENCY_MS = 1_000;
461     /** @hide */
462     public static final int DEFAULT_LOG_HISTORY_SIZE = 10;
463     /** @hide */
464     public static final boolean DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING = false;
465     /** @hide */
466     public static final boolean DEFAULT_ENABLE_CONTENT_CAPTURE_RECEIVER = true;
467     /** @hide */
468     public static final boolean DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER = false;
469     /** @hide */
470     public static final int DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE = 150;
471     /** @hide */
472     public static final List<List<String>> DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS =
473             Collections.emptyList();
474     /** @hide */
475     public static final String DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG = "";
476     /** @hide */
477     public static final List<List<String>> DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS =
478             Collections.emptyList();
479     /** @hide */
480     public static final String DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG = "";
481     /** @hide */
482     public static final int DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD = 0;
483     /** @hide */
484     public static final long DEFAULT_CONTENT_PROTECTION_ALLOWLIST_DELAY_MS = 30000;
485     /** @hide */
486     public static final long DEFAULT_CONTENT_PROTECTION_ALLOWLIST_TIMEOUT_MS = 250;
487     /** @hide */
488     public static final long DEFAULT_CONTENT_PROTECTION_AUTO_DISCONNECT_TIMEOUT_MS = 3000;
489 
490     private final Object mLock = new Object();
491 
492     @NonNull
493     private final StrippedContext mContext;
494 
495     @NonNull
496     private final IContentCaptureManager mService;
497 
498     @GuardedBy("mLock")
499     private final LocalDataShareAdapterResourceManager mDataShareAdapterResourceManager;
500 
501     @NonNull
502     final ContentCaptureOptions mOptions;
503 
504     // Flags used for starting session.
505     @GuardedBy("mLock")
506     private int mFlags;
507 
508     @Nullable
509     @GuardedBy("mLock")
510     private Handler mUiHandler;
511 
512     @Nullable
513     @GuardedBy("mLock")
514     private Handler mContentCaptureHandler;
515 
516     @GuardedBy("mLock")
517     private ContentCaptureSession mMainSession;
518 
519     @Nullable // set on-demand by addDumpable()
520     private Dumper mDumpable;
521 
522     // Created here in order to live across activity and session changes
523     @Nullable private final RingBuffer<ContentCaptureEvent> mContentProtectionEventBuffer;
524 
525     /** @hide */
526     public interface ContentCaptureClient {
527         /**
528          * Gets the component name of the client.
529          */
530         @NonNull
contentCaptureClientGetComponentName()531         ComponentName contentCaptureClientGetComponentName();
532     }
533 
534     /** @hide */
535     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
536     public static class StrippedContext {
537         @NonNull final String mPackageName;
538         @NonNull final String mContext;
539         final @UserIdInt int mUserId;
540 
541         /** @hide */
542         @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
StrippedContext(@onNull Context context)543         public StrippedContext(@NonNull Context context) {
544             mPackageName = context.getPackageName();
545             mContext = context.toString();
546             mUserId = context.getUserId();
547         }
548 
549         @Override
toString()550         public String toString() {
551             return mContext;
552         }
553 
554         @NonNull
getPackageName()555         public String getPackageName() {
556             return mPackageName;
557         }
558 
559         @UserIdInt
getUserId()560         public int getUserId() {
561             return mUserId;
562         }
563     }
564 
565     /** @hide */
ContentCaptureManager(@onNull Context context, @NonNull IContentCaptureManager service, @NonNull ContentCaptureOptions options)566     public ContentCaptureManager(@NonNull Context context,
567             @NonNull IContentCaptureManager service, @NonNull ContentCaptureOptions options) {
568         Objects.requireNonNull(context, "context cannot be null");
569         mContext = new StrippedContext(context);
570         mService = Objects.requireNonNull(service, "service cannot be null");
571         mOptions = Objects.requireNonNull(options, "options cannot be null");
572 
573         ContentCaptureHelper.setLoggingLevel(mOptions.loggingLevel);
574         setFlushViewTreeAppearingEventDisabled(mOptions.disableFlushForViewTreeAppearing);
575 
576         if (sVerbose) Log.v(TAG, "Constructor for " + context.getPackageName());
577 
578         mDataShareAdapterResourceManager = new LocalDataShareAdapterResourceManager();
579 
580         if (mOptions.contentProtectionOptions.enableReceiver
581                 && mOptions.contentProtectionOptions.bufferSize > 0) {
582             mContentProtectionEventBuffer =
583                     new RingBuffer(
584                             ContentCaptureEvent.class,
585                             mOptions.contentProtectionOptions.bufferSize);
586         } else {
587             mContentProtectionEventBuffer = null;
588         }
589     }
590 
591     /**
592      * Gets the main session associated with the context.
593      *
594      * <p>By default there's just one (associated with the activity lifecycle), but apps could
595      * explicitly add more using
596      * {@link ContentCaptureSession#createContentCaptureSession(ContentCaptureContext)}.
597      *
598      * @hide
599      */
600     @NonNull
601     @UiThread
getMainContentCaptureSession()602     public ContentCaptureSession getMainContentCaptureSession() {
603         synchronized (mLock) {
604             if (mMainSession == null) {
605                 mMainSession = prepareMainSession();
606                 if (sVerbose) Log.v(TAG, "getMainContentCaptureSession(): created " + mMainSession);
607             }
608             return mMainSession;
609         }
610     }
611 
612     @NonNull
613     @GuardedBy("mLock")
prepareMainSession()614     private ContentCaptureSession prepareMainSession() {
615         if (runOnBackgroundThreadEnabled()) {
616             return new MainContentCaptureSessionV2(
617                     mContext,
618                     this,
619                     prepareUiHandler(),
620                     prepareContentCaptureHandler(),
621                     mService
622             );
623         } else {
624             return new MainContentCaptureSession(mContext, this, prepareUiHandler(), mService);
625         }
626     }
627 
628     @NonNull
629     @GuardedBy("mLock")
prepareContentCaptureHandler()630     private Handler prepareContentCaptureHandler() {
631         if (mContentCaptureHandler == null) {
632             mContentCaptureHandler = BackgroundThread.getHandler();
633         }
634         return mContentCaptureHandler;
635     }
636 
637     @NonNull
638     @GuardedBy("mLock")
prepareUiHandler()639     private Handler prepareUiHandler() {
640         if (mUiHandler == null) {
641             mUiHandler = Handler.createAsync(Looper.getMainLooper());
642         }
643         return mUiHandler;
644     }
645 
646     /** @hide */
647     @UiThread
onActivityCreated(@onNull IBinder applicationToken, @NonNull IBinder shareableActivityToken, @NonNull ComponentName activityComponent)648     public void onActivityCreated(@NonNull IBinder applicationToken,
649             @NonNull IBinder shareableActivityToken, @NonNull ComponentName activityComponent) {
650         if (mOptions.lite) return;
651         synchronized (mLock) {
652             getMainContentCaptureSession().start(applicationToken, shareableActivityToken,
653                     activityComponent, mFlags);
654         }
655     }
656 
657     /** @hide */
658     @UiThread
onActivityResumed()659     public void onActivityResumed() {
660         if (mOptions.lite) return;
661         getMainContentCaptureSession().notifySessionResumed();
662     }
663 
664     /** @hide */
665     @UiThread
onActivityPaused()666     public void onActivityPaused() {
667         if (mOptions.lite) return;
668         getMainContentCaptureSession().notifySessionPaused();
669     }
670 
671     /** @hide */
672     @UiThread
onActivityDestroyed()673     public void onActivityDestroyed() {
674         if (mOptions.lite) return;
675         getMainContentCaptureSession().destroy();
676     }
677 
678     /**
679      * Flushes the content of all sessions.
680      *
681      * <p>Typically called by {@code Activity} when it's paused / resumed.
682      *
683      * @hide
684      */
685     @UiThread
flush(@lushReason int reason)686     public void flush(@FlushReason int reason) {
687         if (mOptions.lite) return;
688         getMainContentCaptureSession().flush(reason);
689     }
690 
691     /**
692      * Returns the component name of the system service that is consuming the captured events for
693      * the current user.
694      *
695      * @throws RuntimeException if getting the component name is timed out.
696      */
697     @Nullable
getServiceComponentName()698     public ComponentName getServiceComponentName() {
699         if (!isContentCaptureEnabled() && !mOptions.lite) return null;
700 
701         final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
702         try {
703             mService.getServiceComponentName(resultReceiver);
704             return resultReceiver.getParcelableResult();
705         } catch (RemoteException e) {
706             throw e.rethrowFromSystemServer();
707         } catch (SyncResultReceiver.TimeoutException e) {
708             throw new RuntimeException("Fail to get service componentName.");
709         }
710     }
711 
712     /**
713      * Gets the (optional) intent used to launch the service-specific settings.
714      *
715      * <p>This method is static because it's called by Settings, which might not be allowlisted
716      * for content capture (in which case the ContentCaptureManager on its context would be null).
717      *
718      * @hide
719      */
720     // TODO: use "lite" options as it's done by activities from the content capture service
721     @Nullable
getServiceSettingsComponentName()722     public static ComponentName getServiceSettingsComponentName() {
723         final IBinder binder = ServiceManager
724                 .checkService(Context.CONTENT_CAPTURE_MANAGER_SERVICE);
725         if (binder == null) return null;
726 
727         final IContentCaptureManager service = IContentCaptureManager.Stub.asInterface(binder);
728         final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
729         try {
730             service.getServiceSettingsActivity(resultReceiver);
731             final int resultCode = resultReceiver.getIntResult();
732             if (resultCode == RESULT_CODE_SECURITY_EXCEPTION) {
733                 throw new SecurityException(resultReceiver.getStringResult());
734             }
735             return resultReceiver.getParcelableResult();
736         } catch (RemoteException e) {
737             throw e.rethrowFromSystemServer();
738         } catch (SyncResultReceiver.TimeoutException e) {
739             Log.e(TAG, "Fail to get service settings componentName: " + e);
740             return null;
741         }
742     }
743 
744     /**
745      * Checks whether content capture is enabled for this activity.
746      *
747      * <p>There are many reasons it could be disabled, such as:
748      * <ul>
749      *   <li>App itself disabled content capture through {@link #setContentCaptureEnabled(boolean)}.
750      *   <li>Intelligence service did not allowlist content capture for this activity's package.
751      *   <li>Intelligence service did not allowlist content capture for this specific activity.
752      *   <li>Intelligence service disabled content capture globally.
753      *   <li>User disabled content capture globally through the Android Settings app.
754      *   <li>Device manufacturer (OEM) disabled content capture globally.
755      *   <li>Transient errors, such as intelligence service package being updated.
756      * </ul>
757      */
isContentCaptureEnabled()758     public boolean isContentCaptureEnabled() {
759         if (mOptions.lite) return false;
760 
761         final ContentCaptureSession mainSession;
762         synchronized (mLock) {
763             mainSession = mMainSession;
764         }
765         // The main session is only set when the activity starts, so we need to return true until
766         // then.
767         if (mainSession != null && mainSession.isDisabled()) return false;
768 
769         return true;
770     }
771 
772     /**
773      * Gets the list of conditions for when content capture should be allowed.
774      *
775      * <p>This method is typically used by web browsers so they don't generate unnecessary content
776      * capture events for websites the content capture service is not interested on.
777      *
778      * @return list of conditions, or {@code null} if the service didn't set any restriction
779      * (in which case content capture events should always be generated). If the list is empty,
780      * then it should not generate any event at all.
781      */
782     @Nullable
getContentCaptureConditions()783     public Set<ContentCaptureCondition> getContentCaptureConditions() {
784         // NOTE: we could cache the conditions on ContentCaptureOptions, but then it would be stick
785         // to the lifetime of the app. OTOH, by dynamically calling the server every time, we allow
786         // the service to fine tune how long-lived apps (like browsers) are allowlisted.
787         if (!isContentCaptureEnabled() && !mOptions.lite) return null;
788 
789         final SyncResultReceiver resultReceiver = syncRun(
790                 (r) -> mService.getContentCaptureConditions(mContext.getPackageName(), r));
791 
792         try {
793             final ArrayList<ContentCaptureCondition> result = resultReceiver
794                     .getParcelableListResult();
795             return toSet(result);
796         } catch (SyncResultReceiver.TimeoutException e) {
797             throw new RuntimeException("Fail to get content capture conditions.");
798         }
799     }
800 
801     /**
802      * Called by apps to explicitly enable or disable content capture.
803      *
804      * <p><b>Note: </b> this call is not persisted accross reboots, so apps should typically call
805      * it on {@link android.app.Activity#onCreate(android.os.Bundle, android.os.PersistableBundle)}.
806      */
setContentCaptureEnabled(boolean enabled)807     public void setContentCaptureEnabled(boolean enabled) {
808         if (sDebug) {
809             Log.d(TAG, "setContentCaptureEnabled(): setting to " + enabled + " for " + mContext);
810         }
811 
812         ContentCaptureSession mainSession;
813         synchronized (mLock) {
814             if (enabled) {
815                 mFlags &= ~ContentCaptureContext.FLAG_DISABLED_BY_APP;
816             } else {
817                 mFlags |= ContentCaptureContext.FLAG_DISABLED_BY_APP;
818             }
819             mainSession = mMainSession;
820         }
821         if (mainSession != null) {
822             mainSession.setDisabled(!enabled);
823         }
824     }
825 
826     /**
827      * Called by apps to update flag secure when window attributes change.
828      *
829      * @hide
830      */
updateWindowAttributes(@onNull WindowManager.LayoutParams params)831     public void updateWindowAttributes(@NonNull WindowManager.LayoutParams params) {
832         if (sDebug) {
833             Log.d(TAG, "updateWindowAttributes(): window flags=" + params.flags);
834         }
835         final boolean flagSecureEnabled =
836                 (params.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0;
837 
838         ContentCaptureSession mainSession;
839         boolean alreadyDisabledByApp;
840         synchronized (mLock) {
841             alreadyDisabledByApp = (mFlags & ContentCaptureContext.FLAG_DISABLED_BY_APP) != 0;
842             if (flagSecureEnabled) {
843                 mFlags |= ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE;
844             } else {
845                 mFlags &= ~ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE;
846             }
847             mainSession = mMainSession;
848         }
849 
850         // Prevent overriding the status of disabling by app
851         if (mainSession != null && !alreadyDisabledByApp) {
852             mainSession.setDisabled(flagSecureEnabled);
853         }
854     }
855 
856     /**
857      * Explicitly sets enable or disable flush for view tree appearing event.
858      *
859      * @hide
860      */
861     @VisibleForTesting
setFlushViewTreeAppearingEventDisabled(boolean disabled)862     public void setFlushViewTreeAppearingEventDisabled(boolean disabled) {
863         if (sDebug) {
864             Log.d(TAG, "setFlushViewTreeAppearingEventDisabled(): setting to " + disabled);
865         }
866 
867         synchronized (mLock) {
868             if (disabled) {
869                 mFlags |= ContentCaptureContext.FLAG_DISABLED_FLUSH_FOR_VIEW_TREE_APPEARING;
870             } else {
871                 mFlags &= ~ContentCaptureContext.FLAG_DISABLED_FLUSH_FOR_VIEW_TREE_APPEARING;
872             }
873         }
874     }
875 
876     /**
877      * Gets whether content capture is needed to flush for view tree appearing event.
878      *
879      * @hide
880      */
getFlushViewTreeAppearingEventDisabled()881     public boolean getFlushViewTreeAppearingEventDisabled() {
882         synchronized (mLock) {
883             return (mFlags & ContentCaptureContext.FLAG_DISABLED_FLUSH_FOR_VIEW_TREE_APPEARING)
884                     != 0;
885         }
886     }
887 
888     /**
889      * Gets whether content capture is enabled for the given user.
890      *
891      * <p>This method is typically used by the content capture service settings page, so it can
892      * provide a toggle to enable / disable it.
893      *
894      * @throws SecurityException if caller is not the app that owns the content capture service
895      * associated with the user.
896      *
897      * @hide
898      */
899     @SystemApi
isContentCaptureFeatureEnabled()900     public boolean isContentCaptureFeatureEnabled() {
901         final SyncResultReceiver resultReceiver = syncRun(
902                 (r) -> mService.isContentCaptureFeatureEnabled(r));
903 
904         try {
905             final int resultCode = resultReceiver.getIntResult();
906             switch (resultCode) {
907                 case RESULT_CODE_TRUE:
908                     return true;
909                 case RESULT_CODE_FALSE:
910                     return false;
911                 default:
912                     Log.wtf(TAG, "received invalid result: " + resultCode);
913                     return false;
914             }
915         } catch (SyncResultReceiver.TimeoutException e) {
916             Log.e(TAG, "Fail to get content capture feature enable status: " + e);
917             return false;
918         }
919     }
920 
921     /**
922      * Called by the app to request the content capture service to remove content capture data
923      * associated with some context.
924      *
925      * @param request object specifying what user data should be removed.
926      */
removeData(@onNull DataRemovalRequest request)927     public void removeData(@NonNull DataRemovalRequest request) {
928         Objects.requireNonNull(request);
929 
930         try {
931             mService.removeData(request);
932         } catch (RemoteException e) {
933             throw e.rethrowFromSystemServer();
934         }
935     }
936 
937     /**
938      * Called by the app to request data sharing via writing to a file.
939      *
940      * <p>The ContentCaptureService app will receive a read-only file descriptor pointing to the
941      * same file and will be able to read data being shared from it.
942      *
943      * <p>Note: using this API doesn't guarantee the app staying alive and is "best-effort".
944      * Starting a foreground service would minimize the chances of the app getting killed during the
945      * file sharing session.
946      *
947      * @param request object specifying details of the data being shared.
948      */
shareData(@onNull DataShareRequest request, @NonNull @CallbackExecutor Executor executor, @NonNull DataShareWriteAdapter dataShareWriteAdapter)949     public void shareData(@NonNull DataShareRequest request,
950             @NonNull @CallbackExecutor Executor executor,
951             @NonNull DataShareWriteAdapter dataShareWriteAdapter) {
952         Objects.requireNonNull(request);
953         Objects.requireNonNull(dataShareWriteAdapter);
954         Objects.requireNonNull(executor);
955 
956         try {
957             mService.shareData(request,
958                     new DataShareAdapterDelegate(executor, dataShareWriteAdapter,
959                             mDataShareAdapterResourceManager));
960         } catch (RemoteException e) {
961             throw e.rethrowFromSystemServer();
962         }
963     }
964 
965     /**
966      * Runs a sync method in the service, properly handling exceptions.
967      *
968      * @throws SecurityException if caller is not allowed to execute the method.
969      */
970     @NonNull
syncRun(@onNull MyRunnable r)971     private SyncResultReceiver syncRun(@NonNull MyRunnable r) {
972         final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
973         try {
974             r.run(resultReceiver);
975             final int resultCode = resultReceiver.getIntResult();
976             if (resultCode == RESULT_CODE_SECURITY_EXCEPTION) {
977                 throw new SecurityException(resultReceiver.getStringResult());
978             }
979         } catch (RemoteException e) {
980             throw e.rethrowFromSystemServer();
981         } catch (SyncResultReceiver.TimeoutException e) {
982             throw new RuntimeException("Fail to get syn run result from SyncResultReceiver.");
983         }
984         return resultReceiver;
985     }
986 
987     /** @hide */
addDumpable(Activity activity)988     public void addDumpable(Activity activity) {
989         if (mDumpable == null) {
990             mDumpable = new Dumper();
991         }
992         activity.addDumpable(mDumpable);
993     }
994 
995     /** @hide */
996     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
997     @Nullable
getContentProtectionEventBuffer()998     public RingBuffer<ContentCaptureEvent> getContentProtectionEventBuffer() {
999         return mContentProtectionEventBuffer;
1000     }
1001 
1002     // NOTE: ContentCaptureManager cannot implement it directly as it would be exposed as public API
1003     private final class Dumper implements Dumpable {
1004         @Override
dump(@onNull PrintWriter pw, @Nullable String[] args)1005         public void dump(@NonNull PrintWriter pw, @Nullable String[] args) {
1006             String prefix = "";
1007             pw.print(prefix); pw.println("ContentCaptureManager");
1008             final String prefix2 = prefix + "  ";
1009             synchronized (mLock) {
1010                 pw.print(prefix2); pw.print("isContentCaptureEnabled(): ");
1011                 pw.println(isContentCaptureEnabled());
1012                 pw.print(prefix2); pw.print("Debug: "); pw.print(sDebug);
1013                 pw.print(" Verbose: "); pw.println(sVerbose);
1014                 pw.print(prefix2); pw.print("Context: "); pw.println(mContext);
1015                 pw.print(prefix2); pw.print("User: "); pw.println(mContext.getUserId());
1016                 pw.print(prefix2); pw.print("Service: "); pw.println(mService);
1017                 pw.print(prefix2); pw.print("Flags: "); pw.println(mFlags);
1018                 pw.print(prefix2); pw.print("Options: "); mOptions.dumpShort(pw); pw.println();
1019                 if (mMainSession != null) {
1020                     final String prefix3 = prefix2 + "  ";
1021                     pw.print(prefix2); pw.println("Main session:");
1022                     mMainSession.dump(prefix3, pw);
1023                 } else {
1024                     pw.print(prefix2); pw.println("No sessions");
1025                 }
1026             }
1027         }
1028 
1029         @Override
getDumpableName()1030         public String getDumpableName() {
1031             return DUMPABLE_NAME;
1032         }
1033     }
1034 
1035     /**
1036      * Resets the temporary content capture service implementation to the default component.
1037      *
1038      * @hide
1039      */
1040     @TestApi
1041     @RequiresPermission(android.Manifest.permission.MANAGE_CONTENT_CAPTURE)
resetTemporaryService(@serIdInt int userId)1042     public static void resetTemporaryService(@UserIdInt int userId) {
1043         final IContentCaptureManager service = getService();
1044         if (service == null) {
1045             Log.e(TAG, "IContentCaptureManager is null");
1046         }
1047         try {
1048             service.resetTemporaryService(userId);
1049         } catch (RemoteException e) {
1050             throw e.rethrowFromSystemServer();
1051         }
1052     }
1053 
1054     /**
1055      * Temporarily sets the content capture service implementation.
1056      *
1057      * @param userId user Id to set the temporary service on.
1058      * @param serviceName name of the new component
1059      * @param duration how long the change will be valid (the service will be automatically reset
1060      * to the default component after this timeout expires).
1061      *
1062      * @hide
1063      */
1064     @TestApi
1065     @RequiresPermission(android.Manifest.permission.MANAGE_CONTENT_CAPTURE)
setTemporaryService( @serIdInt int userId, @NonNull String serviceName, int duration)1066     public static void setTemporaryService(
1067             @UserIdInt int userId, @NonNull String serviceName, int duration) {
1068         final IContentCaptureManager service = getService();
1069         if (service == null) {
1070             Log.e(TAG, "IContentCaptureManager is null");
1071         }
1072         try {
1073             service.setTemporaryService(userId, serviceName, duration);
1074         } catch (RemoteException e) {
1075             throw e.rethrowFromSystemServer();
1076         }
1077     }
1078 
1079     /**
1080      * Sets whether the default content capture service should be used.
1081      *
1082      * @hide
1083      */
1084     @TestApi
1085     @RequiresPermission(android.Manifest.permission.MANAGE_CONTENT_CAPTURE)
setDefaultServiceEnabled(@serIdInt int userId, boolean enabled)1086     public static void setDefaultServiceEnabled(@UserIdInt int userId, boolean enabled) {
1087         final IContentCaptureManager service = getService();
1088         if (service == null) {
1089             Log.e(TAG, "IContentCaptureManager is null");
1090         }
1091         try {
1092             service.setDefaultServiceEnabled(userId, enabled);
1093         } catch (RemoteException e) {
1094             throw e.rethrowFromSystemServer();
1095         }
1096     }
1097 
getService()1098     private static IContentCaptureManager getService() {
1099         return IContentCaptureManager.Stub.asInterface(ServiceManager.getService(
1100                 Service.CONTENT_CAPTURE_MANAGER_SERVICE));
1101     }
1102 
1103     private interface MyRunnable {
run(@onNull SyncResultReceiver receiver)1104         void run(@NonNull SyncResultReceiver receiver) throws RemoteException;
1105     }
1106 
1107     private static class DataShareAdapterDelegate extends IDataShareWriteAdapter.Stub {
1108 
1109         private final WeakReference<LocalDataShareAdapterResourceManager> mResourceManagerReference;
1110 
DataShareAdapterDelegate(Executor executor, DataShareWriteAdapter adapter, LocalDataShareAdapterResourceManager resourceManager)1111         private DataShareAdapterDelegate(Executor executor, DataShareWriteAdapter adapter,
1112                 LocalDataShareAdapterResourceManager resourceManager) {
1113             Objects.requireNonNull(executor);
1114             Objects.requireNonNull(adapter);
1115             Objects.requireNonNull(resourceManager);
1116 
1117             resourceManager.initializeForDelegate(this, adapter, executor);
1118             mResourceManagerReference = new WeakReference<>(resourceManager);
1119         }
1120 
1121         @Override
write(ParcelFileDescriptor destination)1122         public void write(ParcelFileDescriptor destination)
1123                 throws RemoteException {
1124             executeAdapterMethodLocked(adapter -> adapter.onWrite(destination), "onWrite");
1125         }
1126 
1127         @Override
error(int errorCode)1128         public void error(int errorCode) throws RemoteException {
1129             executeAdapterMethodLocked(adapter -> adapter.onError(errorCode), "onError");
1130             clearHardReferences();
1131         }
1132 
1133         @Override
rejected()1134         public void rejected() throws RemoteException {
1135             executeAdapterMethodLocked(DataShareWriteAdapter::onRejected, "onRejected");
1136             clearHardReferences();
1137         }
1138 
1139         @Override
finish()1140         public void finish() throws RemoteException  {
1141             clearHardReferences();
1142         }
1143 
executeAdapterMethodLocked(Consumer<DataShareWriteAdapter> adapterFn, String methodName)1144         private void executeAdapterMethodLocked(Consumer<DataShareWriteAdapter> adapterFn,
1145                 String methodName) {
1146             LocalDataShareAdapterResourceManager resourceManager = mResourceManagerReference.get();
1147             if (resourceManager == null) {
1148                 Slog.w(TAG, "Can't execute " + methodName + "(), resource manager has been GC'ed");
1149                 return;
1150             }
1151 
1152             DataShareWriteAdapter adapter = resourceManager.getAdapter(this);
1153             Executor executor = resourceManager.getExecutor(this);
1154 
1155             if (adapter == null || executor == null) {
1156                 Slog.w(TAG, "Can't execute " + methodName + "(), references are null");
1157                 return;
1158             }
1159 
1160             final long identity = Binder.clearCallingIdentity();
1161             try {
1162                 executor.execute(() -> adapterFn.accept(adapter));
1163             } finally {
1164                 Binder.restoreCallingIdentity(identity);
1165             }
1166         }
1167 
clearHardReferences()1168         private void clearHardReferences() {
1169             LocalDataShareAdapterResourceManager resourceManager = mResourceManagerReference.get();
1170             if (resourceManager == null) {
1171                 Slog.w(TAG, "Can't clear references, resource manager has been GC'ed");
1172                 return;
1173             }
1174 
1175             resourceManager.clearHardReferences(this);
1176         }
1177     }
1178 
1179     /**
1180      * Wrapper class making sure dependencies on the current application stay in the application
1181      * context.
1182      */
1183     private static class LocalDataShareAdapterResourceManager {
1184 
1185         // Keeping hard references to the remote objects in the current process (static context)
1186         // to prevent them to be gc'ed during the lifetime of the application. This is an
1187         // artifact of only operating with weak references remotely: there has to be at least 1
1188         // hard reference in order for this to not be killed.
1189         private Map<DataShareAdapterDelegate, DataShareWriteAdapter> mWriteAdapterHardReferences =
1190                 new HashMap<>();
1191         private Map<DataShareAdapterDelegate, Executor> mExecutorHardReferences =
1192                 new HashMap<>();
1193 
initializeForDelegate(DataShareAdapterDelegate delegate, DataShareWriteAdapter adapter, Executor executor)1194         void initializeForDelegate(DataShareAdapterDelegate delegate, DataShareWriteAdapter adapter,
1195                 Executor executor) {
1196             mWriteAdapterHardReferences.put(delegate, adapter);
1197             mExecutorHardReferences.put(delegate, executor);
1198         }
1199 
getExecutor(DataShareAdapterDelegate delegate)1200         Executor getExecutor(DataShareAdapterDelegate delegate) {
1201             return mExecutorHardReferences.get(delegate);
1202         }
1203 
getAdapter(DataShareAdapterDelegate delegate)1204         DataShareWriteAdapter getAdapter(DataShareAdapterDelegate delegate) {
1205             return mWriteAdapterHardReferences.get(delegate);
1206         }
1207 
clearHardReferences(DataShareAdapterDelegate delegate)1208         void clearHardReferences(DataShareAdapterDelegate delegate) {
1209             mWriteAdapterHardReferences.remove(delegate);
1210             mExecutorHardReferences.remove(delegate);
1211         }
1212     }
1213 }
1214