blob: 9591470128656f1bc46f9e189b39a0a6d929a4e2 [file] [log] [blame]
/*
* Copyright 2021 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.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.appsearch.annotation.CanIgnoreReturnValue;
import android.os.Bundle;
import android.util.ArrayMap;
import android.util.ArraySet;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/** The response class of {@link AppSearchSession#getSchema} */
public final class GetSchemaResponse {
private static final String VERSION_FIELD = "version";
private static final String SCHEMAS_FIELD = "schemas";
private static final String SCHEMAS_NOT_DISPLAYED_BY_SYSTEM_FIELD =
"schemasNotDisplayedBySystem";
private static final String SCHEMAS_VISIBLE_TO_PACKAGES_FIELD = "schemasVisibleToPackages";
private static final String SCHEMAS_VISIBLE_TO_PERMISSION_FIELD = "schemasVisibleToPermissions";
private static final String ALL_REQUIRED_PERMISSION_FIELD = "allRequiredPermission";
/**
* This Set contains all schemas that are not displayed by the system. All values in the set are
* prefixed with the package-database prefix. We do lazy fetch, the object will be created when
* the user first time fetch it.
*/
@Nullable private Set<String> mSchemasNotDisplayedBySystem;
/**
* This map contains all schemas and {@link PackageIdentifier} that has access to the schema.
* All keys in the map are prefixed with the package-database prefix. We do lazy fetch, the
* object will be created when the user first time fetch it.
*/
@Nullable private Map<String, Set<PackageIdentifier>> mSchemasVisibleToPackages;
/**
* This map contains all schemas and Android Permissions combinations that are required to
* access the schema. All keys in the map are prefixed with the package-database prefix. We do
* lazy fetch, the object will be created when the user first time fetch it. The Map is
* constructed in ANY-ALL cases. The querier could read the {@link GenericDocument} objects
* under the {@code schemaType} if they holds ALL required permissions of ANY combinations. The
* value set represents {@link
* android.app.appsearch.SetSchemaRequest.AppSearchSupportedPermission}.
*/
@Nullable private Map<String, Set<Set<Integer>>> mSchemasVisibleToPermissions;
private final Bundle mBundle;
GetSchemaResponse(@NonNull Bundle bundle) {
mBundle = Objects.requireNonNull(bundle);
}
/**
* Returns the {@link Bundle} populated by this builder.
*
* @hide
*/
@NonNull
public Bundle getBundle() {
return mBundle;
}
/**
* Returns the overall database schema version.
*
* <p>If the database is empty, 0 will be returned.
*/
@IntRange(from = 0)
public int getVersion() {
return mBundle.getInt(VERSION_FIELD);
}
/**
* Return the schemas most recently successfully provided to {@link AppSearchSession#setSchema}.
*
* <p>It is inefficient to call this method repeatedly.
*/
@NonNull
@SuppressWarnings("deprecation")
public Set<AppSearchSchema> getSchemas() {
ArrayList<Bundle> schemaBundles =
Objects.requireNonNull(mBundle.getParcelableArrayList(SCHEMAS_FIELD));
Set<AppSearchSchema> schemas = new ArraySet<>(schemaBundles.size());
for (int i = 0; i < schemaBundles.size(); i++) {
schemas.add(new AppSearchSchema(schemaBundles.get(i)));
}
return schemas;
}
/**
* Returns all the schema types that are opted out of being displayed and visible on any system
* UI surface.
*/
@NonNull
public Set<String> getSchemaTypesNotDisplayedBySystem() {
checkGetVisibilitySettingSupported();
if (mSchemasNotDisplayedBySystem == null) {
List<String> schemasNotDisplayedBySystemList =
mBundle.getStringArrayList(SCHEMAS_NOT_DISPLAYED_BY_SYSTEM_FIELD);
mSchemasNotDisplayedBySystem =
Collections.unmodifiableSet(new ArraySet<>(schemasNotDisplayedBySystemList));
}
return mSchemasNotDisplayedBySystem;
}
/**
* Returns a mapping of schema types to the set of packages that have access to that schema
* type.
*/
@NonNull
@SuppressWarnings("deprecation")
public Map<String, Set<PackageIdentifier>> getSchemaTypesVisibleToPackages() {
checkGetVisibilitySettingSupported();
if (mSchemasVisibleToPackages == null) {
Bundle schemaVisibleToPackagesBundle =
Objects.requireNonNull(mBundle.getBundle(SCHEMAS_VISIBLE_TO_PACKAGES_FIELD));
Map<String, Set<PackageIdentifier>> copy = new ArrayMap<>();
for (String key : schemaVisibleToPackagesBundle.keySet()) {
List<Bundle> PackageIdentifierBundles =
Objects.requireNonNull(
schemaVisibleToPackagesBundle.getParcelableArrayList(key));
Set<PackageIdentifier> packageIdentifiers =
new ArraySet<>(PackageIdentifierBundles.size());
for (int i = 0; i < PackageIdentifierBundles.size(); i++) {
packageIdentifiers.add(new PackageIdentifier(PackageIdentifierBundles.get(i)));
}
copy.put(key, packageIdentifiers);
}
mSchemasVisibleToPackages = Collections.unmodifiableMap(copy);
}
return mSchemasVisibleToPackages;
}
/**
* 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>
*
* @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}.
*/
@NonNull
@SuppressWarnings("deprecation")
public Map<String, Set<Set<Integer>>> getRequiredPermissionsForSchemaTypeVisibility() {
checkGetVisibilitySettingSupported();
if (mSchemasVisibleToPermissions == null) {
Map<String, Set<Set<Integer>>> copy = new ArrayMap<>();
Bundle schemaVisibleToPermissionBundle =
Objects.requireNonNull(mBundle.getBundle(SCHEMAS_VISIBLE_TO_PERMISSION_FIELD));
for (String key : schemaVisibleToPermissionBundle.keySet()) {
ArrayList<Bundle> allRequiredPermissionsBundle =
schemaVisibleToPermissionBundle.getParcelableArrayList(key);
Set<Set<Integer>> visibleToPermissions = new ArraySet<>();
if (allRequiredPermissionsBundle != null) {
// This should never be null
for (int i = 0; i < allRequiredPermissionsBundle.size(); i++) {
visibleToPermissions.add(
new ArraySet<>(
allRequiredPermissionsBundle
.get(i)
.getIntegerArrayList(
ALL_REQUIRED_PERMISSION_FIELD)));
}
}
copy.put(key, visibleToPermissions);
}
mSchemasVisibleToPermissions = Collections.unmodifiableMap(copy);
}
return mSchemasVisibleToPermissions;
}
private void checkGetVisibilitySettingSupported() {
if (!mBundle.containsKey(SCHEMAS_VISIBLE_TO_PACKAGES_FIELD)) {
throw new UnsupportedOperationException(
"Get visibility setting is not supported with"
+ " this backend/Android API level combination.");
}
}
/** Builder for {@link GetSchemaResponse} objects. */
public static final class Builder {
private int mVersion = 0;
private ArrayList<Bundle> mSchemaBundles = new ArrayList<>();
/**
* Creates the object when we actually set them. If we never set visibility settings, we
* should throw {@link UnsupportedOperationException} in the visibility getters.
*/
@Nullable private ArrayList<String> mSchemasNotDisplayedBySystem;
private Bundle mSchemasVisibleToPackages;
private Bundle mSchemasVisibleToPermissions;
private boolean mBuilt = false;
/** Create a {@link Builder} object} */
public Builder() {
this(/*getVisibilitySettingSupported=*/ true);
}
/**
* Create a {@link Builder} object}.
*
* <p>This constructor should only be used in Android API below than T.
*
* @param getVisibilitySettingSupported whether supported {@link
* Features#ADD_PERMISSIONS_AND_GET_VISIBILITY} by this backend/Android API level.
* @hide
*/
public Builder(boolean getVisibilitySettingSupported) {
if (getVisibilitySettingSupported) {
mSchemasNotDisplayedBySystem = new ArrayList<>();
mSchemasVisibleToPackages = new Bundle();
mSchemasVisibleToPermissions = new Bundle();
}
}
/**
* Sets the database overall schema version.
*
* <p>Default version is 0
*/
@CanIgnoreReturnValue
@NonNull
public Builder setVersion(@IntRange(from = 0) int version) {
resetIfBuilt();
mVersion = version;
return this;
}
/** Adds one {@link AppSearchSchema} to the schema list. */
@CanIgnoreReturnValue
@NonNull
public Builder addSchema(@NonNull AppSearchSchema schema) {
Objects.requireNonNull(schema);
resetIfBuilt();
mSchemaBundles.add(schema.getBundle());
return this;
}
/**
* Sets whether or not documents from the provided {@code schemaType} will be displayed and
* visible on any system UI surface.
*
* @param schemaType The name of an {@link AppSearchSchema} within the same {@link
* GetSchemaResponse}, which won't be displayed by system.
*/
// Getter getSchemaTypesNotDisplayedBySystem returns plural objects.
@CanIgnoreReturnValue
@SuppressLint("MissingGetterMatchingBuilder")
@NonNull
public Builder addSchemaTypeNotDisplayedBySystem(@NonNull String schemaType) {
Objects.requireNonNull(schemaType);
resetIfBuilt();
if (mSchemasNotDisplayedBySystem == null) {
mSchemasNotDisplayedBySystem = new ArrayList<>();
}
mSchemasNotDisplayedBySystem.add(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.
*
* @param schemaType The schema type to set visibility on.
* @param packageIdentifiers Represents the package that has access to the given schema
* type.
*/
// Getter getSchemaTypesVisibleToPackages returns a map contains all schema types.
@CanIgnoreReturnValue
@SuppressLint("MissingGetterMatchingBuilder")
@NonNull
public Builder setSchemaTypeVisibleToPackages(
@NonNull String schemaType, @NonNull Set<PackageIdentifier> packageIdentifiers) {
Objects.requireNonNull(schemaType);
Objects.requireNonNull(packageIdentifiers);
resetIfBuilt();
ArrayList<Bundle> bundles = new ArrayList<>(packageIdentifiers.size());
for (PackageIdentifier packageIdentifier : packageIdentifiers) {
bundles.add(packageIdentifier.getBundle());
}
mSchemasVisibleToPackages.putParcelableArrayList(schemaType, bundles);
return this;
}
/**
* Sets a set of required {@link android.Manifest.permission} combinations to the given
* 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>
*
* @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 visibleToPermissions The Android permissions that will be required to access the
* given schema.
*/
// Getter getRequiredPermissionsForSchemaTypeVisibility returns a map for all schemaTypes.
@CanIgnoreReturnValue
@SuppressLint("MissingGetterMatchingBuilder")
@NonNull
public Builder setRequiredPermissionsForSchemaTypeVisibility(
@NonNull String schemaType,
@SetSchemaRequest.AppSearchSupportedPermission @NonNull
Set<Set<Integer>> visibleToPermissions) {
Objects.requireNonNull(schemaType);
Objects.requireNonNull(visibleToPermissions);
resetIfBuilt();
ArrayList<Bundle> visibleToPermissionsBundle = new ArrayList<>();
for (Set<Integer> allRequiredPermissions : visibleToPermissions) {
for (int permission : allRequiredPermissions) {
Preconditions.checkArgumentInRange(
permission,
SetSchemaRequest.READ_SMS,
SetSchemaRequest.READ_ASSISTANT_APP_SEARCH_DATA,
"permission");
}
Bundle allRequiredPermissionsBundle = new Bundle();
allRequiredPermissionsBundle.putIntegerArrayList(
ALL_REQUIRED_PERMISSION_FIELD, new ArrayList<>(allRequiredPermissions));
visibleToPermissionsBundle.add(allRequiredPermissionsBundle);
}
mSchemasVisibleToPermissions.putParcelableArrayList(
schemaType, visibleToPermissionsBundle);
return this;
}
/** Builds a {@link GetSchemaResponse} object. */
@NonNull
public GetSchemaResponse build() {
Bundle bundle = new Bundle();
bundle.putInt(VERSION_FIELD, mVersion);
bundle.putParcelableArrayList(SCHEMAS_FIELD, mSchemaBundles);
if (mSchemasNotDisplayedBySystem != null) {
// Only save the visibility fields if it was actually set.
bundle.putStringArrayList(
SCHEMAS_NOT_DISPLAYED_BY_SYSTEM_FIELD, mSchemasNotDisplayedBySystem);
bundle.putBundle(SCHEMAS_VISIBLE_TO_PACKAGES_FIELD, mSchemasVisibleToPackages);
bundle.putBundle(SCHEMAS_VISIBLE_TO_PERMISSION_FIELD, mSchemasVisibleToPermissions);
}
mBuilt = true;
return new GetSchemaResponse(bundle);
}
private void resetIfBuilt() {
if (mBuilt) {
mSchemaBundles = new ArrayList<>(mSchemaBundles);
if (mSchemasNotDisplayedBySystem != null) {
// Only reset the visibility fields if it was actually set.
mSchemasNotDisplayedBySystem = new ArrayList<>(mSchemasNotDisplayedBySystem);
Bundle copyVisibleToPackages = new Bundle();
copyVisibleToPackages.putAll(mSchemasVisibleToPackages);
mSchemasVisibleToPackages = copyVisibleToPackages;
Bundle copyVisibleToPermissions = new Bundle();
copyVisibleToPermissions.putAll(mSchemasVisibleToPermissions);
mSchemasVisibleToPermissions = copyVisibleToPermissions;
}
mBuilt = false;
}
}
}
}