diff --git a/src/com/android/settings/search/BaseSearchIndexProvider.java b/src/com/android/settings/search/BaseSearchIndexProvider.java index 3c2df9627e0..581eb2eb538 100644 --- a/src/com/android/settings/search/BaseSearchIndexProvider.java +++ b/src/com/android/settings/search/BaseSearchIndexProvider.java @@ -80,6 +80,10 @@ public class BaseSearchIndexProvider implements Indexable.SearchIndexProvider { @CallSuper public List getDynamicRawDataToIndex(Context context, boolean enabled) { final List dynamicRaws = new ArrayList<>(); + if (!isPageSearchEnabled(context)) { + // Entire page should be suppressed, do not add dynamic raw data. + return dynamicRaws; + } final List 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 736a27016fb..feb9510bff9 100644 --- a/src/com/android/settings/search/SettingsSearchIndexablesProvider.java +++ b/src/com/android/settings/search/SettingsSearchIndexablesProvider.java @@ -55,20 +55,22 @@ import android.provider.SearchIndexablesContract; 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 com.android.settingslib.search.SearchIndexableRaw; 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 @@ public class SettingsSearchIndexablesProvider extends SearchIndexablesProvider { private static final Collection INVALID_KEYS; + // Search enabled states for injection (key: category key, value: search enabled) + private Map mSearchEnabledByCategoryKeyMap; + static { INVALID_KEYS = new ArraySet<>(); INVALID_KEYS.add(null); @@ -102,6 +108,7 @@ public class SettingsSearchIndexablesProvider extends SearchIndexablesProvider { @Override public boolean onCreate() { + mSearchEnabledByCategoryKeyMap = new ArrayMap<>(); return true; } @@ -166,7 +173,18 @@ public class SettingsSearchIndexablesProvider extends SearchIndexablesProvider { public Cursor queryDynamicRawData(String[] projection) { final Context context = getContext(); final List rawList = new ArrayList<>(); - rawList.addAll(getDynamicSearchIndexableRawFromProvider(context)); + final Collection 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 @@ public class SettingsSearchIndexablesProvider extends SearchIndexablesProvider { return rawList; } - private List getDynamicSearchIndexableRawFromProvider(Context context) { - final Collection bundles = FeatureFactory.getFactory(context) - .getSearchFeatureProvider().getSearchIndexableResources().getProviderValues(); - final List rawList = new ArrayList<>(); - - for (SearchIndexableData bundle : bundles) { - final Indexable.SearchIndexProvider provider = bundle.getSearchIndexProvider(); - final List 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 getDynamicSearchIndexableRawData(Context context, + SearchIndexableData bundle) { + final Indexable.SearchIndexProvider provider = bundle.getSearchIndexProvider(); + final List 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 getInjectionIndexableRawData(Context context) { + @VisibleForTesting + List getInjectionIndexableRawData(Context context) { final DashboardFeatureProvider dashboardFeatureProvider = FeatureFactory.getFactory(context).getDashboardFeatureProvider(context); - final List 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; @@ -410,6 +424,30 @@ public class SettingsSearchIndexablesProvider extends SearchIndexablesProvider { return rawList; } + @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()) @@ -417,10 +455,6 @@ public class SettingsSearchIndexablesProvider extends SearchIndexablesProvider { // 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 522163079f2..09b1ea944a5 100644 --- a/tests/robotests/src/com/android/settings/search/BaseSearchIndexProviderTest.java +++ b/tests/robotests/src/com/android/settings/search/BaseSearchIndexProviderTest.java @@ -203,6 +203,16 @@ public class BaseSearchIndexProviderTest { assertThat(mIndexProvider.getDynamicRawDataToIndex(mContext, true)).isEmpty(); } + @Test + public void getDynamicRawDataToIndex_disablePageSearch_shouldReturnEmptyList() { + List 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 controllers = new ArrayList<>(); diff --git a/tests/robotests/src/com/android/settings/search/SettingsSearchIndexablesProviderTest.java b/tests/robotests/src/com/android/settings/search/SettingsSearchIndexablesProviderTest.java index 1fc0230efad..21b00a336a9 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.junit.runner.RunWith; 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 @@ public class SettingsSearchIndexablesProviderTest { 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 @@ public class SettingsSearchIndexablesProviderTest { } @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 @@ public class SettingsSearchIndexablesProviderTest { } @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 @@ public class SettingsSearchIndexablesProviderTest { 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; + } + } }