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