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 */
16package com.android.internal.os;
17
18import android.os.BatteryStats;
19import android.telephony.SignalStrength;
20import android.util.Log;
21
22public 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) {
53        mPowerRadioOn = profile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE);
54        for (int i = 0; i < mPowerBins.length; i++) {
55            mPowerBins[i] = profile.getAveragePower(PowerProfile.POWER_RADIO_ON, i);
56        }
57        mPowerScan = profile.getAveragePower(PowerProfile.POWER_RADIO_SCANNING);
58        mStats = stats;
59    }
60
61    @Override
62    public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
63                             long rawUptimeUs, int statsType) {
64        // Add cost of mobile traffic.
65        app.mobileRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA,
66                statsType);
67        app.mobileTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA,
68                statsType);
69        app.mobileActive = u.getMobileRadioActiveTime(statsType) / 1000;
70        app.mobileActiveCount = u.getMobileRadioActiveCount(statsType);
71        app.mobileRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_RX_DATA,
72                statsType);
73        app.mobileTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_TX_DATA,
74                statsType);
75
76        if (app.mobileActive > 0) {
77            // We are tracking when the radio is up, so can use the active time to
78            // determine power use.
79            mTotalAppMobileActiveMs += app.mobileActive;
80            app.mobileRadioPowerMah = (app.mobileActive * mPowerRadioOn) / (1000*60*60);
81        } else {
82            // We are not tracking when the radio is up, so must approximate power use
83            // based on the number of packets.
84            app.mobileRadioPowerMah = (app.mobileRxPackets + app.mobileTxPackets)
85                    * getMobilePowerPerPacket(rawRealtimeUs, statsType);
86        }
87        if (DEBUG && app.mobileRadioPowerMah != 0) {
88            Log.d(TAG, "UID " + u.getUid() + ": mobile packets "
89                    + (app.mobileRxPackets + app.mobileTxPackets)
90                    + " active time " + app.mobileActive
91                    + " power=" + BatteryStatsHelper.makemAh(app.mobileRadioPowerMah));
92        }
93    }
94
95    @Override
96    public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
97                                   long rawUptimeUs, int statsType) {
98        double power = 0;
99        long signalTimeMs = 0;
100        long noCoverageTimeMs = 0;
101        for (int i = 0; i < mPowerBins.length; i++) {
102            long strengthTimeMs = stats.getPhoneSignalStrengthTime(i, rawRealtimeUs, statsType)
103                    / 1000;
104            final double p = (strengthTimeMs * mPowerBins[i]) / (60*60*1000);
105            if (DEBUG && p != 0) {
106                Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power="
107                        + BatteryStatsHelper.makemAh(p));
108            }
109            power += p;
110            signalTimeMs += strengthTimeMs;
111            if (i == 0) {
112                noCoverageTimeMs = strengthTimeMs;
113            }
114        }
115
116        final long scanningTimeMs = stats.getPhoneSignalScanningTime(rawRealtimeUs, statsType)
117                / 1000;
118        final double p = (scanningTimeMs * mPowerScan) / (60*60*1000);
119        if (DEBUG && p != 0) {
120            Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs
121                    + " power=" + BatteryStatsHelper.makemAh(p));
122        }
123        power += p;
124        long radioActiveTimeMs = mStats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000;
125        long remainingActiveTimeMs = radioActiveTimeMs - mTotalAppMobileActiveMs;
126        if (remainingActiveTimeMs > 0) {
127            power += (mPowerRadioOn * remainingActiveTimeMs) / (1000*60*60);
128        }
129
130        if (power != 0) {
131            if (signalTimeMs != 0) {
132                app.noCoveragePercent = noCoverageTimeMs * 100.0 / signalTimeMs;
133            }
134            app.mobileActive = remainingActiveTimeMs;
135            app.mobileActiveCount = stats.getMobileRadioActiveUnknownCount(statsType);
136            app.mobileRadioPowerMah = power;
137        }
138    }
139
140    @Override
141    public void reset() {
142        mTotalAppMobileActiveMs = 0;
143    }
144
145    public void reset(BatteryStats stats) {
146        reset();
147        mStats = stats;
148    }
149}
150