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