/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.os;
import android.os.BatteryStats;
import android.telephony.SignalStrength;
import android.util.Log;
public class MobileRadioPowerCalculator extends PowerCalculator {
private static final String TAG = "MobileRadioPowerController";
private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
private final double mPowerRadioOn;
private final double[] mPowerBins = new double[SignalStrength.NUM_SIGNAL_STRENGTH_BINS];
private final double mPowerScan;
private BatteryStats mStats;
private long mTotalAppMobileActiveMs = 0;
/**
* Return estimated power (in mAs) of sending or receiving a packet with the mobile radio.
*/
private double getMobilePowerPerPacket(long rawRealtimeUs, int statsType) {
final long MOBILE_BPS = 200000; // TODO: Extract average bit rates from system
final double MOBILE_POWER = mPowerRadioOn / 3600;
final long mobileRx = mStats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA,
statsType);
final long mobileTx = mStats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA,
statsType);
final long mobileData = mobileRx + mobileTx;
final long radioDataUptimeMs =
mStats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000;
final double mobilePps = (mobileData != 0 && radioDataUptimeMs != 0)
? (mobileData / (double)radioDataUptimeMs)
: (((double)MOBILE_BPS) / 8 / 2048);
return (MOBILE_POWER / mobilePps) / (60*60);
}
public MobileRadioPowerCalculator(PowerProfile profile, BatteryStats stats) {
mPowerRadioOn = profile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE);
for (int i = 0; i < mPowerBins.length; i++) {
mPowerBins[i] = profile.getAveragePower(PowerProfile.POWER_RADIO_ON, i);
}
mPowerScan = profile.getAveragePower(PowerProfile.POWER_RADIO_SCANNING);
mStats = stats;
}
@Override
public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
long rawUptimeUs, int statsType) {
// Add cost of mobile traffic.
app.mobileRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA,
statsType);
app.mobileTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA,
statsType);
app.mobileActive = u.getMobileRadioActiveTime(statsType) / 1000;
app.mobileActiveCount = u.getMobileRadioActiveCount(statsType);
app.mobileRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_RX_DATA,
statsType);
app.mobileTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_TX_DATA,
statsType);
if (app.mobileActive > 0) {
// We are tracking when the radio is up, so can use the active time to
// determine power use.
mTotalAppMobileActiveMs += app.mobileActive;
app.mobileRadioPowerMah = (app.mobileActive * mPowerRadioOn) / (1000*60*60);
} else {
// We are not tracking when the radio is up, so must approximate power use
// based on the number of packets.
app.mobileRadioPowerMah = (app.mobileRxPackets + app.mobileTxPackets)
* getMobilePowerPerPacket(rawRealtimeUs, statsType);
}
if (DEBUG && app.mobileRadioPowerMah != 0) {
Log.d(TAG, "UID " + u.getUid() + ": mobile packets "
+ (app.mobileRxPackets + app.mobileTxPackets)
+ " active time " + app.mobileActive
+ " power=" + BatteryStatsHelper.makemAh(app.mobileRadioPowerMah));
}
}
@Override
public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
long rawUptimeUs, int statsType) {
double power = 0;
long signalTimeMs = 0;
long noCoverageTimeMs = 0;
for (int i = 0; i < mPowerBins.length; i++) {
long strengthTimeMs = stats.getPhoneSignalStrengthTime(i, rawRealtimeUs, statsType)
/ 1000;
final double p = (strengthTimeMs * mPowerBins[i]) / (60*60*1000);
if (DEBUG && p != 0) {
Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power="
+ BatteryStatsHelper.makemAh(p));
}
power += p;
signalTimeMs += strengthTimeMs;
if (i == 0) {
noCoverageTimeMs = strengthTimeMs;
}
}
final long scanningTimeMs = stats.getPhoneSignalScanningTime(rawRealtimeUs, statsType)
/ 1000;
final double p = (scanningTimeMs * mPowerScan) / (60*60*1000);
if (DEBUG && p != 0) {
Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs
+ " power=" + BatteryStatsHelper.makemAh(p));
}
power += p;
long radioActiveTimeMs = mStats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000;
long remainingActiveTimeMs = radioActiveTimeMs - mTotalAppMobileActiveMs;
if (remainingActiveTimeMs > 0) {
power += (mPowerRadioOn * remainingActiveTimeMs) / (1000*60*60);
}
if (power != 0) {
if (signalTimeMs != 0) {
app.noCoveragePercent = noCoverageTimeMs * 100.0 / signalTimeMs;
}
app.mobileActive = remainingActiveTimeMs;
app.mobileActiveCount = stats.getMobileRadioActiveUnknownCount(statsType);
app.mobileRadioPowerMah = power;
}
}
@Override
public void reset() {
mTotalAppMobileActiveMs = 0;
}
public void reset(BatteryStats stats) {
reset();
mStats = stats;
}
}