• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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 
17 package com.android.server.appsearch.appsindexer;
18 
19 import android.annotation.NonNull;
20 import android.app.appsearch.PackageIdentifier;
21 import android.app.appsearch.exceptions.AppSearchException;
22 import android.content.Context;
23 import android.content.pm.PackageInfo;
24 import android.content.pm.PackageManager;
25 import android.content.pm.ResolveInfo;
26 import android.util.ArrayMap;
27 import android.util.ArraySet;
28 import android.util.Log;
29 
30 import com.android.internal.annotations.VisibleForTesting;
31 
32 import java.io.Closeable;
33 import java.util.ArrayList;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Objects;
37 import java.util.Set;
38 
39 /**
40  * Interactions with PackageManager and AppSearch.
41  *
42  * <p>This class is NOT thread-safe.
43  *
44  * @hide
45  */
46 public final class AppsIndexerImpl implements Closeable {
47     static final String TAG = "AppSearchAppsIndexerImpl";
48 
49     private final Context mContext;
50     private final AppSearchHelper mAppSearchHelper;
51 
AppsIndexerImpl(@onNull Context context)52     public AppsIndexerImpl(@NonNull Context context) throws AppSearchException {
53         mContext = Objects.requireNonNull(context);
54         mAppSearchHelper = AppSearchHelper.createAppSearchHelper(context);
55     }
56 
57     /**
58      * Checks PackageManager and AppSearch to sync the Apps Index in AppSearch.
59      *
60      * <p>It deletes removed apps, inserts newly-added ones, and updates existing ones in the App
61      * corpus in AppSearch.
62      *
63      * @param settings contains update timestamps that help the indexer determine which apps were
64      *     updated.
65      */
66     @VisibleForTesting
doUpdate(@onNull AppsIndexerSettings settings)67     public void doUpdate(@NonNull AppsIndexerSettings settings) throws AppSearchException {
68         Objects.requireNonNull(settings);
69         long currentTimeMillis = System.currentTimeMillis();
70 
71         PackageManager packageManager = mContext.getPackageManager();
72 
73         // Search AppSearch for MobileApplication objects to get a "current" list of indexed apps.
74         Map<String, Long> appUpdatedTimestamps = mAppSearchHelper.getAppsFromAppSearch();
75         Map<PackageInfo, ResolveInfo> launchablePackages =
76                 AppsUtil.getLaunchablePackages(packageManager);
77         Set<PackageInfo> packageInfos = launchablePackages.keySet();
78 
79         Map<PackageInfo, ResolveInfo> packagesToBeAddedOrUpdated = new ArrayMap<>();
80         long mostRecentAppUpdatedTimestampMillis = settings.getLastAppUpdateTimestampMillis();
81 
82         // Prepare a set of current app IDs for efficient lookup
83         Set<String> currentAppIds = new ArraySet<>();
84         for (PackageInfo packageInfo : packageInfos) {
85             currentAppIds.add(packageInfo.packageName);
86 
87             // Update the most recent timestamp as we iterate
88             if (packageInfo.lastUpdateTime > mostRecentAppUpdatedTimestampMillis) {
89                 mostRecentAppUpdatedTimestampMillis = packageInfo.lastUpdateTime;
90             }
91 
92             Long storedUpdateTime = appUpdatedTimestamps.get(packageInfo.packageName);
93 
94             if (storedUpdateTime == null || packageInfo.lastUpdateTime != storedUpdateTime) {
95                 // Added or updated
96                 packagesToBeAddedOrUpdated.put(packageInfo, launchablePackages.get(packageInfo));
97             }
98         }
99 
100         try {
101             if (!currentAppIds.equals(appUpdatedTimestamps.keySet())) {
102                 // The current list of apps in AppSearch does not match what is in PackageManager.
103                 // This means this is the first sync, an app was removed, or an app was added. In
104                 // all cases, we need to call setSchema to keep AppSearch in sync with
105                 // PackageManager.
106                 List<PackageIdentifier> packageIdentifiers = new ArrayList<>();
107                 for (PackageInfo packageInfo : packageInfos) {
108                     // We get certificates here as getting the certificates during the previous for
109                     // loop would be wasteful if we end up not needing to call set schema
110                     byte[] certificate = AppsUtil.getCertificate(packageInfo);
111                     if (certificate == null) {
112                         Log.e(TAG, "Certificate not found for package: " + packageInfo.packageName);
113                         continue;
114                     }
115                     packageIdentifiers.add(
116                             new PackageIdentifier(packageInfo.packageName, certificate));
117                 }
118                 // The certificate is necessary along with the package name as it is used in
119                 // visibility settings.
120                 mAppSearchHelper.setSchemasForPackages(packageIdentifiers);
121             }
122 
123             if (!packagesToBeAddedOrUpdated.isEmpty()) {
124                 mAppSearchHelper.indexApps(
125                         AppsUtil.buildAppsFromPackageInfos(
126                                 packageManager, packagesToBeAddedOrUpdated));
127             }
128 
129             settings.setLastAppUpdateTimestampMillis(mostRecentAppUpdatedTimestampMillis);
130             settings.setLastUpdateTimestampMillis(currentTimeMillis);
131         } catch (AppSearchException e) {
132             // Reset the last update time stamp and app update timestamp so we can try again later.
133             settings.reset();
134             throw e;
135         }
136     }
137 
138     /** Shuts down the {@link AppsIndexerImpl} and its {@link AppSearchHelper}. */
139     @Override
close()140     public void close() {
141         mAppSearchHelper.close();
142     }
143 }
144