Merge changes from topic "caitlinshk-dock-defend-initial" into tm-qpr-dev am: 879714254a
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20454811 Change-Id: I74e233d2b3f566108a29752e78e9a37327314166 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -412,14 +412,13 @@ open class ThemedBatteryDrawable(private val context: Context, frameColor: Int)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "ThemedBatteryDrawable"
|
||||
private const val WIDTH = 12f
|
||||
private const val HEIGHT = 20f
|
||||
const val WIDTH = 12f
|
||||
const val HEIGHT = 20f
|
||||
private const val CRITICAL_LEVEL = 15
|
||||
// On a 12x20 grid, how wide to make the fill protection stroke.
|
||||
// Scales when our size changes
|
||||
private const val PROTECTION_STROKE_WIDTH = 3f
|
||||
// Arbitrarily chosen for visibility at small sizes
|
||||
private const val PROTECTION_MIN_STROKE_WIDTH = 6f
|
||||
const val PROTECTION_MIN_STROKE_WIDTH = 6f
|
||||
}
|
||||
}
|
||||
|
||||
@@ -485,6 +485,12 @@
|
||||
<!-- Whether to show a severe low battery dialog. -->
|
||||
<bool name="config_severe_battery_dialog">false</bool>
|
||||
|
||||
<!-- A path representing a shield. Will sometimes be displayed with the battery icon when
|
||||
needed. This path is a 10px wide and 13px tall. -->
|
||||
<string name="config_batterymeterShieldPath" translatable="false">
|
||||
M5 0L0 1.88V6.19C0 9.35 2.13 12.29 5 13.01C7.87 12.29 10 9.35 10 6.19V1.88L5 0Z
|
||||
</string>
|
||||
|
||||
<!-- A path similar to frameworks/base/core/res/res/values/config.xml
|
||||
config_mainBuiltInDisplayCutout that describes a path larger than the exact path of a display
|
||||
cutout. If present as well as config_enableDisplayCutoutProtection is set to true, then
|
||||
|
||||
@@ -105,6 +105,12 @@
|
||||
so the width of the icon should be 13.0dp * (12.0 / 20.0) -->
|
||||
<dimen name="status_bar_battery_icon_width">7.8dp</dimen>
|
||||
|
||||
<!-- The battery icon is 13dp tall, but the other system icons are 15dp tall (see
|
||||
@*android:dimen/status_bar_system_icon_size) with some top and bottom padding embedded in
|
||||
the drawables themselves. So, the battery icon may need an extra 1dp of spacing so that its
|
||||
bottom still aligns with the bottom of all the other system icons. See b/258672854. -->
|
||||
<dimen name="status_bar_battery_extra_vertical_spacing">1dp</dimen>
|
||||
|
||||
<!-- The font size for the clock in the status bar. -->
|
||||
<dimen name="status_bar_clock_size">14sp</dimen>
|
||||
|
||||
|
||||
@@ -439,11 +439,17 @@
|
||||
<string name="accessibility_battery_level">Battery <xliff:g id="number">%d</xliff:g> percent.</string>
|
||||
|
||||
<!-- Content description of the battery level icon for accessibility, including the estimated time remaining before the phone runs out of battery (not shown on the screen). [CHAR LIMIT=NONE] -->
|
||||
<string name="accessibility_battery_level_with_estimate">Battery <xliff:g id="percentage" example="95%">%1$s</xliff:g> percent, about <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g> left based on your usage</string>
|
||||
<string name="accessibility_battery_level_with_estimate">Battery <xliff:g id="percentage" example="95%">%1$d</xliff:g> percent, about <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g> left based on your usage</string>
|
||||
|
||||
<!-- Content description of the battery level icon for accessibility while the device is charging (not shown on the screen). [CHAR LIMIT=NONE] -->
|
||||
<string name="accessibility_battery_level_charging">Battery charging, <xliff:g id="battery_percentage">%d</xliff:g> percent.</string>
|
||||
|
||||
<!-- Content description of the battery level icon for accessibility, with information that the device charging is paused in order to protect the lifetime of the battery (not shown on screen). [CHAR LIMIT=NONE] -->
|
||||
<string name="accessibility_battery_level_charging_paused">Battery <xliff:g id="percentage" example="90%">%d</xliff:g> percent. Charging paused for battery protection.</string>
|
||||
|
||||
<!-- Content description of the battery level icon for accessibility, including the estimated time remaining before the phone runs out of battery *and* information that the device charging is paused in order to protect the lifetime of the battery (not shown on screen). [CHAR LIMIT=NONE] -->
|
||||
<string name="accessibility_battery_level_charging_paused_with_estimate">Battery <xliff:g id="percentage" example="90%">%1$d</xliff:g> percent, about <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g> left based on your usage. Charging paused for battery protection.</string>
|
||||
|
||||
<!-- Content description of overflow icon container of the notifications for accessibility (not shown on the screen)[CHAR LIMIT=NONE] -->
|
||||
<string name="accessibility_overflow_action">See all notifications</string>
|
||||
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.systemui.battery
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.ColorFilter
|
||||
import android.graphics.Matrix
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Path
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.PorterDuffXfermode
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.DrawableWrapper
|
||||
import android.util.PathParser
|
||||
import com.android.settingslib.graph.ThemedBatteryDrawable
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT
|
||||
import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT_WITH_SHIELD
|
||||
import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH
|
||||
import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH_WITH_SHIELD
|
||||
import com.android.systemui.battery.BatterySpecs.SHIELD_LEFT_OFFSET
|
||||
import com.android.systemui.battery.BatterySpecs.SHIELD_STROKE
|
||||
import com.android.systemui.battery.BatterySpecs.SHIELD_TOP_OFFSET
|
||||
|
||||
/**
|
||||
* A battery drawable that accessorizes [ThemedBatteryDrawable] with additional information if
|
||||
* necessary.
|
||||
*
|
||||
* For now, it adds a shield in the bottom-right corner when [displayShield] is true.
|
||||
*/
|
||||
class AccessorizedBatteryDrawable(
|
||||
private val context: Context,
|
||||
frameColor: Int,
|
||||
) : DrawableWrapper(ThemedBatteryDrawable(context, frameColor)) {
|
||||
private val mainBatteryDrawable: ThemedBatteryDrawable
|
||||
get() = drawable as ThemedBatteryDrawable
|
||||
|
||||
private val shieldPath = Path()
|
||||
private val scaledShield = Path()
|
||||
private val scaleMatrix = Matrix()
|
||||
|
||||
private var shieldLeftOffsetScaled = SHIELD_LEFT_OFFSET
|
||||
private var shieldTopOffsetScaled = SHIELD_TOP_OFFSET
|
||||
|
||||
private var density = context.resources.displayMetrics.density
|
||||
|
||||
private val dualTone =
|
||||
context.resources.getBoolean(com.android.internal.R.bool.config_batterymeterDualTone)
|
||||
|
||||
private val shieldTransparentOutlinePaint =
|
||||
Paint(Paint.ANTI_ALIAS_FLAG).also { p ->
|
||||
p.color = Color.TRANSPARENT
|
||||
p.strokeWidth = ThemedBatteryDrawable.PROTECTION_MIN_STROKE_WIDTH
|
||||
p.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
|
||||
p.style = Paint.Style.FILL_AND_STROKE
|
||||
}
|
||||
|
||||
private val shieldPaint =
|
||||
Paint(Paint.ANTI_ALIAS_FLAG).also { p ->
|
||||
p.color = Color.MAGENTA
|
||||
p.style = Paint.Style.FILL
|
||||
p.isDither = true
|
||||
}
|
||||
|
||||
init {
|
||||
loadPaths()
|
||||
}
|
||||
|
||||
override fun onBoundsChange(bounds: Rect) {
|
||||
super.onBoundsChange(bounds)
|
||||
updateSizes()
|
||||
}
|
||||
|
||||
var displayShield: Boolean = false
|
||||
|
||||
private fun updateSizes() {
|
||||
val b = bounds
|
||||
if (b.isEmpty) {
|
||||
return
|
||||
}
|
||||
|
||||
val mainWidth = BatterySpecs.getMainBatteryWidth(b.width().toFloat(), displayShield)
|
||||
val mainHeight = BatterySpecs.getMainBatteryHeight(b.height().toFloat(), displayShield)
|
||||
|
||||
drawable?.setBounds(
|
||||
b.left,
|
||||
b.top,
|
||||
/* right= */ b.left + mainWidth.toInt(),
|
||||
/* bottom= */ b.top + mainHeight.toInt()
|
||||
)
|
||||
|
||||
if (displayShield) {
|
||||
val sx = b.right / BATTERY_WIDTH_WITH_SHIELD
|
||||
val sy = b.bottom / BATTERY_HEIGHT_WITH_SHIELD
|
||||
scaleMatrix.setScale(sx, sy)
|
||||
shieldPath.transform(scaleMatrix, scaledShield)
|
||||
|
||||
shieldLeftOffsetScaled = sx * SHIELD_LEFT_OFFSET
|
||||
shieldTopOffsetScaled = sy * SHIELD_TOP_OFFSET
|
||||
|
||||
val scaledStrokeWidth =
|
||||
(sx * SHIELD_STROKE).coerceAtLeast(
|
||||
ThemedBatteryDrawable.PROTECTION_MIN_STROKE_WIDTH
|
||||
)
|
||||
shieldTransparentOutlinePaint.strokeWidth = scaledStrokeWidth
|
||||
}
|
||||
}
|
||||
|
||||
override fun getIntrinsicHeight(): Int {
|
||||
val height =
|
||||
if (displayShield) {
|
||||
BATTERY_HEIGHT_WITH_SHIELD
|
||||
} else {
|
||||
BATTERY_HEIGHT
|
||||
}
|
||||
return (height * density).toInt()
|
||||
}
|
||||
|
||||
override fun getIntrinsicWidth(): Int {
|
||||
val width =
|
||||
if (displayShield) {
|
||||
BATTERY_WIDTH_WITH_SHIELD
|
||||
} else {
|
||||
BATTERY_WIDTH
|
||||
}
|
||||
return (width * density).toInt()
|
||||
}
|
||||
|
||||
override fun draw(c: Canvas) {
|
||||
c.saveLayer(null, null)
|
||||
// Draw the main battery icon
|
||||
super.draw(c)
|
||||
|
||||
if (displayShield) {
|
||||
c.translate(shieldLeftOffsetScaled, shieldTopOffsetScaled)
|
||||
// We need a transparent outline around the shield, so first draw the transparent-ness
|
||||
// then draw the shield
|
||||
c.drawPath(scaledShield, shieldTransparentOutlinePaint)
|
||||
c.drawPath(scaledShield, shieldPaint)
|
||||
}
|
||||
c.restore()
|
||||
}
|
||||
|
||||
override fun getOpacity(): Int {
|
||||
return PixelFormat.OPAQUE
|
||||
}
|
||||
|
||||
override fun setAlpha(p0: Int) {
|
||||
// Unused internally -- see [ThemedBatteryDrawable.setAlpha].
|
||||
}
|
||||
|
||||
override fun setColorFilter(colorfilter: ColorFilter?) {
|
||||
super.setColorFilter(colorFilter)
|
||||
shieldPaint.colorFilter = colorFilter
|
||||
}
|
||||
|
||||
/** Sets whether the battery is currently charging. */
|
||||
fun setCharging(charging: Boolean) {
|
||||
mainBatteryDrawable.charging = charging
|
||||
}
|
||||
|
||||
/** Sets the current level (out of 100) of the battery. */
|
||||
fun setBatteryLevel(level: Int) {
|
||||
mainBatteryDrawable.setBatteryLevel(level)
|
||||
}
|
||||
|
||||
/** Sets whether power save is enabled. */
|
||||
fun setPowerSaveEnabled(powerSaveEnabled: Boolean) {
|
||||
mainBatteryDrawable.powerSaveEnabled = powerSaveEnabled
|
||||
}
|
||||
|
||||
/** Returns whether power save is currently enabled. */
|
||||
fun getPowerSaveEnabled(): Boolean {
|
||||
return mainBatteryDrawable.powerSaveEnabled
|
||||
}
|
||||
|
||||
/** Sets the colors to use for the icon. */
|
||||
fun setColors(fgColor: Int, bgColor: Int, singleToneColor: Int) {
|
||||
shieldPaint.color = if (dualTone) fgColor else singleToneColor
|
||||
mainBatteryDrawable.setColors(fgColor, bgColor, singleToneColor)
|
||||
}
|
||||
|
||||
/** Notifies this drawable that the density might have changed. */
|
||||
fun notifyDensityChanged() {
|
||||
density = context.resources.displayMetrics.density
|
||||
}
|
||||
|
||||
private fun loadPaths() {
|
||||
val shieldPathString = context.resources.getString(R.string.config_batterymeterShieldPath)
|
||||
shieldPath.set(PathParser.createPathFromPathData(shieldPathString))
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,6 @@ import android.widget.TextView;
|
||||
import androidx.annotation.StyleRes;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.settingslib.graph.ThemedBatteryDrawable;
|
||||
import com.android.systemui.DualToneHandler;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.animation.Interpolators;
|
||||
@@ -68,7 +67,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
|
||||
public static final int MODE_OFF = 2;
|
||||
public static final int MODE_ESTIMATE = 3;
|
||||
|
||||
private final ThemedBatteryDrawable mDrawable;
|
||||
private final AccessorizedBatteryDrawable mDrawable;
|
||||
private final ImageView mBatteryIconView;
|
||||
private TextView mBatteryPercentView;
|
||||
|
||||
@@ -77,7 +76,10 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
|
||||
private int mLevel;
|
||||
private int mShowPercentMode = MODE_DEFAULT;
|
||||
private boolean mShowPercentAvailable;
|
||||
private String mEstimateText = null;
|
||||
private boolean mCharging;
|
||||
private boolean mIsOverheated;
|
||||
private boolean mDisplayShieldEnabled;
|
||||
// Error state where we know nothing about the current battery state
|
||||
private boolean mBatteryStateUnknown;
|
||||
// Lazily-loaded since this is expected to be a rare-if-ever state
|
||||
@@ -106,7 +108,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
|
||||
final int frameColor = atts.getColor(R.styleable.BatteryMeterView_frameColor,
|
||||
context.getColor(R.color.meter_background_color));
|
||||
mPercentageStyleId = atts.getResourceId(R.styleable.BatteryMeterView_textAppearance, 0);
|
||||
mDrawable = new ThemedBatteryDrawable(context, frameColor);
|
||||
mDrawable = new AccessorizedBatteryDrawable(context, frameColor);
|
||||
atts.recycle();
|
||||
|
||||
mShowPercentAvailable = context.getResources().getBoolean(
|
||||
@@ -170,12 +172,14 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
|
||||
if (mode == mShowPercentMode) return;
|
||||
mShowPercentMode = mode;
|
||||
updateShowPercent();
|
||||
updatePercentText();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
updatePercentView();
|
||||
mDrawable.notifyDensityChanged();
|
||||
}
|
||||
|
||||
public void setColorsFromContext(Context context) {
|
||||
@@ -203,6 +207,17 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
|
||||
mDrawable.setPowerSaveEnabled(isPowerSave);
|
||||
}
|
||||
|
||||
void onIsOverheatedChanged(boolean isOverheated) {
|
||||
boolean valueChanged = mIsOverheated != isOverheated;
|
||||
mIsOverheated = isOverheated;
|
||||
if (valueChanged) {
|
||||
updateContentDescription();
|
||||
// The battery drawable is a different size depending on whether it's currently
|
||||
// overheated or not, so we need to re-scale the view when overheated changes.
|
||||
scaleBatteryMeterViews();
|
||||
}
|
||||
}
|
||||
|
||||
private TextView loadPercentView() {
|
||||
return (TextView) LayoutInflater.from(getContext())
|
||||
.inflate(R.layout.battery_percentage_view, null);
|
||||
@@ -227,13 +242,17 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
|
||||
mBatteryEstimateFetcher = fetcher;
|
||||
}
|
||||
|
||||
void setDisplayShieldEnabled(boolean displayShieldEnabled) {
|
||||
mDisplayShieldEnabled = displayShieldEnabled;
|
||||
}
|
||||
|
||||
void updatePercentText() {
|
||||
if (mBatteryStateUnknown) {
|
||||
setContentDescription(getContext().getString(R.string.accessibility_battery_unknown));
|
||||
return;
|
||||
}
|
||||
|
||||
if (mBatteryEstimateFetcher == null) {
|
||||
setPercentTextAtCurrentLevel();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -245,10 +264,9 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
|
||||
return;
|
||||
}
|
||||
if (estimate != null && mShowPercentMode == MODE_ESTIMATE) {
|
||||
mEstimateText = estimate;
|
||||
mBatteryPercentView.setText(estimate);
|
||||
setContentDescription(getContext().getString(
|
||||
R.string.accessibility_battery_level_with_estimate,
|
||||
mLevel, estimate));
|
||||
updateContentDescription();
|
||||
} else {
|
||||
setPercentTextAtCurrentLevel();
|
||||
}
|
||||
@@ -257,28 +275,49 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
|
||||
setPercentTextAtCurrentLevel();
|
||||
}
|
||||
} else {
|
||||
setContentDescription(
|
||||
getContext().getString(mCharging ? R.string.accessibility_battery_level_charging
|
||||
: R.string.accessibility_battery_level, mLevel));
|
||||
updateContentDescription();
|
||||
}
|
||||
}
|
||||
|
||||
private void setPercentTextAtCurrentLevel() {
|
||||
if (mBatteryPercentView == null) {
|
||||
return;
|
||||
if (mBatteryPercentView != null) {
|
||||
mEstimateText = null;
|
||||
String percentText = NumberFormat.getPercentInstance().format(mLevel / 100f);
|
||||
// Setting text actually triggers a layout pass (because the text view is set to
|
||||
// wrap_content width and TextView always relayouts for this). Avoid needless
|
||||
// relayout if the text didn't actually change.
|
||||
if (!TextUtils.equals(mBatteryPercentView.getText(), percentText)) {
|
||||
mBatteryPercentView.setText(percentText);
|
||||
}
|
||||
}
|
||||
|
||||
String percentText = NumberFormat.getPercentInstance().format(mLevel / 100f);
|
||||
// Setting text actually triggers a layout pass (because the text view is set to
|
||||
// wrap_content width and TextView always relayouts for this). Avoid needless
|
||||
// relayout if the text didn't actually change.
|
||||
if (!TextUtils.equals(mBatteryPercentView.getText(), percentText)) {
|
||||
mBatteryPercentView.setText(percentText);
|
||||
updateContentDescription();
|
||||
}
|
||||
|
||||
private void updateContentDescription() {
|
||||
Context context = getContext();
|
||||
|
||||
String contentDescription;
|
||||
if (mBatteryStateUnknown) {
|
||||
contentDescription = context.getString(R.string.accessibility_battery_unknown);
|
||||
} else if (mShowPercentMode == MODE_ESTIMATE && !TextUtils.isEmpty(mEstimateText)) {
|
||||
contentDescription = context.getString(
|
||||
mIsOverheated
|
||||
? R.string.accessibility_battery_level_charging_paused_with_estimate
|
||||
: R.string.accessibility_battery_level_with_estimate,
|
||||
mLevel,
|
||||
mEstimateText);
|
||||
} else if (mIsOverheated) {
|
||||
contentDescription =
|
||||
context.getString(R.string.accessibility_battery_level_charging_paused, mLevel);
|
||||
} else if (mCharging) {
|
||||
contentDescription =
|
||||
context.getString(R.string.accessibility_battery_level_charging, mLevel);
|
||||
} else {
|
||||
contentDescription = context.getString(R.string.accessibility_battery_level, mLevel);
|
||||
}
|
||||
|
||||
setContentDescription(
|
||||
getContext().getString(mCharging ? R.string.accessibility_battery_level_charging
|
||||
: R.string.accessibility_battery_level, mLevel));
|
||||
setContentDescription(contentDescription);
|
||||
}
|
||||
|
||||
void updateShowPercent() {
|
||||
@@ -329,6 +368,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
|
||||
}
|
||||
|
||||
mBatteryStateUnknown = isUnknown;
|
||||
updateContentDescription();
|
||||
|
||||
if (mBatteryStateUnknown) {
|
||||
mBatteryIconView.setImageDrawable(getUnknownStateDrawable());
|
||||
@@ -349,15 +389,43 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
|
||||
res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
|
||||
float iconScaleFactor = typedValue.getFloat();
|
||||
|
||||
int batteryHeight = res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_height);
|
||||
int batteryWidth = res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_width);
|
||||
float mainBatteryHeight =
|
||||
res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_height) * iconScaleFactor;
|
||||
float mainBatteryWidth =
|
||||
res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_width) * iconScaleFactor;
|
||||
|
||||
// If the battery is marked as overheated, we should display a shield indicating that the
|
||||
// battery is being "defended".
|
||||
boolean displayShield = mDisplayShieldEnabled && mIsOverheated;
|
||||
float fullBatteryIconHeight =
|
||||
BatterySpecs.getFullBatteryHeight(mainBatteryHeight, displayShield);
|
||||
float fullBatteryIconWidth =
|
||||
BatterySpecs.getFullBatteryWidth(mainBatteryWidth, displayShield);
|
||||
|
||||
int marginTop;
|
||||
if (displayShield) {
|
||||
// If the shield is displayed, we need some extra marginTop so that the bottom of the
|
||||
// main icon is still aligned with the bottom of all the other system icons.
|
||||
int shieldHeightAddition = Math.round(fullBatteryIconHeight - mainBatteryHeight);
|
||||
// However, the other system icons have some embedded bottom padding that the battery
|
||||
// doesn't have, so we shouldn't move the battery icon down by the full amount.
|
||||
// See b/258672854.
|
||||
marginTop = shieldHeightAddition
|
||||
- res.getDimensionPixelSize(R.dimen.status_bar_battery_extra_vertical_spacing);
|
||||
} else {
|
||||
marginTop = 0;
|
||||
}
|
||||
|
||||
int marginBottom = res.getDimensionPixelSize(R.dimen.battery_margin_bottom);
|
||||
|
||||
LinearLayout.LayoutParams scaledLayoutParams = new LinearLayout.LayoutParams(
|
||||
(int) (batteryWidth * iconScaleFactor), (int) (batteryHeight * iconScaleFactor));
|
||||
scaledLayoutParams.setMargins(0, 0, 0, marginBottom);
|
||||
Math.round(fullBatteryIconWidth),
|
||||
Math.round(fullBatteryIconHeight));
|
||||
scaledLayoutParams.setMargins(0, marginTop, 0, marginBottom);
|
||||
|
||||
mDrawable.setDisplayShield(displayShield);
|
||||
mBatteryIconView.setLayoutParams(scaledLayoutParams);
|
||||
mBatteryIconView.invalidateDrawable(mDrawable);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -29,6 +29,8 @@ import android.view.View;
|
||||
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.systemui.flags.FeatureFlags;
|
||||
import com.android.systemui.flags.Flags;
|
||||
import com.android.systemui.settings.CurrentUserTracker;
|
||||
import com.android.systemui.statusbar.phone.StatusBarIconController;
|
||||
import com.android.systemui.statusbar.policy.BatteryController;
|
||||
@@ -84,6 +86,11 @@ public class BatteryMeterViewController extends ViewController<BatteryMeterView>
|
||||
public void onBatteryUnknownStateChanged(boolean isUnknown) {
|
||||
mView.onBatteryUnknownStateChanged(isUnknown);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIsOverheatedChanged(boolean isOverheated) {
|
||||
mView.onIsOverheatedChanged(isOverheated);
|
||||
}
|
||||
};
|
||||
|
||||
// Some places may need to show the battery conditionally, and not obey the tuner
|
||||
@@ -98,6 +105,7 @@ public class BatteryMeterViewController extends ViewController<BatteryMeterView>
|
||||
BroadcastDispatcher broadcastDispatcher,
|
||||
@Main Handler mainHandler,
|
||||
ContentResolver contentResolver,
|
||||
FeatureFlags featureFlags,
|
||||
BatteryController batteryController) {
|
||||
super(view);
|
||||
mConfigurationController = configurationController;
|
||||
@@ -106,6 +114,7 @@ public class BatteryMeterViewController extends ViewController<BatteryMeterView>
|
||||
mBatteryController = batteryController;
|
||||
|
||||
mView.setBatteryEstimateFetcher(mBatteryController::getEstimatedTimeRemainingString);
|
||||
mView.setDisplayShieldEnabled(featureFlags.isEnabled(Flags.BATTERY_SHIELD_ICON));
|
||||
|
||||
mSlotBattery = getResources().getString(com.android.internal.R.string.status_bar_battery);
|
||||
mSettingObserver = new SettingObserver(mainHandler);
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.systemui.battery
|
||||
|
||||
import com.android.settingslib.graph.ThemedBatteryDrawable
|
||||
|
||||
/** An object storing specs related to the battery icon in the status bar. */
|
||||
object BatterySpecs {
|
||||
|
||||
/** Width of the main battery icon, not including the shield. */
|
||||
const val BATTERY_WIDTH = ThemedBatteryDrawable.WIDTH
|
||||
/** Height of the main battery icon, not including the shield. */
|
||||
const val BATTERY_HEIGHT = ThemedBatteryDrawable.HEIGHT
|
||||
|
||||
private const val SHIELD_WIDTH = 10f
|
||||
private const val SHIELD_HEIGHT = 13f
|
||||
|
||||
/**
|
||||
* Amount that the left side of the shield should be offset from the left side of the battery.
|
||||
*/
|
||||
const val SHIELD_LEFT_OFFSET = 8f
|
||||
/** Amount that the top of the shield should be offset from the top of the battery. */
|
||||
const val SHIELD_TOP_OFFSET = 10f
|
||||
|
||||
const val SHIELD_STROKE = 4f
|
||||
|
||||
/** The full width of the battery icon, including the main battery icon *and* the shield. */
|
||||
const val BATTERY_WIDTH_WITH_SHIELD = SHIELD_LEFT_OFFSET + SHIELD_WIDTH
|
||||
/** The full height of the battery icon, including the main battery icon *and* the shield. */
|
||||
const val BATTERY_HEIGHT_WITH_SHIELD = SHIELD_TOP_OFFSET + SHIELD_HEIGHT
|
||||
|
||||
/**
|
||||
* Given the desired height of the main battery icon in pixels, returns the height that the full
|
||||
* battery icon will take up in pixels.
|
||||
*
|
||||
* If there's no shield, this will just return [mainBatteryHeight]. Otherwise, the shield
|
||||
* extends slightly below the bottom of the main battery icon so we need some extra height.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getFullBatteryHeight(mainBatteryHeight: Float, displayShield: Boolean): Float {
|
||||
return if (!displayShield) {
|
||||
mainBatteryHeight
|
||||
} else {
|
||||
val verticalScaleFactor = mainBatteryHeight / BATTERY_HEIGHT
|
||||
verticalScaleFactor * BATTERY_HEIGHT_WITH_SHIELD
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the desired width of the main battery icon in pixels, returns the width that the full
|
||||
* battery icon will take up in pixels.
|
||||
*
|
||||
* If there's no shield, this will just return [mainBatteryWidth]. Otherwise, the shield extends
|
||||
* past the right side of the main battery icon so we need some extra width.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getFullBatteryWidth(mainBatteryWidth: Float, displayShield: Boolean): Float {
|
||||
return if (!displayShield) {
|
||||
mainBatteryWidth
|
||||
} else {
|
||||
val horizontalScaleFactor = mainBatteryWidth / BATTERY_WIDTH
|
||||
horizontalScaleFactor * BATTERY_WIDTH_WITH_SHIELD
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the height of the full battery icon, return how tall the main battery icon should be.
|
||||
*
|
||||
* If there's no shield, this will just return [fullBatteryHeight]. Otherwise, the shield takes
|
||||
* up some of the view's height so the main battery width will be just a portion of
|
||||
* [fullBatteryHeight].
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getMainBatteryHeight(fullBatteryHeight: Float, displayShield: Boolean): Float {
|
||||
return if (!displayShield) {
|
||||
fullBatteryHeight
|
||||
} else {
|
||||
return (BATTERY_HEIGHT / BATTERY_HEIGHT_WITH_SHIELD) * fullBatteryHeight
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the width of the full battery icon, return how wide the main battery icon should be.
|
||||
*
|
||||
* If there's no shield, this will just return [fullBatteryWidth]. Otherwise, the shield takes
|
||||
* up some of the view's width so the main battery width will be just a portion of
|
||||
* [fullBatteryWidth].
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getMainBatteryWidth(fullBatteryWidth: Float, displayShield: Boolean): Float {
|
||||
return if (!displayShield) {
|
||||
fullBatteryWidth
|
||||
} else {
|
||||
return (BATTERY_WIDTH / BATTERY_WIDTH_WITH_SHIELD) * fullBatteryWidth
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -239,6 +239,9 @@ object Flags {
|
||||
// TODO(b/256613548): Tracking Bug
|
||||
val NEW_STATUS_BAR_WIFI_ICON_BACKEND = unreleasedFlag(609, "new_status_bar_wifi_icon_backend")
|
||||
|
||||
// TODO(b/256623670): Tracking Bug
|
||||
@JvmField val BATTERY_SHIELD_ICON = unreleasedFlag(610, "battery_shield_icon")
|
||||
|
||||
// 700 - dialer/calls
|
||||
// TODO(b/254512734): Tracking Bug
|
||||
val ONGOING_CALL_STATUS_BAR_CHIP = releasedFlag(700, "ongoing_call_status_bar_chip")
|
||||
|
||||
@@ -226,6 +226,7 @@ public abstract class StatusBarViewModule {
|
||||
BroadcastDispatcher broadcastDispatcher,
|
||||
@Main Handler mainHandler,
|
||||
ContentResolver contentResolver,
|
||||
FeatureFlags featureFlags,
|
||||
BatteryController batteryController
|
||||
) {
|
||||
return new BatteryMeterViewController(
|
||||
@@ -235,6 +236,7 @@ public abstract class StatusBarViewModule {
|
||||
broadcastDispatcher,
|
||||
mainHandler,
|
||||
contentResolver,
|
||||
featureFlags,
|
||||
batteryController);
|
||||
|
||||
}
|
||||
|
||||
@@ -155,6 +155,9 @@ public interface BatteryController extends DemoMode,
|
||||
|
||||
default void onWirelessChargingChanged(boolean isWirlessCharging) {
|
||||
}
|
||||
|
||||
default void onIsOverheatedChanged(boolean isOverheated) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package com.android.systemui.statusbar.policy;
|
||||
|
||||
import static android.os.BatteryManager.BATTERY_HEALTH_OVERHEAT;
|
||||
import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN;
|
||||
import static android.os.BatteryManager.EXTRA_HEALTH;
|
||||
import static android.os.BatteryManager.EXTRA_PRESENT;
|
||||
|
||||
import android.annotation.WorkerThread;
|
||||
@@ -87,6 +90,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
|
||||
protected boolean mPowerSave;
|
||||
private boolean mAodPowerSave;
|
||||
private boolean mWirelessCharging;
|
||||
private boolean mIsOverheated = false;
|
||||
private boolean mTestMode = false;
|
||||
@VisibleForTesting
|
||||
boolean mHasReceivedBattery = false;
|
||||
@@ -184,6 +188,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
|
||||
cb.onPowerSaveChanged(mPowerSave);
|
||||
cb.onBatteryUnknownStateChanged(mStateUnknown);
|
||||
cb.onWirelessChargingChanged(mWirelessCharging);
|
||||
cb.onIsOverheatedChanged(mIsOverheated);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -222,6 +227,13 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
|
||||
fireBatteryUnknownStateChanged();
|
||||
}
|
||||
|
||||
int batteryHealth = intent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN);
|
||||
boolean isOverheated = batteryHealth == BATTERY_HEALTH_OVERHEAT;
|
||||
if (isOverheated != mIsOverheated) {
|
||||
mIsOverheated = isOverheated;
|
||||
fireIsOverheatedChanged();
|
||||
}
|
||||
|
||||
fireBatteryLevelChanged();
|
||||
} else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) {
|
||||
updatePowerSave();
|
||||
@@ -292,6 +304,10 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
|
||||
return mPluggedChargingSource == BatteryManager.BATTERY_PLUGGED_WIRELESS;
|
||||
}
|
||||
|
||||
public boolean isOverheated() {
|
||||
return mIsOverheated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getEstimatedTimeRemainingString(EstimateFetchCompletion completion) {
|
||||
// Need to fetch or refresh the estimate, but it may involve binder calls so offload the
|
||||
@@ -402,6 +418,15 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
|
||||
}
|
||||
}
|
||||
|
||||
private void fireIsOverheatedChanged() {
|
||||
synchronized (mChangeCallbacks) {
|
||||
final int n = mChangeCallbacks.size();
|
||||
for (int i = 0; i < n; i++) {
|
||||
mChangeCallbacks.get(i).onIsOverheatedChanged(mIsOverheated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchDemoCommand(String command, Bundle args) {
|
||||
if (!mDemoModeController.isInDemoMode()) {
|
||||
@@ -412,6 +437,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
|
||||
String plugged = args.getString("plugged");
|
||||
String powerSave = args.getString("powersave");
|
||||
String present = args.getString("present");
|
||||
String overheated = args.getString("overheated");
|
||||
if (level != null) {
|
||||
mLevel = Math.min(Math.max(Integer.parseInt(level), 0), 100);
|
||||
}
|
||||
@@ -426,6 +452,10 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
|
||||
mStateUnknown = !present.equals("true");
|
||||
fireBatteryUnknownStateChanged();
|
||||
}
|
||||
if (overheated != null) {
|
||||
mIsOverheated = overheated.equals("true");
|
||||
fireIsOverheatedChanged();
|
||||
}
|
||||
fireBatteryLevelChanged();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.systemui.battery
|
||||
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT
|
||||
import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT_WITH_SHIELD
|
||||
import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH
|
||||
import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH_WITH_SHIELD
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Test
|
||||
|
||||
@SmallTest
|
||||
class AccessorizedBatteryDrawableTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun intrinsicSize_shieldFalse_isBatterySize() {
|
||||
val drawable = AccessorizedBatteryDrawable(context, frameColor = 0)
|
||||
drawable.displayShield = false
|
||||
|
||||
val density = context.resources.displayMetrics.density
|
||||
assertThat(drawable.intrinsicHeight).isEqualTo((BATTERY_HEIGHT * density).toInt())
|
||||
assertThat(drawable.intrinsicWidth).isEqualTo((BATTERY_WIDTH * density).toInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun intrinsicSize_shieldTrue_isBatteryPlusShieldSize() {
|
||||
val drawable = AccessorizedBatteryDrawable(context, frameColor = 0)
|
||||
drawable.displayShield = true
|
||||
|
||||
val density = context.resources.displayMetrics.density
|
||||
assertThat(drawable.intrinsicHeight)
|
||||
.isEqualTo((BATTERY_HEIGHT_WITH_SHIELD * density).toInt())
|
||||
assertThat(drawable.intrinsicWidth).isEqualTo((BATTERY_WIDTH_WITH_SHIELD * density).toInt())
|
||||
}
|
||||
|
||||
// TODO(b/255625888): Screenshot tests for this drawable would be amazing!
|
||||
}
|
||||
@@ -35,6 +35,8 @@ import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher;
|
||||
import com.android.systemui.flags.FakeFeatureFlags;
|
||||
import com.android.systemui.flags.Flags;
|
||||
import com.android.systemui.statusbar.policy.BatteryController;
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController;
|
||||
import com.android.systemui.tuner.TunerService;
|
||||
@@ -59,6 +61,7 @@ public class BatteryMeterViewControllerTest extends SysuiTestCase {
|
||||
private Handler mHandler;
|
||||
@Mock
|
||||
private ContentResolver mContentResolver;
|
||||
private FakeFeatureFlags mFeatureFlags;
|
||||
@Mock
|
||||
private BatteryController mBatteryController;
|
||||
|
||||
@@ -71,19 +74,13 @@ public class BatteryMeterViewControllerTest extends SysuiTestCase {
|
||||
when(mBatteryMeterView.getContext()).thenReturn(mContext);
|
||||
when(mBatteryMeterView.getResources()).thenReturn(mContext.getResources());
|
||||
|
||||
mController = new BatteryMeterViewController(
|
||||
mBatteryMeterView,
|
||||
mConfigurationController,
|
||||
mTunerService,
|
||||
mBroadcastDispatcher,
|
||||
mHandler,
|
||||
mContentResolver,
|
||||
mBatteryController
|
||||
);
|
||||
mFeatureFlags = new FakeFeatureFlags();
|
||||
mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onViewAttached_callbacksRegistered() {
|
||||
initController();
|
||||
mController.onViewAttached();
|
||||
|
||||
verify(mConfigurationController).addCallback(any());
|
||||
@@ -101,6 +98,7 @@ public class BatteryMeterViewControllerTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void onViewDetached_callbacksUnregistered() {
|
||||
initController();
|
||||
// Set everything up first.
|
||||
mController.onViewAttached();
|
||||
|
||||
@@ -114,6 +112,7 @@ public class BatteryMeterViewControllerTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void ignoreTunerUpdates_afterOnViewAttached_callbackUnregistered() {
|
||||
initController();
|
||||
// Start out receiving tuner updates
|
||||
mController.onViewAttached();
|
||||
|
||||
@@ -124,10 +123,43 @@ public class BatteryMeterViewControllerTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void ignoreTunerUpdates_beforeOnViewAttached_callbackNeverRegistered() {
|
||||
initController();
|
||||
|
||||
mController.ignoreTunerUpdates();
|
||||
|
||||
mController.onViewAttached();
|
||||
|
||||
verify(mTunerService, never()).addTunable(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shieldFlagDisabled_viewNotified() {
|
||||
mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, false);
|
||||
|
||||
initController();
|
||||
|
||||
verify(mBatteryMeterView).setDisplayShieldEnabled(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shieldFlagEnabled_viewNotified() {
|
||||
mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, true);
|
||||
|
||||
initController();
|
||||
|
||||
verify(mBatteryMeterView).setDisplayShieldEnabled(true);
|
||||
}
|
||||
|
||||
private void initController() {
|
||||
mController = new BatteryMeterViewController(
|
||||
mBatteryMeterView,
|
||||
mConfigurationController,
|
||||
mTunerService,
|
||||
mBroadcastDispatcher,
|
||||
mHandler,
|
||||
mContentResolver,
|
||||
mFeatureFlags,
|
||||
mBatteryController
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@ package com.android.systemui.battery
|
||||
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper.RunWithLooper
|
||||
import android.widget.ImageView
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.battery.BatteryMeterView.BatteryEstimateFetcher
|
||||
import com.android.systemui.statusbar.policy.BatteryController.EstimateFetchCompletion
|
||||
@@ -58,6 +60,182 @@ class BatteryMeterViewTest : SysuiTestCase() {
|
||||
// No assert needed
|
||||
}
|
||||
|
||||
@Test
|
||||
fun contentDescription_unknown() {
|
||||
mBatteryMeterView.onBatteryUnknownStateChanged(true)
|
||||
|
||||
assertThat(mBatteryMeterView.contentDescription).isEqualTo(
|
||||
context.getString(R.string.accessibility_battery_unknown)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun contentDescription_estimate() {
|
||||
mBatteryMeterView.onBatteryLevelChanged(15, false)
|
||||
mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
|
||||
mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
|
||||
|
||||
mBatteryMeterView.updatePercentText()
|
||||
|
||||
assertThat(mBatteryMeterView.contentDescription).isEqualTo(
|
||||
context.getString(
|
||||
R.string.accessibility_battery_level_with_estimate, 15, ESTIMATE
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun contentDescription_estimateAndOverheated() {
|
||||
mBatteryMeterView.onBatteryLevelChanged(17, false)
|
||||
mBatteryMeterView.onIsOverheatedChanged(true)
|
||||
mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
|
||||
mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
|
||||
|
||||
mBatteryMeterView.updatePercentText()
|
||||
|
||||
assertThat(mBatteryMeterView.contentDescription).isEqualTo(
|
||||
context.getString(
|
||||
R.string.accessibility_battery_level_charging_paused_with_estimate,
|
||||
17,
|
||||
ESTIMATE,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun contentDescription_overheated() {
|
||||
mBatteryMeterView.onBatteryLevelChanged(90, false)
|
||||
mBatteryMeterView.onIsOverheatedChanged(true)
|
||||
|
||||
assertThat(mBatteryMeterView.contentDescription).isEqualTo(
|
||||
context.getString(R.string.accessibility_battery_level_charging_paused, 90)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun contentDescription_charging() {
|
||||
mBatteryMeterView.onBatteryLevelChanged(45, true)
|
||||
|
||||
assertThat(mBatteryMeterView.contentDescription).isEqualTo(
|
||||
context.getString(R.string.accessibility_battery_level_charging, 45)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun contentDescription_notCharging() {
|
||||
mBatteryMeterView.onBatteryLevelChanged(45, false)
|
||||
|
||||
assertThat(mBatteryMeterView.contentDescription).isEqualTo(
|
||||
context.getString(R.string.accessibility_battery_level, 45)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun changesFromEstimateToPercent_textAndContentDescriptionChanges() {
|
||||
mBatteryMeterView.onBatteryLevelChanged(15, false)
|
||||
mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
|
||||
mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
|
||||
|
||||
mBatteryMeterView.updatePercentText()
|
||||
|
||||
assertThat(mBatteryMeterView.contentDescription).isEqualTo(
|
||||
context.getString(
|
||||
R.string.accessibility_battery_level_with_estimate, 15, ESTIMATE
|
||||
)
|
||||
)
|
||||
|
||||
// Update the show mode from estimate to percent
|
||||
mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON)
|
||||
|
||||
assertThat(mBatteryMeterView.batteryPercentViewText).isEqualTo("15%")
|
||||
assertThat(mBatteryMeterView.contentDescription).isEqualTo(
|
||||
context.getString(R.string.accessibility_battery_level, 15)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun contentDescription_manyUpdates_alwaysUpdated() {
|
||||
// Overheated
|
||||
mBatteryMeterView.onBatteryLevelChanged(90, false)
|
||||
mBatteryMeterView.onIsOverheatedChanged(true)
|
||||
assertThat(mBatteryMeterView.contentDescription).isEqualTo(
|
||||
context.getString(R.string.accessibility_battery_level_charging_paused, 90)
|
||||
)
|
||||
|
||||
// Overheated & estimate
|
||||
mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
|
||||
mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
|
||||
mBatteryMeterView.updatePercentText()
|
||||
assertThat(mBatteryMeterView.contentDescription).isEqualTo(
|
||||
context.getString(
|
||||
R.string.accessibility_battery_level_charging_paused_with_estimate,
|
||||
90,
|
||||
ESTIMATE,
|
||||
)
|
||||
)
|
||||
|
||||
// Just estimate
|
||||
mBatteryMeterView.onIsOverheatedChanged(false)
|
||||
assertThat(mBatteryMeterView.contentDescription).isEqualTo(
|
||||
context.getString(
|
||||
R.string.accessibility_battery_level_with_estimate,
|
||||
90,
|
||||
ESTIMATE,
|
||||
)
|
||||
)
|
||||
|
||||
// Just percent
|
||||
mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON)
|
||||
assertThat(mBatteryMeterView.contentDescription).isEqualTo(
|
||||
context.getString(R.string.accessibility_battery_level, 90)
|
||||
)
|
||||
|
||||
// Charging
|
||||
mBatteryMeterView.onBatteryLevelChanged(90, true)
|
||||
assertThat(mBatteryMeterView.contentDescription).isEqualTo(
|
||||
context.getString(R.string.accessibility_battery_level_charging, 90)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isOverheatedChanged_true_drawableGetsTrue() {
|
||||
mBatteryMeterView.setDisplayShieldEnabled(true)
|
||||
val drawable = getBatteryDrawable()
|
||||
|
||||
mBatteryMeterView.onIsOverheatedChanged(true)
|
||||
|
||||
assertThat(drawable.displayShield).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isOverheatedChanged_false_drawableGetsFalse() {
|
||||
mBatteryMeterView.setDisplayShieldEnabled(true)
|
||||
val drawable = getBatteryDrawable()
|
||||
|
||||
// Start as true
|
||||
mBatteryMeterView.onIsOverheatedChanged(true)
|
||||
|
||||
// Update to false
|
||||
mBatteryMeterView.onIsOverheatedChanged(false)
|
||||
|
||||
assertThat(drawable.displayShield).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isOverheatedChanged_true_featureflagOff_drawableGetsFalse() {
|
||||
mBatteryMeterView.setDisplayShieldEnabled(false)
|
||||
val drawable = getBatteryDrawable()
|
||||
|
||||
mBatteryMeterView.onIsOverheatedChanged(true)
|
||||
|
||||
assertThat(drawable.displayShield).isFalse()
|
||||
}
|
||||
|
||||
private fun getBatteryDrawable(): AccessorizedBatteryDrawable {
|
||||
return (mBatteryMeterView.getChildAt(0) as ImageView)
|
||||
.drawable as AccessorizedBatteryDrawable
|
||||
}
|
||||
|
||||
private class Fetcher : BatteryEstimateFetcher {
|
||||
override fun fetchBatteryTimeRemainingEstimate(
|
||||
completion: EstimateFetchCompletion) {
|
||||
@@ -68,4 +246,4 @@ class BatteryMeterViewTest : SysuiTestCase() {
|
||||
private companion object {
|
||||
const val ESTIMATE = "2 hours 2 minutes"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.systemui.battery
|
||||
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT
|
||||
import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT_WITH_SHIELD
|
||||
import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH
|
||||
import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH_WITH_SHIELD
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Test
|
||||
|
||||
@SmallTest
|
||||
class BatterySpecsTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun getFullBatteryHeight_shieldFalse_returnsMainHeight() {
|
||||
val fullHeight = BatterySpecs.getFullBatteryHeight(56f, displayShield = false)
|
||||
|
||||
assertThat(fullHeight).isEqualTo(56f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getFullBatteryHeight_shieldTrue_returnsMainHeightPlusShield() {
|
||||
val mainHeight = BATTERY_HEIGHT * 5
|
||||
val fullHeight = BatterySpecs.getFullBatteryHeight(mainHeight, displayShield = true)
|
||||
|
||||
// Since the main battery was scaled 5x, the output height should also be scaled 5x
|
||||
val expectedFullHeight = BATTERY_HEIGHT_WITH_SHIELD * 5
|
||||
|
||||
assertThat(fullHeight).isWithin(.0001f).of(expectedFullHeight)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getFullBatteryWidth_shieldFalse_returnsMainWidth() {
|
||||
val fullWidth = BatterySpecs.getFullBatteryWidth(33f, displayShield = false)
|
||||
|
||||
assertThat(fullWidth).isEqualTo(33f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getFullBatteryWidth_shieldTrue_returnsMainWidthPlusShield() {
|
||||
val mainWidth = BATTERY_WIDTH * 3.3f
|
||||
|
||||
val fullWidth = BatterySpecs.getFullBatteryWidth(mainWidth, displayShield = true)
|
||||
|
||||
// Since the main battery was scaled 3.3x, the output width should also be scaled 5x
|
||||
val expectedFullWidth = BATTERY_WIDTH_WITH_SHIELD * 3.3f
|
||||
assertThat(fullWidth).isWithin(.0001f).of(expectedFullWidth)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getMainBatteryHeight_shieldFalse_returnsFullHeight() {
|
||||
val mainHeight = BatterySpecs.getMainBatteryHeight(89f, displayShield = false)
|
||||
|
||||
assertThat(mainHeight).isEqualTo(89f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getMainBatteryHeight_shieldTrue_returnsNotFullHeight() {
|
||||
val fullHeight = BATTERY_HEIGHT_WITH_SHIELD * 7.7f
|
||||
|
||||
val mainHeight = BatterySpecs.getMainBatteryHeight(fullHeight, displayShield = true)
|
||||
|
||||
// Since the full height was scaled 7.7x, the main height should also be scaled 7.7x.
|
||||
val expectedHeight = BATTERY_HEIGHT * 7.7f
|
||||
assertThat(mainHeight).isWithin(.0001f).of(expectedHeight)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getMainBatteryWidth_shieldFalse_returnsFullWidth() {
|
||||
val mainWidth = BatterySpecs.getMainBatteryWidth(2345f, displayShield = false)
|
||||
|
||||
assertThat(mainWidth).isEqualTo(2345f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getMainBatteryWidth_shieldTrue_returnsNotFullWidth() {
|
||||
val fullWidth = BATTERY_WIDTH_WITH_SHIELD * 0.6f
|
||||
|
||||
val mainWidth = BatterySpecs.getMainBatteryWidth(fullWidth, displayShield = true)
|
||||
|
||||
// Since the full width was scaled 0.6x, the main height should also be scaled 0.6x.
|
||||
val expectedWidth = BATTERY_WIDTH * 0.6f
|
||||
assertThat(mainWidth).isWithin(.0001f).of(expectedWidth)
|
||||
}
|
||||
}
|
||||
@@ -221,4 +221,33 @@ public class BatteryControllerTest extends SysuiTestCase {
|
||||
|
||||
Assert.assertFalse(mBatteryController.isChargingSourceDock());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void batteryStateChanged_healthNotOverheated_outputsFalse() {
|
||||
Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
|
||||
intent.putExtra(BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_GOOD);
|
||||
|
||||
mBatteryController.onReceive(getContext(), intent);
|
||||
|
||||
Assert.assertFalse(mBatteryController.isOverheated());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void batteryStateChanged_healthOverheated_outputsTrue() {
|
||||
Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
|
||||
intent.putExtra(BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_OVERHEAT);
|
||||
|
||||
mBatteryController.onReceive(getContext(), intent);
|
||||
|
||||
Assert.assertTrue(mBatteryController.isOverheated());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void batteryStateChanged_noHealthGiven_outputsFalse() {
|
||||
Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
|
||||
|
||||
mBatteryController.onReceive(getContext(), intent);
|
||||
|
||||
Assert.assertFalse(mBatteryController.isOverheated());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user