1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.lowlightclock; 18 19 import android.content.Context; 20 import android.content.res.Resources; 21 import android.os.BatteryManager; 22 import android.os.RemoteException; 23 import android.text.format.Formatter; 24 import android.util.Log; 25 26 import com.android.internal.app.IBatteryStats; 27 import com.android.internal.util.Preconditions; 28 import com.android.keyguard.KeyguardUpdateMonitor; 29 import com.android.keyguard.KeyguardUpdateMonitorCallback; 30 import com.android.settingslib.fuelgauge.BatteryStatus; 31 import com.android.systemui.dagger.qualifiers.Main; 32 import com.android.systemui.res.R; 33 34 import java.text.NumberFormat; 35 36 import javax.inject.Inject; 37 38 /** 39 * Provides charging status as a string to a registered callback such that it can be displayed to 40 * the user (e.g. on the low-light clock). 41 * TODO(b/223681352): Make this code shareable with {@link KeyguardIndicationController}. 42 */ 43 public class ChargingStatusProvider { 44 private static final String TAG = "ChargingStatusProvider"; 45 46 private final Resources mResources; 47 private final Context mContext; 48 private final IBatteryStats mBatteryInfo; 49 private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; 50 private final BatteryState mBatteryState = new BatteryState(); 51 // This callback is registered with KeyguardUpdateMonitor, which only keeps weak references to 52 // its callbacks. Therefore, an explicit reference needs to be kept here to avoid the 53 // callback being GC'd. 54 private ChargingStatusCallback mChargingStatusCallback; 55 56 private Callback mCallback; 57 58 @Inject ChargingStatusProvider( Context context, @Main Resources resources, IBatteryStats iBatteryStats, KeyguardUpdateMonitor keyguardUpdateMonitor)59 public ChargingStatusProvider( 60 Context context, 61 @Main Resources resources, 62 IBatteryStats iBatteryStats, 63 KeyguardUpdateMonitor keyguardUpdateMonitor) { 64 mContext = context; 65 mResources = resources; 66 mBatteryInfo = iBatteryStats; 67 mKeyguardUpdateMonitor = keyguardUpdateMonitor; 68 } 69 70 /** 71 * Start using the {@link ChargingStatusProvider}. 72 * @param callback A callback to be called when the charging status changes. 73 */ startUsing(Callback callback)74 public void startUsing(Callback callback) { 75 Preconditions.checkState( 76 mCallback == null, "ChargingStatusProvider already started!"); 77 mCallback = callback; 78 mChargingStatusCallback = new ChargingStatusCallback(); 79 mKeyguardUpdateMonitor.registerCallback(mChargingStatusCallback); 80 reportStatusToCallback(); 81 } 82 83 /** 84 * Stop using the {@link ChargingStatusProvider}. 85 */ stopUsing()86 public void stopUsing() { 87 mCallback = null; 88 89 if (mChargingStatusCallback != null) { 90 mKeyguardUpdateMonitor.removeCallback(mChargingStatusCallback); 91 mChargingStatusCallback = null; 92 } 93 } 94 computeChargingString()95 private String computeChargingString() { 96 if (!mBatteryState.isValid()) { 97 return null; 98 } 99 100 int chargingId; 101 102 if (mBatteryState.isBatteryDefender()) { 103 return mResources.getString( 104 R.string.keyguard_plugged_in_charging_limited, 105 mBatteryState.getBatteryLevelAsPercentage()); 106 } else if (mBatteryState.isPowerCharged()) { 107 return mResources.getString(R.string.keyguard_charged); 108 } 109 110 final long chargingTimeRemaining = mBatteryState.getChargingTimeRemaining(mBatteryInfo); 111 final boolean hasChargingTime = chargingTimeRemaining > 0; 112 if (mBatteryState.isPowerPluggedInWired()) { 113 switch (mBatteryState.getChargingSpeed(mContext)) { 114 case BatteryStatus.CHARGING_FAST: 115 chargingId = hasChargingTime 116 ? R.string.keyguard_indication_charging_time_fast 117 : R.string.keyguard_plugged_in_charging_fast; 118 break; 119 case BatteryStatus.CHARGING_SLOWLY: 120 chargingId = hasChargingTime 121 ? R.string.keyguard_indication_charging_time_slowly 122 : R.string.keyguard_plugged_in_charging_slowly; 123 break; 124 default: 125 chargingId = hasChargingTime 126 ? R.string.keyguard_indication_charging_time 127 : R.string.keyguard_plugged_in; 128 break; 129 } 130 } else if (mBatteryState.isPowerPluggedInWireless()) { 131 chargingId = hasChargingTime 132 ? R.string.keyguard_indication_charging_time_wireless 133 : R.string.keyguard_plugged_in_wireless; 134 } else if (mBatteryState.isPowerPluggedInDocked()) { 135 chargingId = hasChargingTime 136 ? R.string.keyguard_indication_charging_time_dock 137 : R.string.keyguard_plugged_in_dock; 138 } else { 139 chargingId = hasChargingTime 140 ? R.string.keyguard_indication_charging_time 141 : R.string.keyguard_plugged_in; 142 } 143 144 final String percentage = mBatteryState.getBatteryLevelAsPercentage(); 145 if (hasChargingTime) { 146 final String chargingTimeFormatted = 147 Formatter.formatShortElapsedTimeRoundingUpToMinutes( 148 mContext, chargingTimeRemaining); 149 return mResources.getString(chargingId, chargingTimeFormatted, 150 percentage); 151 } else { 152 return mResources.getString(chargingId, percentage); 153 } 154 } 155 reportStatusToCallback()156 private void reportStatusToCallback() { 157 if (mCallback != null) { 158 final boolean shouldShowStatus = 159 mBatteryState.isPowerPluggedIn() || mBatteryState.isBatteryDefenderEnabled(); 160 mCallback.onChargingStatusChanged(shouldShowStatus, computeChargingString()); 161 } 162 } 163 164 private class ChargingStatusCallback extends KeyguardUpdateMonitorCallback { 165 @Override onRefreshBatteryInfo(BatteryStatus status)166 public void onRefreshBatteryInfo(BatteryStatus status) { 167 mBatteryState.setBatteryStatus(status); 168 reportStatusToCallback(); 169 } 170 } 171 172 /*** 173 * A callback to be called when the charging status changes. 174 */ 175 public interface Callback { 176 /*** 177 * Called when the charging status changes. 178 * @param shouldShowStatus Whether or not to show a charging status message. 179 * @param statusMessage A charging status message. 180 */ onChargingStatusChanged(boolean shouldShowStatus, String statusMessage)181 void onChargingStatusChanged(boolean shouldShowStatus, String statusMessage); 182 } 183 184 /*** 185 * A wrapper around {@link BatteryStatus} for fetching various properties of the current 186 * battery and charging state. 187 */ 188 private static class BatteryState { 189 private BatteryStatus mBatteryStatus; 190 setBatteryStatus(BatteryStatus batteryStatus)191 public void setBatteryStatus(BatteryStatus batteryStatus) { 192 mBatteryStatus = batteryStatus; 193 } 194 isValid()195 public boolean isValid() { 196 return mBatteryStatus != null; 197 } 198 getChargingTimeRemaining(IBatteryStats batteryInfo)199 public long getChargingTimeRemaining(IBatteryStats batteryInfo) { 200 try { 201 return isPowerPluggedIn() ? batteryInfo.computeChargeTimeRemaining() : -1; 202 } catch (RemoteException e) { 203 Log.e(TAG, "Error calling IBatteryStats: ", e); 204 return -1; 205 } 206 } 207 isBatteryDefenderEnabled()208 public boolean isBatteryDefenderEnabled() { 209 return isValid() && mBatteryStatus.isPluggedIn() && isBatteryDefender(); 210 } 211 isBatteryDefender()212 public boolean isBatteryDefender() { 213 return isValid() && mBatteryStatus.isBatteryDefender(); 214 } 215 getBatteryLevel()216 public int getBatteryLevel() { 217 return isValid() ? mBatteryStatus.level : 0; 218 } 219 getChargingSpeed(Context context)220 public int getChargingSpeed(Context context) { 221 return isValid() ? mBatteryStatus.getChargingSpeed(context) : 0; 222 } 223 isPowerCharged()224 public boolean isPowerCharged() { 225 return isValid() && mBatteryStatus.isCharged(); 226 } 227 isPowerPluggedIn()228 public boolean isPowerPluggedIn() { 229 return isValid() && mBatteryStatus.isPluggedIn() && isChargingOrFull(); 230 } 231 isPowerPluggedInWired()232 public boolean isPowerPluggedInWired() { 233 return isValid() 234 && mBatteryStatus.isPluggedInWired() 235 && isChargingOrFull(); 236 } 237 isPowerPluggedInWireless()238 public boolean isPowerPluggedInWireless() { 239 return isValid() 240 && mBatteryStatus.isPluggedInWireless() 241 && isChargingOrFull(); 242 } 243 isPowerPluggedInDocked()244 public boolean isPowerPluggedInDocked() { 245 return isValid() && mBatteryStatus.isPluggedInDock() && isChargingOrFull(); 246 } 247 isChargingOrFull()248 private boolean isChargingOrFull() { 249 return isValid() 250 && (mBatteryStatus.status == BatteryManager.BATTERY_STATUS_CHARGING 251 || mBatteryStatus.isCharged()); 252 } 253 getBatteryLevelAsPercentage()254 private String getBatteryLevelAsPercentage() { 255 return NumberFormat.getPercentInstance().format(getBatteryLevel() / 100f); 256 } 257 } 258 } 259