/* * Copyright (C) 2024 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 com.android.server.appsearch.appsindexer; import android.annotation.NonNull; import android.app.appsearch.AppSearchBatchResult; import android.app.appsearch.AppSearchEnvironmentFactory; import android.app.appsearch.AppSearchManager; import android.app.appsearch.AppSearchResult; import android.app.appsearch.AppSearchSchema; import android.app.appsearch.BatchResultCallback; import android.app.appsearch.PackageIdentifier; import android.app.appsearch.PutDocumentsRequest; import android.app.appsearch.SearchResult; import android.app.appsearch.SearchSpec; import android.app.appsearch.SetSchemaRequest; import android.app.appsearch.exceptions.AppSearchException; import android.content.Context; import android.util.AndroidRuntimeException; import android.util.ArrayMap; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.server.appsearch.appsindexer.appsearchtypes.MobileApplication; import java.io.Closeable; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.ExecutorService; /** * Helper class to manage the App corpus in AppSearch. * *
There are two primary methods in this class, {@link #setSchemasForPackages} and {@link * #indexApps}. On a given Apps Index update, they may not necessarily both be called. For instance, * if the indexer determines that the only change is that an app was deleted, there is no reason to * insert any * apps, so we can save time by only calling setSchemas to erase the deleted app * schema. On the other hand, if the only change is that an app was update, there is no reason to * call setSchema. We can instead just update the updated app with a call to indexApps. Figuring out * what needs to be done is left to {@link AppsIndexerImpl}. * *
This class is thread-safe.
*
* @hide
*/
public class AppSearchHelper implements Closeable {
private static final String TAG = "AppSearchAppsIndexerAppSearchHelper";
// The apps indexer uses one database, and in that database we have one schema for every app
// that is indexed. The reason for this is that we keep the schema types the same for every app
// (MobileApplication), but we need different visibility settings for each app. These different
// visibility settings are set with Public ACL and rely on PackageManager#canPackageQuery.
// Therefore each application needs its own schema. We put all these schema into a single
// database by dynamically renaming the schema so that they have different names.
public static final String APP_DATABASE = "apps-db";
private static final int GET_APP_IDS_PAGE_SIZE = 1000;
private final Context mContext;
private final ExecutorService mExecutor;
private final AppSearchManager mAppSearchManager;
private SyncAppSearchSession mSyncAppSearchSession;
private SyncGlobalSearchSession mSyncGlobalSearchSession;
/** Creates and initializes an {@link AppSearchHelper} */
@NonNull
public static AppSearchHelper createAppSearchHelper(@NonNull Context context)
throws AppSearchException {
Objects.requireNonNull(context);
AppSearchHelper appSearchHelper = new AppSearchHelper(context);
appSearchHelper.initializeAppSearchSessions();
return appSearchHelper;
}
/** Creates an initialized {@link AppSearchHelper}. */
@VisibleForTesting
private AppSearchHelper(@NonNull Context context) {
mContext = Objects.requireNonNull(context);
mAppSearchManager = context.getSystemService(AppSearchManager.class);
if (mAppSearchManager == null) {
throw new AndroidRuntimeException(
"Can't get AppSearchManager to initialize AppSearchHelper.");
}
mExecutor =
AppSearchEnvironmentFactory.getEnvironmentInstance().createSingleThreadExecutor();
}
/**
* Sets up the search session.
*
* @throws AppSearchException if unable to initialize the {@link SyncAppSearchSession} or the
* {@link SyncGlobalSearchSession}.
*/
private void initializeAppSearchSessions() throws AppSearchException {
AppSearchManager.SearchContext searchContext =
new AppSearchManager.SearchContext.Builder(APP_DATABASE).build();
mSyncAppSearchSession =
new SyncAppSearchSessionImpl(mAppSearchManager, searchContext, mExecutor);
mSyncGlobalSearchSession = new SyncGlobalSearchSessionImpl(mAppSearchManager, mExecutor);
}
/** Just for testing, allows us to test various scenarios involving SyncAppSearchSession. */
@VisibleForTesting
/* package */ void setAppSearchSession(@NonNull SyncAppSearchSession session) {
// Close the old one
mSyncAppSearchSession.close();
mSyncAppSearchSession = Objects.requireNonNull(session);
}
/**
* Sets the AppsIndexer database schema to correspond to the list of passed in {@link
* PackageIdentifier}s. Note that this means if a schema exists in AppSearch that does not get
* passed in to this method, it will be erased. And if a schema does not exist in AppSearch that
* is passed in to this method, it will be created.
*/
public void setSchemasForPackages(@NonNull List