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      * @throws RuntimeException if getting the component name is timed out.
488      */
489     @Nullable
getServiceComponentName()490     public ComponentName getServiceComponentName() {
491         if (!isContentCaptureEnabled() && !mOptions.lite) return null;
492 
493         final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
494         try {
495             mService.getServiceComponentName(resultReceiver);
496             return resultReceiver.getParcelableResult();
497         } catch (RemoteException e) {
498             throw e.rethrowFromSystemServer();
499         } catch (SyncResultReceiver.TimeoutException e) {
500             throw new RuntimeException("Fail to get service componentName.");
501         }
502     }
503 
504     /**
505      * Gets the (optional) intent used to launch the service-specific settings.
506      *
507      * <p>This method is static because it's called by Settings, which might not be allowlisted
508      * for content capture (in which case the ContentCaptureManager on its context would be null).
509      *
510      * @hide
511      */
512     // TODO: use "lite" options as it's done by activities from the content capture service
513     @Nullable
getServiceSettingsComponentName()514     public static ComponentName getServiceSettingsComponentName() {
515         final IBinder binder = ServiceManager
516                 .checkService(Context.CONTENT_CAPTURE_MANAGER_SERVICE);
517         if (binder == null) return null;
518 
519         final IContentCaptureManager service = IContentCaptureManager.Stub.asInterface(binder);
520         final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
521         try {
522             service.getServiceSettingsActivity(resultReceiver);
523             final int resultCode = resultReceiver.getIntResult();
524             if (resultCode == RESULT_CODE_SECURITY_EXCEPTION) {
525                 throw new SecurityException(resultReceiver.getStringResult());
526             }
527             return resultReceiver.getParcelableResult();
528         } catch (RemoteException e) {
529             throw e.rethrowFromSystemServer();
530         } catch (SyncResultReceiver.TimeoutException e) {
531             Log.e(TAG, "Fail to get service settings componentName: " + e);
532             return null;
533         }
534     }
535 
536     /**
537      * Checks whether content capture is enabled for this activity.
538      *
539      * <p>There are many reasons it could be disabled, such as:
540      * <ul>
541      *   <li>App itself disabled content capture through {@link #setContentCaptureEnabled(boolean)}.
542      *   <li>Intelligence service did not allowlist content capture for this activity's package.
543      *   <li>Intelligence service did not allowlist content capture for this specific activity.
544      *   <li>Intelligence service disabled content capture globally.
545      *   <li>User disabled content capture globally through the Android Settings app.
546      *   <li>Device manufacturer (OEM) disabled content capture globally.
547      *   <li>Transient errors, such as intelligence service package being updated.
548      * </ul>
549      */
isContentCaptureEnabled()550     public boolean isContentCaptureEnabled() {
551         if (mOptions.lite) return false;
552 
553         final MainContentCaptureSession mainSession;
554         synchronized (mLock) {
555             mainSession = mMainSession;
556         }
557         // The main session is only set when the activity starts, so we need to return true until
558         // then.
559         if (mainSession != null && mainSession.isDisabled()) return false;
560 
561         return true;
562     }
563 
564     /**
565      * Gets the list of conditions for when content capture should be allowed.
566      *
567      * <p>This method is typically used by web browsers so they don't generate unnecessary content
568      * capture events for websites the content capture service is not interested on.
569      *
570      * @return list of conditions, or {@code null} if the service didn't set any restriction
571      * (in which case content capture events should always be generated). If the list is empty,
572      * then it should not generate any event at all.
573      */
574     @Nullable
getContentCaptureConditions()575     public Set<ContentCaptureCondition> getContentCaptureConditions() {
576         // NOTE: we could cache the conditions on ContentCaptureOptions, but then it would be stick
577         // to the lifetime of the app. OTOH, by dynamically calling the server every time, we allow
578         // the service to fine tune how long-lived apps (like browsers) are allowlisted.
579         if (!isContentCaptureEnabled() && !mOptions.lite) return null;
580 
581         final SyncResultReceiver resultReceiver = syncRun(
582                 (r) -> mService.getContentCaptureConditions(mContext.getPackageName(), r));
583 
584         try {
585             final ArrayList<ContentCaptureCondition> result = resultReceiver
586                     .getParcelableListResult();
587             return toSet(result);
588         } catch (SyncResultReceiver.TimeoutException e) {
589             throw new RuntimeException("Fail to get content capture conditions.");
590         }
591     }
592 
593     /**
594      * Called by apps to explicitly enable or disable content capture.
595      *
596      * <p><b>Note: </b> this call is not persisted accross reboots, so apps should typically call
597      * it on {@link android.app.Activity#onCreate(android.os.Bundle, android.os.PersistableBundle)}.
598      */
setContentCaptureEnabled(boolean enabled)599     public void setContentCaptureEnabled(boolean enabled) {
600         if (sDebug) {
601             Log.d(TAG, "setContentCaptureEnabled(): setting to " + enabled + " for " + mContext);
602         }
603 
604         MainContentCaptureSession mainSession;
605         synchronized (mLock) {
606             if (enabled) {
607                 mFlags &= ~ContentCaptureContext.FLAG_DISABLED_BY_APP;
608             } else {
609                 mFlags |= ContentCaptureContext.FLAG_DISABLED_BY_APP;
610             }
611             mainSession = mMainSession;
612         }
613         if (mainSession != null) {
614             mainSession.setDisabled(!enabled);
615         }
616     }
617 
618     /**
619      * Called by apps to update flag secure when window attributes change.
620      *
621      * @hide
622      */
updateWindowAttributes(@onNull WindowManager.LayoutParams params)623     public void updateWindowAttributes(@NonNull WindowManager.LayoutParams params) {
624         if (sDebug) {
625             Log.d(TAG, "updateWindowAttributes(): window flags=" + params.flags);
626         }
627         final boolean flagSecureEnabled =
628                 (params.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0;
629 
630         MainContentCaptureSession mainSession;
631         synchronized (mLock) {
632             if (flagSecureEnabled) {
633                 mFlags |= ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE;
634             } else {
635                 mFlags &= ~ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE;
636             }
637             mainSession = mMainSession;
638         }
639         if (mainSession != null) {
640             mainSession.setDisabled(flagSecureEnabled);
641         }
642     }
643 
644     /**
645      * Gets whether content capture is enabled for the given user.
646      *
647      * <p>This method is typically used by the content capture service settings page, so it can
648      * provide a toggle to enable / disable it.
649      *
650      * @throws SecurityException if caller is not the app that owns the content capture service
651      * associated with the user.
652      *
653      * @hide
654      */
655     @SystemApi
isContentCaptureFeatureEnabled()656     public boolean isContentCaptureFeatureEnabled() {
657         final SyncResultReceiver resultReceiver = syncRun(
658                 (r) -> mService.isContentCaptureFeatureEnabled(r));
659 
660         try {
661             final int resultCode = resultReceiver.getIntResult();
662             switch (resultCode) {
663                 case RESULT_CODE_TRUE:
664                     return true;
665                 case RESULT_CODE_FALSE:
666                     return false;
667                 default:
668                     Log.wtf(TAG, "received invalid result: " + resultCode);
669                     return false;
670             }
671         } catch (SyncResultReceiver.TimeoutException e) {
672             Log.e(TAG, "Fail to get content capture feature enable status: " + e);
673             return false;
674         }
675     }
676 
677     /**
678      * Called by the app to request the content capture service to remove content capture data
679      * associated with some context.
680      *
681      * @param request object specifying what user data should be removed.
682      */
removeData(@onNull DataRemovalRequest request)683     public void removeData(@NonNull DataRemovalRequest request) {
684         Preconditions.checkNotNull(request);
685 
686         try {
687             mService.removeData(request);
688         } catch (RemoteException e) {
689             throw e.rethrowFromSystemServer();
690         }
691     }
692 
693     /**
694      * Called by the app to request data sharing via writing to a file.
695      *
696      * <p>The ContentCaptureService app will receive a read-only file descriptor pointing to the
697      * same file and will be able to read data being shared from it.
698      *
699      * <p>Note: using this API doesn't guarantee the app staying alive and is "best-effort".
700      * Starting a foreground service would minimize the chances of the app getting killed during the
701      * file sharing session.
702      *
703      * @param request object specifying details of the data being shared.
704      */
shareData(@onNull DataShareRequest request, @NonNull @CallbackExecutor Executor executor, @NonNull DataShareWriteAdapter dataShareWriteAdapter)705     public void shareData(@NonNull DataShareRequest request,
706             @NonNull @CallbackExecutor Executor executor,
707             @NonNull DataShareWriteAdapter dataShareWriteAdapter) {
708         Preconditions.checkNotNull(request);
709         Preconditions.checkNotNull(dataShareWriteAdapter);
710         Preconditions.checkNotNull(executor);
711 
712         try {
713             mService.shareData(request,
714                     new DataShareAdapterDelegate(executor, dataShareWriteAdapter,
715                             mDataShareAdapterResourceManager));
716         } catch (RemoteException e) {
717             throw e.rethrowFromSystemServer();
718         }
719     }
720 
721     /**
722      * Runs a sync method in the service, properly handling exceptions.
723      *
724      * @throws SecurityException if caller is not allowed to execute the method.
725      */
726     @NonNull
syncRun(@onNull MyRunnable r)727     private SyncResultReceiver syncRun(@NonNull MyRunnable r) {
728         final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
729         try {
730             r.run(resultReceiver);
731             final int resultCode = resultReceiver.getIntResult();
732             if (resultCode == RESULT_CODE_SECURITY_EXCEPTION) {
733                 throw new SecurityException(resultReceiver.getStringResult());
734             }
735         } catch (RemoteException e) {
736             throw e.rethrowFromSystemServer();
737         } catch (SyncResultReceiver.TimeoutException e) {
738             throw new RuntimeException("Fail to get syn run result from SyncResultReceiver.");
739         }
740         return resultReceiver;
741     }
742 
743     /** @hide */
dump(String prefix, PrintWriter pw)744     public void dump(String prefix, PrintWriter pw) {
745         pw.print(prefix); pw.println("ContentCaptureManager");
746         final String prefix2 = prefix + "  ";
747         synchronized (mLock) {
748             pw.print(prefix2); pw.print("isContentCaptureEnabled(): ");
749             pw.println(isContentCaptureEnabled());
750             pw.print(prefix2); pw.print("Debug: "); pw.print(sDebug);
751             pw.print(" Verbose: "); pw.println(sVerbose);
752             pw.print(prefix2); pw.print("Context: "); pw.println(mContext);
753             pw.print(prefix2); pw.print("User: "); pw.println(mContext.getUserId());
754             pw.print(prefix2); pw.print("Service: "); pw.println(mService);
755             pw.print(prefix2); pw.print("Flags: "); pw.println(mFlags);
756             pw.print(prefix2); pw.print("Options: "); mOptions.dumpShort(pw); pw.println();
757             if (mMainSession != null) {
758                 final String prefix3 = prefix2 + "  ";
759                 pw.print(prefix2); pw.println("Main session:");
760                 mMainSession.dump(prefix3, pw);
761             } else {
762                 pw.print(prefix2); pw.println("No sessions");
763             }
764         }
765     }
766 
767     /**
768      * Resets the temporary content capture service implementation to the default component.
769      *
770      * @hide
771      */
772     @TestApi
773     @RequiresPermission(android.Manifest.permission.MANAGE_CONTENT_CAPTURE)
resetTemporaryService(@serIdInt int userId)774     public static void resetTemporaryService(@UserIdInt int userId) {
775         final IContentCaptureManager service = getService();
776         if (service == null) {
777             Log.e(TAG, "IContentCaptureManager is null");
778         }
779         try {
780             service.resetTemporaryService(userId);
781         } catch (RemoteException e) {
782             throw e.rethrowFromSystemServer();
783         }
784     }
785 
786     /**
787      * Temporarily sets the content capture service implementation.
788      *
789      * @param userId user Id to set the temporary service on.
790      * @param serviceName name of the new component
791      * @param duration how long the change will be valid (the service will be automatically reset
792      * to the default component after this timeout expires).
793      *
794      * @hide
795      */
796     @TestApi
797     @RequiresPermission(android.Manifest.permission.MANAGE_CONTENT_CAPTURE)
setTemporaryService( @serIdInt int userId, @NonNull String serviceName, int duration)798     public static void setTemporaryService(
799             @UserIdInt int userId, @NonNull String serviceName, int duration) {
800         final IContentCaptureManager service = getService();
801         if (service == null) {
802             Log.e(TAG, "IContentCaptureManager is null");
803         }
804         try {
805             service.setTemporaryService(userId, serviceName, duration);
806         } catch (RemoteException e) {
807             throw e.rethrowFromSystemServer();
808         }
809     }
810 
811     /**
812      * Sets whether the default content capture service should be used.
813      *
814      * @hide
815      */
816     @TestApi
817     @RequiresPermission(android.Manifest.permission.MANAGE_CONTENT_CAPTURE)
setDefaultServiceEnabled(@serIdInt int userId, boolean enabled)818     public static void setDefaultServiceEnabled(@UserIdInt int userId, boolean enabled) {
819         final IContentCaptureManager service = getService();
820         if (service == null) {
821             Log.e(TAG, "IContentCaptureManager is null");
822         }
823         try {
824             service.setDefaultServiceEnabled(userId, enabled);
825         } catch (RemoteException e) {
826             throw e.rethrowFromSystemServer();
827         }
828     }
829 
getService()830     private static IContentCaptureManager getService() {
831         return IContentCaptureManager.Stub.asInterface(ServiceManager.getService(
832                 Service.CONTENT_CAPTURE_MANAGER_SERVICE));
833     }
834 
835     private interface MyRunnable {
run(@onNull SyncResultReceiver receiver)836         void run(@NonNull SyncResultReceiver receiver) throws RemoteException;
837     }
838 
839     private static class DataShareAdapterDelegate extends IDataShareWriteAdapter.Stub {
840 
841         private final WeakReference<LocalDataShareAdapterResourceManager> mResourceManagerReference;
842 
DataShareAdapterDelegate(Executor executor, DataShareWriteAdapter adapter, LocalDataShareAdapterResourceManager resourceManager)843         private DataShareAdapterDelegate(Executor executor, DataShareWriteAdapter adapter,
844                 LocalDataShareAdapterResourceManager resourceManager) {
845             Preconditions.checkNotNull(executor);
846             Preconditions.checkNotNull(adapter);
847             Preconditions.checkNotNull(resourceManager);
848 
849             resourceManager.initializeForDelegate(this, adapter, executor);
850             mResourceManagerReference = new WeakReference<>(resourceManager);
851         }
852 
853         @Override
write(ParcelFileDescriptor destination)854         public void write(ParcelFileDescriptor destination)
855                 throws RemoteException {
856             executeAdapterMethodLocked(adapter -> adapter.onWrite(destination), "onWrite");
857         }
858 
859         @Override
error(int errorCode)860         public void error(int errorCode) throws RemoteException {
861             executeAdapterMethodLocked(adapter -> adapter.onError(errorCode), "onError");
862             clearHardReferences();
863         }
864 
865         @Override
rejected()866         public void rejected() throws RemoteException {
867             executeAdapterMethodLocked(DataShareWriteAdapter::onRejected, "onRejected");
868             clearHardReferences();
869         }
870 
871         @Override
finish()872         public void finish() throws RemoteException  {
873             clearHardReferences();
874         }
875 
executeAdapterMethodLocked(Consumer<DataShareWriteAdapter> adapterFn, String methodName)876         private void executeAdapterMethodLocked(Consumer<DataShareWriteAdapter> adapterFn,
877                 String methodName) {
878             LocalDataShareAdapterResourceManager resourceManager = mResourceManagerReference.get();
879             if (resourceManager == null) {
880                 Slog.w(TAG, "Can't execute " + methodName + "(), resource manager has been GC'ed");
881                 return;
882             }
883 
884             DataShareWriteAdapter adapter = resourceManager.getAdapter(this);
885             Executor executor = resourceManager.getExecutor(this);
886 
887             if (adapter == null || executor == null) {
888                 Slog.w(TAG, "Can't execute " + methodName + "(), references are null");
889                 return;
890             }
891 
892             final long identity = Binder.clearCallingIdentity();
893             try {
894                 executor.execute(() -> adapterFn.accept(adapter));
895             } finally {
896                 Binder.restoreCallingIdentity(identity);
897             }
898         }
899 
clearHardReferences()900         private void clearHardReferences() {
901             LocalDataShareAdapterResourceManager resourceManager = mResourceManagerReference.get();
902             if (resourceManager == null) {
903                 Slog.w(TAG, "Can't clear references, resource manager has been GC'ed");
904                 return;
905             }
906 
907             resourceManager.clearHardReferences(this);
908         }
909     }
910 
911     /**
912      * Wrapper class making sure dependencies on the current application stay in the application
913      * context.
914      */
915     private static class LocalDataShareAdapterResourceManager {
916 
917         // Keeping hard references to the remote objects in the current process (static context)
918         // to prevent them to be gc'ed during the lifetime of the application. This is an
919         // artifact of only operating with weak references remotely: there has to be at least 1
920         // hard reference in order for this to not be killed.
921         private Map<DataShareAdapterDelegate, DataShareWriteAdapter> mWriteAdapterHardReferences =
922                 new HashMap<>();
923         private Map<DataShareAdapterDelegate, Executor> mExecutorHardReferences =
924                 new HashMap<>();
925 
initializeForDelegate(DataShareAdapterDelegate delegate, DataShareWriteAdapter adapter, Executor executor)926         void initializeForDelegate(DataShareAdapterDelegate delegate, DataShareWriteAdapter adapter,
927                 Executor executor) {
928             mWriteAdapterHardReferences.put(delegate, adapter);
929             mExecutorHardReferences.put(delegate, executor);
930         }
931 
getExecutor(DataShareAdapterDelegate delegate)932         Executor getExecutor(DataShareAdapterDelegate delegate) {
933             return mExecutorHardReferences.get(delegate);
934         }
935 
getAdapter(DataShareAdapterDelegate delegate)936         DataShareWriteAdapter getAdapter(DataShareAdapterDelegate delegate) {
937             return mWriteAdapterHardReferences.get(delegate);
938         }
939 
clearHardReferences(DataShareAdapterDelegate delegate)940         void clearHardReferences(DataShareAdapterDelegate delegate) {
941             mWriteAdapterHardReferences.remove(delegate);
942             mExecutorHardReferences.remove(delegate);
943         }
944     }
945 }
946