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);