Adds a DumpController to SystemUI Dependencies

The DumpController allows any SystemUI Dumpable class to subscribe to it
and be dumped during dumpsys of Dependency.

Test: manual (dumpsys Dependency shows "DumpController state)
Test: atest DumpControllerTest
Bug: 129544734
Change-Id: If8d3ec667d99a523e5ae25db84173d9b04b6829c
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 70f2cce..a421940 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -293,6 +293,7 @@
     @Inject Lazy<DevicePolicyManagerWrapper> mDevicePolicyManagerWrapper;
     @Inject Lazy<PackageManagerWrapper> mPackageManagerWrapper;
     @Inject Lazy<SensorPrivacyController> mSensorPrivacyController;
+    @Inject Lazy<DumpController> mDumpController;
 
     @Inject
     public Dependency() {
@@ -464,7 +465,7 @@
         mProviders.put(DevicePolicyManagerWrapper.class, mDevicePolicyManagerWrapper::get);
         mProviders.put(PackageManagerWrapper.class, mPackageManagerWrapper::get);
         mProviders.put(SensorPrivacyController.class, mSensorPrivacyController::get);
-
+        mProviders.put(DumpController.class, mDumpController::get);
 
         // TODO(b/118592525): to support multi-display , we start to add something which is
         //                    per-display, while others may be global. I think it's time to add
@@ -478,6 +479,11 @@
     @Override
     public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         super.dump(fd, pw, args);
+
+        // Make sure that the DumpController gets added to mDependencies, as they are only added
+        // with Dependency#get.
+        getDependency(DumpController.class);
+
         pw.println("Dumping existing controllers:");
         mDependencies.values().stream().filter(obj -> obj instanceof Dumpable)
                 .forEach(o -> ((Dumpable) o).dump(fd, pw, args));
diff --git a/packages/SystemUI/src/com/android/systemui/DumpController.kt b/packages/SystemUI/src/com/android/systemui/DumpController.kt
new file mode 100644
index 0000000..646abb5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/DumpController.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2019 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 android.util.Log
+import androidx.annotation.GuardedBy
+import com.android.internal.util.Preconditions
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import java.lang.ref.WeakReference
+import javax.inject.Inject
+import javax.inject.Singleton
+
+// TODO: Move all Dumpable dependencies to use DumpController
+/**
+ * Controller that allows any [Dumpable] to subscribe and be dumped along with other SystemUI
+ * dependencies.
+ */
+@Singleton
+class DumpController @Inject constructor() : Dumpable {
+
+    companion object {
+        private const val TAG = "DumpController"
+        private const val DEBUG = false
+    }
+
+    @GuardedBy("listeners")
+    private val listeners = mutableListOf<WeakReference<Dumpable>>()
+    val numListeners: Int
+        get() = listeners.size
+
+    /**
+     * Adds a [Dumpable] listener to be dumped. It will only be added if it is not already tracked.
+     *
+     * @param listener the [Dumpable] to be added
+     */
+    fun addListener(listener: Dumpable) {
+        Preconditions.checkNotNull(listener, "The listener to be added cannot be null")
+        if (DEBUG) Log.v(TAG, "*** register callback for $listener")
+        synchronized<Unit>(listeners) {
+            if (listeners.any { it.get() == listener }) {
+                if (DEBUG) {
+                    Log.e(TAG, "Object tried to add another callback")
+                }
+            } else {
+                listeners.add(WeakReference(listener))
+            }
+        }
+    }
+
+    /**
+     * Removes a listener from the list of elements to be dumped.
+     *
+     * @param listener the [Dumpable] to be removed.
+     */
+    fun removeListener(listener: Dumpable) {
+        if (DEBUG) Log.v(TAG, "*** unregister callback for $listener")
+        synchronized(listeners) {
+            listeners.removeAll { it.get() == listener || it.get() == null }
+        }
+    }
+
+    /**
+     * Dump all the [Dumpable] registered with the controller
+     */
+    override fun dump(fd: FileDescriptor?, pw: PrintWriter, args: Array<String>?) {
+        pw.println("DumpController state:")
+        synchronized(listeners) {
+            listeners.forEach { it.get()?.dump(fd, pw, args) }
+        }
+    }
+}