1 /* 2 * Copyright (C) 2019 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 android.telephony; 18 19 import android.annotation.NonNull; 20 import android.annotation.RequiresPermission; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.pm.PackageManager; 24 import android.content.pm.ResolveInfo; 25 import android.os.ParcelUuid; 26 27 import com.android.internal.util.IndentingPrintWriter; 28 29 import java.io.FileDescriptor; 30 import java.io.PrintWriter; 31 import java.util.List; 32 import java.util.Map; 33 import java.util.UUID; 34 import java.util.concurrent.ConcurrentHashMap; 35 36 /** 37 * A Simple Surface for Telephony to notify a loosely-coupled debugger of particular issues. 38 * 39 * AnomalyReporter allows an optional external logging component to receive events detected by 40 * the framework and take action. This log surface is designed to provide maximium flexibility 41 * to the receiver of these events. Envisioned use cases of this include notifying a vendor 42 * component of: an event that necessitates (timely) log collection on non-AOSP components; 43 * notifying a vendor component of a rare event that should prompt further action such as a 44 * bug report or user intervention for debug purposes. 45 * 46 * <p>This surface is not intended to enable a diagnostic monitor, nor is it intended to support 47 * streaming logs. 48 * 49 * @hide 50 */ 51 public final class AnomalyReporter { 52 private static final String TAG = "AnomalyReporter"; 53 54 private static Context sContext = null; 55 56 private static Map<UUID, Integer> sEvents = new ConcurrentHashMap<>(); 57 58 /* 59 * Because this is only supporting system packages, once we find a package, it will be the 60 * same package until the next system upgrade. Thus, to save time in processing debug events 61 * we can cache this info and skip the resolution process after it's done the first time. 62 */ 63 private static String sDebugPackageName = null; 64 AnomalyReporter()65 private AnomalyReporter() {}; 66 67 /** 68 * If enabled, build and send an intent to a Debug Service for logging. 69 * 70 * This method sends the {@link TelephonyManager#ACTION_ANOMALY_REPORTED} broadcast, which is 71 * system protected. Invoking this method unless you are the system will result in an error. 72 * 73 * @param eventId a fixed event ID that will be sent for each instance of the same event. This 74 * ID should be generated randomly. 75 * @param description an optional description, that if included will be used as the subject for 76 * identification and discussion of this event. This description should ideally be 77 * static and must not contain any sensitive information (especially PII). 78 */ reportAnomaly(@onNull UUID eventId, String description)79 public static void reportAnomaly(@NonNull UUID eventId, String description) { 80 if (sContext == null) { 81 Rlog.w(TAG, "AnomalyReporter not yet initialized, dropping event=" + eventId); 82 return; 83 } 84 85 // If this event has already occurred, skip sending intents for it; regardless log its 86 // invocation here. 87 Integer count = sEvents.containsKey(eventId) ? sEvents.get(eventId) + 1 : 1; 88 sEvents.put(eventId, count); 89 if (count > 1) return; 90 91 // Even if we are initialized, that doesn't mean that a package name has been found. 92 // This is normal in many cases, such as when no debug package is installed on the system, 93 // so drop these events silently. 94 if (sDebugPackageName == null) return; 95 96 Intent dbgIntent = new Intent(TelephonyManager.ACTION_ANOMALY_REPORTED); 97 dbgIntent.putExtra(TelephonyManager.EXTRA_ANOMALY_ID, new ParcelUuid(eventId)); 98 if (description != null) { 99 dbgIntent.putExtra(TelephonyManager.EXTRA_ANOMALY_DESCRIPTION, description); 100 } 101 dbgIntent.setPackage(sDebugPackageName); 102 sContext.sendBroadcast(dbgIntent, android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE); 103 } 104 105 /** 106 * Initialize the AnomalyReporter with the current context. 107 * 108 * This method must be invoked before any calls to reportAnomaly() will succeed. This method 109 * should only be invoked at most once. 110 * 111 * @param context a Context object used to initialize this singleton AnomalyReporter in 112 * the current process. 113 */ 114 @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) initialize(@onNull Context context)115 public static void initialize(@NonNull Context context) { 116 if (context == null) { 117 throw new IllegalArgumentException("AnomalyReporter needs a non-null context."); 118 } 119 120 // Ensure that this context has sufficient permissions to send debug events. 121 context.enforceCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE, 122 "This app does not have privileges to send debug events"); 123 124 sContext = context; 125 126 // Check to see if there is a valid debug package; if there are multiple, that's a config 127 // error, so just take the first one. 128 PackageManager pm = sContext.getPackageManager(); 129 if (pm == null) return; 130 List<ResolveInfo> packages = pm.queryBroadcastReceivers( 131 new Intent(TelephonyManager.ACTION_ANOMALY_REPORTED), 132 PackageManager.MATCH_SYSTEM_ONLY 133 | PackageManager.MATCH_DIRECT_BOOT_AWARE 134 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); 135 if (packages == null || packages.isEmpty()) return; 136 if (packages.size() > 1) { 137 Rlog.e(TAG, "Multiple Anomaly Receivers installed."); 138 } 139 140 for (ResolveInfo r : packages) { 141 if (r.activityInfo == null 142 || pm.checkPermission( 143 android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, 144 r.activityInfo.packageName) 145 != PackageManager.PERMISSION_GRANTED) { 146 Rlog.w(TAG, 147 "Found package without proper permissions or no activity" 148 + r.activityInfo.packageName); 149 continue; 150 } 151 Rlog.d(TAG, "Found a valid package " + r.activityInfo.packageName); 152 sDebugPackageName = r.activityInfo.packageName; 153 break; 154 } 155 // Initialization may only be performed once. 156 } 157 158 /** Dump the contents of the AnomalyReporter */ dump(FileDescriptor fd, PrintWriter printWriter, String[] args)159 public static void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) { 160 if (sContext == null) return; 161 IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); 162 sContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, "Requires DUMP"); 163 pw.println("Initialized=" + (sContext != null ? "Yes" : "No")); 164 pw.println("Debug Package=" + sDebugPackageName); 165 pw.println("Anomaly Counts:"); 166 pw.increaseIndent(); 167 for (UUID event : sEvents.keySet()) { 168 pw.println(event + ": " + sEvents.get(event)); 169 } 170 pw.decreaseIndent(); 171 pw.flush(); 172 } 173 } 174