290 lines
12 KiB
Java
290 lines
12 KiB
Java
![]() |
/*
|
|||
|
* Copyright (C) 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.CallbackExecutor;
|
|||
|
import android.annotation.FlaggedApi;
|
|||
|
import android.annotation.NonNull;
|
|||
|
import android.annotation.SystemService;
|
|||
|
import android.annotation.UserHandleAware;
|
|||
|
import android.app.appsearch.aidl.AppSearchAttributionSource;
|
|||
|
import android.app.appsearch.aidl.IAppSearchManager;
|
|||
|
import android.app.appsearch.flags.Flags;
|
|||
|
import android.app.appsearch.functions.AppFunctionManager;
|
|||
|
import android.content.Context;
|
|||
|
import android.os.Process;
|
|||
|
|
|||
|
import com.android.internal.util.Preconditions;
|
|||
|
|
|||
|
import java.util.Objects;
|
|||
|
import java.util.concurrent.Executor;
|
|||
|
import java.util.function.Consumer;
|
|||
|
|
|||
|
/**
|
|||
|
* Provides access to the centralized AppSearch index maintained by the system.
|
|||
|
*
|
|||
|
* <p>AppSearch is an offline, on-device search library for managing structured data featuring:
|
|||
|
*
|
|||
|
* <ul>
|
|||
|
* <li>APIs to index and retrieve data via full-text search.
|
|||
|
* <li>An API for applications to explicitly grant read-access permission of their data to other
|
|||
|
* applications. <b>See: {@link
|
|||
|
* SetSchemaRequest.Builder#setSchemaTypeVisibilityForPackage}</b>
|
|||
|
* <li>An API for applications to opt into or out of having their data displayed on System UI
|
|||
|
* surfaces by the System-designated global querier. <b>See: {@link
|
|||
|
* SetSchemaRequest.Builder#setSchemaTypeDisplayedBySystem}</b>
|
|||
|
* </ul>
|
|||
|
*
|
|||
|
* <p>Applications create a database by opening an {@link AppSearchSession}.
|
|||
|
*
|
|||
|
* <p>Example:
|
|||
|
*
|
|||
|
* <pre>
|
|||
|
* AppSearchManager appSearchManager = context.getSystemService(AppSearchManager.class);
|
|||
|
*
|
|||
|
* AppSearchManager.SearchContext searchContext = new AppSearchManager.SearchContext.Builder().
|
|||
|
* setDatabaseName(dbName).build());
|
|||
|
* appSearchManager.createSearchSession(searchContext, mExecutor, appSearchSessionResult -> {
|
|||
|
* mAppSearchSession = appSearchSessionResult.getResultValue();
|
|||
|
* });</pre>
|
|||
|
*
|
|||
|
* <p>After opening the session, a schema must be set in order to define the organizational
|
|||
|
* structure of data. The schema is set by calling {@link AppSearchSession#setSchema}. The schema is
|
|||
|
* composed of a collection of {@link AppSearchSchema} objects, each of which defines a unique type
|
|||
|
* of data.
|
|||
|
*
|
|||
|
* <p>Example:
|
|||
|
*
|
|||
|
* <pre>
|
|||
|
* AppSearchSchema emailSchemaType = new AppSearchSchema.Builder("Email")
|
|||
|
* .addProperty(new StringPropertyConfig.Builder("subject")
|
|||
|
* .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
|
|||
|
* .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
|
|||
|
* .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
|
|||
|
* .build()
|
|||
|
* ).build();
|
|||
|
*
|
|||
|
* SetSchemaRequest request = new SetSchemaRequest.Builder().addSchema(emailSchemaType).build();
|
|||
|
* mAppSearchSession.set(request, mExecutor, appSearchResult -> {
|
|||
|
* if (appSearchResult.isSuccess()) {
|
|||
|
* //Schema has been successfully set.
|
|||
|
* }
|
|||
|
* });</pre>
|
|||
|
*
|
|||
|
* <p>The basic unit of data in AppSearch is represented as a {@link GenericDocument} object,
|
|||
|
* containing an ID, namespace, time-to-live, score, and properties. A namespace organizes a logical
|
|||
|
* group of documents. For example, a namespace can be created to group documents on a per-account
|
|||
|
* basis. An ID identifies a single document within a namespace. The combination of namespace and ID
|
|||
|
* uniquely identifies a {@link GenericDocument} in the database.
|
|||
|
*
|
|||
|
* <p>Once the schema has been set, {@link GenericDocument} objects can be put into the database and
|
|||
|
* indexed by calling {@link AppSearchSession#put}.
|
|||
|
*
|
|||
|
* <p>Example:
|
|||
|
*
|
|||
|
* <pre>
|
|||
|
* // Although for this example we use GenericDocument directly, we recommend extending
|
|||
|
* // GenericDocument to create specific types (i.e. Email) with specific setters/getters.
|
|||
|
* GenericDocument email = new GenericDocument.Builder<>(NAMESPACE, ID, EMAIL_SCHEMA_TYPE)
|
|||
|
* .setPropertyString(“subject”, EMAIL_SUBJECT)
|
|||
|
* .setScore(EMAIL_SCORE)
|
|||
|
* .build();
|
|||
|
*
|
|||
|
* PutDocumentsRequest request = new PutDocumentsRequest.Builder().addGenericDocuments(email)
|
|||
|
* .build();
|
|||
|
* mAppSearchSession.put(request, mExecutor, appSearchBatchResult -> {
|
|||
|
* if (appSearchBatchResult.isSuccess()) {
|
|||
|
* //All documents have been successfully indexed.
|
|||
|
* }
|
|||
|
* });</pre>
|
|||
|
*
|
|||
|
* <p>Searching within the database is done by calling {@link AppSearchSession#search} and providing
|
|||
|
* the query string to search for, as well as a {@link SearchSpec}.
|
|||
|
*
|
|||
|
* <p>Alternatively, {@link AppSearchSession#getByDocumentId} can be called to retrieve documents by
|
|||
|
* namespace and ID.
|
|||
|
*
|
|||
|
* <p>Document removal is done either by time-to-live expiration, or explicitly calling a remove
|
|||
|
* operation. Remove operations can be done by namespace and ID via {@link
|
|||
|
* AppSearchSession#remove(RemoveByDocumentIdRequest, Executor, BatchResultCallback)}, or by query
|
|||
|
* via {@link AppSearchSession#remove(String, SearchSpec, Executor, Consumer)}.
|
|||
|
*/
|
|||
|
@SystemService(Context.APP_SEARCH_SERVICE)
|
|||
|
public class AppSearchManager {
|
|||
|
|
|||
|
private final IAppSearchManager mService;
|
|||
|
private final Context mContext;
|
|||
|
private final AppFunctionManager mAppFunctionManager;
|
|||
|
|
|||
|
/** @hide */
|
|||
|
public AppSearchManager(@NonNull Context context, @NonNull IAppSearchManager service) {
|
|||
|
mContext = Objects.requireNonNull(context);
|
|||
|
mService = Objects.requireNonNull(service);
|
|||
|
mAppFunctionManager = new AppFunctionManager(context, service);
|
|||
|
}
|
|||
|
|
|||
|
/** Contains information about how to create the search session. */
|
|||
|
public static final class SearchContext {
|
|||
|
final String mDatabaseName;
|
|||
|
|
|||
|
SearchContext(@NonNull String databaseName) {
|
|||
|
mDatabaseName = Objects.requireNonNull(databaseName);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Returns the name of the database to create or open.
|
|||
|
*
|
|||
|
* <p>Databases with different names are fully separate with distinct types, namespaces, and
|
|||
|
* data.
|
|||
|
*/
|
|||
|
@NonNull
|
|||
|
public String getDatabaseName() {
|
|||
|
return mDatabaseName;
|
|||
|
}
|
|||
|
|
|||
|
/** Builder for {@link SearchContext} objects. */
|
|||
|
public static final class Builder {
|
|||
|
private final String mDatabaseName;
|
|||
|
private boolean mBuilt = false;
|
|||
|
|
|||
|
/**
|
|||
|
* Creates a new {@link SearchContext.Builder}.
|
|||
|
*
|
|||
|
* <p>{@link AppSearchSession} will create or open a database under the given name.
|
|||
|
*
|
|||
|
* <p>Databases with different names are fully separate with distinct types, namespaces,
|
|||
|
* and data.
|
|||
|
*
|
|||
|
* <p>Database name cannot contain {@code '/'}.
|
|||
|
*
|
|||
|
* @param databaseName The name of the database.
|
|||
|
* @throws IllegalArgumentException if the databaseName contains {@code '/'}.
|
|||
|
*/
|
|||
|
public Builder(@NonNull String databaseName) {
|
|||
|
Objects.requireNonNull(databaseName);
|
|||
|
Preconditions.checkArgument(
|
|||
|
!databaseName.contains("/"), "Database name cannot contain '/'");
|
|||
|
mDatabaseName = databaseName;
|
|||
|
}
|
|||
|
|
|||
|
/** Builds a {@link SearchContext} instance. */
|
|||
|
@NonNull
|
|||
|
public SearchContext build() {
|
|||
|
Preconditions.checkState(!mBuilt, "Builder has already been used");
|
|||
|
mBuilt = true;
|
|||
|
return new SearchContext(mDatabaseName);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Creates a new {@link AppSearchSession}.
|
|||
|
*
|
|||
|
* <p>This process requires an AppSearch native indexing file system. If it's not created, the
|
|||
|
* initialization process will create one under the user's credential encrypted directory.
|
|||
|
*
|
|||
|
* @param searchContext The {@link SearchContext} contains all information to create a new
|
|||
|
* {@link AppSearchSession}
|
|||
|
* @param executor Executor on which to invoke the callback.
|
|||
|
* @param callback The {@link AppSearchResult}<{@link AppSearchSession}> of performing
|
|||
|
* this operation. Or a {@link AppSearchResult} with failure reason code and error
|
|||
|
* information.
|
|||
|
*/
|
|||
|
@UserHandleAware
|
|||
|
public void createSearchSession(
|
|||
|
@NonNull SearchContext searchContext,
|
|||
|
@NonNull @CallbackExecutor Executor executor,
|
|||
|
@NonNull Consumer<AppSearchResult<AppSearchSession>> callback) {
|
|||
|
Objects.requireNonNull(searchContext);
|
|||
|
Objects.requireNonNull(executor);
|
|||
|
Objects.requireNonNull(callback);
|
|||
|
AppSearchSession.createSearchSession(
|
|||
|
searchContext,
|
|||
|
mService,
|
|||
|
mContext.getUser(),
|
|||
|
AppSearchAttributionSource.createAttributionSource(
|
|||
|
mContext, /* callingPid= */ Process.myPid()),
|
|||
|
AppSearchEnvironmentFactory.getEnvironmentInstance().getCacheDir(mContext),
|
|||
|
executor,
|
|||
|
callback);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Creates a new {@link GlobalSearchSession}.
|
|||
|
*
|
|||
|
* <p>This process requires an AppSearch native indexing file system. If it's not created, the
|
|||
|
* initialization process will create one under the user's credential encrypted directory.
|
|||
|
*
|
|||
|
* @param executor Executor on which to invoke the callback.
|
|||
|
* @param callback The {@link AppSearchResult}<{@link GlobalSearchSession}> of performing
|
|||
|
* this operation. Or a {@link AppSearchResult} with failure reason code and error
|
|||
|
* information.
|
|||
|
*/
|
|||
|
@UserHandleAware
|
|||
|
public void createGlobalSearchSession(
|
|||
|
@NonNull @CallbackExecutor Executor executor,
|
|||
|
@NonNull Consumer<AppSearchResult<GlobalSearchSession>> callback) {
|
|||
|
Objects.requireNonNull(executor);
|
|||
|
Objects.requireNonNull(callback);
|
|||
|
GlobalSearchSession.createGlobalSearchSession(
|
|||
|
mService,
|
|||
|
mContext.getUser(),
|
|||
|
AppSearchAttributionSource.createAttributionSource(
|
|||
|
mContext, /* callingPid= */ Process.myPid()),
|
|||
|
executor,
|
|||
|
callback);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Creates a new {@link EnterpriseGlobalSearchSession}
|
|||
|
*
|
|||
|
* <p>EnterpriseGlobalSearchSession queries data from the user’s work profile, allowing apps
|
|||
|
* running on the personal profile to access a limited subset of work profile data. Enterprise
|
|||
|
* access must be explicitly enabled on schemas, and schemas may also specify additional
|
|||
|
* permissions required for enterprise access.
|
|||
|
*
|
|||
|
* <p>This process requires an AppSearch native indexing file system. If it's not created, the
|
|||
|
* initialization process will create one under the user's credential encrypted directory.
|
|||
|
*
|
|||
|
* @param executor Executor on which to invoke the callback.
|
|||
|
* @param callback The {@link AppSearchResult}<{@link EnterpriseGlobalSearchSession}> of
|
|||
|
* performing this operation. Or a {@link AppSearchResult} with failure reason code and
|
|||
|
* error information.
|
|||
|
*/
|
|||
|
@FlaggedApi(Flags.FLAG_ENABLE_ENTERPRISE_GLOBAL_SEARCH_SESSION)
|
|||
|
@UserHandleAware
|
|||
|
public void createEnterpriseGlobalSearchSession(
|
|||
|
@NonNull @CallbackExecutor Executor executor,
|
|||
|
@NonNull Consumer<AppSearchResult<EnterpriseGlobalSearchSession>> callback) {
|
|||
|
Objects.requireNonNull(executor);
|
|||
|
Objects.requireNonNull(callback);
|
|||
|
EnterpriseGlobalSearchSession.createEnterpriseGlobalSearchSession(
|
|||
|
mService,
|
|||
|
mContext.getUser(),
|
|||
|
AppSearchAttributionSource.createAttributionSource(
|
|||
|
mContext, /* callingPid= */ Process.myPid()),
|
|||
|
executor,
|
|||
|
callback);
|
|||
|
}
|
|||
|
|
|||
|
/** Returns an instance of {@link android.app.appsearch.functions.AppFunctionManager}. */
|
|||
|
@FlaggedApi(Flags.FLAG_ENABLE_APP_FUNCTIONS)
|
|||
|
@NonNull
|
|||
|
public AppFunctionManager getAppFunctionManager() {
|
|||
|
return mAppFunctionManager;
|
|||
|
}
|
|||
|
}
|