Move Index provider conversion into Settings provider
Pre-patch, settings search provider would push all of its fragments into to search via SearchIndexableResources with an implicit contract of if the resource's xml == 0, then it was a settings fragment with an Index provider. One, implicit contract is bad. Two, it was messy at indexing time. So this patch moves htat conversion into the search index provider. Such that all of the indexables are either real Resources or Raw. Change-Id: I39f4351c03d123bb9b45edb4df7f924cfaff2b38 Fixes: 65376542 Fixes: 37741509 Test: robotests
This commit is contained in:
@@ -16,9 +16,7 @@
|
|||||||
|
|
||||||
package com.android.settings.search;
|
package com.android.settings.search;
|
||||||
|
|
||||||
import android.provider.SearchIndexableResource;
|
|
||||||
import android.support.annotation.VisibleForTesting;
|
import android.support.annotation.VisibleForTesting;
|
||||||
import android.support.annotation.XmlRes;
|
|
||||||
|
|
||||||
import com.android.settings.DateTimeSettings;
|
import com.android.settings.DateTimeSettings;
|
||||||
import com.android.settings.DeviceInfoSettings;
|
import com.android.settings.DeviceInfoSettings;
|
||||||
@@ -88,22 +86,17 @@ import com.android.settings.wifi.ConfigureWifiSettings;
|
|||||||
import com.android.settings.wifi.WifiSettings;
|
import com.android.settings.wifi.WifiSettings;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public final class SearchIndexableResources {
|
public final class SearchIndexableResources {
|
||||||
@XmlRes
|
|
||||||
public static final int NO_RES_ID = 0;
|
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final HashMap<String, SearchIndexableResource> sResMap = new HashMap<>();
|
static final Set<Class> sProviders = new HashSet<>();
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static void addIndex(Class<?> indexClass) {
|
static void addIndex(Class indexClass) {
|
||||||
String className = indexClass.getName();
|
sProviders.add(indexClass);
|
||||||
SearchIndexableResource resource = new SearchIndexableResource(
|
|
||||||
0 /* rank */, NO_RES_ID, className, NO_RES_ID);
|
|
||||||
|
|
||||||
sResMap.put(className, resource);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static {
|
static {
|
||||||
@@ -178,15 +171,5 @@ public final class SearchIndexableResources {
|
|||||||
private SearchIndexableResources() {
|
private SearchIndexableResources() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int size() {
|
public static Collection<Class> providerValues() { return sProviders;}
|
||||||
return sResMap.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SearchIndexableResource getResourceByName(String className) {
|
|
||||||
return sResMap.get(className);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Collection<SearchIndexableResource> values() {
|
|
||||||
return sResMap.values();
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -17,6 +17,19 @@
|
|||||||
package com.android.settings.search;
|
package com.android.settings.search;
|
||||||
|
|
||||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE;
|
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_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_CLASS_NAME;
|
||||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_ICON_RESID;
|
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_ACTION;
|
||||||
@@ -33,11 +46,12 @@ import android.database.Cursor;
|
|||||||
import android.database.MatrixCursor;
|
import android.database.MatrixCursor;
|
||||||
import android.provider.SearchIndexableResource;
|
import android.provider.SearchIndexableResource;
|
||||||
import android.provider.SearchIndexablesProvider;
|
import android.provider.SearchIndexablesProvider;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.util.ArraySet;
|
import android.util.ArraySet;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class SettingsSearchIndexablesProvider extends SearchIndexablesProvider {
|
public class SettingsSearchIndexablesProvider extends SearchIndexablesProvider {
|
||||||
@@ -60,8 +74,9 @@ public class SettingsSearchIndexablesProvider extends SearchIndexablesProvider {
|
|||||||
@Override
|
@Override
|
||||||
public Cursor queryXmlResources(String[] projection) {
|
public Cursor queryXmlResources(String[] projection) {
|
||||||
MatrixCursor cursor = new MatrixCursor(INDEXABLES_XML_RES_COLUMNS);
|
MatrixCursor cursor = new MatrixCursor(INDEXABLES_XML_RES_COLUMNS);
|
||||||
Collection<SearchIndexableResource> values = SearchIndexableResources.values();
|
final List<SearchIndexableResource> resources =
|
||||||
for (SearchIndexableResource val : values) {
|
getSearchIndexableResourcesFromProvider(getContext());
|
||||||
|
for (SearchIndexableResource val : resources) {
|
||||||
Object[] ref = new Object[INDEXABLES_XML_RES_COLUMNS.length];
|
Object[] ref = new Object[INDEXABLES_XML_RES_COLUMNS.length];
|
||||||
ref[COLUMN_INDEX_XML_RES_RANK] = val.rank;
|
ref[COLUMN_INDEX_XML_RES_RANK] = val.rank;
|
||||||
ref[COLUMN_INDEX_XML_RES_RESID] = val.xmlResId;
|
ref[COLUMN_INDEX_XML_RES_RESID] = val.xmlResId;
|
||||||
@@ -72,13 +87,33 @@ public class SettingsSearchIndexablesProvider extends SearchIndexablesProvider {
|
|||||||
ref[COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS] = null; // intent target class
|
ref[COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS] = null; // intent target class
|
||||||
cursor.addRow(ref);
|
cursor.addRow(ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
return cursor;
|
return cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Cursor queryRawData(String[] projection) {
|
public Cursor queryRawData(String[] projection) {
|
||||||
MatrixCursor result = new MatrixCursor(INDEXABLES_RAW_COLUMNS);
|
MatrixCursor cursor = new MatrixCursor(INDEXABLES_RAW_COLUMNS);
|
||||||
return result;
|
final List<SearchIndexableRaw> raws = getSearchIndexableRawFromProvider(getContext());
|
||||||
|
for (SearchIndexableRaw val : raws) {
|
||||||
|
Object[] ref = new Object[INDEXABLES_RAW_COLUMNS.length];
|
||||||
|
ref[COLUMN_INDEX_RAW_TITLE] = val.title;
|
||||||
|
ref[COLUMN_INDEX_RAW_SUMMARY_ON] = val.summaryOn;
|
||||||
|
ref[COLUMN_INDEX_RAW_SUMMARY_OFF] = val.summaryOff;
|
||||||
|
ref[COLUMN_INDEX_RAW_ENTRIES] = val.entries;
|
||||||
|
ref[COLUMN_INDEX_RAW_KEYWORDS] = val.keywords;
|
||||||
|
ref[COLUMN_INDEX_RAW_SCREEN_TITLE] = val.screenTitle;
|
||||||
|
ref[COLUMN_INDEX_RAW_CLASS_NAME] = val.className;
|
||||||
|
ref[COLUMN_INDEX_RAW_ICON_RESID] = val.iconResId;
|
||||||
|
ref[COLUMN_INDEX_RAW_INTENT_ACTION] = val.intentAction;
|
||||||
|
ref[COLUMN_INDEX_RAW_INTENT_TARGET_PACKAGE] = val.intentTargetPackage;
|
||||||
|
ref[COLUMN_INDEX_RAW_INTENT_TARGET_CLASS] = val.intentTargetClass;
|
||||||
|
ref[COLUMN_INDEX_RAW_KEY] = val.key;
|
||||||
|
ref[COLUMN_INDEX_RAW_USER_ID] = val.userId;
|
||||||
|
cursor.addRow(ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,29 +124,24 @@ public class SettingsSearchIndexablesProvider extends SearchIndexablesProvider {
|
|||||||
@Override
|
@Override
|
||||||
public Cursor queryNonIndexableKeys(String[] projection) {
|
public Cursor queryNonIndexableKeys(String[] projection) {
|
||||||
MatrixCursor cursor = new MatrixCursor(NON_INDEXABLES_KEYS_COLUMNS);
|
MatrixCursor cursor = new MatrixCursor(NON_INDEXABLES_KEYS_COLUMNS);
|
||||||
final Collection<String> values = new HashSet<>();
|
final List<String> nonIndexableKeys = getNonIndexableKeysFromProvider(getContext());
|
||||||
final Context context = getContext();
|
for (String nik : nonIndexableKeys) {
|
||||||
|
final Object[] ref = new Object[NON_INDEXABLES_KEYS_COLUMNS.length];
|
||||||
|
ref[COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE] = nik;
|
||||||
|
cursor.addRow(ref);
|
||||||
|
}
|
||||||
|
|
||||||
for (SearchIndexableResource sir : SearchIndexableResources.values()) {
|
return cursor;
|
||||||
if (DEBUG) {
|
}
|
||||||
Log.d(TAG, "Getting non-indexable from " + sir.className);
|
|
||||||
}
|
private List<String> getNonIndexableKeysFromProvider(Context context) {
|
||||||
|
final Collection<Class> values = SearchIndexableResources.providerValues();
|
||||||
|
final List<String> nonIndexableKeys = new ArrayList<>();
|
||||||
|
|
||||||
|
for (Class<?> clazz : values) {
|
||||||
final long startTime = System.currentTimeMillis();
|
final long startTime = System.currentTimeMillis();
|
||||||
final Class<?> clazz = DatabaseIndexingUtils.getIndexableClass(sir.className);
|
Indexable.SearchIndexProvider provider = DatabaseIndexingUtils.getSearchIndexProvider(
|
||||||
if (clazz == null) {
|
clazz);
|
||||||
Log.d(TAG, "SearchIndexableResource '" + sir.className +
|
|
||||||
"' should implement the " + Indexable.class.getName() + " interface!");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Indexable.SearchIndexProvider provider =
|
|
||||||
DatabaseIndexingUtils.getSearchIndexProvider(clazz);
|
|
||||||
|
|
||||||
if (provider == null) {
|
|
||||||
Log.d(TAG, "Unable to get SearchIndexableProvider from " + clazz);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> providerNonIndexableKeys = provider.getNonIndexableKeys(context);
|
List<String> providerNonIndexableKeys = provider.getNonIndexableKeys(context);
|
||||||
|
|
||||||
if (providerNonIndexableKeys == null || providerNonIndexableKeys.isEmpty()) {
|
if (providerNonIndexableKeys == null || providerNonIndexableKeys.isEmpty()) {
|
||||||
@@ -123,22 +153,71 @@ public class SettingsSearchIndexablesProvider extends SearchIndexablesProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (providerNonIndexableKeys.removeAll(INVALID_KEYS)) {
|
if (providerNonIndexableKeys.removeAll(INVALID_KEYS)) {
|
||||||
Log.v(TAG, clazz.getName() + " tried to add an empty non-indexable key");
|
Log.v(TAG, provider + " tried to add an empty non-indexable key");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
final long totalTime = System.currentTimeMillis() - startTime;
|
final long totalTime = System.currentTimeMillis() - startTime;
|
||||||
Log.d(TAG, "Non-indexables " + providerNonIndexableKeys.size() + ", total time "
|
Log.d(TAG, "Non-indexables " + providerNonIndexableKeys.size() + ", total time "
|
||||||
+ totalTime);
|
+ totalTime);
|
||||||
}
|
}
|
||||||
values.addAll(providerNonIndexableKeys);
|
|
||||||
|
nonIndexableKeys.addAll(providerNonIndexableKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (String nik : values) {
|
return nonIndexableKeys;
|
||||||
|
}
|
||||||
|
|
||||||
final Object[] ref = new Object[NON_INDEXABLES_KEYS_COLUMNS.length];
|
private List<SearchIndexableResource> getSearchIndexableResourcesFromProvider(Context context) {
|
||||||
ref[COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE] = nik;
|
Collection<Class> values = SearchIndexableResources.providerValues();
|
||||||
cursor.addRow(ref);
|
List<SearchIndexableResource> resourceList = new ArrayList<>();
|
||||||
|
|
||||||
|
for (Class<?> clazz : values) {
|
||||||
|
Indexable.SearchIndexProvider provider = DatabaseIndexingUtils.getSearchIndexProvider(
|
||||||
|
clazz);
|
||||||
|
|
||||||
|
final List<SearchIndexableResource> resList =
|
||||||
|
provider.getXmlResourcesToIndex(context, true);
|
||||||
|
|
||||||
|
if (resList == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (SearchIndexableResource item : resList) {
|
||||||
|
item.className = TextUtils.isEmpty(item.className)
|
||||||
|
? clazz.getName()
|
||||||
|
: item.className;
|
||||||
|
}
|
||||||
|
|
||||||
|
resourceList.addAll(resList);
|
||||||
}
|
}
|
||||||
return cursor;
|
|
||||||
|
return resourceList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SearchIndexableRaw> getSearchIndexableRawFromProvider(Context context) {
|
||||||
|
final Collection<Class> values = SearchIndexableResources.providerValues();
|
||||||
|
final List<SearchIndexableRaw> rawList = new ArrayList<>();
|
||||||
|
|
||||||
|
for (Class<?> clazz : values) {
|
||||||
|
Indexable.SearchIndexProvider provider = DatabaseIndexingUtils.getSearchIndexProvider(
|
||||||
|
clazz);
|
||||||
|
final List<SearchIndexableRaw> providerRaws = provider.getRawDataToIndex(context,
|
||||||
|
true /* enabled */);
|
||||||
|
|
||||||
|
if (providerRaws == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (SearchIndexableRaw raw : providerRaws) {
|
||||||
|
// The classname and intent information comes from the PreIndexData
|
||||||
|
// This will be more clear when provider conversion is done at PreIndex time.
|
||||||
|
raw.className = clazz.getName();
|
||||||
|
|
||||||
|
}
|
||||||
|
rawList.addAll(providerRaws);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rawList;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -31,7 +31,6 @@ import android.util.Xml;
|
|||||||
|
|
||||||
import com.android.settings.core.PreferenceControllerMixin;
|
import com.android.settings.core.PreferenceControllerMixin;
|
||||||
import com.android.settings.search.DatabaseIndexingUtils;
|
import com.android.settings.search.DatabaseIndexingUtils;
|
||||||
import com.android.settings.search.Indexable;
|
|
||||||
import com.android.settings.search.ResultPayload;
|
import com.android.settings.search.ResultPayload;
|
||||||
import com.android.settings.search.SearchIndexableRaw;
|
import com.android.settings.search.SearchIndexableRaw;
|
||||||
import com.android.settings.search.XmlParserUtils;
|
import com.android.settings.search.XmlParserUtils;
|
||||||
@@ -89,21 +88,8 @@ public class IndexDataConverter {
|
|||||||
final SearchIndexableResource sir = (SearchIndexableResource) data;
|
final SearchIndexableResource sir = (SearchIndexableResource) data;
|
||||||
final Set<String> resourceNonIndexableKeys =
|
final Set<String> resourceNonIndexableKeys =
|
||||||
getNonIndexableKeysForResource(nonIndexableKeys, sir.packageName);
|
getNonIndexableKeysForResource(nonIndexableKeys, sir.packageName);
|
||||||
|
final List<IndexData> resourceData = convertResource(sir, resourceNonIndexableKeys);
|
||||||
if (sir.xmlResId == 0) {
|
indexData.addAll(resourceData);
|
||||||
// Index from provider
|
|
||||||
final Indexable.SearchIndexProvider provider = getSearchProvider(sir);
|
|
||||||
if (provider == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
indexData.addAll(convertIndexProvider(provider, sir, resourceNonIndexableKeys));
|
|
||||||
|
|
||||||
} else {
|
|
||||||
final List<IndexData> resourceData = convertResource(sir,
|
|
||||||
resourceNonIndexableKeys);
|
|
||||||
indexData.addAll(resourceData);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,84 +291,10 @@ public class IndexDataConverter {
|
|||||||
return resourceIndexData;
|
return resourceIndexData;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<IndexData> convertIndexProvider(Indexable.SearchIndexProvider provider,
|
|
||||||
SearchIndexableResource sir, Set<String> nonIndexableKeys) {
|
|
||||||
final List<IndexData> indexData = new ArrayList<>();
|
|
||||||
|
|
||||||
final String className = sir.className;
|
|
||||||
final String intentAction = sir.intentAction;
|
|
||||||
final String intentTargetPackage = sir.intentTargetPackage;
|
|
||||||
|
|
||||||
// TODO (b/65376542) Move provider conversion to PreIndexTime
|
|
||||||
// TODO (b/37741509) Providers don't use general non-indexable keys
|
|
||||||
nonIndexableKeys.addAll(provider.getNonIndexableKeys(mContext));
|
|
||||||
|
|
||||||
final List<SearchIndexableRaw> rawList = provider.getRawDataToIndex(mContext,
|
|
||||||
true /* enabled */);
|
|
||||||
|
|
||||||
if (rawList != null) {
|
|
||||||
for (SearchIndexableRaw raw : rawList) {
|
|
||||||
// The classname and intent information comes from the PreIndexData
|
|
||||||
// This will be more clear when provider conversion is done at PreIndex time.
|
|
||||||
raw.className = className;
|
|
||||||
raw.intentAction = intentAction;
|
|
||||||
raw.intentTargetPackage = intentTargetPackage;
|
|
||||||
|
|
||||||
IndexData.Builder builder = convertRaw(raw, nonIndexableKeys);
|
|
||||||
if (builder != null) {
|
|
||||||
indexData.add(builder.build(mContext));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<SearchIndexableResource> resList =
|
|
||||||
provider.getXmlResourcesToIndex(mContext, true);
|
|
||||||
|
|
||||||
if (resList != null) {
|
|
||||||
for (SearchIndexableResource item : resList) {
|
|
||||||
item.className = TextUtils.isEmpty(item.className)
|
|
||||||
? className
|
|
||||||
: item.className;
|
|
||||||
item.intentAction = TextUtils.isEmpty(item.intentAction)
|
|
||||||
? intentAction
|
|
||||||
: item.intentAction;
|
|
||||||
item.intentTargetPackage = TextUtils.isEmpty(item.intentTargetPackage)
|
|
||||||
? intentTargetPackage
|
|
||||||
: item.intentTargetPackage;
|
|
||||||
|
|
||||||
indexData.addAll(convertResource(item, nonIndexableKeys));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return indexData;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Set<String> getNonIndexableKeysForResource(Map<String, Set<String>> nonIndexableKeys,
|
private Set<String> getNonIndexableKeysForResource(Map<String, Set<String>> nonIndexableKeys,
|
||||||
String packageName) {
|
String packageName) {
|
||||||
return nonIndexableKeys.containsKey(packageName)
|
return nonIndexableKeys.containsKey(packageName)
|
||||||
? nonIndexableKeys.get(packageName)
|
? nonIndexableKeys.get(packageName)
|
||||||
: new HashSet<>();
|
: new HashSet<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Return the {@link Indexable.SearchIndexProvider} corresponding to the
|
|
||||||
* class specified by the Class name specified by {@param sir}.
|
|
||||||
*/
|
|
||||||
private Indexable.SearchIndexProvider getSearchProvider(SearchIndexableResource sir) {
|
|
||||||
if (TextUtils.isEmpty(sir.className)) {
|
|
||||||
Log.w(LOG_TAG, "Cannot index an empty Search Provider name!");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Class<?> clazz = DatabaseIndexingUtils.getIndexableClass(sir.className);
|
|
||||||
if (clazz == null) {
|
|
||||||
Log.d(LOG_TAG, "SearchIndexableResource '" + sir.className +
|
|
||||||
"' should implement the " + Indexable.class.getName() + " interface!");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Will be non null only for a Local provider implementing a
|
|
||||||
// SEARCH_INDEX_DATA_PROVIDER field
|
|
||||||
return DatabaseIndexingUtils.getSearchIndexProvider(clazz);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -114,11 +114,10 @@ public class SearchIndexProviderCodeInspector extends CodeInspector {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Must be in SearchProviderRegistry
|
// Must be in SearchProviderRegistry
|
||||||
if (SearchIndexableResources.getResourceByName(className) == null) {
|
if (!SearchIndexableResources.providerValues().contains(clazz)) {
|
||||||
if (!notInSearchIndexableRegistryGrandfatherList.remove(className)) {
|
if (!notInSearchIndexableRegistryGrandfatherList.remove(className)) {
|
||||||
notInSearchProviderRegistry.add(className);
|
notInSearchProviderRegistry.add(className);
|
||||||
}
|
}
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -17,12 +17,13 @@
|
|||||||
package com.android.settings.search;
|
package com.android.settings.search;
|
||||||
|
|
||||||
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE;
|
import static android.provider.SearchIndexablesContract.COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE;
|
||||||
import static com.android.settings.search.SearchIndexableResources.NO_RES_ID;
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import static junit.framework.Assert.fail;
|
||||||
|
|
||||||
import static org.mockito.Mockito.spy;
|
import static org.mockito.Mockito.spy;
|
||||||
|
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.provider.SearchIndexableResource;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import com.android.settings.TestConfig;
|
import com.android.settings.TestConfig;
|
||||||
@@ -35,60 +36,48 @@ import org.junit.Test;
|
|||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.robolectric.annotation.Config;
|
import org.robolectric.annotation.Config;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Set;
|
||||||
|
|
||||||
@RunWith(SettingsRobolectricTestRunner.class)
|
@RunWith(SettingsRobolectricTestRunner.class)
|
||||||
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
|
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
|
||||||
public class SearchIndexableResourcesTest {
|
public class SearchIndexableResourcesTest {
|
||||||
|
|
||||||
Map<String, SearchIndexableResource> sResMapCopy;
|
Set<Class> sProviderClassCopy;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
sResMapCopy = new HashMap<>(SearchIndexableResources.sResMap);
|
sProviderClassCopy = new HashSet<>(SearchIndexableResources.sProviders);
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void cleanUp() {
|
public void cleanUp() {
|
||||||
SearchIndexableResources.sResMap.clear();
|
SearchIndexableResources.sProviders.clear();
|
||||||
for (String key : sResMapCopy.keySet()) {
|
SearchIndexableResources.sProviders.addAll(sProviderClassCopy);
|
||||||
SearchIndexableResources.sResMap.put(key, sResMapCopy.get(key));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAddIndex() {
|
public void testAddIndex() {
|
||||||
|
final Class stringClass = java.lang.String.class;
|
||||||
// Confirms that String.class isn't contained in SearchIndexableResources.
|
// Confirms that String.class isn't contained in SearchIndexableResources.
|
||||||
assertThat(SearchIndexableResources.getResourceByName("java.lang.String")).isNull();
|
assertThat(SearchIndexableResources.sProviders).doesNotContain(stringClass);
|
||||||
final int beforeCount = SearchIndexableResources.values().size();
|
final int beforeCount = SearchIndexableResources.providerValues().size();
|
||||||
|
|
||||||
SearchIndexableResources.addIndex(java.lang.String.class);
|
SearchIndexableResources.addIndex(java.lang.String.class);
|
||||||
final SearchIndexableResource index = SearchIndexableResources
|
|
||||||
.getResourceByName("java.lang.String");
|
|
||||||
|
|
||||||
assertThat(index).isNotNull();
|
assertThat(SearchIndexableResources.sProviders).contains(stringClass);
|
||||||
assertThat(index.className).isEqualTo("java.lang.String");
|
final int afterCount = SearchIndexableResources.providerValues().size();
|
||||||
assertThat(index.xmlResId).isEqualTo(NO_RES_ID);
|
|
||||||
assertThat(index.iconResId).isEqualTo(NO_RES_ID);
|
|
||||||
final int afterCount = SearchIndexableResources.values().size();
|
|
||||||
assertThat(afterCount).isEqualTo(beforeCount + 1);
|
assertThat(afterCount).isEqualTo(beforeCount + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testIndexHasWifiSettings() {
|
public void testIndexHasWifiSettings() {
|
||||||
final SearchIndexableResource index = SearchIndexableResources
|
assertThat(sProviderClassCopy).contains(WifiSettings.class);
|
||||||
.getResourceByName(WifiSettings.class.getName());
|
|
||||||
|
|
||||||
assertThat(index).isNotNull();
|
|
||||||
assertThat(index.className).isEqualTo(WifiSettings.class.getName());
|
|
||||||
assertThat(index.xmlResId).isEqualTo(NO_RES_ID);
|
|
||||||
assertThat(index.iconResId).isEqualTo(NO_RES_ID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNonIndexableKeys_GetsKeyFromProvider() {
|
public void testNonIndexableKeys_GetsKeyFromProvider() {
|
||||||
SearchIndexableResources.sResMap.clear();
|
SearchIndexableResources.sProviders.clear();
|
||||||
SearchIndexableResources.addIndex(FakeIndexProvider.class);
|
SearchIndexableResources.addIndex(FakeIndexProvider.class);
|
||||||
|
|
||||||
SettingsSearchIndexablesProvider provider = spy(new SettingsSearchIndexablesProvider());
|
SettingsSearchIndexablesProvider provider = spy(new SettingsSearchIndexablesProvider());
|
||||||
@@ -105,4 +94,13 @@ public class SearchIndexableResourcesTest {
|
|||||||
|
|
||||||
assertThat(hasTestKey).isTrue();
|
assertThat(hasTestKey).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAllClassNamesHaveProviders() {
|
||||||
|
for (Class clazz: sProviderClassCopy) {
|
||||||
|
if(DatabaseIndexingUtils.getSearchIndexProvider(clazz) == null) {
|
||||||
|
fail(clazz.getName() + "is not an index provider");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,119 @@
|
|||||||
|
package com.android.settings.search;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.spy;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.ProviderInfo;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.provider.SearchIndexablesContract;
|
||||||
|
|
||||||
|
import com.android.settings.R;
|
||||||
|
import com.android.settings.TestConfig;
|
||||||
|
import com.android.settings.search.indexing.FakeSettingsFragment;
|
||||||
|
import com.android.settings.testutils.SettingsRobolectricTestRunner;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.robolectric.RuntimeEnvironment;
|
||||||
|
import org.robolectric.annotation.Config;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@RunWith(SettingsRobolectricTestRunner.class)
|
||||||
|
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
|
||||||
|
public class SettingsSearchIndexablesProviderTest {
|
||||||
|
|
||||||
|
private final String BASE_AUTHORITY = "com.android.settings";
|
||||||
|
|
||||||
|
private SettingsSearchIndexablesProvider mProvider;
|
||||||
|
|
||||||
|
Set<Class> sProviderClasses;
|
||||||
|
Context mContext;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
mContext = RuntimeEnvironment.application;
|
||||||
|
|
||||||
|
mProvider = spy(new SettingsSearchIndexablesProvider());
|
||||||
|
ProviderInfo info = new ProviderInfo();
|
||||||
|
info.exported = true;
|
||||||
|
info.grantUriPermissions = true;
|
||||||
|
info.authority = BASE_AUTHORITY;
|
||||||
|
info.readPermission = Manifest.permission.READ_SEARCH_INDEXABLES;
|
||||||
|
mProvider.attachInfo(mContext, info);
|
||||||
|
|
||||||
|
sProviderClasses = new HashSet<>(SearchIndexableResources.sProviders);
|
||||||
|
SearchIndexableResources.sProviders.clear();
|
||||||
|
SearchIndexableResources.sProviders.add(FakeSettingsFragment.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void cleanUp() {
|
||||||
|
SearchIndexableResources.sProviders.clear();
|
||||||
|
SearchIndexableResources.sProviders.addAll(sProviderClasses);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRawColumnFetched() {
|
||||||
|
Uri rawUri = Uri.parse("content://" + BASE_AUTHORITY + "/" +
|
||||||
|
SearchIndexablesContract.INDEXABLES_RAW_PATH);
|
||||||
|
|
||||||
|
final Cursor cursor = mProvider.query(rawUri,
|
||||||
|
SearchIndexablesContract.INDEXABLES_RAW_COLUMNS, null, null, null);
|
||||||
|
|
||||||
|
cursor.moveToFirst();
|
||||||
|
assertThat(cursor.getString(1)).isEqualTo(FakeSettingsFragment.TITLE);
|
||||||
|
assertThat(cursor.getString(2)).isEqualTo(FakeSettingsFragment.SUMMARY_ON);
|
||||||
|
assertThat(cursor.getString(3)).isEqualTo(FakeSettingsFragment.SUMMARY_OFF);
|
||||||
|
assertThat(cursor.getString(4)).isEqualTo(FakeSettingsFragment.ENTRIES);
|
||||||
|
assertThat(cursor.getString(5)).isEqualTo(FakeSettingsFragment.KEYWORDS);
|
||||||
|
assertThat(cursor.getString(6)).isEqualTo(FakeSettingsFragment.SCREEN_TITLE);
|
||||||
|
assertThat(cursor.getString(7)).isEqualTo(FakeSettingsFragment.CLASS_NAME);
|
||||||
|
assertThat(cursor.getInt(8)).isEqualTo(FakeSettingsFragment.ICON);
|
||||||
|
assertThat(cursor.getString(9)).isEqualTo(FakeSettingsFragment.INTENT_ACTION);
|
||||||
|
assertThat(cursor.getString(10)).isEqualTo(FakeSettingsFragment.TARGET_PACKAGE);
|
||||||
|
assertThat(cursor.getString(11)).isEqualTo(FakeSettingsFragment.TARGET_CLASS);
|
||||||
|
assertThat(cursor.getString(12)).isEqualTo(FakeSettingsFragment.KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResourcesColumnFetched() {
|
||||||
|
Uri rawUri = Uri.parse("content://" + BASE_AUTHORITY + "/" +
|
||||||
|
SearchIndexablesContract.INDEXABLES_XML_RES_PATH);
|
||||||
|
|
||||||
|
final Cursor cursor = mProvider.query(rawUri,
|
||||||
|
SearchIndexablesContract.INDEXABLES_XML_RES_COLUMNS, null, null, null);
|
||||||
|
|
||||||
|
cursor.moveToFirst();
|
||||||
|
assertThat(cursor.getCount()).isEqualTo(1);
|
||||||
|
assertThat(cursor.getInt(1)).isEqualTo(R.xml.display_settings);
|
||||||
|
assertThat(cursor.getString(2)).isEqualTo(FakeSettingsFragment.CLASS_NAME);
|
||||||
|
assertThat(cursor.getInt(3)).isEqualTo(0);
|
||||||
|
assertThat(cursor.getString(4)).isNull();
|
||||||
|
assertThat(cursor.getString(5)).isNull();
|
||||||
|
assertThat(cursor.getString(6)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNonIndexablesColumnFetched() {
|
||||||
|
Uri rawUri = Uri.parse("content://" + BASE_AUTHORITY + "/" +
|
||||||
|
SearchIndexablesContract.NON_INDEXABLES_KEYS_PATH);
|
||||||
|
//final ContentResolver resolver = mContext.getContentResolver();
|
||||||
|
|
||||||
|
final Cursor cursor = mProvider.query(rawUri,
|
||||||
|
SearchIndexablesContract.NON_INDEXABLES_KEYS_COLUMNS, null, null, null);
|
||||||
|
|
||||||
|
cursor.moveToFirst();
|
||||||
|
assertThat(cursor.getCount()).isEqualTo(2);
|
||||||
|
assertThat(cursor.getString(0)).isEqualTo("pref_key_1");
|
||||||
|
cursor.moveToNext();
|
||||||
|
assertThat(cursor.getString(0)).isEqualTo("pref_key_3");
|
||||||
|
}
|
||||||
|
}
|
@@ -266,55 +266,6 @@ public class IndexDataConverterTest {
|
|||||||
assertThat(row.iconResId).isGreaterThan(0);
|
assertThat(row.iconResId).isGreaterThan(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests for the flow: IndexOneResource -> IndexFromProvider -> IndexFromResource ->
|
|
||||||
// UpdateOneRowWithFilteredData -> UpdateOneRow
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAddProviderWithResource_rowInserted() {
|
|
||||||
final SearchIndexableResource resource = getFakeResource(0 /* xml */);
|
|
||||||
resource.className = FAKE_CLASS_NAME;
|
|
||||||
final PreIndexData preIndexData = new PreIndexData();
|
|
||||||
preIndexData.dataToUpdate.add(resource);
|
|
||||||
|
|
||||||
List<IndexData> indexData = mConverter.convertPreIndexDataToIndexData(preIndexData);
|
|
||||||
|
|
||||||
assertThat(indexData.size()).isEqualTo(NUM_FAKE_FRAGMENT_ENTRIES);
|
|
||||||
assertThat(findIndexDataForTitle(indexData, PAGE_TITLE)).isNotNull();
|
|
||||||
assertThat(findIndexDataForTitle(indexData, TITLE_ONE)).isNotNull();
|
|
||||||
assertThat(findIndexDataForTitle(indexData, TITLE_TWO)).isNotNull();
|
|
||||||
assertThat(findIndexDataForTitle(indexData, TITLE_THREE)).isNotNull();
|
|
||||||
assertThat(findIndexDataForTitle(indexData, TITLE_FOUR)).isNotNull();
|
|
||||||
assertThat(findIndexDataForTitle(indexData, TITLE_FIVE)).isNotNull();
|
|
||||||
assertThat(findIndexDataForTitle(indexData, FakeSettingsFragment.TITLE)).isNotNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAddProviderWithRaw_rowInserted() {
|
|
||||||
final SearchIndexableResource resource = getFakeResource(0 /* xml */);
|
|
||||||
resource.className = FAKE_CLASS_NAME;
|
|
||||||
final PreIndexData preIndexData = new PreIndexData();
|
|
||||||
preIndexData.dataToUpdate.add(resource);
|
|
||||||
|
|
||||||
List<IndexData> indexData = mConverter.convertPreIndexDataToIndexData(preIndexData);
|
|
||||||
|
|
||||||
final IndexData data = findIndexDataForTitle(indexData, FakeSettingsFragment.TITLE);
|
|
||||||
assertFakeFragment(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAddProvider_disabledRows() {
|
|
||||||
// Note that in FakeSettingsFragment, preferences 1 and 3 are disabled.
|
|
||||||
final SearchIndexableResource resource = getFakeResource(0 /* xml */);
|
|
||||||
resource.className = FAKE_CLASS_NAME;
|
|
||||||
|
|
||||||
final PreIndexData preIndexData = new PreIndexData();
|
|
||||||
preIndexData.dataToUpdate.add(resource);
|
|
||||||
|
|
||||||
List<IndexData> indexData = mConverter.convertPreIndexDataToIndexData(preIndexData);
|
|
||||||
|
|
||||||
assertThat(getEnabledResultCount(indexData)).isEqualTo(NUM_ENABLED_FAKE_FRAGMENT_ENTRIES);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testResource_sameTitleForSettingAndPage_titleNotInserted() {
|
public void testResource_sameTitleForSettingAndPage_titleNotInserted() {
|
||||||
final SearchIndexableResource resource = getFakeResource(R.xml.about_legal);
|
final SearchIndexableResource resource = getFakeResource(R.xml.about_legal);
|
||||||
|
@@ -54,8 +54,7 @@ public class PreferenceControllerContractTest {
|
|||||||
public void controllersInSearchShouldImplementPreferenceControllerMixin() {
|
public void controllersInSearchShouldImplementPreferenceControllerMixin() {
|
||||||
final Set<String> errorClasses = new ArraySet<>();
|
final Set<String> errorClasses = new ArraySet<>();
|
||||||
|
|
||||||
for (SearchIndexableResource page : SearchIndexableResources.values()) {
|
for (Class clazz: SearchIndexableResources.providerValues()) {
|
||||||
final Class<?> clazz = DatabaseIndexingUtils.getIndexableClass(page.className);
|
|
||||||
|
|
||||||
final Indexable.SearchIndexProvider provider =
|
final Indexable.SearchIndexProvider provider =
|
||||||
DatabaseIndexingUtils.getSearchIndexProvider(clazz);
|
DatabaseIndexingUtils.getSearchIndexProvider(clazz);
|
||||||
|
@@ -90,8 +90,8 @@ public class UniquePreferenceTest {
|
|||||||
final Set<String> uniqueKeys = new HashSet<>();
|
final Set<String> uniqueKeys = new HashSet<>();
|
||||||
final Set<String> nullKeyClasses = new HashSet<>();
|
final Set<String> nullKeyClasses = new HashSet<>();
|
||||||
final Set<String> duplicatedKeys = new HashSet<>();
|
final Set<String> duplicatedKeys = new HashSet<>();
|
||||||
for (SearchIndexableResource sir : SearchIndexableResources.values()) {
|
for (Class<?> clazz : SearchIndexableResources.providerValues()) {
|
||||||
verifyPreferenceIdInXml(uniqueKeys, duplicatedKeys, nullKeyClasses, sir);
|
verifyPreferenceIdInXml(uniqueKeys, duplicatedKeys, nullKeyClasses, clazz);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!nullKeyClasses.isEmpty()) {
|
if (!nullKeyClasses.isEmpty()) {
|
||||||
@@ -115,22 +115,24 @@ public class UniquePreferenceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void verifyPreferenceIdInXml(Set<String> uniqueKeys, Set<String> duplicatedKeys,
|
private void verifyPreferenceIdInXml(Set<String> uniqueKeys, Set<String> duplicatedKeys,
|
||||||
Set<String> nullKeyClasses, SearchIndexableResource page)
|
Set<String> nullKeyClasses, Class<?> clazz)
|
||||||
throws IOException, XmlPullParserException, Resources.NotFoundException {
|
throws IOException, XmlPullParserException, Resources.NotFoundException {
|
||||||
final Class<?> clazz = DatabaseIndexingUtils.getIndexableClass(page.className);
|
if (clazz == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final String className = clazz.getName();
|
||||||
final Indexable.SearchIndexProvider provider =
|
final Indexable.SearchIndexProvider provider =
|
||||||
DatabaseIndexingUtils.getSearchIndexProvider(clazz);
|
DatabaseIndexingUtils.getSearchIndexProvider(clazz);
|
||||||
final List<SearchIndexableResource> resourcesToIndex =
|
final List<SearchIndexableResource> resourcesToIndex =
|
||||||
provider.getXmlResourcesToIndex(mContext, true);
|
provider.getXmlResourcesToIndex(mContext, true);
|
||||||
if (resourcesToIndex == null) {
|
if (resourcesToIndex == null) {
|
||||||
Log.d(TAG, page.className + "is not providing SearchIndexableResource, skipping");
|
Log.d(TAG, className + "is not providing SearchIndexableResource, skipping");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (SearchIndexableResource sir : resourcesToIndex) {
|
for (SearchIndexableResource sir : resourcesToIndex) {
|
||||||
if (sir.xmlResId <= 0) {
|
if (sir.xmlResId <= 0) {
|
||||||
Log.d(TAG, page.className + " doesn't have a valid xml to index.");
|
Log.d(TAG, className + " doesn't have a valid xml to index.");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final XmlResourceParser parser = mContext.getResources().getXml(sir.xmlResId);
|
final XmlResourceParser parser = mContext.getResources().getXml(sir.xmlResId);
|
||||||
@@ -154,14 +156,14 @@ public class UniquePreferenceTest {
|
|||||||
final String key = XmlParserUtils.getDataKey(mContext, attrs);
|
final String key = XmlParserUtils.getDataKey(mContext, attrs);
|
||||||
if (TextUtils.isEmpty(key)) {
|
if (TextUtils.isEmpty(key)) {
|
||||||
Log.e(TAG, "Every preference must have an key; found null key"
|
Log.e(TAG, "Every preference must have an key; found null key"
|
||||||
+ " in " + page.className
|
+ " in " + className
|
||||||
+ " at " + parser.getPositionDescription());
|
+ " at " + parser.getPositionDescription());
|
||||||
nullKeyClasses.add(page.className);
|
nullKeyClasses.add(className);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (uniqueKeys.contains(key) && !WHITELISTED_DUPLICATE_KEYS.contains(key)) {
|
if (uniqueKeys.contains(key) && !WHITELISTED_DUPLICATE_KEYS.contains(key)) {
|
||||||
Log.e(TAG, "Every preference key must unique; found " + nodeName
|
Log.e(TAG, "Every preference key must unique; found " + nodeName
|
||||||
+ " in " + page.className
|
+ " in " + className
|
||||||
+ " at " + parser.getPositionDescription());
|
+ " at " + parser.getPositionDescription());
|
||||||
duplicatedKeys.add(key);
|
duplicatedKeys.add(key);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user