summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/Dependency.java52
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUI.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIApplication.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUISecondaryUserService.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIService.java74
-rw-r--r--packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt367
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt150
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt59
15 files changed, 683 insertions, 156 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 69bc2596d411..362a9e8306c3 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -42,6 +42,7 @@ import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -128,8 +129,6 @@ import com.android.systemui.wm.DisplayController;
import com.android.systemui.wm.DisplayImeController;
import com.android.systemui.wm.SystemWindows;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
import java.util.function.Consumer;
import javax.inject.Inject;
@@ -211,6 +210,8 @@ public class Dependency {
private final ArrayMap<Object, Object> mDependencies = new ArrayMap<>();
private final ArrayMap<Object, LazyDependencyCreator> mProviders = new ArrayMap<>();
+ @Inject DumpManager mDumpManager;
+
@Inject Lazy<ActivityStarter> mActivityStarter;
@Inject Lazy<BroadcastDispatcher> mBroadcastDispatcher;
@Inject Lazy<AsyncSensorManager> mAsyncSensorManager;
@@ -534,34 +535,6 @@ public class Dependency {
sDependency = this;
}
- static void staticDump(FileDescriptor fd, PrintWriter pw, String[] args) {
- sDependency.dump(fd, pw, args);
- }
-
- /**
- * {@see SystemUI.dump}
- */
- public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- // Make sure that the DumpController gets added to mDependencies, as they are only added
- // with Dependency#get.
- getDependency(DumpController.class);
- getDependency(BroadcastDispatcher.class);
-
- // If an arg is specified, try to dump the dependency
- String controller = args != null && args.length > 1
- ? args[1].toLowerCase()
- : null;
- if (controller != null) {
- pw.println("Dumping controller=" + controller + ":");
- } else {
- pw.println("Dumping existing controllers:");
- }
- mDependencies.values().stream()
- .filter(obj -> obj instanceof Dumpable && (controller == null
- || obj.getClass().getName().toLowerCase().endsWith(controller)))
- .forEach(o -> ((Dumpable) o).dump(fd, pw, args));
- }
-
protected final <T> T getDependency(Class<T> cls) {
return getDependencyInner(cls);
}
@@ -576,6 +549,11 @@ public class Dependency {
if (obj == null) {
obj = createDependency(key);
mDependencies.put(key, obj);
+
+ // TODO: Get dependencies to register themselves instead
+ if (autoRegisterModulesForDump() && obj instanceof Dumpable) {
+ mDumpManager.registerDumpable(obj.getClass().getName(), (Dumpable) obj);
+ }
}
return obj;
}
@@ -593,6 +571,17 @@ public class Dependency {
return provider.createDependency();
}
+ // Currently, there are situations in tests where we might create more than one instance of a
+ // thing that should be a singleton: the "real" one (created by Dagger, usually as a result of
+ // inflating a view), and a mocked one (injected into Dependency). If we register the mocked
+ // one, the DumpManager will throw an exception complaining (rightly) that we have too many
+ // things registered with that name. So in tests, we disable the auto-registration until the
+ // root cause is fixed, i.e. inflated views in tests with Dagger dependencies.
+ @VisibleForTesting
+ protected boolean autoRegisterModulesForDump() {
+ return true;
+ }
+
private static Dependency sDependency;
/**
@@ -605,6 +594,9 @@ public class Dependency {
private <T> void destroyDependency(Class<T> cls, Consumer<T> destroy) {
T dep = (T) mDependencies.remove(cls);
+ if (dep instanceof Dumpable) {
+ mDumpManager.unregisterDumpable(dep.getClass().getName());
+ }
if (dep != null && destroy != null) {
destroy.accept(dep);
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUI.java b/packages/SystemUI/src/com/android/systemui/SystemUI.java
index f795faf30603..e880cc8fd10a 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUI.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUI.java
@@ -21,10 +21,18 @@ import android.content.Context;
import android.content.res.Configuration;
import android.os.Bundle;
+import androidx.annotation.NonNull;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
-public abstract class SystemUI {
+/**
+ * A top-level module of system UI code (sometimes called "system UI services" elsewhere in code).
+ * Which SystemUI modules are loaded can be controlled via a config resource.
+ *
+ * @see SystemUIApplication#startServicesIfNeeded()
+ */
+public abstract class SystemUI implements Dumpable {
protected final Context mContext;
public SystemUI(Context context) {
@@ -36,7 +44,8 @@ public abstract class SystemUI {
protected void onConfigurationChanged(Configuration newConfig) {
}
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ @Override
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
}
protected void onBootCompleted() {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 13151527cd5f..cbdae4e6fe63 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -33,6 +33,7 @@ import android.util.TimingsTraceLog;
import com.android.systemui.dagger.ContextComponentHelper;
import com.android.systemui.dagger.SystemUIRootComponent;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.util.NotificationChannels;
import java.lang.reflect.Constructor;
@@ -171,6 +172,8 @@ public class SystemUIApplication extends Application implements
}
}
+ final DumpManager dumpManager = mRootComponent.createDumpManager();
+
Log.v(TAG, "Starting SystemUI services for user " +
Process.myUserHandle().getIdentifier() + ".");
TimingsTraceLog log = new TimingsTraceLog("SystemUIBootTiming",
@@ -209,6 +212,8 @@ public class SystemUIApplication extends Application implements
if (mBootCompleteCache.isBootComplete()) {
mServices[i].onBootCompleted();
}
+
+ dumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]);
}
mRootComponent.getInitController().executePostInitTasks();
log.traceEnd();
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUISecondaryUserService.java b/packages/SystemUI/src/com/android/systemui/SystemUISecondaryUserService.java
index 2d2d91db4fe1..f4ec6f75b06b 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUISecondaryUserService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUISecondaryUserService.java
@@ -20,9 +20,6 @@ import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
public class SystemUISecondaryUserService extends Service {
@Override
@@ -35,11 +32,4 @@ public class SystemUISecondaryUserService extends Service {
public IBinder onBind(Intent intent) {
return null;
}
-
- @Override
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- SystemUIService.dumpServices(
- ((SystemUIApplication) getApplication()).getServices(), fd, pw, args);
- }
}
-
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
index 41d83148e093..e65fccd9f132 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
@@ -16,7 +16,6 @@
package com.android.systemui;
-import android.annotation.NonNull;
import android.app.Service;
import android.content.Intent;
import android.os.Build;
@@ -28,8 +27,7 @@ import android.util.Slog;
import com.android.internal.os.BinderInternal;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.shared.plugins.PluginManagerImpl;
+import com.android.systemui.dump.DumpManager;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -39,11 +37,15 @@ import javax.inject.Inject;
public class SystemUIService extends Service {
private final Handler mMainHandler;
+ private final DumpManager mDumpManager;
@Inject
- public SystemUIService(@Main Handler mainHandler) {
+ public SystemUIService(
+ @Main Handler mainHandler,
+ DumpManager dumpManager) {
super();
mMainHandler = mainHandler;
+ mDumpManager = dumpManager;
}
@Override
@@ -79,62 +81,16 @@ public class SystemUIService extends Service {
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- dumpServices(((SystemUIApplication) getApplication()).getServices(), fd, pw, args);
-
- if (args == null || args.length == 0 || args[0].equals("--config")) {
- dumpConfig(pw);
- }
- }
-
- static void dumpServices(
- SystemUI[] services, FileDescriptor fd, PrintWriter pw, String[] args) {
- if (args == null || args.length == 0) {
- pw.println("dumping service: " + Dependency.class.getName());
- Dependency.staticDump(fd, pw, args);
- for (SystemUI ui: services) {
- pw.println("dumping service: " + ui.getClass().getName());
- ui.dump(fd, pw, args);
- }
- if (Build.IS_DEBUGGABLE) {
- pw.println("dumping plugins:");
- ((PluginManagerImpl) Dependency.get(PluginManager.class)).dump(fd, pw, args);
- }
- } else {
- String svc = args[0].toLowerCase();
- if (Dependency.class.getName().toLowerCase().endsWith(svc)) {
- Dependency.staticDump(fd, pw, args);
- }
- for (SystemUI ui: services) {
- String name = ui.getClass().getName().toLowerCase();
- if (name.endsWith(svc)) {
- ui.dump(fd, pw, args);
- }
- }
+ // If no args are passed, assume we're being dumped as part of a bug report (sadly, we have
+ // no better way to guess whether this is taking place). Set the appropriate dump priority
+ // (CRITICAL) to reflect that this is taking place.
+ String[] massagedArgs = args;
+ if (args.length == 0) {
+ massagedArgs = new String[] {
+ DumpManager.PRIORITY_ARG,
+ DumpManager.PRIORITY_ARG_CRITICAL};
}
- }
-
- private void dumpConfig(@NonNull PrintWriter pw) {
- pw.println("SystemUiServiceComponents configuration:");
- pw.print("vendor component: ");
- pw.println(getResources().getString(R.string.config_systemUIVendorServiceComponent));
-
- dumpConfig(pw, "global", R.array.config_systemUIServiceComponents);
- dumpConfig(pw, "per-user", R.array.config_systemUIServiceComponentsPerUser);
- }
-
- private void dumpConfig(@NonNull PrintWriter pw, @NonNull String type, int resId) {
- final String[] services = getResources().getStringArray(resId);
- pw.print(type); pw.print(": ");
- if (services == null) {
- pw.println("N/A");
- return;
- }
- pw.print(services.length);
- pw.println(" services");
- for (int i = 0; i < services.length; i++) {
- pw.print(" "); pw.print(i); pw.print(": "); pw.println(services[i]);
- }
+ mDumpManager.dump(fd, pw, massagedArgs);
}
}
-
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index cedf7c354ccc..bd803fa76f13 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -31,6 +31,7 @@ import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.Dumpable
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
import java.io.FileDescriptor
import java.io.PrintWriter
import java.util.concurrent.Executor
@@ -65,12 +66,18 @@ private const val DEBUG = true
open class BroadcastDispatcher @Inject constructor (
private val context: Context,
@Main private val mainHandler: Handler,
- @Background private val bgLooper: Looper
+ @Background private val bgLooper: Looper,
+ dumpManager: DumpManager
) : Dumpable {
// Only modify in BG thread
private val receiversByUser = SparseArray<UserBroadcastDispatcher>(20)
+ init {
+ // TODO: Don't do this in the constructor
+ dumpManager.registerDumpable(javaClass.name, this)
+ }
+
/**
* Register a receiver for broadcast with the dispatcher
*
@@ -112,10 +119,10 @@ open class BroadcastDispatcher @Inject constructor (
*/
@JvmOverloads
fun registerReceiver(
- receiver: BroadcastReceiver,
- filter: IntentFilter,
- executor: Executor? = context.mainExecutor,
- user: UserHandle = context.user
+ receiver: BroadcastReceiver,
+ filter: IntentFilter,
+ executor: Executor? = context.mainExecutor,
+ user: UserHandle = context.user
) {
checkFilter(filter)
this.handler
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java
index 12b9be11817a..18c3eacbc693 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java
@@ -25,6 +25,7 @@ import com.android.systemui.Dependency;
import com.android.systemui.InitController;
import com.android.systemui.SystemUIAppComponentFactory;
import com.android.systemui.SystemUIFactory;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.KeyguardSliceProvider;
import com.android.systemui.pip.phone.dagger.PipModule;
@@ -76,6 +77,10 @@ public interface SystemUIRootComponent {
@Singleton
Dependency.DependencyInjector createDependency();
+ /** */
+ @Singleton
+ DumpManager createDumpManager();
+
/**
* FragmentCreator generates all Fragments that need injection.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
new file mode 100644
index 000000000000..59a7a328e9ae
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -0,0 +1,367 @@
+/*
+ * Copyright (C) 2020 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.dump
+
+import android.content.Context
+import android.os.SystemClock
+import android.os.Trace
+import android.util.ArrayMap
+import com.android.systemui.Dumpable
+import com.android.systemui.R
+import com.android.systemui.dump.DumpManager.Companion.PRIORITY_ARG_CRITICAL
+import com.android.systemui.dump.DumpManager.Companion.PRIORITY_ARG_HIGH
+import com.android.systemui.dump.DumpManager.Companion.PRIORITY_ARG_NORMAL
+import com.android.systemui.log.LogBuffer
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * Oversees SystemUI's output during bug reports (and dumpsys in general)
+ *
+ * When a bug report is taken, SystemUI dumps various diagnostic information that we hope will be
+ * useful for the eventual readers of the bug report. Code that wishes to participate in this dump
+ * should register itself here.
+ *
+ * Dump output is split into two sections, CRITICAL and NORMAL. All dumpables registered via
+ * [registerDumpable] appear in the CRITICAL section, while all [LogBuffer]s appear in the NORMAL
+ * section (due to their length).
+ *
+ * The CRITICAL and NORMAL sections can be found within a bug report by searching for
+ * "SERVICE com.android.systemui/.SystemUIService" and
+ * "SERVICE com.android.systemui/.dump.SystemUIAuxiliaryDumpService", respectively.
+ *
+ * Finally, some or all of the dump can be triggered on-demand via adb (see below).
+ *
+ * ```
+ * # For the following, let <invocation> be:
+ * $ adb shell dumpsys activity service com.android.systemui/.SystemUIService
+ *
+ * # To dump specific target(s), specify one or more registered names:
+ * $ <invocation> NotifCollection
+ * $ <invocation> StatusBar FalsingManager BootCompleteCacheImpl
+ *
+ * # Log buffers can be dumped in the same way (and can even be mixed in with other dump targets,
+ * # although it's not clear why one would want such a thing):
+ * $ <invocation> NotifLog
+ * $ <invocation> StatusBar NotifLog BootCompleteCacheImpl
+ *
+ * # If passing -t or --tail, shows only the last N lines of any log buffers:
+ * $ <invocation> NotifLog --tail 100
+ *
+ * # Dump targets are matched using String.endsWith(), so dumpables that register using their
+ * # fully-qualified class name can still be dumped using their short name:
+ * $ <invocation> com.android.keyguard.KeyguardUpdateMonitor
+ * $ <invocation> keyguard.KeyguardUpdateMonitor
+ * $ <invocation> KeyguardUpdateMonitor
+ *
+ * # To dump all dumpables or all buffers:
+ * $ <invocation> dumpables
+ * $ <invocation> buffers
+ *
+ * Finally, the following will simulate what we dump during the CRITICAL and NORMAL sections of a
+ * bug report:
+ * $ <invocation> bugreport-critical
+ * $ <invocation> bugreport-normal
+ * ```
+ */
+@Singleton
+class DumpManager @Inject constructor(
+ private val context: Context
+) {
+ private val dumpables: MutableMap<String, RegisteredDumpable<Dumpable>> = ArrayMap()
+ private val buffers: MutableMap<String, RegisteredDumpable<LogBuffer>> = ArrayMap()
+
+ /**
+ * Register a dumpable to be called during a bug report. The dumpable will be called during the
+ * CRITICAL section of the bug report, so don't dump an excessive amount of stuff here.
+ *
+ * @param name The name to register the dumpable under. This is typically the qualified class
+ * name of the thing being dumped (getClass().getName()), but can be anything as long as it
+ * doesn't clash with an existing registration.
+ */
+ @Synchronized
+ fun registerDumpable(name: String, module: Dumpable) {
+ if (RESERVED_NAMES.contains(name)) {
+ throw IllegalArgumentException("'$name' is reserved")
+ }
+
+ if (!canAssignToNameLocked(name, module)) {
+ throw IllegalArgumentException("'$name' is already registered")
+ }
+
+ dumpables[name] = RegisteredDumpable(name, module)
+ }
+
+ /**
+ * Unregisters a previously-registered dumpable.
+ */
+ @Synchronized
+ fun unregisterDumpable(name: String) {
+ dumpables.remove(name)
+ }
+
+ /**
+ * Register a [LogBuffer] to be dumped during a bug report.
+ */
+ @Synchronized
+ fun registerBuffer(name: String, buffer: LogBuffer) {
+ if (!canAssignToNameLocked(name, buffer)) {
+ throw IllegalArgumentException("'$name' is already registered")
+ }
+ buffers[name] = RegisteredDumpable(name, buffer)
+ }
+
+ /**
+ * Dump the diagnostics! Behavior can be controlled via [args].
+ */
+ @Synchronized
+ fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
+ Trace.beginSection("DumpManager#dump()")
+ val start = SystemClock.uptimeMillis()
+
+ val parsedArgs = try {
+ parseArgs(args)
+ } catch (e: ArgParseException) {
+ pw.println(e.message)
+ return
+ }
+
+ when (parsedArgs.dumpPriority) {
+ PRIORITY_ARG_CRITICAL -> dumpCriticalLocked(fd, pw, parsedArgs)
+ PRIORITY_ARG_NORMAL -> dumpNormalLocked(pw, parsedArgs)
+ else -> dumpParameterizedLocked(fd, pw, parsedArgs)
+ }
+
+ pw.println()
+ pw.println("Dump took ${SystemClock.uptimeMillis() - start}ms")
+ Trace.endSection()
+ }
+
+ private fun dumpCriticalLocked(fd: FileDescriptor, pw: PrintWriter, args: ParsedArgs) {
+ dumpDumpablesLocked(fd, pw, args)
+ dumpConfig(pw)
+ }
+
+ private fun dumpNormalLocked(pw: PrintWriter, args: ParsedArgs) {
+ dumpBuffersLocked(pw, args)
+ }
+
+ private fun dumpParameterizedLocked(fd: FileDescriptor, pw: PrintWriter, args: ParsedArgs) {
+ when (args.command) {
+ "bugreport-critical" -> dumpCriticalLocked(fd, pw, args)
+ "bugreport-normal" -> dumpNormalLocked(pw, args)
+ "dumpables" -> dumpDumpablesLocked(fd, pw, args)
+ "buffers" -> dumpBuffersLocked(pw, args)
+ else -> dumpTargetsLocked(args.nonFlagArgs, fd, pw, args)
+ }
+ }
+
+ private fun dumpTargetsLocked(
+ targets: List<String>,
+ fd: FileDescriptor,
+ pw: PrintWriter,
+ args: ParsedArgs
+ ) {
+ if (targets.isEmpty()) {
+ pw.println("Nothing to dump :(")
+ } else {
+ for (target in targets) {
+ dumpTarget(target, fd, pw, args)
+ }
+ }
+ }
+
+ private fun dumpTarget(
+ target: String,
+ fd: FileDescriptor,
+ pw: PrintWriter,
+ args: ParsedArgs
+ ) {
+ if (target == "config") {
+ dumpConfig(pw)
+ return
+ }
+
+ for (dumpable in dumpables.values) {
+ if (dumpable.name.endsWith(target)) {
+ dumpDumpable(dumpable, fd, pw, args)
+ return
+ }
+ }
+
+ for (buffer in buffers.values) {
+ if (buffer.name.endsWith(target)) {
+ dumpBuffer(buffer, pw, args)
+ return
+ }
+ }
+ }
+
+ private fun dumpDumpablesLocked(fd: FileDescriptor, pw: PrintWriter, args: ParsedArgs) {
+ for (module in dumpables.values) {
+ dumpDumpable(module, fd, pw, args)
+ }
+ }
+
+ private fun dumpBuffersLocked(pw: PrintWriter, args: ParsedArgs) {
+ for (buffer in buffers.values) {
+ dumpBuffer(buffer, pw, args)
+ }
+ }
+
+ private fun dumpDumpable(
+ dumpable: RegisteredDumpable<Dumpable>,
+ fd: FileDescriptor,
+ pw: PrintWriter,
+ args: ParsedArgs
+ ) {
+ pw.println()
+ pw.println("${dumpable.name}:")
+ pw.println("----------------------------------------------------------------------------")
+ dumpable.dumpable.dump(fd, pw, args.rawArgs)
+ }
+
+ private fun dumpBuffer(
+ buffer: RegisteredDumpable<LogBuffer>,
+ pw: PrintWriter,
+ args: ParsedArgs
+ ) {
+ pw.println()
+ pw.println()
+ pw.println("BUFFER ${buffer.name}:")
+ pw.println("============================================================================")
+ buffer.dumpable.dump(pw, args.tailLength)
+ }
+
+ private fun dumpConfig(pw: PrintWriter) {
+ pw.println("SystemUiServiceComponents configuration:")
+ pw.print("vendor component: ")
+ pw.println(context.resources.getString(R.string.config_systemUIVendorServiceComponent))
+ dumpServiceList(pw, "global", R.array.config_systemUIServiceComponents)
+ dumpServiceList(pw, "per-user", R.array.config_systemUIServiceComponentsPerUser)
+ }
+
+ private fun dumpServiceList(pw: PrintWriter, type: String, resId: Int) {
+ val services: Array<String>? = context.resources.getStringArray(resId)
+ pw.print(type)
+ pw.print(": ")
+ if (services == null) {
+ pw.println("N/A")
+ return
+ }
+ pw.print(services.size)
+ pw.println(" services")
+ for (i in services.indices) {
+ pw.print(" ")
+ pw.print(i)
+ pw.print(": ")
+ pw.println(services[i])
+ }
+ }
+
+ private fun parseArgs(args: Array<String>): ParsedArgs {
+ val mutArgs = args.toMutableList()
+ val pArgs = ParsedArgs(args, mutArgs)
+
+ val iterator = mutArgs.iterator()
+ while (iterator.hasNext()) {
+ val arg = iterator.next()
+ if (arg.startsWith("-")) {
+ iterator.remove()
+ when (arg) {
+ PRIORITY_ARG -> {
+ pArgs.dumpPriority = readArgument(iterator, PRIORITY_ARG) {
+ if (PRIORITY_OPTIONS.contains(it)) {
+ it
+ } else {
+ throw IllegalArgumentException()
+ }
+ }
+ }
+ "-t", "--tail" -> {
+ pArgs.tailLength = readArgument(iterator, "--tail") {
+ it.toInt()
+ }
+ }
+ else -> {
+ throw ArgParseException("Unknown flag: $arg")
+ }
+ }
+ }
+ }
+
+ if (mutArgs.isNotEmpty() && COMMANDS.contains(mutArgs[0])) {
+ pArgs.command = mutArgs.removeAt(0)
+ }
+
+ return pArgs
+ }
+
+ private fun <T> readArgument(
+ iterator: MutableIterator<String>,
+ flag: String,
+ parser: (arg: String) -> T
+ ): T {
+ if (!iterator.hasNext()) {
+ throw ArgParseException("Missing argument for $flag")
+ }
+ val value = iterator.next()
+
+ return try {
+ parser(value).also { iterator.remove() }
+ } catch (e: Exception) {
+ throw ArgParseException("Invalid argument '$value' for flag $flag")
+ }
+ }
+
+ private fun canAssignToNameLocked(name: String, newDumpable: Any): Boolean {
+ val existingDumpable = dumpables[name]?.dumpable ?: buffers[name]?.dumpable
+ return existingDumpable == null || newDumpable == existingDumpable
+ }
+
+ companion object {
+ const val PRIORITY_ARG = "--dump-priority"
+ const val PRIORITY_ARG_CRITICAL = "CRITICAL"
+ const val PRIORITY_ARG_HIGH = "HIGH"
+ const val PRIORITY_ARG_NORMAL = "NORMAL"
+ }
+}
+
+private val PRIORITY_OPTIONS =
+ arrayOf(PRIORITY_ARG_CRITICAL, PRIORITY_ARG_HIGH, PRIORITY_ARG_NORMAL)
+
+private val COMMANDS = arrayOf("bugreport-critical", "bugreport-normal", "buffers", "dumpables")
+
+private val RESERVED_NAMES = arrayOf("config", *COMMANDS)
+
+private data class RegisteredDumpable<T>(
+ val name: String,
+ val dumpable: T
+)
+
+private class ParsedArgs(
+ val rawArgs: Array<String>,
+ val nonFlagArgs: List<String>
+) {
+ var dumpPriority: String? = null
+ var tailLength: Int = 0
+ var command: String? = null
+}
+
+class ArgParseException(message: String) : Exception(message) \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
index 18c7baec1f74..7defef90380f 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
@@ -17,9 +17,9 @@
package com.android.systemui.log
import android.util.Log
-import com.android.systemui.DumpController
-import com.android.systemui.Dumpable
+import com.android.systemui.dump.DumpManager
import com.android.systemui.log.dagger.LogModule
+import java.io.PrintWriter
import java.text.SimpleDateFormat
import java.util.ArrayDeque
import java.util.Locale
@@ -35,11 +35,10 @@ import java.util.Locale
* You can dump the entire buffer at any time by running:
*
* ```
- * $ adb shell dumpsys activity service com.android.systemui/.SystemUIService \
- * dependency DumpController <bufferName>
+ * $ adb shell dumpsys activity service com.android.systemui/.SystemUIService <bufferName>
* ```
*
- * where `bufferName` is the (case-sensitive) [name] passed to the constructor.
+ * ...where `bufferName` is the (case-sensitive) [name] passed to the constructor.
*
* By default, only messages of WARN level or higher are echoed to logcat, but this can be adjusted
* locally (usually for debugging purposes).
@@ -75,8 +74,8 @@ class LogBuffer(
) {
private val buffer: ArrayDeque<LogMessageImpl> = ArrayDeque()
- fun attach(dumpController: DumpController) {
- dumpController.registerDumpable(name, onDump)
+ fun attach(dumpManager: DumpManager) {
+ dumpManager.registerBuffer(name, this)
}
/**
@@ -174,22 +173,26 @@ class LogBuffer(
}
/** Converts the entire buffer to a newline-delimited string */
- fun dump(): String {
+ fun dump(pw: PrintWriter, tailLength: Int) {
synchronized(buffer) {
- val sb = StringBuilder()
- for (message in buffer) {
- dumpMessage(message, sb)
+ val start = if (tailLength <= 0) { 0 } else { buffer.size - tailLength }
+
+ for ((i, message) in buffer.withIndex()) {
+ if (i >= start) {
+ dumpMessage(message, pw)
+ }
}
- return sb.toString()
}
}
- private fun dumpMessage(message: LogMessage, sb: StringBuilder) {
- sb.append(DATE_FORMAT.format(message.timestamp))
- .append(" ").append(message.level)
- .append(" ").append(message.tag)
- .append(" ").append(message.printer(message))
- .append("\n")
+ private fun dumpMessage(message: LogMessage, pw: PrintWriter) {
+ pw.print(DATE_FORMAT.format(message.timestamp))
+ pw.print(" ")
+ pw.print(message.level)
+ pw.print(" ")
+ pw.print(message.tag)
+ pw.print(" ")
+ pw.println(message.printer(message))
}
private fun echoToLogcat(message: LogMessage) {
@@ -203,10 +206,6 @@ class LogBuffer(
LogLevel.WTF -> Log.wtf(message.tag, strMessage)
}
}
-
- private val onDump = Dumpable { _, pw, _ ->
- pw.println(dump())
- }
}
private const val TAG = "LogBuffer"
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 4a7469c1bd2d..b1d972e6d97f 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -20,8 +20,8 @@ import android.content.ContentResolver;
import android.os.Build;
import android.os.Looper;
-import com.android.systemui.DumpController;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.LogcatEchoTracker;
import com.android.systemui.log.LogcatEchoTrackerDebug;
@@ -43,9 +43,9 @@ public class LogModule {
@DozeLog
public static LogBuffer provideDozeLogBuffer(
LogcatEchoTracker bufferFilter,
- DumpController dumpController) {
+ DumpManager dumpManager) {
LogBuffer buffer = new LogBuffer("DozeLog", 100, 10, bufferFilter);
- buffer.attach(dumpController);
+ buffer.attach(dumpManager);
return buffer;
}
@@ -55,9 +55,9 @@ public class LogModule {
@NotificationLog
public static LogBuffer provideNotificationsLogBuffer(
LogcatEchoTracker bufferFilter,
- DumpController dumpController) {
+ DumpManager dumpManager) {
LogBuffer buffer = new LogBuffer("NotifLog2", 1000, 10, bufferFilter);
- buffer.attach(dumpController);
+ buffer.attach(dumpManager);
return buffer;
}
@@ -67,9 +67,9 @@ public class LogModule {
@QSLog
public static LogBuffer provideQuickSettingsLogBuffer(
LogcatEchoTracker bufferFilter,
- DumpController dumpController) {
+ DumpManager dumpManager) {
LogBuffer buffer = new LogBuffer("QSLog", 500, 10, bufferFilter);
- buffer.attach(dumpController);
+ buffer.attach(dumpManager);
return buffer;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
index bf2d4cd07165..475ddc1ea11a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
@@ -15,29 +15,20 @@
package com.android.systemui;
import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
import android.os.Looper;
import androidx.test.filters.SmallTest;
-import com.android.systemui.Dependency.DependencyKey;
import com.android.systemui.statusbar.policy.FlashlightController;
import org.junit.Assert;
import org.junit.Test;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
@SmallTest
public class DependencyTest extends SysuiTestCase {
- public static final DependencyKey<Dumpable> DUMPABLE = new DependencyKey<>("dumpable");
-
@Test
public void testClassDependency() {
FlashlightController f = mock(FlashlightController.class);
@@ -53,17 +44,6 @@ public class DependencyTest extends SysuiTestCase {
}
@Test
- public void testDump() {
- Dumpable d = mock(Dumpable.class);
- String[] args = new String[0];
- FileDescriptor fd = mock(FileDescriptor.class);
- mDependency.injectTestDependency(DUMPABLE, d);
- Dependency.get(DUMPABLE);
- mDependency.dump(fd, mock(PrintWriter.class), args);
- verify(d).dump(eq(fd), any(), eq(args));
- }
-
- @Test
public void testInitDependency() {
Dependency.clearDependencies();
Dependency dependency = new Dependency();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
index b3071f957fdb..a7f4fa5768b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
@@ -60,6 +60,11 @@ public class TestableDependency extends Dependency {
return super.createDependency(key);
}
+ @Override
+ protected boolean autoRegisterModulesForDump() {
+ return false;
+ }
+
public <T> boolean hasInstantiatedDependency(Class<T> key) {
return mObjs.containsKey(key) || mInstantiatedObjects.contains(key);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
index 22b18373e81d..3357c5863d46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
@@ -27,6 +27,7 @@ import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import junit.framework.Assert.assertSame
@@ -94,6 +95,7 @@ class BroadcastDispatcherTest : SysuiTestCase() {
mockContext,
Handler(testableLooper.looper),
testableLooper.looper,
+ mock(DumpManager::class.java),
mapOf(0 to mockUBRUser0, 1 to mockUBRUser1))
// These should be valid filters
@@ -236,8 +238,9 @@ class BroadcastDispatcherTest : SysuiTestCase() {
context: Context,
mainHandler: Handler,
bgLooper: Looper,
+ dumpManager: DumpManager,
var mockUBRMap: Map<Int, UserBroadcastDispatcher>
- ) : BroadcastDispatcher(context, mainHandler, bgLooper) {
+ ) : BroadcastDispatcher(context, mainHandler, bgLooper, dumpManager) {
override fun createUBRForUser(userId: Int): UserBroadcastDispatcher {
return mockUBRMap.getOrDefault(userId, mock(UserBroadcastDispatcher::class.java))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt
new file mode 100644
index 000000000000..8d530ec0ef0a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2020 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.dump
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.Dumpable
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.util.mockito.any
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import java.io.FileDescriptor
+import java.io.PrintWriter
+
+@SmallTest
+class DumpManagerTest : SysuiTestCase() {
+
+ private lateinit var dumpManager: DumpManager
+
+ @Mock
+ private lateinit var fd: FileDescriptor
+ @Mock
+ private lateinit var pw: PrintWriter
+
+ @Mock
+ private lateinit var dumpable1: Dumpable
+ @Mock
+ private lateinit var dumpable2: Dumpable
+ @Mock
+ private lateinit var dumpable3: Dumpable
+
+ @Mock
+ private lateinit var buffer1: LogBuffer
+ @Mock
+ private lateinit var buffer2: LogBuffer
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ dumpManager = DumpManager(mContext)
+ }
+
+ @Test
+ fun testDumpablesCanBeDumpedSelectively() {
+ // GIVEN a variety of registered dumpables and buffers
+ dumpManager.registerDumpable("dumpable1", dumpable1)
+ dumpManager.registerDumpable("dumpable2", dumpable2)
+ dumpManager.registerDumpable("dumpable3", dumpable3)
+ dumpManager.registerBuffer("buffer1", buffer1)
+ dumpManager.registerBuffer("buffer2", buffer2)
+
+ // WHEN some of them are dumped explicitly
+ val args = arrayOf("dumpable1", "dumpable3", "buffer2")
+ dumpManager.dump(fd, pw, args)
+
+ // THEN only the requested ones have their dump() method called
+ verify(dumpable1).dump(fd, pw, args)
+ verify(dumpable2, never()).dump(
+ any(FileDescriptor::class.java),
+ any(PrintWriter::class.java),
+ any(Array<String>::class.java))
+ verify(dumpable3).dump(fd, pw, args)
+ verify(buffer1, never()).dump(any(PrintWriter::class.java), anyInt())
+ verify(buffer2).dump(pw, 0)
+ }
+
+ @Test
+ fun testDumpableMatchingIsBasedOnEndOfTag() {
+ // GIVEN a dumpable registered to the manager
+ dumpManager.registerDumpable("com.android.foo.bar.dumpable1", dumpable1)
+
+ // WHEN that module is dumped
+ val args = arrayOf("dumpable1")
+ dumpManager.dump(fd, pw, args)
+
+ // THEN its dump() method is called
+ verify(dumpable1).dump(fd, pw, args)
+ }
+
+ @Test
+ fun testCriticalDump() {
+ // GIVEN a variety of registered dumpables and buffers
+ dumpManager.registerDumpable("dumpable1", dumpable1)
+ dumpManager.registerDumpable("dumpable2", dumpable2)
+ dumpManager.registerDumpable("dumpable3", dumpable3)
+ dumpManager.registerBuffer("buffer1", buffer1)
+ dumpManager.registerBuffer("buffer2", buffer2)
+
+ // WHEN a critical dump is requested
+ val args = arrayOf("--dump-priority", "CRITICAL")
+ dumpManager.dump(fd, pw, args)
+
+ // THEN all modules are dumped (but no buffers)
+ verify(dumpable1).dump(fd, pw, args)
+ verify(dumpable2).dump(fd, pw, args)
+ verify(dumpable3).dump(fd, pw, args)
+ verify(buffer1, never()).dump(any(PrintWriter::class.java), anyInt())
+ verify(buffer2, never()).dump(any(PrintWriter::class.java), anyInt())
+ }
+
+ @Test
+ fun testNormalDump() {
+ // GIVEN a variety of registered dumpables and buffers
+ dumpManager.registerDumpable("dumpable1", dumpable1)
+ dumpManager.registerDumpable("dumpable2", dumpable2)
+ dumpManager.registerDumpable("dumpable3", dumpable3)
+ dumpManager.registerBuffer("buffer1", buffer1)
+ dumpManager.registerBuffer("buffer2", buffer2)
+
+ // WHEN a critical dump is requested
+ val args = arrayOf("--dump-priority", "NORMAL")
+ dumpManager.dump(fd, pw, args)
+
+ // THEN all buffers are dumped (but no modules)
+ verify(dumpable1, never()).dump(
+ any(FileDescriptor::class.java),
+ any(PrintWriter::class.java),
+ any(Array<String>::class.java))
+ verify(dumpable2, never()).dump(
+ any(FileDescriptor::class.java),
+ any(PrintWriter::class.java),
+ any(Array<String>::class.java))
+ verify(dumpable3, never()).dump(
+ any(FileDescriptor::class.java),
+ any(PrintWriter::class.java),
+ any(Array<String>::class.java))
+ verify(buffer1).dump(pw, 0)
+ verify(buffer2).dump(pw, 0)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt b/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
new file mode 100644
index 000000000000..3f095c7900f3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2020 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.util.mockito
+
+/**
+ * Kotlin versions of popular mockito methods that can return null in situations when Kotlin expects
+ * a non-null value. Kotlin will throw an IllegalStateException when this takes place ("x must not
+ * be null"). To fix this, we can use methods that modify the return type to be nullable. This
+ * causes Kotlin to skip the null checks.
+ */
+
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito
+
+/**
+ * Returns Mockito.eq() as nullable type to avoid java.lang.IllegalStateException when
+ * null is returned.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+fun <T> eq(obj: T): T = Mockito.eq<T>(obj)
+
+/**
+ * Returns Mockito.any() as nullable type to avoid java.lang.IllegalStateException when
+ * null is returned.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
+
+/**
+ * Returns ArgumentCaptor.capture() as nullable type to avoid java.lang.IllegalStateException
+ * when null is returned.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+
+/**
+ * Helper function for creating an argumentCaptor in kotlin.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+inline fun <reified T : Any> argumentCaptor(): ArgumentCaptor<T> =
+ ArgumentCaptor.forClass(T::class.java)