• 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 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