*/
public void performIndexing() {
final Intent intent = new Intent(SearchIndexablesContract.PROVIDER_INTERFACE);
- final List<ResolveInfo> list =
+ final List<ResolveInfo> 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;
}
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);
}
/**
}
}
}
-}
\ No newline at end of file
+}
* - 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;
return isPrivilegedPackage(packageName, context);
}
- public static boolean isPrivilegedPackage(String packageName, Context context) {
- final PackageManager pm = context.getPackageManager();
- try {
- PackageInfo packInfo = pm.getPackageInfo(packageName, 0);
- return ((packInfo.applicationInfo.privateFlags
- & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0);
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
- }
-
- public static String normalizeHyphen(String input) {
+ static String normalizeHyphen(String input) {
return (input != null) ? input.replaceAll(NON_BREAKING_HYPHEN, HYPHEN) : EMPTY;
}
- public static String normalizeString(String input) {
+ 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) {
+ 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);
+ return ((packInfo.applicationInfo.privateFlags
+ & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
}
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";
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";
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);
+ }
+
+ @VisibleForTesting
+ 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();
+ }
+
+ static void clearCachedIndexed(Context context) {
+ context.getSharedPreferences(INDEX, Context.MODE_PRIVATE).edit().clear().commit();
+ }
+
+ static void setLocaleIndexed(Context context, String locale) {
+ context.getSharedPreferences(INDEX, Context.MODE_PRIVATE)
+ .edit()
+ .putBoolean(locale, true)
+ .apply();
+ }
+
+ static void setProvidersIndexed(Context context, String providerVersionedNames) {
+ context.getSharedPreferences(INDEX, Context.MODE_PRIVATE)
+ .edit()
+ .putString(PREF_KEY_INDEXED_PROVIDERS, providerVersionedNames)
+ .apply();
}
- public static void setLocaleIndexed(Context context, String locale) {
- context.getSharedPreferences(INDEX, 0).edit().putBoolean(locale, true).commit();
+ static boolean isLocaleAlreadyIndexed(Context context, String locale) {
+ return context.getSharedPreferences(INDEX, Context.MODE_PRIVATE).getBoolean(locale, false);
}
- public static boolean isLocaleAlreadyIndexed(Context context, String locale) {
- return context.getSharedPreferences(INDEX, 0).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);
}
- public static boolean isBuildIndexed(Context context, String buildNo) {
- return context.getSharedPreferences(INDEX, 0).getBoolean(buildNo, false);
+ static boolean isBuildIndexed(Context context, String buildNo) {
+ return context.getSharedPreferences(INDEX, Context.MODE_PRIVATE).getBoolean(buildNo, false);
}
- public static void setBuildIndexed(Context context, String buildNo) {
+ static void setBuildIndexed(Context context, String buildNo) {
context.getSharedPreferences(INDEX, 0).edit().putBoolean(buildNo, true).commit();
}
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;
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;
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})
// 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();
DatabaseIndexingManager manager =
spy(new DatabaseIndexingManager(mContext, PACKAGE_ONE));
- doReturn(true).when(manager).isFullIndex(anyString(), anyString());
manager.performIndexing();
@Test
@Config(shadows = {ShadowDatabaseIndexingUtils.class,})
public void testPerformIndexing_incrementalIndex_noDataAdded() {
+ final List<ResolveInfo> 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();
// 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";
DatabaseIndexingManager manager =
spy(new DatabaseIndexingManager(mContext, PACKAGE_ONE));
- doReturn(true).when(manager).isFullIndex(anyString(), anyString());
manager.performIndexing();
@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
+ @Config(shadows = {ShadowDatabaseIndexingUtils.class,})
public void testPerformIndexing_onOta_buildNumberIsCached() {
DummyProvider provider = new DummyProvider();
provider.onCreate();
DatabaseIndexingManager manager =
spy(new DatabaseIndexingManager(mContext, PACKAGE_ONE));
- doReturn(true).when(manager).isFullIndex(anyString(), anyString());
manager.performIndexing();
// 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() {
return getFakeRaw(localeStr);
}
info.providerInfo.exported = true;
info.providerInfo.authority = AUTHORITY_ONE;
info.providerInfo.packageName = PACKAGE_ONE;
+ info.providerInfo.applicationInfo = new ApplicationInfo();
infoList.add(info);
return infoList;