diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java index fdc3d650d797a..71d2177233ee0 100644 --- a/core/java/android/os/BatteryConsumer.java +++ b/core/java/android/os/BatteryConsumer.java @@ -45,6 +45,8 @@ public abstract class BatteryConsumer { POWER_COMPONENT_FLASHLIGHT, POWER_COMPONENT_MOBILE_RADIO, POWER_COMPONENT_SYSTEM_SERVICES, + POWER_COMPONENT_SENSORS, + POWER_COMPONENT_GNSS, }) @Retention(RetentionPolicy.SOURCE) public static @interface PowerComponent { @@ -59,8 +61,10 @@ public abstract class BatteryConsumer { public static final int POWER_COMPONENT_FLASHLIGHT = 6; public static final int POWER_COMPONENT_SYSTEM_SERVICES = 7; public static final int POWER_COMPONENT_MOBILE_RADIO = 8; + public static final int POWER_COMPONENT_SENSORS = 9; + public static final int POWER_COMPONENT_GNSS = 10; - public static final int POWER_COMPONENT_COUNT = 9; + public static final int POWER_COMPONENT_COUNT = 11; public static final int FIRST_CUSTOM_POWER_COMPONENT_ID = 1000; public static final int LAST_CUSTOM_POWER_COMPONENT_ID = 9999; @@ -90,6 +94,8 @@ public abstract class BatteryConsumer { TIME_COMPONENT_CAMERA, TIME_COMPONENT_FLASHLIGHT, TIME_COMPONENT_MOBILE_RADIO, + TIME_COMPONENT_SENSORS, + TIME_COMPONENT_GNSS, }) @Retention(RetentionPolicy.SOURCE) public static @interface TimeComponent { @@ -104,8 +110,10 @@ public abstract class BatteryConsumer { public static final int TIME_COMPONENT_VIDEO = 6; public static final int TIME_COMPONENT_FLASHLIGHT = 7; public static final int TIME_COMPONENT_MOBILE_RADIO = 8; + public static final int TIME_COMPONENT_SENSORS = 9; + public static final int TIME_COMPONENT_GNSS = 10; - public static final int TIME_COMPONENT_COUNT = 9; + public static final int TIME_COMPONENT_COUNT = 11; public static final int FIRST_CUSTOM_TIME_COMPONENT_ID = 1000; public static final int LAST_CUSTOM_TIME_COMPONENT_ID = 9999; diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java index 9e59e50cffd09..4a24358ad5d5c 100644 --- a/core/java/com/android/internal/os/BatteryStatsHelper.java +++ b/core/java/com/android/internal/os/BatteryStatsHelper.java @@ -339,8 +339,9 @@ public class BatteryStatsHelper { } mPowerCalculators.add(new WifiPowerCalculator(mPowerProfile)); mPowerCalculators.add(new BluetoothPowerCalculator(mPowerProfile)); - mPowerCalculators.add(new SensorPowerCalculator(mPowerProfile, + mPowerCalculators.add(new SensorPowerCalculator( mContext.getSystemService(SensorManager.class))); + mPowerCalculators.add(new GnssPowerCalculator(mPowerProfile)); mPowerCalculators.add(new CameraPowerCalculator(mPowerProfile)); mPowerCalculators.add(new FlashlightPowerCalculator(mPowerProfile)); mPowerCalculators.add(new MediaPowerCalculator(mPowerProfile)); diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java index e5d64a0e3c847..b8c066db29afa 100644 --- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java +++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java @@ -62,8 +62,9 @@ public class BatteryUsageStatsProvider { } mPowerCalculators.add(new WifiPowerCalculator(mPowerProfile)); mPowerCalculators.add(new BluetoothPowerCalculator(mPowerProfile)); - mPowerCalculators.add(new SensorPowerCalculator(mPowerProfile, + mPowerCalculators.add(new SensorPowerCalculator( mContext.getSystemService(SensorManager.class))); + mPowerCalculators.add(new GnssPowerCalculator(mPowerProfile)); mPowerCalculators.add(new CameraPowerCalculator(mPowerProfile)); mPowerCalculators.add(new FlashlightPowerCalculator(mPowerProfile)); mPowerCalculators.add(new AudioPowerCalculator(mPowerProfile)); diff --git a/core/java/com/android/internal/os/GnssPowerCalculator.java b/core/java/com/android/internal/os/GnssPowerCalculator.java new file mode 100644 index 0000000000000..9ea934af642a3 --- /dev/null +++ b/core/java/com/android/internal/os/GnssPowerCalculator.java @@ -0,0 +1,124 @@ +/* + * 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.location.GnssSignalQuality; +import android.os.BatteryConsumer; +import android.os.BatteryStats; +import android.os.BatteryUsageStats; +import android.os.BatteryUsageStatsQuery; +import android.os.UidBatteryConsumer; +import android.os.UserHandle; +import android.util.SparseArray; + +import java.util.List; + +/** + * Estimates the amount of power consumed by the GNSS (e.g. GPS). + */ +public class GnssPowerCalculator extends PowerCalculator { + private final double mAveragePowerGnssOn; + private final double[] mAveragePowerPerSignalQuality; + + public GnssPowerCalculator(PowerProfile profile) { + mAveragePowerGnssOn = profile.getAveragePowerOrDefault(PowerProfile.POWER_GPS_ON, -1); + mAveragePowerPerSignalQuality = + new double[GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS]; + for (int i = 0; i < GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS; i++) { + mAveragePowerPerSignalQuality[i] = profile.getAveragePower( + PowerProfile.POWER_GPS_SIGNAL_QUALITY_BASED, i); + } + } + + @Override + public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats, + long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query, + SparseArray asUsers) { + final double averageGnssPowerMa = getAverageGnssPower(batteryStats, rawRealtimeUs, + BatteryStats.STATS_SINCE_CHARGED); + final SparseArray uidBatteryConsumerBuilders = + builder.getUidBatteryConsumerBuilders(); + for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) { + final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i); + calculateApp(app, app.getBatteryStatsUid(), rawRealtimeUs, rawUptimeUs, query, + averageGnssPowerMa); + } + } + + private void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u, + long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query, + double averageGnssPowerMa) { + final long durationMs = computeDuration(u, rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED); + double powerMah = computePower(durationMs, averageGnssPowerMa); + app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_GNSS, durationMs) + .setConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS, powerMah); + } + + @Override + public void calculate(List sippers, BatteryStats batteryStats, + long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray asUsers) { + double averageGnssPowerMa = getAverageGnssPower(batteryStats, rawRealtimeUs, statsType); + for (int i = sippers.size() - 1; i >= 0; i--) { + final BatterySipper app = sippers.get(i); + if (app.drainType == BatterySipper.DrainType.APP) { + calculateApp(app, app.uidObj, rawRealtimeUs, statsType, averageGnssPowerMa); + } + } + } + + protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, + int statsType, double averageGnssPowerMa) { + final long durationMs = computeDuration(u, rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED); + double powerMah = computePower(durationMs, averageGnssPowerMa); + + app.gpsTimeMs = durationMs; + app.gpsPowerMah = powerMah; + } + + private long computeDuration(BatteryStats.Uid u, long rawRealtimeUs, int statsType) { + final SparseArray sensorStats = u.getSensorStats(); + final BatteryStats.Uid.Sensor sensor = sensorStats.get(BatteryStats.Uid.Sensor.GPS); + if (sensor == null) { + return 0; + } + + final BatteryStats.Timer timer = sensor.getSensorTime(); + return timer.getTotalTimeLocked(rawRealtimeUs, statsType) / 1000; + } + + private double computePower(long sensorTime, double averageGnssPowerMa) { + return (sensorTime * averageGnssPowerMa) / (1000 * 60 * 60); + } + + private double getAverageGnssPower(BatteryStats stats, long rawRealtimeUs, int statsType) { + double averagePower = mAveragePowerGnssOn; + if (averagePower != -1) { + return averagePower; + } + averagePower = 0; + long totalTime = 0; + double totalPower = 0; + for (int i = 0; i < GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS; i++) { + long timePerLevel = stats.getGpsSignalQualityTime(i, rawRealtimeUs, statsType); + totalTime += timePerLevel; + totalPower += mAveragePowerPerSignalQuality[i] * timePerLevel; + } + if (totalTime != 0) { + averagePower = totalPower / totalTime; + } + return averagePower; + } +} diff --git a/core/java/com/android/internal/os/SensorPowerCalculator.java b/core/java/com/android/internal/os/SensorPowerCalculator.java index 9c8aafb445f34..78c4fe2060f17 100644 --- a/core/java/com/android/internal/os/SensorPowerCalculator.java +++ b/core/java/com/android/internal/os/SensorPowerCalculator.java @@ -17,81 +17,79 @@ package com.android.internal.os; import android.hardware.Sensor; import android.hardware.SensorManager; -import android.location.GnssSignalQuality; +import android.os.BatteryConsumer; import android.os.BatteryStats; -import android.os.UserHandle; +import android.os.BatteryUsageStatsQuery; +import android.os.UidBatteryConsumer; import android.util.SparseArray; import java.util.List; public class SensorPowerCalculator extends PowerCalculator { - private final PowerProfile mPowerProfile; - private final List mSensors; - private double mGpsPower; + private final SparseArray mSensors; - public SensorPowerCalculator(PowerProfile profile, SensorManager sensorManager) { - mPowerProfile = profile; - mSensors = sensorManager.getSensorList(Sensor.TYPE_ALL); + public SensorPowerCalculator(SensorManager sensorManager) { + List sensors = sensorManager.getSensorList(Sensor.TYPE_ALL); + mSensors = new SparseArray<>(sensors.size()); + for (int i = 0; i < sensors.size(); i++) { + Sensor sensor = sensors.get(i); + mSensors.put(sensor.getHandle(), sensor); + } } @Override - public void calculate(List sippers, BatteryStats batteryStats, - long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray asUsers) { - mGpsPower = getAverageGpsPower(batteryStats, rawRealtimeUs, statsType); - super.calculate(sippers, batteryStats, rawRealtimeUs, rawUptimeUs, statsType, asUsers); + protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u, + long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) { + app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_SENSORS, + calculateDuration(u, rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED)) + .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SENSORS, + calculatePowerMah(u, rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED)); } @Override protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, long rawUptimeUs, int statsType) { - // Process Sensor usage + app.sensorPowerMah = calculatePowerMah(u, rawRealtimeUs, statsType); + } + + private long calculateDuration(BatteryStats.Uid u, long rawRealtimeUs, int statsType) { + long durationMs = 0; final SparseArray sensorStats = u.getSensorStats(); final int NSE = sensorStats.size(); for (int ise = 0; ise < NSE; ise++) { - final BatteryStats.Uid.Sensor sensor = sensorStats.valueAt(ise); final int sensorHandle = sensorStats.keyAt(ise); + if (sensorHandle == BatteryStats.Uid.Sensor.GPS) { + continue; + } + + final BatteryStats.Uid.Sensor sensor = sensorStats.valueAt(ise); + final BatteryStats.Timer timer = sensor.getSensorTime(); + durationMs += timer.getTotalTimeLocked(rawRealtimeUs, statsType) / 1000; + } + return durationMs; + } + + private double calculatePowerMah(BatteryStats.Uid u, long rawRealtimeUs, int statsType) { + double powerMah = 0; + final SparseArray sensorStats = u.getSensorStats(); + final int count = sensorStats.size(); + for (int ise = 0; ise < count; ise++) { + final int sensorHandle = sensorStats.keyAt(ise); + // TODO(b/178127364): remove BatteryStats.Uid.Sensor.GPS and references to it. + if (sensorHandle == BatteryStats.Uid.Sensor.GPS) { + continue; + } + + final BatteryStats.Uid.Sensor sensor = sensorStats.valueAt(ise); final BatteryStats.Timer timer = sensor.getSensorTime(); final long sensorTime = timer.getTotalTimeLocked(rawRealtimeUs, statsType) / 1000; - - switch (sensorHandle) { - case BatteryStats.Uid.Sensor.GPS: - app.gpsTimeMs = sensorTime; - app.gpsPowerMah = (app.gpsTimeMs * mGpsPower) / (1000 * 60 * 60); - break; - default: - final int sensorsCount = mSensors.size(); - for (int i = 0; i < sensorsCount; i++) { - final Sensor s = mSensors.get(i); - if (s.getHandle() == sensorHandle) { - app.sensorPowerMah += (sensorTime * s.getPower()) / (1000 * 60 * 60); - break; - } - } - break; + if (sensorTime != 0) { + Sensor s = mSensors.get(sensorHandle); + if (s != null) { + powerMah += (sensorTime * s.getPower()) / (1000 * 60 * 60); + } } } - } - - private double getAverageGpsPower(BatteryStats stats, long rawRealtimeUs, - int statsType) { - double averagePower = - mPowerProfile.getAveragePowerOrDefault(PowerProfile.POWER_GPS_ON, -1); - if (averagePower != -1) { - return averagePower; - } - averagePower = 0; - long totalTime = 0; - double totalPower = 0; - for (int i = 0; i < GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS; i++) { - long timePerLevel = stats.getGpsSignalQualityTime(i, rawRealtimeUs, statsType); - totalTime += timePerLevel; - totalPower += - mPowerProfile.getAveragePower(PowerProfile.POWER_GPS_SIGNAL_QUALITY_BASED, i) - * timePerLevel; - } - if (totalTime != 0) { - averagePower = totalPower / totalTime; - } - return averagePower; + return powerMah; } } diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java index dc332533b2ca8..143e07a1c8a69 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java @@ -46,6 +46,7 @@ import org.junit.runners.Suite; BstatsCpuTimesValidationTest.class, CameraPowerCalculatorTest.class, FlashlightPowerCalculatorTest.class, + GnssPowerCalculatorTest.class, IdlePowerCalculatorTest.class, KernelCpuProcStringReaderTest.class, KernelCpuUidActiveTimeReaderTest.class, @@ -62,6 +63,7 @@ import org.junit.runners.Suite; PowerCalculatorTest.class, PowerProfileTest.class, ScreenPowerCalculatorTest.class, + SensorPowerCalculatorTest.class, SystemServicePowerCalculatorTest.class, VideoPowerCalculatorTest.class, diff --git a/core/tests/coretests/src/com/android/internal/os/GnssPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/GnssPowerCalculatorTest.java new file mode 100644 index 0000000000000..bd7f1e2c34103 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/GnssPowerCalculatorTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2021 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 static com.google.common.truth.Truth.assertThat; + +import android.os.BatteryConsumer; +import android.os.Process; +import android.os.UidBatteryConsumer; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class GnssPowerCalculatorTest { + private static final double PRECISION = 0.00001; + + private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42; + + @Rule + public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule() + .setAveragePower(PowerProfile.POWER_GPS_ON, 360.0) + .setAveragePower(PowerProfile.POWER_GPS_SIGNAL_QUALITY_BASED, + new double[] {720.0, 1440.0, 1800.0}); + + @Test + public void testTimerBasedModel() { + BatteryStatsImpl.Uid uidStats = mStatsRule.getUidStats(APP_UID); + uidStats.noteStartGps(1000); + uidStats.noteStopGps(2000); + + GnssPowerCalculator calculator = + new GnssPowerCalculator(mStatsRule.getPowerProfile()); + + mStatsRule.apply(calculator); + + UidBatteryConsumer consumer = mStatsRule.getUidBatteryConsumer(APP_UID); + assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_GNSS)) + .isEqualTo(1000); + assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS)) + .isWithin(PRECISION).of(0.1); + } +} diff --git a/core/tests/coretests/src/com/android/internal/os/SensorPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/SensorPowerCalculatorTest.java new file mode 100644 index 0000000000000..b50435bd29286 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/SensorPowerCalculatorTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2021 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 static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.hardware.Sensor; +import android.hardware.SensorManager; +import android.hardware.input.InputSensorInfo; +import android.os.BatteryConsumer; +import android.os.Process; +import android.os.UidBatteryConsumer; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.List; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class SensorPowerCalculatorTest { + private static final double PRECISION = 0.00001; + + private static final int SENSOR_HANDLE_1 = 1; + private static final int SENSOR_HANDLE_2 = 2; + + private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42; + + @Rule + public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule(); + + @Test + public void testTimerBasedModel() { + Sensor sensor1 = createSensor(SENSOR_HANDLE_1, Sensor.TYPE_AMBIENT_TEMPERATURE, 360); + Sensor sensor2 = createSensor(SENSOR_HANDLE_2, Sensor.TYPE_STEP_COUNTER, 720); + + SensorManager sensorManager = mock(SensorManager.class); + when(sensorManager.getSensorList(Sensor.TYPE_ALL)) + .thenReturn(List.of(sensor1, sensor2)); + + BatteryStatsImpl.Uid uidStats = mStatsRule.getUidStats(APP_UID); + uidStats.noteStartSensor(SENSOR_HANDLE_1, 1000); + uidStats.noteStopSensor(SENSOR_HANDLE_1, 2000); + uidStats.noteStartSensor(SENSOR_HANDLE_2, 3000); + uidStats.noteStopSensor(SENSOR_HANDLE_2, 5000); + + SensorPowerCalculator calculator = new SensorPowerCalculator(sensorManager); + + mStatsRule.apply(calculator); + + UidBatteryConsumer consumer = mStatsRule.getUidBatteryConsumer(APP_UID); + assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_SENSORS)) + .isEqualTo(3000); + assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SENSORS)) + .isWithin(PRECISION).of(0.5); + } + + private Sensor createSensor(int handle, int type, double power) { + return new Sensor(new InputSensorInfo("name", "vendor", 0 /* version */, + handle, type, 100.0f /*maxRange */, 0.02f /* resolution */, + (float) power, 1000 /* minDelay */, 0 /* fifoReservedEventCount */, + 0 /* fifoMaxEventCount */, "" /* stringType */, "" /* requiredPermission */, + 0 /* maxDelay */, 0 /* flags */, 0 /* id */)); + } +}