/* * Copyright 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.app.appsearch; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.appsearch.annotation.CanIgnoreReturnValue; import android.app.appsearch.safeparcel.AbstractSafeParcelable; import android.app.appsearch.safeparcel.PackageIdentifierParcel; import android.app.appsearch.safeparcel.SafeParcelable; import android.os.Parcel; import android.os.Parcelable; import android.util.ArraySet; import com.android.appsearch.flags.Flags; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Set; /** * A class to hold a all necessary Visibility information corresponding to the same schema. This * pattern allows for easier association of these documents. * *

This does not correspond to any schema, the properties held in this class are kept in two * separate schemas, VisibilityConfig and PublicAclOverlay. */ @FlaggedApi(Flags.FLAG_ENABLE_SET_SCHEMA_VISIBLE_TO_CONFIGS) @SafeParcelable.Class(creator = "VisibilityConfigCreator") @SuppressWarnings("HiddenSuperclass") public final class SchemaVisibilityConfig extends AbstractSafeParcelable { @NonNull public static final Parcelable.Creator CREATOR = new VisibilityConfigCreator(); @NonNull @Field(id = 1) final List mAllowedPackages; @NonNull @Field(id = 2) final List mRequiredPermissions; @Nullable @Field(id = 3) final PackageIdentifierParcel mPubliclyVisibleTargetPackage; @Nullable private Integer mHashCode; @Nullable private List mAllowedPackagesCached; @Nullable private Set> mRequiredPermissionsCached; @Constructor SchemaVisibilityConfig( @Param(id = 1) @NonNull List allowedPackages, @Param(id = 2) @NonNull List requiredPermissions, @Param(id = 3) @Nullable PackageIdentifierParcel publiclyVisibleTargetPackage) { mAllowedPackages = Objects.requireNonNull(allowedPackages); mRequiredPermissions = Objects.requireNonNull(requiredPermissions); mPubliclyVisibleTargetPackage = publiclyVisibleTargetPackage; } /** Returns a list of {@link PackageIdentifier}s of packages that can access this schema. */ @NonNull public List getAllowedPackages() { if (mAllowedPackagesCached == null) { mAllowedPackagesCached = new ArrayList<>(mAllowedPackages.size()); for (int i = 0; i < mAllowedPackages.size(); i++) { mAllowedPackagesCached.add(new PackageIdentifier(mAllowedPackages.get(i))); } } return mAllowedPackagesCached; } /** * Returns an array of Integers representing Android Permissions that the caller must hold to * access the schema this {@link SchemaVisibilityConfig} represents. * * @see SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility(String, Set) */ @NonNull public Set> getRequiredPermissions() { if (mRequiredPermissionsCached == null) { mRequiredPermissionsCached = new ArraySet<>(mRequiredPermissions.size()); for (int i = 0; i < mRequiredPermissions.size(); i++) { VisibilityPermissionConfig permissionConfig = mRequiredPermissions.get(i); Set requiredPermissions = permissionConfig.getAllRequiredPermissions(); if (mRequiredPermissionsCached != null && requiredPermissions != null) { mRequiredPermissionsCached.add(requiredPermissions); } } } // Added for nullness checker as it is @Nullable, we initialize it above if it is null. return Objects.requireNonNull(mRequiredPermissionsCached); } /** * Returns the {@link PackageIdentifier} of the package that will be used as the target package * in a call to {@link android.content.pm.PackageManager#canPackageQuery} to determine which * packages can access this publicly visible schema. Returns null if the schema is not publicly * visible. */ @Nullable public PackageIdentifier getPubliclyVisibleTargetPackage() { if (mPubliclyVisibleTargetPackage == null) { return null; } return new PackageIdentifier(mPubliclyVisibleTargetPackage); } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { VisibilityConfigCreator.writeToParcel(this, dest, flags); } @Override public boolean equals(@Nullable Object o) { if (this == o) { return true; } if (o == null) { return false; } if (!(o instanceof SchemaVisibilityConfig)) { return false; } SchemaVisibilityConfig that = (SchemaVisibilityConfig) o; return Objects.equals(mAllowedPackages, that.mAllowedPackages) && Objects.equals(mRequiredPermissions, that.mRequiredPermissions) && Objects.equals( mPubliclyVisibleTargetPackage, that.mPubliclyVisibleTargetPackage); } @Override public int hashCode() { if (mHashCode == null) { mHashCode = Objects.hash( mAllowedPackages, mRequiredPermissions, mPubliclyVisibleTargetPackage); } return mHashCode; } /** The builder class of {@link SchemaVisibilityConfig}. */ @FlaggedApi(Flags.FLAG_ENABLE_SET_SCHEMA_VISIBLE_TO_CONFIGS) public static final class Builder { private List mAllowedPackages = new ArrayList<>(); private List mRequiredPermissions = new ArrayList<>(); @Nullable private PackageIdentifierParcel mPubliclyVisibleTargetPackage; private boolean mBuilt; /** Creates a {@link Builder} for a {@link SchemaVisibilityConfig}. */ public Builder() {} /** * Creates a {@link Builder} copying the values from an existing {@link * SchemaVisibilityConfig}. * * @hide */ public Builder(@NonNull SchemaVisibilityConfig schemaVisibilityConfig) { Objects.requireNonNull(schemaVisibilityConfig); mAllowedPackages = new ArrayList<>(schemaVisibilityConfig.mAllowedPackages); mRequiredPermissions = new ArrayList<>(schemaVisibilityConfig.mRequiredPermissions); mPubliclyVisibleTargetPackage = schemaVisibilityConfig.mPubliclyVisibleTargetPackage; } /** Add {@link PackageIdentifier} of packages which has access to this schema. */ @CanIgnoreReturnValue @NonNull public Builder addAllowedPackage(@NonNull PackageIdentifier packageIdentifier) { Objects.requireNonNull(packageIdentifier); resetIfBuilt(); mAllowedPackages.add(packageIdentifier.getPackageIdentifierParcel()); return this; } /** Clears the list of packages which have access to this schema. */ @CanIgnoreReturnValue @NonNull public Builder clearAllowedPackages() { resetIfBuilt(); mAllowedPackages.clear(); return this; } /** * Adds a set of required Android {@link android.Manifest.permission} combination a package * needs to hold to access the schema this {@link SchemaVisibilityConfig} represents. * *

If the querier holds ALL of the required permissions in this combination, they will * have access to read {@link GenericDocument} objects of the given schema type. * *

You can call this method repeatedly to add multiple permission combinations, and the * querier will have access if they holds ANY of the combinations. * *

Merged Set available from {@link #getRequiredPermissions()}. * * @see SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility for supported * Permissions. */ @SuppressWarnings("RequiresPermission") // No permission required to call this method @CanIgnoreReturnValue @NonNull public Builder addRequiredPermissions(@NonNull Set visibleToPermissions) { Objects.requireNonNull(visibleToPermissions); resetIfBuilt(); mRequiredPermissions.add(new VisibilityPermissionConfig(visibleToPermissions)); return this; } /** * Clears all required permissions combinations set to this {@link SchemaVisibilityConfig}. */ @CanIgnoreReturnValue @NonNull public Builder clearRequiredPermissions() { resetIfBuilt(); mRequiredPermissions.clear(); return this; } /** * Specify that this schema should be publicly available, to the same packages that have * visibility to the package passed as a parameter. This visibility is determined by the * result of {@link android.content.pm.PackageManager#canPackageQuery}. * *

It is possible for the packageIdentifier parameter to be different from the package * performing the indexing. This might happen in the case of an on-device indexer processing * information about various packages. The visibility will be the same regardless of which * package indexes the document, as the visibility is based on the packageIdentifier * parameter. * *

Calling this with packageIdentifier set to null is valid, and will remove public * visibility for the schema. * * @param packageIdentifier the {@link PackageIdentifier} of the package that will be used * as the target package in a call to {@link * android.content.pm.PackageManager#canPackageQuery} to determine which packages can * access this publicly visible schema. */ @CanIgnoreReturnValue @NonNull public Builder setPubliclyVisibleTargetPackage( @Nullable PackageIdentifier packageIdentifier) { resetIfBuilt(); if (packageIdentifier == null) { mPubliclyVisibleTargetPackage = null; } else { mPubliclyVisibleTargetPackage = packageIdentifier.getPackageIdentifierParcel(); } return this; } private void resetIfBuilt() { if (mBuilt) { mAllowedPackages = new ArrayList<>(mAllowedPackages); mRequiredPermissions = new ArrayList<>(mRequiredPermissions); mBuilt = false; } } /** Build a {@link SchemaVisibilityConfig} */ @NonNull public SchemaVisibilityConfig build() { mBuilt = true; return new SchemaVisibilityConfig( mAllowedPackages, mRequiredPermissions, mPubliclyVisibleTargetPackage); } } }