Merge "Add missing 'pin' screen lock option" into oc-dr1-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index cbf1eae..b2a8dd1 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -405,6 +405,18 @@
                 android:value="com.android.settings.bluetooth.BluetoothSettings" />
         </activity>
 
+        <activity android:name="Settings$AssistGestureSettingsActivity"
+            android:label="@string/assist_gesture_title"
+            android:icon="@drawable/ic_settings_gestures"
+            android:taskAffinity="">
+            <intent-filter>
+                <action android:name="android.settings.ASSIST_GESTURE_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
+                android:value="com.android.settings.gestures.AssistGestureSettings" />
+        </activity>
+
         <!-- Keep compatibility with old shortcuts. -->
         <activity-alias android:name=".bluetooth.BluetoothSettings"
                 android:label="@string/bluetooth_settings_title"
diff --git a/res/layout/fingerprint_enroll_enrolling_base.xml b/res/layout/fingerprint_enroll_enrolling_base.xml
index 7f8eb1b..e312c50 100644
--- a/res/layout/fingerprint_enroll_enrolling_base.xml
+++ b/res/layout/fingerprint_enroll_enrolling_base.xml
@@ -34,14 +34,14 @@
              switch the text -->
         <FrameLayout
             android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="@dimen/suw_description_glif_margin_top">
+            android:layout_height="wrap_content">
 
             <TextView
                 style="@style/SuwDescription.Glif"
                 android:id="@+id/start_message"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
+                android:minLines="3"
                 android:text="@string/security_settings_fingerprint_enroll_start_message"/>
 
             <TextView
@@ -49,27 +49,36 @@
                 android:id="@+id/repeat_message"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
+                android:minLines="3"
                 android:text="@string/security_settings_fingerprint_enroll_repeat_message"
                 android:visibility="invisible"/>
 
         </FrameLayout>
 
-        <include layout="@layout/fingerprint_enroll_enrolling_content"
-            android:layout_width="@dimen/fingerprint_progress_bar_max_size"
+        <FrameLayout
+            android:layout_width="match_parent"
             android:layout_height="0dp"
             android:layout_weight="1"
-            android:layout_gravity="center_horizontal"
-            android:layout_marginTop="@dimen/fingerprint_enrolling_content_margin_top"/>
+            android:paddingBottom="80dp">
 
-        <TextView
-            style="@style/TextAppearance.FingerprintErrorText"
-            android:id="@+id/error_text"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="24dp"
-            android:layout_gravity="center_horizontal"
-            android:accessibilityLiveRegion="polite"
-            android:visibility="invisible"/>
+            <include layout="@layout/fingerprint_enroll_enrolling_content"
+                android:layout_width="@dimen/fingerprint_progress_bar_max_size"
+                android:layout_height="@dimen/fingerprint_progress_bar_max_size"
+                android:layout_gravity="center"
+                android:layout_marginVertical="24dp"/>
+
+            <TextView
+                style="@style/TextAppearance.FingerprintErrorText"
+                android:id="@+id/error_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="24dp"
+                android:layout_gravity="center_horizontal|bottom"
+                android:accessibilityLiveRegion="polite"
+                android:gravity="center"
+                android:visibility="invisible"/>
+
+        </FrameLayout>
 
     </LinearLayout>
 
diff --git a/res/layout/fingerprint_enroll_finish_base.xml b/res/layout/fingerprint_enroll_finish_base.xml
index f666942..ba3d07f 100644
--- a/res/layout/fingerprint_enroll_finish_base.xml
+++ b/res/layout/fingerprint_enroll_finish_base.xml
@@ -38,12 +38,15 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginTop="@dimen/suw_description_glif_margin_top"
+            android:minLines="3"
             android:text="@string/security_settings_fingerprint_enroll_finish_message"/>
 
         <com.android.setupwizardlib.view.FillContentLayout
             android:layout_width="match_parent"
-            android:layout_height="0dp"
-            android:layout_weight="1">
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:maxWidth="@dimen/fingerprint_finish_max_size"
+            android:maxHeight="@dimen/fingerprint_finish_max_size">
 
             <ImageView
                 android:id="@+id/fingerprint_in_app_indicator"
diff --git a/res/values-sw400dp/dimens.xml b/res/values-sw400dp/dimens.xml
index 0c35d84..35a25d8 100755
--- a/res/values-sw400dp/dimens.xml
+++ b/res/values-sw400dp/dimens.xml
@@ -16,7 +16,7 @@
   -->
 
 <resources>
-    <dimen name="fingerprint_enrolling_content_margin_top">56dp</dimen>
+    <dimen name="fingerprint_enrolling_content_margin_vertical">56dp</dimen>
     <dimen name="fingerprint_find_sensor_graphic_size">240dp</dimen>
 
     <dimen name="support_escalation_card_padding_start">56dp</dimen>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index e3c7032..76dacb3 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -225,9 +225,13 @@
     <dimen name="fingerprint_error_text_appear_distance">16dp</dimen>
     <dimen name="fingerprint_error_text_disappear_distance">-8dp</dimen>
     <dimen name="fingerprint_animation_size">88dp</dimen>
-    <dimen name="fingerprint_progress_bar_max_size">220dp</dimen>
+    <dimen name="fingerprint_progress_bar_max_size">240dp</dimen>
     <dimen name="fingerprint_progress_bar_min_size">120dp</dimen>
-    <dimen name="fingerprint_enrolling_content_margin_top">36dp</dimen>
+    <dimen name="fingerprint_enrolling_content_margin_vertical">24dp</dimen>
+    <!-- To be the same size as fingerprint progress bar:
+         fingerprint_finish_max_size = fingerprint_progress_bar_max_size
+                  + (fingerprint_enrolling_content_margin_vertical x 2) -->
+    <dimen name="fingerprint_finish_max_size">288dp</dimen>
 
     <dimen name="confirm_credentials_security_method_margin">48dp</dimen>
     <dimen name="confirm_credentials_layout_width">@dimen/match_parent</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index dd6abfa..95bbb7a 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -4853,8 +4853,8 @@
     <string name="battery_detail_since_full_charge">Breakdown since last full charge</string>
     <!-- Title for usage time since last full charge. [CHAR LIMIT=60] -->
     <string name="battery_last_full_charge">Last full charge</string>
-    <!-- Description for text in battery footer. [CHAR LIMIT=120] -->
-    <string name="battery_footer_summary">Remaining battery time is approximate and can change based on usage</string>
+    <!-- Description for text in battery footer. [CHAR LIMIT=NONE] -->
+    <string name="battery_footer_summary">Battery usage data is approximate and can change based on usage</string>
     <!-- Title for text that shows the amount of time an app has been running while in the foreground. [CHAR LIMIT=80] -->
     <string name="battery_detail_foreground">While in active use</string>
     <!-- Title for text that shows the amount of time an app has been running while in the background. [CHAR LIMIT=80] -->
@@ -6233,6 +6233,7 @@
     <string name="help_url_system_dashboard" translatable="false"></string>
     <string name="help_url_double_tap_screen" translatable="false"></string>
     <string name="help_url_account_detail" translatable="false"></string>
+    <string name="help_url_icc_lock" translatable="false"></string>
 
     <!-- User account title [CHAR LIMIT=30] -->
     <string name="user_account_title">Account for content</string>
@@ -8592,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>
@@ -8958,7 +8959,7 @@
     <string name="help_url_audio_accessory_not_supported" translatable="false"></string>
 
     <!-- Title label for new device suggestion, which is displayed in Settings homepage [CHAR LIMIT=100] -->
-    <string name="new_device_suggestion_title">What's new and exciting?</string>
+    <string name="new_device_suggestion_title">What\'s new and exciting?</string>
 
     <!-- Summary label for new device suggestion, which is displayed in Settings homepage [CHAR LIMIT=100] -->
     <string name="new_device_suggestion_summary">Check out the top 5 features</string>
diff --git a/src/com/android/settings/IccLockSettings.java b/src/com/android/settings/IccLockSettings.java
index d7de8fb..3e139e1 100644
--- a/src/com/android/settings/IccLockSettings.java
+++ b/src/com/android/settings/IccLockSettings.java
@@ -288,6 +288,11 @@
     }
 
     @Override
+    protected int getHelpResource() {
+        return R.string.help_url_icc_lock;
+    }
+
+    @Override
     public void onSaveInstanceState(Bundle out) {
         // Need to store this state for slider open/close
         // There is one case where the dialog is popped up by the preference
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index 38b6e1b..ab03c58 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -32,6 +32,7 @@
     /*
     * Settings subclasses for launching independently.
     */
+    public static class AssistGestureSettingsActivity extends SettingsActivity { /* empty */}
     public static class BluetoothSettingsActivity extends SettingsActivity { /* empty */ }
     public static class SimSettingsActivity extends SettingsActivity { /* empty */ }
     public static class TetherSettingsActivity extends SettingsActivity { /* empty */ }
diff --git a/src/com/android/settings/SetupWizardUtils.java b/src/com/android/settings/SetupWizardUtils.java
index 7edffed..d53abae 100644
--- a/src/com/android/settings/SetupWizardUtils.java
+++ b/src/com/android/settings/SetupWizardUtils.java
@@ -17,13 +17,21 @@
 package com.android.settings;
 
 import android.content.Intent;
+import android.os.SystemProperties;
+import android.support.annotation.VisibleForTesting;
 
 import com.android.setupwizardlib.util.WizardManagerHelper;
 
 public class SetupWizardUtils {
 
+    @VisibleForTesting
+    static final String SYSTEM_PROP_SETUPWIZARD_THEME = "setupwizard.theme";
+
     public static int getTheme(Intent intent) {
         String theme = intent.getStringExtra(WizardManagerHelper.EXTRA_THEME);
+        if (theme == null) {
+            theme = SystemProperties.get(SYSTEM_PROP_SETUPWIZARD_THEME);
+        }
         if (theme != null) {
             switch (theme) {
                 case WizardManagerHelper.THEME_GLIF_V2_LIGHT:
diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java
index 15e4eb3..cfa8377 100644
--- a/src/com/android/settings/Utils.java
+++ b/src/com/android/settings/Utils.java
@@ -943,6 +943,8 @@
         return result;
     }
 
+    // TODO: move this out of Utils to a mixin or a controller or a helper class.
+    @Deprecated
     public static void handleLoadingContainer(View loading, View doneLoading, boolean done,
             boolean animate) {
         setViewShown(loading, !done, animate);
diff --git a/src/com/android/settings/applications/ManageApplications.java b/src/com/android/settings/applications/ManageApplications.java
index d6ae7b2..07632da 100644
--- a/src/com/android/settings/applications/ManageApplications.java
+++ b/src/com/android/settings/applications/ManageApplications.java
@@ -346,7 +346,6 @@
 
         mRootView = inflater.inflate(R.layout.manage_applications_apps, null);
         mLoadingContainer = mRootView.findViewById(R.id.loading_container);
-        mLoadingContainer.setVisibility(View.VISIBLE);
         mListContainer = mRootView.findViewById(R.id.list_container);
         if (mListContainer != null) {
             // Create adapter and list view here
@@ -395,7 +394,8 @@
         return mRootView;
     }
 
-    private void createHeader() {
+    @VisibleForTesting
+    void createHeader() {
         Activity activity = getActivity();
         FrameLayout pinnedHeader = (FrameLayout) mRootView.findViewById(R.id.pinned_header);
         mSpinnerHeader = activity.getLayoutInflater()
@@ -834,6 +834,10 @@
     static class ApplicationsAdapter extends BaseAdapter implements Filterable,
             ApplicationsState.Callbacks, AppStateBaseBridge.Callback,
             AbsListView.RecyclerListener, SectionIndexer {
+
+        // how long to wait for app list to populate without showing the loading container
+        private static final long DELAY_SHOW_LOADING_CONTAINER_THRESHOLD_MS = 100L;
+
         private static final SectionInfo[] EMPTY_SECTIONS = new SectionInfo[0];
 
         private final ApplicationsState mState;
@@ -889,6 +893,13 @@
             }
         };
 
+        private Runnable mShowLoadingContainerRunnable = new Runnable() {
+            public void run() {
+                Utils.handleLoadingContainer(mManageApplications.mLoadingContainer,
+                        mManageApplications.mListContainer, false /* done */, false /* animate */);
+            }
+        };
+
         public ApplicationsAdapter(ApplicationsState state, ManageApplications manageApplications,
                 int filterMode) {
             mState = state;
@@ -1097,6 +1108,9 @@
 
             if (mSession.getAllApps().size() != 0
                     && mManageApplications.mListContainer.getVisibility() != View.VISIBLE) {
+                // Cancel any pending task to show the loading animation and show the list of
+                // apps directly.
+                mFgHandler.removeCallbacks(mShowLoadingContainerRunnable);
                 Utils.handleLoadingContainer(mManageApplications.mLoadingContainer,
                         mManageApplications.mListContainer, true, true);
             }
@@ -1148,10 +1162,16 @@
             }
         }
 
-        private void updateLoading() {
-            Utils.handleLoadingContainer(mManageApplications.mLoadingContainer,
-                    mManageApplications.mListContainer,
-                    mHasReceivedLoadEntries && mSession.getAllApps().size() != 0, false);
+        @VisibleForTesting
+        void updateLoading() {
+            final boolean appLoaded = mHasReceivedLoadEntries && mSession.getAllApps().size() != 0;
+            if (appLoaded) {
+                Utils.handleLoadingContainer(mManageApplications.mLoadingContainer,
+                        mManageApplications.mListContainer, true /* done */, false /* animate */);
+            } else {
+                mFgHandler.postDelayed(
+                        mShowLoadingContainerRunnable, DELAY_SHOW_LOADING_CONTAINER_THRESHOLD_MS);
+            }
         }
 
         ArrayList<ApplicationsState.AppEntry> applyPrefixFilter(CharSequence prefix,
diff --git a/src/com/android/settings/bluetooth/BluetoothPairingDetail.java b/src/com/android/settings/bluetooth/BluetoothPairingDetail.java
index 987fd3b..6545ed1 100644
--- a/src/com/android/settings/bluetooth/BluetoothPairingDetail.java
+++ b/src/com/android/settings/bluetooth/BluetoothPairingDetail.java
@@ -94,8 +94,7 @@
 
     @Override
     public int getMetricsCategory() {
-        //TODO(b/38383542): add bluetooth pairing category
-        return MetricsEvent.BLUETOOTH;
+        return MetricsEvent.BLUETOOTH_PAIRING;
     }
 
     @Override
diff --git a/src/com/android/settings/fingerprint/FingerprintSettings.java b/src/com/android/settings/fingerprint/FingerprintSettings.java
index a5595f9..89c3d78 100644
--- a/src/com/android/settings/fingerprint/FingerprintSettings.java
+++ b/src/com/android/settings/fingerprint/FingerprintSettings.java
@@ -261,14 +261,18 @@
                         mHandler.postDelayed(mFingerprintLockoutReset,
                                 LOCKOUT_DURATION);
                     }
-                    // Fall through to show message
-                default:
-                    // Activity can be null on a screen rotation.
-                    final Activity activity = getActivity();
-                    if (activity != null) {
-                        Toast.makeText(activity, msg , Toast.LENGTH_SHORT);
-                    }
-                break;
+                    break;
+                case FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT:
+                    mInFingerprintLockout = true;
+                    break;
+            }
+
+            if (mInFingerprintLockout) {
+                // Activity can be null on a screen rotation.
+                final Activity activity = getActivity();
+                if (activity != null) {
+                    Toast.makeText(activity, msg , Toast.LENGTH_SHORT).show();
+                }
             }
             retryFingerprint(); // start again
         }
diff --git a/src/com/android/settings/fuelgauge/BatteryUtils.java b/src/com/android/settings/fuelgauge/BatteryUtils.java
index 0aad7ba..64039ef 100644
--- a/src/com/android/settings/fuelgauge/BatteryUtils.java
+++ b/src/com/android/settings/fuelgauge/BatteryUtils.java
@@ -21,6 +21,7 @@
 import android.os.SystemClock;
 import android.support.annotation.IntDef;
 import android.support.annotation.Nullable;
+import android.support.annotation.StringRes;
 import android.support.annotation.VisibleForTesting;
 import android.text.format.DateUtils;
 import android.util.Log;
@@ -29,6 +30,8 @@
 import com.android.internal.os.BatterySipper;
 import com.android.internal.os.BatteryStatsHelper;
 import com.android.internal.util.ArrayUtils;
+import com.android.settings.R;
+import com.android.settings.fuelgauge.anomaly.Anomaly;
 import com.android.settings.overlay.FeatureFactory;
 
 import java.lang.annotation.Retention;
@@ -138,8 +141,10 @@
                 sippers.remove(i);
                 if (sipper.drainType != BatterySipper.DrainType.OVERCOUNTED
                         && sipper.drainType != BatterySipper.DrainType.SCREEN
-                        && sipper.drainType != BatterySipper.DrainType.UNACCOUNTED) {
-                    // Don't add it if it is overcounted, unaccounted or screen
+                        && sipper.drainType != BatterySipper.DrainType.UNACCOUNTED
+                        && sipper.drainType != BatterySipper.DrainType.BLUETOOTH
+                        && sipper.drainType != BatterySipper.DrainType.WIFI) {
+                    // Don't add it if it is overcounted, unaccounted, wifi, bluetooth, or screen
                     proportionalSmearPowerMah += sipper.totalPowerMah;
                 }
             }
@@ -193,6 +198,8 @@
                 || drainType == BatterySipper.DrainType.SCREEN
                 || drainType == BatterySipper.DrainType.UNACCOUNTED
                 || drainType == BatterySipper.DrainType.OVERCOUNTED
+                || drainType == BatterySipper.DrainType.BLUETOOTH
+                || drainType == BatterySipper.DrainType.WIFI
                 || (sipper.totalPowerMah * SECONDS_IN_HOUR) < MIN_POWER_THRESHOLD_MILLI_AMP
                 || mPowerUsageFeatureProvider.isTypeService(sipper)
                 || mPowerUsageFeatureProvider.isTypeSystem(sipper);
@@ -291,6 +298,20 @@
         }
     }
 
+    @StringRes
+    public int getSummaryResIdFromAnomalyType(@Anomaly.AnomalyType int type) {
+        switch (type) {
+            case Anomaly.AnomalyType.WAKE_LOCK:
+                return R.string.battery_abnormal_wakelock_summary;
+            case Anomaly.AnomalyType.WAKEUP_ALARM:
+                return R.string.battery_abnormal_wakeup_alarm_summary;
+            case Anomaly.AnomalyType.BLUETOOTH_SCAN:
+                return R.string.battery_abnormal_location_summary;
+            default:
+                throw new IllegalArgumentException("Incorrect anomaly type: " + type);
+        }
+    }
+
     public long convertUsToMs(long timeUs) {
         return timeUs / 1000;
     }
diff --git a/src/com/android/settings/fuelgauge/PowerGaugePreference.java b/src/com/android/settings/fuelgauge/PowerGaugePreference.java
index ef0bf29..322c908 100644
--- a/src/com/android/settings/fuelgauge/PowerGaugePreference.java
+++ b/src/com/android/settings/fuelgauge/PowerGaugePreference.java
@@ -25,6 +25,7 @@
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
+
 import com.android.settings.R;
 import com.android.settings.TintablePreference;
 import com.android.settings.Utils;
@@ -96,6 +97,10 @@
         notifyChanged();
     }
 
+    public boolean showAnomalyIcon() {
+        return mShowAnomalyIcon;
+    }
+
     BatteryEntry getInfo() {
         return mInfo;
     }
diff --git a/src/com/android/settings/fuelgauge/PowerUsageAnomalyDetails.java b/src/com/android/settings/fuelgauge/PowerUsageAnomalyDetails.java
index 085b88f..59662b1 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageAnomalyDetails.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageAnomalyDetails.java
@@ -57,6 +57,8 @@
     PreferenceGroup mAbnormalListGroup;
     @VisibleForTesting
     PackageManager mPackageManager;
+    @VisibleForTesting
+    BatteryUtils mBatteryUtils;
     IconDrawableFactory mIconDrawableFactory;
 
     public static void startBatteryAbnormalPage(SettingsActivity caller,
@@ -78,6 +80,7 @@
         mAbnormalListGroup = (PreferenceGroup) findPreference(KEY_PREF_ANOMALY_LIST);
         mPackageManager = context.getPackageManager();
         mIconDrawableFactory = IconDrawableFactory.newInstance(context, false /* EmbedShadow */);
+        mBatteryUtils = BatteryUtils.getInstance(context);
     }
 
     @Override
@@ -126,16 +129,16 @@
     }
 
     void refreshUi() {
-        //TODO(b/37681665): cache the preference so we don't need to create new one every time.
         mAbnormalListGroup.removeAll();
         for (int i = 0, size = mAnomalies.size(); i < size; i++) {
             final Anomaly anomaly = mAnomalies.get(i);
             Preference pref = new AnomalyPreference(getPrefContext(), anomaly);
-
+            pref.setSummary(mBatteryUtils.getSummaryResIdFromAnomalyType(anomaly.type));
             Drawable icon = getIconFromPackageName(anomaly.packageName);
             if (icon != null) {
                 pref.setIcon(icon);
             }
+
             mAbnormalListGroup.addPreference(pref);
         }
     }
diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java
index 0149e22..707246a 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java
@@ -129,8 +129,9 @@
      */
     @VisibleForTesting
     SparseArray<List<Anomaly>> mAnomalySparseArray;
+    @VisibleForTesting
+    PreferenceGroup mAppListGroup;
     private BatteryHeaderPreferenceController mBatteryHeaderPreferenceController;
-    private PreferenceGroup mAppListGroup;
     private AnomalySummaryPreferenceController mAnomalySummaryPreferenceController;
     private int mStatsType = BatteryStats.STATS_SINCE_CHARGED;
 
@@ -148,7 +149,7 @@
                     mAnomalySummaryPreferenceController.updateAnomalySummaryPreference(data);
 
                     updateAnomalySparseArray(data);
-                    refreshAppListGroup();
+                    refreshAnomalyIcon();
                 }
 
                 @Override
@@ -619,7 +620,7 @@
                 pref.setTitle(entry.getLabel());
                 pref.setOrder(i + 1);
                 pref.setPercent(percentOfTotal);
-                pref.shouldShowAnomalyIcon(mAnomalySparseArray.get(sipper.getUid()) != null);
+                pref.shouldShowAnomalyIcon(false);
                 if (sipper.usageTimeMs == 0 && sipper.drainType == DrainType.APP) {
                     sipper.usageTimeMs = mBatteryUtils.getProcessTimeMs(
                             BatteryUtils.StatusType.FOREGROUND, sipper.uidObj, mStatsType);
@@ -647,6 +648,18 @@
     }
 
     @VisibleForTesting
+    void refreshAnomalyIcon() {
+        for (int i = 0, size = mAnomalySparseArray.size(); i < size; i++) {
+            final String key = extractKeyFromUid(mAnomalySparseArray.keyAt(i));
+            final PowerGaugePreference pref = (PowerGaugePreference) mAppListGroup.findPreference(
+                    key);
+            if (pref != null) {
+                pref.shouldShowAnomalyIcon(true);
+            }
+        }
+    }
+
+    @VisibleForTesting
     void initAnomalyDetectionIfPossible() {
         if (mPowerFeatureProvider.isAnomalyDetectionEnabled()) {
             getLoaderManager().initLoader(ANOMALY_LOADER, Bundle.EMPTY, mAnomalyLoaderCallbacks);
@@ -714,7 +727,7 @@
     @VisibleForTesting
     String extractKeyFromSipper(BatterySipper sipper) {
         if (sipper.uidObj != null) {
-            return Integer.toString(sipper.getUid());
+            return extractKeyFromUid(sipper.getUid());
         } else if (sipper.drainType != DrainType.APP) {
             return sipper.drainType.toString();
         } else if (sipper.getPackages() != null) {
@@ -726,6 +739,11 @@
     }
 
     @VisibleForTesting
+    String extractKeyFromUid(int uid) {
+        return Integer.toString(uid);
+    }
+
+    @VisibleForTesting
     void setBatteryLayoutPreference(LayoutPreference layoutPreference) {
         mBatteryLayoutPref = layoutPreference;
     }
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/AnomalyPreference.java b/src/com/android/settings/fuelgauge/anomaly/AnomalyPreference.java
index d874494..9884fbd 100644
--- a/src/com/android/settings/fuelgauge/anomaly/AnomalyPreference.java
+++ b/src/com/android/settings/fuelgauge/anomaly/AnomalyPreference.java
@@ -14,7 +14,6 @@
     public AnomalyPreference(Context context, Anomaly anomaly) {
         super(context);
         mAnomaly = anomaly;
-        setLayoutResource(R.layout.preference_app);
 
         if (anomaly != null) {
             setTitle(anomaly.displayName);
diff --git a/src/com/android/settings/fuelgauge/anomaly/AnomalySummaryPreferenceController.java b/src/com/android/settings/fuelgauge/anomaly/AnomalySummaryPreferenceController.java
index 47c2d2b..59c8b2c 100644
--- a/src/com/android/settings/fuelgauge/anomaly/AnomalySummaryPreferenceController.java
+++ b/src/com/android/settings/fuelgauge/anomaly/AnomalySummaryPreferenceController.java
@@ -23,6 +23,7 @@
 
 import com.android.settings.R;
 import com.android.settings.SettingsActivity;
+import com.android.settings.fuelgauge.BatteryUtils;
 import com.android.settings.fuelgauge.PowerUsageAnomalyDetails;
 
 import java.util.List;
@@ -41,6 +42,8 @@
     Preference mAnomalyPreference;
     @VisibleForTesting
     List<Anomaly> mAnomalies;
+    @VisibleForTesting
+    BatteryUtils mBatteryUtils;
     private SettingsActivity mSettingsActivity;
 
     /**
@@ -56,6 +59,7 @@
         mSettingsActivity = activity;
         mAnomalyPreference = mFragment.getPreferenceScreen().findPreference(ANOMALY_KEY);
         mMetricsKey = metricsKey;
+        mBatteryUtils = BatteryUtils.getInstance(activity.getApplicationContext());
         hideHighUsagePreference();
     }
 
@@ -89,10 +93,14 @@
         if (!mAnomalies.isEmpty()) {
             mAnomalyPreference.setVisible(true);
             final int count = mAnomalies.size();
-            final String summary = context.getResources().getQuantityString(
-                    R.plurals.power_high_usage_summary, count,
-                    mAnomalies.get(0).displayName, count);
+            final String title = context.getResources().getQuantityString(
+                    R.plurals.power_high_usage_title, count, mAnomalies.get(0).displayName);
+            final String summary = count > 1 ?
+                    context.getString(R.string.battery_abnormal_apps_summary, count)
+                    : context.getString(
+                            mBatteryUtils.getSummaryResIdFromAnomalyType(mAnomalies.get(0).type));
 
+            mAnomalyPreference.setTitle(title);
             mAnomalyPreference.setSummary(summary);
         }
     }
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/src/com/android/settings/widget/EntityHeaderController.java b/src/com/android/settings/widget/EntityHeaderController.java
index c9e0805..8f64920 100644
--- a/src/com/android/settings/widget/EntityHeaderController.java
+++ b/src/com/android/settings/widget/EntityHeaderController.java
@@ -52,6 +52,10 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent
+        .ACTION_OPEN_APP_NOTIFICATION_SETTING;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_OPEN_APP_SETTING;
+
 public class EntityHeaderController {
 
     @IntDef({ActionType.ACTION_NONE,
@@ -310,6 +314,9 @@
                     button.setOnClickListener(new View.OnClickListener() {
                         @Override
                         public void onClick(View v) {
+                            FeatureFactory.getFactory(mAppContext).getMetricsFeatureProvider()
+                                    .actionWithSource(mAppContext, mMetricsCategory,
+                                            ACTION_OPEN_APP_NOTIFICATION_SETTING);
                             mFragment.startActivity(mAppNotifPrefIntent);
                         }
                     });
@@ -327,6 +334,9 @@
                 button.setOnClickListener(new View.OnClickListener() {
                     @Override
                     public void onClick(View v) {
+                        FeatureFactory.getFactory(mAppContext).getMetricsFeatureProvider()
+                                .actionWithSource(mAppContext, mMetricsCategory,
+                                        ACTION_OPEN_APP_SETTING);
                         mFragment.startActivity(intent);
                     }
                 });
diff --git a/tests/robotests/src/com/android/settings/SetupWizardUtilsTest.java b/tests/robotests/src/com/android/settings/SetupWizardUtilsTest.java
index 115bc8a..21061c1 100644
--- a/tests/robotests/src/com/android/settings/SetupWizardUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/SetupWizardUtilsTest.java
@@ -16,21 +16,35 @@
 
 package com.android.settings;
 
+import static com.android.settings.testutils.ResIdSubject.assertResId;
+
+import static com.google.common.truth.Truth.assertThat;
+
 import android.content.Intent;
 
+import com.android.settings.testutils.shadow.SettingsShadowSystemProperties;
 import com.android.settings.testutils.SettingsRobolectricTestRunner;
 import com.android.setupwizardlib.util.WizardManagerHelper;
 
+import org.junit.After;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.annotation.Config;
 
-import static com.google.common.truth.Truth.assertThat;
-
 @RunWith(SettingsRobolectricTestRunner.class)
-@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+@Config(
+        manifest = TestConfig.MANIFEST_PATH,
+        sdk = TestConfig.SDK_VERSION,
+        shadows = {
+                SettingsShadowSystemProperties.class
+        })
 public class SetupWizardUtilsTest {
 
+    @After
+    public void tearDown() {
+        SettingsShadowSystemProperties.clear();
+    }
+
     @Test
     public void testCopySetupExtras() throws Throwable {
         Intent fromIntent = new Intent();
@@ -44,4 +58,24 @@
         assertThat(toIntent.getBooleanExtra(WizardManagerHelper.EXTRA_USE_IMMERSIVE_MODE, false))
                 .isTrue();
     }
+
+    @Test
+    public void testGetTheme_withIntentExtra_shouldReturnExtraTheme() {
+        SettingsShadowSystemProperties.set(SetupWizardUtils.SYSTEM_PROP_SETUPWIZARD_THEME,
+                WizardManagerHelper.THEME_GLIF);
+        Intent intent = new Intent();
+        intent.putExtra(WizardManagerHelper.EXTRA_THEME, WizardManagerHelper.THEME_GLIF_V2);
+
+        assertResId(SetupWizardUtils.getTheme(intent)).isEqualTo(R.style.GlifV2Theme);
+    }
+
+    @Test
+    public void testGetTheme_withEmptyIntent_shouldReturnSystemProperty() {
+        SettingsShadowSystemProperties.set(SetupWizardUtils.SYSTEM_PROP_SETUPWIZARD_THEME,
+                WizardManagerHelper.THEME_GLIF_V2_LIGHT);
+        Intent intent = new Intent();
+
+        assertResId(SetupWizardUtils.getTheme(intent)).isEqualTo(R.style.GlifV2Theme_Light);
+    }
+
 }
diff --git a/tests/robotests/src/com/android/settings/applications/ManageApplicationsTest.java b/tests/robotests/src/com/android/settings/applications/ManageApplicationsTest.java
index ea1d2c3..52c1069 100644
--- a/tests/robotests/src/com/android/settings/applications/ManageApplicationsTest.java
+++ b/tests/robotests/src/com/android/settings/applications/ManageApplicationsTest.java
@@ -17,29 +17,38 @@
 package com.android.settings.applications;
 
 import android.app.Activity;
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.os.Handler;
 import android.os.Looper;
+import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
 import android.widget.TextView;
 
 import com.android.settings.R;
 import com.android.settings.Settings;
-import com.android.settings.testutils.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
 import com.android.settings.testutils.shadow.SettingsShadowResources;
 import com.android.settings.testutils.shadow.SettingsShadowResources.SettingsShadowTheme;
 import com.android.settings.testutils.shadow.ShadowDynamicIndexableContentMonitor;
 import com.android.settings.testutils.shadow.ShadowEventLogWriter;
 import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.applications.ApplicationsState.Callbacks;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 
+import java.util.ArrayList;
+
 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.fakes.RoboMenuItem;
 import org.robolectric.util.ReflectionHelpers;
@@ -47,7 +56,12 @@
 import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -132,6 +146,107 @@
         assertThat(mMenu.findItem(R.id.reset_app_preferences).isVisible()).isFalse();
     }
 
+    @Test
+    public void onCreateView_shouldNotShowLoadingContainer() {
+        final ManageApplications fragment = spy(new ManageApplications());
+        ReflectionHelpers.setField(fragment, "mResetAppsHelper",
+                mock(ResetAppsHelper.class));
+        doNothing().when(fragment).createHeader();
+
+        final LayoutInflater layoutInflater = mock(LayoutInflater.class);
+        final View view = mock(View.class);
+        final View loadingContainer = mock(View.class);
+        when(layoutInflater.inflate(anyInt(), eq(null))).thenReturn(view);
+        when(view.findViewById(R.id.loading_container)).thenReturn(loadingContainer);
+
+        fragment.onCreateView(layoutInflater, mock(ViewGroup.class), null);
+
+        verify(loadingContainer, never()).setVisibility(View.VISIBLE);
+    }
+
+    @Test
+    public void updateLoading_appLoaded_shouldNotDelayCallToHandleLoadingContainer() {
+        final ManageApplications fragment = mock(ManageApplications.class);
+        ReflectionHelpers.setField(fragment, "mLoadingContainer", mock(View.class));
+        ReflectionHelpers.setField(fragment, "mListContainer", mock(View.class));
+        when(fragment.getActivity()).thenReturn(mock(Activity.class));
+        final Runnable showLoadingContainerRunnable = mock(Runnable.class);
+        final Handler handler = mock(Handler.class);
+        final ManageApplications.ApplicationsAdapter adapter =
+            spy(new ManageApplications.ApplicationsAdapter(mState, fragment, 0));
+        ReflectionHelpers.setField(adapter, "mShowLoadingContainerRunnable",
+            showLoadingContainerRunnable);
+        ReflectionHelpers.setField(adapter, "mFgHandler", handler);
+
+        // app loading completed
+        ReflectionHelpers.setField(adapter, "mHasReceivedLoadEntries", true);
+        final ArrayList<ApplicationsState.AppEntry> appList = new ArrayList<>();
+        appList.add(mock(ApplicationsState.AppEntry.class));
+        when(mSession.getAllApps()).thenReturn(appList);
+
+        adapter.updateLoading();
+
+        verify(handler, never()).postDelayed(eq(showLoadingContainerRunnable), anyLong());
+    }
+
+    @Test
+    public void updateLoading_appNotLoaded_shouldDelayCallToHandleLoadingContainer() {
+        final ManageApplications fragment = mock(ManageApplications.class);
+        ReflectionHelpers.setField(fragment, "mLoadingContainer", mock(View.class));
+        ReflectionHelpers.setField(fragment, "mListContainer", mock(View.class));
+        when(fragment.getActivity()).thenReturn(mock(Activity.class));
+        final Runnable showLoadingContainerRunnable = mock(Runnable.class);
+        final Handler handler = mock(Handler.class);
+        final ManageApplications.ApplicationsAdapter adapter =
+            spy(new ManageApplications.ApplicationsAdapter(mState, fragment, 0));
+        ReflectionHelpers.setField(adapter, "mShowLoadingContainerRunnable",
+            showLoadingContainerRunnable);
+        ReflectionHelpers.setField(adapter, "mFgHandler", handler);
+
+        // app loading not yet completed
+        ReflectionHelpers.setField(adapter, "mHasReceivedLoadEntries", false);
+
+        adapter.updateLoading();
+
+        verify(handler).postDelayed(eq(showLoadingContainerRunnable), anyLong());
+    }
+
+    @Test
+    public void onRebuildComplete_shouldCancelDelayedRunnable() {
+        final Context context = RuntimeEnvironment.application;
+        final ManageApplications fragment = mock(ManageApplications.class);
+        final View loadingContainer = mock(View.class);
+        when(loadingContainer.getContext()).thenReturn(context);
+        final View listContainer = mock(View.class);
+        when(listContainer.getVisibility()).thenReturn(View.INVISIBLE);
+        when(listContainer.getContext()).thenReturn(context);
+        ReflectionHelpers.setField(fragment, "mLoadingContainer", loadingContainer);
+        ReflectionHelpers.setField(fragment, "mListContainer", listContainer);
+        when(fragment.getActivity()).thenReturn(mock(Activity.class));
+        final Runnable showLoadingContainerRunnable = mock(Runnable.class);
+        final Handler handler = mock(Handler.class);
+        final ManageApplications.ApplicationsAdapter adapter =
+            spy(new ManageApplications.ApplicationsAdapter(mState, fragment, 0));
+        ReflectionHelpers.setField(adapter, "mShowLoadingContainerRunnable",
+            showLoadingContainerRunnable);
+        ReflectionHelpers.setField(adapter, "mFgHandler", handler);
+        ReflectionHelpers.setField(adapter, "mFilterMode", -1);
+
+        // app loading not yet completed
+        ReflectionHelpers.setField(adapter, "mHasReceivedLoadEntries", false);
+        adapter.updateLoading();
+
+        // app loading completed
+        ReflectionHelpers.setField(adapter, "mHasReceivedLoadEntries", true);
+        final ArrayList<ApplicationsState.AppEntry> appList = new ArrayList<>();
+        appList.add(mock(ApplicationsState.AppEntry.class));
+        when(mSession.getAllApps()).thenReturn(appList);
+
+        adapter.onRebuildComplete(null);
+
+        verify(handler).removeCallbacks(showLoadingContainerRunnable);
+    }
+
     private void setUpOptionMenus() {
         when(mMenu.findItem(anyInt())).thenAnswer(invocation -> {
             final Object[] args = invocation.getArguments();
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java
index 053cc98..d23301d 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java
@@ -22,6 +22,8 @@
 
 import com.android.internal.os.BatterySipper;
 import com.android.internal.os.BatteryStatsHelper;
+import com.android.settings.R;
+import com.android.settings.fuelgauge.anomaly.Anomaly;
 import com.android.settings.testutils.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
 import com.android.settings.testutils.FakeFeatureFactory;
@@ -82,6 +84,8 @@
     private static final double BATTERY_OVERACCOUNTED_USAGE = 500;
     private static final double BATTERY_UNACCOUNTED_USAGE = 700;
     private static final double BATTERY_APP_USAGE = 100;
+    private static final double BATTERY_WIFI_USAGE = 200;
+    private static final double BATTERY_BLUETOOTH_USAGE = 300;
     private static final double TOTAL_BATTERY_USAGE = 1000;
     private static final double HIDDEN_USAGE = 200;
     private static final int DISCHARGE_AMOUNT = 80;
@@ -93,6 +97,10 @@
     @Mock
     private BatterySipper mNormalBatterySipper;
     @Mock
+    private BatterySipper mWifiBatterySipper;
+    @Mock
+    private BatterySipper mBluetoothBatterySipper;
+    @Mock
     private BatterySipper mScreenBatterySipper;
     @Mock
     private BatterySipper mOvercountedBatterySipper;
@@ -134,6 +142,12 @@
         mNormalBatterySipper.drainType = BatterySipper.DrainType.APP;
         mNormalBatterySipper.totalPowerMah = TOTAL_BATTERY_USAGE;
 
+        mWifiBatterySipper.drainType = BatterySipper.DrainType.WIFI;
+        mWifiBatterySipper.totalPowerMah = BATTERY_WIFI_USAGE;
+
+        mBluetoothBatterySipper.drainType = BatterySipper.DrainType.BLUETOOTH;
+        mBluetoothBatterySipper.totalPowerMah = BATTERY_BLUETOOTH_USAGE;
+
         mScreenBatterySipper.drainType = BatterySipper.DrainType.SCREEN;
         mScreenBatterySipper.totalPowerMah = BATTERY_SCREEN_USAGE;
 
@@ -193,6 +207,8 @@
         sippers.add(mSystemBatterySipper);
         sippers.add(mOvercountedBatterySipper);
         sippers.add(mUnaccountedBatterySipper);
+        sippers.add(mWifiBatterySipper);
+        sippers.add(mBluetoothBatterySipper);
         when(mProvider.isTypeSystem(mSystemBatterySipper))
                 .thenReturn(true);
         doNothing().when(mBatteryUtils).smearScreenBatterySipper(any(), any());
@@ -234,6 +250,18 @@
     }
 
     @Test
+    public void testShouldHideSipper_TypeWifi_ReturnTrue() {
+        mNormalBatterySipper.drainType = BatterySipper.DrainType.WIFI;
+        assertThat(mBatteryUtils.shouldHideSipper(mNormalBatterySipper)).isTrue();
+    }
+
+    @Test
+    public void testShouldHideSipper_TypeBluetooth_ReturnTrue() {
+        mNormalBatterySipper.drainType = BatterySipper.DrainType.BLUETOOTH;
+        assertThat(mBatteryUtils.shouldHideSipper(mNormalBatterySipper)).isTrue();
+    }
+
+    @Test
     public void testShouldHideSipper_TypeSystem_ReturnTrue() {
         mNormalBatterySipper.drainType = BatterySipper.DrainType.APP;
         when(mNormalBatterySipper.getUid()).thenReturn(Process.ROOT_UID);
@@ -315,6 +343,16 @@
                 mBatteryStatsHelper, currentTimeMs)).isEqualTo(TIME_SINCE_LAST_FULL_CHARGE_MS);
     }
 
+    @Test
+    public void testGetSummaryResIdFromAnomalyType() {
+        assertThat(mBatteryUtils.getSummaryResIdFromAnomalyType(Anomaly.AnomalyType.WAKE_LOCK))
+                .isEqualTo(R.string.battery_abnormal_wakelock_summary);
+        assertThat(mBatteryUtils.getSummaryResIdFromAnomalyType(Anomaly.AnomalyType.WAKEUP_ALARM))
+                .isEqualTo(R.string.battery_abnormal_wakeup_alarm_summary);
+        assertThat(mBatteryUtils.getSummaryResIdFromAnomalyType(Anomaly.AnomalyType.BLUETOOTH_SCAN))
+                .isEqualTo(R.string.battery_abnormal_location_summary);
+    }
+
     private BatterySipper createTestSmearBatterySipper(long activityTime, double totalPowerMah,
             int uidCode, boolean isUidNull) {
         final BatterySipper sipper = mock(BatterySipper.class);
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAnomalyDetailsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAnomalyDetailsTest.java
index 7845870..71aa295 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAnomalyDetailsTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageAnomalyDetailsTest.java
@@ -60,8 +60,10 @@
 public class PowerUsageAnomalyDetailsTest {
     private static final String NAME_APP_1 = "app1";
     private static final String NAME_APP_2 = "app2";
+    private static final String NAME_APP_3 = "app3";
     private static final String PACKAGE_NAME_1 = "com.android.app1";
     private static final String PACKAGE_NAME_2 = "com.android.app2";
+    private static final String PACKAGE_NAME_3 = "com.android.app3";
 
     @Mock
     private SettingsActivity mSettingsActivity;
@@ -71,6 +73,8 @@
     private Drawable mDrawable1;
     @Mock
     private Drawable mDrawable2;
+    @Mock
+    private Drawable mDrawable3;
     private Context mContext;
     private PowerUsageAnomalyDetails mFragment;
     private PreferenceGroup mAbnormalListGroup;
@@ -92,30 +96,36 @@
                 .build();
         mAnomalyList.add(anomaly1);
         Anomaly anomaly2 = new Anomaly.Builder()
-                .setType(Anomaly.AnomalyType.WAKE_LOCK)
+                .setType(Anomaly.AnomalyType.WAKEUP_ALARM)
                 .setPackageName(PACKAGE_NAME_2)
                 .setDisplayName(NAME_APP_2)
                 .build();
         mAnomalyList.add(anomaly2);
+        Anomaly anomaly3 = new Anomaly.Builder()
+                .setType(Anomaly.AnomalyType.BLUETOOTH_SCAN)
+                .setPackageName(PACKAGE_NAME_3)
+                .setDisplayName(NAME_APP_3)
+                .build();
+        mAnomalyList.add(anomaly3);
 
         mFragment = spy(new PowerUsageAnomalyDetails());
         doReturn(null).when(mFragment).getIconFromPackageName(any());
         mFragment.mAbnormalListGroup = mAbnormalListGroup;
         mFragment.mAnomalies = mAnomalyList;
+        mFragment.mBatteryUtils = new BatteryUtils(mContext);
         doReturn(mPreferenceManager).when(mFragment).getPreferenceManager();
         doReturn(mContext).when(mPreferenceManager).getContext();
     }
 
     @Test
-    public void testRefreshUi_dataCorrect() {
-        final List<Anomaly> testAnomalyList = new ArrayList<>();
+    public void testRefreshUi_displayCorrectTitleAndSummary() {
+        final List<Preference> testPreferences = new ArrayList<>();
         final ArgumentCaptor<Preference> preferenceCaptor = ArgumentCaptor.forClass(
                 Preference.class);
         Answer<Void> prefCallable = new Answer<Void>() {
             @Override
             public Void answer(InvocationOnMock invocation) throws Throwable {
-                testAnomalyList.add(
-                        ((AnomalyPreference) preferenceCaptor.getValue()).getAnomaly());
+                testPreferences.add(preferenceCaptor.getValue());
                 return null;
             }
         };
@@ -123,13 +133,22 @@
 
         mFragment.refreshUi();
 
-        assertThat(testAnomalyList).containsExactlyElementsIn(mAnomalyList);
+        final Preference wakelockPreference = testPreferences.get(0);
+        assertThat(wakelockPreference.getTitle()).isEqualTo(NAME_APP_1);
+        assertThat(wakelockPreference.getSummary()).isEqualTo("Keeping device awake");
+        final Preference wakeupPreference = testPreferences.get(1);
+        assertThat(wakeupPreference.getTitle()).isEqualTo(NAME_APP_2);
+        assertThat(wakeupPreference.getSummary()).isEqualTo("Waking up device in background");
+        final Preference bluetoothPreference = testPreferences.get(2);
+        assertThat(bluetoothPreference.getTitle()).isEqualTo(NAME_APP_3);
+        assertThat(bluetoothPreference.getSummary()).isEqualTo("Requesting location frequently");
     }
 
     @Test
     public void testRefreshUi_iconCorrect() {
         doReturn(mDrawable1).when(mFragment).getIconFromPackageName(PACKAGE_NAME_1);
         doReturn(mDrawable2).when(mFragment).getIconFromPackageName(PACKAGE_NAME_2);
+        doReturn(mDrawable3).when(mFragment).getIconFromPackageName(PACKAGE_NAME_3);
 
         final List<Drawable> testIcons = new ArrayList<>();
         final ArgumentCaptor<Preference> preferenceCaptor = ArgumentCaptor.forClass(
@@ -145,7 +164,7 @@
 
         mFragment.refreshUi();
 
-        assertThat(testIcons).containsExactly(mDrawable1, mDrawable2);
+        assertThat(testIcons).containsExactly(mDrawable1, mDrawable2, mDrawable3);
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java
index a3747e2..659b079 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java
@@ -15,16 +15,14 @@
  */
 package com.android.settings.fuelgauge;
 
-import android.view.View;
 import java.util.List;
-import org.robolectric.Robolectric;
-import org.robolectric.RuntimeEnvironment;
 import android.app.LoaderManager;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
 import android.content.ContentResolver;
 import android.os.PowerManager;
+import android.support.v7.preference.PreferenceGroup;
 import android.support.v7.preference.PreferenceScreen;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
@@ -32,6 +30,7 @@
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
+import android.view.View;
 import android.widget.TextView;
 
 import com.android.internal.logging.nano.MetricsProto;
@@ -58,6 +57,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.annotation.Config;
+import org.robolectric.Robolectric;
+import org.robolectric.RuntimeEnvironment;
 
 import java.util.ArrayList;
 
@@ -148,6 +149,8 @@
     private ContentResolver mContentResolver;
     @Mock
     private PreferenceScreen mPreferenceScreen;
+    @Mock
+    private PreferenceGroup mAppListGroup;
 
     private List<BatterySipper> mUsageList;
     private Context mRealContext;
@@ -215,6 +218,7 @@
         mFragment.mScreenUsagePref = mScreenUsagePref;
         mFragment.mLastFullChargePref = mLastFullChargePref;
         mFragment.mBatteryUtils = spy(new BatteryUtils(mRealContext));
+        mFragment.mAppListGroup = mAppListGroup;
     }
 
     @Test
@@ -493,6 +497,20 @@
         assertThat(mFragment.mShowAllApps).isTrue();
     }
 
+    @Test
+    public void testRefreshAnomalyIcon_containsAnomaly_showAnomalyIcon() {
+        PowerGaugePreference preference = new PowerGaugePreference(mRealContext);
+        final String key = mFragment.extractKeyFromUid(UID);
+        preference.setKey(key);
+        doReturn(preference).when(mAppListGroup).findPreference(key);
+        mFragment.mAnomalySparseArray = new SparseArray<>();
+        mFragment.mAnomalySparseArray.append(UID, null);
+
+        mFragment.refreshAnomalyIcon();
+
+        assertThat(preference.showAnomalyIcon()).isTrue();
+    }
+
     public static class TestFragment extends PowerUsageSummary {
 
         private Context mContext;
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/AnomalySummaryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalySummaryPreferenceControllerTest.java
index 2aa2417..72d8b69 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalySummaryPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalySummaryPreferenceControllerTest.java
@@ -32,6 +32,7 @@
 import android.support.v7.preference.Preference;
 
 import com.android.settings.SettingsActivity;
+import com.android.settings.fuelgauge.BatteryUtils;
 import com.android.settings.testutils.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
 
@@ -53,7 +54,7 @@
     @Anomaly.AnomalyType
     private static final int ANOMALY_TYPE = Anomaly.AnomalyType.WAKE_LOCK;
     private static final String PACKAGE_NAME = "com.android.app";
-    private static final String DISPLAY_NAME = "app";
+    private static final String DISPLAY_NAME = "appName";
     private static final int UID = 111;
 
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
@@ -80,6 +81,7 @@
         when(mFragment.getFragmentManager()).thenReturn(mFragmentManager);
         when(mFragmentManager.beginTransaction()).thenReturn(mFragmentTransaction);
         when(mFragment.getContext()).thenReturn(mContext);
+        when(mSettingsActivity.getApplicationContext()).thenReturn(mContext);
 
         mAnomalyList = new ArrayList<>();
 
@@ -100,7 +102,8 @@
 
         mAnomalySummaryPreferenceController.updateAnomalySummaryPreference(mAnomalyList);
 
-        assertThat(mPreference.getSummary()).isEqualTo("app behaving abnormally");
+        assertThat(mPreference.getTitle()).isEqualTo("appName draining battery");
+        assertThat(mPreference.getSummary()).isEqualTo("Keeping device awake");
     }
 
     @Test
@@ -110,7 +113,8 @@
 
         mAnomalySummaryPreferenceController.updateAnomalySummaryPreference(mAnomalyList);
 
-        assertThat(mPreference.getSummary()).isEqualTo("2 apps behaving abnormally");
+        assertThat(mPreference.getTitle()).isEqualTo("Apps draining battery");
+        assertThat(mPreference.getSummary()).isEqualTo("2 apps misbehaving");
     }
 
     @Test
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();
+    }
+
+}
diff --git a/tests/robotests/src/com/android/settings/testutils/ResIdSubject.java b/tests/robotests/src/com/android/settings/testutils/ResIdSubject.java
new file mode 100644
index 0000000..2305edb
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/testutils/ResIdSubject.java
@@ -0,0 +1,81 @@
+/*
+ * 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.testutils;
+
+import static com.google.common.truth.Truth.assertAbout;
+
+import static org.robolectric.RuntimeEnvironment.application;
+
+import android.support.annotation.Nullable;
+
+import com.google.common.truth.ComparableSubject;
+import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.SubjectFactory;
+
+/**
+ * Custom subject for use with {@link com.google.common.truth.Truth}, to provide a more readable
+ * error message, so that instead of "Not true that 2130706432 equals to 17170444", it will say
+ * "Not true that color/my_color equals to android:color/black".
+ *
+ * <p>Usage:
+ * <pre>{@code
+ *     ResIdSubject.assertResId(activity.getThemeResId()).isEqualTo(android.R.style.Theme_Material)
+ * }</pre>
+ */
+public class ResIdSubject extends ComparableSubject<ResIdSubject, Integer> {
+
+    public static final SubjectFactory<ResIdSubject, Integer> FACTORY =
+            new SubjectFactory<ResIdSubject, Integer>() {
+                @Override
+                public ResIdSubject getSubject(
+                        FailureStrategy failureStrategy, Integer integer) {
+                    return new ResIdSubject(failureStrategy, integer);
+                }
+            };
+
+    public static ResIdSubject assertResId(int resId) {
+        return assertAbout(ResIdSubject.FACTORY).that(resId);
+    }
+
+    public ResIdSubject(
+            FailureStrategy failureStrategy,
+            @Nullable Integer integer) {
+        super(failureStrategy, integer);
+    }
+
+    public void isEqualTo(int other) {
+        Integer subject = getSubject();
+        if (subject == null || subject != other) {
+            fail("equals to", resIdToString(other));
+        }
+    }
+
+    @Override
+    protected String getDisplaySubject() {
+        String resourceName = "<" + resIdToString(getSubject()) + ">";
+        String customName = internalCustomName();
+        if (customName != null) {
+            return customName + " " + resourceName;
+        } else {
+            return resourceName;
+        }
+    }
+
+    private static String resIdToString(int resId) {
+        return application.getResources().getResourceName(resId);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/widget/EntityHeaderControllerTest.java b/tests/robotests/src/com/android/settings/widget/EntityHeaderControllerTest.java
index 49d5e7d..4294dcf 100644
--- a/tests/robotests/src/com/android/settings/widget/EntityHeaderControllerTest.java
+++ b/tests/robotests/src/com/android/settings/widget/EntityHeaderControllerTest.java
@@ -32,11 +32,12 @@
 import android.view.View;
 import android.widget.TextView;
 
+import com.android.internal.logging.nano.MetricsProto;
 import com.android.settings.R;
-import com.android.settings.testutils.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
 import com.android.settings.applications.LayoutPreference;
 import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -70,11 +71,12 @@
     private LayoutInflater mLayoutInflater;
     private PackageInfo mInfo;
     private EntityHeaderController mController;
+    private FakeFeatureFactory mFeatureFactory;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        FakeFeatureFactory.setupForTest(mContext);
+        mFeatureFactory = FakeFeatureFactory.setupForTest(mContext);
         mShadowContext = RuntimeEnvironment.application;
         when(mActivity.getApplicationContext()).thenReturn(mShadowContext);
         when(mContext.getApplicationContext()).thenReturn(mContext);
@@ -154,6 +156,9 @@
         } catch (Exception e) {
             // Ignore exception because the launching intent is fake.
         }
+        verify(mFeatureFactory.metricsFeatureProvider).actionWithSource(mContext,
+                MetricsProto.MetricsEvent.VIEW_UNKNOWN,
+                MetricsProto.MetricsEvent.ACTION_OPEN_APP_SETTING);
         verify(mFragment).startActivity(any(Intent.class));
     }