1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package android.app.appsearch; 17 18 import android.annotation.CallbackExecutor; 19 import android.annotation.FlaggedApi; 20 import android.annotation.NonNull; 21 import android.annotation.SystemService; 22 import android.annotation.UserHandleAware; 23 import android.app.appsearch.aidl.AppSearchAttributionSource; 24 import android.app.appsearch.aidl.IAppSearchManager; 25 import android.app.appsearch.functions.AppFunctionManager; 26 import android.content.Context; 27 import android.os.Process; 28 29 import com.android.appsearch.flags.Flags; 30 import com.android.internal.util.Preconditions; 31 32 import java.util.Objects; 33 import java.util.concurrent.Executor; 34 import java.util.function.Consumer; 35 36 /** 37 * Provides access to the centralized AppSearch index maintained by the system. 38 * 39 * <p>AppSearch is an offline, on-device search library for managing structured data featuring: 40 * 41 * <ul> 42 * <li>APIs to index and retrieve data via full-text search. 43 * <li>An API for applications to explicitly grant read-access permission of their data to other 44 * applications. <b>See: {@link 45 * SetSchemaRequest.Builder#setSchemaTypeVisibilityForPackage}</b> 46 * <li>An API for applications to opt into or out of having their data displayed on System UI 47 * surfaces by the System-designated global querier. <b>See: {@link 48 * SetSchemaRequest.Builder#setSchemaTypeDisplayedBySystem}</b> 49 * </ul> 50 * 51 * <p>Applications create a database by opening an {@link AppSearchSession}. 52 * 53 * <p>Example: 54 * 55 * <pre> 56 * AppSearchManager appSearchManager = context.getSystemService(AppSearchManager.class); 57 * 58 * AppSearchManager.SearchContext searchContext = new AppSearchManager.SearchContext.Builder(). 59 * setDatabaseName(dbName).build()); 60 * appSearchManager.createSearchSession(searchContext, mExecutor, appSearchSessionResult -> { 61 * mAppSearchSession = appSearchSessionResult.getResultValue(); 62 * });</pre> 63 * 64 * <p>After opening the session, a schema must be set in order to define the organizational 65 * structure of data. The schema is set by calling {@link AppSearchSession#setSchema}. The schema is 66 * composed of a collection of {@link AppSearchSchema} objects, each of which defines a unique type 67 * of data. 68 * 69 * <p>Example: 70 * 71 * <pre> 72 * AppSearchSchema emailSchemaType = new AppSearchSchema.Builder("Email") 73 * .addProperty(new StringPropertyConfig.Builder("subject") 74 * .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL) 75 * .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES) 76 * .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN) 77 * .build() 78 * ).build(); 79 * 80 * SetSchemaRequest request = new SetSchemaRequest.Builder().addSchema(emailSchemaType).build(); 81 * mAppSearchSession.set(request, mExecutor, appSearchResult -> { 82 * if (appSearchResult.isSuccess()) { 83 * // Schema has been successfully set. 84 * } 85 * });</pre> 86 * 87 * <p>The basic unit of data in AppSearch is represented as a {@link GenericDocument} object, 88 * containing an ID, namespace, time-to-live, score, and properties. A namespace organizes a logical 89 * group of documents. For example, a namespace can be created to group documents on a per-account 90 * basis. An ID identifies a single document within a namespace. The combination of namespace and ID 91 * uniquely identifies a {@link GenericDocument} in the database. 92 * 93 * <p>Once the schema has been set, {@link GenericDocument} objects can be put into the database and 94 * indexed by calling {@link AppSearchSession#put}. 95 * 96 * <p>Example: 97 * 98 * <pre> 99 * // Although for this example we use GenericDocument directly, we recommend extending 100 * // GenericDocument to create specific types (i.e. Email) with specific setters/getters. 101 * GenericDocument email = new GenericDocument.Builder<>(NAMESPACE, ID, EMAIL_SCHEMA_TYPE) 102 * .setPropertyString(“subject”, EMAIL_SUBJECT) 103 * .setScore(EMAIL_SCORE) 104 * .build(); 105 * 106 * PutDocumentsRequest request = new PutDocumentsRequest.Builder().addGenericDocuments(email) 107 * .build(); 108 * mAppSearchSession.put(request, mExecutor, appSearchBatchResult -> { 109 * if (appSearchBatchResult.isSuccess()) { 110 * // All documents have been successfully indexed. 111 * } 112 * });</pre> 113 * 114 * <p>Searching within the database is done by calling {@link AppSearchSession#search} and providing 115 * the query string to search for, as well as a {@link SearchSpec}. 116 * 117 * <p>Alternatively, {@link AppSearchSession#getByDocumentId} can be called to retrieve documents by 118 * namespace and ID. 119 * 120 * <p>Document removal is done either by time-to-live expiration, or explicitly calling a remove 121 * operation. Remove operations can be done by namespace and ID via {@link 122 * AppSearchSession#remove(RemoveByDocumentIdRequest, Executor, BatchResultCallback)}, or by query 123 * via {@link AppSearchSession#remove(String, SearchSpec, Executor, Consumer)}. 124 */ 125 @SystemService(Context.APP_SEARCH_SERVICE) 126 public class AppSearchManager { 127 128 private final IAppSearchManager mService; 129 private final Context mContext; 130 private final AppFunctionManager mAppFunctionManager; 131 132 /** @hide */ AppSearchManager(@onNull Context context, @NonNull IAppSearchManager service)133 public AppSearchManager(@NonNull Context context, @NonNull IAppSearchManager service) { 134 mContext = Objects.requireNonNull(context); 135 mService = Objects.requireNonNull(service); 136 mAppFunctionManager = new AppFunctionManager(context, service); 137 } 138 139 /** Contains information about how to create the search session. */ 140 public static final class SearchContext { 141 final String mDatabaseName; 142 SearchContext(@onNull String databaseName)143 SearchContext(@NonNull String databaseName) { 144 mDatabaseName = Objects.requireNonNull(databaseName); 145 } 146 147 /** 148 * Returns the name of the database to create or open. 149 * 150 * <p>Databases with different names are fully separate with distinct types, namespaces, and 151 * data. 152 */ 153 @NonNull getDatabaseName()154 public String getDatabaseName() { 155 return mDatabaseName; 156 } 157 158 /** Builder for {@link SearchContext} objects. */ 159 public static final class Builder { 160 private final String mDatabaseName; 161 private boolean mBuilt = false; 162 163 /** 164 * Creates a new {@link SearchContext.Builder}. 165 * 166 * <p>{@link AppSearchSession} will create or open a database under the given name. 167 * 168 * <p>Databases with different names are fully separate with distinct types, namespaces, 169 * and data. 170 * 171 * <p>Database name cannot contain {@code '/'}. 172 * 173 * @param databaseName The name of the database. 174 * @throws IllegalArgumentException if the databaseName contains {@code '/'}. 175 */ Builder(@onNull String databaseName)176 public Builder(@NonNull String databaseName) { 177 Objects.requireNonNull(databaseName); 178 Preconditions.checkArgument( 179 !databaseName.contains("/"), "Database name cannot contain '/'"); 180 mDatabaseName = databaseName; 181 } 182 183 /** Builds a {@link SearchContext} instance. */ 184 @NonNull build()185 public SearchContext build() { 186 Preconditions.checkState(!mBuilt, "Builder has already been used"); 187 mBuilt = true; 188 return new SearchContext(mDatabaseName); 189 } 190 } 191 } 192 193 /** 194 * Creates a new {@link AppSearchSession}. 195 * 196 * <p>This process requires an AppSearch native indexing file system. If it's not created, the 197 * initialization process will create one under the user's credential encrypted directory. 198 * 199 * @param searchContext The {@link SearchContext} contains all information to create a new 200 * {@link AppSearchSession} 201 * @param executor Executor on which to invoke the callback. 202 * @param callback The {@link AppSearchResult}<{@link AppSearchSession}> of performing 203 * this operation. Or a {@link AppSearchResult} with failure reason code and error 204 * information. 205 */ 206 @UserHandleAware createSearchSession( @onNull SearchContext searchContext, @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<AppSearchResult<AppSearchSession>> callback)207 public void createSearchSession( 208 @NonNull SearchContext searchContext, 209 @NonNull @CallbackExecutor Executor executor, 210 @NonNull Consumer<AppSearchResult<AppSearchSession>> callback) { 211 Objects.requireNonNull(searchContext); 212 Objects.requireNonNull(executor); 213 Objects.requireNonNull(callback); 214 AppSearchSession.createSearchSession( 215 searchContext, 216 mService, 217 mContext.getUser(), 218 AppSearchAttributionSource.createAttributionSource( 219 mContext, /* callingPid= */ Process.myPid()), 220 AppSearchEnvironmentFactory.getEnvironmentInstance().getCacheDir(mContext), 221 executor, 222 callback); 223 } 224 225 /** 226 * Creates a new {@link GlobalSearchSession}. 227 * 228 * <p>This process requires an AppSearch native indexing file system. If it's not created, the 229 * initialization process will create one under the user's credential encrypted directory. 230 * 231 * @param executor Executor on which to invoke the callback. 232 * @param callback The {@link AppSearchResult}<{@link GlobalSearchSession}> of performing 233 * this operation. Or a {@link AppSearchResult} with failure reason code and error 234 * information. 235 */ 236 @UserHandleAware createGlobalSearchSession( @onNull @allbackExecutor Executor executor, @NonNull Consumer<AppSearchResult<GlobalSearchSession>> callback)237 public void createGlobalSearchSession( 238 @NonNull @CallbackExecutor Executor executor, 239 @NonNull Consumer<AppSearchResult<GlobalSearchSession>> callback) { 240 Objects.requireNonNull(executor); 241 Objects.requireNonNull(callback); 242 GlobalSearchSession.createGlobalSearchSession( 243 mService, 244 mContext.getUser(), 245 AppSearchAttributionSource.createAttributionSource( 246 mContext, /* callingPid= */ Process.myPid()), 247 executor, 248 callback); 249 } 250 251 /** 252 * Creates a new {@link EnterpriseGlobalSearchSession} 253 * 254 * <p>EnterpriseGlobalSearchSession queries data from the user’s work profile, allowing apps 255 * running on the personal profile to access a limited subset of work profile data. Enterprise 256 * access must be explicitly enabled on schemas, and schemas may also specify additional 257 * permissions required for enterprise access. 258 * 259 * <p>This process requires an AppSearch native indexing file system. If it's not created, the 260 * initialization process will create one under the user's credential encrypted directory. 261 * 262 * @param executor Executor on which to invoke the callback. 263 * @param callback The {@link AppSearchResult}<{@link EnterpriseGlobalSearchSession}> of 264 * performing this operation. Or a {@link AppSearchResult} with failure reason code and 265 * error information. 266 */ 267 @FlaggedApi(Flags.FLAG_ENABLE_ENTERPRISE_GLOBAL_SEARCH_SESSION) 268 @UserHandleAware createEnterpriseGlobalSearchSession( @onNull @allbackExecutor Executor executor, @NonNull Consumer<AppSearchResult<EnterpriseGlobalSearchSession>> callback)269 public void createEnterpriseGlobalSearchSession( 270 @NonNull @CallbackExecutor Executor executor, 271 @NonNull Consumer<AppSearchResult<EnterpriseGlobalSearchSession>> callback) { 272 Objects.requireNonNull(executor); 273 Objects.requireNonNull(callback); 274 EnterpriseGlobalSearchSession.createEnterpriseGlobalSearchSession( 275 mService, 276 mContext.getUser(), 277 AppSearchAttributionSource.createAttributionSource( 278 mContext, /* callingPid= */ Process.myPid()), 279 executor, 280 callback); 281 } 282 283 /** Returns an instance of {@link android.app.appsearch.functions.AppFunctionManager}. */ 284 @FlaggedApi(Flags.FLAG_ENABLE_APP_FUNCTIONS) 285 @NonNull getAppFunctionManager()286 public AppFunctionManager getAppFunctionManager() { 287 return mAppFunctionManager; 288 } 289 } 290