Merge "Skip indexing dynamic and injected raw data when page search is disabled" into sc-dev
diff --git a/src/com/android/settings/search/BaseSearchIndexProvider.java b/src/com/android/settings/search/BaseSearchIndexProvider.java
index 3c2df96..581eb2e 100644
--- a/src/com/android/settings/search/BaseSearchIndexProvider.java
+++ b/src/com/android/settings/search/BaseSearchIndexProvider.java
@@ -80,6 +80,10 @@
     @CallSuper
     public List<SearchIndexableRaw> getDynamicRawDataToIndex(Context context, boolean enabled) {
         final List<SearchIndexableRaw> dynamicRaws = new ArrayList<>();
+        if (!isPageSearchEnabled(context)) {
+            // Entire page should be suppressed, do not add dynamic raw data.
+            return dynamicRaws;
+        }
         final List<AbstractPreferenceController> controllers = getPreferenceControllers(context);
         if (controllers == null || controllers.isEmpty()) {
             return dynamicRaws;
diff --git a/src/com/android/settings/search/SettingsSearchIndexablesProvider.java b/src/com/android/settings/search/SettingsSearchIndexablesProvider.java
index 736a270..feb9510 100644
--- a/src/com/android/settings/search/SettingsSearchIndexablesProvider.java
+++ b/src/com/android/settings/search/SettingsSearchIndexablesProvider.java
@@ -55,20 +55,22 @@
 import android.provider.SearchIndexablesProvider;
 import android.provider.SettingsSlicesContract;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
 
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 import androidx.slice.SliceViewManager;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.settings.R;
 import com.android.settings.SettingsActivity;
+import com.android.settings.dashboard.CategoryManager;
 import com.android.settings.dashboard.DashboardFeatureProvider;
+import com.android.settings.dashboard.DashboardFragmentRegistry;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.slices.SettingsSliceProvider;
 import com.android.settingslib.drawer.ActivityTile;
-import com.android.settingslib.drawer.CategoryKey;
 import com.android.settingslib.drawer.DashboardCategory;
 import com.android.settingslib.drawer.Tile;
 import com.android.settingslib.search.Indexable;
@@ -78,6 +80,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 
 public class SettingsSearchIndexablesProvider extends SearchIndexablesProvider {
 
@@ -94,6 +97,9 @@
 
     private static final Collection<String> INVALID_KEYS;
 
+    // Search enabled states for injection (key: category key, value: search enabled)
+    private Map<String, Boolean> mSearchEnabledByCategoryKeyMap;
+
     static {
         INVALID_KEYS = new ArraySet<>();
         INVALID_KEYS.add(null);
@@ -102,6 +108,7 @@
 
     @Override
     public boolean onCreate() {
+        mSearchEnabledByCategoryKeyMap = new ArrayMap<>();
         return true;
     }
 
@@ -166,7 +173,18 @@
     public Cursor queryDynamicRawData(String[] projection) {
         final Context context = getContext();
         final List<SearchIndexableRaw> rawList = new ArrayList<>();
-        rawList.addAll(getDynamicSearchIndexableRawFromProvider(context));
+        final Collection<SearchIndexableData> bundles = FeatureFactory.getFactory(context)
+                .getSearchFeatureProvider().getSearchIndexableResources().getProviderValues();
+
+        for (SearchIndexableData bundle : bundles) {
+            rawList.addAll(getDynamicSearchIndexableRawData(context, bundle));
+
+            // Refresh the search enabled state for indexing injection raw data
+            final Indexable.SearchIndexProvider provider = bundle.getSearchIndexProvider();
+            if (provider instanceof BaseSearchIndexProvider) {
+                refreshSearchEnabledState(context, (BaseSearchIndexProvider) provider);
+            }
+        }
         rawList.addAll(getInjectionIndexableRawData(context));
 
         final MatrixCursor cursor = new MatrixCursor(INDEXABLES_RAW_COLUMNS);
@@ -355,39 +373,35 @@
         return rawList;
     }
 
-    private List<SearchIndexableRaw> getDynamicSearchIndexableRawFromProvider(Context context) {
-        final Collection<SearchIndexableData> bundles = FeatureFactory.getFactory(context)
-                .getSearchFeatureProvider().getSearchIndexableResources().getProviderValues();
-        final List<SearchIndexableRaw> rawList = new ArrayList<>();
-
-        for (SearchIndexableData bundle : bundles) {
-            final Indexable.SearchIndexProvider provider = bundle.getSearchIndexProvider();
-            final List<SearchIndexableRaw> providerRaws =
-                    provider.getDynamicRawDataToIndex(context, true /* enabled */);
-
-            if (providerRaws == null) {
-                continue;
-            }
-
-            for (SearchIndexableRaw raw : providerRaws) {
-                // The classname and intent information comes from the PreIndexData
-                // This will be more clear when provider conversion is done at PreIndex time.
-                raw.className = bundle.getTargetClass().getName();
-
-            }
-            rawList.addAll(providerRaws);
+    private List<SearchIndexableRaw> getDynamicSearchIndexableRawData(Context context,
+            SearchIndexableData bundle) {
+        final Indexable.SearchIndexProvider provider = bundle.getSearchIndexProvider();
+        final List<SearchIndexableRaw> providerRaws =
+                provider.getDynamicRawDataToIndex(context, true /* enabled */);
+        if (providerRaws == null) {
+            return new ArrayList<>();
         }
 
-        return rawList;
+        for (SearchIndexableRaw raw : providerRaws) {
+            // The classname and intent information comes from the PreIndexData
+            // This will be more clear when provider conversion is done at PreIndex time.
+            raw.className = bundle.getTargetClass().getName();
+        }
+        return providerRaws;
     }
 
-    private List<SearchIndexableRaw> getInjectionIndexableRawData(Context context) {
+    @VisibleForTesting
+    List<SearchIndexableRaw> getInjectionIndexableRawData(Context context) {
         final DashboardFeatureProvider dashboardFeatureProvider =
                 FeatureFactory.getFactory(context).getDashboardFeatureProvider(context);
-
         final List<SearchIndexableRaw> rawList = new ArrayList<>();
         final String currentPackageName = context.getPackageName();
         for (DashboardCategory category : dashboardFeatureProvider.getAllCategories()) {
+            if (mSearchEnabledByCategoryKeyMap.containsKey(category.key)
+                    && !mSearchEnabledByCategoryKeyMap.get(category.key)) {
+                Log.i(TAG, "Skip indexing category: " + category.key);
+                continue;
+            }
             for (Tile tile : category.getTiles()) {
                 if (!isEligibleForIndexing(currentPackageName, tile)) {
                     continue;
@@ -411,16 +425,36 @@
     }
 
     @VisibleForTesting
+    void refreshSearchEnabledState(Context context, BaseSearchIndexProvider provider) {
+        // Provider's class name is like "com.android.settings.Settings$1"
+        String className = provider.getClass().getName();
+        final int delimiter = className.lastIndexOf("$");
+        if (delimiter > 0) {
+            // Parse the outer class name of this provider
+            className = className.substring(0, delimiter);
+        }
+
+        // Lookup the category key by the class name
+        final String categoryKey = DashboardFragmentRegistry.PARENT_TO_CATEGORY_KEY_MAP
+                .get(className);
+        if (categoryKey == null) {
+            return;
+        }
+
+        final DashboardCategory category = CategoryManager.get(context)
+                .getTilesByCategory(context, categoryKey);
+        if (category != null) {
+            mSearchEnabledByCategoryKeyMap.put(category.key, provider.isPageSearchEnabled(context));
+        }
+    }
+
+    @VisibleForTesting
     boolean isEligibleForIndexing(String packageName, Tile tile) {
         if (TextUtils.equals(packageName, tile.getPackageName())
                 && tile instanceof ActivityTile) {
             // Skip Settings injected items because they should be indexed in the sub-pages.
             return false;
         }
-        if (TextUtils.equals(tile.getCategory(), CategoryKey.CATEGORY_HOMEPAGE)) {
-            // Skip homepage injected items since we would like to index their target activity.
-            return false;
-        }
         return true;
     }
 
diff --git a/tests/robotests/src/com/android/settings/search/BaseSearchIndexProviderTest.java b/tests/robotests/src/com/android/settings/search/BaseSearchIndexProviderTest.java
index 5221630..09b1ea9 100644
--- a/tests/robotests/src/com/android/settings/search/BaseSearchIndexProviderTest.java
+++ b/tests/robotests/src/com/android/settings/search/BaseSearchIndexProviderTest.java
@@ -204,6 +204,16 @@
     }
 
     @Test
+    public void getDynamicRawDataToIndex_disablePageSearch_shouldReturnEmptyList() {
+        List<AbstractPreferenceController> controllers = new ArrayList<>();
+        controllers.add(new AvailablePreferenceController(mContext));
+        doReturn(controllers).when(mIndexProvider).createPreferenceControllers(mContext);
+        doReturn(false).when(mIndexProvider).isPageSearchEnabled(mContext);
+
+        assertThat(mIndexProvider.getDynamicRawDataToIndex(mContext, true)).isEmpty();
+    }
+
+    @Test
     public void getDynamicRawDataToIndex_hasDynamicRaw_shouldNotEmpty() {
         List<AbstractPreferenceController> controllers = new ArrayList<>();
         controllers.add(new AvailablePreferenceController(mContext));
diff --git a/tests/robotests/src/com/android/settings/search/SettingsSearchIndexablesProviderTest.java b/tests/robotests/src/com/android/settings/search/SettingsSearchIndexablesProviderTest.java
index 1fc0230..21b00a3 100644
--- a/tests/robotests/src/com/android/settings/search/SettingsSearchIndexablesProviderTest.java
+++ b/tests/robotests/src/com/android/settings/search/SettingsSearchIndexablesProviderTest.java
@@ -1,21 +1,31 @@
 package com.android.settings.search;
 
+import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
 
 import android.Manifest;
+import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ProviderInfo;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Bundle;
 import android.provider.SearchIndexablesContract;
 
 import com.android.settings.R;
+import com.android.settings.accounts.ManagedProfileSettings;
+import com.android.settings.dashboard.CategoryManager;
+import com.android.settings.homepage.TopLevelSettings;
+import com.android.settings.network.NetworkDashboardFragment;
 import com.android.settings.testutils.FakeFeatureFactory;
 import com.android.settingslib.drawer.ActivityTile;
 import com.android.settingslib.drawer.CategoryKey;
+import com.android.settingslib.drawer.DashboardCategory;
 import com.android.settingslib.search.SearchIndexableData;
 
 import org.junit.After;
@@ -25,21 +35,28 @@
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 @RunWith(RobolectricTestRunner.class)
+@Config(shadows = SettingsSearchIndexablesProviderTest.ShadowCategoryManager.class)
 public class SettingsSearchIndexablesProviderTest {
 
     private static final String PACKAGE_NAME = "com.android.settings";
     private static final String BASE_AUTHORITY = "content://" + PACKAGE_NAME + "/";
 
+    private Context mContext;
     private SettingsSearchIndexablesProvider mProvider;
     private FakeFeatureFactory mFakeFeatureFactory;
 
     @Before
     public void setUp() {
+        mContext = RuntimeEnvironment.application;
         mProvider = spy(new SettingsSearchIndexablesProvider());
         ProviderInfo info = new ProviderInfo();
         info.exported = true;
@@ -55,10 +72,22 @@
                         FakeSettingsFragment.SEARCH_INDEX_DATA_PROVIDER));
         mFakeFeatureFactory = FakeFeatureFactory.setupForTest();
         mFakeFeatureFactory.searchFeatureProvider = featureProvider;
+
+        final ActivityInfo activityInfo = new ActivityInfo();
+        activityInfo.packageName = "pkg";
+        activityInfo.name = "class";
+        activityInfo.metaData = new Bundle();
+        activityInfo.metaData.putString(META_DATA_PREFERENCE_TITLE, "title");
+        final DashboardCategory category = new DashboardCategory("key");
+        when(mFakeFeatureFactory.dashboardFeatureProvider.getAllCategories())
+                .thenReturn(Arrays.asList(category));
+        category.addTile(new ActivityTile(activityInfo, category.key));
+        ShadowCategoryManager.setDashboardCategory(category);
     }
 
     @After
     public void cleanUp() {
+        ShadowCategoryManager.reset();
         mFakeFeatureFactory.searchFeatureProvider = mock(SearchFeatureProvider.class);
     }
 
@@ -121,7 +150,41 @@
     }
 
     @Test
-    public void testIsEligibleForIndexing_isSettingsInjectedItem_ShouldBeFalse() {
+    public void refreshSearchEnabledState_classNotFoundInCategoryMap_hasInjectionRawData() {
+        mProvider.refreshSearchEnabledState(mContext,
+                ManagedProfileSettings.SEARCH_INDEX_DATA_PROVIDER);
+
+        assertThat(mProvider.getInjectionIndexableRawData(mContext)).isNotEmpty();
+    }
+
+    @Test
+    public void refreshSearchEnabledState_noDashboardCategory_hasInjectionRawData() {
+        ShadowCategoryManager.setDashboardCategory(null);
+
+        mProvider.refreshSearchEnabledState(mContext,
+                TopLevelSettings.SEARCH_INDEX_DATA_PROVIDER);
+
+        assertThat(mProvider.getInjectionIndexableRawData(mContext)).isNotEmpty();
+    }
+
+    @Test
+    public void refreshSearchEnabledState_pageSearchEnabled_hasInjectionRawData() {
+        mProvider.refreshSearchEnabledState(mContext,
+                NetworkDashboardFragment.SEARCH_INDEX_DATA_PROVIDER);
+
+        assertThat(mProvider.getInjectionIndexableRawData(mContext)).isNotEmpty();
+    }
+
+    @Test
+    public void refreshSearchEnabledState_pageSearchDisable_noInjectionRawData() {
+        mProvider.refreshSearchEnabledState(mContext,
+                TopLevelSettings.SEARCH_INDEX_DATA_PROVIDER);
+
+        assertThat(mProvider.getInjectionIndexableRawData(mContext)).isEmpty();
+    }
+
+    @Test
+    public void isEligibleForIndexing_isSettingsInjectedItem_shouldReturnFalse() {
         final ActivityInfo activityInfo = new ActivityInfo();
         activityInfo.packageName = PACKAGE_NAME;
         activityInfo.name = "class";
@@ -132,18 +195,7 @@
     }
 
     @Test
-    public void testIsEligibleForIndexing_isHomepageInjectedItem_ShouldBeFalse() {
-        final ActivityInfo activityInfo = new ActivityInfo();
-        activityInfo.packageName = "pkg";
-        activityInfo.name = "class";
-        final ActivityTile activityTile = new ActivityTile(activityInfo,
-                CategoryKey.CATEGORY_HOMEPAGE);
-
-        assertThat(mProvider.isEligibleForIndexing(PACKAGE_NAME, activityTile)).isFalse();
-    }
-
-    @Test
-    public void testIsEligibleForIndexing_normalInjectedItem_ShouldBeTrue() {
+    public void isEligibleForIndexing_normalInjectedItem_shouldReturnTrue() {
         final ActivityInfo activityInfo = new ActivityInfo();
         activityInfo.packageName = "pkg";
         activityInfo.name = "class";
@@ -152,4 +204,24 @@
 
         assertThat(mProvider.isEligibleForIndexing(PACKAGE_NAME, activityTile)).isTrue();
     }
+
+    @Implements(CategoryManager.class)
+    public static class ShadowCategoryManager {
+
+        private static DashboardCategory sCategory;
+
+        @Resetter
+        static void reset() {
+            sCategory = null;
+        }
+
+        @Implementation
+        public DashboardCategory getTilesByCategory(Context context, String categoryKey) {
+            return sCategory;
+        }
+
+        static void setDashboardCategory(DashboardCategory category) {
+            sCategory = category;
+        }
+    }
 }