From a4d057d791024227324b4972b25cc8f2fb2c63a6 Mon Sep 17 00:00:00 2001 From: Michael Wachenschwanz Date: Mon, 6 Dec 2021 16:49:24 -0800 Subject: [PATCH] Collect fine grain time-in-state Modem durations Break down active modem time on a per Radio Access Technology, per Frequency Range (for NR only), and per signal strength granularity. Bug: 202413770 Test: atest BatteryStatsNoteTest Change-Id: I299dc008ff1b370885601a9afbd8154dfd1082e5 --- core/java/android/os/BatteryStats.java | 126 +++++++++ .../android/internal/app/IBatteryStats.aidl | 2 +- .../android/internal/os/BatteryStatsImpl.java | 266 +++++++++++++++++- .../internal/os/BatteryStatsNoteTest.java | 212 ++++++++++++++ .../server/am/BatteryStatsService.java | 4 +- .../server/am/DataConnectionStats.java | 2 +- 6 files changed, 602 insertions(+), 10 deletions(-) diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 4666c5c121899..1a11446e5f744 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -34,6 +34,7 @@ import android.server.ServerProtoEnums; import android.service.batterystats.BatteryStatsServiceDumpHistoryProto; import android.service.batterystats.BatteryStatsServiceDumpProto; import android.telephony.CellSignalStrength; +import android.telephony.ServiceState; import android.telephony.TelephonyManager; import android.text.format.DateFormat; import android.util.ArrayMap; @@ -2654,6 +2655,46 @@ public abstract class BatteryStats implements Parcelable { */ public abstract Timer getPhoneDataConnectionTimer(int dataType); + /** @hide */ + public static final int RADIO_ACCESS_TECHNOLOGY_OTHER = 0; + /** @hide */ + public static final int RADIO_ACCESS_TECHNOLOGY_LTE = 1; + /** @hide */ + public static final int RADIO_ACCESS_TECHNOLOGY_NR = 2; + /** @hide */ + public static final int RADIO_ACCESS_TECHNOLOGY_COUNT = 3; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "RADIO_ACCESS_TECHNOLOGY_", + value = {RADIO_ACCESS_TECHNOLOGY_OTHER, RADIO_ACCESS_TECHNOLOGY_LTE, + RADIO_ACCESS_TECHNOLOGY_NR}) + public @interface RadioAccessTechnology { + } + + /** @hide */ + public static final String[] RADIO_ACCESS_TECHNOLOGY_NAMES = {"Other", "LTE", "NR"}; + + /** + * Returns the time in microseconds that the mobile radio has been active on a + * given Radio Access Technology (RAT), at a given frequency (NR RAT only), for a given + * transmission power level. + * + * @param rat Radio Access Technology {@see RadioAccessTechnology} + * @param frequencyRange frequency range {@see ServiceState.FrequencyRange}, only needed for + * RADIO_ACCESS_TECHNOLOGY_NR. Use + * {@link ServiceState.FREQUENCY_RANGE_UNKNOWN} for other Radio Access + * Technologies. + * @param signalStrength the cellular signal strength. {@see CellSignalStrength#getLevel()} + * @param elapsedRealtimeMs current elapsed realtime + * @return time (in milliseconds) the mobile radio spent active in the specified state, + * while on battery. + * @hide + */ + public abstract long getActiveRadioDurationMs(@RadioAccessTechnology int rat, + @ServiceState.FrequencyRange int frequencyRange, int signalStrength, + long elapsedRealtimeMs); + static final String[] WIFI_SUPPL_STATE_NAMES = { "invalid", "disconn", "disabled", "inactive", "scanning", "authenticating", "associating", "associated", "4-way-handshake", @@ -3992,6 +4033,89 @@ public abstract class BatteryStats implements Parcelable { } } + private void printCellularPerRatBreakdown(PrintWriter pw, StringBuilder sb, String prefix, + long rawRealtimeMs) { + final String allFrequenciesHeader = + " All frequencies:\n"; + final String[] nrFrequencyRangeDescription = new String[]{ + " Unknown frequency:\n", + " Low frequency (less than 1GHz):\n", + " Middle frequency (1GHz to 3GHz):\n", + " High frequency (3GHz to 6GHz):\n", + " Mmwave frequency (greater than 6GHz):\n"}; + final String signalStrengthHeader = + " Signal Strength Time:\n"; + final String[] signalStrengthDescription = new String[]{ + " unknown: ", + " poor: ", + " moderate: ", + " good: ", + " great: "}; + + final long totalActiveTimesMs = getMobileRadioActiveTime(rawRealtimeMs * 1000, + STATS_SINCE_CHARGED) / 1000; + + sb.setLength(0); + sb.append(prefix); + sb.append("Active Cellular Radio Access Technology Breakdown:"); + pw.println(sb); + + boolean hasData = false; + final int numSignalStrength = CellSignalStrength.getNumSignalStrengthLevels(); + for (int rat = RADIO_ACCESS_TECHNOLOGY_COUNT - 1; rat >= 0; rat--) { + sb.setLength(0); + sb.append(prefix); + sb.append(" "); + sb.append(RADIO_ACCESS_TECHNOLOGY_NAMES[rat]); + sb.append(":\n"); + sb.append(prefix); + + final int numFreqLvl = + rat == RADIO_ACCESS_TECHNOLOGY_NR ? nrFrequencyRangeDescription.length : 1; + for (int freqLvl = numFreqLvl - 1; freqLvl >= 0; freqLvl--) { + final int freqDescriptionStart = sb.length(); + boolean hasFreqData = false; + if (rat == RADIO_ACCESS_TECHNOLOGY_NR) { + sb.append(nrFrequencyRangeDescription[freqLvl]); + } else { + sb.append(allFrequenciesHeader); + } + + sb.append(prefix); + sb.append(signalStrengthHeader); + for (int strength = 0; strength < numSignalStrength; strength++) { + final long timeMs = getActiveRadioDurationMs(rat, freqLvl, strength, + rawRealtimeMs); + if (timeMs <= 0) continue; + hasFreqData = true; + sb.append(prefix); + sb.append(signalStrengthDescription[strength]); + formatTimeMs(sb, timeMs); + sb.append("("); + sb.append(formatRatioLocked(timeMs, totalActiveTimesMs)); + sb.append(")\n"); + } + + if (hasFreqData) { + hasData = true; + pw.print(sb); + sb.setLength(0); + sb.append(prefix); + } else { + // No useful data was printed, rewind sb to before the start of this frequency. + sb.setLength(freqDescriptionStart); + } + } + } + + if (!hasData) { + sb.setLength(0); + sb.append(prefix); + sb.append(" (no activity)"); + pw.println(sb); + } + } + /** * Temporary for settings. */ @@ -5264,6 +5388,8 @@ public abstract class BatteryStats implements Parcelable { printControllerActivity(pw, sb, prefix, CELLULAR_CONTROLLER_NAME, getModemControllerActivity(), which); + printCellularPerRatBreakdown(pw, sb, prefix + " ", rawRealtimeMs); + pw.print(" Cellular data received: "); pw.println(formatBytesLocked(mobileRxTotalBytes)); pw.print(" Cellular data sent: "); pw.println(formatBytesLocked(mobileTxTotalBytes)); pw.print(" Cellular packets received: "); pw.println(mobileRxTotalPackets); diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index 587876df0df6a..588b036fdcc4f 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -112,7 +112,7 @@ interface IBatteryStats { void notePhoneOn(); void notePhoneOff(); void notePhoneSignalStrength(in SignalStrength signalStrength); - void notePhoneDataConnectionState(int dataType, boolean hasData, int serviceType); + void notePhoneDataConnectionState(int dataType, boolean hasData, int serviceType, int nrFrequency); void notePhoneState(int phoneState); void noteWifiOn(); void noteWifiOff(); diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 451eec0c18df1..baf05330f5260 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -68,10 +68,14 @@ import android.os.connectivity.GpsBatteryStats; import android.os.connectivity.WifiActivityEnergyInfo; import android.os.connectivity.WifiBatteryStats; import android.provider.Settings; +import android.telephony.Annotation.NetworkType; import android.telephony.CellSignalStrength; +import android.telephony.CellSignalStrengthLte; +import android.telephony.CellSignalStrengthNr; import android.telephony.DataConnectionRealTimeInfo; import android.telephony.ModemActivityInfo; import android.telephony.ServiceState; +import android.telephony.ServiceState.RegState; import android.telephony.SignalStrength; import android.telephony.TelephonyManager; import android.text.TextUtils; @@ -934,6 +938,119 @@ public class BatteryStatsImpl extends BatteryStats { final StopwatchTimer[] mPhoneDataConnectionsTimer = new StopwatchTimer[NUM_DATA_CONNECTION_TYPES]; + @RadioAccessTechnology + int mActiveRat = RADIO_ACCESS_TECHNOLOGY_OTHER; + + private static class RadioAccessTechnologyBatteryStats { + /** + * This RAT is currently being used. + */ + private boolean mActive = false; + /** + * Current active frequency range for this RAT. + */ + @ServiceState.FrequencyRange + private int mFrequencyRange = ServiceState.FREQUENCY_RANGE_UNKNOWN; + /** + * Current signal strength for this RAT. + */ + private int mSignalStrength = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + /** + * Timers for each combination of frequency range and signal strength. + */ + public final StopwatchTimer[][] perStateTimers; + + RadioAccessTechnologyBatteryStats(int freqCount, Clock clock, TimeBase timeBase) { + perStateTimers = + new StopwatchTimer[freqCount][CellSignalStrength.NUM_SIGNAL_STRENGTH_BINS]; + for (int i = 0; i < freqCount; i++) { + for (int j = 0; j < CellSignalStrength.NUM_SIGNAL_STRENGTH_BINS; j++) { + perStateTimers[i][j] = new StopwatchTimer(clock, null, -1, null, timeBase); + } + } + } + + /** + * Note this RAT is currently being used. + */ + public void noteActive(boolean active, long elapsedRealtimeMs) { + if (mActive == active) return; + mActive = active; + if (mActive) { + perStateTimers[mFrequencyRange][mSignalStrength].startRunningLocked( + elapsedRealtimeMs); + } else { + perStateTimers[mFrequencyRange][mSignalStrength].stopRunningLocked( + elapsedRealtimeMs); + } + } + + /** + * Note current frequency range has changed. + */ + public void noteFrequencyRange(@ServiceState.FrequencyRange int frequencyRange, + long elapsedRealtimeMs) { + if (mFrequencyRange == frequencyRange) return; + + if (!mActive) { + // RAT not in use, note the frequency change and move on. + mFrequencyRange = frequencyRange; + return; + } + perStateTimers[mFrequencyRange][mSignalStrength].stopRunningLocked(elapsedRealtimeMs); + perStateTimers[frequencyRange][mSignalStrength].startRunningLocked(elapsedRealtimeMs); + mFrequencyRange = frequencyRange; + } + + /** + * Note current signal strength has changed. + */ + public void noteSignalStrength(int signalStrength, long elapsedRealtimeMs) { + if (mSignalStrength == signalStrength) return; + + if (!mActive) { + // RAT not in use, note the signal strength change and move on. + mSignalStrength = signalStrength; + return; + } + perStateTimers[mFrequencyRange][mSignalStrength].stopRunningLocked(elapsedRealtimeMs); + perStateTimers[mFrequencyRange][signalStrength].startRunningLocked(elapsedRealtimeMs); + mSignalStrength = signalStrength; + } + + /** + * Reset display timers. + */ + public void reset(long elapsedRealtimeUs) { + final int size = perStateTimers.length; + for (int i = 0; i < size; i++) { + for (int j = 0; j < CellSignalStrength.NUM_SIGNAL_STRENGTH_BINS; j++) { + perStateTimers[i][j].reset(false, elapsedRealtimeUs); + } + } + } + } + + /** + * Number of frequency ranges, keep in sync with {@link ServiceState.FrequencyRange} + */ + private static final int NR_FREQUENCY_COUNT = 5; + + RadioAccessTechnologyBatteryStats[] mPerRatBatteryStats = + new RadioAccessTechnologyBatteryStats[RADIO_ACCESS_TECHNOLOGY_COUNT]; + + @GuardedBy("this") + private RadioAccessTechnologyBatteryStats getRatBatteryStatsLocked( + @RadioAccessTechnology int rat) { + RadioAccessTechnologyBatteryStats stats = mPerRatBatteryStats[rat]; + if (stats == null) { + final int freqCount = rat == RADIO_ACCESS_TECHNOLOGY_NR ? NR_FREQUENCY_COUNT : 1; + stats = new RadioAccessTechnologyBatteryStats(freqCount, mClock, mOnBatteryTimeBase); + mPerRatBatteryStats[rat] = stats; + } + return stats; + } + final LongSamplingCounter[] mNetworkByteActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES]; @@ -5843,6 +5960,10 @@ public class BatteryStatsImpl extends BatteryStats { + Integer.toHexString(mHistoryCur.states)); addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); mMobileRadioPowerState = powerState; + + // Inform current RatBatteryStats that the modem active state might have changed. + getRatBatteryStatsLocked(mActiveRat).noteActive(active, elapsedRealtimeMs); + if (active) { mMobileRadioActiveTimer.startRunningLocked(elapsedRealtimeMs); mMobileRadioActivePerAppTimer.startRunningLocked(elapsedRealtimeMs); @@ -6264,21 +6385,86 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("this") public void notePhoneSignalStrengthLocked(SignalStrength signalStrength, long elapsedRealtimeMs, long uptimeMs) { - // Bin the strength. - int bin = signalStrength.getLevel(); - updateAllPhoneStateLocked(mPhoneServiceStateRaw, mPhoneSimStateRaw, bin, + final int overallSignalStrength = signalStrength.getLevel(); + final SparseIntArray perRatSignalStrength = new SparseIntArray( + BatteryStats.RADIO_ACCESS_TECHNOLOGY_COUNT); + + // Extract signal strength level for each RAT. + final List cellSignalStrengths = + signalStrength.getCellSignalStrengths(); + final int size = cellSignalStrengths.size(); + for (int i = 0; i < size; i++) { + CellSignalStrength cellSignalStrength = cellSignalStrengths.get(i); + // Map each CellSignalStrength to a BatteryStats.RadioAccessTechnology + final int ratType; + final int level; + if (cellSignalStrength instanceof CellSignalStrengthNr) { + ratType = RADIO_ACCESS_TECHNOLOGY_NR; + level = cellSignalStrength.getLevel(); + } else if (cellSignalStrength instanceof CellSignalStrengthLte) { + ratType = RADIO_ACCESS_TECHNOLOGY_LTE; + level = cellSignalStrength.getLevel(); + } else { + ratType = RADIO_ACCESS_TECHNOLOGY_OTHER; + level = cellSignalStrength.getLevel(); + } + + // According to SignalStrength#getCellSignalStrengths(), multiple of the same + // cellSignalStrength can be present. Just take the highest level one for each RAT. + if (perRatSignalStrength.get(ratType, -1) < level) { + perRatSignalStrength.put(ratType, level); + } + } + + notePhoneSignalStrengthLocked(overallSignalStrength, perRatSignalStrength, + elapsedRealtimeMs, uptimeMs); + } + + /** + * Note phone signal strength change, including per RAT signal strength. + * + * @param signalStrength overall signal strength {@see SignalStrength#getLevel()} + * @param perRatSignalStrength signal strength of available RATs + */ + @GuardedBy("this") + public void notePhoneSignalStrengthLocked(int signalStrength, + SparseIntArray perRatSignalStrength) { + notePhoneSignalStrengthLocked(signalStrength, perRatSignalStrength, + mClock.elapsedRealtime(), mClock.uptimeMillis()); + } + + /** + * Note phone signal strength change, including per RAT signal strength. + * + * @param signalStrength overall signal strength {@see SignalStrength#getLevel()} + * @param perRatSignalStrength signal strength of available RATs + */ + @GuardedBy("this") + public void notePhoneSignalStrengthLocked(int signalStrength, + SparseIntArray perRatSignalStrength, + long elapsedRealtimeMs, long uptimeMs) { + // Note each RAT's signal strength. + final int size = perRatSignalStrength.size(); + for (int i = 0; i < size; i++) { + final int rat = perRatSignalStrength.keyAt(i); + final int ratSignalStrength = perRatSignalStrength.valueAt(i); + getRatBatteryStatsLocked(rat).noteSignalStrength(ratSignalStrength, elapsedRealtimeMs); + } + updateAllPhoneStateLocked(mPhoneServiceStateRaw, mPhoneSimStateRaw, signalStrength, elapsedRealtimeMs, uptimeMs); } @UnsupportedAppUsage @GuardedBy("this") - public void notePhoneDataConnectionStateLocked(int dataType, boolean hasData, int serviceType) { - notePhoneDataConnectionStateLocked(dataType, hasData, serviceType, + public void notePhoneDataConnectionStateLocked(@NetworkType int dataType, boolean hasData, + @RegState int serviceType, @ServiceState.FrequencyRange int nrFrequency) { + notePhoneDataConnectionStateLocked(dataType, hasData, serviceType, nrFrequency, mClock.elapsedRealtime(), mClock.uptimeMillis()); } @GuardedBy("this") - public void notePhoneDataConnectionStateLocked(int dataType, boolean hasData, int serviceType, + public void notePhoneDataConnectionStateLocked(@NetworkType int dataType, boolean hasData, + @RegState int serviceType, @ServiceState.FrequencyRange int nrFrequency, long elapsedRealtimeMs, long uptimeMs) { // BatteryStats uses 0 to represent no network type. // Telephony does not have a concept of no network type, and uses 0 to represent unknown. @@ -6301,6 +6487,13 @@ public class BatteryStatsImpl extends BatteryStats { } } } + + final int newRat = mapNetworkTypeToRadioAccessTechnology(bin); + if (newRat == RADIO_ACCESS_TECHNOLOGY_NR) { + // Note possible frequency change for the NR RAT. + getRatBatteryStatsLocked(newRat).noteFrequencyRange(nrFrequency, elapsedRealtimeMs); + } + if (DEBUG) Log.i(TAG, "Phone Data Connection -> " + dataType + " = " + hasData); if (mPhoneDataConnectionType != bin) { mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_DATA_CONNECTION_MASK) @@ -6314,6 +6507,45 @@ public class BatteryStatsImpl extends BatteryStats { } mPhoneDataConnectionType = bin; mPhoneDataConnectionsTimer[bin].startRunningLocked(elapsedRealtimeMs); + + if (mActiveRat != newRat) { + getRatBatteryStatsLocked(mActiveRat).noteActive(false, elapsedRealtimeMs); + mActiveRat = newRat; + } + final boolean modemActive = mMobileRadioActiveTimer.isRunningLocked(); + getRatBatteryStatsLocked(newRat).noteActive(modemActive, elapsedRealtimeMs); + } + } + + @RadioAccessTechnology + private static int mapNetworkTypeToRadioAccessTechnology(@NetworkType int dataType) { + switch (dataType) { + case TelephonyManager.NETWORK_TYPE_NR: + return RADIO_ACCESS_TECHNOLOGY_NR; + case TelephonyManager.NETWORK_TYPE_LTE: + return RADIO_ACCESS_TECHNOLOGY_LTE; + case TelephonyManager.NETWORK_TYPE_UNKNOWN: //fallthrough + case TelephonyManager.NETWORK_TYPE_GPRS: //fallthrough + case TelephonyManager.NETWORK_TYPE_EDGE: //fallthrough + case TelephonyManager.NETWORK_TYPE_UMTS: //fallthrough + case TelephonyManager.NETWORK_TYPE_CDMA: //fallthrough + case TelephonyManager.NETWORK_TYPE_EVDO_0: //fallthrough + case TelephonyManager.NETWORK_TYPE_EVDO_A: //fallthrough + case TelephonyManager.NETWORK_TYPE_1xRTT: //fallthrough + case TelephonyManager.NETWORK_TYPE_HSDPA: //fallthrough + case TelephonyManager.NETWORK_TYPE_HSUPA: //fallthrough + case TelephonyManager.NETWORK_TYPE_HSPA: //fallthrough + case TelephonyManager.NETWORK_TYPE_IDEN: //fallthrough + case TelephonyManager.NETWORK_TYPE_EVDO_B: //fallthrough + case TelephonyManager.NETWORK_TYPE_EHRPD: //fallthrough + case TelephonyManager.NETWORK_TYPE_HSPAP: //fallthrough + case TelephonyManager.NETWORK_TYPE_GSM: //fallthrough + case TelephonyManager.NETWORK_TYPE_TD_SCDMA: //fallthrough + case TelephonyManager.NETWORK_TYPE_IWLAN: //fallthrough + return RADIO_ACCESS_TECHNOLOGY_OTHER; + default: + Slog.w(TAG, "Unhandled NetworkType (" + dataType + "), mapping to OTHER"); + return RADIO_ACCESS_TECHNOLOGY_OTHER; } } @@ -7688,6 +7920,23 @@ public class BatteryStatsImpl extends BatteryStats { return mPhoneDataConnectionsTimer[dataType]; } + @Override public long getActiveRadioDurationMs(@RadioAccessTechnology int rat, + @ServiceState.FrequencyRange int frequencyRange, int signalStrength, + long elapsedRealtimeMs) { + final RadioAccessTechnologyBatteryStats stats = mPerRatBatteryStats[rat]; + if (stats == null) return 0L; + + final int freqCount = stats.perStateTimers.length; + if (frequencyRange < 0 || frequencyRange >= freqCount) return 0L; + + final StopwatchTimer[] strengthTimers = stats.perStateTimers[frequencyRange]; + final int strengthCount = strengthTimers.length; + if (signalStrength < 0 || signalStrength >= strengthCount) return 0L; + + return stats.perStateTimers[frequencyRange][signalStrength].getTotalTimeLocked( + elapsedRealtimeMs * 1000, STATS_SINCE_CHARGED) / 1000; + } + @UnsupportedAppUsage @Override public long getMobileRadioActiveTime(long elapsedRealtimeUs, int which) { return mMobileRadioActiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which); @@ -12510,6 +12759,11 @@ public class BatteryStatsImpl extends BatteryStats { mNetworkByteActivityCounters[i].reset(false, elapsedRealtimeUs); mNetworkPacketActivityCounters[i].reset(false, elapsedRealtimeUs); } + for (int i = 0; i < RADIO_ACCESS_TECHNOLOGY_COUNT; i++) { + final RadioAccessTechnologyBatteryStats stats = mPerRatBatteryStats[i]; + if (stats == null) continue; + stats.reset(elapsedRealtimeUs); + } mMobileRadioActiveTimer.reset(false, elapsedRealtimeUs); mMobileRadioActivePerAppTimer.reset(false, elapsedRealtimeUs); mMobileRadioActiveAdjustedTime.reset(false, elapsedRealtimeUs); diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java index b655369d7e609..f5cbffb64bb55 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java @@ -17,6 +17,7 @@ package com.android.internal.os; import static android.os.BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; +import static android.os.BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR; import static android.os.BatteryStats.STATS_SINCE_CHARGED; import static android.os.BatteryStats.WAKE_TYPE_PARTIAL; @@ -30,6 +31,12 @@ import android.os.BatteryStats.Uid.Sensor; import android.os.Process; import android.os.UserHandle; import android.os.WorkSource; +import android.telephony.Annotation; +import android.telephony.CellSignalStrength; +import android.telephony.DataConnectionRealTimeInfo; +import android.telephony.ServiceState; +import android.telephony.TelephonyManager; +import android.util.SparseIntArray; import android.util.SparseLongArray; import android.view.Display; @@ -1165,6 +1172,185 @@ public class BatteryStatsNoteTest extends TestCase { "D", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi); } + @SmallTest + public void testGetPerStateActiveRadioDurationMs() { + final MockClock clock = new MockClock(); // holds realtime and uptime in ms + final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clock); + final int ratCount = BatteryStats.RADIO_ACCESS_TECHNOLOGY_COUNT; + final int frequencyCount = ServiceState.FREQUENCY_RANGE_MMWAVE + 1; + final int txLevelCount = CellSignalStrength.getNumSignalStrengthLevels(); + + final long[][][] expectedDurationsMs = new long[ratCount][frequencyCount][txLevelCount]; + for (int rat = 0; rat < ratCount; rat++) { + for (int freq = 0; freq < frequencyCount; freq++) { + for (int txLvl = 0; txLvl < txLevelCount; txLvl++) { + expectedDurationsMs[rat][freq][txLvl] = 0; + } + } + } + + class ModemAndBatteryState { + public long currentTimeMs = 100; + public boolean onBattery = false; + public boolean modemActive = false; + @Annotation.NetworkType + public int currentNetworkDataType = TelephonyManager.NETWORK_TYPE_UNKNOWN; + @BatteryStats.RadioAccessTechnology + public int currentRat = BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER; + @ServiceState.FrequencyRange + public int currentFrequencyRange = ServiceState.FREQUENCY_RANGE_UNKNOWN; + public SparseIntArray currentSignalStrengths = new SparseIntArray(); + + void setOnBattery(boolean onBattery) { + this.onBattery = onBattery; + bi.updateTimeBasesLocked(onBattery, Display.STATE_OFF, currentTimeMs * 1000, + currentTimeMs * 1000); + } + + void setModemActive(boolean active) { + modemActive = active; + final int state = active ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH + : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; + bi.noteMobileRadioPowerStateLocked(state, currentTimeMs * 1000_000L, UID); + } + + void setRatType(@Annotation.NetworkType int dataType, + @BatteryStats.RadioAccessTechnology int rat) { + currentNetworkDataType = dataType; + currentRat = rat; + bi.notePhoneDataConnectionStateLocked(dataType, true, ServiceState.STATE_IN_SERVICE, + currentFrequencyRange); + } + + void setFrequencyRange(@ServiceState.FrequencyRange int frequency) { + currentFrequencyRange = frequency; + bi.notePhoneDataConnectionStateLocked(currentNetworkDataType, true, + ServiceState.STATE_IN_SERVICE, frequency); + } + + void setSignalStrength(@BatteryStats.RadioAccessTechnology int rat, int strength) { + currentSignalStrengths.put(rat, strength); + final int size = currentSignalStrengths.size(); + final int newestGenSignalStrength = currentSignalStrengths.valueAt(size - 1); + bi.notePhoneSignalStrengthLocked(newestGenSignalStrength, currentSignalStrengths); + } + } + final ModemAndBatteryState state = new ModemAndBatteryState(); + + IntConsumer incrementTime = inc -> { + state.currentTimeMs += inc; + clock.realtime = clock.uptime = state.currentTimeMs; + + // If the device is not on battery, no timers should increment. + if (!state.onBattery) return; + // If the modem is not active, no timers should increment. + if (!state.modemActive) return; + + final int currentRat = state.currentRat; + final int currentFrequencyRange = + currentRat == RADIO_ACCESS_TECHNOLOGY_NR ? state.currentFrequencyRange : 0; + int currentSignalStrength = state.currentSignalStrengths.get(currentRat); + expectedDurationsMs[currentRat][currentFrequencyRange][currentSignalStrength] += inc; + }; + + state.setOnBattery(false); + state.setModemActive(false); + state.setRatType(TelephonyManager.NETWORK_TYPE_UNKNOWN, + BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER); + state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_UNKNOWN); + state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER, + CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + // While not on battery, the timers should not increase. + state.setModemActive(true); + incrementTime.accept(100); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + state.setRatType(TelephonyManager.NETWORK_TYPE_NR, BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR); + incrementTime.accept(200); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR, + CellSignalStrength.SIGNAL_STRENGTH_GOOD); + incrementTime.accept(500); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_MMWAVE); + incrementTime.accept(300); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + state.setRatType(TelephonyManager.NETWORK_TYPE_LTE, + BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE); + incrementTime.accept(400); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE, + CellSignalStrength.SIGNAL_STRENGTH_MODERATE); + incrementTime.accept(500); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + // When set on battery, currently active state (RAT:LTE, Signal Strength:Moderate) should + // start counting up. + state.setOnBattery(true); + incrementTime.accept(600); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + // Changing LTE signal strength should be tracked. + state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE, + CellSignalStrength.SIGNAL_STRENGTH_POOR); + incrementTime.accept(700); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE, + CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN); + incrementTime.accept(800); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE, + CellSignalStrength.SIGNAL_STRENGTH_GOOD); + incrementTime.accept(900); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE, + CellSignalStrength.SIGNAL_STRENGTH_GREAT); + incrementTime.accept(1000); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + // Change in the signal strength of nonactive RAT should not affect anything. + state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER, + CellSignalStrength.SIGNAL_STRENGTH_POOR); + incrementTime.accept(1100); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + // Changing to OTHER Rat should start tracking the poor signal strength. + state.setRatType(TelephonyManager.NETWORK_TYPE_CDMA, + BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER); + incrementTime.accept(1200); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + // Noting frequency change should not affect non NR Rat. + state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_HIGH); + incrementTime.accept(1300); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + // Now the NR Rat, HIGH frequency range, good signal strength should start counting. + state.setRatType(TelephonyManager.NETWORK_TYPE_NR, BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR); + incrementTime.accept(1400); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + // Noting frequency change should not affect non NR Rat. + state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_LOW); + incrementTime.accept(1500); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + // Modem no longer active, should not be tracking any more. + state.setModemActive(false); + incrementTime.accept(1500); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + } + private void setFgState(int uid, boolean fgOn, MockBatteryStatsImpl bi) { // Note that noteUidProcessStateLocked uses ActivityManager process states. if (fgOn) { @@ -1238,4 +1424,30 @@ public class BatteryStatsNoteTest extends TestCase { bi.getScreenBrightnessTime(bin, currentTimeMs * 1000, STATS_SINCE_CHARGED)); } } + + private void checkPerStateActiveRadioDurations(long[][][] expectedDurationsMs, + BatteryStatsImpl bi, long currentTimeMs) { + for (int rat = 0; rat < expectedDurationsMs.length; rat++) { + final long[][] expectedRatDurationsMs = expectedDurationsMs[rat]; + for (int freq = 0; freq < expectedRatDurationsMs.length; freq++) { + final long[] expectedFreqDurationsMs = expectedRatDurationsMs[freq]; + for (int strength = 0; strength < expectedFreqDurationsMs.length; strength++) { + final long expectedSignalStrengthDurationMs = expectedFreqDurationsMs[strength]; + final long actualDurationMs = bi.getActiveRadioDurationMs(rat, freq, + strength, currentTimeMs); + + // Build a verbose fail message, just in case. + final StringBuilder sb = new StringBuilder(); + sb.append("Wrong time in state for RAT:"); + sb.append(BatteryStats.RADIO_ACCESS_TECHNOLOGY_NAMES[rat]); + sb.append(", frequency:"); + sb.append(ServiceState.frequencyRangeToString(freq)); + sb.append(", strength:"); + sb.append(strength); + + assertEquals(sb.toString(), expectedSignalStrengthDurationMs, actualDurationMs); + } + } + } + } } diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 921208cbfa3d9..621c14783129f 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -1359,7 +1359,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub } public void notePhoneDataConnectionState(final int dataType, final boolean hasData, - final int serviceType) { + final int serviceType, final int nrFrequency) { enforceCallingPermission(); synchronized (mLock) { final long elapsedRealtime = SystemClock.elapsedRealtime(); @@ -1367,7 +1367,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub mHandler.post(() -> { synchronized (mStats) { mStats.notePhoneDataConnectionStateLocked(dataType, hasData, serviceType, - elapsedRealtime, uptime); + nrFrequency, elapsedRealtime, uptime); } }); } diff --git a/services/core/java/com/android/server/am/DataConnectionStats.java b/services/core/java/com/android/server/am/DataConnectionStats.java index 6e39a4c802d98..f0910dcb0da2b 100644 --- a/services/core/java/com/android/server/am/DataConnectionStats.java +++ b/services/core/java/com/android/server/am/DataConnectionStats.java @@ -109,7 +109,7 @@ public class DataConnectionStats extends BroadcastReceiver { } try { mBatteryStats.notePhoneDataConnectionState(networkType, visible, - mServiceState.getState()); + mServiceState.getState(), mServiceState.getNrFrequencyRange()); } catch (RemoteException e) { Log.w(TAG, "Error noting data connection state", e); }