diff options
3 files changed, 160 insertions, 39 deletions
diff --git a/services/core/java/com/android/server/search/SearchManagerService.java b/services/core/java/com/android/server/search/SearchManagerService.java index 7091c47b8e83..ecfc040ae29c 100644 --- a/services/core/java/com/android/server/search/SearchManagerService.java +++ b/services/core/java/com/android/server/search/SearchManagerService.java @@ -17,6 +17,7 @@ package com.android.server.search; import android.annotation.NonNull; +import android.annotation.UserIdInt; import android.app.ISearchManager; import android.app.SearchManager; import android.app.SearchableInfo; @@ -24,6 +25,7 @@ import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.database.ContentObserver; import android.os.Binder; @@ -32,6 +34,7 @@ import android.os.Handler; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; +import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; @@ -47,6 +50,7 @@ import com.android.server.statusbar.StatusBarManagerInternal; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.List; /** @@ -71,11 +75,6 @@ public class SearchManagerService extends ISearchManager.Stub { } @Override - public void onUserUnlocking(@NonNull TargetUser user) { - mService.mHandler.post(() -> mService.onUnlockUser(user.getUserIdentifier())); - } - - @Override public void onUserStopped(@NonNull TargetUser user) { mService.onCleanupUser(user.getUserIdentifier()); } @@ -102,10 +101,6 @@ public class SearchManagerService extends ISearchManager.Stub { } private Searchables getSearchables(int userId) { - return getSearchables(userId, false); - } - - private Searchables getSearchables(int userId, boolean forceUpdate) { final long token = Binder.clearCallingIdentity(); try { final UserManager um = mContext.getSystemService(UserManager.class); @@ -122,21 +117,11 @@ public class SearchManagerService extends ISearchManager.Stub { Searchables searchables = mSearchables.get(userId); if (searchables == null) { searchables = new Searchables(mContext, userId); - searchables.updateSearchableList(); - mSearchables.append(userId, searchables); - } else if (forceUpdate) { - searchables.updateSearchableList(); + mSearchables.put(userId, searchables); } - return searchables; - } - } - private void onUnlockUser(int userId) { - try { - getSearchables(userId, true); - } catch (IllegalStateException ignored) { - // We're just trying to warm a cache, so we don't mind if the user - // was stopped or destroyed before we got here. + searchables.updateSearchableListIfNeeded(); + return searchables; } } @@ -150,28 +135,110 @@ public class SearchManagerService extends ISearchManager.Stub { * Refreshes the "searchables" list when packages are added/removed. */ class MyPackageMonitor extends PackageMonitor { + /** + * Packages that are appeared, disappeared, or modified for whatever reason. + */ + private final ArrayList<String> mChangedPackages = new ArrayList<>(); + + /** + * {@code true} if one or more packages that contain {@link SearchableInfo} appeared. + */ + private boolean mSearchablePackageAppeared = false; + + @Override + public void onBeginPackageChanges() { + clearPackageChangeState(); + } + + @Override + public void onPackageAppeared(String packageName, int reason) { + if (!mSearchablePackageAppeared) { + // Check if the new appeared package contains SearchableInfo. + mSearchablePackageAppeared = + hasSearchableForPackage(packageName, getChangingUserId()); + } + mChangedPackages.add(packageName); + } @Override - public void onSomePackagesChanged() { - updateSearchables(); + public void onPackageDisappeared(String packageName, int reason) { + mChangedPackages.add(packageName); } @Override - public void onPackageModified(String pkg) { - updateSearchables(); + public void onPackageModified(String packageName) { + mChangedPackages.add(packageName); } - private void updateSearchables() { + @Override + public void onFinishPackageChanges() { + onFinishPackageChangesInternal(); + clearPackageChangeState(); + } + + private void clearPackageChangeState() { + mChangedPackages.clear(); + mSearchablePackageAppeared = false; + } + + private boolean hasSearchableForPackage(String packageName, int userId) { + final List<ResolveInfo> searchList = querySearchableActivities(mContext, + new Intent(Intent.ACTION_SEARCH).setPackage(packageName), userId); + if (!searchList.isEmpty()) { + return true; + } + + final List<ResolveInfo> webSearchList = querySearchableActivities(mContext, + new Intent(Intent.ACTION_WEB_SEARCH).setPackage(packageName), userId); + if (!webSearchList.isEmpty()) { + return true; + } + + final List<ResolveInfo> globalSearchList = querySearchableActivities(mContext, + new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH).setPackage(packageName), + userId); + return !globalSearchList.isEmpty(); + } + + private boolean shouldRebuildSearchableList(@UserIdInt int changingUserId) { + // This method is guaranteed to be called only on getRegisteredHandler() + if (mSearchablePackageAppeared) { + return true; + } + + ArraySet<String> knownSearchablePackageNames = new ArraySet<>(); + synchronized (mSearchables) { + Searchables searchables = mSearchables.get(changingUserId); + if (searchables != null) { + knownSearchablePackageNames = searchables.getKnownSearchablePackageNames(); + } + } + + final int numOfPackages = mChangedPackages.size(); + for (int i = 0; i < numOfPackages; i++) { + final String packageName = mChangedPackages.get(i); + if (knownSearchablePackageNames.contains(packageName)) { + return true; + } + } + + return false; + } + + private void onFinishPackageChangesInternal() { final int changingUserId = getChangingUserId(); + if (!shouldRebuildSearchableList(changingUserId)) { + return; + } + synchronized (mSearchables) { - // Update list of searchable activities - for (int i = 0; i < mSearchables.size(); i++) { - if (changingUserId == mSearchables.keyAt(i)) { - mSearchables.valueAt(i).updateSearchableList(); - break; - } + // Invalidate the searchable list. + Searchables searchables = mSearchables.get(changingUserId); + if (searchables != null) { + searchables.invalidateSearchableList(); } } + // Inform all listeners that the list of searchables has been updated. Intent intent = new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING @@ -180,6 +247,17 @@ public class SearchManagerService extends ISearchManager.Stub { } } + @NonNull + static List<ResolveInfo> querySearchableActivities(Context context, Intent searchIntent, + @UserIdInt int userId) { + final List<ResolveInfo> activities = context.getPackageManager() + .queryIntentActivitiesAsUser(searchIntent, PackageManager.GET_META_DATA + | PackageManager.MATCH_INSTANT + | PackageManager.MATCH_DEBUG_TRIAGED_MISSING, userId); + return activities; + } + + class GlobalSearchProviderObserver extends ContentObserver { private final ContentResolver mResolver; @@ -196,7 +274,7 @@ public class SearchManagerService extends ISearchManager.Stub { public void onChange(boolean selfChange) { synchronized (mSearchables) { for (int i = 0; i < mSearchables.size(); i++) { - mSearchables.valueAt(i).updateSearchableList(); + mSearchables.valueAt(i).invalidateSearchableList(); } } Intent intent = new Intent(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED); diff --git a/services/core/java/com/android/server/search/Searchables.java b/services/core/java/com/android/server/search/Searchables.java index 7b397755173d..dc6733941357 100644 --- a/services/core/java/com/android/server/search/Searchables.java +++ b/services/core/java/com/android/server/search/Searchables.java @@ -35,8 +35,10 @@ import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; +import android.util.ArraySet; import android.util.Log; +import com.android.internal.annotations.GuardedBy; import com.android.server.LocalServices; import java.io.FileDescriptor; @@ -62,7 +64,6 @@ public class Searchables { private static final String MD_SEARCHABLE_SYSTEM_SEARCH = "*"; private Context mContext; - private HashMap<ComponentName, SearchableInfo> mSearchablesMap = null; private ArrayList<SearchableInfo> mSearchablesList = null; private ArrayList<SearchableInfo> mSearchablesInGlobalSearchList = null; @@ -81,6 +82,12 @@ public class Searchables { final private IPackageManager mPm; // User for which this Searchables caches information private int mUserId; + @GuardedBy("this") + private boolean mRebuildSearchables = true; + + // Package names that are known to contain {@link SearchableInfo} + @GuardedBy("this") + private ArraySet<String> mKnownSearchablePackageNames = new ArraySet<>(); /** * @@ -224,7 +231,14 @@ public class Searchables { * * TODO: sort the list somehow? UI choice. */ - public void updateSearchableList() { + public void updateSearchableListIfNeeded() { + synchronized (this) { + if (!mRebuildSearchables) { + // The searchable list is valid, no need to rebuild. + return; + } + } + // These will become the new values at the end of the method HashMap<ComponentName, SearchableInfo> newSearchablesMap = new HashMap<ComponentName, SearchableInfo>(); @@ -232,6 +246,7 @@ public class Searchables { = new ArrayList<SearchableInfo>(); ArrayList<SearchableInfo> newSearchablesInGlobalSearchList = new ArrayList<SearchableInfo>(); + ArraySet<String> newKnownSearchablePackageNames = new ArraySet<>(); // Use intent resolver to generate list of ACTION_SEARCH & ACTION_WEB_SEARCH receivers. List<ResolveInfo> searchList; @@ -264,6 +279,7 @@ public class Searchables { mUserId); if (searchable != null) { newSearchablesList.add(searchable); + newKnownSearchablePackageNames.add(ai.packageName); newSearchablesMap.put(searchable.getSearchActivity(), searchable); if (searchable.shouldIncludeInGlobalSearch()) { newSearchablesInGlobalSearchList.add(searchable); @@ -286,16 +302,41 @@ public class Searchables { synchronized (this) { mSearchablesMap = newSearchablesMap; mSearchablesList = newSearchablesList; + mKnownSearchablePackageNames = newKnownSearchablePackageNames; mSearchablesInGlobalSearchList = newSearchablesInGlobalSearchList; mGlobalSearchActivities = newGlobalSearchActivities; mCurrentGlobalSearchActivity = newGlobalSearchActivity; mWebSearchActivity = newWebSearchActivity; + for (ResolveInfo globalSearchActivity: mGlobalSearchActivities) { + mKnownSearchablePackageNames.add( + globalSearchActivity.getComponentInfo().packageName); + } + if (mCurrentGlobalSearchActivity != null) { + mKnownSearchablePackageNames.add( + mCurrentGlobalSearchActivity.getPackageName()); + } + if (mWebSearchActivity != null) { + mKnownSearchablePackageNames.add(mWebSearchActivity.getPackageName()); + } + + mRebuildSearchables = false; } } finally { Binder.restoreCallingIdentity(ident); } } + synchronized ArraySet<String> getKnownSearchablePackageNames() { + return mKnownSearchablePackageNames; + } + + synchronized void invalidateSearchableList() { + mRebuildSearchables = true; + + // Don't rebuild the searchable list, it will be rebuilt + // when the next updateSearchableList gets called. + } + /** * Returns a sorted list of installed search providers as per * the following heuristics: @@ -532,6 +573,8 @@ public class Searchables { pw.print(" "); pw.println(info.getSuggestAuthority()); } } + + pw.println("mRebuildSearchables = " + mRebuildSearchables); } } } diff --git a/services/tests/servicestests/src/com/android/server/search/SearchablesTest.java b/services/tests/servicestests/src/com/android/server/search/SearchablesTest.java index f5c6795484fa..771a76517b22 100644 --- a/services/tests/servicestests/src/com/android/server/search/SearchablesTest.java +++ b/services/tests/servicestests/src/com/android/server/search/SearchablesTest.java @@ -92,7 +92,7 @@ public class SearchablesTest { public void testNonSearchable() { // test basic array & hashmap Searchables searchables = new Searchables(mContext, 0); - searchables.updateSearchableList(); + searchables.updateSearchableListIfNeeded(); // confirm that we return null for non-searchy activities ComponentName nonActivity = new ComponentName("com.android.frameworks.servicestests", @@ -121,7 +121,7 @@ public class SearchablesTest { doReturn(true).when(mPackageManagerInternal).canAccessComponent(anyInt(), any(), anyInt()); Searchables searchables = new Searchables(mContext, 0); - searchables.updateSearchableList(); + searchables.updateSearchableListIfNeeded(); // tests with "real" searchables (deprecate, this should be a unit test) ArrayList<SearchableInfo> searchablesList = searchables.getSearchablesList(); int count = searchablesList.size(); @@ -139,7 +139,7 @@ public class SearchablesTest { doReturn(false).when(mPackageManagerInternal).canAccessComponent(anyInt(), any(), anyInt()); Searchables searchables = new Searchables(mContext, 0); - searchables.updateSearchableList(); + searchables.updateSearchableListIfNeeded(); ArrayList<SearchableInfo> searchablesList = searchables.getSearchablesList(); assertNotNull(searchablesList); MoreAsserts.assertEmpty(searchablesList); |