1 /* 2 * Copyright 2021 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 17 package android.app.appsearch; 18 19 import android.annotation.IntRange; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.SuppressLint; 23 import android.app.appsearch.annotation.CanIgnoreReturnValue; 24 import android.os.Bundle; 25 import android.util.ArrayMap; 26 import android.util.ArraySet; 27 28 import com.android.internal.util.Preconditions; 29 30 import java.util.ArrayList; 31 import java.util.Collections; 32 import java.util.List; 33 import java.util.Map; 34 import java.util.Objects; 35 import java.util.Set; 36 37 /** The response class of {@link AppSearchSession#getSchema} */ 38 public final class GetSchemaResponse { 39 private static final String VERSION_FIELD = "version"; 40 private static final String SCHEMAS_FIELD = "schemas"; 41 private static final String SCHEMAS_NOT_DISPLAYED_BY_SYSTEM_FIELD = 42 "schemasNotDisplayedBySystem"; 43 private static final String SCHEMAS_VISIBLE_TO_PACKAGES_FIELD = "schemasVisibleToPackages"; 44 private static final String SCHEMAS_VISIBLE_TO_PERMISSION_FIELD = "schemasVisibleToPermissions"; 45 private static final String ALL_REQUIRED_PERMISSION_FIELD = "allRequiredPermission"; 46 /** 47 * This Set contains all schemas that are not displayed by the system. All values in the set are 48 * prefixed with the package-database prefix. We do lazy fetch, the object will be created when 49 * you first time fetch it. 50 */ 51 @Nullable private Set<String> mSchemasNotDisplayedBySystem; 52 /** 53 * This map contains all schemas and {@link PackageIdentifier} that has access to the schema. 54 * All keys in the map are prefixed with the package-database prefix. We do lazy fetch, the 55 * object will be created when you first time fetch it. 56 */ 57 @Nullable private Map<String, Set<PackageIdentifier>> mSchemasVisibleToPackages; 58 59 /** 60 * This map contains all schemas and Android Permissions combinations that are required to 61 * access the schema. All keys in the map are prefixed with the package-database prefix. We do 62 * lazy fetch, the object will be created when you first time fetch it. The Map is constructed 63 * in ANY-ALL cases. The querier could read the {@link GenericDocument} objects under the {@code 64 * schemaType} if they holds ALL required permissions of ANY combinations. The value set 65 * represents {@link android.app.appsearch.SetSchemaRequest.AppSearchSupportedPermission}. 66 */ 67 @Nullable private Map<String, Set<Set<Integer>>> mSchemasVisibleToPermissions; 68 69 private final Bundle mBundle; 70 GetSchemaResponse(@onNull Bundle bundle)71 GetSchemaResponse(@NonNull Bundle bundle) { 72 mBundle = Objects.requireNonNull(bundle); 73 } 74 75 /** 76 * Returns the {@link Bundle} populated by this builder. 77 * 78 * @hide 79 */ 80 @NonNull getBundle()81 public Bundle getBundle() { 82 return mBundle; 83 } 84 85 /** 86 * Returns the overall database schema version. 87 * 88 * <p>If the database is empty, 0 will be returned. 89 */ 90 @IntRange(from = 0) getVersion()91 public int getVersion() { 92 return mBundle.getInt(VERSION_FIELD); 93 } 94 95 /** 96 * Return the schemas most recently successfully provided to {@link AppSearchSession#setSchema}. 97 * 98 * <p>It is inefficient to call this method repeatedly. 99 */ 100 @NonNull 101 @SuppressWarnings("deprecation") getSchemas()102 public Set<AppSearchSchema> getSchemas() { 103 ArrayList<Bundle> schemaBundles = 104 Objects.requireNonNull(mBundle.getParcelableArrayList(SCHEMAS_FIELD)); 105 Set<AppSearchSchema> schemas = new ArraySet<>(schemaBundles.size()); 106 for (int i = 0; i < schemaBundles.size(); i++) { 107 schemas.add(new AppSearchSchema(schemaBundles.get(i))); 108 } 109 return schemas; 110 } 111 112 /** 113 * Returns all the schema types that are opted out of being displayed and visible on any system 114 * UI surface. 115 */ 116 @NonNull getSchemaTypesNotDisplayedBySystem()117 public Set<String> getSchemaTypesNotDisplayedBySystem() { 118 checkGetVisibilitySettingSupported(); 119 if (mSchemasNotDisplayedBySystem == null) { 120 List<String> schemasNotDisplayedBySystemList = 121 mBundle.getStringArrayList(SCHEMAS_NOT_DISPLAYED_BY_SYSTEM_FIELD); 122 mSchemasNotDisplayedBySystem = 123 Collections.unmodifiableSet(new ArraySet<>(schemasNotDisplayedBySystemList)); 124 } 125 return mSchemasNotDisplayedBySystem; 126 } 127 128 /** 129 * Returns a mapping of schema types to the set of packages that have access to that schema 130 * type. 131 */ 132 @NonNull 133 @SuppressWarnings("deprecation") getSchemaTypesVisibleToPackages()134 public Map<String, Set<PackageIdentifier>> getSchemaTypesVisibleToPackages() { 135 checkGetVisibilitySettingSupported(); 136 if (mSchemasVisibleToPackages == null) { 137 Bundle schemaVisibleToPackagesBundle = 138 Objects.requireNonNull(mBundle.getBundle(SCHEMAS_VISIBLE_TO_PACKAGES_FIELD)); 139 Map<String, Set<PackageIdentifier>> copy = new ArrayMap<>(); 140 for (String key : schemaVisibleToPackagesBundle.keySet()) { 141 List<Bundle> PackageIdentifierBundles = 142 Objects.requireNonNull( 143 schemaVisibleToPackagesBundle.getParcelableArrayList(key)); 144 Set<PackageIdentifier> packageIdentifiers = 145 new ArraySet<>(PackageIdentifierBundles.size()); 146 for (int i = 0; i < PackageIdentifierBundles.size(); i++) { 147 packageIdentifiers.add(new PackageIdentifier(PackageIdentifierBundles.get(i))); 148 } 149 copy.put(key, packageIdentifiers); 150 } 151 mSchemasVisibleToPackages = Collections.unmodifiableMap(copy); 152 } 153 return mSchemasVisibleToPackages; 154 } 155 156 /** 157 * Returns a mapping of schema types to the Map of {@link android.Manifest.permission} 158 * combinations that querier must hold to access that schema type. 159 * 160 * <p>The querier could read the {@link GenericDocument} objects under the {@code schemaType} if 161 * they holds ALL required permissions of ANY of the individual value sets. 162 * 163 * <p>For example, if the Map contains {@code {% verbatim %}{{permissionA, PermissionB}, { 164 * PermissionC, PermissionD}, {PermissionE}}{% endverbatim %}}. 165 * 166 * <ul> 167 * <li>A querier holds both PermissionA and PermissionB has access. 168 * <li>A querier holds both PermissionC and PermissionD has access. 169 * <li>A querier holds only PermissionE has access. 170 * <li>A querier holds both PermissionA and PermissionE has access. 171 * <li>A querier holds only PermissionA doesn't have access. 172 * <li>A querier holds both PermissionA and PermissionC doesn't have access. 173 * </ul> 174 * 175 * @return The map contains schema type and all combinations of required permission for querier 176 * to access it. The supported Permission are {@link SetSchemaRequest#READ_SMS}, {@link 177 * SetSchemaRequest#READ_CALENDAR}, {@link SetSchemaRequest#READ_CONTACTS}, {@link 178 * SetSchemaRequest#READ_EXTERNAL_STORAGE}, {@link 179 * SetSchemaRequest#READ_HOME_APP_SEARCH_DATA} and {@link 180 * SetSchemaRequest#READ_ASSISTANT_APP_SEARCH_DATA}. 181 */ 182 @NonNull 183 @SuppressWarnings("deprecation") getRequiredPermissionsForSchemaTypeVisibility()184 public Map<String, Set<Set<Integer>>> getRequiredPermissionsForSchemaTypeVisibility() { 185 checkGetVisibilitySettingSupported(); 186 if (mSchemasVisibleToPermissions == null) { 187 Map<String, Set<Set<Integer>>> copy = new ArrayMap<>(); 188 Bundle schemaVisibleToPermissionBundle = 189 Objects.requireNonNull(mBundle.getBundle(SCHEMAS_VISIBLE_TO_PERMISSION_FIELD)); 190 for (String key : schemaVisibleToPermissionBundle.keySet()) { 191 ArrayList<Bundle> allRequiredPermissionsBundle = 192 schemaVisibleToPermissionBundle.getParcelableArrayList(key); 193 Set<Set<Integer>> visibleToPermissions = new ArraySet<>(); 194 if (allRequiredPermissionsBundle != null) { 195 // This should never be null 196 for (int i = 0; i < allRequiredPermissionsBundle.size(); i++) { 197 visibleToPermissions.add( 198 new ArraySet<>( 199 allRequiredPermissionsBundle 200 .get(i) 201 .getIntegerArrayList( 202 ALL_REQUIRED_PERMISSION_FIELD))); 203 } 204 } 205 copy.put(key, visibleToPermissions); 206 } 207 mSchemasVisibleToPermissions = Collections.unmodifiableMap(copy); 208 } 209 return mSchemasVisibleToPermissions; 210 } 211 checkGetVisibilitySettingSupported()212 private void checkGetVisibilitySettingSupported() { 213 if (!mBundle.containsKey(SCHEMAS_VISIBLE_TO_PACKAGES_FIELD)) { 214 throw new UnsupportedOperationException( 215 "Get visibility setting is not supported with" 216 + " this backend/Android API level combination."); 217 } 218 } 219 220 /** Builder for {@link GetSchemaResponse} objects. */ 221 public static final class Builder { 222 private int mVersion = 0; 223 private ArrayList<Bundle> mSchemaBundles = new ArrayList<>(); 224 /** 225 * Creates the object when we actually set them. If we never set visibility settings, we 226 * should throw {@link UnsupportedOperationException} in the visibility getters. 227 */ 228 @Nullable private ArrayList<String> mSchemasNotDisplayedBySystem; 229 230 private Bundle mSchemasVisibleToPackages; 231 private Bundle mSchemasVisibleToPermissions; 232 private boolean mBuilt = false; 233 234 /** Create a {@link Builder} object} */ Builder()235 public Builder() { 236 this(/*getVisibilitySettingSupported=*/ true); 237 } 238 239 /** 240 * Create a {@link Builder} object}. 241 * 242 * <p>This constructor should only be used in Android API below than T. 243 * 244 * @param getVisibilitySettingSupported whether supported {@link 245 * Features#ADD_PERMISSIONS_AND_GET_VISIBILITY} by this backend/Android API level. 246 * @hide 247 */ Builder(boolean getVisibilitySettingSupported)248 public Builder(boolean getVisibilitySettingSupported) { 249 if (getVisibilitySettingSupported) { 250 mSchemasNotDisplayedBySystem = new ArrayList<>(); 251 mSchemasVisibleToPackages = new Bundle(); 252 mSchemasVisibleToPermissions = new Bundle(); 253 } 254 } 255 256 /** 257 * Sets the database overall schema version. 258 * 259 * <p>Default version is 0 260 */ 261 @CanIgnoreReturnValue 262 @NonNull setVersion(@ntRangefrom = 0) int version)263 public Builder setVersion(@IntRange(from = 0) int version) { 264 resetIfBuilt(); 265 mVersion = version; 266 return this; 267 } 268 269 /** Adds one {@link AppSearchSchema} to the schema list. */ 270 @CanIgnoreReturnValue 271 @NonNull addSchema(@onNull AppSearchSchema schema)272 public Builder addSchema(@NonNull AppSearchSchema schema) { 273 Objects.requireNonNull(schema); 274 resetIfBuilt(); 275 mSchemaBundles.add(schema.getBundle()); 276 return this; 277 } 278 279 /** 280 * Sets whether or not documents from the provided {@code schemaType} will be displayed and 281 * visible on any system UI surface. 282 * 283 * @param schemaType The name of an {@link AppSearchSchema} within the same {@link 284 * GetSchemaResponse}, which won't be displayed by system. 285 */ 286 // Getter getSchemaTypesNotDisplayedBySystem returns plural objects. 287 @CanIgnoreReturnValue 288 @SuppressLint("MissingGetterMatchingBuilder") 289 @NonNull addSchemaTypeNotDisplayedBySystem(@onNull String schemaType)290 public Builder addSchemaTypeNotDisplayedBySystem(@NonNull String schemaType) { 291 Objects.requireNonNull(schemaType); 292 resetIfBuilt(); 293 if (mSchemasNotDisplayedBySystem == null) { 294 mSchemasNotDisplayedBySystem = new ArrayList<>(); 295 } 296 mSchemasNotDisplayedBySystem.add(schemaType); 297 return this; 298 } 299 300 /** 301 * Sets whether or not documents from the provided {@code schemaType} can be read by the 302 * specified package. 303 * 304 * <p>Each package is represented by a {@link PackageIdentifier}, containing a package name 305 * and a byte array of type {@link android.content.pm.PackageManager#CERT_INPUT_SHA256}. 306 * 307 * <p>To opt into one-way data sharing with another application, the developer will need to 308 * explicitly grant the other application’s package name and certificate Read access to its 309 * data. 310 * 311 * <p>For two-way data sharing, both applications need to explicitly grant Read access to 312 * one another. 313 * 314 * @param schemaType The schema type to set visibility on. 315 * @param packageIdentifiers Represents the package that has access to the given schema 316 * type. 317 */ 318 // Getter getSchemaTypesVisibleToPackages returns a map contains all schema types. 319 @CanIgnoreReturnValue 320 @SuppressLint("MissingGetterMatchingBuilder") 321 @NonNull setSchemaTypeVisibleToPackages( @onNull String schemaType, @NonNull Set<PackageIdentifier> packageIdentifiers)322 public Builder setSchemaTypeVisibleToPackages( 323 @NonNull String schemaType, @NonNull Set<PackageIdentifier> packageIdentifiers) { 324 Objects.requireNonNull(schemaType); 325 Objects.requireNonNull(packageIdentifiers); 326 resetIfBuilt(); 327 ArrayList<Bundle> bundles = new ArrayList<>(packageIdentifiers.size()); 328 for (PackageIdentifier packageIdentifier : packageIdentifiers) { 329 bundles.add(packageIdentifier.getBundle()); 330 } 331 mSchemasVisibleToPackages.putParcelableArrayList(schemaType, bundles); 332 return this; 333 } 334 335 /** 336 * Sets a set of required {@link android.Manifest.permission} combinations to the given 337 * schema type. 338 * 339 * <p>The querier could read the {@link GenericDocument} objects under the {@code 340 * schemaType} if they holds ALL required permissions of ANY of the individual value sets. 341 * 342 * <p>For example, if the Map contains {@code {% verbatim %}{{permissionA, PermissionB}, 343 * {PermissionC, PermissionD}, {PermissionE}}{% endverbatim %}}. 344 * 345 * <ul> 346 * <li>A querier holds both PermissionA and PermissionB has access. 347 * <li>A querier holds both PermissionC and PermissionD has access. 348 * <li>A querier holds only PermissionE has access. 349 * <li>A querier holds both PermissionA and PermissionE has access. 350 * <li>A querier holds only PermissionA doesn't have access. 351 * <li>A querier holds both PermissionA and PermissionC doesn't have access. 352 * </ul> 353 * 354 * @see android.Manifest.permission#READ_SMS 355 * @see android.Manifest.permission#READ_CALENDAR 356 * @see android.Manifest.permission#READ_CONTACTS 357 * @see android.Manifest.permission#READ_EXTERNAL_STORAGE 358 * @see android.Manifest.permission#READ_HOME_APP_SEARCH_DATA 359 * @see android.Manifest.permission#READ_ASSISTANT_APP_SEARCH_DATA 360 * @param schemaType The schema type to set visibility on. 361 * @param visibleToPermissions The Android permissions that will be required to access the 362 * given schema. 363 */ 364 // Getter getRequiredPermissionsForSchemaTypeVisibility returns a map for all schemaTypes. 365 @CanIgnoreReturnValue 366 @SuppressLint("MissingGetterMatchingBuilder") 367 @NonNull setRequiredPermissionsForSchemaTypeVisibility( @onNull String schemaType, @SetSchemaRequest.AppSearchSupportedPermission @NonNull Set<Set<Integer>> visibleToPermissions)368 public Builder setRequiredPermissionsForSchemaTypeVisibility( 369 @NonNull String schemaType, 370 @SetSchemaRequest.AppSearchSupportedPermission @NonNull 371 Set<Set<Integer>> visibleToPermissions) { 372 Objects.requireNonNull(schemaType); 373 Objects.requireNonNull(visibleToPermissions); 374 resetIfBuilt(); 375 ArrayList<Bundle> visibleToPermissionsBundle = new ArrayList<>(); 376 for (Set<Integer> allRequiredPermissions : visibleToPermissions) { 377 for (int permission : allRequiredPermissions) { 378 Preconditions.checkArgumentInRange( 379 permission, 380 SetSchemaRequest.READ_SMS, 381 SetSchemaRequest.READ_ASSISTANT_APP_SEARCH_DATA, 382 "permission"); 383 } 384 Bundle allRequiredPermissionsBundle = new Bundle(); 385 allRequiredPermissionsBundle.putIntegerArrayList( 386 ALL_REQUIRED_PERMISSION_FIELD, new ArrayList<>(allRequiredPermissions)); 387 visibleToPermissionsBundle.add(allRequiredPermissionsBundle); 388 } 389 mSchemasVisibleToPermissions.putParcelableArrayList( 390 schemaType, visibleToPermissionsBundle); 391 return this; 392 } 393 394 /** Builds a {@link GetSchemaResponse} object. */ 395 @NonNull build()396 public GetSchemaResponse build() { 397 Bundle bundle = new Bundle(); 398 bundle.putInt(VERSION_FIELD, mVersion); 399 bundle.putParcelableArrayList(SCHEMAS_FIELD, mSchemaBundles); 400 if (mSchemasNotDisplayedBySystem != null) { 401 // Only save the visibility fields if it was actually set. 402 bundle.putStringArrayList( 403 SCHEMAS_NOT_DISPLAYED_BY_SYSTEM_FIELD, mSchemasNotDisplayedBySystem); 404 bundle.putBundle(SCHEMAS_VISIBLE_TO_PACKAGES_FIELD, mSchemasVisibleToPackages); 405 bundle.putBundle(SCHEMAS_VISIBLE_TO_PERMISSION_FIELD, mSchemasVisibleToPermissions); 406 } 407 mBuilt = true; 408 return new GetSchemaResponse(bundle); 409 } 410 resetIfBuilt()411 private void resetIfBuilt() { 412 if (mBuilt) { 413 mSchemaBundles = new ArrayList<>(mSchemaBundles); 414 if (mSchemasNotDisplayedBySystem != null) { 415 // Only reset the visibility fields if it was actually set. 416 mSchemasNotDisplayedBySystem = new ArrayList<>(mSchemasNotDisplayedBySystem); 417 Bundle copyVisibleToPackages = new Bundle(); 418 copyVisibleToPackages.putAll(mSchemasVisibleToPackages); 419 mSchemasVisibleToPackages = copyVisibleToPackages; 420 Bundle copyVisibleToPermissions = new Bundle(); 421 copyVisibleToPermissions.putAll(mSchemasVisibleToPermissions); 422 mSchemasVisibleToPermissions = copyVisibleToPermissions; 423 } 424 mBuilt = false; 425 } 426 } 427 } 428 } 429