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:
Caitlin Shkuratov
2022-11-14 14:55:26 +00:00
committed by Automerger Merge Worker
17 changed files with 880 additions and 40 deletions

View File

@@ -412,14 +412,13 @@ open class ThemedBatteryDrawable(private val context: Context, frameColor: Int)
} }
companion object { companion object {
private const val TAG = "ThemedBatteryDrawable" const val WIDTH = 12f
private const val WIDTH = 12f const val HEIGHT = 20f
private const val HEIGHT = 20f
private const val CRITICAL_LEVEL = 15 private const val CRITICAL_LEVEL = 15
// On a 12x20 grid, how wide to make the fill protection stroke. // On a 12x20 grid, how wide to make the fill protection stroke.
// Scales when our size changes // Scales when our size changes
private const val PROTECTION_STROKE_WIDTH = 3f private const val PROTECTION_STROKE_WIDTH = 3f
// Arbitrarily chosen for visibility at small sizes // Arbitrarily chosen for visibility at small sizes
private const val PROTECTION_MIN_STROKE_WIDTH = 6f const val PROTECTION_MIN_STROKE_WIDTH = 6f
} }
} }

View File

@@ -485,6 +485,12 @@
<!-- Whether to show a severe low battery dialog. --> <!-- Whether to show a severe low battery dialog. -->
<bool name="config_severe_battery_dialog">false</bool> <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 <!-- 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 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 cutout. If present as well as config_enableDisplayCutoutProtection is set to true, then

View File

@@ -105,6 +105,12 @@
so the width of the icon should be 13.0dp * (12.0 / 20.0) --> so the width of the icon should be 13.0dp * (12.0 / 20.0) -->
<dimen name="status_bar_battery_icon_width">7.8dp</dimen> <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. --> <!-- The font size for the clock in the status bar. -->
<dimen name="status_bar_clock_size">14sp</dimen> <dimen name="status_bar_clock_size">14sp</dimen>

View File

@@ -439,11 +439,17 @@
<string name="accessibility_battery_level">Battery <xliff:g id="number">%d</xliff:g> percent.</string> <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] --> <!-- 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] --> <!-- 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> <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] --> <!-- 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> <string name="accessibility_overflow_action">See all notifications</string>

View File

@@ -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))
}
}

View File

@@ -45,7 +45,6 @@ import android.widget.TextView;
import androidx.annotation.StyleRes; import androidx.annotation.StyleRes;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import com.android.settingslib.graph.ThemedBatteryDrawable;
import com.android.systemui.DualToneHandler; import com.android.systemui.DualToneHandler;
import com.android.systemui.R; import com.android.systemui.R;
import com.android.systemui.animation.Interpolators; 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_OFF = 2;
public static final int MODE_ESTIMATE = 3; public static final int MODE_ESTIMATE = 3;
private final ThemedBatteryDrawable mDrawable; private final AccessorizedBatteryDrawable mDrawable;
private final ImageView mBatteryIconView; private final ImageView mBatteryIconView;
private TextView mBatteryPercentView; private TextView mBatteryPercentView;
@@ -77,7 +76,10 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
private int mLevel; private int mLevel;
private int mShowPercentMode = MODE_DEFAULT; private int mShowPercentMode = MODE_DEFAULT;
private boolean mShowPercentAvailable; private boolean mShowPercentAvailable;
private String mEstimateText = null;
private boolean mCharging; private boolean mCharging;
private boolean mIsOverheated;
private boolean mDisplayShieldEnabled;
// Error state where we know nothing about the current battery state // Error state where we know nothing about the current battery state
private boolean mBatteryStateUnknown; private boolean mBatteryStateUnknown;
// Lazily-loaded since this is expected to be a rare-if-ever state // 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, final int frameColor = atts.getColor(R.styleable.BatteryMeterView_frameColor,
context.getColor(R.color.meter_background_color)); context.getColor(R.color.meter_background_color));
mPercentageStyleId = atts.getResourceId(R.styleable.BatteryMeterView_textAppearance, 0); mPercentageStyleId = atts.getResourceId(R.styleable.BatteryMeterView_textAppearance, 0);
mDrawable = new ThemedBatteryDrawable(context, frameColor); mDrawable = new AccessorizedBatteryDrawable(context, frameColor);
atts.recycle(); atts.recycle();
mShowPercentAvailable = context.getResources().getBoolean( mShowPercentAvailable = context.getResources().getBoolean(
@@ -170,12 +172,14 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
if (mode == mShowPercentMode) return; if (mode == mShowPercentMode) return;
mShowPercentMode = mode; mShowPercentMode = mode;
updateShowPercent(); updateShowPercent();
updatePercentText();
} }
@Override @Override
protected void onConfigurationChanged(Configuration newConfig) { protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig); super.onConfigurationChanged(newConfig);
updatePercentView(); updatePercentView();
mDrawable.notifyDensityChanged();
} }
public void setColorsFromContext(Context context) { public void setColorsFromContext(Context context) {
@@ -203,6 +207,17 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
mDrawable.setPowerSaveEnabled(isPowerSave); 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() { private TextView loadPercentView() {
return (TextView) LayoutInflater.from(getContext()) return (TextView) LayoutInflater.from(getContext())
.inflate(R.layout.battery_percentage_view, null); .inflate(R.layout.battery_percentage_view, null);
@@ -227,13 +242,17 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
mBatteryEstimateFetcher = fetcher; mBatteryEstimateFetcher = fetcher;
} }
void setDisplayShieldEnabled(boolean displayShieldEnabled) {
mDisplayShieldEnabled = displayShieldEnabled;
}
void updatePercentText() { void updatePercentText() {
if (mBatteryStateUnknown) { if (mBatteryStateUnknown) {
setContentDescription(getContext().getString(R.string.accessibility_battery_unknown));
return; return;
} }
if (mBatteryEstimateFetcher == null) { if (mBatteryEstimateFetcher == null) {
setPercentTextAtCurrentLevel();
return; return;
} }
@@ -245,10 +264,9 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
return; return;
} }
if (estimate != null && mShowPercentMode == MODE_ESTIMATE) { if (estimate != null && mShowPercentMode == MODE_ESTIMATE) {
mEstimateText = estimate;
mBatteryPercentView.setText(estimate); mBatteryPercentView.setText(estimate);
setContentDescription(getContext().getString( updateContentDescription();
R.string.accessibility_battery_level_with_estimate,
mLevel, estimate));
} else { } else {
setPercentTextAtCurrentLevel(); setPercentTextAtCurrentLevel();
} }
@@ -257,17 +275,13 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
setPercentTextAtCurrentLevel(); setPercentTextAtCurrentLevel();
} }
} else { } else {
setContentDescription( updateContentDescription();
getContext().getString(mCharging ? R.string.accessibility_battery_level_charging
: R.string.accessibility_battery_level, mLevel));
} }
} }
private void setPercentTextAtCurrentLevel() { private void setPercentTextAtCurrentLevel() {
if (mBatteryPercentView == null) { if (mBatteryPercentView != null) {
return; mEstimateText = null;
}
String percentText = NumberFormat.getPercentInstance().format(mLevel / 100f); String percentText = NumberFormat.getPercentInstance().format(mLevel / 100f);
// Setting text actually triggers a layout pass (because the text view is set to // 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 // wrap_content width and TextView always relayouts for this). Avoid needless
@@ -275,10 +289,35 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
if (!TextUtils.equals(mBatteryPercentView.getText(), percentText)) { if (!TextUtils.equals(mBatteryPercentView.getText(), percentText)) {
mBatteryPercentView.setText(percentText); mBatteryPercentView.setText(percentText);
} }
}
setContentDescription( updateContentDescription();
getContext().getString(mCharging ? R.string.accessibility_battery_level_charging }
: R.string.accessibility_battery_level, mLevel));
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(contentDescription);
} }
void updateShowPercent() { void updateShowPercent() {
@@ -329,6 +368,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
} }
mBatteryStateUnknown = isUnknown; mBatteryStateUnknown = isUnknown;
updateContentDescription();
if (mBatteryStateUnknown) { if (mBatteryStateUnknown) {
mBatteryIconView.setImageDrawable(getUnknownStateDrawable()); 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); res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
float iconScaleFactor = typedValue.getFloat(); float iconScaleFactor = typedValue.getFloat();
int batteryHeight = res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_height); float mainBatteryHeight =
int batteryWidth = res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_width); 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); int marginBottom = res.getDimensionPixelSize(R.dimen.battery_margin_bottom);
LinearLayout.LayoutParams scaledLayoutParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams scaledLayoutParams = new LinearLayout.LayoutParams(
(int) (batteryWidth * iconScaleFactor), (int) (batteryHeight * iconScaleFactor)); Math.round(fullBatteryIconWidth),
scaledLayoutParams.setMargins(0, 0, 0, marginBottom); Math.round(fullBatteryIconHeight));
scaledLayoutParams.setMargins(0, marginTop, 0, marginBottom);
mDrawable.setDisplayShield(displayShield);
mBatteryIconView.setLayoutParams(scaledLayoutParams); mBatteryIconView.setLayoutParams(scaledLayoutParams);
mBatteryIconView.invalidateDrawable(mDrawable);
} }
@Override @Override

View File

@@ -29,6 +29,8 @@ import android.view.View;
import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Main; 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.settings.CurrentUserTracker;
import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BatteryController;
@@ -84,6 +86,11 @@ public class BatteryMeterViewController extends ViewController<BatteryMeterView>
public void onBatteryUnknownStateChanged(boolean isUnknown) { public void onBatteryUnknownStateChanged(boolean isUnknown) {
mView.onBatteryUnknownStateChanged(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 // 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, BroadcastDispatcher broadcastDispatcher,
@Main Handler mainHandler, @Main Handler mainHandler,
ContentResolver contentResolver, ContentResolver contentResolver,
FeatureFlags featureFlags,
BatteryController batteryController) { BatteryController batteryController) {
super(view); super(view);
mConfigurationController = configurationController; mConfigurationController = configurationController;
@@ -106,6 +114,7 @@ public class BatteryMeterViewController extends ViewController<BatteryMeterView>
mBatteryController = batteryController; mBatteryController = batteryController;
mView.setBatteryEstimateFetcher(mBatteryController::getEstimatedTimeRemainingString); mView.setBatteryEstimateFetcher(mBatteryController::getEstimatedTimeRemainingString);
mView.setDisplayShieldEnabled(featureFlags.isEnabled(Flags.BATTERY_SHIELD_ICON));
mSlotBattery = getResources().getString(com.android.internal.R.string.status_bar_battery); mSlotBattery = getResources().getString(com.android.internal.R.string.status_bar_battery);
mSettingObserver = new SettingObserver(mainHandler); mSettingObserver = new SettingObserver(mainHandler);

View File

@@ -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
}
}
}

View File

@@ -239,6 +239,9 @@ object Flags {
// TODO(b/256613548): Tracking Bug // TODO(b/256613548): Tracking Bug
val NEW_STATUS_BAR_WIFI_ICON_BACKEND = unreleasedFlag(609, "new_status_bar_wifi_icon_backend") 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 // 700 - dialer/calls
// TODO(b/254512734): Tracking Bug // TODO(b/254512734): Tracking Bug
val ONGOING_CALL_STATUS_BAR_CHIP = releasedFlag(700, "ongoing_call_status_bar_chip") val ONGOING_CALL_STATUS_BAR_CHIP = releasedFlag(700, "ongoing_call_status_bar_chip")

View File

@@ -226,6 +226,7 @@ public abstract class StatusBarViewModule {
BroadcastDispatcher broadcastDispatcher, BroadcastDispatcher broadcastDispatcher,
@Main Handler mainHandler, @Main Handler mainHandler,
ContentResolver contentResolver, ContentResolver contentResolver,
FeatureFlags featureFlags,
BatteryController batteryController BatteryController batteryController
) { ) {
return new BatteryMeterViewController( return new BatteryMeterViewController(
@@ -235,6 +236,7 @@ public abstract class StatusBarViewModule {
broadcastDispatcher, broadcastDispatcher,
mainHandler, mainHandler,
contentResolver, contentResolver,
featureFlags,
batteryController); batteryController);
} }

View File

@@ -155,6 +155,9 @@ public interface BatteryController extends DemoMode,
default void onWirelessChargingChanged(boolean isWirlessCharging) { default void onWirelessChargingChanged(boolean isWirlessCharging) {
} }
default void onIsOverheatedChanged(boolean isOverheated) {
}
} }
/** /**

View File

@@ -16,6 +16,9 @@
package com.android.systemui.statusbar.policy; 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 static android.os.BatteryManager.EXTRA_PRESENT;
import android.annotation.WorkerThread; import android.annotation.WorkerThread;
@@ -87,6 +90,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
protected boolean mPowerSave; protected boolean mPowerSave;
private boolean mAodPowerSave; private boolean mAodPowerSave;
private boolean mWirelessCharging; private boolean mWirelessCharging;
private boolean mIsOverheated = false;
private boolean mTestMode = false; private boolean mTestMode = false;
@VisibleForTesting @VisibleForTesting
boolean mHasReceivedBattery = false; boolean mHasReceivedBattery = false;
@@ -184,6 +188,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
cb.onPowerSaveChanged(mPowerSave); cb.onPowerSaveChanged(mPowerSave);
cb.onBatteryUnknownStateChanged(mStateUnknown); cb.onBatteryUnknownStateChanged(mStateUnknown);
cb.onWirelessChargingChanged(mWirelessCharging); cb.onWirelessChargingChanged(mWirelessCharging);
cb.onIsOverheatedChanged(mIsOverheated);
} }
@Override @Override
@@ -222,6 +227,13 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
fireBatteryUnknownStateChanged(); fireBatteryUnknownStateChanged();
} }
int batteryHealth = intent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN);
boolean isOverheated = batteryHealth == BATTERY_HEALTH_OVERHEAT;
if (isOverheated != mIsOverheated) {
mIsOverheated = isOverheated;
fireIsOverheatedChanged();
}
fireBatteryLevelChanged(); fireBatteryLevelChanged();
} else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) { } else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) {
updatePowerSave(); updatePowerSave();
@@ -292,6 +304,10 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
return mPluggedChargingSource == BatteryManager.BATTERY_PLUGGED_WIRELESS; return mPluggedChargingSource == BatteryManager.BATTERY_PLUGGED_WIRELESS;
} }
public boolean isOverheated() {
return mIsOverheated;
}
@Override @Override
public void getEstimatedTimeRemainingString(EstimateFetchCompletion completion) { public void getEstimatedTimeRemainingString(EstimateFetchCompletion completion) {
// Need to fetch or refresh the estimate, but it may involve binder calls so offload the // 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 @Override
public void dispatchDemoCommand(String command, Bundle args) { public void dispatchDemoCommand(String command, Bundle args) {
if (!mDemoModeController.isInDemoMode()) { if (!mDemoModeController.isInDemoMode()) {
@@ -412,6 +437,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
String plugged = args.getString("plugged"); String plugged = args.getString("plugged");
String powerSave = args.getString("powersave"); String powerSave = args.getString("powersave");
String present = args.getString("present"); String present = args.getString("present");
String overheated = args.getString("overheated");
if (level != null) { if (level != null) {
mLevel = Math.min(Math.max(Integer.parseInt(level), 0), 100); 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"); mStateUnknown = !present.equals("true");
fireBatteryUnknownStateChanged(); fireBatteryUnknownStateChanged();
} }
if (overheated != null) {
mIsOverheated = overheated.equals("true");
fireIsOverheatedChanged();
}
fireBatteryLevelChanged(); fireBatteryLevelChanged();
} }

View File

@@ -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!
}

View File

@@ -35,6 +35,8 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher; 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.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService;
@@ -59,6 +61,7 @@ public class BatteryMeterViewControllerTest extends SysuiTestCase {
private Handler mHandler; private Handler mHandler;
@Mock @Mock
private ContentResolver mContentResolver; private ContentResolver mContentResolver;
private FakeFeatureFlags mFeatureFlags;
@Mock @Mock
private BatteryController mBatteryController; private BatteryController mBatteryController;
@@ -71,19 +74,13 @@ public class BatteryMeterViewControllerTest extends SysuiTestCase {
when(mBatteryMeterView.getContext()).thenReturn(mContext); when(mBatteryMeterView.getContext()).thenReturn(mContext);
when(mBatteryMeterView.getResources()).thenReturn(mContext.getResources()); when(mBatteryMeterView.getResources()).thenReturn(mContext.getResources());
mController = new BatteryMeterViewController( mFeatureFlags = new FakeFeatureFlags();
mBatteryMeterView, mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, false);
mConfigurationController,
mTunerService,
mBroadcastDispatcher,
mHandler,
mContentResolver,
mBatteryController
);
} }
@Test @Test
public void onViewAttached_callbacksRegistered() { public void onViewAttached_callbacksRegistered() {
initController();
mController.onViewAttached(); mController.onViewAttached();
verify(mConfigurationController).addCallback(any()); verify(mConfigurationController).addCallback(any());
@@ -101,6 +98,7 @@ public class BatteryMeterViewControllerTest extends SysuiTestCase {
@Test @Test
public void onViewDetached_callbacksUnregistered() { public void onViewDetached_callbacksUnregistered() {
initController();
// Set everything up first. // Set everything up first.
mController.onViewAttached(); mController.onViewAttached();
@@ -114,6 +112,7 @@ public class BatteryMeterViewControllerTest extends SysuiTestCase {
@Test @Test
public void ignoreTunerUpdates_afterOnViewAttached_callbackUnregistered() { public void ignoreTunerUpdates_afterOnViewAttached_callbackUnregistered() {
initController();
// Start out receiving tuner updates // Start out receiving tuner updates
mController.onViewAttached(); mController.onViewAttached();
@@ -124,10 +123,43 @@ public class BatteryMeterViewControllerTest extends SysuiTestCase {
@Test @Test
public void ignoreTunerUpdates_beforeOnViewAttached_callbackNeverRegistered() { public void ignoreTunerUpdates_beforeOnViewAttached_callbackNeverRegistered() {
initController();
mController.ignoreTunerUpdates(); mController.ignoreTunerUpdates();
mController.onViewAttached(); mController.onViewAttached();
verify(mTunerService, never()).addTunable(any(), any()); 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
);
}
} }

View File

@@ -17,7 +17,9 @@ package com.android.systemui.battery
import android.testing.AndroidTestingRunner import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper import android.testing.TestableLooper.RunWithLooper
import android.widget.ImageView
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.battery.BatteryMeterView.BatteryEstimateFetcher import com.android.systemui.battery.BatteryMeterView.BatteryEstimateFetcher
import com.android.systemui.statusbar.policy.BatteryController.EstimateFetchCompletion import com.android.systemui.statusbar.policy.BatteryController.EstimateFetchCompletion
@@ -58,6 +60,182 @@ class BatteryMeterViewTest : SysuiTestCase() {
// No assert needed // 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 { private class Fetcher : BatteryEstimateFetcher {
override fun fetchBatteryTimeRemainingEstimate( override fun fetchBatteryTimeRemainingEstimate(
completion: EstimateFetchCompletion) { completion: EstimateFetchCompletion) {

View File

@@ -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)
}
}

View File

@@ -221,4 +221,33 @@ public class BatteryControllerTest extends SysuiTestCase {
Assert.assertFalse(mBatteryController.isChargingSourceDock()); 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());
}
} }