OSDN Git Service

Move a11y indexing from DynamicContentMonitor to loader
authorFan Zhang <zhfan@google.com>
Thu, 3 Aug 2017 00:32:43 +0000 (17:32 -0700)
committerFan Zhang <zhfan@google.com>
Thu, 3 Aug 2017 17:10:59 +0000 (10:10 -0700)
This is necessary to kill DynamicContentMonitor later

- Removed all logic related to indexing accesiblitysetting from the
  monitor class and AccessibilitySetting page itself
- Created a loader to search against A11yServices at runtime

I noticed adding a loader in SearchResultsAdapter is rather manual. It's
something we should consider refactor in the future.

Bug: 64310452
Test: robotests
Change-Id: Iff31aff65ce000991229433f294e2ec69af99da2

13 files changed:
src/com/android/settings/accessibility/AccessibilitySettings.java
src/com/android/settings/search/AccessibilityServiceResultLoader.java [new file with mode: 0644]
src/com/android/settings/search/DynamicIndexableContentMonitor.java
src/com/android/settings/search/InstalledAppResultLoader.java
src/com/android/settings/search/SearchFeatureProvider.java
src/com/android/settings/search/SearchFeatureProviderImpl.java
src/com/android/settings/search/SearchFragment.java
src/com/android/settings/search/SearchResultsAdapter.java
tests/robotests/src/com/android/settings/search/AccessibilityServiceResultLoaderTest.java [new file with mode: 0644]
tests/robotests/src/com/android/settings/search/DynamicIndexableContentMonitorTest.java
tests/robotests/src/com/android/settings/search/InstalledAppResultLoaderTest.java
tests/robotests/src/com/android/settings/search/MockAccessiblityLoader.java [new file with mode: 0644]
tests/robotests/src/com/android/settings/search/SearchFragmentTest.java

index afb3c3a..51b676d 100644 (file)
@@ -20,7 +20,6 @@ import android.accessibilityservice.AccessibilityServiceInfo;
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
@@ -52,7 +51,6 @@ import com.android.settings.SettingsPreferenceFragment;
 import com.android.settings.Utils;
 import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settings.search.Indexable;
-import com.android.settings.search.SearchIndexableRaw;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 import com.android.settingslib.RestrictedPreference;
@@ -727,40 +725,6 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements
 
     public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
             new BaseSearchIndexProvider() {
-        @Override
-        public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
-            List<SearchIndexableRaw> indexables = new ArrayList<>();
-
-            PackageManager packageManager = context.getPackageManager();
-            AccessibilityManager accessibilityManager =
-                    context.getSystemService(AccessibilityManager.class);
-
-            String screenTitle = context.getResources().getString(
-                    R.string.accessibility_settings);
-
-            // Indexing all services, regardless if enabled.
-            List<AccessibilityServiceInfo> services = accessibilityManager
-                    .getInstalledAccessibilityServiceList();
-            final int serviceCount = services.size();
-            for (int i = 0; i < serviceCount; i++) {
-                AccessibilityServiceInfo service = services.get(i);
-                if (service == null || service.getResolveInfo() == null) {
-                    continue;
-                }
-
-                ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo;
-                ComponentName componentName = new ComponentName(serviceInfo.packageName,
-                        serviceInfo.name);
-
-                SearchIndexableRaw indexable = new SearchIndexableRaw(context);
-                indexable.key = componentName.flattenToString();
-                indexable.title = service.getResolveInfo().loadLabel(packageManager).toString();
-                indexable.screenTitle = screenTitle;
-                indexables.add(indexable);
-            }
-
-            return indexables;
-        }
 
         @Override
         public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
diff --git a/src/com/android/settings/search/AccessibilityServiceResultLoader.java b/src/com/android/settings/search/AccessibilityServiceResultLoader.java
new file mode 100644 (file)
index 0000000..7ffbcfc
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.search;
+
+import static com.android.settings.search.InstalledAppResultLoader.getWordDifference;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.VisibleForTesting;
+import android.support.v4.content.ContextCompat;
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.settings.R;
+import com.android.settings.accessibility.AccessibilitySettings;
+import com.android.settings.dashboard.SiteMapManager;
+import com.android.settings.utils.AsyncLoader;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+public class AccessibilityServiceResultLoader extends AsyncLoader<Set<? extends SearchResult>> {
+
+    private static final int NAME_NO_MATCH = -1;
+
+    private List<String> mBreadcrumb;
+    private SiteMapManager mSiteMapManager;
+    @VisibleForTesting
+    final String mQuery;
+    private final AccessibilityManager mAccessibilityManager;
+    private final PackageManager mPackageManager;
+
+
+    public AccessibilityServiceResultLoader(Context context, String query,
+            SiteMapManager mapManager) {
+        super(context);
+        mSiteMapManager = mapManager;
+        mPackageManager = context.getPackageManager();
+        mAccessibilityManager =
+                (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
+        mQuery = query;
+    }
+
+    @Override
+    public Set<? extends SearchResult> loadInBackground() {
+        final Set<SearchResult> results = new HashSet<>();
+        final Context context = getContext();
+        final List<AccessibilityServiceInfo> services = mAccessibilityManager
+                .getInstalledAccessibilityServiceList();
+        final String screenTitle = context.getString(R.string.accessibility_settings);
+        for (AccessibilityServiceInfo service : services) {
+            if (service == null) {
+                continue;
+            }
+            final ResolveInfo resolveInfo = service.getResolveInfo();
+            if (service.getResolveInfo() == null) {
+                continue;
+            }
+            final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+            final CharSequence title = resolveInfo.loadLabel(mPackageManager);
+            final int wordDiff = getWordDifference(title.toString(), mQuery);
+            if (wordDiff == NAME_NO_MATCH) {
+                continue;
+            }
+            final Drawable icon;
+            if (resolveInfo.getIconResource() == 0) {
+                icon = ContextCompat.getDrawable(context, R.mipmap.ic_accessibility_generic);
+            } else {
+                icon = resolveInfo.loadIcon(mPackageManager);
+            }
+            final String componentName = new ComponentName(serviceInfo.packageName,
+                    serviceInfo.name).flattenToString();
+            final Intent intent = DatabaseIndexingUtils.buildSubsettingIntent(context,
+                    AccessibilitySettings.class.getName(), componentName, screenTitle);
+
+            results.add(new SearchResult.Builder()
+                    .setTitle(title)
+                    .addBreadcrumbs(getBreadCrumb())
+                    .setPayload(new ResultPayload(intent))
+                    .setRank(wordDiff)
+                    .setIcon(icon)
+                    .setStableId(Objects.hash(screenTitle, componentName))
+                    .build());
+        }
+        return results;
+    }
+
+    private List<String> getBreadCrumb() {
+        if (mBreadcrumb == null || mBreadcrumb.isEmpty()) {
+            final Context context = getContext();
+            mBreadcrumb = mSiteMapManager.buildBreadCrumb(
+                    context, AccessibilitySettings.class.getName(),
+                    context.getString(R.string.accessibility_settings));
+        }
+        return mBreadcrumb;
+    }
+
+    @Override
+    protected void onDiscardResult(Set<? extends SearchResult> result) {
+
+    }
+}
index 0758387..a0e3c02 100644 (file)
@@ -17,7 +17,6 @@
 package com.android.settings.search;
 
 import android.accessibilityservice.AccessibilityService;
-import android.accessibilityservice.AccessibilityServiceInfo;
 import android.app.Activity;
 import android.app.LoaderManager;
 import android.content.ContentResolver;
@@ -41,13 +40,11 @@ import android.provider.UserDictionary;
 import android.support.annotation.Nullable;
 import android.support.annotation.VisibleForTesting;
 import android.util.Log;
-import android.view.accessibility.AccessibilityManager;
 import android.view.inputmethod.InputMethod;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodManager;
 
 import com.android.internal.content.PackageMonitor;
-import com.android.settings.accessibility.AccessibilitySettings;
 import com.android.settings.inputmethod.AvailableVirtualKeyboardFragment;
 import com.android.settings.inputmethod.PhysicalKeyboardFragment;
 import com.android.settings.inputmethod.VirtualKeyboardFragment;
@@ -89,7 +86,6 @@ public final class DynamicIndexableContentMonitor implements
     @VisibleForTesting
     static void resetForTesting() {
         InputDevicesMonitor.getInstance().resetForTesting();
-        AccessibilityServicesMonitor.getInstance().resetForTesting();
         InputMethodServicesMonitor.getInstance().resetForTesting();
     }
 
@@ -144,7 +140,6 @@ public final class DynamicIndexableContentMonitor implements
         InputDevicesMonitor.getInstance().initialize(context, mIndexManager);
 
         // Start tracking packages.
-        AccessibilityServicesMonitor.getInstance().initialize(context, mIndexManager);
         InputMethodServicesMonitor.getInstance().initialize(context, mIndexManager);
     }
 
@@ -301,86 +296,17 @@ public final class DynamicIndexableContentMonitor implements
 
         private void postPackageAvailable(final String packageName) {
             getRegisteredHandler().postDelayed(() -> {
-                AccessibilityServicesMonitor.getInstance().onPackageAvailable(packageName);
                 InputMethodServicesMonitor.getInstance().onPackageAvailable(packageName);
             }, DELAY_PROCESS_PACKAGE_CHANGE);
         }
 
         private void postPackageUnavailable(final String packageName) {
             getRegisteredHandler().postDelayed(() -> {
-                AccessibilityServicesMonitor.getInstance().onPackageUnavailable(packageName);
                 InputMethodServicesMonitor.getInstance().onPackageUnavailable(packageName);
             }, DELAY_PROCESS_PACKAGE_CHANGE);
         }
     }
 
-    // A singleton that holds list of available accessibility services and updates search index.
-    private static class AccessibilityServicesMonitor {
-
-        // Null if not initialized.
-        @Nullable private DatabaseIndexingManager mIndexManager;
-        private PackageManager mPackageManager;
-        private final List<String> mAccessibilityServices = new ArrayList<>();
-
-        private AccessibilityServicesMonitor() {}
-
-        private static class SingletonHolder {
-            private static final AccessibilityServicesMonitor INSTANCE =
-                    new AccessibilityServicesMonitor();
-        }
-
-        static AccessibilityServicesMonitor getInstance() {
-            return SingletonHolder.INSTANCE;
-        }
-
-        @VisibleForTesting
-        synchronized void resetForTesting() {
-            mIndexManager = null;
-        }
-
-        synchronized void initialize(Context context, DatabaseIndexingManager index) {
-            if (mIndexManager != null) return;
-            mIndexManager = index;
-            mPackageManager = context.getPackageManager();
-            mAccessibilityServices.clear();
-            buildIndex();
-
-            // Cache accessibility service packages to know when they go away.
-            AccessibilityManager accessibilityManager = (AccessibilityManager) context
-                    .getSystemService(Context.ACCESSIBILITY_SERVICE);
-            for (final AccessibilityServiceInfo accessibilityService
-                    : accessibilityManager.getInstalledAccessibilityServiceList()) {
-                ResolveInfo resolveInfo = accessibilityService.getResolveInfo();
-                if (resolveInfo != null && resolveInfo.serviceInfo != null) {
-                    mAccessibilityServices.add(resolveInfo.serviceInfo.packageName);
-                }
-            }
-        }
-
-        private void buildIndex() {
-            mIndexManager.updateFromClassNameResource(AccessibilitySettings.class.getName(),
-                    true /* includeInSearchResults */);
-        }
-
-        synchronized void onPackageAvailable(String packageName) {
-            if (mIndexManager == null) return;
-            if (mAccessibilityServices.contains(packageName)) return;
-
-            final Intent intent = getAccessibilityServiceIntent(packageName);
-            final List<ResolveInfo> services = mPackageManager
-                    .queryIntentServices(intent, 0 /* flags */);
-            if (services == null || services.isEmpty()) return;
-            mAccessibilityServices.add(packageName);
-            buildIndex();
-        }
-
-        synchronized void onPackageUnavailable(String packageName) {
-            if (mIndexManager == null) return;
-            if (!mAccessibilityServices.remove(packageName)) return;
-            buildIndex();
-        }
-    }
-
     // A singleton that holds list of available input methods and updates search index.
     // Also it monitors user dictionary changes and updates search index.
     private static class InputMethodServicesMonitor extends ContentObserver {
index 6340a61..9d80b73 100644 (file)
@@ -163,8 +163,9 @@ public class InstalledAppResultLoader extends AsyncLoader<Set<? extends SearchRe
      * appName: Abcde, query: bc, Returns NAME_NO_MATCH
      * appName: Abcde, query: xyz, Returns NAME_NO_MATCH
      * appName: Abc de, query: de, Returns 4
+     * TODO: Move this to a common util class.
      */
-    private int getWordDifference(String appName, String query) {
+    static int getWordDifference(String appName, String query) {
         if (TextUtils.isEmpty(appName) || TextUtils.isEmpty(query)) {
             return NAME_NO_MATCH;
         }
index 81fcb2b..7e0e086 100644 (file)
@@ -43,6 +43,12 @@ public interface SearchFeatureProvider {
     InstalledAppResultLoader getInstalledAppSearchLoader(Context context, String query);
 
     /**
+     * Returns a new loader to search accessibility services.
+     */
+    AccessibilityServiceResultLoader getAccessibilityServiceResultLoader(Context context,
+            String query);
+
+    /**
      * Returns a new loader to get all recently saved queries search terms.
      */
     SavedQueryLoader getSavedQueryLoader(Context context);
@@ -95,8 +101,8 @@ public interface SearchFeatureProvider {
     /**
      * Query search results based on the input query.
      *
-     * @param context application context
-     * @param query input user query
+     * @param context                     application context
+     * @param query                       input user query
      * @param searchResultsRankerCallback {@link SearchResultsRankerCallback}
      */
     default void querySearchResults(Context context, String query,
@@ -112,8 +118,8 @@ public interface SearchFeatureProvider {
     /**
      * Notify that a search result is clicked.
      *
-     * @param context application context
-     * @param query input user query
+     * @param context      application context
+     * @param query        input user query
      * @param searchResult clicked result
      */
     default void searchResultClicked(Context context, String query, SearchResult searchResult) {
index e32246e..b90547e 100644 (file)
@@ -55,6 +55,13 @@ public class SearchFeatureProviderImpl implements SearchFeatureProvider {
     }
 
     @Override
+    public AccessibilityServiceResultLoader getAccessibilityServiceResultLoader(Context context,
+            String query) {
+        return new AccessibilityServiceResultLoader(context, cleanQuery(query),
+                getSiteMapManager());
+    }
+
+    @Override
     public SavedQueryLoader getSavedQueryLoader(Context context) {
         return new SavedQueryLoader(context);
     }
index 38605ff..fb89d6e 100644 (file)
@@ -82,8 +82,10 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
     static final int LOADER_ID_DATABASE = 1;
     @VisibleForTesting
     static final int LOADER_ID_INSTALLED_APPS = 2;
+    @VisibleForTesting
+    static final int LOADER_ID_ACCESSIBILITY_SERVICES = 3;
 
-    private static final int NUM_QUERY_LOADERS = 2;
+    private static final int NUM_QUERY_LOADERS = 3;
 
     @VisibleForTesting
     AtomicInteger mUnfinishedLoadersCount = new AtomicInteger(NUM_QUERY_LOADERS);
@@ -281,6 +283,7 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
             final LoaderManager loaderManager = getLoaderManager();
             loaderManager.destroyLoader(LOADER_ID_DATABASE);
             loaderManager.destroyLoader(LOADER_ID_INSTALLED_APPS);
+            loaderManager.destroyLoader(LOADER_ID_ACCESSIBILITY_SERVICES);
             mShowingSavedQuery = true;
             mSavedQueryController.loadSavedQueries();
             mSearchFeatureProvider.hideFeedbackButton();
@@ -309,6 +312,8 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
                 return mSearchFeatureProvider.getDatabaseSearchLoader(activity, mQuery);
             case LOADER_ID_INSTALLED_APPS:
                 return mSearchFeatureProvider.getInstalledAppSearchLoader(activity, mQuery);
+            case LOADER_ID_ACCESSIBILITY_SERVICES:
+                return mSearchFeatureProvider.getAccessibilityServiceResultLoader(activity, mQuery);
             default:
                 return null;
         }
@@ -341,8 +346,11 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
             mSavedQueryController.loadSavedQueries();
         } else {
             final LoaderManager loaderManager = getLoaderManager();
-            loaderManager.initLoader(LOADER_ID_DATABASE, null, this);
-            loaderManager.initLoader(LOADER_ID_INSTALLED_APPS, null, this);
+            loaderManager.initLoader(LOADER_ID_DATABASE, null /* args */, this /* callback */);
+            loaderManager.initLoader(
+                    LOADER_ID_INSTALLED_APPS, null /* args */, this /* callback */);
+            loaderManager.initLoader(
+                    LOADER_ID_ACCESSIBILITY_SERVICES, null /* args */, this /* callback */);
         }
 
         requery();
@@ -382,6 +390,8 @@ public class SearchFragment extends InstrumentedFragment implements SearchView.O
         mUnfinishedLoadersCount.set(NUM_QUERY_LOADERS);
         loaderManager.restartLoader(LOADER_ID_DATABASE, null /* args */, this /* callback */);
         loaderManager.restartLoader(LOADER_ID_INSTALLED_APPS, null /* args */, this /* callback */);
+        loaderManager.restartLoader(LOADER_ID_ACCESSIBILITY_SERVICES, null /* args */,
+                this /* callback */);
     }
 
     public String getQuery() {
index 42e4bb5..6c58a0d 100644 (file)
@@ -56,6 +56,8 @@ public class SearchResultsAdapter extends RecyclerView.Adapter<SearchViewHolder>
 
     @VisibleForTesting
     static final String APP_RESULTS_LOADER_KEY = InstalledAppResultLoader.class.getName();
+    @VisibleForTesting
+    static final String ACCESSIBLITY_LOADER_KEY = AccessibilityServiceResultLoader.class.getName();
 
     @VisibleForTesting
     static final int MSG_RANKING_TIMED_OUT = 1;
@@ -262,11 +264,16 @@ public class SearchResultsAdapter extends RecyclerView.Adapter<SearchViewHolder>
                 getSortedLoadedResults(DB_RESULTS_LOADER_KEY);
         List<? extends SearchResult> installedAppResults =
                 getSortedLoadedResults(APP_RESULTS_LOADER_KEY);
+        List<? extends SearchResult> accessibilityResults =
+                getSortedLoadedResults(ACCESSIBLITY_LOADER_KEY);
+
         int dbSize = databaseResults.size();
         int appSize = installedAppResults.size();
+        int a11ySize = accessibilityResults.size();
 
         int dbIndex = 0;
         int appIndex = 0;
+        int a11yIndex = 0;
         int rank = SearchResult.TOP_RANK;
 
         mStaticallyRankedSearchResults.clear();
@@ -277,6 +284,9 @@ public class SearchResultsAdapter extends RecyclerView.Adapter<SearchViewHolder>
             while ((appIndex < appSize) && (installedAppResults.get(appIndex).rank == rank)) {
                 mStaticallyRankedSearchResults.add(installedAppResults.get(appIndex++));
             }
+            while ((a11yIndex < a11ySize) && (accessibilityResults.get(a11yIndex).rank == rank)) {
+                mStaticallyRankedSearchResults.add(accessibilityResults.get(a11yIndex++));
+            }
             rank++;
         }
 
@@ -286,6 +296,9 @@ public class SearchResultsAdapter extends RecyclerView.Adapter<SearchViewHolder>
         while (appIndex < appSize) {
             mStaticallyRankedSearchResults.add(installedAppResults.get(appIndex++));
         }
+        while(a11yIndex < a11ySize) {
+            mStaticallyRankedSearchResults.add(accessibilityResults.get(a11yIndex++));
+        }
     }
 
     private void updateSearchResults() {
@@ -318,10 +331,13 @@ public class SearchResultsAdapter extends RecyclerView.Adapter<SearchViewHolder>
                 getUnsortedLoadedResults(DB_RESULTS_LOADER_KEY);
         List<? extends SearchResult> installedAppResults =
                 getSortedLoadedResults(APP_RESULTS_LOADER_KEY);
+        List<? extends SearchResult> accessibilityResults =
+                getSortedLoadedResults(ACCESSIBLITY_LOADER_KEY);
         int dbSize = databaseResults.size();
         int appSize = installedAppResults.size();
+        int a11ySize = accessibilityResults.size();
 
-        final List<SearchResult> asyncRankingResults = new ArrayList<>(dbSize + appSize);
+        final List<SearchResult> asyncRankingResults = new ArrayList<>(dbSize + appSize + a11ySize);
         TreeSet<SearchResult> dbResultsSortedByScores = new TreeSet<>(
                 new Comparator<SearchResult>() {
                     @Override
@@ -339,8 +355,9 @@ public class SearchResultsAdapter extends RecyclerView.Adapter<SearchViewHolder>
                 });
         dbResultsSortedByScores.addAll(databaseResults);
         asyncRankingResults.addAll(dbResultsSortedByScores);
-        // App results are not ranked by async ranking and appended at the end of the list.
+        // Other results are not ranked by async ranking and appended at the end of the list.
         asyncRankingResults.addAll(installedAppResults);
+        asyncRankingResults.addAll(accessibilityResults);
         return asyncRankingResults;
     }
 
diff --git a/tests/robotests/src/com/android/settings/search/AccessibilityServiceResultLoaderTest.java b/tests/robotests/src/com/android/settings/search/AccessibilityServiceResultLoaderTest.java
new file mode 100644 (file)
index 0000000..4896dc4
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.search;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.settings.TestConfig;
+import com.android.settings.dashboard.SiteMapManager;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class AccessibilityServiceResultLoaderTest {
+
+    private static final String QUERY = "test_query";
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private AccessibilityManager mAccessibilityManager;
+    @Mock
+    private SiteMapManager mSiteMapManager;
+
+    private AccessibilityServiceResultLoader mLoader;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getSystemService(Context.ACCESSIBILITY_SERVICE))
+                .thenReturn(mAccessibilityManager);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+
+        mLoader = new AccessibilityServiceResultLoader(mContext, QUERY, mSiteMapManager);
+    }
+
+    @Test
+    public void query_noService_shouldNotReturnAnything() {
+        assertThat(mLoader.loadInBackground()).isEmpty();
+    }
+
+    @Test
+    public void query_hasServiceMatchingTitle_shouldReturnResult() {
+        addFakeAccessibilityService();
+
+        List<? extends SearchResult> results = new ArrayList<>(mLoader.loadInBackground());
+        assertThat(results).hasSize(1);
+
+        SearchResult result = results.get(0);
+        assertThat(result.title).isEqualTo(QUERY);
+    }
+
+    @Test
+    public void query_serviceDoesNotMatchTitle_shouldReturnResult() {
+        addFakeAccessibilityService();
+
+        mLoader = new AccessibilityServiceResultLoader(mContext,
+                QUERY + "no_match", mSiteMapManager);
+
+        assertThat(mLoader.loadInBackground()).isEmpty();
+    }
+
+    private void addFakeAccessibilityService() {
+        final List<AccessibilityServiceInfo> services = new ArrayList<>();
+        final AccessibilityServiceInfo info = mock(AccessibilityServiceInfo.class);
+        final ResolveInfo resolveInfo = mock(ResolveInfo.class);
+        when(info.getResolveInfo()).thenReturn(resolveInfo);
+        when(resolveInfo.loadIcon(mPackageManager)).thenReturn(new ColorDrawable(Color.BLUE));
+        when(resolveInfo.loadLabel(mPackageManager)).thenReturn(QUERY);
+        resolveInfo.serviceInfo = new ServiceInfo();
+        resolveInfo.serviceInfo.packageName = "pkg";
+        resolveInfo.serviceInfo.name = "class";
+        services.add(info);
+
+        when(mAccessibilityManager.getInstalledAccessibilityServiceList()).thenReturn(services);
+    }
+}
index 64a658e..26c89d5 100644 (file)
 
 package com.android.settings.search;
 
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.only;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.app.Activity;
 import android.app.Application;
@@ -40,15 +53,14 @@ import android.provider.UserDictionary;
 import android.view.inputmethod.InputMethodInfo;
 
 import com.android.internal.content.PackageMonitor;
-import com.android.settings.testutils.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
-import com.android.settings.accessibility.AccessibilitySettings;
 import com.android.settings.inputmethod.AvailableVirtualKeyboardFragment;
 import com.android.settings.inputmethod.PhysicalKeyboardFragment;
 import com.android.settings.inputmethod.VirtualKeyboardFragment;
 import com.android.settings.language.LanguageAndInputSettings;
 import com.android.settings.print.PrintSettingsFragment;
 import com.android.settings.testutils.DatabaseTestUtils;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
 import com.android.settings.testutils.shadow.ShadowActivityWithLoadManager;
 import com.android.settings.testutils.shadow.ShadowContextImplWithRegisterReceiver;
 import com.android.settings.testutils.shadow.ShadowInputManager;
@@ -77,19 +89,6 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.only;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
 @RunWith(SettingsRobolectricTestRunner.class)
 @Config(
         manifest = TestConfig.MANIFEST_PATH,
@@ -272,67 +271,6 @@ public class DynamicIndexableContentMonitorTest {
     }
 
     @Test
-    public void testAccessibilityServicesMonitor() throws Exception {
-        mMonitor.register(mActivity, LOADER_ID, mIndexManager, true /* isUserUnlocked */);
-
-        verifyIncrementalIndexing(AccessibilitySettings.class);
-
-        /*
-         * When an accessibility service package is installed, incremental indexing happen.
-         */
-        reset(mIndexManager);
-
-        installAccessibilityService(A11Y_PACKAGE_1);
-
-        verifyIncrementalIndexing(AccessibilitySettings.class);
-
-        /*
-         * When another accessibility service package is installed, incremental indexing happens.
-         */
-        reset(mIndexManager);
-
-        installAccessibilityService(A11Y_PACKAGE_2);
-
-        verifyIncrementalIndexing(AccessibilitySettings.class);
-
-        /*
-         * When an accessibility service is disabled, rebuild indexing happens.
-         */
-        reset(mIndexManager);
-
-        disableInstalledPackage(A11Y_PACKAGE_1);
-
-        verifyIncrementalIndexing(AccessibilitySettings.class);
-
-        /*
-         * When an accessibility service is enabled, incremental indexing happens.
-         */
-        reset(mIndexManager);
-
-        enableInstalledPackage(A11Y_PACKAGE_1);
-
-        verifyIncrementalIndexing(AccessibilitySettings.class);
-
-        /*
-         * When an accessibility service package is uninstalled, rebuild indexing happens.
-         */
-        reset(mIndexManager);
-
-        uninstallAccessibilityService(A11Y_PACKAGE_1);
-
-        verifyIncrementalIndexing(AccessibilitySettings.class);
-
-        /*
-         * When an input method service package is installed, nothing happens.
-         */
-        reset(mIndexManager);
-
-        installInputMethodService(IME_PACKAGE_1);
-
-        verifyNoIndexing(AccessibilitySettings.class);
-    }
-
-    @Test
     public void testInputMethodServicesMonitor() throws Exception {
         mMonitor.register(mActivity, LOADER_ID, mIndexManager, true /* isUserUnlocked */);
 
index d0a200d..19854fc 100644 (file)
 
 package com.android.settings.search;
 
+import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
+import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyList;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -26,12 +40,12 @@ import android.os.UserHandle;
 import android.os.UserManager;
 
 import com.android.settings.R;
-import com.android.settings.testutils.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
 import com.android.settings.applications.PackageManagerWrapper;
 import com.android.settings.dashboard.SiteMapManager;
 import com.android.settings.testutils.ApplicationTestUtils;
 import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -50,20 +64,6 @@ import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
 
-import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
-import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyList;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
 @RunWith(SettingsRobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
 public class InstalledAppResultLoaderTest {
@@ -82,8 +82,8 @@ public class InstalledAppResultLoaderTest {
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        FakeFeatureFactory.setupForTest(mContext);
-        FakeFeatureFactory factory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
+
+        final FakeFeatureFactory factory = FakeFeatureFactory.setupForTest(mContext);
         when(factory.searchFeatureProvider.getSiteMapManager())
                 .thenReturn(mSiteMapManager);
         final List<UserInfo> infos = new ArrayList<>();
diff --git a/tests/robotests/src/com/android/settings/search/MockAccessiblityLoader.java b/tests/robotests/src/com/android/settings/search/MockAccessiblityLoader.java
new file mode 100644 (file)
index 0000000..aa9b778
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.search;
+
+import android.content.Context;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class MockAccessiblityLoader extends AccessibilityServiceResultLoader {
+
+    public MockAccessiblityLoader(Context context) {
+        super(context, "test_query", null);
+    }
+
+    @Override
+    public Set<? extends SearchResult> loadInBackground() {
+        return new HashSet<>();
+    }
+
+    @Override
+    protected void onDiscardResult(Set<? extends SearchResult> result) {
+
+    }
+}
index bc05c16..6588805 100644 (file)
@@ -81,6 +81,8 @@ public class SearchFragmentTest {
     private DatabaseResultLoader mDatabaseResultLoader;
     @Mock
     private InstalledAppResultLoader mInstalledAppResultLoader;
+    @Mock
+    private AccessibilityServiceResultLoader mAccessibilityServiceResultLoader;
 
     @Mock
     private SavedQueryLoader mSavedQueryLoader;
@@ -113,6 +115,9 @@ public class SearchFragmentTest {
         when(mFeatureFactory.searchFeatureProvider
                 .getInstalledAppSearchLoader(any(Context.class), anyString()))
                 .thenReturn(mInstalledAppResultLoader);
+        when(mFeatureFactory.searchFeatureProvider
+                .getAccessibilityServiceResultLoader(any(Context.class), anyString()))
+                .thenReturn(mAccessibilityServiceResultLoader);
         when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
                 .thenReturn(mSavedQueryLoader);
 
@@ -170,6 +175,9 @@ public class SearchFragmentTest {
         when(mFeatureFactory.searchFeatureProvider
                 .getInstalledAppSearchLoader(any(Context.class), anyString()))
                 .thenReturn(mInstalledAppResultLoader);
+        when(mFeatureFactory.searchFeatureProvider
+                .getAccessibilityServiceResultLoader(any(Context.class), anyString()))
+                .thenReturn(mAccessibilityServiceResultLoader);
         when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
                 .thenReturn(mSavedQueryLoader);
 
@@ -225,6 +233,9 @@ public class SearchFragmentTest {
         when(mFeatureFactory.searchFeatureProvider
                 .getInstalledAppSearchLoader(any(Context.class), anyString()))
                 .thenReturn(mInstalledAppResultLoader);
+        when(mFeatureFactory.searchFeatureProvider
+                .getAccessibilityServiceResultLoader(any(Context.class), anyString()))
+                .thenReturn(mAccessibilityServiceResultLoader);
         when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
                 .thenReturn(mSavedQueryLoader);
         ActivityController<SearchActivity> activityController =
@@ -256,6 +267,9 @@ public class SearchFragmentTest {
         when(mFeatureFactory.searchFeatureProvider
                 .getInstalledAppSearchLoader(any(Context.class), anyString()))
                 .thenReturn(mInstalledAppResultLoader);
+        when(mFeatureFactory.searchFeatureProvider
+                .getAccessibilityServiceResultLoader(any(Context.class), anyString()))
+                .thenReturn(mAccessibilityServiceResultLoader);
         when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
                 .thenReturn(mSavedQueryLoader);
 
@@ -333,6 +347,9 @@ public class SearchFragmentTest {
         when(mFeatureFactory.searchFeatureProvider
                 .getInstalledAppSearchLoader(any(Context.class), anyString()))
                 .thenReturn(new MockAppLoader(RuntimeEnvironment.application));
+        when(mFeatureFactory.searchFeatureProvider
+                .getAccessibilityServiceResultLoader(any(Context.class), anyString()))
+                .thenReturn(new MockAccessiblityLoader(RuntimeEnvironment.application));
         when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
                 .thenReturn(mSavedQueryLoader);
         ActivityController<SearchActivity> activityController =