From e063427d0b9c8e1269984100dbfb9b741ba0b61b Mon Sep 17 00:00:00 2001 From: Andrew Sapperstein Date: Thu, 5 Oct 2017 18:19:32 -0700 Subject: [PATCH] Update wakeup anomaly to exclude blacklisted wakeups. Also disables all anomalies out of the box. Provides the ability to ignore certain wakeups if blacklisted in AnomalyDetectionPolicy. Compares each wakeup to the blacklist and if it exists, does not include it in the count used to compare against the threshold. Change-Id: I4ef548bd0952be5f0d4e36df5698f287839d0704 Fixes: 67000019 Test: robotests --- .../anomaly/AnomalyDetectionPolicy.java | 40 +++++++++++++--- .../checker/WakeupAlarmAnomalyDetector.java | 17 +++++-- .../wrapper/KeyValueListParserWrapper.java | 12 ++++- .../anomaly/AnomalyDetectionPolicyTest.java | 46 ++++++++++++++----- .../WakeupAlarmAnomalyDetectorTest.java | 26 ++++++++++- 5 files changed, 117 insertions(+), 24 deletions(-) diff --git a/src/com/android/settings/fuelgauge/anomaly/AnomalyDetectionPolicy.java b/src/com/android/settings/fuelgauge/anomaly/AnomalyDetectionPolicy.java index 382c6921dbf..3791d89c8de 100644 --- a/src/com/android/settings/fuelgauge/anomaly/AnomalyDetectionPolicy.java +++ b/src/com/android/settings/fuelgauge/anomaly/AnomalyDetectionPolicy.java @@ -17,6 +17,7 @@ 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; @@ -25,6 +26,10 @@ import android.util.Log; import com.android.settings.wrapper.KeyValueListParserWrapper; +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} @@ -45,6 +50,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"; /** @@ -95,6 +102,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 * @@ -121,15 +136,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); } @@ -150,4 +168,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/checker/WakeupAlarmAnomalyDetector.java b/src/com/android/settings/fuelgauge/anomaly/checker/WakeupAlarmAnomalyDetector.java index 8823a177092..46f31ab3a8e 100644 --- a/src/com/android/settings/fuelgauge/anomaly/checker/WakeupAlarmAnomalyDetector.java +++ b/src/com/android/settings/fuelgauge/anomaly/checker/WakeupAlarmAnomalyDetector.java @@ -21,6 +21,8 @@ import android.os.BatteryStats; import android.support.annotation.VisibleForTesting; import android.text.format.DateUtils; import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Log; import com.android.internal.os.BatterySipper; import com.android.internal.os.BatteryStatsHelper; @@ -29,10 +31,12 @@ 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.Collections; import java.util.List; +import java.util.Map; +import java.util.Set; /** * Check whether apps has too many wakeup alarms @@ -42,6 +46,7 @@ public class WakeupAlarmAnomalyDetector implements AnomalyDetector { @VisibleForTesting BatteryUtils mBatteryUtils; private long mWakeupAlarmThreshold; + private Set mWakeupBlacklistedTags; private Context mContext; private AnomalyUtils mAnomalyUtils; @@ -56,6 +61,7 @@ public class WakeupAlarmAnomalyDetector implements AnomalyDetector { mBatteryUtils = BatteryUtils.getInstance(context); mAnomalyUtils = anomalyUtils; mWakeupAlarmThreshold = policy.wakeupAlarmThreshold; + mWakeupBlacklistedTags = policy.wakeupBlacklistedTags; } @Override @@ -123,11 +129,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/src/com/android/settings/wrapper/KeyValueListParserWrapper.java b/src/com/android/settings/wrapper/KeyValueListParserWrapper.java index 16dc50eecdc..3fab5711db0 100644 --- a/src/com/android/settings/wrapper/KeyValueListParserWrapper.java +++ b/src/com/android/settings/wrapper/KeyValueListParserWrapper.java @@ -56,12 +56,22 @@ public class KeyValueListParserWrapper { * Get the value for key as a boolean. * @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. + * @return the boolean value associated with the key. */ public boolean getBoolean(String key, boolean defaultValue) { return mParser.getBoolean(key, defaultValue); } + /** + * 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. + */ + public String getString(String key, String defaultValue) { + return mParser.getString(key, defaultValue); + } + /** * Get the value for key as a long. * @param key The key to lookup. 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 9bbc9bd7be6..46db6b37f92 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyDetectionPolicyTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyDetectionPolicyTest.java @@ -41,11 +41,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; @@ -59,7 +61,7 @@ public class AnomalyDetectionPolicyTest { } @Test - public void testInit_containsDataFromSettings() { + public void testInit_usesConfigValues() { AnomalyDetectionPolicy anomalyDetectionPolicy = createAnomalyPolicyWithConfig(); assertThat(anomalyDetectionPolicy.anomalyDetectionEnabled).isTrue(); @@ -67,12 +69,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 @@ -82,18 +86,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( @@ -104,18 +109,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); + } }