1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.util; 18 19 import android.annotation.Nullable; 20 import android.app.AppOpsManager; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.pm.PackageManager; 24 import android.os.Binder; 25 import android.os.Handler; 26 import android.text.TextUtils; 27 import android.util.Slog; 28 import android.util.SparseArray; 29 30 import java.io.PrintWriter; 31 import java.io.StringWriter; 32 import java.util.Objects; 33 import java.util.function.Predicate; 34 35 /** 36 * Helper functions for dumping the state of system services. 37 * Test: 38 atest FrameworksCoreTests:DumpUtilsTest 39 */ 40 @android.ravenwood.annotation.RavenwoodKeepWholeClass 41 public final class DumpUtils { 42 43 /** 44 * List of component names that should be dumped in the bug report critical section. 45 * 46 * @hide 47 */ 48 public static final ComponentName[] CRITICAL_SECTION_COMPONENTS = { 49 new ComponentName("com.android.systemui", "com.android.systemui.SystemUIService") 50 }; 51 private static final String TAG = "DumpUtils"; 52 private static final boolean DEBUG = false; 53 DumpUtils()54 private DumpUtils() { 55 } 56 57 /** 58 * Helper for dumping state owned by a handler thread. 59 * 60 * Because the caller might be holding an important lock that the handler is 61 * trying to acquire, we use a short timeout to avoid deadlocks. The process 62 * is inelegant but this function is only used for debugging purposes. 63 */ dumpAsync(Handler handler, final Dump dump, PrintWriter pw, final String prefix, long timeout)64 public static void dumpAsync(Handler handler, final Dump dump, PrintWriter pw, 65 final String prefix, long timeout) { 66 final StringWriter sw = new StringWriter(); 67 if (handler.runWithScissors(new Runnable() { 68 @Override 69 public void run() { 70 PrintWriter lpw = new FastPrintWriter(sw); 71 dump.dump(lpw, prefix); 72 lpw.close(); 73 } 74 }, timeout)) { 75 pw.print(sw.toString()); 76 } else { 77 pw.println("... timed out"); 78 } 79 } 80 81 public interface Dump { dump(PrintWriter pw, String prefix)82 void dump(PrintWriter pw, String prefix); 83 } 84 logMessage(PrintWriter pw, String msg)85 private static void logMessage(PrintWriter pw, String msg) { 86 if (DEBUG) Slog.v(TAG, msg); 87 pw.println(msg); 88 } 89 90 /** 91 * Verify that caller holds {@link android.Manifest.permission#DUMP}. 92 * 93 * @return true if access should be granted. 94 * @hide 95 */ 96 @android.ravenwood.annotation.RavenwoodThrow( 97 blockedBy = android.permission.PermissionManager.class) checkDumpPermission(Context context, String tag, PrintWriter pw)98 public static boolean checkDumpPermission(Context context, String tag, PrintWriter pw) { 99 if (context.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) 100 != PackageManager.PERMISSION_GRANTED) { 101 logMessage(pw, "Permission Denial: can't dump " + tag + " from from pid=" 102 + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() 103 + " due to missing android.permission.DUMP permission"); 104 return false; 105 } else { 106 return true; 107 } 108 } 109 110 /** 111 * Verify that caller holds 112 * {@link android.Manifest.permission#PACKAGE_USAGE_STATS} and that they 113 * have {@link AppOpsManager#OP_GET_USAGE_STATS} access. 114 * 115 * @return true if access should be granted. 116 * @hide 117 */ 118 @android.ravenwood.annotation.RavenwoodThrow( 119 blockedBy = android.permission.PermissionManager.class) checkUsageStatsPermission(Context context, String tag, PrintWriter pw)120 public static boolean checkUsageStatsPermission(Context context, String tag, PrintWriter pw) { 121 // System internals always get access 122 final int uid = Binder.getCallingUid(); 123 switch (uid) { 124 case android.os.Process.ROOT_UID: 125 case android.os.Process.SYSTEM_UID: 126 case android.os.Process.SHELL_UID: 127 case android.os.Process.INCIDENTD_UID: 128 return true; 129 } 130 131 // Caller always needs to hold permission 132 if (context.checkCallingOrSelfPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) 133 != PackageManager.PERMISSION_GRANTED) { 134 logMessage(pw, "Permission Denial: can't dump " + tag + " from from pid=" 135 + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() 136 + " due to missing android.permission.PACKAGE_USAGE_STATS permission"); 137 return false; 138 } 139 140 // And finally, caller needs to have appops access; this is totally 141 // hacky, but it's the easiest way to wire this up without retrofitting 142 // Binder.dump() to pass through package names. 143 final AppOpsManager appOps = context.getSystemService(AppOpsManager.class); 144 final String[] pkgs = context.getPackageManager().getPackagesForUid(uid); 145 if (pkgs != null) { 146 for (String pkg : pkgs) { 147 switch (appOps.noteOpNoThrow(AppOpsManager.OP_GET_USAGE_STATS, uid, pkg)) { 148 case AppOpsManager.MODE_ALLOWED: 149 if (DEBUG) Slog.v(TAG, "Found package " + pkg + " with " 150 + "android:get_usage_stats allowed"); 151 return true; 152 case AppOpsManager.MODE_DEFAULT: 153 if (DEBUG) Slog.v(TAG, "Found package " + pkg + " with " 154 + "android:get_usage_stats default"); 155 return true; 156 } 157 } 158 } 159 160 logMessage(pw, "Permission Denial: can't dump " + tag + " from from pid=" 161 + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() 162 + " due to android:get_usage_stats app-op not allowed"); 163 return false; 164 } 165 166 /** 167 * Verify that caller holds both {@link android.Manifest.permission#DUMP} 168 * and {@link android.Manifest.permission#PACKAGE_USAGE_STATS}, and that 169 * they have {@link AppOpsManager#OP_GET_USAGE_STATS} access. 170 * 171 * @return true if access should be granted. 172 * @hide 173 */ 174 @android.ravenwood.annotation.RavenwoodThrow( 175 blockedBy = android.permission.PermissionManager.class) checkDumpAndUsageStatsPermission(Context context, String tag, PrintWriter pw)176 public static boolean checkDumpAndUsageStatsPermission(Context context, String tag, 177 PrintWriter pw) { 178 return checkDumpPermission(context, tag, pw) && checkUsageStatsPermission(context, tag, pw); 179 } 180 181 /** 182 * Return whether a package name is considered to be part of the platform. 183 * @hide 184 */ isPlatformPackage(@ullable String packageName)185 public static boolean isPlatformPackage(@Nullable String packageName) { 186 return (packageName != null) 187 && (packageName.equals("android") 188 || packageName.startsWith("android.") 189 || packageName.startsWith("com.android.")); 190 } 191 192 /** 193 * Return whether a package name is considered to be part of the platform. 194 * @hide 195 */ isPlatformPackage(@ullable ComponentName cname)196 public static boolean isPlatformPackage(@Nullable ComponentName cname) { 197 return (cname != null) && isPlatformPackage(cname.getPackageName()); 198 } 199 200 /** 201 * Return whether a package name is considered to be part of the platform. 202 * @hide 203 */ isPlatformPackage(@ullable ComponentName.WithComponentName wcn)204 public static boolean isPlatformPackage(@Nullable ComponentName.WithComponentName wcn) { 205 return (wcn != null) && isPlatformPackage(wcn.getComponentName()); 206 } 207 208 /** 209 * Return whether a package name is NOT considered to be part of the platform. 210 * @hide 211 */ isNonPlatformPackage(@ullable String packageName)212 public static boolean isNonPlatformPackage(@Nullable String packageName) { 213 return (packageName != null) && !isPlatformPackage(packageName); 214 } 215 216 /** 217 * Return whether a package name is NOT considered to be part of the platform. 218 * @hide 219 */ isNonPlatformPackage(@ullable ComponentName cname)220 public static boolean isNonPlatformPackage(@Nullable ComponentName cname) { 221 return (cname != null) && isNonPlatformPackage(cname.getPackageName()); 222 } 223 224 /** 225 * Return whether a package name is NOT considered to be part of the platform. 226 * @hide 227 */ isNonPlatformPackage(@ullable ComponentName.WithComponentName wcn)228 public static boolean isNonPlatformPackage(@Nullable ComponentName.WithComponentName wcn) { 229 return (wcn != null) && !isPlatformPackage(wcn.getComponentName()); 230 } 231 232 /** 233 * Return whether a package should be dumped in the critical section. 234 */ isCriticalPackage(@ullable ComponentName cname)235 private static boolean isCriticalPackage(@Nullable ComponentName cname) { 236 if (cname == null) { 237 return false; 238 } 239 240 for (int i = 0; i < CRITICAL_SECTION_COMPONENTS.length; i++) { 241 if (cname.equals(CRITICAL_SECTION_COMPONENTS[i])) { 242 return true; 243 } 244 } 245 return false; 246 } 247 248 /** 249 * Return whether a package name is considered to be part of the platform and in the critical 250 * section. 251 * 252 * @hide 253 */ isPlatformCriticalPackage(@ullable ComponentName.WithComponentName wcn)254 public static boolean isPlatformCriticalPackage(@Nullable ComponentName.WithComponentName wcn) { 255 return (wcn != null) && isPlatformPackage(wcn.getComponentName()) && 256 isCriticalPackage(wcn.getComponentName()); 257 } 258 259 /** 260 * Return whether a package name is considered to be part of the platform but not in the the 261 * critical section. 262 * 263 * @hide 264 */ isPlatformNonCriticalPackage( @ullable ComponentName.WithComponentName wcn)265 public static boolean isPlatformNonCriticalPackage( 266 @Nullable ComponentName.WithComponentName wcn) { 267 return (wcn != null) && isPlatformPackage(wcn.getComponentName()) && 268 !isCriticalPackage(wcn.getComponentName()); 269 } 270 271 /** 272 * Used for dumping providers and services. Return a predicate for a given filter string. 273 * @hide 274 */ filterRecord( @ullable String filterString)275 public static <TRec extends ComponentName.WithComponentName> Predicate<TRec> filterRecord( 276 @Nullable String filterString) { 277 278 if (TextUtils.isEmpty(filterString)) { 279 return rec -> false; 280 } 281 282 // Dump all? 283 if ("all".equals(filterString)) { 284 return Objects::nonNull; 285 } 286 287 // Dump all platform? 288 if ("all-platform".equals(filterString)) { 289 return DumpUtils::isPlatformPackage; 290 } 291 292 // Dump all non-platform? 293 if ("all-non-platform".equals(filterString)) { 294 return DumpUtils::isNonPlatformPackage; 295 } 296 297 // Dump all platform-critical? 298 if ("all-platform-critical".equals(filterString)) { 299 return DumpUtils::isPlatformCriticalPackage; 300 } 301 302 // Dump all platform-non-critical? 303 if ("all-platform-non-critical".equals(filterString)) { 304 return DumpUtils::isPlatformNonCriticalPackage; 305 } 306 307 // Is the filter a component name? If so, do an exact match. 308 final ComponentName filterCname = ComponentName.unflattenFromString(filterString); 309 if (filterCname != null) { 310 // Do exact component name check. 311 return rec -> (rec != null) && filterCname.equals(rec.getComponentName()); 312 } 313 314 // Otherwise, do a partial match against the component name. 315 // Also if the filter is a hex-decimal string, do the object ID match too. 316 final int id = ParseUtils.parseIntWithBase(filterString, 16, -1); 317 return rec -> { 318 final ComponentName cn = rec.getComponentName(); 319 return ((id != -1) && (System.identityHashCode(rec) == id)) 320 || cn.flattenToString().toLowerCase().contains(filterString.toLowerCase()); 321 }; 322 } 323 324 /** 325 * Lambda used to dump a key (and its index) while iterating though a collection. 326 */ 327 public interface KeyDumper { 328 329 /** Dumps the index and key.*/ dump(int index, int key)330 void dump(int index, int key); 331 } 332 333 /** 334 * Lambda used to dump a value while iterating though a collection. 335 * 336 * @param <T> type of the value. 337 */ 338 public interface ValueDumper<T> { 339 340 /** Dumps the value.*/ dump(T value)341 void dump(T value); 342 } 343 344 /** 345 * Dumps a sparse array. 346 */ dumpSparseArray(PrintWriter pw, String prefix, SparseArray<?> array, String name)347 public static void dumpSparseArray(PrintWriter pw, String prefix, SparseArray<?> array, 348 String name) { 349 dumpSparseArray(pw, prefix, array, name, /* keyDumper= */ null, /* valueDumper= */ null); 350 } 351 352 /** 353 * Dumps the values of a sparse array. 354 */ dumpSparseArrayValues(PrintWriter pw, String prefix, SparseArray<T> array, String name)355 public static <T> void dumpSparseArrayValues(PrintWriter pw, String prefix, 356 SparseArray<T> array, String name) { 357 dumpSparseArray(pw, prefix, array, name, (i, k) -> { 358 pw.printf("%s%s", prefix, prefix); 359 }, /* valueDumper= */ null); 360 } 361 362 /** 363 * Dumps a sparse array, customizing each line. 364 */ dumpSparseArray(PrintWriter pw, String prefix, SparseArray<T> array, String name, @Nullable KeyDumper keyDumper, @Nullable ValueDumper<T> valueDumper)365 public static <T> void dumpSparseArray(PrintWriter pw, String prefix, SparseArray<T> array, 366 String name, @Nullable KeyDumper keyDumper, @Nullable ValueDumper<T> valueDumper) { 367 int size = array.size(); 368 if (size == 0) { 369 pw.print(prefix); 370 pw.print("No "); 371 pw.print(name); 372 pw.println("s"); 373 return; 374 } 375 pw.print(prefix); 376 pw.print(size); 377 pw.print(' '); 378 pw.print(name); 379 pw.println("(s):"); 380 381 String prefix2 = prefix + prefix; 382 for (int i = 0; i < size; i++) { 383 int key = array.keyAt(i); 384 T value = array.valueAt(i); 385 if (keyDumper != null) { 386 keyDumper.dump(i, key); 387 } else { 388 pw.print(prefix2); 389 pw.print(i); 390 pw.print(": "); 391 pw.print(key); 392 pw.print("->"); 393 } 394 if (value == null) { 395 pw.print("(null)"); 396 } else if (valueDumper != null) { 397 valueDumper.dump(value); 398 } else { 399 pw.print(value); 400 } 401 pw.println(); 402 } 403 } 404 } 405