diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 752fd3dada5..86763b77169 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -72,6 +72,14 @@ + + + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 44d43c3b9c5..85f133f2a22 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -5406,6 +5406,12 @@ Turn on automatically + + No schedule + + + Set a schedule + At %1$s diff --git a/res/xml/battery_saver_schedule_settings.xml b/res/xml/battery_saver_schedule_settings.xml new file mode 100644 index 00000000000..f91e4ca25d1 --- /dev/null +++ b/res/xml/battery_saver_schedule_settings.xml @@ -0,0 +1,23 @@ + + + + + diff --git a/src/com/android/settings/core/PreferenceXmlParserUtils.java b/src/com/android/settings/core/PreferenceXmlParserUtils.java index 9fdeeefbe5a..ce5c5051f5a 100644 --- a/src/com/android/settings/core/PreferenceXmlParserUtils.java +++ b/src/com/android/settings/core/PreferenceXmlParserUtils.java @@ -55,6 +55,8 @@ public class PreferenceXmlParserUtils { private static final List SUPPORTED_PREF_TYPES = Arrays.asList( "Preference", "PreferenceCategory", "PreferenceScreen", "com.android.settings.widget.WorkOnlyCategory"); + public static final int PREPEND_VALUE = 0; + public static final int APPEND_VALUE = 1; /** * Flag definition to indicate which metadata should be extracted when @@ -84,6 +86,7 @@ public class PreferenceXmlParserUtils { int FLAG_NEED_KEYWORDS = 1 << 8; int FLAG_NEED_SEARCHABLE = 1 << 9; int FLAG_ALLOW_DYNAMIC_SUMMARY_IN_SLICE = 1 << 10; + int FLAG_NEED_PREF_APPEND = 1 << 11; } public static final String METADATA_PREF_TYPE = "type"; @@ -97,6 +100,7 @@ public class PreferenceXmlParserUtils { public static final String METADATA_SEARCHABLE = "searchable"; public static final String METADATA_ALLOW_DYNAMIC_SUMMARY_IN_SLICE = "allow_dynamic_summary_in_slice"; + public static final String METADATA_APPEND = "staticPreferenceLocation"; private static final String ENTRIES_SEPARATOR = "|"; @@ -184,14 +188,13 @@ public class PreferenceXmlParserUtils { // Parse next until start tag is found } final int outerDepth = parser.getDepth(); - + final boolean hasPrefScreenFlag = hasFlag(flags, MetadataFlag.FLAG_INCLUDE_PREF_SCREEN); do { if (type != XmlPullParser.START_TAG) { continue; } final String nodeName = parser.getName(); - if (!hasFlag(flags, MetadataFlag.FLAG_INCLUDE_PREF_SCREEN) - && TextUtils.equals(PREF_SCREEN_TAG, nodeName)) { + if (!hasPrefScreenFlag && TextUtils.equals(PREF_SCREEN_TAG, nodeName)) { continue; } if (!SUPPORTED_PREF_TYPES.contains(nodeName) && !nodeName.endsWith("Preference")) { @@ -199,8 +202,14 @@ public class PreferenceXmlParserUtils { } final Bundle preferenceMetadata = new Bundle(); final AttributeSet attrs = Xml.asAttributeSet(parser); + final TypedArray preferenceAttributes = context.obtainStyledAttributes(attrs, R.styleable.Preference); + TypedArray preferenceScreenAttributes = null; + if (hasPrefScreenFlag) { + preferenceScreenAttributes = context.obtainStyledAttributes( + attrs, R.styleable.PreferenceScreen); + } if (hasFlag(flags, MetadataFlag.FLAG_NEED_PREF_TYPE)) { preferenceMetadata.putString(METADATA_PREF_TYPE, nodeName); @@ -236,6 +245,10 @@ public class PreferenceXmlParserUtils { preferenceMetadata.putBoolean(METADATA_ALLOW_DYNAMIC_SUMMARY_IN_SLICE, isDynamicSummaryAllowed(preferenceAttributes)); } + if (hasFlag(flags, MetadataFlag.FLAG_NEED_PREF_APPEND) && hasPrefScreenFlag) { + preferenceMetadata.putBoolean(METADATA_APPEND, + isAppended(preferenceScreenAttributes)); + } metadata.add(preferenceMetadata); preferenceAttributes.recycle(); @@ -325,7 +338,12 @@ public class PreferenceXmlParserUtils { false /* default */); } - private static String getKeywords(TypedArray styleAttributes) { - return styleAttributes.getString(R.styleable.Preference_keywords); + private static String getKeywords(TypedArray styledAttributes) { + return styledAttributes.getString(R.styleable.Preference_keywords); + } + + private static boolean isAppended(TypedArray styledAttributes) { + return styledAttributes.getInt(R.styleable.PreferenceScreen_staticPreferenceLocation, + PREPEND_VALUE) == APPEND_VALUE; } } diff --git a/src/com/android/settings/widget/RadioButtonPickerFragment.java b/src/com/android/settings/widget/RadioButtonPickerFragment.java index 89df487200e..50c1b58442f 100644 --- a/src/com/android/settings/widget/RadioButtonPickerFragment.java +++ b/src/com/android/settings/widget/RadioButtonPickerFragment.java @@ -22,10 +22,12 @@ import android.os.UserHandle; import android.os.UserManager; import android.text.TextUtils; import android.util.ArrayMap; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.Toast; import androidx.annotation.LayoutRes; import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; @@ -34,16 +36,23 @@ import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.core.InstrumentedPreferenceFragment; +import com.android.settings.core.PreferenceXmlParserUtils; +import com.android.settings.core.PreferenceXmlParserUtils.MetadataFlag; import com.android.settingslib.widget.CandidateInfo; +import java.io.IOException; import java.util.List; import java.util.Map; +import org.xmlpull.v1.XmlPullParserException; public abstract class RadioButtonPickerFragment extends InstrumentedPreferenceFragment implements RadioButtonPreference.OnClickListener { - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + @VisibleForTesting static final String EXTRA_FOR_WORK = "for_work"; + private static final String TAG = "RadioButtonPckrFrgmt"; + @VisibleForTesting + boolean mAppendStaticPreferences = false; private final Map mCandidates = new ArrayMap<>(); @@ -69,6 +78,19 @@ public abstract class RadioButtonPickerFragment extends InstrumentedPreferenceFr @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { super.onCreatePreferences(savedInstanceState, rootKey); + try { + // Check if the xml specifies if static preferences should go on the top or bottom + final List metadata = PreferenceXmlParserUtils.extractMetadata(getContext(), + getPreferenceScreenResId(), + MetadataFlag.FLAG_INCLUDE_PREF_SCREEN | + MetadataFlag.FLAG_NEED_PREF_APPEND); + mAppendStaticPreferences = metadata.get(0) + .getBoolean(PreferenceXmlParserUtils.METADATA_APPEND); + } catch (IOException e) { + Log.e(TAG, "Error trying to open xml file", e); + } catch (XmlPullParserException e) { + Log.e(TAG, "Error parsing xml", e); + } updateCandidates(); } @@ -142,7 +164,9 @@ public abstract class RadioButtonPickerFragment extends InstrumentedPreferenceFr final String systemDefaultKey = getSystemDefaultKey(); final PreferenceScreen screen = getPreferenceScreen(); screen.removeAll(); - addStaticPreferences(screen); + if (!mAppendStaticPreferences) { + addStaticPreferences(screen); + } final int customLayoutResId = getRadioButtonPreferenceCustomLayoutResId(); if (shouldShowItemNone()) { @@ -168,6 +192,9 @@ public abstract class RadioButtonPickerFragment extends InstrumentedPreferenceFr } } mayCheckOnlyRadioButton(); + if (mAppendStaticPreferences) { + addStaticPreferences(screen); + } } @VisibleForTesting diff --git a/tests/robotests/res/xml-mcc999/battery_saver_schedule_settings.xml b/tests/robotests/res/xml-mcc999/battery_saver_schedule_settings.xml new file mode 100644 index 00000000000..f91e4ca25d1 --- /dev/null +++ b/tests/robotests/res/xml-mcc999/battery_saver_schedule_settings.xml @@ -0,0 +1,23 @@ + + + + + diff --git a/tests/robotests/src/com/android/settings/core/PreferenceXmlParserUtilsTest.java b/tests/robotests/src/com/android/settings/core/PreferenceXmlParserUtilsTest.java index 3b1b5afc3ab..ebf325273a0 100644 --- a/tests/robotests/src/com/android/settings/core/PreferenceXmlParserUtilsTest.java +++ b/tests/robotests/src/com/android/settings/core/PreferenceXmlParserUtilsTest.java @@ -18,6 +18,7 @@ package com.android.settings.core; import static com.android.settings.core.PreferenceXmlParserUtils .METADATA_ALLOW_DYNAMIC_SUMMARY_IN_SLICE; +import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_APPEND; import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_KEY; import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_KEYWORDS; import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_SEARCHABLE; @@ -315,6 +316,32 @@ public class PreferenceXmlParserUtilsTest { } } + @Test + @Config(qualifiers = "mcc999") + public void extractMetadata_requestAppendProperty_shouldDefaultToFalse() + throws Exception { + final List metadata = PreferenceXmlParserUtils.extractMetadata(mContext, + R.xml.display_settings, + MetadataFlag.FLAG_INCLUDE_PREF_SCREEN | MetadataFlag.FLAG_NEED_PREF_APPEND); + + for (Bundle bundle : metadata) { + assertThat(bundle.getBoolean(METADATA_APPEND)).isFalse(); + } + } + + @Test + @Config(qualifiers = "mcc999") + public void extractMetadata_requestAppendProperty_shouldReturnCorrectValue() + throws Exception { + final List metadata = PreferenceXmlParserUtils.extractMetadata(mContext, + R.xml.battery_saver_schedule_settings, + MetadataFlag.FLAG_INCLUDE_PREF_SCREEN | MetadataFlag.FLAG_NEED_PREF_APPEND); + + for (Bundle bundle : metadata) { + assertThat(bundle.getBoolean(METADATA_APPEND)).isTrue(); + } + } + /** * @param resId the ID for the XML preference * @return an XML resource parser that points to the start tag diff --git a/tests/robotests/src/com/android/settings/display/ColorModePreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/display/ColorModePreferenceFragmentTest.java index 859912aa71c..57f0ebbc6a0 100644 --- a/tests/robotests/src/com/android/settings/display/ColorModePreferenceFragmentTest.java +++ b/tests/robotests/src/com/android/settings/display/ColorModePreferenceFragmentTest.java @@ -209,6 +209,7 @@ public class ColorModePreferenceFragmentTest { @Test public void onCreatePreferences_useNewTitle_shouldAddColorModePreferences() { + when(mFragment.getContext()).thenReturn(RuntimeEnvironment.application); doNothing().when(mFragment).addPreferencesFromResource(anyInt()); doNothing().when(mFragment).updateCandidates(); diff --git a/tests/robotests/src/com/android/settings/widget/RadioButtonPickerFragmentTest.java b/tests/robotests/src/com/android/settings/widget/RadioButtonPickerFragmentTest.java index 64352d96638..55d212ffef1 100644 --- a/tests/robotests/src/com/android/settings/widget/RadioButtonPickerFragmentTest.java +++ b/tests/robotests/src/com/android/settings/widget/RadioButtonPickerFragmentTest.java @@ -18,6 +18,7 @@ package com.android.settings.widget; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -37,7 +38,9 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; +import org.mockito.InOrder; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; @@ -98,6 +101,26 @@ public class RadioButtonPickerFragmentTest { assertThat(mFragment.setDefaultKeyCalled).isTrue(); } + @Test + public void staticPreferencesPrepended_addedFirst() { + mFragment.mAppendStaticPreferences = false; + mFragment.updateCandidates(); + + InOrder inOrder = Mockito.inOrder(mFragment); + inOrder.verify(mFragment).addStaticPreferences(any()); + inOrder.verify(mFragment).getRadioButtonPreferenceCustomLayoutResId(); + } + + @Test + public void staticPreferencesAppended_addedLast() { + mFragment.mAppendStaticPreferences = true; + mFragment.updateCandidates(); + + InOrder inOrder = Mockito.inOrder(mFragment); + inOrder.verify(mFragment).mayCheckOnlyRadioButton(); + inOrder.verify(mFragment).addStaticPreferences(any()); + } + @Test public void shouldHaveNoCustomPreferenceLayout() { assertThat(mFragment.getRadioButtonPreferenceCustomLayoutResId()).isEqualTo(0);