/* * 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 static org.mockito.Mockito.spy; 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.TogglePreferenceController; import com.android.settings.core.codeinspection.ClassScanner; 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 com.android.settings.testutils.SettingsRobolectricTestRunner; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RuntimeEnvironment; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; @RunWith(SettingsRobolectricTestRunner.class) public class SliceControllerInXmlTest { private static final List mSliceControllerClasses = Collections.singletonList( TogglePreferenceController.class ); private final List mXmlDeclaredControllers = new ArrayList<>(); private final List 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 Context mContext; private SearchFeatureProvider mSearchProvider; private FakeFeatureFactory mFakeFeatureFactory; @Before public void setUp() throws IOException, XmlPullParserException { mContext = spy(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 xmlResources = getIndexableXml(); for (int xmlResId : xmlResources) { final List 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(); } @Test public void testAllControllersDeclaredInXml() throws Exception { final List> classes = new ClassScanner().getClassesForPackage(mContext.getPackageName()); final List missingControllersInXml = new ArrayList<>(); for (Class clazz : classes) { 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 errorClasses) { final StringBuilder error = new StringBuilder(errorSummary); for (String c : errorClasses) { error.append(c).append("\n"); } return error.toString(); } private List getIndexableXml() { final List xmlResSet = new ArrayList<>(); final Collection indexableClasses = FeatureFactory.getFactory( mContext).getSearchFeatureProvider().getSearchIndexableResources() .getProviderValues(); for (Class clazz : indexableClasses) { Indexable.SearchIndexProvider provider = DatabaseIndexingUtils.getSearchIndexProvider( clazz); if (provider == null) { continue; } List 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; } }