Home | History | Annotate | Download | only in util
      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 
     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