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.content;
17 
18 import android.annotation.NonNull;
19 import android.annotation.Nullable;
20 import android.annotation.SuppressLint;
21 import android.annotation.TestApi;
22 import android.app.ActivityThread;
23 import android.os.Parcel;
24 import android.os.Parcelable;
25 import android.util.ArraySet;
26 import android.util.Log;
27 import android.view.contentcapture.ContentCaptureManager;
28 import android.view.contentcapture.ContentCaptureManager.ContentCaptureClient;
29 
30 import com.android.internal.annotations.VisibleForTesting;
31 
32 import java.io.PrintWriter;
33 import java.util.ArrayList;
34 import java.util.Collections;
35 import java.util.List;
36 import java.util.stream.Collectors;
37 import java.util.stream.IntStream;
38 
39 /**
40  * Content capture options for a given package.
41  *
42  * <p>This object is created by the Content Capture System Service and passed back to the app when
43  * the application is created.
44  *
45  * @hide
46  */
47 @TestApi
48 public final class ContentCaptureOptions implements Parcelable {
49 
50     private static final String TAG = ContentCaptureOptions.class.getSimpleName();
51 
52     /**
53      * Logging level for {@code logcat} statements.
54      */
55     public final int loggingLevel;
56 
57     /**
58      * Maximum number of events that are buffered before sent to the app.
59      */
60     public final int maxBufferSize;
61 
62     /**
63      * Frequency the buffer is flushed if idle.
64      */
65     public final int idleFlushingFrequencyMs;
66 
67     /**
68      * Frequency the buffer is flushed if last event is a text change.
69      */
70     public final int textChangeFlushingFrequencyMs;
71 
72     /**
73      * Size of events that are logging on {@code dump}.
74      */
75     public final int logHistorySize;
76 
77     /**
78      * Disable flush when receiving a VIEW_TREE_APPEARING event.
79      * @hide
80      */
81     public final boolean disableFlushForViewTreeAppearing;
82 
83     /**
84      * Is the content capture receiver enabled.
85      *
86      * @hide
87      */
88     public final boolean enableReceiver;
89 
90     /**
91      * Options for the content protection flow.
92      *
93      * @hide
94      */
95     @NonNull public final ContentProtectionOptions contentProtectionOptions;
96 
97     /**
98      * List of activities explicitly allowlisted for content capture (or {@code null} if allowlisted
99      * for all acitivites in the package).
100      */
101     @Nullable
102     @SuppressLint("NullableCollection")
103     public final ArraySet<ComponentName> whitelistedComponents;
104 
105     /**
106      * Used to enable just a small set of APIs so it can used by activities belonging to the
107      * content capture service APK.
108      */
109     public final boolean lite;
110 
111     /**
112      * Constructor for "lite" objects that are just used to enable a {@link ContentCaptureManager}
113      * for contexts belonging to the content capture service app.
114      */
ContentCaptureOptions(int loggingLevel)115     public ContentCaptureOptions(int loggingLevel) {
116         this(
117                 /* lite= */ true,
118                 loggingLevel,
119                 /* maxBufferSize= */ 0,
120                 /* idleFlushingFrequencyMs= */ 0,
121                 /* textChangeFlushingFrequencyMs= */ 0,
122                 /* logHistorySize= */ 0,
123                 /* disableFlushForViewTreeAppearing= */ false,
124                 /* enableReceiver= */ false,
125                 new ContentProtectionOptions(
126                         /* enableReceiver= */ false,
127                         /* bufferSize= */ 0,
128                         /* requiredGroups= */ Collections.emptyList(),
129                         /* optionalGroups= */ Collections.emptyList(),
130                         /* optionalGroupsThreshold= */ 0),
131                 /* whitelistedComponents= */ null);
132     }
133 
134     /** Default constructor. */
ContentCaptureOptions( int loggingLevel, int maxBufferSize, int idleFlushingFrequencyMs, int textChangeFlushingFrequencyMs, int logHistorySize, @SuppressLint({"ConcreteCollection", "NullableCollection"}) @Nullable ArraySet<ComponentName> whitelistedComponents)135     public ContentCaptureOptions(
136             int loggingLevel,
137             int maxBufferSize,
138             int idleFlushingFrequencyMs,
139             int textChangeFlushingFrequencyMs,
140             int logHistorySize,
141             @SuppressLint({"ConcreteCollection", "NullableCollection"}) @Nullable
142                     ArraySet<ComponentName> whitelistedComponents) {
143         this(
144                 /* lite= */ false,
145                 loggingLevel,
146                 maxBufferSize,
147                 idleFlushingFrequencyMs,
148                 textChangeFlushingFrequencyMs,
149                 logHistorySize,
150                 ContentCaptureManager.DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING,
151                 ContentCaptureManager.DEFAULT_ENABLE_CONTENT_CAPTURE_RECEIVER,
152                 new ContentProtectionOptions(),
153                 whitelistedComponents);
154     }
155 
156     /** @hide */
ContentCaptureOptions( int loggingLevel, int maxBufferSize, int idleFlushingFrequencyMs, int textChangeFlushingFrequencyMs, int logHistorySize, boolean disableFlushForViewTreeAppearing, boolean enableReceiver, @NonNull ContentProtectionOptions contentProtectionOptions, @SuppressLint({"ConcreteCollection", "NullableCollection"}) @Nullable ArraySet<ComponentName> whitelistedComponents)157     public ContentCaptureOptions(
158             int loggingLevel,
159             int maxBufferSize,
160             int idleFlushingFrequencyMs,
161             int textChangeFlushingFrequencyMs,
162             int logHistorySize,
163             boolean disableFlushForViewTreeAppearing,
164             boolean enableReceiver,
165             @NonNull ContentProtectionOptions contentProtectionOptions,
166             @SuppressLint({"ConcreteCollection", "NullableCollection"}) @Nullable
167                     ArraySet<ComponentName> whitelistedComponents) {
168         this(
169                 /* lite= */ false,
170                 loggingLevel,
171                 maxBufferSize,
172                 idleFlushingFrequencyMs,
173                 textChangeFlushingFrequencyMs,
174                 logHistorySize,
175                 disableFlushForViewTreeAppearing,
176                 enableReceiver,
177                 contentProtectionOptions,
178                 whitelistedComponents);
179     }
180 
181     /** @hide */
182     @VisibleForTesting
ContentCaptureOptions(@ullable ArraySet<ComponentName> whitelistedComponents)183     public ContentCaptureOptions(@Nullable ArraySet<ComponentName> whitelistedComponents) {
184         this(
185                 ContentCaptureManager.LOGGING_LEVEL_VERBOSE,
186                 ContentCaptureManager.DEFAULT_MAX_BUFFER_SIZE,
187                 ContentCaptureManager.DEFAULT_IDLE_FLUSHING_FREQUENCY_MS,
188                 ContentCaptureManager.DEFAULT_TEXT_CHANGE_FLUSHING_FREQUENCY_MS,
189                 ContentCaptureManager.DEFAULT_LOG_HISTORY_SIZE,
190                 ContentCaptureManager.DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING,
191                 ContentCaptureManager.DEFAULT_ENABLE_CONTENT_CAPTURE_RECEIVER,
192                 new ContentProtectionOptions(),
193                 whitelistedComponents);
194     }
195 
ContentCaptureOptions( boolean lite, int loggingLevel, int maxBufferSize, int idleFlushingFrequencyMs, int textChangeFlushingFrequencyMs, int logHistorySize, boolean disableFlushForViewTreeAppearing, boolean enableReceiver, @NonNull ContentProtectionOptions contentProtectionOptions, @SuppressLint({"ConcreteCollection", "NullableCollection"}) @Nullable ArraySet<ComponentName> whitelistedComponents)196     private ContentCaptureOptions(
197             boolean lite,
198             int loggingLevel,
199             int maxBufferSize,
200             int idleFlushingFrequencyMs,
201             int textChangeFlushingFrequencyMs,
202             int logHistorySize,
203             boolean disableFlushForViewTreeAppearing,
204             boolean enableReceiver,
205             @NonNull ContentProtectionOptions contentProtectionOptions,
206             @SuppressLint({"ConcreteCollection", "NullableCollection"}) @Nullable
207                     ArraySet<ComponentName> whitelistedComponents) {
208         this.lite = lite;
209         this.loggingLevel = loggingLevel;
210         this.maxBufferSize = maxBufferSize;
211         this.idleFlushingFrequencyMs = idleFlushingFrequencyMs;
212         this.textChangeFlushingFrequencyMs = textChangeFlushingFrequencyMs;
213         this.logHistorySize = logHistorySize;
214         this.disableFlushForViewTreeAppearing = disableFlushForViewTreeAppearing;
215         this.enableReceiver = enableReceiver;
216         this.contentProtectionOptions = contentProtectionOptions;
217         this.whitelistedComponents = whitelistedComponents;
218     }
219 
forWhitelistingItself()220     public static ContentCaptureOptions forWhitelistingItself() {
221         final ActivityThread at = ActivityThread.currentActivityThread();
222         if (at == null) {
223             throw new IllegalStateException("No ActivityThread");
224         }
225 
226         final String packageName = at.getApplication().getPackageName();
227 
228         if (!"android.contentcaptureservice.cts".equals(packageName)
229                 && !"android.translation.cts".equals(packageName)) {
230             Log.e(TAG, "forWhitelistingItself(): called by " + packageName);
231             throw new SecurityException("Thou shall not pass!");
232         }
233 
234         final ContentCaptureOptions options =
235                 new ContentCaptureOptions(/* whitelistedComponents= */ null);
236         // Always log, as it's used by test only
237         Log.i(TAG, "forWhitelistingItself(" + packageName + "): " + options);
238 
239         return options;
240     }
241 
242     /** @hide */
243     @VisibleForTesting
isWhitelisted(@onNull Context context)244     public boolean isWhitelisted(@NonNull Context context) {
245         if (whitelistedComponents == null) return true; // whole package is allowlisted
246         final ContentCaptureClient client = context.getContentCaptureClient();
247         if (client == null) {
248             // Shouldn't happen, but it doesn't hurt to check...
249             Log.w(TAG, "isWhitelisted(): no ContentCaptureClient on " + context);
250             return false;
251         }
252         return whitelistedComponents.contains(client.contentCaptureClientGetComponentName());
253     }
254 
255     @Override
toString()256     public String toString() {
257         if (lite) {
258             return "ContentCaptureOptions [loggingLevel=" + loggingLevel + " (lite)]";
259         }
260         final StringBuilder string = new StringBuilder("ContentCaptureOptions [");
261         string.append("loggingLevel=")
262                 .append(loggingLevel)
263                 .append(", maxBufferSize=")
264                 .append(maxBufferSize)
265                 .append(", idleFlushingFrequencyMs=")
266                 .append(idleFlushingFrequencyMs)
267                 .append(", textChangeFlushingFrequencyMs=")
268                 .append(textChangeFlushingFrequencyMs)
269                 .append(", logHistorySize=")
270                 .append(logHistorySize)
271                 .append(", disableFlushForViewTreeAppearing=")
272                 .append(disableFlushForViewTreeAppearing)
273                 .append(", enableReceiver=")
274                 .append(enableReceiver)
275                 .append(", contentProtectionOptions=")
276                 .append(contentProtectionOptions);
277         if (whitelistedComponents != null) {
278             string.append(", whitelisted=").append(whitelistedComponents);
279         }
280         return string.append(']').toString();
281     }
282 
283     /** @hide */
dumpShort(@onNull PrintWriter pw)284     public void dumpShort(@NonNull PrintWriter pw) {
285         pw.print("logLvl="); pw.print(loggingLevel);
286         if (lite) {
287             pw.print(", lite");
288             return;
289         }
290         pw.print(", bufferSize=");
291         pw.print(maxBufferSize);
292         pw.print(", idle=");
293         pw.print(idleFlushingFrequencyMs);
294         pw.print(", textIdle=");
295         pw.print(textChangeFlushingFrequencyMs);
296         pw.print(", logSize=");
297         pw.print(logHistorySize);
298         pw.print(", disableFlushForViewTreeAppearing=");
299         pw.print(disableFlushForViewTreeAppearing);
300         pw.print(", enableReceiver=");
301         pw.print(enableReceiver);
302         pw.print(", contentProtectionOptions=[");
303         contentProtectionOptions.dumpShort(pw);
304         pw.print("]");
305         if (whitelistedComponents != null) {
306             pw.print(", whitelisted="); pw.print(whitelistedComponents);
307         }
308     }
309 
310     @Override
describeContents()311     public int describeContents() {
312         return 0;
313     }
314 
315     @Override
writeToParcel(Parcel parcel, int flags)316     public void writeToParcel(Parcel parcel, int flags) {
317         parcel.writeBoolean(lite);
318         parcel.writeInt(loggingLevel);
319         if (lite) return;
320 
321         parcel.writeInt(maxBufferSize);
322         parcel.writeInt(idleFlushingFrequencyMs);
323         parcel.writeInt(textChangeFlushingFrequencyMs);
324         parcel.writeInt(logHistorySize);
325         parcel.writeBoolean(disableFlushForViewTreeAppearing);
326         parcel.writeBoolean(enableReceiver);
327         contentProtectionOptions.writeToParcel(parcel);
328         parcel.writeArraySet(whitelistedComponents);
329     }
330 
331     public static final @android.annotation.NonNull Parcelable.Creator<ContentCaptureOptions> CREATOR =
332             new Parcelable.Creator<ContentCaptureOptions>() {
333 
334                 @Override
335                 public ContentCaptureOptions createFromParcel(Parcel parcel) {
336                     final boolean lite = parcel.readBoolean();
337                     final int loggingLevel = parcel.readInt();
338                     if (lite) {
339                         return new ContentCaptureOptions(loggingLevel);
340                     }
341                     final int maxBufferSize = parcel.readInt();
342                     final int idleFlushingFrequencyMs = parcel.readInt();
343                     final int textChangeFlushingFrequencyMs = parcel.readInt();
344                     final int logHistorySize = parcel.readInt();
345                     final boolean disableFlushForViewTreeAppearing = parcel.readBoolean();
346                     final boolean enableReceiver = parcel.readBoolean();
347                     final ContentProtectionOptions contentProtectionOptions =
348                             ContentProtectionOptions.createFromParcel(parcel);
349                     @SuppressWarnings("unchecked")
350                     final ArraySet<ComponentName> whitelistedComponents =
351                             (ArraySet<ComponentName>) parcel.readArraySet(null);
352                     return new ContentCaptureOptions(
353                             loggingLevel,
354                             maxBufferSize,
355                             idleFlushingFrequencyMs,
356                             textChangeFlushingFrequencyMs,
357                             logHistorySize,
358                             disableFlushForViewTreeAppearing,
359                             enableReceiver,
360                             contentProtectionOptions,
361                             whitelistedComponents);
362                 }
363 
364                 @Override
365                 public ContentCaptureOptions[] newArray(int size) {
366                     return new ContentCaptureOptions[size];
367                 }
368     };
369 
370     /**
371      * Content protection options for a given package.
372      *
373      * <p>Does not implement {@code Parcelable} since it is an inner class without a matching AIDL.
374      *
375      * @hide
376      */
377     public static class ContentProtectionOptions {
378 
379         /**
380          * Is the content protection receiver enabled.
381          *
382          * @hide
383          */
384         public final boolean enableReceiver;
385 
386         /**
387          * Size of the in-memory ring buffer for the content protection flow.
388          *
389          * @hide
390          */
391         public final int bufferSize;
392 
393         /**
394          * The list of required groups of strings to match.
395          *
396          * @hide
397          */
398         @NonNull public final List<List<String>> requiredGroups;
399 
400         /**
401          * The list of optional groups of strings to match.
402          *
403          * @hide
404          */
405         @NonNull public final List<List<String>> optionalGroups;
406 
407         /**
408          * The minimal number of optional groups that have to be matched. This is the threshold
409          * value and comparison is done with greater than or equals.
410          *
411          * @hide
412          */
413         public final int optionalGroupsThreshold;
414 
415         /**
416          * Empty constructor with default values.
417          *
418          * @hide
419          */
ContentProtectionOptions()420         public ContentProtectionOptions() {
421             this(
422                     ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER,
423                     ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE,
424                     ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS,
425                     ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS,
426                     ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD);
427         }
428 
429         /**
430          * Full primary constructor.
431          *
432          * @hide
433          */
ContentProtectionOptions( boolean enableReceiver, int bufferSize, @NonNull List<List<String>> requiredGroups, @NonNull List<List<String>> optionalGroups, int optionalGroupsThreshold)434         public ContentProtectionOptions(
435                 boolean enableReceiver,
436                 int bufferSize,
437                 @NonNull List<List<String>> requiredGroups,
438                 @NonNull List<List<String>> optionalGroups,
439                 int optionalGroupsThreshold) {
440             this.enableReceiver = enableReceiver;
441             this.bufferSize = bufferSize;
442             this.requiredGroups = requiredGroups;
443             this.optionalGroups = optionalGroups;
444             this.optionalGroupsThreshold = optionalGroupsThreshold;
445         }
446 
447         @Override
toString()448         public String toString() {
449             StringBuilder stringBuilder = new StringBuilder("ContentProtectionOptions [");
450             stringBuilder
451                     .append("enableReceiver=")
452                     .append(enableReceiver)
453                     .append(", bufferSize=")
454                     .append(bufferSize)
455                     .append(", requiredGroupsSize=")
456                     .append(requiredGroups.size())
457                     .append(", optionalGroupsSize=")
458                     .append(optionalGroups.size())
459                     .append(", optionalGroupsThreshold=")
460                     .append(optionalGroupsThreshold);
461 
462             return stringBuilder.append(']').toString();
463         }
464 
dumpShort(@onNull PrintWriter pw)465         private void dumpShort(@NonNull PrintWriter pw) {
466             pw.print("enableReceiver=");
467             pw.print(enableReceiver);
468             pw.print(", bufferSize=");
469             pw.print(bufferSize);
470             pw.print(", requiredGroupsSize=");
471             pw.print(requiredGroups.size());
472             pw.print(", optionalGroupsSize=");
473             pw.print(optionalGroups.size());
474             pw.print(", optionalGroupsThreshold=");
475             pw.print(optionalGroupsThreshold);
476         }
477 
writeToParcel(@onNull Parcel parcel)478         private void writeToParcel(@NonNull Parcel parcel) {
479             parcel.writeBoolean(enableReceiver);
480             parcel.writeInt(bufferSize);
481             writeGroupsToParcel(requiredGroups, parcel);
482             writeGroupsToParcel(optionalGroups, parcel);
483             parcel.writeInt(optionalGroupsThreshold);
484         }
485 
486         @NonNull
createFromParcel(@onNull Parcel parcel)487         private static ContentProtectionOptions createFromParcel(@NonNull Parcel parcel) {
488             boolean enableReceiver = parcel.readBoolean();
489             int bufferSize = parcel.readInt();
490             List<List<String>> requiredGroups = createGroupsFromParcel(parcel);
491             List<List<String>> optionalGroups = createGroupsFromParcel(parcel);
492             int optionalGroupsThreshold = parcel.readInt();
493             return new ContentProtectionOptions(
494                     enableReceiver,
495                     bufferSize,
496                     requiredGroups,
497                     optionalGroups,
498                     optionalGroupsThreshold);
499         }
500 
writeGroupsToParcel( @onNull List<List<String>> groups, @NonNull Parcel parcel)501         private static void writeGroupsToParcel(
502                 @NonNull List<List<String>> groups, @NonNull Parcel parcel) {
503             parcel.writeInt(groups.size());
504             groups.forEach(parcel::writeStringList);
505         }
506 
507         @NonNull
createGroupsFromParcel(@onNull Parcel parcel)508         private static List<List<String>> createGroupsFromParcel(@NonNull Parcel parcel) {
509             int size = parcel.readInt();
510             return IntStream.range(0, size)
511                     .mapToObj(i -> new ArrayList<String>())
512                     .peek(parcel::readStringList)
513                     .collect(Collectors.toUnmodifiableList());
514         }
515     }
516 }
517