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
17package com.android.internal.util;
18
19import android.annotation.Nullable;
20import android.app.AppOpsManager;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.pm.PackageManager;
24import android.os.Binder;
25import android.os.Handler;
26import android.text.TextUtils;
27import android.util.Slog;
28
29import java.io.PrintWriter;
30import java.io.StringWriter;
31import java.util.Objects;
32import 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 */
39public final class DumpUtils {
40    private static final String TAG = "DumpUtils";
41    private static final boolean DEBUG = false;
42
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     */
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 {
71        void dump(PrintWriter pw, String prefix);
72    }
73
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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