diff --git a/src/com/android/settings/fuelgauge/anomaly/AnomalyDetectionPolicy.java b/src/com/android/settings/fuelgauge/anomaly/AnomalyDetectionPolicy.java index 0c401b500f5..dd8ebc7c88e 100644 --- a/src/com/android/settings/fuelgauge/anomaly/AnomalyDetectionPolicy.java +++ b/src/com/android/settings/fuelgauge/anomaly/AnomalyDetectionPolicy.java @@ -17,12 +17,17 @@ package com.android.settings.fuelgauge.anomaly; import android.content.Context; +import android.net.Uri; import android.provider.Settings; import android.support.annotation.VisibleForTesting; import android.text.format.DateUtils; import android.util.KeyValueListParser; import android.util.Log; +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + /** * Class to store the policy for anomaly detection, which comes from * {@link android.provider.Settings.Global} @@ -43,6 +48,8 @@ public class AnomalyDetectionPolicy { @VisibleForTesting static final String KEY_WAKEUP_ALARM_THRESHOLD = "wakeup_alarm_threshold"; @VisibleForTesting + static final String KEY_WAKEUP_BLACKLISTED_TAGS = "wakeup_blacklisted_tags"; + @VisibleForTesting static final String KEY_BLUETOOTH_SCAN_THRESHOLD = "bluetooth_scan_threshold"; /** @@ -93,6 +100,14 @@ public class AnomalyDetectionPolicy { */ public final long wakeupAlarmThreshold; + /** + * Array of blacklisted wakeups, by tag. + * + * @see Settings.Global#ANOMALY_DETECTION_CONSTANTS + * @see #KEY_WAKEUP_BLACKLISTED_TAGS + */ + public final Set wakeupBlacklistedTags; + /** * Threshold for bluetooth unoptimized scanning time in milli seconds * @@ -119,15 +134,18 @@ public class AnomalyDetectionPolicy { Log.e(TAG, "Bad anomaly detection constants"); } - anomalyDetectionEnabled = mParserWrapper.getBoolean(KEY_ANOMALY_DETECTION_ENABLED, true); - wakeLockDetectionEnabled = mParserWrapper.getBoolean(KEY_WAKELOCK_DETECTION_ENABLED, true); - wakeupAlarmDetectionEnabled = mParserWrapper.getBoolean(KEY_WAKEUP_ALARM_DETECTION_ENABLED, - false); + anomalyDetectionEnabled = + mParserWrapper.getBoolean(KEY_ANOMALY_DETECTION_ENABLED, false); + wakeLockDetectionEnabled = + mParserWrapper.getBoolean(KEY_WAKELOCK_DETECTION_ENABLED,false); + wakeupAlarmDetectionEnabled = + mParserWrapper.getBoolean(KEY_WAKEUP_ALARM_DETECTION_ENABLED,false); bluetoothScanDetectionEnabled = mParserWrapper.getBoolean( - KEY_BLUETOOTH_SCAN_DETECTION_ENABLED, true); + KEY_BLUETOOTH_SCAN_DETECTION_ENABLED, false); wakeLockThreshold = mParserWrapper.getLong(KEY_WAKELOCK_THRESHOLD, DateUtils.HOUR_IN_MILLIS); - wakeupAlarmThreshold = mParserWrapper.getLong(KEY_WAKEUP_ALARM_THRESHOLD, 60); + wakeupAlarmThreshold = mParserWrapper.getLong(KEY_WAKEUP_ALARM_THRESHOLD, 10); + wakeupBlacklistedTags = parseStringSet(KEY_WAKEUP_BLACKLISTED_TAGS, null); bluetoothScanThreshold = mParserWrapper.getLong(KEY_BLUETOOTH_SCAN_THRESHOLD, 30 * DateUtils.MINUTE_IN_MILLIS); } @@ -148,4 +166,14 @@ public class AnomalyDetectionPolicy { return false; // Disabled when no this type } } + + private Set parseStringSet(final String key, final Set defaultSet) { + final String value = mParserWrapper.getString(key, null); + if (value != null) { + return Arrays.stream(value.split(":")) + .map(String::trim).map(Uri::decode).collect(Collectors.toSet()); + } else { + return defaultSet; + } + } } diff --git a/src/com/android/settings/fuelgauge/anomaly/KeyValueListParserWrapper.java b/src/com/android/settings/fuelgauge/anomaly/KeyValueListParserWrapper.java index 4a9c2a92b45..7d216c57b44 100644 --- a/src/com/android/settings/fuelgauge/anomaly/KeyValueListParserWrapper.java +++ b/src/com/android/settings/fuelgauge/anomaly/KeyValueListParserWrapper.java @@ -43,6 +43,14 @@ public interface KeyValueListParserWrapper { */ void setString(String str) throws IllegalArgumentException; + /** + * Get the value for key as a string. + * @param key The key to lookup. + * @param defaultValue The value to return if the key was not found. + * @return the string value associated with the key. + */ + String getString(String key, String defaultValue); + /** * Get the value for key as a boolean. * @param key The key to lookup. diff --git a/src/com/android/settings/fuelgauge/anomaly/KeyValueListParserWrapperImpl.java b/src/com/android/settings/fuelgauge/anomaly/KeyValueListParserWrapperImpl.java index f7240341e6f..cf3aa957c13 100644 --- a/src/com/android/settings/fuelgauge/anomaly/KeyValueListParserWrapperImpl.java +++ b/src/com/android/settings/fuelgauge/anomaly/KeyValueListParserWrapperImpl.java @@ -38,6 +38,11 @@ public class KeyValueListParserWrapperImpl implements KeyValueListParserWrapper mParser.setString(str); } + @Override + public String getString(String key, String defaultValue) { + return mParser.getString(key, defaultValue); + } + @Override public boolean getBoolean(String key, boolean defaultValue) { return mParser.getBoolean(key, defaultValue); diff --git a/src/com/android/settings/fuelgauge/anomaly/checker/WakeupAlarmAnomalyDetector.java b/src/com/android/settings/fuelgauge/anomaly/checker/WakeupAlarmAnomalyDetector.java index 8823a177092..936fe30a4ee 100644 --- a/src/com/android/settings/fuelgauge/anomaly/checker/WakeupAlarmAnomalyDetector.java +++ b/src/com/android/settings/fuelgauge/anomaly/checker/WakeupAlarmAnomalyDetector.java @@ -29,10 +29,11 @@ import com.android.settings.fuelgauge.BatteryUtils; import com.android.settings.fuelgauge.anomaly.Anomaly; import com.android.settings.fuelgauge.anomaly.AnomalyDetectionPolicy; import com.android.settings.fuelgauge.anomaly.AnomalyUtils; -import com.android.settings.fuelgauge.anomaly.action.AnomalyAction; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.Set; /** * Check whether apps has too many wakeup alarms @@ -42,6 +43,7 @@ public class WakeupAlarmAnomalyDetector implements AnomalyDetector { @VisibleForTesting BatteryUtils mBatteryUtils; private long mWakeupAlarmThreshold; + private Set mWakeupBlacklistedTags; private Context mContext; private AnomalyUtils mAnomalyUtils; @@ -56,6 +58,7 @@ public class WakeupAlarmAnomalyDetector implements AnomalyDetector { mBatteryUtils = BatteryUtils.getInstance(context); mAnomalyUtils = anomalyUtils; mWakeupAlarmThreshold = policy.wakeupAlarmThreshold; + mWakeupBlacklistedTags = policy.wakeupBlacklistedTags; } @Override @@ -123,11 +126,14 @@ public class WakeupAlarmAnomalyDetector implements AnomalyDetector { final BatteryStats.Uid.Pkg ps = packageStats.valueAt(ipkg); final ArrayMap alarms = ps.getWakeupAlarmStats(); - for (int iwa = alarms.size() - 1; iwa >= 0; iwa--) { - int count = alarms.valueAt(iwa).getCountLocked(BatteryStats.STATS_SINCE_CHARGED); + for (Map.Entry alarm : alarms.entrySet()) { + if (mWakeupBlacklistedTags != null + && mWakeupBlacklistedTags.contains(alarm.getKey())) { + continue; + } + int count = alarm.getValue().getCountLocked(BatteryStats.STATS_SINCE_CHARGED); wakeups += count; } - } return wakeups; diff --git a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyDetectionPolicyTest.java b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyDetectionPolicyTest.java index 914cc2f514d..8d076299374 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyDetectionPolicyTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyDetectionPolicyTest.java @@ -40,11 +40,13 @@ import org.robolectric.annotation.Config; @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) public class AnomalyDetectionPolicyTest { - private static final String ANOMALY_DETECTION_CONSTANTS_VALUE = "anomaly_detection_enabled=true" + private static final String ANOMALY_DETECTION_CONSTANTS_VALUE = + "anomaly_detection_enabled=true" + ",wakelock_enabled=false" + ",wakelock_threshold=3000" + ",wakeup_alarm_enabled=true" + ",wakeup_alarm_threshold=100" + + ",wakeup_blacklisted_tags=tag1:tag2:with%2Ccomma:with%3Acolon" + ",bluetooth_scan_enabled=true" + ",bluetooth_scan_threshold=2000"; private Context mContext; @@ -58,7 +60,7 @@ public class AnomalyDetectionPolicyTest { } @Test - public void testInit_containsDataFromSettings() { + public void testInit_usesConfigValues() { AnomalyDetectionPolicy anomalyDetectionPolicy = createAnomalyPolicyWithConfig(); assertThat(anomalyDetectionPolicy.anomalyDetectionEnabled).isTrue(); @@ -66,12 +68,14 @@ public class AnomalyDetectionPolicyTest { assertThat(anomalyDetectionPolicy.wakeLockThreshold).isEqualTo(3000); assertThat(anomalyDetectionPolicy.wakeupAlarmDetectionEnabled).isTrue(); assertThat(anomalyDetectionPolicy.wakeupAlarmThreshold).isEqualTo(100); + assertThat(anomalyDetectionPolicy.wakeupBlacklistedTags) + .containsExactly("tag1", "tag2", "with,comma", "with:colon"); assertThat(anomalyDetectionPolicy.bluetoothScanDetectionEnabled).isTrue(); assertThat(anomalyDetectionPolicy.bluetoothScanThreshold).isEqualTo(2000); } @Test - public void testInit_containsDefaultData() { + public void testInit_defaultValues() { Settings.Global.putString(mContext.getContentResolver(), Settings.Global.ANOMALY_DETECTION_CONSTANTS, ""); // Mock it to avoid noSuchMethodError @@ -81,18 +85,19 @@ public class AnomalyDetectionPolicyTest { AnomalyDetectionPolicy anomalyDetectionPolicy = new AnomalyDetectionPolicy(mContext, mKeyValueListParserWrapper); - assertThat(anomalyDetectionPolicy.anomalyDetectionEnabled).isTrue(); - assertThat(anomalyDetectionPolicy.wakeLockDetectionEnabled).isTrue(); + assertThat(anomalyDetectionPolicy.anomalyDetectionEnabled).isFalse(); + assertThat(anomalyDetectionPolicy.wakeLockDetectionEnabled).isFalse(); assertThat(anomalyDetectionPolicy.wakeLockThreshold).isEqualTo(DateUtils.HOUR_IN_MILLIS); assertThat(anomalyDetectionPolicy.wakeupAlarmDetectionEnabled).isFalse(); - assertThat(anomalyDetectionPolicy.wakeupAlarmThreshold).isEqualTo(60); - assertThat(anomalyDetectionPolicy.bluetoothScanDetectionEnabled).isTrue(); + assertThat(anomalyDetectionPolicy.wakeupAlarmThreshold).isEqualTo(10); + assertThat(anomalyDetectionPolicy.wakeupBlacklistedTags).isNull(); + assertThat(anomalyDetectionPolicy.bluetoothScanDetectionEnabled).isFalse(); assertThat(anomalyDetectionPolicy.bluetoothScanThreshold).isEqualTo( 30 * DateUtils.MINUTE_IN_MILLIS); } @Test - public void testIsAnomalyDetectorEnabled() { + public void testIsAnomalyDetectorEnabled_usesConfigValues() { AnomalyDetectionPolicy anomalyDetectionPolicy = createAnomalyPolicyWithConfig(); assertThat(anomalyDetectionPolicy.isAnomalyDetectorEnabled( @@ -103,18 +108,37 @@ public class AnomalyDetectionPolicyTest { Anomaly.AnomalyType.BLUETOOTH_SCAN)).isTrue(); } + @Test + public void testIsAnomalyDetectorEnabled_usesDefaultValues() { + Settings.Global.putString(mContext.getContentResolver(), + Settings.Global.ANOMALY_DETECTION_CONSTANTS, ""); + // Mock it to avoid noSuchMethodError + doReturn(true).when(mKeyValueListParserWrapper).getBoolean(anyString(), eq(true)); + doReturn(false).when(mKeyValueListParserWrapper).getBoolean(anyString(), eq(false)); + + AnomalyDetectionPolicy anomalyDetectionPolicy = new AnomalyDetectionPolicy(mContext, + mKeyValueListParserWrapper); + + assertThat(anomalyDetectionPolicy.isAnomalyDetectorEnabled( + Anomaly.AnomalyType.WAKE_LOCK)).isFalse(); + assertThat(anomalyDetectionPolicy.isAnomalyDetectorEnabled( + Anomaly.AnomalyType.WAKEUP_ALARM)).isFalse(); + assertThat(anomalyDetectionPolicy.isAnomalyDetectorEnabled( + Anomaly.AnomalyType.BLUETOOTH_SCAN)).isFalse(); + } + private AnomalyDetectionPolicy createAnomalyPolicyWithConfig() { Settings.Global.putString(mContext.getContentResolver(), Settings.Global.ANOMALY_DETECTION_CONSTANTS, ANOMALY_DETECTION_CONSTANTS_VALUE); // Mock it to avoid noSuchMethodError doReturn(true).when(mKeyValueListParserWrapper).getBoolean( - AnomalyDetectionPolicy.KEY_ANOMALY_DETECTION_ENABLED, true); + AnomalyDetectionPolicy.KEY_ANOMALY_DETECTION_ENABLED, false); doReturn(false).when(mKeyValueListParserWrapper).getBoolean( - AnomalyDetectionPolicy.KEY_WAKELOCK_DETECTION_ENABLED, true); + AnomalyDetectionPolicy.KEY_WAKELOCK_DETECTION_ENABLED, false); doReturn(true).when(mKeyValueListParserWrapper).getBoolean( AnomalyDetectionPolicy.KEY_WAKEUP_ALARM_DETECTION_ENABLED, false); doReturn(true).when(mKeyValueListParserWrapper).getBoolean( - AnomalyDetectionPolicy.KEY_BLUETOOTH_SCAN_DETECTION_ENABLED, true); + AnomalyDetectionPolicy.KEY_BLUETOOTH_SCAN_DETECTION_ENABLED, false); return new AnomalyDetectionPolicy(mContext, mKeyValueListParserWrapper); } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/checker/WakeupAlarmAnomalyDetectorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/checker/WakeupAlarmAnomalyDetectorTest.java index 55be734e232..13a5ab8531f 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/checker/WakeupAlarmAnomalyDetectorTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/checker/WakeupAlarmAnomalyDetectorTest.java @@ -30,6 +30,7 @@ import android.os.BatteryStats; import android.os.Build; import android.text.format.DateUtils; import android.util.ArrayMap; +import android.util.ArraySet; import com.android.internal.os.BatterySipper; import com.android.internal.os.BatteryStatsHelper; @@ -52,6 +53,7 @@ import org.robolectric.util.ReflectionHelpers; import java.util.ArrayList; import java.util.List; +import java.util.Set; @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) @@ -69,6 +71,7 @@ public class WakeupAlarmAnomalyDetectorTest { 1 * DateUtils.HOUR_IN_MILLIS + 10 * DateUtils.MINUTE_IN_MILLIS; private static final int ANOMALY_WAKEUP_COUNT = 500; private static final int NORMAL_WAKEUP_COUNT = 61; + private static final int BLACKLISTED_WAKEUP_COUNT = 37; private static final int ANOMALY_WAKEUP_FREQUENCY = 428; // count per hour @Mock private BatteryStatsHelper mBatteryStatsHelper; @@ -87,12 +90,12 @@ public class WakeupAlarmAnomalyDetectorTest { @Mock private BatteryUtils mBatteryUtils; @Mock - private ApplicationInfo mApplicationInfo; - @Mock private BatteryStats.Uid.Pkg mPkg; @Mock private BatteryStats.Counter mCounter; @Mock + private BatteryStats.Counter mCounter2; + @Mock private AnomalyDetectionPolicy mPolicy; @Mock private AnomalyAction mAnomalyAction; @@ -111,6 +114,9 @@ public class WakeupAlarmAnomalyDetectorTest { mContext = spy(RuntimeEnvironment.application); ReflectionHelpers.setField(mPolicy, "wakeupAlarmThreshold", 60); + final Set blacklistedTags = new ArraySet<>(); + blacklistedTags.add("blacklistedTag"); + ReflectionHelpers.setField(mPolicy, "wakeupBlacklistedTags", blacklistedTags); doReturn(false).when(mBatteryUtils).shouldHideSipper(any()); doReturn(RUNNING_TIME_MS).when(mBatteryUtils).calculateRunningTimeBasedOnStatsType(any(), @@ -207,4 +213,20 @@ public class WakeupAlarmAnomalyDetectorTest { assertThat(mWakeupAlarmAnomalyDetector.getWakeupAlarmCountFromUid(mAnomalyUid)).isEqualTo( 2 * NORMAL_WAKEUP_COUNT); } + + @Test + public void testGetWakeupAlarmCountFromUid_filterOutBlacklistedTags() { + final ArrayMap packageStats = new ArrayMap<>(); + final ArrayMap alarms = new ArrayMap<>(); + doReturn(alarms).when(mPkg).getWakeupAlarmStats(); + doReturn(NORMAL_WAKEUP_COUNT).when(mCounter).getCountLocked(anyInt()); + doReturn(BLACKLISTED_WAKEUP_COUNT).when(mCounter2).getCountLocked(anyInt()); + doReturn(packageStats).when(mAnomalyUid).getPackageStats(); + packageStats.put("", mPkg); + alarms.put("allowedTag", mCounter); + alarms.put("blacklistedTag", mCounter2); + + assertThat(mWakeupAlarmAnomalyDetector.getWakeupAlarmCountFromUid(mAnomalyUid)).isEqualTo( + NORMAL_WAKEUP_COUNT); + } }