blob: 2d96fcc4dac693f49baa653cfa7a9da597a88dd1 [file] [log] [blame]
/*
* 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.server.power.stats;
import android.annotation.Nullable;
import android.os.BatteryConsumer;
import android.os.BatteryStats;
import android.os.BatteryStats.ControllerActivityCounter;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
import android.os.UidBatteryConsumer;
import android.util.Log;
import android.util.SparseArray;
import com.android.internal.os.PowerProfile;
import java.util.Arrays;
public class BluetoothPowerCalculator extends PowerCalculator {
private static final String TAG = "BluetoothPowerCalc";
private static final boolean DEBUG = PowerCalculator.DEBUG;
private static final BatteryConsumer.Key[] UNINITIALIZED_KEYS = new BatteryConsumer.Key[0];
private final double mIdleMa;
private final double mRxMa;
private final double mTxMa;
private final boolean mHasBluetoothPowerController;
private static class PowerAndDuration {
// Return value of BT duration per app
public long durationMs;
// Return value of BT power per app
public double powerMah;
public BatteryConsumer.Key[] keys;
public double[] powerPerKeyMah;
// Aggregated BT duration across all apps
public long totalDurationMs;
// Aggregated BT power across all apps
public double totalPowerMah;
}
public BluetoothPowerCalculator(PowerProfile profile) {
mIdleMa = profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_IDLE);
mRxMa = profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_RX);
mTxMa = profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_TX);
mHasBluetoothPowerController = mIdleMa != 0 && mRxMa != 0 && mTxMa != 0;
}
@Override
public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
return powerComponent == BatteryConsumer.POWER_COMPONENT_BLUETOOTH;
}
@Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
if (!batteryStats.hasBluetoothActivityReporting()) {
return;
}
BatteryConsumer.Key[] keys = UNINITIALIZED_KEYS;
final PowerAndDuration powerAndDuration = new PowerAndDuration();
final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
builder.getUidBatteryConsumerBuilders();
for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
if (keys == UNINITIALIZED_KEYS) {
if (query.isProcessStateDataNeeded()) {
keys = app.getKeys(BatteryConsumer.POWER_COMPONENT_BLUETOOTH);
powerAndDuration.keys = keys;
powerAndDuration.powerPerKeyMah = new double[keys.length];
} else {
keys = null;
}
}
calculateApp(app, powerAndDuration, query);
}
final long consumedEnergyUC = batteryStats.getBluetoothEnergyConsumptionUC();
final int powerModel = getPowerModel(consumedEnergyUC, query);
final ControllerActivityCounter activityCounter =
batteryStats.getBluetoothControllerActivity();
calculatePowerAndDuration(null, powerModel, consumedEnergyUC,
activityCounter, query.shouldForceUsePowerProfileModel(), powerAndDuration);
// Subtract what the apps used, but clamp to 0.
final long systemComponentDurationMs = Math.max(0,
powerAndDuration.durationMs - powerAndDuration.totalDurationMs);
if (DEBUG) {
Log.d(TAG, "Bluetooth active: time=" + (systemComponentDurationMs)
+ " power=" + BatteryStats.formatCharge(powerAndDuration.powerMah));
}
builder.getAggregateBatteryConsumerBuilder(
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH,
powerAndDuration.durationMs)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH,
Math.max(powerAndDuration.powerMah, powerAndDuration.totalPowerMah),
powerModel);
builder.getAggregateBatteryConsumerBuilder(
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH,
powerAndDuration.totalDurationMs)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH,
powerAndDuration.totalPowerMah,
powerModel);
}
private void calculateApp(UidBatteryConsumer.Builder app, PowerAndDuration powerAndDuration,
BatteryUsageStatsQuery query) {
final long consumedEnergyUC =
app.getBatteryStatsUid().getBluetoothEnergyConsumptionUC();
final int powerModel = getPowerModel(consumedEnergyUC, query);
final ControllerActivityCounter activityCounter =
app.getBatteryStatsUid().getBluetoothControllerActivity();
calculatePowerAndDuration(app.getBatteryStatsUid(), powerModel, consumedEnergyUC,
activityCounter, query.shouldForceUsePowerProfileModel(), powerAndDuration);
app.setUsageDurationMillis(
BatteryConsumer.POWER_COMPONENT_BLUETOOTH, powerAndDuration.durationMs)
.setConsumedPower(
BatteryConsumer.POWER_COMPONENT_BLUETOOTH, powerAndDuration.powerMah,
powerModel);
if (!app.isVirtualUid()) {
powerAndDuration.totalDurationMs += powerAndDuration.durationMs;
powerAndDuration.totalPowerMah += powerAndDuration.powerMah;
}
if (query.isProcessStateDataNeeded() && powerAndDuration.keys != null) {
for (int j = 0; j < powerAndDuration.keys.length; j++) {
BatteryConsumer.Key key = powerAndDuration.keys[j];
final int processState = key.processState;
if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
// Already populated with the powerAndDuration across all process states
continue;
}
app.setConsumedPower(key, powerAndDuration.powerPerKeyMah[j], powerModel);
}
}
}
/** Returns bluetooth power usage based on the best data available. */
private void calculatePowerAndDuration(@Nullable BatteryStats.Uid uid,
@BatteryConsumer.PowerModel int powerModel,
long consumedEnergyUC, ControllerActivityCounter counter, boolean ignoreReportedPower,
PowerAndDuration powerAndDuration) {
if (counter == null) {
powerAndDuration.durationMs = 0;
powerAndDuration.powerMah = 0;
if (powerAndDuration.powerPerKeyMah != null) {
Arrays.fill(powerAndDuration.powerPerKeyMah, 0);
}
return;
}
final BatteryStats.LongCounter idleTimeCounter = counter.getIdleTimeCounter();
final BatteryStats.LongCounter rxTimeCounter = counter.getRxTimeCounter();
final BatteryStats.LongCounter txTimeCounter = counter.getTxTimeCounters()[0];
final long idleTimeMs = idleTimeCounter.getCountLocked(BatteryStats.STATS_SINCE_CHARGED);
final long rxTimeMs = rxTimeCounter.getCountLocked(BatteryStats.STATS_SINCE_CHARGED);
final long txTimeMs = txTimeCounter.getCountLocked(BatteryStats.STATS_SINCE_CHARGED);
powerAndDuration.durationMs = idleTimeMs + rxTimeMs + txTimeMs;
if (powerModel == BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) {
powerAndDuration.powerMah = uCtoMah(consumedEnergyUC);
if (uid != null && powerAndDuration.keys != null) {
for (int i = 0; i < powerAndDuration.keys.length; i++) {
BatteryConsumer.Key key = powerAndDuration.keys[i];
final int processState = key.processState;
if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
// Already populated with the powerAndDuration across all process states
continue;
}
powerAndDuration.powerPerKeyMah[i] =
uCtoMah(uid.getBluetoothEnergyConsumptionUC(processState));
}
}
} else {
if (!ignoreReportedPower) {
final double powerMah =
counter.getPowerCounter().getCountLocked(BatteryStats.STATS_SINCE_CHARGED)
/ (double) (1000 * 60 * 60);
if (powerMah != 0) {
powerAndDuration.powerMah = powerMah;
if (powerAndDuration.powerPerKeyMah != null) {
// Leave this use case unsupported: used energy is reported
// via BluetoothActivityEnergyInfo rather than PowerStats HAL.
Arrays.fill(powerAndDuration.powerPerKeyMah, 0);
}
return;
}
}
if (mHasBluetoothPowerController) {
powerAndDuration.powerMah = calculatePowerMah(rxTimeMs, txTimeMs, idleTimeMs);
if (powerAndDuration.keys != null) {
for (int i = 0; i < powerAndDuration.keys.length; i++) {
BatteryConsumer.Key key = powerAndDuration.keys[i];
final int processState = key.processState;
if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
// Already populated with the powerAndDuration across all process states
continue;
}
powerAndDuration.powerPerKeyMah[i] =
calculatePowerMah(
rxTimeCounter.getCountForProcessState(processState),
txTimeCounter.getCountForProcessState(processState),
idleTimeCounter.getCountForProcessState(processState));
}
}
} else {
powerAndDuration.powerMah = 0;
if (powerAndDuration.powerPerKeyMah != null) {
Arrays.fill(powerAndDuration.powerPerKeyMah, 0);
}
}
}
}
/** Returns estimated bluetooth power usage based on usage times. */
public double calculatePowerMah(long rxTimeMs, long txTimeMs, long idleTimeMs) {
return ((idleTimeMs * mIdleMa) + (rxTimeMs * mRxMa) + (txTimeMs * mTxMa))
/ (1000 * 60 * 60);
}
}