diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 2ba7919a1b5..e3fa0700853 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -97,6 +97,7 @@ + diff --git a/res/xml/display_settings.xml b/res/xml/display_settings.xml index f9f5d2b6d42..333fd2af705 100644 --- a/res/xml/display_settings.xml +++ b/res/xml/display_settings.xml @@ -25,8 +25,9 @@ - + settings:keywords="@string/keywords_display_brightness_level" + settings:controller="com.android.settings.display.AutoBrightnessPreferenceController"> + + android:fragment="com.android.settings.gestures.GestureSettings" + settings:controller="com.android.settings.gestures.GesturesSettingPreferenceController"/> + android:order="-60" + settings:controller="com.android.settings.backup.BackupSettingsActivityPreferenceController"> @@ -44,14 +46,16 @@ android:title="@string/system_update_settings_list_item_title" android:summary="@string/summary_placeholder" android:icon="@drawable/ic_system_update" - android:order="-30"> + android:order="-30" + settings:controller="com.android.settings.deviceinfo.SystemUpdatePreferenceController"> + android:order="-31" + settings:controller="com.android.settings.deviceinfo.AdditionalSystemUpdatePreferenceController"> diff --git a/src/com/android/settings/backup/BackupSettingsActivityPreferenceController.java b/src/com/android/settings/backup/BackupSettingsActivityPreferenceController.java index afc13b47d1c..7a7530cfc8a 100644 --- a/src/com/android/settings/backup/BackupSettingsActivityPreferenceController.java +++ b/src/com/android/settings/backup/BackupSettingsActivityPreferenceController.java @@ -22,31 +22,29 @@ import android.os.UserManager; import android.support.v7.preference.Preference; import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; import com.android.settings.core.PreferenceControllerMixin; import com.android.settingslib.core.AbstractPreferenceController; -public class BackupSettingsActivityPreferenceController extends - AbstractPreferenceController implements PreferenceControllerMixin { +public class BackupSettingsActivityPreferenceController extends BasePreferenceController { + private static final String TAG = "BackupSettingActivityPC"; + private static final String KEY_BACKUP_SETTINGS = "backup_settings"; - private static final String TAG = "BackupSettingActivityPC" ; private final UserManager mUm; private final BackupManager mBackupManager; public BackupSettingsActivityPreferenceController(Context context) { - super(context); + super(context, KEY_BACKUP_SETTINGS); mUm = (UserManager) context.getSystemService(Context.USER_SERVICE); mBackupManager = new BackupManager(context); } @Override - public boolean isAvailable() { - return mUm.isAdminUser(); - } - - @Override - public String getPreferenceKey() { - return KEY_BACKUP_SETTINGS; + public int getAvailabilityStatus() { + return mUm.isAdminUser() + ? AVAILABLE + : DISABLED_UNSUPPORTED; } @Override @@ -57,4 +55,4 @@ public class BackupSettingsActivityPreferenceController extends ? R.string.accessibility_feature_state_on : R.string.accessibility_feature_state_off); } -} +} \ No newline at end of file diff --git a/src/com/android/settings/deviceinfo/AdditionalSystemUpdatePreferenceController.java b/src/com/android/settings/deviceinfo/AdditionalSystemUpdatePreferenceController.java index 06bdb3fae81..f91ed4e69a5 100644 --- a/src/com/android/settings/deviceinfo/AdditionalSystemUpdatePreferenceController.java +++ b/src/com/android/settings/deviceinfo/AdditionalSystemUpdatePreferenceController.java @@ -17,26 +17,23 @@ package com.android.settings.deviceinfo; import android.content.Context; +import com.android.settings.core.BasePreferenceController; import com.android.settings.core.PreferenceControllerMixin; import com.android.settingslib.core.AbstractPreferenceController; -public class AdditionalSystemUpdatePreferenceController extends - AbstractPreferenceController implements PreferenceControllerMixin { +public class AdditionalSystemUpdatePreferenceController extends BasePreferenceController { private static final String KEY_UPDATE_SETTING = "additional_system_update_settings"; public AdditionalSystemUpdatePreferenceController(Context context) { - super(context); + super(context, KEY_UPDATE_SETTING); } @Override - public boolean isAvailable() { + public int getAvailabilityStatus() { return mContext.getResources().getBoolean( - com.android.settings.R.bool.config_additional_system_update_setting_enable); + com.android.settings.R.bool.config_additional_system_update_setting_enable) + ? AVAILABLE + : DISABLED_UNSUPPORTED; } - - @Override - public String getPreferenceKey() { - return KEY_UPDATE_SETTING; - } -} +} \ No newline at end of file diff --git a/src/com/android/settings/deviceinfo/SystemUpdatePreferenceController.java b/src/com/android/settings/deviceinfo/SystemUpdatePreferenceController.java index d8a64a82113..92c33d86e4b 100644 --- a/src/com/android/settings/deviceinfo/SystemUpdatePreferenceController.java +++ b/src/com/android/settings/deviceinfo/SystemUpdatePreferenceController.java @@ -30,11 +30,9 @@ import android.util.Log; import com.android.settings.R; import com.android.settings.Utils; -import com.android.settings.core.PreferenceControllerMixin; -import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settings.core.BasePreferenceController; -public class SystemUpdatePreferenceController extends AbstractPreferenceController implements - PreferenceControllerMixin { +public class SystemUpdatePreferenceController extends BasePreferenceController { private static final String TAG = "SysUpdatePrefContr"; @@ -42,19 +40,16 @@ public class SystemUpdatePreferenceController extends AbstractPreferenceControll private final UserManager mUm; - public SystemUpdatePreferenceController(Context context, UserManager um) { - super(context); - mUm = um; + public SystemUpdatePreferenceController(Context context) { + super(context, KEY_SYSTEM_UPDATE_SETTINGS); + mUm = UserManager.get(context); } @Override - public boolean isAvailable() { - return mUm.isAdminUser(); - } - - @Override - public String getPreferenceKey() { - return KEY_SYSTEM_UPDATE_SETTINGS; + public int getAvailabilityStatus() { + return mUm.isAdminUser() + ? AVAILABLE + : DISABLED_UNSUPPORTED; } @Override @@ -62,14 +57,14 @@ public class SystemUpdatePreferenceController extends AbstractPreferenceControll super.displayPreference(screen); if (isAvailable()) { Utils.updatePreferenceToSpecificActivityOrRemove(mContext, screen, - KEY_SYSTEM_UPDATE_SETTINGS, + getPreferenceKey(), Utils.UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY); } } @Override public boolean handlePreferenceTreeClick(Preference preference) { - if (KEY_SYSTEM_UPDATE_SETTINGS.equals(preference.getKey())) { + if (TextUtils.equals(getPreferenceKey(), preference.getKey())) { CarrierConfigManager configManager = (CarrierConfigManager) mContext.getSystemService(CARRIER_CONFIG_SERVICE); PersistableBundle b = configManager.getConfig(); @@ -108,4 +103,4 @@ public class SystemUpdatePreferenceController extends AbstractPreferenceControll mContext.getApplicationContext().sendBroadcast(intent); } } -} +} \ No newline at end of file diff --git a/src/com/android/settings/gestures/GesturesSettingPreferenceController.java b/src/com/android/settings/gestures/GesturesSettingPreferenceController.java index d1b47b21301..819b12861c2 100644 --- a/src/com/android/settings/gestures/GesturesSettingPreferenceController.java +++ b/src/com/android/settings/gestures/GesturesSettingPreferenceController.java @@ -23,27 +23,26 @@ import android.support.v7.preference.Preference; import com.android.internal.hardware.AmbientDisplayConfiguration; import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; import com.android.settings.core.PreferenceControllerMixin; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.AbstractPreferenceController; import java.util.List; -public class GesturesSettingPreferenceController extends AbstractPreferenceController - implements PreferenceControllerMixin { - - private static final String KEY_GESTURES_SETTINGS = "gesture_settings"; - +public class GesturesSettingPreferenceController extends BasePreferenceController { private final AssistGestureFeatureProvider mFeatureProvider; private List mGestureControllers; + private static final String KEY_GESTURES_SETTINGS = "gesture_settings"; + public GesturesSettingPreferenceController(Context context) { - super(context); + super(context, KEY_GESTURES_SETTINGS); mFeatureProvider = FeatureFactory.getFactory(context).getAssistGestureFeatureProvider(); } @Override - public boolean isAvailable() { + public int getAvailabilityStatus() { if (mGestureControllers == null) { mGestureControllers = GestureSettings.buildPreferenceControllers(mContext, null /* lifecycle */, new AmbientDisplayConfiguration(mContext)); @@ -52,12 +51,9 @@ public class GesturesSettingPreferenceController extends AbstractPreferenceContr for (AbstractPreferenceController controller : mGestureControllers) { isAvailable = isAvailable || controller.isAvailable(); } - return isAvailable; - } - - @Override - public String getPreferenceKey() { - return KEY_GESTURES_SETTINGS; + return isAvailable + ? AVAILABLE + : DISABLED_UNSUPPORTED; } @Override @@ -83,5 +79,4 @@ public class GesturesSettingPreferenceController extends AbstractPreferenceContr } preference.setSummary(summary); } - -} +} \ No newline at end of file diff --git a/src/com/android/settings/search/XmlParserUtils.java b/src/com/android/settings/search/XmlParserUtils.java index b4ffc532f7c..27c5cd36593 100644 --- a/src/com/android/settings/search/XmlParserUtils.java +++ b/src/com/android/settings/search/XmlParserUtils.java @@ -71,6 +71,10 @@ public class XmlParserUtils { return getData(context, attrs, R.styleable.Preference, R.styleable.Preference_keywords); } + public static String getController(Context context, AttributeSet attrs) { + return getData(context, attrs, R.styleable.Preference, R.styleable.Preference_controller); + } + public static int getDataIcon(Context context, AttributeSet attrs) { final TypedArray ta = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Preference); diff --git a/src/com/android/settings/system/SystemDashboardFragment.java b/src/com/android/settings/system/SystemDashboardFragment.java index b344d8b0df4..323a2d42cd0 100644 --- a/src/com/android/settings/system/SystemDashboardFragment.java +++ b/src/com/android/settings/system/SystemDashboardFragment.java @@ -82,7 +82,7 @@ public class SystemDashboardFragment extends DashboardFragment { private static List buildPreferenceControllers(Context context) { final List controllers = new ArrayList<>(); - controllers.add(new SystemUpdatePreferenceController(context, UserManager.get(context))); + controllers.add(new SystemUpdatePreferenceController(context)); controllers.add(new AdditionalSystemUpdatePreferenceController(context)); controllers.add(new BackupSettingsActivityPreferenceController(context)); controllers.add(new GesturesSettingPreferenceController(context)); @@ -124,10 +124,10 @@ public class SystemDashboardFragment extends DashboardFragment { @Override public List getNonIndexableKeys(Context context) { List keys = super.getNonIndexableKeys(context); - keys.add((new BackupSettingsActivityPreferenceController(context) - .getPreferenceKey())); + keys.add((new BackupSettingsActivityPreferenceController( + context).getPreferenceKey())); keys.add(KEY_RESET); return keys; } }; -} +} \ No newline at end of file diff --git a/tests/robotests/res/xml-mcc999/about_legal.xml b/tests/robotests/res/xml-mcc999/about_legal.xml index 53a2b897391..3e008cb7bad 100644 --- a/tests/robotests/res/xml-mcc999/about_legal.xml +++ b/tests/robotests/res/xml-mcc999/about_legal.xml @@ -30,5 +30,6 @@ + android:title="bears_bears_bears" + settings:controller="mind_flayer"/> \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/core/XmlControllerAttributeTest.java b/tests/robotests/src/com/android/settings/core/XmlControllerAttributeTest.java new file mode 100644 index 00000000000..ed4e815c6e4 --- /dev/null +++ b/tests/robotests/src/com/android/settings/core/XmlControllerAttributeTest.java @@ -0,0 +1,275 @@ +package com.android.settings.core; + +import static com.google.common.truth.Truth.assertWithMessage; + +import android.content.Context; +import android.content.res.XmlResourceParser; +import android.provider.SearchIndexableResource; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.Xml; + +import com.android.settings.R; +import com.android.settings.TestConfig; +import com.android.settings.search.DatabaseIndexingUtils; +import com.android.settings.search.Indexable; +import com.android.settings.search.SearchIndexableResources; +import com.android.settings.search.XmlParserUtils; +import com.android.settings.security.SecuritySettings; +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 org.xmlpull.v1.XmlPullParser; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class XmlControllerAttributeTest { + + // List of classes that are too hard to mock in order to retrieve xml information. + private final List illegalClasses = new ArrayList<>( + Arrays.asList( + SecuritySettings.class + )); + + // List of XML that could be retrieved from the illegalClasses list. + private final List whitelistXml = new ArrayList<>( + Arrays.asList( + R.xml.security_settings_misc, + R.xml.security_settings_lockscreen_profile, + R.xml.security_settings_lockscreen, + R.xml.security_settings_chooser, + R.xml.security_settings_pattern_profile, + R.xml.security_settings_pin_profile, + R.xml.security_settings_password_profile, + R.xml.security_settings_pattern, + R.xml.security_settings_pin, + R.xml.security_settings_password, + R.xml.security_settings, + R.xml.security_settings_status + )); + + private static final String NO_VALID_CONSTRUCTOR_ERROR = + "Controllers added in XML need a constructor following either:" + + "\n\tClassName(Context)\n\tClassName(Context, String)" + + "\nThese controllers are missing a valid constructor:\n"; + + private static final String NOT_BASE_PREF_CONTROLLER_ERROR = + "Controllers added in XML need to extend com.android.settings.core" + + ".BasePreferenceController\nThese controllers do not:\n"; + + private static final String BAD_CLASSNAME_ERROR = + "The following controllers set in the XML did not have valid class names:\n"; + + private static final String BAD_CONSTRUCTOR_ERROR = + "The constructor provided by the following classes were insufficient to instantiate " + + "the object. It could be due to being an interface, abstract, or an " + + "IllegalAccessException. Please fix the following classes:\n"; + + Context mContext; + + private Set mProviderClassesCopy; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mProviderClassesCopy = new HashSet<>(SearchIndexableResources.providerValues()); + } + + @After + public void cleanUp() { + SearchIndexableResources.providerValues().clear(); + SearchIndexableResources.providerValues().addAll(mProviderClassesCopy); + } + + @Test + public void testAllIndexableXML_onlyValidBasePreferenceControllersAdded() { + Set xmlSet = getIndexableXml(); + xmlSet.addAll(whitelistXml); + + List xmlControllers = new ArrayList<>(); + Set invalidConstructors = new HashSet<>(); + Set invalidClassHierarchy = new HashSet<>(); + Set badClassNameControllers = new HashSet<>(); + Set badConstructorControllers = new HashSet<>(); + + for (int resId : xmlSet) { + xmlControllers.addAll(getXmlControllers(resId)); + } + + for (String controllerClassName : xmlControllers) { + Class clazz = getClassFromClassName(controllerClassName); + + if (clazz == null) { + badClassNameControllers.add(controllerClassName); + continue; + } + + Constructor constructor = getConstructorFromClass(clazz); + + if (constructor == null) { + invalidConstructors.add(controllerClassName); + continue; + } + + Object controller = getObjectFromConstructor(constructor); + if (controller == null) { + badConstructorControllers.add(controllerClassName); + continue; + } + + if (!(controller instanceof BasePreferenceController)) { + invalidClassHierarchy.add(controllerClassName); + } + } + + final String invalidConstructorError = buildErrorMessage(NO_VALID_CONSTRUCTOR_ERROR, + invalidConstructors); + final String invalidClassHierarchyError = buildErrorMessage(NOT_BASE_PREF_CONTROLLER_ERROR, + invalidClassHierarchy); + final String badClassNameError = buildErrorMessage(BAD_CLASSNAME_ERROR, + badClassNameControllers); + final String badConstructorError = buildErrorMessage(BAD_CONSTRUCTOR_ERROR, + badConstructorControllers); + + assertWithMessage(invalidConstructorError).that(invalidConstructors).isEmpty(); + assertWithMessage(invalidClassHierarchyError).that(invalidClassHierarchy).isEmpty(); + assertWithMessage(badClassNameError).that(badClassNameControllers).isEmpty(); + assertWithMessage(badConstructorError).that(badConstructorControllers).isEmpty(); + } + + private Set getIndexableXml() { + Set xmlResSet = new HashSet(); + + Collection indexableClasses = SearchIndexableResources.providerValues(); + indexableClasses.removeAll(illegalClasses); + + 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; + } + + private List getXmlControllers(int xmlResId) { + List xmlControllers = new ArrayList<>(); + + XmlResourceParser parser; + try { + parser = mContext.getResources().getXml(xmlResId); + + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && type != XmlPullParser.START_TAG) { + // Parse next until start tag is found + } + + final int outerDepth = parser.getDepth(); + final AttributeSet attrs = Xml.asAttributeSet(parser); + String controllerClassName; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + controllerClassName = XmlParserUtils.getController(mContext, attrs); + // If controller is not indexed, then it is not compatible with + if (!TextUtils.isEmpty(controllerClassName)) { + xmlControllers.add(controllerClassName); + } + } + } catch (Exception e) { + // Assume an issue with robolectric resources + } + return xmlControllers; + } + + private String buildErrorMessage(String errorSummary, Set errorClasses) { + final StringBuilder error = new StringBuilder(errorSummary); + for (String c : errorClasses) { + error.append(c).append("\n"); + } + return error.toString(); + } + + private Class getClassFromClassName(String className) { + Class clazz = null; + try { + clazz = Class.forName(className); + } catch (ClassNotFoundException e) { + } + return clazz; + } + + private Constructor getConstructorFromClass(Class clazz) { + Constructor constructor = null; + try { + constructor = clazz.getConstructor(Context.class); + } catch (NoSuchMethodException e) { + } + + if (constructor != null) { + return constructor; + } + + try { + constructor = clazz.getConstructor(Context.class, String.class); + } catch (NoSuchMethodException e) { + } + + return constructor; + } + + private Object getObjectFromConstructor(Constructor constructor) { + Object controller = null; + + try { + controller = constructor.newInstance(mContext); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | + IllegalArgumentException e) { + } + + if (controller != null) { + return controller; + } + + try { + controller = constructor.newInstance(mContext, "key"); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | + IllegalArgumentException e) { + } + + return controller; + } +} diff --git a/tests/robotests/src/com/android/settings/deviceinfo/SystemUpdatePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/SystemUpdatePreferenceControllerTest.java index 05670e28faa..1fd543042c1 100644 --- a/tests/robotests/src/com/android/settings/deviceinfo/SystemUpdatePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/deviceinfo/SystemUpdatePreferenceControllerTest.java @@ -57,7 +57,9 @@ public class SystemUpdatePreferenceControllerTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); - mController = new SystemUpdatePreferenceController(mContext, mUserManager); + + when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); + mController = new SystemUpdatePreferenceController(mContext); mPreference = new Preference(RuntimeEnvironment.application); mPreference.setKey(mController.getPreferenceKey()); when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); @@ -82,7 +84,7 @@ public class SystemUpdatePreferenceControllerTest { mController.updateNonIndexableKeys(keys); - assertThat(keys.size()).isEqualTo(1); + assertThat(keys).hasSize(1); } @Test @@ -94,8 +96,8 @@ public class SystemUpdatePreferenceControllerTest { @Test public void updateState_shouldSetToAndroidVersion() { - mController = new SystemUpdatePreferenceController( - RuntimeEnvironment.application, mUserManager); + mController = new SystemUpdatePreferenceController(RuntimeEnvironment.application); + mController.updateState(mPreference); assertThat(mPreference.getSummary()) diff --git a/tests/robotests/src/com/android/settings/search/XmlParserUtilTest.java b/tests/robotests/src/com/android/settings/search/XmlParserUtilTest.java index 6050b32396f..2bec503afe5 100644 --- a/tests/robotests/src/com/android/settings/search/XmlParserUtilTest.java +++ b/tests/robotests/src/com/android/settings/search/XmlParserUtilTest.java @@ -128,6 +128,16 @@ public class XmlParserUtilTest { assertThat(key).isNull(); } + @Test + @Config(qualifiers = "mcc999") + public void testControllerAttribute_returnsValidData() { + XmlResourceParser parser = getChildByType(R.xml.about_legal, "Preference"); + final AttributeSet attrs = Xml.asAttributeSet(parser); + + String controller = XmlParserUtils.getController(mContext, attrs); + assertThat(controller).isEqualTo("mind_flayer"); + } + @Test public void testDataSummaryInvalid_ReturnsNull() { XmlResourceParser parser = getParentPrimedParser(R.xml.display_settings);