diff --git a/src/com/android/settings/search/DatabaseIndexingManager.java b/src/com/android/settings/search/DatabaseIndexingManager.java index 6a6c73763cd..d2b2d238ecd 100644 --- a/src/com/android/settings/search/DatabaseIndexingManager.java +++ b/src/com/android/settings/search/DatabaseIndexingManager.java @@ -166,18 +166,22 @@ public class DatabaseIndexingManager { */ public void performIndexing() { final Intent intent = new Intent(SearchIndexablesContract.PROVIDER_INTERFACE); - final List list = + final List providers = mContext.getPackageManager().queryIntentContentProviders(intent, 0); - String localeStr = Locale.getDefault().toString(); - String fingerprint = Build.FINGERPRINT; - final boolean isFullIndex = isFullIndex(localeStr, fingerprint); + final String localeStr = Locale.getDefault().toString(); + final String fingerprint = Build.FINGERPRINT; + final String providerVersionedNames = + IndexDatabaseHelper.buildProviderVersionedNames(providers); + + final boolean isFullIndex = IndexDatabaseHelper.isFullIndex(mContext, localeStr, + fingerprint, providerVersionedNames); if (isFullIndex) { rebuildDatabase(); } - for (final ResolveInfo info : list) { + for (final ResolveInfo info : providers) { if (!DatabaseIndexingUtils.isWellKnownProvider(info, mContext)) { continue; } @@ -192,24 +196,10 @@ public class DatabaseIndexingManager { updateDatabase(isFullIndex, localeStr); + //TODO(63922686): Setting indexed should be a single method, not 3 separate setters. IndexDatabaseHelper.setLocaleIndexed(mContext, localeStr); IndexDatabaseHelper.setBuildIndexed(mContext, fingerprint); - } - - /** - * Perform a full index on an OTA or when the locale has changed - * - * @param locale is the default for the device - * @param fingerprint id for the current build. - * @return true when the locale or build has changed since last index. - */ - @VisibleForTesting - boolean isFullIndex(String locale, String fingerprint) { - final boolean isLocaleIndexed = IndexDatabaseHelper.getInstance(mContext) - .isLocaleAlreadyIndexed(mContext, locale); - final boolean isBuildIndexed = IndexDatabaseHelper.getInstance(mContext) - .isBuildIndexed(mContext, fingerprint); - return !isLocaleIndexed || !isBuildIndexed; + IndexDatabaseHelper.setProvidersIndexed(mContext, providerVersionedNames); } /** @@ -1318,4 +1308,4 @@ public class DatabaseIndexingManager { } } } -} \ No newline at end of file +} diff --git a/src/com/android/settings/search/DatabaseIndexingUtils.java b/src/com/android/settings/search/DatabaseIndexingUtils.java index a6f3cb1ab1e..40ba7eeb203 100644 --- a/src/com/android/settings/search/DatabaseIndexingUtils.java +++ b/src/com/android/settings/search/DatabaseIndexingUtils.java @@ -174,7 +174,7 @@ public class DatabaseIndexingUtils { * - have read/write {@link Manifest.permission#READ_SEARCH_INDEXABLES} * - be from a privileged package */ - public static boolean isWellKnownProvider(ResolveInfo info, Context context) { + static boolean isWellKnownProvider(ResolveInfo info, Context context) { final String authority = info.providerInfo.authority; final String packageName = info.providerInfo.applicationInfo.packageName; @@ -197,7 +197,22 @@ public class DatabaseIndexingUtils { return isPrivilegedPackage(packageName, context); } - public static boolean isPrivilegedPackage(String packageName, Context context) { + static String normalizeHyphen(String input) { + return (input != null) ? input.replaceAll(NON_BREAKING_HYPHEN, HYPHEN) : EMPTY; + } + + static String normalizeString(String input) { + final String nohyphen = (input != null) ? input.replaceAll(HYPHEN, EMPTY) : EMPTY; + final String normalized = Normalizer.normalize(nohyphen, Normalizer.Form.NFD); + + return REMOVE_DIACRITICALS_PATTERN.matcher(normalized).replaceAll("").toLowerCase(); + } + + static String normalizeKeywords(String input) { + return (input != null) ? input.replaceAll(LIST_DELIMITERS, SPACE) : EMPTY; + } + + private static boolean isPrivilegedPackage(String packageName, Context context) { final PackageManager pm = context.getPackageManager(); try { PackageInfo packInfo = pm.getPackageInfo(packageName, 0); @@ -207,19 +222,4 @@ public class DatabaseIndexingUtils { return false; } } - - public static String normalizeHyphen(String input) { - return (input != null) ? input.replaceAll(NON_BREAKING_HYPHEN, HYPHEN) : EMPTY; - } - - public static String normalizeString(String input) { - final String nohyphen = (input != null) ? input.replaceAll(HYPHEN, EMPTY) : EMPTY; - final String normalized = Normalizer.normalize(nohyphen, Normalizer.Form.NFD); - - return REMOVE_DIACRITICALS_PATTERN.matcher(normalized).replaceAll("").toLowerCase(); - } - - public static String normalizeKeywords(String input) { - return (input != null) ? input.replaceAll(LIST_DELIMITERS, SPACE) : EMPTY; - } } diff --git a/src/com/android/settings/search/IndexDatabaseHelper.java b/src/com/android/settings/search/IndexDatabaseHelper.java index 76346ecc5cc..d78b6119410 100644 --- a/src/com/android/settings/search/IndexDatabaseHelper.java +++ b/src/com/android/settings/search/IndexDatabaseHelper.java @@ -17,12 +17,17 @@ package com.android.settings.search; import android.content.Context; +import android.content.pm.ResolveInfo; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.os.Build; +import android.support.annotation.VisibleForTesting; +import android.text.TextUtils; import android.util.Log; +import java.util.List; + public class IndexDatabaseHelper extends SQLiteOpenHelper { private static final String TAG = "IndexDatabaseHelper"; @@ -32,6 +37,8 @@ public class IndexDatabaseHelper extends SQLiteOpenHelper { private static final String INDEX = "index"; + private static final String PREF_KEY_INDEXED_PROVIDERS = "indexed_providers"; + public interface Tables { String TABLE_PREFS_INDEX = "prefs_index"; String TABLE_SITE_MAP = "site_map"; @@ -245,23 +252,69 @@ public class IndexDatabaseHelper extends SQLiteOpenHelper { return version; } - public static void clearCachedIndexed(Context context) { - context.getSharedPreferences(INDEX, 0).edit().clear().commit(); + /** + * Perform a full index on an OTA or when the locale has changed + * + * @param locale is the default for the device + * @param fingerprint id for the current build. + * @return true when the locale or build has changed since last index. + */ + @VisibleForTesting + static boolean isFullIndex(Context context, String locale, String fingerprint, + String providerVersionedNames) { + final boolean isLocaleIndexed = IndexDatabaseHelper.isLocaleAlreadyIndexed(context, locale); + final boolean isBuildIndexed = IndexDatabaseHelper.isBuildIndexed(context, fingerprint); + final boolean areProvidersIndexed = IndexDatabaseHelper + .areProvidersIndexed(context, providerVersionedNames); + + return !(isLocaleIndexed && isBuildIndexed && areProvidersIndexed); } - public static void setLocaleIndexed(Context context, String locale) { - context.getSharedPreferences(INDEX, 0).edit().putBoolean(locale, true).commit(); + @VisibleForTesting + static String buildProviderVersionedNames(List providers) { + StringBuilder sb = new StringBuilder(); + for (ResolveInfo info : providers) { + sb.append(info.providerInfo.packageName) + .append(':') + .append(info.providerInfo.applicationInfo.versionCode) + .append(','); + } + return sb.toString(); } - public static boolean isLocaleAlreadyIndexed(Context context, String locale) { - return context.getSharedPreferences(INDEX, 0).getBoolean(locale, false); + static void clearCachedIndexed(Context context) { + context.getSharedPreferences(INDEX, Context.MODE_PRIVATE).edit().clear().commit(); } - public static boolean isBuildIndexed(Context context, String buildNo) { - return context.getSharedPreferences(INDEX, 0).getBoolean(buildNo, false); + static void setLocaleIndexed(Context context, String locale) { + context.getSharedPreferences(INDEX, Context.MODE_PRIVATE) + .edit() + .putBoolean(locale, true) + .apply(); } - public static void setBuildIndexed(Context context, String buildNo) { + static void setProvidersIndexed(Context context, String providerVersionedNames) { + context.getSharedPreferences(INDEX, Context.MODE_PRIVATE) + .edit() + .putString(PREF_KEY_INDEXED_PROVIDERS, providerVersionedNames) + .apply(); + } + + static boolean isLocaleAlreadyIndexed(Context context, String locale) { + return context.getSharedPreferences(INDEX, Context.MODE_PRIVATE).getBoolean(locale, false); + } + + static boolean areProvidersIndexed(Context context, String providerVersionedNames) { + final String indexedProviders = context.getSharedPreferences(INDEX, Context.MODE_PRIVATE) + .getString(PREF_KEY_INDEXED_PROVIDERS, null); + return TextUtils.equals(indexedProviders, providerVersionedNames); + } + + static boolean isBuildIndexed(Context context, String buildNo) { + return context.getSharedPreferences(INDEX, Context.MODE_PRIVATE).getBoolean(buildNo, false); + } + + static void setBuildIndexed(Context context, String buildNo) { context.getSharedPreferences(INDEX, 0).edit().putBoolean(buildNo, true).commit(); } diff --git a/tests/robotests/src/com/android/settings/search/DatabaseIndexingManagerTest.java b/tests/robotests/src/com/android/settings/search/DatabaseIndexingManagerTest.java index 711b3558e2d..3641368cb96 100644 --- a/tests/robotests/src/com/android/settings/search/DatabaseIndexingManagerTest.java +++ b/tests/robotests/src/com/android/settings/search/DatabaseIndexingManagerTest.java @@ -17,12 +17,28 @@ package com.android.settings.search; +import static android.provider.SearchIndexablesContract.INDEXABLES_RAW_COLUMNS; +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.anyMap; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ContentProvider; import android.content.ContentValues; import android.content.Context; import android.content.Intent; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; @@ -35,10 +51,10 @@ import android.provider.SearchIndexableResource; import android.util.ArrayMap; import com.android.settings.R; -import com.android.settings.testutils.FakeFeatureFactory; -import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; import com.android.settings.testutils.DatabaseTestUtils; +import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.shadow.ShadowDatabaseIndexingUtils; import com.android.settings.testutils.shadow.ShadowRunnableAsyncTask; @@ -62,21 +78,6 @@ import java.util.Locale; import java.util.Map; import java.util.Set; -import static android.provider.SearchIndexablesContract.INDEXABLES_RAW_COLUMNS; -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.anyMap; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION, shadows = {ShadowRunnableAsyncTask.class}) @@ -746,7 +747,7 @@ public class DatabaseIndexingManagerTest { // Test new public indexing flow @Test - @Config(shadows = {ShadowDatabaseIndexingUtils.class,}) + @Config(shadows = {ShadowDatabaseIndexingUtils.class}) public void testPerformIndexing_fullIndex_getsDataFromProviders() { DummyProvider provider = new DummyProvider(); provider.onCreate(); @@ -758,7 +759,6 @@ public class DatabaseIndexingManagerTest { DatabaseIndexingManager manager = spy(new DatabaseIndexingManager(mContext, PACKAGE_ONE)); - doReturn(true).when(manager).isFullIndex(anyString(), anyString()); manager.performIndexing(); @@ -769,17 +769,17 @@ public class DatabaseIndexingManagerTest { @Test @Config(shadows = {ShadowDatabaseIndexingUtils.class,}) public void testPerformIndexing_incrementalIndex_noDataAdded() { + final List providerInfo = getDummyResolveInfo(); + skipFullIndex(providerInfo); DummyProvider provider = new DummyProvider(); provider.onCreate(); ShadowContentResolver.registerProvider(AUTHORITY_ONE, provider); - // Test that Indexables are added for Full indexing when(mPackageManager.queryIntentContentProviders(any(Intent.class), anyInt())) - .thenReturn(getDummyResolveInfo()); + .thenReturn(providerInfo); DatabaseIndexingManager manager = spy(new DatabaseIndexingManager(mContext, PACKAGE_ONE)); - doReturn(false).when(manager).isFullIndex(anyString(), anyString()); manager.mDataToProcess.dataToUpdate.clear(); @@ -805,7 +805,6 @@ public class DatabaseIndexingManagerTest { // Initialize the Manager DatabaseIndexingManager manager = spy(new DatabaseIndexingManager(mContext, PACKAGE_ONE)); - doReturn(true).when(manager).isFullIndex(anyString(), anyString()); // Insert data point which will be dropped final String oldTitle = "This is French"; @@ -845,13 +844,34 @@ public class DatabaseIndexingManagerTest { DatabaseIndexingManager manager = spy(new DatabaseIndexingManager(mContext, PACKAGE_ONE)); - doReturn(true).when(manager).isFullIndex(anyString(), anyString()); manager.performIndexing(); verify(manager).updateDatabase(true /* isFullIndex */, Locale.getDefault().toString()); } + @Test + @Config(shadows = {ShadowDatabaseIndexingUtils.class,}) + public void testPerformIndexing_onPackageChange_shouldFullIndex() { + final List providers = getDummyResolveInfo(); + final String buildNumber = Build.FINGERPRINT; + final String locale = Locale.getDefault().toString(); + skipFullIndex(providers); + + // This snapshot is already indexed. Should return false + assertThat(IndexDatabaseHelper.isFullIndex( + mContext, locale, buildNumber, + IndexDatabaseHelper.buildProviderVersionedNames(providers))) + .isFalse(); + + // Change provider version number, this should trigger full index. + providers.get(0).providerInfo.applicationInfo.versionCode++; + + assertThat(IndexDatabaseHelper.isFullIndex(mContext, locale, buildNumber, + IndexDatabaseHelper.buildProviderVersionedNames(providers))) + .isTrue(); + } + @Test @Config(shadows = {ShadowDatabaseIndexingUtils.class,}) public void testPerformIndexing_onOta_buildNumberIsCached() { @@ -867,7 +887,6 @@ public class DatabaseIndexingManagerTest { DatabaseIndexingManager manager = spy(new DatabaseIndexingManager(mContext, PACKAGE_ONE)); - doReturn(true).when(manager).isFullIndex(anyString(), anyString()); manager.performIndexing(); @@ -1037,6 +1056,13 @@ public class DatabaseIndexingManagerTest { // Util functions + private void skipFullIndex(List providers) { + IndexDatabaseHelper.setLocaleIndexed(mContext, Locale.getDefault().toString()); + IndexDatabaseHelper.setBuildIndexed(mContext, Build.FINGERPRINT); + IndexDatabaseHelper.setProvidersIndexed(mContext, + IndexDatabaseHelper.buildProviderVersionedNames(providers)); + } + private SearchIndexableRaw getFakeRaw() { return getFakeRaw(localeStr); } @@ -1092,6 +1118,7 @@ public class DatabaseIndexingManagerTest { info.providerInfo.exported = true; info.providerInfo.authority = AUTHORITY_ONE; info.providerInfo.packageName = PACKAGE_ONE; + info.providerInfo.applicationInfo = new ApplicationInfo(); infoList.add(info); return infoList;