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