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