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