Merge "Add power stats collector and processor for Camera and GNSS" into main
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index 7bdd53d..02f3a25 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -200,6 +200,8 @@
POWER_COMPONENT_AUDIO,
POWER_COMPONENT_VIDEO,
POWER_COMPONENT_FLASHLIGHT,
+ POWER_COMPONENT_CAMERA,
+ POWER_COMPONENT_GNSS,
};
static final int COLUMN_INDEX_BATTERY_CONSUMER_TYPE = 0;
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 3e6223a..065b3d6 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1994,7 +1994,8 @@
// STATES2 bits that are used for Power Stats tracking
public static final int IMPORTANT_FOR_POWER_STATS_STATES2 =
- STATE2_VIDEO_ON_FLAG | STATE2_FLASHLIGHT_FLAG | STATE2_CAMERA_FLAG;
+ STATE2_VIDEO_ON_FLAG | STATE2_FLASHLIGHT_FLAG | STATE2_CAMERA_FLAG
+ | STATE2_GPS_SIGNAL_QUALITY_MASK;
@UnsupportedAppUsage
public int states2;
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 29e0f7a..1ac37ad 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -128,8 +128,10 @@
import com.android.server.power.stats.BatteryStatsImpl;
import com.android.server.power.stats.BatteryUsageStatsProvider;
import com.android.server.power.stats.BluetoothPowerStatsProcessor;
+import com.android.server.power.stats.CameraPowerStatsProcessor;
import com.android.server.power.stats.CpuPowerStatsProcessor;
import com.android.server.power.stats.FlashlightPowerStatsProcessor;
+import com.android.server.power.stats.GnssPowerStatsProcessor;
import com.android.server.power.stats.MobileRadioPowerStatsProcessor;
import com.android.server.power.stats.PhoneCallPowerStatsProcessor;
import com.android.server.power.stats.PowerStatsAggregator;
@@ -528,8 +530,7 @@
AggregatedPowerStatsConfig.STATE_SCREEN,
AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
.setProcessor(
- new AudioPowerStatsProcessor(mPowerProfile,
- mPowerStatsUidResolver));
+ new AudioPowerStatsProcessor(mPowerProfile, mPowerStatsUidResolver));
config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_VIDEO)
.trackDeviceStates(
@@ -539,9 +540,7 @@
AggregatedPowerStatsConfig.STATE_POWER,
AggregatedPowerStatsConfig.STATE_SCREEN,
AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
- .setProcessor(
- new VideoPowerStatsProcessor(mPowerProfile,
- mPowerStatsUidResolver));
+ .setProcessor(new VideoPowerStatsProcessor(mPowerProfile, mPowerStatsUidResolver));
config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)
.trackDeviceStates(
@@ -552,8 +551,29 @@
AggregatedPowerStatsConfig.STATE_SCREEN,
AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
.setProcessor(
- new FlashlightPowerStatsProcessor(mPowerProfile,
- mPowerStatsUidResolver));
+ new FlashlightPowerStatsProcessor(mPowerProfile, mPowerStatsUidResolver));
+
+ config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CAMERA)
+ .trackDeviceStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN)
+ .trackUidStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN,
+ AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
+ .setProcessor(
+ new CameraPowerStatsProcessor(mPowerProfile, mPowerStatsUidResolver));
+
+ config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_GNSS)
+ .trackDeviceStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN)
+ .trackUidStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN,
+ AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
+ .setProcessor(
+ new GnssPowerStatsProcessor(mPowerProfile, mPowerStatsUidResolver));
return config;
}
@@ -639,6 +659,12 @@
BatteryConsumer.POWER_COMPONENT_FLASHLIGHT,
Flags.streamlinedMiscBatteryStats());
+ mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_CAMERA,
+ Flags.streamlinedMiscBatteryStats());
+ mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
+ BatteryConsumer.POWER_COMPONENT_CAMERA,
+ Flags.streamlinedMiscBatteryStats());
+
mWorker.systemServicesReady();
mStats.systemServicesReady(mContext);
mCpuWakeupStats.systemServicesReady();
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 5bae5a4..322ed86 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -298,6 +298,8 @@
private final MobileRadioPowerStatsCollector mMobileRadioPowerStatsCollector;
private final WifiPowerStatsCollector mWifiPowerStatsCollector;
private final BluetoothPowerStatsCollector mBluetoothPowerStatsCollector;
+ private final CameraPowerStatsCollector mCameraPowerStatsCollector;
+ private final GnssPowerStatsCollector mGnssPowerStatsCollector;
private final SparseBooleanArray mPowerStatsCollectorEnabled = new SparseBooleanArray();
private final WifiPowerStatsCollector.WifiStatsRetriever mWifiStatsRetriever =
new WifiPowerStatsCollector.WifiStatsRetriever() {
@@ -1963,7 +1965,7 @@
private class PowerStatsCollectorInjector implements CpuPowerStatsCollector.Injector,
MobileRadioPowerStatsCollector.Injector, WifiPowerStatsCollector.Injector,
- BluetoothPowerStatsCollector.Injector {
+ BluetoothPowerStatsCollector.Injector, EnergyConsumerPowerStatsCollector.Injector {
private PackageManager mPackageManager;
private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever;
private NetworkStatsManager mNetworkStatsManager;
@@ -5446,7 +5448,10 @@
final int mappedUid = mapUid(uid);
if (mGpsNesting == 0) {
mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
- HistoryItem.STATE_GPS_ON_FLAG);
+ HistoryItem.STATE_GPS_ON_FLAG, uid, "gnss");
+ if (mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_GNSS)) {
+ mGnssPowerStatsCollector.schedule();
+ }
}
mGpsNesting++;
@@ -5465,11 +5470,14 @@
mGpsNesting--;
if (mGpsNesting == 0) {
mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
- HistoryItem.STATE_GPS_ON_FLAG);
+ HistoryItem.STATE_GPS_ON_FLAG, uid, "gnss");
mHistory.recordGpsSignalQualityEvent(elapsedRealtimeMs, uptimeMs,
GPS_SIGNAL_QUALITY_NONE);
stopAllGpsSignalQualityTimersLocked(-1, elapsedRealtimeMs);
mGpsSignalQualityBin = -1;
+ if (mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_GNSS)) {
+ mGnssPowerStatsCollector.schedule();
+ }
}
mFrameworkStatsLogger.gpsScanStateChanged(mapIsolatedUid(uid), workChain, /* on */ false);
@@ -6652,13 +6660,17 @@
uid = mapUid(uid);
if (mCameraOnNesting++ == 0) {
mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
- HistoryItem.STATE2_CAMERA_FLAG);
+ HistoryItem.STATE2_CAMERA_FLAG, uid, "camera");
mCameraOnTimer.startRunningLocked(elapsedRealtimeMs);
}
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
.noteCameraTurnedOnLocked(elapsedRealtimeMs);
- scheduleSyncExternalStatsLocked("camera-on", ExternalStatsSync.UPDATE_CAMERA);
+ if (mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_CAMERA)) {
+ mCameraPowerStatsCollector.schedule();
+ } else {
+ scheduleSyncExternalStatsLocked("camera-on", ExternalStatsSync.UPDATE_CAMERA);
+ }
}
@GuardedBy("this")
@@ -6669,13 +6681,17 @@
uid = mapUid(uid);
if (--mCameraOnNesting == 0) {
mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
- HistoryItem.STATE2_CAMERA_FLAG);
+ HistoryItem.STATE2_CAMERA_FLAG, uid, "camera");
mCameraOnTimer.stopRunningLocked(elapsedRealtimeMs);
}
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
.noteCameraTurnedOffLocked(elapsedRealtimeMs);
- scheduleSyncExternalStatsLocked("camera-off", ExternalStatsSync.UPDATE_CAMERA);
+ if (mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_CAMERA)) {
+ mCameraPowerStatsCollector.schedule();
+ } else {
+ scheduleSyncExternalStatsLocked("camera-off", ExternalStatsSync.UPDATE_CAMERA);
+ }
}
@GuardedBy("this")
@@ -11281,6 +11297,12 @@
mPowerStatsCollectorInjector);
mBluetoothPowerStatsCollector.addConsumer(this::recordPowerStats);
+ mCameraPowerStatsCollector = new CameraPowerStatsCollector(mPowerStatsCollectorInjector);
+ mCameraPowerStatsCollector.addConsumer(this::recordPowerStats);
+
+ mGnssPowerStatsCollector = new GnssPowerStatsCollector(mPowerStatsCollectorInjector);
+ mGnssPowerStatsCollector.addConsumer(this::recordPowerStats);
+
mStartCount++;
initTimersAndCounters();
mOnBattery = mOnBatteryInternal = false;
@@ -14703,6 +14725,14 @@
mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_BLUETOOTH));
mBluetoothPowerStatsCollector.schedule();
+ mCameraPowerStatsCollector.setEnabled(
+ mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_CAMERA));
+ mCameraPowerStatsCollector.schedule();
+
+ mGnssPowerStatsCollector.setEnabled(
+ mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_GNSS));
+ mGnssPowerStatsCollector.schedule();
+
mSystemReady = true;
}
@@ -14721,6 +14751,10 @@
return mWifiPowerStatsCollector;
case BatteryConsumer.POWER_COMPONENT_BLUETOOTH:
return mBluetoothPowerStatsCollector;
+ case BatteryConsumer.POWER_COMPONENT_CAMERA:
+ return mCameraPowerStatsCollector;
+ case BatteryConsumer.POWER_COMPONENT_GNSS:
+ return mGnssPowerStatsCollector;
}
return null;
}
@@ -16258,6 +16292,8 @@
mMobileRadioPowerStatsCollector.forceSchedule();
mWifiPowerStatsCollector.forceSchedule();
mBluetoothPowerStatsCollector.forceSchedule();
+ mCameraPowerStatsCollector.forceSchedule();
+ mGnssPowerStatsCollector.forceSchedule();
}
/**
@@ -16278,6 +16314,8 @@
mMobileRadioPowerStatsCollector.collectAndDump(pw);
mWifiPowerStatsCollector.collectAndDump(pw);
mBluetoothPowerStatsCollector.collectAndDump(pw);
+ mCameraPowerStatsCollector.collectAndDump(pw);
+ mGnssPowerStatsCollector.collectAndDump(pw);
}
private final Runnable mWriteAsyncRunnable = () -> {
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index ba6e4a9..ce0ee39 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -95,8 +95,12 @@
}
mPowerCalculators.add(new SensorPowerCalculator(
mContext.getSystemService(SensorManager.class)));
- mPowerCalculators.add(new GnssPowerCalculator(mPowerProfile));
- mPowerCalculators.add(new CameraPowerCalculator(mPowerProfile));
+ if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_GNSS)) {
+ mPowerCalculators.add(new GnssPowerCalculator(mPowerProfile));
+ }
+ if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_CAMERA)) {
+ mPowerCalculators.add(new CameraPowerCalculator(mPowerProfile));
+ }
if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) {
mPowerCalculators.add(new FlashlightPowerCalculator(mPowerProfile));
}
diff --git a/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsProcessor.java
index 490bd5e..599e63d 100644
--- a/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsProcessor.java
@@ -51,7 +51,7 @@
private long mLastUpdateTimestamp;
private PowerStats.Descriptor mDescriptor;
- private final BinaryStatePowerStatsLayout mStatsLayout = new BinaryStatePowerStatsLayout();
+ private final BinaryStatePowerStatsLayout mStatsLayout;
private PowerStats mPowerStats;
private PowerEstimationPlan mPlan;
private long[] mTmpDeviceStatsArray;
@@ -59,9 +59,17 @@
BinaryStatePowerStatsProcessor(int powerComponentId,
PowerStatsUidResolver uidResolver, double averagePowerMilliAmp) {
+ this(powerComponentId, uidResolver, averagePowerMilliAmp,
+ new BinaryStatePowerStatsLayout());
+ }
+
+ BinaryStatePowerStatsProcessor(int powerComponentId,
+ PowerStatsUidResolver uidResolver, double averagePowerMilliAmp,
+ BinaryStatePowerStatsLayout statsLayout) {
mPowerComponentId = powerComponentId;
mUsageBasedPowerEstimator = new UsageBasedPowerEstimator(averagePowerMilliAmp);
mUidResolver = uidResolver;
+ mStatsLayout = statsLayout;
}
protected abstract @BinaryState int getBinaryState(BatteryStats.HistoryItem item);
@@ -107,7 +115,7 @@
mInitiatingUid = mUidResolver.mapUid(item.eventTag.uid);
}
} else {
- recordUsageDuration(item.time);
+ recordUsageDuration(mPowerStats, mInitiatingUid, item.time);
mInitiatingUid = Process.INVALID_UID;
if (!mEnergyConsumerSupported) {
flushPowerStats(stats, item.time);
@@ -117,20 +125,16 @@
mLastState = state;
}
- private void recordUsageDuration(long time) {
- if (mLastState == STATE_OFF) {
- return;
- }
-
+ protected void recordUsageDuration(PowerStats powerStats, int uid, long time) {
long durationMs = time - mLastStateTimestamp;
mStatsLayout.setUsageDuration(mPowerStats.stats,
mStatsLayout.getUsageDuration(mPowerStats.stats) + durationMs);
- if (mInitiatingUid != Process.INVALID_UID) {
- long[] uidStats = mPowerStats.uidStats.get(mInitiatingUid);
+ if (uid != Process.INVALID_UID) {
+ long[] uidStats = mPowerStats.uidStats.get(uid);
if (uidStats == null) {
uidStats = new long[mDescriptor.uidStatsArrayLength];
- mPowerStats.uidStats.put(mInitiatingUid, uidStats);
+ mPowerStats.uidStats.put(uid, uidStats);
mStatsLayout.setUidUsageDuration(uidStats, durationMs);
} else {
mStatsLayout.setUsageDuration(mPowerStats.stats,
@@ -143,7 +147,11 @@
void addPowerStats(PowerComponentAggregatedPowerStats stats, PowerStats powerStats,
long timestampMs) {
ensureInitialized();
- recordUsageDuration(timestampMs);
+
+ if (mLastState == STATE_ON) {
+ recordUsageDuration(mPowerStats, mInitiatingUid, timestampMs);
+ }
+
long consumedEnergy = mStatsLayout.getConsumedEnergy(powerStats.stats, 0);
if (consumedEnergy != BatteryStats.POWER_DATA_UNAVAILABLE) {
mEnergyConsumerSupported = true;
@@ -169,14 +177,16 @@
@Override
void finish(PowerComponentAggregatedPowerStats stats, long timestampMs) {
- recordUsageDuration(timestampMs);
+ if (mLastState == STATE_ON) {
+ recordUsageDuration(mPowerStats, mInitiatingUid, timestampMs);
+ }
flushPowerStats(stats, timestampMs);
if (mPlan == null) {
mPlan = new PowerEstimationPlan(stats.getConfig());
}
- computeDevicePowerEstimates(stats);
+ computeDevicePowerEstimates(stats, mPlan, mEnergyConsumerSupported);
combineDevicePowerEstimates(stats);
List<Integer> uids = new ArrayList<>();
@@ -186,9 +196,10 @@
computeUidPowerEstimates(stats, uids);
}
- private void computeDevicePowerEstimates(PowerComponentAggregatedPowerStats stats) {
- for (int i = mPlan.deviceStateEstimations.size() - 1; i >= 0; i--) {
- DeviceStateEstimation estimation = mPlan.deviceStateEstimations.get(i);
+ protected void computeDevicePowerEstimates(PowerComponentAggregatedPowerStats stats,
+ PowerEstimationPlan plan, boolean energyConsumerSupported) {
+ for (int i = plan.deviceStateEstimations.size() - 1; i >= 0; i--) {
+ DeviceStateEstimation estimation = plan.deviceStateEstimations.get(i);
if (!stats.getDeviceStats(mTmpDeviceStatsArray, estimation.stateValues)) {
continue;
}
@@ -196,7 +207,7 @@
long duration = mStatsLayout.getUsageDuration(mTmpDeviceStatsArray);
if (duration > 0) {
double power;
- if (mEnergyConsumerSupported) {
+ if (energyConsumerSupported) {
power = uCtoMah(mStatsLayout.getConsumedEnergy(mTmpDeviceStatsArray, 0));
} else {
power = mUsageBasedPowerEstimator.calculatePower(duration);
diff --git a/services/core/java/com/android/server/power/stats/CameraPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CameraPowerStatsCollector.java
new file mode 100644
index 0000000..8705bd5
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/CameraPowerStatsCollector.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 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.hardware.power.stats.EnergyConsumerType;
+import android.os.BatteryConsumer;
+
+public class CameraPowerStatsCollector extends EnergyConsumerPowerStatsCollector {
+
+ CameraPowerStatsCollector(Injector injector) {
+ super(injector, BatteryConsumer.POWER_COMPONENT_CAMERA,
+ BatteryConsumer.powerComponentIdToString(BatteryConsumer.POWER_COMPONENT_CAMERA),
+ EnergyConsumerType.CAMERA, /* energy consumer name */ null,
+ new BinaryStatePowerStatsLayout());
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/CameraPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/CameraPowerStatsProcessor.java
new file mode 100644
index 0000000..15c3eb8
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/CameraPowerStatsProcessor.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 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.os.BatteryConsumer;
+import android.os.BatteryStats;
+
+import com.android.internal.os.PowerProfile;
+
+public class CameraPowerStatsProcessor extends BinaryStatePowerStatsProcessor {
+ public CameraPowerStatsProcessor(PowerProfile powerProfile,
+ PowerStatsUidResolver uidResolver) {
+ super(BatteryConsumer.POWER_COMPONENT_CAMERA, uidResolver,
+ powerProfile.getAveragePower(PowerProfile.POWER_CAMERA));
+ }
+
+ @Override
+ protected @BinaryState int getBinaryState(BatteryStats.HistoryItem item) {
+ return (item.states2 & BatteryStats.HistoryItem.STATE2_CAMERA_FLAG) != 0
+ ? STATE_ON
+ : STATE_OFF;
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java
new file mode 100644
index 0000000..2021f85
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2024 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.hardware.power.stats.EnergyConsumerType;
+import android.os.Handler;
+import android.os.PersistableBundle;
+import android.util.Slog;
+
+import com.android.internal.os.Clock;
+import com.android.internal.os.PowerStats;
+
+import java.util.function.IntSupplier;
+
+public class EnergyConsumerPowerStatsCollector extends PowerStatsCollector {
+ private static final String TAG = "CameraPowerStatsCollector";
+
+ private static final long CAMERA_ACTIVITY_REQUEST_TIMEOUT = 20000;
+
+ private static final long ENERGY_UNSPECIFIED = -1;
+
+ interface Injector {
+ Handler getHandler();
+ Clock getClock();
+ PowerStatsUidResolver getUidResolver();
+ long getPowerStatsCollectionThrottlePeriod(String powerComponentName);
+ ConsumedEnergyRetriever getConsumedEnergyRetriever();
+ IntSupplier getVoltageSupplier();
+ }
+
+ private final Injector mInjector;
+ private final int mPowerComponentId;
+ private final String mPowerComponentName;
+ private final int mEnergyConsumerType;
+ private final String mEnergyConsumerName;
+
+ private final BinaryStatePowerStatsLayout mLayout;
+ private boolean mIsInitialized;
+
+ private PowerStats mPowerStats;
+ private ConsumedEnergyRetriever mConsumedEnergyRetriever;
+ private IntSupplier mVoltageSupplier;
+ private int[] mEnergyConsumerIds = new int[0];
+ private long mLastConsumedEnergyUws = ENERGY_UNSPECIFIED;
+ private int mLastVoltageMv;
+ private long mLastUpdateTimestamp;
+ private boolean mFirstCollection = true;
+
+ EnergyConsumerPowerStatsCollector(Injector injector, int powerComponentId,
+ String powerComponentName, @EnergyConsumerType int energyConsumerType,
+ String energyConsumerName, BinaryStatePowerStatsLayout statsLayout) {
+ super(injector.getHandler(),
+ injector.getPowerStatsCollectionThrottlePeriod(powerComponentName),
+ injector.getUidResolver(), injector.getClock());
+ mInjector = injector;
+ mPowerComponentId = powerComponentId;
+ mPowerComponentName = powerComponentName;
+ mEnergyConsumerType = energyConsumerType;
+ mEnergyConsumerName = energyConsumerName;
+ mLayout = statsLayout;
+ }
+
+ private boolean ensureInitialized() {
+ if (mIsInitialized) {
+ return true;
+ }
+
+ if (!isEnabled()) {
+ return false;
+ }
+
+ mConsumedEnergyRetriever = mInjector.getConsumedEnergyRetriever();
+ mVoltageSupplier = mInjector.getVoltageSupplier();
+ mEnergyConsumerIds = mConsumedEnergyRetriever.getEnergyConsumerIds(mEnergyConsumerType,
+ mEnergyConsumerName);
+
+ PersistableBundle extras = new PersistableBundle();
+ mLayout.toExtras(extras);
+ PowerStats.Descriptor powerStatsDescriptor = new PowerStats.Descriptor(
+ mPowerComponentId, mPowerComponentName, mLayout.getDeviceStatsArrayLength(),
+ null, 0, mLayout.getUidStatsArrayLength(),
+ extras);
+ mPowerStats = new PowerStats(powerStatsDescriptor);
+
+ mIsInitialized = true;
+ return true;
+ }
+
+ @Override
+ protected PowerStats collectStats() {
+ if (!ensureInitialized()) {
+ return null;
+ }
+
+ if (mEnergyConsumerIds.length == 0) {
+ return null;
+ }
+
+ long consumedEnergy = 0;
+ int voltageMv = mVoltageSupplier.getAsInt();
+ if (voltageMv <= 0) {
+ Slog.wtf(TAG, "Unexpected battery voltage (" + voltageMv
+ + " mV) when querying energy consumers");
+ } else {
+ long[] energyUws = mConsumedEnergyRetriever.getConsumedEnergyUws(mEnergyConsumerIds);
+ if (energyUws != null) {
+ for (int i = energyUws.length - 1; i >= 0; i--) {
+ if (energyUws[i] != ENERGY_UNSPECIFIED) {
+ consumedEnergy += energyUws[i];
+ }
+ }
+ }
+ }
+
+ long energyDelta = mLastConsumedEnergyUws != ENERGY_UNSPECIFIED
+ ? consumedEnergy - mLastConsumedEnergyUws : 0;
+ mLastConsumedEnergyUws = consumedEnergy;
+ if (energyDelta < 0) {
+ // Likely, restart of powerstats HAL
+ energyDelta = 0;
+ }
+
+ if (energyDelta == 0 && !mFirstCollection) {
+ return null;
+ }
+
+ int averageVoltage = mLastVoltageMv != 0 ? (mLastVoltageMv + voltageMv) / 2 : voltageMv;
+ mLastVoltageMv = voltageMv;
+ mLayout.setConsumedEnergy(mPowerStats.stats, 0, uJtoUc(energyDelta, averageVoltage));
+ long timestamp = mClock.elapsedRealtime();
+ mPowerStats.durationMs = timestamp - mLastUpdateTimestamp;
+ mLastUpdateTimestamp = timestamp;
+ mFirstCollection = false;
+ return mPowerStats;
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/GnssPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/GnssPowerStatsCollector.java
new file mode 100644
index 0000000..168a874
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/GnssPowerStatsCollector.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 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.hardware.power.stats.EnergyConsumerType;
+import android.os.BatteryConsumer;
+
+public class GnssPowerStatsCollector extends EnergyConsumerPowerStatsCollector {
+
+ GnssPowerStatsCollector(Injector injector) {
+ super(injector, BatteryConsumer.POWER_COMPONENT_GNSS,
+ BatteryConsumer.powerComponentIdToString(BatteryConsumer.POWER_COMPONENT_GNSS),
+ EnergyConsumerType.GNSS, /* energy consumer name */ null,
+ new GnssPowerStatsLayout());
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/GnssPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/GnssPowerStatsLayout.java
new file mode 100644
index 0000000..9a1317d
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/GnssPowerStatsLayout.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 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.location.GnssSignalQuality;
+import android.os.PersistableBundle;
+
+class GnssPowerStatsLayout extends BinaryStatePowerStatsLayout {
+ private static final String EXTRA_DEVICE_TIME_SIGNAL_LEVEL_POSITION = "dt-sig";
+ private static final String EXTRA_UID_TIME_SIGNAL_LEVEL_POSITION = "ut-sig";
+
+ private int mDeviceSignalLevelTimePosition;
+ private int mUidSignalLevelTimePosition;
+
+ GnssPowerStatsLayout() {
+ mDeviceSignalLevelTimePosition = addDeviceSection(
+ GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS, "level");
+ mUidSignalLevelTimePosition = addUidSection(
+ GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS, "level");
+ }
+
+ @Override
+ public void fromExtras(PersistableBundle extras) {
+ super.fromExtras(extras);
+ mDeviceSignalLevelTimePosition = extras.getInt(EXTRA_DEVICE_TIME_SIGNAL_LEVEL_POSITION);
+ mUidSignalLevelTimePosition = extras.getInt(EXTRA_UID_TIME_SIGNAL_LEVEL_POSITION);
+ }
+
+ @Override
+ public void toExtras(PersistableBundle extras) {
+ super.toExtras(extras);
+ extras.putInt(EXTRA_DEVICE_TIME_SIGNAL_LEVEL_POSITION, mDeviceSignalLevelTimePosition);
+ extras.putInt(EXTRA_UID_TIME_SIGNAL_LEVEL_POSITION, mUidSignalLevelTimePosition);
+ }
+
+ public void setDeviceSignalLevelTime(long[] stats, int signalLevel, long durationMillis) {
+ stats[mDeviceSignalLevelTimePosition + signalLevel] = durationMillis;
+ }
+
+ public long getDeviceSignalLevelTime(long[] stats, int signalLevel) {
+ return stats[mDeviceSignalLevelTimePosition + signalLevel];
+ }
+
+ public void setUidSignalLevelTime(long[] stats, int signalLevel, long durationMillis) {
+ stats[mUidSignalLevelTimePosition + signalLevel] = durationMillis;
+ }
+
+ public long getUidSignalLevelTime(long[] stats, int signalLevel) {
+ return stats[mUidSignalLevelTimePosition + signalLevel];
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/GnssPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/GnssPowerStatsProcessor.java
new file mode 100644
index 0000000..572bde9
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/GnssPowerStatsProcessor.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2024 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.location.GnssSignalQuality;
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.Process;
+
+import com.android.internal.os.PowerProfile;
+import com.android.internal.os.PowerStats;
+
+import java.util.Arrays;
+
+public class GnssPowerStatsProcessor extends BinaryStatePowerStatsProcessor {
+ private int mGnssSignalLevel = GnssSignalQuality.GNSS_SIGNAL_QUALITY_UNKNOWN;
+ private long mGnssSignalLevelTimestamp;
+ private final long[] mGnssSignalDurations =
+ new long[GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS];
+ private static final GnssPowerStatsLayout sStatsLayout = new GnssPowerStatsLayout();
+ private final UsageBasedPowerEstimator[] mSignalLevelEstimators =
+ new UsageBasedPowerEstimator[GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS];
+ private final boolean mUseSignalLevelEstimators;
+ private long[] mTmpDeviceStatsArray;
+
+ public GnssPowerStatsProcessor(PowerProfile powerProfile, PowerStatsUidResolver uidResolver) {
+ super(BatteryConsumer.POWER_COMPONENT_GNSS, uidResolver,
+ powerProfile.getAveragePower(PowerProfile.POWER_GPS_ON),
+ sStatsLayout);
+
+ boolean useSignalLevelEstimators = false;
+ for (int level = 0; level < GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS; level++) {
+ double power = powerProfile.getAveragePower(
+ PowerProfile.POWER_GPS_SIGNAL_QUALITY_BASED, level);
+ if (power != 0) {
+ useSignalLevelEstimators = true;
+ }
+ mSignalLevelEstimators[level] = new UsageBasedPowerEstimator(power);
+ }
+ mUseSignalLevelEstimators = useSignalLevelEstimators;
+ }
+
+ @Override
+ protected @BinaryState int getBinaryState(BatteryStats.HistoryItem item) {
+ if ((item.states & BatteryStats.HistoryItem.STATE_GPS_ON_FLAG) == 0) {
+ mGnssSignalLevel = GnssSignalQuality.GNSS_SIGNAL_QUALITY_UNKNOWN;
+ return STATE_OFF;
+ }
+
+ noteGnssSignalLevel(item);
+ return STATE_ON;
+ }
+
+ private void noteGnssSignalLevel(BatteryStats.HistoryItem item) {
+ int signalLevel = (item.states2 & BatteryStats.HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK)
+ >> BatteryStats.HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT;
+ if (signalLevel >= GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS) {
+ signalLevel = GnssSignalQuality.GNSS_SIGNAL_QUALITY_UNKNOWN;
+ }
+ if (signalLevel == mGnssSignalLevel) {
+ return;
+ }
+
+ if (mGnssSignalLevel != GnssSignalQuality.GNSS_SIGNAL_QUALITY_UNKNOWN) {
+ mGnssSignalDurations[mGnssSignalLevel] += item.time - mGnssSignalLevelTimestamp;
+ }
+ mGnssSignalLevel = signalLevel;
+ mGnssSignalLevelTimestamp = item.time;
+ }
+
+ @Override
+ protected void recordUsageDuration(PowerStats powerStats, int uid, long time) {
+ super.recordUsageDuration(powerStats, uid, time);
+
+ if (mGnssSignalLevel != GnssSignalQuality.GNSS_SIGNAL_QUALITY_UNKNOWN) {
+ mGnssSignalDurations[mGnssSignalLevel] += time - mGnssSignalLevelTimestamp;
+ } else if (mUseSignalLevelEstimators) {
+ // Default GNSS signal quality to GOOD for the purposes of power attribution
+ mGnssSignalDurations[GnssSignalQuality.GNSS_SIGNAL_QUALITY_GOOD] +=
+ time - mGnssSignalLevelTimestamp;
+ }
+
+ for (int level = 0; level < GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS; level++) {
+ long duration = mGnssSignalDurations[level];
+ sStatsLayout.setDeviceSignalLevelTime(powerStats.stats, level, duration);
+ if (uid != Process.INVALID_UID) {
+ long[] uidStats = powerStats.uidStats.get(uid);
+ if (uidStats == null) {
+ uidStats = new long[powerStats.descriptor.uidStatsArrayLength];
+ powerStats.uidStats.put(uid, uidStats);
+ sStatsLayout.setUidSignalLevelTime(uidStats, level, duration);
+ } else {
+ sStatsLayout.setUidSignalLevelTime(uidStats, level,
+ sStatsLayout.getUidSignalLevelTime(uidStats, level) + duration);
+ }
+ }
+ }
+
+ mGnssSignalLevelTimestamp = time;
+ Arrays.fill(mGnssSignalDurations, 0);
+ }
+
+ protected void computeDevicePowerEstimates(PowerComponentAggregatedPowerStats stats,
+ PowerEstimationPlan plan, boolean energyConsumerSupported) {
+ if (!mUseSignalLevelEstimators || energyConsumerSupported) {
+ super.computeDevicePowerEstimates(stats, plan, energyConsumerSupported);
+ return;
+ }
+
+ if (mTmpDeviceStatsArray == null) {
+ mTmpDeviceStatsArray = new long[stats.getPowerStatsDescriptor().statsArrayLength];
+ }
+
+ for (int i = plan.deviceStateEstimations.size() - 1; i >= 0; i--) {
+ DeviceStateEstimation estimation = plan.deviceStateEstimations.get(i);
+ if (!stats.getDeviceStats(mTmpDeviceStatsArray, estimation.stateValues)) {
+ continue;
+ }
+
+ double power = 0;
+ for (int level = 0; level < GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS; level++) {
+ long duration = sStatsLayout.getDeviceSignalLevelTime(mTmpDeviceStatsArray, level);
+ power += mSignalLevelEstimators[level].calculatePower(duration);
+ }
+ sStatsLayout.setDevicePowerEstimate(mTmpDeviceStatsArray, power);
+ stats.setDeviceStats(estimation.stateValues, mTmpDeviceStatsArray);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
index b82c021..d442c61 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
@@ -231,10 +231,14 @@
}
interface ConsumedEnergyRetriever {
- int[] getEnergyConsumerIds(@EnergyConsumerType int energyConsumerType);
+ int[] getEnergyConsumerIds(@EnergyConsumerType int energyConsumerType, String name);
@Nullable
long[] getConsumedEnergyUws(int[] energyConsumerIds);
+
+ default int[] getEnergyConsumerIds(@EnergyConsumerType int energyConsumerType) {
+ return getEnergyConsumerIds(energyConsumerType, null);
+ }
}
static class ConsumedEnergyRetrieverImpl implements ConsumedEnergyRetriever {
@@ -245,7 +249,7 @@
}
@Override
- public int[] getEnergyConsumerIds(int energyConsumerType) {
+ public int[] getEnergyConsumerIds(int energyConsumerType, String name) {
if (mPowerStatsInternal == null) {
return new int[0];
}
@@ -257,7 +261,8 @@
List<EnergyConsumer> energyConsumers = new ArrayList<>();
for (EnergyConsumer energyConsumer : energyConsumerInfo) {
- if (energyConsumer.type == energyConsumerType) {
+ if (energyConsumer.type == energyConsumerType
+ && (name == null || name.equals(energyConsumer.name))) {
energyConsumers.add(energyConsumer);
}
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
index 976cc18..a3f0770 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
@@ -91,7 +91,7 @@
final Parcel parcel = Parcel.obtain();
parcel.writeParcelable(outBatteryUsageStats, 0);
- assertThat(parcel.dataSize()).isLessThan(10000);
+ assertThat(parcel.dataSize()).isLessThan(12000);
parcel.setDataPosition(0);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerStatsTest.java
new file mode 100644
index 0000000..36deb08
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerStatsTest.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2024 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 static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND;
+import static android.os.BatteryConsumer.PROCESS_STATE_CACHED;
+import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND;
+import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE;
+
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.hardware.power.stats.EnergyConsumerType;
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.Handler;
+import android.os.Process;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import com.android.internal.os.Clock;
+import com.android.internal.os.MonotonicClock;
+import com.android.internal.os.PowerProfile;
+import com.android.internal.os.PowerStats;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.IntSupplier;
+
+public class CameraPowerStatsTest {
+ @Rule(order = 0)
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setProvideMainThread(true)
+ .build();
+
+ @Rule(order = 1)
+ public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+ .setAveragePower(PowerProfile.POWER_CAMERA, 100.0)
+ .initMeasuredEnergyStatsLocked();
+
+ private static final double PRECISION = 0.00001;
+ private static final int APP_UID1 = Process.FIRST_APPLICATION_UID + 42;
+ private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 101;
+ private static final int VOLTAGE_MV = 3500;
+ private static final int ENERGY_CONSUMER_ID = 777;
+
+ private final PowerStatsUidResolver mUidResolver = new PowerStatsUidResolver();
+ @Mock
+ private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever;
+
+ EnergyConsumerPowerStatsCollector.Injector mInjector =
+ new EnergyConsumerPowerStatsCollector.Injector() {
+ @Override
+ public Handler getHandler() {
+ return mStatsRule.getHandler();
+ }
+
+ @Override
+ public Clock getClock() {
+ return mStatsRule.getMockClock();
+ }
+
+ @Override
+ public PowerStatsUidResolver getUidResolver() {
+ return mUidResolver;
+ }
+
+ @Override
+ public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+ return 0;
+ }
+
+ @Override
+ public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() {
+ return mConsumedEnergyRetriever;
+ }
+
+ @Override
+ public IntSupplier getVoltageSupplier() {
+ return () -> VOLTAGE_MV;
+ }
+ };
+
+ private MonotonicClock mMonotonicClock;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mMonotonicClock = new MonotonicClock(0, mStatsRule.getMockClock());
+ }
+
+ @Test
+ public void energyConsumerModel() {
+ when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.CAMERA, null))
+ .thenReturn(new int[]{ENERGY_CONSUMER_ID});
+ CameraPowerStatsProcessor processor = new CameraPowerStatsProcessor(
+ mStatsRule.getPowerProfile(), mUidResolver);
+
+ PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats(processor);
+
+ CameraPowerStatsCollector collector = new CameraPowerStatsCollector(mInjector);
+ collector.addConsumer(
+ powerStats -> {
+ processor.addPowerStats(stats, powerStats, mMonotonicClock.monotonicTime());
+ });
+ collector.setEnabled(true);
+
+ // Establish a baseline
+ when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{ENERGY_CONSUMER_ID}))
+ .thenReturn(new long[]{uCtoUj(10000)});
+ collector.collectAndDeliverStats();
+
+ processor.noteStateChange(stats, buildHistoryItem(0, true, APP_UID1));
+
+ // Turn the screen off after 2.5 seconds
+ stats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500);
+ stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500);
+ stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE, 5000);
+
+ processor.noteStateChange(stats, buildHistoryItem(6000, false, APP_UID1));
+
+ when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{ENERGY_CONSUMER_ID}))
+ .thenReturn(new long[]{uCtoUj(2_170_000)});
+ collector.collectAndDeliverStats();
+
+ processor.noteStateChange(stats, buildHistoryItem(7000, true, APP_UID2));
+
+ mStatsRule.setTime(11_000, 11_000);
+ when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{ENERGY_CONSUMER_ID}))
+ .thenReturn(new long[]{uCtoUj(3_610_000)});
+ collector.collectAndDeliverStats();
+
+ processor.finish(stats, 11_000);
+
+ PowerStats.Descriptor descriptor = stats.getPowerStatsDescriptor();
+ BinaryStatePowerStatsLayout statsLayout = new BinaryStatePowerStatsLayout();
+ statsLayout.fromExtras(descriptor.extras);
+
+ // Total estimated power = 3,600,000 uC = 1.0 mAh
+ // of which 3,000,000 is distributed:
+ // Screen-on - 2500/6000 * 2160000 = 900000 uC = 0.25 mAh
+ // Screen-off - 3500/6000 * 2160000 = 1260000 uC = 0.35 mAh
+ // and 600,000 was fully with screen off:
+ // Screen-off - 1440000 uC = 0.4 mAh
+ long[] deviceStats = new long[descriptor.statsArrayLength];
+ stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON));
+ assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+ .isWithin(PRECISION).of(0.25);
+
+ stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER));
+ assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+ .isWithin(PRECISION).of(0.35 + 0.4);
+
+ // UID1 =
+ // 2,160,000 uC = 0.6 mAh
+ // split between three different states
+ // fg screen-on: 2500/6000
+ // bg screen-off: 2500/6000
+ // fgs screen-off: 1000/6000
+ double expectedPower1 = 0.6;
+ long[] uidStats = new long[descriptor.uidStatsArrayLength];
+ stats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower1 * 2500 / 6000);
+
+
+ stats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower1 * 2500 / 6000);
+
+ stats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower1 * 1000 / 6000);
+
+ // UID2 =
+ // 1440000 mA-ms = 0.4 mAh
+ // all in the same state
+ double expectedPower2 = 0.4;
+ stats.getUidStats(uidStats, APP_UID2,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower2);
+
+ stats.getUidStats(uidStats, APP_UID2,
+ states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(0);
+ }
+
+ private BatteryStats.HistoryItem buildHistoryItem(int timestamp, boolean stateOn,
+ int uid) {
+ mStatsRule.setTime(timestamp, timestamp);
+ BatteryStats.HistoryItem historyItem = new BatteryStats.HistoryItem();
+ historyItem.time = mMonotonicClock.monotonicTime();
+ historyItem.states2 = stateOn ? BatteryStats.HistoryItem.STATE2_CAMERA_FLAG : 0;
+ if (stateOn) {
+ historyItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE
+ | BatteryStats.HistoryItem.EVENT_FLAG_START;
+ } else {
+ historyItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE
+ | BatteryStats.HistoryItem.EVENT_FLAG_FINISH;
+ }
+ historyItem.eventTag = historyItem.localEventTag;
+ historyItem.eventTag.uid = uid;
+ historyItem.eventTag.string = "camera";
+ return historyItem;
+ }
+
+ private int[] states(int... states) {
+ return states;
+ }
+
+ private static PowerComponentAggregatedPowerStats createAggregatedPowerStats(
+ BinaryStatePowerStatsProcessor processor) {
+ AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
+ config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CAMERA)
+ .trackDeviceStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN)
+ .trackUidStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN,
+ AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
+ .setProcessor(processor);
+
+ AggregatedPowerStats aggregatedPowerStats = new AggregatedPowerStats(config);
+ PowerComponentAggregatedPowerStats powerComponentStats =
+ aggregatedPowerStats.getPowerComponentStats(BatteryConsumer.POWER_COMPONENT_CAMERA);
+ processor.start(powerComponentStats, 0);
+
+ powerComponentStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
+ powerComponentStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0);
+ powerComponentStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0);
+ powerComponentStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0);
+
+ return powerComponentStats;
+ }
+
+ private static long uCtoUj(long uc) {
+ return (long) (uc * (double) VOLTAGE_MV / 1000);
+ }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerStatsTest.java
new file mode 100644
index 0000000..8a391c6
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerStatsTest.java
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) 2024 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 static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND;
+import static android.os.BatteryConsumer.PROCESS_STATE_CACHED;
+import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND;
+import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE;
+
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.hardware.power.stats.EnergyConsumerType;
+import android.location.GnssSignalQuality;
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.Handler;
+import android.os.Process;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import com.android.internal.os.Clock;
+import com.android.internal.os.MonotonicClock;
+import com.android.internal.os.PowerProfile;
+import com.android.internal.os.PowerStats;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.IntSupplier;
+
+public class GnssPowerStatsTest {
+ @Rule(order = 0)
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setProvideMainThread(true)
+ .build();
+
+ @Rule(order = 1)
+ public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+ .setAveragePower(PowerProfile.POWER_GPS_ON, 100.0)
+ .setAveragePower(PowerProfile.POWER_GPS_SIGNAL_QUALITY_BASED, new double[]{1000, 100})
+ .initMeasuredEnergyStatsLocked();
+
+ private static final double PRECISION = 0.00001;
+ private static final int APP_UID1 = Process.FIRST_APPLICATION_UID + 42;
+ private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 101;
+ private static final int VOLTAGE_MV = 3500;
+ private static final int ENERGY_CONSUMER_ID = 777;
+
+ private final PowerStatsUidResolver mUidResolver = new PowerStatsUidResolver();
+ @Mock
+ private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever;
+
+ EnergyConsumerPowerStatsCollector.Injector mInjector =
+ new EnergyConsumerPowerStatsCollector.Injector() {
+ @Override
+ public Handler getHandler() {
+ return mStatsRule.getHandler();
+ }
+
+ @Override
+ public Clock getClock() {
+ return mStatsRule.getMockClock();
+ }
+
+ @Override
+ public PowerStatsUidResolver getUidResolver() {
+ return mUidResolver;
+ }
+
+ @Override
+ public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+ return 0;
+ }
+
+ @Override
+ public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() {
+ return mConsumedEnergyRetriever;
+ }
+
+ @Override
+ public IntSupplier getVoltageSupplier() {
+ return () -> VOLTAGE_MV;
+ }
+ };
+
+ private MonotonicClock mMonotonicClock;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mMonotonicClock = new MonotonicClock(0, mStatsRule.getMockClock());
+ }
+
+ @Test
+ public void powerProfileModel() {
+ // ODPM unsupported
+ when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.GNSS, null))
+ .thenReturn(new int[0]);
+ GnssPowerStatsProcessor processor = new GnssPowerStatsProcessor(
+ mStatsRule.getPowerProfile(), mUidResolver);
+
+ PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats(processor);
+
+ GnssPowerStatsCollector collector = new GnssPowerStatsCollector(mInjector);
+ collector.addConsumer(
+ powerStats -> {
+ processor.addPowerStats(stats, powerStats, mMonotonicClock.monotonicTime());
+ });
+ collector.setEnabled(true);
+
+ // Establish a baseline
+ collector.collectAndDeliverStats();
+
+ processor.noteStateChange(stats, buildHistoryItem(0, true, APP_UID1));
+
+ // Turn the screen off after 2.5 seconds
+ stats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500);
+ stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500);
+ stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE, 5000);
+
+ processor.noteStateChange(stats, buildHistoryItem(6000, false, APP_UID1));
+
+ collector.collectAndDeliverStats();
+
+ processor.noteStateChange(stats, buildHistoryItem(7000, true, APP_UID2));
+ processor.noteStateChange(stats, buildHistoryItem(7000,
+ GnssSignalQuality.GNSS_SIGNAL_QUALITY_GOOD));
+ processor.noteStateChange(stats, buildHistoryItem(8000,
+ GnssSignalQuality.GNSS_SIGNAL_QUALITY_POOR));
+ mStatsRule.setTime(11_000, 11_000);
+ collector.collectAndDeliverStats();
+
+ processor.finish(stats, 11_000);
+
+ PowerStats.Descriptor descriptor = stats.getPowerStatsDescriptor();
+ BinaryStatePowerStatsLayout statsLayout = new BinaryStatePowerStatsLayout();
+ statsLayout.fromExtras(descriptor.extras);
+
+ // scr-on, GNSS-good: 2500 * 100 = 250000 mA-ms = 0.06944 mAh
+ // scr-off GNSS=good: 4500 * 100 = 0.12500 mAh
+ // scr-off GNSS=poor: 3000 * 1000 = 0.83333 mAh
+ // scr-off GNSS-on: 0.12500 + 0.83333 = 0.95833 mAh
+ long[] deviceStats = new long[descriptor.statsArrayLength];
+ stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON));
+ assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+ .isWithin(PRECISION).of(0.06944);
+
+ stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER));
+ assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+ .isWithin(PRECISION).of(0.12500 + 0.83333);
+
+ // UID1 =
+ // scr-on FG: 2500 -> 0.06944 mAh
+ // scr-off BG: 2500/7500 * 0.95833 = 0.31944 mAh
+ // scr-off FGS: 1000/7500 * 0.95833 = 0.12777 mAh
+ long[] uidStats = new long[descriptor.uidStatsArrayLength];
+ stats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(0.06944);
+
+ stats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(0.31944);
+
+ stats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(0.12777);
+
+ // UID2 =
+ // scr-off cached: 4000/7500 * 0.95833 = 0.51111 mAh
+ stats.getUidStats(uidStats, APP_UID2,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(0.51111);
+
+ stats.getUidStats(uidStats, APP_UID2,
+ states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(0);
+ }
+
+ @Test
+ public void energyConsumerModel() {
+ when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.GNSS, null))
+ .thenReturn(new int[]{ENERGY_CONSUMER_ID});
+ GnssPowerStatsProcessor processor = new GnssPowerStatsProcessor(
+ mStatsRule.getPowerProfile(), mUidResolver);
+
+ PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats(processor);
+
+ GnssPowerStatsCollector collector = new GnssPowerStatsCollector(mInjector);
+ collector.addConsumer(
+ powerStats -> {
+ processor.addPowerStats(stats, powerStats, mMonotonicClock.monotonicTime());
+ });
+ collector.setEnabled(true);
+
+ // Establish a baseline
+ when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{ENERGY_CONSUMER_ID}))
+ .thenReturn(new long[]{uCtoUj(10000)});
+ collector.collectAndDeliverStats();
+
+ processor.noteStateChange(stats, buildHistoryItem(0, true, APP_UID1));
+
+ // Turn the screen off after 2.5 seconds
+ stats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500);
+ stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500);
+ stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE, 5000);
+
+ processor.noteStateChange(stats, buildHistoryItem(6000, false, APP_UID1));
+
+ when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{ENERGY_CONSUMER_ID}))
+ .thenReturn(new long[]{uCtoUj(2_170_000)});
+ collector.collectAndDeliverStats();
+
+ processor.noteStateChange(stats, buildHistoryItem(7000, true, APP_UID2));
+ processor.noteStateChange(stats, buildHistoryItem(7000,
+ GnssSignalQuality.GNSS_SIGNAL_QUALITY_GOOD));
+ processor.noteStateChange(stats, buildHistoryItem(8000,
+ GnssSignalQuality.GNSS_SIGNAL_QUALITY_POOR));
+ mStatsRule.setTime(11_000, 11_000);
+ when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{ENERGY_CONSUMER_ID}))
+ .thenReturn(new long[]{uCtoUj(3_610_000)});
+ collector.collectAndDeliverStats();
+
+ processor.finish(stats, 11_000);
+
+ PowerStats.Descriptor descriptor = stats.getPowerStatsDescriptor();
+ BinaryStatePowerStatsLayout statsLayout = new BinaryStatePowerStatsLayout();
+ statsLayout.fromExtras(descriptor.extras);
+
+ // Total estimated power = 3,600,000 uC = 1.0 mAh
+ // of which 3,000,000 is distributed:
+ // Screen-on - 2500/6000 * 2160000 = 900000 uC = 0.25 mAh
+ // Screen-off - 3500/6000 * 2160000 = 1260000 uC = 0.35 mAh
+ // and 600,000 was fully with screen off:
+ // Screen-off - 1440000 uC = 0.4 mAh
+ long[] deviceStats = new long[descriptor.statsArrayLength];
+ stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON));
+ assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+ .isWithin(PRECISION).of(0.25);
+
+ stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER));
+ assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+ .isWithin(PRECISION).of(0.35 + 0.4);
+
+ // UID1 =
+ // 2,160,000 uC = 0.6 mAh
+ // split between three different states
+ // fg screen-on: 2500/6000
+ // bg screen-off: 2500/6000
+ // fgs screen-off: 1000/6000
+ double expectedPower1 = 0.6;
+ long[] uidStats = new long[descriptor.uidStatsArrayLength];
+ stats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower1 * 2500 / 6000);
+
+ stats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower1 * 2500 / 6000);
+
+ stats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower1 * 1000 / 6000);
+
+ // UID2 =
+ // 1440000 mA-ms = 0.4 mAh
+ // all in the same state
+ double expectedPower2 = 0.4;
+ stats.getUidStats(uidStats, APP_UID2,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower2);
+
+ stats.getUidStats(uidStats, APP_UID2,
+ states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(0);
+ }
+
+ private BatteryStats.HistoryItem buildHistoryItem(int timestamp, boolean stateOn,
+ int uid) {
+ mStatsRule.setTime(timestamp, timestamp);
+ BatteryStats.HistoryItem historyItem = new BatteryStats.HistoryItem();
+ historyItem.time = mMonotonicClock.monotonicTime();
+ historyItem.states = stateOn ? BatteryStats.HistoryItem.STATE_GPS_ON_FLAG : 0;
+ if (stateOn) {
+ historyItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE
+ | BatteryStats.HistoryItem.EVENT_FLAG_START;
+ } else {
+ historyItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE
+ | BatteryStats.HistoryItem.EVENT_FLAG_FINISH;
+ }
+ historyItem.eventTag = historyItem.localEventTag;
+ historyItem.eventTag.uid = uid;
+ historyItem.eventTag.string = "gnss";
+ return historyItem;
+ }
+
+ private BatteryStats.HistoryItem buildHistoryItem(int timestamp, int signalLevel) {
+ mStatsRule.setTime(timestamp, timestamp);
+ BatteryStats.HistoryItem historyItem = new BatteryStats.HistoryItem();
+ historyItem.time = mMonotonicClock.monotonicTime();
+ historyItem.states = BatteryStats.HistoryItem.STATE_GPS_ON_FLAG;
+ historyItem.states2 =
+ signalLevel << BatteryStats.HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT;
+ return historyItem;
+ }
+
+ private int[] states(int... states) {
+ return states;
+ }
+
+ private static PowerComponentAggregatedPowerStats createAggregatedPowerStats(
+ BinaryStatePowerStatsProcessor processor) {
+ AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
+ config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_GNSS)
+ .trackDeviceStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN)
+ .trackUidStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN,
+ AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
+ .setProcessor(processor);
+
+ AggregatedPowerStats aggregatedPowerStats = new AggregatedPowerStats(config);
+ PowerComponentAggregatedPowerStats powerComponentStats =
+ aggregatedPowerStats.getPowerComponentStats(BatteryConsumer.POWER_COMPONENT_GNSS);
+ processor.start(powerComponentStats, 0);
+
+ powerComponentStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
+ powerComponentStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0);
+ powerComponentStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0);
+ powerComponentStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0);
+
+ return powerComponentStats;
+ }
+
+ private static long uCtoUj(long uc) {
+ return (long) (uc * (double) VOLTAGE_MV / 1000);
+ }
+}