• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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.traceur;
18 
19 import android.app.Notification;
20 import android.app.NotificationChannel;
21 import android.app.NotificationManager;
22 import android.app.PendingIntent;
23 import android.content.BroadcastReceiver;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.SharedPreferences;
28 import android.content.pm.PackageManager;
29 import android.database.ContentObserver;
30 import android.net.Uri;
31 import android.os.Build;
32 import android.os.Handler;
33 import android.os.RemoteException;
34 import android.os.ServiceManager;
35 import android.os.UserManager;
36 import android.preference.PreferenceManager;
37 import android.provider.Settings;
38 import android.text.TextUtils;
39 import android.util.ArraySet;
40 import android.util.Log;
41 
42 import com.android.internal.statusbar.IStatusBarService;
43 
44 import java.util.Arrays;
45 import java.util.List;
46 import java.util.Set;
47 
48 public class Receiver extends BroadcastReceiver {
49 
50     public static final String STOP_ACTION = "com.android.traceur.STOP";
51     public static final String OPEN_ACTION = "com.android.traceur.OPEN";
52     public static final String BUGREPORT_STARTED =
53             "com.android.internal.intent.action.BUGREPORT_STARTED";
54 
55     public static final String NOTIFICATION_CHANNEL_TRACING = "trace-is-being-recorded";
56     public static final String NOTIFICATION_CHANNEL_OTHER = "system-tracing";
57 
58     private static final List<String> TRACE_TAGS = Arrays.asList(
59             "aidl", "am", "binder_driver", "camera", "dalvik", "disk", "freq",
60             "gfx", "hal", "idle", "input", "memory", "memreclaim", "network", "power",
61             "res", "sched", "sync", "thermal", "view", "webview", "wm", "workq");
62 
63     /* The user list doesn't include workq or sync, because the user builds don't have
64      * permissions for them. */
65     private static final List<String> TRACE_TAGS_USER = Arrays.asList(
66             "aidl", "am", "binder_driver", "camera", "dalvik", "disk", "freq",
67             "gfx", "hal", "idle", "input", "memory", "memreclaim", "network", "power",
68             "res", "sched", "thermal", "view", "webview", "wm");
69 
70     private static final String TAG = "Traceur";
71 
72     private static final String BETTERBUG_PACKAGE_NAME =
73             "com.google.android.apps.internal.betterbug";
74 
75     private static Set<String> mDefaultTagList = null;
76     private static ContentObserver mDeveloperOptionsObserver;
77 
78     @Override
onReceive(Context context, Intent intent)79     public void onReceive(Context context, Intent intent) {
80         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
81 
82         if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
83             Log.i(TAG, "Received BOOT_COMPLETE");
84             createNotificationChannels(context);
85             updateDeveloperOptionsWatcher(context);
86             // We know that Perfetto won't be tracing already at boot, so pass the
87             // tracingIsOff argument to avoid the Perfetto check.
88             updateTracing(context, /* assumeTracingIsOff= */ true);
89         } else if (Intent.ACTION_USER_FOREGROUND.equals(intent.getAction())) {
90             boolean developerOptionsEnabled = (1 ==
91                 Settings.Global.getInt(context.getContentResolver(),
92                     Settings.Global.DEVELOPMENT_SETTINGS_ENABLED , 0));
93             UserManager userManager = context.getSystemService(UserManager.class);
94             boolean isAdminUser = userManager.isAdminUser();
95             boolean debuggingDisallowed = userManager.hasUserRestriction(
96                     UserManager.DISALLOW_DEBUGGING_FEATURES);
97             updateStorageProvider(context,
98                     developerOptionsEnabled && isAdminUser && !debuggingDisallowed);
99         } else if (STOP_ACTION.equals(intent.getAction())) {
100             prefs.edit().putBoolean(
101                     context.getString(R.string.pref_key_tracing_on), false).commit();
102             updateTracing(context);
103         } else if (OPEN_ACTION.equals(intent.getAction())) {
104             context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
105             context.startActivity(new Intent(context, MainActivity.class)
106                     .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
107         } else if (BUGREPORT_STARTED.equals(intent.getAction())) {
108             // If stop_on_bugreport is set and attach_to_bugreport is not, stop tracing.
109             // Otherwise, if attach_to_bugreport is set perfetto will end the session,
110             // and we should not take action on the Traceur side.
111             if (prefs.getBoolean(context.getString(R.string.pref_key_stop_on_bugreport), false) &&
112                 !prefs.getBoolean(context.getString(
113                         R.string.pref_key_attach_to_bugreport), true)) {
114                 Log.d(TAG, "Bugreport started, ending trace.");
115                 prefs.edit().putBoolean(context.getString(R.string.pref_key_tracing_on), false).commit();
116                 updateTracing(context);
117             }
118         }
119     }
120 
121     /*
122      * Updates the current tracing state based on the current state of preferences.
123      */
updateTracing(Context context)124     public static void updateTracing(Context context) {
125         updateTracing(context, false);
126     }
127 
updateTracing(Context context, boolean assumeTracingIsOff)128     public static void updateTracing(Context context, boolean assumeTracingIsOff) {
129         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
130         boolean prefsTracingOn =
131                 prefs.getBoolean(context.getString(R.string.pref_key_tracing_on), false);
132 
133         boolean traceUtilsTracingOn = assumeTracingIsOff ? false : TraceUtils.isTracingOn();
134 
135         if (prefsTracingOn != traceUtilsTracingOn) {
136             if (prefsTracingOn) {
137                 // Show notification if the tags in preferences are not all actually available.
138                 Set<String> activeAvailableTags = getActiveTags(context, prefs, true);
139                 Set<String> activeTags = getActiveTags(context, prefs, false);
140 
141                 if (!activeAvailableTags.equals(activeTags)) {
142                     postCategoryNotification(context, prefs);
143                 }
144 
145                 int bufferSize = Integer.parseInt(
146                     prefs.getString(context.getString(R.string.pref_key_buffer_size),
147                         context.getString(R.string.default_buffer_size)));
148 
149                 boolean appTracing = prefs.getBoolean(context.getString(R.string.pref_key_apps), true);
150                 boolean longTrace = prefs.getBoolean(context.getString(R.string.pref_key_long_traces), true);
151 
152                 int maxLongTraceSize = Integer.parseInt(
153                     prefs.getString(context.getString(R.string.pref_key_max_long_trace_size),
154                         context.getString(R.string.default_long_trace_size)));
155 
156                 int maxLongTraceDuration = Integer.parseInt(
157                     prefs.getString(context.getString(R.string.pref_key_max_long_trace_duration),
158                         context.getString(R.string.default_long_trace_duration)));
159 
160                 TraceService.startTracing(context, activeAvailableTags, bufferSize,
161                     appTracing, longTrace, maxLongTraceSize, maxLongTraceDuration);
162             } else {
163                 TraceService.stopTracing(context);
164             }
165         }
166 
167         // Update the main UI and the QS tile.
168         context.sendBroadcast(new Intent(MainFragment.ACTION_REFRESH_TAGS));
169         QsService.updateTile();
170     }
171 
172     /*
173      * Updates the current Quick Settings tile state based on the current state
174      * of preferences.
175      */
updateQuickSettings(Context context)176     public static void updateQuickSettings(Context context) {
177         boolean quickSettingsEnabled =
178             PreferenceManager.getDefaultSharedPreferences(context)
179               .getBoolean(context.getString(R.string.pref_key_quick_setting), false);
180 
181         ComponentName name = new ComponentName(context, QsService.class);
182         context.getPackageManager().setComponentEnabledSetting(name,
183             quickSettingsEnabled
184                 ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
185                 : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
186             PackageManager.DONT_KILL_APP);
187 
188         IStatusBarService statusBarService = IStatusBarService.Stub.asInterface(
189             ServiceManager.checkService(Context.STATUS_BAR_SERVICE));
190 
191         try {
192             if (statusBarService != null) {
193                 if (quickSettingsEnabled) {
194                     statusBarService.addTile(name);
195                 } else {
196                     statusBarService.remTile(name);
197                 }
198             }
199         } catch (RemoteException e) {
200             Log.e(TAG, "Failed to modify QS tile for Traceur.", e);
201         }
202 
203         QsService.updateTile();
204     }
205 
206     /*
207      * When Developer Options are toggled, also toggle the Storage Provider that
208      * shows "System traces" in Files.
209      * When Developer Options are turned off, reset the Show Quick Settings Tile
210      * preference to false to hide the tile. The user will need to re-enable the
211      * preference if they decide to turn Developer Options back on again.
212      */
updateDeveloperOptionsWatcher(Context context)213     static void updateDeveloperOptionsWatcher(Context context) {
214         if (mDeveloperOptionsObserver == null) {
215             Uri settingUri = Settings.Global.getUriFor(
216                 Settings.Global.DEVELOPMENT_SETTINGS_ENABLED);
217 
218             mDeveloperOptionsObserver =
219                 new ContentObserver(new Handler()) {
220                     @Override
221                     public void onChange(boolean selfChange) {
222                         super.onChange(selfChange);
223 
224                         boolean developerOptionsEnabled = (1 ==
225                             Settings.Global.getInt(context.getContentResolver(),
226                                 Settings.Global.DEVELOPMENT_SETTINGS_ENABLED , 0));
227                         UserManager userManager = context.getSystemService(UserManager.class);
228                         boolean isAdminUser = userManager.isAdminUser();
229                         boolean debuggingDisallowed = userManager.hasUserRestriction(
230                                 UserManager.DISALLOW_DEBUGGING_FEATURES);
231                         updateStorageProvider(context,
232                                 developerOptionsEnabled && isAdminUser && !debuggingDisallowed);
233 
234                         if (!developerOptionsEnabled) {
235                             SharedPreferences prefs =
236                                 PreferenceManager.getDefaultSharedPreferences(context);
237                             prefs.edit().putBoolean(
238                                 context.getString(R.string.pref_key_quick_setting), false)
239                                 .commit();
240                             updateQuickSettings(context);
241                             // Stop an ongoing trace if one exists.
242                             if (TraceUtils.isTracingOn()) {
243                                 TraceService.stopTracingWithoutSaving(context);
244                             }
245                         }
246                     }
247                 };
248 
249             context.getContentResolver().registerContentObserver(settingUri,
250                 false, mDeveloperOptionsObserver);
251             mDeveloperOptionsObserver.onChange(true);
252         }
253     }
254 
255     // Enables/disables the System Traces storage component. enableProvider should be true iff
256     // developer options are enabled and the current user is an admin user.
updateStorageProvider(Context context, boolean enableProvider)257     static void updateStorageProvider(Context context, boolean enableProvider) {
258         ComponentName name = new ComponentName(context, StorageProvider.class);
259         context.getPackageManager().setComponentEnabledSetting(name,
260                 enableProvider
261                         ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
262                         : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
263                 PackageManager.DONT_KILL_APP);
264     }
265 
postCategoryNotification(Context context, SharedPreferences prefs)266     private static void postCategoryNotification(Context context, SharedPreferences prefs) {
267         Intent sendIntent = new Intent(context, MainActivity.class);
268 
269         String title = context.getString(R.string.tracing_categories_unavailable);
270         String msg = TextUtils.join(", ", getActiveUnavailableTags(context, prefs));
271         final Notification.Builder builder =
272             new Notification.Builder(context, NOTIFICATION_CHANNEL_OTHER)
273                 .setSmallIcon(R.drawable.bugfood_icon)
274                 .setContentTitle(title)
275                 .setTicker(title)
276                 .setContentText(msg)
277                 .setContentIntent(PendingIntent.getActivity(
278                         context, 0, sendIntent, PendingIntent.FLAG_ONE_SHOT
279                                 | PendingIntent.FLAG_CANCEL_CURRENT
280                                 | PendingIntent.FLAG_IMMUTABLE))
281                 .setAutoCancel(true)
282                 .setLocalOnly(true)
283                 .setColor(context.getColor(
284                         com.android.internal.R.color.system_notification_accent_color));
285 
286         if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
287             builder.extend(new Notification.TvExtender());
288         }
289 
290         context.getSystemService(NotificationManager.class)
291             .notify(Receiver.class.getName(), 0, builder.build());
292     }
293 
createNotificationChannels(Context context)294     private static void createNotificationChannels(Context context) {
295         NotificationChannel tracingChannel = new NotificationChannel(
296             NOTIFICATION_CHANNEL_TRACING,
297             context.getString(R.string.trace_is_being_recorded),
298             NotificationManager.IMPORTANCE_HIGH);
299         tracingChannel.setBypassDnd(true);
300         tracingChannel.enableVibration(true);
301         tracingChannel.setSound(null, null);
302 
303         NotificationChannel saveTraceChannel = new NotificationChannel(
304             NOTIFICATION_CHANNEL_OTHER,
305             context.getString(R.string.saving_trace),
306             NotificationManager.IMPORTANCE_HIGH);
307         saveTraceChannel.setBypassDnd(true);
308         saveTraceChannel.enableVibration(true);
309         saveTraceChannel.setSound(null, null);
310 
311         NotificationManager notificationManager =
312             context.getSystemService(NotificationManager.class);
313         notificationManager.createNotificationChannel(tracingChannel);
314         notificationManager.createNotificationChannel(saveTraceChannel);
315     }
316 
getActiveTags(Context context, SharedPreferences prefs, boolean onlyAvailable)317     public static Set<String> getActiveTags(Context context, SharedPreferences prefs, boolean onlyAvailable) {
318         Set<String> tags = prefs.getStringSet(context.getString(R.string.pref_key_tags),
319                 getDefaultTagList());
320         Set<String> available = TraceUtils.listCategories().keySet();
321 
322         if (onlyAvailable) {
323             tags.retainAll(available);
324         }
325 
326         Log.v(TAG, "getActiveTags(onlyAvailable=" + onlyAvailable + ") = \"" + tags.toString() + "\"");
327         return tags;
328     }
329 
getActiveUnavailableTags(Context context, SharedPreferences prefs)330     public static Set<String> getActiveUnavailableTags(Context context, SharedPreferences prefs) {
331         Set<String> tags = prefs.getStringSet(context.getString(R.string.pref_key_tags),
332                 getDefaultTagList());
333         Set<String> available = TraceUtils.listCategories().keySet();
334 
335         tags.removeAll(available);
336 
337         Log.v(TAG, "getActiveUnavailableTags() = \"" + tags.toString() + "\"");
338         return tags;
339     }
340 
getDefaultTagList()341     public static Set<String> getDefaultTagList() {
342         if (mDefaultTagList == null) {
343             mDefaultTagList = new ArraySet<String>(Build.TYPE.equals("user")
344                 ? TRACE_TAGS_USER : TRACE_TAGS);
345         }
346 
347         return mDefaultTagList;
348     }
349 }
350