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.CellSignalStrength;
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[CellSignalStrength.getNumSignalStrengthLevels()];
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      */
getMobilePowerPerPacket(long rawRealtimeUs, int statsType)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 
MobileRadioPowerCalculator(PowerProfile profile, BatteryStats stats)52     public MobileRadioPowerCalculator(PowerProfile profile, BatteryStats stats) {
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);
64         }
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);
80         mStats = stats;
81     }
82 
83     @Override
calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, long rawUptimeUs, int statsType)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
calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs, long rawUptimeUs, int statsType)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) {
153             if (signalTimeMs != 0) {
154                 app.noCoveragePercent = noCoverageTimeMs * 100.0 / signalTimeMs;
155             }
156             app.mobileActive = remainingActiveTimeMs;
157             app.mobileActiveCount = stats.getMobileRadioActiveUnknownCount(statsType);
158             app.mobileRadioPowerMah = power;
159         }
160     }
161 
162     @Override
reset()163     public void reset() {
164         mTotalAppMobileActiveMs = 0;
165     }
166 
reset(BatteryStats stats)167     public void reset(BatteryStats stats) {
168         reset();
169         mStats = stats;
170     }
171 }
172