Reindex db when package w/ searchIndexProvider changes

We do this by tracking a list of package/version that provides search
indexing data. When they change, we do a full reindex.

Change-Id: I906a1524f5b1292932f63727d605283ddb7d6ee2
Bug: 63903835
Test: robotests
This commit is contained in:
Fan Zhang
2017-07-20 17:05:01 -07:00
parent a39daa25e7
commit f8d95fa35d
4 changed files with 144 additions and 73 deletions

View File

@@ -169,18 +169,22 @@ public class DatabaseIndexingManager {
public void performIndexing() { public void performIndexing() {
final long startTime = System.currentTimeMillis(); final long startTime = System.currentTimeMillis();
final Intent intent = new Intent(SearchIndexablesContract.PROVIDER_INTERFACE); final Intent intent = new Intent(SearchIndexablesContract.PROVIDER_INTERFACE);
final List<ResolveInfo> list = final List<ResolveInfo> providers =
mContext.getPackageManager().queryIntentContentProviders(intent, 0); mContext.getPackageManager().queryIntentContentProviders(intent, 0);
String localeStr = Locale.getDefault().toString(); final String localeStr = Locale.getDefault().toString();
String fingerprint = Build.FINGERPRINT; final String fingerprint = Build.FINGERPRINT;
final boolean isFullIndex = isFullIndex(localeStr, fingerprint); final String providerVersionedNames =
IndexDatabaseHelper.buildProviderVersionedNames(providers);
final boolean isFullIndex = IndexDatabaseHelper.isFullIndex(mContext, localeStr,
fingerprint, providerVersionedNames);
if (isFullIndex) { if (isFullIndex) {
rebuildDatabase(); rebuildDatabase();
} }
for (final ResolveInfo info : list) { for (final ResolveInfo info : providers) {
if (!DatabaseIndexingUtils.isWellKnownProvider(info, mContext)) { if (!DatabaseIndexingUtils.isWellKnownProvider(info, mContext)) {
continue; continue;
} }
@@ -205,8 +209,11 @@ public class DatabaseIndexingManager {
Log.d(LOG_TAG, "performIndexing updateDatabase took time: " + updateDatabaseTime); Log.d(LOG_TAG, "performIndexing updateDatabase took time: " + updateDatabaseTime);
} }
//TODO(63922686): Setting indexed should be a single method, not 3 separate setters.
IndexDatabaseHelper.setLocaleIndexed(mContext, localeStr); IndexDatabaseHelper.setLocaleIndexed(mContext, localeStr);
IndexDatabaseHelper.setBuildIndexed(mContext, fingerprint); IndexDatabaseHelper.setBuildIndexed(mContext, fingerprint);
IndexDatabaseHelper.setProvidersIndexed(mContext, providerVersionedNames);
if (SettingsSearchIndexablesProvider.DEBUG) { if (SettingsSearchIndexablesProvider.DEBUG) {
final long indexingTime = System.currentTimeMillis() - startTime; final long indexingTime = System.currentTimeMillis() - startTime;
Log.d(LOG_TAG, "performIndexing took time: " + indexingTime Log.d(LOG_TAG, "performIndexing took time: " + indexingTime
@@ -214,22 +221,6 @@ public class DatabaseIndexingManager {
} }
} }
/**
* 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;
}
/** /**
* Reconstruct the database in the following cases: * Reconstruct the database in the following cases:
* - Language has changed * - Language has changed
@@ -1339,4 +1330,4 @@ public class DatabaseIndexingManager {
} }
} }
} }
} }

View File

@@ -180,7 +180,7 @@ public class DatabaseIndexingUtils {
* - have read/write {@link Manifest.permission#READ_SEARCH_INDEXABLES} * - have read/write {@link Manifest.permission#READ_SEARCH_INDEXABLES}
* - be from a privileged package * - 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 authority = info.providerInfo.authority;
final String packageName = info.providerInfo.applicationInfo.packageName; final String packageName = info.providerInfo.applicationInfo.packageName;
@@ -203,7 +203,22 @@ public class DatabaseIndexingUtils {
return isPrivilegedPackage(packageName, context); 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(); final PackageManager pm = context.getPackageManager();
try { try {
PackageInfo packInfo = pm.getPackageInfo(packageName, 0); PackageInfo packInfo = pm.getPackageInfo(packageName, 0);
@@ -213,19 +228,4 @@ public class DatabaseIndexingUtils {
return false; 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;
}
} }

View File

@@ -17,12 +17,17 @@
package com.android.settings.search; package com.android.settings.search;
import android.content.Context; import android.content.Context;
import android.content.pm.ResolveInfo;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import android.os.Build; import android.os.Build;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import java.util.List;
public class IndexDatabaseHelper extends SQLiteOpenHelper { public class IndexDatabaseHelper extends SQLiteOpenHelper {
private static final String TAG = "IndexDatabaseHelper"; 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 INDEX = "index";
private static final String PREF_KEY_INDEXED_PROVIDERS = "indexed_providers";
public interface Tables { public interface Tables {
String TABLE_PREFS_INDEX = "prefs_index"; String TABLE_PREFS_INDEX = "prefs_index";
String TABLE_SITE_MAP = "site_map"; String TABLE_SITE_MAP = "site_map";
@@ -245,23 +252,69 @@ public class IndexDatabaseHelper extends SQLiteOpenHelper {
return version; 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) { @VisibleForTesting
context.getSharedPreferences(INDEX, 0).edit().putBoolean(locale, true).commit(); static String buildProviderVersionedNames(List<ResolveInfo> 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) { static void clearCachedIndexed(Context context) {
return context.getSharedPreferences(INDEX, 0).getBoolean(locale, false); context.getSharedPreferences(INDEX, Context.MODE_PRIVATE).edit().clear().commit();
} }
public static boolean isBuildIndexed(Context context, String buildNo) { static void setLocaleIndexed(Context context, String locale) {
return context.getSharedPreferences(INDEX, 0).getBoolean(buildNo, false); 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(); context.getSharedPreferences(INDEX, 0).edit().putBoolean(buildNo, true).commit();
} }

View File

@@ -17,12 +17,28 @@
package com.android.settings.search; 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.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.content.ContentProvider; import android.content.ContentProvider;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo; import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo;
@@ -35,10 +51,10 @@ import android.provider.SearchIndexableResource;
import android.util.ArrayMap; import android.util.ArrayMap;
import com.android.settings.R; 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.TestConfig;
import com.android.settings.testutils.DatabaseTestUtils; 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.ShadowDatabaseIndexingUtils;
import com.android.settings.testutils.shadow.ShadowRunnableAsyncTask; import com.android.settings.testutils.shadow.ShadowRunnableAsyncTask;
@@ -62,21 +78,6 @@ import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; 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) @RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION, @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
shadows = {ShadowRunnableAsyncTask.class}) shadows = {ShadowRunnableAsyncTask.class})
@@ -746,7 +747,7 @@ public class DatabaseIndexingManagerTest {
// Test new public indexing flow // Test new public indexing flow
@Test @Test
@Config(shadows = {ShadowDatabaseIndexingUtils.class,}) @Config(shadows = {ShadowDatabaseIndexingUtils.class})
public void testPerformIndexing_fullIndex_getsDataFromProviders() { public void testPerformIndexing_fullIndex_getsDataFromProviders() {
DummyProvider provider = new DummyProvider(); DummyProvider provider = new DummyProvider();
provider.onCreate(); provider.onCreate();
@@ -758,7 +759,6 @@ public class DatabaseIndexingManagerTest {
DatabaseIndexingManager manager = DatabaseIndexingManager manager =
spy(new DatabaseIndexingManager(mContext, PACKAGE_ONE)); spy(new DatabaseIndexingManager(mContext, PACKAGE_ONE));
doReturn(true).when(manager).isFullIndex(anyString(), anyString());
manager.performIndexing(); manager.performIndexing();
@@ -769,17 +769,17 @@ public class DatabaseIndexingManagerTest {
@Test @Test
@Config(shadows = {ShadowDatabaseIndexingUtils.class,}) @Config(shadows = {ShadowDatabaseIndexingUtils.class,})
public void testPerformIndexing_incrementalIndex_noDataAdded() { public void testPerformIndexing_incrementalIndex_noDataAdded() {
final List<ResolveInfo> providerInfo = getDummyResolveInfo();
skipFullIndex(providerInfo);
DummyProvider provider = new DummyProvider(); DummyProvider provider = new DummyProvider();
provider.onCreate(); provider.onCreate();
ShadowContentResolver.registerProvider(AUTHORITY_ONE, provider); ShadowContentResolver.registerProvider(AUTHORITY_ONE, provider);
// Test that Indexables are added for Full indexing // Test that Indexables are added for Full indexing
when(mPackageManager.queryIntentContentProviders(any(Intent.class), anyInt())) when(mPackageManager.queryIntentContentProviders(any(Intent.class), anyInt()))
.thenReturn(getDummyResolveInfo()); .thenReturn(providerInfo);
DatabaseIndexingManager manager = DatabaseIndexingManager manager =
spy(new DatabaseIndexingManager(mContext, PACKAGE_ONE)); spy(new DatabaseIndexingManager(mContext, PACKAGE_ONE));
doReturn(false).when(manager).isFullIndex(anyString(), anyString());
manager.mDataToProcess.dataToUpdate.clear(); manager.mDataToProcess.dataToUpdate.clear();
@@ -805,7 +805,6 @@ public class DatabaseIndexingManagerTest {
// Initialize the Manager // Initialize the Manager
DatabaseIndexingManager manager = DatabaseIndexingManager manager =
spy(new DatabaseIndexingManager(mContext, PACKAGE_ONE)); spy(new DatabaseIndexingManager(mContext, PACKAGE_ONE));
doReturn(true).when(manager).isFullIndex(anyString(), anyString());
// Insert data point which will be dropped // Insert data point which will be dropped
final String oldTitle = "This is French"; final String oldTitle = "This is French";
@@ -845,13 +844,34 @@ public class DatabaseIndexingManagerTest {
DatabaseIndexingManager manager = DatabaseIndexingManager manager =
spy(new DatabaseIndexingManager(mContext, PACKAGE_ONE)); spy(new DatabaseIndexingManager(mContext, PACKAGE_ONE));
doReturn(true).when(manager).isFullIndex(anyString(), anyString());
manager.performIndexing(); manager.performIndexing();
verify(manager).updateDatabase(true /* isFullIndex */, Locale.getDefault().toString()); verify(manager).updateDatabase(true /* isFullIndex */, Locale.getDefault().toString());
} }
@Test
@Config(shadows = {ShadowDatabaseIndexingUtils.class,})
public void testPerformIndexing_onPackageChange_shouldFullIndex() {
final List<ResolveInfo> 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 @Test
@Config(shadows = {ShadowDatabaseIndexingUtils.class,}) @Config(shadows = {ShadowDatabaseIndexingUtils.class,})
public void testPerformIndexing_onOta_buildNumberIsCached() { public void testPerformIndexing_onOta_buildNumberIsCached() {
@@ -867,7 +887,6 @@ public class DatabaseIndexingManagerTest {
DatabaseIndexingManager manager = DatabaseIndexingManager manager =
spy(new DatabaseIndexingManager(mContext, PACKAGE_ONE)); spy(new DatabaseIndexingManager(mContext, PACKAGE_ONE));
doReturn(true).when(manager).isFullIndex(anyString(), anyString());
manager.performIndexing(); manager.performIndexing();
@@ -1037,6 +1056,13 @@ public class DatabaseIndexingManagerTest {
// Util functions // Util functions
private void skipFullIndex(List<ResolveInfo> providers) {
IndexDatabaseHelper.setLocaleIndexed(mContext, Locale.getDefault().toString());
IndexDatabaseHelper.setBuildIndexed(mContext, Build.FINGERPRINT);
IndexDatabaseHelper.setProvidersIndexed(mContext,
IndexDatabaseHelper.buildProviderVersionedNames(providers));
}
private SearchIndexableRaw getFakeRaw() { private SearchIndexableRaw getFakeRaw() {
return getFakeRaw(localeStr); return getFakeRaw(localeStr);
} }
@@ -1092,6 +1118,7 @@ public class DatabaseIndexingManagerTest {
info.providerInfo.exported = true; info.providerInfo.exported = true;
info.providerInfo.authority = AUTHORITY_ONE; info.providerInfo.authority = AUTHORITY_ONE;
info.providerInfo.packageName = PACKAGE_ONE; info.providerInfo.packageName = PACKAGE_ONE;
info.providerInfo.applicationInfo = new ApplicationInfo();
infoList.add(info); infoList.add(info);
return infoList; return infoList;