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