1 /* 2 * Copyright 2023 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.FlaggedApi; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.app.appsearch.annotation.CanIgnoreReturnValue; 23 import android.app.appsearch.safeparcel.AbstractSafeParcelable; 24 import android.app.appsearch.safeparcel.PackageIdentifierParcel; 25 import android.app.appsearch.safeparcel.SafeParcelable; 26 import android.os.Parcel; 27 import android.os.Parcelable; 28 import android.util.ArraySet; 29 30 import com.android.appsearch.flags.Flags; 31 32 import java.util.ArrayList; 33 import java.util.List; 34 import java.util.Objects; 35 import java.util.Set; 36 37 /** 38 * A class to hold a all necessary Visibility information corresponding to the same schema. This 39 * pattern allows for easier association of these documents. 40 * 41 * <p>This does not correspond to any schema, the properties held in this class are kept in two 42 * separate schemas, VisibilityConfig and PublicAclOverlay. 43 */ 44 @FlaggedApi(Flags.FLAG_ENABLE_SET_SCHEMA_VISIBLE_TO_CONFIGS) 45 @SafeParcelable.Class(creator = "VisibilityConfigCreator") 46 // TODO(b/384721898): Switch to JSpecify annotations 47 @SuppressWarnings({"HiddenSuperclass", "JSpecifyNullness"}) 48 public final class SchemaVisibilityConfig extends AbstractSafeParcelable { 49 50 public static final @NonNull Parcelable.Creator<SchemaVisibilityConfig> CREATOR = 51 new VisibilityConfigCreator(); 52 53 @Field(id = 1) 54 final @NonNull List<PackageIdentifierParcel> mAllowedPackages; 55 56 @Field(id = 2) 57 final @NonNull List<VisibilityPermissionConfig> mRequiredPermissions; 58 59 @Field(id = 3) 60 final @Nullable PackageIdentifierParcel mPubliclyVisibleTargetPackage; 61 62 private @Nullable Integer mHashCode; 63 private @Nullable List<PackageIdentifier> mAllowedPackagesCached; 64 private @Nullable Set<Set<Integer>> mRequiredPermissionsCached; 65 66 @Constructor SchemaVisibilityConfig( @aramid = 1) @onNull List<PackageIdentifierParcel> allowedPackages, @Param(id = 2) @NonNull List<VisibilityPermissionConfig> requiredPermissions, @Param(id = 3) @Nullable PackageIdentifierParcel publiclyVisibleTargetPackage)67 SchemaVisibilityConfig( 68 @Param(id = 1) @NonNull List<PackageIdentifierParcel> allowedPackages, 69 @Param(id = 2) @NonNull List<VisibilityPermissionConfig> requiredPermissions, 70 @Param(id = 3) @Nullable PackageIdentifierParcel publiclyVisibleTargetPackage) { 71 mAllowedPackages = Objects.requireNonNull(allowedPackages); 72 mRequiredPermissions = Objects.requireNonNull(requiredPermissions); 73 mPubliclyVisibleTargetPackage = publiclyVisibleTargetPackage; 74 } 75 76 /** Returns a list of {@link PackageIdentifier}s of packages that can access this schema. */ getAllowedPackages()77 public @NonNull List<PackageIdentifier> getAllowedPackages() { 78 if (mAllowedPackagesCached == null) { 79 mAllowedPackagesCached = new ArrayList<>(mAllowedPackages.size()); 80 for (int i = 0; i < mAllowedPackages.size(); i++) { 81 mAllowedPackagesCached.add(new PackageIdentifier(mAllowedPackages.get(i))); 82 } 83 } 84 return mAllowedPackagesCached; 85 } 86 87 /** 88 * Returns an array of Integers representing Android Permissions that the caller must hold to 89 * access the schema this {@link SchemaVisibilityConfig} represents. 90 * 91 * @see SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility(String, Set) 92 */ getRequiredPermissions()93 public @NonNull Set<Set<Integer>> getRequiredPermissions() { 94 if (mRequiredPermissionsCached == null) { 95 mRequiredPermissionsCached = new ArraySet<>(mRequiredPermissions.size()); 96 for (int i = 0; i < mRequiredPermissions.size(); i++) { 97 VisibilityPermissionConfig permissionConfig = mRequiredPermissions.get(i); 98 Set<Integer> requiredPermissions = permissionConfig.getAllRequiredPermissions(); 99 if (mRequiredPermissionsCached != null && requiredPermissions != null) { 100 mRequiredPermissionsCached.add(requiredPermissions); 101 } 102 } 103 } 104 // Added for nullness checker as it is @Nullable, we initialize it above if it is null. 105 return Objects.requireNonNull(mRequiredPermissionsCached); 106 } 107 108 /** 109 * Returns the {@link PackageIdentifier} of the package that will be used as the target package 110 * in a call to {@link android.content.pm.PackageManager#canPackageQuery} to determine which 111 * packages can access this publicly visible schema. Returns null if the schema is not publicly 112 * visible. 113 */ getPubliclyVisibleTargetPackage()114 public @Nullable PackageIdentifier getPubliclyVisibleTargetPackage() { 115 if (mPubliclyVisibleTargetPackage == null) { 116 return null; 117 } 118 return new PackageIdentifier(mPubliclyVisibleTargetPackage); 119 } 120 121 @Override writeToParcel(@onNull Parcel dest, int flags)122 public void writeToParcel(@NonNull Parcel dest, int flags) { 123 VisibilityConfigCreator.writeToParcel(this, dest, flags); 124 } 125 126 @Override equals(@ullable Object o)127 public boolean equals(@Nullable Object o) { 128 if (this == o) { 129 return true; 130 } 131 if (o == null) { 132 return false; 133 } 134 if (!(o instanceof SchemaVisibilityConfig)) { 135 return false; 136 } 137 SchemaVisibilityConfig that = (SchemaVisibilityConfig) o; 138 return Objects.equals(mAllowedPackages, that.mAllowedPackages) 139 && Objects.equals(mRequiredPermissions, that.mRequiredPermissions) 140 && Objects.equals( 141 mPubliclyVisibleTargetPackage, that.mPubliclyVisibleTargetPackage); 142 } 143 144 @Override hashCode()145 public int hashCode() { 146 if (mHashCode == null) { 147 mHashCode = 148 Objects.hash( 149 mAllowedPackages, mRequiredPermissions, mPubliclyVisibleTargetPackage); 150 } 151 return mHashCode; 152 } 153 154 /** The builder class of {@link SchemaVisibilityConfig}. */ 155 @FlaggedApi(Flags.FLAG_ENABLE_SET_SCHEMA_VISIBLE_TO_CONFIGS) 156 public static final class Builder { 157 private List<PackageIdentifierParcel> mAllowedPackages = new ArrayList<>(); 158 private List<VisibilityPermissionConfig> mRequiredPermissions = new ArrayList<>(); 159 private @Nullable PackageIdentifierParcel mPubliclyVisibleTargetPackage; 160 private boolean mBuilt; 161 162 /** Creates a {@link Builder} for a {@link SchemaVisibilityConfig}. */ Builder()163 public Builder() {} 164 165 /** 166 * Creates a {@link Builder} copying the values from an existing {@link 167 * SchemaVisibilityConfig}. 168 * 169 * @hide 170 */ Builder(@onNull SchemaVisibilityConfig schemaVisibilityConfig)171 public Builder(@NonNull SchemaVisibilityConfig schemaVisibilityConfig) { 172 Objects.requireNonNull(schemaVisibilityConfig); 173 mAllowedPackages = new ArrayList<>(schemaVisibilityConfig.mAllowedPackages); 174 mRequiredPermissions = new ArrayList<>(schemaVisibilityConfig.mRequiredPermissions); 175 mPubliclyVisibleTargetPackage = schemaVisibilityConfig.mPubliclyVisibleTargetPackage; 176 } 177 178 /** Add {@link PackageIdentifier} of packages which has access to this schema. */ 179 @CanIgnoreReturnValue addAllowedPackage(@onNull PackageIdentifier packageIdentifier)180 public @NonNull Builder addAllowedPackage(@NonNull PackageIdentifier packageIdentifier) { 181 Objects.requireNonNull(packageIdentifier); 182 resetIfBuilt(); 183 mAllowedPackages.add(packageIdentifier.getPackageIdentifierParcel()); 184 return this; 185 } 186 187 /** Clears the list of packages which have access to this schema. */ 188 @CanIgnoreReturnValue clearAllowedPackages()189 public @NonNull Builder clearAllowedPackages() { 190 resetIfBuilt(); 191 mAllowedPackages.clear(); 192 return this; 193 } 194 195 /** 196 * Adds a set of required Android {@link android.Manifest.permission} combination a package 197 * needs to hold to access the schema this {@link SchemaVisibilityConfig} represents. 198 * 199 * <p>If the querier holds ALL of the required permissions in this combination, they will 200 * have access to read {@link GenericDocument} objects of the given schema type. 201 * 202 * <p>You can call this method repeatedly to add multiple permission combinations, and the 203 * querier will have access if they holds ANY of the combinations. 204 * 205 * <p>Merged Set available from {@link #getRequiredPermissions()}. 206 * 207 * @see SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility for supported 208 * Permissions. 209 */ 210 @SuppressWarnings("RequiresPermission") // No permission required to call this method 211 @CanIgnoreReturnValue addRequiredPermissions(@onNull Set<Integer> visibleToPermissions)212 public @NonNull Builder addRequiredPermissions(@NonNull Set<Integer> visibleToPermissions) { 213 Objects.requireNonNull(visibleToPermissions); 214 resetIfBuilt(); 215 mRequiredPermissions.add(new VisibilityPermissionConfig(visibleToPermissions)); 216 return this; 217 } 218 219 /** 220 * Clears all required permissions combinations set to this {@link SchemaVisibilityConfig}. 221 */ 222 @CanIgnoreReturnValue clearRequiredPermissions()223 public @NonNull Builder clearRequiredPermissions() { 224 resetIfBuilt(); 225 mRequiredPermissions.clear(); 226 return this; 227 } 228 229 /** 230 * Specify that this schema should be publicly available, to the same packages that have 231 * visibility to the package passed as a parameter. This visibility is determined by the 232 * result of {@link android.content.pm.PackageManager#canPackageQuery}. 233 * 234 * <p>It is possible for the packageIdentifier parameter to be different from the package 235 * performing the indexing. This might happen in the case of an on-device indexer processing 236 * information about various packages. The visibility will be the same regardless of which 237 * package indexes the document, as the visibility is based on the packageIdentifier 238 * parameter. 239 * 240 * <p>Calling this with packageIdentifier set to null is valid, and will remove public 241 * visibility for the schema. 242 * 243 * @param packageIdentifier the {@link PackageIdentifier} of the package that will be used 244 * as the target package in a call to {@link 245 * android.content.pm.PackageManager#canPackageQuery} to determine which packages can 246 * access this publicly visible schema. 247 */ 248 @CanIgnoreReturnValue setPubliclyVisibleTargetPackage( @ullable PackageIdentifier packageIdentifier)249 public @NonNull Builder setPubliclyVisibleTargetPackage( 250 @Nullable PackageIdentifier packageIdentifier) { 251 resetIfBuilt(); 252 if (packageIdentifier == null) { 253 mPubliclyVisibleTargetPackage = null; 254 } else { 255 mPubliclyVisibleTargetPackage = packageIdentifier.getPackageIdentifierParcel(); 256 } 257 return this; 258 } 259 resetIfBuilt()260 private void resetIfBuilt() { 261 if (mBuilt) { 262 mAllowedPackages = new ArrayList<>(mAllowedPackages); 263 mRequiredPermissions = new ArrayList<>(mRequiredPermissions); 264 mBuilt = false; 265 } 266 } 267 268 /** Build a {@link SchemaVisibilityConfig} */ build()269 public @NonNull SchemaVisibilityConfig build() { 270 mBuilt = true; 271 return new SchemaVisibilityConfig( 272 mAllowedPackages, mRequiredPermissions, mPubliclyVisibleTargetPackage); 273 } 274 } 275 } 276