diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
index ce60fafaa31b7..b3205d7563b2c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
@@ -25,6 +25,7 @@ import static android.os.BatteryManager.EXTRA_LEVEL;
import static android.os.BatteryManager.EXTRA_MAX_CHARGING_CURRENT;
import static android.os.BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE;
import static android.os.BatteryManager.EXTRA_PLUGGED;
+import static android.os.BatteryManager.EXTRA_PRESENT;
import static android.os.BatteryManager.EXTRA_STATUS;
import android.content.Context;
@@ -50,14 +51,16 @@ public class BatteryStatus {
public final int plugged;
public final int health;
public final int maxChargingWattage;
+ public final boolean present;
public BatteryStatus(int status, int level, int plugged, int health,
- int maxChargingWattage) {
+ int maxChargingWattage, boolean present) {
this.status = status;
this.level = level;
this.plugged = plugged;
this.health = health;
this.maxChargingWattage = maxChargingWattage;
+ this.present = present;
}
public BatteryStatus(Intent batteryChangedIntent) {
@@ -65,6 +68,7 @@ public class BatteryStatus {
plugged = batteryChangedIntent.getIntExtra(EXTRA_PLUGGED, 0);
level = batteryChangedIntent.getIntExtra(EXTRA_LEVEL, 0);
health = batteryChangedIntent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN);
+ present = batteryChangedIntent.getBooleanExtra(EXTRA_PRESENT, true);
final int maxChargingMicroAmp = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT,
-1);
diff --git a/packages/SystemUI/res/drawable/ic_battery_unknown.xml b/packages/SystemUI/res/drawable/ic_battery_unknown.xml
new file mode 100644
index 0000000000000..8b2ba12fe0bef
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_battery_unknown.xml
@@ -0,0 +1,24 @@
+
+
+
+
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 5e5df6bfd9880..9530a74d0ece2 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -579,4 +579,9 @@
320
- 1.25
+
+
+ false
+
+
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 824521ecd1e76..174f5c737d147 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -437,6 +437,8 @@
Battery three bars.
Battery full.
+
+ Battery percentage unknown.
No phone.
@@ -2870,4 +2872,11 @@
Couldn\'t connect. Try again.
Pair new device
+
+
+ Problem reading your battery meter
+
+ Tap for more information
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 60cd24019b973..deaa4255c2f1e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1696,7 +1696,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
// Take a guess at initial SIM state, battery status and PLMN until we get an update
- mBatteryStatus = new BatteryStatus(BATTERY_STATUS_UNKNOWN, 100, 0, 0, 0);
+ mBatteryStatus = new BatteryStatus(BATTERY_STATUS_UNKNOWN, 100, 0, 0, 0, true);
// Watch for interesting updates
final IntentFilter filter = new IntentFilter();
@@ -2563,6 +2563,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
final boolean wasPluggedIn = old.isPluggedIn();
final boolean stateChangedWhilePluggedIn = wasPluggedIn && nowPluggedIn
&& (old.status != current.status);
+ final boolean nowPresent = current.present;
+ final boolean wasPresent = old.present;
// change in plug state is always interesting
if (wasPluggedIn != nowPluggedIn || stateChangedWhilePluggedIn) {
@@ -2584,6 +2586,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
return true;
}
+ // Battery either showed up or disappeared
+ if (wasPresent != nowPresent) {
+ return true;
+ }
+
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index a46ab3a9e35b6..fc30be416c0f9 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -33,6 +33,7 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.database.ContentObserver;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Handler;
import android.provider.Settings;
@@ -95,12 +96,15 @@ public class BatteryMeterView extends LinearLayout implements
private int mTextColor;
private int mLevel;
private int mShowPercentMode = MODE_DEFAULT;
- private boolean mForceShowPercent;
private boolean mShowPercentAvailable;
// Some places may need to show the battery conditionally, and not obey the tuner
private boolean mIgnoreTunerUpdates;
private boolean mIsSubscribedForTunerUpdates;
private boolean mCharging;
+ // 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
+ private Drawable mUnknownStateDrawable;
private DualToneHandler mDualToneHandler;
private int mUser;
@@ -350,6 +354,11 @@ public class BatteryMeterView extends LinearLayout implements
}
private void updatePercentText() {
+ if (mBatteryStateUnknown) {
+ setContentDescription(getContext().getString(R.string.accessibility_battery_unknown));
+ return;
+ }
+
if (mBatteryController == null) {
return;
}
@@ -390,9 +399,13 @@ public class BatteryMeterView extends LinearLayout implements
final boolean systemSetting = 0 != whitelistIpcs(() -> Settings.System
.getIntForUser(getContext().getContentResolver(),
SHOW_BATTERY_PERCENT, 0, mUser));
+ boolean shouldShow =
+ (mShowPercentAvailable && systemSetting && mShowPercentMode != MODE_OFF)
+ || mShowPercentMode == MODE_ON
+ || mShowPercentMode == MODE_ESTIMATE;
+ shouldShow = shouldShow && !mBatteryStateUnknown;
- if ((mShowPercentAvailable && systemSetting && mShowPercentMode != MODE_OFF)
- || mShowPercentMode == MODE_ON || mShowPercentMode == MODE_ESTIMATE) {
+ if (shouldShow) {
if (!showing) {
mBatteryPercentView = loadPercentView();
if (mPercentageStyleId != 0) { // Only set if specified as attribute
@@ -418,6 +431,32 @@ public class BatteryMeterView extends LinearLayout implements
scaleBatteryMeterViews();
}
+ private Drawable getUnknownStateDrawable() {
+ if (mUnknownStateDrawable == null) {
+ mUnknownStateDrawable = mContext.getDrawable(R.drawable.ic_battery_unknown);
+ mUnknownStateDrawable.setTint(mTextColor);
+ }
+
+ return mUnknownStateDrawable;
+ }
+
+ @Override
+ public void onBatteryUnknownStateChanged(boolean isUnknown) {
+ if (mBatteryStateUnknown == isUnknown) {
+ return;
+ }
+
+ mBatteryStateUnknown = isUnknown;
+
+ if (mBatteryStateUnknown) {
+ mBatteryIconView.setImageDrawable(getUnknownStateDrawable());
+ } else {
+ mBatteryIconView.setImageDrawable(mDrawable);
+ }
+
+ updateShowPercent();
+ }
+
/**
* Looks up the scale factor for status bar icons and scales the battery view by that amount.
*/
@@ -458,6 +497,10 @@ public class BatteryMeterView extends LinearLayout implements
if (mBatteryPercentView != null) {
mBatteryPercentView.setTextColor(singleToneColor);
}
+
+ if (mUnknownStateDrawable != null) {
+ mUnknownStateDrawable.setTint(singleToneColor);
+ }
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -467,8 +510,8 @@ public class BatteryMeterView extends LinearLayout implements
pw.println(" mDrawable.getPowerSave: " + powerSave);
pw.println(" mBatteryPercentView.getText(): " + percent);
pw.println(" mTextColor: #" + Integer.toHexString(mTextColor));
+ pw.println(" mBatteryStateUnknown: " + mBatteryStateUnknown);
pw.println(" mLevel: " + mLevel);
- pw.println(" mForceShowPercent: " + mForceShowPercent);
}
private final class SettingObserver extends ContentObserver {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
index 708002d5b9460..1f41038c260fa 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
@@ -32,6 +32,7 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpHandler;
import com.android.systemui.dump.LogBufferFreezer;
import com.android.systemui.dump.SystemUIAuxiliaryDumpService;
+import com.android.systemui.statusbar.policy.BatteryStateNotifier;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -44,18 +45,21 @@ public class SystemUIService extends Service {
private final DumpHandler mDumpHandler;
private final BroadcastDispatcher mBroadcastDispatcher;
private final LogBufferFreezer mLogBufferFreezer;
+ private final BatteryStateNotifier mBatteryStateNotifier;
@Inject
public SystemUIService(
@Main Handler mainHandler,
DumpHandler dumpHandler,
BroadcastDispatcher broadcastDispatcher,
- LogBufferFreezer logBufferFreezer) {
+ LogBufferFreezer logBufferFreezer,
+ BatteryStateNotifier batteryStateNotifier) {
super();
mMainHandler = mainHandler;
mDumpHandler = dumpHandler;
mBroadcastDispatcher = broadcastDispatcher;
mLogBufferFreezer = logBufferFreezer;
+ mBatteryStateNotifier = batteryStateNotifier;
}
@Override
@@ -68,6 +72,11 @@ public class SystemUIService extends Service {
// Finish initializing dump logic
mLogBufferFreezer.attach(mBroadcastDispatcher);
+ // If configured, set up a battery notification
+ if (getResources().getBoolean(R.bool.config_showNotificationForUnknownBatteryState)) {
+ mBatteryStateNotifier.startListening();
+ }
+
// For debugging RescueParty
if (Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.crash_sysui", false)) {
throw new RuntimeException();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 39d2f71e7e0bd..155ec5ea72de2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -123,6 +123,7 @@ public class KeyguardIndicationController implements StateListener,
private int mChargingSpeed;
private int mChargingWattage;
private int mBatteryLevel;
+ private boolean mBatteryPresent = true;
private long mChargingTimeRemaining;
private float mDisclosureMaxAlpha;
private String mMessageToShowOnScreenOn;
@@ -391,86 +392,103 @@ public class KeyguardIndicationController implements StateListener,
mWakeLock.setAcquired(false);
}
- if (mVisible) {
- // Walk down a precedence-ordered list of what indication
- // should be shown based on user or device state
- if (mDozing) {
- // When dozing we ignore any text color and use white instead, because
- // colors can be hard to read in low brightness.
- mTextView.setTextColor(Color.WHITE);
- if (!TextUtils.isEmpty(mTransientIndication)) {
- mTextView.switchIndication(mTransientIndication);
- } else if (!TextUtils.isEmpty(mAlignmentIndication)) {
- mTextView.switchIndication(mAlignmentIndication);
- mTextView.setTextColor(mContext.getColor(R.color.misalignment_text_color));
- } else if (mPowerPluggedIn || mEnableBatteryDefender) {
- String indication = computePowerIndication();
- if (animate) {
- animateText(mTextView, indication);
- } else {
- mTextView.switchIndication(indication);
- }
- } else {
- String percentage = NumberFormat.getPercentInstance()
- .format(mBatteryLevel / 100f);
- mTextView.switchIndication(percentage);
- }
- return;
- }
+ if (!mVisible) {
+ return;
+ }
- int userId = KeyguardUpdateMonitor.getCurrentUser();
- String trustGrantedIndication = getTrustGrantedIndication();
- String trustManagedIndication = getTrustManagedIndication();
+ // A few places might need to hide the indication, so always start by making it visible
+ mIndicationArea.setVisibility(View.VISIBLE);
- String powerIndication = null;
- if (mPowerPluggedIn || mEnableBatteryDefender) {
- powerIndication = computePowerIndication();
- }
-
- boolean isError = false;
- if (!mKeyguardUpdateMonitor.isUserUnlocked(userId)) {
- mTextView.switchIndication(com.android.internal.R.string.lockscreen_storage_locked);
- } else if (!TextUtils.isEmpty(mTransientIndication)) {
- if (powerIndication != null && !mTransientIndication.equals(powerIndication)) {
- String indication = mContext.getResources().getString(
- R.string.keyguard_indication_trust_unlocked_plugged_in,
- mTransientIndication, powerIndication);
- mTextView.switchIndication(indication);
- } else {
- mTextView.switchIndication(mTransientIndication);
- }
- isError = mTransientTextIsError;
- } else if (!TextUtils.isEmpty(trustGrantedIndication)
- && mKeyguardUpdateMonitor.getUserHasTrust(userId)) {
- if (powerIndication != null) {
- String indication = mContext.getResources().getString(
- R.string.keyguard_indication_trust_unlocked_plugged_in,
- trustGrantedIndication, powerIndication);
- mTextView.switchIndication(indication);
- } else {
- mTextView.switchIndication(trustGrantedIndication);
- }
+ // Walk down a precedence-ordered list of what indication
+ // should be shown based on user or device state
+ if (mDozing) {
+ // When dozing we ignore any text color and use white instead, because
+ // colors can be hard to read in low brightness.
+ mTextView.setTextColor(Color.WHITE);
+ if (!TextUtils.isEmpty(mTransientIndication)) {
+ mTextView.switchIndication(mTransientIndication);
+ } else if (!mBatteryPresent) {
+ // If there is no battery detected, hide the indication and bail
+ mIndicationArea.setVisibility(View.GONE);
} else if (!TextUtils.isEmpty(mAlignmentIndication)) {
mTextView.switchIndication(mAlignmentIndication);
- isError = true;
+ mTextView.setTextColor(mContext.getColor(R.color.misalignment_text_color));
} else if (mPowerPluggedIn || mEnableBatteryDefender) {
- if (DEBUG_CHARGING_SPEED) {
- powerIndication += ", " + (mChargingWattage / 1000) + " mW";
- }
+ String indication = computePowerIndication();
if (animate) {
- animateText(mTextView, powerIndication);
+ animateText(mTextView, indication);
} else {
- mTextView.switchIndication(powerIndication);
+ mTextView.switchIndication(indication);
}
- } else if (!TextUtils.isEmpty(trustManagedIndication)
- && mKeyguardUpdateMonitor.getUserTrustIsManaged(userId)
- && !mKeyguardUpdateMonitor.getUserHasTrust(userId)) {
- mTextView.switchIndication(trustManagedIndication);
} else {
- mTextView.switchIndication(mRestingIndication);
+ String percentage = NumberFormat.getPercentInstance()
+ .format(mBatteryLevel / 100f);
+ mTextView.switchIndication(percentage);
}
- mTextView.setTextColor(isError ? Utils.getColorError(mContext)
- : mInitialTextColorState);
+ return;
+ }
+
+ int userId = KeyguardUpdateMonitor.getCurrentUser();
+ String trustGrantedIndication = getTrustGrantedIndication();
+ String trustManagedIndication = getTrustManagedIndication();
+
+ String powerIndication = null;
+ if (mPowerPluggedIn || mEnableBatteryDefender) {
+ powerIndication = computePowerIndication();
+ }
+
+ // Some cases here might need to hide the indication (if the battery is not present)
+ boolean hideIndication = false;
+ boolean isError = false;
+ if (!mKeyguardUpdateMonitor.isUserUnlocked(userId)) {
+ mTextView.switchIndication(com.android.internal.R.string.lockscreen_storage_locked);
+ } else if (!TextUtils.isEmpty(mTransientIndication)) {
+ if (powerIndication != null && !mTransientIndication.equals(powerIndication)) {
+ String indication = mContext.getResources().getString(
+ R.string.keyguard_indication_trust_unlocked_plugged_in,
+ mTransientIndication, powerIndication);
+ mTextView.switchIndication(indication);
+ hideIndication = !mBatteryPresent;
+ } else {
+ mTextView.switchIndication(mTransientIndication);
+ }
+ isError = mTransientTextIsError;
+ } else if (!TextUtils.isEmpty(trustGrantedIndication)
+ && mKeyguardUpdateMonitor.getUserHasTrust(userId)) {
+ if (powerIndication != null) {
+ String indication = mContext.getResources().getString(
+ R.string.keyguard_indication_trust_unlocked_plugged_in,
+ trustGrantedIndication, powerIndication);
+ mTextView.switchIndication(indication);
+ hideIndication = !mBatteryPresent;
+ } else {
+ mTextView.switchIndication(trustGrantedIndication);
+ }
+ } else if (!TextUtils.isEmpty(mAlignmentIndication)) {
+ mTextView.switchIndication(mAlignmentIndication);
+ isError = true;
+ hideIndication = !mBatteryPresent;
+ } else if (mPowerPluggedIn || mEnableBatteryDefender) {
+ if (DEBUG_CHARGING_SPEED) {
+ powerIndication += ", " + (mChargingWattage / 1000) + " mW";
+ }
+ if (animate) {
+ animateText(mTextView, powerIndication);
+ } else {
+ mTextView.switchIndication(powerIndication);
+ }
+ hideIndication = !mBatteryPresent;
+ } else if (!TextUtils.isEmpty(trustManagedIndication)
+ && mKeyguardUpdateMonitor.getUserTrustIsManaged(userId)
+ && !mKeyguardUpdateMonitor.getUserHasTrust(userId)) {
+ mTextView.switchIndication(trustManagedIndication);
+ } else {
+ mTextView.switchIndication(mRestingIndication);
+ }
+ mTextView.setTextColor(isError ? Utils.getColorError(mContext)
+ : mInitialTextColorState);
+ if (hideIndication) {
+ mIndicationArea.setVisibility(View.GONE);
}
}
@@ -654,6 +672,7 @@ public class KeyguardIndicationController implements StateListener,
pw.println(" mMessageToShowOnScreenOn: " + mMessageToShowOnScreenOn);
pw.println(" mDozing: " + mDozing);
pw.println(" mBatteryLevel: " + mBatteryLevel);
+ pw.println(" mBatteryPresent: " + mBatteryPresent);
pw.println(" mTextView.getText(): " + (mTextView == null ? null : mTextView.getText()));
pw.println(" computePowerIndication(): " + computePowerIndication());
}
@@ -694,6 +713,7 @@ public class KeyguardIndicationController implements StateListener,
mBatteryLevel = status.level;
mBatteryOverheated = status.isOverheated();
mEnableBatteryDefender = mBatteryOverheated && status.isPluggedIn();
+ mBatteryPresent = status.present;
try {
mChargingTimeRemaining = mPowerPluggedIn
? mBatteryInfo.computeChargeTimeRemaining() : -1;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index e5a46797d0356..5009fce5216d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -97,6 +97,9 @@ public interface BatteryController extends DemoMode, Dumpable,
default void onPowerSaveChanged(boolean isPowerSave) {
}
+ default void onBatteryUnknownStateChanged(boolean isUnknown) {
+ }
+
default void onReverseChanged(boolean isReverse, int level, String name) {
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index d30f01a658f6b..ea79c246f0145 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.policy;
+import static android.os.BatteryManager.EXTRA_PRESENT;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -70,6 +72,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
protected int mLevel;
protected boolean mPluggedIn;
protected boolean mCharging;
+ private boolean mStateUnknown = false;
private boolean mCharged;
protected boolean mPowerSave;
private boolean mAodPowerSave;
@@ -126,6 +129,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
pw.print(" mCharging="); pw.println(mCharging);
pw.print(" mCharged="); pw.println(mCharged);
pw.print(" mPowerSave="); pw.println(mPowerSave);
+ pw.print(" mStateUnknown="); pw.println(mStateUnknown);
}
@Override
@@ -139,8 +143,11 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
mChangeCallbacks.add(cb);
}
if (!mHasReceivedBattery) return;
+
+ // Make sure new callbacks get the correct initial state
cb.onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
cb.onPowerSaveChanged(mPowerSave);
+ cb.onBatteryUnknownStateChanged(mStateUnknown);
}
@Override
@@ -168,6 +175,13 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
mWirelessCharging = mCharging && intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0)
== BatteryManager.BATTERY_PLUGGED_WIRELESS;
+ boolean present = intent.getBooleanExtra(EXTRA_PRESENT, true);
+ boolean unknown = !present;
+ if (unknown != mStateUnknown) {
+ mStateUnknown = unknown;
+ fireBatteryUnknownStateChanged();
+ }
+
fireBatteryLevelChanged();
} else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) {
updatePowerSave();
@@ -316,6 +330,15 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
}
}
+ private void fireBatteryUnknownStateChanged() {
+ synchronized (mChangeCallbacks) {
+ final int n = mChangeCallbacks.size();
+ for (int i = 0; i < n; i++) {
+ mChangeCallbacks.get(i).onBatteryUnknownStateChanged(mStateUnknown);
+ }
+ }
+ }
+
private void firePowerSaveChanged() {
synchronized (mChangeCallbacks) {
final int N = mChangeCallbacks.size();
@@ -340,6 +363,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
String level = args.getString("level");
String plugged = args.getString("plugged");
String powerSave = args.getString("powersave");
+ String present = args.getString("present");
if (level != null) {
mLevel = Math.min(Math.max(Integer.parseInt(level), 0), 100);
}
@@ -350,6 +374,10 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
mPowerSave = powerSave.equals("true");
firePowerSaveChanged();
}
+ if (present != null) {
+ mStateUnknown = !present.equals("true");
+ fireBatteryUnknownStateChanged();
+ }
fireBatteryLevelChanged();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt
new file mode 100644
index 0000000000000..92e5b78f776a4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import com.android.systemui.R
+import com.android.systemui.util.concurrency.DelayableExecutor
+import javax.inject.Inject
+
+/**
+ * Listens for important battery states and sends non-dismissible system notifications if there is a
+ * problem
+ */
+class BatteryStateNotifier @Inject constructor(
+ val controller: BatteryController,
+ val noMan: NotificationManager,
+ val delayableExecutor: DelayableExecutor,
+ val context: Context
+) : BatteryController.BatteryStateChangeCallback {
+ var stateUnknown = false
+
+ fun startListening() {
+ controller.addCallback(this)
+ }
+
+ fun stopListening() {
+ controller.removeCallback(this)
+ }
+
+ override fun onBatteryUnknownStateChanged(isUnknown: Boolean) {
+ stateUnknown = isUnknown
+ if (stateUnknown) {
+ val channel = NotificationChannel("battery_status", "Battery status",
+ NotificationManager.IMPORTANCE_DEFAULT)
+ noMan.createNotificationChannel(channel)
+
+ val intent = Intent(Intent.ACTION_VIEW,
+ Uri.parse(context.getString(R.string.config_batteryStateUnknownUrl)))
+ val pi = PendingIntent.getActivity(context, 0, intent, 0)
+
+ val builder = Notification.Builder(context, channel.id)
+ .setAutoCancel(false)
+ .setContentTitle(
+ context.getString(R.string.battery_state_unknown_notification_title))
+ .setContentText(
+ context.getString(R.string.battery_state_unknown_notification_text))
+ .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
+ .setContentIntent(pi)
+ .setAutoCancel(true)
+ .setOngoing(true)
+
+ noMan.notify(TAG, ID, builder.build())
+ } else {
+ scheduleNotificationCancel()
+ }
+ }
+
+ private fun scheduleNotificationCancel() {
+ val r = {
+ if (!stateUnknown) {
+ noMan.cancel(ID)
+ }
+ }
+ delayableExecutor.executeDelayed(r, DELAY_MILLIS)
+ }
+}
+
+private const val TAG = "BatteryStateNotifier"
+private const val ID = 666
+private const val DELAY_MILLIS: Long = 4 * 60 * 60 * 1000
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 052f3382099f6..91144be7347d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -495,7 +495,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
createController();
BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
80 /* level */, BatteryManager.BATTERY_PLUGGED_WIRELESS, 100 /* health */,
- 0 /* maxChargingWattage */);
+ 0 /* maxChargingWattage */, true /* present */);
mController.getKeyguardCallback().onRefreshBatteryInfo(status);
verify(mIBatteryStats).computeChargeTimeRemaining();
@@ -507,7 +507,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
createController();
BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
80 /* level */, 0 /* plugged */, 100 /* health */,
- 0 /* maxChargingWattage */);
+ 0 /* maxChargingWattage */, true /* present */);
mController.getKeyguardCallback().onRefreshBatteryInfo(status);
verify(mIBatteryStats, never()).computeChargeTimeRemaining();
@@ -553,7 +553,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
createController();
BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
80 /* level */, BatteryManager.BATTERY_PLUGGED_AC,
- BatteryManager.BATTERY_HEALTH_OVERHEAT, 0 /* maxChargingWattage */);
+ BatteryManager.BATTERY_HEALTH_OVERHEAT, 0 /* maxChargingWattage */,
+ true /* present */);
mController.getKeyguardCallback().onRefreshBatteryInfo(status);
mController.setVisible(true);
@@ -569,7 +570,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
createController();
BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_DISCHARGING,
80 /* level */, BatteryManager.BATTERY_PLUGGED_AC,
- BatteryManager.BATTERY_HEALTH_OVERHEAT, 0 /* maxChargingWattage */);
+ BatteryManager.BATTERY_HEALTH_OVERHEAT, 0 /* maxChargingWattage */,
+ true /* present */);
mController.getKeyguardCallback().onRefreshBatteryInfo(status);
mController.setVisible(true);
@@ -585,7 +587,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
createController();
BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
100 /* level */, BatteryManager.BATTERY_PLUGGED_AC,
- BatteryManager.BATTERY_HEALTH_OVERHEAT, 0 /* maxChargingWattage */);
+ BatteryManager.BATTERY_HEALTH_OVERHEAT, 0 /* maxChargingWattage */,
+ true /* present */);
mController.getKeyguardCallback().onRefreshBatteryInfo(status);
mController.setVisible(true);
@@ -599,7 +602,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
createController();
BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_DISCHARGING,
90 /* level */, 0 /* plugged */, BatteryManager.BATTERY_HEALTH_OVERHEAT,
- 0 /* maxChargingWattage */);
+ 0 /* maxChargingWattage */, true /* present */);
mController.getKeyguardCallback().onRefreshBatteryInfo(status);
mController.setDozing(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index eca48c8c2ee12..e985a61c7a56e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -16,7 +16,11 @@
package com.android.systemui.statusbar.policy;
+import static android.os.BatteryManager.EXTRA_PRESENT;
+
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Intent;
@@ -30,6 +34,7 @@ import android.testing.TestableLooper;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.power.EnhancedEstimates;
+import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
import org.junit.Assert;
import org.junit.Before;
@@ -93,4 +98,36 @@ public class BatteryControllerTest extends SysuiTestCase {
Assert.assertFalse(mBatteryController.isAodPowerSave());
}
+ @Test
+ public void testBatteryPresentState_notPresent() {
+ // GIVEN a battery state callback listening for changes
+ BatteryStateChangeCallback cb = mock(BatteryStateChangeCallback.class);
+ mBatteryController.addCallback(cb);
+
+ // WHEN the state of the battery becomes unknown
+ Intent i = new Intent(Intent.ACTION_BATTERY_CHANGED);
+ i.putExtra(EXTRA_PRESENT, false);
+ mBatteryController.onReceive(getContext(), i);
+
+ // THEN the callback is notified
+ verify(cb, atLeastOnce()).onBatteryUnknownStateChanged(true);
+ }
+
+ @Test
+ public void testBatteryPresentState_callbackAddedAfterStateChange() {
+ // GIVEN a battery state callback
+ BatteryController.BatteryStateChangeCallback cb =
+ mock(BatteryController.BatteryStateChangeCallback.class);
+
+ // GIVEN the state has changed before adding a new callback
+ Intent i = new Intent(Intent.ACTION_BATTERY_CHANGED);
+ i.putExtra(EXTRA_PRESENT, false);
+ mBatteryController.onReceive(getContext(), i);
+
+ // WHEN a callback is added
+ mBatteryController.addCallback(cb);
+
+ // THEN it is informed about the battery state
+ verify(cb, atLeastOnce()).onBatteryUnknownStateChanged(true);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt
new file mode 100644
index 0000000000000..dcd57f137d713
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import android.app.NotificationManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+
+import androidx.test.filters.SmallTest
+
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+private fun anyObject(): T {
+ return Mockito.anyObject()
+}
+
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper()
+@SmallTest
+class BatteryStateNotifierTest : SysuiTestCase() {
+ @Mock private lateinit var batteryController: BatteryController
+ @Mock private lateinit var noMan: NotificationManager
+
+ private val clock = FakeSystemClock()
+ private val executor = FakeExecutor(clock)
+
+ private lateinit var notifier: BatteryStateNotifier
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ notifier = BatteryStateNotifier(batteryController, noMan, executor, context)
+ notifier.startListening()
+
+ context.ensureTestableResources()
+ }
+
+ @Test
+ fun testNotifyWhenStateUnknown() {
+ notifier.onBatteryUnknownStateChanged(true)
+ verify(noMan).notify(anyString(), anyInt(), anyObject())
+ }
+
+ @Test
+ fun testCancelAfterDelay() {
+ notifier.onBatteryUnknownStateChanged(true)
+ notifier.onBatteryUnknownStateChanged(false)
+
+ clock.advanceTime(DELAY_MILLIS + 1)
+ verify(noMan).cancel(anyInt())
+ }
+}
+
+// From BatteryStateNotifier.kt
+private const val DELAY_MILLIS: Long = 40 * 60 * 60 * 1000