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