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      * List of activities explicitly allowlisted for content capture (or {@code null} if allowlisted
74      * for all acitivites in the package).
75      */
76     @Nullable
77     @SuppressLint("NullableCollection")
78     public final ArraySet<ComponentName> whitelistedComponents;
79 
80     /**
81      * Used to enable just a small set of APIs so it can used by activities belonging to the
82      * content capture service APK.
83      */
84     public final boolean lite;
85 
86     /**
87      * Constructor for "lite" objects that are just used to enable a {@link ContentCaptureManager}
88      * for contexts belonging to the content capture service app.
89      */
ContentCaptureOptions(int loggingLevel)90     public ContentCaptureOptions(int loggingLevel) {
91         this(/* lite= */ true, loggingLevel, /* maxBufferSize= */ 0,
92                 /* idleFlushingFrequencyMs= */ 0, /* textChangeFlushingFrequencyMs= */ 0,
93                 /* logHistorySize= */ 0, /* whitelistedComponents= */ null);
94     }
95 
96     /**
97      * Default constructor.
98      */
ContentCaptureOptions(int loggingLevel, int maxBufferSize, int idleFlushingFrequencyMs, int textChangeFlushingFrequencyMs, int logHistorySize, @SuppressLint(R) @Nullable ArraySet<ComponentName> whitelistedComponents)99     public ContentCaptureOptions(int loggingLevel, int maxBufferSize, int idleFlushingFrequencyMs,
100             int textChangeFlushingFrequencyMs, int logHistorySize,
101             @SuppressLint("NullableCollection")
102             @Nullable ArraySet<ComponentName> whitelistedComponents) {
103         this(/* lite= */ false, loggingLevel, maxBufferSize, idleFlushingFrequencyMs,
104                 textChangeFlushingFrequencyMs, logHistorySize, whitelistedComponents);
105     }
106 
107     /** @hide */
108     @VisibleForTesting
ContentCaptureOptions(@ullable ArraySet<ComponentName> whitelistedComponents)109     public ContentCaptureOptions(@Nullable ArraySet<ComponentName> whitelistedComponents) {
110         this(ContentCaptureManager.LOGGING_LEVEL_VERBOSE,
111                 ContentCaptureManager.DEFAULT_MAX_BUFFER_SIZE,
112                 ContentCaptureManager.DEFAULT_IDLE_FLUSHING_FREQUENCY_MS,
113                 ContentCaptureManager.DEFAULT_TEXT_CHANGE_FLUSHING_FREQUENCY_MS,
114                 ContentCaptureManager.DEFAULT_LOG_HISTORY_SIZE, whitelistedComponents);
115     }
116 
ContentCaptureOptions(boolean lite, int loggingLevel, int maxBufferSize, int idleFlushingFrequencyMs, int textChangeFlushingFrequencyMs, int logHistorySize, @Nullable ArraySet<ComponentName> whitelistedComponents)117     private ContentCaptureOptions(boolean lite, int loggingLevel, int maxBufferSize,
118             int idleFlushingFrequencyMs, int textChangeFlushingFrequencyMs, int logHistorySize,
119             @Nullable ArraySet<ComponentName> whitelistedComponents) {
120         this.lite = lite;
121         this.loggingLevel = loggingLevel;
122         this.maxBufferSize = maxBufferSize;
123         this.idleFlushingFrequencyMs = idleFlushingFrequencyMs;
124         this.textChangeFlushingFrequencyMs = textChangeFlushingFrequencyMs;
125         this.logHistorySize = logHistorySize;
126         this.whitelistedComponents = whitelistedComponents;
127     }
128 
forWhitelistingItself()129     public static ContentCaptureOptions forWhitelistingItself() {
130         final ActivityThread at = ActivityThread.currentActivityThread();
131         if (at == null) {
132             throw new IllegalStateException("No ActivityThread");
133         }
134 
135         final String packageName = at.getApplication().getPackageName();
136 
137         if (!"android.contentcaptureservice.cts".equals(packageName)
138                 && !"android.translation.cts".equals(packageName)) {
139             Log.e(TAG, "forWhitelistingItself(): called by " + packageName);
140             throw new SecurityException("Thou shall not pass!");
141         }
142 
143         final ContentCaptureOptions options =
144                 new ContentCaptureOptions(/* whitelistedComponents= */ null);
145         // Always log, as it's used by test only
146         Log.i(TAG, "forWhitelistingItself(" + packageName + "): " + options);
147 
148         return options;
149     }
150 
151     /** @hide */
152     @VisibleForTesting
isWhitelisted(@onNull Context context)153     public boolean isWhitelisted(@NonNull Context context) {
154         if (whitelistedComponents == null) return true; // whole package is allowlisted
155         final ContentCaptureClient client = context.getContentCaptureClient();
156         if (client == null) {
157             // Shouldn't happen, but it doesn't hurt to check...
158             Log.w(TAG, "isWhitelisted(): no ContentCaptureClient on " + context);
159             return false;
160         }
161         return whitelistedComponents.contains(client.contentCaptureClientGetComponentName());
162     }
163 
164     @Override
toString()165     public String toString() {
166         if (lite) {
167             return "ContentCaptureOptions [loggingLevel=" + loggingLevel + " (lite)]";
168         }
169         final StringBuilder string = new StringBuilder("ContentCaptureOptions [");
170         string.append("loggingLevel=").append(loggingLevel)
171             .append(", maxBufferSize=").append(maxBufferSize)
172             .append(", idleFlushingFrequencyMs=").append(idleFlushingFrequencyMs)
173             .append(", textChangeFlushingFrequencyMs=").append(textChangeFlushingFrequencyMs)
174             .append(", logHistorySize=").append(logHistorySize);
175         if (whitelistedComponents != null) {
176             string.append(", whitelisted=").append(whitelistedComponents);
177         }
178         return string.append(']').toString();
179     }
180 
181     /** @hide */
dumpShort(@onNull PrintWriter pw)182     public void dumpShort(@NonNull PrintWriter pw) {
183         pw.print("logLvl="); pw.print(loggingLevel);
184         if (lite) {
185             pw.print(", lite");
186             return;
187         }
188         pw.print(", bufferSize="); pw.print(maxBufferSize);
189         pw.print(", idle="); pw.print(idleFlushingFrequencyMs);
190         pw.print(", textIdle="); pw.print(textChangeFlushingFrequencyMs);
191         pw.print(", logSize="); pw.print(logHistorySize);
192         if (whitelistedComponents != null) {
193             pw.print(", whitelisted="); pw.print(whitelistedComponents);
194         }
195     }
196 
197     @Override
describeContents()198     public int describeContents() {
199         return 0;
200     }
201 
202     @Override
writeToParcel(Parcel parcel, int flags)203     public void writeToParcel(Parcel parcel, int flags) {
204         parcel.writeBoolean(lite);
205         parcel.writeInt(loggingLevel);
206         if (lite) return;
207 
208         parcel.writeInt(maxBufferSize);
209         parcel.writeInt(idleFlushingFrequencyMs);
210         parcel.writeInt(textChangeFlushingFrequencyMs);
211         parcel.writeInt(logHistorySize);
212         parcel.writeArraySet(whitelistedComponents);
213     }
214 
215     public static final @android.annotation.NonNull Parcelable.Creator<ContentCaptureOptions> CREATOR =
216             new Parcelable.Creator<ContentCaptureOptions>() {
217 
218                 @Override
219                 public ContentCaptureOptions createFromParcel(Parcel parcel) {
220                     final boolean lite = parcel.readBoolean();
221                     final int loggingLevel = parcel.readInt();
222                     if (lite) {
223                         return new ContentCaptureOptions(loggingLevel);
224                     }
225                     final int maxBufferSize = parcel.readInt();
226                     final int idleFlushingFrequencyMs = parcel.readInt();
227                     final int textChangeFlushingFrequencyMs = parcel.readInt();
228                     final int logHistorySize = parcel.readInt();
229                     @SuppressWarnings("unchecked")
230                     final ArraySet<ComponentName> whitelistedComponents =
231                             (ArraySet<ComponentName>) parcel.readArraySet(null);
232                     return new ContentCaptureOptions(loggingLevel, maxBufferSize,
233                             idleFlushingFrequencyMs, textChangeFlushingFrequencyMs, logHistorySize,
234                             whitelistedComponents);
235                 }
236 
237                 @Override
238                 public ContentCaptureOptions[] newArray(int size) {
239                     return new ContentCaptureOptions[size];
240                 }
241     };
242 }
243