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