Add Battery Defender feature to Settings

- Reupload CL from ag/13108999 to fix the merge conflict
 - Adding new tips of Battery Defender, will be presented once battery is overheated
 - Launch Help Center article of battery overheat when clicking Battery Defender tip
 Screenshots: https://screenshot.googleplex.com/7jUibTJANgR6UQ6.png
 	      https://screenshot.googleplex.com/tUj2LLi87SfndBN.png

Bug: 172794045
Bug: 173497281
Bug: 173496188
Test: make RunSettingsRoboTests -j40
Change-Id: Ibb106a5d42cdf6232abf9ddf4b3225bdcebccf4a
This commit is contained in:
Wesley.CW Wang
2020-11-26 20:12:58 +08:00
parent 2cd9bd7fe1
commit 9a6aae5006
19 changed files with 433 additions and 12 deletions

View File

@@ -20,6 +20,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.PowerManager;
import android.util.Log;
@@ -39,6 +40,7 @@ import java.lang.annotation.RetentionPolicy;
* 1. Battery level(e.g. 100%->99%)
* 2. Battery status(e.g. plugged->unplugged)
* 3. Battery saver(e.g. off->on)
* 4. Battery health(e.g. good->overheat)
*/
public class BatteryBroadcastReceiver extends BroadcastReceiver {
@@ -49,6 +51,7 @@ public class BatteryBroadcastReceiver extends BroadcastReceiver {
* Battery level(e.g. 100%->99%)
* Battery status(e.g. plugged->unplugged)
* Battery saver(e.g. off->on)
* Battery health(e.g. good->overheat)
*/
public interface OnBatteryChangedListener {
void onBatteryChanged(@BatteryUpdateType int type);
@@ -59,19 +62,23 @@ public class BatteryBroadcastReceiver extends BroadcastReceiver {
BatteryUpdateType.BATTERY_LEVEL,
BatteryUpdateType.BATTERY_SAVER,
BatteryUpdateType.BATTERY_STATUS,
BatteryUpdateType.BATTERY_HEALTH,
BatteryUpdateType.BATTERY_NOT_PRESENT})
public @interface BatteryUpdateType {
int MANUAL = 0;
int BATTERY_LEVEL = 1;
int BATTERY_SAVER = 2;
int BATTERY_STATUS = 3;
int BATTERY_NOT_PRESENT = 4;
int BATTERY_HEALTH = 4;
int BATTERY_NOT_PRESENT = 5;
}
@VisibleForTesting
String mBatteryLevel;
@VisibleForTesting
String mBatteryStatus;
@VisibleForTesting
int mBatteryHealth;
private OnBatteryChangedListener mBatteryListener;
private Context mContext;
@@ -106,11 +113,15 @@ public class BatteryBroadcastReceiver extends BroadcastReceiver {
if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
final String batteryLevel = Utils.getBatteryPercentage(intent);
final String batteryStatus = Utils.getBatteryStatus(mContext, intent);
final int batteryHealth = intent.getIntExtra(
BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_UNKNOWN);
if (!Utils.isBatteryPresent(intent)) {
Log.w(TAG, "Problem reading the battery meter.");
mBatteryListener.onBatteryChanged(BatteryUpdateType.BATTERY_NOT_PRESENT);
} else if (forceUpdate) {
mBatteryListener.onBatteryChanged(BatteryUpdateType.MANUAL);
} else if (batteryHealth != mBatteryHealth) {
mBatteryListener.onBatteryChanged(BatteryUpdateType.BATTERY_HEALTH);
} else if(!batteryLevel.equals(mBatteryLevel)) {
mBatteryListener.onBatteryChanged(BatteryUpdateType.BATTERY_LEVEL);
} else if (!batteryStatus.equals(mBatteryStatus)) {
@@ -118,6 +129,7 @@ public class BatteryBroadcastReceiver extends BroadcastReceiver {
}
mBatteryLevel = batteryLevel;
mBatteryStatus = batteryStatus;
mBatteryHealth = batteryHealth;
} else if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(intent.getAction())) {
mBatteryListener.onBatteryChanged(BatteryUpdateType.BATTERY_SAVER);
}

View File

@@ -124,7 +124,9 @@ public class BatteryHeaderPreferenceController extends BasePreferenceController
public void updateHeaderPreference(BatteryInfo info) {
mBatteryPercentText.setText(formatBatteryPercentageText(info.batteryLevel));
if (!mBatteryStatusFeatureProvider.triggerBatteryStatusUpdate(this, info)) {
if (info.remainingLabel == null) {
if (BatteryUtils.isBatteryDefenderOn(info)) {
mSummary1.setText(null);
} else if (info.remainingLabel == null) {
mSummary1.setText(info.statusLabel);
} else {
mSummary1.setText(info.remainingLabel);

View File

@@ -45,6 +45,7 @@ public class BatteryInfo {
public CharSequence remainingLabel;
public int batteryLevel;
public boolean discharging = true;
public boolean isOverheated;
public long remainingTimeUs = 0;
public long averageTimeToDischarge = EstimateKt.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN;
public String batteryPercentString;
@@ -232,6 +233,9 @@ public class BatteryInfo {
info.batteryPercentString = Utils.formatPercentage(info.batteryLevel);
info.mCharging = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
info.averageTimeToDischarge = estimate.getAverageDischargeTime();
info.isOverheated = batteryBroadcast.getIntExtra(
BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_UNKNOWN)
== BatteryManager.BATTERY_HEALTH_OVERHEAT;
info.statusLabel = Utils.getBatteryStatus(context, batteryBroadcast);
if (!info.mCharging) {
@@ -251,7 +255,12 @@ public class BatteryInfo {
BatteryManager.BATTERY_STATUS_UNKNOWN);
info.discharging = false;
info.suggestionLabel = null;
if (chargeTime > 0 && status != BatteryManager.BATTERY_STATUS_FULL) {
if (info.isOverheated && status != BatteryManager.BATTERY_STATUS_FULL) {
info.remainingLabel = null;
int chargingLimitedResId = R.string.power_charging_limited;
info.chargeLabel =
context.getString(chargingLimitedResId, info.batteryPercentString);
} else if (chargeTime > 0 && status != BatteryManager.BATTERY_STATUS_FULL) {
info.remainingTimeUs = chargeTime;
CharSequence timeString = StringUtil.formatElapsedTime(context,
PowerUtil.convertUsToMs(info.remainingTimeUs), false /* withSeconds */);

View File

@@ -403,6 +403,13 @@ public class BatteryUtils {
Log.d(tag, message + ": " + (System.currentTimeMillis() - startTime) + "ms");
}
/**
* Return {@code true} if battery is overheated and charging.
*/
public static boolean isBatteryDefenderOn(BatteryInfo batteryInfo) {
return batteryInfo.isOverheated && !batteryInfo.discharging;
}
/**
* Find package uid from package name
*

View File

@@ -23,6 +23,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.internal.os.BatteryStatsHelper;
import com.android.settings.fuelgauge.BatteryInfo;
import com.android.settings.fuelgauge.BatteryUtils;
import com.android.settings.fuelgauge.batterytip.detectors.BatteryDefenderDetector;
import com.android.settings.fuelgauge.batterytip.detectors.EarlyWarningDetector;
import com.android.settings.fuelgauge.batterytip.detectors.HighUsageDetector;
import com.android.settings.fuelgauge.batterytip.detectors.LowBatteryDetector;
@@ -72,6 +73,7 @@ public class BatteryTipLoader extends AsyncLoaderCompat<List<BatteryTip>> {
batteryInfo.discharging).detect());
tips.add(new SmartBatteryDetector(policy, context.getContentResolver()).detect());
tips.add(new EarlyWarningDetector(policy, context).detect());
tips.add(new BatteryDefenderDetector(batteryInfo).detect());
tips.add(new SummaryDetector(policy, batteryInfo.averageTimeToDischarge).detect());
// Disable this feature now since it introduces false positive cases. We will try to improve
// it in the future.

View File

@@ -29,6 +29,7 @@ import androidx.annotation.NonNull;
import com.android.internal.util.CollectionUtils;
import com.android.settings.SettingsActivity;
import com.android.settings.core.InstrumentedPreferenceFragment;
import com.android.settings.fuelgauge.batterytip.actions.BatteryDefenderAction;
import com.android.settings.fuelgauge.batterytip.actions.BatterySaverAction;
import com.android.settings.fuelgauge.batterytip.actions.BatteryTipAction;
import com.android.settings.fuelgauge.batterytip.actions.OpenBatterySaverAction;
@@ -111,6 +112,8 @@ public class BatteryTipUtils {
}
case BatteryTip.TipType.REMOVE_APP_RESTRICTION:
return new UnrestrictAppAction(settingsActivity, (UnrestrictAppTip) batteryTip);
case BatteryTip.TipType.BATTERY_DEFENDER:
return new BatteryDefenderAction(settingsActivity);
default:
return null;
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (C) 2020 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.actions;
import android.content.Intent;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settingslib.HelpUtils;
/**
* Action to open the Support Center article
*/
public class BatteryDefenderAction extends BatteryTipAction {
private SettingsActivity mSettingsActivity;
public BatteryDefenderAction(SettingsActivity settingsActivity) {
super(settingsActivity.getApplicationContext());
mSettingsActivity = settingsActivity;
}
/**
* Handle the action when user clicks positive button
*/
@Override
public void handlePositiveAction(int metricsKey) {
final Intent intent = HelpUtils.getHelpIntent(
mContext,
mContext.getString(R.string.help_url_battery_defender),
getClass().getName());
if (intent != null) {
mSettingsActivity.startActivityForResult(intent, 0);
}
// TODO(b/173985153): Add logging enums for Battery Defender.
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2020 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.detectors;
import com.android.settings.fuelgauge.BatteryInfo;
import com.android.settings.fuelgauge.BatteryUtils;
import com.android.settings.fuelgauge.batterytip.tips.BatteryDefenderTip;
import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
/**
* Detect whether the battery is overheated
*/
public class BatteryDefenderDetector implements BatteryTipDetector {
private BatteryInfo mBatteryInfo;
public BatteryDefenderDetector(BatteryInfo batteryInfo) {
mBatteryInfo = batteryInfo;
}
@Override
public BatteryTip detect() {
final int state =
BatteryUtils.isBatteryDefenderOn(mBatteryInfo)
? BatteryTip.StateType.NEW
: BatteryTip.StateType.INVISIBLE;
return new BatteryDefenderTip(state);
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright (C) 2020 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 android.content.Context;
import android.os.Parcel;
import com.android.settings.R;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
/**
* Tip to show current battery is overheated
*/
public class BatteryDefenderTip extends BatteryTip {
public BatteryDefenderTip(@StateType int state) {
super(TipType.BATTERY_DEFENDER, state, false /* showDialog */);
}
private BatteryDefenderTip(Parcel in) {
super(in);
}
@Override
public CharSequence getTitle(Context context) {
return context.getString(R.string.battery_tip_limited_temporarily_title);
}
@Override
public CharSequence getSummary(Context context) {
return context.getString(R.string.battery_tip_limited_temporarily_summary);
}
@Override
public int getIconId() {
return R.drawable.ic_battery_status_good_24dp;
}
@Override
public void updateState(BatteryTip tip) {
mState = tip.mState;
}
@Override
public void log(Context context, MetricsFeatureProvider metricsFeatureProvider) {
// TODO(b/173985153): Add logging enums for Battery Defender.
}
public static final Creator CREATOR = new Creator() {
public BatteryTip createFromParcel(Parcel in) {
return new BatteryDefenderTip(in);
}
public BatteryTip[] newArray(int size) {
return new BatteryDefenderTip[size];
}
};
}

View File

@@ -57,7 +57,8 @@ public abstract class BatteryTip implements Comparable<BatteryTip>, Parcelable {
TipType.APP_RESTRICTION,
TipType.REDUCED_BATTERY,
TipType.LOW_BATTERY,
TipType.REMOVE_APP_RESTRICTION})
TipType.REMOVE_APP_RESTRICTION,
TipType.BATTERY_DEFENDER})
public @interface TipType {
int SMART_BATTERY_MANAGER = 0;
int APP_RESTRICTION = 1;
@@ -67,20 +68,22 @@ public abstract class BatteryTip implements Comparable<BatteryTip>, Parcelable {
int LOW_BATTERY = 5;
int SUMMARY = 6;
int REMOVE_APP_RESTRICTION = 7;
int BATTERY_DEFENDER = 8;
}
@VisibleForTesting
static final SparseIntArray TIP_ORDER;
static {
TIP_ORDER = new SparseIntArray();
TIP_ORDER.append(TipType.APP_RESTRICTION, 0);
TIP_ORDER.append(TipType.BATTERY_SAVER, 1);
TIP_ORDER.append(TipType.HIGH_DEVICE_USAGE, 2);
TIP_ORDER.append(TipType.LOW_BATTERY, 3);
TIP_ORDER.append(TipType.SUMMARY, 4);
TIP_ORDER.append(TipType.SMART_BATTERY_MANAGER, 5);
TIP_ORDER.append(TipType.REDUCED_BATTERY, 6);
TIP_ORDER.append(TipType.REMOVE_APP_RESTRICTION, 7);
TIP_ORDER.append(TipType.BATTERY_DEFENDER, 0);
TIP_ORDER.append(TipType.APP_RESTRICTION, 1);
TIP_ORDER.append(TipType.BATTERY_SAVER, 2);
TIP_ORDER.append(TipType.HIGH_DEVICE_USAGE, 3);
TIP_ORDER.append(TipType.LOW_BATTERY, 4);
TIP_ORDER.append(TipType.SUMMARY, 5);
TIP_ORDER.append(TipType.SMART_BATTERY_MANAGER, 6);
TIP_ORDER.append(TipType.REDUCED_BATTERY, 7);
TIP_ORDER.append(TipType.REMOVE_APP_RESTRICTION, 8);
}
private static final String KEY_PREFIX = "key_battery_tip";