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