/* * 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.functions.AppFunctionManager; import android.content.Context; import android.os.Process; import com.android.appsearch.flags.Flags; 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. * *
AppSearch is an offline, on-device search library for managing structured data featuring: * *
Applications create a database by opening an {@link AppSearchSession}. * *
Example: * *
* AppSearchManager appSearchManager = context.getSystemService(AppSearchManager.class); * * AppSearchManager.SearchContext searchContext = new AppSearchManager.SearchContext.Builder(). * setDatabaseName(dbName).build()); * appSearchManager.createSearchSession(searchContext, mExecutor, appSearchSessionResult -> { * mAppSearchSession = appSearchSessionResult.getResultValue(); * });* *
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. * *
Example: * *
* 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. * } * });* *
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. * *
Once the schema has been set, {@link GenericDocument} objects can be put into the database and * indexed by calling {@link AppSearchSession#put}. * *
Example: * *
* // 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. * } * });* *
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}. * *
Alternatively, {@link AppSearchSession#getByDocumentId} can be called to retrieve documents by * namespace and ID. * *
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. * *
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}. * *
{@link AppSearchSession} will create or open a database under the given name. * *
Databases with different names are fully separate with distinct types, namespaces, * and data. * *
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}. * *
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 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 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.
*
* 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