diff options
13 files changed, 398 insertions, 79 deletions
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java index bc40903d88e4..f9bb90e9616a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java @@ -24,6 +24,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; @@ -49,14 +50,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) { @@ -64,6 +67,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 000000000000..8b2ba12fe0be --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_battery_unknown.xml @@ -0,0 +1,24 @@ +<!-- + 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="12dp" + android:height="24dp" + android:viewportWidth="12.0" + android:viewportHeight="24.0"> + <path + android:pathData="M10.404,2.4L8.4,2.4L8.4,0L3.6,0L3.6,2.4L1.596,2.4C0.72,2.4 0,3.12 0,3.996L0,22.392C0,23.28 0.72,24 1.596,24L10.392,24C11.28,24 12,23.28 12,22.404L12,3.996C12,3.12 11.28,2.4 10.404,2.4ZM7.14,19.14L4.86,19.14L4.86,16.86L7.14,16.86L7.14,19.14ZM8.76,12.828C8.76,12.828 8.304,13.332 7.956,13.68C7.38,14.256 6.96,15.06 6.96,15.6L5.04,15.6C5.04,14.604 5.592,13.776 6.156,13.2L7.272,12.072C7.596,11.748 7.8,11.292 7.8,10.8C7.8,9.804 6.996,9 6,9C5.004,9 4.2,9.804 4.2,10.8L2.4,10.8C2.4,8.808 4.008,7.2 6,7.2C7.992,7.2 9.6,8.808 9.6,10.8C9.6,11.592 9.276,12.312 8.76,12.828L8.76,12.828Z" + android:fillColor="#ffffff" /> +</vector> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 57ce0b5da7c1..1a72fc231815 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -568,4 +568,9 @@ <!-- The duraction of the hide animation for the volume dialog in milliseconds --> <integer name="config_dialogHideAnimationDurationMs">250</integer> + + <!-- Whether or not to show a notification for an unknown battery state --> + <bool name="config_showNotificationForUnknownBatteryState">false</bool> + <!-- content URL in a notification when ACTION_BATTERY_CHANGED.EXTRA_PRESENT field is false --> + <string translatable="false" name="config_batteryStateUnknownUrl"></string> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 2be89c1dff63..d5c98233b952 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -439,6 +439,8 @@ <string name="accessibility_battery_three_bars">Battery three bars.</string> <!-- Content description of the battery when it is full for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_battery_full">Battery full.</string> + <!-- Content description of the battery when battery state is unknown for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> + <string name="accessibility_battery_unknown">Battery percentage unknown.</string> <!-- Content description of the phone signal when no signal for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_no_phone">No phone.</string> @@ -2862,4 +2864,10 @@ <!-- Status for conversation without interaction data [CHAR LIMIT=120] --> <string name="basic_status" translatable="false">Open conversation</string> + <!-- Title to display in a notification when ACTION_BATTERY_CHANGED.EXTRA_PRESENT field is false + [CHAR LIMIT=NONE] --> + <string name="battery_state_unknown_notification_title">Problem reading your battery meter</string> + <!-- Text to display in a notification when ACTION_BATTERY_CHANGED.EXTRA_PRESENT field is false + [CHAR LIMIT=NONE] --> + <string name="battery_state_unknown_notification_text">Tap for more information</string> </resources> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 9beb7db40096..2e845fbb21dd 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -1717,7 +1717,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(); @@ -2621,6 +2621,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) { @@ -2637,6 +2639,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 521bb8d58c2b..caaee5fd3f37 100644 --- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java +++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java @@ -31,6 +31,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; @@ -91,12 +92,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; @@ -341,6 +345,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; } @@ -381,9 +390,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 @@ -409,6 +422,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. */ @@ -449,6 +488,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) { @@ -458,8 +501,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 708002d5b946..1f41038c260f 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 a59ff38896dc..a252a7a12274 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -121,6 +121,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; @@ -389,86 +390,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) { - 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) { - 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) { - 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) { + 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) { + 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); } } @@ -647,6 +665,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()); } @@ -685,6 +704,7 @@ public class KeyguardIndicationController implements StateListener, mChargingWattage = status.maxChargingWattage; mChargingSpeed = status.getChargingSpeed(mContext); mBatteryLevel = status.level; + 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 06e4731265e3..08e70a97e0ca 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 57ac85e1e86d..d8710bf85a6c 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; @@ -75,6 +77,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; @@ -138,6 +141,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 @@ -180,6 +184,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(); @@ -328,6 +339,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(); @@ -346,6 +366,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); } @@ -356,6 +377,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 000000000000..92e5b78f776a --- /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 e0d268127d90..f9b14ba7cd31 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -494,7 +494,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(); @@ -506,7 +506,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(); 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 000000000000..dcd57f137d71 --- /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 <T> anyObject(): T { + return Mockito.anyObject<T>() +} + +@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 |