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