• 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, /* fromBootIntent */ true);
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             // Only one of tracing or stack sampling should be enabled, but because they use the
101             // same path for stopping and saving, set both to false.
102             prefs.edit().putBoolean(
103                     context.getString(R.string.pref_key_tracing_on), false).commit();
104             prefs.edit().putBoolean(
105                     context.getString(R.string.pref_key_stack_sampling_on), false).commit();
106             updateTracing(context);
107         } else if (OPEN_ACTION.equals(intent.getAction())) {
108             context.closeSystemDialogs();
109             context.startActivity(new Intent(context, MainActivity.class)
110                     .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
111         } else if (BUGREPORT_STARTED.equals(intent.getAction())) {
112             // If stop_on_bugreport is set and attach_to_bugreport is not, stop tracing.
113             // Otherwise, if attach_to_bugreport is set perfetto will end the session,
114             // and we should not take action on the Traceur side.
115             if (prefs.getBoolean(context.getString(R.string.pref_key_stop_on_bugreport), false) &&
116                 !prefs.getBoolean(context.getString(
117                         R.string.pref_key_attach_to_bugreport), true)) {
118                 Log.d(TAG, "Bugreport started, ending trace.");
119                 prefs.edit().putBoolean(context.getString(R.string.pref_key_tracing_on), false).commit();
120                 updateTracing(context);
121             }
122         }
123     }
124 
125     /*
126      * Updates the current tracing state based on the current state of preferences.
127      */
updateTracing(Context context)128     public static void updateTracing(Context context) {
129         updateTracing(context, false);
130     }
131 
updateTracing(Context context, boolean assumeTracingIsOff)132     public static void updateTracing(Context context, boolean assumeTracingIsOff) {
133         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
134         boolean prefsTracingOn =
135                 prefs.getBoolean(context.getString(R.string.pref_key_tracing_on), false);
136         boolean prefsStackSamplingOn =
137                 prefs.getBoolean(context.getString(R.string.pref_key_stack_sampling_on), false);
138 
139         // This should never happen because enabling one toggle should disable the other. Just in
140         // case, set both preferences to false and stop any ongoing trace.
141         if (prefsTracingOn && prefsStackSamplingOn) {
142             Log.e(TAG, "Preference state thinks that both trace configs should be active; " +
143                     "disabling both and stopping the ongoing trace if one exists.");
144             prefs.edit().putBoolean(
145                     context.getString(R.string.pref_key_tracing_on), false).commit();
146             prefs.edit().putBoolean(
147                     context.getString(R.string.pref_key_stack_sampling_on), false).commit();
148             if (TraceUtils.isTracingOn()) {
149                 TraceService.stopTracing(context);
150             }
151             context.sendBroadcast(new Intent(MainFragment.ACTION_REFRESH_TAGS));
152             QsService.updateTile();
153             return;
154         }
155 
156         boolean traceUtilsTracingOn = assumeTracingIsOff ? false : TraceUtils.isTracingOn();
157 
158         if ((prefsTracingOn || prefsStackSamplingOn) != traceUtilsTracingOn) {
159             if (prefsStackSamplingOn) {
160                 TraceService.startStackSampling(context);
161             } else if (prefsTracingOn) {
162                 // Show notification if the tags in preferences are not all actually available.
163                 Set<String> activeAvailableTags = getActiveTags(context, prefs, true);
164                 Set<String> activeTags = getActiveTags(context, prefs, false);
165 
166                 if (!activeAvailableTags.equals(activeTags)) {
167                     postCategoryNotification(context, prefs);
168                 }
169 
170                 int bufferSize = Integer.parseInt(
171                     prefs.getString(context.getString(R.string.pref_key_buffer_size),
172                         context.getString(R.string.default_buffer_size)));
173 
174                 boolean appTracing = prefs.getBoolean(context.getString(R.string.pref_key_apps), true);
175                 boolean longTrace = prefs.getBoolean(context.getString(R.string.pref_key_long_traces), true);
176 
177                 int maxLongTraceSize = Integer.parseInt(
178                     prefs.getString(context.getString(R.string.pref_key_max_long_trace_size),
179                         context.getString(R.string.default_long_trace_size)));
180 
181                 int maxLongTraceDuration = Integer.parseInt(
182                     prefs.getString(context.getString(R.string.pref_key_max_long_trace_duration),
183                         context.getString(R.string.default_long_trace_duration)));
184 
185                 TraceService.startTracing(context, activeAvailableTags, bufferSize,
186                     appTracing, longTrace, maxLongTraceSize, maxLongTraceDuration);
187             } else {
188                 TraceService.stopTracing(context);
189             }
190         }
191 
192         // Update the main UI and the QS tile.
193         context.sendBroadcast(new Intent(MainFragment.ACTION_REFRESH_TAGS));
194         QsService.updateTile();
195     }
196 
197     /*
198      * Updates the current Quick Settings tile state based on the current state
199      * of preferences.
200      */
updateQuickSettings(Context context)201     public static void updateQuickSettings(Context context) {
202         boolean quickSettingsEnabled =
203             PreferenceManager.getDefaultSharedPreferences(context)
204               .getBoolean(context.getString(R.string.pref_key_quick_setting), false);
205 
206         ComponentName name = new ComponentName(context, QsService.class);
207         context.getPackageManager().setComponentEnabledSetting(name,
208             quickSettingsEnabled
209                 ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
210                 : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
211             PackageManager.DONT_KILL_APP);
212 
213         IStatusBarService statusBarService = IStatusBarService.Stub.asInterface(
214             ServiceManager.checkService(Context.STATUS_BAR_SERVICE));
215 
216         try {
217             if (statusBarService != null) {
218                 if (quickSettingsEnabled) {
219                     statusBarService.addTile(name);
220                 } else {
221                     statusBarService.remTile(name);
222                 }
223             }
224         } catch (RemoteException e) {
225             Log.e(TAG, "Failed to modify QS tile for Traceur.", e);
226         }
227 
228         QsService.updateTile();
229     }
230 
231     /*
232      * When Developer Options are toggled, also toggle the Storage Provider that
233      * shows "System traces" in Files.
234      * When Developer Options are turned off, reset the Show Quick Settings Tile
235      * preference to false to hide the tile. The user will need to re-enable the
236      * preference if they decide to turn Developer Options back on again.
237      */
updateDeveloperOptionsWatcher(Context context, boolean fromBootIntent)238     static void updateDeveloperOptionsWatcher(Context context, boolean fromBootIntent) {
239         if (mDeveloperOptionsObserver == null) {
240             Uri settingUri = Settings.Global.getUriFor(
241                 Settings.Global.DEVELOPMENT_SETTINGS_ENABLED);
242 
243             mDeveloperOptionsObserver =
244                 new ContentObserver(new Handler()) {
245                     @Override
246                     public void onChange(boolean selfChange) {
247                         super.onChange(selfChange);
248 
249                         boolean developerOptionsEnabled = (1 ==
250                             Settings.Global.getInt(context.getContentResolver(),
251                                 Settings.Global.DEVELOPMENT_SETTINGS_ENABLED , 0));
252                         UserManager userManager = context.getSystemService(UserManager.class);
253                         boolean isAdminUser = userManager.isAdminUser();
254                         boolean debuggingDisallowed = userManager.hasUserRestriction(
255                                 UserManager.DISALLOW_DEBUGGING_FEATURES);
256                         updateStorageProvider(context,
257                                 developerOptionsEnabled && isAdminUser && !debuggingDisallowed);
258 
259                         if (!developerOptionsEnabled) {
260                             SharedPreferences prefs =
261                                 PreferenceManager.getDefaultSharedPreferences(context);
262                             prefs.edit().putBoolean(
263                                 context.getString(R.string.pref_key_quick_setting), false)
264                                 .commit();
265                             updateQuickSettings(context);
266                             // Stop an ongoing trace if one exists.
267                             if (TraceUtils.isTracingOn()) {
268                                 TraceService.stopTracingWithoutSaving(context);
269                             }
270                         }
271                     }
272                 };
273 
274             context.getContentResolver().registerContentObserver(settingUri,
275                 false, mDeveloperOptionsObserver);
276             // If this observer is being created and registered on boot, it can be assumed that
277             // developer options did not change in the meantime.
278             if (!fromBootIntent) {
279                 mDeveloperOptionsObserver.onChange(true);
280             }
281         }
282     }
283 
284     // Enables/disables the System Traces storage component. enableProvider should be true iff
285     // developer options are enabled and the current user is an admin user.
updateStorageProvider(Context context, boolean enableProvider)286     static void updateStorageProvider(Context context, boolean enableProvider) {
287         ComponentName name = new ComponentName(context, StorageProvider.class);
288         context.getPackageManager().setComponentEnabledSetting(name,
289                 enableProvider
290                         ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
291                         : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
292                 PackageManager.DONT_KILL_APP);
293     }
294 
postCategoryNotification(Context context, SharedPreferences prefs)295     private static void postCategoryNotification(Context context, SharedPreferences prefs) {
296         Intent sendIntent = new Intent(context, MainActivity.class);
297 
298         String title = context.getString(R.string.tracing_categories_unavailable);
299         String msg = TextUtils.join(", ", getActiveUnavailableTags(context, prefs));
300         final Notification.Builder builder =
301             new Notification.Builder(context, NOTIFICATION_CHANNEL_OTHER)
302                 .setSmallIcon(R.drawable.bugfood_icon)
303                 .setContentTitle(title)
304                 .setTicker(title)
305                 .setContentText(msg)
306                 .setContentIntent(PendingIntent.getActivity(
307                         context, 0, sendIntent, PendingIntent.FLAG_ONE_SHOT
308                                 | PendingIntent.FLAG_CANCEL_CURRENT
309                                 | PendingIntent.FLAG_IMMUTABLE))
310                 .setAutoCancel(true)
311                 .setLocalOnly(true)
312                 .setColor(context.getColor(
313                         com.android.internal.R.color.system_notification_accent_color));
314 
315         if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
316             builder.extend(new Notification.TvExtender());
317         }
318 
319         context.getSystemService(NotificationManager.class)
320             .notify(Receiver.class.getName(), 0, builder.build());
321     }
322 
createNotificationChannels(Context context)323     private static void createNotificationChannels(Context context) {
324         NotificationChannel tracingChannel = new NotificationChannel(
325             NOTIFICATION_CHANNEL_TRACING,
326             context.getString(R.string.trace_is_being_recorded),
327             NotificationManager.IMPORTANCE_HIGH);
328         tracingChannel.setBypassDnd(true);
329         tracingChannel.enableVibration(true);
330         tracingChannel.setSound(null, null);
331         tracingChannel.setBlockable(true);
332 
333         NotificationChannel saveTraceChannel = new NotificationChannel(
334             NOTIFICATION_CHANNEL_OTHER,
335             context.getString(R.string.saving_trace),
336             NotificationManager.IMPORTANCE_HIGH);
337         saveTraceChannel.setBypassDnd(true);
338         saveTraceChannel.enableVibration(true);
339         saveTraceChannel.setSound(null, null);
340         saveTraceChannel.setBlockable(true);
341 
342         NotificationManager notificationManager =
343             context.getSystemService(NotificationManager.class);
344         notificationManager.createNotificationChannel(tracingChannel);
345         notificationManager.createNotificationChannel(saveTraceChannel);
346     }
347 
getActiveTags(Context context, SharedPreferences prefs, boolean onlyAvailable)348     public static Set<String> getActiveTags(Context context, SharedPreferences prefs, boolean onlyAvailable) {
349         Set<String> tags = prefs.getStringSet(context.getString(R.string.pref_key_tags),
350                 getDefaultTagList());
351         Set<String> available = TraceUtils.listCategories().keySet();
352 
353         if (onlyAvailable) {
354             tags.retainAll(available);
355         }
356 
357         Log.v(TAG, "getActiveTags(onlyAvailable=" + onlyAvailable + ") = \"" + tags.toString() + "\"");
358         return tags;
359     }
360 
getActiveUnavailableTags(Context context, SharedPreferences prefs)361     public static Set<String> getActiveUnavailableTags(Context context, SharedPreferences prefs) {
362         Set<String> tags = prefs.getStringSet(context.getString(R.string.pref_key_tags),
363                 getDefaultTagList());
364         Set<String> available = TraceUtils.listCategories().keySet();
365 
366         tags.removeAll(available);
367 
368         Log.v(TAG, "getActiveUnavailableTags() = \"" + tags.toString() + "\"");
369         return tags;
370     }
371 
getDefaultTagList()372     public static Set<String> getDefaultTagList() {
373         if (mDefaultTagList == null) {
374             mDefaultTagList = new ArraySet<String>(Build.TYPE.equals("user")
375                 ? TRACE_TAGS_USER : TRACE_TAGS);
376         }
377 
378         return mDefaultTagList;
379     }
380 }
381