• 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 
20 import android.app.IntentService;
21 import android.app.Notification;
22 import android.app.NotificationManager;
23 import android.app.PendingIntent;
24 import android.app.Service;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.SharedPreferences;
28 import android.content.pm.PackageManager;
29 import android.os.UserManager;
30 import android.preference.PreferenceManager;
31 import android.provider.Settings;
32 import android.text.format.DateUtils;
33 import android.util.EventLog;
34 import android.util.Log;
35 
36 import java.io.File;
37 import java.util.ArrayList;
38 import java.util.Collection;
39 
40 public class TraceService extends IntentService {
41     /* Indicates Perfetto has stopped tracing due to either the supplied long trace limitations
42      * or limited storage capacity. */
43     static String INTENT_ACTION_NOTIFY_SESSION_STOPPED =
44             "com.android.traceur.NOTIFY_SESSION_STOPPED";
45     /* Indicates a Traceur-associated tracing session has been attached to a bug report */
46     static String INTENT_ACTION_NOTIFY_SESSION_STOLEN =
47             "com.android.traceur.NOTIFY_SESSION_STOLEN";
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 
51     private static String INTENT_EXTRA_TAGS= "tags";
52     private static String INTENT_EXTRA_BUFFER = "buffer";
53     private static String INTENT_EXTRA_APPS = "apps";
54     private static String INTENT_EXTRA_LONG_TRACE = "long_trace";
55     private static String INTENT_EXTRA_LONG_TRACE_SIZE = "long_trace_size";
56     private static String INTENT_EXTRA_LONG_TRACE_DURATION = "long_trace_duration";
57 
58     private static String BETTERBUG_PACKAGE_NAME = "com.google.android.apps.internal.betterbug";
59 
60     private static int TRACE_NOTIFICATION = 1;
61     private static int SAVING_TRACE_NOTIFICATION = 2;
62 
63     private static final int MIN_KEEP_COUNT = 3;
64     private static final long MIN_KEEP_AGE = 4 * DateUtils.WEEK_IN_MILLIS;
65 
startTracing(final Context context, Collection<String> tags, int bufferSizeKb, boolean apps, boolean longTrace, int maxLongTraceSizeMb, int maxLongTraceDurationMinutes)66     public static void startTracing(final Context context,
67             Collection<String> tags, int bufferSizeKb, boolean apps,
68             boolean longTrace, int maxLongTraceSizeMb, int maxLongTraceDurationMinutes) {
69         Intent intent = new Intent(context, TraceService.class);
70         intent.setAction(INTENT_ACTION_START_TRACING);
71         intent.putExtra(INTENT_EXTRA_TAGS, new ArrayList(tags));
72         intent.putExtra(INTENT_EXTRA_BUFFER, bufferSizeKb);
73         intent.putExtra(INTENT_EXTRA_APPS, apps);
74         intent.putExtra(INTENT_EXTRA_LONG_TRACE, longTrace);
75         intent.putExtra(INTENT_EXTRA_LONG_TRACE_SIZE, maxLongTraceSizeMb);
76         intent.putExtra(INTENT_EXTRA_LONG_TRACE_DURATION, maxLongTraceDurationMinutes);
77         context.startForegroundService(intent);
78     }
79 
stopTracing(final Context context)80     public static void stopTracing(final Context context) {
81         Intent intent = new Intent(context, TraceService.class);
82         intent.setAction(INTENT_ACTION_STOP_TRACING);
83         context.startForegroundService(intent);
84     }
85 
86     // Silently stops a trace without saving it. This is intended to be called when tracing is no
87     // longer allowed, i.e. if developer options are turned off while tracing. The usual method of
88     // stopping a trace via intent, stopTracing(), will not work because intents cannot be received
89     // when developer options are disabled.
stopTracingWithoutSaving(final Context context)90     static void stopTracingWithoutSaving(final Context context) {
91         NotificationManager notificationManager =
92             context.getSystemService(NotificationManager.class);
93         notificationManager.cancel(TRACE_NOTIFICATION);
94         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
95         prefs.edit().putBoolean(context.getString(
96             R.string.pref_key_tracing_on), false).commit();
97         TraceUtils.traceStop();
98     }
99 
TraceService()100     public TraceService() {
101         this("TraceService");
102     }
103 
TraceService(String name)104     protected TraceService(String name) {
105         super(name);
106         setIntentRedelivery(true);
107     }
108 
109     @Override
onHandleIntent(Intent intent)110     public void onHandleIntent(Intent intent) {
111         Context context = getApplicationContext();
112         // Checks that developer options are enabled and the user is an admin before continuing.
113         boolean developerOptionsEnabled =
114                 Settings.Global.getInt(context.getContentResolver(),
115                         Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
116         if (!developerOptionsEnabled) {
117             // Refer to b/204992293.
118             EventLog.writeEvent(0x534e4554, "204992293", -1, "");
119             return;
120         }
121         UserManager userManager = context.getSystemService(UserManager.class);
122         boolean isAdminUser = userManager.isAdminUser();
123         boolean debuggingDisallowed = userManager.hasUserRestriction(
124                 UserManager.DISALLOW_DEBUGGING_FEATURES);
125         if (!isAdminUser || debuggingDisallowed) {
126             return;
127         }
128 
129         if (intent.getAction().equals(INTENT_ACTION_START_TRACING)) {
130             startTracingInternal(intent.getStringArrayListExtra(INTENT_EXTRA_TAGS),
131                 intent.getIntExtra(INTENT_EXTRA_BUFFER,
132                     Integer.parseInt(context.getString(R.string.default_buffer_size))),
133                 intent.getBooleanExtra(INTENT_EXTRA_APPS, false),
134                 intent.getBooleanExtra(INTENT_EXTRA_LONG_TRACE, false),
135                 intent.getIntExtra(INTENT_EXTRA_LONG_TRACE_SIZE,
136                     Integer.parseInt(context.getString(R.string.default_long_trace_size))),
137                 intent.getIntExtra(INTENT_EXTRA_LONG_TRACE_DURATION,
138                     Integer.parseInt(context.getString(R.string.default_long_trace_duration))));
139         } else if (intent.getAction().equals(INTENT_ACTION_STOP_TRACING)) {
140             stopTracingInternal(TraceUtils.getOutputFilename(), false, false);
141         } else if (intent.getAction().equals(INTENT_ACTION_NOTIFY_SESSION_STOPPED)) {
142             stopTracingInternal(TraceUtils.getOutputFilename(), true, false);
143         } else if (intent.getAction().equals(INTENT_ACTION_NOTIFY_SESSION_STOLEN)) {
144             stopTracingInternal("", false, true);
145         }
146     }
147 
startTracingInternal(Collection<String> tags, int bufferSizeKb, boolean appTracing, boolean longTrace, int maxLongTraceSizeMb, int maxLongTraceDurationMinutes)148     private void startTracingInternal(Collection<String> tags, int bufferSizeKb, boolean appTracing,
149             boolean longTrace, int maxLongTraceSizeMb, int maxLongTraceDurationMinutes) {
150         Context context = getApplicationContext();
151         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
152         Intent stopIntent = new Intent(Receiver.STOP_ACTION,
153             null, context, Receiver.class);
154         stopIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
155 
156         String title = context.getString(R.string.trace_is_being_recorded);
157         String msg = context.getString(R.string.tap_to_stop_tracing);
158 
159         boolean attachToBugreport =
160                 prefs.getBoolean(context.getString(R.string.pref_key_attach_to_bugreport), true);
161 
162         Notification.Builder notification =
163             new Notification.Builder(context, Receiver.NOTIFICATION_CHANNEL_TRACING)
164                 .setSmallIcon(R.drawable.bugfood_icon)
165                 .setContentTitle(title)
166                 .setTicker(title)
167                 .setContentText(msg)
168                 .setContentIntent(
169                     PendingIntent.getBroadcast(context, 0, stopIntent, PendingIntent.FLAG_IMMUTABLE))
170                 .setOngoing(true)
171                 .setLocalOnly(true)
172                 .setForegroundServiceBehavior(Notification.FOREGROUND_SERVICE_IMMEDIATE)
173                 .setColor(getColor(
174                     com.android.internal.R.color.system_notification_accent_color));
175 
176         if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
177             notification.extend(new Notification.TvExtender());
178         }
179 
180         startForeground(TRACE_NOTIFICATION, notification.build());
181 
182         if (TraceUtils.traceStart(tags, bufferSizeKb, appTracing,
183                 longTrace, attachToBugreport, maxLongTraceSizeMb, maxLongTraceDurationMinutes)) {
184             stopForeground(Service.STOP_FOREGROUND_DETACH);
185         } else {
186             // Starting the trace was unsuccessful, so ensure that tracing
187             // is stopped and the preference is reset.
188             TraceUtils.traceStop();
189             prefs.edit().putBoolean(context.getString(R.string.pref_key_tracing_on),
190                         false).commit();
191             QsService.updateTile();
192             stopForeground(Service.STOP_FOREGROUND_REMOVE);
193         }
194     }
195 
stopTracingInternal(String outputFilename, boolean forceStop, boolean sessionStolen)196     private void stopTracingInternal(String outputFilename, boolean forceStop,
197             boolean sessionStolen) {
198         Context context = getApplicationContext();
199         NotificationManager notificationManager =
200             getSystemService(NotificationManager.class);
201 
202         Notification.Builder notification;
203         if (sessionStolen) {
204             notification = getBaseTraceurNotification()
205                 .setContentTitle(getString(R.string.attaching_to_report))
206                 .setTicker(getString(R.string.attaching_to_report))
207                 .setProgress(1, 0, true);
208         } else {
209             notification = getBaseTraceurNotification()
210                 .setContentTitle(getString(R.string.saving_trace))
211                 .setTicker(getString(R.string.saving_trace))
212                 .setProgress(1, 0, true);
213         }
214 
215         startForeground(SAVING_TRACE_NOTIFICATION, notification.build());
216 
217         notificationManager.cancel(TRACE_NOTIFICATION);
218 
219         if (sessionStolen) {
220             Notification.Builder notificationAttached = getBaseTraceurNotification()
221                 .setContentTitle(getString(R.string.attached_to_report))
222                 .setTicker(getString(R.string.attached_to_report))
223                 .setAutoCancel(true);
224 
225             Intent openIntent =
226                     getPackageManager().getLaunchIntentForPackage(BETTERBUG_PACKAGE_NAME);
227             if (openIntent != null) {
228                 // Add "Tap to open BetterBug" to notification only if intent is non-null.
229                 notificationAttached.setContentText(getString(
230                         R.string.attached_to_report_summary));
231                 notificationAttached.setContentIntent(PendingIntent.getActivity(
232                         context, 0, openIntent, PendingIntent.FLAG_ONE_SHOT
233                                 | PendingIntent.FLAG_CANCEL_CURRENT
234                                 | PendingIntent.FLAG_IMMUTABLE));
235             }
236 
237             // Adds an action button to the notification for starting a new trace.
238             Intent restartIntent = new Intent(context, InternalReceiver.class);
239             restartIntent.setAction(InternalReceiver.START_ACTION);
240             PendingIntent restartPendingIntent = PendingIntent.getBroadcast(context, 0,
241                     restartIntent, PendingIntent.FLAG_ONE_SHOT
242                             | PendingIntent.FLAG_CANCEL_CURRENT
243                             | PendingIntent.FLAG_IMMUTABLE);
244             Notification.Action action = new Notification.Action.Builder(
245                     R.drawable.bugfood_icon, context.getString(R.string.start_new_trace),
246                     restartPendingIntent).build();
247             notificationAttached.addAction(action);
248 
249             NotificationManager.from(context).notify(0, notificationAttached.build());
250         } else {
251             File file = TraceUtils.getOutputFile(outputFilename);
252 
253             if (TraceUtils.traceDump(file)) {
254                 FileSender.postNotification(getApplicationContext(), file);
255             }
256         }
257 
258         stopForeground(Service.STOP_FOREGROUND_REMOVE);
259 
260         TraceUtils.cleanupOlderFiles(MIN_KEEP_COUNT, MIN_KEEP_AGE);
261     }
262 
getBaseTraceurNotification()263     private Notification.Builder getBaseTraceurNotification() {
264         Context context = getApplicationContext();
265         Notification.Builder notification =
266                 new Notification.Builder(this, Receiver.NOTIFICATION_CHANNEL_OTHER)
267                     .setSmallIcon(R.drawable.bugfood_icon)
268                     .setLocalOnly(true)
269                     .setForegroundServiceBehavior(Notification.FOREGROUND_SERVICE_IMMEDIATE)
270                     .setColor(context.getColor(
271                             com.android.internal.R.color.system_notification_accent_color));
272 
273         if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
274             notification.extend(new Notification.TvExtender());
275         }
276 
277         return notification;
278     }
279 }
280