From afa802a889917c417bc401f2ad1c00391ce866bc Mon Sep 17 00:00:00 2001 From: Fabian Kozynski Date: Wed, 6 Jul 2022 14:02:53 -0400 Subject: [PATCH] Proto dump from SystemUI. Needs to be called by doing `adb shell dumpsys statusbar --proto` because using `dumpsys activity service` adds text to the dump. Will also be dumped as part of the BR Test: atest SystemUITests Test: Used in CTS Test: manual dump and BR Fixes 237786667 Change-Id: I74abf65a76c059ea9bb5a14728192b6b683b7fc7 --- .../internal/statusbar/IStatusBar.aidl | 3 + .../src/com/android/systemui/Dumpable.java | 1 - .../src/com/android/systemui/ProtoDumpable.kt | 7 ++ .../com/android/systemui/SystemUIService.java | 2 +- .../com/android/systemui/dump/DumpHandler.kt | 54 +++++++-- .../com/android/systemui/dump/DumpManager.kt | 44 +++++++- .../dump/SystemUIAuxiliaryDumpService.java | 1 + .../src/com/android/systemui/dump/sysui.proto | 27 +++++ .../com/android/systemui/qs/QSTileHost.java | 20 +++- .../android/systemui/qs/TileStateToProto.kt | 51 +++++++++ .../com/android/systemui/qs/proto/tiles.proto | 47 ++++++++ .../systemui/statusbar/CommandQueue.java | 43 +++++++- .../CentralSurfacesDependenciesModule.java | 7 +- .../systemui/util/proto/component_name.proto | 26 +++++ .../android/systemui/dump/DumpHandlerTest.kt | 50 +++++++-- .../android/systemui/qs/QSTileHostTest.java | 42 +++++-- .../systemui/qs/TileStateToProtoTest.kt | 104 ++++++++++++++++++ .../statusbar/StatusBarManagerService.java | 19 ++++ .../java/com/android/server/SystemServer.java | 3 +- 19 files changed, 514 insertions(+), 37 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/ProtoDumpable.kt create mode 100644 packages/SystemUI/src/com/android/systemui/dump/sysui.proto create mode 100644 packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt create mode 100644 packages/SystemUI/src/com/android/systemui/qs/proto/tiles.proto create mode 100644 packages/SystemUI/src/com/android/systemui/util/proto/component_name.proto create mode 100644 packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index 44cfe1aa4a790..1d4b246de5c8e 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -322,4 +322,7 @@ oneway interface IStatusBar /** Unregisters a nearby media devices provider. */ void unregisterNearbyMediaDevicesProvider(in INearbyMediaDevicesProvider provider); + + /** Dump protos from SystemUI. The proto definition is defined there */ + void dumpProto(in String[] args, in ParcelFileDescriptor pfd); } diff --git a/packages/SystemUI/src/com/android/systemui/Dumpable.java b/packages/SystemUI/src/com/android/systemui/Dumpable.java index 652595100c0f3..73fdce6c90457 100644 --- a/packages/SystemUI/src/com/android/systemui/Dumpable.java +++ b/packages/SystemUI/src/com/android/systemui/Dumpable.java @@ -30,7 +30,6 @@ public interface Dumpable { /** * Called when it's time to dump the internal state - * @param fd A file descriptor. * @param pw Where to write your dump to. * @param args Arguments. */ diff --git a/packages/SystemUI/src/com/android/systemui/ProtoDumpable.kt b/packages/SystemUI/src/com/android/systemui/ProtoDumpable.kt new file mode 100644 index 0000000000000..4c3a7ff4e2eb7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/ProtoDumpable.kt @@ -0,0 +1,7 @@ +package com.android.systemui + +import com.android.systemui.dump.nano.SystemUIProtoDump + +interface ProtoDumpable : Dumpable { + fun dumpProto(systemUIProtoDump: SystemUIProtoDump, args: Array) +} diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java index 7bcba3cc1c468..50e03992df49e 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java @@ -121,6 +121,6 @@ public class SystemUIService extends Service { DumpHandler.PRIORITY_ARG_CRITICAL}; } - mDumpHandler.dump(pw, massagedArgs); + mDumpHandler.dump(fd, pw, massagedArgs); } } diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt index 478f86169718d..609bd76cf210f 100644 --- a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt @@ -24,8 +24,13 @@ import com.android.systemui.R import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_CRITICAL import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_HIGH import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_NORMAL +import com.android.systemui.dump.nano.SystemUIProtoDump import com.android.systemui.plugins.log.LogBuffer import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager +import com.google.protobuf.nano.MessageNano +import java.io.BufferedOutputStream +import java.io.FileDescriptor +import java.io.FileOutputStream import java.io.PrintWriter import javax.inject.Inject import javax.inject.Provider @@ -100,7 +105,7 @@ class DumpHandler @Inject constructor( /** * Dump the diagnostics! Behavior can be controlled via [args]. */ - fun dump(pw: PrintWriter, args: Array) { + fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array) { Trace.beginSection("DumpManager#dump()") val start = SystemClock.uptimeMillis() @@ -111,10 +116,12 @@ class DumpHandler @Inject constructor( return } - when (parsedArgs.dumpPriority) { - PRIORITY_ARG_CRITICAL -> dumpCritical(pw, parsedArgs) - PRIORITY_ARG_NORMAL -> dumpNormal(pw, parsedArgs) - else -> dumpParameterized(pw, parsedArgs) + when { + parsedArgs.dumpPriority == PRIORITY_ARG_CRITICAL -> dumpCritical(pw, parsedArgs) + parsedArgs.dumpPriority == PRIORITY_ARG_NORMAL && !parsedArgs.proto -> { + dumpNormal(pw, parsedArgs) + } + else -> dumpParameterized(fd, pw, parsedArgs) } pw.println() @@ -122,7 +129,7 @@ class DumpHandler @Inject constructor( Trace.endSection() } - private fun dumpParameterized(pw: PrintWriter, args: ParsedArgs) { + private fun dumpParameterized(fd: FileDescriptor, pw: PrintWriter, args: ParsedArgs) { when (args.command) { "bugreport-critical" -> dumpCritical(pw, args) "bugreport-normal" -> dumpNormal(pw, args) @@ -130,7 +137,13 @@ class DumpHandler @Inject constructor( "buffers" -> dumpBuffers(pw, args) "config" -> dumpConfig(pw) "help" -> dumpHelp(pw) - else -> dumpTargets(args.nonFlagArgs, pw, args) + else -> { + if (args.proto) { + dumpProtoTargets(args.nonFlagArgs, fd, args) + } else { + dumpTargets(args.nonFlagArgs, pw, args) + } + } } } @@ -160,6 +173,26 @@ class DumpHandler @Inject constructor( } } + private fun dumpProtoTargets( + targets: List, + fd: FileDescriptor, + args: ParsedArgs + ) { + val systemUIProto = SystemUIProtoDump() + if (targets.isNotEmpty()) { + for (target in targets) { + dumpManager.dumpProtoTarget(target, systemUIProto, args.rawArgs) + } + } else { + dumpManager.dumpProtoDumpables(systemUIProto, args.rawArgs) + } + val buffer = BufferedOutputStream(FileOutputStream(fd)) + buffer.use { + it.write(MessageNano.toByteArray(systemUIProto)) + it.flush() + } + } + private fun dumpTargets( targets: List, pw: PrintWriter, @@ -267,6 +300,7 @@ class DumpHandler @Inject constructor( } } } + PROTO -> pArgs.proto = true "-t", "--tail" -> { pArgs.tailLength = readArgument(iterator, arg) { it.toInt() @@ -278,6 +312,9 @@ class DumpHandler @Inject constructor( "-h", "--help" -> { pArgs.command = "help" } + // This flag is passed as part of the proto dump in Bug reports, we can ignore + // it because this is our default behavior. + "-a" -> {} else -> { throw ArgParseException("Unknown flag: $arg") } @@ -314,7 +351,7 @@ class DumpHandler @Inject constructor( const val PRIORITY_ARG_CRITICAL = "CRITICAL" const val PRIORITY_ARG_HIGH = "HIGH" const val PRIORITY_ARG_NORMAL = "NORMAL" - const val PROTO = "--sysui_proto" + const val PROTO = "--proto" } } @@ -338,6 +375,7 @@ private class ParsedArgs( var tailLength: Int = 0 var command: String? = null var listOnly = false + var proto = false } class ArgParseException(message: String) : Exception(message) diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt index dbca65122fcb4..ae780896a7e2f 100644 --- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt +++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt @@ -18,6 +18,8 @@ package com.android.systemui.dump import android.util.ArrayMap import com.android.systemui.Dumpable +import com.android.systemui.ProtoDumpable +import com.android.systemui.dump.nano.SystemUIProtoDump import com.android.systemui.plugins.log.LogBuffer import java.io.PrintWriter import javax.inject.Inject @@ -90,7 +92,7 @@ open class DumpManager @Inject constructor() { target: String, pw: PrintWriter, args: Array, - tailLength: Int + tailLength: Int, ) { for (dumpable in dumpables.values) { if (dumpable.name.endsWith(target)) { @@ -107,6 +109,36 @@ open class DumpManager @Inject constructor() { } } + @Synchronized + fun dumpProtoTarget( + target: String, + protoDump: SystemUIProtoDump, + args: Array + ) { + for (dumpable in dumpables.values) { + if (dumpable.dumpable is ProtoDumpable && dumpable.name.endsWith(target)) { + dumpProtoDumpable(dumpable.dumpable, protoDump, args) + return + } + } + } + + @Synchronized + fun dumpProtoDumpables( + systemUIProtoDump: SystemUIProtoDump, + args: Array + ) { + for (dumpable in dumpables.values) { + if (dumpable.dumpable is ProtoDumpable) { + dumpProtoDumpable( + dumpable.dumpable, + systemUIProtoDump, + args + ) + } + } + } + /** * Dumps all registered dumpables to [pw] */ @@ -184,6 +216,14 @@ open class DumpManager @Inject constructor() { buffer.dumpable.dump(pw, tailLength) } + private fun dumpProtoDumpable( + protoDumpable: ProtoDumpable, + systemUIProtoDump: SystemUIProtoDump, + args: Array + ) { + protoDumpable.dumpProto(systemUIProtoDump, args) + } + private fun canAssignToNameLocked(name: String, newDumpable: Any): Boolean { val existingDumpable = dumpables[name]?.dumpable ?: buffers[name]?.dumpable return existingDumpable == null || newDumpable == existingDumpable @@ -195,4 +235,4 @@ private data class RegisteredDumpable( val dumpable: T ) -private const val TAG = "DumpManager" \ No newline at end of file +private const val TAG = "DumpManager" diff --git a/packages/SystemUI/src/com/android/systemui/dump/SystemUIAuxiliaryDumpService.java b/packages/SystemUI/src/com/android/systemui/dump/SystemUIAuxiliaryDumpService.java index 0a41a56b5ecb0..da983ab03a1dd 100644 --- a/packages/SystemUI/src/com/android/systemui/dump/SystemUIAuxiliaryDumpService.java +++ b/packages/SystemUI/src/com/android/systemui/dump/SystemUIAuxiliaryDumpService.java @@ -51,6 +51,7 @@ public class SystemUIAuxiliaryDumpService extends Service { protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { // Simulate the NORMAL priority arg being passed to us mDumpHandler.dump( + fd, pw, new String[] { DumpHandler.PRIORITY_ARG, DumpHandler.PRIORITY_ARG_NORMAL }); } diff --git a/packages/SystemUI/src/com/android/systemui/dump/sysui.proto b/packages/SystemUI/src/com/android/systemui/dump/sysui.proto new file mode 100644 index 0000000000000..cd8c08aeb2dc5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dump/sysui.proto @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022 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. + */ +syntax = "proto3"; + +package com.android.systemui.dump; + +import "frameworks/base/packages/SystemUI/src/com/android/systemui/qs/proto/tiles.proto"; + +option java_multiple_files = true; + +message SystemUIProtoDump { + repeated com.android.systemui.qs.QsTileState tiles = 1; +} + diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java index ac46c85c10a4f..f37d668770698 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java @@ -34,10 +34,12 @@ import com.android.internal.logging.InstanceId; import com.android.internal.logging.InstanceIdSequence; import com.android.internal.logging.UiEventLogger; import com.android.systemui.Dumpable; +import com.android.systemui.ProtoDumpable; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; +import com.android.systemui.dump.nano.SystemUIProtoDump; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.qs.QSFactory; import com.android.systemui.plugins.qs.QSTile; @@ -48,6 +50,7 @@ import com.android.systemui.qs.external.TileLifecycleManager; import com.android.systemui.qs.external.TileServiceKey; import com.android.systemui.qs.external.TileServiceRequestController; import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.qs.nano.QsTileState; import com.android.systemui.settings.UserFileManager; import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.plugins.PluginManager; @@ -59,16 +62,20 @@ import com.android.systemui.tuner.TunerService.Tunable; import com.android.systemui.util.leak.GarbageMonitor; import com.android.systemui.util.settings.SecureSettings; +import org.jetbrains.annotations.NotNull; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.Executor; import java.util.function.Predicate; +import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Provider; @@ -82,7 +89,7 @@ import javax.inject.Provider; * This class also provides the interface for adding/removing/changing tiles. */ @SysUISingleton -public class QSTileHost implements QSHost, Tunable, PluginListener, Dumpable { +public class QSTileHost implements QSHost, Tunable, PluginListener, ProtoDumpable { private static final String TAG = "QSTileHost"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final int MAX_QS_INSTANCE_ID = 1 << 20; @@ -671,4 +678,15 @@ public class QSTileHost implements QSHost, Tunable, PluginListener, D mTiles.values().stream().filter(obj -> obj instanceof Dumpable) .forEach(o -> ((Dumpable) o).dump(pw, args)); } + + @Override + public void dumpProto(@NotNull SystemUIProtoDump systemUIProtoDump, @NotNull String[] args) { + List data = mTiles.values().stream() + .map(QSTile::getState) + .map(TileStateToProtoKt::toProto) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + systemUIProtoDump.tiles = data.toArray(new QsTileState[0]); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt b/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt new file mode 100644 index 0000000000000..2c8a5a4981d07 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2022 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.qs + +import android.service.quicksettings.Tile +import android.text.TextUtils +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.external.CustomTile +import com.android.systemui.qs.nano.QsTileState +import com.android.systemui.util.nano.ComponentNameProto + +fun QSTile.State.toProto(): QsTileState? { + if (TextUtils.isEmpty(spec)) return null + val state = QsTileState() + if (spec.startsWith(CustomTile.PREFIX)) { + val protoComponentName = ComponentNameProto() + val tileComponentName = CustomTile.getComponentFromSpec(spec) + protoComponentName.packageName = tileComponentName.packageName + protoComponentName.className = tileComponentName.className + state.componentName = protoComponentName + } else { + state.spec = spec + } + state.state = + when (this.state) { + Tile.STATE_UNAVAILABLE -> QsTileState.UNAVAILABLE + Tile.STATE_INACTIVE -> QsTileState.INACTIVE + Tile.STATE_ACTIVE -> QsTileState.ACTIVE + else -> QsTileState.UNAVAILABLE + } + label?.let { state.label = it.toString() } + secondaryLabel?.let { state.secondaryLabel = it.toString() } + if (this is QSTile.BooleanState) { + state.booleanState = value + } + return state +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/proto/tiles.proto b/packages/SystemUI/src/com/android/systemui/qs/proto/tiles.proto new file mode 100644 index 0000000000000..2a61033cb302f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/proto/tiles.proto @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2022 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. + */ + +syntax = "proto3"; + +package com.android.systemui.qs; + +import "frameworks/base/packages/SystemUI/src/com/android/systemui/util/proto/component_name.proto"; + +option java_multiple_files = true; + +message QsTileState { + oneof identifier { + string spec = 1; + com.android.systemui.util.ComponentNameProto component_name = 2; + } + + enum State { + UNAVAILABLE = 0; + INACTIVE = 1; + ACTIVE = 2; + } + + State state = 3; + oneof optional_boolean_state { + bool boolean_state = 4; + } + oneof optional_label { + string label = 5; + } + oneof optional_secondary_label { + string secondary_label = 6; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 04621168493b6..e6d7e4124d01f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -69,12 +69,15 @@ import com.android.internal.statusbar.LetterboxDetails; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.util.GcUtils; import com.android.internal.view.AppearanceRegion; +import com.android.systemui.dump.DumpHandler; import com.android.systemui.statusbar.CommandQueue.Callbacks; import com.android.systemui.statusbar.commandline.CommandRegistry; import com.android.systemui.statusbar.policy.CallbackController; import com.android.systemui.tracing.ProtoTracer; +import java.io.FileDescriptor; import java.io.FileOutputStream; +import java.io.OutputStream; import java.io.PrintWriter; import java.util.ArrayList; @@ -184,6 +187,7 @@ public class CommandQueue extends IStatusBar.Stub implements private int mLastUpdatedImeDisplayId = INVALID_DISPLAY; private ProtoTracer mProtoTracer; private final @Nullable CommandRegistry mRegistry; + private final @Nullable DumpHandler mDumpHandler; /** * These methods are called back on the main thread. @@ -473,12 +477,18 @@ public class CommandQueue extends IStatusBar.Stub implements } public CommandQueue(Context context) { - this(context, null, null); + this(context, null, null, null); } - public CommandQueue(Context context, ProtoTracer protoTracer, CommandRegistry registry) { + public CommandQueue( + Context context, + ProtoTracer protoTracer, + CommandRegistry registry, + DumpHandler dumpHandler + ) { mProtoTracer = protoTracer; mRegistry = registry; + mDumpHandler = dumpHandler; context.getSystemService(DisplayManager.class).registerDisplayListener(this, mHandler); // We always have default display. setDisabled(DEFAULT_DISPLAY, DISABLE_NONE, DISABLE2_NONE); @@ -1177,6 +1187,35 @@ public class CommandQueue extends IStatusBar.Stub implements thr.start(); } + @Override + public void dumpProto(String[] args, ParcelFileDescriptor pfd) { + final FileDescriptor fd = pfd.getFileDescriptor(); + // This is mimicking Binder#dumpAsync, but on this side of the binder. Might be possible + // to just throw this work onto the handler just like the other messages + Thread thr = new Thread("Sysui.dumpProto") { + public void run() { + try { + if (mDumpHandler == null) { + return; + } + // We won't be using the PrintWriter. + OutputStream o = new OutputStream() { + @Override + public void write(int b) {} + }; + mDumpHandler.dump(fd, new PrintWriter(o), args); + } finally { + try { + // Close the file descriptor so the TransferPipe finishes its thread + pfd.close(); + } catch (Exception e) { + } + } + } + }; + thr.start(); + } + @Override public void runGcForTest() { // Gc sysui diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java index 11e3d1773c4cd..f574be056109f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java @@ -29,6 +29,7 @@ import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.dump.DumpHandler; import com.android.systemui.dump.DumpManager; import com.android.systemui.media.MediaDataManager; import com.android.systemui.plugins.ActivityStarter; @@ -181,8 +182,10 @@ public interface CentralSurfacesDependenciesModule { static CommandQueue provideCommandQueue( Context context, ProtoTracer protoTracer, - CommandRegistry registry) { - return new CommandQueue(context, protoTracer, registry); + CommandRegistry registry, + DumpHandler dumpHandler + ) { + return new CommandQueue(context, protoTracer, registry, dumpHandler); } /** diff --git a/packages/SystemUI/src/com/android/systemui/util/proto/component_name.proto b/packages/SystemUI/src/com/android/systemui/util/proto/component_name.proto new file mode 100644 index 0000000000000..b7166d96d4015 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/proto/component_name.proto @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2022 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. + */ + +syntax = "proto3"; + +package com.android.systemui.util; + +option java_multiple_files = true; + +message ComponentNameProto { + string package_name = 1; + string class_name = 2; +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt index 65b44a14d2ad0..65ae90b8f7e84 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt @@ -19,11 +19,17 @@ package com.android.systemui.dump import androidx.test.filters.SmallTest import com.android.systemui.CoreStartable import com.android.systemui.Dumpable +import com.android.systemui.ProtoDumpable import com.android.systemui.SysuiTestCase import com.android.systemui.plugins.log.LogBuffer import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq import com.google.common.truth.Truth.assertThat +import java.io.FileDescriptor +import java.io.PrintWriter +import java.io.StringWriter +import javax.inject.Provider import org.junit.Before import org.junit.Test import org.mockito.Mock @@ -31,9 +37,6 @@ import org.mockito.Mockito.anyInt import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -import java.io.PrintWriter -import java.io.StringWriter -import javax.inject.Provider @SmallTest class DumpHandlerTest : SysuiTestCase() { @@ -47,6 +50,8 @@ class DumpHandlerTest : SysuiTestCase() { @Mock private lateinit var pw: PrintWriter + @Mock + private lateinit var fd: FileDescriptor @Mock private lateinit var dumpable1: Dumpable @@ -55,6 +60,11 @@ class DumpHandlerTest : SysuiTestCase() { @Mock private lateinit var dumpable3: Dumpable + @Mock + private lateinit var protoDumpable1: ProtoDumpable + @Mock + private lateinit var protoDumpable2: ProtoDumpable + @Mock private lateinit var buffer1: LogBuffer @Mock @@ -88,7 +98,7 @@ class DumpHandlerTest : SysuiTestCase() { // WHEN some of them are dumped explicitly val args = arrayOf("dumpable1", "dumpable3", "buffer2") - dumpHandler.dump(pw, args) + dumpHandler.dump(fd, pw, args) // THEN only the requested ones have their dump() method called verify(dumpable1).dump(pw, args) @@ -107,7 +117,7 @@ class DumpHandlerTest : SysuiTestCase() { // WHEN that module is dumped val args = arrayOf("dumpable1") - dumpHandler.dump(pw, args) + dumpHandler.dump(fd, pw, args) // THEN its dump() method is called verify(dumpable1).dump(pw, args) @@ -124,7 +134,7 @@ class DumpHandlerTest : SysuiTestCase() { // WHEN a critical dump is requested val args = arrayOf("--dump-priority", "CRITICAL") - dumpHandler.dump(pw, args) + dumpHandler.dump(fd, pw, args) // THEN all modules are dumped (but no buffers) verify(dumpable1).dump(pw, args) @@ -145,7 +155,7 @@ class DumpHandlerTest : SysuiTestCase() { // WHEN a normal dump is requested val args = arrayOf("--dump-priority", "NORMAL") - dumpHandler.dump(pw, args) + dumpHandler.dump(fd, pw, args) // THEN all buffers are dumped (but no modules) verify(dumpable1, never()).dump( @@ -168,11 +178,35 @@ class DumpHandlerTest : SysuiTestCase() { val spw = PrintWriter(stringWriter) // When a config dump is requested - dumpHandler.dump(spw, arrayOf("config")) + dumpHandler.dump(fd, spw, arrayOf("config")) assertThat(stringWriter.toString()).contains(EmptyCoreStartable::class.java.simpleName) } + @Test + fun testDumpAllProtoDumpables() { + dumpManager.registerDumpable("protoDumpable1", protoDumpable1) + dumpManager.registerDumpable("protoDumpable2", protoDumpable2) + + val args = arrayOf(DumpHandler.PROTO) + dumpHandler.dump(fd, pw, args) + + verify(protoDumpable1).dumpProto(any(), eq(args)) + verify(protoDumpable2).dumpProto(any(), eq(args)) + } + + @Test + fun testDumpSingleProtoDumpable() { + dumpManager.registerDumpable("protoDumpable1", protoDumpable1) + dumpManager.registerDumpable("protoDumpable2", protoDumpable2) + + val args = arrayOf(DumpHandler.PROTO, "protoDumpable1") + dumpHandler.dump(fd, pw, args) + + verify(protoDumpable1).dumpProto(any(), eq(args)) + verify(protoDumpable2, never()).dumpProto(any(), any()) + } + private class EmptyCoreStartable : CoreStartable { override fun start() {} } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java index 3c58b6fc13548..c452872a527ee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java @@ -52,6 +52,7 @@ import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.dump.DumpManager; +import com.android.systemui.dump.nano.SystemUIProtoDump; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.qs.QSFactory; import com.android.systemui.plugins.qs.QSTile; @@ -114,8 +115,6 @@ public class QSTileHostTest extends SysuiTestCase { @Mock private DumpManager mDumpManager; @Mock - private QSTile.State mMockState; - @Mock private CentralSurfaces mCentralSurfaces; @Mock private QSLogger mQSLogger; @@ -195,7 +194,6 @@ public class QSTileHostTest extends SysuiTestCase { } private void setUpTileFactory() { - when(mMockState.toString()).thenReturn(MOCK_STATE_STRING); // Only create this kind of tiles when(mDefaultFactory.createTile(anyString())).thenAnswer( invocation -> { @@ -209,7 +207,11 @@ public class QSTileHostTest extends SysuiTestCase { } else if ("na".equals(spec)) { return new NotAvailableTile(mQSTileHost); } else if (CUSTOM_TILE_SPEC.equals(spec)) { - return mCustomTile; + QSTile tile = mCustomTile; + QSTile.State s = mock(QSTile.State.class); + s.spec = spec; + when(mCustomTile.getState()).thenReturn(s); + return tile; } else if ("internet".equals(spec) || "wifi".equals(spec) || "cell".equals(spec)) { @@ -647,7 +649,7 @@ public class QSTileHostTest extends SysuiTestCase { @Test public void testSetTileRemoved_removedBySystem() { int user = mUserTracker.getUserId(); - saveSetting("spec1" + CUSTOM_TILE_SPEC); + saveSetting("spec1," + CUSTOM_TILE_SPEC); // This will be done by TileServiceManager mQSTileHost.setTileAdded(CUSTOM_TILE, user, true); @@ -658,6 +660,27 @@ public class QSTileHostTest extends SysuiTestCase { .getBoolean(CUSTOM_TILE.flattenToString(), false)); } + @Test + public void testProtoDump_noTiles() { + SystemUIProtoDump proto = new SystemUIProtoDump(); + mQSTileHost.dumpProto(proto, new String[0]); + + assertEquals(0, proto.tiles.length); + } + + @Test + public void testTilesInOrder() { + saveSetting("spec1," + CUSTOM_TILE_SPEC); + + SystemUIProtoDump proto = new SystemUIProtoDump(); + mQSTileHost.dumpProto(proto, new String[0]); + + assertEquals(2, proto.tiles.length); + assertEquals("spec1", proto.tiles[0].getSpec()); + assertEquals(CUSTOM_TILE.getPackageName(), proto.tiles[1].getComponentName().packageName); + assertEquals(CUSTOM_TILE.getClassName(), proto.tiles[1].getComponentName().className); + } + private SharedPreferences getSharedPreferenecesForUser(int user) { return mUserFileManager.getSharedPreferences(QSTileHost.TILES, 0, user); } @@ -707,12 +730,9 @@ public class QSTileHostTest extends SysuiTestCase { @Override public State newTileState() { - return mMockState; - } - - @Override - public State getState() { - return mMockState; + State s = mock(QSTile.State.class); + when(s.toString()).thenReturn(MOCK_STATE_STRING); + return s; } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt new file mode 100644 index 0000000000000..629c663943db9 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt @@ -0,0 +1,104 @@ +package com.android.systemui.qs + +import android.content.ComponentName +import android.service.quicksettings.Tile +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.external.CustomTile +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class TileStateToProtoTest : SysuiTestCase() { + + companion object { + private const val TEST_LABEL = "label" + private const val TEST_SUBTITLE = "subtitle" + private const val TEST_SPEC = "spec" + private val TEST_COMPONENT = ComponentName("test_pkg", "test_cls") + } + + @Test + fun platformTile_INACTIVE() { + val state = + QSTile.State().apply { + spec = TEST_SPEC + label = TEST_LABEL + secondaryLabel = TEST_SUBTITLE + state = Tile.STATE_INACTIVE + } + val proto = state.toProto() + + assertThat(proto).isNotNull() + assertThat(proto?.hasSpec()).isTrue() + assertThat(proto?.spec).isEqualTo(TEST_SPEC) + assertThat(proto?.hasComponentName()).isFalse() + assertThat(proto?.label).isEqualTo(TEST_LABEL) + assertThat(proto?.secondaryLabel).isEqualTo(TEST_SUBTITLE) + assertThat(proto?.state).isEqualTo(Tile.STATE_INACTIVE) + assertThat(proto?.hasBooleanState()).isFalse() + } + + @Test + fun componentTile_UNAVAILABLE() { + val state = + QSTile.State().apply { + spec = CustomTile.toSpec(TEST_COMPONENT) + label = TEST_LABEL + secondaryLabel = TEST_SUBTITLE + state = Tile.STATE_UNAVAILABLE + } + val proto = state.toProto() + + assertThat(proto).isNotNull() + assertThat(proto?.hasSpec()).isFalse() + assertThat(proto?.hasComponentName()).isTrue() + val componentName = proto?.componentName + assertThat(componentName?.packageName).isEqualTo(TEST_COMPONENT.packageName) + assertThat(componentName?.className).isEqualTo(TEST_COMPONENT.className) + assertThat(proto?.label).isEqualTo(TEST_LABEL) + assertThat(proto?.secondaryLabel).isEqualTo(TEST_SUBTITLE) + assertThat(proto?.state).isEqualTo(Tile.STATE_UNAVAILABLE) + assertThat(proto?.hasBooleanState()).isFalse() + } + + @Test + fun booleanState_ACTIVE() { + val state = + QSTile.BooleanState().apply { + spec = TEST_SPEC + label = TEST_LABEL + secondaryLabel = TEST_SUBTITLE + state = Tile.STATE_ACTIVE + value = true + } + val proto = state.toProto() + + assertThat(proto).isNotNull() + assertThat(proto?.hasSpec()).isTrue() + assertThat(proto?.spec).isEqualTo(TEST_SPEC) + assertThat(proto?.hasComponentName()).isFalse() + assertThat(proto?.label).isEqualTo(TEST_LABEL) + assertThat(proto?.secondaryLabel).isEqualTo(TEST_SUBTITLE) + assertThat(proto?.state).isEqualTo(Tile.STATE_ACTIVE) + assertThat(proto?.hasBooleanState()).isTrue() + assertThat(proto?.booleanState).isTrue() + } + + @Test + fun noSpec_returnsNull() { + val state = + QSTile.State().apply { + label = TEST_LABEL + secondaryLabel = TEST_SUBTITLE + state = Tile.STATE_ACTIVE + } + val proto = state.toProto() + + assertThat(proto).isNull() + } +} diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 653b51a959935..3df8f584fef10 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -2253,6 +2253,25 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; + boolean proto = false; + for (int i = 0; i < args.length; i++) { + if ("--proto".equals(args[i])) { + proto = true; + } + } + if (proto) { + if (mBar == null) return; + try (TransferPipe tp = new TransferPipe()) { + // Sending the command to the remote, which needs to execute async to avoid blocking + // See Binder#dumpAsync() for inspiration + mBar.dumpProto(args, tp.getWriteFd()); + // Times out after 5s + tp.go(fd); + } catch (Throwable t) { + Slog.e(TAG, "Error sending command to IStatusBar", t); + } + return; + } synchronized (mLock) { for (int i = 0; i < mDisplayUiState.size(); i++) { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 2ee4af56d3203..46141940a9076 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1823,7 +1823,8 @@ public final class SystemServer implements Dumpable { t.traceBegin("StartStatusBarManagerService"); try { statusBar = new StatusBarManagerService(context); - ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar); + ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar, false, + DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO); } catch (Throwable e) { reportWtf("starting StatusBarManagerService", e); }