Loading services/core/java/com/android/server/search/SearchManagerService.java +112 −34 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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; /** Loading @@ -70,11 +74,6 @@ public class SearchManagerService extends ISearchManager.Stub { publishBinderService(Context.SEARCH_SERVICE, mService); } @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()); Loading Loading @@ -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); Loading @@ -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(); } return searchables; } mSearchables.put(userId, 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; } } Loading @@ -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 onSomePackagesChanged() { updateSearchables(); public void onBeginPackageChanges() { clearPackageChangeState(); } @Override public void onPackageModified(String pkg) { updateSearchables(); public void onPackageAppeared(String packageName, int reason) { if (!mSearchablePackageAppeared) { // Check if the new appeared package contains SearchableInfo. mSearchablePackageAppeared = hasSearchableForPackage(packageName, getChangingUserId()); } mChangedPackages.add(packageName); } private void updateSearchables() { final int changingUserId = getChangingUserId(); @Override public void onPackageDisappeared(String packageName, int reason) { mChangedPackages.add(packageName); } @Override public void onPackageModified(String packageName) { mChangedPackages.add(packageName); } @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) { // Update list of searchable activities for (int i = 0; i < mSearchables.size(); i++) { if (changingUserId == mSearchables.keyAt(i)) { mSearchables.valueAt(i).updateSearchableList(); break; 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) { // 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 Loading @@ -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; Loading @@ -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); Loading services/core/java/com/android/server/search/Searchables.java +45 −2 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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<>(); /** * Loading Loading @@ -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>(); Loading @@ -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; Loading Loading @@ -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); Loading @@ -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: Loading Loading @@ -532,6 +573,8 @@ public class Searchables { pw.print(" "); pw.println(info.getSuggestAuthority()); } } pw.println("mRebuildSearchables = " + mRebuildSearchables); } } } services/tests/servicestests/src/com/android/server/search/SearchablesTest.java +3 −3 Original line number Diff line number Diff line Loading @@ -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", Loading Loading @@ -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(); Loading @@ -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); Loading Loading
services/core/java/com/android/server/search/SearchManagerService.java +112 −34 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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; /** Loading @@ -70,11 +74,6 @@ public class SearchManagerService extends ISearchManager.Stub { publishBinderService(Context.SEARCH_SERVICE, mService); } @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()); Loading Loading @@ -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); Loading @@ -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(); } return searchables; } mSearchables.put(userId, 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; } } Loading @@ -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 onSomePackagesChanged() { updateSearchables(); public void onBeginPackageChanges() { clearPackageChangeState(); } @Override public void onPackageModified(String pkg) { updateSearchables(); public void onPackageAppeared(String packageName, int reason) { if (!mSearchablePackageAppeared) { // Check if the new appeared package contains SearchableInfo. mSearchablePackageAppeared = hasSearchableForPackage(packageName, getChangingUserId()); } mChangedPackages.add(packageName); } private void updateSearchables() { final int changingUserId = getChangingUserId(); @Override public void onPackageDisappeared(String packageName, int reason) { mChangedPackages.add(packageName); } @Override public void onPackageModified(String packageName) { mChangedPackages.add(packageName); } @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) { // Update list of searchable activities for (int i = 0; i < mSearchables.size(); i++) { if (changingUserId == mSearchables.keyAt(i)) { mSearchables.valueAt(i).updateSearchableList(); break; 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) { // 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 Loading @@ -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; Loading @@ -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); Loading
services/core/java/com/android/server/search/Searchables.java +45 −2 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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<>(); /** * Loading Loading @@ -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>(); Loading @@ -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; Loading Loading @@ -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); Loading @@ -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: Loading Loading @@ -532,6 +573,8 @@ public class Searchables { pw.print(" "); pw.println(info.getSuggestAuthority()); } } pw.println("mRebuildSearchables = " + mRebuildSearchables); } } }
services/tests/servicestests/src/com/android/server/search/SearchablesTest.java +3 −3 Original line number Diff line number Diff line Loading @@ -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", Loading Loading @@ -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(); Loading @@ -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); Loading