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 static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE; 20 21 import android.app.IntentService; 22 import android.app.Notification; 23 import android.app.NotificationManager; 24 import android.app.PendingIntent; 25 import android.app.Service; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.SharedPreferences; 29 import android.content.pm.PackageManager; 30 import android.net.Uri; 31 import android.preference.PreferenceManager; 32 33 import java.io.File; 34 import java.util.ArrayList; 35 import java.util.Collection; 36 import java.util.Collections; 37 import java.util.List; 38 import java.util.Set; 39 import java.util.Optional; 40 41 public class TraceService extends IntentService { 42 // Authority used to share trace files from Traceur to other apps 43 static final String AUTHORITY = "com.android.traceur.files"; 44 /* Indicates Perfetto has stopped tracing due to either the supplied long trace limitations 45 * or limited storage capacity. */ 46 static String INTENT_ACTION_NOTIFY_SESSION_STOPPED = 47 "com.android.traceur.NOTIFY_SESSION_STOPPED"; 48 private static String INTENT_ACTION_STOP_TRACING = "com.android.traceur.STOP_TRACING"; 49 private static String INTENT_ACTION_START_TRACING = "com.android.traceur.START_TRACING"; 50 private static String INTENT_ACTION_START_STACK_SAMPLING = 51 "com.android.traceur.START_STACK_SAMPLING"; 52 private static String INTENT_ACTION_START_HEAP_DUMP = 53 "com.android.traceur.START_HEAP_DUMP"; 54 55 private static String INTENT_EXTRA_TAGS= "tags"; 56 private static String INTENT_EXTRA_BUFFER = "buffer"; 57 private static String INTENT_EXTRA_WINSCOPE = "winscope"; 58 private static String INTENT_EXTRA_APPS = "apps"; 59 private static String INTENT_EXTRA_LONG_TRACE = "long_trace"; 60 private static String INTENT_EXTRA_LONG_TRACE_SIZE = "long_trace_size"; 61 private static String INTENT_EXTRA_LONG_TRACE_DURATION = "long_trace_duration"; 62 63 private static String BETTERBUG_PACKAGE_NAME = "com.google.android.apps.internal.betterbug"; 64 65 private static int TRACE_NOTIFICATION = 1; 66 private static int SAVING_TRACE_NOTIFICATION = 2; 67 startTracing(final Context context, Collection<String> tags, int bufferSizeKb, boolean winscope, boolean apps, boolean longTrace, int maxLongTraceSizeMb, int maxLongTraceDurationMinutes)68 public static void startTracing(final Context context, 69 Collection<String> tags, int bufferSizeKb, boolean winscope, boolean apps, 70 boolean longTrace, int maxLongTraceSizeMb, int maxLongTraceDurationMinutes) { 71 Intent intent = new Intent(context, TraceService.class); 72 intent.setAction(INTENT_ACTION_START_TRACING); 73 intent.putExtra(INTENT_EXTRA_TAGS, new ArrayList(tags)); 74 intent.putExtra(INTENT_EXTRA_BUFFER, bufferSizeKb); 75 intent.putExtra(INTENT_EXTRA_WINSCOPE, winscope); 76 intent.putExtra(INTENT_EXTRA_APPS, apps); 77 intent.putExtra(INTENT_EXTRA_LONG_TRACE, longTrace); 78 intent.putExtra(INTENT_EXTRA_LONG_TRACE_SIZE, maxLongTraceSizeMb); 79 intent.putExtra(INTENT_EXTRA_LONG_TRACE_DURATION, maxLongTraceDurationMinutes); 80 context.startForegroundService(intent); 81 } 82 startStackSampling(final Context context)83 public static void startStackSampling(final Context context) { 84 Intent intent = new Intent(context, TraceService.class); 85 intent.setAction(INTENT_ACTION_START_STACK_SAMPLING); 86 context.startForegroundService(intent); 87 } 88 startHeapDump(final Context context)89 public static void startHeapDump(final Context context) { 90 Intent intent = new Intent(context, TraceService.class); 91 intent.setAction(INTENT_ACTION_START_HEAP_DUMP); 92 context.startForegroundService(intent); 93 } 94 stopTracing(final Context context)95 public static void stopTracing(final Context context) { 96 Intent intent = new Intent(context, TraceService.class); 97 intent.setAction(INTENT_ACTION_STOP_TRACING); 98 context.startForegroundService(intent); 99 } 100 101 // Silently stops a trace without saving it. This is intended to be called when tracing is no 102 // longer allowed, i.e. if developer options are turned off while tracing. The usual method of 103 // stopping a trace via intent, stopTracing(), will not work because intents cannot be received 104 // when developer options are disabled. stopTracingWithoutSaving(final Context context)105 static void stopTracingWithoutSaving(final Context context) { 106 NotificationManager notificationManager = 107 context.getSystemService(NotificationManager.class); 108 notificationManager.cancel(TRACE_NOTIFICATION); 109 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 110 prefs.edit().putBoolean(context.getString( 111 R.string.pref_key_tracing_on), false).commit(); 112 TraceUtils.traceStop(context); 113 } 114 TraceService()115 public TraceService() { 116 this("TraceService"); 117 } 118 TraceService(String name)119 protected TraceService(String name) { 120 super(name); 121 setIntentRedelivery(true); 122 } 123 124 @Override onHandleIntent(Intent intent)125 public void onHandleIntent(Intent intent) { 126 Context context = getApplicationContext(); 127 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 128 if (!Receiver.isTraceurAllowed(context)) { 129 return; 130 } 131 132 TraceUtils.RecordingType type = getRecentTraceType(context); 133 134 if (intent.getAction().equals(INTENT_ACTION_START_TRACING)) { 135 startTracingInternal(intent.getStringArrayListExtra(INTENT_EXTRA_TAGS), 136 intent.getIntExtra(INTENT_EXTRA_BUFFER, 137 Integer.parseInt(context.getString(R.string.default_buffer_size))), 138 intent.getBooleanExtra(INTENT_EXTRA_WINSCOPE, false), 139 intent.getBooleanExtra(INTENT_EXTRA_APPS, false), 140 intent.getBooleanExtra(INTENT_EXTRA_LONG_TRACE, false), 141 intent.getIntExtra(INTENT_EXTRA_LONG_TRACE_SIZE, 142 Integer.parseInt(context.getString(R.string.default_long_trace_size))), 143 intent.getIntExtra(INTENT_EXTRA_LONG_TRACE_DURATION, 144 Integer.parseInt(context.getString(R.string.default_long_trace_duration)))); 145 } else if (intent.getAction().equals(INTENT_ACTION_START_STACK_SAMPLING)) { 146 startStackSamplingInternal(); 147 } else if (intent.getAction().equals(INTENT_ACTION_START_HEAP_DUMP)) { 148 startHeapDumpInternal(); 149 } else if (intent.getAction().equals(INTENT_ACTION_STOP_TRACING) || 150 intent.getAction().equals(INTENT_ACTION_NOTIFY_SESSION_STOPPED)) { 151 stopTracingInternal(TraceUtils.getOutputFilename(type)); 152 } 153 } 154 updateAllQuickSettingsTiles()155 static void updateAllQuickSettingsTiles() { 156 TracingQsService.updateTile(); 157 StackSamplingQsService.updateTile(); 158 } 159 getRecentTraceType(Context context)160 private static TraceUtils.RecordingType getRecentTraceType(Context context) { 161 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 162 boolean recordingWasTrace = prefs.getBoolean( 163 context.getString(R.string.pref_key_recording_was_trace), true); 164 boolean recordingWasStackSamples = prefs.getBoolean( 165 context.getString(R.string.pref_key_recording_was_stack_samples), true); 166 if (recordingWasTrace) { 167 return TraceUtils.RecordingType.TRACE; 168 } else if (recordingWasStackSamples) { 169 return TraceUtils.RecordingType.STACK_SAMPLES; 170 } else { 171 return TraceUtils.RecordingType.HEAP_DUMP; 172 } 173 } 174 startTracingInternal(Collection<String> tags, int bufferSizeKb, boolean winscopeTracing, boolean appTracing, boolean longTrace, int maxLongTraceSizeMb, int maxLongTraceDurationMinutes)175 private void startTracingInternal(Collection<String> tags, int bufferSizeKb, 176 boolean winscopeTracing, boolean appTracing, boolean longTrace, int maxLongTraceSizeMb, 177 int maxLongTraceDurationMinutes) { 178 Context context = getApplicationContext(); 179 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 180 181 Intent stopIntent = new Intent(Receiver.STOP_ACTION, 182 null, context, Receiver.class); 183 stopIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 184 185 boolean attachToBugreport = 186 prefs.getBoolean(context.getString(R.string.pref_key_attach_to_bugreport), true); 187 188 Notification.Builder notification = getTraceurNotification( 189 context.getString(R.string.trace_is_being_recorded), 190 context.getString(R.string.tap_to_stop_tracing), 191 Receiver.NOTIFICATION_CHANNEL_TRACING); 192 notification.setOngoing(true) 193 .setContentIntent(PendingIntent.getBroadcast(context, 0, stopIntent, 194 PendingIntent.FLAG_IMMUTABLE)); 195 196 startForeground(TRACE_NOTIFICATION, notification.build(), 197 FOREGROUND_SERVICE_TYPE_SPECIAL_USE); 198 199 if (TraceUtils.traceStart(this, tags, bufferSizeKb, winscopeTracing, 200 appTracing, longTrace, attachToBugreport, maxLongTraceSizeMb, 201 maxLongTraceDurationMinutes)) { 202 stopForeground(Service.STOP_FOREGROUND_DETACH); 203 } else { 204 // Starting the trace was unsuccessful, so ensure that tracing 205 // is stopped and the preference is reset. 206 TraceUtils.traceStop(this); 207 prefs.edit().putBoolean(context.getString(R.string.pref_key_tracing_on), 208 false).commit(); 209 updateAllQuickSettingsTiles(); 210 stopForeground(Service.STOP_FOREGROUND_REMOVE); 211 } 212 213 // This is used to keep track of whether the most recent recording was a trace for the 214 // purpose of 1) determining which notification should be sent after the recording is done, 215 // and 2) choosing the filename format for the saved recording. 216 prefs.edit().putBoolean( 217 context.getString(R.string.pref_key_recording_was_trace), true).commit(); 218 prefs.edit().putBoolean( 219 context.getString(R.string.pref_key_recording_was_stack_samples), false).commit(); 220 } 221 startStackSamplingInternal()222 private void startStackSamplingInternal() { 223 Context context = getApplicationContext(); 224 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 225 226 Intent stopIntent = new Intent(Receiver.STOP_ACTION, null, context, Receiver.class); 227 stopIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 228 229 boolean attachToBugreport = 230 prefs.getBoolean(context.getString(R.string.pref_key_attach_to_bugreport), true); 231 232 Notification.Builder notification = getTraceurNotification( 233 context.getString(R.string.stack_samples_are_being_recorded), 234 context.getString(R.string.tap_to_stop_stack_sampling), 235 Receiver.NOTIFICATION_CHANNEL_TRACING); 236 notification.setOngoing(true) 237 .setContentIntent(PendingIntent.getBroadcast(context, 0, stopIntent, 238 PendingIntent.FLAG_IMMUTABLE)); 239 240 startForeground(TRACE_NOTIFICATION, notification.build(), 241 FOREGROUND_SERVICE_TYPE_SPECIAL_USE); 242 243 if (TraceUtils.stackSampleStart(attachToBugreport)) { 244 stopForeground(Service.STOP_FOREGROUND_DETACH); 245 } else { 246 // Starting stack sampling was unsuccessful, so ensure that it is stopped and the 247 // preference is reset. 248 TraceUtils.traceStop(this); 249 prefs.edit().putBoolean( 250 context.getString(R.string.pref_key_stack_sampling_on), false).commit(); 251 updateAllQuickSettingsTiles(); 252 stopForeground(Service.STOP_FOREGROUND_REMOVE); 253 } 254 255 // This is used to keep track of whether the most recent recording was a trace for the 256 // purpose of 1) determining which notification should be sent after the recording is done, 257 // and 2) choosing the filename format for the saved recording. 258 prefs.edit().putBoolean( 259 context.getString(R.string.pref_key_recording_was_trace), false).commit(); 260 prefs.edit().putBoolean( 261 context.getString(R.string.pref_key_recording_was_stack_samples), true).commit(); 262 } 263 startHeapDumpInternal()264 private void startHeapDumpInternal() { 265 Context context = getApplicationContext(); 266 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 267 268 Intent stopIntent = new Intent(Receiver.STOP_ACTION, null, context, Receiver.class); 269 stopIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 270 271 boolean attachToBugreport = 272 prefs.getBoolean(context.getString(R.string.pref_key_attach_to_bugreport), true); 273 boolean continuousDump = 274 prefs.getBoolean(context.getString(R.string.pref_key_continuous_heap_dump), false); 275 Set<String> processes = prefs.getStringSet( 276 context.getString(R.string.pref_key_heap_dump_processes), Collections.emptySet()); 277 278 int dumpIntervalSeconds = Integer.parseInt( 279 prefs.getString(context.getString(R.string.pref_key_continuous_heap_dump_interval), 280 context.getString(R.string.default_continuous_heap_dump_interval))); 281 282 Notification.Builder notification = getTraceurNotification( 283 context.getString(R.string.heap_dump_is_being_recorded), 284 context.getString(R.string.tap_to_stop_heap_dump), 285 Receiver.NOTIFICATION_CHANNEL_TRACING); 286 notification.setOngoing(true) 287 .setContentIntent(PendingIntent.getBroadcast(context, 0, stopIntent, 288 PendingIntent.FLAG_IMMUTABLE)); 289 290 startForeground(TRACE_NOTIFICATION, notification.build(), 291 FOREGROUND_SERVICE_TYPE_SPECIAL_USE); 292 293 if (TraceUtils.heapDumpStart(processes, continuousDump, dumpIntervalSeconds, 294 attachToBugreport)) { 295 stopForeground(Service.STOP_FOREGROUND_DETACH); 296 } else { 297 TraceUtils.traceStop(this); 298 prefs.edit().putBoolean( 299 context.getString(R.string.pref_key_heap_dump_on), false).commit(); 300 updateAllQuickSettingsTiles(); 301 stopForeground(Service.STOP_FOREGROUND_REMOVE); 302 } 303 304 prefs.edit().putBoolean( 305 context.getString(R.string.pref_key_recording_was_trace), false).commit(); 306 prefs.edit().putBoolean( 307 context.getString(R.string.pref_key_recording_was_stack_samples), false).commit(); 308 } 309 stopTracingInternal(String outputFilename)310 private void stopTracingInternal(String outputFilename) { 311 Context context = getApplicationContext(); 312 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 313 NotificationManager notificationManager = 314 getSystemService(NotificationManager.class); 315 316 // This helps determine which text to show on the post-recording notifications. 317 TraceUtils.RecordingType type = getRecentTraceType(context); 318 int savingTextResId; 319 switch (type) { 320 case STACK_SAMPLES: 321 savingTextResId = R.string.saving_stack_samples; 322 break; 323 case HEAP_DUMP: 324 savingTextResId = R.string.saving_heap_dump; 325 break; 326 case TRACE: 327 case UNKNOWN: 328 default: 329 savingTextResId = R.string.saving_trace; 330 break; 331 } 332 Notification.Builder notification = getTraceurNotification(context.getString( 333 savingTextResId), null, Receiver.NOTIFICATION_CHANNEL_OTHER); 334 notification.setProgress(1, 0, true); 335 336 startForeground(SAVING_TRACE_NOTIFICATION, notification.build(), 337 FOREGROUND_SERVICE_TYPE_SPECIAL_USE); 338 339 notificationManager.cancel(TRACE_NOTIFICATION); 340 341 Optional<List<File>> files = TraceUtils.traceDump(this, outputFilename); 342 if (files.isPresent()) { 343 postFileSharingNotification(getApplicationContext(), files.get()); 344 } 345 346 notificationManager.cancel(SAVING_TRACE_NOTIFICATION); 347 stopForeground(Service.STOP_FOREGROUND_REMOVE); 348 349 TraceUtils.cleanupOlderFiles(); 350 } 351 postFileSharingNotification(Context context, List<File> files)352 private void postFileSharingNotification(Context context, List<File> files) { 353 if (files.isEmpty()) { 354 return; 355 } 356 357 // Files are kept on private storage, so turn into Uris that we can 358 // grant temporary permissions for. 359 final List<Uri> traceUris = FileSender.getUriForFiles(context, files, AUTHORITY); 360 361 // Intent to send the file 362 Intent sendIntent = FileSender.buildSendIntent(context, traceUris); 363 sendIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 364 365 // This dialog will show to warn the user about sharing traces, then will execute 366 // the above file-sharing intent. 367 final Intent intent = new Intent(context, UserConsentActivityDialog.class); 368 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_RECEIVER_FOREGROUND); 369 intent.putExtra(Intent.EXTRA_INTENT, Intent.createChooser(sendIntent, null)); 370 371 TraceUtils.RecordingType type = getRecentTraceType(context); 372 int titleResId; 373 switch (type) { 374 case STACK_SAMPLES: 375 titleResId = R.string.stack_samples_saved; 376 break; 377 case HEAP_DUMP: 378 titleResId = R.string.heap_dump_saved; 379 break; 380 case TRACE: 381 case UNKNOWN: 382 default: 383 titleResId = R.string.trace_saved; 384 break; 385 } 386 final Notification.Builder builder = getTraceurNotification(context.getString(titleResId), 387 context.getString(R.string.tap_to_share), Receiver.NOTIFICATION_CHANNEL_OTHER) 388 .setContentIntent(PendingIntent.getActivity( 389 context, traceUris.get(0).hashCode(), intent, 390 PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE)) 391 .setAutoCancel(true); 392 NotificationManager.from(context).notify(files.get(0).getName(), 0, builder.build()); 393 } 394 395 // Creates a Traceur notification for the given channel using the provided title and message. getTraceurNotification(String title, String msg, String channel)396 private Notification.Builder getTraceurNotification(String title, String msg, String channel) { 397 Context context = getApplicationContext(); 398 Notification.Builder notification = new Notification.Builder(context, channel) 399 .setContentTitle(title) 400 .setTicker(title) 401 .setSmallIcon(R.drawable.bugfood_icon) 402 .setLocalOnly(true) 403 .setForegroundServiceBehavior(Notification.FOREGROUND_SERVICE_IMMEDIATE) 404 .setColor(context.getColor( 405 com.android.internal.R.color.system_notification_accent_color)); 406 407 // Some Traceur notifications only have a title. 408 if (msg != null) { 409 notification.setContentText(msg); 410 } 411 412 if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) { 413 notification.extend(new Notification.TvExtender()); 414 } 415 416 return notification; 417 } 418 } 419