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