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 
34 /**
35  * Content capture options for a given package.
36  *
37  * <p>This object is created by the Content Capture System Service and passed back to the app when
38  * the application is created.
39  *
40  * @hide
41  */
42 @TestApi
43 public final class ContentCaptureOptions implements Parcelable {
44 
45     private static final String TAG = ContentCaptureOptions.class.getSimpleName();
46 
47     /**
48      * Logging level for {@code logcat} statements.
49      */
50     public final int loggingLevel;
51 
52     /**
53      * Maximum number of events that are buffered before sent to the app.
54      */
55     public final int maxBufferSize;
56 
57     /**
58      * Frequency the buffer is flushed if idle.
59      */
60     public final int idleFlushingFrequencyMs;
61 
62     /**
63      * Frequency the buffer is flushed if last event is a text change.
64      */
65     public final int textChangeFlushingFrequencyMs;
66 
67     /**
68      * Size of events that are logging on {@code dump}.
69      */
70     public final int logHistorySize;
71 
72     /**
73      * Disable flush when receiving a VIEW_TREE_APPEARING event.
74      * @hide
75      */
76     public final boolean disableFlushForViewTreeAppearing;
77 
78     /**
79      * Is the content capture receiver enabled.
80      *
81      * @hide
82      */
83     public final boolean enableReceiver;
84 
85     /**
86      * Options for the content protection flow.
87      *
88      * @hide
89      */
90     @NonNull public final ContentProtectionOptions contentProtectionOptions;
91 
92     /**
93      * List of activities explicitly allowlisted for content capture (or {@code null} if allowlisted
94      * for all acitivites in the package).
95      */
96     @Nullable
97     @SuppressLint("NullableCollection")
98     public final ArraySet<ComponentName> whitelistedComponents;
99 
100     /**
101      * Used to enable just a small set of APIs so it can used by activities belonging to the
102      * content capture service APK.
103      */
104     public final boolean lite;
105 
106     /**
107      * Constructor for "lite" objects that are just used to enable a {@link ContentCaptureManager}
108      * for contexts belonging to the content capture service app.
109      */
ContentCaptureOptions(int loggingLevel)110     public ContentCaptureOptions(int loggingLevel) {
111         this(
112                 /* lite= */ true,
113                 loggingLevel,
114                 /* maxBufferSize= */ 0,
115                 /* idleFlushingFrequencyMs= */ 0,
116                 /* textChangeFlushingFrequencyMs= */ 0,
117                 /* logHistorySize= */ 0,
118                 /* disableFlushForViewTreeAppearing= */ false,
119                 /* enableReceiver= */ false,
120                 new ContentProtectionOptions(
121                         /* enableReceiver= */ false,
122                         /* bufferSize= */ 0),
123                 /* whitelistedComponents= */ null);
124     }
125 
126     /** Default constructor. */
ContentCaptureOptions( int loggingLevel, int maxBufferSize, int idleFlushingFrequencyMs, int textChangeFlushingFrequencyMs, int logHistorySize, @SuppressLint({R, R}) @Nullable ArraySet<ComponentName> whitelistedComponents)127     public ContentCaptureOptions(
128             int loggingLevel,
129             int maxBufferSize,
130             int idleFlushingFrequencyMs,
131             int textChangeFlushingFrequencyMs,
132             int logHistorySize,
133             @SuppressLint({"ConcreteCollection", "NullableCollection"}) @Nullable
134                     ArraySet<ComponentName> whitelistedComponents) {
135         this(
136                 /* lite= */ false,
137                 loggingLevel,
138                 maxBufferSize,
139                 idleFlushingFrequencyMs,
140                 textChangeFlushingFrequencyMs,
141                 logHistorySize,
142                 ContentCaptureManager.DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING,
143                 ContentCaptureManager.DEFAULT_ENABLE_CONTENT_CAPTURE_RECEIVER,
144                 new ContentProtectionOptions(
145                         ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER,
146                         ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE),
147                 whitelistedComponents);
148     }
149 
150     /** @hide */
ContentCaptureOptions( int loggingLevel, int maxBufferSize, int idleFlushingFrequencyMs, int textChangeFlushingFrequencyMs, int logHistorySize, boolean disableFlushForViewTreeAppearing, boolean enableReceiver, @NonNull ContentProtectionOptions contentProtectionOptions, @SuppressLint({R, R}) @Nullable ArraySet<ComponentName> whitelistedComponents)151     public ContentCaptureOptions(
152             int loggingLevel,
153             int maxBufferSize,
154             int idleFlushingFrequencyMs,
155             int textChangeFlushingFrequencyMs,
156             int logHistorySize,
157             boolean disableFlushForViewTreeAppearing,
158             boolean enableReceiver,
159             @NonNull ContentProtectionOptions contentProtectionOptions,
160             @SuppressLint({"ConcreteCollection", "NullableCollection"}) @Nullable
161                     ArraySet<ComponentName> whitelistedComponents) {
162         this(
163                 /* lite= */ false,
164                 loggingLevel,
165                 maxBufferSize,
166                 idleFlushingFrequencyMs,
167                 textChangeFlushingFrequencyMs,
168                 logHistorySize,
169                 disableFlushForViewTreeAppearing,
170                 enableReceiver,
171                 contentProtectionOptions,
172                 whitelistedComponents);
173     }
174 
175     /** @hide */
176     @VisibleForTesting
ContentCaptureOptions(@ullable ArraySet<ComponentName> whitelistedComponents)177     public ContentCaptureOptions(@Nullable ArraySet<ComponentName> whitelistedComponents) {
178         this(
179                 ContentCaptureManager.LOGGING_LEVEL_VERBOSE,
180                 ContentCaptureManager.DEFAULT_MAX_BUFFER_SIZE,
181                 ContentCaptureManager.DEFAULT_IDLE_FLUSHING_FREQUENCY_MS,
182                 ContentCaptureManager.DEFAULT_TEXT_CHANGE_FLUSHING_FREQUENCY_MS,
183                 ContentCaptureManager.DEFAULT_LOG_HISTORY_SIZE,
184                 ContentCaptureManager.DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING,
185                 ContentCaptureManager.DEFAULT_ENABLE_CONTENT_CAPTURE_RECEIVER,
186                 new ContentProtectionOptions(
187                         ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER,
188                         ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE),
189                 whitelistedComponents);
190     }
191 
ContentCaptureOptions( boolean lite, int loggingLevel, int maxBufferSize, int idleFlushingFrequencyMs, int textChangeFlushingFrequencyMs, int logHistorySize, boolean disableFlushForViewTreeAppearing, boolean enableReceiver, @NonNull ContentProtectionOptions contentProtectionOptions, @SuppressLint({R, R}) @Nullable ArraySet<ComponentName> whitelistedComponents)192     private ContentCaptureOptions(
193             boolean lite,
194             int loggingLevel,
195             int maxBufferSize,
196             int idleFlushingFrequencyMs,
197             int textChangeFlushingFrequencyMs,
198             int logHistorySize,
199             boolean disableFlushForViewTreeAppearing,
200             boolean enableReceiver,
201             @NonNull ContentProtectionOptions contentProtectionOptions,
202             @SuppressLint({"ConcreteCollection", "NullableCollection"}) @Nullable
203                     ArraySet<ComponentName> whitelistedComponents) {
204         this.lite = lite;
205         this.loggingLevel = loggingLevel;
206         this.maxBufferSize = maxBufferSize;
207         this.idleFlushingFrequencyMs = idleFlushingFrequencyMs;
208         this.textChangeFlushingFrequencyMs = textChangeFlushingFrequencyMs;
209         this.logHistorySize = logHistorySize;
210         this.disableFlushForViewTreeAppearing = disableFlushForViewTreeAppearing;
211         this.enableReceiver = enableReceiver;
212         this.contentProtectionOptions = contentProtectionOptions;
213         this.whitelistedComponents = whitelistedComponents;
214     }
215 
forWhitelistingItself()216     public static ContentCaptureOptions forWhitelistingItself() {
217         final ActivityThread at = ActivityThread.currentActivityThread();
218         if (at == null) {
219             throw new IllegalStateException("No ActivityThread");
220         }
221 
222         final String packageName = at.getApplication().getPackageName();
223 
224         if (!"android.contentcaptureservice.cts".equals(packageName)
225                 && !"android.translation.cts".equals(packageName)) {
226             Log.e(TAG, "forWhitelistingItself(): called by " + packageName);
227             throw new SecurityException("Thou shall not pass!");
228         }
229 
230         final ContentCaptureOptions options =
231                 new ContentCaptureOptions(/* whitelistedComponents= */ null);
232         // Always log, as it's used by test only
233         Log.i(TAG, "forWhitelistingItself(" + packageName + "): " + options);
234 
235         return options;
236     }
237 
238     /** @hide */
239     @VisibleForTesting
isWhitelisted(@onNull Context context)240     public boolean isWhitelisted(@NonNull Context context) {
241         if (whitelistedComponents == null) return true; // whole package is allowlisted
242         final ContentCaptureClient client = context.getContentCaptureClient();
243         if (client == null) {
244             // Shouldn't happen, but it doesn't hurt to check...
245             Log.w(TAG, "isWhitelisted(): no ContentCaptureClient on " + context);
246             return false;
247         }
248         return whitelistedComponents.contains(client.contentCaptureClientGetComponentName());
249     }
250 
251     @Override
toString()252     public String toString() {
253         if (lite) {
254             return "ContentCaptureOptions [loggingLevel=" + loggingLevel + " (lite)]";
255         }
256         final StringBuilder string = new StringBuilder("ContentCaptureOptions [");
257         string.append("loggingLevel=")
258                 .append(loggingLevel)
259                 .append(", maxBufferSize=")
260                 .append(maxBufferSize)
261                 .append(", idleFlushingFrequencyMs=")
262                 .append(idleFlushingFrequencyMs)
263                 .append(", textChangeFlushingFrequencyMs=")
264                 .append(textChangeFlushingFrequencyMs)
265                 .append(", logHistorySize=")
266                 .append(logHistorySize)
267                 .append(", disableFlushForViewTreeAppearing=")
268                 .append(disableFlushForViewTreeAppearing)
269                 .append(", enableReceiver=")
270                 .append(enableReceiver)
271                 .append(", contentProtectionOptions=")
272                 .append(contentProtectionOptions);
273         if (whitelistedComponents != null) {
274             string.append(", whitelisted=").append(whitelistedComponents);
275         }
276         return string.append(']').toString();
277     }
278 
279     /** @hide */
dumpShort(@onNull PrintWriter pw)280     public void dumpShort(@NonNull PrintWriter pw) {
281         pw.print("logLvl="); pw.print(loggingLevel);
282         if (lite) {
283             pw.print(", lite");
284             return;
285         }
286         pw.print(", bufferSize=");
287         pw.print(maxBufferSize);
288         pw.print(", idle=");
289         pw.print(idleFlushingFrequencyMs);
290         pw.print(", textIdle=");
291         pw.print(textChangeFlushingFrequencyMs);
292         pw.print(", logSize=");
293         pw.print(logHistorySize);
294         pw.print(", disableFlushForViewTreeAppearing=");
295         pw.print(disableFlushForViewTreeAppearing);
296         pw.print(", enableReceiver=");
297         pw.print(enableReceiver);
298         pw.print(", contentProtectionOptions=[");
299         contentProtectionOptions.dumpShort(pw);
300         pw.print("]");
301         if (whitelistedComponents != null) {
302             pw.print(", whitelisted="); pw.print(whitelistedComponents);
303         }
304     }
305 
306     @Override
describeContents()307     public int describeContents() {
308         return 0;
309     }
310 
311     @Override
writeToParcel(Parcel parcel, int flags)312     public void writeToParcel(Parcel parcel, int flags) {
313         parcel.writeBoolean(lite);
314         parcel.writeInt(loggingLevel);
315         if (lite) return;
316 
317         parcel.writeInt(maxBufferSize);
318         parcel.writeInt(idleFlushingFrequencyMs);
319         parcel.writeInt(textChangeFlushingFrequencyMs);
320         parcel.writeInt(logHistorySize);
321         parcel.writeBoolean(disableFlushForViewTreeAppearing);
322         parcel.writeBoolean(enableReceiver);
323         contentProtectionOptions.writeToParcel(parcel);
324         parcel.writeArraySet(whitelistedComponents);
325     }
326 
327     public static final @android.annotation.NonNull Parcelable.Creator<ContentCaptureOptions> CREATOR =
328             new Parcelable.Creator<ContentCaptureOptions>() {
329 
330                 @Override
331                 public ContentCaptureOptions createFromParcel(Parcel parcel) {
332                     final boolean lite = parcel.readBoolean();
333                     final int loggingLevel = parcel.readInt();
334                     if (lite) {
335                         return new ContentCaptureOptions(loggingLevel);
336                     }
337                     final int maxBufferSize = parcel.readInt();
338                     final int idleFlushingFrequencyMs = parcel.readInt();
339                     final int textChangeFlushingFrequencyMs = parcel.readInt();
340                     final int logHistorySize = parcel.readInt();
341                     final boolean disableFlushForViewTreeAppearing = parcel.readBoolean();
342                     final boolean enableReceiver = parcel.readBoolean();
343                     final ContentProtectionOptions contentProtectionOptions =
344                             ContentProtectionOptions.createFromParcel(parcel);
345                     @SuppressWarnings("unchecked")
346                     final ArraySet<ComponentName> whitelistedComponents =
347                             (ArraySet<ComponentName>) parcel.readArraySet(null);
348                     return new ContentCaptureOptions(
349                             loggingLevel,
350                             maxBufferSize,
351                             idleFlushingFrequencyMs,
352                             textChangeFlushingFrequencyMs,
353                             logHistorySize,
354                             disableFlushForViewTreeAppearing,
355                             enableReceiver,
356                             contentProtectionOptions,
357                             whitelistedComponents);
358                 }
359 
360                 @Override
361                 public ContentCaptureOptions[] newArray(int size) {
362                     return new ContentCaptureOptions[size];
363                 }
364     };
365 
366     /**
367      * Content protection options for a given package.
368      *
369      * <p>Does not implement {@code Parcelable} since it is an inner class without a matching AIDL.
370      *
371      * @hide
372      */
373     public static class ContentProtectionOptions {
374 
375         /**
376          * Is the content protection receiver enabled.
377          *
378          * @hide
379          */
380         public final boolean enableReceiver;
381 
382         /**
383          * Size of the in-memory ring buffer for the content protection flow.
384          *
385          * @hide
386          */
387         public final int bufferSize;
388 
ContentProtectionOptions(boolean enableReceiver, int bufferSize)389         public ContentProtectionOptions(boolean enableReceiver, int bufferSize) {
390             this.enableReceiver = enableReceiver;
391             this.bufferSize = bufferSize;
392         }
393 
394         @Override
toString()395         public String toString() {
396             StringBuilder stringBuilder = new StringBuilder("ContentProtectionOptions [");
397             stringBuilder
398                     .append("enableReceiver=")
399                     .append(enableReceiver)
400                     .append(", bufferSize=")
401                     .append(bufferSize);
402             return stringBuilder.append(']').toString();
403         }
404 
dumpShort(@onNull PrintWriter pw)405         private void dumpShort(@NonNull PrintWriter pw) {
406             pw.print("enableReceiver=");
407             pw.print(enableReceiver);
408             pw.print(", bufferSize=");
409             pw.print(bufferSize);
410         }
411 
writeToParcel(Parcel parcel)412         private void writeToParcel(Parcel parcel) {
413             parcel.writeBoolean(enableReceiver);
414             parcel.writeInt(bufferSize);
415         }
416 
createFromParcel(Parcel parcel)417         private static ContentProtectionOptions createFromParcel(Parcel parcel) {
418             boolean enableReceiver = parcel.readBoolean();
419             int bufferSize = parcel.readInt();
420             return new ContentProtectionOptions(enableReceiver, bufferSize);
421         }
422     }
423 }
424