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