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

Commit 322e94e4 authored by Xin Guan's avatar Xin Guan
Browse files

SearchManager: Lazy build the searchable list.

- Avoid building searchable list during the user unlocking, only rebuild
  the list when apps request if necessary.
- Invalidate the searchable list when packages containing searchables
  get changed.

Bug: 319129155
Test: atest CtsAppTestCases:android.app.cts.SearchManagerTest
      atest FrameworksServicesTests:com.android.server.search.SearchablesTest

Change-Id: I6afec9258f108e5bd9633740aae9ba3c6b7c88f0
parent 9e3b6391
Loading
Loading
Loading
Loading
+112 −34
Original line number Diff line number Diff line
@@ -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;

/**
@@ -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());
@@ -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();
            }
            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;
        }
    }

@@ -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
@@ -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);
+45 −2
Original line number Diff line number Diff line
@@ -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);
        }
    }
}
+3 −3
Original line number Diff line number Diff line
@@ -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);