/* * 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 Predicate 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 type of the value. */ public interface ValueDumper { /** 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 void dumpSparseArrayValues(PrintWriter pw, String prefix, SparseArray 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 void dumpSparseArray(PrintWriter pw, String prefix, SparseArray array, String name, @Nullable KeyDumper keyDumper, @Nullable ValueDumper 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(); } } }