Add estimated time remaining text to QS

- Add an API to BatteryController to get an estimated time remaining
string.
- BatteryController will now check up to once per minute what the
estimated time will be and builds the string using PowerUtil.
- If the "show percentage" setting is on, the estimated time remaining
string (and battery icon) will show next to the system icons in QS
- Also make the battery percent in QS obey the setting

Test: visual
Bug: 116481529
Change-Id: Iaafa00127c8b8baae40956254a1237c8b7ac079b
This commit is contained in:
Evan Laird
2018-10-22 14:24:32 -04:00
parent b513876655
commit 4bf21dffaa
8 changed files with 212 additions and 5 deletions

View File

@@ -930,6 +930,8 @@
<string name="power_discharge_by">Should last until about <xliff:g id="time">%1$s</xliff:g> (<xliff:g id="level">%2$s</xliff:g>)</string>
<!-- [CHAR_LIMIT=100] Label for estimated time that phone will run out of battery -->
<string name="power_discharge_by_only">Should last until about <xliff:g id="time">%1$s</xliff:g></string>
<!-- [CHAR_LIMIT=100] Label for estimated time that phone will run out of battery -->
<string name="power_discharge_by_only_short">Until <xliff:g id="time" example="12 PM">%1$s</xliff:g></string>
<!-- [CHAR_LIMIT=60] label for estimated remaining duration of battery when under a certain amount -->
<string name="power_remaining_less_than_duration_only">Less than <xliff:g id="threshold">%1$s</xliff:g> remaining</string>

View File

@@ -81,6 +81,30 @@ public class PowerUtil {
return null;
}
/**
* Method to produce a shortened string describing the remaining battery. Suitable for Quick
* Settings and other areas where space is constrained.
*
* @param context context to fetch descriptions from
* @param drainTimeMs The estimated time remaining before the phone dies in milliseconds.
*
* @return a properly formatted and localized short string describing how much time remains
* before the battery runs out.
*/
@Nullable
public static String getBatteryRemainingShortStringFormatted(
Context context, long drainTimeMs) {
if (drainTimeMs <= 0) {
return null;
}
if (drainTimeMs <= ONE_DAY_MILLIS) {
return getRegularTimeRemainingShortString(context, drainTimeMs);
} else {
return getMoreThanOneDayShortString(context, drainTimeMs);
}
}
private static String getShutdownImminentString(Context context, String percentageString) {
return TextUtils.isEmpty(percentageString)
? context.getString(R.string.power_remaining_duration_only_shutdown_imminent)
@@ -120,6 +144,14 @@ public class PowerUtil {
}
}
private static String getMoreThanOneDayShortString(Context context, long drainTimeMs) {
final long roundedTimeMs = roundTimeToNearestThreshold(drainTimeMs, ONE_HOUR_MILLIS);
CharSequence timeString = StringUtil.formatElapsedTime(context, roundedTimeMs,
false /* withSeconds */);
return context.getString(R.string.power_remaining_duration_only_short, timeString);
}
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);
@@ -162,6 +194,22 @@ public class PowerUtil {
}
}
private static String getRegularTimeRemainingShortString(Context context, long drainTimeMs) {
// 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);
return context.getString(R.string.power_discharge_by_only_short, timeString);
}
public static long convertUsToMs(long timeUs) {
return timeUs / 1000;
}

View File

@@ -42,6 +42,20 @@
android:id="@+id/statusIcons"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
android:layout_weight="1"
android:paddingEnd="@dimen/signal_cluster_battery_padding" />
<com.android.systemui.BatteryMeterView
android:id="@+id/batteryRemainingIcon"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:paddingEnd="2dp" />
<TextView
android:id="@+id/batteryRemainingText"
android:textAppearance="@style/TextAppearance.QS.TileLabel"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:gravity="center_vertical" />
</LinearLayout>

View File

@@ -19,7 +19,10 @@ import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
import static android.app.StatusBarManager.DISABLE_NONE;
import static android.provider.Settings.System.SHOW_BATTERY_PERCENT;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.animation.ArgbEvaluator;
import android.annotation.IntDef;
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Resources;
@@ -55,15 +58,23 @@ import com.android.systemui.statusbar.policy.IconLogger;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
import com.android.systemui.util.Utils.DisableStateTracker;
import com.android.systemui.R;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.text.NumberFormat;
public class BatteryMeterView extends LinearLayout implements
BatteryStateChangeCallback, Tunable, DarkReceiver, ConfigurationListener {
@Retention(SOURCE)
@IntDef({MODE_DEFAULT, MODE_ON, MODE_OFF})
public @interface BatteryPercentMode {}
public static final int MODE_DEFAULT = 0;
public static final int MODE_ON = 1;
public static final int MODE_OFF = 2;
private final BatteryMeterDrawableBase mDrawable;
private final String mSlotBattery;
private final ImageView mBatteryIconView;
@@ -74,6 +85,7 @@ public class BatteryMeterView extends LinearLayout implements
private SettingObserver mSettingObserver;
private int mTextColor;
private int mLevel;
private int mShowPercentMode = MODE_DEFAULT;
private boolean mForceShowPercent;
private boolean mShowPercentAvailable;
@@ -154,7 +166,19 @@ public class BatteryMeterView extends LinearLayout implements
}
public void setForceShowPercent(boolean show) {
mForceShowPercent = show;
setPercentShowMode(show ? MODE_ON : MODE_DEFAULT);
}
/**
* Force a particular mode of showing percent
*
* 0 - No preference
* 1 - Force on
* 2 - Force off
* @param mode desired mode (none, on, off)
*/
public void setPercentShowMode(@BatteryPercentMode int mode) {
mShowPercentMode = mode;
updateShowPercent();
}
@@ -273,7 +297,8 @@ public class BatteryMeterView extends LinearLayout implements
.getIntForUser(getContext().getContentResolver(),
SHOW_BATTERY_PERCENT, 0, mUser);
if ((mShowPercentAvailable && systemSetting) || mForceShowPercent) {
if ((mShowPercentAvailable && systemSetting && mShowPercentMode != MODE_OFF)
|| mShowPercentMode == MODE_ON) {
if (!showing) {
mBatteryPercentView = loadPercentView();
if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor);

View File

@@ -15,6 +15,7 @@
package com.android.systemui.qs;
import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
import static android.provider.Settings.System.SHOW_BATTERY_PERCENT;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -28,12 +29,15 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Color;
import android.graphics.Rect;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.provider.AlarmClock;
import android.provider.Settings;
import android.service.notification.ZenModeConfig;
import android.text.format.DateUtils;
import android.util.AttributeSet;
@@ -68,6 +72,7 @@ import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
import com.android.systemui.statusbar.phone.StatusIconContainer;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.Clock;
import com.android.systemui.statusbar.policy.DarkIconDispatcher;
import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
@@ -132,6 +137,9 @@ public class QuickStatusBarHeader extends RelativeLayout implements
private DateView mDateView;
private OngoingPrivacyChip mPrivacyChip;
private Space mSpace;
private BatteryMeterView mBatteryRemainingIcon;
private TextView mBatteryRemainingText;
private boolean mShowBatteryPercentAndEstimate;
private NextAlarmController mAlarmController;
private ZenModeController mZenController;
@@ -148,6 +156,9 @@ public class QuickStatusBarHeader extends RelativeLayout implements
};
private boolean mHasTopCutout = false;
private final PercentSettingObserver mPercentSettingObserver =
new PercentSettingObserver(new Handler(mContext.getMainLooper()));
/**
* Runnable for automatically fading out the long press tooltip (as if it were animating away).
*/
@@ -204,8 +215,12 @@ public class QuickStatusBarHeader extends RelativeLayout implements
// Set the correct tint for the status icons so they contrast
mIconManager.setTint(fillColor);
mShowBatteryPercentAndEstimate = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_battery_percentage_setting_available);
mBatteryMeterView = findViewById(R.id.battery);
mBatteryMeterView.setForceShowPercent(true);
mBatteryMeterView.setPercentShowMode(mShowBatteryPercentAndEstimate
? BatteryMeterView.MODE_ON : BatteryMeterView.MODE_OFF);
mBatteryMeterView.setOnClickListener(this);
mClockView = findViewById(R.id.clock);
mClockView.setOnClickListener(this);
@@ -213,6 +228,15 @@ public class QuickStatusBarHeader extends RelativeLayout implements
mPrivacyChip = findViewById(R.id.privacy_chip);
mPrivacyChip.setOnClickListener(this);
mSpace = findViewById(R.id.space);
// Tint for the battery icons are handled in setupHost()
mBatteryRemainingIcon = findViewById(R.id.batteryRemainingIcon);
mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_OFF);
mBatteryRemainingText = findViewById(R.id.batteryRemainingText);
mBatteryRemainingText.setTextColor(fillColor);
updateShowPercent();
}
private void updateStatusText() {
@@ -371,6 +395,14 @@ public class QuickStatusBarHeader extends RelativeLayout implements
.build();
}
private void updateBatteryRemainingText() {
if (!mShowBatteryPercentAndEstimate) {
return;
}
mBatteryRemainingText.setText(
Dependency.get(BatteryController.class).getEstimatedTimeRemainingString());
}
public void setExpanded(boolean expanded) {
if (mExpanded == expanded) return;
mExpanded = expanded;
@@ -436,6 +468,9 @@ public class QuickStatusBarHeader extends RelativeLayout implements
super.onAttachedToWindow();
Dependency.get(StatusBarIconController.class).addIconGroup(mIconManager);
requestApplyInsets();
mContext.getContentResolver().registerContentObserver(
Settings.System.getUriFor(SHOW_BATTERY_PERCENT), false, mPercentSettingObserver,
ActivityManager.getCurrentUser());
}
@Override
@@ -475,6 +510,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements
public void onDetachedFromWindow() {
setListening(false);
Dependency.get(StatusBarIconController.class).removeIconGroup(mIconManager);
mContext.getContentResolver().unregisterContentObserver(mPercentSettingObserver);
super.onDetachedFromWindow();
}
@@ -491,6 +527,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements
mAlarmController.addCallback(this);
mContext.registerReceiver(mRingerReceiver,
new IntentFilter(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION));
updateBatteryRemainingText();
} else {
mZenController.removeCallback(this);
mAlarmController.removeCallback(this);
@@ -660,6 +697,14 @@ public class QuickStatusBarHeader extends RelativeLayout implements
// Use SystemUI context to get battery meter colors, and let it use the default tint (white)
mBatteryMeterView.setColorsFromContext(mHost.getContext());
mBatteryMeterView.onDarkChanged(new Rect(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT);
Rect tintArea = new Rect(0, 0, 0, 0);
int colorForeground = Utils.getColorAttrDefaultColor(getContext(),
android.R.attr.colorForeground);
float intensity = getColorIntensity(colorForeground);
int fillColor = fillColorForIntensity(intensity, getContext());
mBatteryRemainingIcon.setColorsFromContext(mHost.getContext());
mBatteryRemainingIcon.onDarkChanged(tintArea, intensity, fillColor);
}
public void setCallback(Callback qsPanelCallback) {
@@ -692,4 +737,39 @@ public class QuickStatusBarHeader extends RelativeLayout implements
lp.rightMargin = sideMargins;
}
}
private void updateShowPercent() {
final boolean systemSetting = 0 != Settings.System
.getIntForUser(getContext().getContentResolver(),
SHOW_BATTERY_PERCENT, 0, ActivityManager.getCurrentUser());
mShowBatteryPercentAndEstimate = systemSetting;
updateBatteryViews();
}
private void updateBatteryViews() {
if (mShowBatteryPercentAndEstimate) {
mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON);
mBatteryRemainingIcon.setVisibility(View.VISIBLE);
mBatteryRemainingText.setVisibility(View.VISIBLE);
updateBatteryRemainingText();
} else {
mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_OFF);
mBatteryRemainingIcon.setVisibility(View.GONE);
mBatteryRemainingText.setVisibility(View.GONE);
}
}
private final class PercentSettingObserver extends ContentObserver {
PercentSettingObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
updateShowPercent();
}
}
}

View File

@@ -20,6 +20,7 @@ import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.service.quicksettings.Tile;
import android.widget.Switch;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.graph.BatteryMeterDrawableBase;
import com.android.systemui.Dependency;

View File

@@ -55,4 +55,11 @@ public interface BatteryController extends DemoMode, Dumpable,
default void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {}
default void onPowerSaveChanged(boolean isPowerSave) {}
}
/**
* If available, get the estimated battery time remaining as a string
*/
default String getEstimatedTimeRemainingString() {
return null;
}
}

View File

@@ -29,9 +29,14 @@ import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.fuelgauge.BatterySaverUtils;
import com.android.settingslib.utils.PowerUtil;
import com.android.systemui.Dependency;
import com.android.systemui.power.EnhancedEstimates;
import com.android.systemui.power.Estimate;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.text.NumberFormat;
import java.util.ArrayList;
/**
@@ -44,7 +49,9 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
public static final String ACTION_LEVEL_TEST = "com.android.systemui.BATTERY_LEVEL_TEST";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final int UPDATE_GRANULARITY_MSEC = 1000 * 60;
private final EnhancedEstimates mEstimates = Dependency.get(EnhancedEstimates.class);
private final ArrayList<BatteryController.BatteryStateChangeCallback> mChangeCallbacks = new ArrayList<>();
private final PowerManager mPowerManager;
private final Handler mHandler;
@@ -58,6 +65,8 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
protected boolean mAodPowerSave;
private boolean mTestmode = false;
private boolean mHasReceivedBattery = false;
private Estimate mEstimate;
private long mLastEstimateTimestamp = -1;
public BatteryControllerImpl(Context context) {
this(context, context.getSystemService(PowerManager.class));
@@ -71,6 +80,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
registerReceiver();
updatePowerSave();
updateEstimate();
}
private void registerReceiver() {
@@ -180,6 +190,26 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
return mAodPowerSave;
}
@Override
public String getEstimatedTimeRemainingString() {
if (mEstimate == null
|| System.currentTimeMillis() > mLastEstimateTimestamp + UPDATE_GRANULARITY_MSEC) {
updateEstimate();
}
// Estimates may not exist yet even if we've checked
if (mEstimate == null) {
return null;
}
final String percentage = NumberFormat.getPercentInstance().format((double) mLevel / 100.0);
return PowerUtil.getBatteryRemainingShortStringFormatted(
mContext, mEstimate.estimateMillis);
}
private void updateEstimate() {
mEstimate = mEstimates.getEstimate();
mLastEstimateTimestamp = System.currentTimeMillis();
}
private void updatePowerSave() {
setPowerSave(mPowerManager.isPowerSaveMode());
}