Separate collection of indexable data from indexing
The first step in refactoring the god class, DatabaseIndexingManager. The class has one major entry point: indexDatabase which begins a chain of calls that first collects all the data from the fragments, and then massages that data into the SQLite database. Unfortunately, most of the methods do not return data, and just pass along some mutated form of the data until it can be insterted. Reading and testing this class is very difficult. This first step moves the collection of the indexable data into a new class which has a few benefits: - The data can be easily mocked in tests - Reduces complexity of D.I.M. - Separates data collection from indexing, which allows the indexable data to be piped into a new API that unbundled search can consume. Bug:33577327 Test: make RunSettingsRoboTests Test: Grabbed a DB dump before change, compared to DB dump after change to make sure everything is still indexed. Change-Id: Ibc91e3d75ff5dcf5274b93b29bf3544f90b2194d
This commit is contained in:
@@ -17,27 +17,6 @@
|
||||
|
||||
package com.android.settings.search;
|
||||
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_CLASS_NAME;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_ENTRIES;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_ICON_RESID;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_INTENT_ACTION;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_INTENT_TARGET_CLASS;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_INTENT_TARGET_PACKAGE;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_KEY;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_KEYWORDS;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_RANK;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_SCREEN_TITLE;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_SUMMARY_OFF;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_SUMMARY_ON;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_TITLE;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_USER_ID;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_CLASS_NAME;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_ICON_RESID;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_ACTION;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_RESID;
|
||||
import static com.android.settings.search.DatabaseResultLoader.COLUMN_INDEX_ID;
|
||||
import static com.android.settings.search.DatabaseResultLoader
|
||||
.COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE;
|
||||
@@ -70,17 +49,14 @@ import static com.android.settings.search.IndexDatabaseHelper.IndexColumns.USER_
|
||||
import static com.android.settings.search.IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.res.XmlResourceParser;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.provider.SearchIndexableData;
|
||||
@@ -89,7 +65,6 @@ import android.provider.SearchIndexablesContract;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArraySet;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.util.Xml;
|
||||
@@ -98,13 +73,13 @@ import com.android.settings.SettingsActivity;
|
||||
import com.android.settings.core.PreferenceControllerMixin;
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
|
||||
import com.android.settings.search.indexing.IndexableDataCollector;
|
||||
import com.android.settings.search.indexing.PreIndexData;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
@@ -132,23 +107,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.emptyList();
|
||||
|
||||
private final String mBaseAuthority;
|
||||
|
||||
@VisibleForTesting
|
||||
final AtomicBoolean mIsIndexingComplete = new AtomicBoolean(false);
|
||||
|
||||
@VisibleForTesting
|
||||
final UpdateData mDataToProcess = new UpdateData();
|
||||
private IndexableDataCollector mCollector;
|
||||
|
||||
private Context mContext;
|
||||
|
||||
public DatabaseIndexingManager(Context context, String baseAuthority) {
|
||||
mContext = context;
|
||||
mBaseAuthority = baseAuthority;
|
||||
}
|
||||
|
||||
public void setContext(Context context) {
|
||||
public DatabaseIndexingManager(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@@ -177,33 +143,17 @@ public class DatabaseIndexingManager {
|
||||
final String providerVersionedNames =
|
||||
IndexDatabaseHelper.buildProviderVersionedNames(providers);
|
||||
|
||||
final boolean isFullIndex = IndexDatabaseHelper.isFullIndex(mContext, localeStr,
|
||||
final boolean isFullIndex = isFullIndex(mContext, localeStr,
|
||||
fingerprint, providerVersionedNames);
|
||||
|
||||
if (isFullIndex) {
|
||||
rebuildDatabase();
|
||||
}
|
||||
|
||||
for (final ResolveInfo info : providers) {
|
||||
if (!DatabaseIndexingUtils.isWellKnownProvider(info, mContext)) {
|
||||
continue;
|
||||
}
|
||||
final String authority = info.providerInfo.authority;
|
||||
final String packageName = info.providerInfo.packageName;
|
||||
PreIndexData indexData = getIndexDataFromProviders(providers, isFullIndex);
|
||||
|
||||
if (isFullIndex) {
|
||||
addIndexablesFromRemoteProvider(packageName, authority);
|
||||
}
|
||||
final long nonIndexableStartTime = System.currentTimeMillis();
|
||||
addNonIndexablesKeysFromRemoteProvider(packageName, authority);
|
||||
if (SettingsSearchIndexablesProvider.DEBUG) {
|
||||
final long nonIndextableTime = System.currentTimeMillis() - nonIndexableStartTime;
|
||||
Log.d(LOG_TAG, "performIndexing update non-indexable for package " + packageName
|
||||
+ " took time: " + nonIndextableTime);
|
||||
}
|
||||
}
|
||||
final long updateDatabaseStartTime = System.currentTimeMillis();
|
||||
updateDatabase(isFullIndex, localeStr);
|
||||
updateDatabase(indexData, isFullIndex, localeStr);
|
||||
if (SettingsSearchIndexablesProvider.DEBUG) {
|
||||
final long updateDatabaseTime = System.currentTimeMillis() - updateDatabaseStartTime;
|
||||
Log.d(LOG_TAG, "performIndexing updateDatabase took time: " + updateDatabaseTime);
|
||||
@@ -221,10 +171,37 @@ public class DatabaseIndexingManager {
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
PreIndexData getIndexDataFromProviders(List<ResolveInfo> providers, boolean isFullIndex) {
|
||||
if (mCollector == null) {
|
||||
mCollector = new IndexableDataCollector(mContext);
|
||||
}
|
||||
return mCollector.collectIndexableData(providers, isFullIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconstruct the database in the following cases:
|
||||
* - Language has changed
|
||||
* - Build has changed
|
||||
* Checks if the indexed data is obsolete, when either:
|
||||
* - Device language has changed
|
||||
* - Device has taken an OTA.
|
||||
* In both cases, the device requires a full index.
|
||||
*
|
||||
* @param locale is the default for the device
|
||||
* @param fingerprint id for the current build.
|
||||
* @return true if a full index should be preformed.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop the currently stored database, and clear the flags which mark the database as indexed.
|
||||
*/
|
||||
private void rebuildDatabase() {
|
||||
// Drop the database when the locale or build has changed. This eliminates rows which are
|
||||
@@ -244,16 +221,9 @@ public class DatabaseIndexingManager {
|
||||
* @param localeStr the default locale for the device.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void updateDatabase(boolean needsReindexing, String localeStr) {
|
||||
final UpdateData copy;
|
||||
|
||||
synchronized (mDataToProcess) {
|
||||
copy = mDataToProcess.copy();
|
||||
mDataToProcess.clear();
|
||||
}
|
||||
|
||||
final List<SearchIndexableData> dataToUpdate = copy.dataToUpdate;
|
||||
final Map<String, Set<String>> nonIndexableKeys = copy.nonIndexableKeys;
|
||||
void updateDatabase(PreIndexData indexData, boolean needsReindexing, String localeStr) {
|
||||
final List<SearchIndexableData> dataToUpdate = indexData.dataToUpdate;
|
||||
final Map<String, Set<String>> nonIndexableKeys = indexData.nonIndexableKeys;
|
||||
|
||||
final SQLiteDatabase database = getWritableDatabase();
|
||||
if (database == null) {
|
||||
@@ -378,99 +348,10 @@ public class DatabaseIndexingManager {
|
||||
disabledResults.close();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
boolean addIndexablesFromRemoteProvider(String packageName, String authority) {
|
||||
try {
|
||||
final Context context = mBaseAuthority.equals(authority) ?
|
||||
mContext : mContext.createPackageContext(packageName, 0);
|
||||
|
||||
final Uri uriForResources = buildUriForXmlResources(authority);
|
||||
addIndexablesForXmlResourceUri(context, packageName, uriForResources,
|
||||
SearchIndexablesContract.INDEXABLES_XML_RES_COLUMNS);
|
||||
|
||||
final Uri uriForRawData = buildUriForRawData(authority);
|
||||
addIndexablesForRawDataUri(context, packageName, uriForRawData,
|
||||
SearchIndexablesContract.INDEXABLES_RAW_COLUMNS);
|
||||
return true;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.w(LOG_TAG, "Could not create context for " + packageName + ": "
|
||||
+ Log.getStackTraceString(e));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void addNonIndexablesKeysFromRemoteProvider(String packageName,
|
||||
String authority) {
|
||||
final List<String> keys =
|
||||
getNonIndexablesKeysFromRemoteProvider(packageName, authority);
|
||||
|
||||
addNonIndexableKeys(packageName, keys);
|
||||
}
|
||||
|
||||
private List<String> getNonIndexablesKeysFromRemoteProvider(String packageName,
|
||||
String authority) {
|
||||
try {
|
||||
final Context packageContext = mContext.createPackageContext(packageName, 0);
|
||||
|
||||
final Uri uriForNonIndexableKeys = buildUriForNonIndexableKeys(authority);
|
||||
return getNonIndexablesKeys(packageContext, uriForNonIndexableKeys,
|
||||
SearchIndexablesContract.NON_INDEXABLES_KEYS_COLUMNS);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.w(LOG_TAG, "Could not create context for " + packageName + ": "
|
||||
+ Log.getStackTraceString(e));
|
||||
return EMPTY_LIST;
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> getNonIndexablesKeys(Context packageContext, Uri uri,
|
||||
String[] projection) {
|
||||
|
||||
final ContentResolver resolver = packageContext.getContentResolver();
|
||||
final Cursor cursor = resolver.query(uri, projection, null, null, null);
|
||||
|
||||
if (cursor == null) {
|
||||
Log.w(LOG_TAG, "Cannot add index data for Uri: " + uri.toString());
|
||||
return EMPTY_LIST;
|
||||
}
|
||||
|
||||
final List<String> result = new ArrayList<>();
|
||||
try {
|
||||
final int count = cursor.getCount();
|
||||
if (count > 0) {
|
||||
while (cursor.moveToNext()) {
|
||||
final String key = cursor.getString(COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE);
|
||||
|
||||
if (TextUtils.isEmpty(key) && Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
|
||||
Log.v(LOG_TAG, "Empty non-indexable key from: "
|
||||
+ packageContext.getPackageName());
|
||||
continue;
|
||||
}
|
||||
|
||||
result.add(key);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void addIndexableData(SearchIndexableData data) {
|
||||
synchronized (mDataToProcess) {
|
||||
mDataToProcess.dataToUpdate.add(data);
|
||||
}
|
||||
}
|
||||
|
||||
public void addNonIndexableKeys(String authority, List<String> keys) {
|
||||
synchronized (mDataToProcess) {
|
||||
if (keys != null && !keys.isEmpty()) {
|
||||
mDataToProcess.nonIndexableKeys.put(authority, new ArraySet<>(keys));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO (b/64951285): Deprecate this method
|
||||
*
|
||||
* Update the Index for a specific class name resources
|
||||
*
|
||||
* @param className the class name (typically a fragment name).
|
||||
@@ -491,9 +372,9 @@ public class DatabaseIndexingManager {
|
||||
AsyncTask.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
addIndexableData(res);
|
||||
updateDatabase(false, Locale.getDefault().toString());
|
||||
res.enabled = false;
|
||||
// addIndexableData(res);
|
||||
// updateDatabase(false, Locale.getDefault().toString());
|
||||
// res.enabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -507,126 +388,9 @@ public class DatabaseIndexingManager {
|
||||
}
|
||||
}
|
||||
|
||||
private static Uri buildUriForXmlResources(String authority) {
|
||||
return Uri.parse("content://" + authority + "/" +
|
||||
SearchIndexablesContract.INDEXABLES_XML_RES_PATH);
|
||||
}
|
||||
|
||||
private static Uri buildUriForRawData(String authority) {
|
||||
return Uri.parse("content://" + authority + "/" +
|
||||
SearchIndexablesContract.INDEXABLES_RAW_PATH);
|
||||
}
|
||||
|
||||
private static Uri buildUriForNonIndexableKeys(String authority) {
|
||||
return Uri.parse("content://" + authority + "/" +
|
||||
SearchIndexablesContract.NON_INDEXABLES_KEYS_PATH);
|
||||
}
|
||||
|
||||
private void addIndexablesForXmlResourceUri(Context packageContext, String packageName,
|
||||
Uri uri, String[] projection) {
|
||||
|
||||
final ContentResolver resolver = packageContext.getContentResolver();
|
||||
final Cursor cursor = resolver.query(uri, projection, null, null, null);
|
||||
|
||||
if (cursor == null) {
|
||||
Log.w(LOG_TAG, "Cannot add index data for Uri: " + uri.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final int count = cursor.getCount();
|
||||
if (count > 0) {
|
||||
while (cursor.moveToNext()) {
|
||||
final int xmlResId = cursor.getInt(COLUMN_INDEX_XML_RES_RESID);
|
||||
|
||||
final String className = cursor.getString(COLUMN_INDEX_XML_RES_CLASS_NAME);
|
||||
final int iconResId = cursor.getInt(COLUMN_INDEX_XML_RES_ICON_RESID);
|
||||
|
||||
final String action = cursor.getString(COLUMN_INDEX_XML_RES_INTENT_ACTION);
|
||||
final String targetPackage = cursor.getString(
|
||||
COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE);
|
||||
final String targetClass = cursor.getString(
|
||||
COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS);
|
||||
|
||||
SearchIndexableResource sir = new SearchIndexableResource(packageContext);
|
||||
sir.xmlResId = xmlResId;
|
||||
sir.className = className;
|
||||
sir.packageName = packageName;
|
||||
sir.iconResId = iconResId;
|
||||
sir.intentAction = action;
|
||||
sir.intentTargetPackage = targetPackage;
|
||||
sir.intentTargetClass = targetClass;
|
||||
|
||||
addIndexableData(sir);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void addIndexablesForRawDataUri(Context packageContext, String packageName,
|
||||
Uri uri, String[] projection) {
|
||||
|
||||
final ContentResolver resolver = packageContext.getContentResolver();
|
||||
final Cursor cursor = resolver.query(uri, projection, null, null, null);
|
||||
|
||||
if (cursor == null) {
|
||||
Log.w(LOG_TAG, "Cannot add index data for Uri: " + uri.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final int count = cursor.getCount();
|
||||
if (count > 0) {
|
||||
while (cursor.moveToNext()) {
|
||||
final int providerRank = cursor.getInt(COLUMN_INDEX_RAW_RANK);
|
||||
// TODO Remove rank
|
||||
final String title = cursor.getString(COLUMN_INDEX_RAW_TITLE);
|
||||
final String summaryOn = cursor.getString(COLUMN_INDEX_RAW_SUMMARY_ON);
|
||||
final String summaryOff = cursor.getString(COLUMN_INDEX_RAW_SUMMARY_OFF);
|
||||
final String entries = cursor.getString(COLUMN_INDEX_RAW_ENTRIES);
|
||||
final String keywords = cursor.getString(COLUMN_INDEX_RAW_KEYWORDS);
|
||||
|
||||
final String screenTitle = cursor.getString(COLUMN_INDEX_RAW_SCREEN_TITLE);
|
||||
|
||||
final String className = cursor.getString(COLUMN_INDEX_RAW_CLASS_NAME);
|
||||
final int iconResId = cursor.getInt(COLUMN_INDEX_RAW_ICON_RESID);
|
||||
|
||||
final String action = cursor.getString(COLUMN_INDEX_RAW_INTENT_ACTION);
|
||||
final String targetPackage = cursor.getString(
|
||||
COLUMN_INDEX_RAW_INTENT_TARGET_PACKAGE);
|
||||
final String targetClass = cursor.getString(
|
||||
COLUMN_INDEX_RAW_INTENT_TARGET_CLASS);
|
||||
|
||||
final String key = cursor.getString(COLUMN_INDEX_RAW_KEY);
|
||||
final int userId = cursor.getInt(COLUMN_INDEX_RAW_USER_ID);
|
||||
|
||||
SearchIndexableRaw data = new SearchIndexableRaw(packageContext);
|
||||
data.title = title;
|
||||
data.summaryOn = summaryOn;
|
||||
data.summaryOff = summaryOff;
|
||||
data.entries = entries;
|
||||
data.keywords = keywords;
|
||||
data.screenTitle = screenTitle;
|
||||
data.className = className;
|
||||
data.packageName = packageName;
|
||||
data.iconResId = iconResId;
|
||||
data.intentAction = action;
|
||||
data.intentTargetPackage = targetPackage;
|
||||
data.intentTargetClass = targetClass;
|
||||
data.key = key;
|
||||
data.userId = userId;
|
||||
|
||||
addIndexableData(data);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void indexOneSearchIndexableData(SQLiteDatabase database, String localeStr,
|
||||
@VisibleForTesting
|
||||
void indexOneSearchIndexableData(SQLiteDatabase database, String localeStr,
|
||||
SearchIndexableData data, Map<String, Set<String>> nonIndexableKeys) {
|
||||
if (data instanceof SearchIndexableResource) {
|
||||
indexOneResource(database, localeStr, (SearchIndexableResource) data, nonIndexableKeys);
|
||||
@@ -1010,38 +774,6 @@ public class DatabaseIndexingManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A private class to describe the indexDatabase data for the Index database
|
||||
*/
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
static class UpdateData {
|
||||
public List<SearchIndexableData> dataToUpdate;
|
||||
public List<SearchIndexableData> dataToDisable;
|
||||
public Map<String, Set<String>> nonIndexableKeys;
|
||||
|
||||
public UpdateData() {
|
||||
dataToUpdate = new ArrayList<>();
|
||||
dataToDisable = new ArrayList<>();
|
||||
nonIndexableKeys = new HashMap<>();
|
||||
}
|
||||
|
||||
public UpdateData(UpdateData other) {
|
||||
dataToUpdate = new ArrayList<>(other.dataToUpdate);
|
||||
dataToDisable = new ArrayList<>(other.dataToDisable);
|
||||
nonIndexableKeys = new HashMap<>(other.nonIndexableKeys);
|
||||
}
|
||||
|
||||
public UpdateData copy() {
|
||||
return new UpdateData(this);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
dataToUpdate.clear();
|
||||
dataToDisable.clear();
|
||||
nonIndexableKeys.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public static class DatabaseRow {
|
||||
public final String locale;
|
||||
public final String updatedTitle;
|
||||
|
@@ -174,35 +174,6 @@ public class DatabaseIndexingUtils {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only allow a "well known" SearchIndexablesProvider. The provider should:
|
||||
*
|
||||
* - have read/write {@link Manifest.permission#READ_SEARCH_INDEXABLES}
|
||||
* - be from a privileged package
|
||||
*/
|
||||
static boolean isWellKnownProvider(ResolveInfo info, Context context) {
|
||||
final String authority = info.providerInfo.authority;
|
||||
final String packageName = info.providerInfo.applicationInfo.packageName;
|
||||
|
||||
if (TextUtils.isEmpty(authority) || TextUtils.isEmpty(packageName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final String readPermission = info.providerInfo.readPermission;
|
||||
final String writePermission = info.providerInfo.writePermission;
|
||||
|
||||
if (TextUtils.isEmpty(readPermission) || TextUtils.isEmpty(writePermission)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!android.Manifest.permission.READ_SEARCH_INDEXABLES.equals(readPermission) ||
|
||||
!android.Manifest.permission.READ_SEARCH_INDEXABLES.equals(writePermission)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isPrivilegedPackage(packageName, context);
|
||||
}
|
||||
|
||||
static String normalizeHyphen(String input) {
|
||||
return (input != null) ? input.replaceAll(NON_BREAKING_HYPHEN, HYPHEN) : EMPTY;
|
||||
}
|
||||
@@ -217,15 +188,4 @@ public class DatabaseIndexingUtils {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -40,19 +40,19 @@ public class DatabaseResultLoader extends AsyncLoader<Set<? extends SearchResult
|
||||
/* These indices are used to match the columns of the this loader's SELECT statement.
|
||||
These are not necessarily the same order nor similar coverage as the schema defined in
|
||||
IndexDatabaseHelper */
|
||||
static final int COLUMN_INDEX_ID = 0;
|
||||
static final int COLUMN_INDEX_TITLE = 1;
|
||||
static final int COLUMN_INDEX_SUMMARY_ON = 2;
|
||||
static final int COLUMN_INDEX_SUMMARY_OFF = 3;
|
||||
static final int COLUMN_INDEX_CLASS_NAME = 4;
|
||||
static final int COLUMN_INDEX_SCREEN_TITLE = 5;
|
||||
static final int COLUMN_INDEX_ICON = 6;
|
||||
static final int COLUMN_INDEX_INTENT_ACTION = 7;
|
||||
static final int COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE = 8;
|
||||
static final int COLUMN_INDEX_INTENT_ACTION_TARGET_CLASS = 9;
|
||||
static final int COLUMN_INDEX_KEY = 10;
|
||||
static final int COLUMN_INDEX_PAYLOAD_TYPE = 11;
|
||||
static final int COLUMN_INDEX_PAYLOAD = 12;
|
||||
public static final int COLUMN_INDEX_ID = 0;
|
||||
public static final int COLUMN_INDEX_TITLE = 1;
|
||||
public static final int COLUMN_INDEX_SUMMARY_ON = 2;
|
||||
public static final int COLUMN_INDEX_SUMMARY_OFF = 3;
|
||||
public static final int COLUMN_INDEX_CLASS_NAME = 4;
|
||||
public static final int COLUMN_INDEX_SCREEN_TITLE = 5;
|
||||
public static final int COLUMN_INDEX_ICON = 6;
|
||||
public static final int COLUMN_INDEX_INTENT_ACTION = 7;
|
||||
public static final int COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE = 8;
|
||||
public static final int COLUMN_INDEX_INTENT_ACTION_TARGET_CLASS = 9;
|
||||
public static final int COLUMN_INDEX_KEY = 10;
|
||||
public static final int COLUMN_INDEX_PAYLOAD_TYPE = 11;
|
||||
public static final int COLUMN_INDEX_PAYLOAD = 12;
|
||||
|
||||
public static final String[] SELECT_COLUMNS = {
|
||||
IndexColumns.DOCID,
|
||||
|
@@ -35,7 +35,7 @@ public class IndexDatabaseHelper extends SQLiteOpenHelper {
|
||||
private static final String DATABASE_NAME = "search_index.db";
|
||||
private static final int DATABASE_VERSION = 117;
|
||||
|
||||
private static final String INDEX = "index";
|
||||
private static final String SHARED_PREFS_TAG = "indexing_manager";
|
||||
|
||||
private static final String PREF_KEY_INDEXED_PROVIDERS = "indexed_providers";
|
||||
|
||||
@@ -179,7 +179,7 @@ public class IndexDatabaseHelper extends SQLiteOpenHelper {
|
||||
|
||||
public IndexDatabaseHelper(Context context) {
|
||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
mContext = context;
|
||||
mContext = context.getApplicationContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -230,6 +230,10 @@ public class IndexDatabaseHelper extends SQLiteOpenHelper {
|
||||
}
|
||||
|
||||
public void reconstruct(SQLiteDatabase db) {
|
||||
mContext.getSharedPreferences(SHARED_PREFS_TAG, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
.clear()
|
||||
.commit();
|
||||
dropTables(db);
|
||||
bootstrapDB(db);
|
||||
}
|
||||
@@ -252,24 +256,6 @@ public class IndexDatabaseHelper extends SQLiteOpenHelper {
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
@@ -282,44 +268,42 @@ public class IndexDatabaseHelper extends SQLiteOpenHelper {
|
||||
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)
|
||||
context.getSharedPreferences(SHARED_PREFS_TAG, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
.putBoolean(locale, true)
|
||||
.apply();
|
||||
}
|
||||
|
||||
static void setProvidersIndexed(Context context, String providerVersionedNames) {
|
||||
context.getSharedPreferences(INDEX, Context.MODE_PRIVATE)
|
||||
context.getSharedPreferences(SHARED_PREFS_TAG, 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);
|
||||
return context.getSharedPreferences(SHARED_PREFS_TAG, 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);
|
||||
final String indexedProviders =
|
||||
context.getSharedPreferences(SHARED_PREFS_TAG, 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);
|
||||
return context.getSharedPreferences(SHARED_PREFS_TAG,
|
||||
Context.MODE_PRIVATE).getBoolean(buildNo, false);
|
||||
}
|
||||
|
||||
static void setBuildIndexed(Context context, String buildNo) {
|
||||
context.getSharedPreferences(INDEX, 0).edit().putBoolean(buildNo, true).commit();
|
||||
context.getSharedPreferences(SHARED_PREFS_TAG, 0).edit().putBoolean(buildNo, true).commit();
|
||||
}
|
||||
|
||||
private void dropTables(SQLiteDatabase db) {
|
||||
clearCachedIndexed(mContext);
|
||||
db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_META_INDEX);
|
||||
db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_PREFS_INDEX);
|
||||
db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_SAVED_QUERIES);
|
||||
|
@@ -74,8 +74,7 @@ public class SearchFeatureProviderImpl implements SearchFeatureProvider {
|
||||
@Override
|
||||
public DatabaseIndexingManager getIndexingManager(Context context) {
|
||||
if (mDatabaseIndexingManager == null) {
|
||||
mDatabaseIndexingManager = new DatabaseIndexingManager(context.getApplicationContext(),
|
||||
context.getPackageName());
|
||||
mDatabaseIndexingManager = new DatabaseIndexingManager(context.getApplicationContext());
|
||||
}
|
||||
return mDatabaseIndexingManager;
|
||||
}
|
||||
|
@@ -0,0 +1,361 @@
|
||||
/*
|
||||
* 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.search.indexing;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.SearchIndexableResource;
|
||||
import android.provider.SearchIndexablesContract;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.settings.search.SearchIndexableRaw;
|
||||
import com.android.settings.search.SettingsSearchIndexablesProvider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_CLASS_NAME;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_ICON_RESID;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_RESID;
|
||||
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_CLASS_NAME;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_ENTRIES;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_ICON_RESID;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_INTENT_ACTION;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_INTENT_TARGET_CLASS;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_INTENT_TARGET_PACKAGE;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_KEY;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_KEYWORDS;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_RANK;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_SCREEN_TITLE;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_SUMMARY_OFF;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_SUMMARY_ON;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_TITLE;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_USER_ID;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_ACTION;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS;
|
||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE;
|
||||
|
||||
/**
|
||||
* Collects all data from {@link android.provider.SearchIndexablesProvider} to be indexed.
|
||||
*/
|
||||
public class IndexableDataCollector {
|
||||
|
||||
private static final String TAG = "IndexableDataCollector";
|
||||
|
||||
// TODO (b/64938328) update to new search package.
|
||||
private final String BASE_AUTHORITY = "com.android.settings";
|
||||
|
||||
private static final List<String> EMPTY_LIST = Collections.emptyList();
|
||||
|
||||
private Context mContext;
|
||||
|
||||
private PreIndexData mIndexData;
|
||||
|
||||
public IndexableDataCollector(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public PreIndexData collectIndexableData(List<ResolveInfo> providers, boolean isFullIndex) {
|
||||
mIndexData = new PreIndexData();
|
||||
|
||||
for (final ResolveInfo info : providers) {
|
||||
if (!isWellKnownProvider(info)) {
|
||||
continue;
|
||||
}
|
||||
final String authority = info.providerInfo.authority;
|
||||
final String packageName = info.providerInfo.packageName;
|
||||
|
||||
if (isFullIndex) {
|
||||
addIndexablesFromRemoteProvider(packageName, authority);
|
||||
}
|
||||
|
||||
final long nonIndexableStartTime = System.currentTimeMillis();
|
||||
addNonIndexablesKeysFromRemoteProvider(packageName, authority);
|
||||
if (SettingsSearchIndexablesProvider.DEBUG) {
|
||||
final long nonIndexableTime = System.currentTimeMillis() - nonIndexableStartTime;
|
||||
Log.d(TAG, "performIndexing update non-indexable for package " + packageName
|
||||
+ " took time: " + nonIndexableTime);
|
||||
}
|
||||
}
|
||||
|
||||
return mIndexData;
|
||||
}
|
||||
|
||||
private boolean addIndexablesFromRemoteProvider(String packageName, String authority) {
|
||||
try {
|
||||
final Context context = BASE_AUTHORITY.equals(authority) ?
|
||||
mContext : mContext.createPackageContext(packageName, 0);
|
||||
|
||||
final Uri uriForResources = buildUriForXmlResources(authority);
|
||||
mIndexData.dataToUpdate.addAll(getIndexablesForXmlResourceUri(context, packageName,
|
||||
uriForResources, SearchIndexablesContract.INDEXABLES_XML_RES_COLUMNS));
|
||||
|
||||
final Uri uriForRawData = buildUriForRawData(authority);
|
||||
mIndexData.dataToUpdate.addAll(getIndexablesForRawDataUri(context, packageName,
|
||||
uriForRawData, SearchIndexablesContract.INDEXABLES_RAW_COLUMNS));
|
||||
return true;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.w(TAG, "Could not create context for " + packageName + ": "
|
||||
+ Log.getStackTraceString(e));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
List<SearchIndexableResource> getIndexablesForXmlResourceUri(Context packageContext,
|
||||
String packageName, Uri uri, String[] projection) {
|
||||
|
||||
final ContentResolver resolver = packageContext.getContentResolver();
|
||||
final Cursor cursor = resolver.query(uri, projection, null, null, null);
|
||||
List<SearchIndexableResource> resources = new ArrayList<>();
|
||||
|
||||
if (cursor == null) {
|
||||
Log.w(TAG, "Cannot add index data for Uri: " + uri.toString());
|
||||
return resources;
|
||||
}
|
||||
|
||||
try {
|
||||
final int count = cursor.getCount();
|
||||
if (count > 0) {
|
||||
while (cursor.moveToNext()) {
|
||||
final int xmlResId = cursor.getInt(COLUMN_INDEX_XML_RES_RESID);
|
||||
|
||||
final String className = cursor.getString(COLUMN_INDEX_XML_RES_CLASS_NAME);
|
||||
final int iconResId = cursor.getInt(COLUMN_INDEX_XML_RES_ICON_RESID);
|
||||
|
||||
final String action = cursor.getString(COLUMN_INDEX_XML_RES_INTENT_ACTION);
|
||||
final String targetPackage = cursor.getString(
|
||||
COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE);
|
||||
final String targetClass = cursor.getString(
|
||||
COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS);
|
||||
|
||||
SearchIndexableResource sir = new SearchIndexableResource(packageContext);
|
||||
sir.xmlResId = xmlResId;
|
||||
sir.className = className;
|
||||
sir.packageName = packageName;
|
||||
sir.iconResId = iconResId;
|
||||
sir.intentAction = action;
|
||||
sir.intentTargetPackage = targetPackage;
|
||||
sir.intentTargetClass = targetClass;
|
||||
|
||||
resources.add(sir);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
return resources;
|
||||
}
|
||||
|
||||
private void addNonIndexablesKeysFromRemoteProvider(String packageName,
|
||||
String authority) {
|
||||
final List<String> keys =
|
||||
getNonIndexablesKeysFromRemoteProvider(packageName, authority);
|
||||
|
||||
if (keys != null && !keys.isEmpty()) {
|
||||
mIndexData.nonIndexableKeys.put(authority, new ArraySet<>(keys));
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
List<String> getNonIndexablesKeysFromRemoteProvider(String packageName,
|
||||
String authority) {
|
||||
try {
|
||||
final Context packageContext = mContext.createPackageContext(packageName, 0);
|
||||
|
||||
final Uri uriForNonIndexableKeys = buildUriForNonIndexableKeys(authority);
|
||||
return getNonIndexablesKeys(packageContext, uriForNonIndexableKeys,
|
||||
SearchIndexablesContract.NON_INDEXABLES_KEYS_COLUMNS);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.w(TAG, "Could not create context for " + packageName + ": "
|
||||
+ Log.getStackTraceString(e));
|
||||
return EMPTY_LIST;
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
Uri buildUriForXmlResources(String authority) {
|
||||
return Uri.parse("content://" + authority + "/" +
|
||||
SearchIndexablesContract.INDEXABLES_XML_RES_PATH);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
Uri buildUriForRawData(String authority) {
|
||||
return Uri.parse("content://" + authority + "/" +
|
||||
SearchIndexablesContract.INDEXABLES_RAW_PATH);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
Uri buildUriForNonIndexableKeys(String authority) {
|
||||
return Uri.parse("content://" + authority + "/" +
|
||||
SearchIndexablesContract.NON_INDEXABLES_KEYS_PATH);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
List<SearchIndexableRaw> getIndexablesForRawDataUri(Context packageContext, String packageName,
|
||||
Uri uri, String[] projection) {
|
||||
final ContentResolver resolver = packageContext.getContentResolver();
|
||||
final Cursor cursor = resolver.query(uri, projection, null, null, null);
|
||||
List<SearchIndexableRaw> rawData = new ArrayList<>();
|
||||
|
||||
if (cursor == null) {
|
||||
Log.w(TAG, "Cannot add index data for Uri: " + uri.toString());
|
||||
return rawData;
|
||||
}
|
||||
|
||||
try {
|
||||
final int count = cursor.getCount();
|
||||
if (count > 0) {
|
||||
while (cursor.moveToNext()) {
|
||||
final int providerRank = cursor.getInt(COLUMN_INDEX_RAW_RANK);
|
||||
// TODO Remove rank
|
||||
final String title = cursor.getString(COLUMN_INDEX_RAW_TITLE);
|
||||
final String summaryOn = cursor.getString(COLUMN_INDEX_RAW_SUMMARY_ON);
|
||||
final String summaryOff = cursor.getString(COLUMN_INDEX_RAW_SUMMARY_OFF);
|
||||
final String entries = cursor.getString(COLUMN_INDEX_RAW_ENTRIES);
|
||||
final String keywords = cursor.getString(COLUMN_INDEX_RAW_KEYWORDS);
|
||||
|
||||
final String screenTitle = cursor.getString(COLUMN_INDEX_RAW_SCREEN_TITLE);
|
||||
|
||||
final String className = cursor.getString(COLUMN_INDEX_RAW_CLASS_NAME);
|
||||
final int iconResId = cursor.getInt(COLUMN_INDEX_RAW_ICON_RESID);
|
||||
|
||||
final String action = cursor.getString(COLUMN_INDEX_RAW_INTENT_ACTION);
|
||||
final String targetPackage = cursor.getString(
|
||||
COLUMN_INDEX_RAW_INTENT_TARGET_PACKAGE);
|
||||
final String targetClass = cursor.getString(
|
||||
COLUMN_INDEX_RAW_INTENT_TARGET_CLASS);
|
||||
|
||||
final String key = cursor.getString(COLUMN_INDEX_RAW_KEY);
|
||||
final int userId = cursor.getInt(COLUMN_INDEX_RAW_USER_ID);
|
||||
|
||||
SearchIndexableRaw data = new SearchIndexableRaw(packageContext);
|
||||
data.title = title;
|
||||
data.summaryOn = summaryOn;
|
||||
data.summaryOff = summaryOff;
|
||||
data.entries = entries;
|
||||
data.keywords = keywords;
|
||||
data.screenTitle = screenTitle;
|
||||
data.className = className;
|
||||
data.packageName = packageName;
|
||||
data.iconResId = iconResId;
|
||||
data.intentAction = action;
|
||||
data.intentTargetPackage = targetPackage;
|
||||
data.intentTargetClass = targetClass;
|
||||
data.key = key;
|
||||
data.userId = userId;
|
||||
|
||||
rawData.add(data);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
return rawData;
|
||||
}
|
||||
|
||||
private List<String> getNonIndexablesKeys(Context packageContext, Uri uri,
|
||||
String[] projection) {
|
||||
|
||||
final ContentResolver resolver = packageContext.getContentResolver();
|
||||
final Cursor cursor = resolver.query(uri, projection, null, null, null);
|
||||
final List<String> result = new ArrayList<>();
|
||||
|
||||
if (cursor == null) {
|
||||
Log.w(TAG, "Cannot add index data for Uri: " + uri.toString());
|
||||
return result;
|
||||
}
|
||||
|
||||
try {
|
||||
final int count = cursor.getCount();
|
||||
if (count > 0) {
|
||||
while (cursor.moveToNext()) {
|
||||
final String key = cursor.getString(COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE);
|
||||
|
||||
if (TextUtils.isEmpty(key) && Log.isLoggable(TAG, Log.VERBOSE)) {
|
||||
Log.v(TAG, "Empty non-indexable key from: "
|
||||
+ packageContext.getPackageName());
|
||||
continue;
|
||||
}
|
||||
|
||||
result.add(key);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Only allow a "well known" SearchIndexablesProvider. The provider should:
|
||||
*
|
||||
* - have read/write {@link Manifest.permission#READ_SEARCH_INDEXABLES}
|
||||
* - be from a privileged package
|
||||
*/
|
||||
@VisibleForTesting
|
||||
boolean isWellKnownProvider(ResolveInfo info) {
|
||||
final String authority = info.providerInfo.authority;
|
||||
final String packageName = info.providerInfo.applicationInfo.packageName;
|
||||
|
||||
if (TextUtils.isEmpty(authority) || TextUtils.isEmpty(packageName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final String readPermission = info.providerInfo.readPermission;
|
||||
final String writePermission = info.providerInfo.writePermission;
|
||||
|
||||
if (TextUtils.isEmpty(readPermission) || TextUtils.isEmpty(writePermission)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!android.Manifest.permission.READ_SEARCH_INDEXABLES.equals(readPermission) ||
|
||||
!android.Manifest.permission.READ_SEARCH_INDEXABLES.equals(writePermission)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isPrivilegedPackage(packageName, mContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the {@param packageName} is privileged.
|
||||
*/
|
||||
private 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;
|
||||
}
|
||||
}
|
||||
}
|
55
src/com/android/settings/search/indexing/PreIndexData.java
Normal file
55
src/com/android/settings/search/indexing/PreIndexData.java
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.search.indexing;
|
||||
|
||||
import android.provider.SearchIndexableData;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
/**
|
||||
* Holds Data sources for indexable data.
|
||||
* TODO (b/33577327) add getters and setters for data.
|
||||
*/
|
||||
public class PreIndexData {
|
||||
public List<SearchIndexableData> dataToUpdate;
|
||||
public Map<String, Set<String>> nonIndexableKeys;
|
||||
|
||||
public PreIndexData() {
|
||||
dataToUpdate = new ArrayList<>();
|
||||
nonIndexableKeys = new HashMap<>();
|
||||
}
|
||||
|
||||
public PreIndexData(PreIndexData other) {
|
||||
dataToUpdate = new ArrayList<>(other.dataToUpdate);
|
||||
nonIndexableKeys = new HashMap<>(other.nonIndexableKeys);
|
||||
}
|
||||
|
||||
public PreIndexData copy() {
|
||||
return new PreIndexData(this);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
dataToUpdate.clear();
|
||||
nonIndexableKeys.clear();
|
||||
}
|
||||
}
|
@@ -17,24 +17,20 @@
|
||||
|
||||
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.Matchers.anyBoolean;
|
||||
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;
|
||||
@@ -43,19 +39,18 @@ 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.os.Build;
|
||||
import android.provider.SearchIndexableData;
|
||||
import android.provider.SearchIndexableResource;
|
||||
import android.util.ArrayMap;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.TestConfig;
|
||||
import com.android.settings.search.indexing.PreIndexData;
|
||||
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 org.junit.After;
|
||||
@@ -67,7 +62,6 @@ import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.Robolectric;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.shadows.ShadowContentResolver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -84,8 +78,6 @@ import java.util.Set;
|
||||
sdk = TestConfig.SDK_VERSION,
|
||||
shadows = {
|
||||
ShadowRunnableAsyncTask.class,
|
||||
ShadowDatabaseIndexingUtils.class,
|
||||
ShadowContentResolver.class
|
||||
}
|
||||
)
|
||||
public class DatabaseIndexingManagerTest {
|
||||
@@ -129,6 +121,8 @@ public class DatabaseIndexingManagerTest {
|
||||
private DatabaseIndexingManager mManager;
|
||||
private SQLiteDatabase mDb;
|
||||
|
||||
private final List<ResolveInfo> FAKE_PROVIDER_LIST = new ArrayList<>();
|
||||
|
||||
@Mock
|
||||
private PackageManager mPackageManager;
|
||||
|
||||
@@ -136,10 +130,12 @@ public class DatabaseIndexingManagerTest {
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mContext = spy(RuntimeEnvironment.application);
|
||||
mManager = spy(new DatabaseIndexingManager(mContext, PACKAGE_ONE));
|
||||
mManager = spy(new DatabaseIndexingManager(mContext));
|
||||
mDb = IndexDatabaseHelper.getInstance(mContext).getWritableDatabase();
|
||||
|
||||
doReturn(mPackageManager).when(mContext).getPackageManager();
|
||||
doReturn(FAKE_PROVIDER_LIST).when(mPackageManager)
|
||||
.queryIntentContentProviders(any(Intent.class), anyInt());
|
||||
FakeFeatureFactory.setupForTest(mContext);
|
||||
}
|
||||
|
||||
@@ -755,113 +751,60 @@ public class DatabaseIndexingManagerTest {
|
||||
|
||||
@Test
|
||||
public void testPerformIndexing_fullIndex_getsDataFromProviders() {
|
||||
DummyProvider provider = new DummyProvider();
|
||||
provider.onCreate();
|
||||
ShadowContentResolver.registerProvider(AUTHORITY_ONE, provider);
|
||||
SearchIndexableRaw rawData = getFakeRaw();
|
||||
PreIndexData data = getPreIndexData(rawData);
|
||||
doReturn(data).when(mManager).getIndexDataFromProviders(anyList(), anyBoolean());
|
||||
doReturn(true).when(mManager).isFullIndex(any(Context.class), anyString(), anyString(),
|
||||
anyString());
|
||||
|
||||
// Test that Indexables are added for Full indexing
|
||||
when(mPackageManager.queryIntentContentProviders(any(Intent.class), anyInt()))
|
||||
.thenReturn(getDummyResolveInfo());
|
||||
mManager.performIndexing();
|
||||
|
||||
DatabaseIndexingManager manager =
|
||||
spy(new DatabaseIndexingManager(mContext, PACKAGE_ONE));
|
||||
|
||||
manager.performIndexing();
|
||||
|
||||
verify(manager).addIndexablesFromRemoteProvider(PACKAGE_ONE, AUTHORITY_ONE);
|
||||
verify(manager).updateDatabase(true /* isFullIndex */, Locale.getDefault().toString());
|
||||
verify(mManager).updateDatabase(data, true /* isFullIndex */,
|
||||
Locale.getDefault().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
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(providerInfo);
|
||||
|
||||
public void testPerformIndexing_fullIndex_databaseDropped() {
|
||||
// Initialize the Manager and force rebuild
|
||||
DatabaseIndexingManager manager =
|
||||
spy(new DatabaseIndexingManager(mContext, PACKAGE_ONE));
|
||||
|
||||
manager.mDataToProcess.dataToUpdate.clear();
|
||||
|
||||
manager.performIndexing();
|
||||
|
||||
verify(manager, times(0)).addDataToDatabase(any(SQLiteDatabase.class), anyString(),
|
||||
anyList(), anyMap());
|
||||
verify(manager, times(0)).addIndexablesFromRemoteProvider(PACKAGE_ONE, AUTHORITY_ONE);
|
||||
verify(manager).updateDataInDatabase(any(SQLiteDatabase.class), anyMap());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPerformIndexing_localeChanged_databaseDropped() {
|
||||
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());
|
||||
|
||||
// Initialize the Manager
|
||||
DatabaseIndexingManager manager =
|
||||
spy(new DatabaseIndexingManager(mContext, PACKAGE_ONE));
|
||||
spy(new DatabaseIndexingManager(mContext));
|
||||
doReturn(false).when(mManager).isFullIndex(any(Context.class), anyString(), anyString(),
|
||||
anyString());
|
||||
|
||||
// Insert data point which will be dropped
|
||||
final String oldTitle = "This is French";
|
||||
insertSpecialCase(oldTitle, true, "key");
|
||||
|
||||
// Add a data point to be added by the indexing
|
||||
SearchIndexableRaw raw = new SearchIndexableRaw(mContext);
|
||||
final String newTitle = "This is English";
|
||||
raw.title = newTitle;
|
||||
manager.mDataToProcess.dataToUpdate.add(raw);
|
||||
insertSpecialCase("Ceci n'est pas un pipe", true, "oui oui mon ami");
|
||||
|
||||
manager.performIndexing();
|
||||
|
||||
// Assert that the New Title is inserted
|
||||
final Cursor newCursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE data_title = '" +
|
||||
newTitle + "'", null);
|
||||
assertThat(newCursor.getCount()).isEqualTo(1);
|
||||
|
||||
// Assert that the Old Title is no longer in the database, since it was dropped
|
||||
final Cursor oldCursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE data_title = '" +
|
||||
oldTitle + "'", null);
|
||||
final Cursor oldCursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
|
||||
|
||||
assertThat(oldCursor.getCount()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPerformIndexing_onOta_FullIndex() {
|
||||
DummyProvider provider = new DummyProvider();
|
||||
provider.onCreate();
|
||||
ShadowContentResolver.registerProvider(
|
||||
AUTHORITY_ONE, provider
|
||||
);
|
||||
public void testPerformIndexing_isfullIndex() {
|
||||
SearchIndexableRaw rawData = getFakeRaw();
|
||||
PreIndexData data = getPreIndexData(rawData);
|
||||
doReturn(data).when(mManager).getIndexDataFromProviders(anyList(), anyBoolean());
|
||||
doReturn(true).when(mManager).isFullIndex(any(Context.class), anyString(), anyString(),
|
||||
anyString());
|
||||
|
||||
// Test that Indexables are added for Full indexing
|
||||
when(mPackageManager.queryIntentContentProviders(any(Intent.class), anyInt()))
|
||||
.thenReturn(getDummyResolveInfo());
|
||||
mManager.performIndexing();
|
||||
|
||||
DatabaseIndexingManager manager =
|
||||
spy(new DatabaseIndexingManager(mContext, PACKAGE_ONE));
|
||||
|
||||
manager.performIndexing();
|
||||
|
||||
verify(manager).updateDatabase(true /* isFullIndex */, Locale.getDefault().toString());
|
||||
verify(mManager).updateDatabase(data, true /* isFullIndex */,
|
||||
Locale.getDefault().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPerformIndexing_onPackageChange_shouldFullIndex() {
|
||||
public void testPerformIndexing_onPackageChange_fullIndex() {
|
||||
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(
|
||||
assertThat(mManager.isFullIndex(
|
||||
mContext, locale, buildNumber,
|
||||
IndexDatabaseHelper.buildProviderVersionedNames(providers)))
|
||||
.isFalse();
|
||||
@@ -869,65 +812,46 @@ public class DatabaseIndexingManagerTest {
|
||||
// Change provider version number, this should trigger full index.
|
||||
providers.get(0).providerInfo.applicationInfo.versionCode++;
|
||||
|
||||
assertThat(IndexDatabaseHelper.isFullIndex(mContext, locale, buildNumber,
|
||||
assertThat(mManager.isFullIndex(mContext, locale, buildNumber,
|
||||
IndexDatabaseHelper.buildProviderVersionedNames(providers)))
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPerformIndexing_onOta_buildNumberIsCached() {
|
||||
DummyProvider provider = new DummyProvider();
|
||||
provider.onCreate();
|
||||
ShadowContentResolver.registerProvider(
|
||||
AUTHORITY_ONE, provider
|
||||
);
|
||||
mManager.performIndexing();
|
||||
|
||||
// Test that Indexables are added for Full indexing
|
||||
when(mPackageManager.queryIntentContentProviders(any(Intent.class), anyInt()))
|
||||
.thenReturn(getDummyResolveInfo());
|
||||
|
||||
DatabaseIndexingManager manager =
|
||||
spy(new DatabaseIndexingManager(mContext, PACKAGE_ONE));
|
||||
|
||||
manager.performIndexing();
|
||||
|
||||
assertThat(IndexDatabaseHelper.getInstance(mContext).isBuildIndexed(mContext,
|
||||
Build.FINGERPRINT)).isTrue();
|
||||
assertThat(IndexDatabaseHelper.isBuildIndexed(mContext, Build.FINGERPRINT)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFullUpdatedDatabase_noData_addDataToDatabaseNotCalled() {
|
||||
mManager.updateDatabase(true /* isFullIndex */, localeStr);
|
||||
mManager.mDataToProcess.dataToUpdate.clear();
|
||||
PreIndexData emptydata = new PreIndexData();
|
||||
mManager.updateDatabase(emptydata, true /* isFullIndex */, localeStr);
|
||||
verify(mManager, times(0)).addDataToDatabase(any(SQLiteDatabase.class), anyString(),
|
||||
anyList(), anyMap());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFullUpdatedDatabase_updatedDataInDatabaseNotCalled() {
|
||||
mManager.updateDatabase(true /* isFullIndex */, localeStr);
|
||||
verify(mManager, times(0)).updateDataInDatabase(any(SQLiteDatabase.class), anyMap());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocaleUpdated_afterIndexing_localeNotAdded() {
|
||||
mManager.updateDatabase(true /* isFullIndex */, localeStr);
|
||||
assertThat(IndexDatabaseHelper.getInstance(mContext)
|
||||
.isLocaleAlreadyIndexed(mContext, localeStr)).isFalse();
|
||||
PreIndexData emptydata = new PreIndexData();
|
||||
mManager.updateDatabase(emptydata, true /* isFullIndex */, localeStr);
|
||||
|
||||
assertThat(IndexDatabaseHelper.isLocaleAlreadyIndexed(mContext, localeStr)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocaleUpdated_afterFullIndexing_localeAdded() {
|
||||
mManager.performIndexing();
|
||||
assertThat(IndexDatabaseHelper.getInstance(mContext)
|
||||
.isLocaleAlreadyIndexed(mContext, localeStr)).isTrue();
|
||||
assertThat(IndexDatabaseHelper.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(true /* isFullIndex */, localeStr);
|
||||
PreIndexData indexData = new PreIndexData();
|
||||
indexData.dataToUpdate.add(getFakeRaw());
|
||||
mManager.updateDatabase(indexData, true /* isFullIndex */, localeStr);
|
||||
|
||||
Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
|
||||
cursor.moveToPosition(0);
|
||||
@@ -1020,8 +944,8 @@ public class DatabaseIndexingManagerTest {
|
||||
@Test
|
||||
public void testEmptyNonIndexableKeys_emptyDataKeyResources_addedToDatabase() {
|
||||
insertSpecialCase(TITLE_ONE, true /* enabled */, null /* dataReferenceKey */);
|
||||
|
||||
mManager.updateDatabase(false, localeStr);
|
||||
PreIndexData emptydata = new PreIndexData();
|
||||
mManager.updateDatabase(emptydata, false /* needsReindexing */, localeStr);
|
||||
|
||||
Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE enabled = 1", null);
|
||||
cursor.moveToPosition(0);
|
||||
@@ -1111,46 +1035,6 @@ public class DatabaseIndexingManagerTest {
|
||||
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;
|
||||
info.providerInfo.applicationInfo = new ApplicationInfo();
|
||||
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(INDEXABLES_RAW_COLUMNS);
|
||||
final String BLANK = "";
|
||||
|
||||
ArrayList<String> item =
|
||||
new ArrayList<>(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());
|
||||
@@ -1179,43 +1063,22 @@ public class DatabaseIndexingManagerTest {
|
||||
mDb.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX, null, values);
|
||||
}
|
||||
|
||||
private class DummyProvider extends ContentProvider {
|
||||
private PreIndexData getPreIndexData(SearchIndexableData fakeData) {
|
||||
PreIndexData data = new PreIndexData();
|
||||
data.dataToUpdate.add(fakeData);
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return false;
|
||||
}
|
||||
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;
|
||||
info.providerInfo.applicationInfo = new ApplicationInfo();
|
||||
infoList.add(info);
|
||||
|
||||
@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;
|
||||
}
|
||||
return infoList;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* 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.search.indexing;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.ProviderInfo;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.net.Uri;
|
||||
import android.provider.SearchIndexableResource;
|
||||
import com.android.settings.TestConfig;
|
||||
import com.android.settings.search.SearchIndexableRaw;
|
||||
import com.android.settings.testutils.SettingsRobolectricTestRunner;
|
||||
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 java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
@RunWith(SettingsRobolectricTestRunner.class)
|
||||
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
|
||||
public class IndexableDataCollectorTest {
|
||||
|
||||
private final String AUTHORITY_ONE = "authority";
|
||||
private final String PACKAGE_ONE = "com.android.settings";
|
||||
|
||||
@Mock
|
||||
ContentResolver mResolver;
|
||||
|
||||
Context mContext;
|
||||
|
||||
IndexableDataCollector mDataCollector;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mContext = spy(RuntimeEnvironment.application);
|
||||
doReturn(mResolver).when(mContext).getContentResolver();
|
||||
//doReturn(mPackageManager).when(mContext).getPackageManager();
|
||||
|
||||
mDataCollector = spy(new IndexableDataCollector(mContext));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCollectIndexableData_addsResourceData() {
|
||||
final List<ResolveInfo> providerInfo = getDummyResolveInfo();
|
||||
doReturn(true).when(mDataCollector).isWellKnownProvider(any(ResolveInfo.class));
|
||||
|
||||
List<SearchIndexableResource> resources = getFakeResource();
|
||||
doReturn(resources).when(mDataCollector).getIndexablesForXmlResourceUri(
|
||||
any(Context.class), anyString(), any(Uri.class), any(String[].class));
|
||||
|
||||
PreIndexData data = mDataCollector.collectIndexableData(providerInfo,
|
||||
true /* isFullIndex */);
|
||||
|
||||
assertThat(data.dataToUpdate).containsAllIn(resources);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCollectIndexableData_addsRawData() {
|
||||
final List<ResolveInfo> providerInfo = getDummyResolveInfo();
|
||||
doReturn(true).when(mDataCollector).isWellKnownProvider(any(ResolveInfo.class));
|
||||
|
||||
List<SearchIndexableRaw> rawData = getFakeRaw();
|
||||
doReturn(rawData).when(mDataCollector).getIndexablesForRawDataUri(any(Context.class),
|
||||
anyString(), any(Uri.class), any(String[].class));
|
||||
|
||||
|
||||
PreIndexData data = mDataCollector.collectIndexableData(providerInfo,
|
||||
true /* isFullIndex */);
|
||||
|
||||
assertThat(data.dataToUpdate).containsAllIn(rawData);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCollectIndexableData_addsNonIndexables() {
|
||||
final List<ResolveInfo> providerInfo = getDummyResolveInfo();
|
||||
doReturn(true).when(mDataCollector).isWellKnownProvider(any(ResolveInfo.class));
|
||||
|
||||
List<String> niks = getFakeNonIndexables();
|
||||
|
||||
doReturn(niks).when(mDataCollector).getNonIndexablesKeysFromRemoteProvider(anyString(),
|
||||
anyString());
|
||||
|
||||
PreIndexData data = mDataCollector.collectIndexableData(providerInfo,
|
||||
true /* isFullIndex */);
|
||||
|
||||
assertThat(data.nonIndexableKeys.get(AUTHORITY_ONE)).containsAllIn(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;
|
||||
info.providerInfo.applicationInfo = new ApplicationInfo();
|
||||
infoList.add(info);
|
||||
|
||||
return infoList;
|
||||
}
|
||||
|
||||
private List<SearchIndexableResource> getFakeResource() {
|
||||
List<SearchIndexableResource> resources = new ArrayList<>();
|
||||
final String BLANK = "";
|
||||
|
||||
SearchIndexableResource sir = new SearchIndexableResource(mContext);
|
||||
sir.rank = 0;
|
||||
sir.xmlResId = 0;
|
||||
sir.className = BLANK;
|
||||
sir.packageName = BLANK;
|
||||
sir.iconResId = 0;
|
||||
sir.intentAction = BLANK;
|
||||
sir.intentTargetPackage = BLANK;
|
||||
sir.intentTargetClass = BLANK;
|
||||
sir.enabled = true;
|
||||
resources.add(sir);
|
||||
|
||||
return resources;
|
||||
}
|
||||
|
||||
private List<SearchIndexableRaw> getFakeRaw() {
|
||||
List<SearchIndexableRaw> rawData = new ArrayList<>();
|
||||
|
||||
SearchIndexableRaw data = new SearchIndexableRaw(mContext);
|
||||
data.title = "bront";
|
||||
data.key = "brint";
|
||||
rawData.add(data);
|
||||
|
||||
return rawData;
|
||||
}
|
||||
|
||||
private List<String> getFakeNonIndexables() {
|
||||
List<String> niks = new ArrayList<>();
|
||||
niks.add("they're");
|
||||
niks.add("good");
|
||||
niks.add("dogs");
|
||||
niks.add("brent");
|
||||
return niks;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user