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