diff --git a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceController.java b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceController.java index 5c57c0ca96d..d4b29b4e439 100644 --- a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceController.java +++ b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceController.java @@ -38,6 +38,7 @@ import com.android.settingslib.fuelgauge.BatterySaverUtils; import com.android.settingslib.widget.MainSwitchPreference; /** Controller to update the battery saver button */ +// LINT.IfChange public class BatterySaverButtonPreferenceController extends TogglePreferenceController implements LifecycleObserver, OnStart, OnStop, BatterySaverReceiver.BatterySaverListener { private static final long SWITCH_ANIMATION_DURATION = 350L; @@ -129,3 +130,4 @@ public class BatterySaverButtonPreferenceController extends TogglePreferenceCont } } } +// LINT.ThenChange(BatterySaverPreference.kt) diff --git a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverPreference.kt b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverPreference.kt new file mode 100644 index 00000000000..f8c058ffdce --- /dev/null +++ b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverPreference.kt @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2024 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.Context +import android.os.Handler +import android.os.Looper +import android.os.PowerManager +import com.android.settings.R +import com.android.settings.fuelgauge.BatterySaverReceiver +import com.android.settings.fuelgauge.BatterySaverReceiver.BatterySaverListener +import com.android.settingslib.datastore.KeyValueStore +import com.android.settingslib.datastore.NoOpKeyedObservable +import com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_SETTINGS +import com.android.settingslib.fuelgauge.BatterySaverUtils +import com.android.settingslib.fuelgauge.BatteryStatus +import com.android.settingslib.fuelgauge.BatteryUtils +import com.android.settingslib.metadata.MainSwitchPreference +import com.android.settingslib.metadata.PreferenceLifecycleContext +import com.android.settingslib.metadata.PreferenceLifecycleProvider + +// LINT.IfChange +class BatterySaverPreference : + MainSwitchPreference(KEY, R.string.battery_saver_master_switch_title), + PreferenceLifecycleProvider { + + private var batterySaverReceiver: BatterySaverReceiver? = null + private val handler by lazy { Handler(Looper.getMainLooper()) } + + override fun storage(context: Context) = BatterySaverStore(context) + + override fun isEnabled(context: Context) = + !BatteryStatus(BatteryUtils.getBatteryIntent(context)).isPluggedIn + + override fun onStart(context: PreferenceLifecycleContext) { + BatterySaverReceiver(context).apply { + batterySaverReceiver = this + setBatterySaverListener( + object : BatterySaverListener { + override fun onPowerSaveModeChanged() { + handler.postDelayed( + { context.notifyPreferenceChange(this@BatterySaverPreference) }, + SWITCH_ANIMATION_DURATION, + ) + } + + override fun onBatteryChanged(pluggedIn: Boolean) = + context.notifyPreferenceChange(this@BatterySaverPreference) + } + ) + setListening(true) + } + } + + override fun onStop(context: PreferenceLifecycleContext) { + batterySaverReceiver?.setListening(false) + batterySaverReceiver = null + handler.removeCallbacksAndMessages(null /* token */) + } + + @Suppress("UNCHECKED_CAST") + class BatterySaverStore(private val context: Context) : + NoOpKeyedObservable(), KeyValueStore { + override fun contains(key: String) = key == KEY + + override fun getValue(key: String, valueType: Class) = + context.isPowerSaveMode() as T + + override fun setValue(key: String, valueType: Class, value: T?) { + BatterySaverUtils.setPowerSaveMode( + context, + value as Boolean, + /* needFirstTimeWarning= */ false, + SAVER_ENABLED_SETTINGS, + ) + } + + private fun Context.isPowerSaveMode() = + getSystemService(PowerManager::class.java)?.isPowerSaveMode == true + } + + companion object { + private const val KEY = "battery_saver" + private const val SWITCH_ANIMATION_DURATION: Long = 350L + } +} +// LINT.ThenChange(BatterySaverButtonPreferenceController.java) diff --git a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScreen.kt b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScreen.kt index 2226e37cd93..34be9e875f1 100644 --- a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScreen.kt +++ b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScreen.kt @@ -39,7 +39,9 @@ class BatterySaverScreen : PreferenceScreenCreator { override fun hasCompleteHierarchy() = false - override fun getPreferenceHierarchy(context: Context) = preferenceHierarchy(this) {} + override fun getPreferenceHierarchy(context: Context) = preferenceHierarchy(this) { + +BatterySaverPreference() + } companion object { const val KEY = "battery_saver_screen" 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 cdcb12fdd35..8fe18eb7e1d 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverButtonPreferenceControllerTest.java @@ -40,6 +40,7 @@ import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +// LINT.IfChange @RunWith(RobolectricTestRunner.class) public class BatterySaverButtonPreferenceControllerTest { @@ -120,3 +121,4 @@ public class BatterySaverButtonPreferenceControllerTest { assertThat(mController.isPublicSlice()).isTrue(); } } +// LINT.ThenChange(BatterySaverPreferenceTest.kt) diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverPreferenceTest.kt b/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverPreferenceTest.kt new file mode 100644 index 00000000000..052ba757dea --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverPreferenceTest.kt @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2024 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.BroadcastReceiver +import android.content.Context +import android.content.ContextWrapper +import android.content.Intent +import android.content.IntentFilter +import android.os.BatteryManager.EXTRA_PLUGGED +import android.os.PowerManager +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.preference.createAndBindWidget +import com.android.settingslib.widget.MainSwitchPreference +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.stub +import org.mockito.kotlin.verify + +// LINT.IfChange +@RunWith(AndroidJUnit4::class) +class BatterySaverPreferenceTest { + private val powerManager = mock() + + private val context: Context = + object : ContextWrapper(ApplicationProvider.getApplicationContext()) { + override fun getSystemService(name: String): Any? = + when { + name == getSystemServiceName(PowerManager::class.java) -> powerManager + else -> super.getSystemService(name) + } + + override fun registerReceiver(receiver: BroadcastReceiver?, filter: IntentFilter?) = + Intent().putExtra(EXTRA_PLUGGED, 0) + } + + private val contextPlugIn: Context = + object : ContextWrapper(ApplicationProvider.getApplicationContext()) { + override fun registerReceiver(receiver: BroadcastReceiver?, filter: IntentFilter?) = + Intent().putExtra(EXTRA_PLUGGED, 1) + } + + private val batterySaverPreference = BatterySaverPreference() + + @Test + fun lowPowerOn_preferenceIsChecked() { + powerManager.stub { on { isPowerSaveMode } doReturn true } + + assertThat(getMainSwitchPreference().isChecked).isTrue() + } + + @Test + fun lowPowerOff_preferenceIsUnChecked() { + powerManager.stub { on { isPowerSaveMode } doReturn false } + + assertThat(getMainSwitchPreference().isChecked).isFalse() + } + + @Test + fun storeSetOn_setPowerSaveMode() { + batterySaverPreference + .storage(context) + .setValue(batterySaverPreference.key, Boolean::class.javaObjectType, true) + + verify(powerManager).setPowerSaveModeEnabled(true) + } + + @Test + fun storeSetOff_unsetPowerSaveMode() { + batterySaverPreference + .storage(context) + .setValue(batterySaverPreference.key, Boolean::class.javaObjectType, false) + + verify(powerManager).setPowerSaveModeEnabled(false) + } + + @Test + fun isUnPlugIn_preferenceEnabled() { + assertThat(getMainSwitchPreference().isEnabled).isTrue() + } + + @Test + fun isPlugIn_preferenceDisabled() { + assertThat(getMainSwitchPreference(contextPlugIn).isEnabled).isFalse() + } + + private fun getMainSwitchPreference(ctx: Context = context) = + batterySaverPreference.createAndBindWidget(ctx) +} +// LINT.ThenChange(BatterySaverButtonPreferenceControllerTest.java) diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScreenTest.kt b/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScreenTest.kt index a034e5205cc..f706351167e 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScreenTest.kt +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScreenTest.kt @@ -15,21 +15,37 @@ */ package com.android.settings.fuelgauge.batterysaver +import android.content.Intent +import android.os.BatteryManager import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settings.flags.Flags import com.android.settingslib.preference.CatalystScreenTestCase import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class BatterySaverScreenTest : CatalystScreenTestCase() { + private val intent = + Intent(Intent.ACTION_BATTERY_CHANGED).putExtra(BatteryManager.EXTRA_PLUGGED, 0) override val preferenceScreenCreator = BatterySaverScreen() override val flagName: String get() = Flags.FLAG_CATALYST_BATTERY_SAVER_SCREEN + @Before + fun setUp() { + appContext.sendStickyBroadcast(intent) + } + + @After + fun tearDown() { + appContext.removeStickyBroadcast(intent) + } + @Test fun key() { assertThat(preferenceScreenCreator.key).isEqualTo(BatterySaverScreen.KEY)