From ddc6c646c0aa75001fec7cf67d2bc05ab455df35 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Tue, 22 May 2018 14:12:32 -0700 Subject: [PATCH] Convert BatterySaverButton controller to Slice compatible. - Update preference key to match the key defined in SettingsSlicesContract - Model TwoStateButtonPreference similar to TwoStatePreference (add setChecked, isChecked method) - Remove TwoStateButtonPreferenceController entirely because all methods are moved into Preference directly for better encapsulation. - Make BatterySaverButtonPrefController direclty implement TogglePreferenceController. It was not possible before because the interface between TwoStateButtonPreferene is too different from TwoStatePreference. Bug: 80106671 Test: robotests Change-Id: Ib72807dcf1b36e959e08df8d80538c3f9f79b76d --- res/xml/battery_saver_settings.xml | 15 ++-- ...atterySaverButtonPreferenceController.java | 79 ++++++++++++------- .../batterysaver/BatterySaverSettings.java | 1 - .../widget/TwoStateButtonPreference.java | 65 ++++++++++++--- .../TwoStateButtonPreferenceController.java | 78 ------------------ ...rySaverButtonPreferenceControllerTest.java | 47 +++++------ ...java => TwoStateButtonPreferenceTest.java} | 72 ++++++----------- 7 files changed, 159 insertions(+), 198 deletions(-) delete mode 100644 src/com/android/settings/widget/TwoStateButtonPreferenceController.java rename tests/robotests/src/com/android/settings/widget/{TwoStateButtonPreferenceControllerTest.java => TwoStateButtonPreferenceTest.java} (60%) diff --git a/res/xml/battery_saver_settings.xml b/res/xml/battery_saver_settings.xml index 116079d5247..0460459d961 100644 --- a/res/xml/battery_saver_settings.xml +++ b/res/xml/battery_saver_settings.xml @@ -18,32 +18,35 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:settings="http://schemas.android.com/apk/res-auto" android:title="@string/battery_saver" - android:key="battery_saver"> + android:key="battery_saver_page"> + settings:controller="com.android.settings.fuelgauge.batterysaver.AutoBatterySaverPreferenceController" /> + android:min="5" /> + settings:textOff="@string/battery_saver_button_turn_off" + settings:platform_slice="true" + settings:controller="com.android.settings.fuelgauge.batterysaver.BatterySaverButtonPreferenceController" /> + android:selectable="false" /> diff --git a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceController.java b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceController.java index f5d3a971597..00b890d24df 100644 --- a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceController.java +++ b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceController.java @@ -18,36 +18,45 @@ package com.android.settings.fuelgauge.batterysaver; import android.content.Context; import android.os.PowerManager; -import androidx.annotation.VisibleForTesting; -import androidx.preference.Preference; +import com.android.settings.core.TogglePreferenceController; import com.android.settings.fuelgauge.BatterySaverReceiver; -import com.android.settings.widget.TwoStateButtonPreferenceController; -import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settings.widget.TwoStateButtonPreference; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnStart; import com.android.settingslib.core.lifecycle.events.OnStop; import com.android.settingslib.fuelgauge.BatterySaverUtils; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + /** * Controller to update the battery saver button */ public class BatterySaverButtonPreferenceController extends - TwoStateButtonPreferenceController implements + TogglePreferenceController implements LifecycleObserver, OnStart, OnStop, BatterySaverReceiver.BatterySaverListener { - private static final String KEY = "battery_saver_button_container"; - private BatterySaverReceiver mBatterySaverReceiver; - @VisibleForTesting - PowerManager mPowerManager; - public BatterySaverButtonPreferenceController(Context context, Lifecycle lifecycle) { - super(context, KEY); + private final BatterySaverReceiver mBatterySaverReceiver; + private final PowerManager mPowerManager; + + private TwoStateButtonPreference mPreference; + + public BatterySaverButtonPreferenceController(Context context, String key) { + super(context, key); mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); mBatterySaverReceiver = new BatterySaverReceiver(context); mBatterySaverReceiver.setBatterySaverListener(this); - if (lifecycle != null) { - lifecycle.addObserver(this); - } + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public boolean isSliceable() { + return true; } @Override @@ -60,30 +69,44 @@ public class BatterySaverButtonPreferenceController extends mBatterySaverReceiver.setListening(false); } + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = (TwoStateButtonPreference) screen.findPreference(getPreferenceKey()); + } + + @Override + public boolean isChecked() { + return mPowerManager.isPowerSaveMode(); + } + + @Override + public boolean setChecked(boolean stateOn) { + // This screen already shows a warning, so we don't need another warning. + return BatterySaverUtils.setPowerSaveMode(mContext, stateOn, + false /* needFirstTimeWarning */); + } + @Override public void updateState(Preference preference) { super.updateState(preference); - setButtonVisibility(!mPowerManager.isPowerSaveMode()); - } - - @Override - public int getAvailabilityStatus() { - return AVAILABLE; - } - - @Override - public void onButtonClicked(boolean stateOn) { - // This screen already shows a warning, so we don't need another warning. - BatterySaverUtils.setPowerSaveMode(mContext, stateOn, /*needFirstTimeWarning*/ false); + if (mPreference != null) { + mPreference.setChecked(isChecked()); + } } @Override public void onPowerSaveModeChanged() { - setButtonVisibility(!mPowerManager.isPowerSaveMode()); + final boolean isChecked = isChecked(); + if (mPreference != null && mPreference.isChecked() != isChecked) { + mPreference.setChecked(isChecked); + } } @Override public void onBatteryChanged(boolean pluggedIn) { - setButtonEnabled(!pluggedIn); + if (mPreference != null) { + mPreference.setButtonEnabled(!pluggedIn); + } } } diff --git a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverSettings.java b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverSettings.java index aa16fc9fe16..52889700935 100644 --- a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverSettings.java +++ b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverSettings.java @@ -75,7 +75,6 @@ public class BatterySaverSettings extends DashboardFragment { final List controllers = new ArrayList<>(); controllers.add(new AutoBatterySaverPreferenceController(context)); controllers.add(new AutoBatterySeekBarPreferenceController(context, lifecycle)); - controllers.add(new BatterySaverButtonPreferenceController(context, lifecycle)); return controllers; } diff --git a/src/com/android/settings/widget/TwoStateButtonPreference.java b/src/com/android/settings/widget/TwoStateButtonPreference.java index 35877a0bec3..ae3e4956b0b 100644 --- a/src/com/android/settings/widget/TwoStateButtonPreference.java +++ b/src/com/android/settings/widget/TwoStateButtonPreference.java @@ -18,22 +18,34 @@ package com.android.settings.widget; import android.content.Context; import android.content.res.TypedArray; -import androidx.core.content.res.TypedArrayUtils; import android.util.AttributeSet; +import android.view.View; import android.widget.Button; import com.android.settings.R; import com.android.settings.applications.LayoutPreference; +import androidx.annotation.VisibleForTesting; +import androidx.core.content.res.TypedArrayUtils; + /** * Preference that presents a button with two states(On vs Off) */ -public class TwoStateButtonPreference extends LayoutPreference { +public class TwoStateButtonPreference extends LayoutPreference implements + View.OnClickListener { + + private boolean mIsChecked; + private final Button mButtonOn; + private final Button mButtonOff; + public TwoStateButtonPreference(Context context, AttributeSet attrs) { super(context, attrs, TypedArrayUtils.getAttr( context, R.attr.twoStateButtonPreferenceStyle, android.R.attr.preferenceStyle)); - if (attrs != null) { + if (attrs == null) { + mButtonOn = null; + mButtonOff = null; + } else { final TypedArray styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.TwoStateButtonPreference); final int textOnId = styledAttrs.getResourceId( @@ -44,19 +56,52 @@ public class TwoStateButtonPreference extends LayoutPreference { R.string.summary_placeholder); styledAttrs.recycle(); - final Button buttonOn = getStateOnButton(); - buttonOn.setText(textOnId); - final Button buttonOff = getStateOffButton(); - buttonOff.setText(textOffId); + mButtonOn = findViewById(R.id.state_on_button); + mButtonOn.setText(textOnId); + mButtonOn.setOnClickListener(this); + mButtonOff = findViewById(R.id.state_off_button); + mButtonOff.setText(textOffId); + mButtonOff.setOnClickListener(this); + setChecked(isChecked()); } } - public Button getStateOnButton() { - return findViewById(R.id.state_on_button); + @Override + public void onClick(View v) { + final boolean stateOn = v.getId() == R.id.state_on_button; + setChecked(stateOn); + callChangeListener(stateOn); } + public void setChecked(boolean checked) { + // Update state + mIsChecked = checked; + // And update UI + if (checked) { + mButtonOn.setVisibility(View.GONE); + mButtonOff.setVisibility(View.VISIBLE); + } else { + mButtonOn.setVisibility(View.VISIBLE); + mButtonOff.setVisibility(View.GONE); + } + } + public boolean isChecked() { + return mIsChecked; + } + + public void setButtonEnabled(boolean enabled) { + mButtonOn.setEnabled(enabled); + mButtonOff.setEnabled(enabled); + } + + @VisibleForTesting + public Button getStateOnButton() { + return mButtonOn; + } + + @VisibleForTesting public Button getStateOffButton() { - return findViewById(R.id.state_off_button); + return mButtonOff; } } \ No newline at end of file diff --git a/src/com/android/settings/widget/TwoStateButtonPreferenceController.java b/src/com/android/settings/widget/TwoStateButtonPreferenceController.java deleted file mode 100644 index 72bf45e331e..00000000000 --- a/src/com/android/settings/widget/TwoStateButtonPreferenceController.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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 android.content.Context; -import androidx.preference.PreferenceScreen; -import android.view.View; -import android.widget.Button; - -import com.android.settings.R; -import com.android.settings.core.BasePreferenceController; - -/** - * Controller to update the button with two states(On vs Off). - */ -public abstract class TwoStateButtonPreferenceController extends BasePreferenceController - implements View.OnClickListener { - private Button mButtonOn; - private Button mButtonOff; - - public TwoStateButtonPreferenceController(Context context, String key) { - super(context, key); - } - - @Override - public void displayPreference(PreferenceScreen screen) { - super.displayPreference(screen); - final TwoStateButtonPreference preference = - (TwoStateButtonPreference) screen.findPreference(getPreferenceKey()); - mButtonOn = preference.getStateOnButton(); - mButtonOn.setOnClickListener(this); - mButtonOff = preference.getStateOffButton(); - mButtonOff.setOnClickListener(this); - } - - protected void setButtonVisibility(boolean stateOn) { - if (stateOn) { - mButtonOff.setVisibility(View.GONE); - mButtonOn.setVisibility(View.VISIBLE); - } else { - mButtonOff.setVisibility(View.VISIBLE); - mButtonOn.setVisibility(View.GONE); - } - } - - protected void setButtonEnabled(boolean enabled) { - mButtonOn.setEnabled(enabled); - mButtonOff.setEnabled(enabled); - } - - @Override - public void onClick(View v) { - final boolean stateOn = v.getId() == R.id.state_on_button; - onButtonClicked(stateOn); - } - - /** - * Callback when button is clicked - * - * @param stateOn {@code true} if stateOn button is clicked, otherwise it means stateOff - * button is clicked - */ - public abstract void onButtonClicked(boolean stateOn); -} \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceControllerTest.java index fbe6cf8fe8b..a4e8a7e75f4 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceControllerTest.java @@ -21,17 +21,12 @@ import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; -import androidx.lifecycle.LifecycleOwner; import android.content.Context; import android.os.PowerManager; -import androidx.preference.PreferenceScreen; -import android.view.View; import android.widget.Button; -import com.android.settings.R; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.widget.TwoStateButtonPreference; -import com.android.settingslib.core.lifecycle.Lifecycle; import org.junit.Before; import org.junit.Test; @@ -41,6 +36,9 @@ import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowPowerManager; +import org.robolectric.util.ReflectionHelpers; + +import androidx.preference.PreferenceScreen; @RunWith(SettingsRobolectricTestRunner.class) @Config(shadows = ShadowPowerManager.class) @@ -48,67 +46,58 @@ public class BatterySaverButtonPreferenceControllerTest { private BatterySaverButtonPreferenceController mController; private Context mContext; - private Lifecycle mLifecycle; - private LifecycleOwner mLifecycleOwner; private Button mButtonOn; private Button mButtonOff; private PowerManager mPowerManager; - @Mock private TwoStateButtonPreference mPreference; + @Mock private PreferenceScreen mPreferenceScreen; @Before public void setUp() { MockitoAnnotations.initMocks(this); - - mLifecycleOwner = () -> mLifecycle; - mLifecycle = new Lifecycle(mLifecycleOwner); mContext = spy(RuntimeEnvironment.application); + mButtonOn = new Button(mContext); + mButtonOff = new Button(mContext); mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mPreference = spy(new TwoStateButtonPreference(mContext, null /* AttributeSet */)); + ReflectionHelpers.setField(mPreference, "mButtonOn", mButtonOn); + ReflectionHelpers.setField(mPreference, "mButtonOff", mButtonOff); doReturn(mPreference).when(mPreferenceScreen).findPreference(anyString()); - mButtonOn = new Button(mContext); - mButtonOn.setId(R.id.state_on_button); - doReturn(mButtonOn).when(mPreference).getStateOnButton(); - mButtonOff = new Button(mContext); - mButtonOff.setId(R.id.state_off_button); - doReturn(mButtonOff).when(mPreference).getStateOffButton(); - - mController = new BatterySaverButtonPreferenceController(mContext, mLifecycle); + mController = new BatterySaverButtonPreferenceController(mContext, "test_key"); mController.displayPreference(mPreferenceScreen); } @Test - public void testUpdateState_lowPowerOn_displayButtonOff() { + public void updateState_lowPowerOn_preferenceIsChecked() { mPowerManager.setPowerSaveMode(true); mController.updateState(mPreference); - assertThat(mButtonOn.getVisibility()).isEqualTo(View.GONE); - assertThat(mButtonOff.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mPreference.isChecked()).isTrue(); } @Test - public void testUpdateState_lowPowerOff_displayButtonOn() { + public void testUpdateState_lowPowerOff_preferenceIsUnchecked() { mPowerManager.setPowerSaveMode(false); mController.updateState(mPreference); - assertThat(mButtonOn.getVisibility()).isEqualTo(View.VISIBLE); - assertThat(mButtonOff.getVisibility()).isEqualTo(View.GONE); + assertThat(mPreference.isChecked()).isFalse(); } @Test - public void testOnClick_clickButtonOn_setPowerSaveMode() { - mController.onClick(mButtonOn); + public void setChecked_on_setPowerSaveMode() { + mController.setChecked(true); assertThat(mPowerManager.isPowerSaveMode()).isTrue(); } @Test - public void testOnClick_clickButtonOff_clearPowerSaveMode() { - mController.onClick(mButtonOff); + public void setChecked_off_unsetPowerSaveMode() { + mController.setChecked(false); assertThat(mPowerManager.isPowerSaveMode()).isFalse(); } diff --git a/tests/robotests/src/com/android/settings/widget/TwoStateButtonPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/widget/TwoStateButtonPreferenceTest.java similarity index 60% rename from tests/robotests/src/com/android/settings/widget/TwoStateButtonPreferenceControllerTest.java rename to tests/robotests/src/com/android/settings/widget/TwoStateButtonPreferenceTest.java index 5ede7735ac0..c88e7f87d00 100644 --- a/tests/robotests/src/com/android/settings/widget/TwoStateButtonPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/widget/TwoStateButtonPreferenceTest.java @@ -17,34 +17,27 @@ package com.android.settings.widget; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import android.content.Context; -import androidx.preference.PreferenceScreen; import android.view.View; import android.widget.Button; +import com.android.settings.R; import com.android.settings.testutils.SettingsRobolectricTestRunner; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; +import org.robolectric.util.ReflectionHelpers; @RunWith(SettingsRobolectricTestRunner.class) -public class TwoStateButtonPreferenceControllerTest { +public class TwoStateButtonPreferenceTest { - private static final String KEY = "pref_key"; - - @Mock - private PreferenceScreen mPreferenceScreen; - @Mock private TwoStateButtonPreference mPreference; - private TwoStateButtonPreferenceController mController; private Context mContext; private Button mButtonOn; private Button mButtonOff; @@ -53,35 +46,34 @@ public class TwoStateButtonPreferenceControllerTest { public void setUp() { MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application); - doReturn(mPreference).when(mPreferenceScreen).findPreference(anyString()); + mPreference = spy(new TwoStateButtonPreference(mContext, null /* AttributeSet */)); mButtonOn = new Button(mContext); - doReturn(mButtonOn).when(mPreference).getStateOnButton(); + mButtonOn.setId(R.id.state_on_button); mButtonOff = new Button(mContext); - doReturn(mButtonOff).when(mPreference).getStateOffButton(); - - mController = new TestButtonsPreferenceController(mContext, KEY); - mController.displayPreference(mPreferenceScreen); + mButtonOff.setId(R.id.state_off_button); + ReflectionHelpers.setField(mPreference, "mButtonOn", mButtonOn); + ReflectionHelpers.setField(mPreference, "mButtonOff", mButtonOff); } @Test public void testSetButtonVisibility_stateOn_onlyShowButtonOn() { - mController.setButtonVisibility(true /* stateOn */); - - assertThat(mButtonOn.getVisibility()).isEqualTo(View.VISIBLE); - assertThat(mButtonOff.getVisibility()).isEqualTo(View.GONE); - } - - @Test - public void testSetButtonVisibility_stateOff_onlyShowButtonOff() { - mController.setButtonVisibility(false /* stateOn */); + mPreference.setChecked(true /* stateOn */); assertThat(mButtonOn.getVisibility()).isEqualTo(View.GONE); assertThat(mButtonOff.getVisibility()).isEqualTo(View.VISIBLE); } + @Test + public void testSetButtonVisibility_stateOff_onlyShowButtonOff() { + mPreference.setChecked(false /* stateOn */); + + assertThat(mButtonOn.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mButtonOff.getVisibility()).isEqualTo(View.GONE); + } + @Test public void testSetButtonEnabled_enabled_buttonEnabled() { - mController.setButtonEnabled(true /* enabled */); + mPreference.setButtonEnabled(true /* enabled */); assertThat(mButtonOn.isEnabled()).isTrue(); assertThat(mButtonOff.isEnabled()).isTrue(); @@ -89,30 +81,18 @@ public class TwoStateButtonPreferenceControllerTest { @Test public void testSetButtonEnabled_disabled_buttonDisabled() { - mController.setButtonEnabled(false /* enabled */); + mPreference.setButtonEnabled(false /* enabled */); assertThat(mButtonOn.isEnabled()).isFalse(); assertThat(mButtonOff.isEnabled()).isFalse(); } - /** - * Controller to test methods in {@link TwoStateButtonPreferenceController} - */ - public static class TestButtonsPreferenceController - extends TwoStateButtonPreferenceController { + @Test + public void onClick_shouldPropagateChangeToListener() { + mPreference.onClick(mButtonOn); + verify(mPreference).callChangeListener(true); - TestButtonsPreferenceController(Context context, String key) { - super(context, key); - } - - @Override - public void onButtonClicked(boolean stateOn) { - //do nothing - } - - @Override - public int getAvailabilityStatus() { - return AVAILABLE; - } + mPreference.onClick(mButtonOff); + verify(mPreference).callChangeListener(false); } }