Merge "SearchFragment removes stale data from the database"
This commit is contained in:
committed by
Android (Google) Code Review
commit
54c962f55e
@@ -27,7 +27,6 @@ import android.content.res.XmlResourceParser;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.database.sqlite.SQLiteFullException;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.provider.SearchIndexableData;
|
||||
@@ -53,10 +52,12 @@ import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE;
|
||||
@@ -82,9 +83,40 @@ import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INT
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_RANK;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_RESID;
|
||||
|
||||
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.CLASS_NAME;
|
||||
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DATA_ENTRIES;
|
||||
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DATA_KEYWORDS;
|
||||
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DATA_KEY_REF;
|
||||
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DATA_RANK;
|
||||
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_OFF;
|
||||
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_OFF_NORMALIZED;
|
||||
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON;
|
||||
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON_NORMALIZED;
|
||||
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DATA_TITLE;
|
||||
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DATA_TITLE_NORMALIZED;
|
||||
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.DOCID;
|
||||
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.ENABLED;
|
||||
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.ICON;
|
||||
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.INTENT_ACTION;
|
||||
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.INTENT_TARGET_CLASS;
|
||||
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.INTENT_TARGET_PACKAGE;
|
||||
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.LOCALE;
|
||||
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.PAYLOAD;
|
||||
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.PAYLOAD_TYPE;
|
||||
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.SCREEN_TITLE;
|
||||
import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.USER_ID;
|
||||
import static com.android.settings.search.IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX;
|
||||
|
||||
import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_ID;
|
||||
import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE;
|
||||
import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_KEY;
|
||||
import static com.android.settings.search2.DatabaseResultLoader.SELECT_COLUMNS;
|
||||
|
||||
/**
|
||||
* Consumes the SearchIndexableProvider content providers.
|
||||
* Updates the Resource, Raw Data and non-indexable data for Search.
|
||||
*
|
||||
* TODO this class needs to be refactored by moving most of its methods into controllers
|
||||
*/
|
||||
public class DatabaseIndexingManager {
|
||||
private static final String LOG_TAG = "DatabaseIndexingManager";
|
||||
@@ -93,51 +125,14 @@ public class DatabaseIndexingManager {
|
||||
private static final String NODE_NAME_CHECK_BOX_PREFERENCE = "CheckBoxPreference";
|
||||
private static final String NODE_NAME_LIST_PREFERENCE = "ListPreference";
|
||||
|
||||
private static final List<String> EMPTY_LIST = Collections.<String>emptyList();
|
||||
private static final List<String> EMPTY_LIST = Collections.emptyList();
|
||||
|
||||
private final String mBaseAuthority;
|
||||
|
||||
/**
|
||||
* A private class to describe the update data for the Index database
|
||||
*/
|
||||
private static class UpdateData {
|
||||
public List<SearchIndexableData> dataToUpdate;
|
||||
public List<SearchIndexableData> dataToDelete;
|
||||
public Map<String, List<String>> nonIndexableKeys;
|
||||
|
||||
public boolean forceUpdate;
|
||||
public boolean fullIndex;
|
||||
|
||||
public UpdateData() {
|
||||
dataToUpdate = new ArrayList<>();
|
||||
dataToDelete = new ArrayList<>();
|
||||
nonIndexableKeys = new HashMap<>();
|
||||
}
|
||||
|
||||
public UpdateData(DatabaseIndexingManager.UpdateData other) {
|
||||
dataToUpdate = new ArrayList<>(other.dataToUpdate);
|
||||
dataToDelete = new ArrayList<>(other.dataToDelete);
|
||||
nonIndexableKeys = new HashMap<>(other.nonIndexableKeys);
|
||||
forceUpdate = other.forceUpdate;
|
||||
fullIndex = other.fullIndex;
|
||||
}
|
||||
|
||||
public DatabaseIndexingManager.UpdateData copy() {
|
||||
return new DatabaseIndexingManager.UpdateData(this);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
dataToUpdate.clear();
|
||||
dataToDelete.clear();
|
||||
nonIndexableKeys.clear();
|
||||
forceUpdate = false;
|
||||
fullIndex = false;
|
||||
}
|
||||
}
|
||||
|
||||
private final AtomicBoolean mIsAvailable = new AtomicBoolean(false);
|
||||
private final DatabaseIndexingManager.UpdateData mDataToProcess =
|
||||
new DatabaseIndexingManager.UpdateData();
|
||||
|
||||
@VisibleForTesting
|
||||
final UpdateData mDataToProcess = new UpdateData();
|
||||
private Context mContext;
|
||||
|
||||
public DatabaseIndexingManager(Context context, String baseAuthority) {
|
||||
@@ -157,31 +152,201 @@ public class DatabaseIndexingManager {
|
||||
AsyncTask.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final Intent intent = new Intent(SearchIndexablesContract.PROVIDER_INTERFACE);
|
||||
List<ResolveInfo> list =
|
||||
mContext.getPackageManager().queryIntentContentProviders(intent, 0);
|
||||
|
||||
final int size = list.size();
|
||||
for (int n = 0; n < size; n++) {
|
||||
final ResolveInfo info = list.get(n);
|
||||
if (!DatabaseIndexingUtils.isWellKnownProvider(info, mContext)) {
|
||||
continue;
|
||||
}
|
||||
final String authority = info.providerInfo.authority;
|
||||
final String packageName = info.providerInfo.packageName;
|
||||
|
||||
addIndexablesFromRemoteProvider(packageName, authority);
|
||||
addNonIndexablesKeysFromRemoteProvider(packageName, authority);
|
||||
}
|
||||
|
||||
mDataToProcess.fullIndex = true;
|
||||
updateInternal();
|
||||
performIndexing();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean addIndexablesFromRemoteProvider(String packageName, String authority) {
|
||||
/**
|
||||
* Accumulate all data and non-indexable keys from each of the content-providers.
|
||||
* Only the first indexing for the default language gets static search results - subsequent
|
||||
* calls will only gather non-indexable keys.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void performIndexing() {
|
||||
final Intent intent = new Intent(SearchIndexablesContract.PROVIDER_INTERFACE);
|
||||
final List<ResolveInfo> list =
|
||||
mContext.getPackageManager().queryIntentContentProviders(intent, 0);
|
||||
|
||||
final boolean isLocaleIndexed = isLocaleIndexed();
|
||||
|
||||
for (final ResolveInfo info : list) {
|
||||
if (!DatabaseIndexingUtils.isWellKnownProvider(info, mContext)) {
|
||||
continue;
|
||||
}
|
||||
final String authority = info.providerInfo.authority;
|
||||
final String packageName = info.providerInfo.packageName;
|
||||
|
||||
if (!isLocaleIndexed) {
|
||||
addIndexablesFromRemoteProvider(packageName, authority);
|
||||
}
|
||||
addNonIndexablesKeysFromRemoteProvider(packageName, authority);
|
||||
}
|
||||
|
||||
final String localeStr = Locale.getDefault().toString();
|
||||
updateDatabase(isLocaleIndexed, localeStr);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
boolean isLocaleIndexed() {
|
||||
final String locale = Locale.getDefault().toString();
|
||||
return IndexDatabaseHelper.getInstance(mContext).isLocaleAlreadyIndexed(mContext, locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new data to the database and verifies the correctness of the ENABLED column.
|
||||
* First, the data to be updated and all non-indexable keys are copied locally.
|
||||
* Then all new data to be added is inserted.
|
||||
* Then search results are verified to have the correct value of enabled.
|
||||
* Finally, we record that the locale has been indexed.
|
||||
*
|
||||
* @param isIncrementalUpdate true when the language has already been indexed.
|
||||
* @param localeStr the default locale for the device.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void updateDatabase(boolean isIncrementalUpdate, String localeStr) {
|
||||
mIsAvailable.set(false);
|
||||
final UpdateData copy;
|
||||
|
||||
synchronized (mDataToProcess) {
|
||||
copy = mDataToProcess.copy();
|
||||
mDataToProcess.clear();
|
||||
}
|
||||
|
||||
final List<SearchIndexableData> dataToUpdate = copy.dataToUpdate;
|
||||
final Map<String, Set<String>> nonIndexableKeys = copy.nonIndexableKeys;
|
||||
|
||||
final SQLiteDatabase database = getWritableDatabase();
|
||||
if (database == null) {
|
||||
Log.w(LOG_TAG, "Cannot indexDatabase Index as I cannot get a writable database");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
database.beginTransaction();
|
||||
|
||||
// Add new data from Providers at initial index time, or inserted later.
|
||||
if (dataToUpdate.size() > 0) {
|
||||
addDataToDatabase(database, localeStr, dataToUpdate, nonIndexableKeys);
|
||||
}
|
||||
|
||||
// Only check for non-indexable key updates after initial index.
|
||||
// Enabled state with non-indexable keys is checked when items are first inserted.
|
||||
if (isIncrementalUpdate) {
|
||||
updateDataInDatabase(database, nonIndexableKeys);
|
||||
}
|
||||
|
||||
database.setTransactionSuccessful();
|
||||
} finally {
|
||||
database.endTransaction();
|
||||
}
|
||||
// TODO Refactor: move the locale out of the helper class
|
||||
IndexDatabaseHelper.setLocaleIndexed(mContext, localeStr);
|
||||
|
||||
mIsAvailable.set(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts {@link SearchIndexableData} into the database.
|
||||
*
|
||||
* @param database where the data will be inserted.
|
||||
* @param localeStr is the locale of the data to be inserted.
|
||||
* @param dataToUpdate is a {@link List} of the data to be inserted.
|
||||
* @param nonIndexableKeys is a {@link Map} from Package Name to a {@link Set} of keys which
|
||||
* identify search results which should not be surfaced.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void addDataToDatabase(SQLiteDatabase database, String localeStr,
|
||||
List<SearchIndexableData> dataToUpdate, Map<String, Set<String>> nonIndexableKeys) {
|
||||
final long current = System.currentTimeMillis();
|
||||
|
||||
for (SearchIndexableData data : dataToUpdate) {
|
||||
try {
|
||||
indexOneSearchIndexableData(database, localeStr, data, nonIndexableKeys);
|
||||
} catch (Exception e) {
|
||||
Log.e(LOG_TAG, "Cannot index: " + (data != null ? data.className : data)
|
||||
+ " for locale: " + localeStr, e);
|
||||
}
|
||||
}
|
||||
|
||||
final long now = System.currentTimeMillis();
|
||||
Log.d(LOG_TAG, "Indexing locale '" + localeStr + "' took " +
|
||||
(now - current) + " millis");
|
||||
}
|
||||
|
||||
/**
|
||||
* Upholds the validity of enabled data for the user.
|
||||
* All rows which are enabled but are now flagged with non-indexable keys will become disabled.
|
||||
* All rows which are disabled but no longer a non-indexable key will become enabled.
|
||||
*
|
||||
* @param database The database to validate.
|
||||
* @param nonIndexableKeys A map between package name and the set of non-indexable keys for it.
|
||||
*/
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
void updateDataInDatabase(SQLiteDatabase database,
|
||||
Map<String, Set<String>> nonIndexableKeys) {
|
||||
final String whereEnabled = ENABLED + " = 1";
|
||||
final String whereDisabled = ENABLED + " = 0";
|
||||
|
||||
final Cursor enabledResults = database.query(TABLE_PREFS_INDEX, SELECT_COLUMNS,
|
||||
whereEnabled, null, null, null, null);
|
||||
|
||||
final ContentValues enabledToDisabledValue = new ContentValues();
|
||||
enabledToDisabledValue.put(ENABLED, 0);
|
||||
|
||||
String packageName;
|
||||
// TODO Refactor: Move these two loops into one method.
|
||||
while (enabledResults.moveToNext()) {
|
||||
// Package name is the key for remote providers.
|
||||
// If package name is null, the provider is Settings.
|
||||
packageName = enabledResults.getString(COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE);
|
||||
if (packageName == null) {
|
||||
packageName = mContext.getPackageName();
|
||||
}
|
||||
|
||||
final String key = enabledResults.getString(COLUMN_INDEX_KEY);
|
||||
final Set<String> packageKeys = nonIndexableKeys.get(packageName);
|
||||
|
||||
// The indexed item is set to Enabled but is now non-indexable
|
||||
if (packageKeys != null && packageKeys.contains(key)) {
|
||||
final String whereClause = DOCID + " = " + enabledResults.getInt(COLUMN_INDEX_ID);
|
||||
database.update(TABLE_PREFS_INDEX, enabledToDisabledValue, whereClause, null);
|
||||
}
|
||||
}
|
||||
enabledResults.close();
|
||||
|
||||
final Cursor disabledResults = database.query(TABLE_PREFS_INDEX, SELECT_COLUMNS,
|
||||
whereDisabled, null, null, null, null);
|
||||
|
||||
final ContentValues disabledToEnabledValue = new ContentValues();
|
||||
disabledToEnabledValue.put(ENABLED, 1);
|
||||
|
||||
while (disabledResults.moveToNext()) {
|
||||
// Package name is the key for remote providers.
|
||||
// If package name is null, the provider is Settings.
|
||||
packageName = disabledResults.getString(COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE);
|
||||
if (packageName == null) {
|
||||
packageName = mContext.getPackageName();
|
||||
}
|
||||
|
||||
final String key = disabledResults.getString(COLUMN_INDEX_KEY);
|
||||
final Set<String> packageKeys = nonIndexableKeys.get(packageName);
|
||||
|
||||
// The indexed item is set to Disabled but is no longer non-indexable.
|
||||
// We do not enable keys when packageKeys is null because it means the keys came
|
||||
// from an unrecognized package and therefore should not be surfaced as results.
|
||||
if (packageKeys != null && !packageKeys.contains(key)) {
|
||||
String whereClause = DOCID + " = " + disabledResults.getInt(COLUMN_INDEX_ID);
|
||||
database.update(TABLE_PREFS_INDEX, disabledToEnabledValue, whereClause, null);
|
||||
}
|
||||
}
|
||||
disabledResults.close();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
boolean addIndexablesFromRemoteProvider(String packageName, String authority) {
|
||||
try {
|
||||
// TODO delete base rank. does nothing.
|
||||
final int baseRank = Ranking.getBaseRankForAuthority(authority);
|
||||
|
||||
final Context context = mBaseAuthority.equals(authority) ?
|
||||
@@ -202,11 +367,12 @@ public class DatabaseIndexingManager {
|
||||
}
|
||||
}
|
||||
|
||||
private void addNonIndexablesKeysFromRemoteProvider(String packageName,
|
||||
@VisibleForTesting
|
||||
void addNonIndexablesKeysFromRemoteProvider(String packageName,
|
||||
String authority) {
|
||||
final List<String> keys =
|
||||
getNonIndexablesKeysFromRemoteProvider(packageName, authority);
|
||||
addNonIndexableKeys(packageName, keys);
|
||||
addNonIndexableKeys(packageName, new HashSet<>(keys));
|
||||
}
|
||||
|
||||
private List<String> getNonIndexablesKeysFromRemoteProvider(String packageName,
|
||||
@@ -235,7 +401,7 @@ public class DatabaseIndexingManager {
|
||||
return EMPTY_LIST;
|
||||
}
|
||||
|
||||
List<String> result = new ArrayList<String>();
|
||||
final List<String> result = new ArrayList<>();
|
||||
try {
|
||||
final int count = cursor.getCount();
|
||||
if (count > 0) {
|
||||
@@ -256,31 +422,19 @@ public class DatabaseIndexingManager {
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteIndexableData(SearchIndexableData data) {
|
||||
synchronized (mDataToProcess) {
|
||||
mDataToProcess.dataToDelete.add(data);
|
||||
}
|
||||
}
|
||||
|
||||
public void addNonIndexableKeys(String authority, List<String> keys) {
|
||||
public void addNonIndexableKeys(String authority, Set<String> keys) {
|
||||
synchronized (mDataToProcess) {
|
||||
mDataToProcess.nonIndexableKeys.put(authority, keys);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateFromRemoteProvider(String packageName, String authority) {
|
||||
if (addIndexablesFromRemoteProvider(packageName, authority)) {
|
||||
updateInternal();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the Index for a specific class name resources
|
||||
*
|
||||
* @param className the class name (typically a fragment name).
|
||||
* @param rebuild true means that you want to delete the data from the Index first.
|
||||
* @param className the class name (typically a fragment name).
|
||||
* @param rebuild true means that you want to delete the data from the Index first.
|
||||
* @param includeInSearchResults true means that you want the bit "enabled" set so that the
|
||||
* data will be seen included into the search results
|
||||
* data will be seen included into the search results
|
||||
*/
|
||||
public void updateFromClassNameResource(String className, final boolean rebuild,
|
||||
boolean includeInSearchResults) {
|
||||
@@ -297,12 +451,8 @@ public class DatabaseIndexingManager {
|
||||
AsyncTask.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (rebuild) {
|
||||
deleteIndexableData(res);
|
||||
}
|
||||
addIndexableData(res);
|
||||
mDataToProcess.forceUpdate = true;
|
||||
updateInternal();
|
||||
performIndexing();
|
||||
res.enabled = false;
|
||||
}
|
||||
});
|
||||
@@ -313,8 +463,7 @@ public class DatabaseIndexingManager {
|
||||
@Override
|
||||
public void run() {
|
||||
addIndexableData(data);
|
||||
mDataToProcess.forceUpdate = true;
|
||||
updateInternal();
|
||||
performIndexing();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -347,16 +496,6 @@ public class DatabaseIndexingManager {
|
||||
SearchIndexablesContract.NON_INDEXABLES_KEYS_PATH);
|
||||
}
|
||||
|
||||
private void updateInternal() {
|
||||
synchronized (mDataToProcess) {
|
||||
final DatabaseIndexingManager.UpdateIndexTask task =
|
||||
new DatabaseIndexingManager.UpdateIndexTask();
|
||||
DatabaseIndexingManager.UpdateData copy = mDataToProcess.copy();
|
||||
task.execute(copy);
|
||||
mDataToProcess.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void addIndexablesForXmlResourceUri(Context packageContext, String packageName,
|
||||
Uri uri, String[] projection, int baseRank) {
|
||||
|
||||
@@ -468,7 +607,7 @@ public class DatabaseIndexingManager {
|
||||
}
|
||||
|
||||
public void indexOneSearchIndexableData(SQLiteDatabase database, String localeStr,
|
||||
SearchIndexableData data, Map<String, List<String>> nonIndexableKeys) {
|
||||
SearchIndexableData data, Map<String, Set<String>> nonIndexableKeys) {
|
||||
if (data instanceof SearchIndexableResource) {
|
||||
indexOneResource(database, localeStr, (SearchIndexableResource) data, nonIndexableKeys);
|
||||
} else if (data instanceof SearchIndexableRaw) {
|
||||
@@ -502,7 +641,7 @@ public class DatabaseIndexingManager {
|
||||
}
|
||||
|
||||
private void indexOneResource(SQLiteDatabase database, String localeStr,
|
||||
SearchIndexableResource sir, Map<String, List<String>> nonIndexableKeysFromResource) {
|
||||
SearchIndexableResource sir, Map<String, Set<String>> nonIndexableKeysFromResource) {
|
||||
|
||||
if (sir == null) {
|
||||
Log.e(LOG_TAG, "Cannot index a null resource!");
|
||||
@@ -512,9 +651,9 @@ public class DatabaseIndexingManager {
|
||||
final List<String> nonIndexableKeys = new ArrayList<String>();
|
||||
|
||||
if (sir.xmlResId > SearchIndexableResources.NO_DATA_RES_ID) {
|
||||
List<String> resNonIndxableKeys = nonIndexableKeysFromResource.get(sir.packageName);
|
||||
if (resNonIndxableKeys != null && resNonIndxableKeys.size() > 0) {
|
||||
nonIndexableKeys.addAll(resNonIndxableKeys);
|
||||
Set<String> resNonIndexableKeys = nonIndexableKeysFromResource.get(sir.packageName);
|
||||
if (resNonIndexableKeys != null && resNonIndexableKeys.size() > 0) {
|
||||
nonIndexableKeys.addAll(resNonIndexableKeys);
|
||||
}
|
||||
|
||||
indexFromResource(database, localeStr, sir, nonIndexableKeys);
|
||||
@@ -605,6 +744,7 @@ public class DatabaseIndexingManager {
|
||||
headerKeywords = XmlParserUtils.getDataKeywords(context, attrs);
|
||||
enabled = !nonIndexableKeys.contains(key);
|
||||
|
||||
// TODO: Set payload type for header results
|
||||
DatabaseRow.Builder headerBuilder = new DatabaseRow.Builder();
|
||||
headerBuilder.setLocale(localeStr)
|
||||
.setEntries(null)
|
||||
@@ -799,31 +939,29 @@ public class DatabaseIndexingManager {
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(IndexDatabaseHelper.IndexColumns.DOCID, row.getDocId());
|
||||
values.put(IndexDatabaseHelper.IndexColumns.LOCALE, row.locale);
|
||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_RANK, row.rank);
|
||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE, row.updatedTitle);
|
||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE_NORMALIZED, row.normalizedTitle);
|
||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON, row.updatedSummaryOn);
|
||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON_NORMALIZED,
|
||||
row.normalizedSummaryOn);
|
||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_OFF, row.updatedSummaryOff);
|
||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_OFF_NORMALIZED,
|
||||
row.normalizedSummaryOff);
|
||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_ENTRIES, row.entries);
|
||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_KEYWORDS, row.spaceDelimitedKeywords);
|
||||
values.put(IndexDatabaseHelper.IndexColumns.CLASS_NAME, row.className);
|
||||
values.put(IndexDatabaseHelper.IndexColumns.SCREEN_TITLE, row.screenTitle);
|
||||
values.put(IndexDatabaseHelper.IndexColumns.INTENT_ACTION, row.intentAction);
|
||||
values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_PACKAGE, row.intentTargetPackage);
|
||||
values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_CLASS, row.intentTargetClass);
|
||||
values.put(IndexDatabaseHelper.IndexColumns.ICON, row.iconResId);
|
||||
values.put(IndexDatabaseHelper.IndexColumns.ENABLED, row.enabled);
|
||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_KEY_REF, row.key);
|
||||
values.put(IndexDatabaseHelper.IndexColumns.USER_ID, row.userId);
|
||||
values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD_TYPE, row.payloadType);
|
||||
values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD, row.payload);
|
||||
values.put(LOCALE, row.locale);
|
||||
values.put(DATA_RANK, row.rank);
|
||||
values.put(DATA_TITLE, row.updatedTitle);
|
||||
values.put(DATA_TITLE_NORMALIZED, row.normalizedTitle);
|
||||
values.put(DATA_SUMMARY_ON, row.updatedSummaryOn);
|
||||
values.put(DATA_SUMMARY_ON_NORMALIZED, row.normalizedSummaryOn);
|
||||
values.put(DATA_SUMMARY_OFF, row.updatedSummaryOff);
|
||||
values.put(DATA_SUMMARY_OFF_NORMALIZED, row.normalizedSummaryOff);
|
||||
values.put(DATA_ENTRIES, row.entries);
|
||||
values.put(DATA_KEYWORDS, row.spaceDelimitedKeywords);
|
||||
values.put(CLASS_NAME, row.className);
|
||||
values.put(SCREEN_TITLE, row.screenTitle);
|
||||
values.put(INTENT_ACTION, row.intentAction);
|
||||
values.put(INTENT_TARGET_PACKAGE, row.intentTargetPackage);
|
||||
values.put(INTENT_TARGET_CLASS, row.intentTargetClass);
|
||||
values.put(ICON, row.iconResId);
|
||||
values.put(ENABLED, row.enabled);
|
||||
values.put(DATA_KEY_REF, row.key);
|
||||
values.put(USER_ID, row.userId);
|
||||
values.put(PAYLOAD_TYPE, row.payloadType);
|
||||
values.put(PAYLOAD, row.payload);
|
||||
|
||||
database.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX, null, values);
|
||||
database.replaceOrThrow(TABLE_PREFS_INDEX, null, values);
|
||||
|
||||
if (!TextUtils.isEmpty(row.className) && !TextUtils.isEmpty(row.childClassName)) {
|
||||
ContentValues siteMapPair = new ContentValues();
|
||||
@@ -839,129 +977,34 @@ public class DatabaseIndexingManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* A private class for updating the Index database
|
||||
* A private class to describe the indexDatabase data for the Index database
|
||||
*/
|
||||
private class UpdateIndexTask extends AsyncTask<DatabaseIndexingManager.UpdateData, Integer,
|
||||
Void> {
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
static class UpdateData {
|
||||
public List<SearchIndexableData> dataToUpdate;
|
||||
public List<SearchIndexableData> dataToDisable;
|
||||
public Map<String, Set<String>> nonIndexableKeys;
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
super.onPreExecute();
|
||||
mIsAvailable.set(false);
|
||||
public UpdateData() {
|
||||
dataToUpdate = new ArrayList<>();
|
||||
dataToDisable = new ArrayList<>();
|
||||
nonIndexableKeys = new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid) {
|
||||
super.onPostExecute(aVoid);
|
||||
mIsAvailable.set(true);
|
||||
public UpdateData(UpdateData other) {
|
||||
dataToUpdate = new ArrayList<>(other.dataToUpdate);
|
||||
dataToDisable = new ArrayList<>(other.dataToDisable);
|
||||
nonIndexableKeys = new HashMap<>(other.nonIndexableKeys);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(DatabaseIndexingManager.UpdateData... params) {
|
||||
try {
|
||||
final List<SearchIndexableData> dataToUpdate = params[0].dataToUpdate;
|
||||
final List<SearchIndexableData> dataToDelete = params[0].dataToDelete;
|
||||
final Map<String, List<String>> nonIndexableKeys = params[0].nonIndexableKeys;
|
||||
|
||||
final boolean forceUpdate = params[0].forceUpdate;
|
||||
final boolean fullIndex = params[0].fullIndex;
|
||||
|
||||
final SQLiteDatabase database = getWritableDatabase();
|
||||
if (database == null) {
|
||||
Log.e(LOG_TAG, "Cannot update Index as I cannot get a writable database");
|
||||
return null;
|
||||
}
|
||||
final String localeStr = Locale.getDefault().toString();
|
||||
|
||||
try {
|
||||
database.beginTransaction();
|
||||
if (dataToDelete.size() > 0) {
|
||||
processDataToDelete(database, localeStr, dataToDelete);
|
||||
}
|
||||
if (dataToUpdate.size() > 0) {
|
||||
processDataToUpdate(database, localeStr, dataToUpdate, nonIndexableKeys,
|
||||
forceUpdate);
|
||||
}
|
||||
database.setTransactionSuccessful();
|
||||
} finally {
|
||||
database.endTransaction();
|
||||
}
|
||||
if (fullIndex) {
|
||||
IndexDatabaseHelper.setLocaleIndexed(mContext, localeStr);
|
||||
}
|
||||
} catch (SQLiteFullException e) {
|
||||
Log.e(LOG_TAG, "Unable to index search, out of space", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
public UpdateData copy() {
|
||||
return new UpdateData(this);
|
||||
}
|
||||
|
||||
private boolean processDataToUpdate(SQLiteDatabase database, String localeStr,
|
||||
List<SearchIndexableData> dataToUpdate, Map<String, List<String>> nonIndexableKeys,
|
||||
boolean forceUpdate) {
|
||||
|
||||
if (!forceUpdate && IndexDatabaseHelper.isLocaleAlreadyIndexed(mContext, localeStr)) {
|
||||
Log.d(LOG_TAG, "Locale '" + localeStr + "' is already indexed");
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean result = false;
|
||||
final long current = System.currentTimeMillis();
|
||||
|
||||
final int count = dataToUpdate.size();
|
||||
for (int n = 0; n < count; n++) {
|
||||
final SearchIndexableData data = dataToUpdate.get(n);
|
||||
try {
|
||||
indexOneSearchIndexableData(database, localeStr, data, nonIndexableKeys);
|
||||
} catch (Exception e) {
|
||||
Log.e(LOG_TAG, "Cannot index: " + (data != null ? data.className : data)
|
||||
+ " for locale: " + localeStr, e);
|
||||
}
|
||||
}
|
||||
|
||||
final long now = System.currentTimeMillis();
|
||||
Log.d(LOG_TAG, "Indexing locale '" + localeStr + "' took " +
|
||||
(now - current) + " millis");
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean processDataToDelete(SQLiteDatabase database, String localeStr,
|
||||
List<SearchIndexableData> dataToDelete) {
|
||||
|
||||
boolean result = false;
|
||||
final long current = System.currentTimeMillis();
|
||||
|
||||
final int count = dataToDelete.size();
|
||||
for (int n = 0; n < count; n++) {
|
||||
final SearchIndexableData data = dataToDelete.get(n);
|
||||
if (data == null) {
|
||||
continue;
|
||||
}
|
||||
if (!TextUtils.isEmpty(data.className)) {
|
||||
delete(database, IndexDatabaseHelper.IndexColumns.CLASS_NAME, data.className);
|
||||
} else {
|
||||
if (data instanceof SearchIndexableRaw) {
|
||||
final SearchIndexableRaw raw = (SearchIndexableRaw) data;
|
||||
if (!TextUtils.isEmpty(raw.title)) {
|
||||
delete(database, IndexDatabaseHelper.IndexColumns.DATA_TITLE,
|
||||
raw.title);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final long now = System.currentTimeMillis();
|
||||
Log.d(LOG_TAG, "Deleting data for locale '" + localeStr + "' took " +
|
||||
(now - current) + " millis");
|
||||
return result;
|
||||
}
|
||||
|
||||
private int delete(SQLiteDatabase database, String columName, String value) {
|
||||
final String whereClause = columName + "=?";
|
||||
final String[] whereArgs = new String[]{value};
|
||||
|
||||
return database.delete(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX, whereClause,
|
||||
whereArgs);
|
||||
public void clear() {
|
||||
dataToUpdate.clear();
|
||||
dataToDisable.clear();
|
||||
nonIndexableKeys.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,37 +17,65 @@
|
||||
|
||||
package com.android.settings.search2;
|
||||
|
||||
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.PackageManager;
|
||||
import android.content.pm.ProviderInfo;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.net.Uri;
|
||||
import android.provider.SearchIndexableResource;
|
||||
|
||||
import android.provider.SearchIndexablesContract;
|
||||
import android.util.ArrayMap;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.SettingsRobolectricTestRunner;
|
||||
import com.android.settings.TestConfig;
|
||||
import com.android.settings.search.IndexDatabaseHelper;
|
||||
import com.android.settings.search.SearchIndexableRaw;
|
||||
import com.android.settings.testutils.DatabaseTestUtils;
|
||||
|
||||
import com.android.settings.testutils.shadow.ShadowDatabaseIndexingUtils;
|
||||
import com.android.settings.testutils.shadow.ShadowRunnableAsyncTask;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.shadows.ShadowApplication;
|
||||
import org.robolectric.shadows.ShadowContentResolver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
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.doReturn;
|
||||
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)
|
||||
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
|
||||
shadows={ShadowRunnableAsyncTask.class})
|
||||
public class DatabaseIndexingManagerTest {
|
||||
private final String localeStr = "en_US";
|
||||
|
||||
@@ -75,15 +103,30 @@ public class DatabaseIndexingManagerTest {
|
||||
private final int userId = -1;
|
||||
private final boolean enabled = true;
|
||||
|
||||
private final String AUTHORITY_ONE = "authority";
|
||||
private final String PACKAGE_ONE = "com.android.settings";
|
||||
|
||||
private final String TITLE_ONE = "title one";
|
||||
private final String TITLE_TWO = "title two";
|
||||
private final String KEY_ONE = "key one";
|
||||
private final String KEY_TWO = "key two";
|
||||
|
||||
private Context mContext;
|
||||
|
||||
private DatabaseIndexingManager mManager;
|
||||
private SQLiteDatabase mDb;
|
||||
|
||||
@Mock
|
||||
private PackageManager mPackageManager;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mContext = ShadowApplication.getInstance().getApplicationContext();
|
||||
mManager = spy(new DatabaseIndexingManager(mContext, mContext.getPackageName()));
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mContext = spy(RuntimeEnvironment.application);
|
||||
mManager = spy(new DatabaseIndexingManager(mContext,"com.android.settings"));
|
||||
mDb = IndexDatabaseHelper.getInstance(mContext).getWritableDatabase();
|
||||
|
||||
doReturn(mPackageManager).when(mContext).getPackageManager();
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -126,7 +169,7 @@ public class DatabaseIndexingManagerTest {
|
||||
// Tests for the flow: IndexOneRaw -> UpdateOneRowWithFilteredData -> UpdateOneRow
|
||||
|
||||
@Test
|
||||
public void testInsertRawColumn_RowInserted() {
|
||||
public void testInsertRawColumn_rowInserted() {
|
||||
SearchIndexableRaw raw = getFakeRaw();
|
||||
mManager.indexOneSearchIndexableData(mDb, localeStr, raw, null /* Non-indexable keys */);
|
||||
Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
|
||||
@@ -134,7 +177,7 @@ public class DatabaseIndexingManagerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertRawColumn_RowMatches() {
|
||||
public void testInsertRawColumn_rowMatches() {
|
||||
SearchIndexableRaw raw = getFakeRaw();
|
||||
mManager.indexOneSearchIndexableData(mDb, localeStr, raw, null /* Non-indexable keys */);
|
||||
Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
|
||||
@@ -185,7 +228,7 @@ public class DatabaseIndexingManagerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertRawColumnMismatchedLocale_NoRowInserted() {
|
||||
public void testInsertRawColumn_mismatchedLocale_noRowInserted() {
|
||||
SearchIndexableRaw raw = getFakeRaw("ca-fr");
|
||||
mManager.indexOneSearchIndexableData(mDb, localeStr, raw, null /* Non-indexable keys */);
|
||||
Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
|
||||
@@ -206,18 +249,18 @@ public class DatabaseIndexingManagerTest {
|
||||
@Test
|
||||
public void testAddResource_RowsInserted() {
|
||||
SearchIndexableResource resource = getFakeResource(R.xml.ia_display_settings);
|
||||
mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
|
||||
new HashMap<>());
|
||||
mManager.indexOneSearchIndexableData(mDb, localeStr, resource, new HashMap<>());
|
||||
Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
|
||||
assertThat(cursor.getCount()).isEqualTo(16);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddResourceWithNIKs_RowsInsertedDisabled() {
|
||||
public void testAddResource_withNIKs_rowsInsertedDisabled() {
|
||||
SearchIndexableResource resource = getFakeResource(R.xml.ia_display_settings);
|
||||
// Only add 2 of 16 items to be disabled.
|
||||
String[] keys = {"brightness", "wallpaper"};
|
||||
Map<String, List<String>> niks = getNonIndexableKeys(keys);
|
||||
Map<String, Set<String>> niks = getNonIndexableKeys(keys);
|
||||
|
||||
mManager.indexOneSearchIndexableData(mDb, localeStr, resource, niks);
|
||||
|
||||
Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE enabled = 0", null);
|
||||
@@ -227,10 +270,9 @@ public class DatabaseIndexingManagerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddResourceHeader_RowsMatch() {
|
||||
public void testAddResourceHeader_rowsMatch() {
|
||||
SearchIndexableResource resource = getFakeResource(R.xml.application_settings);
|
||||
mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
|
||||
new HashMap<>());
|
||||
mManager.indexOneSearchIndexableData(mDb, localeStr, resource, new HashMap<>());
|
||||
|
||||
Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index ORDER BY data_title", null);
|
||||
cursor.moveToPosition(1);
|
||||
@@ -280,7 +322,7 @@ public class DatabaseIndexingManagerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddResourceWithChildFragment_shouldUpdateSiteMapDb() {
|
||||
public void testAddResource_withChildFragment_shouldUpdateSiteMapDb() {
|
||||
// FIXME: This test was failing. (count = 6 at the end)
|
||||
|
||||
// SearchIndexableResource resource = getFakeResource(R.xml.network_and_internet);
|
||||
@@ -305,10 +347,9 @@ public class DatabaseIndexingManagerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddResourceCustomSetting_RowsMatch() {
|
||||
public void testAddResource_customSetting_rowsMatch() {
|
||||
SearchIndexableResource resource = getFakeResource(R.xml.swipe_to_notification_settings);
|
||||
mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
|
||||
new HashMap<>());
|
||||
mManager.indexOneSearchIndexableData(mDb, localeStr, resource, new HashMap<>());
|
||||
final String prefTitle =
|
||||
mContext.getString(R.string.fingerprint_swipe_for_notifications_title);
|
||||
final String prefSummary =
|
||||
@@ -363,10 +404,9 @@ public class DatabaseIndexingManagerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddResourceCheckboxPreference_RowsMatch() {
|
||||
public void testAddResource_checkboxPreference_rowsMatch() {
|
||||
SearchIndexableResource resource = getFakeResource(R.xml.application_settings);
|
||||
mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
|
||||
new HashMap<>());
|
||||
mManager.indexOneSearchIndexableData(mDb, localeStr, resource, new HashMap<>());
|
||||
|
||||
/* Should return 6 results, with the following titles:
|
||||
* Advanced Settings, Apps, Manage Apps, Preferred install location, Running Services
|
||||
@@ -418,10 +458,9 @@ public class DatabaseIndexingManagerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddResourceListPreference_RowsMatch() {
|
||||
public void testAddResource_listPreference_rowsMatch() {
|
||||
SearchIndexableResource resource = getFakeResource(R.xml.application_settings);
|
||||
mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
|
||||
new HashMap<>());
|
||||
mManager.indexOneSearchIndexableData(mDb, localeStr, resource, new HashMap<>());
|
||||
|
||||
Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index ORDER BY data_title", null);
|
||||
cursor.moveToPosition(3);
|
||||
@@ -476,25 +515,23 @@ public class DatabaseIndexingManagerTest {
|
||||
// UpdateOneRowWithFilteredData -> UpdateOneRow
|
||||
|
||||
@Test
|
||||
public void testResourceProvider_RowInserted() {
|
||||
public void testResourceProvider_rowInserted() {
|
||||
SearchIndexableResource resource = getFakeResource(R.xml.swipe_to_notification_settings);
|
||||
resource.xmlResId = 0;
|
||||
resource.className = "com.android.settings.display.ScreenZoomSettings";
|
||||
|
||||
mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
|
||||
new HashMap<>());
|
||||
mManager.indexOneSearchIndexableData(mDb, localeStr, resource, new HashMap<>());
|
||||
Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
|
||||
assertThat(cursor.getCount()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResourceProvider_Matches() {
|
||||
public void testResourceProvider_rowMatches() {
|
||||
SearchIndexableResource resource = getFakeResource(R.xml.swipe_to_notification_settings);
|
||||
resource.xmlResId = 0;
|
||||
resource.className = "com.android.settings.display.ScreenZoomSettings";
|
||||
|
||||
mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
|
||||
new HashMap<>());
|
||||
mManager.indexOneSearchIndexableData(mDb, localeStr, resource, new HashMap<>());
|
||||
Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
|
||||
cursor.moveToPosition(0);
|
||||
|
||||
@@ -544,23 +581,21 @@ public class DatabaseIndexingManagerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResourceProvider_ResourceRowInserted() {
|
||||
public void testResourceProvider_resourceRowInserted() {
|
||||
SearchIndexableResource resource = getFakeResource(0);
|
||||
resource.className = "com.android.settings.LegalSettings";
|
||||
|
||||
mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
|
||||
new HashMap<>());
|
||||
mManager.indexOneSearchIndexableData(mDb, localeStr, resource, new HashMap<>());
|
||||
Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
|
||||
assertThat(cursor.getCount()).isEqualTo(6);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResourceProvider_ResourceRowMatches() {
|
||||
public void testResourceProvider_resourceRowMatches() {
|
||||
SearchIndexableResource resource = getFakeResource(0);
|
||||
resource.className = "com.android.settings.display.ScreenZoomSettings";
|
||||
|
||||
mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
|
||||
new HashMap<>());
|
||||
mManager.indexOneSearchIndexableData(mDb, localeStr, resource, new HashMap<>());
|
||||
Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index ORDER BY data_title", null);
|
||||
cursor.moveToPosition(0);
|
||||
|
||||
@@ -611,12 +646,12 @@ public class DatabaseIndexingManagerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResourceProvider_DisabledResourceRowsInserted() {
|
||||
public void testResourceProvider_disabledResource_rowsInserted() {
|
||||
SearchIndexableResource resource = getFakeResource(0);
|
||||
resource.className = "com.android.settings.LegalSettings";
|
||||
|
||||
mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
|
||||
new HashMap<String, List<String>>());
|
||||
new HashMap<String, Set<String>>());
|
||||
|
||||
Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE enabled = 1", null);
|
||||
assertThat(cursor.getCount()).isEqualTo(2);
|
||||
@@ -625,7 +660,7 @@ public class DatabaseIndexingManagerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResourceWithTitleAndSettingName_TitleNotInserted() {
|
||||
public void testResource_withTitleAndSettingName_titleNotInserted() {
|
||||
SearchIndexableResource resource = getFakeResource(R.xml.swipe_to_notification_settings);
|
||||
mManager.indexFromResource(mDb, localeStr, resource, new ArrayList<String>());
|
||||
|
||||
@@ -634,6 +669,176 @@ public class DatabaseIndexingManagerTest {
|
||||
assertThat(cursor.getCount()).isEqualTo(1);
|
||||
}
|
||||
|
||||
// Test new public indexing flow
|
||||
|
||||
@Test
|
||||
@Config(shadows= {
|
||||
ShadowDatabaseIndexingUtils.class,
|
||||
})
|
||||
public void testPerformIndexing_fullIndex_getsDataFromProviders() {
|
||||
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());
|
||||
|
||||
DatabaseIndexingManager manager =
|
||||
spy(new DatabaseIndexingManager(mContext, "com.android.settings"));
|
||||
doReturn(false).when(manager).isLocaleIndexed();
|
||||
|
||||
manager.performIndexing();
|
||||
|
||||
verify(manager).updateDatabase(false, Locale.getDefault().toString());
|
||||
|
||||
Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
|
||||
cursor.moveToPosition(0);
|
||||
|
||||
// Data Title
|
||||
assertThat(cursor.getString(2)).isEqualTo(TITLE_ONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(shadows= {
|
||||
ShadowDatabaseIndexingUtils.class,
|
||||
})
|
||||
public void testPerformIndexing_incrementalIndex_noDataAdded() {
|
||||
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());
|
||||
|
||||
DatabaseIndexingManager manager =
|
||||
spy(new DatabaseIndexingManager(mContext, "com.android.settings"));
|
||||
doReturn(true).when(manager).isLocaleIndexed();
|
||||
|
||||
manager.performIndexing();
|
||||
|
||||
final Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
|
||||
|
||||
assertThat(cursor.getCount()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFullUpdatedDatabase_noData_addDataToDatabaseNotCalled() {
|
||||
mManager.updateDatabase(false, localeStr);
|
||||
mManager.mDataToProcess.dataToUpdate.clear();
|
||||
verify(mManager, times(0)).addDataToDatabase(any(SQLiteDatabase.class), anyString(),
|
||||
anyList(), anyMap());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFullUpdatedDatabase_updatedDataInDatabaseNotCalled() {
|
||||
mManager.updateDatabase(false, localeStr);
|
||||
verify(mManager, times(0)).updateDataInDatabase(any(SQLiteDatabase.class), anyMap());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocaleUpdated_afterIndexing_localeAdded() {
|
||||
mManager.updateDatabase(false, localeStr);
|
||||
assertThat(IndexDatabaseHelper.getInstance(mContext)
|
||||
.isLocaleAlreadyIndexed(mContext, localeStr)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateDatabase_newEligibleData_addedToDatabase() {
|
||||
// Test that addDataToDatabase is called when dataToUpdate is non-empty
|
||||
mManager.mDataToProcess.dataToUpdate.add(getFakeRaw());
|
||||
mManager.updateDatabase(false, localeStr);
|
||||
|
||||
Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
|
||||
cursor.moveToPosition(0);
|
||||
|
||||
// Locale
|
||||
assertThat(cursor.getString(0)).isEqualTo(localeStr);
|
||||
// Data Rank
|
||||
assertThat(cursor.getInt(1)).isEqualTo(rank);
|
||||
// Data Title
|
||||
assertThat(cursor.getString(2)).isEqualTo(updatedTitle);
|
||||
// Normalized Title
|
||||
assertThat(cursor.getString(3)).isEqualTo(normalizedTitle);
|
||||
// Summary On
|
||||
assertThat(cursor.getString(4)).isEqualTo(updatedSummaryOn);
|
||||
// Summary On Normalized
|
||||
assertThat(cursor.getString(5)).isEqualTo(normalizedSummaryOn);
|
||||
// Summary Off
|
||||
assertThat(cursor.getString(6)).isEqualTo(updatedSummaryOff);
|
||||
// Summary off normalized
|
||||
assertThat(cursor.getString(7)).isEqualTo(normalizedSummaryOff);
|
||||
// Entries
|
||||
assertThat(cursor.getString(8)).isEqualTo(entries);
|
||||
// Keywords
|
||||
assertThat(cursor.getString(9)).isEqualTo(spaceDelimittedKeywords);
|
||||
// Screen Title
|
||||
assertThat(cursor.getString(10)).isEqualTo(screenTitle);
|
||||
// Class Name
|
||||
assertThat(cursor.getString(11)).isEqualTo(className);
|
||||
// Icon
|
||||
assertThat(cursor.getInt(12)).isEqualTo(iconResId);
|
||||
// Intent Action
|
||||
assertThat(cursor.getString(13)).isEqualTo(action);
|
||||
// Target Package
|
||||
assertThat(cursor.getString(14)).isEqualTo(targetPackage);
|
||||
// Target Class
|
||||
assertThat(cursor.getString(15)).isEqualTo(targetClass);
|
||||
// Enabled
|
||||
assertThat(cursor.getInt(16) == 1).isEqualTo(enabled);
|
||||
// Data ref key
|
||||
assertThat(cursor.getString(17)).isNotNull();
|
||||
// User Id
|
||||
assertThat(cursor.getInt(18)).isEqualTo(userId);
|
||||
// Payload Type - default is 0
|
||||
assertThat(cursor.getInt(19)).isEqualTo(0);
|
||||
// Payload
|
||||
assertThat(cursor.getBlob(20)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateDataInDatabase_enabledResultsAreNonIndexable_becomeDisabled() {
|
||||
// Both results are enabled, and then TITLE_ONE gets disabled.
|
||||
final boolean enabled = true;
|
||||
insertSpecialCase(TITLE_ONE, enabled, KEY_ONE);
|
||||
insertSpecialCase(TITLE_TWO, enabled, KEY_TWO);
|
||||
Map<String, Set<String>> niks = new ArrayMap<>();
|
||||
Set<String> keys = new HashSet<>();
|
||||
keys.add(KEY_ONE);
|
||||
niks.put(targetPackage, keys);
|
||||
|
||||
mManager.updateDataInDatabase(mDb, niks);
|
||||
|
||||
Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE enabled = 0", null);
|
||||
cursor.moveToPosition(0);
|
||||
|
||||
assertThat(cursor.getString(2)).isEqualTo(TITLE_ONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateDataInDatabase_DisabledResultsAreIndexable_BecomeEnabled() {
|
||||
// Both results are initially disabled, and then TITLE_TWO gets enabled.
|
||||
final boolean enabled = false;
|
||||
insertSpecialCase(TITLE_ONE, enabled, KEY_ONE);
|
||||
insertSpecialCase(TITLE_TWO, enabled, KEY_TWO);
|
||||
Map<String, Set<String>> niks = new ArrayMap<>();
|
||||
Set<String> keys = new HashSet<>();
|
||||
keys.add(KEY_ONE);
|
||||
niks.put(targetPackage, keys);
|
||||
|
||||
mManager.updateDataInDatabase(mDb, niks);
|
||||
|
||||
Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE enabled = 1", null);
|
||||
cursor.moveToPosition(0);
|
||||
|
||||
assertThat(cursor.getString(2)).isEqualTo(TITLE_TWO);
|
||||
}
|
||||
|
||||
// Util functions
|
||||
|
||||
private SearchIndexableRaw getFakeRaw() {
|
||||
@@ -676,10 +881,119 @@ public class DatabaseIndexingManagerTest {
|
||||
return sir;
|
||||
}
|
||||
|
||||
private Map<String, List<String>> getNonIndexableKeys(String[] keys) {
|
||||
Map<String, List<String>> niks = new HashMap<>();
|
||||
List<String> keysList = new ArrayList<>(Arrays.asList(keys));
|
||||
private Map<String, Set<String>> getNonIndexableKeys(String[] keys) {
|
||||
Map<String, Set<String>> niks = new HashMap<>();
|
||||
Set<String> keysList = new HashSet<>();
|
||||
keysList.addAll(Arrays.asList(keys));
|
||||
niks.put(packageName, keysList);
|
||||
return niks;
|
||||
}
|
||||
|
||||
private List<ResolveInfo> getDummyResolveInfo() {
|
||||
List<ResolveInfo> infoList = new ArrayList<>();
|
||||
ResolveInfo info = new ResolveInfo();
|
||||
info.providerInfo = new ProviderInfo();
|
||||
info.providerInfo.exported = true;
|
||||
info.providerInfo.authority = AUTHORITY_ONE;
|
||||
info.providerInfo.packageName = PACKAGE_ONE;
|
||||
infoList.add(info);
|
||||
|
||||
return infoList;
|
||||
}
|
||||
|
||||
// TODO move this method and its counterpart in CursorToSearchResultConverterTest into
|
||||
// a util class with public fields to assert values.
|
||||
private Cursor getDummyCursor() {
|
||||
MatrixCursor cursor = new MatrixCursor(SearchIndexablesContract.INDEXABLES_RAW_COLUMNS);
|
||||
final String BLANK = "";
|
||||
|
||||
ArrayList<String> item =
|
||||
new ArrayList<>(SearchIndexablesContract.INDEXABLES_RAW_COLUMNS.length);
|
||||
item.add("42"); // Rank
|
||||
item.add(TITLE_ONE); // Title
|
||||
item.add(BLANK); // Summary on
|
||||
item.add(BLANK); // summary off
|
||||
item.add(BLANK); // entries
|
||||
item.add(BLANK); // keywords
|
||||
item.add(BLANK); // screen title
|
||||
item.add(BLANK); // classname
|
||||
item.add("123"); // Icon
|
||||
item.add(BLANK); // Intent action
|
||||
item.add(BLANK); // target package
|
||||
item.add(BLANK); // target class
|
||||
item.add(KEY_ONE); // Key
|
||||
item.add("-1"); // userId
|
||||
cursor.addRow(item);
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
private void insertSpecialCase(String specialCase, boolean enabled, String key) {
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(IndexDatabaseHelper.IndexColumns.DOCID, specialCase.hashCode());
|
||||
values.put(IndexDatabaseHelper.IndexColumns.LOCALE, localeStr);
|
||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_RANK, 1);
|
||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE, specialCase);
|
||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE_NORMALIZED, "");
|
||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON, "");
|
||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_ON_NORMALIZED, "");
|
||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_OFF, "");
|
||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_SUMMARY_OFF_NORMALIZED, "");
|
||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_ENTRIES, "");
|
||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_KEYWORDS, "");
|
||||
values.put(IndexDatabaseHelper.IndexColumns.CLASS_NAME, "");
|
||||
values.put(IndexDatabaseHelper.IndexColumns.SCREEN_TITLE, "Moves");
|
||||
values.put(IndexDatabaseHelper.IndexColumns.INTENT_ACTION, "");
|
||||
values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_PACKAGE, targetPackage);
|
||||
values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_CLASS, "");
|
||||
values.put(IndexDatabaseHelper.IndexColumns.ICON, "");
|
||||
values.put(IndexDatabaseHelper.IndexColumns.ENABLED, enabled);
|
||||
values.put(IndexDatabaseHelper.IndexColumns.DATA_KEY_REF, key);
|
||||
values.put(IndexDatabaseHelper.IndexColumns.USER_ID, 0);
|
||||
values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD_TYPE, 0);
|
||||
values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD, (String) null);
|
||||
|
||||
mDb.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX, null, values);
|
||||
}
|
||||
|
||||
private class DummyProvider extends ContentProvider {
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(@NonNull Uri uri, @Nullable String[] projection,
|
||||
@Nullable String selection, @Nullable String[] selectionArgs,
|
||||
@Nullable String sortOrder) {
|
||||
if (uri.toString().contains("xml")) {
|
||||
return null;
|
||||
}
|
||||
return getDummyCursor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType(@NonNull Uri uri) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(@NonNull Uri uri, @Nullable String selection,
|
||||
@Nullable String[] selectionArgs) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(@NonNull Uri uri, @Nullable ContentValues values,
|
||||
@Nullable String selection, @Nullable String[] selectionArgs) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -29,5 +29,4 @@ public class ShadowContentResolver {
|
||||
public static SyncAdapterType[] getSyncAdapterTypesAsUser(int userId) {
|
||||
return new SyncAdapterType[0];
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.android.settings.testutils.shadow;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import com.android.settings.search2.DatabaseIndexingUtils;
|
||||
import org.robolectric.annotation.Implementation;
|
||||
import org.robolectric.annotation.Implements;
|
||||
|
||||
/**
|
||||
* Shadow of {@link DatabaseIndexingUtils}
|
||||
*/
|
||||
@Implements(DatabaseIndexingUtils.class)
|
||||
public class ShadowDatabaseIndexingUtils {
|
||||
@Implementation
|
||||
public static boolean isWellKnownProvider(ResolveInfo info, Context context) {
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.android.settings.testutils.shadow;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
import org.robolectric.annotation.Implementation;
|
||||
import org.robolectric.annotation.Implements;
|
||||
import org.robolectric.shadows.ShadowAsyncTask;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* Shadow async task to handle runnables in roboletric
|
||||
*/
|
||||
@Implements(AsyncTask.class)
|
||||
public class ShadowRunnableAsyncTask<Params, Progress, Result> extends
|
||||
ShadowAsyncTask<Params, Progress, Result> {
|
||||
|
||||
@Implementation
|
||||
public AsyncTask<Params, Progress, Result> executeOnExecutor(Executor executor,
|
||||
Params... params) {
|
||||
return super.execute(params);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user