Merge "Separate collection of indexable data from indexing"

This commit is contained in:
TreeHugger Robot
2017-09-07 17:51:22 +00:00
committed by Android (Google) Code Review
9 changed files with 729 additions and 605 deletions

View File

@@ -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;

View File

@@ -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;
}
}
}

View File

@@ -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,

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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;
}
}
}

View 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();
}
}