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