405 lines
14 KiB
Java
405 lines
14 KiB
Java
![]() |
/*
|
||
|
* Copyright (C) 2012 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;
|
||
|
|
||
|
import android.annotation.Nullable;
|
||
|
import android.app.AppOpsManager;
|
||
|
import android.content.ComponentName;
|
||
|
import android.content.Context;
|
||
|
import android.content.pm.PackageManager;
|
||
|
import android.os.Binder;
|
||
|
import android.os.Handler;
|
||
|
import android.text.TextUtils;
|
||
|
import android.util.Slog;
|
||
|
import android.util.SparseArray;
|
||
|
|
||
|
import java.io.PrintWriter;
|
||
|
import java.io.StringWriter;
|
||
|
import java.util.Objects;
|
||
|
import java.util.function.Predicate;
|
||
|
|
||
|
/**
|
||
|
* Helper functions for dumping the state of system services.
|
||
|
* Test:
|
||
|
atest FrameworksCoreTests:DumpUtilsTest
|
||
|
*/
|
||
|
@android.ravenwood.annotation.RavenwoodKeepWholeClass
|
||
|
public final class DumpUtils {
|
||
|
|
||
|
/**
|
||
|
* List of component names that should be dumped in the bug report critical section.
|
||
|
*
|
||
|
* @hide
|
||
|
*/
|
||
|
public static final ComponentName[] CRITICAL_SECTION_COMPONENTS = {
|
||
|
new ComponentName("com.android.systemui", "com.android.systemui.SystemUIService")
|
||
|
};
|
||
|
private static final String TAG = "DumpUtils";
|
||
|
private static final boolean DEBUG = false;
|
||
|
|
||
|
private DumpUtils() {
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Helper for dumping state owned by a handler thread.
|
||
|
*
|
||
|
* Because the caller might be holding an important lock that the handler is
|
||
|
* trying to acquire, we use a short timeout to avoid deadlocks. The process
|
||
|
* is inelegant but this function is only used for debugging purposes.
|
||
|
*/
|
||
|
public static void dumpAsync(Handler handler, final Dump dump, PrintWriter pw,
|
||
|
final String prefix, long timeout) {
|
||
|
final StringWriter sw = new StringWriter();
|
||
|
if (handler.runWithScissors(new Runnable() {
|
||
|
@Override
|
||
|
public void run() {
|
||
|
PrintWriter lpw = new FastPrintWriter(sw);
|
||
|
dump.dump(lpw, prefix);
|
||
|
lpw.close();
|
||
|
}
|
||
|
}, timeout)) {
|
||
|
pw.print(sw.toString());
|
||
|
} else {
|
||
|
pw.println("... timed out");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public interface Dump {
|
||
|
void dump(PrintWriter pw, String prefix);
|
||
|
}
|
||
|
|
||
|
private static void logMessage(PrintWriter pw, String msg) {
|
||
|
if (DEBUG) Slog.v(TAG, msg);
|
||
|
pw.println(msg);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Verify that caller holds {@link android.Manifest.permission#DUMP}.
|
||
|
*
|
||
|
* @return true if access should be granted.
|
||
|
* @hide
|
||
|
*/
|
||
|
@android.ravenwood.annotation.RavenwoodThrow(
|
||
|
blockedBy = android.permission.PermissionManager.class)
|
||
|
public static boolean checkDumpPermission(Context context, String tag, PrintWriter pw) {
|
||
|
if (context.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
|
||
|
!= PackageManager.PERMISSION_GRANTED) {
|
||
|
logMessage(pw, "Permission Denial: can't dump " + tag + " from from pid="
|
||
|
+ Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
|
||
|
+ " due to missing android.permission.DUMP permission");
|
||
|
return false;
|
||
|
} else {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Verify that caller holds
|
||
|
* {@link android.Manifest.permission#PACKAGE_USAGE_STATS} and that they
|
||
|
* have {@link AppOpsManager#OP_GET_USAGE_STATS} access.
|
||
|
*
|
||
|
* @return true if access should be granted.
|
||
|
* @hide
|
||
|
*/
|
||
|
@android.ravenwood.annotation.RavenwoodThrow(
|
||
|
blockedBy = android.permission.PermissionManager.class)
|
||
|
public static boolean checkUsageStatsPermission(Context context, String tag, PrintWriter pw) {
|
||
|
// System internals always get access
|
||
|
final int uid = Binder.getCallingUid();
|
||
|
switch (uid) {
|
||
|
case android.os.Process.ROOT_UID:
|
||
|
case android.os.Process.SYSTEM_UID:
|
||
|
case android.os.Process.SHELL_UID:
|
||
|
case android.os.Process.INCIDENTD_UID:
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Caller always needs to hold permission
|
||
|
if (context.checkCallingOrSelfPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)
|
||
|
!= PackageManager.PERMISSION_GRANTED) {
|
||
|
logMessage(pw, "Permission Denial: can't dump " + tag + " from from pid="
|
||
|
+ Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
|
||
|
+ " due to missing android.permission.PACKAGE_USAGE_STATS permission");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// And finally, caller needs to have appops access; this is totally
|
||
|
// hacky, but it's the easiest way to wire this up without retrofitting
|
||
|
// Binder.dump() to pass through package names.
|
||
|
final AppOpsManager appOps = context.getSystemService(AppOpsManager.class);
|
||
|
final String[] pkgs = context.getPackageManager().getPackagesForUid(uid);
|
||
|
if (pkgs != null) {
|
||
|
for (String pkg : pkgs) {
|
||
|
switch (appOps.noteOpNoThrow(AppOpsManager.OP_GET_USAGE_STATS, uid, pkg)) {
|
||
|
case AppOpsManager.MODE_ALLOWED:
|
||
|
if (DEBUG) Slog.v(TAG, "Found package " + pkg + " with "
|
||
|
+ "android:get_usage_stats allowed");
|
||
|
return true;
|
||
|
case AppOpsManager.MODE_DEFAULT:
|
||
|
if (DEBUG) Slog.v(TAG, "Found package " + pkg + " with "
|
||
|
+ "android:get_usage_stats default");
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
logMessage(pw, "Permission Denial: can't dump " + tag + " from from pid="
|
||
|
+ Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
|
||
|
+ " due to android:get_usage_stats app-op not allowed");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Verify that caller holds both {@link android.Manifest.permission#DUMP}
|
||
|
* and {@link android.Manifest.permission#PACKAGE_USAGE_STATS}, and that
|
||
|
* they have {@link AppOpsManager#OP_GET_USAGE_STATS} access.
|
||
|
*
|
||
|
* @return true if access should be granted.
|
||
|
* @hide
|
||
|
*/
|
||
|
@android.ravenwood.annotation.RavenwoodThrow(
|
||
|
blockedBy = android.permission.PermissionManager.class)
|
||
|
public static boolean checkDumpAndUsageStatsPermission(Context context, String tag,
|
||
|
PrintWriter pw) {
|
||
|
return checkDumpPermission(context, tag, pw) && checkUsageStatsPermission(context, tag, pw);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return whether a package name is considered to be part of the platform.
|
||
|
* @hide
|
||
|
*/
|
||
|
public static boolean isPlatformPackage(@Nullable String packageName) {
|
||
|
return (packageName != null)
|
||
|
&& (packageName.equals("android")
|
||
|
|| packageName.startsWith("android.")
|
||
|
|| packageName.startsWith("com.android."));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return whether a package name is considered to be part of the platform.
|
||
|
* @hide
|
||
|
*/
|
||
|
public static boolean isPlatformPackage(@Nullable ComponentName cname) {
|
||
|
return (cname != null) && isPlatformPackage(cname.getPackageName());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return whether a package name is considered to be part of the platform.
|
||
|
* @hide
|
||
|
*/
|
||
|
public static boolean isPlatformPackage(@Nullable ComponentName.WithComponentName wcn) {
|
||
|
return (wcn != null) && isPlatformPackage(wcn.getComponentName());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return whether a package name is NOT considered to be part of the platform.
|
||
|
* @hide
|
||
|
*/
|
||
|
public static boolean isNonPlatformPackage(@Nullable String packageName) {
|
||
|
return (packageName != null) && !isPlatformPackage(packageName);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return whether a package name is NOT considered to be part of the platform.
|
||
|
* @hide
|
||
|
*/
|
||
|
public static boolean isNonPlatformPackage(@Nullable ComponentName cname) {
|
||
|
return (cname != null) && isNonPlatformPackage(cname.getPackageName());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return whether a package name is NOT considered to be part of the platform.
|
||
|
* @hide
|
||
|
*/
|
||
|
public static boolean isNonPlatformPackage(@Nullable ComponentName.WithComponentName wcn) {
|
||
|
return (wcn != null) && !isPlatformPackage(wcn.getComponentName());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return whether a package should be dumped in the critical section.
|
||
|
*/
|
||
|
private static boolean isCriticalPackage(@Nullable ComponentName cname) {
|
||
|
if (cname == null) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
for (int i = 0; i < CRITICAL_SECTION_COMPONENTS.length; i++) {
|
||
|
if (cname.equals(CRITICAL_SECTION_COMPONENTS[i])) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return whether a package name is considered to be part of the platform and in the critical
|
||
|
* section.
|
||
|
*
|
||
|
* @hide
|
||
|
*/
|
||
|
public static boolean isPlatformCriticalPackage(@Nullable ComponentName.WithComponentName wcn) {
|
||
|
return (wcn != null) && isPlatformPackage(wcn.getComponentName()) &&
|
||
|
isCriticalPackage(wcn.getComponentName());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return whether a package name is considered to be part of the platform but not in the the
|
||
|
* critical section.
|
||
|
*
|
||
|
* @hide
|
||
|
*/
|
||
|
public static boolean isPlatformNonCriticalPackage(
|
||
|
@Nullable ComponentName.WithComponentName wcn) {
|
||
|
return (wcn != null) && isPlatformPackage(wcn.getComponentName()) &&
|
||
|
!isCriticalPackage(wcn.getComponentName());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Used for dumping providers and services. Return a predicate for a given filter string.
|
||
|
* @hide
|
||
|
*/
|
||
|
public static <TRec extends ComponentName.WithComponentName> Predicate<TRec> filterRecord(
|
||
|
@Nullable String filterString) {
|
||
|
|
||
|
if (TextUtils.isEmpty(filterString)) {
|
||
|
return rec -> false;
|
||
|
}
|
||
|
|
||
|
// Dump all?
|
||
|
if ("all".equals(filterString)) {
|
||
|
return Objects::nonNull;
|
||
|
}
|
||
|
|
||
|
// Dump all platform?
|
||
|
if ("all-platform".equals(filterString)) {
|
||
|
return DumpUtils::isPlatformPackage;
|
||
|
}
|
||
|
|
||
|
// Dump all non-platform?
|
||
|
if ("all-non-platform".equals(filterString)) {
|
||
|
return DumpUtils::isNonPlatformPackage;
|
||
|
}
|
||
|
|
||
|
// Dump all platform-critical?
|
||
|
if ("all-platform-critical".equals(filterString)) {
|
||
|
return DumpUtils::isPlatformCriticalPackage;
|
||
|
}
|
||
|
|
||
|
// Dump all platform-non-critical?
|
||
|
if ("all-platform-non-critical".equals(filterString)) {
|
||
|
return DumpUtils::isPlatformNonCriticalPackage;
|
||
|
}
|
||
|
|
||
|
// Is the filter a component name? If so, do an exact match.
|
||
|
final ComponentName filterCname = ComponentName.unflattenFromString(filterString);
|
||
|
if (filterCname != null) {
|
||
|
// Do exact component name check.
|
||
|
return rec -> (rec != null) && filterCname.equals(rec.getComponentName());
|
||
|
}
|
||
|
|
||
|
// Otherwise, do a partial match against the component name.
|
||
|
// Also if the filter is a hex-decimal string, do the object ID match too.
|
||
|
final int id = ParseUtils.parseIntWithBase(filterString, 16, -1);
|
||
|
return rec -> {
|
||
|
final ComponentName cn = rec.getComponentName();
|
||
|
return ((id != -1) && (System.identityHashCode(rec) == id))
|
||
|
|| cn.flattenToString().toLowerCase().contains(filterString.toLowerCase());
|
||
|
};
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Lambda used to dump a key (and its index) while iterating though a collection.
|
||
|
*/
|
||
|
public interface KeyDumper {
|
||
|
|
||
|
/** Dumps the index and key.*/
|
||
|
void dump(int index, int key);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Lambda used to dump a value while iterating though a collection.
|
||
|
*
|
||
|
* @param <T> type of the value.
|
||
|
*/
|
||
|
public interface ValueDumper<T> {
|
||
|
|
||
|
/** Dumps the value.*/
|
||
|
void dump(T value);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Dumps a sparse array.
|
||
|
*/
|
||
|
public static void dumpSparseArray(PrintWriter pw, String prefix, SparseArray<?> array,
|
||
|
String name) {
|
||
|
dumpSparseArray(pw, prefix, array, name, /* keyDumper= */ null, /* valueDumper= */ null);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Dumps the values of a sparse array.
|
||
|
*/
|
||
|
public static <T> void dumpSparseArrayValues(PrintWriter pw, String prefix,
|
||
|
SparseArray<T> array, String name) {
|
||
|
dumpSparseArray(pw, prefix, array, name, (i, k) -> {
|
||
|
pw.printf("%s%s", prefix, prefix);
|
||
|
}, /* valueDumper= */ null);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Dumps a sparse array, customizing each line.
|
||
|
*/
|
||
|
public static <T> void dumpSparseArray(PrintWriter pw, String prefix, SparseArray<T> array,
|
||
|
String name, @Nullable KeyDumper keyDumper, @Nullable ValueDumper<T> valueDumper) {
|
||
|
int size = array.size();
|
||
|
if (size == 0) {
|
||
|
pw.print(prefix);
|
||
|
pw.print("No ");
|
||
|
pw.print(name);
|
||
|
pw.println("s");
|
||
|
return;
|
||
|
}
|
||
|
pw.print(prefix);
|
||
|
pw.print(size);
|
||
|
pw.print(' ');
|
||
|
pw.print(name);
|
||
|
pw.println("(s):");
|
||
|
|
||
|
String prefix2 = prefix + prefix;
|
||
|
for (int i = 0; i < size; i++) {
|
||
|
int key = array.keyAt(i);
|
||
|
T value = array.valueAt(i);
|
||
|
if (keyDumper != null) {
|
||
|
keyDumper.dump(i, key);
|
||
|
} else {
|
||
|
pw.print(prefix2);
|
||
|
pw.print(i);
|
||
|
pw.print(": ");
|
||
|
pw.print(key);
|
||
|
pw.print("->");
|
||
|
}
|
||
|
if (value == null) {
|
||
|
pw.print("(null)");
|
||
|
} else if (valueDumper != null) {
|
||
|
valueDumper.dump(value);
|
||
|
} else {
|
||
|
pw.print(value);
|
||
|
}
|
||
|
pw.println();
|
||
|
}
|
||
|
}
|
||
|
}
|