/*
* 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.annotation.WorkerThread;
import android.app.appsearch.AppSearchBatchResult;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.GenericDocument;
import android.app.appsearch.PackageIdentifier;
import android.app.appsearch.exceptions.AppSearchException;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.SystemClock;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import com.android.appsearch.flags.Flags;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.appsearch.appsindexer.appsearchtypes.AppFunctionDocument;
import com.android.server.appsearch.appsindexer.appsearchtypes.AppFunctionStaticMetadata;
import com.android.server.appsearch.appsindexer.appsearchtypes.MobileApplication;
import java.io.Closeable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* Interactions with PackageManager and AppSearch.
*
*
This class is NOT thread-safe.
*
* @hide
*/
public final class AppsIndexerImpl implements Closeable {
static final String TAG = "AppSearchAppsIndexerImpl";
private final Context mContext;
private final AppSearchHelper mAppSearchHelper;
private final AppsIndexerConfig mAppsIndexerConfig;
public AppsIndexerImpl(@NonNull Context context, @NonNull AppsIndexerConfig appsIndexerConfig)
throws AppSearchException {
mContext = Objects.requireNonNull(context);
mAppSearchHelper = new AppSearchHelper(context);
mAppsIndexerConfig = Objects.requireNonNull(appsIndexerConfig);
}
/**
* Checks PackageManager and AppSearch to sync the Apps Index in AppSearch.
*
*
It deletes removed apps, inserts newly-added ones, and updates existing ones in the App
* corpus in AppSearch.
*
* @param settings contains update timestamps that help the indexer determine which apps were
* updated.
* @param appsUpdateStats contains stats about the apps indexer update. This method will
* populate the fields of this {@link AppsUpdateStats} structure.
* @param isFullUpdateRequired whether to re-index all apps irrespective of their last update
* timestamp.
*/
@VisibleForTesting
@WorkerThread
public void doUpdateIncrementalPut(
@NonNull AppsIndexerSettings settings,
@NonNull AppsUpdateStats appsUpdateStats,
boolean isFullUpdateRequired)
throws AppSearchException {
// TODO(b/357551503): Add metrics for app function indexing
Objects.requireNonNull(settings);
Objects.requireNonNull(appsUpdateStats);
long currentTimeMillis = System.currentTimeMillis();
// Search AppSearch for MobileApplication objects to get a "current" list of indexed apps.
long beforeGetTimestamp = SystemClock.elapsedRealtime();
Map appUpdatedTimestamps = mAppSearchHelper.getAppsFromAppSearch();
appsUpdateStats.mAppSearchGetLatencyMillis =
SystemClock.elapsedRealtime() - beforeGetTimestamp;
long beforePackageManagerTimestamp = SystemClock.elapsedRealtime();
PackageManager packageManager = mContext.getPackageManager();
Map packagesToIndex =
AppsUtil.getPackagesToIndex(packageManager);
appsUpdateStats.mPackageManagerLatencyMillis =
SystemClock.elapsedRealtime() - beforePackageManagerTimestamp;
List functionDocumentsToAddOrUpdate = new ArrayList<>();
// To remove, we only need the id
Set functionIdsToRemove = new ArraySet<>();
long mostRecentAppUpdatedTimestampMillis = settings.getLastAppUpdateTimestampMillis();
// This boolean will be turned on if an app was added, an app was removed, all app
// functions were removed from an app, or app functions were added to an app that
// didn't have them previously. In all cases, we need to call setSchema to keep
// AppSearch in sync with PackageManager.
boolean addedOrRemovedFlag = false;
Set packagesToIndexIdSet = new ArraySet<>();
// Prepare a list of newly added and updated packages. Added packages will have all
// their app functions added to AppSearch, without checking what's in AppSearch. Updated
// packages will require an additional call to AppSearch to see if we need to
// add/update/remove individual app function documents. We don't do this for added apps as
// we can just assume we need to add all of them. This saves a call to AppSearch. For both
// added and updated packages, we parse xml. We don't check what functions are in AppSearch
// for removed packages, as we can just remove the entire MobileApplication +
// AppFunctionStaticMetadata schemas, which will in turn remove the documents.
Map packagesToBeAddedOrUpdated = new ArrayMap<>();
Set updatedPackageIds = new ArraySet<>();
// First loop, determine the status of apps
for (Map.Entry packageEntry : packagesToIndex.entrySet()) {
PackageInfo packageInfo = packageEntry.getKey();
packagesToIndexIdSet.add(packageInfo.packageName);
// Update the most recent timestamp as we iterate
if (packageInfo.lastUpdateTime > mostRecentAppUpdatedTimestampMillis) {
mostRecentAppUpdatedTimestampMillis = packageInfo.lastUpdateTime;
}
Long storedAppUpdateTime = appUpdatedTimestamps.get(packageInfo.packageName);
if (storedAppUpdateTime == null) {
// New app.
addedOrRemovedFlag = true;
appsUpdateStats.mNumberOfAppsAdded++;
packagesToBeAddedOrUpdated.put(packageInfo, packageEntry.getValue());
} else if (packageInfo.lastUpdateTime != storedAppUpdateTime || isFullUpdateRequired) {
// Package last update timestamp discrepancy between AppSearch and PackageManager
// or app indexer code was updated. Add this to the list of updated
// apps so we can check what functions are indexed in AppSearch
appsUpdateStats.mNumberOfAppsUpdated++;
updatedPackageIds.add(packageInfo.packageName);
packagesToBeAddedOrUpdated.put(packageInfo, packagesToIndex.get(packageInfo));
} else {
// Not updated.
appsUpdateStats.mNumberOfAppsUnchanged++;
}
}
// Now check for removed apps
for (String appPackageId : appUpdatedTimestamps.keySet()) {
if (!packagesToIndexIdSet.contains(appPackageId)) {
// App was removed, remove all it's functions. This is simple because removing the
// schema will remove all the functions. Do not add the app to the list of schemas
// to set.
appsUpdateStats.mNumberOfAppsRemoved++;
addedOrRemovedFlag = true;
}
}
Map> dynamicAppFunctionSchemasForPackages = null;
if (Flags.enableAppFunctionsSchemaParser()) {
// TODO(b/382254638): Skip XML parsing for packages that were not updated by using
// AppSearchSessio#getSchema.
dynamicAppFunctionSchemasForPackages =
AppsUtil.getDynamicAppFunctionSchemasForPackages(
packageManager,
packagesToIndex,
mAppsIndexerConfig.getMaxAllowedAppFunctionSchemasPerPackage());
}
// Parse and build all necessary AppFunctionStaticMetadata from PackageManager.
Map>
currentAppFunctionsForAddedUpdatedPackages =
AppsUtil.buildAppFunctionDocumentsIntoMap(
packageManager,
packagesToBeAddedOrUpdated,
/* indexerPackageName= */ mContext.getPackageName(),
mAppsIndexerConfig,
dynamicAppFunctionSchemasForPackages);
// Get all currently indexed AppFunctionStaticMetadata docs for the necessary packages.
Map> appFunctionsFromAppSearch =
mAppSearchHelper.getAppFunctionDocumentsFromAppSearch(updatedPackageIds);
for (Map.Entry> packageEntry :
currentAppFunctionsForAddedUpdatedPackages.entrySet()) {
String packageName = packageEntry.getKey();
Map currentAppFunctionsPerApp =
packageEntry.getValue();
// This might be null, in the case of functions newly added to a package
Map appSearchAppFunctionsPerApp =
appFunctionsFromAppSearch.get(packageName);
if (appSearchAppFunctionsPerApp == null && !currentAppFunctionsPerApp.isEmpty()) {
// Functions added to an app that didn't have them
functionDocumentsToAddOrUpdate.addAll(currentAppFunctionsPerApp.values());
addedOrRemovedFlag = true;
}
if (appSearchAppFunctionsPerApp != null) {
if (currentAppFunctionsPerApp.isEmpty()) {
// All functions removed from an app that had them
addedOrRemovedFlag = true;
} else {
// App updated that had packages, we should check
comparePackageFunctionDocuments(
currentAppFunctionsPerApp,
appSearchAppFunctionsPerApp,
functionDocumentsToAddOrUpdate,
functionIdsToRemove);
}
}
}
try {
// TODO(b/382254638): Skip set schema calls if no packages have an updated schema.
if (dynamicAppFunctionSchemasForPackages != null) {
// Since dynamic schemas are enabled, we need to account for schema changes in
// both updated packages and newly added packages.
Pair, List>
mobileAppAndAppFunctionIdentifiers =
getPackageIdentifiers(
packagesToIndex,
currentAppFunctionsForAddedUpdatedPackages);
long beforeSetSchemaTimestamp = SystemClock.elapsedRealtime();
mAppSearchHelper.setSchemasForPackages(
/* mobileAppPkgs= */ mobileAppAndAppFunctionIdentifiers.first,
/* appFunctionPkgs= */ mobileAppAndAppFunctionIdentifiers.second,
dynamicAppFunctionSchemasForPackages);
appsUpdateStats.mAppSearchSetSchemaLatencyMillis =
SystemClock.elapsedRealtime() - beforeSetSchemaTimestamp;
} else if (addedOrRemovedFlag) {
// This branch is executed when dynamic schemas are disabled and new packages are
// added or removed to keep the AppSearch schema in sync with
// PackageManager.
Pair, List>
mobileAppAndAppFunctionIdentifiers =
getPackageIdentifiers(
packagesToIndex,
currentAppFunctionsForAddedUpdatedPackages);
// The certificate is necessary along with the package name as it is used in
// visibility settings.
long beforeSetSchemaTimestamp = SystemClock.elapsedRealtime();
mAppSearchHelper.setSchemasForPackages(
/* mobileAppPkgs= */ mobileAppAndAppFunctionIdentifiers.first,
/* appFunctionPkgs= */ mobileAppAndAppFunctionIdentifiers.second);
appsUpdateStats.mAppSearchSetSchemaLatencyMillis =
SystemClock.elapsedRealtime() - beforeSetSchemaTimestamp;
}
if (!packagesToBeAddedOrUpdated.isEmpty()
|| !functionDocumentsToAddOrUpdate.isEmpty()) {
long beforePutTimestamp = SystemClock.elapsedRealtime();
List mobileApplications =
AppsUtil.buildAppsFromPackageInfos(
packageManager, packagesToBeAddedOrUpdated);
AppSearchBatchResult result =
mAppSearchHelper.indexApps(
mobileApplications, functionDocumentsToAddOrUpdate);
if (result.isSuccess()) {
appsUpdateStats.mUpdateStatusCodes.add(AppSearchResult.RESULT_OK);
} else {
Collection> values = result.getAll().values();
for (AppSearchResult putResult : values) {
appsUpdateStats.mUpdateStatusCodes.add(putResult.getResultCode());
}
}
appsUpdateStats.mAppSearchPutLatencyMillis =
SystemClock.elapsedRealtime() - beforePutTimestamp;
}
if (!functionIdsToRemove.isEmpty()) {
AppSearchBatchResult result =
mAppSearchHelper.removeAppFunctionsById(functionIdsToRemove);
if (result.isSuccess()) {
appsUpdateStats.mUpdateStatusCodes.add(AppSearchResult.RESULT_OK);
}
}
settings.setLastAppUpdateTimestampMillis(mostRecentAppUpdatedTimestampMillis);
settings.setLastUpdateTimestampMillis(currentTimeMillis);
appsUpdateStats.mLastAppUpdateTimestampMillis = mostRecentAppUpdatedTimestampMillis;
} catch (AppSearchException e) {
// Reset the last update time stamp and app update timestamp so we can try again later.
settings.reset();
appsUpdateStats.mUpdateStatusCodes.clear();
appsUpdateStats.mUpdateStatusCodes.add(e.getResultCode());
throw e;
}
}
/**
* Return a pair of lists of {@link PackageIdentifier}s, the first list representing all
* packages, and the second list representing packages with app functions.
*
* The second list is always a subset of the first list.
*
* @param packagesToIndex a mapping of {@link PackageInfo}s with their corresponding {@link
* ResolveInfos} for the packages launch activity and maybe app function resolve info.
* @param currentAppFunctionsForAddedUpdatedPackages a mapping of package name to a map of all
* app functions for the packages that were either updated or added.
* @return a pair of lists of {@link PackageIdentifier}s, the first list representing all
* packages, and the second list representing packages with app functions.
*/
private Pair, List> getPackageIdentifiers(
@NonNull Map packagesToIndex,
Map>
currentAppFunctionsForAddedUpdatedPackages) {
List packageIdentifiers = new ArrayList<>();
List packageIdentifiersWithAppFunctions = new ArrayList<>();
for (Map.Entry entry : packagesToIndex.entrySet()) {
// We get certificates here as getting the certificates during the previous for
// loop would be wasteful if we end up not needing to call set schema
PackageInfo packageInfo = entry.getKey();
byte[] certificate = AppsUtil.getCertificate(packageInfo);
if (certificate == null) {
Log.e(TAG, "Certificate not found for package: " + packageInfo.packageName);
continue;
}
PackageIdentifier packageIdentifier =
new PackageIdentifier(packageInfo.packageName, certificate);
packageIdentifiers.add(packageIdentifier);
// Check if the package was updated and all app functions were removed. The map only
// contains entries for packages that updated or newly added, for packages with no
// change we would rely solely on presence of AppFunctionServiceInfo to decide if it's
// an app function package.
boolean appFunctionsRemoved =
currentAppFunctionsForAddedUpdatedPackages.containsKey(packageInfo.packageName)
&& currentAppFunctionsForAddedUpdatedPackages
.get(packageInfo.packageName)
.isEmpty();
if (entry.getValue().getAppFunctionServiceInfo() != null && !appFunctionsRemoved) {
packageIdentifiersWithAppFunctions.add(packageIdentifier);
}
}
return new Pair<>(packageIdentifiers, packageIdentifiersWithAppFunctions);
}
/**
* Compares the app function documents in PackageManager vs those in AppSearch, and updates
* functionDocumentsToAddOrUpdate and functionDocumentIdsToRemove accordingly.
*
* @param currentAppFunctionDocumentsPerApp the mapping of function ids to documents
* corresponding to what is in the apps metadata.
* @param appSearchAppFunctionDocumentsPerApp the mapping of function ids to documents
* corresponding to what is in AppSearch
* @param functionDocumentsToAddOrUpdate the List of {@link AppFunctionDocument} that will be
* sent to a put call to AppSearch
* @param functionDocumentIdsToRemove the set of ids that will be sent to a remove call in
* AppSearch
*/
private void comparePackageFunctionDocuments(
@NonNull Map currentAppFunctionDocumentsPerApp,
@NonNull Map appSearchAppFunctionDocumentsPerApp,
@NonNull List functionDocumentsToAddOrUpdate,
@NonNull Set functionDocumentIdsToRemove) {
Objects.requireNonNull(currentAppFunctionDocumentsPerApp);
Objects.requireNonNull(appSearchAppFunctionDocumentsPerApp);
Objects.requireNonNull(functionDocumentsToAddOrUpdate);
Objects.requireNonNull(functionDocumentIdsToRemove);
for (Map.Entry currentFunctionEntry :
currentAppFunctionDocumentsPerApp.entrySet()) {
String functionId = currentFunctionEntry.getKey();
AppFunctionDocument currentFunctionDocument = currentFunctionEntry.getValue();
AppFunctionDocument appSearchFunctionDocument =
appSearchAppFunctionDocumentsPerApp.get(functionId);
// appSearchFunctionDocument == null means it's a new document, document inequality
// means
// updated document. Both mean we need to call put with this document.
if (appSearchFunctionDocument == null
|| !areFunctionDocumentsEqual(
appSearchFunctionDocument, currentFunctionDocument)) {
functionDocumentsToAddOrUpdate.add(currentFunctionDocument);
}
}
for (Map.Entry appSearchFunctionEntry :
appSearchAppFunctionDocumentsPerApp.entrySet()) {
if (!currentAppFunctionDocumentsPerApp.containsKey(appSearchFunctionEntry.getKey())) {
functionDocumentIdsToRemove.add(appSearchFunctionEntry.getValue().getId());
}
}
}
/**
* Checks if two AppFunction documents are equal. It isn't enough to call equals. We also need
* to ignore creation timestamp and parent types. These are set in AppSearch, but aren't set for
* the "about to be indexed" docs
*
* @return true if the documents are equal, false otherwise.
*/
private boolean areFunctionDocumentsEqual(
@NonNull GenericDocument document1, @NonNull GenericDocument document2) {
Objects.requireNonNull(document1);
Objects.requireNonNull(document2);
document1 = clearTimestampsAndParentTypesInDocument(document1);
document2 = clearTimestampsAndParentTypesInDocument(document2);
return document1.equals(document2);
}
private GenericDocument clearTimestampsAndParentTypesInDocument(
@NonNull GenericDocument document) {
GenericDocument.Builder> builder =
new GenericDocument.Builder<>(document)
.setCreationTimestampMillis(0)
// GenericDocument#PARENT_TYPES_SYNTHETIC_PROPERTY is hidden
.clearProperty("$$__AppSearch__parentTypes");
for (String propertyName : document.getPropertyNames()) {
Object property = document.getProperty(propertyName);
if (property instanceof GenericDocument[] nestedDocuments) {
GenericDocument[] clearedNestedDocuments =
new GenericDocument[nestedDocuments.length];
for (int i = 0; i < nestedDocuments.length; i++) {
clearedNestedDocuments[i] =
clearTimestampsAndParentTypesInDocument(nestedDocuments[i]);
}
builder.setPropertyDocument(propertyName, clearedNestedDocuments);
}
}
return builder.build();
}
/**
* Checks PackageManager and AppSearch to sync the Apps Index in AppSearch.
*
* It deletes removed apps, inserts newly-added ones, and updates existing ones in the App
* corpus in AppSearch.
*
* @param settings contains update timestamps that help the indexer determine which apps were
* updated.
* @param appsUpdateStats contains stats about the apps indexer update. This method will
* populate the fields of this {@link AppsUpdateStats} structure.
*/
@VisibleForTesting
@WorkerThread
public void doUpdate(
@NonNull AppsIndexerSettings settings, @NonNull AppsUpdateStats appsUpdateStats)
throws AppSearchException {
Objects.requireNonNull(settings);
Objects.requireNonNull(appsUpdateStats);
long currentTimeMillis = System.currentTimeMillis();
// Search AppSearch for MobileApplication objects to get a "current" list of indexed apps.
long beforeGetTimestamp = SystemClock.elapsedRealtime();
Map appUpdatedTimestamps = mAppSearchHelper.getAppsFromAppSearch();
appsUpdateStats.mAppSearchGetLatencyMillis =
SystemClock.elapsedRealtime() - beforeGetTimestamp;
long beforePackageManagerTimestamp = SystemClock.elapsedRealtime();
PackageManager packageManager = mContext.getPackageManager();
Map packagesToIndex =
AppsUtil.getPackagesToIndex(packageManager);
appsUpdateStats.mPackageManagerLatencyMillis =
SystemClock.elapsedRealtime() - beforePackageManagerTimestamp;
Set packageInfos = packagesToIndex.keySet();
Map packagesToBeAddedOrUpdated = new ArrayMap<>();
long mostRecentAppUpdatedTimestampMillis = settings.getLastAppUpdateTimestampMillis();
// Prepare a set of current app IDs for efficient lookup
Set currentAppIds = new ArraySet<>();
for (PackageInfo packageInfo : packageInfos) {
currentAppIds.add(packageInfo.packageName);
// Update the most recent timestamp as we iterate
if (packageInfo.lastUpdateTime > mostRecentAppUpdatedTimestampMillis) {
mostRecentAppUpdatedTimestampMillis = packageInfo.lastUpdateTime;
}
Long storedUpdateTime = appUpdatedTimestamps.get(packageInfo.packageName);
boolean added = storedUpdateTime == null;
boolean updated =
storedUpdateTime != null && packageInfo.lastUpdateTime != storedUpdateTime;
if (added) {
appsUpdateStats.mNumberOfAppsAdded++;
}
if (updated) {
appsUpdateStats.mNumberOfAppsUpdated++;
}
if (added || updated) {
packagesToBeAddedOrUpdated.put(packageInfo, packagesToIndex.get(packageInfo));
} else {
appsUpdateStats.mNumberOfAppsUnchanged++;
}
}
List appSearchAppFunctions =
mAppSearchHelper.getAppFunctionsFromAppSearch();
try {
if (!currentAppIds.equals(appUpdatedTimestamps.keySet())
|| requiresInsertSchemaForAppFunction(packagesToIndex, appSearchAppFunctions)) {
// The current list of apps/app functions in AppSearch does not match what is in
// PackageManager. This means this is the first sync, an app/app function was
// removed, or an app/app function was added. In all cases, we need to call
// setSchema to keep AppSearch in sync with PackageManager.
// currentAppIds comes from PackageManager, appUpdatedTimestamps comes from
// AppSearch. Deleted apps are those in appUpdateTimestamps and NOT in currentAppIds
appsUpdateStats.mNumberOfAppsRemoved = 0;
for (String appSearchApp : appUpdatedTimestamps.keySet()) {
if (!currentAppIds.contains(appSearchApp)) {
appsUpdateStats.mNumberOfAppsRemoved++;
}
}
List packageIdentifiers = new ArrayList<>();
List packageIdentifiersWithAppFunctions = new ArrayList<>();
for (Map.Entry entry : packagesToIndex.entrySet()) {
// We get certificates here as getting the certificates during the previous for
// loop would be wasteful if we end up not needing to call set schema
PackageInfo packageInfo = entry.getKey();
byte[] certificate = AppsUtil.getCertificate(packageInfo);
if (certificate == null) {
Log.e(TAG, "Certificate not found for package: " + packageInfo.packageName);
continue;
}
PackageIdentifier packageIdentifier =
new PackageIdentifier(packageInfo.packageName, certificate);
packageIdentifiers.add(packageIdentifier);
if (entry.getValue().getAppFunctionServiceInfo() != null) {
packageIdentifiersWithAppFunctions.add(packageIdentifier);
}
}
// The certificate is necessary along with the package name as it is used in
// visibility settings.
long beforeSetSchemaTimestamp = SystemClock.elapsedRealtime();
mAppSearchHelper.setSchemasForPackages(
packageIdentifiers, packageIdentifiersWithAppFunctions);
appsUpdateStats.mAppSearchSetSchemaLatencyMillis =
SystemClock.elapsedRealtime() - beforeSetSchemaTimestamp;
}
if (!packagesToBeAddedOrUpdated.isEmpty()) {
long beforePutTimestamp = SystemClock.elapsedRealtime();
List mobileApplications =
AppsUtil.buildAppsFromPackageInfos(
packageManager, packagesToBeAddedOrUpdated);
List appFunctions =
AppsUtil.buildAppFunctionStaticMetadata(
packageManager,
packagesToBeAddedOrUpdated,
/* indexerPackageName= */ mContext.getPackageName(),
mAppsIndexerConfig);
AppSearchBatchResult result =
mAppSearchHelper.indexApps(
mobileApplications,
appFunctions,
appSearchAppFunctions,
appsUpdateStats);
// Here we log all of these functions as "updated". However, some of these may be
// added or unchanged.
// TODO(b/357551503): Log function counts more accurately
appsUpdateStats.mNumberOfFunctionsUpdated = appFunctions.size();
if (result.isSuccess()) {
appsUpdateStats.mUpdateStatusCodes.add(AppSearchResult.RESULT_OK);
} else {
Collection> values = result.getAll().values();
for (AppSearchResult putResult : values) {
appsUpdateStats.mUpdateStatusCodes.add(putResult.getResultCode());
}
}
appsUpdateStats.mAppSearchPutLatencyMillis =
SystemClock.elapsedRealtime() - beforePutTimestamp;
}
settings.setLastAppUpdateTimestampMillis(mostRecentAppUpdatedTimestampMillis);
settings.setLastUpdateTimestampMillis(currentTimeMillis);
appsUpdateStats.mLastAppUpdateTimestampMillis = mostRecentAppUpdatedTimestampMillis;
} catch (AppSearchException e) {
// Reset the last update time stamp and app update timestamp so we can try again later.
settings.reset();
appsUpdateStats.mUpdateStatusCodes.clear();
appsUpdateStats.mUpdateStatusCodes.add(e.getResultCode());
throw e;
}
}
/** Returns whether the indexer should insert schema for app functions. */
private boolean requiresInsertSchemaForAppFunction(
@NonNull Map targetedPackages,
List appSearchAppFunctions)
throws AppSearchException {
// Should re-insert the schema as long as the indexed packages does not match the current
// set of packages.
Set indexedAppFunctionPackages = new ArraySet<>();
for (int i = 0; i < appSearchAppFunctions.size(); i++) {
indexedAppFunctionPackages.add(
appSearchAppFunctions
.get(i)
.getPropertyString(AppFunctionDocument.PROPERTY_PACKAGE_NAME));
}
Set currentAppFunctionPackages = getCurrentAppFunctionPackages(targetedPackages);
return !indexedAppFunctionPackages.equals(currentAppFunctionPackages);
}
/** Returns a set of currently installed packages that have app functions. */
private Set getCurrentAppFunctionPackages(
@NonNull Map targetedPackages) {
Set currentAppFunctionPackages = new ArraySet<>();
for (Map.Entry entry : targetedPackages.entrySet()) {
PackageInfo packageInfo = entry.getKey();
ResolveInfos resolveInfos = entry.getValue();
if (resolveInfos.getAppFunctionServiceInfo() != null) {
currentAppFunctionPackages.add(packageInfo.packageName);
}
}
return currentAppFunctionPackages;
}
/** Shuts down the {@link AppsIndexerImpl} and its {@link AppSearchHelper}. */
@Override
public void close() {
mAppSearchHelper.close();
}
}