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/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);
+    }
+}