blob: 0769af5d748cc819e1fd772160895c7aa7090f1e [file] [log] [blame]
/*
* Copyright (C) 2016 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;
import static com.google.common.truth.Truth.assertWithMessage;
import android.util.ArraySet;
import android.util.Log;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.core.codeinspection.CodeInspector;
import com.android.settings.dashboard.DashboardFragmentSearchIndexProviderInspector;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* {@link CodeInspector} to ensure fragments implement search components correctly.
*/
public class SearchIndexProviderCodeInspector extends CodeInspector {
private static final String TAG = "SearchCodeInspector";
private static final String NOT_IMPLEMENTING_INDEXABLE_ERROR =
"SettingsPreferenceFragment should implement Indexable, but these do not:\n";
private static final String NOT_CONTAINING_PROVIDER_OBJECT_ERROR =
"Indexable should have public field "
+ DatabaseIndexingManager.FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER
+ " but these are not:\n";
private static final String NOT_SHARING_PREF_CONTROLLERS_BETWEEN_FRAG_AND_PROVIDER =
"DashboardFragment should share pref controllers with its SearchIndexProvider, but "
+ " these are not: \n";
private static final String NOT_IN_INDEXABLE_PROVIDER_REGISTRY =
"Class containing " + DatabaseIndexingManager.FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER
+ " must be added to " + SearchIndexableResources.class.getName()
+ " but these are not: \n";
private final List<String> notImplementingIndexableGrandfatherList;
private final List<String> notImplementingIndexProviderGrandfatherList;
private final List<String> notInSearchIndexableRegistryGrandfatherList;
private final List<String> notSharingPrefControllersGrandfatherList;
public SearchIndexProviderCodeInspector(List<Class<?>> classes) {
super(classes);
notImplementingIndexableGrandfatherList = new ArrayList<>();
notImplementingIndexProviderGrandfatherList = new ArrayList<>();
notInSearchIndexableRegistryGrandfatherList = new ArrayList<>();
notSharingPrefControllersGrandfatherList = new ArrayList<>();
initializeGrandfatherList(notImplementingIndexableGrandfatherList,
"grandfather_not_implementing_indexable");
initializeGrandfatherList(notImplementingIndexProviderGrandfatherList,
"grandfather_not_implementing_index_provider");
initializeGrandfatherList(notInSearchIndexableRegistryGrandfatherList,
"grandfather_not_in_search_index_provider_registry");
initializeGrandfatherList(notSharingPrefControllersGrandfatherList,
"grandfather_not_sharing_pref_controllers_with_search_provider");
}
@Override
public void run() {
final Set<String> notImplementingIndexable = new ArraySet<>();
final Set<String> notImplementingIndexProvider = new ArraySet<>();
final Set<String> notInSearchProviderRegistry = new ArraySet<>();
final Set<String> notSharingPreferenceControllers = new ArraySet<>();
for (Class clazz : mClasses) {
if (!isConcreteSettingsClass(clazz)) {
continue;
}
final String className = clazz.getName();
// Skip fragments if it's not SettingsPreferenceFragment.
if (!SettingsPreferenceFragment.class.isAssignableFrom(clazz)) {
continue;
}
// If it's a SettingsPreferenceFragment, it must also be Indexable.
final boolean implementsIndexable = Indexable.class.isAssignableFrom(clazz);
if (!implementsIndexable) {
if (!notImplementingIndexableGrandfatherList.remove(className)) {
notImplementingIndexable.add(className);
}
continue;
}
final boolean hasSearchIndexProvider = hasSearchIndexProvider(clazz);
// If it implements Indexable, it must also implement the index provider field.
if (!hasSearchIndexProvider) {
if (!notImplementingIndexProviderGrandfatherList.remove(className)) {
notImplementingIndexProvider.add(className);
}
continue;
}
// If it implements index provider field AND it's a DashboardFragment, its fragment and
// search provider must share the same set of PreferenceControllers.
final boolean isSharingPrefControllers = DashboardFragmentSearchIndexProviderInspector
.isSharingPreferenceControllers(clazz);
if (!isSharingPrefControllers) {
if (!notSharingPrefControllersGrandfatherList.remove(className)) {
notSharingPreferenceControllers.add(className);
}
continue;
}
// Must be in SearchProviderRegistry
if (SearchIndexableResources.getResourceByName(className) == null) {
if (!notInSearchIndexableRegistryGrandfatherList.remove(className)) {
notInSearchProviderRegistry.add(className);
}
continue;
}
}
// Build error messages
final String indexableError = buildErrorMessage(NOT_IMPLEMENTING_INDEXABLE_ERROR,
notImplementingIndexable);
final String indexProviderError = buildErrorMessage(NOT_CONTAINING_PROVIDER_OBJECT_ERROR,
notImplementingIndexProvider);
final String notSharingPrefControllerError = buildErrorMessage(
NOT_SHARING_PREF_CONTROLLERS_BETWEEN_FRAG_AND_PROVIDER,
notSharingPreferenceControllers);
final String notInProviderRegistryError =
buildErrorMessage(NOT_IN_INDEXABLE_PROVIDER_REGISTRY, notInSearchProviderRegistry);
assertWithMessage(indexableError)
.that(notImplementingIndexable)
.isEmpty();
assertWithMessage(indexProviderError)
.that(notImplementingIndexProvider)
.isEmpty();
assertWithMessage(notSharingPrefControllerError)
.that(notSharingPreferenceControllers)
.isEmpty();
assertWithMessage(notInProviderRegistryError)
.that(notInSearchProviderRegistry)
.isEmpty();
}
private boolean hasSearchIndexProvider(Class clazz) {
try {
final Field f = clazz.getField(
DatabaseIndexingManager.FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER);
return f != null;
} catch (NoClassDefFoundError e) {
// Cannot find class def, ignore
return true;
} catch (NoSuchFieldException e) {
Log.e(TAG, "error fetching search provider from class " + clazz.getName());
return false;
}
}
private String buildErrorMessage(String errorSummary, Set<String> errorClasses) {
final StringBuilder error = new StringBuilder(errorSummary);
for (String c : errorClasses) {
error.append(c).append("\n");
}
return error.toString();
}
}