Add preference controller for battery seekbar

Also update SeekBarPreference so we can set min for it.

Bug: 72228477
Test: RunSettingsRoboTests
Change-Id: I5ab1bfd78b0bd461551012c121c7e76764843a30
diff --git a/src/com/android/settings/fuelgauge/batterysaver/AutoBatterySeekBarPreferenceController.java b/src/com/android/settings/fuelgauge/batterysaver/AutoBatterySeekBarPreferenceController.java
new file mode 100644
index 0000000..1c787ab
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batterysaver/AutoBatterySeekBarPreferenceController.java
@@ -0,0 +1,135 @@
+/*
+ * 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.fuelgauge.batterysaver;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.Settings;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.Utils;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.widget.SeekBarPreference;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
+
+/**
+ * Controller that update the battery saver seekbar
+ */
+public class AutoBatterySeekBarPreferenceController extends BasePreferenceController implements
+        LifecycleObserver, OnStart, OnStop, SeekBarPreference.OnPreferenceChangeListener {
+    @VisibleForTesting
+    static final String KEY_AUTO_BATTERY_SEEK_BAR = "battery_saver_seek_bar";
+    private SeekBarPreference mPreference;
+    private AutoBatterySaverSettingObserver mContentObserver;
+
+    public AutoBatterySeekBarPreferenceController(Context context, Lifecycle lifecycle) {
+        super(context, KEY_AUTO_BATTERY_SEEK_BAR);
+        mContentObserver = new AutoBatterySaverSettingObserver(new Handler());
+        if (lifecycle != null) {
+            lifecycle.addObserver(this);
+        }
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        mPreference = (SeekBarPreference) screen.findPreference(
+                KEY_AUTO_BATTERY_SEEK_BAR);
+        updatePreference(mPreference);
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return AVAILABLE;
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        super.updateState(preference);
+        updatePreference(preference);
+    }
+
+    @Override
+    public void onStart() {
+        mContentObserver.registerContentObserver();
+    }
+
+    @Override
+    public void onStop() {
+        mContentObserver.unRegisterContentObserver();
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        final int progress = (int) newValue;
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, progress);
+        return true;
+    }
+
+    @VisibleForTesting
+    void updatePreference(Preference preference) {
+        final ContentResolver contentResolver = mContext.getContentResolver();
+        final int level = Settings.Global.getInt(contentResolver,
+                Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
+        if (level == 0) {
+            preference.setVisible(false);
+        } else {
+            preference.setVisible(true);
+            preference.setTitle(mContext.getString(R.string.battery_saver_seekbar_title,
+                    Utils.formatPercentage(level)));
+            ((SeekBarPreference) preference).setProgress(level);
+        }
+    }
+
+    /**
+     * Observer that listens to change from {@link Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL}
+     */
+    private final class AutoBatterySaverSettingObserver extends ContentObserver {
+        private final Uri mUri = Settings.Global.getUriFor(
+                Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL);
+        private final ContentResolver mContentResolver;
+
+        public AutoBatterySaverSettingObserver(Handler handler) {
+            super(handler);
+            mContentResolver = mContext.getContentResolver();
+        }
+
+        public void registerContentObserver() {
+            mContentResolver.registerContentObserver(mUri, false, this);
+        }
+
+        public void unRegisterContentObserver() {
+            mContentResolver.unregisterContentObserver(this);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri, int userId) {
+            if (mUri.equals(uri)) {
+                updatePreference(mPreference);
+            }
+        }
+    }
+}
diff --git a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverSettings.java b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverSettings.java
index 270c29c..392032c 100644
--- a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverSettings.java
+++ b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverSettings.java
@@ -77,6 +77,7 @@
             Context context, Lifecycle lifecycle) {
         final List<AbstractPreferenceController> controllers = new ArrayList<>();
         controllers.add(new AutoBatterySaverPreferenceController(context));
+        controllers.add(new AutoBatterySeekBarPreferenceController(context, lifecycle));
         return controllers;
     }
 
diff --git a/src/com/android/settings/widget/SeekBarPreference.java b/src/com/android/settings/widget/SeekBarPreference.java
index ee7d4b8..5af21b3 100644
--- a/src/com/android/settings/widget/SeekBarPreference.java
+++ b/src/com/android/settings/widget/SeekBarPreference.java
@@ -40,6 +40,7 @@
 
     private int mProgress;
     private int mMax;
+    private int mMin;
     private boolean mTrackingTouch;
 
     private boolean mContinuousUpdates;
@@ -55,6 +56,7 @@
         TypedArray a = context.obtainStyledAttributes(
                 attrs, com.android.internal.R.styleable.ProgressBar, defStyleAttr, defStyleRes);
         setMax(a.getInt(com.android.internal.R.styleable.ProgressBar_max, mMax));
+        setMin(a.getInt(com.android.internal.R.styleable.ProgressBar_min, mMin));
         a.recycle();
 
         a = context.obtainStyledAttributes(attrs,
@@ -94,6 +96,7 @@
                 com.android.internal.R.id.seekbar);
         mSeekBar.setOnSeekBarChangeListener(this);
         mSeekBar.setMax(mMax);
+        mSeekBar.setMin(mMin);
         mSeekBar.setProgress(mProgress);
         mSeekBar.setEnabled(isEnabled());
         final CharSequence title = getTitle();
@@ -154,10 +157,21 @@
         }
     }
 
+    public void setMin(int min) {
+        if (min != mMin) {
+            mMin = min;
+            notifyChanged();
+        }
+    }
+
     public int getMax() {
         return mMax;
     }
 
+    public int getMin() {
+        return mMin;
+    }
+
     public void setProgress(int progress) {
         setProgress(progress, true);
     }
@@ -187,8 +201,8 @@
         if (progress > mMax) {
             progress = mMax;
         }
-        if (progress < 0) {
-            progress = 0;
+        if (progress < mMin) {
+            progress = mMin;
         }
         if (progress != mProgress) {
             mProgress = progress;
@@ -257,6 +271,7 @@
         final SavedState myState = new SavedState(superState);
         myState.progress = mProgress;
         myState.max = mMax;
+        myState.min = mMin;
         return myState;
     }
 
@@ -273,6 +288,7 @@
         super.onRestoreInstanceState(myState.getSuperState());
         mProgress = myState.progress;
         mMax = myState.max;
+        mMin = myState.min;
         notifyChanged();
     }
 
@@ -285,6 +301,7 @@
     private static class SavedState extends BaseSavedState {
         int progress;
         int max;
+        int min;
 
         public SavedState(Parcel source) {
             super(source);
@@ -292,6 +309,7 @@
             // Restore the click counter
             progress = source.readInt();
             max = source.readInt();
+            min = source.readInt();
         }
 
         @Override
@@ -301,6 +319,7 @@
             // Save the click counter
             dest.writeInt(progress);
             dest.writeInt(max);
+            dest.writeInt(min);
         }
 
         public SavedState(Parcelable superState) {
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/AutoBatterySeekBarPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/AutoBatterySeekBarPreferenceControllerTest.java
new file mode 100644
index 0000000..32a4fac
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/AutoBatterySeekBarPreferenceControllerTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.fuelgauge.batterysaver;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.arch.lifecycle.LifecycleOwner;
+import android.content.Context;
+import android.provider.Settings;
+import android.support.v14.preference.SwitchPreference;
+
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settings.widget.SeekBarPreference;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class AutoBatterySeekBarPreferenceControllerTest {
+    private static final int TRIGGER_LEVEL = 15;
+
+    private AutoBatterySeekBarPreferenceController mController;
+    private Context mContext;
+    private SeekBarPreference mPreference;
+    private Lifecycle mLifecycle;
+    private LifecycleOwner mLifecycleOwner;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mLifecycleOwner = () -> mLifecycle;
+        mLifecycle = new Lifecycle(mLifecycleOwner);
+
+        mContext = RuntimeEnvironment.application;
+        mPreference = new SeekBarPreference(mContext);
+        mPreference.setMax(100);
+        mController = new AutoBatterySeekBarPreferenceController(mContext, mLifecycle);
+    }
+
+    @Test
+    public void testPreference_lowPowerLevelZero_preferenceInvisible() {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
+
+        mController.updateState(mPreference);
+
+        assertThat(mPreference.isVisible()).isFalse();
+    }
+
+    @Test
+    public void testPreference_lowPowerLevelNotZero_updatePreference() {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, TRIGGER_LEVEL);
+        mController.updateState(mPreference);
+
+        assertThat(mPreference.isVisible()).isTrue();
+        assertThat(mPreference.getTitle()).isEqualTo("Turn on automatically at 15%");
+        assertThat(mPreference.getProgress()).isEqualTo(TRIGGER_LEVEL);
+    }
+
+    @Test
+    public void testOnPreferenceChange_updateValue() {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
+
+        mController.onPreferenceChange(mPreference, TRIGGER_LEVEL);
+
+        assertThat(Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0)).isEqualTo(TRIGGER_LEVEL);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/widget/SeekBarPreferenceTest.java b/tests/robotests/src/com/android/settings/widget/SeekBarPreferenceTest.java
new file mode 100644
index 0000000..7a042a0
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/widget/SeekBarPreferenceTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.widget;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.Parcelable;
+
+import com.android.settings.TestConfig;
+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.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SeekBarPreferenceTest {
+    private static final int MAX = 75;
+    private static final int MIN = 5;
+    private static final int PROGRESS = 16;
+
+    private Context mContext;
+    private SeekBarPreference mSeekBarPreference;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+
+        mSeekBarPreference = new SeekBarPreference(mContext);
+        mSeekBarPreference.setMax(MAX);
+        mSeekBarPreference.setMin(MIN);
+        mSeekBarPreference.setProgress(PROGRESS);
+        mSeekBarPreference.setPersistent(false);
+    }
+
+    @Test
+    public void testSaveAndRestoreInstanceState() {
+        final Parcelable parcelable = mSeekBarPreference.onSaveInstanceState();
+
+        final SeekBarPreference preference = new SeekBarPreference(mContext);
+        preference.onRestoreInstanceState(parcelable);
+
+        assertThat(preference.getMax()).isEqualTo(MAX);
+        assertThat(preference.getMin()).isEqualTo(MIN);
+        assertThat(preference.getProgress()).isEqualTo(PROGRESS);
+    }
+}