• 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.IntentFilter;
28 import android.content.SharedPreferences;
29 import android.content.pm.PackageManager;
30 import android.database.ContentObserver;
31 import android.net.Uri;
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.Log;
40 
41 import com.android.internal.statusbar.IStatusBarService;
42 
43 import java.util.Set;
44 
45 public class Receiver extends BroadcastReceiver {
46 
47     public static final String STOP_ACTION = "com.android.traceur.STOP";
48     public static final String OPEN_ACTION = "com.android.traceur.OPEN";
49     public static final String BUGREPORT_STARTED =
50             "com.android.internal.intent.action.BUGREPORT_STARTED";
51 
52     public static final String NOTIFICATION_CHANNEL_TRACING = "trace-is-being-recorded";
53     public static final String NOTIFICATION_CHANNEL_OTHER = "system-tracing";
54 
55     private static final String TAG = "Traceur";
56 
57     private static final String BETTERBUG_PACKAGE_NAME =
58             "com.google.android.apps.internal.betterbug";
59 
60     private static ContentObserver mDeveloperOptionsObserver;
61 
62     @Override
onReceive(Context context, Intent intent)63     public void onReceive(Context context, Intent intent) {
64         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
65 
66         if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
67             Log.i(TAG, "Received BOOT_COMPLETE");
68             // USER_FOREGROUND and USER_BACKGROUND can only be received by explicitly registered
69             // receivers; manifest-declared receivers are not sufficient.
70             registerUserSwitchReceiver(context, this);
71             createNotificationChannels(context);
72             updateDeveloperOptionsWatcher(context, /* fromBootIntent */ true);
73             // We know that Perfetto won't be tracing already at boot, so pass the
74             // tracingIsOff argument to avoid the Perfetto check.
75             updateTracing(context, /* assumeTracingIsOff= */ true);
76             TraceUtils.cleanupOlderFiles();
77         } else if (Intent.ACTION_USER_FOREGROUND.equals(intent.getAction())) {
78             boolean traceurAllowed = isTraceurAllowed(context);
79             updateStorageProvider(context, traceurAllowed);
80             if (!traceurAllowed) {
81                 // We don't need to check for ongoing traces to stop because if
82                 // ACTION_USER_FOREGROUND is received, there should be no ongoing traces.
83                 removeQuickSettingsTiles(context);
84             }
85         } else if (Intent.ACTION_USER_BACKGROUND.equals(intent.getAction()) ||
86                 STOP_ACTION.equals(intent.getAction())) {
87             // Only one of these should be enabled, but they all use the same path for stopping and
88             // saving, so set them all to false.
89             prefs.edit().putBoolean(
90                     context.getString(R.string.pref_key_tracing_on), false).commit();
91             prefs.edit().putBoolean(
92                     context.getString(R.string.pref_key_stack_sampling_on), false).commit();
93             prefs.edit().putBoolean(
94                     context.getString(R.string.pref_key_heap_dump_on), false).commit();
95             updateTracing(context);
96         } else if (OPEN_ACTION.equals(intent.getAction())) {
97             context.closeSystemDialogs();
98             context.startActivity(new Intent(context, MainActivity.class)
99                     .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
100         } else if (BUGREPORT_STARTED.equals(intent.getAction())) {
101             // If stop_on_bugreport is set and attach_to_bugreport is not, stop tracing.
102             // Otherwise, if attach_to_bugreport is set perfetto will end the session,
103             // and we should not take action on the Traceur side.
104             if (prefs.getBoolean(context.getString(R.string.pref_key_stop_on_bugreport), false) &&
105                 !prefs.getBoolean(context.getString(
106                         R.string.pref_key_attach_to_bugreport), true)) {
107                 Log.d(TAG, "Bugreport started, ending trace.");
108                 prefs.edit().putBoolean(context.getString(R.string.pref_key_tracing_on), false).commit();
109                 updateTracing(context);
110             }
111         }
112     }
113 
114     /*
115      * Updates the current tracing state based on the current state of preferences.
116      */
updateTracing(Context context)117     public static void updateTracing(Context context) {
118         updateTracing(context, false);
119     }
120 
updateTracing(Context context, boolean assumeTracingIsOff)121     public static void updateTracing(Context context, boolean assumeTracingIsOff) {
122         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
123         boolean prefsTracingOn =
124                 prefs.getBoolean(context.getString(R.string.pref_key_tracing_on), false);
125         boolean prefsStackSamplingOn =
126                 prefs.getBoolean(context.getString(R.string.pref_key_stack_sampling_on), false);
127         boolean prefsHeapDumpOn =
128                 prefs.getBoolean(context.getString(R.string.pref_key_heap_dump_on), false);
129 
130         // This checks that at most one of the three tracing types are enabled. This shouldn't
131         // happen because enabling one toggle should disable the others. Just in case, set all
132         // preferences to false and stop any ongoing trace.
133         if ((prefsTracingOn ^ prefsStackSamplingOn) ? prefsHeapDumpOn : prefsTracingOn) {
134             Log.e(TAG, "Preference state thinks that multiple trace configs should be active; " +
135                     "disabling all of them and stopping the ongoing trace if one exists.");
136             prefs.edit().putBoolean(
137                     context.getString(R.string.pref_key_tracing_on), false).commit();
138             prefs.edit().putBoolean(
139                     context.getString(R.string.pref_key_stack_sampling_on), false).commit();
140             prefs.edit().putBoolean(
141                     context.getString(R.string.pref_key_heap_dump_on), false).commit();
142             if (TraceUtils.isTracingOn()) {
143                 TraceService.stopTracing(context);
144             }
145             context.sendBroadcast(new Intent(MainFragment.ACTION_REFRESH_TAGS));
146             TraceService.updateAllQuickSettingsTiles();
147             return;
148         }
149 
150         boolean traceUtilsTracingOn = assumeTracingIsOff ? false : TraceUtils.isTracingOn();
151 
152         if ((prefsTracingOn || prefsStackSamplingOn || prefsHeapDumpOn) != traceUtilsTracingOn) {
153             if (prefsStackSamplingOn) {
154                 TraceService.startStackSampling(context);
155             } else if (prefsHeapDumpOn) {
156                 TraceService.startHeapDump(context);
157             } else if (prefsTracingOn) {
158                 // Show notification if the tags in preferences are not all actually available.
159                 Set<String> activeAvailableTags = getActiveTags(context, prefs, true);
160                 Set<String> activeTags = getActiveTags(context, prefs, false);
161 
162                 if (!activeAvailableTags.equals(activeTags)) {
163                     postCategoryNotification(context, prefs);
164                 }
165 
166                 int bufferSize = Integer.parseInt(
167                     prefs.getString(context.getString(R.string.pref_key_buffer_size),
168                         context.getString(R.string.default_buffer_size)));
169 
170                 boolean winscopeTracing = prefs.getBoolean(
171                     context.getString(R.string.pref_key_winscope),
172                         false);
173                 boolean appTracing = prefs.getBoolean(context.getString(R.string.pref_key_apps), true);
174                 boolean longTrace = prefs.getBoolean(context.getString(R.string.pref_key_long_traces), true);
175 
176                 int maxLongTraceSize = Integer.parseInt(
177                     prefs.getString(context.getString(R.string.pref_key_max_long_trace_size),
178                         context.getString(R.string.default_long_trace_size)));
179 
180                 int maxLongTraceDuration = Integer.parseInt(
181                     prefs.getString(context.getString(R.string.pref_key_max_long_trace_duration),
182                         context.getString(R.string.default_long_trace_duration)));
183 
184                 TraceService.startTracing(context, activeAvailableTags, bufferSize, winscopeTracing,
185                     appTracing, longTrace, maxLongTraceSize, maxLongTraceDuration);
186             } else {
187                 TraceService.stopTracing(context);
188             }
189         }
190 
191         // Update the main UI and the QS tile.
192         context.sendBroadcast(new Intent(MainFragment.ACTION_REFRESH_TAGS));
193         TraceService.updateAllQuickSettingsTiles();
194     }
195 
196     /*
197      * Updates the input Quick Settings tile state based on the current state of preferences.
198      */
updateQuickSettingsPanel(Context context, boolean enabled, Class serviceClass)199     private static void updateQuickSettingsPanel(Context context, boolean enabled,
200             Class serviceClass) {
201         ComponentName name = new ComponentName(context, serviceClass);
202         context.getPackageManager().setComponentEnabledSetting(name,
203             enabled
204                 ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
205                 : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
206             PackageManager.DONT_KILL_APP);
207 
208         IStatusBarService statusBarService = IStatusBarService.Stub.asInterface(
209             ServiceManager.checkService(Context.STATUS_BAR_SERVICE));
210 
211         try {
212             if (statusBarService != null) {
213                 if (enabled) {
214                     statusBarService.addTile(name);
215                 } else {
216                     statusBarService.remTile(name);
217                 }
218             }
219         } catch (RemoteException e) {
220             Log.e(TAG, "Failed to modify QS tile for Traceur.", e);
221         }
222         TraceService.updateAllQuickSettingsTiles();
223     }
224 
updateTracingQuickSettings(Context context)225     public static void updateTracingQuickSettings(Context context) {
226         boolean tracingQsEnabled =
227             PreferenceManager.getDefaultSharedPreferences(context)
228               .getBoolean(context.getString(R.string.pref_key_tracing_quick_setting), false);
229         updateQuickSettingsPanel(context, tracingQsEnabled, TracingQsService.class);
230     }
231 
updateStackSamplingQuickSettings(Context context)232     public static void updateStackSamplingQuickSettings(Context context) {
233         boolean stackSamplingQsEnabled =
234             PreferenceManager.getDefaultSharedPreferences(context)
235               .getBoolean(context.getString(R.string.pref_key_stack_sampling_quick_setting), false);
236         updateQuickSettingsPanel(context, stackSamplingQsEnabled, StackSamplingQsService.class);
237     }
238 
removeQuickSettingsTiles(Context context)239     private static void removeQuickSettingsTiles(Context context) {
240         SharedPreferences prefs =
241             PreferenceManager.getDefaultSharedPreferences(context);
242         prefs.edit().putBoolean(
243             context.getString(R.string.pref_key_tracing_quick_setting), false)
244             .commit();
245         prefs.edit().putBoolean(
246             context.getString(
247                 R.string.pref_key_stack_sampling_quick_setting), false)
248             .commit();
249         updateTracingQuickSettings(context);
250         updateStackSamplingQuickSettings(context);
251     }
252 
253     /*
254      * When Developer Options are toggled, also toggle the Storage Provider that
255      * shows "System traces" in Files.
256      * When Developer Options are turned off, reset the Show Quick Settings Tile
257      * preference to false to hide the tile. The user will need to re-enable the
258      * preference if they decide to turn Developer Options back on again.
259      */
updateDeveloperOptionsWatcher(Context context, boolean fromBootIntent)260     static void updateDeveloperOptionsWatcher(Context context, boolean fromBootIntent) {
261         if (mDeveloperOptionsObserver == null) {
262             Uri settingUri = Settings.Global.getUriFor(
263                 Settings.Global.DEVELOPMENT_SETTINGS_ENABLED);
264 
265             mDeveloperOptionsObserver =
266                 new ContentObserver(new Handler()) {
267                     @Override
268                     public void onChange(boolean selfChange) {
269                         super.onChange(selfChange);
270                         boolean traceurAllowed = isTraceurAllowed(context);
271                         updateStorageProvider(context, traceurAllowed);
272                         if (!traceurAllowed) {
273                             removeQuickSettingsTiles(context);
274                             // Stop an ongoing trace if one exists.
275                             if (TraceUtils.isTracingOn()) {
276                                 TraceService.stopTracingWithoutSaving(context);
277                             }
278                         }
279                     }
280                 };
281 
282             context.getContentResolver().registerContentObserver(settingUri,
283                 false, mDeveloperOptionsObserver);
284             // If this observer is being created and registered on boot, it can be assumed that
285             // developer options did not change in the meantime.
286             if (!fromBootIntent) {
287                 mDeveloperOptionsObserver.onChange(true);
288             }
289         }
290     }
291 
292     // Enables/disables the System Traces storage component.
updateStorageProvider(Context context, boolean enableProvider)293     static void updateStorageProvider(Context context, boolean enableProvider) {
294         ComponentName name = new ComponentName(context, StorageProvider.class);
295         context.getPackageManager().setComponentEnabledSetting(name,
296                 enableProvider
297                         ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
298                         : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
299                 PackageManager.DONT_KILL_APP);
300     }
301 
postCategoryNotification(Context context, SharedPreferences prefs)302     private static void postCategoryNotification(Context context, SharedPreferences prefs) {
303         Intent sendIntent = new Intent(context, MainActivity.class);
304 
305         String title = context.getString(R.string.tracing_categories_unavailable);
306         String msg = TextUtils.join(", ", getActiveUnavailableTags(context, prefs));
307         final Notification.Builder builder =
308             new Notification.Builder(context, NOTIFICATION_CHANNEL_OTHER)
309                 .setSmallIcon(R.drawable.bugfood_icon)
310                 .setContentTitle(title)
311                 .setTicker(title)
312                 .setContentText(msg)
313                 .setContentIntent(PendingIntent.getActivity(
314                         context, 0, sendIntent, PendingIntent.FLAG_ONE_SHOT
315                                 | PendingIntent.FLAG_CANCEL_CURRENT
316                                 | PendingIntent.FLAG_IMMUTABLE))
317                 .setAutoCancel(true)
318                 .setLocalOnly(true)
319                 .setColor(context.getColor(
320                         com.android.internal.R.color.system_notification_accent_color));
321 
322         if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
323             builder.extend(new Notification.TvExtender());
324         }
325 
326         context.getSystemService(NotificationManager.class)
327             .notify(Receiver.class.getName(), 0, builder.build());
328     }
329 
createNotificationChannels(Context context)330     private static void createNotificationChannels(Context context) {
331         NotificationChannel tracingChannel = new NotificationChannel(
332             NOTIFICATION_CHANNEL_TRACING,
333             context.getString(R.string.trace_is_being_recorded),
334             NotificationManager.IMPORTANCE_HIGH);
335         tracingChannel.setBypassDnd(true);
336         tracingChannel.enableVibration(true);
337         tracingChannel.setSound(null, null);
338         tracingChannel.setBlockable(true);
339 
340         NotificationChannel saveTraceChannel = new NotificationChannel(
341             NOTIFICATION_CHANNEL_OTHER,
342             context.getString(R.string.saving_trace),
343             NotificationManager.IMPORTANCE_HIGH);
344         saveTraceChannel.setBypassDnd(true);
345         saveTraceChannel.enableVibration(true);
346         saveTraceChannel.setSound(null, null);
347         saveTraceChannel.setBlockable(true);
348 
349         NotificationManager notificationManager =
350             context.getSystemService(NotificationManager.class);
351         notificationManager.createNotificationChannel(tracingChannel);
352         notificationManager.createNotificationChannel(saveTraceChannel);
353     }
354 
registerUserSwitchReceiver(Context context, BroadcastReceiver receiver)355     private static void registerUserSwitchReceiver(Context context, BroadcastReceiver receiver) {
356         IntentFilter filter = new IntentFilter();
357         filter.addAction(Intent.ACTION_USER_FOREGROUND);
358         filter.addAction(Intent.ACTION_USER_BACKGROUND);
359         context.registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED);
360     }
361 
getActiveTags(Context context, SharedPreferences prefs, boolean onlyAvailable)362     public static Set<String> getActiveTags(Context context, SharedPreferences prefs, boolean onlyAvailable) {
363         Set<String> tags = prefs.getStringSet(context.getString(R.string.pref_key_tags),
364                 PresetTraceConfigs.getDefaultConfig().getTags());
365         Set<String> available = TraceUtils.listCategories().keySet();
366 
367         if (onlyAvailable) {
368             tags.retainAll(available);
369         }
370 
371         Log.v(TAG, "getActiveTags(onlyAvailable=" + onlyAvailable + ") = \"" + tags.toString() + "\"");
372         return tags;
373     }
374 
getActiveUnavailableTags(Context context, SharedPreferences prefs)375     public static Set<String> getActiveUnavailableTags(Context context, SharedPreferences prefs) {
376         Set<String> tags = prefs.getStringSet(context.getString(R.string.pref_key_tags),
377                 PresetTraceConfigs.getDefaultConfig().getTags());
378         Set<String> available = TraceUtils.listCategories().keySet();
379 
380         tags.removeAll(available);
381 
382         Log.v(TAG, "getActiveUnavailableTags() = \"" + tags.toString() + "\"");
383         return tags;
384     }
385 
isTraceurAllowed(Context context)386     public static boolean isTraceurAllowed(Context context) {
387         boolean developerOptionsEnabled = Settings.Global.getInt(context.getContentResolver(),
388                 Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
389         UserManager userManager = context.getSystemService(UserManager.class);
390         boolean isAdminUser = userManager.isAdminUser();
391         boolean debuggingDisallowed = userManager.hasUserRestriction(
392                 UserManager.DISALLOW_DEBUGGING_FEATURES);
393 
394         // For Traceur usage to be allowed, developer options must be enabled, the user must be an
395         // admin, and the user must not have debugging features disallowed.
396         return developerOptionsEnabled && isAdminUser && !debuggingDisallowed;
397     }
398 }
399