Triage SearchManagerService for missing apps.
Searchables aren't available until after the user is unlocked. We
could scan for them while locked, but we're aiming for a minimalist
environment, and scanning them while locked would require us to send
a changed broadcast on every boot.
Switch to using new SystemService lifecycle for faster event
delivery. Restore broken global search observer. Verified that
apps on external storage are handled correctly.
Bug: 26471205
Change-Id: Id99ffe2ea6db37a394454cc7dfa4eab10280ff35
diff --git a/services/core/java/com/android/server/search/SearchManagerService.java b/services/core/java/com/android/server/search/SearchManagerService.java
index 4c7f888..e3e1097 100644
--- a/services/core/java/com/android/server/search/SearchManagerService.java
+++ b/services/core/java/com/android/server/search/SearchManagerService.java
@@ -23,19 +23,16 @@
import android.app.ISearchManager;
import android.app.SearchManager;
import android.app.SearchableInfo;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.ContentObserver;
import android.os.Binder;
import android.os.Bundle;
-import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
@@ -43,9 +40,11 @@
import android.util.Log;
import android.util.SparseArray;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageMonitor;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
+import com.android.server.SystemService;
import com.android.server.statusbar.StatusBarManagerInternal;
import java.io.FileDescriptor;
@@ -53,19 +52,42 @@
import java.util.List;
/**
- * The search manager service handles the search UI, and maintains a registry of searchable
- * activities.
+ * The search manager service handles the search UI, and maintains a registry of
+ * searchable activities.
*/
public class SearchManagerService extends ISearchManager.Stub {
-
- // general debugging support
private static final String TAG = "SearchManagerService";
+ public static class Lifecycle extends SystemService {
+ private SearchManagerService mService;
+
+ public Lifecycle(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ mService = new SearchManagerService(getContext());
+ publishBinderService(Context.SEARCH_SERVICE, mService);
+ }
+
+ @Override
+ public void onUnlockUser(int userHandle) {
+ mService.onUnlockUser(userHandle);
+ }
+
+ @Override
+ public void onCleanupUser(int userHandle) {
+ mService.onCleanupUser(userHandle);
+ }
+ }
+
// Context that the service is running in.
private final Context mContext;
// This field is initialized lazily in getSearchables(), and then never modified.
- private final SparseArray<Searchables> mSearchables = new SparseArray<Searchables>();
+ @GuardedBy("mSearchables")
+ private final SparseArray<Searchables> mSearchables = new SparseArray<>();
/**
* Initializes the Search Manager service in the provided system context.
@@ -75,65 +97,47 @@
*/
public SearchManagerService(Context context) {
mContext = context;
- IntentFilter filter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
- filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
- mContext.registerReceiver(new BootCompletedReceiver(), filter);
- mContext.registerReceiver(new UserReceiver(),
- new IntentFilter(Intent.ACTION_USER_REMOVED));
new MyPackageMonitor().register(context, null, UserHandle.ALL, true);
+ new GlobalSearchProviderObserver(context.getContentResolver());
}
private Searchables getSearchables(int userId) {
- long origId = Binder.clearCallingIdentity();
+ return getSearchables(userId, false);
+ }
+
+ private Searchables getSearchables(int userId, boolean forceUpdate) {
+ final long token = Binder.clearCallingIdentity();
try {
- boolean userExists = ((UserManager) mContext.getSystemService(Context.USER_SERVICE))
- .getUserInfo(userId) != null;
- if (!userExists) return null;
+ final UserManager um = mContext.getSystemService(UserManager.class);
+ if (um.getUserInfo(userId) == null) {
+ throw new IllegalStateException("User " + userId + " doesn't exist");
+ }
+ if (!um.isUserUnlocked(userId)) {
+ throw new IllegalStateException("User " + userId + " isn't unlocked");
+ }
} finally {
- Binder.restoreCallingIdentity(origId);
+ Binder.restoreCallingIdentity(token);
}
synchronized (mSearchables) {
Searchables searchables = mSearchables.get(userId);
-
if (searchables == null) {
- //Log.i(TAG, "Building list of searchable activities for userId=" + userId);
searchables = new Searchables(mContext, userId);
- searchables.buildSearchableList();
+ searchables.updateSearchableList();
mSearchables.append(userId, searchables);
+ } else if (forceUpdate) {
+ searchables.updateSearchableList();
}
return searchables;
}
}
- private void onUserRemoved(int userId) {
- if (userId != UserHandle.USER_NULL) {
- synchronized (mSearchables) {
- mSearchables.remove(userId);
- }
- }
+ private void onUnlockUser(int userId) {
+ getSearchables(userId, true);
}
- /**
- * Creates the initial searchables list after boot.
- */
- private final class BootCompletedReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- new Thread() {
- @Override
- public void run() {
- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- mContext.unregisterReceiver(BootCompletedReceiver.this);
- getSearchables(0);
- }
- }.start();
- }
- }
-
- private final class UserReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- onUserRemoved(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL));
+ private void onCleanupUser(int userId) {
+ synchronized (mSearchables) {
+ mSearchables.remove(userId);
}
}
@@ -158,7 +162,7 @@
// Update list of searchable activities
for (int i = 0; i < mSearchables.size(); i++) {
if (changingUserId == mSearchables.keyAt(i)) {
- getSearchables(mSearchables.keyAt(i)).buildSearchableList();
+ mSearchables.valueAt(i).updateSearchableList();
break;
}
}
@@ -187,14 +191,13 @@
public void onChange(boolean selfChange) {
synchronized (mSearchables) {
for (int i = 0; i < mSearchables.size(); i++) {
- getSearchables(mSearchables.keyAt(i)).buildSearchableList();
+ mSearchables.valueAt(i).updateSearchableList();
}
}
Intent intent = new Intent(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
}
-
}
//
@@ -208,6 +211,7 @@
* @return Returns a SearchableInfo record describing the parameters of the search,
* or null if no searchable metadata was available.
*/
+ @Override
public SearchableInfo getSearchableInfo(final ComponentName launchActivity) {
if (launchActivity == null) {
Log.e(TAG, "getSearchableInfo(), activity == null");
@@ -219,10 +223,12 @@
/**
* Returns a list of the searchable activities that can be included in global search.
*/
+ @Override
public List<SearchableInfo> getSearchablesInGlobalSearch() {
return getSearchables(UserHandle.getCallingUserId()).getSearchablesInGlobalSearchList();
}
+ @Override
public List<ResolveInfo> getGlobalSearchActivities() {
return getSearchables(UserHandle.getCallingUserId()).getGlobalSearchActivities();
}
@@ -230,6 +236,7 @@
/**
* Gets the name of the global search activity.
*/
+ @Override
public ComponentName getGlobalSearchActivity() {
return getSearchables(UserHandle.getCallingUserId()).getGlobalSearchActivity();
}
@@ -237,6 +244,7 @@
/**
* Gets the name of the web search activity.
*/
+ @Override
public ComponentName getWebSearchActivity() {
return getSearchables(UserHandle.getCallingUserId()).getWebSearchActivity();
}
diff --git a/services/core/java/com/android/server/search/Searchables.java b/services/core/java/com/android/server/search/Searchables.java
index 0ffbb7d..0046fbb 100644
--- a/services/core/java/com/android/server/search/Searchables.java
+++ b/services/core/java/com/android/server/search/Searchables.java
@@ -200,7 +200,7 @@
*
* TODO: sort the list somehow? UI choice.
*/
- public void buildSearchableList() {
+ public void updateSearchableList() {
// These will become the new values at the end of the method
HashMap<ComponentName, SearchableInfo> newSearchablesMap
= new HashMap<ComponentName, SearchableInfo>();
@@ -215,11 +215,13 @@
long ident = Binder.clearCallingIdentity();
try {
- searchList = queryIntentActivities(intent, PackageManager.GET_META_DATA);
+ searchList = queryIntentActivities(intent,
+ PackageManager.GET_META_DATA | PackageManager.MATCH_DEBUG_TRIAGED_MISSING);
List<ResolveInfo> webSearchInfoList;
final Intent webSearchIntent = new Intent(Intent.ACTION_WEB_SEARCH);
- webSearchInfoList = queryIntentActivities(webSearchIntent, PackageManager.GET_META_DATA);
+ webSearchInfoList = queryIntentActivities(webSearchIntent,
+ PackageManager.GET_META_DATA | PackageManager.MATCH_DEBUG_TRIAGED_MISSING);
// analyze each one, generate a Searchables record, and record
if (searchList != null || webSearchInfoList != null) {
@@ -282,8 +284,8 @@
// Step 1 : Query the package manager for a list
// of activities that can handle the GLOBAL_SEARCH intent.
Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
- List<ResolveInfo> activities =
- queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ List<ResolveInfo> activities = queryIntentActivities(intent,
+ PackageManager.MATCH_DEFAULT_ONLY | PackageManager.MATCH_DEBUG_TRIAGED_MISSING);
if (activities != null && !activities.isEmpty()) {
// Step 2: Rank matching activities according to our heuristics.
Collections.sort(activities, GLOBAL_SEARCH_RANKER);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index dd6493c..287b39c 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -45,7 +45,6 @@
import android.util.EventLog;
import android.util.Slog;
import android.view.WindowManager;
-import android.webkit.WebViewFactory;
import com.android.internal.R;
import com.android.internal.os.BinderInternal;
@@ -81,7 +80,6 @@
import com.android.server.power.PowerManagerService;
import com.android.server.power.ShutdownThread;
import com.android.server.restrictions.RestrictionsManagerService;
-import com.android.server.search.SearchManagerService;
import com.android.server.statusbar.StatusBarManagerService;
import com.android.server.storage.DeviceStorageMonitorService;
import com.android.server.telecom.TelecomLoaderService;
@@ -138,6 +136,9 @@
"com.android.server.job.JobSchedulerService";
private static final String MOUNT_SERVICE_CLASS =
"com.android.server.MountService$Lifecycle";
+ private static final String SEARCH_MANAGER_SERVICE_CLASS =
+ "com.android.server.search.SearchManagerService$Lifecycle";
+
private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
/**
@@ -844,8 +845,7 @@
if (!disableNonCoreServices) {
traceBeginAndSlog("StartSearchManagerService");
try {
- ServiceManager.addService(Context.SEARCH_SERVICE,
- new SearchManagerService(context));
+ mSystemServiceManager.startService(SEARCH_MANAGER_SERVICE_CLASS);
} catch (Throwable e) {
reportWtf("starting Search Service", e);
}
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 79b9135..0f9bf2f 100644
--- a/services/tests/servicestests/src/com/android/server/search/SearchablesTest.java
+++ b/services/tests/servicestests/src/com/android/server/search/SearchablesTest.java
@@ -72,7 +72,7 @@
public void testNonSearchable() {
// test basic array & hashmap
Searchables searchables = new Searchables(mContext, 0);
- searchables.buildSearchableList();
+ searchables.updateSearchableList();
// confirm that we return null for non-searchy activities
ComponentName nonActivity = new ComponentName(
@@ -104,7 +104,7 @@
// build item list with real-world source data
mockPM.setSearchablesMode(MyMockPackageManager.SEARCHABLES_PASSTHROUGH);
Searchables searchables = new Searchables(mockContext, 0);
- searchables.buildSearchableList();
+ searchables.updateSearchableList();
// tests with "real" searchables (deprecate, this should be a unit test)
ArrayList<SearchableInfo> searchablesList = searchables.getSearchablesList();
int count = searchablesList.size();
@@ -123,7 +123,7 @@
mockPM.setSearchablesMode(MyMockPackageManager.SEARCHABLES_MOCK_ZERO);
Searchables searchables = new Searchables(mockContext, 0);
- searchables.buildSearchableList();
+ searchables.updateSearchableList();
ArrayList<SearchableInfo> searchablesList = searchables.getSearchablesList();
assertNotNull(searchablesList);
MoreAsserts.assertEmpty(searchablesList);