Creates a generic mechanism to dump app-side information.
Currently, 'dumpsys activity' can dump the state of some managers: - AutofillManager - ContentCapturemaanger - UiTranslationController But the support for these custom dumping is hardcoded into Activity itself, which makes it harder to extend. For example, automotive builds provide an app-side Car object, which currently cannot be dumped. This CL makes the mechanism more flexible by providing a couple new public / SystemAPIs that let Automotive (or other mainline modules) extend it. Examples: $ adb shell dumpsys activity com.android.car.carlauncher/.CarLauncher --list-dumpables $ adb shell dumpsys activity com.android.car.carlauncher/.CarLauncher --dump-dumpable CarUserManager $ adb shell dumpsys activity service com.android.systemui/.SystemUIService CarUserManager NOTE: this CL only adds the new APIs; a follow-up CL will change the existing managers to use them. Test: see above Test: m update-api Bug: 149254050 CTS-Coverage-Bug: 149254050 Change-Id: I6920ff3542d3d75edd667c2c7658e9d0a7af534f
This commit is contained in:
@@ -47166,6 +47166,15 @@ package android.util {
|
||||
field public float ydpi;
|
||||
}
|
||||
|
||||
public interface Dumpable {
|
||||
method public void dump(@NonNull java.io.PrintWriter, @Nullable String[]);
|
||||
method @NonNull public default String getDumpableName();
|
||||
}
|
||||
|
||||
public interface DumpableContainer {
|
||||
method public boolean addDumpable(@NonNull android.util.Dumpable);
|
||||
}
|
||||
|
||||
public class EventLog {
|
||||
method public static int getTagCode(String);
|
||||
method public static String getTagName(int);
|
||||
|
||||
@@ -9,6 +9,10 @@ package android {
|
||||
|
||||
package android.app {
|
||||
|
||||
@UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
|
||||
method public final boolean addDumpable(@NonNull android.util.Dumpable);
|
||||
}
|
||||
|
||||
public class ActivityManager {
|
||||
method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void addHomeVisibilityListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.HomeVisibilityListener);
|
||||
method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void removeHomeVisibilityListener(@NonNull android.app.HomeVisibilityListener);
|
||||
|
||||
@@ -90,6 +90,7 @@ import android.transition.Scene;
|
||||
import android.transition.TransitionManager;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Dumpable;
|
||||
import android.util.EventLog;
|
||||
import android.util.Log;
|
||||
import android.util.PrintWriterPrinter;
|
||||
@@ -145,6 +146,7 @@ import com.android.internal.app.IVoiceInteractor;
|
||||
import com.android.internal.app.ToolbarActionBar;
|
||||
import com.android.internal.app.WindowDecorActionBar;
|
||||
import com.android.internal.policy.PhoneWindow;
|
||||
import com.android.internal.util.dump.DumpableContainerImpl;
|
||||
|
||||
import dalvik.system.VMRuntime;
|
||||
|
||||
@@ -954,6 +956,9 @@ public class Activity extends ContextThemeWrapper
|
||||
|
||||
private SplashScreen mSplashScreen;
|
||||
|
||||
@Nullable
|
||||
private DumpableContainerImpl mDumpableContainer;
|
||||
|
||||
private final WindowControllerCallback mWindowControllerCallback =
|
||||
new WindowControllerCallback() {
|
||||
/**
|
||||
@@ -7081,8 +7086,23 @@ public class Activity extends ContextThemeWrapper
|
||||
dumpInner(prefix, fd, writer, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link android.util.DumpableContainer#addDumpable(Dumpable)}.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
|
||||
public final boolean addDumpable(@NonNull Dumpable dumpable) {
|
||||
if (mDumpableContainer == null) {
|
||||
mDumpableContainer = new DumpableContainerImpl();
|
||||
}
|
||||
return mDumpableContainer.addDumpable(dumpable);
|
||||
}
|
||||
|
||||
void dumpInner(@NonNull String prefix, @Nullable FileDescriptor fd,
|
||||
@NonNull PrintWriter writer, @Nullable String[] args) {
|
||||
String innerPrefix = prefix + " ";
|
||||
|
||||
if (args != null && args.length > 0) {
|
||||
// Handle special cases
|
||||
switch (args[0]) {
|
||||
@@ -7095,12 +7115,33 @@ public class Activity extends ContextThemeWrapper
|
||||
case "--translation":
|
||||
dumpUiTranslation(prefix, writer);
|
||||
return;
|
||||
case "--list-dumpables":
|
||||
if (mDumpableContainer == null) {
|
||||
writer.print(prefix); writer.println("No dumpables");
|
||||
return;
|
||||
}
|
||||
mDumpableContainer.listDumpables(prefix, writer);
|
||||
return;
|
||||
case "--dump-dumpable":
|
||||
if (args.length == 1) {
|
||||
writer.println("--dump-dumpable requires the dumpable name");
|
||||
return;
|
||||
}
|
||||
if (mDumpableContainer == null) {
|
||||
writer.println("no dumpables");
|
||||
return;
|
||||
}
|
||||
// Strips --dump-dumpable NAME
|
||||
String[] prunedArgs = new String[args.length - 2];
|
||||
System.arraycopy(args, 2, prunedArgs, 0, prunedArgs.length);
|
||||
mDumpableContainer.dumpOneDumpable(prefix, writer, args[1], prunedArgs);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
writer.print(prefix); writer.print("Local Activity ");
|
||||
writer.print(Integer.toHexString(System.identityHashCode(this)));
|
||||
writer.println(" State:");
|
||||
String innerPrefix = prefix + " ";
|
||||
writer.print(innerPrefix); writer.print("mResumed=");
|
||||
writer.print(mResumed); writer.print(" mStopped=");
|
||||
writer.print(mStopped); writer.print(" mFinished=");
|
||||
@@ -7138,6 +7179,10 @@ public class Activity extends ContextThemeWrapper
|
||||
dumpUiTranslation(prefix, writer);
|
||||
|
||||
ResourcesManager.getInstance().dump(prefix, writer);
|
||||
|
||||
if (mDumpableContainer != null) {
|
||||
mDumpableContainer.dumpAllDumpables(prefix, writer, args);
|
||||
}
|
||||
}
|
||||
|
||||
void dumpContentCaptureManager(String prefix, PrintWriter writer) {
|
||||
|
||||
47
core/java/android/util/Dumpable.java
Normal file
47
core/java/android/util/Dumpable.java
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 android.util;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
* Represents an object whose state can be dumped into a {@link PrintWriter}.
|
||||
*/
|
||||
public interface Dumpable {
|
||||
|
||||
/**
|
||||
* Gets the name of the {@link Dumpable}.
|
||||
*
|
||||
* @return class name, by default.
|
||||
*/
|
||||
@NonNull
|
||||
default String getDumpableName() {
|
||||
return getClass().getName();
|
||||
}
|
||||
|
||||
//TODO(b/149254050): decide whether it should take a ParcelFileDescription as well.
|
||||
|
||||
/**
|
||||
* Dumps the internal state into the given {@code writer}.
|
||||
*
|
||||
* @param writer writer to be written to
|
||||
* @param args optional list of arguments
|
||||
*/
|
||||
void dump(@NonNull PrintWriter writer, @Nullable String[] args);
|
||||
}
|
||||
40
core/java/android/util/DumpableContainer.java
Normal file
40
core/java/android/util/DumpableContainer.java
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 android.util;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Objects that contains a list of {@link Dumpable}, which will be dumped when the object itself
|
||||
* is dumped.
|
||||
*/
|
||||
public interface DumpableContainer {
|
||||
|
||||
/**
|
||||
* Adds the given {@link Dumpable dumpable} to the container.
|
||||
*
|
||||
* <p>If a dumpable with the same {@link Dumpable#getDumpableName() name} was added before, this
|
||||
* call is ignored.
|
||||
*
|
||||
* @param dumpable dumpable to be added.
|
||||
*
|
||||
* @throws IllegalArgumentException if the {@link Dumpable#getDumpableName() dumpable name} is
|
||||
* {@code null}.
|
||||
*
|
||||
* @return {@code true} if the dumpable was added, {@code false} if the call was ignored.
|
||||
*/
|
||||
boolean addDumpable(@NonNull Dumpable dumpable);
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.internal.util.dump;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Dumpable;
|
||||
import android.util.DumpableContainer;
|
||||
import android.util.IndentingPrintWriter;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Objects;
|
||||
|
||||
// TODO(b/149254050): add unit tests
|
||||
/**
|
||||
* Helper class for {@link DumpableContainer} implementations - they can "implement it by
|
||||
* association", i.e., by delegating the interface methods to a {@code DumpableContainerImpl}.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final class DumpableContainerImpl implements DumpableContainer {
|
||||
|
||||
private static final String TAG = DumpableContainerImpl.class.getSimpleName();
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
@Nullable
|
||||
private final ArrayMap<String, Dumpable> mDumpables = new ArrayMap<>();
|
||||
|
||||
@Override
|
||||
public boolean addDumpable(Dumpable dumpable) {
|
||||
Objects.requireNonNull(dumpable, "dumpable");
|
||||
String name = dumpable.getDumpableName();
|
||||
Objects.requireNonNull(name, () -> "name of" + dumpable);
|
||||
|
||||
if (mDumpables.containsKey(name)) {
|
||||
Log.e(TAG, "addDumpable(): ignoring " + dumpable + " as there is already a dumpable"
|
||||
+ " with that name (" + name + "): " + mDumpables.get(name));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Adding " + name + " -> " + dumpable);
|
||||
}
|
||||
mDumpables.put(name, dumpable);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps the number of dumpable, without a newline.
|
||||
*/
|
||||
private int dumpNumberDumpables(IndentingPrintWriter writer) {
|
||||
int size = mDumpables == null ? 0 : mDumpables.size();
|
||||
if (size == 0) {
|
||||
writer.print("No dumpables");
|
||||
} else {
|
||||
writer.print(size); writer.print(" dumpables");
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists the name of all dumpables to the given {@code writer}.
|
||||
*/
|
||||
public void listDumpables(String prefix, PrintWriter writer) {
|
||||
IndentingPrintWriter ipw = new IndentingPrintWriter(writer, prefix, prefix);
|
||||
|
||||
int size = dumpNumberDumpables(ipw);
|
||||
if (size == 0) {
|
||||
ipw.println();
|
||||
return;
|
||||
}
|
||||
ipw.print(": ");
|
||||
for (int i = 0; i < size; i++) {
|
||||
ipw.print(mDumpables.keyAt(i));
|
||||
if (i < size - 1) ipw.print(' ');
|
||||
}
|
||||
ipw.println();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps the content of all dumpables to the given {@code writer}.
|
||||
*/
|
||||
public void dumpAllDumpables(String prefix, PrintWriter writer, String[] args) {
|
||||
IndentingPrintWriter ipw = new IndentingPrintWriter(writer, prefix, prefix);
|
||||
int size = dumpNumberDumpables(ipw);
|
||||
if (size == 0) {
|
||||
ipw.println();
|
||||
return;
|
||||
}
|
||||
ipw.println(": ");
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
String dumpableName = mDumpables.keyAt(i);
|
||||
ipw.print('#'); ipw.print(i); ipw.print(": "); ipw.println(dumpableName);
|
||||
Dumpable dumpable = mDumpables.valueAt(i);
|
||||
indentAndDump(ipw, dumpable, args);
|
||||
}
|
||||
}
|
||||
|
||||
private void indentAndDump(IndentingPrintWriter writer, Dumpable dumpable, String[] args) {
|
||||
writer.increaseIndent();
|
||||
try {
|
||||
dumpable.dump(writer, args);
|
||||
} finally {
|
||||
writer.decreaseIndent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps the content of a specific dumpable to the given {@code writer}.
|
||||
*/
|
||||
@SuppressWarnings("resource") // cannot close ipw as it would close writer
|
||||
public void dumpOneDumpable(String prefix, PrintWriter writer, String dumpableName,
|
||||
String[] args) {
|
||||
IndentingPrintWriter ipw = new IndentingPrintWriter(writer, prefix, prefix);
|
||||
Dumpable dumpable = mDumpables.get(dumpableName);
|
||||
if (dumpable == null) {
|
||||
ipw.print("No "); ipw.println(dumpableName);
|
||||
return;
|
||||
}
|
||||
ipw.print(dumpableName); ipw.println(':');
|
||||
indentAndDump(ipw, dumpable, args);
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,9 @@ import android.os.RemoteException;
|
||||
import android.os.SystemProperties;
|
||||
import android.os.Trace;
|
||||
import android.os.UserHandle;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Dumpable;
|
||||
import android.util.DumpableContainer;
|
||||
import android.util.Log;
|
||||
import android.util.TimingsTraceLog;
|
||||
import android.view.SurfaceControl;
|
||||
@@ -53,13 +56,19 @@ import java.util.Collections;
|
||||
* Application class for SystemUI.
|
||||
*/
|
||||
public class SystemUIApplication extends Application implements
|
||||
SystemUIAppComponentFactory.ContextInitializer {
|
||||
SystemUIAppComponentFactory.ContextInitializer, DumpableContainer {
|
||||
|
||||
public static final String TAG = "SystemUIService";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private ContextComponentHelper mComponentHelper;
|
||||
private BootCompleteCacheImpl mBootCompleteCache;
|
||||
private DumpManager mDumpManager;
|
||||
|
||||
/**
|
||||
* Map of dumpables added externally.
|
||||
*/
|
||||
private final ArrayMap<String, Dumpable> mDumpables = new ArrayMap<>();
|
||||
|
||||
/**
|
||||
* Hold a reference on the stuff we start.
|
||||
@@ -214,7 +223,7 @@ public class SystemUIApplication extends Application implements
|
||||
}
|
||||
}
|
||||
|
||||
final DumpManager dumpManager = mSysUIComponent.createDumpManager();
|
||||
mDumpManager = mSysUIComponent.createDumpManager();
|
||||
|
||||
Log.v(TAG, "Starting SystemUI services for user " +
|
||||
Process.myUserHandle().getIdentifier() + ".");
|
||||
@@ -255,7 +264,7 @@ public class SystemUIApplication extends Application implements
|
||||
mServices[i].onBootCompleted();
|
||||
}
|
||||
|
||||
dumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]);
|
||||
mDumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]);
|
||||
}
|
||||
mSysUIComponent.getInitController().executePostInitTasks();
|
||||
log.traceEnd();
|
||||
@@ -263,6 +272,29 @@ public class SystemUIApplication extends Application implements
|
||||
mServicesStarted = true;
|
||||
}
|
||||
|
||||
// TODO(b/149254050): add unit tests? There doesn't seem to be a SystemUiApplicationTest...
|
||||
@Override
|
||||
public boolean addDumpable(Dumpable dumpable) {
|
||||
String name = dumpable.getDumpableName();
|
||||
if (mDumpables.containsKey(name)) {
|
||||
// This is normal because SystemUIApplication is an application context that is shared
|
||||
// among multiple components
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "addDumpable(): ignoring " + dumpable + " as there is already a dumpable"
|
||||
+ " with that name (" + name + "): " + mDumpables.get(name));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (DEBUG) Log.d(TAG, "addDumpable(): adding '" + name + "' = " + dumpable);
|
||||
mDumpables.put(name, dumpable);
|
||||
|
||||
// TODO(b/149254050): replace com.android.systemui.dump.Dumpable by
|
||||
// com.android.util.Dumpable and get rid of the intermediate lambda
|
||||
mDumpManager.registerDumpable(dumpable.getDumpableName(),
|
||||
(fd, pw, args) -> dumpable.dump(pw, args));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
if (mServicesStarted) {
|
||||
|
||||
@@ -24,6 +24,8 @@ import android.util.IndentingPrintWriter;
|
||||
*
|
||||
* <p>See {@link SystemServer.SystemServerDumper} for usage example.
|
||||
*/
|
||||
// TODO(b/149254050): replace / merge with package android.util.Dumpable (it would require
|
||||
// exporting IndentingPrintWriter as @SystemApi) and/or changing the method to use a prefix
|
||||
public interface Dumpable {
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user