Merge "Keep candidates in radio button picker UI ordered"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index ec7183a..f15759e 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -8598,8 +8598,8 @@
     <!-- Preference menu title for accessing the deletion helper from the storage manager settings. [CHAR LIMIT=30]-->
     <string name="deletion_helper_preference_title">Free up space now</string>
 
-    <!-- Preference title for gesture settings [CHAR LIMIT=40]-->
-    <string name="gesture_preference_title">Gesture shortcuts</string>
+    <!-- Preference title for gesture settings [CHAR LIMIT=40 BACKUP_MESSAGE_ID:5280023307132819052]-->
+    <string name="gesture_preference_title">Gesture</string>
 
     <!-- Preference summary for gesture settings (phone) [CHAR LIMIT=NONE]-->
     <string name="gesture_preference_summary" product="default">Quick gestures to control your phone</string>
diff --git a/res/xml/bluetooth_settings.xml b/res/xml/bluetooth_settings.xml
index 046295b..1ab2b16 100644
--- a/res/xml/bluetooth_settings.xml
+++ b/res/xml/bluetooth_settings.xml
@@ -16,6 +16,7 @@
 
 <PreferenceScreen
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
     android:title="@string/bluetooth_settings">
 
     <Preference
@@ -25,6 +26,12 @@
         android:key="paired_devices"
         android:title="@string/bluetooth_paired_device_title"/>
 
+    <com.android.settings.DividerPreference
+        android:key="bt_received_files"
+        android:title="@string/bluetooth_show_received_files"
+        settings:allowDividerAbove="true"
+        settings:allowDividerBelow="true"/>
+
     <com.android.settingslib.widget.FooterPreference/>
 
 </PreferenceScreen>
diff --git a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java
index 757d67d..033d81b 100644
--- a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java
@@ -107,6 +107,8 @@
                     videoView.setLayoutParams(videoLp);
                     videoView.invalidate();
                     videoView.start();
+                    mVideoBackgroundView.getViewTreeObserver()
+                            .removeOnGlobalLayoutListener(mLayoutListener);
                 }
             };
 
diff --git a/src/com/android/settings/applications/defaultapps/DefaultNotificationAssistantPicker.java b/src/com/android/settings/applications/defaultapps/DefaultNotificationAssistantPicker.java
deleted file mode 100644
index 8ffcb68..0000000
--- a/src/com/android/settings/applications/defaultapps/DefaultNotificationAssistantPicker.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * 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.applications.defaultapps;
-
-import android.app.ActivityManager;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.provider.Settings;
-import android.service.notification.NotificationAssistantService;
-import android.util.Slog;
-
-import com.android.settings.R;
-import com.android.settings.utils.ManagedServiceSettings;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class DefaultNotificationAssistantPicker extends DefaultAppPickerFragment {
-    private static final String TAG = "DefaultNotiAssist";
-
-    private final ManagedServiceSettings.Config mConfig = getConfig();
-
-    @Override
-    public int getMetricsCategory() {
-        return 0;
-    }
-
-    @Override
-    protected String getDefaultKey() {
-        return Settings.Secure.getString(getContext().getContentResolver(), mConfig.setting);
-    }
-
-    @Override
-    protected boolean setDefaultKey(String value) {
-        Settings.Secure.putString(getContext().getContentResolver(), mConfig.setting, value);
-        return true;
-    }
-
-    @Override
-    protected List<DefaultAppInfo> getCandidates() {
-        List<DefaultAppInfo> candidates = new ArrayList<>();
-        final int user = ActivityManager.getCurrentUser();
-
-        List<ResolveInfo> installedServices = mPm.queryIntentServicesAsUser(
-                new Intent(mConfig.intentAction),
-                PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
-                user);
-
-        for (int i = 0, count = installedServices.size(); i < count; i++) {
-            ResolveInfo resolveInfo = installedServices.get(i);
-            ServiceInfo info = resolveInfo.serviceInfo;
-
-            if (!mConfig.permission.equals(info.permission)) {
-                Slog.w(mConfig.tag, "Skipping " + mConfig.noun + " service "
-                        + info.packageName + "/" + info.name
-                        + ": it does not require the permission "
-                        + mConfig.permission);
-                continue;
-            }
-
-            candidates.add(new DefaultAppInfo(mPm,
-                    mUserId, new ComponentName(info.packageName, info.name)));
-        }
-        return candidates;
-    }
-
-    @Override
-    protected boolean shouldShowItemNone() {
-        return true;
-    }
-
-    private ManagedServiceSettings.Config getConfig() {
-        final ManagedServiceSettings.Config c = new ManagedServiceSettings.Config();
-        c.tag = TAG;
-        c.setting = Settings.Secure.ENABLED_NOTIFICATION_ASSISTANT;
-        c.intentAction = NotificationAssistantService.SERVICE_INTERFACE;
-        c.permission = android.Manifest.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE;
-        c.noun = "notification assistant";
-        c.warningDialogTitle = R.string.notification_listener_security_warning_title;
-        c.warningDialogSummary = R.string.notification_listener_security_warning_summary;
-        c.emptyText = R.string.no_notification_listeners;
-        return c;
-    }
-}
diff --git a/src/com/android/settings/bluetooth/BluetoothFilesPreferenceController.java b/src/com/android/settings/bluetooth/BluetoothFilesPreferenceController.java
new file mode 100644
index 0000000..c425cdc
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothFilesPreferenceController.java
@@ -0,0 +1,79 @@
+/*
+ * 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.bluetooth;
+
+import android.content.Context;
+import android.content.Intent;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.preference.Preference;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.core.PreferenceController;
+import com.android.settings.core.instrumentation.MetricsFeatureProvider;
+import com.android.settings.overlay.FeatureFactory;
+
+/**
+ * Controller that shows received files
+ */
+public class BluetoothFilesPreferenceController extends PreferenceController {
+    private static final String TAG = "BluetoothFilesPrefCtrl";
+
+    public static final String KEY_RECEIVED_FILES = "bt_received_files";
+
+    /* Private intent to show the list of received files */
+    @VisibleForTesting
+    static final String ACTION_OPEN_FILES = "com.android.bluetooth.action.TransferHistory";
+    @VisibleForTesting
+    static final String EXTRA_SHOW_ALL_FILES = "android.btopp.intent.extra.SHOW_ALL";
+    @VisibleForTesting
+    static final String EXTRA_DIRECTION = "direction";
+
+    private MetricsFeatureProvider mMetricsFeatureProvider;
+
+    public BluetoothFilesPreferenceController(Context context) {
+        super(context);
+        mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_RECEIVED_FILES;
+    }
+
+    @Override
+    public boolean handlePreferenceTreeClick(Preference preference) {
+        if (KEY_RECEIVED_FILES.equals(preference.getKey())) {
+            mMetricsFeatureProvider.action(mContext,
+                    MetricsProto.MetricsEvent.ACTION_BLUETOOTH_FILES);
+            Intent intent = new Intent(ACTION_OPEN_FILES);
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+            intent.putExtra(EXTRA_DIRECTION, 1 /* DIRECTION_INBOUND */);
+            intent.putExtra(EXTRA_SHOW_ALL_FILES, true);
+            mContext.startActivity(intent);
+            return true;
+        }
+
+        return false;
+    }
+
+
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothSettings.java b/src/com/android/settings/bluetooth/BluetoothSettings.java
index f8648ef..4703031 100644
--- a/src/com/android/settings/bluetooth/BluetoothSettings.java
+++ b/src/com/android/settings/bluetooth/BluetoothSettings.java
@@ -22,7 +22,6 @@
 import android.bluetooth.BluetoothDevice;
 import android.content.ContentResolver;
 import android.content.Context;
-import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.res.Resources;
 import android.os.Bundle;
@@ -31,13 +30,9 @@
 import android.support.annotation.VisibleForTesting;
 import android.support.v7.preference.Preference;
 import android.support.v7.preference.PreferenceGroup;
-import android.support.v7.preference.PreferenceScreen;
 import android.text.Spannable;
 import android.text.style.TextAppearanceSpan;
 import android.util.Log;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
 import android.view.View;
 import android.widget.TextView;
 
@@ -75,15 +70,6 @@
  */
 public class BluetoothSettings extends DeviceListPreferenceFragment implements Indexable {
     private static final String TAG = "BluetoothSettings";
-
-    private static final int MENU_ID_SHOW_RECEIVED = Menu.FIRST + 1;
-
-    /* Private intent to show the list of received files */
-    private static final String BTOPP_ACTION_OPEN_RECEIVED_FILES =
-            "android.btopp.intent.action.OPEN_RECEIVED_FILES";
-    private static final String BTOPP_PACKAGE =
-            "com.android.bluetooth";
-
     private static final int PAIRED_DEVICE_ORDER = 1;
     private static final int PAIRING_PREF_ORDER = 2;
 
@@ -185,31 +171,6 @@
     }
 
     @Override
-    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
-        if (mLocalAdapter == null) return;
-        // If the user is not allowed to configure bluetooth, do not show the menu.
-        if (isUiRestricted()) return;
-
-        menu.add(Menu.NONE, MENU_ID_SHOW_RECEIVED, 0, R.string.bluetooth_show_received_files)
-                .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
-        super.onCreateOptionsMenu(menu, inflater);
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        switch (item.getItemId()) {
-            case MENU_ID_SHOW_RECEIVED:
-                mMetricsFeatureProvider.action(getActivity(),
-                        MetricsEvent.ACTION_BLUETOOTH_FILES);
-                Intent intent = new Intent(BTOPP_ACTION_OPEN_RECEIVED_FILES);
-                intent.setPackage(BTOPP_PACKAGE);
-                getActivity().sendBroadcast(intent);
-                return true;
-        }
-        return super.onOptionsItemSelected(item);
-    }
-
-    @Override
     public String getDeviceListKey() {
         return KEY_PAIRED_DEVICES;
     }
@@ -233,7 +194,6 @@
                 mPairedDevicesCategory.addPreference(mPairingPreference);
                 updateFooterPreference(mFooterPreference);
 
-                getActivity().invalidateOptionsMenu();
                 mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
                 return; // not break
 
@@ -258,9 +218,6 @@
         if (messageId != 0) {
             getEmptyTextView().setText(messageId);
         }
-        if (!isUiRestricted()) {
-            getActivity().invalidateOptionsMenu();
-        }
     }
 
     private void setOffMessage() {
@@ -312,16 +269,6 @@
     }
 
     @Override
-    public void onScanningStateChanged(boolean started) {
-        super.onScanningStateChanged(started);
-        // Update options' enabled state
-        final Activity activity = getActivity();
-        if (activity != null) {
-            activity.invalidateOptionsMenu();
-        }
-    }
-
-    @Override
     public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
         updateContent(mLocalAdapter.getBluetoothState());
     }
@@ -417,6 +364,7 @@
                 (SettingsActivity) getActivity());
         controllers.add(mDeviceNamePrefController);
         controllers.add(mPairingPrefController);
+        controllers.add(new BluetoothFilesPreferenceController(context));
 
         return controllers;
     }
diff --git a/src/com/android/settings/deviceinfo/StorageProfileFragment.java b/src/com/android/settings/deviceinfo/StorageProfileFragment.java
index dee6793..f5129ed 100644
--- a/src/com/android/settings/deviceinfo/StorageProfileFragment.java
+++ b/src/com/android/settings/deviceinfo/StorageProfileFragment.java
@@ -120,7 +120,6 @@
     @Override
     public void onLoadFinished(Loader<SparseArray<AppsStorageResult>> loader,
             SparseArray<AppsStorageResult> result) {
-        scrubAppsFromResult(result.get(mUserId));
         mPreferenceController.onLoadFinished(result, mUserId);
     }
 
@@ -132,17 +131,4 @@
     void setPreferenceController(StorageItemPreferenceController controller) {
         mPreferenceController = controller;
     }
-
-    private AppsStorageResult scrubAppsFromResult(AppsStorageResult result) {
-        if (result == null) {
-            return null;
-        }
-
-        // TODO(b/35927909): Attribute app sizes better than zeroing out for profiles.
-        result.gamesSize = 0;
-        result.musicAppsSize = 0;
-        result.videoAppsSize = 0;
-        result.otherAppsSize = 0;
-        return result;
-    }
 }
diff --git a/src/com/android/settings/deviceinfo/storage/StorageAsyncLoader.java b/src/com/android/settings/deviceinfo/storage/StorageAsyncLoader.java
index 74474b3..0b685d0 100644
--- a/src/com/android/settings/deviceinfo/storage/StorageAsyncLoader.java
+++ b/src/com/android/settings/deviceinfo/storage/StorageAsyncLoader.java
@@ -25,6 +25,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.UserInfo;
 import android.os.UserHandle;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -34,6 +35,8 @@
 import com.android.settingslib.applications.StorageStatsSource;
 
 import java.io.IOException;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 
 /**
@@ -48,6 +51,7 @@
     private String mUuid;
     private StorageStatsSource mStatsManager;
     private PackageManagerWrapper mPackageManager;
+    private ArraySet<String> mSeenPackages;
 
     public StorageAsyncLoader(Context context, UserManagerWrapper userManager,
             String uuid, StorageStatsSource source, PackageManagerWrapper pm) {
@@ -64,8 +68,18 @@
     }
 
     private SparseArray<AppsStorageResult> loadApps() {
+        mSeenPackages = new ArraySet<>();
         SparseArray<AppsStorageResult> result = new SparseArray<>();
         List<UserInfo> infos = mUserManager.getUsers();
+        // Sort the users by user id ascending.
+        Collections.sort(
+                infos,
+                new Comparator<UserInfo>() {
+                    @Override
+                    public int compare(UserInfo userInfo, UserInfo otherUser) {
+                        return Integer.compare(userInfo.id, otherUser.id);
+                    }
+                });
         for (int i = 0, userCount = infos.size(); i < userCount; i++) {
             UserInfo info = infos.get(i);
             result.put(info.id, getStorageResultForUser(info.id));
@@ -93,10 +107,11 @@
 
             long blamedSize = stats.getDataBytes() - stats.getCacheBytes();
 
-            // Only count app code against the current user; we don't want
-            // double-counting on multi-user devices.
-            if (userId == UserHandle.myUserId()) {
+            // This isn't quite right because it slams the first user by user id with the whole code
+            // size, but this ensures that we count all apps seen once.
+            if (!mSeenPackages.contains(app.packageName)) {
                 blamedSize += stats.getCodeBytes();
+                mSeenPackages.add(app.packageName);
             }
 
             switch (app.category) {
@@ -140,6 +155,7 @@
         public long musicAppsSize;
         public long videoAppsSize;
         public long otherAppsSize;
+        public long cacheSize;
         public StorageStatsSource.ExternalStorageStats externalStats;
     }
 
diff --git a/src/com/android/settings/deviceinfo/storage/UserProfileController.java b/src/com/android/settings/deviceinfo/storage/UserProfileController.java
index 18fa7b7..fc297ca 100644
--- a/src/com/android/settings/deviceinfo/storage/UserProfileController.java
+++ b/src/com/android/settings/deviceinfo/storage/UserProfileController.java
@@ -96,7 +96,13 @@
         int userId = mUser.id;
         StorageAsyncLoader.AppsStorageResult result = stats.get(userId);
         if (result != null) {
-            setSize(result.externalStats.totalBytes, mTotalSizeBytes);
+            setSize(
+                    result.externalStats.totalBytes
+                            + result.otherAppsSize
+                            + result.videoAppsSize
+                            + result.musicAppsSize
+                            + result.gamesSize,
+                    mTotalSizeBytes);
         }
     }
 
diff --git a/src/com/android/settings/fuelgauge/anomaly/Anomaly.java b/src/com/android/settings/fuelgauge/anomaly/Anomaly.java
index 37b52fc..746bd7f 100644
--- a/src/com/android/settings/fuelgauge/anomaly/Anomaly.java
+++ b/src/com/android/settings/fuelgauge/anomaly/Anomaly.java
@@ -35,10 +35,12 @@
 public class Anomaly implements Parcelable {
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({AnomalyType.WAKE_LOCK,
-            AnomalyType.WAKEUP_ALARM})
+            AnomalyType.WAKEUP_ALARM,
+            AnomalyType.BLUETOOTH_SCAN})
     public @interface AnomalyType {
         int WAKE_LOCK = 0;
         int WAKEUP_ALARM = 1;
+        int BLUETOOTH_SCAN = 2;
     }
 
     @Retention(RetentionPolicy.SOURCE)
@@ -52,7 +54,8 @@
     @AnomalyType
     public static final int[] ANOMALY_TYPE_LIST =
             {AnomalyType.WAKE_LOCK,
-            AnomalyType.WAKEUP_ALARM};
+            AnomalyType.WAKEUP_ALARM,
+            AnomalyType.BLUETOOTH_SCAN};
 
     /**
      * Type of this this anomaly
diff --git a/src/com/android/settings/fuelgauge/anomaly/AnomalyDetectionPolicy.java b/src/com/android/settings/fuelgauge/anomaly/AnomalyDetectionPolicy.java
index 7dbae36..647737c 100644
--- a/src/com/android/settings/fuelgauge/anomaly/AnomalyDetectionPolicy.java
+++ b/src/com/android/settings/fuelgauge/anomaly/AnomalyDetectionPolicy.java
@@ -37,9 +37,13 @@
     @VisibleForTesting
     static final String KEY_WAKEUP_ALARM_DETECTION_ENABLED = "wakeup_alarm_enabled";
     @VisibleForTesting
+    static final String KEY_BLUETOOTH_SCAN_DETECTION_ENABLED = "bluetooth_scan_enabled";
+    @VisibleForTesting
     static final String KEY_WAKELOCK_THRESHOLD = "wakelock_threshold";
     @VisibleForTesting
     static final String KEY_WAKEUP_ALARM_THRESHOLD = "wakeup_alarm_threshold";
+    @VisibleForTesting
+    static final String KEY_BLUETOOTH_SCAN_THRESHOLD = "bluetooth_scan_threshold";
 
     /**
      * {@code true} if general anomaly detection is enabled
@@ -66,6 +70,14 @@
     public final boolean wakeupAlarmDetectionEnabled;
 
     /**
+     * {@code true} if bluetooth scanning detection is enabled
+     *
+     * @see Settings.Global#ANOMALY_DETECTION_CONSTANTS
+     * @see #KEY_BLUETOOTH_SCAN_THRESHOLD
+     */
+    public final boolean bluetoothScanDetectionEnabled;
+
+    /**
      * Threshold for wakelock time in milli seconds
      *
      * @see Settings.Global#ANOMALY_DETECTION_CONSTANTS
@@ -81,6 +93,14 @@
      */
     public final long wakeupAlarmThreshold;
 
+    /**
+     * Threshold for bluetooth unoptimized scanning time in milli seconds
+     *
+     * @see Settings.Global#ANOMALY_DETECTION_CONSTANTS
+     * @see #KEY_BLUETOOTH_SCAN_THRESHOLD
+     */
+    public final long bluetoothScanThreshold;
+
     private final KeyValueListParserWrapper mParserWrapper;
 
     public AnomalyDetectionPolicy(Context context) {
@@ -103,9 +123,13 @@
         wakeLockDetectionEnabled = mParserWrapper.getBoolean(KEY_WAKELOCK_DETECTION_ENABLED, true);
         wakeupAlarmDetectionEnabled = mParserWrapper.getBoolean(KEY_WAKEUP_ALARM_DETECTION_ENABLED,
                 true);
+        bluetoothScanDetectionEnabled = mParserWrapper.getBoolean(
+                KEY_BLUETOOTH_SCAN_DETECTION_ENABLED, true);
         wakeLockThreshold = mParserWrapper.getLong(KEY_WAKELOCK_THRESHOLD,
                 DateUtils.HOUR_IN_MILLIS);
         wakeupAlarmThreshold = mParserWrapper.getLong(KEY_WAKEUP_ALARM_THRESHOLD, 60);
+        bluetoothScanThreshold = mParserWrapper.getLong(KEY_BLUETOOTH_SCAN_THRESHOLD,
+                30 * DateUtils.MINUTE_IN_MILLIS);
     }
 
     public boolean isAnomalyDetectorEnabled(@Anomaly.AnomalyType int type) {
@@ -114,6 +138,8 @@
                 return wakeLockDetectionEnabled;
             case Anomaly.AnomalyType.WAKEUP_ALARM:
                 return wakeupAlarmDetectionEnabled;
+            case Anomaly.AnomalyType.BLUETOOTH_SCAN:
+                return bluetoothScanDetectionEnabled;
             default:
                 return false; // Disabled when no this type
         }
diff --git a/src/com/android/settings/fuelgauge/anomaly/AnomalyLoader.java b/src/com/android/settings/fuelgauge/anomaly/AnomalyLoader.java
index 264c390..03d4d23 100644
--- a/src/com/android/settings/fuelgauge/anomaly/AnomalyLoader.java
+++ b/src/com/android/settings/fuelgauge/anomaly/AnomalyLoader.java
@@ -22,9 +22,9 @@
 import android.os.Bundle;
 import android.os.UserManager;
 import android.support.annotation.VisibleForTesting;
+import android.util.Log;
 
 import com.android.internal.os.BatteryStatsHelper;
-import com.android.settings.Utils;
 import com.android.settings.utils.AsyncLoader;
 
 import java.util.ArrayList;
@@ -35,6 +35,8 @@
  * an empty list if there is no anomaly.
  */
 public class AnomalyLoader extends AsyncLoader<List<Anomaly>> {
+    private static final String TAG = "AnomalyLoader";
+
     private static final boolean USE_FAKE_DATA = false;
     private BatteryStatsHelper mBatteryStatsHelper;
     private String mPackageName;
@@ -108,9 +110,9 @@
     List<Anomaly> generateFakeData() {
         final List<Anomaly> anomalies = new ArrayList<>();
         final Context context = getContext();
+        final String packageName = "com.android.settings";
+        final CharSequence displayName = "Settings";
         try {
-            final String packageName = "com.android.settings";
-            final CharSequence displayName = "Settings";
             final int uid = context.getPackageManager().getPackageUid(packageName, 0);
 
             anomalies.add(new Anomaly.Builder()
@@ -125,8 +127,14 @@
                     .setPackageName(packageName)
                     .setDisplayName(displayName)
                     .build());
+            anomalies.add(new Anomaly.Builder()
+                    .setUid(uid)
+                    .setType(Anomaly.AnomalyType.BLUETOOTH_SCAN)
+                    .setPackageName(packageName)
+                    .setDisplayName(displayName)
+                    .build());
         } catch (PackageManager.NameNotFoundException e) {
-            e.printStackTrace();
+            Log.e(TAG, "Cannot find package by name: " + packageName, e);
         }
         return anomalies;
     }
diff --git a/src/com/android/settings/fuelgauge/anomaly/AnomalyUtils.java b/src/com/android/settings/fuelgauge/anomaly/AnomalyUtils.java
index d226899..de9f7aa 100644
--- a/src/com/android/settings/fuelgauge/anomaly/AnomalyUtils.java
+++ b/src/com/android/settings/fuelgauge/anomaly/AnomalyUtils.java
@@ -23,6 +23,7 @@
 import com.android.settings.fuelgauge.anomaly.action.BackgroundCheckAction;
 import com.android.settings.fuelgauge.anomaly.action.ForceStopAction;
 import com.android.settings.fuelgauge.anomaly.checker.AnomalyDetector;
+import com.android.settings.fuelgauge.anomaly.checker.BluetoothScanAnomalyDetector;
 import com.android.settings.fuelgauge.anomaly.checker.WakeLockAnomalyDetector;
 import com.android.settings.fuelgauge.anomaly.checker.WakeupAlarmAnomalyDetector;
 
@@ -56,6 +57,7 @@
             case Anomaly.AnomalyType.WAKE_LOCK:
                 return new ForceStopAction(mContext);
             case Anomaly.AnomalyType.WAKEUP_ALARM:
+            case Anomaly.AnomalyType.BLUETOOTH_SCAN:
                 return new BackgroundCheckAction(mContext);
             default:
                 return null;
@@ -74,6 +76,8 @@
                 return new WakeLockAnomalyDetector(mContext);
             case Anomaly.AnomalyType.WAKEUP_ALARM:
                 return new WakeupAlarmAnomalyDetector(mContext);
+            case Anomaly.AnomalyType.BLUETOOTH_SCAN:
+                return new BluetoothScanAnomalyDetector(mContext);
             default:
                 return null;
         }
diff --git a/src/com/android/settings/fuelgauge/anomaly/checker/BluetoothScanAnomalyDetector.java b/src/com/android/settings/fuelgauge/anomaly/checker/BluetoothScanAnomalyDetector.java
new file mode 100644
index 0000000..619386e
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/anomaly/checker/BluetoothScanAnomalyDetector.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.anomaly.checker;
+
+import android.content.Context;
+import android.os.BatteryStats;
+import android.os.SystemClock;
+import android.support.annotation.VisibleForTesting;
+import android.text.format.DateUtils;
+import android.util.ArrayMap;
+
+import com.android.internal.os.BatterySipper;
+import com.android.internal.os.BatteryStatsHelper;
+import com.android.settings.Utils;
+import com.android.settings.fuelgauge.BatteryUtils;
+import com.android.settings.fuelgauge.anomaly.Anomaly;
+import com.android.settings.fuelgauge.anomaly.AnomalyDetectionPolicy;
+import com.android.settings.fuelgauge.anomaly.AnomalyUtils;
+import com.android.settings.fuelgauge.anomaly.action.AnomalyAction;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Check whether apps have unoptimized bluetooth scanning in the background
+ */
+public class BluetoothScanAnomalyDetector implements AnomalyDetector {
+    private static final String TAG = "BluetoothScanAnomalyDetector";
+    @VisibleForTesting
+    BatteryUtils mBatteryUtils;
+    @VisibleForTesting
+    AnomalyAction mAnomalyAction;
+    private long mBluetoothScanningThreshold;
+    private Context mContext;
+
+    public BluetoothScanAnomalyDetector(Context context) {
+        this(context, new AnomalyDetectionPolicy(context));
+    }
+
+    @VisibleForTesting
+    BluetoothScanAnomalyDetector(Context context, AnomalyDetectionPolicy policy) {
+        mContext = context;
+        mBatteryUtils = BatteryUtils.getInstance(context);
+        mAnomalyAction = AnomalyUtils.getInstance(context).getAnomalyAction(
+                Anomaly.AnomalyType.BLUETOOTH_SCAN);
+        mBluetoothScanningThreshold = policy.bluetoothScanThreshold;
+    }
+
+    @Override
+    public List<Anomaly> detectAnomalies(BatteryStatsHelper batteryStatsHelper) {
+        // Detect all apps if targetPackageName is null
+        return detectAnomalies(batteryStatsHelper, null /* targetPackageName */);
+    }
+
+    @Override
+    public List<Anomaly> detectAnomalies(BatteryStatsHelper batteryStatsHelper,
+            String targetPackageName) {
+        final List<BatterySipper> batterySippers = batteryStatsHelper.getUsageList();
+        final List<Anomaly> anomalies = new ArrayList<>();
+        final int targetUid = mBatteryUtils.getPackageUid(targetPackageName);
+        final long elapsedRealtimeMs = SystemClock.elapsedRealtime();
+
+        for (int i = 0, size = batterySippers.size(); i < size; i++) {
+            final BatterySipper sipper = batterySippers.get(i);
+            final BatteryStats.Uid uid = sipper.uidObj;
+            if (uid == null
+                    || mBatteryUtils.shouldHideSipper(sipper)
+                    || (targetUid != BatteryUtils.UID_NULL && targetUid != uid.getUid())) {
+                continue;
+            }
+
+            final long bluetoothTimeMs = getBluetoothUnoptimizedBgTimeMs(uid, elapsedRealtimeMs);
+            if (bluetoothTimeMs > mBluetoothScanningThreshold) {
+                final String packageName = mBatteryUtils.getPackageName(uid.getUid());
+                final CharSequence displayName = Utils.getApplicationLabel(mContext,
+                        packageName);
+
+                Anomaly anomaly = new Anomaly.Builder()
+                        .setUid(uid.getUid())
+                        .setType(Anomaly.AnomalyType.BLUETOOTH_SCAN)
+                        .setDisplayName(displayName)
+                        .setPackageName(packageName)
+                        .build();
+
+                if (mAnomalyAction.isActionActive(anomaly)) {
+                    anomalies.add(anomaly);
+                }
+            }
+        }
+
+        return anomalies;
+    }
+
+    @VisibleForTesting
+    public long getBluetoothUnoptimizedBgTimeMs(BatteryStats.Uid uid, long elapsedRealtimeMs) {
+        BatteryStats.Timer timer = uid.getBluetoothUnoptimizedScanBackgroundTimer();
+
+        return timer != null ? timer.getTotalDurationMsLocked(elapsedRealtimeMs) : 0;
+    }
+
+}
diff --git a/src/com/android/settings/fuelgauge/anomaly/checker/WakeLockAnomalyDetector.java b/src/com/android/settings/fuelgauge/anomaly/checker/WakeLockAnomalyDetector.java
index 545bb26..5fa0e41 100644
--- a/src/com/android/settings/fuelgauge/anomaly/checker/WakeLockAnomalyDetector.java
+++ b/src/com/android/settings/fuelgauge/anomaly/checker/WakeLockAnomalyDetector.java
@@ -65,7 +65,8 @@
 
     @Override
     public List<Anomaly> detectAnomalies(BatteryStatsHelper batteryStatsHelper) {
-        return detectAnomalies(batteryStatsHelper, null);
+        // Detect all apps if targetPackageName is null
+        return detectAnomalies(batteryStatsHelper, null /* targetPackageName */);
     }
 
     @Override
diff --git a/src/com/android/settings/fuelgauge/anomaly/checker/WakeupAlarmAnomalyDetector.java b/src/com/android/settings/fuelgauge/anomaly/checker/WakeupAlarmAnomalyDetector.java
index 3502e73..aadfa0c 100644
--- a/src/com/android/settings/fuelgauge/anomaly/checker/WakeupAlarmAnomalyDetector.java
+++ b/src/com/android/settings/fuelgauge/anomaly/checker/WakeupAlarmAnomalyDetector.java
@@ -61,7 +61,8 @@
 
     @Override
     public List<Anomaly> detectAnomalies(BatteryStatsHelper batteryStatsHelper) {
-        return detectAnomalies(batteryStatsHelper, null);
+        // Detect all apps if targetPackageName is null
+        return detectAnomalies(batteryStatsHelper, null /* targetPackageName */);
     }
 
     @Override
diff --git a/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java b/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java
index a78fed7..6a13282 100644
--- a/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java
+++ b/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java
@@ -27,7 +27,9 @@
 import android.Manifest;
 import android.annotation.Nullable;
 import android.app.Activity;
+import android.app.NotificationManager;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.DialogInterface;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
@@ -53,12 +55,14 @@
     private int mUserId;
     private ComponentName mComponentName;
     private TouchOverlayManager mTouchOverlayManager;
+    private NotificationManager mNm;
 
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
         mTouchOverlayManager = new TouchOverlayManager(this);
+        mNm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
 
         mComponentName = getIntent().getParcelableExtra(EXTRA_COMPONENT_NAME);
         mUserId = getIntent().getIntExtra(EXTRA_USER_ID, UserHandle.USER_NULL);
@@ -94,12 +98,7 @@
             return;
         }
 
-        final SettingsStringUtil.SettingStringHelper setting =
-                 new SettingsStringUtil.SettingStringHelper(
-                         getContentResolver(),
-                         Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
-                         mUserId);
-        setting.write(SettingsStringUtil.ComponentNameSet.add(setting.read(), mComponentName));
+        mNm.setNotificationListenerAccessGranted(mComponentName, true);
 
         finish();
     }
diff --git a/src/com/android/settings/notification/NotificationAccessSettings.java b/src/com/android/settings/notification/NotificationAccessSettings.java
index 2cd728c..37601b3 100644
--- a/src/com/android/settings/notification/NotificationAccessSettings.java
+++ b/src/com/android/settings/notification/NotificationAccessSettings.java
@@ -41,7 +41,6 @@
     private static final String TAG = NotificationAccessSettings.class.getSimpleName();
     private static final Config CONFIG = getNotificationListenerConfig();
 
-
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
@@ -70,10 +69,11 @@
         return CONFIG;
     }
 
+    @Override
     protected boolean setEnabled(ComponentName service, String title, boolean enable) {
         logSpecialPermissionChange(enable, service.getPackageName());
         if (!enable) {
-            if (!mServiceListing.isEnabled(service)) {
+            if (!isServiceEnabled(service)) {
                 return true; // already disabled
             }
             // show a friendly dialog
@@ -82,10 +82,27 @@
                     .show(getFragmentManager(), "friendlydialog");
             return false;
         } else {
-            return super.setEnabled(service, title, enable);
+            if (isServiceEnabled(service)) {
+                return true; // already enabled
+            }
+            // show a scary dialog
+            new ScaryWarningDialogFragment()
+                    .setServiceInfo(service, title, this)
+                    .show(getFragmentManager(), "dialog");
+            return false;
         }
     }
 
+    @Override
+    protected boolean isServiceEnabled(ComponentName cn) {
+        return mNm.isNotificationListenerAccessGranted(cn);
+    }
+
+    @Override
+    protected void enable(ComponentName service) {
+        mNm.setNotificationListenerAccessGranted(service, true);
+    }
+
     @VisibleForTesting
     void logSpecialPermissionChange(boolean enable, String packageName) {
         int logCategory = enable ? MetricsEvent.APP_SPECIAL_PERMISSION_NOTIVIEW_ALLOW
@@ -94,17 +111,14 @@
                 logCategory, packageName);
     }
 
-    private static void disable(final Context context, final NotificationAccessSettings parent,
-            final ComponentName cn) {
-        parent.mServiceListing.setEnabled(cn, false);
+    private static void disable(final NotificationAccessSettings parent, final ComponentName cn) {
+        parent.mNm.setNotificationListenerAccessGranted(cn, false);
         AsyncTask.execute(new Runnable() {
             @Override
             public void run() {
-                final NotificationManager mgr = context.getSystemService(NotificationManager.class);
-
-                if (!mgr.isNotificationPolicyAccessGrantedForPackage(
+                if (!parent.mNm.isNotificationPolicyAccessGrantedForPackage(
                         cn.getPackageName())) {
-                    mgr.removeAutomaticZenRules(cn.getPackageName());
+                    parent.mNm.removeAutomaticZenRules(cn.getPackageName());
                 }
             }
         });
@@ -145,7 +159,7 @@
                     .setPositiveButton(R.string.notification_listener_disable_warning_confirm,
                             new DialogInterface.OnClickListener() {
                                 public void onClick(DialogInterface dialog, int id) {
-                                    disable(getContext(), parent, cn);
+                                    disable(parent, cn);
                                 }
                             })
                     .setNegativeButton(R.string.notification_listener_disable_warning_cancel,
diff --git a/src/com/android/settings/notification/ZenAccessSettings.java b/src/com/android/settings/notification/ZenAccessSettings.java
index a41a733..0d37980 100644
--- a/src/com/android/settings/notification/ZenAccessSettings.java
+++ b/src/com/android/settings/notification/ZenAccessSettings.java
@@ -17,7 +17,9 @@
 package com.android.settings.notification;
 
 import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.app.AlertDialog;
+import android.app.AppGlobals;
 import android.app.Dialog;
 import android.app.DialogFragment;
 import android.app.NotificationManager;
@@ -25,14 +27,17 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.RemoteException;
 import android.provider.Settings;
 import android.provider.Settings.Secure;
 import android.support.v14.preference.SwitchPreference;
@@ -41,6 +46,7 @@
 import android.support.v7.preference.PreferenceScreen;
 import android.text.TextUtils;
 import android.util.ArraySet;
+import android.util.Log;
 import android.view.View;
 import android.widget.Toast;
 
@@ -55,10 +61,9 @@
 import java.util.List;
 
 public class ZenAccessSettings extends EmptyTextSettings {
+    private final String TAG = "ZenAccessSettings";
 
     private final SettingObserver mObserver = new SettingObserver();
-    private static final String ENABLED_SERVICES_SEPARATOR = ":";
-
     private Context mContext;
     private PackageManager mPkgMan;
     private NotificationManager mNoMan;
@@ -106,7 +111,7 @@
         final PreferenceScreen screen = getPreferenceScreen();
         screen.removeAll();
         final ArrayList<ApplicationInfo> apps = new ArrayList<>();
-        final ArraySet<String> requesting = mNoMan.getPackagesRequestingNotificationPolicyAccess();
+        final ArraySet<String> requesting = getPackagesRequestingNotificationPolicyAccess();
         if (!requesting.isEmpty()) {
             final List<ApplicationInfo> installed = mPkgMan.getInstalledApplications(0);
             if (installed != null) {
@@ -117,7 +122,8 @@
                 }
             }
         }
-        ArraySet<String> autoApproved = getEnabledNotificationListeners();
+        ArraySet<String> autoApproved = new ArraySet<>();
+        autoApproved.addAll(mNoMan.getEnabledNotificationListenerPackages());
         requesting.addAll(autoApproved);
         Collections.sort(apps, new PackageItemInfo.DisplayNameComparator(mPkgMan));
         for (ApplicationInfo app : apps) {
@@ -152,20 +158,25 @@
         }
     }
 
-    private ArraySet<String> getEnabledNotificationListeners() {
-        ArraySet<String> packages = new ArraySet<>();
-        String settingValue = Settings.Secure.getString(getContext().getContentResolver(),
-                Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
-        if (!TextUtils.isEmpty(settingValue)) {
-            String[] restored = settingValue.split(ENABLED_SERVICES_SEPARATOR);
-            for (int i = 0; i < restored.length; i++) {
-                ComponentName value = ComponentName.unflattenFromString(restored[i]);
-                if (null != value) {
-                    packages.add(value.getPackageName());
+    private ArraySet<String> getPackagesRequestingNotificationPolicyAccess() {
+        ArraySet<String> requestingPackages = new ArraySet<>();
+        try {
+            final String[] PERM = {
+                    android.Manifest.permission.ACCESS_NOTIFICATION_POLICY
+            };
+            final ParceledListSlice list = AppGlobals.getPackageManager()
+                    .getPackagesHoldingPermissions(PERM, 0 /*flags*/,
+                            ActivityManager.getCurrentUser());
+            final List<PackageInfo> pkgs = list.getList();
+            if (pkgs != null) {
+                for (PackageInfo info : pkgs) {
+                    requestingPackages.add(info.packageName);
                 }
             }
+        } catch(RemoteException e) {
+            Log.e(TAG, "Cannot reach packagemanager", e);
         }
-        return packages;
+        return requestingPackages;
     }
 
     private boolean hasAccess(String pkg) {
diff --git a/src/com/android/settings/notification/ZenModeSettings.java b/src/com/android/settings/notification/ZenModeSettings.java
index 854857a..0d9c78f 100644
--- a/src/com/android/settings/notification/ZenModeSettings.java
+++ b/src/com/android/settings/notification/ZenModeSettings.java
@@ -179,8 +179,6 @@
     private static ManagedServiceSettings.Config getConditionProviderConfig() {
         final ManagedServiceSettings.Config c = new ManagedServiceSettings.Config();
         c.tag = TAG;
-        c.setting = Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES;
-        c.secondarySetting = Settings.Secure.ENABLED_NOTIFICATION_LISTENERS;
         c.intentAction = ConditionProviderService.SERVICE_INTERFACE;
         c.permission = android.Manifest.permission.BIND_CONDITION_PROVIDER_SERVICE;
         c.noun = "condition provider";
diff --git a/src/com/android/settings/utils/ManagedServiceSettings.java b/src/com/android/settings/utils/ManagedServiceSettings.java
index 6e3841d..d76520f 100644
--- a/src/com/android/settings/utils/ManagedServiceSettings.java
+++ b/src/com/android/settings/utils/ManagedServiceSettings.java
@@ -20,6 +20,8 @@
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.Fragment;
+import android.app.Notification;
+import android.app.NotificationManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.Context;
@@ -54,6 +56,7 @@
     private PackageManager mPm;
     private DevicePolicyManager mDpm;
     protected ServiceListing mServiceListing;
+    protected NotificationManager mNm;
 
     abstract protected Config getConfig();
 
@@ -68,6 +71,7 @@
         mContext = getActivity();
         mPm = mContext.getPackageManager();
         mDpm = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+        mNm = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
         mServiceListing = new ServiceListing(mContext, mConfig);
         mServiceListing.addCallback(new ServiceListing.Callback() {
             @Override
@@ -124,7 +128,7 @@
             } else {
                 pref.setTitle(summary);
             }
-            pref.setChecked(mServiceListing.isEnabled(cn));
+            pref.setChecked(isServiceEnabled(cn));
             if (managedProfileId != UserHandle.USER_NULL
                     && !mDpm.isNotificationListenerServicePermitted(
                             service.packageName, managedProfileId)) {
@@ -148,6 +152,10 @@
         return UserHandle.myUserId();
     }
 
+    protected boolean isServiceEnabled(ComponentName cn) {
+        return mServiceListing.isEnabled(cn);
+    }
+
     protected boolean setEnabled(ComponentName service, String title, boolean enable) {
         if (!enable) {
             // the simple version: disabling
@@ -165,6 +173,10 @@
         }
     }
 
+    protected void enable(ComponentName service) {
+        mServiceListing.setEnabled(service, true);
+    }
+
     public static class ScaryWarningDialogFragment extends InstrumentedDialogFragment {
         static final String KEY_COMPONENT = "c";
         static final String KEY_LABEL = "l";
@@ -202,7 +214,7 @@
                     .setPositiveButton(R.string.allow,
                             new DialogInterface.OnClickListener() {
                                 public void onClick(DialogInterface dialog, int id) {
-                                    parent.mServiceListing.setEnabled(cn, true);
+                                    parent.enable(cn);
                                 }
                             })
                     .setNegativeButton(R.string.deny,
diff --git a/src/com/android/settings/utils/ZenServiceListing.java b/src/com/android/settings/utils/ZenServiceListing.java
index 167b066..40a4f34 100644
--- a/src/com/android/settings/utils/ZenServiceListing.java
+++ b/src/com/android/settings/utils/ZenServiceListing.java
@@ -16,6 +16,7 @@
 package com.android.settings.utils;
 
 import android.app.ActivityManager;
+import android.app.NotificationManager;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -35,16 +36,16 @@
 
 public class ZenServiceListing {
 
-    private final ContentResolver mContentResolver;
     private final Context mContext;
     private final ManagedServiceSettings.Config mConfig;
     private final Set<ServiceInfo> mApprovedServices = new ArraySet<ServiceInfo>();
     private final List<Callback> mZenCallbacks = new ArrayList<>();
+    private final NotificationManager mNm;
 
     public ZenServiceListing(Context context, ManagedServiceSettings.Config config) {
         mContext = context;
         mConfig = config;
-        mContentResolver = context.getContentResolver();
+        mNm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
     }
 
     public ServiceInfo findService(final ComponentName cn) {
@@ -67,23 +68,18 @@
 
     public void reloadApprovedServices() {
         mApprovedServices.clear();
-        String[] settings = {mConfig.setting, mConfig.secondarySetting};
 
-        for (String setting : settings) {
-            if (!TextUtils.isEmpty(setting)) {
-                final String flat = Settings.Secure.getString(mContentResolver, setting);
-                if (!TextUtils.isEmpty(flat)) {
-                    final List<String> names = Arrays.asList(flat.split(":"));
-                    List<ServiceInfo> services = new ArrayList<>();
-                    getServices(mConfig, services, mContext.getPackageManager());
-                    for (ServiceInfo service : services) {
-                        if (matchesApprovedPackage(names, service.getComponentName())) {
-                            mApprovedServices.add(service);
-                        }
-                    }
-                }
+        List<String> enabledNotificationListenerPkgs = mNm.getEnabledNotificationListenerPackages();
+        List<ServiceInfo> services = new ArrayList<>();
+        getServices(mConfig, services, mContext.getPackageManager());
+        for (ServiceInfo service : services) {
+            final String servicePackage = service.getComponentName().getPackageName();
+            if (mNm.isNotificationPolicyAccessGrantedForPackage(servicePackage)
+                || enabledNotificationListenerPkgs.contains(servicePackage)) {
+                mApprovedServices.add(service);
             }
         }
+
         if (!mApprovedServices.isEmpty()) {
             for (Callback callback : mZenCallbacks) {
                 callback.onServicesReloaded(mApprovedServices);
@@ -91,25 +87,6 @@
         }
     }
 
-    // Setting could contain: the component name of the condition provider, the package name of
-    // the condition provider, the component name of the notification listener.
-    private boolean matchesApprovedPackage(List<String> approved, ComponentName serviceOwner) {
-        String flatCn = serviceOwner.flattenToString();
-        if (approved.contains(flatCn) || approved.contains(serviceOwner.getPackageName())) {
-            return true;
-        }
-        for (String entry : approved) {
-            if (!TextUtils.isEmpty(entry)) {
-                ComponentName approvedComponent = ComponentName.unflattenFromString(entry);
-                if (approvedComponent != null && approvedComponent.getPackageName().equals(
-                        serviceOwner.getPackageName())) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
     private static int getServices(ManagedServiceSettings.Config c, List<ServiceInfo> list,
             PackageManager pm) {
         int services = 0;
diff --git a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultNotificationAssistantPickerTest.java b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultNotificationAssistantPickerTest.java
deleted file mode 100644
index a60b43c..0000000
--- a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultNotificationAssistantPickerTest.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * 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.applications.defaultapps;
-
-
-import android.app.Activity;
-import android.content.Context;
-import android.os.UserManager;
-import android.provider.Settings;
-
-import com.android.settings.testutils.SettingsRobolectricTestRunner;
-import com.android.settings.TestConfig;
-import com.android.settings.applications.PackageManagerWrapper;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Answers;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Config;
-import org.robolectric.util.ReflectionHelpers;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-
-@RunWith(SettingsRobolectricTestRunner.class)
-@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
-public class DefaultNotificationAssistantPickerTest {
-
-    private static final String TEST_APP_KEY = "com.android.settings/PickerTest";
-
-    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
-    private Activity mActivity;
-    @Mock
-    private UserManager mUserManager;
-    @Mock
-    private PackageManagerWrapper mPackageManager;
-
-    private DefaultNotificationAssistantPicker mPicker;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        when(mActivity.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
-
-        mPicker = spy(new DefaultNotificationAssistantPicker());
-        mPicker.onAttach((Context) mActivity);
-
-        ReflectionHelpers.setField(mPicker, "mPm", mPackageManager);
-        doReturn(RuntimeEnvironment.application).when(mPicker).getContext();
-    }
-
-    @Test
-    public void setDefaultAppKey_shouldUpdateDefault() {
-        mPicker.setDefaultKey(TEST_APP_KEY);
-
-        assertThat(mPicker.getDefaultKey()).isEqualTo(TEST_APP_KEY);
-    }
-
-    @Test
-    public void getDefaultAppKey_shouldReturnDefault() {
-        Settings.Secure.putString(RuntimeEnvironment.application.getContentResolver(),
-                Settings.Secure.ENABLED_NOTIFICATION_ASSISTANT,
-                TEST_APP_KEY);
-
-        assertThat(mPicker.getDefaultKey()).isEqualTo(TEST_APP_KEY);
-    }
-}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothFilesPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothFilesPreferenceControllerTest.java
new file mode 100644
index 0000000..7713aaf
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothFilesPreferenceControllerTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.preference.Preference;
+import android.text.TextUtils;
+
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+import java.util.List;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class BluetoothFilesPreferenceControllerTest {
+    private Context mContext;
+    private BluetoothFilesPreferenceController mController;
+    private Preference mPreference;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = RuntimeEnvironment.application;
+        mController = new BluetoothFilesPreferenceController(mContext);
+        mPreference = new Preference(mContext);
+        mPreference.setKey(BluetoothFilesPreferenceController.KEY_RECEIVED_FILES);
+    }
+
+    @Test
+    public void testHandlePreferenceTreeClick_sendBroadcast() {
+        mController.handlePreferenceTreeClick(mPreference);
+
+        final Intent intent = ShadowApplication.getInstance().getNextStartedActivity();
+        assertThat(intent).isNotNull();
+        assertThat(intent.getAction()).isEqualTo(
+                BluetoothFilesPreferenceController.ACTION_OPEN_FILES);
+
+        final Bundle bundle = intent.getExtras();
+        assertThat(bundle.getInt(BluetoothFilesPreferenceController.EXTRA_DIRECTION)).isEqualTo(1);
+        assertThat(bundle.getBoolean(
+                BluetoothFilesPreferenceController.EXTRA_SHOW_ALL_FILES)).isTrue();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/deviceinfo/StorageProfileFragmentTest.java b/tests/robotests/src/com/android/settings/deviceinfo/StorageProfileFragmentTest.java
index 44e060f..b0f464c 100644
--- a/tests/robotests/src/com/android/settings/deviceinfo/StorageProfileFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/deviceinfo/StorageProfileFragmentTest.java
@@ -43,7 +43,7 @@
     private ArgumentCaptor<SparseArray<StorageAsyncLoader.AppsStorageResult>> mCaptor;
 
     @Test
-    public void verifyAppSizesAreZeroedOut() {
+    public void verifyAppSizesAreNotZeroedOut() {
         StorageItemPreferenceController controller = mock(StorageItemPreferenceController.class);
         StorageProfileFragment fragment = new StorageProfileFragment();
         StorageAsyncLoader.AppsStorageResult result = new StorageAsyncLoader.AppsStorageResult();
@@ -62,10 +62,10 @@
         verify(controller).onLoadFinished(mCaptor.capture(), anyInt());
 
         StorageAsyncLoader.AppsStorageResult extractedResult = mCaptor.getValue().get(0);
-        assertThat(extractedResult.musicAppsSize).isEqualTo(0);
-        assertThat(extractedResult.videoAppsSize).isEqualTo(0);
-        assertThat(extractedResult.otherAppsSize).isEqualTo(0);
-        assertThat(extractedResult.gamesSize).isEqualTo(0);
+        assertThat(extractedResult.musicAppsSize).isEqualTo(100);
+        assertThat(extractedResult.videoAppsSize).isEqualTo(400);
+        assertThat(extractedResult.otherAppsSize).isEqualTo(200);
+        assertThat(extractedResult.gamesSize).isEqualTo(300);
         assertThat(extractedResult.externalStats.audioBytes).isEqualTo(1);
         assertThat(extractedResult.externalStats.videoBytes).isEqualTo(2);
         assertThat(extractedResult.externalStats.imageBytes).isEqualTo(3);
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyDetectionPolicyTest.java b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyDetectionPolicyTest.java
index d5bd53b..169cba8 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyDetectionPolicyTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyDetectionPolicyTest.java
@@ -44,7 +44,9 @@
             + ",wakelock_enabled=false"
             + ",wakelock_threshold=3000"
             + ",wakeup_alarm_enabled=true"
-            + ",wakeup_alarm_threshold=100";
+            + ",wakeup_alarm_threshold=100"
+            + ",bluetooth_scan_enabled=true"
+            + ",bluetooth_scan_threshold=2000";
     private Context mContext;
     private KeyValueListParserWrapper mKeyValueListParserWrapper;
 
@@ -64,6 +66,8 @@
         assertThat(anomalyDetectionPolicy.wakeLockThreshold).isEqualTo(3000);
         assertThat(anomalyDetectionPolicy.wakeupAlarmDetectionEnabled).isTrue();
         assertThat(anomalyDetectionPolicy.wakeupAlarmThreshold).isEqualTo(100);
+        assertThat(anomalyDetectionPolicy.bluetoothScanDetectionEnabled).isTrue();
+        assertThat(anomalyDetectionPolicy.bluetoothScanThreshold).isEqualTo(2000);
     }
 
     @Test
@@ -82,6 +86,9 @@
         assertThat(anomalyDetectionPolicy.wakeLockThreshold).isEqualTo(DateUtils.HOUR_IN_MILLIS);
         assertThat(anomalyDetectionPolicy.wakeupAlarmDetectionEnabled).isTrue();
         assertThat(anomalyDetectionPolicy.wakeupAlarmThreshold).isEqualTo(60);
+        assertThat(anomalyDetectionPolicy.bluetoothScanDetectionEnabled).isTrue();
+        assertThat(anomalyDetectionPolicy.bluetoothScanThreshold).isEqualTo(
+                30 * DateUtils.MINUTE_IN_MILLIS);
     }
 
     @Test
@@ -92,6 +99,8 @@
                 Anomaly.AnomalyType.WAKE_LOCK)).isFalse();
         assertThat(anomalyDetectionPolicy.isAnomalyDetectorEnabled(
                 Anomaly.AnomalyType.WAKEUP_ALARM)).isTrue();
+        assertThat(anomalyDetectionPolicy.isAnomalyDetectorEnabled(
+                Anomaly.AnomalyType.BLUETOOTH_SCAN)).isTrue();
     }
 
     private AnomalyDetectionPolicy createAnomalyPolicyWithConfig() {
@@ -104,6 +113,8 @@
                 AnomalyDetectionPolicy.KEY_WAKELOCK_DETECTION_ENABLED, true);
         doReturn(true).when(mKeyValueListParserWrapper).getBoolean(
                 AnomalyDetectionPolicy.KEY_WAKEUP_ALARM_DETECTION_ENABLED, true);
+        doReturn(true).when(mKeyValueListParserWrapper).getBoolean(
+                AnomalyDetectionPolicy.KEY_BLUETOOTH_SCAN_DETECTION_ENABLED, true);
 
         return new AnomalyDetectionPolicy(mContext, mKeyValueListParserWrapper);
     }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyLoaderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyLoaderTest.java
index 8119168..48749d5 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyLoaderTest.java
@@ -30,6 +30,7 @@
 import android.os.UserManager;
 
 import com.android.internal.os.BatteryStatsHelper;
+import com.android.settings.fuelgauge.anomaly.checker.BluetoothScanAnomalyDetector;
 import com.android.settings.testutils.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
 import com.android.settings.fuelgauge.anomaly.checker.WakeLockAnomalyDetector;
@@ -62,13 +63,17 @@
     @Mock
     private WakeupAlarmAnomalyDetector mWakeupAlarmAnomalyDetector;
     @Mock
+    private BluetoothScanAnomalyDetector mBluetoothScanAnomalyDetector;
+    @Mock
     private AnomalyDetectionPolicy mAnomalyDetectionPolicy;
     @Mock
     private UserManager mUserManager;
     private Anomaly mWakeLockAnomaly;
     private Anomaly mWakeupAlarmAnomaly;
+    private Anomaly mBluetoothScanAnomaly;
     private List<Anomaly> mWakeLockAnomalies;
     private List<Anomaly> mWakeupAlarmAnomalies;
+    private List<Anomaly> mBluetoothScanAnomalies;
     private AnomalyLoader mAnomalyLoader;
 
     @Before
@@ -91,6 +96,12 @@
         doReturn(mWakeupAlarmAnomalies).when(mWakeupAlarmAnomalyDetector).detectAnomalies(any(),
                 any());
 
+        mBluetoothScanAnomalies = new ArrayList<>();
+        mBluetoothScanAnomaly = createAnomaly(Anomaly.AnomalyType.BLUETOOTH_SCAN);
+        mBluetoothScanAnomalies.add(mBluetoothScanAnomaly);
+        doReturn(mBluetoothScanAnomalies).when(mBluetoothScanAnomalyDetector).detectAnomalies(any(),
+                any());
+
         mAnomalyLoader = new AnomalyLoader(mContext, mBatteryStatsHelper, null,
                 mAnomalyDetectionPolicy);
         mAnomalyLoader.mAnomalyUtils = spy(new AnomalyUtils(mContext));
@@ -102,10 +113,14 @@
                 Anomaly.AnomalyType.WAKE_LOCK);
         doReturn(mWakeupAlarmAnomalyDetector).when(mAnomalyLoader.mAnomalyUtils).getAnomalyDetector(
                 Anomaly.AnomalyType.WAKEUP_ALARM);
+        doReturn(mBluetoothScanAnomalyDetector).when(
+                mAnomalyLoader.mAnomalyUtils).getAnomalyDetector(
+                Anomaly.AnomalyType.BLUETOOTH_SCAN);
 
         List<Anomaly> anomalies = mAnomalyLoader.loadInBackground();
 
-        assertThat(anomalies).containsExactly(mWakeLockAnomaly, mWakeupAlarmAnomaly);
+        assertThat(anomalies).containsExactly(mWakeLockAnomaly, mWakeupAlarmAnomaly,
+                mBluetoothScanAnomaly);
     }
 
     private Anomaly createAnomaly(@Anomaly.AnomalyType int type) {
@@ -121,6 +136,7 @@
     public void testGenerateFakeData() {
         List<Anomaly> anomalies = mAnomalyLoader.generateFakeData();
 
-        assertThat(anomalies).containsExactly(mWakeLockAnomaly, mWakeupAlarmAnomaly);
+        assertThat(anomalies).containsExactly(mWakeLockAnomaly, mWakeupAlarmAnomaly,
+                mBluetoothScanAnomaly);
     }
 }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/checker/BluetoothScanAnomalyDetectorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/checker/BluetoothScanAnomalyDetectorTest.java
new file mode 100644
index 0000000..941e9cd
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/checker/BluetoothScanAnomalyDetectorTest.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.anomaly.checker;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.BatteryStats;
+import android.text.format.DateUtils;
+
+import com.android.internal.os.BatterySipper;
+import com.android.internal.os.BatteryStatsHelper;
+import com.android.settings.TestConfig;
+import com.android.settings.fuelgauge.BatteryUtils;
+import com.android.settings.fuelgauge.anomaly.Anomaly;
+import com.android.settings.fuelgauge.anomaly.AnomalyDetectionPolicy;
+import com.android.settings.fuelgauge.anomaly.action.AnomalyAction;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class BluetoothScanAnomalyDetectorTest {
+    private static final String TARGET_PACKAGE_NAME = "com.android.app";
+    private static final int ANOMALY_UID = 111;
+    private static final int NORMAL_UID = 222;
+    private static final int TARGET_UID = 333;
+    private static final long ANOMALY_BLUETOOTH_SCANNING_TIME = DateUtils.HOUR_IN_MILLIS;
+    private static final long NORMAL_BLUETOOTH_SCANNING_TIME = DateUtils.MINUTE_IN_MILLIS;
+    @Mock
+    private BatteryStatsHelper mBatteryStatsHelper;
+    @Mock
+    private BatterySipper mAnomalySipper;
+    @Mock
+    private BatterySipper mNormalSipper;
+    @Mock
+    private BatterySipper mTargetSipper;
+    @Mock
+    private BatteryStats.Uid mAnomalyUid;
+    @Mock
+    private BatteryStats.Uid mNormalUid;
+    @Mock
+    private BatteryStats.Uid mTargetUid;
+    @Mock
+    private BatteryUtils mBatteryUtils;
+    @Mock
+    private AnomalyDetectionPolicy mPolicy;
+    @Mock
+    private AnomalyAction mAnomalyAction;
+
+    private BluetoothScanAnomalyDetector mBluetoothScanAnomalyDetector;
+    private Context mContext;
+    private List<BatterySipper> mUsageList;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = spy(RuntimeEnvironment.application);
+        ReflectionHelpers.setField(mPolicy, "bluetoothScanThreshold",
+                30 * DateUtils.MINUTE_IN_MILLIS);
+
+        mAnomalySipper.uidObj = mAnomalyUid;
+        doReturn(ANOMALY_UID).when(mAnomalyUid).getUid();
+        mNormalSipper.uidObj = mNormalUid;
+        doReturn(NORMAL_UID).when(mNormalUid).getUid();
+        mTargetSipper.uidObj = mTargetUid;
+        doReturn(TARGET_UID).when(mTargetUid).getUid();
+
+        mUsageList = new ArrayList<>();
+        mUsageList.add(mAnomalySipper);
+        mUsageList.add(mNormalSipper);
+        mUsageList.add(mTargetSipper);
+        doReturn(mUsageList).when(mBatteryStatsHelper).getUsageList();
+
+        mBluetoothScanAnomalyDetector = spy(new BluetoothScanAnomalyDetector(mContext, mPolicy));
+        mBluetoothScanAnomalyDetector.mBatteryUtils = mBatteryUtils;
+        mBluetoothScanAnomalyDetector.mAnomalyAction = mAnomalyAction;
+        doReturn(false).when(mBatteryUtils).shouldHideSipper(any());
+        doReturn(true).when(mAnomalyAction).isActionActive(any());
+
+        doReturn(ANOMALY_BLUETOOTH_SCANNING_TIME).when(
+                mBluetoothScanAnomalyDetector).getBluetoothUnoptimizedBgTimeMs(eq(mAnomalyUid),
+                anyLong());
+        doReturn(ANOMALY_BLUETOOTH_SCANNING_TIME).when(
+                mBluetoothScanAnomalyDetector).getBluetoothUnoptimizedBgTimeMs(eq(mTargetUid),
+                anyLong());
+        doReturn(NORMAL_BLUETOOTH_SCANNING_TIME).when(
+                mBluetoothScanAnomalyDetector).getBluetoothUnoptimizedBgTimeMs(eq(mNormalUid),
+                anyLong());
+    }
+
+    @Test
+    public void testDetectAnomalies_containsAnomaly_detectIt() {
+        doReturn(-1).when(mBatteryUtils).getPackageUid(nullable(String.class));
+        final Anomaly anomaly = createBluetoothAnomaly(ANOMALY_UID);
+        final Anomaly targetAnomaly = createBluetoothAnomaly(TARGET_UID);
+
+        List<Anomaly> mAnomalies = mBluetoothScanAnomalyDetector.detectAnomalies(
+                mBatteryStatsHelper);
+
+        assertThat(mAnomalies).containsExactly(anomaly, targetAnomaly);
+    }
+
+    @Test
+    public void testDetectAnomalies_detectTargetAnomaly_detectIt() {
+        doReturn(TARGET_UID).when(mBatteryUtils).getPackageUid(TARGET_PACKAGE_NAME);
+        final Anomaly targetAnomaly = createBluetoothAnomaly(TARGET_UID);
+
+        List<Anomaly> mAnomalies = mBluetoothScanAnomalyDetector.detectAnomalies(
+                mBatteryStatsHelper, TARGET_PACKAGE_NAME);
+
+        assertThat(mAnomalies).containsExactly(targetAnomaly);
+
+    }
+
+    private Anomaly createBluetoothAnomaly(int uid) {
+        return new Anomaly.Builder()
+                .setUid(uid)
+                .setType(Anomaly.AnomalyType.BLUETOOTH_SCAN)
+                .build();
+    }
+
+}
diff --git a/tests/unit/src/com/android/settings/deviceinfo/storage/StorageAsyncLoaderTest.java b/tests/unit/src/com/android/settings/deviceinfo/storage/StorageAsyncLoaderTest.java
index 79a4595..28bd861 100644
--- a/tests/unit/src/com/android/settings/deviceinfo/storage/StorageAsyncLoaderTest.java
+++ b/tests/unit/src/com/android/settings/deviceinfo/storage/StorageAsyncLoaderTest.java
@@ -27,6 +27,7 @@
 
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.UserInfo;
 import android.os.UserHandle;
 import android.support.test.filters.SmallTest;
@@ -74,7 +75,8 @@
         MockitoAnnotations.initMocks(this);
         mInfo = new ArrayList<>();
         mLoader = new StorageAsyncLoader(mContext, mUserManager, "id", mSource, mPackageManager);
-        when(mPackageManager.getInstalledApplicationsAsUser(anyInt(), anyInt())).thenReturn(mInfo);
+        when(mPackageManager.getInstalledApplicationsAsUser(eq(PRIMARY_USER_ID), anyInt()))
+                .thenReturn(mInfo);
         UserInfo info = new UserInfo();
         mUsers = new ArrayList<>();
         mUsers.add(info);
@@ -174,13 +176,37 @@
         info.category = ApplicationInfo.CATEGORY_UNDEFINED;
         mInfo.add(info);
         when(mSource.getStatsForPackage(anyString(), anyString(), any(UserHandle.class)))
-                .thenThrow(new IllegalStateException());
+                .thenThrow(new NameNotFoundException());
 
         SparseArray<StorageAsyncLoader.AppsStorageResult> result = mLoader.loadInBackground();
 
         // Should not crash.
     }
 
+    @Test
+    public void testPackageIsNotDoubleCounted() throws Exception {
+        UserInfo info = new UserInfo();
+        info.id = SECONDARY_USER_ID;
+        mUsers.add(info);
+        when(mSource.getExternalStorageStats(anyString(), eq(UserHandle.SYSTEM)))
+                .thenReturn(new StorageStatsSource.ExternalStorageStats(9, 2, 3, 4, 0));
+        when(mSource.getExternalStorageStats(anyString(), eq(new UserHandle(SECONDARY_USER_ID))))
+                .thenReturn(new StorageStatsSource.ExternalStorageStats(10, 3, 3, 4, 0));
+        addPackage(PACKAGE_NAME_1, 0, 1, 10, ApplicationInfo.CATEGORY_VIDEO);
+        ArrayList<ApplicationInfo> secondaryUserApps = new ArrayList<>();
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.packageName = PACKAGE_NAME_1;
+        appInfo.category = ApplicationInfo.CATEGORY_VIDEO;
+        secondaryUserApps.add(appInfo);
+
+        SparseArray<StorageAsyncLoader.AppsStorageResult> result = mLoader.loadInBackground();
+
+        assertThat(result.size()).isEqualTo(2);
+        assertThat(result.get(PRIMARY_USER_ID).videoAppsSize).isEqualTo(11L);
+        // No code size for the second user.
+        assertThat(result.get(SECONDARY_USER_ID).videoAppsSize).isEqualTo(10L);
+    }
+
     private ApplicationInfo addPackage(String packageName, long cacheSize, long codeSize,
             long dataSize, int category) throws Exception {
         StorageStatsSource.AppStorageStats storageStats =
@@ -196,4 +222,5 @@
         mInfo.add(info);
         return info;
     }
+
 }