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