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