Merge "Add help url placeholder for SimLock settings" into oc-dr1-dev
diff --git a/res/values/strings.xml b/res/values/strings.xml
index b60e2df..78a30ca 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -8593,8 +8593,8 @@
<!-- Preference menu title for accessing the deletion helper from the storage manager settings. [CHAR LIMIT=30]-->
<string name="deletion_helper_preference_title">Free up space now</string>
- <!-- Preference title for gesture settings [CHAR LIMIT=40]-->
- <string name="gesture_preference_title">Gesture shortcuts</string>
+ <!-- Preference title for gesture settings [CHAR LIMIT=40 BACKUP_MESSAGE_ID:5280023307132819052]-->
+ <string name="gesture_preference_title">Gesture</string>
<!-- Preference summary for gesture settings (phone) [CHAR LIMIT=NONE]-->
<string name="gesture_preference_summary" product="default">Quick gestures to control your phone</string>
diff --git a/src/com/android/settings/fuelgauge/anomaly/Anomaly.java b/src/com/android/settings/fuelgauge/anomaly/Anomaly.java
index 37b52fc..746bd7f 100644
--- a/src/com/android/settings/fuelgauge/anomaly/Anomaly.java
+++ b/src/com/android/settings/fuelgauge/anomaly/Anomaly.java
@@ -35,10 +35,12 @@
public class Anomaly implements Parcelable {
@Retention(RetentionPolicy.SOURCE)
@IntDef({AnomalyType.WAKE_LOCK,
- AnomalyType.WAKEUP_ALARM})
+ AnomalyType.WAKEUP_ALARM,
+ AnomalyType.BLUETOOTH_SCAN})
public @interface AnomalyType {
int WAKE_LOCK = 0;
int WAKEUP_ALARM = 1;
+ int BLUETOOTH_SCAN = 2;
}
@Retention(RetentionPolicy.SOURCE)
@@ -52,7 +54,8 @@
@AnomalyType
public static final int[] ANOMALY_TYPE_LIST =
{AnomalyType.WAKE_LOCK,
- AnomalyType.WAKEUP_ALARM};
+ AnomalyType.WAKEUP_ALARM,
+ AnomalyType.BLUETOOTH_SCAN};
/**
* Type of this this anomaly
diff --git a/src/com/android/settings/fuelgauge/anomaly/AnomalyDetectionPolicy.java b/src/com/android/settings/fuelgauge/anomaly/AnomalyDetectionPolicy.java
index 7dbae36..647737c 100644
--- a/src/com/android/settings/fuelgauge/anomaly/AnomalyDetectionPolicy.java
+++ b/src/com/android/settings/fuelgauge/anomaly/AnomalyDetectionPolicy.java
@@ -37,9 +37,13 @@
@VisibleForTesting
static final String KEY_WAKEUP_ALARM_DETECTION_ENABLED = "wakeup_alarm_enabled";
@VisibleForTesting
+ static final String KEY_BLUETOOTH_SCAN_DETECTION_ENABLED = "bluetooth_scan_enabled";
+ @VisibleForTesting
static final String KEY_WAKELOCK_THRESHOLD = "wakelock_threshold";
@VisibleForTesting
static final String KEY_WAKEUP_ALARM_THRESHOLD = "wakeup_alarm_threshold";
+ @VisibleForTesting
+ static final String KEY_BLUETOOTH_SCAN_THRESHOLD = "bluetooth_scan_threshold";
/**
* {@code true} if general anomaly detection is enabled
@@ -66,6 +70,14 @@
public final boolean wakeupAlarmDetectionEnabled;
/**
+ * {@code true} if bluetooth scanning detection is enabled
+ *
+ * @see Settings.Global#ANOMALY_DETECTION_CONSTANTS
+ * @see #KEY_BLUETOOTH_SCAN_THRESHOLD
+ */
+ public final boolean bluetoothScanDetectionEnabled;
+
+ /**
* Threshold for wakelock time in milli seconds
*
* @see Settings.Global#ANOMALY_DETECTION_CONSTANTS
@@ -81,6 +93,14 @@
*/
public final long wakeupAlarmThreshold;
+ /**
+ * Threshold for bluetooth unoptimized scanning time in milli seconds
+ *
+ * @see Settings.Global#ANOMALY_DETECTION_CONSTANTS
+ * @see #KEY_BLUETOOTH_SCAN_THRESHOLD
+ */
+ public final long bluetoothScanThreshold;
+
private final KeyValueListParserWrapper mParserWrapper;
public AnomalyDetectionPolicy(Context context) {
@@ -103,9 +123,13 @@
wakeLockDetectionEnabled = mParserWrapper.getBoolean(KEY_WAKELOCK_DETECTION_ENABLED, true);
wakeupAlarmDetectionEnabled = mParserWrapper.getBoolean(KEY_WAKEUP_ALARM_DETECTION_ENABLED,
true);
+ bluetoothScanDetectionEnabled = mParserWrapper.getBoolean(
+ KEY_BLUETOOTH_SCAN_DETECTION_ENABLED, true);
wakeLockThreshold = mParserWrapper.getLong(KEY_WAKELOCK_THRESHOLD,
DateUtils.HOUR_IN_MILLIS);
wakeupAlarmThreshold = mParserWrapper.getLong(KEY_WAKEUP_ALARM_THRESHOLD, 60);
+ bluetoothScanThreshold = mParserWrapper.getLong(KEY_BLUETOOTH_SCAN_THRESHOLD,
+ 30 * DateUtils.MINUTE_IN_MILLIS);
}
public boolean isAnomalyDetectorEnabled(@Anomaly.AnomalyType int type) {
@@ -114,6 +138,8 @@
return wakeLockDetectionEnabled;
case Anomaly.AnomalyType.WAKEUP_ALARM:
return wakeupAlarmDetectionEnabled;
+ case Anomaly.AnomalyType.BLUETOOTH_SCAN:
+ return bluetoothScanDetectionEnabled;
default:
return false; // Disabled when no this type
}
diff --git a/src/com/android/settings/fuelgauge/anomaly/AnomalyLoader.java b/src/com/android/settings/fuelgauge/anomaly/AnomalyLoader.java
index 264c390..03d4d23 100644
--- a/src/com/android/settings/fuelgauge/anomaly/AnomalyLoader.java
+++ b/src/com/android/settings/fuelgauge/anomaly/AnomalyLoader.java
@@ -22,9 +22,9 @@
import android.os.Bundle;
import android.os.UserManager;
import android.support.annotation.VisibleForTesting;
+import android.util.Log;
import com.android.internal.os.BatteryStatsHelper;
-import com.android.settings.Utils;
import com.android.settings.utils.AsyncLoader;
import java.util.ArrayList;
@@ -35,6 +35,8 @@
* an empty list if there is no anomaly.
*/
public class AnomalyLoader extends AsyncLoader<List<Anomaly>> {
+ private static final String TAG = "AnomalyLoader";
+
private static final boolean USE_FAKE_DATA = false;
private BatteryStatsHelper mBatteryStatsHelper;
private String mPackageName;
@@ -108,9 +110,9 @@
List<Anomaly> generateFakeData() {
final List<Anomaly> anomalies = new ArrayList<>();
final Context context = getContext();
+ final String packageName = "com.android.settings";
+ final CharSequence displayName = "Settings";
try {
- final String packageName = "com.android.settings";
- final CharSequence displayName = "Settings";
final int uid = context.getPackageManager().getPackageUid(packageName, 0);
anomalies.add(new Anomaly.Builder()
@@ -125,8 +127,14 @@
.setPackageName(packageName)
.setDisplayName(displayName)
.build());
+ anomalies.add(new Anomaly.Builder()
+ .setUid(uid)
+ .setType(Anomaly.AnomalyType.BLUETOOTH_SCAN)
+ .setPackageName(packageName)
+ .setDisplayName(displayName)
+ .build());
} catch (PackageManager.NameNotFoundException e) {
- e.printStackTrace();
+ Log.e(TAG, "Cannot find package by name: " + packageName, e);
}
return anomalies;
}
diff --git a/src/com/android/settings/fuelgauge/anomaly/AnomalyUtils.java b/src/com/android/settings/fuelgauge/anomaly/AnomalyUtils.java
index d226899..de9f7aa 100644
--- a/src/com/android/settings/fuelgauge/anomaly/AnomalyUtils.java
+++ b/src/com/android/settings/fuelgauge/anomaly/AnomalyUtils.java
@@ -23,6 +23,7 @@
import com.android.settings.fuelgauge.anomaly.action.BackgroundCheckAction;
import com.android.settings.fuelgauge.anomaly.action.ForceStopAction;
import com.android.settings.fuelgauge.anomaly.checker.AnomalyDetector;
+import com.android.settings.fuelgauge.anomaly.checker.BluetoothScanAnomalyDetector;
import com.android.settings.fuelgauge.anomaly.checker.WakeLockAnomalyDetector;
import com.android.settings.fuelgauge.anomaly.checker.WakeupAlarmAnomalyDetector;
@@ -56,6 +57,7 @@
case Anomaly.AnomalyType.WAKE_LOCK:
return new ForceStopAction(mContext);
case Anomaly.AnomalyType.WAKEUP_ALARM:
+ case Anomaly.AnomalyType.BLUETOOTH_SCAN:
return new BackgroundCheckAction(mContext);
default:
return null;
@@ -74,6 +76,8 @@
return new WakeLockAnomalyDetector(mContext);
case Anomaly.AnomalyType.WAKEUP_ALARM:
return new WakeupAlarmAnomalyDetector(mContext);
+ case Anomaly.AnomalyType.BLUETOOTH_SCAN:
+ return new BluetoothScanAnomalyDetector(mContext);
default:
return null;
}
diff --git a/src/com/android/settings/fuelgauge/anomaly/checker/BluetoothScanAnomalyDetector.java b/src/com/android/settings/fuelgauge/anomaly/checker/BluetoothScanAnomalyDetector.java
new file mode 100644
index 0000000..619386e
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/anomaly/checker/BluetoothScanAnomalyDetector.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2017 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.settings.fuelgauge.anomaly.checker;
+
+import android.content.Context;
+import android.os.BatteryStats;
+import android.os.SystemClock;
+import android.support.annotation.VisibleForTesting;
+import android.text.format.DateUtils;
+import android.util.ArrayMap;
+
+import com.android.internal.os.BatterySipper;
+import com.android.internal.os.BatteryStatsHelper;
+import com.android.settings.Utils;
+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;
+
+/**
+ * Check whether apps have unoptimized bluetooth scanning in the background
+ */
+public class BluetoothScanAnomalyDetector implements AnomalyDetector {
+ private static final String TAG = "BluetoothScanAnomalyDetector";
+ @VisibleForTesting
+ BatteryUtils mBatteryUtils;
+ @VisibleForTesting
+ AnomalyAction mAnomalyAction;
+ private long mBluetoothScanningThreshold;
+ private Context mContext;
+
+ public BluetoothScanAnomalyDetector(Context context) {
+ this(context, new AnomalyDetectionPolicy(context));
+ }
+
+ @VisibleForTesting
+ BluetoothScanAnomalyDetector(Context context, AnomalyDetectionPolicy policy) {
+ mContext = context;
+ mBatteryUtils = BatteryUtils.getInstance(context);
+ mAnomalyAction = AnomalyUtils.getInstance(context).getAnomalyAction(
+ Anomaly.AnomalyType.BLUETOOTH_SCAN);
+ mBluetoothScanningThreshold = policy.bluetoothScanThreshold;
+ }
+
+ @Override
+ public List<Anomaly> detectAnomalies(BatteryStatsHelper batteryStatsHelper) {
+ // Detect all apps if targetPackageName is null
+ return detectAnomalies(batteryStatsHelper, null /* targetPackageName */);
+ }
+
+ @Override
+ public List<Anomaly> detectAnomalies(BatteryStatsHelper batteryStatsHelper,
+ String targetPackageName) {
+ final List<BatterySipper> batterySippers = batteryStatsHelper.getUsageList();
+ final List<Anomaly> anomalies = new ArrayList<>();
+ final int targetUid = mBatteryUtils.getPackageUid(targetPackageName);
+ final long elapsedRealtimeMs = SystemClock.elapsedRealtime();
+
+ for (int i = 0, size = batterySippers.size(); i < size; i++) {
+ final BatterySipper sipper = batterySippers.get(i);
+ final BatteryStats.Uid uid = sipper.uidObj;
+ if (uid == null
+ || mBatteryUtils.shouldHideSipper(sipper)
+ || (targetUid != BatteryUtils.UID_NULL && targetUid != uid.getUid())) {
+ continue;
+ }
+
+ final long bluetoothTimeMs = getBluetoothUnoptimizedBgTimeMs(uid, elapsedRealtimeMs);
+ if (bluetoothTimeMs > mBluetoothScanningThreshold) {
+ final String packageName = mBatteryUtils.getPackageName(uid.getUid());
+ final CharSequence displayName = Utils.getApplicationLabel(mContext,
+ packageName);
+
+ Anomaly anomaly = new Anomaly.Builder()
+ .setUid(uid.getUid())
+ .setType(Anomaly.AnomalyType.BLUETOOTH_SCAN)
+ .setDisplayName(displayName)
+ .setPackageName(packageName)
+ .build();
+
+ if (mAnomalyAction.isActionActive(anomaly)) {
+ anomalies.add(anomaly);
+ }
+ }
+ }
+
+ return anomalies;
+ }
+
+ @VisibleForTesting
+ public long getBluetoothUnoptimizedBgTimeMs(BatteryStats.Uid uid, long elapsedRealtimeMs) {
+ BatteryStats.Timer timer = uid.getBluetoothUnoptimizedScanBackgroundTimer();
+
+ return timer != null ? timer.getTotalDurationMsLocked(elapsedRealtimeMs) : 0;
+ }
+
+}
diff --git a/src/com/android/settings/fuelgauge/anomaly/checker/WakeLockAnomalyDetector.java b/src/com/android/settings/fuelgauge/anomaly/checker/WakeLockAnomalyDetector.java
index 545bb26..5fa0e41 100644
--- a/src/com/android/settings/fuelgauge/anomaly/checker/WakeLockAnomalyDetector.java
+++ b/src/com/android/settings/fuelgauge/anomaly/checker/WakeLockAnomalyDetector.java
@@ -65,7 +65,8 @@
@Override
public List<Anomaly> detectAnomalies(BatteryStatsHelper batteryStatsHelper) {
- return detectAnomalies(batteryStatsHelper, null);
+ // Detect all apps if targetPackageName is null
+ return detectAnomalies(batteryStatsHelper, null /* targetPackageName */);
}
@Override
diff --git a/src/com/android/settings/fuelgauge/anomaly/checker/WakeupAlarmAnomalyDetector.java b/src/com/android/settings/fuelgauge/anomaly/checker/WakeupAlarmAnomalyDetector.java
index 3502e73..aadfa0c 100644
--- a/src/com/android/settings/fuelgauge/anomaly/checker/WakeupAlarmAnomalyDetector.java
+++ b/src/com/android/settings/fuelgauge/anomaly/checker/WakeupAlarmAnomalyDetector.java
@@ -61,7 +61,8 @@
@Override
public List<Anomaly> detectAnomalies(BatteryStatsHelper batteryStatsHelper) {
- return detectAnomalies(batteryStatsHelper, null);
+ // Detect all apps if targetPackageName is null
+ return detectAnomalies(batteryStatsHelper, null /* targetPackageName */);
}
@Override
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 d5bd53b..169cba8 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyDetectionPolicyTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyDetectionPolicyTest.java
@@ -44,7 +44,9 @@
+ ",wakelock_enabled=false"
+ ",wakelock_threshold=3000"
+ ",wakeup_alarm_enabled=true"
- + ",wakeup_alarm_threshold=100";
+ + ",wakeup_alarm_threshold=100"
+ + ",bluetooth_scan_enabled=true"
+ + ",bluetooth_scan_threshold=2000";
private Context mContext;
private KeyValueListParserWrapper mKeyValueListParserWrapper;
@@ -64,6 +66,8 @@
assertThat(anomalyDetectionPolicy.wakeLockThreshold).isEqualTo(3000);
assertThat(anomalyDetectionPolicy.wakeupAlarmDetectionEnabled).isTrue();
assertThat(anomalyDetectionPolicy.wakeupAlarmThreshold).isEqualTo(100);
+ assertThat(anomalyDetectionPolicy.bluetoothScanDetectionEnabled).isTrue();
+ assertThat(anomalyDetectionPolicy.bluetoothScanThreshold).isEqualTo(2000);
}
@Test
@@ -82,6 +86,9 @@
assertThat(anomalyDetectionPolicy.wakeLockThreshold).isEqualTo(DateUtils.HOUR_IN_MILLIS);
assertThat(anomalyDetectionPolicy.wakeupAlarmDetectionEnabled).isTrue();
assertThat(anomalyDetectionPolicy.wakeupAlarmThreshold).isEqualTo(60);
+ assertThat(anomalyDetectionPolicy.bluetoothScanDetectionEnabled).isTrue();
+ assertThat(anomalyDetectionPolicy.bluetoothScanThreshold).isEqualTo(
+ 30 * DateUtils.MINUTE_IN_MILLIS);
}
@Test
@@ -92,6 +99,8 @@
Anomaly.AnomalyType.WAKE_LOCK)).isFalse();
assertThat(anomalyDetectionPolicy.isAnomalyDetectorEnabled(
Anomaly.AnomalyType.WAKEUP_ALARM)).isTrue();
+ assertThat(anomalyDetectionPolicy.isAnomalyDetectorEnabled(
+ Anomaly.AnomalyType.BLUETOOTH_SCAN)).isTrue();
}
private AnomalyDetectionPolicy createAnomalyPolicyWithConfig() {
@@ -104,6 +113,8 @@
AnomalyDetectionPolicy.KEY_WAKELOCK_DETECTION_ENABLED, true);
doReturn(true).when(mKeyValueListParserWrapper).getBoolean(
AnomalyDetectionPolicy.KEY_WAKEUP_ALARM_DETECTION_ENABLED, true);
+ doReturn(true).when(mKeyValueListParserWrapper).getBoolean(
+ AnomalyDetectionPolicy.KEY_BLUETOOTH_SCAN_DETECTION_ENABLED, true);
return new AnomalyDetectionPolicy(mContext, mKeyValueListParserWrapper);
}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyLoaderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyLoaderTest.java
index 8119168..48749d5 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyLoaderTest.java
@@ -30,6 +30,7 @@
import android.os.UserManager;
import com.android.internal.os.BatteryStatsHelper;
+import com.android.settings.fuelgauge.anomaly.checker.BluetoothScanAnomalyDetector;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import com.android.settings.fuelgauge.anomaly.checker.WakeLockAnomalyDetector;
@@ -62,13 +63,17 @@
@Mock
private WakeupAlarmAnomalyDetector mWakeupAlarmAnomalyDetector;
@Mock
+ private BluetoothScanAnomalyDetector mBluetoothScanAnomalyDetector;
+ @Mock
private AnomalyDetectionPolicy mAnomalyDetectionPolicy;
@Mock
private UserManager mUserManager;
private Anomaly mWakeLockAnomaly;
private Anomaly mWakeupAlarmAnomaly;
+ private Anomaly mBluetoothScanAnomaly;
private List<Anomaly> mWakeLockAnomalies;
private List<Anomaly> mWakeupAlarmAnomalies;
+ private List<Anomaly> mBluetoothScanAnomalies;
private AnomalyLoader mAnomalyLoader;
@Before
@@ -91,6 +96,12 @@
doReturn(mWakeupAlarmAnomalies).when(mWakeupAlarmAnomalyDetector).detectAnomalies(any(),
any());
+ mBluetoothScanAnomalies = new ArrayList<>();
+ mBluetoothScanAnomaly = createAnomaly(Anomaly.AnomalyType.BLUETOOTH_SCAN);
+ mBluetoothScanAnomalies.add(mBluetoothScanAnomaly);
+ doReturn(mBluetoothScanAnomalies).when(mBluetoothScanAnomalyDetector).detectAnomalies(any(),
+ any());
+
mAnomalyLoader = new AnomalyLoader(mContext, mBatteryStatsHelper, null,
mAnomalyDetectionPolicy);
mAnomalyLoader.mAnomalyUtils = spy(new AnomalyUtils(mContext));
@@ -102,10 +113,14 @@
Anomaly.AnomalyType.WAKE_LOCK);
doReturn(mWakeupAlarmAnomalyDetector).when(mAnomalyLoader.mAnomalyUtils).getAnomalyDetector(
Anomaly.AnomalyType.WAKEUP_ALARM);
+ doReturn(mBluetoothScanAnomalyDetector).when(
+ mAnomalyLoader.mAnomalyUtils).getAnomalyDetector(
+ Anomaly.AnomalyType.BLUETOOTH_SCAN);
List<Anomaly> anomalies = mAnomalyLoader.loadInBackground();
- assertThat(anomalies).containsExactly(mWakeLockAnomaly, mWakeupAlarmAnomaly);
+ assertThat(anomalies).containsExactly(mWakeLockAnomaly, mWakeupAlarmAnomaly,
+ mBluetoothScanAnomaly);
}
private Anomaly createAnomaly(@Anomaly.AnomalyType int type) {
@@ -121,6 +136,7 @@
public void testGenerateFakeData() {
List<Anomaly> anomalies = mAnomalyLoader.generateFakeData();
- assertThat(anomalies).containsExactly(mWakeLockAnomaly, mWakeupAlarmAnomaly);
+ assertThat(anomalies).containsExactly(mWakeLockAnomaly, mWakeupAlarmAnomaly,
+ mBluetoothScanAnomaly);
}
}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/checker/BluetoothScanAnomalyDetectorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/checker/BluetoothScanAnomalyDetectorTest.java
new file mode 100644
index 0000000..941e9cd
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/checker/BluetoothScanAnomalyDetectorTest.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2017 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.settings.fuelgauge.anomaly.checker;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.BatteryStats;
+import android.text.format.DateUtils;
+
+import com.android.internal.os.BatterySipper;
+import com.android.internal.os.BatteryStatsHelper;
+import com.android.settings.TestConfig;
+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.action.AnomalyAction;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class BluetoothScanAnomalyDetectorTest {
+ private static final String TARGET_PACKAGE_NAME = "com.android.app";
+ private static final int ANOMALY_UID = 111;
+ private static final int NORMAL_UID = 222;
+ private static final int TARGET_UID = 333;
+ private static final long ANOMALY_BLUETOOTH_SCANNING_TIME = DateUtils.HOUR_IN_MILLIS;
+ private static final long NORMAL_BLUETOOTH_SCANNING_TIME = DateUtils.MINUTE_IN_MILLIS;
+ @Mock
+ private BatteryStatsHelper mBatteryStatsHelper;
+ @Mock
+ private BatterySipper mAnomalySipper;
+ @Mock
+ private BatterySipper mNormalSipper;
+ @Mock
+ private BatterySipper mTargetSipper;
+ @Mock
+ private BatteryStats.Uid mAnomalyUid;
+ @Mock
+ private BatteryStats.Uid mNormalUid;
+ @Mock
+ private BatteryStats.Uid mTargetUid;
+ @Mock
+ private BatteryUtils mBatteryUtils;
+ @Mock
+ private AnomalyDetectionPolicy mPolicy;
+ @Mock
+ private AnomalyAction mAnomalyAction;
+
+ private BluetoothScanAnomalyDetector mBluetoothScanAnomalyDetector;
+ private Context mContext;
+ private List<BatterySipper> mUsageList;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = spy(RuntimeEnvironment.application);
+ ReflectionHelpers.setField(mPolicy, "bluetoothScanThreshold",
+ 30 * DateUtils.MINUTE_IN_MILLIS);
+
+ mAnomalySipper.uidObj = mAnomalyUid;
+ doReturn(ANOMALY_UID).when(mAnomalyUid).getUid();
+ mNormalSipper.uidObj = mNormalUid;
+ doReturn(NORMAL_UID).when(mNormalUid).getUid();
+ mTargetSipper.uidObj = mTargetUid;
+ doReturn(TARGET_UID).when(mTargetUid).getUid();
+
+ mUsageList = new ArrayList<>();
+ mUsageList.add(mAnomalySipper);
+ mUsageList.add(mNormalSipper);
+ mUsageList.add(mTargetSipper);
+ doReturn(mUsageList).when(mBatteryStatsHelper).getUsageList();
+
+ mBluetoothScanAnomalyDetector = spy(new BluetoothScanAnomalyDetector(mContext, mPolicy));
+ mBluetoothScanAnomalyDetector.mBatteryUtils = mBatteryUtils;
+ mBluetoothScanAnomalyDetector.mAnomalyAction = mAnomalyAction;
+ doReturn(false).when(mBatteryUtils).shouldHideSipper(any());
+ doReturn(true).when(mAnomalyAction).isActionActive(any());
+
+ doReturn(ANOMALY_BLUETOOTH_SCANNING_TIME).when(
+ mBluetoothScanAnomalyDetector).getBluetoothUnoptimizedBgTimeMs(eq(mAnomalyUid),
+ anyLong());
+ doReturn(ANOMALY_BLUETOOTH_SCANNING_TIME).when(
+ mBluetoothScanAnomalyDetector).getBluetoothUnoptimizedBgTimeMs(eq(mTargetUid),
+ anyLong());
+ doReturn(NORMAL_BLUETOOTH_SCANNING_TIME).when(
+ mBluetoothScanAnomalyDetector).getBluetoothUnoptimizedBgTimeMs(eq(mNormalUid),
+ anyLong());
+ }
+
+ @Test
+ public void testDetectAnomalies_containsAnomaly_detectIt() {
+ doReturn(-1).when(mBatteryUtils).getPackageUid(nullable(String.class));
+ final Anomaly anomaly = createBluetoothAnomaly(ANOMALY_UID);
+ final Anomaly targetAnomaly = createBluetoothAnomaly(TARGET_UID);
+
+ List<Anomaly> mAnomalies = mBluetoothScanAnomalyDetector.detectAnomalies(
+ mBatteryStatsHelper);
+
+ assertThat(mAnomalies).containsExactly(anomaly, targetAnomaly);
+ }
+
+ @Test
+ public void testDetectAnomalies_detectTargetAnomaly_detectIt() {
+ doReturn(TARGET_UID).when(mBatteryUtils).getPackageUid(TARGET_PACKAGE_NAME);
+ final Anomaly targetAnomaly = createBluetoothAnomaly(TARGET_UID);
+
+ List<Anomaly> mAnomalies = mBluetoothScanAnomalyDetector.detectAnomalies(
+ mBatteryStatsHelper, TARGET_PACKAGE_NAME);
+
+ assertThat(mAnomalies).containsExactly(targetAnomaly);
+
+ }
+
+ private Anomaly createBluetoothAnomaly(int uid) {
+ return new Anomaly.Builder()
+ .setUid(uid)
+ .setType(Anomaly.AnomalyType.BLUETOOTH_SCAN)
+ .build();
+ }
+
+}