Update and expose the low battery tip.

This tip was punted however we need to bring it back to P. It happens
when battery level is low or remaining time is less than 3 hour. The
suggestion is to turn on battery saver.

1. Extend tip from EarlyWarningTip since it has most common logic
2. Update the detector to align it to battery saver notifcation in
systemui.
3. Update tip order to surface low battery tip.

Follow CL will:
1. Hook up the low battery threshold to server side
2. Add test stub for this tip, so we could trigger it by adb.

Change-Id: I14f9696a549393bf980e31838fb86afd5d9efbc7
Bug: 76113067
Test: RunSettingsRoboTests
This commit is contained in:
Lei Yu
2018-04-20 14:58:05 -07:00
parent 99902e1faf
commit b2f089c468
7 changed files with 179 additions and 41 deletions

View File

@@ -67,7 +67,7 @@ public class BatteryTipLoader extends AsyncLoader<List<BatteryTip>> {
final BatteryInfo batteryInfo = mBatteryUtils.getBatteryInfo(mBatteryStatsHelper, TAG); final BatteryInfo batteryInfo = mBatteryUtils.getBatteryInfo(mBatteryStatsHelper, TAG);
final Context context = getContext(); final Context context = getContext();
tips.add(new LowBatteryDetector(policy, batteryInfo).detect()); tips.add(new LowBatteryDetector(context, policy, batteryInfo).detect());
tips.add(new HighUsageDetector(context, policy, mBatteryStatsHelper, tips.add(new HighUsageDetector(context, policy, mBatteryStatsHelper,
batteryInfo.discharging).detect()); batteryInfo.discharging).detect());
tips.add(new SmartBatteryDetector(policy, context.getContentResolver()).detect()); tips.add(new SmartBatteryDetector(policy, context.getContentResolver()).detect());
@@ -87,7 +87,8 @@ public class BatteryTipLoader extends AsyncLoader<List<BatteryTip>> {
final List<BatteryTip> tips = new ArrayList<>(); final List<BatteryTip> tips = new ArrayList<>();
tips.add(new SummaryTip(BatteryTip.StateType.NEW, tips.add(new SummaryTip(BatteryTip.StateType.NEW,
Estimate.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN)); Estimate.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN));
tips.add(new LowBatteryTip(BatteryTip.StateType.NEW)); tips.add(new LowBatteryTip(BatteryTip.StateType.NEW, false /* powerSaveModeOn */,
"Fake data"));
return tips; return tips;
} }

View File

@@ -16,31 +16,52 @@
package com.android.settings.fuelgauge.batterytip.detectors; package com.android.settings.fuelgauge.batterytip.detectors;
import android.text.format.DateUtils; import android.content.Context;
import android.os.PowerManager;
import com.android.settings.fuelgauge.BatteryInfo; import com.android.settings.fuelgauge.BatteryInfo;
import com.android.settings.fuelgauge.batterytip.BatteryTipPolicy; import com.android.settings.fuelgauge.batterytip.BatteryTipPolicy;
import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
import com.android.settings.fuelgauge.batterytip.tips.LowBatteryTip; import com.android.settings.fuelgauge.batterytip.tips.LowBatteryTip;
import java.util.concurrent.TimeUnit;
/** /**
* Detect whether the battery is too low * Detect whether the battery is too low
*/ */
public class LowBatteryDetector implements BatteryTipDetector { public class LowBatteryDetector implements BatteryTipDetector {
private BatteryInfo mBatteryInfo; private BatteryInfo mBatteryInfo;
private BatteryTipPolicy mPolicy; private BatteryTipPolicy mPolicy;
private PowerManager mPowerManager;
private int mWarningLevel;
public LowBatteryDetector(BatteryTipPolicy policy, BatteryInfo batteryInfo) { public LowBatteryDetector(Context context, BatteryTipPolicy policy, BatteryInfo batteryInfo) {
mPolicy = policy; mPolicy = policy;
mBatteryInfo = batteryInfo; mBatteryInfo = batteryInfo;
mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mWarningLevel = context.getResources().getInteger(
com.android.internal.R.integer.config_lowBatteryWarningLevel);
} }
@Override @Override
public BatteryTip detect() { public BatteryTip detect() {
// Show it if battery life is less than mPolicy.lowBatteryHour final boolean powerSaveModeOn = mPowerManager.isPowerSaveMode();
final boolean isShown = mPolicy.lowBatteryEnabled && mBatteryInfo.discharging //TODO(jackqdyulei): hook up this 3 hours to server side
&& mBatteryInfo.remainingTimeUs < mPolicy.lowBatteryHour * DateUtils.HOUR_IN_MILLIS; final boolean lowBattery = mBatteryInfo.batteryLevel <= mWarningLevel
|| (mBatteryInfo.discharging
&& mBatteryInfo.remainingTimeUs < TimeUnit.HOURS.toMicros(3));
int state = BatteryTip.StateType.INVISIBLE;
if (mPolicy.lowBatteryEnabled) {
if (powerSaveModeOn) {
// Show it is handled if battery saver is on
state = BatteryTip.StateType.HANDLED;
} else if (mBatteryInfo.discharging && lowBattery) {
state = BatteryTip.StateType.NEW;
}
}
return new LowBatteryTip( return new LowBatteryTip(
isShown ? BatteryTip.StateType.NEW : BatteryTip.StateType.INVISIBLE); state, powerSaveModeOn, mBatteryInfo.remainingLabel);
} }
} }

View File

@@ -74,10 +74,10 @@ public abstract class BatteryTip implements Comparable<BatteryTip>, Parcelable {
TIP_ORDER.append(TipType.APP_RESTRICTION, 0); TIP_ORDER.append(TipType.APP_RESTRICTION, 0);
TIP_ORDER.append(TipType.BATTERY_SAVER, 1); TIP_ORDER.append(TipType.BATTERY_SAVER, 1);
TIP_ORDER.append(TipType.HIGH_DEVICE_USAGE, 2); TIP_ORDER.append(TipType.HIGH_DEVICE_USAGE, 2);
TIP_ORDER.append(TipType.SUMMARY, 3); TIP_ORDER.append(TipType.LOW_BATTERY, 3);
TIP_ORDER.append(TipType.SMART_BATTERY_MANAGER, 4); TIP_ORDER.append(TipType.SUMMARY, 4);
TIP_ORDER.append(TipType.REDUCED_BATTERY, 5); TIP_ORDER.append(TipType.SMART_BATTERY_MANAGER, 5);
TIP_ORDER.append(TipType.LOW_BATTERY, 6); TIP_ORDER.append(TipType.REDUCED_BATTERY, 6);
TIP_ORDER.append(TipType.REMOVE_APP_RESTRICTION, 7); TIP_ORDER.append(TipType.REMOVE_APP_RESTRICTION, 7);
} }

View File

@@ -25,36 +25,32 @@ import com.android.settings.R;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
/** /**
* Tip to show current battery life is short * Tip to show current battery level is low or remaining time is less than a certain period
*/ */
public class LowBatteryTip extends BatteryTip { public class LowBatteryTip extends EarlyWarningTip {
private CharSequence mSummary;
public LowBatteryTip(@StateType int state) { public LowBatteryTip(@StateType int state, boolean powerSaveModeOn, CharSequence summary) {
super(TipType.LOW_BATTERY, state, false /* showDialog */); super(state, powerSaveModeOn);
mType = TipType.LOW_BATTERY;
mSummary = summary;
} }
private LowBatteryTip(Parcel in) { public LowBatteryTip(Parcel in) {
super(in); super(in);
} mSummary = in.readCharSequence();
@Override
public CharSequence getTitle(Context context) {
return context.getString(R.string.battery_tip_low_battery_title);
} }
@Override @Override
public CharSequence getSummary(Context context) { public CharSequence getSummary(Context context) {
return context.getString(R.string.battery_tip_low_battery_summary); return mState == StateType.HANDLED ? context.getString(
R.string.battery_tip_early_heads_up_done_summary) : mSummary;
} }
@Override @Override
public int getIconId() { public void writeToParcel(Parcel dest, int flags) {
return R.drawable.ic_perm_device_information_red_24dp; super.writeToParcel(dest, flags);
} dest.writeCharSequence(mSummary);
@Override
public void updateState(BatteryTip tip) {
mState = tip.mState;
} }
@Override @Override

View File

@@ -48,9 +48,9 @@ public class BatteryTipLoaderTest {
BatteryTip.TipType.APP_RESTRICTION, BatteryTip.TipType.APP_RESTRICTION,
BatteryTip.TipType.BATTERY_SAVER, BatteryTip.TipType.BATTERY_SAVER,
BatteryTip.TipType.HIGH_DEVICE_USAGE, BatteryTip.TipType.HIGH_DEVICE_USAGE,
BatteryTip.TipType.LOW_BATTERY,
BatteryTip.TipType.SUMMARY, BatteryTip.TipType.SUMMARY,
BatteryTip.TipType.SMART_BATTERY_MANAGER, BatteryTip.TipType.SMART_BATTERY_MANAGER};
BatteryTip.TipType.LOW_BATTERY};
@Mock(answer = Answers.RETURNS_DEEP_STUBS) @Mock(answer = Answers.RETURNS_DEEP_STUBS)
private BatteryStatsHelper mBatteryStatsHelper; private BatteryStatsHelper mBatteryStatsHelper;
@Mock @Mock

View File

@@ -17,12 +17,15 @@
package com.android.settings.fuelgauge.batterytip.detectors; package com.android.settings.fuelgauge.batterytip.detectors;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import android.text.format.DateUtils; import android.content.Context;
import android.os.PowerManager;
import com.android.settings.fuelgauge.BatteryInfo; import com.android.settings.fuelgauge.BatteryInfo;
import com.android.settings.fuelgauge.batterytip.BatteryTipPolicy; import com.android.settings.fuelgauge.batterytip.BatteryTipPolicy;
import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.SettingsRobolectricTestRunner;
import org.junit.Before; import org.junit.Before;
@@ -31,8 +34,12 @@ import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment; import org.robolectric.RuntimeEnvironment;
import org.robolectric.Shadows;
import org.robolectric.shadows.ShadowPowerManager;
import org.robolectric.util.ReflectionHelpers; import org.robolectric.util.ReflectionHelpers;
import java.util.concurrent.TimeUnit;
@RunWith(SettingsRobolectricTestRunner.class) @RunWith(SettingsRobolectricTestRunner.class)
public class LowBatteryDetectorTest { public class LowBatteryDetectorTest {
@@ -40,36 +47,60 @@ public class LowBatteryDetectorTest {
private BatteryInfo mBatteryInfo; private BatteryInfo mBatteryInfo;
private BatteryTipPolicy mPolicy; private BatteryTipPolicy mPolicy;
private LowBatteryDetector mLowBatteryDetector; private LowBatteryDetector mLowBatteryDetector;
private ShadowPowerManager mShadowPowerManager;
private Context mContext;
@Before @Before
public void setUp() { public void setUp() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mPolicy = spy(new BatteryTipPolicy(RuntimeEnvironment.application)); mPolicy = spy(new BatteryTipPolicy(RuntimeEnvironment.application));
mContext = RuntimeEnvironment.application;
mShadowPowerManager = Shadows.shadowOf(mContext.getSystemService(PowerManager.class));
ReflectionHelpers.setField(mPolicy, "lowBatteryEnabled", true); ReflectionHelpers.setField(mPolicy, "lowBatteryEnabled", true);
mBatteryInfo.discharging = true;
mLowBatteryDetector = new LowBatteryDetector(mPolicy, mBatteryInfo); mLowBatteryDetector = new LowBatteryDetector(mContext, mPolicy, mBatteryInfo);
} }
@Test @Test
public void testDetect_disabledByPolicy_tipInvisible() { public void testDetect_disabledByPolicy_tipInvisible() {
ReflectionHelpers.setField(mPolicy, "lowBatteryEnabled", false); ReflectionHelpers.setField(mPolicy, "lowBatteryEnabled", false);
mShadowPowerManager.setIsPowerSaveMode(true);
assertThat(mLowBatteryDetector.detect().isVisible()).isFalse(); assertThat(mLowBatteryDetector.detect().isVisible()).isFalse();
} }
@Test @Test
public void testDetect_shortBatteryLife_tipVisible() { public void testDetect_lowBattery_tipNew() {
mBatteryInfo.discharging = true; mBatteryInfo.batteryLevel = 3;
mBatteryInfo.remainingTimeUs = DateUtils.MINUTE_IN_MILLIS; mBatteryInfo.remainingTimeUs = TimeUnit.DAYS.toMillis(1);
assertThat(mLowBatteryDetector.detect().getState()).isEqualTo(BatteryTip.StateType.NEW);
assertThat(mLowBatteryDetector.detect().isVisible()).isTrue(); mBatteryInfo.batteryLevel = 50;
mBatteryInfo.remainingTimeUs = TimeUnit.MINUTES.toMillis(1);
assertThat(mLowBatteryDetector.detect().getState()).isEqualTo(BatteryTip.StateType.NEW);
} }
@Test @Test
public void testDetect_longBatteryLife_tipInvisible() { public void testDetect_batterySaverOn_tipHandled() {
mBatteryInfo.discharging = true; mShadowPowerManager.setIsPowerSaveMode(true);
mBatteryInfo.remainingTimeUs = DateUtils.DAY_IN_MILLIS;
assertThat(mLowBatteryDetector.detect().getState())
.isEqualTo(BatteryTip.StateType.HANDLED);
}
@Test
public void testDetect_charging_tipInvisible() {
mBatteryInfo.discharging = false;
assertThat(mLowBatteryDetector.detect().isVisible()).isFalse();
}
@Test
public void testDetect_noEarlyWarning_tipInvisible() {
mBatteryInfo.remainingTimeUs = TimeUnit.DAYS.toMicros(1);
mBatteryInfo.batteryLevel = 100;
assertThat(mLowBatteryDetector.detect().isVisible()).isFalse(); assertThat(mLowBatteryDetector.detect().isVisible()).isFalse();
} }

View File

@@ -0,0 +1,89 @@
/*
* 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.batterytip.tips;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.os.Parcel;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
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;
@RunWith(SettingsRobolectricTestRunner.class)
public class LowBatteryTipTest {
private static final CharSequence SUMMARY = "Only 15 minutes left";
@Mock
private MetricsFeatureProvider mMetricsFeatureProvider;
private Context mContext;
private LowBatteryTip mLowBatteryTip;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mLowBatteryTip = new LowBatteryTip(BatteryTip.StateType.NEW, false /* powerSaveModeOn */,
SUMMARY);
}
@Test
public void testParcelable() {
Parcel parcel = Parcel.obtain();
mLowBatteryTip.writeToParcel(parcel, mLowBatteryTip.describeContents());
parcel.setDataPosition(0);
final LowBatteryTip parcelTip = new LowBatteryTip(parcel);
assertThat(parcelTip.isPowerSaveModeOn()).isFalse();
assertThat(parcelTip.getSummary(mContext)).isEqualTo(SUMMARY);
}
@Test
public void getSummary_tipHandled_showSummary() {
mLowBatteryTip.mState = BatteryTip.StateType.HANDLED;
assertThat(mLowBatteryTip.getSummary(mContext)).isEqualTo("Some features may be limited");
}
@Test
public void getSummary_tipNew_showSummary() {
mLowBatteryTip.mState = BatteryTip.StateType.NEW;
assertThat(mLowBatteryTip.getSummary(mContext)).isEqualTo(SUMMARY);
}
@Test
public void log_lowBatteryActionWithCorrectState() {
mLowBatteryTip.log(mContext, mMetricsFeatureProvider);
verify(mMetricsFeatureProvider).action(mContext,
MetricsProto.MetricsEvent.ACTION_LOW_BATTERY_TIP, BatteryTip.StateType.NEW);
}
}