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