Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4175be2f authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

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
parent 5aa86938
Loading
Loading
Loading
Loading
+59 −51
Original line number Diff line number Diff line
@@ -23,19 +23,16 @@ import android.app.IActivityManager;
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.provider.Settings;
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.io.PrintWriter;
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 class SearchManagerService extends ISearchManager.Stub {
     */
    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);
            }
        }
    }

    /**
     * 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 void onUnlockUser(int userId) {
        getSearchables(userId, true);
    }

    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 @@ public class SearchManagerService extends ISearchManager.Stub {
                // 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 class SearchManagerService extends ISearchManager.Stub {
        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 @@ public class SearchManagerService extends ISearchManager.Stub {
     * @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 @@ public class SearchManagerService extends ISearchManager.Stub {
    /**
     * 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 @@ public class SearchManagerService extends ISearchManager.Stub {
    /**
     * Gets the name of the global search activity.
     */
    @Override
    public ComponentName getGlobalSearchActivity() {
        return getSearchables(UserHandle.getCallingUserId()).getGlobalSearchActivity();
    }
@@ -237,6 +244,7 @@ public class SearchManagerService extends ISearchManager.Stub {
    /**
     * Gets the name of the web search activity.
     */
    @Override
    public ComponentName getWebSearchActivity() {
        return getSearchables(UserHandle.getCallingUserId()).getWebSearchActivity();
    }
+7 −5
Original line number Diff line number Diff line
@@ -200,7 +200,7 @@ public class Searchables {
     *
     * 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 @@ public class Searchables {

        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 @@ public class Searchables {
        // 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);
+4 −4
Original line number Diff line number Diff line
@@ -45,7 +45,6 @@ import android.util.DisplayMetrics;
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.pm.UserManagerService;
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 @@ public final class SystemServer {
            "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 @@ public final class SystemServer {
            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);
                }
+3 −3
Original line number Diff line number Diff line
@@ -72,7 +72,7 @@ public class SearchablesTest extends AndroidTestCase {
    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 @@ public class SearchablesTest extends AndroidTestCase {
        // 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 @@ public class SearchablesTest extends AndroidTestCase {

        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);