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