Fix leaks in sysui

Add support for testing for PluginManager and TunerService leaks
and add tests for the known leaks and fix them. Also port PluginManager
and TunerService to Dependency to make them easier to handle in
tests.

Test: runtest systemui
Change-Id: I5642539ee24dd72f802905106decd0c87b41b4eb
Fixes: 34846972
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java
index 4714547..f4adc93 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java
@@ -70,7 +70,7 @@
     private boolean mListening;
     private boolean mHasOneShot;
 
-    private PluginManager(Context context) {
+    public PluginManager(Context context) {
         this(context, new PluginInstanceManagerFactory(),
                 Build.IS_DEBUGGABLE, Thread.getDefaultUncaughtExceptionHandler());
     }
@@ -252,13 +252,6 @@
         return new PluginContextWrapper(mContext.createApplicationContext(info, 0), classLoader);
     }
 
-    public static PluginManager getInstance(Context context) {
-        if (sInstance == null) {
-            sInstance = new PluginManager(context.getApplicationContext());
-        }
-        return sInstance;
-    }
-
     private class AllPluginClassLoader extends ClassLoader {
         public AllPluginClassLoader(ClassLoader classLoader) {
             super(classLoader);
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java
index 0728482..918d6e9 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java
@@ -32,6 +32,8 @@
         public boolean onInterceptTouchEvent(MotionEvent event);
 
         public void setBarState(boolean vertical, boolean isRtl);
+
+        public default void destroy() { }
     }
 
 }
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index b30b596..29a8da0 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -76,7 +76,7 @@
         mDrawable.setBatteryController(mBatteryController);
         mBatteryController.addCallback(this);
         mDrawable.startListening();
-        TunerService.get(getContext()).addTunable(this, StatusBarIconController.ICON_BLACKLIST);
+        Dependency.get(TunerService.class).addTunable(this, StatusBarIconController.ICON_BLACKLIST);
     }
 
     @Override
@@ -84,7 +84,7 @@
         super.onDetachedFromWindow();
         mBatteryController.removeCallback(this);
         mDrawable.stopListening();
-        TunerService.get(getContext()).removeTunable(this);
+        Dependency.get(TunerService.class).removeTunable(this);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 135b129..e1f3176 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -14,6 +14,7 @@
 
 package com.android.systemui;
 
+import android.content.Context;
 import android.content.res.Configuration;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -23,6 +24,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
 import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;
 import com.android.systemui.statusbar.policy.AccessibilityController;
@@ -56,9 +58,11 @@
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.statusbar.policy.ZenModeControllerImpl;
+import com.android.systemui.tuner.TunerService;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.HashMap;
 
 /**
  * Class to handle ugly dependencies throughout sysui until we determine the
@@ -167,12 +171,18 @@
         mProviders.put(DeviceProvisionedController.class.getName(), () ->
                 new DeviceProvisionedControllerImpl(mContext));
 
+        mProviders.put(PluginManager.class.getName(), () ->
+                new PluginManager(mContext));
+
         mProviders.put(AssistManager.class.getName(), () ->
                 new AssistManager(getDependency(DeviceProvisionedController.class), mContext));
 
         mProviders.put(SecurityController.class.getName(), () ->
                 new SecurityControllerImpl(mContext));
 
+        mProviders.put(TunerService.class.getName(), () ->
+                new TunerService(mContext));
+
         // Put all dependencies above here so the factory can override them if it wants.
         SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
     }
@@ -220,6 +230,17 @@
         T createDependency();
     }
 
+    /**
+     * Used in separate processes (like tuner settings) to init the dependencies.
+     */
+    public static void initDependencies(Context context) {
+        if (sDependency != null) return;
+        Dependency d = new Dependency();
+        d.mContext = context.getApplicationContext();
+        d.mComponents = new HashMap<>();
+        d.start();
+    }
+
     public static <T> T get(Class<T> cls) {
         return sDependency.getDependency(cls.getName());
     }
diff --git a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
index efddf20..9cc6613 100644
--- a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
@@ -76,7 +76,7 @@
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         if (mAction != null) {
-            PluginManager.getInstance(getContext()).addPluginListener(mAction, this, mVersion);
+            Dependency.get(PluginManager.class).addPluginListener(mAction, this, mVersion);
         }
     }
 
@@ -84,7 +84,7 @@
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         if (mAction != null) {
-            PluginManager.getInstance(getContext()).removePluginListener(this);
+            Dependency.get(PluginManager.class).removePluginListener(this);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 9515585..a9ac2d9 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -65,7 +65,6 @@
     private final Class<?>[] SERVICES = new Class[] {
             Dependency.class,
             FragmentService.class,
-            TunerService.class,
             NotificationChannels.class,
             CommandQueue.CommandQueueStart.class,
             KeyguardViewMediator.class,
@@ -205,7 +204,7 @@
                 mServices[i].onBootCompleted();
             }
         }
-        PluginManager.getInstance(this).addPluginListener(OverlayPlugin.ACTION,
+        Dependency.get(PluginManager.class).addPluginListener(OverlayPlugin.ACTION,
                 new PluginListener<OverlayPlugin>() {
             @Override
             public void onPluginConnected(OverlayPlugin plugin, Context pluginContext) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index 828728f..94dc9a3 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -19,6 +19,7 @@
 import android.service.dreams.DreamService;
 import android.util.Log;
 
+import com.android.systemui.Dependency;
 import com.android.systemui.plugins.Plugin;
 import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.doze.DozeProvider;
@@ -47,7 +48,7 @@
             return;
         }
 
-        DozeProvider provider = PluginManager.getInstance(this)
+        DozeProvider provider = Dependency.get(PluginManager.class)
                 .getOneShotPlugin(DozeProvider.ACTION, DozeProvider.VERSION);
         mDozeMachine = new DozeFactory(provider).assembleMachine(this);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java b/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java
index 2e6de4a..1eaca6f 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java
@@ -19,6 +19,7 @@
 import android.util.Log;
 import android.view.View;
 
+import com.android.systemui.Dependency;
 import com.android.systemui.plugins.FragmentBase;
 import com.android.systemui.plugins.Plugin;
 import com.android.systemui.plugins.PluginListener;
@@ -38,7 +39,7 @@
             Class<? extends FragmentBase> expectedInterface) {
         mTag = tag;
         mFragmentHostManager = FragmentHostManager.get(view);
-        mPluginManager = PluginManager.getInstance(view.getContext());
+        mPluginManager = Dependency.get(PluginManager.class);
         mExpectedInterface = expectedInterface;
         mDefaultClass = defaultFragment;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 30aa03c..c7b15ef 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -49,6 +49,7 @@
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.policy.PipMotionHelper;
 import com.android.internal.policy.PipSnapAlgorithm;
+import com.android.systemui.Dependency;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 import com.android.systemui.tuner.TunerService;
 
@@ -200,7 +201,7 @@
         setSnapToEdge(true);
 
         // Register any tuner settings changes
-        TunerService.get(context).addTunable(this, TUNER_KEY_DRAG_TO_DISMISS,
+        Dependency.get(TunerService.class).addTunable(this, TUNER_KEY_DRAG_TO_DISMISS,
                 TUNER_KEY_ALLOW_MINIMIZE);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 615063c..c85f83b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -20,6 +20,7 @@
 import android.view.View.OnLayoutChangeListener;
 import android.widget.TextView;
 
+import com.android.systemui.Dependency;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.qs.PagedTileLayout.PageListener;
 import com.android.systemui.qs.QSPanel.QSTileLayout;
@@ -105,7 +106,7 @@
 
     @Override
     public void onViewAttachedToWindow(View v) {
-        TunerService.get(mQs.getContext()).addTunable(this, ALLOW_FANCY_ANIMATION,
+        Dependency.get(TunerService.class).addTunable(this, ALLOW_FANCY_ANIMATION,
                 MOVE_FULL_ROWS, QuickQSPanel.NUM_QUICK_TILES);
     }
 
@@ -114,7 +115,7 @@
         if (mHost != null) {
             mHost.removeCallback(this);
         }
-        TunerService.get(mQs.getContext()).removeTunable(this);
+        Dependency.get(TunerService.class).removeTunable(this);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index e004828..504678c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -29,6 +29,7 @@
 import android.widget.LinearLayout;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.plugins.qs.QS.DetailAdapter;
@@ -126,7 +127,7 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        TunerService.get(mContext).addTunable(this, QS_SHOW_BRIGHTNESS);
+        Dependency.get(TunerService.class).addTunable(this, QS_SHOW_BRIGHTNESS);
         if (mHost != null) {
             setTiles(mHost.getTiles());
         }
@@ -134,8 +135,10 @@
 
     @Override
     protected void onDetachedFromWindow() {
-        TunerService.get(mContext).removeTunable(this);
-        mHost.removeCallback(this);
+        Dependency.get(TunerService.class).removeTunable(this);
+        if (mHost != null) {
+            mHost.removeCallback(this);
+        }
         for (TileRecord record : mRecords) {
             record.tile.removeCallbacks();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index 16b351e..d789b44 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -24,6 +24,7 @@
 import android.widget.LinearLayout;
 import android.widget.Space;
 
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.qs.QSTile.SignalState;
 import com.android.systemui.qs.QSTile.State;
@@ -62,13 +63,13 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        TunerService.get(mContext).addTunable(mNumTiles, NUM_QUICK_TILES);
+        Dependency.get(TunerService.class).addTunable(mNumTiles, NUM_QUICK_TILES);
     }
 
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        TunerService.get(mContext).removeTunable(mNumTiles);
+        Dependency.get(TunerService.class).removeTunable(mNumTiles);
     }
 
     public void setQSPanelAndHeader(QSPanel fullPanel, View header) {
@@ -141,7 +142,7 @@
     };
 
     public int getNumQuickTiles(Context context) {
-        return TunerService.get(context).getValue(NUM_QUICK_TILES, 6);
+        return Dependency.get(TunerService.class).getValue(NUM_QUICK_TILES, 6);
     }
 
     private static class HeaderTileLayout extends LinearLayout implements QSTileLayout {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
index fad63dd..355022f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
@@ -30,6 +30,7 @@
 
 import java.util.ArrayList;
 
+import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.plugins.PluginListener;
@@ -82,10 +83,21 @@
     public NotificationMenuRow(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs);
-        PluginManager.getInstance(getContext()).addPluginListener(
+        mMenuItems.addAll(getDefaultNotificationMenuItems());
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        Dependency.get(PluginManager.class).addPluginListener(
                 NotificationMenuRowProvider.ACTION, this,
                 NotificationMenuRowProvider.VERSION, false /* Allow multiple */);
-        mMenuItems.addAll(getDefaultNotificationMenuItems());
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        Dependency.get(PluginManager.class).removePluginListener(this);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
index 9a76ad6..45eb5df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
@@ -220,7 +220,7 @@
         int endPadding = mMobileSignalGroup.getChildCount() > 0 ? mMobileSignalGroupEndPadding : 0;
         mMobileSignalGroup.setPaddingRelative(0, 0, endPadding, 0);
 
-        TunerService.get(mContext).addTunable(this, StatusBarIconController.ICON_BLACKLIST);
+        Dependency.get(TunerService.class).addTunable(this, StatusBarIconController.ICON_BLACKLIST);
 
         apply();
         applyIconTint();
@@ -231,7 +231,7 @@
     @Override
     protected void onDetachedFromWindow() {
         mMobileSignalGroup.removeAllViews();
-        TunerService.get(mContext).removeTunable(this);
+        Dependency.get(TunerService.class).removeTunable(this);
         mSecurityController.removeCallback(this);
         mNetworkController.removeCallback(this);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index bfc0a80..55bf34b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -257,11 +257,11 @@
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         mAccessibilityController.addStateChangedCallback(this);
-        PluginManager.getInstance(getContext()).addPluginListener(RIGHT_BUTTON_PLUGIN,
+        Dependency.get(PluginManager.class).addPluginListener(RIGHT_BUTTON_PLUGIN,
                 mRightListener, IntentButtonProvider.VERSION, false /* Only allow one */);
-        PluginManager.getInstance(getContext()).addPluginListener(LEFT_BUTTON_PLUGIN,
+        Dependency.get(PluginManager.class).addPluginListener(LEFT_BUTTON_PLUGIN,
                 mLeftListener, IntentButtonProvider.VERSION, false /* Only allow one */);
-        TunerService.get(getContext()).addTunable(this, LockscreenFragment.LOCKSCREEN_LEFT_BUTTON,
+        Dependency.get(TunerService.class).addTunable(this, LockscreenFragment.LOCKSCREEN_LEFT_BUTTON,
                 LockscreenFragment.LOCKSCREEN_RIGHT_BUTTON);
     }
 
@@ -269,9 +269,9 @@
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         mAccessibilityController.removeStateChangedCallback(this);
-        PluginManager.getInstance(getContext()).removePluginListener(mRightListener);
-        PluginManager.getInstance(getContext()).removePluginListener(mLeftListener);
-        TunerService.get(getContext()).removeTunable(this);
+        Dependency.get(PluginManager.class).removePluginListener(mRightListener);
+        Dependency.get(PluginManager.class).removePluginListener(mLeftListener);
+        Dependency.get(TunerService.class).removeTunable(this);
     }
 
     private void initAccessibility() {
@@ -573,7 +573,7 @@
             AsyncTask.execute(runnable);
         } else {
             boolean dismissShade = !TextUtils.isEmpty(mRightButtonStr)
-                    && TunerService.get(getContext()).getValue(LOCKSCREEN_RIGHT_UNLOCK, 1) != 0;
+                    && Dependency.get(TunerService.class).getValue(LOCKSCREEN_RIGHT_UNLOCK, 1) != 0;
             mStatusBar.executeRunnableDismissingKeyguard(runnable, null /* cancelAction */,
                     dismissShade, false /* afterKeyguardGone */, true /* deferred */);
         }
@@ -594,7 +594,7 @@
             });
         } else {
             boolean dismissShade = !TextUtils.isEmpty(mLeftButtonStr)
-                    && TunerService.get(getContext()).getValue(LOCKSCREEN_LEFT_UNLOCK, 1) != 0;
+                    && Dependency.get(TunerService.class).getValue(LOCKSCREEN_LEFT_UNLOCK, 1) != 0;
             mActivityStarter.startActivity(mLeftButton.getIntent(), dismissShade);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
index 228e8ea..ee9a791 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
@@ -29,6 +29,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.RecentsComponent;
 import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
@@ -87,7 +88,11 @@
         mScrollTouchSlop = r.getDimensionPixelSize(R.dimen.navigation_bar_min_swipe_distance);
         mMinFlingVelocity = configuration.getScaledMinimumFlingVelocity();
         mTaskSwitcherDetector = new GestureDetector(context, this);
-        TunerService.get(context).addTunable(this, KEY_DOCK_WINDOW_GESTURE);
+        Dependency.get(TunerService.class).addTunable(this, KEY_DOCK_WINDOW_GESTURE);
+    }
+
+    public void destroy() {
+        Dependency.get(TunerService.class).removeTunable(this);
     }
 
     public void setComponents(RecentsComponent recentsComponent, Divider divider,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
index 9b4867e..5fb99da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -30,6 +30,7 @@
 import android.widget.LinearLayout;
 import android.widget.Space;
 
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.PluginManager;
@@ -135,15 +136,16 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        TunerService.get(getContext()).addTunable(this, NAV_BAR_VIEWS, NAV_BAR_LEFT,
+        Dependency.get(TunerService.class).addTunable(this, NAV_BAR_VIEWS, NAV_BAR_LEFT,
                 NAV_BAR_RIGHT);
-        PluginManager.getInstance(getContext()).addPluginListener(NavBarButtonProvider.ACTION, this,
+        Dependency.get(PluginManager.class).addPluginListener(NavBarButtonProvider.ACTION, this,
                 NavBarButtonProvider.VERSION, true /* Allow multiple */);
     }
 
     @Override
     protected void onDetachedFromWindow() {
-        TunerService.get(getContext()).removeTunable(this);
+        Dependency.get(TunerService.class).removeTunable(this);
+        Dependency.get(PluginManager.class).removePluginListener(this);
         super.onDetachedFromWindow();
     }
 
@@ -278,10 +280,10 @@
         View v = null;
         String button = extractButton(buttonSpec);
         if (LEFT.equals(button)) {
-            buttonSpec = TunerService.get(mContext).getValue(NAV_BAR_LEFT, NAVSPACE);
+            buttonSpec = Dependency.get(TunerService.class).getValue(NAV_BAR_LEFT, NAVSPACE);
             button = extractButton(buttonSpec);
         } else if (RIGHT.equals(button)) {
-            buttonSpec = TunerService.get(mContext).getValue(NAV_BAR_RIGHT, MENU_IME);
+            buttonSpec = Dependency.get(TunerService.class).getValue(NAV_BAR_RIGHT, MENU_IME);
             button = extractButton(buttonSpec);
         }
         // Let plugins go first so they can override a standard view if they want.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 5e988fc..dced747 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -46,6 +46,7 @@
 import android.view.inputmethod.InputMethodManager;
 import android.widget.FrameLayout;
 
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.RecentsComponent;
 import com.android.systemui.plugins.PluginListener;
@@ -200,7 +201,6 @@
 
         mVertical = false;
         mShowMenu = false;
-        mGestureHelper = new NavigationBarGestureHelper(context);
 
         mConfiguration = new Configuration();
         mConfiguration.updateFrom(context.getResources().getConfiguration());
@@ -633,6 +633,7 @@
     }
 
     private void updateTaskSwitchHelper() {
+        if (mGestureHelper == null) return;
         boolean isRtl = (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
         mGestureHelper.setBarState(mVertical, isRtl);
     }
@@ -752,14 +753,18 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        PluginManager.getInstance(getContext()).addPluginListener(NavGesture.ACTION, this,
+        onPluginDisconnected(null); // Create default gesture helper
+        Dependency.get(PluginManager.class).addPluginListener(NavGesture.ACTION, this,
                 NavGesture.VERSION, false /* Only one */);
     }
 
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        PluginManager.getInstance(getContext()).removePluginListener(this);
+        Dependency.get(PluginManager.class).removePluginListener(this);
+        if (mGestureHelper != null) {
+            mGestureHelper.destroy();
+        }
     }
 
     @Override
@@ -772,6 +777,9 @@
     public void onPluginDisconnected(NavGesture plugin) {
         NavigationBarGestureHelper defaultHelper = new NavigationBarGestureHelper(getContext());
         defaultHelper.setComponents(mRecentsComponent, mDivider, this);
+        if (mGestureHelper != null) {
+            mGestureHelper.destroy();
+        }
         mGestureHelper = defaultHelper;
         updateTaskSwitchHelper();
     }
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 dd567e8..a1022c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -89,7 +89,7 @@
 
         mServices = new TileServices(this, Dependency.get(Dependency.BG_LOOPER));
 
-        TunerService.get(mContext).addTunable(this, TILES_SETTING);
+        Dependency.get(TunerService.class).addTunable(this, TILES_SETTING);
         // AutoTileManager can modify mTiles so make sure mTiles has already been initialized.
         mAutoTiles = new AutoTileManager(context, this);
     }
@@ -101,7 +101,7 @@
     public void destroy() {
         mTiles.values().forEach(tile -> tile.destroy());
         mAutoTiles.destroy();
-        TunerService.get(mContext).removeTunable(this);
+        Dependency.get(TunerService.class).removeTunable(this);
         mServices.destroy();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 46ca3c6..41f8a91 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -36,6 +36,7 @@
 import android.widget.TextView;
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.systemui.BatteryMeterView;
+import com.android.systemui.Dependency;
 import com.android.systemui.FontSizeUtils;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
@@ -127,7 +128,7 @@
         mLightModeIconColorSingleTone = context.getColor(R.color.light_mode_icon_color_single_tone);
         loadDimens();
 
-        TunerService.get(mContext).addTunable(this, ICON_BLACKLIST);
+        Dependency.get(TunerService.class).addTunable(this, ICON_BLACKLIST);
 
         mTransitionsController = new LightBarTransitionsController(this::setIconTintInternal);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index 9cc9749..ffc0d97 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -108,7 +108,7 @@
 
             getContext().registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter,
                     null, Dependency.get(Dependency.TIME_TICK_HANDLER));
-            TunerService.get(getContext()).addTunable(this, CLOCK_SECONDS,
+            Dependency.get(TunerService.class).addTunable(this, CLOCK_SECONDS,
                     StatusBarIconController.ICON_BLACKLIST);
         }
 
@@ -129,7 +129,7 @@
         if (mAttached) {
             getContext().unregisterReceiver(mIntentReceiver);
             mAttached = false;
-            TunerService.get(getContext()).removeTunable(this);
+            Dependency.get(TunerService.class).removeTunable(this);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/BatteryPreference.java b/packages/SystemUI/src/com/android/systemui/tuner/BatteryPreference.java
index 9998283..3058c0a 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/BatteryPreference.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/BatteryPreference.java
@@ -22,6 +22,7 @@
 import android.util.AttributeSet;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 
 import static com.android.systemui.BatteryMeterDrawable.SHOW_PERCENT_SETTING;
@@ -49,12 +50,12 @@
         super.onAttached();
         mHasPercentage = Settings.System.getInt(getContext().getContentResolver(),
                 SHOW_PERCENT_SETTING, 0) != 0;
-        TunerService.get(getContext()).addTunable(this, StatusBarIconController.ICON_BLACKLIST);
+        Dependency.get(TunerService.class).addTunable(this, StatusBarIconController.ICON_BLACKLIST);
     }
 
     @Override
     public void onDetached() {
-        TunerService.get(getContext()).removeTunable(this);
+        Dependency.get(TunerService.class).removeTunable(this);
         super.onDetached();
     }
 
@@ -89,7 +90,7 @@
         } else {
             mBlacklist.remove(mBattery);
         }
-        TunerService.get(getContext()).setValue(StatusBarIconController.ICON_BLACKLIST,
+        Dependency.get(TunerService.class).setValue(StatusBarIconController.ICON_BLACKLIST,
                 TextUtils.join(",", mBlacklist));
         return true;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/ClockPreference.java b/packages/SystemUI/src/com/android/systemui/tuner/ClockPreference.java
index caa0527..014ec92 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/ClockPreference.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/ClockPreference.java
@@ -18,6 +18,8 @@
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.AttributeSet;
+
+import com.android.systemui.Dependency;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.policy.Clock;
 
@@ -44,13 +46,13 @@
     @Override
     public void onAttached() {
         super.onAttached();
-        TunerService.get(getContext()).addTunable(this, StatusBarIconController.ICON_BLACKLIST,
+        Dependency.get(TunerService.class).addTunable(this, StatusBarIconController.ICON_BLACKLIST,
                 Clock.CLOCK_SECONDS);
     }
 
     @Override
     public void onDetached() {
-        TunerService.get(getContext()).removeTunable(this);
+        Dependency.get(TunerService.class).removeTunable(this);
         super.onDetached();
     }
 
@@ -81,13 +83,14 @@
 
     @Override
     protected boolean persistString(String value) {
-        TunerService.get(getContext()).setValue(Clock.CLOCK_SECONDS, SECONDS.equals(value) ? 1 : 0);
+        Dependency.get(TunerService.class).setValue(Clock.CLOCK_SECONDS, SECONDS.equals(value) ? 1
+                : 0);
         if (DISABLED.equals(value)) {
             mBlacklist.add(mClock);
         } else {
             mBlacklist.remove(mClock);
         }
-        TunerService.get(getContext()).setValue(StatusBarIconController.ICON_BLACKLIST,
+        Dependency.get(TunerService.class).setValue(StatusBarIconController.ICON_BLACKLIST,
                 TextUtils.join(",", mBlacklist));
         return true;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/LockscreenFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/LockscreenFragment.java
index 41786b5..9d579f5 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/LockscreenFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/LockscreenFragment.java
@@ -43,6 +43,7 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.plugins.IntentButtonProvider.IntentButton;
 import com.android.systemui.statusbar.phone.ExpandableIndicator;
@@ -71,7 +72,7 @@
 
     @Override
     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
-        mTunerService = TunerService.get(getContext());
+        mTunerService = Dependency.get(TunerService.class);
         mHandler = new Handler();
         addPreferencesFromResource(R.xml.lockscreen_settings);
         setupGroup((PreferenceGroup) findPreference(KEY_LEFT), LOCKSCREEN_LEFT_BUTTON,
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
index 9593c45..28a0057 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
@@ -51,6 +51,7 @@
 import android.view.KeyEvent;
 import android.widget.EditText;
 
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.phone.NavigationBarInflaterView;
 import com.android.systemui.tuner.TunerService.Tunable;
@@ -104,12 +105,12 @@
     @Override
     public void onDestroy() {
         super.onDestroy();
-        mTunables.forEach(t -> TunerService.get(getContext()).removeTunable(t));
+        mTunables.forEach(t -> Dependency.get(TunerService.class).removeTunable(t));
     }
 
     private void addTunable(Tunable tunable, String... keys) {
         mTunables.add(tunable);
-        TunerService.get(getContext()).addTunable(tunable, keys);
+        Dependency.get(TunerService.class).addTunable(tunable, keys);
     }
 
     private void bindLayout(ListPreference preference) {
@@ -123,7 +124,7 @@
         preference.setOnPreferenceChangeListener((preference1, newValue) -> {
             String val = (String) newValue;
             if ("default".equals(val)) val = null;
-            TunerService.get(getContext()).setValue(NAV_BAR_VIEWS, val);
+            Dependency.get(TunerService.class).setValue(NAV_BAR_VIEWS, val);
             return true;
         });
     }
@@ -215,7 +216,7 @@
             }
             button = button + KEY_CODE_START + code + KEY_IMAGE_DELIM + uri + KEY_CODE_END;
         }
-        TunerService.get(getContext()).setValue(setting, button);
+        Dependency.get(TunerService.class).setValue(setting, button);
     }
 
     private void setupIcons(ListPreference icon) {
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/PreviewNavInflater.java b/packages/SystemUI/src/com/android/systemui/tuner/PreviewNavInflater.java
index e6e8f4e..e7a695f 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/PreviewNavInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/PreviewNavInflater.java
@@ -18,6 +18,7 @@
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 
+import com.android.systemui.Dependency;
 import com.android.systemui.statusbar.phone.NavigationBarInflaterView;
 
 public class PreviewNavInflater extends NavigationBarInflaterView {
@@ -31,7 +32,7 @@
         super.onAttachedToWindow();
         // Immediately remove tuner listening, since this is a preview, all values will be injected
         // manually.
-        TunerService.get(getContext()).removeTunable(this);
+        Dependency.get(TunerService.class).removeTunable(this);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/StatusBarSwitch.java b/packages/SystemUI/src/com/android/systemui/tuner/StatusBarSwitch.java
index dea2f50..8a2407a 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/StatusBarSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/StatusBarSwitch.java
@@ -25,6 +25,7 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.tuner.TunerService.Tunable;
 
@@ -41,12 +42,12 @@
     @Override
     public void onAttached() {
         super.onAttached();
-        TunerService.get(getContext()).addTunable(this, StatusBarIconController.ICON_BLACKLIST);
+        Dependency.get(TunerService.class).addTunable(this, StatusBarIconController.ICON_BLACKLIST);
     }
 
     @Override
     public void onDetached() {
-        TunerService.get(getContext()).removeTunable(this);
+        Dependency.get(TunerService.class).removeTunable(this);
         super.onDetached();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
index 3b14e60..74280a3 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
@@ -24,6 +24,7 @@
 import android.util.Log;
 
 import com.android.settingslib.drawer.SettingsDrawerActivity;
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
 
 public class TunerActivity extends SettingsDrawerActivity implements
@@ -36,6 +37,7 @@
 
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        Dependency.initDependencies(this);
 
         if (getFragmentManager().findFragmentByTag(TAG_TUNER) == null) {
             final String action = getIntent().getAction();
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
index fb94061..ca582b3 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -40,6 +40,7 @@
 
 import com.android.systemui.BatteryMeterDrawable;
 import com.android.systemui.DemoMode;
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
 import com.android.systemui.SystemUIApplication;
@@ -51,7 +52,7 @@
 import java.util.Set;
 
 
-public class TunerService extends SystemUI {
+public class TunerService {
 
     public static final String ACTION_CLEAR = "com.android.systemui.action.CLEAR_TUNER";
 
@@ -64,13 +65,14 @@
     private final ArrayMap<Uri, String> mListeningUris = new ArrayMap<>();
     // Map of settings keys to the listener.
     private final HashMap<String, Set<Tunable>> mTunableLookup = new HashMap<>();
+    private final Context mContext;
 
     private ContentResolver mContentResolver;
     private int mCurrentUser;
     private CurrentUserTracker mUserTracker;
 
-    @Override
-    public void start() {
+    public TunerService(Context context) {
+        mContext = context;
         mContentResolver = mContext.getContentResolver();
 
         for (UserInfo user : UserManager.get(mContext).getUsers()) {
@@ -79,7 +81,6 @@
                 upgradeTuner(getValue(TUNER_VERSION, 0), CURRENT_TUNER_VERSION);
             }
         }
-        putComponent(TunerService.class, this);
 
         mCurrentUser = ActivityManager.getCurrentUser();
         mUserTracker = new CurrentUserTracker(mContext) {
@@ -209,32 +210,6 @@
         }
     }
 
-    // Only used in other processes, such as the tuner.
-    private static TunerService sInstance;
-
-    public static TunerService get(Context context) {
-        TunerService service = null;
-        if (context.getApplicationContext() instanceof SystemUIApplication) {
-            SystemUIApplication sysUi = (SystemUIApplication) context.getApplicationContext();
-            service = sysUi.getComponent(TunerService.class);
-        }
-        if (service == null) {
-            // Can't get it as a component, must in the tuner, lets just create one for now.
-            return getStaticService(context);
-        }
-        return service;
-    }
-
-    private static TunerService getStaticService(Context context) {
-        if (sInstance == null) {
-            sInstance = new TunerService();
-            sInstance.mContext = context.getApplicationContext();
-            sInstance.mComponents = new HashMap<>();
-            sInstance.start();
-        }
-        return sInstance;
-    }
-
     public static final void showResetRequest(final Context context, final Runnable onDisabled) {
         SystemUIDialog dialog = new SystemUIDialog(context);
         dialog.setShowForAllUsers(true);
@@ -310,7 +285,7 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             if (ACTION_CLEAR.equals(intent.getAction())) {
-                get(context).clearAll();
+                Dependency.get(TunerService.class).clearAll();
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerSwitch.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerSwitch.java
index 5b9ebd7..d5b6ccd 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerSwitch.java
@@ -7,6 +7,7 @@
 import android.util.AttributeSet;
 
 import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.tuner.TunerService.Tunable;
 
@@ -26,12 +27,12 @@
     @Override
     public void onAttached() {
         super.onAttached();
-        TunerService.get(getContext()).addTunable(this, getKey().split(","));
+        Dependency.get(TunerService.class).addTunable(this, getKey().split(","));
     }
 
     @Override
     public void onDetached() {
-        TunerService.get(getContext()).removeTunable(this);
+        Dependency.get(TunerService.class).removeTunable(this);
         super.onDetached();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
index d057d863..b9cb575 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
@@ -69,6 +69,7 @@
 import android.widget.TextView;
 
 import com.android.settingslib.Utils;
+import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.policy.ZenModeController;
@@ -165,7 +166,7 @@
 
         controller.addCallback(mControllerCallbackH, mHandler);
         controller.getState();
-        TunerService.get(mContext).addTunable(this, SHOW_FULL_ZEN);
+        Dependency.get(TunerService.class).addTunable(this, SHOW_FULL_ZEN);
 
         final Configuration currentConfig = mContext.getResources().getConfiguration();
         mDensity = currentConfig.densityDpi;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
index 78145fe..0a1d34f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@@ -72,7 +72,7 @@
         mDialog = new VolumeDialog(context, WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY,
                 mController, mZenModeController, mVolumeDialogCallback);
         applyConfiguration();
-        TunerService.get(mContext).addTunable(this, VOLUME_DOWN_SILENT, VOLUME_UP_SILENT,
+        Dependency.get(TunerService.class).addTunable(this, VOLUME_DOWN_SILENT, VOLUME_UP_SILENT,
                 VOLUME_SILENT_DO_NOT_DISTURB);
     }
 
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 408e8f3..6d62435 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -17,6 +17,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.systemui.tests">
 
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
     <uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" />
     <uses-permission android:name="android.permission.INJECT_EVENTS" />
     <uses-permission android:name="android.permission.READ_PHONE_STATE" />
diff --git a/packages/SystemUI/tests/src/com/android/systemui/FragmentTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/FragmentTestCase.java
index 447edac..f8f67bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/FragmentTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/FragmentTestCase.java
@@ -19,13 +19,17 @@
 import android.app.FragmentController;
 import android.app.FragmentHostCallback;
 import android.app.FragmentManagerNonConfig;
+import android.graphics.PixelFormat;
 import android.os.Handler;
-import android.os.HandlerThread;
+import android.os.Looper;
 import android.os.Parcelable;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
 import android.widget.FrameLayout;
 
+import com.android.systemui.utils.ViewUtils;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
 
 import org.junit.After;
@@ -45,7 +49,6 @@
 
     private static final int VIEW_ID = 42;
     private final Class<? extends Fragment> mCls;
-    private HandlerThread mHandlerThread;
     private Handler mHandler;
     private FrameLayout mView;
     protected FragmentController mFragments;
@@ -59,9 +62,7 @@
     public void setupFragment() throws IllegalAccessException, InstantiationException {
         mView = new FrameLayout(mContext);
         mView.setId(VIEW_ID);
-        mHandlerThread = new HandlerThread("FragmentTestThread");
-        mHandlerThread.start();
-        mHandler = new Handler(mHandlerThread.getLooper());
+        mHandler = new Handler(Looper.getMainLooper());
         mFragment = mCls.newInstance();
         postAndWait(() -> {
             mFragments = FragmentController.createController(new HostCallbacks());
@@ -78,7 +79,6 @@
             // Set mFragments to null to let it know not to destroy.
             postAndWait(() -> mFragments.dispatchDestroy());
         }
-        mHandlerThread.quit();
     }
 
     @Test
@@ -100,6 +100,26 @@
     }
 
     @Test
+    public void testAttachDetach() {
+        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
+                LayoutParams.TYPE_SYSTEM_ALERT,
+                0, PixelFormat.TRANSLUCENT);
+        postAndWait(() -> mFragments.dispatchResume());
+        attachFragmentToWindow();
+        detachFragmentToWindow();
+        postAndWait(() -> mFragments.dispatchPause());
+    }
+
+    protected void attachFragmentToWindow() {
+        ViewUtils.attachView(mView);
+    }
+
+    protected void detachFragmentToWindow() {
+        ViewUtils.detachView(mView);
+    }
+
+    @Test
     public void testRecreate() {
         postAndWait(() -> mFragments.dispatchResume());
         postAndWait(() -> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index f258e5d..81a50d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -65,7 +65,7 @@
         waitForIdleSync(mHandler);
     }
 
-    protected void waitForIdleSync(Handler h) {
+    public static void waitForIdleSync(Handler h) {
         validateThread(h.getLooper());
         Idler idler = new Idler(null);
         h.getLooper().getQueue().addIdleHandler(idler);
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 5345031..29d5a36 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -42,6 +42,7 @@
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.tuner.TunerService;
+import com.android.systemui.utils.leaks.LeakCheckedTest;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -60,11 +61,7 @@
     public void addLeakCheckDependencies() {
         injectTestDependency(Dependency.BG_LOOPER, Looper.getMainLooper());
         injectMockDependency(UserSwitcherController.class);
-        injectLeakCheckedDependencies(BluetoothController.class, LocationController.class,
-                RotationLockController.class, NetworkController.class, ZenModeController.class,
-                HotspotController.class, CastController.class, FlashlightController.class,
-                UserInfoController.class, KeyguardMonitor.class, SecurityController.class,
-                BatteryController.class, NextAlarmController.class);
+        injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
     }
 
     @Test
@@ -88,6 +85,6 @@
 
         host.destroy();
         // Ensure the tuner cleans up its persistent listeners.
-        TunerService.get(mContext).destroy();
+        Dependency.get(TunerService.class).destroy();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java
new file mode 100644
index 0000000..7141170
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import com.android.systemui.utils.ViewUtils;
+import com.android.systemui.utils.leaks.LeakCheckedTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class NotificationMenuRowTest extends LeakCheckedTest {
+
+    @Before
+    public void setup() {
+        injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
+    }
+
+    @Test
+    public void testAttachDetach() {
+        NotificationMenuRow row = new NotificationMenuRow(mContext);
+        ViewUtils.attachView(row);
+        ViewUtils.detachView(row);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
index e28d077..a9d6df7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
@@ -40,6 +40,7 @@
         mContext.putComponent(Recents.class, mock(Recents.class));
         mContext.putComponent(Divider.class, mock(Divider.class));
         mContext.addMockSystemService(Context.WINDOW_SERVICE, mock(WindowManager.class));
+        injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/ViewUtils.java b/packages/SystemUI/tests/src/com/android/systemui/utils/ViewUtils.java
new file mode 100644
index 0000000..01996ca
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/ViewUtils.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.utils;
+
+import android.graphics.PixelFormat;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.support.test.InstrumentationRegistry;
+
+import com.android.systemui.SysuiTestCase;
+
+public class ViewUtils {
+
+    public static void attachView(View view) {
+        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
+                LayoutParams.TYPE_SYSTEM_ALERT,
+                0, PixelFormat.TRANSLUCENT);
+        Handler handler = new Handler(Looper.getMainLooper());
+        handler.post(() -> InstrumentationRegistry.getContext()
+                .getSystemService(WindowManager.class).addView(view, lp));
+        SysuiTestCase.waitForIdleSync(handler);
+    }
+
+    public static void detachView(View view) {
+        Handler handler = new Handler(Looper.getMainLooper());
+        handler.post(() -> InstrumentationRegistry.getContext()
+                .getSystemService(WindowManager.class).removeView(view));
+        SysuiTestCase.waitForIdleSync(handler);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java
new file mode 100644
index 0000000..d1abcca
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.utils.leaks;
+
+import android.content.Context;
+
+import com.android.systemui.plugins.Plugin;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
+
+public class FakePluginManager extends PluginManager {
+
+    private final BaseLeakChecker<PluginListener> mLeakChecker;
+
+    public FakePluginManager(Context context, LeakCheckedTest test) {
+        super(context);
+        mLeakChecker = new BaseLeakChecker<>(test, "Plugin");
+    }
+
+    @Override
+    public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
+            int version) {
+        mLeakChecker.addCallback(listener);
+    }
+
+    @Override
+    public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
+            int version, boolean allowMultiple) {
+        mLeakChecker.addCallback(listener);
+    }
+
+    @Override
+    public void removePluginListener(PluginListener<?> listener) {
+        mLeakChecker.removeCallback(listener);
+    }
+
+    @Override
+    public <T extends Plugin> T getOneShotPlugin(String action, int version) {
+        return null;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeTunerService.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeTunerService.java
new file mode 100644
index 0000000..f553277
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeTunerService.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.utils.leaks;
+
+import android.content.Context;
+
+import com.android.systemui.tuner.TunerService;
+
+public class FakeTunerService extends TunerService {
+
+    private final BaseLeakChecker<Tunable> mBaseLeakChecker;
+
+    public FakeTunerService(Context context, LeakCheckedTest test) {
+        super(context);
+        mBaseLeakChecker = new BaseLeakChecker<>(test, "tunable");
+    }
+
+    @Override
+    public void addTunable(Tunable tunable, String... keys) {
+        for (String key : keys) {
+            tunable.onTuningChanged(key, null);
+        }
+        mBaseLeakChecker.addCallback(tunable);
+    }
+
+    @Override
+    public void removeTunable(Tunable tunable) {
+        mBaseLeakChecker.removeCallback(tunable);
+    }
+}
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
index c182493..c2048c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
@@ -20,6 +20,7 @@
 import android.util.ArrayMap;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BluetoothController;
@@ -35,6 +36,7 @@
 import com.android.systemui.statusbar.policy.SecurityController;
 import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.tuner.TunerService;
 
 import org.junit.Assert;
 import org.junit.Rule;
@@ -56,6 +58,25 @@
     private final Map<String, Tracker> mTrackers = new HashMap<>();
     private final Map<Class, Object> mLeakCheckers = new ArrayMap<>();
 
+    public static final Class<?>[] ALL_SUPPORTED_CLASSES = new Class[] {
+            BluetoothController.class,
+            LocationController.class,
+            RotationLockController.class,
+            ZenModeController.class,
+            CastController.class,
+            HotspotController.class,
+            FlashlightController.class,
+            UserInfoController.class,
+            KeyguardMonitor.class,
+            BatteryController.class,
+            SecurityController.class,
+            ManagedProfileController.class,
+            NextAlarmController.class,
+            NetworkController.class,
+            PluginManager.class,
+            TunerService.class,
+    };
+
     @Rule
     public TestWatcher successWatcher = new TestWatcher() {
         @Override
@@ -96,6 +117,10 @@
                 obj = new FakeNextAlarmController(this);
             } else if (cls == NetworkController.class) {
                 obj = new FakeNetworkController(this);
+            } else if (cls == PluginManager.class) {
+                obj = new FakePluginManager(mContext, this);
+            } else if (cls == TunerService.class) {
+                obj = new FakeTunerService(mContext, this);
             } else {
                 Assert.fail(cls.getName() + " is not supported by LeakCheckedTest yet");
             }