blob: 9c8b9462c9256576ac302c07428851d683a2a2c3 [file] [log] [blame]
/*
* Copyright (C) 2018 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.slices;
import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_CONTROLLER;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import android.content.Context;
import android.os.Bundle;
import android.provider.SearchIndexableResource;
import android.text.TextUtils;
import com.android.settings.core.PreferenceXmlParserUtils;
import com.android.settings.core.SliderPreferenceController;
import com.android.settings.core.TogglePreferenceController;
import com.android.settings.core.codeinspection.CodeInspector;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.DatabaseIndexingUtils;
import com.android.settings.search.Indexable;
import com.android.settings.search.SearchFeatureProvider;
import com.android.settings.search.SearchFeatureProviderImpl;
import com.android.settings.testutils.FakeFeatureFactory;
import org.robolectric.RuntimeEnvironment;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
public class SliceControllerInXmlCodeInspector extends CodeInspector {
private static final List<Class> mSliceControllerClasses = Arrays.asList(
TogglePreferenceController.class,
SliderPreferenceController.class
);
private final List<String> mXmlDeclaredControllers = new ArrayList<>();
private final List<String> mGrandfatheredClasses = new ArrayList<>();
private final String ERROR_MISSING_CONTROLLER =
"The following controllers were expected to be declared by "
+ "'settings:controller=Controller_Class_Name' in their corresponding Xml. "
+ "If it should not appear in XML, add the controller's classname to "
+ "grandfather_slice_controller_not_in_xml. Controllers:\n";
private final Context mContext;
private final SearchFeatureProvider mSearchProvider;
private final FakeFeatureFactory mFakeFeatureFactory;
public SliceControllerInXmlCodeInspector(List<Class<?>> classes) throws Exception {
super(classes);
mContext = RuntimeEnvironment.application;
mSearchProvider = new SearchFeatureProviderImpl();
mFakeFeatureFactory = FakeFeatureFactory.setupForTest();
mFakeFeatureFactory.searchFeatureProvider = mSearchProvider;
CodeInspector.initializeGrandfatherList(mGrandfatheredClasses,
"grandfather_slice_controller_not_in_xml");
initDeclaredControllers();
}
private void initDeclaredControllers() throws IOException, XmlPullParserException {
final List<Integer> xmlResources = getIndexableXml();
for (int xmlResId : xmlResources) {
final List<Bundle> metadata = PreferenceXmlParserUtils.extractMetadata(mContext,
xmlResId, PreferenceXmlParserUtils.MetadataFlag.FLAG_NEED_PREF_CONTROLLER);
for (Bundle bundle : metadata) {
final String controllerClassName = bundle.getString(METADATA_CONTROLLER);
if (TextUtils.isEmpty(controllerClassName)) {
continue;
}
mXmlDeclaredControllers.add(controllerClassName);
}
}
// We definitely have some controllers in xml, so assert not-empty here as a proxy to
// make sure the parser didn't fail
assertThat(mXmlDeclaredControllers).isNotEmpty();
}
@Override
public void run() {
final List<String> missingControllersInXml = new ArrayList<>();
for (Class<?> clazz : mClasses) {
if (!isConcreteSettingsClass(clazz)) {
// Only care about non-abstract classes.
continue;
}
if (!isInlineSliceClass(clazz)) {
// Only care about inline-slice controller classes.
continue;
}
if (!mXmlDeclaredControllers.contains(clazz.getName())) {
// Class clazz should have been declared in XML (unless whitelisted).
missingControllersInXml.add(clazz.getName());
}
}
// Removed whitelisted classes
missingControllersInXml.removeAll(mGrandfatheredClasses);
final String missingControllerError =
buildErrorMessage(ERROR_MISSING_CONTROLLER, missingControllersInXml);
assertWithMessage(missingControllerError).that(missingControllersInXml).isEmpty();
}
private boolean isInlineSliceClass(Class clazz) {
while (clazz != null) {
clazz = clazz.getSuperclass();
if (mSliceControllerClasses.contains(clazz)) {
return true;
}
}
return false;
}
private String buildErrorMessage(String errorSummary, List<String> errorClasses) {
final StringBuilder error = new StringBuilder(errorSummary);
for (String c : errorClasses) {
error.append(c).append("\n");
}
return error.toString();
}
private List<Integer> getIndexableXml() {
final List<Integer> xmlResSet = new ArrayList<>();
final Collection<Class> indexableClasses = FeatureFactory.getFactory(
mContext).getSearchFeatureProvider().getSearchIndexableResources()
.getProviderValues();
for (Class clazz : indexableClasses) {
Indexable.SearchIndexProvider provider = DatabaseIndexingUtils.getSearchIndexProvider(
clazz);
if (provider == null) {
continue;
}
List<SearchIndexableResource> resources = provider.getXmlResourcesToIndex(mContext,
true);
if (resources == null) {
continue;
}
for (SearchIndexableResource resource : resources) {
// Add '0's anyway. It won't break the test.
xmlResSet.add(resource.xmlResId);
}
}
return xmlResSet;
}
}