Cleanup and refactoring of test utilities

 - Make leak checking faster by converting to fakes
    - Requires making clean interfaces for all CallbackControllers
 - Integrate leak checking into the TestableContext

Test: runtest systemui
Change-Id: Ic57a06360d01a0323ef26735a543e9d1805459e2
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileController.java
index 1a46815..4969a1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileController.java
@@ -14,134 +14,16 @@
 
 package com.android.systemui.statusbar.phone;
 
-import android.app.ActivityManager;
-import android.app.StatusBarManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.UserInfo;
-import android.os.UserHandle;
-import android.os.UserManager;
-
 import com.android.systemui.statusbar.phone.ManagedProfileController.Callback;
 import com.android.systemui.statusbar.policy.CallbackController;
 
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
+public interface ManagedProfileController extends CallbackController<Callback> {
 
-public class ManagedProfileController implements CallbackController<Callback> {
+    void setWorkModeEnabled(boolean enabled);
 
-    private final List<Callback> mCallbacks = new ArrayList<>();
+    boolean hasActiveProfile();
 
-    private final Context mContext;
-    private final UserManager mUserManager;
-    private final LinkedList<UserInfo> mProfiles;
-    private boolean mListening;
-    private int mCurrentUser;
-
-    public ManagedProfileController(QSTileHost host) {
-        mContext = host.getContext();
-        mUserManager = UserManager.get(mContext);
-        mProfiles = new LinkedList<UserInfo>();
-    }
-
-    public void addCallback(Callback callback) {
-        mCallbacks.add(callback);
-        if (mCallbacks.size() == 1) {
-            setListening(true);
-        }
-        callback.onManagedProfileChanged();
-    }
-
-    public void removeCallback(Callback callback) {
-        if (mCallbacks.remove(callback) && mCallbacks.size() == 0) {
-            setListening(false);
-        }
-    }
-
-    public void setWorkModeEnabled(boolean enableWorkMode) {
-        synchronized (mProfiles) {
-            for (UserInfo ui : mProfiles) {
-                if (enableWorkMode) {
-                    if (!mUserManager.trySetQuietModeDisabled(ui.id, null)) {
-                        StatusBarManager statusBarManager = (StatusBarManager) mContext
-                                .getSystemService(android.app.Service.STATUS_BAR_SERVICE);
-                        statusBarManager.collapsePanels();
-                    }
-                } else {
-                    mUserManager.setQuietModeEnabled(ui.id, true);
-                }
-            }
-        }
-    }
-
-    private void reloadManagedProfiles() {
-        synchronized (mProfiles) {
-            boolean hadProfile = mProfiles.size() > 0;
-            int user = ActivityManager.getCurrentUser();
-            mProfiles.clear();
-
-            for (UserInfo ui : mUserManager.getEnabledProfiles(user)) {
-                if (ui.isManagedProfile()) {
-                    mProfiles.add(ui);
-                }
-            }
-            if (mProfiles.size() == 0 && hadProfile && (user == mCurrentUser)) {
-                for (Callback callback : mCallbacks) {
-                    callback.onManagedProfileRemoved();
-                }
-            }
-            mCurrentUser = user;
-        }
-    }
-
-    public boolean hasActiveProfile() {
-        if (!mListening) reloadManagedProfiles();
-        synchronized (mProfiles) {
-            return mProfiles.size() > 0;
-        }
-    }
-
-    public boolean isWorkModeEnabled() {
-        if (!mListening) reloadManagedProfiles();
-        synchronized (mProfiles) {
-            for (UserInfo ui : mProfiles) {
-                if (ui.isQuietModeEnabled()) {
-                    return false;
-                }
-            }
-            return true;
-        }
-    }
-
-    private void setListening(boolean listening) {
-        mListening = listening;
-        if (listening) {
-            reloadManagedProfiles();
-
-            final IntentFilter filter = new IntentFilter();
-            filter.addAction(Intent.ACTION_USER_SWITCHED);
-            filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
-            filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
-            filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
-            filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
-            mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, null);
-        } else {
-            mContext.unregisterReceiver(mReceiver);
-        }
-    }
-
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            reloadManagedProfiles();
-            for (Callback callback : mCallbacks) {
-                callback.onManagedProfileChanged();
-            }
-        }
-    };
+    boolean isWorkModeEnabled();
 
     public interface Callback {
         void onManagedProfileChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
new file mode 100644
index 0000000..fc33ace
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2016 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.systemui.statusbar.phone;
+
+import android.app.ActivityManager;
+import android.app.StatusBarManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.UserInfo;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+public class ManagedProfileControllerImpl implements ManagedProfileController {
+
+    private final List<Callback> mCallbacks = new ArrayList<>();
+
+    private final Context mContext;
+    private final UserManager mUserManager;
+    private final LinkedList<UserInfo> mProfiles;
+    private boolean mListening;
+    private int mCurrentUser;
+
+    public ManagedProfileControllerImpl(QSTileHost host) {
+        mContext = host.getContext();
+        mUserManager = UserManager.get(mContext);
+        mProfiles = new LinkedList<UserInfo>();
+    }
+
+    public void addCallback(Callback callback) {
+        mCallbacks.add(callback);
+        if (mCallbacks.size() == 1) {
+            setListening(true);
+        }
+        callback.onManagedProfileChanged();
+    }
+
+    public void removeCallback(Callback callback) {
+        if (mCallbacks.remove(callback) && mCallbacks.size() == 0) {
+            setListening(false);
+        }
+    }
+
+    public void setWorkModeEnabled(boolean enableWorkMode) {
+        synchronized (mProfiles) {
+            for (UserInfo ui : mProfiles) {
+                if (enableWorkMode) {
+                    if (!mUserManager.trySetQuietModeDisabled(ui.id, null)) {
+                        StatusBarManager statusBarManager = (StatusBarManager) mContext
+                                .getSystemService(android.app.Service.STATUS_BAR_SERVICE);
+                        statusBarManager.collapsePanels();
+                    }
+                } else {
+                    mUserManager.setQuietModeEnabled(ui.id, true);
+                }
+            }
+        }
+    }
+
+    private void reloadManagedProfiles() {
+        synchronized (mProfiles) {
+            boolean hadProfile = mProfiles.size() > 0;
+            int user = ActivityManager.getCurrentUser();
+            mProfiles.clear();
+
+            for (UserInfo ui : mUserManager.getEnabledProfiles(user)) {
+                if (ui.isManagedProfile()) {
+                    mProfiles.add(ui);
+                }
+            }
+            if (mProfiles.size() == 0 && hadProfile && (user == mCurrentUser)) {
+                for (Callback callback : mCallbacks) {
+                    callback.onManagedProfileRemoved();
+                }
+            }
+            mCurrentUser = user;
+        }
+    }
+
+    public boolean hasActiveProfile() {
+        if (!mListening) reloadManagedProfiles();
+        synchronized (mProfiles) {
+            return mProfiles.size() > 0;
+        }
+    }
+
+    public boolean isWorkModeEnabled() {
+        if (!mListening) reloadManagedProfiles();
+        synchronized (mProfiles) {
+            for (UserInfo ui : mProfiles) {
+                if (ui.isQuietModeEnabled()) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    private void setListening(boolean listening) {
+        mListening = listening;
+        if (listening) {
+            reloadManagedProfiles();
+
+            final IntentFilter filter = new IntentFilter();
+            filter.addAction(Intent.ACTION_USER_SWITCHED);
+            filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
+            filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
+            filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
+            filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
+            mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, null);
+        } else {
+            mContext.unregisterReceiver(mReceiver);
+        }
+    }
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            reloadManagedProfiles();
+            for (Callback callback : mCallbacks) {
+                callback.onManagedProfileChanged();
+            }
+        }
+    };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 2c61358..4579ce6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -22,6 +22,7 @@
 import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
 import static android.app.StatusBarManager.windowStateToString;
+
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
@@ -83,8 +84,8 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
-import android.os.Trace;
 import android.os.SystemProperties;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.Vibrator;
@@ -123,13 +124,13 @@
 import com.android.keyguard.KeyguardStatusView;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.LatencyTracker;
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.BatteryMeterView;
 import com.android.systemui.DemoMode;
 import com.android.systemui.EventLogConstants;
 import com.android.systemui.EventLogTags;
 import com.android.systemui.Interpolators;
-import com.android.keyguard.LatencyTracker;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.SystemUIFactory;
@@ -178,19 +179,20 @@
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.statusbar.policy.CastControllerImpl;
 import com.android.systemui.statusbar.policy.EncryptionHelper;
-import com.android.systemui.statusbar.policy.FlashlightController;
+import com.android.systemui.statusbar.policy.FlashlightControllerImpl;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.HotspotControllerImpl;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardMonitorImpl;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
 import com.android.systemui.statusbar.policy.LocationControllerImpl;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.NetworkControllerImpl;
 import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.NextAlarmControllerImpl;
 import com.android.systemui.statusbar.policy.PreviewInflater;
 import com.android.systemui.statusbar.policy.RotationLockControllerImpl;
 import com.android.systemui.statusbar.policy.SecurityControllerImpl;
-import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
@@ -319,15 +321,15 @@
     NetworkControllerImpl mNetworkController;
     HotspotControllerImpl mHotspotController;
     RotationLockControllerImpl mRotationLockController;
-    UserInfoController mUserInfoController;
+    UserInfoControllerImpl mUserInfoController;
     protected ZenModeController mZenModeController;
     CastControllerImpl mCastController;
     VolumeComponent mVolumeComponent;
     KeyguardUserSwitcher mKeyguardUserSwitcher;
-    FlashlightController mFlashlightController;
+    FlashlightControllerImpl mFlashlightController;
     protected UserSwitcherController mUserSwitcherController;
-    NextAlarmController mNextAlarmController;
-    protected KeyguardMonitor mKeyguardMonitor;
+    NextAlarmControllerImpl mNextAlarmController;
+    protected KeyguardMonitorImpl mKeyguardMonitor;
     BrightnessMirrorController mBrightnessMirrorController;
     AccessibilityController mAccessibilityController;
     protected FingerprintUnlockController mFingerprintUnlockController;
@@ -895,7 +897,7 @@
         if (mContext.getResources().getBoolean(R.bool.config_showRotationLock)) {
             mRotationLockController = new RotationLockControllerImpl(mContext);
         }
-        mUserInfoController = new UserInfoController(mContext);
+        mUserInfoController = new UserInfoControllerImpl(mContext);
         mVolumeComponent = getComponent(VolumeComponent.class);
         if (mVolumeComponent != null) {
             mZenModeController = mVolumeComponent.getZenController();
@@ -906,16 +908,16 @@
         initSignalCluster(mKeyguardStatusBar);
         initEmergencyCryptkeeperText();
 
-        mFlashlightController = new FlashlightController(mContext);
+        mFlashlightController = new FlashlightControllerImpl(mContext);
         mKeyguardBottomArea.setFlashlightController(mFlashlightController);
         mKeyguardBottomArea.setPhoneStatusBar(this);
         mKeyguardBottomArea.setUserSetupComplete(mUserSetup);
         mAccessibilityController = new AccessibilityController(mContext);
         mKeyguardBottomArea.setAccessibilityController(mAccessibilityController);
-        mNextAlarmController = new NextAlarmController(mContext);
+        mNextAlarmController = new NextAlarmControllerImpl(mContext);
         mLightStatusBarController = new LightStatusBarController(mIconController,
                 mBatteryController);
-        mKeyguardMonitor = new KeyguardMonitor(mContext);
+        mKeyguardMonitor = new KeyguardMonitorImpl(mContext);
             mUserSwitcherController = createUserSwitcherController();
         if (UserManager.get(mContext).isUserSwitcherEnabled()) {
             createUserSwitcher();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index 8fd6bbf..567ab3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -143,7 +143,7 @@
         mBattery = battery;
         mIconController = iconController;
         mNextAlarmController = nextAlarmController;
-        mProfileController = new ManagedProfileController(this);
+        mProfileController = new ManagedProfileControllerImpl(this);
 
         mHandlerThread = new HandlerThread(QSTileHost.class.getSimpleName(),
                 Process.THREAD_PRIORITY_BACKGROUND);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
index 28aed87..9ab4d77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -236,10 +236,17 @@
     }
 
     @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mHost.getUserInfoController().addCallback(this);
+    }
+
+    @Override
     protected void onDetachedFromWindow() {
         setListening(false);
         mHost.getUserInfoController().removeCallback(this);
         mHost.getNetworkController().removeEmergencyListener(this);
+        mHost.getUserInfoController().removeCallback(this);
         super.onDetachedFromWindow();
     }
 
@@ -368,7 +375,7 @@
     }
 
     public void setUserInfoController(UserInfoController userInfoController) {
-        userInfoController.addCallback(this);
+        // Don't care
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverController.java
index e5f1e68..0df7859 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverController.java
@@ -14,92 +14,14 @@
 
 package com.android.systemui.statusbar.policy;
 
-import android.content.Context;
-import android.net.INetworkPolicyListener;
-import android.net.NetworkPolicyManager;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.RemoteException;
-
 import com.android.systemui.statusbar.policy.DataSaverController.Listener;
 
-import java.util.ArrayList;
+public interface DataSaverController extends CallbackController<Listener> {
 
-public class DataSaverController implements CallbackController<Listener> {
-
-    private final Handler mHandler = new Handler(Looper.getMainLooper());
-    private final ArrayList<Listener> mListeners = new ArrayList<>();
-    private final NetworkPolicyManager mPolicyManager;
-
-    public DataSaverController(Context context) {
-        mPolicyManager = NetworkPolicyManager.from(context);
-    }
-
-    private void handleRestrictBackgroundChanged(boolean isDataSaving) {
-        synchronized (mListeners) {
-            for (int i = 0; i < mListeners.size(); i++) {
-                mListeners.get(i).onDataSaverChanged(isDataSaving);
-            }
-        }
-    }
-
-    public void addCallback(Listener listener) {
-        synchronized (mListeners) {
-            mListeners.add(listener);
-            if (mListeners.size() == 1) {
-                mPolicyManager.registerListener(mPolicyListener);
-            }
-        }
-        listener.onDataSaverChanged(isDataSaverEnabled());
-    }
-
-    public void removeCallback(Listener listener) {
-        synchronized (mListeners) {
-            mListeners.remove(listener);
-            if (mListeners.size() == 0) {
-                mPolicyManager.unregisterListener(mPolicyListener);
-            }
-        }
-    }
-
-    public boolean isDataSaverEnabled() {
-        return mPolicyManager.getRestrictBackground();
-    }
-
-    public void setDataSaverEnabled(boolean enabled) {
-        mPolicyManager.setRestrictBackground(enabled);
-        try {
-            mPolicyListener.onRestrictBackgroundChanged(enabled);
-        } catch (RemoteException e) {
-        }
-    }
-
-    private final INetworkPolicyListener mPolicyListener = new INetworkPolicyListener.Stub() {
-        @Override
-        public void onUidRulesChanged(int uid, int uidRules) throws RemoteException {
-        }
-
-        @Override
-        public void onMeteredIfacesChanged(String[] strings) throws RemoteException {
-        }
-
-        @Override
-        public void onRestrictBackgroundChanged(final boolean isDataSaving) throws RemoteException {
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    handleRestrictBackgroundChanged(isDataSaving);
-                }
-            });
-        }
-
-        @Override
-        public void onUidPoliciesChanged(int uid, int uidPolicies) throws RemoteException {
-        }
-    };
+    boolean isDataSaverEnabled();
+    void setDataSaverEnabled(boolean enabled);
 
     public interface Listener {
         void onDataSaverChanged(boolean isDataSaving);
     }
-
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java
new file mode 100644
index 0000000..2951943
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2016 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.systemui.statusbar.policy;
+
+import android.content.Context;
+import android.net.INetworkPolicyListener;
+import android.net.NetworkPolicyManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+
+import com.android.systemui.statusbar.policy.DataSaverController.Listener;
+
+import java.util.ArrayList;
+
+public class DataSaverControllerImpl implements DataSaverController {
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private final ArrayList<Listener> mListeners = new ArrayList<>();
+    private final NetworkPolicyManager mPolicyManager;
+
+    public DataSaverControllerImpl(Context context) {
+        mPolicyManager = NetworkPolicyManager.from(context);
+    }
+
+    private void handleRestrictBackgroundChanged(boolean isDataSaving) {
+        synchronized (mListeners) {
+            for (int i = 0; i < mListeners.size(); i++) {
+                mListeners.get(i).onDataSaverChanged(isDataSaving);
+            }
+        }
+    }
+
+    public void addCallback(Listener listener) {
+        synchronized (mListeners) {
+            mListeners.add(listener);
+            if (mListeners.size() == 1) {
+                mPolicyManager.registerListener(mPolicyListener);
+            }
+        }
+        listener.onDataSaverChanged(isDataSaverEnabled());
+    }
+
+    public void removeCallback(Listener listener) {
+        synchronized (mListeners) {
+            mListeners.remove(listener);
+            if (mListeners.size() == 0) {
+                mPolicyManager.unregisterListener(mPolicyListener);
+            }
+        }
+    }
+
+    public boolean isDataSaverEnabled() {
+        return mPolicyManager.getRestrictBackground();
+    }
+
+    public void setDataSaverEnabled(boolean enabled) {
+        mPolicyManager.setRestrictBackground(enabled);
+        try {
+            mPolicyListener.onRestrictBackgroundChanged(enabled);
+        } catch (RemoteException e) {
+        }
+    }
+
+    private final INetworkPolicyListener mPolicyListener = new INetworkPolicyListener.Stub() {
+        @Override
+        public void onUidRulesChanged(int uid, int uidRules) throws RemoteException {
+        }
+
+        @Override
+        public void onMeteredIfacesChanged(String[] strings) throws RemoteException {
+        }
+
+        @Override
+        public void onRestrictBackgroundChanged(final boolean isDataSaving) throws RemoteException {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    handleRestrictBackgroundChanged(isDataSaving);
+                }
+            });
+        }
+
+        @Override
+        public void onUidPoliciesChanged(int uid, int uidPolicies) throws RemoteException {
+        }
+    };
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
index 0f77b03..6023f3e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
@@ -1,255 +1,27 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2016 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
+ * 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
+ * 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.systemui.statusbar.policy;
 
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.hardware.camera2.CameraAccessException;
-import android.hardware.camera2.CameraCharacteristics;
-import android.hardware.camera2.CameraManager;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Process;
-import android.text.TextUtils;
-import android.util.Log;
-
 import com.android.systemui.statusbar.policy.FlashlightController.FlashlightListener;
 
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
+public interface FlashlightController extends CallbackController<FlashlightListener> {
 
-/**
- * Manages the flashlight.
- */
-public class FlashlightController implements CallbackController<FlashlightListener> {
-
-    private static final String TAG = "FlashlightController";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-    private static final int DISPATCH_ERROR = 0;
-    private static final int DISPATCH_CHANGED = 1;
-    private static final int DISPATCH_AVAILABILITY_CHANGED = 2;
-
-    private final CameraManager mCameraManager;
-    private final Context mContext;
-    /** Call {@link #ensureHandler()} before using */
-    private Handler mHandler;
-
-    /** Lock on mListeners when accessing */
-    private final ArrayList<WeakReference<FlashlightListener>> mListeners = new ArrayList<>(1);
-
-    /** Lock on {@code this} when accessing */
-    private boolean mFlashlightEnabled;
-
-    private String mCameraId;
-    private boolean mTorchAvailable;
-
-    public FlashlightController(Context context) {
-        mContext = context;
-        mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
-
-        tryInitCamera();
-    }
-
-    private void tryInitCamera() {
-        try {
-            mCameraId = getCameraId();
-        } catch (Throwable e) {
-            Log.e(TAG, "Couldn't initialize.", e);
-            return;
-        }
-
-        if (mCameraId != null) {
-            ensureHandler();
-            mCameraManager.registerTorchCallback(mTorchCallback, mHandler);
-        }
-    }
-
-    public void setFlashlight(boolean enabled) {
-        boolean pendingError = false;
-        synchronized (this) {
-            if (mCameraId == null) return;
-            if (mFlashlightEnabled != enabled) {
-                mFlashlightEnabled = enabled;
-                try {
-                    mCameraManager.setTorchMode(mCameraId, enabled);
-                } catch (CameraAccessException e) {
-                    Log.e(TAG, "Couldn't set torch mode", e);
-                    mFlashlightEnabled = false;
-                    pendingError = true;
-                }
-            }
-        }
-        dispatchModeChanged(mFlashlightEnabled);
-        if (pendingError) {
-            dispatchError();
-        }
-    }
-
-    public boolean hasFlashlight() {
-        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH);
-    }
-
-    public synchronized boolean isEnabled() {
-        return mFlashlightEnabled;
-    }
-
-    public synchronized boolean isAvailable() {
-        return mTorchAvailable;
-    }
-
-    public void addCallback(FlashlightListener l) {
-        synchronized (mListeners) {
-            if (mCameraId == null) {
-                tryInitCamera();
-            }
-            cleanUpListenersLocked(l);
-            mListeners.add(new WeakReference<>(l));
-        }
-    }
-
-    public void removeCallback(FlashlightListener l) {
-        synchronized (mListeners) {
-            cleanUpListenersLocked(l);
-        }
-    }
-
-    private synchronized void ensureHandler() {
-        if (mHandler == null) {
-            HandlerThread thread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
-            thread.start();
-            mHandler = new Handler(thread.getLooper());
-        }
-    }
-
-    private String getCameraId() throws CameraAccessException {
-        String[] ids = mCameraManager.getCameraIdList();
-        for (String id : ids) {
-            CameraCharacteristics c = mCameraManager.getCameraCharacteristics(id);
-            Boolean flashAvailable = c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
-            Integer lensFacing = c.get(CameraCharacteristics.LENS_FACING);
-            if (flashAvailable != null && flashAvailable
-                    && lensFacing != null && lensFacing == CameraCharacteristics.LENS_FACING_BACK) {
-                return id;
-            }
-        }
-        return null;
-    }
-
-    private void dispatchModeChanged(boolean enabled) {
-        dispatchListeners(DISPATCH_CHANGED, enabled);
-    }
-
-    private void dispatchError() {
-        dispatchListeners(DISPATCH_CHANGED, false /* argument (ignored) */);
-    }
-
-    private void dispatchAvailabilityChanged(boolean available) {
-        dispatchListeners(DISPATCH_AVAILABILITY_CHANGED, available);
-    }
-
-    private void dispatchListeners(int message, boolean argument) {
-        synchronized (mListeners) {
-            final int N = mListeners.size();
-            boolean cleanup = false;
-            for (int i = 0; i < N; i++) {
-                FlashlightListener l = mListeners.get(i).get();
-                if (l != null) {
-                    if (message == DISPATCH_ERROR) {
-                        l.onFlashlightError();
-                    } else if (message == DISPATCH_CHANGED) {
-                        l.onFlashlightChanged(argument);
-                    } else if (message == DISPATCH_AVAILABILITY_CHANGED) {
-                        l.onFlashlightAvailabilityChanged(argument);
-                    }
-                } else {
-                    cleanup = true;
-                }
-            }
-            if (cleanup) {
-                cleanUpListenersLocked(null);
-            }
-        }
-    }
-
-    private void cleanUpListenersLocked(FlashlightListener listener) {
-        for (int i = mListeners.size() - 1; i >= 0; i--) {
-            FlashlightListener found = mListeners.get(i).get();
-            if (found == null || found == listener) {
-                mListeners.remove(i);
-            }
-        }
-    }
-
-    private final CameraManager.TorchCallback mTorchCallback =
-            new CameraManager.TorchCallback() {
-
-        @Override
-        public void onTorchModeUnavailable(String cameraId) {
-            if (TextUtils.equals(cameraId, mCameraId)) {
-                setCameraAvailable(false);
-            }
-        }
-
-        @Override
-        public void onTorchModeChanged(String cameraId, boolean enabled) {
-            if (TextUtils.equals(cameraId, mCameraId)) {
-                setCameraAvailable(true);
-                setTorchMode(enabled);
-            }
-        }
-
-        private void setCameraAvailable(boolean available) {
-            boolean changed;
-            synchronized (FlashlightController.this) {
-                changed = mTorchAvailable != available;
-                mTorchAvailable = available;
-            }
-            if (changed) {
-                if (DEBUG) Log.d(TAG, "dispatchAvailabilityChanged(" + available + ")");
-                dispatchAvailabilityChanged(available);
-            }
-        }
-
-        private void setTorchMode(boolean enabled) {
-            boolean changed;
-            synchronized (FlashlightController.this) {
-                changed = mFlashlightEnabled != enabled;
-                mFlashlightEnabled = enabled;
-            }
-            if (changed) {
-                if (DEBUG) Log.d(TAG, "dispatchModeChanged(" + enabled + ")");
-                dispatchModeChanged(enabled);
-            }
-        }
-    };
-
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("FlashlightController state:");
-
-        pw.print("  mCameraId=");
-        pw.println(mCameraId);
-        pw.print("  mFlashlightEnabled=");
-        pw.println(mFlashlightEnabled);
-        pw.print("  mTorchAvailable=");
-        pw.println(mTorchAvailable);
-    }
+    boolean hasFlashlight();
+    void setFlashlight(boolean newState);
+    boolean isAvailable();
+    boolean isEnabled();
 
     public interface FlashlightListener {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
new file mode 100644
index 0000000..008d837
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2014 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.systemui.statusbar.policy;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.systemui.statusbar.policy.FlashlightController.FlashlightListener;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+/**
+ * Manages the flashlight.
+ */
+public class FlashlightControllerImpl implements FlashlightController {
+
+    private static final String TAG = "FlashlightController";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private static final int DISPATCH_ERROR = 0;
+    private static final int DISPATCH_CHANGED = 1;
+    private static final int DISPATCH_AVAILABILITY_CHANGED = 2;
+
+    private final CameraManager mCameraManager;
+    private final Context mContext;
+    /** Call {@link #ensureHandler()} before using */
+    private Handler mHandler;
+
+    /** Lock on mListeners when accessing */
+    private final ArrayList<WeakReference<FlashlightListener>> mListeners = new ArrayList<>(1);
+
+    /** Lock on {@code this} when accessing */
+    private boolean mFlashlightEnabled;
+
+    private String mCameraId;
+    private boolean mTorchAvailable;
+
+    public FlashlightControllerImpl(Context context) {
+        mContext = context;
+        mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
+
+        tryInitCamera();
+    }
+
+    private void tryInitCamera() {
+        try {
+            mCameraId = getCameraId();
+        } catch (Throwable e) {
+            Log.e(TAG, "Couldn't initialize.", e);
+            return;
+        }
+
+        if (mCameraId != null) {
+            ensureHandler();
+            mCameraManager.registerTorchCallback(mTorchCallback, mHandler);
+        }
+    }
+
+    public void setFlashlight(boolean enabled) {
+        boolean pendingError = false;
+        synchronized (this) {
+            if (mCameraId == null) return;
+            if (mFlashlightEnabled != enabled) {
+                mFlashlightEnabled = enabled;
+                try {
+                    mCameraManager.setTorchMode(mCameraId, enabled);
+                } catch (CameraAccessException e) {
+                    Log.e(TAG, "Couldn't set torch mode", e);
+                    mFlashlightEnabled = false;
+                    pendingError = true;
+                }
+            }
+        }
+        dispatchModeChanged(mFlashlightEnabled);
+        if (pendingError) {
+            dispatchError();
+        }
+    }
+
+    public boolean hasFlashlight() {
+        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH);
+    }
+
+    public synchronized boolean isEnabled() {
+        return mFlashlightEnabled;
+    }
+
+    public synchronized boolean isAvailable() {
+        return mTorchAvailable;
+    }
+
+    public void addCallback(FlashlightListener l) {
+        synchronized (mListeners) {
+            if (mCameraId == null) {
+                tryInitCamera();
+            }
+            cleanUpListenersLocked(l);
+            mListeners.add(new WeakReference<>(l));
+        }
+    }
+
+    public void removeCallback(FlashlightListener l) {
+        synchronized (mListeners) {
+            cleanUpListenersLocked(l);
+        }
+    }
+
+    private synchronized void ensureHandler() {
+        if (mHandler == null) {
+            HandlerThread thread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
+            thread.start();
+            mHandler = new Handler(thread.getLooper());
+        }
+    }
+
+    private String getCameraId() throws CameraAccessException {
+        String[] ids = mCameraManager.getCameraIdList();
+        for (String id : ids) {
+            CameraCharacteristics c = mCameraManager.getCameraCharacteristics(id);
+            Boolean flashAvailable = c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
+            Integer lensFacing = c.get(CameraCharacteristics.LENS_FACING);
+            if (flashAvailable != null && flashAvailable
+                    && lensFacing != null && lensFacing == CameraCharacteristics.LENS_FACING_BACK) {
+                return id;
+            }
+        }
+        return null;
+    }
+
+    private void dispatchModeChanged(boolean enabled) {
+        dispatchListeners(DISPATCH_CHANGED, enabled);
+    }
+
+    private void dispatchError() {
+        dispatchListeners(DISPATCH_CHANGED, false /* argument (ignored) */);
+    }
+
+    private void dispatchAvailabilityChanged(boolean available) {
+        dispatchListeners(DISPATCH_AVAILABILITY_CHANGED, available);
+    }
+
+    private void dispatchListeners(int message, boolean argument) {
+        synchronized (mListeners) {
+            final int N = mListeners.size();
+            boolean cleanup = false;
+            for (int i = 0; i < N; i++) {
+                FlashlightListener l = mListeners.get(i).get();
+                if (l != null) {
+                    if (message == DISPATCH_ERROR) {
+                        l.onFlashlightError();
+                    } else if (message == DISPATCH_CHANGED) {
+                        l.onFlashlightChanged(argument);
+                    } else if (message == DISPATCH_AVAILABILITY_CHANGED) {
+                        l.onFlashlightAvailabilityChanged(argument);
+                    }
+                } else {
+                    cleanup = true;
+                }
+            }
+            if (cleanup) {
+                cleanUpListenersLocked(null);
+            }
+        }
+    }
+
+    private void cleanUpListenersLocked(FlashlightListener listener) {
+        for (int i = mListeners.size() - 1; i >= 0; i--) {
+            FlashlightListener found = mListeners.get(i).get();
+            if (found == null || found == listener) {
+                mListeners.remove(i);
+            }
+        }
+    }
+
+    private final CameraManager.TorchCallback mTorchCallback =
+            new CameraManager.TorchCallback() {
+
+        @Override
+        public void onTorchModeUnavailable(String cameraId) {
+            if (TextUtils.equals(cameraId, mCameraId)) {
+                setCameraAvailable(false);
+            }
+        }
+
+        @Override
+        public void onTorchModeChanged(String cameraId, boolean enabled) {
+            if (TextUtils.equals(cameraId, mCameraId)) {
+                setCameraAvailable(true);
+                setTorchMode(enabled);
+            }
+        }
+
+        private void setCameraAvailable(boolean available) {
+            boolean changed;
+            synchronized (FlashlightControllerImpl.this) {
+                changed = mTorchAvailable != available;
+                mTorchAvailable = available;
+            }
+            if (changed) {
+                if (DEBUG) Log.d(TAG, "dispatchAvailabilityChanged(" + available + ")");
+                dispatchAvailabilityChanged(available);
+            }
+        }
+
+        private void setTorchMode(boolean enabled) {
+            boolean changed;
+            synchronized (FlashlightControllerImpl.this) {
+                changed = mFlashlightEnabled != enabled;
+                mFlashlightEnabled = enabled;
+            }
+            if (changed) {
+                if (DEBUG) Log.d(TAG, "dispatchModeChanged(" + enabled + ")");
+                dispatchModeChanged(enabled);
+            }
+        }
+    };
+
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("FlashlightController state:");
+
+        pw.print("  mCameraId=");
+        pw.println(mCameraId);
+        pw.print("  mFlashlightEnabled=");
+        pw.println(mFlashlightEnabled);
+        pw.print("  mTorchAvailable=");
+        pw.println(mTorchAvailable);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
index 0396613..de47267 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
@@ -1,126 +1,28 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2016 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
+ * 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.
+ * 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.systemui.statusbar.policy;
 
-import android.app.ActivityManager;
-import android.content.Context;
-import android.os.RemoteException;
-import android.view.WindowManagerGlobal;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.systemui.settings.CurrentUserTracker;
 import com.android.systemui.statusbar.policy.KeyguardMonitor.Callback;
 
-import java.util.ArrayList;
+public interface KeyguardMonitor extends CallbackController<Callback> {
 
-public class KeyguardMonitor extends KeyguardUpdateMonitorCallback
-        implements CallbackController<Callback> {
-
-    private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
-
-    private final Context mContext;
-    private final CurrentUserTracker mUserTracker;
-    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-
-    private int mCurrentUser;
-    private boolean mShowing;
-    private boolean mSecure;
-    private boolean mOccluded;
-    private boolean mCanSkipBouncer;
-
-    private boolean mListening;
-
-    public KeyguardMonitor(Context context) {
-        mContext = context;
-        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
-        mUserTracker = new CurrentUserTracker(mContext) {
-            @Override
-            public void onUserSwitched(int newUserId) {
-                mCurrentUser = newUserId;
-                updateCanSkipBouncerState();
-            }
-        };
-    }
-
-    public void addCallback(Callback callback) {
-        mCallbacks.add(callback);
-        if (mCallbacks.size() != 0 && !mListening) {
-            mListening = true;
-            mCurrentUser = ActivityManager.getCurrentUser();
-            updateCanSkipBouncerState();
-            mKeyguardUpdateMonitor.registerCallback(this);
-            mUserTracker.startTracking();
-        }
-    }
-
-    public void removeCallback(Callback callback) {
-        if (mCallbacks.remove(callback) && mCallbacks.size() == 0 && mListening) {
-            mListening = false;
-            mKeyguardUpdateMonitor.removeCallback(this);
-            mUserTracker.stopTracking();
-        }
-    }
-
-    public boolean isShowing() {
-        return mShowing;
-    }
-
-    public boolean isSecure() {
-        return mSecure;
-    }
-
-    public boolean isOccluded() {
-        return mOccluded;
-    }
-
-    public boolean canSkipBouncer() {
-        return mCanSkipBouncer;
-    }
-
-    public void notifyKeyguardState(boolean showing, boolean secure, boolean occluded) {
-        if (mShowing == showing && mSecure == secure && mOccluded == occluded) return;
-        mShowing = showing;
-        mSecure = secure;
-        mOccluded = occluded;
-        notifyKeyguardChanged();
-    }
-
-    @Override
-    public void onTrustChanged(int userId) {
-        updateCanSkipBouncerState();
-        notifyKeyguardChanged();
-    }
-
-    public boolean isDeviceInteractive() {
-        return mKeyguardUpdateMonitor.isDeviceInteractive();
-    }
-
-    private void updateCanSkipBouncerState() {
-        mCanSkipBouncer = mKeyguardUpdateMonitor.getUserCanSkipBouncer(mCurrentUser);
-    }
-
-    private void notifyKeyguardChanged() {
-        for (Callback callback : mCallbacks) {
-            callback.onKeyguardChanged();
-        }
-    }
+    boolean isSecure();
+    boolean canSkipBouncer();
+    boolean isShowing();
 
     public interface Callback {
         void onKeyguardChanged();
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java
new file mode 100644
index 0000000..769f93f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2014 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.systemui.statusbar.policy;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.os.RemoteException;
+import android.view.WindowManagerGlobal;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.statusbar.policy.KeyguardMonitor.Callback;
+
+import java.util.ArrayList;
+
+public class KeyguardMonitorImpl extends KeyguardUpdateMonitorCallback
+        implements KeyguardMonitor {
+
+    private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
+
+    private final Context mContext;
+    private final CurrentUserTracker mUserTracker;
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+
+    private int mCurrentUser;
+    private boolean mShowing;
+    private boolean mSecure;
+    private boolean mOccluded;
+    private boolean mCanSkipBouncer;
+
+    private boolean mListening;
+
+    public KeyguardMonitorImpl(Context context) {
+        mContext = context;
+        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+        mUserTracker = new CurrentUserTracker(mContext) {
+            @Override
+            public void onUserSwitched(int newUserId) {
+                mCurrentUser = newUserId;
+                updateCanSkipBouncerState();
+            }
+        };
+    }
+
+    public void addCallback(Callback callback) {
+        mCallbacks.add(callback);
+        if (mCallbacks.size() != 0 && !mListening) {
+            mListening = true;
+            mCurrentUser = ActivityManager.getCurrentUser();
+            updateCanSkipBouncerState();
+            mKeyguardUpdateMonitor.registerCallback(this);
+            mUserTracker.startTracking();
+        }
+    }
+
+    public void removeCallback(Callback callback) {
+        if (mCallbacks.remove(callback) && mCallbacks.size() == 0 && mListening) {
+            mListening = false;
+            mKeyguardUpdateMonitor.removeCallback(this);
+            mUserTracker.stopTracking();
+        }
+    }
+
+    public boolean isShowing() {
+        return mShowing;
+    }
+
+    public boolean isSecure() {
+        return mSecure;
+    }
+
+    public boolean isOccluded() {
+        return mOccluded;
+    }
+
+    public boolean canSkipBouncer() {
+        return mCanSkipBouncer;
+    }
+
+    public void notifyKeyguardState(boolean showing, boolean secure, boolean occluded) {
+        if (mShowing == showing && mSecure == secure && mOccluded == occluded) return;
+        mShowing = showing;
+        mSecure = secure;
+        mOccluded = occluded;
+        notifyKeyguardChanged();
+    }
+
+    @Override
+    public void onTrustChanged(int userId) {
+        updateCanSkipBouncerState();
+        notifyKeyguardChanged();
+    }
+
+    public boolean isDeviceInteractive() {
+        return mKeyguardUpdateMonitor.isDeviceInteractive();
+    }
+
+    private void updateCanSkipBouncerState() {
+        mCanSkipBouncer = mKeyguardUpdateMonitor.getUserCanSkipBouncer(mCurrentUser);
+    }
+
+    private void notifyKeyguardChanged() {
+        for (Callback callback : mCallbacks) {
+            callback.onKeyguardChanged();
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 1a9756f..a7fab41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -159,7 +159,7 @@
         mConfig = config;
         mReceiverHandler = new Handler(bgLooper);
         mCallbackHandler = callbackHandler;
-        mDataSaverController = new DataSaverController(context);
+        mDataSaverController = new DataSaverControllerImpl(context);
 
         mSubscriptionManager = subManager;
         mSubDefaults = defaultsHandler;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmController.java
index 28935bf..e5b0c03 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmController.java
@@ -1,84 +1,24 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2016 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
+ * 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
+ * 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.systemui.statusbar.policy;
 
 import android.app.AlarmManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.UserHandle;
 
 import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
 
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
-public class NextAlarmController extends BroadcastReceiver
-        implements CallbackController<NextAlarmChangeCallback> {
-
-    private final ArrayList<NextAlarmChangeCallback> mChangeCallbacks = new ArrayList<>();
-
-    private AlarmManager mAlarmManager;
-    private AlarmManager.AlarmClockInfo mNextAlarm;
-
-    public NextAlarmController(Context context) {
-        mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_USER_SWITCHED);
-        filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
-        context.registerReceiverAsUser(this, UserHandle.ALL, filter, null, null);
-        updateNextAlarm();
-    }
-
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("NextAlarmController state:");
-        pw.print("  mNextAlarm="); pw.println(mNextAlarm);
-    }
-
-    public void addCallback(NextAlarmChangeCallback cb) {
-        mChangeCallbacks.add(cb);
-        cb.onNextAlarmChanged(mNextAlarm);
-    }
-
-    public void removeCallback(NextAlarmChangeCallback cb) {
-        mChangeCallbacks.remove(cb);
-    }
-
-    public void onReceive(Context context, Intent intent) {
-        final String action = intent.getAction();
-        if (action.equals(Intent.ACTION_USER_SWITCHED)
-                || action.equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) {
-            updateNextAlarm();
-        }
-    }
-
-    private void updateNextAlarm() {
-        mNextAlarm = mAlarmManager.getNextAlarmClock(UserHandle.USER_CURRENT);
-        fireNextAlarmChanged();
-    }
-
-    private void fireNextAlarmChanged() {
-        int n = mChangeCallbacks.size();
-        for (int i = 0; i < n; i++) {
-            mChangeCallbacks.get(i).onNextAlarmChanged(mNextAlarm);
-        }
-    }
+public interface NextAlarmController extends CallbackController<NextAlarmChangeCallback> {
 
     public interface NextAlarmChangeCallback {
         void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
new file mode 100644
index 0000000..dfdeae1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2014 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.systemui.statusbar.policy;
+
+import android.app.AlarmManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.UserHandle;
+
+import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+public class NextAlarmControllerImpl extends BroadcastReceiver
+        implements NextAlarmController {
+
+    private final ArrayList<NextAlarmChangeCallback> mChangeCallbacks = new ArrayList<>();
+
+    private AlarmManager mAlarmManager;
+    private AlarmManager.AlarmClockInfo mNextAlarm;
+
+    public NextAlarmControllerImpl(Context context) {
+        mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_USER_SWITCHED);
+        filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
+        context.registerReceiverAsUser(this, UserHandle.ALL, filter, null, null);
+        updateNextAlarm();
+    }
+
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("NextAlarmController state:");
+        pw.print("  mNextAlarm="); pw.println(mNextAlarm);
+    }
+
+    public void addCallback(NextAlarmChangeCallback cb) {
+        mChangeCallbacks.add(cb);
+        cb.onNextAlarmChanged(mNextAlarm);
+    }
+
+    public void removeCallback(NextAlarmChangeCallback cb) {
+        mChangeCallbacks.remove(cb);
+    }
+
+    public void onReceive(Context context, Intent intent) {
+        final String action = intent.getAction();
+        if (action.equals(Intent.ACTION_USER_SWITCHED)
+                || action.equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) {
+            updateNextAlarm();
+        }
+    }
+
+    private void updateNextAlarm() {
+        mNextAlarm = mAlarmManager.getNextAlarmClock(UserHandle.USER_CURRENT);
+        fireNextAlarmChanged();
+    }
+
+    private void fireNextAlarmChanged() {
+        int n = mChangeCallbacks.size();
+        for (int i = 0; i < n; i++) {
+            mChangeCallbacks.get(i).onNextAlarmChanged(mNextAlarm);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java
index c09747b..1e23a20 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java
@@ -1,233 +1,28 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2016 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
+ * 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
+ * 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.systemui.statusbar.policy;
 
-import android.app.ActivityManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
-import android.os.AsyncTask;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.ContactsContract;
-import android.util.Log;
 
-import com.android.internal.util.UserIcons;
-import com.android.settingslib.drawable.UserIconDrawable;
-import com.android.systemui.R;
 import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
 
-import java.util.ArrayList;
+public interface UserInfoController extends CallbackController<OnUserInfoChangedListener> {
 
-public class UserInfoController implements CallbackController<OnUserInfoChangedListener> {
-
-    private static final String TAG = "UserInfoController";
-
-    private final Context mContext;
-    private final ArrayList<OnUserInfoChangedListener> mCallbacks =
-            new ArrayList<OnUserInfoChangedListener>();
-    private AsyncTask<Void, Void, UserInfoQueryResult> mUserInfoTask;
-
-    private String mUserName;
-    private Drawable mUserDrawable;
-    private String mUserAccount;
-
-    public UserInfoController(Context context) {
-        mContext = context;
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_USER_SWITCHED);
-        mContext.registerReceiver(mReceiver, filter);
-
-        IntentFilter profileFilter = new IntentFilter();
-        profileFilter.addAction(ContactsContract.Intents.ACTION_PROFILE_CHANGED);
-        profileFilter.addAction(Intent.ACTION_USER_INFO_CHANGED);
-        mContext.registerReceiverAsUser(mProfileReceiver, UserHandle.ALL, profileFilter,
-                null, null);
-    }
-
-    public void addCallback(OnUserInfoChangedListener callback) {
-        mCallbacks.add(callback);
-        callback.onUserInfoChanged(mUserName, mUserDrawable, mUserAccount);
-    }
-
-    public void removeCallback(OnUserInfoChangedListener callback) {
-        mCallbacks.remove(callback);
-    }
-
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (Intent.ACTION_USER_SWITCHED.equals(action)) {
-                reloadUserInfo();
-            }
-        }
-    };
-
-    private final BroadcastReceiver mProfileReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (ContactsContract.Intents.ACTION_PROFILE_CHANGED.equals(action) ||
-                    Intent.ACTION_USER_INFO_CHANGED.equals(action)) {
-                try {
-                    final int currentUser = ActivityManager.getService().getCurrentUser().id;
-                    final int changedUser =
-                            intent.getIntExtra(Intent.EXTRA_USER_HANDLE, getSendingUserId());
-                    if (changedUser == currentUser) {
-                        reloadUserInfo();
-                    }
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Couldn't get current user id for profile change", e);
-                }
-            }
-        }
-    };
-
-    public void reloadUserInfo() {
-        if (mUserInfoTask != null) {
-            mUserInfoTask.cancel(false);
-            mUserInfoTask = null;
-        }
-        queryForUserInformation();
-    }
-
-    private void queryForUserInformation() {
-        Context currentUserContext;
-        UserInfo userInfo;
-        try {
-            userInfo = ActivityManager.getService().getCurrentUser();
-            currentUserContext = mContext.createPackageContextAsUser("android", 0,
-                    new UserHandle(userInfo.id));
-        } catch (PackageManager.NameNotFoundException e) {
-            Log.e(TAG, "Couldn't create user context", e);
-            throw new RuntimeException(e);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Couldn't get user info", e);
-            throw new RuntimeException(e);
-        }
-        final int userId = userInfo.id;
-        final boolean isGuest = userInfo.isGuest();
-        final String userName = userInfo.name;
-
-        final Resources res = mContext.getResources();
-        final int avatarSize = Math.max(
-                res.getDimensionPixelSize(R.dimen.multi_user_avatar_expanded_size),
-                res.getDimensionPixelSize(R.dimen.multi_user_avatar_keyguard_size));
-
-        final Context context = currentUserContext;
-        mUserInfoTask = new AsyncTask<Void, Void, UserInfoQueryResult>() {
-
-            @Override
-            protected UserInfoQueryResult doInBackground(Void... params) {
-                final UserManager um = UserManager.get(mContext);
-
-                // Fall back to the UserManager nickname if we can't read the name from the local
-                // profile below.
-                String name = userName;
-                Drawable avatar = null;
-                Bitmap rawAvatar = um.getUserIcon(userId);
-                if (rawAvatar != null) {
-                    avatar = new UserIconDrawable(avatarSize)
-                            .setIcon(rawAvatar).setBadgeIfManagedUser(mContext, userId).bake();
-                } else {
-                    avatar = UserIcons.getDefaultUserIcon(isGuest? UserHandle.USER_NULL : userId,
-                            /* light= */ true);
-                }
-
-                // If it's a single-user device, get the profile name, since the nickname is not
-                // usually valid
-                if (um.getUsers().size() <= 1) {
-                    // Try and read the display name from the local profile
-                    final Cursor cursor = context.getContentResolver().query(
-                            ContactsContract.Profile.CONTENT_URI, new String[] {
-                                    ContactsContract.CommonDataKinds.Phone._ID,
-                                    ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME
-                            }, null, null, null);
-                    if (cursor != null) {
-                        try {
-                            if (cursor.moveToFirst()) {
-                                name = cursor.getString(cursor.getColumnIndex(
-                                        ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
-                            }
-                        } finally {
-                            cursor.close();
-                        }
-                    }
-                }
-                String userAccount = um.getUserAccount(userId);
-                return new UserInfoQueryResult(name, avatar, userAccount);
-            }
-
-            @Override
-            protected void onPostExecute(UserInfoQueryResult result) {
-                mUserName = result.getName();
-                mUserDrawable = result.getAvatar();
-                mUserAccount = result.getUserAccount();
-                mUserInfoTask = null;
-                notifyChanged();
-            }
-        };
-        mUserInfoTask.execute();
-    }
-
-    private void notifyChanged() {
-        for (OnUserInfoChangedListener listener : mCallbacks) {
-            listener.onUserInfoChanged(mUserName, mUserDrawable, mUserAccount);
-        }
-    }
-
-    public void onDensityOrFontScaleChanged() {
-        reloadUserInfo();
-    }
+    void reloadUserInfo();
 
     public interface OnUserInfoChangedListener {
         public void onUserInfoChanged(String name, Drawable picture, String userAccount);
     }
-
-    private static class UserInfoQueryResult {
-        private String mName;
-        private Drawable mAvatar;
-        private String mUserAccount;
-
-        public UserInfoQueryResult(String name, Drawable avatar, String userAccount) {
-            mName = name;
-            mAvatar = avatar;
-            mUserAccount = userAccount;
-        }
-
-        public String getName() {
-            return mName;
-        }
-
-        public Drawable getAvatar() {
-            return mAvatar;
-        }
-
-        public String getUserAccount() {
-            return mUserAccount;
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
new file mode 100644
index 0000000..b1e4b03
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2014 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.systemui.statusbar.policy;
+
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.ContactsContract;
+import android.util.Log;
+
+import com.android.internal.util.UserIcons;
+import com.android.settingslib.drawable.UserIconDrawable;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
+
+import java.util.ArrayList;
+
+public class UserInfoControllerImpl implements UserInfoController {
+
+    private static final String TAG = "UserInfoController";
+
+    private final Context mContext;
+    private final ArrayList<OnUserInfoChangedListener> mCallbacks =
+            new ArrayList<OnUserInfoChangedListener>();
+    private AsyncTask<Void, Void, UserInfoQueryResult> mUserInfoTask;
+
+    private String mUserName;
+    private Drawable mUserDrawable;
+    private String mUserAccount;
+
+    public UserInfoControllerImpl(Context context) {
+        mContext = context;
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_USER_SWITCHED);
+        mContext.registerReceiver(mReceiver, filter);
+
+        IntentFilter profileFilter = new IntentFilter();
+        profileFilter.addAction(ContactsContract.Intents.ACTION_PROFILE_CHANGED);
+        profileFilter.addAction(Intent.ACTION_USER_INFO_CHANGED);
+        mContext.registerReceiverAsUser(mProfileReceiver, UserHandle.ALL, profileFilter,
+                null, null);
+    }
+
+    public void addCallback(OnUserInfoChangedListener callback) {
+        mCallbacks.add(callback);
+        callback.onUserInfoChanged(mUserName, mUserDrawable, mUserAccount);
+    }
+
+    public void removeCallback(OnUserInfoChangedListener callback) {
+        mCallbacks.remove(callback);
+    }
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+                reloadUserInfo();
+            }
+        }
+    };
+
+    private final BroadcastReceiver mProfileReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (ContactsContract.Intents.ACTION_PROFILE_CHANGED.equals(action) ||
+                    Intent.ACTION_USER_INFO_CHANGED.equals(action)) {
+                try {
+                    final int currentUser = ActivityManager.getService().getCurrentUser().id;
+                    final int changedUser =
+                            intent.getIntExtra(Intent.EXTRA_USER_HANDLE, getSendingUserId());
+                    if (changedUser == currentUser) {
+                        reloadUserInfo();
+                    }
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Couldn't get current user id for profile change", e);
+                }
+            }
+        }
+    };
+
+    public void reloadUserInfo() {
+        if (mUserInfoTask != null) {
+            mUserInfoTask.cancel(false);
+            mUserInfoTask = null;
+        }
+        queryForUserInformation();
+    }
+
+    private void queryForUserInformation() {
+        Context currentUserContext;
+        UserInfo userInfo;
+        try {
+            userInfo = ActivityManager.getService().getCurrentUser();
+            currentUserContext = mContext.createPackageContextAsUser("android", 0,
+                    new UserHandle(userInfo.id));
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(TAG, "Couldn't create user context", e);
+            throw new RuntimeException(e);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Couldn't get user info", e);
+            throw new RuntimeException(e);
+        }
+        final int userId = userInfo.id;
+        final boolean isGuest = userInfo.isGuest();
+        final String userName = userInfo.name;
+
+        final Resources res = mContext.getResources();
+        final int avatarSize = Math.max(
+                res.getDimensionPixelSize(R.dimen.multi_user_avatar_expanded_size),
+                res.getDimensionPixelSize(R.dimen.multi_user_avatar_keyguard_size));
+
+        final Context context = currentUserContext;
+        mUserInfoTask = new AsyncTask<Void, Void, UserInfoQueryResult>() {
+
+            @Override
+            protected UserInfoQueryResult doInBackground(Void... params) {
+                final UserManager um = UserManager.get(mContext);
+
+                // Fall back to the UserManager nickname if we can't read the name from the local
+                // profile below.
+                String name = userName;
+                Drawable avatar = null;
+                Bitmap rawAvatar = um.getUserIcon(userId);
+                if (rawAvatar != null) {
+                    avatar = new UserIconDrawable(avatarSize)
+                            .setIcon(rawAvatar).setBadgeIfManagedUser(mContext, userId).bake();
+                } else {
+                    avatar = UserIcons.getDefaultUserIcon(isGuest? UserHandle.USER_NULL : userId,
+                            /* light= */ true);
+                }
+
+                // If it's a single-user device, get the profile name, since the nickname is not
+                // usually valid
+                if (um.getUsers().size() <= 1) {
+                    // Try and read the display name from the local profile
+                    final Cursor cursor = context.getContentResolver().query(
+                            ContactsContract.Profile.CONTENT_URI, new String[] {
+                                    ContactsContract.CommonDataKinds.Phone._ID,
+                                    ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME
+                            }, null, null, null);
+                    if (cursor != null) {
+                        try {
+                            if (cursor.moveToFirst()) {
+                                name = cursor.getString(cursor.getColumnIndex(
+                                        ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
+                            }
+                        } finally {
+                            cursor.close();
+                        }
+                    }
+                }
+                String userAccount = um.getUserAccount(userId);
+                return new UserInfoQueryResult(name, avatar, userAccount);
+            }
+
+            @Override
+            protected void onPostExecute(UserInfoQueryResult result) {
+                mUserName = result.getName();
+                mUserDrawable = result.getAvatar();
+                mUserAccount = result.getUserAccount();
+                mUserInfoTask = null;
+                notifyChanged();
+            }
+        };
+        mUserInfoTask.execute();
+    }
+
+    private void notifyChanged() {
+        for (OnUserInfoChangedListener listener : mCallbacks) {
+            listener.onUserInfoChanged(mUserName, mUserDrawable, mUserAccount);
+        }
+    }
+
+    public void onDensityOrFontScaleChanged() {
+        reloadUserInfo();
+    }
+
+    private static class UserInfoQueryResult {
+        private String mName;
+        private Drawable mAvatar;
+        private String mUserAccount;
+
+        public UserInfoQueryResult(String name, Drawable avatar, String userAccount) {
+            mName = name;
+            mAvatar = avatar;
+            mUserAccount = userAccount;
+        }
+
+        public String getName() {
+            return mName;
+        }
+
+        public Drawable getAvatar() {
+            return mAvatar;
+        }
+
+        public String getUserAccount() {
+            return mUserAccount;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
index ebc962d..565ac08 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -92,6 +92,10 @@
         mUserTracker.startTracking();
     }
 
+    public void destroy() {
+        mUserTracker.stopTracking();
+    }
+
     private void upgradeTuner(int oldVersion, int newVersion) {
         if (oldVersion < 1) {
             String blacklistStr = getValue(StatusBarIconController.ICON_BLACKLIST);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/FragmentTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/FragmentTestCase.java
index f87336c..447edac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/FragmentTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/FragmentTestCase.java
@@ -26,6 +26,8 @@
 import android.view.View;
 import android.widget.FrameLayout;
 
+import com.android.systemui.utils.leaks.LeakCheckedTest;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -139,7 +141,7 @@
 
     private class HostCallbacks extends FragmentHostCallback<FragmentTestCase> {
         public HostCallbacks() {
-            super(getTrackedContext(), FragmentTestCase.this.mHandler, 0);
+            super(mContext, FragmentTestCase.this.mHandler, 0);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/LeakCheckedTest.java b/packages/SystemUI/tests/src/com/android/systemui/LeakCheckedTest.java
deleted file mode 100644
index d64669d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/LeakCheckedTest.java
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * Copyright (C) 2016 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.systemui;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.content.BroadcastReceiver;
-import android.content.ComponentCallbacks;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.ServiceConnection;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.util.ArrayMap;
-import android.util.Log;
-
-import com.android.systemui.statusbar.phone.ManagedProfileController;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.BluetoothController;
-import com.android.systemui.statusbar.policy.CastController;
-import com.android.systemui.statusbar.policy.DataSaverController;
-import com.android.systemui.statusbar.policy.FlashlightController;
-import com.android.systemui.statusbar.policy.HotspotController;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
-import com.android.systemui.statusbar.policy.LocationController;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.NextAlarmController;
-import com.android.systemui.statusbar.policy.RotationLockController;
-import com.android.systemui.statusbar.policy.SecurityController;
-import com.android.systemui.statusbar.policy.CallbackController;
-import com.android.systemui.statusbar.policy.UserInfoController;
-import com.android.systemui.statusbar.policy.ZenModeController;
-
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.rules.TestWatcher;
-import org.junit.runner.Description;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Base class for tests to check if receivers are left registered, services bound, or other
- * listeners listening.
- */
-public class LeakCheckedTest extends SysuiTestCase {
-    private static final String TAG = "LeakCheckedTest";
-
-    private final Map<String, Tracker> mTrackers = new HashMap<>();
-    private final Map<Class, Object> mLeakCheckers = new ArrayMap<>();
-    private TrackingContext mTrackedContext;
-
-    @Rule
-    public TestWatcher successWatcher = new TestWatcher() {
-        @Override
-        protected void succeeded(Description description) {
-            verify();
-        }
-    };
-
-    @Before
-    public void setup() {
-        mTrackedContext = new TrackingContext(mContext);
-        addSupportedLeakCheckers();
-    }
-
-    public <T> T getLeakChecker(Class<T> cls) {
-        T obj = (T) mLeakCheckers.get(cls);
-        if (obj == null) {
-            Assert.fail(cls.getName() + " is not supported by LeakCheckedTest yet");
-        }
-        return obj;
-    }
-
-    public Context getTrackedContext() {
-        return mTrackedContext;
-    }
-
-    private Tracker getTracker(String tag) {
-        Tracker t = mTrackers.get(tag);
-        if (t == null) {
-            t = new Tracker();
-            mTrackers.put(tag, t);
-        }
-        return t;
-    }
-
-    public void verify() {
-        mTrackers.values().forEach(Tracker::verify);
-    }
-
-    public static class Tracker {
-        private Map<Object, LeakInfo> mObjects = new ArrayMap<>();
-
-        LeakInfo getLeakInfo(Object object) {
-            LeakInfo leakInfo = mObjects.get(object);
-            if (leakInfo == null) {
-                leakInfo = new LeakInfo();
-                mObjects.put(object, leakInfo);
-            }
-            return leakInfo;
-        }
-
-        private void verify() {
-            mObjects.values().forEach(LeakInfo::verify);
-        }
-    }
-
-    public static class LeakInfo {
-        private List<Throwable> mThrowables = new ArrayList<>();
-
-        private LeakInfo() {
-        }
-
-        private void addAllocation(Throwable t) {
-            // TODO: Drop off the first element in the stack trace here to have a cleaner stack.
-            mThrowables.add(t);
-        }
-
-        private void clearAllocations() {
-            mThrowables.clear();
-        }
-
-        public void verify() {
-            if (mThrowables.size() == 0) return;
-            Log.e(TAG, "Listener or binding not properly released");
-            for (Throwable t : mThrowables) {
-                Log.e(TAG, "Allocation found", t);
-            }
-            StringWriter writer = new StringWriter();
-            mThrowables.get(0).printStackTrace(new PrintWriter(writer));
-            Assert.fail("Listener or binding not properly released\n"
-                    + writer.toString());
-        }
-    }
-
-    private void addSupportedLeakCheckers() {
-        addListening("bluetooth", BluetoothController.class);
-        addListening("location", LocationController.class);
-        addListening("rotation", RotationLockController.class);
-        addListening("zen", ZenModeController.class);
-        addListening("cast", CastController.class);
-        addListening("hotspot", HotspotController.class);
-        addListening("flashlight", FlashlightController.class);
-        addListening("user", UserInfoController.class);
-        addListening("keyguard", KeyguardMonitor.class);
-        addListening("battery", BatteryController.class);
-        addListening("security", SecurityController.class);
-        addListening("profile", ManagedProfileController.class);
-        addListening("alarm", NextAlarmController.class);
-        NetworkController network = addListening("network", NetworkController.class);
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) throws Throwable {
-                getTracker("emergency").getLeakInfo(invocation.getArguments()[0])
-                        .addAllocation(new Throwable());
-                return null;
-            }
-        }).when(network).addEmergencyListener(any());
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) throws Throwable {
-                getTracker("emergency").getLeakInfo(invocation.getArguments()[0]).clearAllocations();
-                return null;
-            }
-        }).when(network).removeEmergencyListener(any());
-        DataSaverController datasaver = addListening("datasaver", DataSaverController.class);
-        when(network.getDataSaverController()).thenReturn(datasaver);
-    }
-
-    private <T extends CallbackController> T addListening(final String tag, Class<T> cls) {
-        T mock = mock(cls);
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) throws Throwable {
-                getTracker(tag).getLeakInfo(invocation.getArguments()[0])
-                        .addAllocation(new Throwable());
-                return null;
-            }
-        }).when(mock).addCallback(any());
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) throws Throwable {
-                getTracker(tag).getLeakInfo(invocation.getArguments()[0]).clearAllocations();
-                return null;
-            }
-        }).when(mock).removeCallback(any());
-        mLeakCheckers.put(cls, mock);
-        return mock;
-    }
-
-    class TrackingContext extends ContextWrapper {
-        public TrackingContext(Context base) {
-            super(base);
-        }
-
-        @Override
-        public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
-            getTracker("receiver").getLeakInfo(receiver).addAllocation(new Throwable());
-            return super.registerReceiver(receiver, filter);
-        }
-
-        @Override
-        public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
-                String broadcastPermission, Handler scheduler) {
-            getTracker("receiver").getLeakInfo(receiver).addAllocation(new Throwable());
-            return super.registerReceiver(receiver, filter, broadcastPermission, scheduler);
-        }
-
-        @Override
-        public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
-                IntentFilter filter, String broadcastPermission, Handler scheduler) {
-            getTracker("receiver").getLeakInfo(receiver).addAllocation(new Throwable());
-            return super.registerReceiverAsUser(receiver, user, filter, broadcastPermission,
-                    scheduler);
-        }
-
-        @Override
-        public void unregisterReceiver(BroadcastReceiver receiver) {
-            getTracker("receiver").getLeakInfo(receiver).clearAllocations();
-            super.unregisterReceiver(receiver);
-        }
-
-        @Override
-        public boolean bindService(Intent service, ServiceConnection conn, int flags) {
-            getTracker("service").getLeakInfo(conn).addAllocation(new Throwable());
-            return super.bindService(service, conn, flags);
-        }
-
-        @Override
-        public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
-                Handler handler, UserHandle user) {
-            getTracker("service").getLeakInfo(conn).addAllocation(new Throwable());
-            return super.bindServiceAsUser(service, conn, flags, handler, user);
-        }
-
-        @Override
-        public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
-                UserHandle user) {
-            getTracker("service").getLeakInfo(conn).addAllocation(new Throwable());
-            return super.bindServiceAsUser(service, conn, flags, user);
-        }
-
-        @Override
-        public void unbindService(ServiceConnection conn) {
-            getTracker("service").getLeakInfo(conn).clearAllocations();
-            super.unbindService(conn);
-        }
-
-        @Override
-        public void registerComponentCallbacks(ComponentCallbacks callback) {
-            getTracker("component").getLeakInfo(callback).addAllocation(new Throwable());
-            super.registerComponentCallbacks(callback);
-        }
-
-        @Override
-        public void unregisterComponentCallbacks(ComponentCallbacks callback) {
-            getTracker("component").getLeakInfo(callback).clearAllocations();
-            super.unregisterComponentCallbacks(callback);
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index 5dac8e5..008580a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -22,6 +22,7 @@
 import android.os.MessageQueue;
 
 import com.android.systemui.utils.TestableContext;
+import com.android.systemui.utils.leaks.Tracker;
 
 import org.junit.After;
 import org.junit.Before;
@@ -29,14 +30,14 @@
 /**
  * Base class that does System UI specific setup.
  */
-public class SysuiTestCase {
+public abstract class SysuiTestCase {
 
     private Handler mHandler;
     protected TestableContext mContext;
 
     @Before
     public void SysuiSetup() throws Exception {
-        mContext = new TestableContext(InstrumentationRegistry.getTargetContext());
+        mContext = new TestableContext(InstrumentationRegistry.getTargetContext(), this);
     }
 
     @After
@@ -71,6 +72,11 @@
         }
     }
 
+    // Used for leak tracking, returns null to indicate no leak tracking by default.
+    public Tracker getTracker(String tag) {
+        return null;
+    }
+
     public static final class EmptyRunnable implements Runnable {
         public void run() {
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 6ceaead..1973b54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -38,6 +38,7 @@
 import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.tuner.TunerService;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -59,7 +60,7 @@
         KeyguardMonitor keyguardMonitor = getLeakChecker(KeyguardMonitor.class);
         when(userSwitcher.getKeyguardMonitor()).thenReturn(keyguardMonitor);
         when(userSwitcher.getUsers()).thenReturn(new ArrayList<>());
-        QSTileHost host = new QSTileHost(getTrackedContext(),
+        QSTileHost host = new QSTileHost(mContext,
                 mock(PhoneStatusBar.class),
                 getLeakChecker(BluetoothController.class),
                 getLeakChecker(LocationController.class),
@@ -86,5 +87,7 @@
         waitForIdleSync(h);
 
         host.destroy();
+        // Ensure the tuner cleans up its persistent listeners.
+        TunerService.get(mContext).destroy();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java b/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java
index 5179823..bf73416 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java
@@ -14,18 +14,31 @@
 
 package com.android.systemui.utils;
 
+import android.content.BroadcastReceiver;
+import android.content.ComponentCallbacks;
 import android.content.ContentProviderClient;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.UserHandle;
 import android.provider.Settings;
 
+import com.android.systemui.utils.leaks.Tracker;
+import com.android.systemui.SysuiTestCase;
+
 public class TestableContext extends ContextWrapper {
 
     private final FakeContentResolver mFakeContentResolver;
     private final FakeSettingsProvider mSettingsProvider;
 
-    public TestableContext(Context base) {
+    private Tracker mReceiver;
+    private Tracker mService;
+    private Tracker mComponent;
+
+    public TestableContext(Context base, SysuiTestCase test) {
         super(base);
         mFakeContentResolver = new FakeContentResolver(base);
         ContentProviderClient settings = base.getContentResolver()
@@ -33,6 +46,9 @@
         mSettingsProvider = FakeSettingsProvider.getFakeSettingsProvider(settings,
                 mFakeContentResolver);
         mFakeContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider);
+        mReceiver = test.getTracker("receiver");
+        mService = test.getTracker("service");
+        mComponent = test.getTracker("component");
     }
 
     public FakeSettingsProvider getSettingsProvider() {
@@ -49,4 +65,69 @@
         // Return this so its always a TestableContext.
         return this;
     }
+
+    @Override
+    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+        if (mReceiver != null) mReceiver.getLeakInfo(receiver).addAllocation(new Throwable());
+        return super.registerReceiver(receiver, filter);
+    }
+
+    @Override
+    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
+            String broadcastPermission, Handler scheduler) {
+        if (mReceiver != null) mReceiver.getLeakInfo(receiver).addAllocation(new Throwable());
+        return super.registerReceiver(receiver, filter, broadcastPermission, scheduler);
+    }
+
+    @Override
+    public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
+            IntentFilter filter, String broadcastPermission, Handler scheduler) {
+        if (mReceiver != null) mReceiver.getLeakInfo(receiver).addAllocation(new Throwable());
+        return super.registerReceiverAsUser(receiver, user, filter, broadcastPermission,
+                scheduler);
+    }
+
+    @Override
+    public void unregisterReceiver(BroadcastReceiver receiver) {
+        if (mReceiver != null) mReceiver.getLeakInfo(receiver).clearAllocations();
+        super.unregisterReceiver(receiver);
+    }
+
+    @Override
+    public boolean bindService(Intent service, ServiceConnection conn, int flags) {
+        if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable());
+        return super.bindService(service, conn, flags);
+    }
+
+    @Override
+    public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
+            Handler handler, UserHandle user) {
+        if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable());
+        return super.bindServiceAsUser(service, conn, flags, handler, user);
+    }
+
+    @Override
+    public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
+            UserHandle user) {
+        if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable());
+        return super.bindServiceAsUser(service, conn, flags, user);
+    }
+
+    @Override
+    public void unbindService(ServiceConnection conn) {
+        if (mService != null) mService.getLeakInfo(conn).clearAllocations();
+        super.unbindService(conn);
+    }
+
+    @Override
+    public void registerComponentCallbacks(ComponentCallbacks callback) {
+        if (mComponent != null) mComponent.getLeakInfo(callback).addAllocation(new Throwable());
+        super.registerComponentCallbacks(callback);
+    }
+
+    @Override
+    public void unregisterComponentCallbacks(ComponentCallbacks callback) {
+        if (mComponent != null) mComponent.getLeakInfo(callback).clearAllocations();
+        super.unregisterComponentCallbacks(callback);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java
new file mode 100644
index 0000000..0238bf7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.CallbackController;
+
+public class BaseLeakChecker<T> implements CallbackController<T> {
+
+    private final Tracker mTracker;
+
+    public BaseLeakChecker(LeakCheckedTest test, String tag) {
+        mTracker = test.getTracker(tag);
+    }
+
+    protected final Tracker getTracker() {
+        return mTracker;
+    }
+
+    @Override
+    public void addCallback(T listener) {
+        mTracker.getLeakInfo(listener).addAllocation(new Throwable());
+    }
+
+    @Override
+    public void removeCallback(T listener) {
+        mTracker.getLeakInfo(listener).clearAllocations();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java
new file mode 100644
index 0000000..fa07d33
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import android.os.Bundle;
+
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+public class FakeBatteryController extends BaseLeakChecker<BatteryStateChangeCallback>
+        implements BatteryController {
+    public FakeBatteryController(LeakCheckedTest test) {
+        super(test, "battery");
+    }
+
+    @Override
+    public void dispatchDemoCommand(String command, Bundle args) {
+
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+
+    }
+
+    @Override
+    public void setPowerSaveMode(boolean powerSave) {
+
+    }
+
+    @Override
+    public boolean isPowerSave() {
+        return false;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
new file mode 100644
index 0000000..6074a01
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.systemui.statusbar.policy.BluetoothController;
+import com.android.systemui.statusbar.policy.BluetoothController.Callback;
+
+import java.util.Collection;
+
+public class FakeBluetoothController extends BaseLeakChecker<Callback> implements
+        BluetoothController {
+
+    public FakeBluetoothController(LeakCheckedTest test) {
+        super(test, "bluetooth");
+    }
+
+    @Override
+    public boolean isBluetoothSupported() {
+        return false;
+    }
+
+    @Override
+    public boolean isBluetoothEnabled() {
+        return false;
+    }
+
+    @Override
+    public int getBluetoothState() {
+        return 0;
+    }
+
+    @Override
+    public boolean isBluetoothConnected() {
+        return false;
+    }
+
+    @Override
+    public boolean isBluetoothConnecting() {
+        return false;
+    }
+
+    @Override
+    public String getLastDeviceName() {
+        return null;
+    }
+
+    @Override
+    public void setBluetoothEnabled(boolean enabled) {
+
+    }
+
+    @Override
+    public Collection<CachedBluetoothDevice> getDevices() {
+        return null;
+    }
+
+    @Override
+    public void connect(CachedBluetoothDevice device) {
+
+    }
+
+    @Override
+    public void disconnect(CachedBluetoothDevice device) {
+
+    }
+
+    @Override
+    public boolean canConfigBluetooth() {
+        return false;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java
new file mode 100644
index 0000000..08211f8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.CastController;
+import com.android.systemui.statusbar.policy.CastController.Callback;
+
+import java.util.Set;
+
+public class FakeCastController extends BaseLeakChecker<Callback> implements CastController {
+    public FakeCastController(LeakCheckedTest test) {
+        super(test, "cast");
+    }
+
+    @Override
+    public void setDiscovering(boolean request) {
+
+    }
+
+    @Override
+    public void setCurrentUserId(int currentUserId) {
+
+    }
+
+    @Override
+    public Set<CastDevice> getCastDevices() {
+        return null;
+    }
+
+    @Override
+    public void startCasting(CastDevice device) {
+
+    }
+
+    @Override
+    public void stopCasting(CastDevice device) {
+
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeDataSaverController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeDataSaverController.java
new file mode 100644
index 0000000..857a785
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeDataSaverController.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.DataSaverController;
+import com.android.systemui.statusbar.policy.DataSaverController.Listener;
+
+public class FakeDataSaverController extends BaseLeakChecker<Listener> implements DataSaverController {
+
+    public FakeDataSaverController(LeakCheckedTest test) {
+        super(test, "datasaver");
+    }
+
+    @Override
+    public boolean isDataSaverEnabled() {
+        return false;
+    }
+
+    @Override
+    public void setDataSaverEnabled(boolean enabled) {
+
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeFlashlightController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
new file mode 100644
index 0000000..630abd7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.FlashlightController;
+import com.android.systemui.statusbar.policy.FlashlightController.FlashlightListener;
+
+public class FakeFlashlightController extends BaseLeakChecker<FlashlightListener>
+        implements FlashlightController {
+    public FakeFlashlightController(LeakCheckedTest test) {
+        super(test, "flashlight");
+    }
+
+    @Override
+    public boolean hasFlashlight() {
+        return false;
+    }
+
+    @Override
+    public void setFlashlight(boolean newState) {
+
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return false;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return false;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java
new file mode 100644
index 0000000..781960d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.HotspotController;
+import com.android.systemui.statusbar.policy.HotspotController.Callback;
+
+public class FakeHotspotController extends BaseLeakChecker<Callback> implements HotspotController {
+
+    public FakeHotspotController(LeakCheckedTest test) {
+        super(test, "hotspot");
+    }
+
+    @Override
+    public boolean isHotspotEnabled() {
+        return false;
+    }
+
+    @Override
+    public void setHotspotEnabled(boolean enabled) {
+
+    }
+
+    @Override
+    public boolean isHotspotSupported() {
+        return false;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java
new file mode 100644
index 0000000..39bbf2d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
+
+public class FakeKeyguardMonitor implements KeyguardMonitor {
+
+    private final BaseLeakChecker<Callback> mCallbackController;
+
+    public FakeKeyguardMonitor(LeakCheckedTest test) {
+        mCallbackController = new BaseLeakChecker<Callback>(test, "keyguard");
+    }
+
+    @Override
+    public void addCallback(Callback callback) {
+        mCallbackController.addCallback(callback);
+    }
+
+    @Override
+    public void removeCallback(Callback callback) {
+        mCallbackController.removeCallback(callback);
+    }
+
+    @Override
+    public boolean isSecure() {
+        return false;
+    }
+
+    @Override
+    public boolean isShowing() {
+        return false;
+    }
+
+    @Override
+    public boolean canSkipBouncer() {
+        return false;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeLocationController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeLocationController.java
new file mode 100644
index 0000000..eab436c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeLocationController.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.LocationController;
+import com.android.systemui.statusbar.policy.LocationController.LocationSettingsChangeCallback;
+
+public class FakeLocationController extends BaseLeakChecker<LocationSettingsChangeCallback>
+        implements LocationController {
+    public FakeLocationController(LeakCheckedTest test) {
+        super(test, "location");
+    }
+
+    @Override
+    public boolean isLocationEnabled() {
+        return false;
+    }
+
+    @Override
+    public boolean setLocationEnabled(boolean enabled) {
+        return false;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java
new file mode 100644
index 0000000..0ec0d77
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.phone.ManagedProfileController;
+import com.android.systemui.statusbar.phone.ManagedProfileController.Callback;
+
+public class FakeManagedProfileController extends BaseLeakChecker<Callback> implements
+        ManagedProfileController {
+    public FakeManagedProfileController(LeakCheckedTest test) {
+        super(test, "profile");
+    }
+
+    @Override
+    public void setWorkModeEnabled(boolean enabled) {
+
+    }
+
+    @Override
+    public boolean hasActiveProfile() {
+        return false;
+    }
+
+    @Override
+    public boolean isWorkModeEnabled() {
+        return false;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java
new file mode 100644
index 0000000..fcfe9aa
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import com.android.settingslib.net.DataUsageController;
+import com.android.systemui.statusbar.policy.DataSaverController;
+import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+
+public class FakeNetworkController extends BaseLeakChecker<SignalCallback>
+        implements NetworkController {
+
+    private final FakeDataSaverController mDataSaverController;
+    private final BaseLeakChecker<EmergencyListener> mEmergencyChecker;
+
+    public FakeNetworkController(LeakCheckedTest test) {
+        super(test, "network");
+        mDataSaverController = new FakeDataSaverController(test);
+        mEmergencyChecker = new BaseLeakChecker<EmergencyListener>(test, "emergency");
+    }
+
+    @Override
+    public void addEmergencyListener(EmergencyListener listener) {
+        mEmergencyChecker.addCallback(listener);
+    }
+
+    @Override
+    public void removeEmergencyListener(EmergencyListener listener) {
+        mEmergencyChecker.removeCallback(listener);
+    }
+
+    @Override
+    public DataSaverController getDataSaverController() {
+        return mDataSaverController;
+    }
+
+    @Override
+    public boolean hasMobileDataFeature() {
+        return false;
+    }
+
+    @Override
+    public void setWifiEnabled(boolean enabled) {
+
+    }
+
+    @Override
+    public void onUserSwitched(int newUserId) {
+
+    }
+
+    @Override
+    public AccessPointController getAccessPointController() {
+        return null;
+    }
+
+    @Override
+    public DataUsageController getMobileDataController() {
+        return null;
+    }
+
+    @Override
+    public boolean hasVoiceCallingFeature() {
+        return false;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java
new file mode 100644
index 0000000..707fc4b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
+
+public class FakeNextAlarmController extends BaseLeakChecker<NextAlarmChangeCallback>
+        implements NextAlarmController {
+
+    public FakeNextAlarmController(LeakCheckedTest test) {
+        super(test, "alarm");
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
new file mode 100644
index 0000000..00e2404
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.RotationLockController;
+import com.android.systemui.statusbar.policy.RotationLockController.RotationLockControllerCallback;
+
+public class FakeRotationLockController extends BaseLeakChecker<RotationLockControllerCallback>
+        implements RotationLockController {
+    public FakeRotationLockController(LeakCheckedTest test) {
+        super(test, "rotation");
+    }
+
+    @Override
+    public void setListening(boolean listening) {
+
+    }
+
+    @Override
+    public int getRotationLockOrientation() {
+        return 0;
+    }
+
+    @Override
+    public boolean isRotationLockAffordanceVisible() {
+        return false;
+    }
+
+    @Override
+    public boolean isRotationLocked() {
+        return false;
+    }
+
+    @Override
+    public void setRotationLocked(boolean locked) {
+
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java
new file mode 100644
index 0000000..331df58
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.SecurityController;
+import com.android.systemui.statusbar.policy.SecurityController.SecurityControllerCallback;
+
+public class FakeSecurityController extends BaseLeakChecker<SecurityControllerCallback>
+        implements SecurityController {
+    public FakeSecurityController(LeakCheckedTest test) {
+        super(test, "security");
+    }
+
+    @Override
+    public boolean isDeviceManaged() {
+        return false;
+    }
+
+    @Override
+    public boolean hasProfileOwner() {
+        return false;
+    }
+
+    @Override
+    public String getDeviceOwnerName() {
+        return null;
+    }
+
+    @Override
+    public String getProfileOwnerName() {
+        return null;
+    }
+
+    @Override
+    public CharSequence getDeviceOwnerOrganizationName() {
+        return null;
+    }
+
+    @Override
+    public boolean isVpnEnabled() {
+        return false;
+    }
+
+    @Override
+    public boolean isVpnRestricted() {
+        return false;
+    }
+
+    @Override
+    public boolean isVpnBranded() {
+        return false;
+    }
+
+    @Override
+    public String getPrimaryVpnName() {
+        return null;
+    }
+
+    @Override
+    public String getProfileVpnName() {
+        return null;
+    }
+
+    @Override
+    public void onUserSwitched(int newUserId) {
+
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeUserInfoController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeUserInfoController.java
new file mode 100644
index 0000000..578b310
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeUserInfoController.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
+
+public class FakeUserInfoController extends BaseLeakChecker<OnUserInfoChangedListener>
+        implements UserInfoController {
+    public FakeUserInfoController(LeakCheckedTest test) {
+        super(test, "user_info");
+    }
+
+    @Override
+    public void reloadUserInfo() {
+
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java
new file mode 100644
index 0000000..13ea385
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ZenRule;
+
+import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.statusbar.policy.ZenModeController.Callback;
+
+public class FakeZenModeController extends BaseLeakChecker<Callback> implements ZenModeController {
+    public FakeZenModeController(LeakCheckedTest test) {
+        super(test, "zen");
+    }
+
+    @Override
+    public void setZen(int zen, Uri conditionId, String reason) {
+
+    }
+
+    @Override
+    public int getZen() {
+        return 0;
+    }
+
+    @Override
+    public ZenRule getManualRule() {
+        return null;
+    }
+
+    @Override
+    public ZenModeConfig getConfig() {
+        return null;
+    }
+
+    @Override
+    public long getNextAlarm() {
+        return 0;
+    }
+
+    @Override
+    public void setUserId(int userId) {
+
+    }
+
+    @Override
+    public boolean isZenAvailable() {
+        return false;
+    }
+
+    @Override
+    public ComponentName getEffectsSuppressor() {
+        return null;
+    }
+
+    @Override
+    public boolean isCountdownConditionSupported() {
+        return false;
+    }
+
+    @Override
+    public int getCurrentUser() {
+        return 0;
+    }
+
+    @Override
+    public boolean isVolumeRestricted() {
+        return false;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
new file mode 100644
index 0000000..728ed60
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doAnswer;
+
+import android.util.ArrayMap;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.phone.ManagedProfileController;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.BluetoothController;
+import com.android.systemui.statusbar.policy.CallbackController;
+import com.android.systemui.statusbar.policy.CastController;
+import com.android.systemui.statusbar.policy.FlashlightController;
+import com.android.systemui.statusbar.policy.HotspotController;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.LocationController;
+import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.RotationLockController;
+import com.android.systemui.statusbar.policy.SecurityController;
+import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.policy.ZenModeController;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Base class for tests to check if receivers are left registered, services bound, or other
+ * listeners listening.
+ */
+public abstract class LeakCheckedTest extends SysuiTestCase {
+    private static final String TAG = "LeakCheckedTest";
+
+    private final Map<String, Tracker> mTrackers = new HashMap<>();
+    private final Map<Class, Object> mLeakCheckers = new ArrayMap<>();
+
+    @Rule
+    public TestWatcher successWatcher = new TestWatcher() {
+        @Override
+        protected void succeeded(Description description) {
+            verify();
+        }
+    };
+
+    public <T> T getLeakChecker(Class<T> cls) {
+        Object obj = mLeakCheckers.get(cls);
+        if (obj == null) {
+            // Lazy create checkers so we only have the ones we need.
+            if (cls == BluetoothController.class) {
+                obj = new FakeBluetoothController(this);
+            } else if (cls == LocationController.class) {
+                obj = new FakeLocationController(this);
+            } else if (cls == RotationLockController.class) {
+                obj = new FakeRotationLockController(this);
+            } else if (cls == ZenModeController.class) {
+                obj = new FakeZenModeController(this);
+            } else if (cls == CastController.class) {
+                obj = new FakeCastController(this);
+            } else if (cls == HotspotController.class) {
+                obj = new FakeHotspotController(this);
+            } else if (cls == FlashlightController.class) {
+                obj = new FakeFlashlightController(this);
+            } else if (cls == UserInfoController.class) {
+                obj = new FakeUserInfoController(this);
+            } else if (cls == KeyguardMonitor.class) {
+                obj = new FakeKeyguardMonitor(this);
+            } else if (cls == BatteryController.class) {
+                obj = new FakeBatteryController(this);
+            } else if (cls == SecurityController.class) {
+                obj = new FakeSecurityController(this);
+            } else if (cls == ManagedProfileController.class) {
+                obj = new FakeManagedProfileController(this);
+            } else if (cls == NextAlarmController.class) {
+                obj = new FakeNextAlarmController(this);
+            } else if (cls == NetworkController.class) {
+                obj = new FakeNetworkController(this);
+            } else {
+                Assert.fail(cls.getName() + " is not supported by LeakCheckedTest yet");
+            }
+            mLeakCheckers.put(cls, obj);
+        }
+        return (T) obj;
+    }
+
+    @Override
+    public Tracker getTracker(String tag) {
+        Tracker t = mTrackers.get(tag);
+        if (t == null) {
+            t = new Tracker();
+            mTrackers.put(tag, t);
+        }
+        return t;
+    }
+
+    public void verify() {
+        mTrackers.values().forEach(Tracker::verify);
+    }
+
+    public <T extends CallbackController> T addListening(T mock, Class<T> cls, String tag) {
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                getTracker(tag).getLeakInfo(invocation.getArguments()[0])
+                        .addAllocation(new Throwable());
+                return null;
+            }
+        }).when(mock).addCallback(any());
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                getTracker(tag).getLeakInfo(invocation.getArguments()[0]).clearAllocations();
+                return null;
+            }
+        }).when(mock).removeCallback(any());
+        mLeakCheckers.put(cls, mock);
+        return mock;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakInfo.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakInfo.java
new file mode 100644
index 0000000..1d016fb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakInfo.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import android.util.Log;
+
+import org.junit.Assert;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+public class LeakInfo {
+    private static final String TAG = "LeakInfo";
+    private List<Throwable> mThrowables = new ArrayList<>();
+
+    LeakInfo() {
+    }
+
+    public void addAllocation(Throwable t) {
+        // TODO: Drop off the first element in the stack trace here to have a cleaner stack.
+        mThrowables.add(t);
+    }
+
+    public void clearAllocations() {
+        mThrowables.clear();
+    }
+
+    void verify() {
+        if (mThrowables.size() == 0) return;
+        Log.e(TAG, "Listener or binding not properly released");
+        for (Throwable t : mThrowables) {
+            Log.e(TAG, "Allocation found", t);
+        }
+        StringWriter writer = new StringWriter();
+        mThrowables.get(0).printStackTrace(new PrintWriter(writer));
+        Assert.fail("Listener or binding not properly released\n"
+                + writer.toString());
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/Tracker.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/Tracker.java
new file mode 100644
index 0000000..26ffd10
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/Tracker.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import android.util.ArrayMap;
+
+import com.android.systemui.utils.leaks.LeakInfo;
+
+import java.util.Map;
+
+public class Tracker {
+    private Map<Object, LeakInfo> mObjects = new ArrayMap<>();
+
+    public LeakInfo getLeakInfo(Object object) {
+        LeakInfo leakInfo = mObjects.get(object);
+        if (leakInfo == null) {
+            leakInfo = new LeakInfo();
+            mObjects.put(object, leakInfo);
+        }
+        return leakInfo;
+    }
+
+    void verify() {
+        mObjects.values().forEach(LeakInfo::verify);
+    }
+}