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