Adam Lesinski | e08af19 | 2015-03-25 16:42:59 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2015 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 | package com.android.internal.os; |
| 17 | |
| 18 | import android.os.BatteryStats; |
| 19 | import android.telephony.SignalStrength; |
| 20 | import android.util.Log; |
| 21 | |
| 22 | public class MobileRadioPowerCalculator extends PowerCalculator { |
| 23 | private static final String TAG = "MobileRadioPowerController"; |
| 24 | private static final boolean DEBUG = BatteryStatsHelper.DEBUG; |
| 25 | private final double mPowerRadioOn; |
| 26 | private final double[] mPowerBins = new double[SignalStrength.NUM_SIGNAL_STRENGTH_BINS]; |
| 27 | private final double mPowerScan; |
| 28 | private BatteryStats mStats; |
| 29 | private long mTotalAppMobileActiveMs = 0; |
| 30 | |
| 31 | /** |
| 32 | * Return estimated power (in mAs) of sending or receiving a packet with the mobile radio. |
| 33 | */ |
| 34 | private double getMobilePowerPerPacket(long rawRealtimeUs, int statsType) { |
| 35 | final long MOBILE_BPS = 200000; // TODO: Extract average bit rates from system |
| 36 | final double MOBILE_POWER = mPowerRadioOn / 3600; |
| 37 | |
| 38 | final long mobileRx = mStats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA, |
| 39 | statsType); |
| 40 | final long mobileTx = mStats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA, |
| 41 | statsType); |
| 42 | final long mobileData = mobileRx + mobileTx; |
| 43 | |
| 44 | final long radioDataUptimeMs = |
| 45 | mStats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000; |
| 46 | final double mobilePps = (mobileData != 0 && radioDataUptimeMs != 0) |
| 47 | ? (mobileData / (double)radioDataUptimeMs) |
| 48 | : (((double)MOBILE_BPS) / 8 / 2048); |
| 49 | return (MOBILE_POWER / mobilePps) / (60*60); |
| 50 | } |
| 51 | |
| 52 | public MobileRadioPowerCalculator(PowerProfile profile, BatteryStats stats) { |
Hui Yu | 39b29bc | 2018-05-23 14:39:24 -0700 | [diff] [blame] | 53 | double temp = |
| 54 | profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ACTIVE, -1); |
| 55 | if (temp != -1) { |
| 56 | mPowerRadioOn = temp; |
| 57 | } else { |
| 58 | double sum = 0; |
| 59 | sum += profile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX); |
| 60 | for (int i = 0; i < mPowerBins.length; i++) { |
| 61 | sum += profile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, i); |
| 62 | } |
| 63 | mPowerRadioOn = sum / (mPowerBins.length + 1); |
Adam Lesinski | e08af19 | 2015-03-25 16:42:59 -0700 | [diff] [blame] | 64 | } |
Hui Yu | 39b29bc | 2018-05-23 14:39:24 -0700 | [diff] [blame] | 65 | |
| 66 | temp = profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ON, -1); |
| 67 | if (temp != -1 ) { |
| 68 | for (int i = 0; i < mPowerBins.length; i++) { |
| 69 | mPowerBins[i] = profile.getAveragePower(PowerProfile.POWER_RADIO_ON, i); |
| 70 | } |
| 71 | } else { |
| 72 | double idle = profile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_IDLE); |
| 73 | mPowerBins[0] = idle * 25 / 180; |
| 74 | for (int i = 1; i < mPowerBins.length; i++) { |
| 75 | mPowerBins[i] = Math.max(1, idle / 256); |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | mPowerScan = profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_SCANNING, 0); |
Adam Lesinski | e08af19 | 2015-03-25 16:42:59 -0700 | [diff] [blame] | 80 | mStats = stats; |
| 81 | } |
| 82 | |
| 83 | @Override |
| 84 | public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, |
| 85 | long rawUptimeUs, int statsType) { |
| 86 | // Add cost of mobile traffic. |
| 87 | app.mobileRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA, |
| 88 | statsType); |
| 89 | app.mobileTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA, |
| 90 | statsType); |
| 91 | app.mobileActive = u.getMobileRadioActiveTime(statsType) / 1000; |
| 92 | app.mobileActiveCount = u.getMobileRadioActiveCount(statsType); |
| 93 | app.mobileRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_RX_DATA, |
| 94 | statsType); |
| 95 | app.mobileTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_TX_DATA, |
| 96 | statsType); |
| 97 | |
| 98 | if (app.mobileActive > 0) { |
| 99 | // We are tracking when the radio is up, so can use the active time to |
| 100 | // determine power use. |
| 101 | mTotalAppMobileActiveMs += app.mobileActive; |
| 102 | app.mobileRadioPowerMah = (app.mobileActive * mPowerRadioOn) / (1000*60*60); |
| 103 | } else { |
| 104 | // We are not tracking when the radio is up, so must approximate power use |
| 105 | // based on the number of packets. |
| 106 | app.mobileRadioPowerMah = (app.mobileRxPackets + app.mobileTxPackets) |
| 107 | * getMobilePowerPerPacket(rawRealtimeUs, statsType); |
| 108 | } |
| 109 | if (DEBUG && app.mobileRadioPowerMah != 0) { |
| 110 | Log.d(TAG, "UID " + u.getUid() + ": mobile packets " |
| 111 | + (app.mobileRxPackets + app.mobileTxPackets) |
| 112 | + " active time " + app.mobileActive |
| 113 | + " power=" + BatteryStatsHelper.makemAh(app.mobileRadioPowerMah)); |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | @Override |
| 118 | public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs, |
| 119 | long rawUptimeUs, int statsType) { |
| 120 | double power = 0; |
| 121 | long signalTimeMs = 0; |
| 122 | long noCoverageTimeMs = 0; |
| 123 | for (int i = 0; i < mPowerBins.length; i++) { |
| 124 | long strengthTimeMs = stats.getPhoneSignalStrengthTime(i, rawRealtimeUs, statsType) |
| 125 | / 1000; |
| 126 | final double p = (strengthTimeMs * mPowerBins[i]) / (60*60*1000); |
| 127 | if (DEBUG && p != 0) { |
| 128 | Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power=" |
| 129 | + BatteryStatsHelper.makemAh(p)); |
| 130 | } |
| 131 | power += p; |
| 132 | signalTimeMs += strengthTimeMs; |
| 133 | if (i == 0) { |
| 134 | noCoverageTimeMs = strengthTimeMs; |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | final long scanningTimeMs = stats.getPhoneSignalScanningTime(rawRealtimeUs, statsType) |
| 139 | / 1000; |
| 140 | final double p = (scanningTimeMs * mPowerScan) / (60*60*1000); |
| 141 | if (DEBUG && p != 0) { |
| 142 | Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs |
| 143 | + " power=" + BatteryStatsHelper.makemAh(p)); |
| 144 | } |
| 145 | power += p; |
| 146 | long radioActiveTimeMs = mStats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000; |
| 147 | long remainingActiveTimeMs = radioActiveTimeMs - mTotalAppMobileActiveMs; |
| 148 | if (remainingActiveTimeMs > 0) { |
| 149 | power += (mPowerRadioOn * remainingActiveTimeMs) / (1000*60*60); |
| 150 | } |
| 151 | |
| 152 | if (power != 0) { |
Adam Lesinski | d9b48d5 | 2015-05-05 19:19:00 -0700 | [diff] [blame] | 153 | if (signalTimeMs != 0) { |
| 154 | app.noCoveragePercent = noCoverageTimeMs * 100.0 / signalTimeMs; |
| 155 | } |
Adam Lesinski | e08af19 | 2015-03-25 16:42:59 -0700 | [diff] [blame] | 156 | app.mobileActive = remainingActiveTimeMs; |
| 157 | app.mobileActiveCount = stats.getMobileRadioActiveUnknownCount(statsType); |
| 158 | app.mobileRadioPowerMah = power; |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | @Override |
| 163 | public void reset() { |
| 164 | mTotalAppMobileActiveMs = 0; |
| 165 | } |
| 166 | |
| 167 | public void reset(BatteryStats stats) { |
| 168 | reset(); |
| 169 | mStats = stats; |
| 170 | } |
| 171 | } |