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 android.app.Notification; 20 import android.app.NotificationChannel; 21 import android.app.NotificationManager; 22 import android.app.PendingIntent; 23 import android.content.BroadcastReceiver; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.SharedPreferences; 28 import android.content.pm.PackageManager; 29 import android.database.ContentObserver; 30 import android.net.Uri; 31 import android.os.Build; 32 import android.os.Handler; 33 import android.os.RemoteException; 34 import android.os.ServiceManager; 35 import android.preference.PreferenceManager; 36 import android.provider.Settings; 37 import android.text.TextUtils; 38 import android.util.ArraySet; 39 import android.util.Log; 40 41 import com.android.internal.statusbar.IStatusBarService; 42 43 import java.util.Arrays; 44 import java.util.List; 45 import java.util.Set; 46 47 public class Receiver extends BroadcastReceiver { 48 49 public static final String STOP_ACTION = "com.android.traceur.STOP"; 50 public static final String OPEN_ACTION = "com.android.traceur.OPEN"; 51 public static final String BUGREPORT_STARTED = 52 "com.android.internal.intent.action.BUGREPORT_STARTED"; 53 54 public static final String NOTIFICATION_CHANNEL_TRACING = "trace-is-being-recorded"; 55 public static final String NOTIFICATION_CHANNEL_OTHER = "system-tracing"; 56 57 private static final List<String> TRACE_TAGS = Arrays.asList( 58 "am", "binder_driver", "camera", "dalvik", "freq", "gfx", "hal", 59 "idle", "input", "memory", "memreclaim", "power", "res", "sched", 60 "sync", "thermal", "view", "webview", "wm", "workq"); 61 62 /* The user list doesn't include workq or sync, because the user builds don't have 63 * permissions for them. */ 64 private static final List<String> TRACE_TAGS_USER = Arrays.asList( 65 "am", "binder_driver", "camera", "dalvik", "freq", "gfx", "hal", 66 "idle", "input", "memory", "memreclaim", "power", "res", "sched", 67 "thermal", "view", "webview", "wm"); 68 69 private static final String TAG = "Traceur"; 70 71 private static final String BETTERBUG_PACKAGE_NAME = 72 "com.google.android.apps.internal.betterbug"; 73 74 private static Set<String> mDefaultTagList = null; 75 private static ContentObserver mDeveloperOptionsObserver; 76 77 @Override onReceive(Context context, Intent intent)78 public void onReceive(Context context, Intent intent) { 79 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 80 81 if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { 82 Log.i(TAG, "Received BOOT_COMPLETE"); 83 createNotificationChannels(context); 84 updateDeveloperOptionsWatcher(context); 85 // We know that Perfetto won't be tracing already at boot, so pass the 86 // tracingIsOff argument to avoid the Perfetto check. 87 updateTracing(context, /* assumeTracingIsOff= */ true); 88 } else if (STOP_ACTION.equals(intent.getAction())) { 89 prefs.edit().putBoolean( 90 context.getString(R.string.pref_key_tracing_on), false).commit(); 91 updateTracing(context); 92 } else if (OPEN_ACTION.equals(intent.getAction())) { 93 context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); 94 context.startActivity(new Intent(context, MainActivity.class) 95 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); 96 } else if (BUGREPORT_STARTED.equals(intent.getAction())) { 97 // If stop_on_bugreport is set and attach_to_bugreport is not, stop tracing. 98 // Otherwise, if attach_to_bugreport is set perfetto will end the session, 99 // and we should not take action on the Traceur side. 100 if (prefs.getBoolean(context.getString(R.string.pref_key_stop_on_bugreport), false) && 101 !prefs.getBoolean(context.getString( 102 R.string.pref_key_attach_to_bugreport), true)) { 103 Log.d(TAG, "Bugreport started, ending trace."); 104 prefs.edit().putBoolean(context.getString(R.string.pref_key_tracing_on), false).commit(); 105 updateTracing(context); 106 } 107 } 108 } 109 110 /* 111 * Updates the current tracing state based on the current state of preferences. 112 */ updateTracing(Context context)113 public static void updateTracing(Context context) { 114 updateTracing(context, false); 115 } 116 updateTracing(Context context, boolean assumeTracingIsOff)117 public static void updateTracing(Context context, boolean assumeTracingIsOff) { 118 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 119 boolean prefsTracingOn = 120 prefs.getBoolean(context.getString(R.string.pref_key_tracing_on), false); 121 122 boolean traceUtilsTracingOn = assumeTracingIsOff ? false : TraceUtils.isTracingOn(); 123 124 if (prefsTracingOn != traceUtilsTracingOn) { 125 if (prefsTracingOn) { 126 // Show notification if the tags in preferences are not all actually available. 127 Set<String> activeAvailableTags = getActiveTags(context, prefs, true); 128 Set<String> activeTags = getActiveTags(context, prefs, false); 129 130 if (!activeAvailableTags.equals(activeTags)) { 131 postCategoryNotification(context, prefs); 132 } 133 134 int bufferSize = Integer.parseInt( 135 prefs.getString(context.getString(R.string.pref_key_buffer_size), 136 context.getString(R.string.default_buffer_size))); 137 138 boolean appTracing = prefs.getBoolean(context.getString(R.string.pref_key_apps), true); 139 boolean longTrace = prefs.getBoolean(context.getString(R.string.pref_key_long_traces), true); 140 141 int maxLongTraceSize = Integer.parseInt( 142 prefs.getString(context.getString(R.string.pref_key_max_long_trace_size), 143 context.getString(R.string.default_long_trace_size))); 144 145 int maxLongTraceDuration = Integer.parseInt( 146 prefs.getString(context.getString(R.string.pref_key_max_long_trace_duration), 147 context.getString(R.string.default_long_trace_duration))); 148 149 TraceService.startTracing(context, activeAvailableTags, bufferSize, 150 appTracing, longTrace, maxLongTraceSize, maxLongTraceDuration); 151 } else { 152 TraceService.stopTracing(context); 153 } 154 } 155 156 // Update the main UI and the QS tile. 157 context.sendBroadcast(new Intent(MainFragment.ACTION_REFRESH_TAGS)); 158 QsService.updateTile(); 159 } 160 161 /* 162 * Updates the current Quick Settings tile state based on the current state 163 * of preferences. 164 */ updateQuickSettings(Context context)165 public static void updateQuickSettings(Context context) { 166 boolean quickSettingsEnabled = 167 PreferenceManager.getDefaultSharedPreferences(context) 168 .getBoolean(context.getString(R.string.pref_key_quick_setting), false); 169 170 ComponentName name = new ComponentName(context, QsService.class); 171 context.getPackageManager().setComponentEnabledSetting(name, 172 quickSettingsEnabled 173 ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED 174 : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 175 PackageManager.DONT_KILL_APP); 176 177 IStatusBarService statusBarService = IStatusBarService.Stub.asInterface( 178 ServiceManager.checkService(Context.STATUS_BAR_SERVICE)); 179 180 try { 181 if (statusBarService != null) { 182 if (quickSettingsEnabled) { 183 statusBarService.addTile(name); 184 } else { 185 statusBarService.remTile(name); 186 } 187 } 188 } catch (RemoteException e) { 189 Log.e(TAG, "Failed to modify QS tile for Traceur.", e); 190 } 191 192 QsService.updateTile(); 193 } 194 195 /* 196 * When Developer Options are toggled, also toggle the Storage Provider that 197 * shows "System traces" in Files. 198 * When Developer Options are turned off, reset the Show Quick Settings Tile 199 * preference to false to hide the tile. The user will need to re-enable the 200 * preference if they decide to turn Developer Options back on again. 201 */ updateDeveloperOptionsWatcher(Context context)202 static void updateDeveloperOptionsWatcher(Context context) { 203 if (mDeveloperOptionsObserver == null) { 204 Uri settingUri = Settings.Global.getUriFor( 205 Settings.Global.DEVELOPMENT_SETTINGS_ENABLED); 206 207 mDeveloperOptionsObserver = 208 new ContentObserver(new Handler()) { 209 @Override 210 public void onChange(boolean selfChange) { 211 super.onChange(selfChange); 212 213 boolean developerOptionsEnabled = (1 == 214 Settings.Global.getInt(context.getContentResolver(), 215 Settings.Global.DEVELOPMENT_SETTINGS_ENABLED , 0)); 216 217 ComponentName name = new ComponentName(context, 218 StorageProvider.class); 219 context.getPackageManager().setComponentEnabledSetting(name, 220 developerOptionsEnabled 221 ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED 222 : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 223 PackageManager.DONT_KILL_APP); 224 225 if (!developerOptionsEnabled) { 226 SharedPreferences prefs = 227 PreferenceManager.getDefaultSharedPreferences(context); 228 prefs.edit().putBoolean( 229 context.getString(R.string.pref_key_quick_setting), false) 230 .commit(); 231 updateQuickSettings(context); 232 // Stop an ongoing trace if one exists. 233 if (TraceUtils.isTracingOn()) { 234 TraceService.stopTracingWithoutSaving(context); 235 } 236 } 237 } 238 }; 239 240 context.getContentResolver().registerContentObserver(settingUri, 241 false, mDeveloperOptionsObserver); 242 mDeveloperOptionsObserver.onChange(true); 243 } 244 } 245 postCategoryNotification(Context context, SharedPreferences prefs)246 private static void postCategoryNotification(Context context, SharedPreferences prefs) { 247 Intent sendIntent = new Intent(context, MainActivity.class); 248 249 String title = context.getString(R.string.tracing_categories_unavailable); 250 String msg = TextUtils.join(", ", getActiveUnavailableTags(context, prefs)); 251 final Notification.Builder builder = 252 new Notification.Builder(context, NOTIFICATION_CHANNEL_OTHER) 253 .setSmallIcon(R.drawable.bugfood_icon) 254 .setContentTitle(title) 255 .setTicker(title) 256 .setContentText(msg) 257 .setContentIntent(PendingIntent.getActivity( 258 context, 0, sendIntent, PendingIntent.FLAG_ONE_SHOT 259 | PendingIntent.FLAG_CANCEL_CURRENT 260 | PendingIntent.FLAG_IMMUTABLE)) 261 .setAutoCancel(true) 262 .setLocalOnly(true) 263 .setColor(context.getColor( 264 com.android.internal.R.color.system_notification_accent_color)); 265 266 if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) { 267 builder.extend(new Notification.TvExtender()); 268 } 269 270 context.getSystemService(NotificationManager.class) 271 .notify(Receiver.class.getName(), 0, builder.build()); 272 } 273 createNotificationChannels(Context context)274 private static void createNotificationChannels(Context context) { 275 NotificationChannel tracingChannel = new NotificationChannel( 276 NOTIFICATION_CHANNEL_TRACING, 277 context.getString(R.string.trace_is_being_recorded), 278 NotificationManager.IMPORTANCE_HIGH); 279 tracingChannel.setBypassDnd(true); 280 tracingChannel.enableVibration(true); 281 tracingChannel.setSound(null, null); 282 283 NotificationChannel saveTraceChannel = new NotificationChannel( 284 NOTIFICATION_CHANNEL_OTHER, 285 context.getString(R.string.saving_trace), 286 NotificationManager.IMPORTANCE_HIGH); 287 saveTraceChannel.setBypassDnd(true); 288 saveTraceChannel.enableVibration(true); 289 saveTraceChannel.setSound(null, null); 290 291 NotificationManager notificationManager = 292 context.getSystemService(NotificationManager.class); 293 notificationManager.createNotificationChannel(tracingChannel); 294 notificationManager.createNotificationChannel(saveTraceChannel); 295 } 296 getActiveTags(Context context, SharedPreferences prefs, boolean onlyAvailable)297 public static Set<String> getActiveTags(Context context, SharedPreferences prefs, boolean onlyAvailable) { 298 Set<String> tags = prefs.getStringSet(context.getString(R.string.pref_key_tags), 299 getDefaultTagList()); 300 Set<String> available = TraceUtils.listCategories().keySet(); 301 302 if (onlyAvailable) { 303 tags.retainAll(available); 304 } 305 306 Log.v(TAG, "getActiveTags(onlyAvailable=" + onlyAvailable + ") = \"" + tags.toString() + "\""); 307 return tags; 308 } 309 getActiveUnavailableTags(Context context, SharedPreferences prefs)310 public static Set<String> getActiveUnavailableTags(Context context, SharedPreferences prefs) { 311 Set<String> tags = prefs.getStringSet(context.getString(R.string.pref_key_tags), 312 getDefaultTagList()); 313 Set<String> available = TraceUtils.listCategories().keySet(); 314 315 tags.removeAll(available); 316 317 Log.v(TAG, "getActiveUnavailableTags() = \"" + tags.toString() + "\""); 318 return tags; 319 } 320 getDefaultTagList()321 public static Set<String> getDefaultTagList() { 322 if (mDefaultTagList == null) { 323 mDefaultTagList = new ArraySet<String>(Build.TYPE.equals("user") 324 ? TRACE_TAGS_USER : TRACE_TAGS); 325 } 326 327 return mDefaultTagList; 328 } 329 } 330