Merge "Remove subtitle for Recent location apps" into oc-mr1-dev
diff --git a/res/layout-sw320dp/settings_entity_header.xml b/res/layout-sw320dp/settings_entity_header.xml
index 5cedd8d..951961c 100644
--- a/res/layout-sw320dp/settings_entity_header.xml
+++ b/res/layout-sw320dp/settings_entity_header.xml
@@ -38,7 +38,7 @@
             android:id="@+id/entity_header_icon"
             android:layout_width="48dp"
             android:layout_height="48dp"
-            android:scaleType="fitXY"
+            android:scaleType="fitCenter"
             android:layout_gravity="center_horizontal"
             android:antialias="true" />
 
diff --git a/res/xml/development_prefs.xml b/res/xml/development_prefs.xml
index 8992446..1846a8c 100644
--- a/res/xml/development_prefs.xml
+++ b/res/xml/development_prefs.xml
@@ -210,6 +210,11 @@
             android:entryValues="@array/usb_configuration_values" />
 
         <SwitchPreference
+            android:key="bluetooth_show_devices_without_names"
+            android:title="@string/bluetooth_show_devices_without_names"
+            android:summary="@string/bluetooth_show_devices_without_names_summary"/>
+
+        <SwitchPreference
             android:key="bluetooth_disable_absolute_volume"
             android:title="@string/bluetooth_disable_absolute_volume"
             android:summary="@string/bluetooth_disable_absolute_volume_summary"/>
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsHeaderController.java b/src/com/android/settings/bluetooth/BluetoothDetailsHeaderController.java
index 5ec7c85..1470214 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsHeaderController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsHeaderController.java
@@ -16,8 +16,8 @@
 
 package com.android.settings.bluetooth;
 
-
 import android.content.Context;
+import android.graphics.drawable.Drawable;
 import android.support.v14.preference.PreferenceFragment;
 import android.support.v7.preference.PreferenceScreen;
 import android.util.Pair;
@@ -51,11 +51,11 @@
     }
 
     protected void setHeaderProperties() {
-        Pair<Integer, String> pair = Utils.getBtClassDrawableWithDescription
-                (mContext.getResources(), mCachedDevice);
+        final Pair<Drawable, String> pair = Utils.getBtClassDrawableWithDescription
+                (mContext, mCachedDevice);
         String summaryText = mCachedDevice.getConnectionSummary();
         mHeaderController.setLabel(mCachedDevice.getName());
-        mHeaderController.setIcon(mContext.getDrawable(pair.first));
+        mHeaderController.setIcon(pair.first);
         mHeaderController.setIconContentDescription(pair.second);
         mHeaderController.setSummary(summaryText);
     }
diff --git a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
index a216400..084b50e 100644
--- a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
+++ b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
 import android.os.UserManager;
 import android.support.v7.preference.Preference;
 import android.support.v7.preference.PreferenceViewHolder;
@@ -53,16 +54,17 @@
     private final UserManager mUserManager;
 
     private AlertDialog mDisconnectDialog;
-
     private String contentDescription = null;
-
+    private DeviceListPreferenceFragment mDeviceListPreferenceFragment;
     /* Talk-back descriptions for various BT icons */
     Resources mResources;
 
-    public BluetoothDevicePreference(Context context, CachedBluetoothDevice cachedDevice) {
+    public BluetoothDevicePreference(Context context, CachedBluetoothDevice cachedDevice,
+            DeviceListPreferenceFragment deviceListPreferenceFragment) {
         super(context, null);
         mResources = getContext().getResources();
         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+        mDeviceListPreferenceFragment = deviceListPreferenceFragment;
 
         if (sDimAlpha == Integer.MIN_VALUE) {
             TypedValue outValue = new TypedValue();
@@ -120,9 +122,9 @@
         // Null check is done at the framework
         setSummary(mCachedDevice.getConnectionSummary());
 
-        Pair<Integer, String> pair = Utils.getBtClassDrawableWithDescription(mResources,
+        final Pair<Drawable, String> pair = Utils.getBtClassDrawableWithDescription(getContext(),
                 mCachedDevice);
-        if (pair.first != 0) {
+        if (pair.first != null) {
             setIcon(pair.first);
             contentDescription = pair.second;
         }
@@ -130,6 +132,11 @@
         // Used to gray out the item
         setEnabled(!mCachedDevice.isBusy());
 
+        // Device is only visible in the UI if it has a valid name besides MAC address or when user
+        // allows showing devices without user-friendly name in developer settings
+        setVisible(mDeviceListPreferenceFragment.shouldShowDevicesWithoutNames()
+                || mCachedDevice.hasHumanReadableName());
+
         // This could affect ordering, so notify that
         notifyHierarchyChanged();
     }
diff --git a/src/com/android/settings/bluetooth/BluetoothSettings.java b/src/com/android/settings/bluetooth/BluetoothSettings.java
index bd86c4b..361bc2f 100644
--- a/src/com/android/settings/bluetooth/BluetoothSettings.java
+++ b/src/com/android/settings/bluetooth/BluetoothSettings.java
@@ -140,6 +140,8 @@
             mBluetoothEnabler.resume(getActivity());
         }
         super.onStart();
+        // Always show paired devices regardless whether user-friendly name exists
+        mShowDevicesWithoutNames = true;
         if (isUiRestricted()) {
             getPreferenceScreen().removeAll();
             if (!isUiRestrictedByOnlyAdmin()) {
diff --git a/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java
index ca06e3c..0485e69 100644
--- a/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java
+++ b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java
@@ -19,6 +19,7 @@
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.os.Bundle;
+import android.os.SystemProperties;
 import android.support.annotation.VisibleForTesting;
 import android.support.v7.preference.Preference;
 import android.support.v7.preference.PreferenceCategory;
@@ -52,6 +53,10 @@
 
     private static final String KEY_BT_SCAN = "bt_scan";
 
+    // Copied from DevelopmentSettings.java
+    private static final String BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY =
+            "persist.bluetooth.showdeviceswithoutnames";
+
     private BluetoothDeviceFilter.Filter mFilter;
 
     @VisibleForTesting
@@ -68,6 +73,8 @@
     final WeakHashMap<CachedBluetoothDevice, BluetoothDevicePreference> mDevicePreferenceMap =
             new WeakHashMap<CachedBluetoothDevice, BluetoothDevicePreference>();
 
+    boolean mShowDevicesWithoutNames;
+
     DeviceListPreferenceFragment(String restrictedKey) {
         super(restrictedKey);
         mFilter = BluetoothDeviceFilter.ALL_FILTER;
@@ -103,6 +110,8 @@
     @Override
     public void onStart() {
         super.onStart();
+        mShowDevicesWithoutNames = SystemProperties.getBoolean(
+                BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY, false);
         if (mLocalManager == null || isUiRestricted()) return;
 
         mLocalManager.setForegroundActivity(getActivity());
@@ -181,7 +190,7 @@
         BluetoothDevicePreference preference = (BluetoothDevicePreference) getCachedPreference(key);
 
         if (preference == null) {
-            preference = new BluetoothDevicePreference(getPrefContext(), cachedDevice);
+            preference = new BluetoothDevicePreference(getPrefContext(), cachedDevice, this);
             preference.setKey(key);
             mDeviceListGroup.addPreference(preference);
         } else {
@@ -271,4 +280,8 @@
      * Return the key of the {@link PreferenceGroup} that contains the bluetooth devices
      */
     public abstract String getDeviceListKey();
+
+    public boolean shouldShowDevicesWithoutNames() {
+        return mShowDevicesWithoutNames;
+    }
 }
diff --git a/src/com/android/settings/bluetooth/Utils.java b/src/com/android/settings/bluetooth/Utils.java
index 26edd84..e80237eb 100755
--- a/src/com/android/settings/bluetooth/Utils.java
+++ b/src/com/android/settings/bluetooth/Utils.java
@@ -23,6 +23,9 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.IdRes;
 import android.support.annotation.VisibleForTesting;
 import android.util.Pair;
 import android.widget.Toast;
@@ -36,6 +39,7 @@
 import com.android.settingslib.bluetooth.LocalBluetoothManager.BluetoothManagerCallback;
 import com.android.settingslib.bluetooth.LocalBluetoothProfile;
 import com.android.settingslib.bluetooth.Utils.ErrorListener;
+import com.android.settingslib.graph.BluetoothDeviceLayerDrawable;
 
 import java.util.List;
 
@@ -150,27 +154,31 @@
         }
     };
 
-    static Pair<Integer, String> getBtClassDrawableWithDescription(Resources r,
+    static Pair<Drawable, String> getBtClassDrawableWithDescription(Context context,
             CachedBluetoothDevice cachedDevice) {
         BluetoothClass btClass = cachedDevice.getBtClass();
+        final int level = cachedDevice.getBatteryLevel();
         if (btClass != null) {
             switch (btClass.getMajorDeviceClass()) {
                 case BluetoothClass.Device.Major.COMPUTER:
-                    return new Pair<Integer, String>(R.drawable.ic_bt_laptop,
-                           r.getString(R.string.bluetooth_talkback_computer));
+                    return new Pair<>(getBluetoothDrawable(context, R.drawable.ic_bt_laptop, level),
+                            context.getString(R.string.bluetooth_talkback_computer));
 
                 case BluetoothClass.Device.Major.PHONE:
-                    return new Pair<Integer, String>(R.drawable.ic_bt_cellphone,
-                            r.getString(R.string.bluetooth_talkback_phone));
+                    return new Pair<>(
+                            getBluetoothDrawable(context, R.drawable.ic_bt_cellphone, level),
+                            context.getString(R.string.bluetooth_talkback_phone));
 
                 case BluetoothClass.Device.Major.PERIPHERAL:
-                    return new Pair<Integer, String>(HidProfile.getHidClassDrawable(btClass),
-                            r.getString(
-                                    R.string.bluetooth_talkback_input_peripheral));
+                    return new Pair<>(
+                            getBluetoothDrawable(context, HidProfile.getHidClassDrawable(btClass),
+                                    level),
+                            context.getString(R.string.bluetooth_talkback_input_peripheral));
 
                 case BluetoothClass.Device.Major.IMAGING:
-                    return new Pair<Integer, String>(R.drawable.ic_settings_print,
-                            r.getString(R.string.bluetooth_talkback_imaging));
+                    return new Pair<>(
+                            getBluetoothDrawable(context, R.drawable.ic_settings_print, level),
+                            context.getString(R.string.bluetooth_talkback_imaging));
 
                 default:
                     // unrecognized device class; continue
@@ -181,20 +189,34 @@
         for (LocalBluetoothProfile profile : profiles) {
             int resId = profile.getDrawableResource(btClass);
             if (resId != 0) {
-                return new Pair<Integer, String>(resId, null);
+                return new Pair<>(getBluetoothDrawable(context, resId, level), null);
             }
         }
         if (btClass != null) {
             if (btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) {
-                return new Pair<Integer, String>(R.drawable.ic_bt_headset_hfp,
-                        r.getString(R.string.bluetooth_talkback_headset));
+                return new Pair<>(
+                        getBluetoothDrawable(context, R.drawable.ic_bt_headset_hfp, level),
+                        context.getString(R.string.bluetooth_talkback_headset));
             }
             if (btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) {
-                return new Pair<Integer, String>(R.drawable.ic_bt_headphones_a2dp,
-                        r.getString(R.string.bluetooth_talkback_headphone));
+                return new Pair<>(
+                        getBluetoothDrawable(context, R.drawable.ic_bt_headphones_a2dp, level),
+                        context.getString(R.string.bluetooth_talkback_headphone));
             }
         }
-        return new Pair<Integer, String>(R.drawable.ic_settings_bluetooth,
-                r.getString(R.string.bluetooth_talkback_bluetooth));
+        return new Pair<>(getBluetoothDrawable(context, R.drawable.ic_settings_bluetooth, level),
+                context.getString(R.string.bluetooth_talkback_bluetooth));
+    }
+
+    @VisibleForTesting
+    static Drawable getBluetoothDrawable(Context context, @DrawableRes int resId,
+            int batteryLevel) {
+        if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
+            return BluetoothDeviceLayerDrawable.createLayerDrawable(context, resId, batteryLevel);
+        } else if (resId != 0) {
+            return context.getDrawable(resId);
+        } else {
+            return null;
+        }
     }
 }
diff --git a/src/com/android/settings/dashboard/DashboardSummary.java b/src/com/android/settings/dashboard/DashboardSummary.java
index fba27fb..be5fbdf 100644
--- a/src/com/android/settings/dashboard/DashboardSummary.java
+++ b/src/com/android/settings/dashboard/DashboardSummary.java
@@ -75,6 +75,7 @@
     private DashboardFeatureProvider mDashboardFeatureProvider;
     private SuggestionFeatureProvider mSuggestionFeatureProvider;
     private boolean isOnCategoriesChangedCalled;
+    private boolean mOnConditionsChangedCalled;
 
     @Override
     public int getMetricsCategory() {
@@ -237,10 +238,21 @@
     @Override
     public void onConditionsChanged() {
         Log.d(TAG, "onConditionsChanged");
-        final boolean scrollToTop = mLayoutManager.findFirstCompletelyVisibleItemPosition() <= 1;
-        mAdapter.setConditions(mConditionManager.getConditions());
-        if (scrollToTop) {
-            mDashboard.scrollToPosition(0);
+        // Bypass refreshing the conditions on the first call of onConditionsChanged.
+        // onConditionsChanged is called immediately everytime we start listening to the conditions
+        // change when we gain window focus. Since the conditions are passed to the adapter's
+        // constructor when we create the view, the first handling is not necessary.
+        // But, on the subsequent calls we need to handle it because there might be real changes to
+        // conditions.
+        if (mOnConditionsChangedCalled) {
+            final boolean scrollToTop =
+                    mLayoutManager.findFirstCompletelyVisibleItemPosition() <= 1;
+            mAdapter.setConditions(mConditionManager.getConditions());
+            if (scrollToTop) {
+                mDashboard.scrollToPosition(0);
+            }
+        } else {
+            mOnConditionsChangedCalled = true;
         }
     }
 
diff --git a/src/com/android/settings/development/DevelopmentSettings.java b/src/com/android/settings/development/DevelopmentSettings.java
index b442449..ce8acc2 100644
--- a/src/com/android/settings/development/DevelopmentSettings.java
+++ b/src/com/android/settings/development/DevelopmentSettings.java
@@ -199,6 +199,10 @@
     private static final String FORCE_RESIZABLE_KEY = "force_resizable_activities";
     private static final String COLOR_TEMPERATURE_KEY = "color_temperature";
 
+    private static final String BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_KEY =
+            "bluetooth_show_devices_without_names";
+    private static final String BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY =
+            "persist.bluetooth.showdeviceswithoutnames";
     private static final String BLUETOOTH_DISABLE_ABSOLUTE_VOLUME_KEY =
             "bluetooth_disable_absolute_volume";
     private static final String BLUETOOTH_DISABLE_ABSOLUTE_VOLUME_PROPERTY =
@@ -282,6 +286,7 @@
     private SwitchPreference mWifiAggressiveHandover;
     private SwitchPreference mMobileDataAlwaysOn;
     private SwitchPreference mTetheringHardwareOffload;
+    private SwitchPreference mBluetoothShowDevicesWithoutNames;
     private SwitchPreference mBluetoothDisableAbsVolume;
     private SwitchPreference mBluetoothEnableInbandRinging;
 
@@ -498,6 +503,8 @@
             mLogpersist = null;
         }
         mUsbConfiguration = addListPreference(USB_CONFIGURATION_KEY);
+        mBluetoothShowDevicesWithoutNames =
+                findAndInitSwitchPref(BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_KEY);
         mBluetoothDisableAbsVolume = findAndInitSwitchPref(BLUETOOTH_DISABLE_ABSOLUTE_VOLUME_KEY);
         mBluetoothEnableInbandRinging = findAndInitSwitchPref(BLUETOOTH_ENABLE_INBAND_RINGING_KEY);
         if (!BluetoothHeadset.isInbandRingingSupported(getContext())) {
@@ -838,6 +845,7 @@
         if (mColorTemperaturePreference != null) {
             updateColorTemperature();
         }
+        updateBluetoothShowDevicesWithoutUserFriendlyNameOptions();
         updateBluetoothDisableAbsVolumeOptions();
         updateBluetoothEnableInbandRingingOptions();
         updateBluetoothA2dpConfigurationValues();
@@ -1468,6 +1476,17 @@
         mWifiManager.setAllowScansWithTraffic(mWifiAllowScansWithTraffic.isChecked() ? 1 : 0);
     }
 
+    private void updateBluetoothShowDevicesWithoutUserFriendlyNameOptions() {
+        updateSwitchPreference(mBluetoothShowDevicesWithoutNames,
+                SystemProperties.getBoolean(
+                        BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY, false));
+    }
+
+    private void writeBluetoothShowDevicesWithoutUserFriendlyNameOptions() {
+        SystemProperties.set(BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY,
+                mBluetoothShowDevicesWithoutNames.isChecked() ? "true" : "false");
+    }
+
     private void updateBluetoothDisableAbsVolumeOptions() {
         updateSwitchPreference(mBluetoothDisableAbsVolume,
                 SystemProperties.getBoolean(BLUETOOTH_DISABLE_ABSOLUTE_VOLUME_PROPERTY, false));
@@ -2532,6 +2551,8 @@
             writeUSBAudioOptions();
         } else if (preference == mForceResizable) {
             writeForceResizableOptions();
+        } else if (preference == mBluetoothShowDevicesWithoutNames) {
+            writeBluetoothShowDevicesWithoutUserFriendlyNameOptions();
         } else if (preference == mBluetoothDisableAbsVolume) {
             writeBluetoothDisableAbsVolumeOptions();
         } else if (preference == mBluetoothEnableInbandRinging) {
diff --git a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
index be72539..33d7d36 100644
--- a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
+++ b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
@@ -109,8 +109,9 @@
     public void onResume() {
         super.onResume();
         getLoaderManager().restartLoader(STORAGE_JOB_ID, Bundle.EMPTY, this);
+        getLoaderManager()
+                .restartLoader(VOLUME_SIZE_JOB_ID, Bundle.EMPTY, new VolumeSizeCallbacks());
         getLoaderManager().initLoader(ICON_JOB_ID, Bundle.EMPTY, new IconLoaderCallbacks());
-        getLoaderManager().initLoader(VOLUME_SIZE_JOB_ID, Bundle.EMPTY, new VolumeSizeCallbacks());
     }
 
     @Override
diff --git a/src/com/android/settings/wifi/WifiSettings.java b/src/com/android/settings/wifi/WifiSettings.java
index f0457e6..89e5b74 100644
--- a/src/com/android/settings/wifi/WifiSettings.java
+++ b/src/com/android/settings/wifi/WifiSettings.java
@@ -176,6 +176,12 @@
     // For Search
     private static final String DATA_KEY_REFERENCE = "main_toggle_wifi";
 
+    /**
+     * Tracks whether the user initiated a connection via clicking in order to autoscroll to the
+     * network once connected.
+     */
+    private boolean mClickedConnect;
+
     /* End of "used in Wifi Setup context" */
 
     public WifiSettings() {
@@ -894,6 +900,10 @@
 
         mConnectedAccessPointPreferenceCategory.addPreference(pref);
         mConnectedAccessPointPreferenceCategory.setVisible(true);
+        if (mClickedConnect) {
+            mClickedConnect = false;
+            scrollToPreference(mConnectedAccessPointPreferenceCategory);
+        }
     }
 
     /** Removes all preferences and hide the {@link #mConnectedAccessPointPreferenceCategory}. */
@@ -1039,7 +1049,7 @@
         mMetricsFeatureProvider.action(getActivity(), MetricsEvent.ACTION_WIFI_CONNECT,
                 isSavedNetwork);
         mWifiManager.connect(config, mConnectListener);
-        scrollToPreference(mConnectedAccessPointPreferenceCategory);
+        mClickedConnect = true;
     }
 
     protected void connect(final int networkId, boolean isSavedNetwork) {
diff --git a/tests/anomaly-tester/Android.mk b/tests/anomaly-tester/Android.mk
index beb5d69..ade37db 100644
--- a/tests/anomaly-tester/Android.mk
+++ b/tests/anomaly-tester/Android.mk
@@ -2,6 +2,15 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := tests
+LOCAL_CERTIFICATE := platform
+
+LOCAL_JAVA_LIBRARIES := android.test.runner bouncycastle
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    mockito-target \
+    ub-uiautomator \
+    truth-prebuilt \
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 
@@ -9,7 +18,7 @@
 
 LOCAL_PACKAGE_NAME := AnomalyTester
 
-LOCAL_CERTIFICATE := platform
+LOCAL_INSTRUMENTATION_FOR := Settings
 
 LOCAL_USE_AAPT2 := true
 
diff --git a/tests/anomaly-tester/AndroidManifest.xml b/tests/anomaly-tester/AndroidManifest.xml
index b5f50c4..7893b86 100644
--- a/tests/anomaly-tester/AndroidManifest.xml
+++ b/tests/anomaly-tester/AndroidManifest.xml
@@ -14,13 +14,21 @@
      limitations under the License.
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.settings.anomaly.tester">
+          package="com.android.settings.anomaly.tester">
+
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+    <uses-permission android:name="android.permission.BLUETOOTH"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+    <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
 
     <application
         android:allowBackup="false"
         android:label="@string/app_name"
         android:supportsRtl="true"
         android:theme="@android:style/Theme.Material.Light.DarkActionBar">
+        <uses-library android:name="android.test.runner" />
         <activity
             android:name=".AnomalyActivity"
             android:exported="true">
@@ -29,6 +37,16 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+
+        <service
+            android:name=".service.AnomalyService"
+            android:exported="false"/>
     </application>
 
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.settings"
+        android:label="Settings Test Cases">
+    </instrumentation>
+
 </manifest>
\ No newline at end of file
diff --git a/tests/anomaly-tester/res/layout/activity_main.xml b/tests/anomaly-tester/res/layout/activity_main.xml
index 63d025e..0561cff 100644
--- a/tests/anomaly-tester/res/layout/activity_main.xml
+++ b/tests/anomaly-tester/res/layout/activity_main.xml
@@ -52,6 +52,8 @@
 
             <include layout="@layout/bluetooth_anomaly"/>
 
+            <include layout="@layout/wakelock_anomaly"/>
+
         </LinearLayout>
     </ScrollView>
 </LinearLayout>
\ No newline at end of file
diff --git a/tests/anomaly-tester/res/layout/bluetooth_anomaly.xml b/tests/anomaly-tester/res/layout/bluetooth_anomaly.xml
index 7de558e..87e61b0 100644
--- a/tests/anomaly-tester/res/layout/bluetooth_anomaly.xml
+++ b/tests/anomaly-tester/res/layout/bluetooth_anomaly.xml
@@ -35,16 +35,21 @@
             android:layout_width="0dp"
             android:layout_weight="3"
             android:layout_height="wrap_content"
-            android:hint="Threshold(ms)"/>
+            android:hint="Threshold(ms)"
+            android:text="3000"
+            android:inputType="number"/>
 
         <EditText
             android:id="@+id/bluetooth_run_time"
             android:layout_width="0dp"
             android:layout_weight="3"
             android:layout_height="wrap_content"
-            android:hint="Run time(ms)"/>
+            android:hint="Run time(ms)"
+            android:text="6000"
+            android:inputType="number"/>
 
         <Button
+            android:id="@+id/bluetooth_button"
             android:layout_width="0dp"
             android:layout_weight="2"
             android:layout_height="wrap_content"
diff --git a/tests/anomaly-tester/res/layout/wakelock_anomaly.xml b/tests/anomaly-tester/res/layout/wakelock_anomaly.xml
new file mode 100644
index 0000000..08cb795
--- /dev/null
+++ b/tests/anomaly-tester/res/layout/wakelock_anomaly.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--  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.
+ -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:paddingBottom="6dp">
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="Wakelock Anomaly"
+        android:textSize="16sp"/>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+        <EditText
+            android:id="@+id/wakelock_threshold"
+            android:layout_width="0dp"
+            android:layout_weight="3"
+            android:layout_height="wrap_content"
+            android:hint="Threshold(ms)"
+            android:text="3000"
+            android:inputType="number"/>
+
+        <EditText
+            android:id="@+id/wakelock_run_time"
+            android:layout_width="0dp"
+            android:layout_weight="3"
+            android:layout_height="wrap_content"
+            android:hint="Run time(ms)"
+            android:text="6000"
+            android:inputType="number"/>
+
+        <Button
+            android:id="@+id/wakelock_button"
+            android:layout_width="0dp"
+            android:layout_weight="2"
+            android:layout_height="wrap_content"
+            android:text="START"
+            android:onClick="startWakelockAnomaly"/>
+    </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/anomaly-tester/src/com/android/settings/anomaly/tester/AnomalyActivity.java b/tests/anomaly-tester/src/com/android/settings/anomaly/tester/AnomalyActivity.java
index 6aa8edd..e4567c2 100644
--- a/tests/anomaly-tester/src/com/android/settings/anomaly/tester/AnomalyActivity.java
+++ b/tests/anomaly-tester/src/com/android/settings/anomaly/tester/AnomalyActivity.java
@@ -15,18 +15,120 @@
 package com.android.settings.anomaly.tester;
 
 import android.app.Activity;
+import android.content.Intent;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.ResultReceiver;
+import android.provider.Settings;
+import android.util.Log;
 import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.Toast;
 
+import com.android.settings.anomaly.tester.service.AnomalyService;
+import com.android.settings.anomaly.tester.utils.AnomalyActions;
+import com.android.settings.anomaly.tester.utils.AnomalyPolicyBuilder;
+
+/**
+ * Main activity to control and start anomaly
+ */
 public class AnomalyActivity extends Activity {
+    private static final String TAG = AnomalyActivity.class.getSimpleName();
+
+    public static final String KEY_TARGET_BUTTON = "target_button";
+
+    private AnomalyResultReceiver mResultReceiver;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
+        mResultReceiver = new AnomalyResultReceiver(new Handler());
     }
 
     public void startBluetoothAnomaly(View view) {
-        // Add in future cl
+        try {
+            // Enable anomaly detection and change the threshold
+            final String config = new AnomalyPolicyBuilder()
+                    .addPolicy(AnomalyPolicyBuilder.KEY_ANOMALY_DETECTION_ENABLED, true)
+                    .addPolicy(AnomalyPolicyBuilder.KEY_BLUETOOTH_SCAN_DETECTION_ENABLED, true)
+                    .addPolicy(AnomalyPolicyBuilder.KEY_BLUETOOTH_SCAN_THRESHOLD,
+                            getValueFromEditText(R.id.bluetooth_threshold))
+                    .build();
+            Settings.Global.putString(getContentResolver(),
+                    Settings.Global.ANOMALY_DETECTION_CONSTANTS, config);
+
+            // Start the anomaly service
+            Intent intent = new Intent(this, AnomalyService.class);
+            intent.putExtra(AnomalyActions.KEY_ACTION, AnomalyActions.ACTION_BLE_SCAN_UNOPTIMIZED);
+            intent.putExtra(AnomalyActions.KEY_DURATION_MS,
+                    getValueFromEditText(R.id.bluetooth_run_time));
+            intent.putExtra(AnomalyActions.KEY_RESULT_RECEIVER, mResultReceiver);
+            intent.putExtra(KEY_TARGET_BUTTON, view.getId());
+            startService(intent);
+
+            view.setEnabled(false);
+        } catch (NumberFormatException e) {
+            Toast.makeText(getApplicationContext(), e.toString(), Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    public void startWakelockAnomaly(View view) {
+        try {
+            // Enable anomaly detection and change the threshold
+            final String config = new AnomalyPolicyBuilder()
+                    .addPolicy(AnomalyPolicyBuilder.KEY_ANOMALY_DETECTION_ENABLED, true)
+                    .addPolicy(AnomalyPolicyBuilder.KEY_WAKELOCK_DETECTION_ENABLED, true)
+                    .addPolicy(AnomalyPolicyBuilder.KEY_WAKELOCK_THRESHOLD,
+                            getValueFromEditText(R.id.wakelock_threshold))
+                    .build();
+            Settings.Global.putString(getContentResolver(),
+                    Settings.Global.ANOMALY_DETECTION_CONSTANTS,
+                    config);
+
+            // Start the anomaly service
+            Intent intent = new Intent(this, AnomalyService.class);
+            intent.putExtra(AnomalyActions.KEY_ACTION, AnomalyActions.ACTION_WAKE_LOCK);
+            intent.putExtra(AnomalyActions.KEY_DURATION_MS,
+                    getValueFromEditText(R.id.wakelock_run_time));
+            intent.putExtra(AnomalyActions.KEY_RESULT_RECEIVER, mResultReceiver);
+            intent.putExtra(KEY_TARGET_BUTTON, view.getId());
+            startService(intent);
+
+            view.setEnabled(false);
+        } catch (NumberFormatException e) {
+            Toast.makeText(getApplicationContext(), e.toString(), Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    private long getValueFromEditText(final int id) throws NumberFormatException {
+        final EditText editText = findViewById(id);
+        if (editText != null) {
+            final long value = Long.parseLong(editText.getText().toString());
+            if (value > 0) {
+                return value;
+            }
+        }
+
+        throw new NumberFormatException("Number should be positive");
+    }
+
+    private class AnomalyResultReceiver extends ResultReceiver {
+
+        public AnomalyResultReceiver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            super.onReceiveResult(resultCode, resultData);
+
+            final Button button = findViewById(resultData.getInt(KEY_TARGET_BUTTON));
+            if (button != null) {
+                button.setEnabled(true);
+            }
+
+        }
     }
 }
diff --git a/tests/anomaly-tester/src/com/android/settings/anomaly/tester/service/AnomalyService.java b/tests/anomaly-tester/src/com/android/settings/anomaly/tester/service/AnomalyService.java
new file mode 100644
index 0000000..b569bce
--- /dev/null
+++ b/tests/anomaly-tester/src/com/android/settings/anomaly/tester/service/AnomalyService.java
@@ -0,0 +1,49 @@
+/*
+ * 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.anomaly.tester.service;
+
+import android.annotation.Nullable;
+import android.app.IntentService;
+import android.content.Intent;
+import android.os.ResultReceiver;
+
+import com.android.settings.anomaly.tester.utils.AnomalyActions;
+
+/**
+ * Service to run the anomaly action
+ */
+public class AnomalyService extends IntentService {
+    private static final String TAG = AnomalyService.class.getSimpleName();
+
+    public AnomalyService() {
+        super(AnomalyService.class.getSimpleName());
+    }
+
+    @Override
+    protected void onHandleIntent(@Nullable Intent intent) {
+        final String action = intent.getStringExtra(AnomalyActions.KEY_ACTION);
+        final long durationMs = intent.getLongExtra(AnomalyActions.KEY_DURATION_MS, 0);
+        final ResultReceiver resultReceiver = intent.getParcelableExtra(
+                AnomalyActions.KEY_RESULT_RECEIVER);
+
+        AnomalyActions.doAction(this, action, durationMs);
+
+        if (resultReceiver != null) {
+            resultReceiver.send(0 /* resultCode */, intent.getExtras());
+        }
+    }
+}
diff --git a/tests/anomaly-tester/src/com/android/settings/anomaly/tester/utils/AnomalyActions.java b/tests/anomaly-tester/src/com/android/settings/anomaly/tester/utils/AnomalyActions.java
new file mode 100644
index 0000000..58e5a99
--- /dev/null
+++ b/tests/anomaly-tester/src/com/android/settings/anomaly/tester/utils/AnomalyActions.java
@@ -0,0 +1,113 @@
+/*
+ * 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.anomaly.tester.utils;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.ScanCallback;
+import android.bluetooth.le.ScanResult;
+import android.bluetooth.le.ScanSettings;
+import android.content.Context;
+import android.os.PowerManager;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Actions to generate anomaly.
+ */
+public class AnomalyActions {
+    private static final String TAG = AnomalyActions.class.getSimpleName();
+
+    public static final String KEY_ACTION = "action";
+    public static final String KEY_DURATION_MS = "duration_ms";
+    public static final String KEY_RESULT_RECEIVER = "result_receiver";
+
+    public static final String ACTION_BLE_SCAN_UNOPTIMIZED = "action.ble_scan_unoptimized";
+    public static final String ACTION_WAKE_LOCK = "action.wake_lock";
+
+    public static void doAction(Context ctx, String actionCode, long durationMs) {
+        if (actionCode == null) {
+            Log.e(TAG, "Intent was missing action.");
+            return;
+        }
+        switch (actionCode) {
+            case ACTION_BLE_SCAN_UNOPTIMIZED:
+                doUnoptimizedBleScan(ctx, durationMs);
+                break;
+            case ACTION_WAKE_LOCK:
+                doHoldWakelock(ctx, durationMs);
+            default:
+                Log.e(TAG, "Intent had invalid action");
+        }
+    }
+
+    private static void doUnoptimizedBleScan(Context ctx, long durationMs) {
+        ScanSettings scanSettings = new ScanSettings.Builder()
+                .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
+
+        // perform ble scanning
+        BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+        if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled() ) {
+            Log.e(TAG, "Device does not support Bluetooth or Bluetooth not enabled");
+            return;
+        }
+        BluetoothLeScanner bleScanner = bluetoothAdapter.getBluetoothLeScanner();
+        if (bleScanner == null) {
+            Log.e(TAG, "Cannot access BLE scanner");
+            return;
+        }
+
+        ScanCallback scanCallback = new ScanCallback() {
+            @Override
+            public void onScanResult(int callbackType, ScanResult result) {
+                Log.v(TAG, "called onScanResult");
+            }
+
+            @Override
+            public void onScanFailed(int errorCode) {
+                Log.v(TAG, "called onScanFailed");
+            }
+
+            @Override
+            public void onBatchScanResults(List<ScanResult> results) {
+                Log.v(TAG, "called onBatchScanResults");
+            }
+        };
+
+        bleScanner.startScan(null, scanSettings, scanCallback);
+        try {
+            Thread.sleep(durationMs);
+        } catch (InterruptedException e) {
+            Log.e(TAG, "Thread couldn't sleep for " + durationMs, e);
+        }
+        bleScanner.stopScan(scanCallback);
+    }
+
+    private static void doHoldWakelock(Context ctx, long durationMs) {
+        PowerManager powerManager = ctx.getSystemService(PowerManager.class);
+        PowerManager.WakeLock wl = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                "AnomalyWakeLock");
+        wl.acquire();
+        try {
+            Thread.sleep(durationMs);
+        } catch (InterruptedException e) {
+            Log.e(TAG, "Thread couldn't sleep for " + durationMs, e);
+        }
+        wl.release();
+    }
+}
diff --git a/tests/anomaly-tester/src/com/android/settings/anomaly/tester/utils/AnomalyPolicyBuilder.java b/tests/anomaly-tester/src/com/android/settings/anomaly/tester/utils/AnomalyPolicyBuilder.java
new file mode 100644
index 0000000..bf8e075
--- /dev/null
+++ b/tests/anomaly-tester/src/com/android/settings/anomaly/tester/utils/AnomalyPolicyBuilder.java
@@ -0,0 +1,72 @@
+/*
+ * 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.anomaly.tester.utils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Builder to build the anomaly policy string, used in {@link android.provider.Settings.Global}
+ *
+ * @see android.provider.Settings.Global#ANOMALY_DETECTION_CONSTANTS
+ */
+public class AnomalyPolicyBuilder {
+    public static final String KEY_ANOMALY_DETECTION_ENABLED = "anomaly_detection_enabled";
+    public static final String KEY_WAKELOCK_DETECTION_ENABLED = "wakelock_enabled";
+    public static final String KEY_WAKEUP_ALARM_DETECTION_ENABLED = "wakeup_alarm_enabled";
+    public static final String KEY_BLUETOOTH_SCAN_DETECTION_ENABLED = "bluetooth_scan_enabled";
+    public static final String KEY_WAKELOCK_THRESHOLD = "wakelock_threshold";
+    public static final String KEY_WAKEUP_ALARM_THRESHOLD = "wakeup_alarm_threshold";
+    public static final String KEY_BLUETOOTH_SCAN_THRESHOLD = "bluetooth_scan_threshold";
+
+    public static final String DELIM = ",";
+
+    private Map<String, String> mValues;
+
+    public AnomalyPolicyBuilder() {
+        mValues = new HashMap<>();
+    }
+
+    public AnomalyPolicyBuilder addPolicy(String key, String value) {
+        mValues.put(key, value);
+        return this;
+    }
+
+    public AnomalyPolicyBuilder addPolicy(String key, long value) {
+        mValues.put(key, Long.toString(value));
+        return this;
+    }
+
+
+    public AnomalyPolicyBuilder addPolicy(String key, boolean value) {
+        mValues.put(key, value ? "true" : "false");
+        return this;
+    }
+
+    public String build() {
+        StringBuilder sb = new StringBuilder();
+        for (Map.Entry<String, String> entry : mValues.entrySet()) {
+            sb.append(entry.getKey() + "=" + entry.getValue() + DELIM);
+        }
+
+        if (sb.length() != 0) {
+            return sb.substring(0, sb.length() - 1);
+        } else {
+            return "";
+        }
+    }
+}
diff --git a/tests/anomaly-tester/src/com/android/settings/anomaly/tests/BluetoothAnomalyTest.java b/tests/anomaly-tester/src/com/android/settings/anomaly/tests/BluetoothAnomalyTest.java
new file mode 100644
index 0000000..3630ce4
--- /dev/null
+++ b/tests/anomaly-tester/src/com/android/settings/anomaly/tests/BluetoothAnomalyTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.anomaly.tests;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.Until;
+import android.text.format.DateUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Functional test for bluetooth unoptimized scanning anomaly detector
+ *
+ * @see com.android.settings.fuelgauge.anomaly.checker.BluetoothScanAnomalyDetector
+ */
+@RunWith(AndroidJUnit4.class)
+public class BluetoothAnomalyTest {
+    private static final String BATTERY_INTENT = "android.intent.action.POWER_USAGE_SUMMARY";
+    private static final String RES_BT_EDITTEXT =
+            "com.android.settings.anomaly.tester:id/bluetooth_run_time";
+    private static final String RES_BT_BUTTON =
+            "com.android.settings.anomaly.tester:id/bluetooth_button";
+    private static final long TIME_OUT = 3000;
+    private UiDevice mDevice;
+
+    @Before
+    public void setUp() {
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        final Context context = instrumentation.getContext();
+        mDevice = UiDevice.getInstance(instrumentation);
+
+        // setup environment
+        TestUtils.setUp(instrumentation);
+        // start anomaly-tester app
+        TestUtils.startAnomalyApp(context, mDevice);
+    }
+
+    @After
+    public void tearDown() {
+        TestUtils.tearDown(InstrumentationRegistry.getInstrumentation());
+    }
+
+    @Test
+    public void testBluetoothAnomaly_longScanningTime_reportAnomaly() throws InterruptedException {
+        // Set running time
+        final long durationMs = DateUtils.SECOND_IN_MILLIS * 15;
+        TestUtils.setEditTextWithValue(mDevice, RES_BT_EDITTEXT, durationMs);
+
+        // Click start button
+        TestUtils.clickButton(mDevice, RES_BT_BUTTON);
+
+        // Wait for its running
+        mDevice.pressHome();
+        TestUtils.wait(mDevice, durationMs);
+
+        // Check it in battery main page
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        instrumentation.startActivitySync(new Intent(BATTERY_INTENT));
+        assertWithMessage("Doesn't have bluetooth anomaly").that(
+                mDevice.wait(Until.findObject(By.text("AnomalyTester draining battery")),
+                        TIME_OUT)).isNotNull();
+    }
+
+    @Test
+    public void testBluetoothAnomaly_shortScanningTime_notReport() throws InterruptedException {
+        // Set running time
+        final long durationMs = DateUtils.SECOND_IN_MILLIS;
+        TestUtils.setEditTextWithValue(mDevice, RES_BT_EDITTEXT, durationMs);
+
+        // Click start button
+        TestUtils.clickButton(mDevice, RES_BT_BUTTON);
+
+        // Wait for its running
+        mDevice.pressHome();
+        TestUtils.wait(mDevice, durationMs);
+
+        // Check it in battery main page
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        instrumentation.startActivitySync(new Intent(BATTERY_INTENT));
+        assertWithMessage("Shouldn't have bluetooth anomaly").that(
+                mDevice.wait(Until.findObject(By.text("AnomalyTester draining battery")),
+                        TIME_OUT)).isNull();
+    }
+
+}
diff --git a/tests/anomaly-tester/src/com/android/settings/anomaly/tests/TestUtils.java b/tests/anomaly-tester/src/com/android/settings/anomaly/tests/TestUtils.java
new file mode 100644
index 0000000..ac15d77
--- /dev/null
+++ b/tests/anomaly-tester/src/com/android/settings/anomaly/tests/TestUtils.java
@@ -0,0 +1,91 @@
+/*
+ * 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.anomaly.tests;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.content.Intent;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+
+public class TestUtils {
+    private static final String PACKAGE_NAME = "com.android.settings.anomaly.tester";
+    private static final long TIME_OUT = 3000;
+
+    /**
+     * This method set up the environment for anomaly test
+     *
+     * @param instrumentation to execute command
+     */
+    public static void setUp(Instrumentation instrumentation) {
+        final UiAutomation uiAutomation = instrumentation.getUiAutomation();
+        // pretend unplug and screen off, also reset the battery stats
+        uiAutomation.executeShellCommand("dumpsys battery unplug");
+        uiAutomation.executeShellCommand("dumpsys batterystats enable pretend-screen-off");
+        uiAutomation.executeShellCommand("dumpsys batterystats --reset");
+    }
+
+    /**
+     * This method cleans up all the commands in {@link #setUp(Instrumentation)}
+     *
+     * @param instrumentation to execute command
+     */
+    public static void tearDown(Instrumentation instrumentation) {
+        final UiAutomation uiAutomation = instrumentation.getUiAutomation();
+        // reset unplug and screen-off
+        uiAutomation.executeShellCommand("dumpsys battery reset");
+        uiAutomation.executeShellCommand("dumpsys batterystats disable pretend-screen-off");
+    }
+
+    public static void startAnomalyApp(Context context, UiDevice uiDevice) {
+        final Intent intent = context.getPackageManager().getLaunchIntentForPackage(PACKAGE_NAME);
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        context.startActivity(intent);
+        uiDevice.wait(Until.hasObject(By.pkg(PACKAGE_NAME).depth(0)), TIME_OUT);
+    }
+
+    /**
+     * Find {@link android.widget.EditText} with {@code res} and set its {@code value}
+     */
+    public static void setEditTextWithValue(UiDevice uiDevice, String res, long value) {
+        final UiObject2 editText = uiDevice.findObject(By.res(res));
+        assertWithMessage("Cannot find editText with res: " + res).that(editText).isNotNull();
+        editText.setText(String.valueOf(value));
+    }
+
+    /**
+     * Find {@link android.widget.Button} with {@code res} and click it
+     */
+    public static void clickButton(UiDevice uiDevice, String res) {
+        final UiObject2 button = uiDevice.findObject(By.res(res));
+        assertWithMessage("Cannot find button with res: " + res).that(button).isNotNull();
+        button.click();
+    }
+
+    /**
+     * Make {@link UiDevice} wait for {@code timeMs}
+     *
+     * @see Thread#sleep(long)
+     */
+    public static void wait(UiDevice uiDevice, long timeMs) throws InterruptedException {
+        uiDevice.waitForIdle();
+        Thread.sleep(timeMs);
+    }
+}
diff --git a/tests/anomaly-tester/src/com/android/settings/anomaly/tests/WakelockAnomalyTest.java b/tests/anomaly-tester/src/com/android/settings/anomaly/tests/WakelockAnomalyTest.java
new file mode 100644
index 0000000..a2f3804
--- /dev/null
+++ b/tests/anomaly-tester/src/com/android/settings/anomaly/tests/WakelockAnomalyTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.anomaly.tests;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.Until;
+import android.text.format.DateUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Functional test for bluetooth unoptimized scanning anomaly detector
+ *
+ * @see com.android.settings.fuelgauge.anomaly.checker.BluetoothScanAnomalyDetector
+ */
+@RunWith(AndroidJUnit4.class)
+public class WakelockAnomalyTest {
+    private static final String BATTERY_INTENT = "android.intent.action.POWER_USAGE_SUMMARY";
+    private static final String RES_WAKELOCK_EDITTEXT =
+            "com.android.settings.anomaly.tester:id/wakelock_run_time";
+    private static final String RES_WAKELOCK_BUTTON =
+            "com.android.settings.anomaly.tester:id/wakelock_button";
+    private static final long TIME_OUT = 3000;
+    private UiDevice mDevice;
+
+    @Before
+    public void setUp() {
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        final Context context = instrumentation.getContext();
+        mDevice = UiDevice.getInstance(instrumentation);
+
+        // setup environment
+        TestUtils.setUp(instrumentation);
+        // start anomaly-tester app
+        TestUtils.startAnomalyApp(context, mDevice);
+    }
+
+    @After
+    public void tearDown() {
+        TestUtils.tearDown(InstrumentationRegistry.getInstrumentation());
+    }
+
+    @Test
+    public void testWakelockAnomaly_longTimeWhileRunning_report() throws InterruptedException {
+        // Set running time
+        final long durationMs = DateUtils.SECOND_IN_MILLIS * 15;
+        TestUtils.setEditTextWithValue(mDevice, RES_WAKELOCK_EDITTEXT, durationMs);
+
+        // Click start button
+        TestUtils.clickButton(mDevice, RES_WAKELOCK_BUTTON);
+
+        // Wait for its running
+        mDevice.pressHome();
+        // Sleeping time less than running time, so the app still holding wakelock when we check
+        TestUtils.wait(mDevice, durationMs - TIME_OUT);
+
+        // Check it in battery main page
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        instrumentation.startActivitySync(new Intent(BATTERY_INTENT));
+        assertWithMessage("Doesn't have wakelock anomaly").that(
+                mDevice.wait(Until.findObject(By.text("AnomalyTester draining battery")),
+                        TIME_OUT)).isNotNull();
+    }
+
+    @Test
+    public void testWakelockAnomaly_shortTime_notReport() throws InterruptedException {
+        // Set running time
+        final long durationMs = DateUtils.SECOND_IN_MILLIS;
+        TestUtils.setEditTextWithValue(mDevice, RES_WAKELOCK_EDITTEXT, durationMs);
+
+        // Click start button
+        TestUtils.clickButton(mDevice, RES_WAKELOCK_BUTTON);
+
+        // Wait for its running
+        mDevice.pressHome();
+        TestUtils.wait(mDevice, durationMs);
+
+        // Check it in battery main page
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        instrumentation.startActivitySync(new Intent(BATTERY_INTENT));
+        assertWithMessage("Shouldn't have wakelock anomaly").that(
+                mDevice.wait(Until.findObject(By.text("AnomalyTester draining battery")),
+                        TIME_OUT)).isNull();
+    }
+
+    @Test
+    public void testWakelockAnomaly_longTimeWhileNotRunning_notReport()
+            throws InterruptedException {
+        // Set running time
+        final long durationMs = DateUtils.SECOND_IN_MILLIS * 10;
+        TestUtils.setEditTextWithValue(mDevice, RES_WAKELOCK_EDITTEXT, durationMs);
+
+        // Click start button
+        TestUtils.clickButton(mDevice, RES_WAKELOCK_BUTTON);
+
+        // Wait for its running
+        mDevice.pressHome();
+        // Wait more time for releasing the wakelock
+        TestUtils.wait(mDevice, durationMs + TIME_OUT);
+
+        // Check it in battery main page
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        instrumentation.startActivitySync(new Intent(BATTERY_INTENT));
+        assertWithMessage("Shouldn't have wakelock anomaly").that(
+                mDevice.wait(Until.findObject(By.text("AnomalyTester draining battery")),
+                        TIME_OUT)).isNull();
+    }
+
+}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHeaderControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHeaderControllerTest.java
index 27c1a83..98a3580 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHeaderControllerTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHeaderControllerTest.java
@@ -32,6 +32,7 @@
 import com.android.settings.TestConfig;
 import com.android.settings.testutils.FakeFeatureFactory;
 import com.android.settings.testutils.shadow.SettingsShadowBluetoothDevice;
+import com.android.settings.testutils.shadow.SettingsShadowResources;
 import com.android.settings.testutils.shadow.ShadowEntityHeaderController;
 import com.android.settings.widget.EntityHeaderController;
 
@@ -45,7 +46,8 @@
 
 @RunWith(SettingsRobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
-        shadows={SettingsShadowBluetoothDevice.class, ShadowEntityHeaderController.class})
+        shadows={SettingsShadowBluetoothDevice.class, ShadowEntityHeaderController.class,
+                SettingsShadowResources.class})
 public class BluetoothDetailsHeaderControllerTest extends BluetoothDetailsControllerTestBase {
     private BluetoothDetailsHeaderController mController;
     private LayoutPreference mPreference;
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePreferenceTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePreferenceTest.java
index d60571c..a1db5de 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePreferenceTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePreferenceTest.java
@@ -26,6 +26,7 @@
 import com.android.settings.TestConfig;
 import com.android.settings.core.instrumentation.MetricsFeatureProvider;
 import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.shadow.SettingsShadowResources;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 
 import org.junit.Before;
@@ -38,18 +39,23 @@
 import org.robolectric.util.ReflectionHelpers;
 
 import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 @RunWith(SettingsRobolectricTestRunner.class)
-@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
+        shadows = SettingsShadowResources.class)
 public class BluetoothDevicePreferenceTest {
 
     private Context mContext;
     @Mock
     private CachedBluetoothDevice mCachedBluetoothDevice;
+    @Mock
+    private DeviceListPreferenceFragment mDeviceListPreferenceFragment;
 
     private FakeFeatureFactory mFakeFeatureFactory;
     private MetricsFeatureProvider mMetricsFeatureProvider;
@@ -62,7 +68,8 @@
         FakeFeatureFactory.setupForTest(mContext);
         mFakeFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
         mMetricsFeatureProvider = mFakeFeatureFactory.getMetricsFeatureProvider();
-        mPreference = new BluetoothDevicePreference(mContext, mCachedBluetoothDevice);
+        mPreference = new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
+                mDeviceListPreferenceFragment);
     }
 
     @Test
@@ -140,10 +147,58 @@
 
     @Test
     public void imagingDeviceIcon_isICSettingsPrint() {
+        when(mCachedBluetoothDevice.getBatteryLevel()).thenReturn(
+                BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
         when(mCachedBluetoothDevice.getBtClass()).thenReturn(
                 new BluetoothClass(BluetoothClass.Device.Major.IMAGING));
+
         mPreference.onDeviceAttributesChanged();
         assertThat(mPreference.getIcon()).isEqualTo(
                 mContext.getDrawable(R.drawable.ic_settings_print));
     }
+
+    @Test
+    public void testVisible_notVisibleThenVisible() {
+        when(mDeviceListPreferenceFragment.shouldShowDevicesWithoutNames()).thenReturn(false);
+        final boolean[] humanReadableName = {false};
+        doAnswer(invocation -> humanReadableName[0]).when(mCachedBluetoothDevice)
+                .hasHumanReadableName();
+        BluetoothDevicePreference preference =
+                new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
+                        mDeviceListPreferenceFragment);
+        assertThat(preference.isVisible()).isFalse();
+        humanReadableName[0] = true;
+        preference.onDeviceAttributesChanged();
+        assertThat(preference.isVisible()).isTrue();
+    }
+
+    @Test
+    public void testVisible_visibleThenNotVisible() {
+        when(mDeviceListPreferenceFragment.shouldShowDevicesWithoutNames()).thenReturn(false);
+        final boolean[] humanReadableName = {true};
+        doAnswer(invocation -> humanReadableName[0]).when(mCachedBluetoothDevice)
+                .hasHumanReadableName();
+        BluetoothDevicePreference preference =
+                new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
+                        mDeviceListPreferenceFragment);
+        assertThat(preference.isVisible()).isTrue();
+        humanReadableName[0] = false;
+        preference.onDeviceAttributesChanged();
+        assertThat(preference.isVisible()).isFalse();
+    }
+
+    @Test
+    public void testVisible_alwaysVisibleWhenEnabled() {
+        when(mDeviceListPreferenceFragment.shouldShowDevicesWithoutNames()).thenReturn(true);
+        final boolean[] humanReadableName = {true};
+        doAnswer(invocation -> humanReadableName[0]).when(mCachedBluetoothDevice)
+                .hasHumanReadableName();
+        BluetoothDevicePreference preference =
+                new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
+                        mDeviceListPreferenceFragment);
+        assertThat(preference.isVisible()).isTrue();
+        humanReadableName[0] = false;
+        preference.onDeviceAttributesChanged();
+        assertThat(preference.isVisible()).isTrue();
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/bluetooth/UtilsTest.java b/tests/robotests/src/com/android/settings/bluetooth/UtilsTest.java
index 4667dac..7654921 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/UtilsTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/UtilsTest.java
@@ -15,14 +15,21 @@
  */
 package com.android.settings.bluetooth;
 
+import static com.google.common.truth.Truth.assertThat;
+
+import android.bluetooth.BluetoothDevice;
 import android.content.Context;
+import android.graphics.drawable.Drawable;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settings.R;
 import com.android.settings.testutils.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
 import com.android.settings.core.instrumentation.MetricsFeatureProvider;
 import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.shadow.SettingsShadowResources;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.graph.BluetoothDeviceLayerDrawable;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -30,6 +37,7 @@
 import org.mockito.Answers;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 
 import static org.mockito.Matchers.anyInt;
@@ -40,7 +48,8 @@
 import static org.mockito.Mockito.when;
 
 @RunWith(SettingsRobolectricTestRunner.class)
-@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
+        shadows = SettingsShadowResources.class)
 public class UtilsTest {
 
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
@@ -60,11 +69,27 @@
     }
 
     @Test
-    public void showConnectingError_shouldLogBluetoothConnectError() {
+    public void testShowConnectingError_shouldLogBluetoothConnectError() {
         when(mContext.getString(anyInt(), anyString())).thenReturn("testMessage");
         Utils.showConnectingError(mContext, "testName", mock(LocalBluetoothManager.class));
 
         verify(mMetricsFeatureProvider).visible(eq(mContext), anyInt(),
-            eq(MetricsEvent.ACTION_SETTINGS_BLUETOOTH_CONNECT_ERROR));
+                eq(MetricsEvent.ACTION_SETTINGS_BLUETOOTH_CONNECT_ERROR));
+    }
+
+    @Test
+    public void testGetBluetoothDrawable_noBatteryLevel_returnSimpleDrawable() {
+        final Drawable drawable = Utils.getBluetoothDrawable(RuntimeEnvironment.application,
+                R.drawable.ic_bt_laptop, BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
+
+        assertThat(drawable).isNotInstanceOf(BluetoothDeviceLayerDrawable.class);
+    }
+
+    @Test
+    public void testGetBluetoothDrawable_hasBatteryLevel_returnLayerDrawable() {
+        final Drawable drawable = Utils.getBluetoothDrawable(RuntimeEnvironment.application,
+                R.drawable.ic_bt_laptop, 10 /* batteryLevel */);
+
+        assertThat(drawable).isInstanceOf(BluetoothDeviceLayerDrawable.class);
     }
 }
diff --git a/tests/robotests/src/com/android/settings/dashboard/DashboardSummaryTest.java b/tests/robotests/src/com/android/settings/dashboard/DashboardSummaryTest.java
index 967ed24..f3ed57c 100644
--- a/tests/robotests/src/com/android/settings/dashboard/DashboardSummaryTest.java
+++ b/tests/robotests/src/com/android/settings/dashboard/DashboardSummaryTest.java
@@ -36,6 +36,7 @@
 import org.robolectric.util.ReflectionHelpers;
 
 import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -90,6 +91,7 @@
     public void onConditionChanged_PositionAtTop_ScrollToTop() {
         when(mLayoutManager.findFirstCompletelyVisibleItemPosition()).thenReturn(1);
         mSummary.onConditionsChanged();
+        mSummary.onConditionsChanged();
         verify(mDashboard).scrollToPosition(0);
     }
 
@@ -97,10 +99,24 @@
     public void onConditionChanged_PositionNotTop_RemainPosition() {
         when(mLayoutManager.findFirstCompletelyVisibleItemPosition()).thenReturn(2);
         mSummary.onConditionsChanged();
+        mSummary.onConditionsChanged();
         verify(mDashboard, never()).scrollToPosition(0);
     }
 
     @Test
+    public void onConditionChanged_firstCall_shouldIgnore() {
+        mSummary.onConditionsChanged();
+        verify(mAdapter, never()).setConditions(any());
+    }
+
+    @Test
+    public void onConditionChanged_secondCall_shouldSetConditionsOnAdapter() {
+        mSummary.onConditionsChanged();
+        mSummary.onConditionsChanged();
+        verify(mAdapter).setConditions(any());
+    }
+
+    @Test
     public void onCategoryChanged_noRebuildOnFirstCall() {
         doReturn(mock(Activity.class)).when(mSummary).getActivity();
         doNothing().when(mSummary).rebuildUI();