[电池]Android 9.0 电池未充电与充电字符串提示信息

2019-04-14 20:51发布

1. 电池电量提醒

1.1 未充电提醒

  1. 若没有预估时间,则提示显示电池百分比
  2. 若预估时间小于7分钟,则提示手机可能即将关机
  3. 若预估时间小于15分钟,则提示剩余电池续航时间不到15分钟
  4. 若15分钟<预估时间<1天,则提示估计大约还能用到xx h, xx min, xx sec
  5. 若预估时间大于1天,则提示大约还可使用 1 days, x hr, x min, x sec
  6. 若预估时间大于2天,则提示电量剩余使用时间超过 x 天

1.2 充电提醒

  1. 若没有预估充满电时间,则默认显示:xx%电量,正在充电
  2. 若预估充电时间大于0且未充满电,显示还需 xx,充满电

2. 源码

充电提醒

2.0 PowerUsageBase.refreshUi

package com.android.settings.fuelgauge; /** * Common base class for things that need to show the battery usage graph. */ public abstract class PowerUsageBase extends DashboardFragment { protected abstract void refreshUi(@BatteryUpdateType int refreshType); @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); mStatsHelper.create(icicle); setHasOptionsMenu(true); mBatteryBroadcastReceiver = new BatteryBroadcastReceiver(getContext()); mBatteryBroadcastReceiver.setBatteryChangedListener(type -> { restartBatteryStatsLoader(type); }); } /** * {@link android.app.LoaderManager.LoaderCallbacks} for {@link PowerUsageBase} to load * the {@link BatteryStatsHelper} */ public class PowerLoaderCallback implements LoaderManager.LoaderCallbacks { private int mRefreshType; @Override public Loader onCreateLoader(int id, Bundle args) { mRefreshType = args.getInt(KEY_REFRESH_TYPE); return new BatteryStatsHelperLoader(getContext()); } @Override public void onLoadFinished(Loader loader, BatteryStatsHelper statsHelper) { mStatsHelper = statsHelper; refreshUi(mRefreshType); } @Override public void onLoaderReset(Loader loader) { } }

2.1 PowerUsageSummary.refreshUi

package com.android.settings.fuelgauge; /** * Displays a list of apps and subsystems that consume power, ordered by how much power was * consumed since the last time it was unplugged. */ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickListener, BatteryTipPreferenceController.BatteryTipListener { protected void refreshUi(@BatteryUpdateType int refreshType) { final Context context = getContext(); if (context == null) { return; } ... restartBatteryTipLoader } // 手动点击事件 @Override public void onBatteryTipHandled(BatteryTip batteryTip) { restartBatteryTipLoader(); } @VisibleForTesting void restartBatteryTipLoader() { getLoaderManager().restartLoader(BATTERY_TIP_LOADER, Bundle.EMPTY, mBatteryTipsCallbacks); } private LoaderManager.LoaderCallbacks> mBatteryTipsCallbacks = new LoaderManager.LoaderCallbacks>() { @Override public Loader> onCreateLoader(int id, Bundle args) { return new BatteryTipLoader(getContext(), mStatsHelper); } @Override public void onLoadFinished(Loader> loader, List data) { mBatteryTipPreferenceController.updateBatteryTips(data); } @Override public void onLoaderReset(Loader> loader) { } };

2.2 BatteryUtils.getBatteryInfo

package com.android.settings.fuelgauge.batterytip; /** * Loader to compute and return a battery tip list. It will always return a full length list even * though some tips may have state {@code BaseBatteryTip.StateType.INVISIBLE}. */ public class BatteryTipLoader extends AsyncLoader> { private static final String TAG = "BatteryTipLoader"; @Override public List loadInBackground() { if (USE_FAKE_DATA) { return getFakeData(); } final BatteryInfo batteryInfo = mBatteryUtils.getBatteryInfo(mBatteryStatsHelper, TAG);

2.3 BatteryUtils.getBatteryInfo

  • BatteryUtils.getInstance.getBatteryInfo
  • mBatteryUtils.getBatteryInfo
package com.android.settings.fuelgauge; /** * Utils for battery operation */ public class BatteryUtils { @WorkerThread public BatteryInfo getBatteryInfo(final BatteryStatsHelper statsHelper, final String tag) { final long startTime = System.currentTimeMillis(); // Stuff we always need to get BatteryInfo // 获取电池广播 final Intent batteryBroadcast = mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); // 获取当前时间,并转换为us单位 final long elapsedRealtimeUs = PowerUtil.convertMsToUs( SystemClock.elapsedRealtime()); final BatteryStats stats = statsHelper.getStats(); BatteryInfo batteryInfo; final Estimate estimate; // Get enhanced prediction if available if (mPowerUsageFeatureProvider != null && mPowerUsageFeatureProvider.isEnhancedBatteryPredictionEnabled(mContext)) {// 这里默认为false,这里为基于用户使用 estimate = mPowerUsageFeatureProvider.getEnhancedBatteryPrediction(mContext); } else { // 预估时间对象 estimate = new Estimate( // 预估时间 PowerUtil.convertUsToMs(stats.computeBatteryTimeRemaining(elapsedRealtimeUs)), // 不基于用户使用 false /* isBasedOnUsage */, Estimate.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN); } BatteryUtils.logRuntime(tag, "BatteryInfoLoader post query", startTime); // 参数(context, 电池广播,电池使用状态,预估时间,当前时间,长字符串显示) batteryInfo = BatteryInfo.getBatteryInfo(mContext, batteryBroadcast, stats, estimate, elapsedRealtimeUs, false /* shortString */); BatteryUtils.logRuntime(tag, "BatteryInfoLoader.loadInBackground", startTime); return batteryInfo; }

2.3.1 PowerUsageFeatureProvider.isEnhancedBatteryPredictionEnabled

package com.android.settings.fuelgauge; public class PowerUsageFeatureProviderImpl implements PowerUsageFeatureProvider { @Override public boolean isEnhancedBatteryPredictionEnabled(Context context) { return false; }

2.3.2 Estimate 对象类

package com.android.settings.fuelgauge; public class Estimate { // Value to indicate averageTimeToDischarge could not be obtained public static final int AVERAGE_TIME_TO_DISCHARGE_UNKNOWN = -1; public final long estimateMillis; public final boolean isBasedOnUsage; public final long averageDischargeTime; public Estimate(long estimateMillis, boolean isBasedOnUsage, long averageDischargeTime) { this.estimateMillis = estimateMillis; this.isBasedOnUsage = isBasedOnUsage; this.averageDischargeTime = averageDischargeTime; } }

2.3.3 computeBatteryTimeRemaining

  • frameworks/base/core/java/android/os/BatteryStats.java
/** * Compute an approximation for how much run time (in microseconds) is remaining on * the battery. Returns -1 if no time can be computed: either there is not * enough current data to make a decision, or the battery is currently * charging. * * @param curTime The current elepsed realtime in microseconds. */ public abstract long computeBatteryTimeRemaining(long curTime);
  • frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
public long computeBatteryTimeRemaining() { synchronized (mStats) { long time = mStats.computeBatteryTimeRemaining(SystemClock.elapsedRealtime()); return time >= 0 ? (time/1000) : time; } }
  • frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
@Override public long computeBatteryTimeRemaining(long curTime) { if (!mOnBattery) { return -1; } /* Simple implementation just looks at the average discharge per level across the entire sample period. int discharge = (getLowDischargeAmountSinceCharge()+getHighDischargeAmountSinceCharge())/2; if (discharge < 2) { return -1; } long duration = computeBatteryRealtime(curTime, STATS_SINCE_CHARGED); if (duration < 1000*1000) { return -1; } long usPerLevel = duration/discharge; return usPerLevel * mCurrentBatteryLevel; */ if (mDischargeStepTracker.mNumStepDurations < 1) { return -1; } long msPerLevel = mDischargeStepTracker.computeTimePerLevel(); if (msPerLevel <= 0) { return -1; } return (msPerLevel * mCurrentBatteryLevel) * 1000; } @Override public long computeBatteryRealtime(long curTime, int which) { return mOnBatteryTimeBase.computeRealtime(curTime, which); }

2.4 BatteryInfo.getBatteryInfo

package com.android.settings.fuelgauge; public class BatteryInfo { // 参数(context, 电池广播,电池使用状态,预估时间,当前时间,长字符串显示) @WorkerThread public static BatteryInfo getBatteryInfo(Context context, Intent batteryBroadcast, BatteryStats stats, Estimate estimate, long elapsedRealtimeUs, boolean shortString) { final long startTime = System.currentTimeMillis(); BatteryInfo info = new BatteryInfo(); info.mStats = stats; info.batteryLevel = Utils.getBatteryLevel(batteryBroadcast); info.batteryPercentString = Utils.formatPercentage(info.batteryLevel); info.mCharging = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0; info.averageTimeToDischarge = estimate.averageDischargeTime; final Resources resources = context.getResources(); info.statusLabel = Utils.getBatteryStatus(resources, batteryBroadcast); if (!info.mCharging) { // 未充电 updateBatteryInfoDischarging(context, shortString, estimate, info); } else { // 充电 updateBatteryInfoCharging(context, batteryBroadcast, stats, elapsedRealtimeUs, info); } BatteryUtils.logRuntime(LOG_TAG, "time for getBatteryInfo", startTime); return info; }

2.5 未充电 BatteryInfo.updateBatteryInfoDischarging

package com.android.settings.fuelgauge; public class BatteryInfo { private static void updateBatteryInfoDischarging(Context context, boolean shortString, Estimate estimate, BatteryInfo info) { // 预估时间 final long drainTimeUs = PowerUtil.convertMsToUs(estimate.estimateMillis); // 预估时间大于0,其中-1为缺省值 if (drainTimeUs > 0) { info.remainingTimeUs = drainTimeUs; // 参数:context, 预估时间,电池百分比字符串(null),基于用户使用(false) info.remainingLabel = PowerUtil.getBatteryRemainingStringFormatted( context, PowerUtil.convertUsToMs(drainTimeUs), null /* percentageString */, estimate.isBasedOnUsage && !shortString ); // 参数:context, 预估时间,电池百分比字符串(null),基于用户使用(false) info.chargeLabel = PowerUtil.getBatteryRemainingStringFormatted( context, PowerUtil.convertUsToMs(drainTimeUs), info.batteryPercentString, estimate.isBasedOnUsage && !shortString ); } else { info.remainingLabel = null; info.chargeLabel = info.batteryPercentString; } }

2.5.1 PowerUtil.getBatteryRemainingStringFormatted

  • frameworks/base/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java
private static final long SEVEN_MINUTES_MILLIS = TimeUnit.MINUTES.toMillis(7); private static final long FIFTEEN_MINUTES_MILLIS = TimeUnit.MINUTES.toMillis(15); private static final long ONE_DAY_MILLIS = TimeUnit.DAYS.toMillis(1); private static final long TWO_DAYS_MILLIS = TimeUnit.DAYS.toMillis(2); private static final long ONE_HOUR_MILLIS = TimeUnit.HOURS.toMillis(1); package com.android.settingslib.utils; /** Utility class for keeping power related strings consistent**/ public class PowerUtil { /** * This method produces the text used in various places throughout the system to describe the * remaining battery life of the phone in a consistent manner. * * @param context * @param drainTimeMs The estimated time remaining before the phone dies in milliseconds. * @param percentageString An optional percentage of battery remaining string. * @param basedOnUsage Whether this estimate is based on usage or simple extrapolation. * @return a properly formatted and localized string describing how much time remains * before the battery runs out. */ public static String getBatteryRemainingStringFormatted(Context context, long drainTimeMs, @Nullable String percentageString, boolean basedOnUsage) { if (drainTimeMs > 0) { if (drainTimeMs <= SEVEN_MINUTES_MILLIS) { // show a imminent shutdown warning if less than 7 minutes remain return getShutdownImminentString(context, percentageString); } else if (drainTimeMs <= FIFTEEN_MINUTES_MILLIS) { // show a less than 15 min remaining warning if appropriate CharSequence timeString = StringUtil.formatElapsedTime(context, FIFTEEN_MINUTES_MILLIS, false /* withSeconds */); return getUnderFifteenString(context, timeString, percentageString); } else if (drainTimeMs >= TWO_DAYS_MILLIS) { // just say more than two day if over 48 hours return getMoreThanTwoDaysString(context, percentageString); } else if (drainTimeMs >= ONE_DAY_MILLIS) { // show remaining days & hours if more than a day return getMoreThanOneDayString(context, drainTimeMs, percentageString, basedOnUsage); } else { // show the time of day we think you'll run out return getRegularTimeRemainingString(context, drainTimeMs, percentageString, basedOnUsage); } } return null; }
2.5.1.1 getShutdownImminentString
为充电时,当预估时间小于7分钟,则提示手机可能即将关机 private static String getShutdownImminentString(Context context, String percentageString) { return TextUtils.isEmpty(percentageString) ? context.getString(R.string.power_remaining_duration_only_shutdown_imminent) : context.getString( R.string.power_remaining_duration_shutdown_imminent, percentageString); } "手机可能即将关机" "手机可能即将关机 (%1$s)"
2.5.1.2 getUnderFifteenString
为充电时,当预估时间小于15分钟,则提示剩余电池续航时间不到15分钟 "剩余电池续航时间不到 %1$s" private static String getUnderFifteenString(Context context, CharSequence timeString, String percentageString) { return TextUtils.isEmpty(percentageString) ? context.getString(R.string.power_remaining_less_than_duration_only, timeString) : context.getString( R.string.power_remaining_less_than_duration, timeString, percentageString); }
2.5.1.3 getMoreThanTwoDaysString
为充电时,当预估时间大于1天,则提示电量剩余使用时间超过 x 天 "电量剩余使用时间超过 %1$s" private static String getMoreThanTwoDaysString(Context context, String percentageString) { final Locale currentLocale = context.getResources().getConfiguration().getLocales().get(0); final MeasureFormat frmt = MeasureFormat.getInstance(currentLocale, FormatWidth.SHORT); final Measure daysMeasure = new Measure(2, MeasureUnit.DAY); return TextUtils.isEmpty(percentageString) ? context.getString(R.string.power_remaining_only_more_than_subtext, frmt.formatMeasures(daysMeasure)) : context.getString( R.string.power_remaining_more_than_subtext, frmt.formatMeasures(daysMeasure), percentageString); }
2.5.1.4 getMoreThanOneDayString
为充电时,当预估时间大于1天,则提示大约还可使用 1 days, 5 hr, 40 min, 29 sec "根据您的使用情况,大约还可使用 %1$s" "大约还可使用 %1$s" private static String getMoreThanOneDayString(Context context, long drainTimeMs, String percentageString, boolean basedOnUsage) { final long roundedTimeMs = roundTimeToNearestThreshold(drainTimeMs, ONE_HOUR_MILLIS); CharSequence timeString = StringUtil.formatElapsedTime(context, roundedTimeMs, false /* withSeconds */); if (TextUtils.isEmpty(percentageString)) { int id = basedOnUsage ? R.string.power_remaining_duration_only_enhanced : R.string.power_remaining_duration_only; return context.getString(id, timeString); } else { int id = basedOnUsage ? R.string.power_discharging_duration_enhanced : R.string.power_discharging_duration; return context.getString(id, timeString, percentageString); } } 时间转化 package com.android.settingslib.utils; /** Utility class for generally useful string methods **/ public class StringUtil { /** * Returns elapsed time for the given millis, in the following format: * 2 days, 5 hr, 40 min, 29 sec * * @param context the application context * @param millis the elapsed time in milli seconds * @param withSeconds include seconds? * @return the formatted elapsed time */ public static CharSequence formatElapsedTime(Context context, double millis, boolean withSeconds) { SpannableStringBuilder sb = new SpannableStringBuilder(); int seconds = (int) Math.floor(millis / 1000); if (!withSeconds) { // Round up. seconds += 30; } int days = 0, hours = 0, minutes = 0; if (seconds >= SECONDS_PER_DAY) { days = seconds / SECONDS_PER_DAY; seconds -= days * SECONDS_PER_DAY; } if (seconds >= SECONDS_PER_HOUR) { hours = seconds / SECONDS_PER_HOUR; seconds -= hours * SECONDS_PER_HOUR; } if (seconds >= SECONDS_PER_MINUTE) { minutes = seconds / SECONDS_PER_MINUTE; seconds -= minutes * SECONDS_PER_MINUTE; } final ArrayList measureList = new ArrayList(4); if (days > 0) { measureList.add(new Measure(days, MeasureUnit.DAY)); } if (hours > 0) { measureList.add(new Measure(hours, MeasureUnit.HOUR)); } if (minutes > 0) { measureList.add(new Measure(minutes, MeasureUnit.MINUTE)); } if (withSeconds && seconds > 0) { measureList.add(new Measure(seconds, MeasureUnit.SECOND)); } if (measureList.size() == 0) { // Everything addable was zero, so nothing was added. We add a zero. measureList.add(new Measure(0, withSeconds ? MeasureUnit.SECOND : MeasureUnit.MINUTE)); } final Measure[] measureArray = measureList.toArray(new Measure[measureList.size()]); final Locale locale = context.getResources().getConfiguration().locale; final MeasureFormat measureFormat = MeasureFormat.getInstance( locale, FormatWidth.SHORT); sb.append(measureFormat.formatMeasures(measureArray)); if (measureArray.length == 1 && MeasureUnit.MINUTE.equals(measureArray[0].getUnit())) { // Add ttsSpan if it only have minute value, because it will be read as "meters" final TtsSpan ttsSpan = new TtsSpan.MeasureBuilder().setNumber(minutes) .setUnit("minute").build(); sb.setSpan(ttsSpan, 0, sb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } return sb; }
2.5.1.5 getRegularTimeRemainingString
为充电时,当预估时间大于15分钟小于1天,则提示估计大约还能用到xx h, xx min, xx sec "估计大约还能用到%1$s" private static String getRegularTimeRemainingString(Context context, long drainTimeMs, String percentageString, boolean basedOnUsage) { // Get the time of day we think device will die rounded to the nearest 15 min. final long roundedTimeOfDayMs = roundTimeToNearestThreshold( System.currentTimeMillis() + drainTimeMs, FIFTEEN_MINUTES_MILLIS); // convert the time to a properly formatted string. String skeleton = android.text.format.DateFormat.getTimeFormatString(context); DateFormat fmt = DateFormat.getInstanceForSkeleton(skeleton); Date date = Date.from(Instant.ofEpochMilli(roundedTimeOfDayMs)); CharSequence timeString = fmt.format(date); if (TextUtils.isEmpty(percentageString)) { int id = basedOnUsage ? R.string.power_discharge_by_only_enhanced : R.string.power_discharge_by_only; return context.getString(id, timeString); } else { int id = basedOnUsage ? R.string.power_discharge_by_enhanced : R.string.power_discharge_by; return context.getString(id, timeString, percentageString); } }

2.6 充电 BatteryInfo.updateBatteryInfoCharging

"还需 %1$s充满电" "正在充电" package com.android.settings.fuelgauge; public class BatteryInfo { private static void updateBatteryInfoCharging(Context context, Intent batteryBroadcast, BatteryStats stats, long elapsedRealtimeUs, BatteryInfo info) { final Resources resources = context.getResources(); // 预估充电时间 final long chargeTime = stats.computeChargeTimeRemaining(elapsedRealtimeUs); // 电池状态 final int status = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN); info.discharging = false; // 预估充电时间大于0且未充满电,则显示还需 xx,充满电 if (chargeTime > 0 && status != BatteryManager.BATTERY_STATUS_FULL) { info.remainingTimeUs = chargeTime; CharSequence timeString = StringUtil.formatElapsedTime(context, PowerUtil.convertUsToMs(info.remainingTimeUs), false /* withSeconds */); int resId = R.string.power_charging_duration; info.remainingLabel = context.getString( R.string.power_remaining_charging_duration_only, timeString); info.chargeLabel = context.getString(resId, info.batteryPercentString, timeString); // 默认显示:xx%电量,正在充电 } else { final String chargeStatusLabel = resources.getString( R.string.battery_info_status_charging_lower); info.remainingLabel = null; info.chargeLabel = info.batteryLevel == 100 ? info.batteryPercentString : resources.getString(R.string.power_charging, info.batteryPercentString, chargeStatusLabel); } }