823 lines
37 KiB
Java
823 lines
37 KiB
Java
/*
|
||
* Copyright 2020 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.IntDef;
|
||
import android.annotation.IntRange;
|
||
import android.annotation.NonNull;
|
||
import android.annotation.Nullable;
|
||
import android.annotation.SuppressLint;
|
||
import android.app.appsearch.annotation.CanIgnoreReturnValue;
|
||
import android.app.appsearch.flags.Flags;
|
||
import android.util.ArrayMap;
|
||
import android.util.ArraySet;
|
||
|
||
import com.android.internal.util.Preconditions;
|
||
|
||
import java.lang.annotation.Retention;
|
||
import java.lang.annotation.RetentionPolicy;
|
||
import java.util.Arrays;
|
||
import java.util.Collection;
|
||
import java.util.Collections;
|
||
import java.util.Map;
|
||
import java.util.Objects;
|
||
import java.util.Set;
|
||
|
||
/**
|
||
* Encapsulates a request to update the schema of an {@link AppSearchSession} database.
|
||
*
|
||
* <p>The schema is composed of a collection of {@link AppSearchSchema} objects, each of which
|
||
* defines a unique type of data.
|
||
*
|
||
* <p>The first call to SetSchemaRequest will set the provided schema and store it within the {@link
|
||
* AppSearchSession} database.
|
||
*
|
||
* <p>Subsequent calls will compare the provided schema to the previously saved schema, to determine
|
||
* how to treat existing documents.
|
||
*
|
||
* <p>The following types of schema modifications are always safe and are made without deleting any
|
||
* existing documents:
|
||
*
|
||
* <ul>
|
||
* <li>Addition of new {@link AppSearchSchema} types
|
||
* <li>Addition of new properties to an existing {@link AppSearchSchema} type
|
||
* <li>Changing the cardinality of a property to be less restrictive
|
||
* </ul>
|
||
*
|
||
* <p>The following types of schema changes are not backwards compatible:
|
||
*
|
||
* <ul>
|
||
* <li>Removal of an existing {@link AppSearchSchema} type
|
||
* <li>Removal of a property from an existing {@link AppSearchSchema} type
|
||
* <li>Changing the data type of an existing property
|
||
* <li>Changing the cardinality of a property to be more restrictive
|
||
* </ul>
|
||
*
|
||
* <p>Providing a schema with incompatible changes, will throw an {@link
|
||
* android.app.appsearch.exceptions.AppSearchException}, with a message describing the
|
||
* incompatibility. As a result, the previously set schema will remain unchanged.
|
||
*
|
||
* <p>Backward incompatible changes can be made by :
|
||
*
|
||
* <ul>
|
||
* <li>setting {@link SetSchemaRequest.Builder#setForceOverride} method to {@code true}. This
|
||
* deletes all documents that are incompatible with the new schema. The new schema is then
|
||
* saved and persisted to disk.
|
||
* <li>Add a {@link Migrator} for each incompatible type and make no deletion. The migrator will
|
||
* migrate documents from its old schema version to the new version. Migrated types will be
|
||
* set into both {@link SetSchemaResponse#getIncompatibleTypes()} and {@link
|
||
* SetSchemaResponse#getMigratedTypes()}. See the migration section below.
|
||
* </ul>
|
||
*
|
||
* @see AppSearchSession#setSchema
|
||
* @see Migrator
|
||
*/
|
||
public final class SetSchemaRequest {
|
||
|
||
/**
|
||
* List of Android Permission are supported in {@link
|
||
* SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility}
|
||
*
|
||
* @see android.Manifest.permission
|
||
* @hide
|
||
*/
|
||
@IntDef(
|
||
value = {
|
||
READ_SMS,
|
||
READ_CALENDAR,
|
||
READ_CONTACTS,
|
||
READ_EXTERNAL_STORAGE,
|
||
READ_HOME_APP_SEARCH_DATA,
|
||
READ_ASSISTANT_APP_SEARCH_DATA,
|
||
ENTERPRISE_ACCESS,
|
||
MANAGED_PROFILE_CONTACTS_ACCESS,
|
||
})
|
||
@Retention(RetentionPolicy.SOURCE)
|
||
public @interface AppSearchSupportedPermission {}
|
||
|
||
/**
|
||
* The {@link android.Manifest.permission#READ_SMS} AppSearch supported in {@link
|
||
* SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility}
|
||
*/
|
||
public static final int READ_SMS = 1;
|
||
|
||
/**
|
||
* The {@link android.Manifest.permission#READ_CALENDAR} AppSearch supported in {@link
|
||
* SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility}
|
||
*/
|
||
public static final int READ_CALENDAR = 2;
|
||
|
||
/**
|
||
* The {@link android.Manifest.permission#READ_CONTACTS} AppSearch supported in {@link
|
||
* SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility}
|
||
*/
|
||
public static final int READ_CONTACTS = 3;
|
||
|
||
/**
|
||
* The {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} AppSearch supported in {@link
|
||
* SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility}
|
||
*/
|
||
public static final int READ_EXTERNAL_STORAGE = 4;
|
||
|
||
/**
|
||
* The {@link android.Manifest.permission#READ_HOME_APP_SEARCH_DATA} AppSearch supported in
|
||
* {@link SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility}
|
||
*/
|
||
public static final int READ_HOME_APP_SEARCH_DATA = 5;
|
||
|
||
/**
|
||
* The {@link android.Manifest.permission#READ_ASSISTANT_APP_SEARCH_DATA} AppSearch supported in
|
||
* {@link SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility}
|
||
*/
|
||
public static final int READ_ASSISTANT_APP_SEARCH_DATA = 6;
|
||
|
||
/**
|
||
* A schema must have this permission set through {@link
|
||
* SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility} to be visible to an
|
||
* {@link EnterpriseGlobalSearchSession}. A call from a regular {@link GlobalSearchSession} will
|
||
* not count as having this permission.
|
||
*
|
||
* @hide
|
||
*/
|
||
public static final int ENTERPRISE_ACCESS = 7;
|
||
|
||
/**
|
||
* A schema with this permission set through {@link
|
||
* SetSchemaRequest.Builder#addRequiredPermissionsForSchemaTypeVisibility} requires the caller
|
||
* to have managed profile contacts access from {@link android.app.admin.DevicePolicyManager} to
|
||
* be visible. This permission indicates that the protected schema may expose managed profile
|
||
* data for contacts search.
|
||
*
|
||
* @hide
|
||
*/
|
||
public static final int MANAGED_PROFILE_CONTACTS_ACCESS = 8;
|
||
|
||
private final Set<AppSearchSchema> mSchemas;
|
||
private final Set<String> mSchemasNotDisplayedBySystem;
|
||
private final Map<String, Set<PackageIdentifier>> mSchemasVisibleToPackages;
|
||
private final Map<String, Set<Set<Integer>>> mSchemasVisibleToPermissions;
|
||
private final Map<String, PackageIdentifier> mPubliclyVisibleSchemas;
|
||
private final Map<String, Set<SchemaVisibilityConfig>> mSchemasVisibleToConfigs;
|
||
private final Map<String, Migrator> mMigrators;
|
||
private final boolean mForceOverride;
|
||
private final int mVersion;
|
||
|
||
SetSchemaRequest(
|
||
@NonNull Set<AppSearchSchema> schemas,
|
||
@NonNull Set<String> schemasNotDisplayedBySystem,
|
||
@NonNull Map<String, Set<PackageIdentifier>> schemasVisibleToPackages,
|
||
@NonNull Map<String, Set<Set<Integer>>> schemasVisibleToPermissions,
|
||
@NonNull Map<String, PackageIdentifier> publiclyVisibleSchemas,
|
||
@NonNull Map<String, Set<SchemaVisibilityConfig>> schemasVisibleToConfigs,
|
||
@NonNull Map<String, Migrator> migrators,
|
||
boolean forceOverride,
|
||
int version) {
|
||
mSchemas = Objects.requireNonNull(schemas);
|
||
mSchemasNotDisplayedBySystem = Objects.requireNonNull(schemasNotDisplayedBySystem);
|
||
mSchemasVisibleToPackages = Objects.requireNonNull(schemasVisibleToPackages);
|
||
mSchemasVisibleToPermissions = Objects.requireNonNull(schemasVisibleToPermissions);
|
||
mPubliclyVisibleSchemas = Objects.requireNonNull(publiclyVisibleSchemas);
|
||
mSchemasVisibleToConfigs = Objects.requireNonNull(schemasVisibleToConfigs);
|
||
mMigrators = Objects.requireNonNull(migrators);
|
||
mForceOverride = forceOverride;
|
||
mVersion = version;
|
||
}
|
||
|
||
/** Returns the {@link AppSearchSchema} types that are part of this request. */
|
||
@NonNull
|
||
public Set<AppSearchSchema> getSchemas() {
|
||
return Collections.unmodifiableSet(mSchemas);
|
||
}
|
||
|
||
/**
|
||
* Returns all the schema types that are opted out of being displayed and visible on any system
|
||
* UI surface.
|
||
*/
|
||
@NonNull
|
||
public Set<String> getSchemasNotDisplayedBySystem() {
|
||
return Collections.unmodifiableSet(mSchemasNotDisplayedBySystem);
|
||
}
|
||
|
||
/**
|
||
* Returns a mapping of schema types to the set of packages that have access to that schema
|
||
* type.
|
||
*
|
||
* <p>It’s inefficient to call this method repeatedly.
|
||
*/
|
||
@NonNull
|
||
public Map<String, Set<PackageIdentifier>> getSchemasVisibleToPackages() {
|
||
Map<String, Set<PackageIdentifier>> copy = new ArrayMap<>();
|
||
for (Map.Entry<String, Set<PackageIdentifier>> entry :
|
||
mSchemasVisibleToPackages.entrySet()) {
|
||
copy.put(entry.getKey(), new ArraySet<>(entry.getValue()));
|
||
}
|
||
return copy;
|
||
}
|
||
|
||
/**
|
||
* Returns a mapping of schema types to the Map of {@link android.Manifest.permission}
|
||
* combinations that querier must hold to access that schema type.
|
||
*
|
||
* <p>The querier could read the {@link GenericDocument} objects under the {@code schemaType} if
|
||
* they holds ALL required permissions of ANY of the individual value sets.
|
||
*
|
||
* <p>For example, if the Map contains {@code {% verbatim %}{{permissionA, PermissionB},
|
||
* {PermissionC, PermissionD}, {PermissionE}}{% endverbatim %}}.
|
||
*
|
||
* <ul>
|
||
* <li>A querier holds both PermissionA and PermissionB has access.
|
||
* <li>A querier holds both PermissionC and PermissionD has access.
|
||
* <li>A querier holds only PermissionE has access.
|
||
* <li>A querier holds both PermissionA and PermissionE has access.
|
||
* <li>A querier holds only PermissionA doesn't have access.
|
||
* <li>A querier holds both PermissionA and PermissionC doesn't have access.
|
||
* </ul>
|
||
*
|
||
* <p>It’s inefficient to call this method repeatedly.
|
||
*
|
||
* @return The map contains schema type and all combinations of required permission for querier
|
||
* to access it. The supported Permission are {@link SetSchemaRequest#READ_SMS}, {@link
|
||
* SetSchemaRequest#READ_CALENDAR}, {@link SetSchemaRequest#READ_CONTACTS}, {@link
|
||
* SetSchemaRequest#READ_EXTERNAL_STORAGE}, {@link
|
||
* SetSchemaRequest#READ_HOME_APP_SEARCH_DATA} and {@link
|
||
* SetSchemaRequest#READ_ASSISTANT_APP_SEARCH_DATA}.
|
||
*/
|
||
// TODO(b/237388235): add enterprise permissions to javadocs after they're unhidden
|
||
@NonNull
|
||
public Map<String, Set<Set<Integer>>> getRequiredPermissionsForSchemaTypeVisibility() {
|
||
return deepCopy(mSchemasVisibleToPermissions);
|
||
}
|
||
|
||
/**
|
||
* Returns a mapping of publicly visible schemas to the {@link PackageIdentifier} specifying the
|
||
* package the schemas are from.
|
||
*/
|
||
@FlaggedApi(Flags.FLAG_ENABLE_SET_PUBLICLY_VISIBLE_SCHEMA)
|
||
@NonNull
|
||
public Map<String, PackageIdentifier> getPubliclyVisibleSchemas() {
|
||
return Collections.unmodifiableMap(mPubliclyVisibleSchemas);
|
||
}
|
||
|
||
/**
|
||
* Returns a mapping of schema types to the set of {@link SchemaVisibilityConfig} that have
|
||
* access to that schema type.
|
||
*
|
||
* <p>It’s inefficient to call this method repeatedly.
|
||
*
|
||
* @see SetSchemaRequest.Builder#addSchemaTypeVisibleToConfig
|
||
*/
|
||
@FlaggedApi(Flags.FLAG_ENABLE_SET_SCHEMA_VISIBLE_TO_CONFIGS)
|
||
@NonNull
|
||
public Map<String, Set<SchemaVisibilityConfig>> getSchemasVisibleToConfigs() {
|
||
Map<String, Set<SchemaVisibilityConfig>> copy = new ArrayMap<>();
|
||
for (Map.Entry<String, Set<SchemaVisibilityConfig>> entry :
|
||
mSchemasVisibleToConfigs.entrySet()) {
|
||
copy.put(entry.getKey(), new ArraySet<>(entry.getValue()));
|
||
}
|
||
return copy;
|
||
}
|
||
|
||
/**
|
||
* Returns the map of {@link Migrator}, the key will be the schema type of the {@link Migrator}
|
||
* associated with.
|
||
*/
|
||
@NonNull
|
||
public Map<String, Migrator> getMigrators() {
|
||
return Collections.unmodifiableMap(mMigrators);
|
||
}
|
||
|
||
/**
|
||
* Returns a mapping of {@link AppSearchSchema} types to the set of packages that have access to
|
||
* that schema type.
|
||
*
|
||
* <p>A more efficient version of {@link #getSchemasVisibleToPackages}, but it returns a
|
||
* modifiable map. This is not meant to be unhidden and should only be used by internal classes.
|
||
*
|
||
* @hide
|
||
*/
|
||
@NonNull
|
||
public Map<String, Set<PackageIdentifier>> getSchemasVisibleToPackagesInternal() {
|
||
return mSchemasVisibleToPackages;
|
||
}
|
||
|
||
/** Returns whether this request will force the schema to be overridden. */
|
||
public boolean isForceOverride() {
|
||
return mForceOverride;
|
||
}
|
||
|
||
/** Returns the database overall schema version. */
|
||
@IntRange(from = 1)
|
||
public int getVersion() {
|
||
return mVersion;
|
||
}
|
||
|
||
/** Builder for {@link SetSchemaRequest} objects. */
|
||
public static final class Builder {
|
||
private static final int DEFAULT_VERSION = 1;
|
||
private ArraySet<AppSearchSchema> mSchemas = new ArraySet<>();
|
||
private ArraySet<String> mSchemasNotDisplayedBySystem = new ArraySet<>();
|
||
private ArrayMap<String, Set<PackageIdentifier>> mSchemasVisibleToPackages =
|
||
new ArrayMap<>();
|
||
private ArrayMap<String, Set<Set<Integer>>> mSchemasVisibleToPermissions = new ArrayMap<>();
|
||
private ArrayMap<String, PackageIdentifier> mPubliclyVisibleSchemas = new ArrayMap<>();
|
||
private ArrayMap<String, Set<SchemaVisibilityConfig>> mSchemaVisibleToConfigs =
|
||
new ArrayMap<>();
|
||
private ArrayMap<String, Migrator> mMigrators = new ArrayMap<>();
|
||
private boolean mForceOverride = false;
|
||
private int mVersion = DEFAULT_VERSION;
|
||
private boolean mBuilt = false;
|
||
|
||
/**
|
||
* Adds one or more {@link AppSearchSchema} types to the schema.
|
||
*
|
||
* <p>An {@link AppSearchSchema} object represents one type of structured data.
|
||
*
|
||
* <p>Any documents of these types will be displayed on system UI surfaces by default.
|
||
*/
|
||
@CanIgnoreReturnValue
|
||
@NonNull
|
||
public Builder addSchemas(@NonNull AppSearchSchema... schemas) {
|
||
Objects.requireNonNull(schemas);
|
||
resetIfBuilt();
|
||
return addSchemas(Arrays.asList(schemas));
|
||
}
|
||
|
||
/**
|
||
* Adds a collection of {@link AppSearchSchema} objects to the schema.
|
||
*
|
||
* <p>An {@link AppSearchSchema} object represents one type of structured data.
|
||
*/
|
||
@CanIgnoreReturnValue
|
||
@NonNull
|
||
public Builder addSchemas(@NonNull Collection<AppSearchSchema> schemas) {
|
||
Objects.requireNonNull(schemas);
|
||
resetIfBuilt();
|
||
mSchemas.addAll(schemas);
|
||
return this;
|
||
}
|
||
|
||
/**
|
||
* Sets whether or not documents from the provided {@code schemaType} will be displayed and
|
||
* visible on any system UI surface.
|
||
*
|
||
* <p>This setting applies to the provided {@code schemaType} only, and does not persist
|
||
* across {@link AppSearchSession#setSchema} calls.
|
||
*
|
||
* <p>The default behavior, if this method is not called, is to allow types to be displayed
|
||
* on system UI surfaces.
|
||
*
|
||
* @param schemaType The name of an {@link AppSearchSchema} within the same {@link
|
||
* SetSchemaRequest}, which will be configured.
|
||
* @param displayed Whether documents of this type will be displayed on system UI surfaces.
|
||
*/
|
||
// Merged list available from getSchemasNotDisplayedBySystem
|
||
@CanIgnoreReturnValue
|
||
@SuppressLint("MissingGetterMatchingBuilder")
|
||
@NonNull
|
||
public Builder setSchemaTypeDisplayedBySystem(
|
||
@NonNull String schemaType, boolean displayed) {
|
||
Objects.requireNonNull(schemaType);
|
||
resetIfBuilt();
|
||
if (displayed) {
|
||
mSchemasNotDisplayedBySystem.remove(schemaType);
|
||
} else {
|
||
mSchemasNotDisplayedBySystem.add(schemaType);
|
||
}
|
||
return this;
|
||
}
|
||
|
||
/**
|
||
* Adds a set of required Android {@link android.Manifest.permission} combination to the
|
||
* given schema type.
|
||
*
|
||
* <p>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.
|
||
*
|
||
* <p>You can call this method to add multiple permission combinations, and the querier will
|
||
* have access if they holds ANY of the combinations.
|
||
*
|
||
* <p>The supported Permissions are {@link #READ_SMS}, {@link #READ_CALENDAR}, {@link
|
||
* #READ_CONTACTS}, {@link #READ_EXTERNAL_STORAGE}, {@link #READ_HOME_APP_SEARCH_DATA} and
|
||
* {@link #READ_ASSISTANT_APP_SEARCH_DATA}.
|
||
*
|
||
* <p>The relationship between permissions added in this method and package visibility
|
||
* setting {@link #setSchemaTypeVisibilityForPackage} is "OR". The caller could access the
|
||
* schema if they match ANY requirements. If you want to set "AND" requirements like a
|
||
* caller must hold required permissions AND it is a specified package, please use {@link
|
||
* #addSchemaTypeVisibleToConfig}.
|
||
*
|
||
* @see android.Manifest.permission#READ_SMS
|
||
* @see android.Manifest.permission#READ_CALENDAR
|
||
* @see android.Manifest.permission#READ_CONTACTS
|
||
* @see android.Manifest.permission#READ_EXTERNAL_STORAGE
|
||
* @see android.Manifest.permission#READ_HOME_APP_SEARCH_DATA
|
||
* @see android.Manifest.permission#READ_ASSISTANT_APP_SEARCH_DATA
|
||
* @param schemaType The schema type to set visibility on.
|
||
* @param permissions A set of required Android permissions the caller need to hold to
|
||
* access {@link GenericDocument} objects that under the given schema.
|
||
* @throws IllegalArgumentException – if input unsupported permission.
|
||
*/
|
||
// TODO(b/237388235): add enterprise permissions to javadocs after they're unhidden
|
||
// Merged list available from getRequiredPermissionsForSchemaTypeVisibility
|
||
@CanIgnoreReturnValue
|
||
@SuppressLint("MissingGetterMatchingBuilder")
|
||
@NonNull
|
||
public Builder addRequiredPermissionsForSchemaTypeVisibility(
|
||
@NonNull String schemaType,
|
||
@AppSearchSupportedPermission @NonNull Set<Integer> permissions) {
|
||
Objects.requireNonNull(schemaType);
|
||
Objects.requireNonNull(permissions);
|
||
for (int permission : permissions) {
|
||
Preconditions.checkArgumentInRange(
|
||
permission, READ_SMS, MANAGED_PROFILE_CONTACTS_ACCESS, "permission");
|
||
}
|
||
resetIfBuilt();
|
||
Set<Set<Integer>> visibleToPermissions = mSchemasVisibleToPermissions.get(schemaType);
|
||
if (visibleToPermissions == null) {
|
||
visibleToPermissions = new ArraySet<>();
|
||
mSchemasVisibleToPermissions.put(schemaType, visibleToPermissions);
|
||
}
|
||
visibleToPermissions.add(permissions);
|
||
return this;
|
||
}
|
||
|
||
/** Clears all required permissions combinations for the given schema type. */
|
||
@CanIgnoreReturnValue
|
||
@NonNull
|
||
public Builder clearRequiredPermissionsForSchemaTypeVisibility(@NonNull String schemaType) {
|
||
Objects.requireNonNull(schemaType);
|
||
resetIfBuilt();
|
||
mSchemasVisibleToPermissions.remove(schemaType);
|
||
return this;
|
||
}
|
||
|
||
/**
|
||
* Sets whether or not documents from the provided {@code schemaType} can be read by the
|
||
* specified package.
|
||
*
|
||
* <p>Each package is represented by a {@link PackageIdentifier}, containing a package name
|
||
* and a byte array of type {@link android.content.pm.PackageManager#CERT_INPUT_SHA256}.
|
||
*
|
||
* <p>To opt into one-way data sharing with another application, the developer will need to
|
||
* explicitly grant the other application’s package name and certificate Read access to its
|
||
* data.
|
||
*
|
||
* <p>For two-way data sharing, both applications need to explicitly grant Read access to
|
||
* one another.
|
||
*
|
||
* <p>By default, data sharing between applications is disabled.
|
||
*
|
||
* <p>The relationship between permissions added in this method and package visibility
|
||
* setting {@link #setSchemaTypeVisibilityForPackage} is "OR". The caller could access the
|
||
* schema if they match ANY requirements. If you want to set "AND" requirements like a
|
||
* caller must hold required permissions AND it is a specified package, please use {@link
|
||
* #addSchemaTypeVisibleToConfig}.
|
||
*
|
||
* @param schemaType The schema type to set visibility on.
|
||
* @param visible Whether the {@code schemaType} will be visible or not.
|
||
* @param packageIdentifier Represents the package that will be granted visibility.
|
||
*/
|
||
// Merged list available from getSchemasVisibleToPackages
|
||
@CanIgnoreReturnValue
|
||
@SuppressLint("MissingGetterMatchingBuilder")
|
||
@NonNull
|
||
public Builder setSchemaTypeVisibilityForPackage(
|
||
@NonNull String schemaType,
|
||
boolean visible,
|
||
@NonNull PackageIdentifier packageIdentifier) {
|
||
Objects.requireNonNull(schemaType);
|
||
Objects.requireNonNull(packageIdentifier);
|
||
resetIfBuilt();
|
||
|
||
Set<PackageIdentifier> packageIdentifiers = mSchemasVisibleToPackages.get(schemaType);
|
||
if (visible) {
|
||
if (packageIdentifiers == null) {
|
||
packageIdentifiers = new ArraySet<>();
|
||
}
|
||
packageIdentifiers.add(packageIdentifier);
|
||
mSchemasVisibleToPackages.put(schemaType, packageIdentifiers);
|
||
} else {
|
||
if (packageIdentifiers == null) {
|
||
// Return early since there was nothing set to begin with.
|
||
return this;
|
||
}
|
||
packageIdentifiers.remove(packageIdentifier);
|
||
if (packageIdentifiers.isEmpty()) {
|
||
// Remove the entire key so that we don't have empty sets as values.
|
||
mSchemasVisibleToPackages.remove(schemaType);
|
||
}
|
||
}
|
||
|
||
return this;
|
||
}
|
||
|
||
/**
|
||
* Specify that the schema should be publicly available, to packages which already have
|
||
* visibility to {@code packageIdentifier}. This visibility is determined by the result of
|
||
* {@link android.content.pm.PackageManager#canPackageQuery}.
|
||
*
|
||
* <p>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.
|
||
*
|
||
* <p>If this is called repeatedly with the same schema, the {@link PackageIdentifier} in
|
||
* the last call will be used as the "from" package for that schema.
|
||
*
|
||
* <p>Calling this with packageIdentifier set to null is valid, and will remove public
|
||
* visibility for the schema.
|
||
*
|
||
* @param schema the schema to make publicly accessible.
|
||
* @param packageIdentifier if an app can see this package via
|
||
* PackageManager#canPackageQuery, it will be able to see the documents of type {@code
|
||
* schema}.
|
||
*/
|
||
// Merged list available from getPubliclyVisibleSchemas
|
||
@CanIgnoreReturnValue
|
||
@SuppressLint("MissingGetterMatchingBuilder")
|
||
@FlaggedApi(Flags.FLAG_ENABLE_SET_PUBLICLY_VISIBLE_SCHEMA)
|
||
@NonNull
|
||
public Builder setPubliclyVisibleSchema(
|
||
@NonNull String schema, @Nullable PackageIdentifier packageIdentifier) {
|
||
Objects.requireNonNull(schema);
|
||
resetIfBuilt();
|
||
|
||
// If the package identifier is null or empty we clear public visibility
|
||
if (packageIdentifier == null || packageIdentifier.getPackageName().isEmpty()) {
|
||
mPubliclyVisibleSchemas.remove(schema);
|
||
return this;
|
||
}
|
||
|
||
mPubliclyVisibleSchemas.put(schema, packageIdentifier);
|
||
return this;
|
||
}
|
||
|
||
/**
|
||
* Sets the documents from the provided {@code schemaType} can be read by the caller if they
|
||
* match the ALL visibility requirements set in {@link SchemaVisibilityConfig}.
|
||
*
|
||
* <p>The requirements in a {@link SchemaVisibilityConfig} is "AND" relationship. A caller
|
||
* must match ALL requirements to access the schema. For example, a caller must hold
|
||
* required permissions AND it is a specified package.
|
||
*
|
||
* <p>You can call this method repeatedly to add multiple {@link SchemaVisibilityConfig}s,
|
||
* and the querier will have access if they match ANY of the {@link SchemaVisibilityConfig}.
|
||
*
|
||
* @param schemaType The schema type to set visibility on.
|
||
* @param schemaVisibilityConfig The {@link SchemaVisibilityConfig} holds all requirements
|
||
* that a call must to match to access the schema.
|
||
*/
|
||
// Merged list available from getSchemasVisibleToConfigs
|
||
@CanIgnoreReturnValue
|
||
@SuppressLint("MissingGetterMatchingBuilder")
|
||
@FlaggedApi(Flags.FLAG_ENABLE_SET_SCHEMA_VISIBLE_TO_CONFIGS)
|
||
@NonNull
|
||
public Builder addSchemaTypeVisibleToConfig(
|
||
@NonNull String schemaType,
|
||
@NonNull SchemaVisibilityConfig schemaVisibilityConfig) {
|
||
Objects.requireNonNull(schemaType);
|
||
Objects.requireNonNull(schemaVisibilityConfig);
|
||
resetIfBuilt();
|
||
Set<SchemaVisibilityConfig> visibleToConfigs = mSchemaVisibleToConfigs.get(schemaType);
|
||
if (visibleToConfigs == null) {
|
||
visibleToConfigs = new ArraySet<>();
|
||
mSchemaVisibleToConfigs.put(schemaType, visibleToConfigs);
|
||
}
|
||
visibleToConfigs.add(schemaVisibilityConfig);
|
||
return this;
|
||
}
|
||
|
||
/** Clears all visible to {@link SchemaVisibilityConfig} for the given schema type. */
|
||
@CanIgnoreReturnValue
|
||
@FlaggedApi(Flags.FLAG_ENABLE_SET_SCHEMA_VISIBLE_TO_CONFIGS)
|
||
@NonNull
|
||
public Builder clearSchemaTypeVisibleToConfigs(@NonNull String schemaType) {
|
||
Objects.requireNonNull(schemaType);
|
||
resetIfBuilt();
|
||
mSchemaVisibleToConfigs.remove(schemaType);
|
||
return this;
|
||
}
|
||
|
||
/**
|
||
* Sets the {@link Migrator} associated with the given SchemaType.
|
||
*
|
||
* <p>The {@link Migrator} migrates all {@link GenericDocument}s under given schema type
|
||
* from the current version number stored in AppSearch to the final version set via {@link
|
||
* #setVersion}.
|
||
*
|
||
* <p>A {@link Migrator} will be invoked if the current version number stored in AppSearch
|
||
* is different from the final version set via {@link #setVersion} and {@link
|
||
* Migrator#shouldMigrate} returns {@code true}.
|
||
*
|
||
* <p>The target schema type of the output {@link GenericDocument} of {@link
|
||
* Migrator#onUpgrade} or {@link Migrator#onDowngrade} must exist in this {@link
|
||
* SetSchemaRequest}.
|
||
*
|
||
* @param schemaType The schema type to set migrator on.
|
||
* @param migrator The migrator translates a document from its current version to the final
|
||
* version set via {@link #setVersion}.
|
||
* @see SetSchemaRequest.Builder#setVersion
|
||
* @see SetSchemaRequest.Builder#addSchemas
|
||
* @see AppSearchSession#setSchema
|
||
*/
|
||
@CanIgnoreReturnValue
|
||
@NonNull
|
||
@SuppressLint("MissingGetterMatchingBuilder") // Getter return plural objects.
|
||
public Builder setMigrator(@NonNull String schemaType, @NonNull Migrator migrator) {
|
||
Objects.requireNonNull(schemaType);
|
||
Objects.requireNonNull(migrator);
|
||
resetIfBuilt();
|
||
mMigrators.put(schemaType, migrator);
|
||
return this;
|
||
}
|
||
|
||
/**
|
||
* Sets a Map of {@link Migrator}s.
|
||
*
|
||
* <p>The key of the map is the schema type that the {@link Migrator} value applies to.
|
||
*
|
||
* <p>The {@link Migrator} migrates all {@link GenericDocument}s under given schema type
|
||
* from the current version number stored in AppSearch to the final version set via {@link
|
||
* #setVersion}.
|
||
*
|
||
* <p>A {@link Migrator} will be invoked if the current version number stored in AppSearch
|
||
* is different from the final version set via {@link #setVersion} and {@link
|
||
* Migrator#shouldMigrate} returns {@code true}.
|
||
*
|
||
* <p>The target schema type of the output {@link GenericDocument} of {@link
|
||
* Migrator#onUpgrade} or {@link Migrator#onDowngrade} must exist in this {@link
|
||
* SetSchemaRequest}.
|
||
*
|
||
* @param migrators A {@link Map} of migrators that translate a document from its current
|
||
* version to the final version set via {@link #setVersion}. The key of the map is the
|
||
* schema type that the {@link Migrator} value applies to.
|
||
* @see SetSchemaRequest.Builder#setVersion
|
||
* @see SetSchemaRequest.Builder#addSchemas
|
||
* @see AppSearchSession#setSchema
|
||
*/
|
||
@CanIgnoreReturnValue
|
||
@NonNull
|
||
public Builder setMigrators(@NonNull Map<String, Migrator> migrators) {
|
||
Objects.requireNonNull(migrators);
|
||
resetIfBuilt();
|
||
mMigrators.putAll(migrators);
|
||
return this;
|
||
}
|
||
|
||
/**
|
||
* Sets whether or not to override the current schema in the {@link AppSearchSession}
|
||
* database.
|
||
*
|
||
* <p>Call this method whenever backward incompatible changes need to be made by setting
|
||
* {@code forceOverride} to {@code true}. As a result, during execution of the setSchema
|
||
* operation, all documents that are incompatible with the new schema will be deleted and
|
||
* the new schema will be saved and persisted.
|
||
*
|
||
* <p>By default, this is {@code false}.
|
||
*/
|
||
@CanIgnoreReturnValue
|
||
@NonNull
|
||
public Builder setForceOverride(boolean forceOverride) {
|
||
resetIfBuilt();
|
||
mForceOverride = forceOverride;
|
||
return this;
|
||
}
|
||
|
||
/**
|
||
* Sets the version number of the overall {@link AppSearchSchema} in the database.
|
||
*
|
||
* <p>The {@link AppSearchSession} database can only ever hold documents for one version at
|
||
* a time.
|
||
*
|
||
* <p>Setting a version number that is different from the version number currently stored in
|
||
* AppSearch will result in AppSearch calling the {@link Migrator}s provided to {@link
|
||
* AppSearchSession#setSchema} to migrate the documents already in AppSearch from the
|
||
* previous version to the one set in this request. The version number can be updated
|
||
* without any other changes to the set of schemas.
|
||
*
|
||
* <p>The version number can stay the same, increase, or decrease relative to the current
|
||
* version number that is already stored in the {@link AppSearchSession} database.
|
||
*
|
||
* <p>The version of an empty database will always be 0. You cannot set version to the
|
||
* {@link SetSchemaRequest}, if it doesn't contains any {@link AppSearchSchema}.
|
||
*
|
||
* @param version A positive integer representing the version of the entire set of schemas
|
||
* represents the version of the whole schema in the {@link AppSearchSession} database,
|
||
* default version is 1.
|
||
* @throws IllegalArgumentException if the version is negative.
|
||
* @see AppSearchSession#setSchema
|
||
* @see Migrator
|
||
* @see SetSchemaRequest.Builder#setMigrator
|
||
*/
|
||
@CanIgnoreReturnValue
|
||
@NonNull
|
||
public Builder setVersion(@IntRange(from = 1) int version) {
|
||
Preconditions.checkArgument(version >= 1, "Version must be a positive number.");
|
||
resetIfBuilt();
|
||
mVersion = version;
|
||
return this;
|
||
}
|
||
|
||
/**
|
||
* Builds a new {@link SetSchemaRequest} object.
|
||
*
|
||
* @throws IllegalArgumentException if schema types were referenced, but the corresponding
|
||
* {@link AppSearchSchema} type was never added.
|
||
*/
|
||
@NonNull
|
||
public SetSchemaRequest build() {
|
||
// Verify that any schema types with display or visibility settings refer to a real
|
||
// schema.
|
||
// Create a copy because we're going to remove from the set for verification purposes.
|
||
Set<String> referencedSchemas = new ArraySet<>(mSchemasNotDisplayedBySystem);
|
||
referencedSchemas.addAll(mSchemasVisibleToPackages.keySet());
|
||
referencedSchemas.addAll(mSchemasVisibleToPermissions.keySet());
|
||
referencedSchemas.addAll(mPubliclyVisibleSchemas.keySet());
|
||
referencedSchemas.addAll(mSchemaVisibleToConfigs.keySet());
|
||
|
||
for (AppSearchSchema schema : mSchemas) {
|
||
referencedSchemas.remove(schema.getSchemaType());
|
||
}
|
||
if (!referencedSchemas.isEmpty()) {
|
||
// We still have schema types that weren't seen in our mSchemas set. This means
|
||
// there wasn't a corresponding AppSearchSchema.
|
||
throw new IllegalArgumentException(
|
||
"Schema types " + referencedSchemas + " referenced, but were not added.");
|
||
}
|
||
if (mSchemas.isEmpty() && mVersion != DEFAULT_VERSION) {
|
||
throw new IllegalArgumentException(
|
||
"Cannot set version to the request if schema is empty.");
|
||
}
|
||
mBuilt = true;
|
||
return new SetSchemaRequest(
|
||
mSchemas,
|
||
mSchemasNotDisplayedBySystem,
|
||
mSchemasVisibleToPackages,
|
||
mSchemasVisibleToPermissions,
|
||
mPubliclyVisibleSchemas,
|
||
mSchemaVisibleToConfigs,
|
||
mMigrators,
|
||
mForceOverride,
|
||
mVersion);
|
||
}
|
||
|
||
private void resetIfBuilt() {
|
||
if (mBuilt) {
|
||
ArrayMap<String, Set<PackageIdentifier>> schemasVisibleToPackages =
|
||
new ArrayMap<>(mSchemasVisibleToPackages.size());
|
||
for (Map.Entry<String, Set<PackageIdentifier>> entry :
|
||
mSchemasVisibleToPackages.entrySet()) {
|
||
schemasVisibleToPackages.put(entry.getKey(), new ArraySet<>(entry.getValue()));
|
||
}
|
||
mSchemasVisibleToPackages = schemasVisibleToPackages;
|
||
|
||
mPubliclyVisibleSchemas = new ArrayMap<>(mPubliclyVisibleSchemas);
|
||
|
||
mSchemasVisibleToPermissions = deepCopy(mSchemasVisibleToPermissions);
|
||
|
||
ArrayMap<String, Set<SchemaVisibilityConfig>> schemaVisibleToConfigs =
|
||
new ArrayMap<>(mSchemaVisibleToConfigs.size());
|
||
for (Map.Entry<String, Set<SchemaVisibilityConfig>> entry :
|
||
mSchemaVisibleToConfigs.entrySet()) {
|
||
schemaVisibleToConfigs.put(entry.getKey(), new ArraySet<>(entry.getValue()));
|
||
}
|
||
mSchemaVisibleToConfigs = schemaVisibleToConfigs;
|
||
|
||
mSchemas = new ArraySet<>(mSchemas);
|
||
mSchemasNotDisplayedBySystem = new ArraySet<>(mSchemasNotDisplayedBySystem);
|
||
mMigrators = new ArrayMap<>(mMigrators);
|
||
mBuilt = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
private static ArrayMap<String, Set<Set<Integer>>> deepCopy(
|
||
@NonNull Map<String, Set<Set<Integer>>> original) {
|
||
ArrayMap<String, Set<Set<Integer>>> copy = new ArrayMap<>(original.size());
|
||
for (Map.Entry<String, Set<Set<Integer>>> entry : original.entrySet()) {
|
||
Set<Set<Integer>> valueCopy = new ArraySet<>();
|
||
for (Set<Integer> innerValue : entry.getValue()) {
|
||
valueCopy.add(new ArraySet<>(innerValue));
|
||
}
|
||
copy.put(entry.getKey(), valueCopy);
|
||
}
|
||
return copy;
|
||
}
|
||
}
|