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