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 29 import java.io.PrintWriter; 30 import java.io.StringWriter; 31 import java.util.Objects; 32 import java.util.function.Predicate; 33 34 /** 35 * Helper functions for dumping the state of system services. 36 * Test: 37 atest FrameworksCoreTests:DumpUtilsTest 38 */ 39 public final class DumpUtils { 40 41 /** 42 * List of component names that should be dumped in the bug report critical section. 43 * 44 * @hide 45 */ 46 public static final ComponentName[] CRITICAL_SECTION_COMPONENTS = { 47 new ComponentName("com.android.systemui", "com.android.systemui.SystemUIService") 48 }; 49 private static final String TAG = "DumpUtils"; 50 private static final boolean DEBUG = false; 51 DumpUtils()52 private DumpUtils() { 53 } 54 55 /** 56 * Helper for dumping state owned by a handler thread. 57 * 58 * Because the caller might be holding an important lock that the handler is 59 * trying to acquire, we use a short timeout to avoid deadlocks. The process 60 * is inelegant but this function is only used for debugging purposes. 61 */ dumpAsync(Handler handler, final Dump dump, PrintWriter pw, final String prefix, long timeout)62 public static void dumpAsync(Handler handler, final Dump dump, PrintWriter pw, 63 final String prefix, long timeout) { 64 final StringWriter sw = new StringWriter(); 65 if (handler.runWithScissors(new Runnable() { 66 @Override 67 public void run() { 68 PrintWriter lpw = new FastPrintWriter(sw); 69 dump.dump(lpw, prefix); 70 lpw.close(); 71 } 72 }, timeout)) { 73 pw.print(sw.toString()); 74 } else { 75 pw.println("... timed out"); 76 } 77 } 78 79 public interface Dump { dump(PrintWriter pw, String prefix)80 void dump(PrintWriter pw, String prefix); 81 } 82 logMessage(PrintWriter pw, String msg)83 private static void logMessage(PrintWriter pw, String msg) { 84 if (DEBUG) Slog.v(TAG, msg); 85 pw.println(msg); 86 } 87 88 /** 89 * Verify that caller holds {@link android.Manifest.permission#DUMP}. 90 * 91 * @return true if access should be granted. 92 * @hide 93 */ checkDumpPermission(Context context, String tag, PrintWriter pw)94 public static boolean checkDumpPermission(Context context, String tag, PrintWriter pw) { 95 if (context.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) 96 != PackageManager.PERMISSION_GRANTED) { 97 logMessage(pw, "Permission Denial: can't dump " + tag + " from from pid=" 98 + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() 99 + " due to missing android.permission.DUMP permission"); 100 return false; 101 } else { 102 return true; 103 } 104 } 105 106 /** 107 * Verify that caller holds 108 * {@link android.Manifest.permission#PACKAGE_USAGE_STATS} and that they 109 * have {@link AppOpsManager#OP_GET_USAGE_STATS} access. 110 * 111 * @return true if access should be granted. 112 * @hide 113 */ checkUsageStatsPermission(Context context, String tag, PrintWriter pw)114 public static boolean checkUsageStatsPermission(Context context, String tag, PrintWriter pw) { 115 // System internals always get access 116 final int uid = Binder.getCallingUid(); 117 switch (uid) { 118 case android.os.Process.ROOT_UID: 119 case android.os.Process.SYSTEM_UID: 120 case android.os.Process.SHELL_UID: 121 case android.os.Process.INCIDENTD_UID: 122 return true; 123 } 124 125 // Caller always needs to hold permission 126 if (context.checkCallingOrSelfPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) 127 != PackageManager.PERMISSION_GRANTED) { 128 logMessage(pw, "Permission Denial: can't dump " + tag + " from from pid=" 129 + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() 130 + " due to missing android.permission.PACKAGE_USAGE_STATS permission"); 131 return false; 132 } 133 134 // And finally, caller needs to have appops access; this is totally 135 // hacky, but it's the easiest way to wire this up without retrofitting 136 // Binder.dump() to pass through package names. 137 final AppOpsManager appOps = context.getSystemService(AppOpsManager.class); 138 final String[] pkgs = context.getPackageManager().getPackagesForUid(uid); 139 if (pkgs != null) { 140 for (String pkg : pkgs) { 141 switch (appOps.noteOpNoThrow(AppOpsManager.OP_GET_USAGE_STATS, uid, pkg)) { 142 case AppOpsManager.MODE_ALLOWED: 143 if (DEBUG) Slog.v(TAG, "Found package " + pkg + " with " 144 + "android:get_usage_stats allowed"); 145 return true; 146 case AppOpsManager.MODE_DEFAULT: 147 if (DEBUG) Slog.v(TAG, "Found package " + pkg + " with " 148 + "android:get_usage_stats default"); 149 return true; 150 } 151 } 152 } 153 154 logMessage(pw, "Permission Denial: can't dump " + tag + " from from pid=" 155 + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() 156 + " due to android:get_usage_stats app-op not allowed"); 157 return false; 158 } 159 160 /** 161 * Verify that caller holds both {@link android.Manifest.permission#DUMP} 162 * and {@link android.Manifest.permission#PACKAGE_USAGE_STATS}, and that 163 * they have {@link AppOpsManager#OP_GET_USAGE_STATS} access. 164 * 165 * @return true if access should be granted. 166 * @hide 167 */ checkDumpAndUsageStatsPermission(Context context, String tag, PrintWriter pw)168 public static boolean checkDumpAndUsageStatsPermission(Context context, String tag, 169 PrintWriter pw) { 170 return checkDumpPermission(context, tag, pw) && checkUsageStatsPermission(context, tag, pw); 171 } 172 173 /** 174 * Return whether a package name is considered to be part of the platform. 175 * @hide 176 */ isPlatformPackage(@ullable String packageName)177 public static boolean isPlatformPackage(@Nullable String packageName) { 178 return (packageName != null) 179 && (packageName.equals("android") 180 || packageName.startsWith("android.") 181 || packageName.startsWith("com.android.")); 182 } 183 184 /** 185 * Return whether a package name is considered to be part of the platform. 186 * @hide 187 */ isPlatformPackage(@ullable ComponentName cname)188 public static boolean isPlatformPackage(@Nullable ComponentName cname) { 189 return (cname != null) && isPlatformPackage(cname.getPackageName()); 190 } 191 192 /** 193 * Return whether a package name is considered to be part of the platform. 194 * @hide 195 */ isPlatformPackage(@ullable ComponentName.WithComponentName wcn)196 public static boolean isPlatformPackage(@Nullable ComponentName.WithComponentName wcn) { 197 return (wcn != null) && isPlatformPackage(wcn.getComponentName()); 198 } 199 200 /** 201 * Return whether a package name is NOT considered to be part of the platform. 202 * @hide 203 */ isNonPlatformPackage(@ullable String packageName)204 public static boolean isNonPlatformPackage(@Nullable String packageName) { 205 return (packageName != null) && !isPlatformPackage(packageName); 206 } 207 208 /** 209 * Return whether a package name is NOT considered to be part of the platform. 210 * @hide 211 */ isNonPlatformPackage(@ullable ComponentName cname)212 public static boolean isNonPlatformPackage(@Nullable ComponentName cname) { 213 return (cname != null) && isNonPlatformPackage(cname.getPackageName()); 214 } 215 216 /** 217 * Return whether a package name is NOT considered to be part of the platform. 218 * @hide 219 */ isNonPlatformPackage(@ullable ComponentName.WithComponentName wcn)220 public static boolean isNonPlatformPackage(@Nullable ComponentName.WithComponentName wcn) { 221 return (wcn != null) && !isPlatformPackage(wcn.getComponentName()); 222 } 223 224 /** 225 * Return whether a package should be dumped in the critical section. 226 */ isCriticalPackage(@ullable ComponentName cname)227 private static boolean isCriticalPackage(@Nullable ComponentName cname) { 228 if (cname == null) { 229 return false; 230 } 231 232 for (int i = 0; i < CRITICAL_SECTION_COMPONENTS.length; i++) { 233 if (cname.equals(CRITICAL_SECTION_COMPONENTS[i])) { 234 return true; 235 } 236 } 237 return false; 238 } 239 240 /** 241 * Return whether a package name is considered to be part of the platform and in the critical 242 * section. 243 * 244 * @hide 245 */ isPlatformCriticalPackage(@ullable ComponentName.WithComponentName wcn)246 public static boolean isPlatformCriticalPackage(@Nullable ComponentName.WithComponentName wcn) { 247 return (wcn != null) && isPlatformPackage(wcn.getComponentName()) && 248 isCriticalPackage(wcn.getComponentName()); 249 } 250 251 /** 252 * Return whether a package name is considered to be part of the platform but not in the the 253 * critical section. 254 * 255 * @hide 256 */ isPlatformNonCriticalPackage( @ullable ComponentName.WithComponentName wcn)257 public static boolean isPlatformNonCriticalPackage( 258 @Nullable ComponentName.WithComponentName wcn) { 259 return (wcn != null) && isPlatformPackage(wcn.getComponentName()) && 260 !isCriticalPackage(wcn.getComponentName()); 261 } 262 263 /** 264 * Used for dumping providers and services. Return a predicate for a given filter string. 265 * @hide 266 */ filterRecord( @ullable String filterString)267 public static <TRec extends ComponentName.WithComponentName> Predicate<TRec> filterRecord( 268 @Nullable String filterString) { 269 270 if (TextUtils.isEmpty(filterString)) { 271 return rec -> false; 272 } 273 274 // Dump all? 275 if ("all".equals(filterString)) { 276 return Objects::nonNull; 277 } 278 279 // Dump all platform? 280 if ("all-platform".equals(filterString)) { 281 return DumpUtils::isPlatformPackage; 282 } 283 284 // Dump all non-platform? 285 if ("all-non-platform".equals(filterString)) { 286 return DumpUtils::isNonPlatformPackage; 287 } 288 289 // Dump all platform-critical? 290 if ("all-platform-critical".equals(filterString)) { 291 return DumpUtils::isPlatformCriticalPackage; 292 } 293 294 // Dump all platform-non-critical? 295 if ("all-platform-non-critical".equals(filterString)) { 296 return DumpUtils::isPlatformNonCriticalPackage; 297 } 298 299 // Is the filter a component name? If so, do an exact match. 300 final ComponentName filterCname = ComponentName.unflattenFromString(filterString); 301 if (filterCname != null) { 302 // Do exact component name check. 303 return rec -> (rec != null) && filterCname.equals(rec.getComponentName()); 304 } 305 306 // Otherwise, do a partial match against the component name. 307 // Also if the filter is a hex-decimal string, do the object ID match too. 308 final int id = ParseUtils.parseIntWithBase(filterString, 16, -1); 309 return rec -> { 310 final ComponentName cn = rec.getComponentName(); 311 return ((id != -1) && (System.identityHashCode(rec) == id)) 312 || cn.flattenToString().toLowerCase().contains(filterString.toLowerCase()); 313 }; 314 } 315 } 316 317