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 com.google.android.collect.Sets; 20 21 import android.app.Notification; 22 import android.app.NotificationChannel; 23 import android.app.NotificationManager; 24 import android.app.PendingIntent; 25 import android.content.BroadcastReceiver; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.SharedPreferences; 30 import android.content.pm.PackageManager; 31 import android.database.ContentObserver; 32 import android.net.Uri; 33 import android.os.Build; 34 import android.os.Handler; 35 import android.os.RemoteException; 36 import android.os.ServiceManager; 37 import android.preference.PreferenceManager; 38 import android.provider.Settings; 39 import android.text.TextUtils; 40 import android.util.Log; 41 42 import com.android.internal.statusbar.IStatusBarService; 43 44 import java.util.Set; 45 import java.util.TreeMap; 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 = "system-tracing"; 53 54 private static final Set<String> ATRACE_TAGS = Sets.newArraySet( 55 "am", "binder_driver", "camera", "dalvik", "freq", "gfx", "hal", 56 "idle", "input", "irq", "res", "sched", "sync", "view", "wm", 57 "workq"); 58 59 /* The user list doesn't include workq, irq, or sync, because the user builds don't have 60 * permissions for them. */ 61 private static final Set<String> ATRACE_TAGS_USER = Sets.newArraySet( 62 "am", "binder_driver", "camera", "dalvik", "freq", "gfx", "hal", 63 "idle", "input", "res", "sched", "view", "wm"); 64 65 private static final String TAG = "Traceur"; 66 67 private static ContentObserver mDeveloperOptionsObserver; 68 69 @Override onReceive(Context context, Intent intent)70 public void onReceive(Context context, Intent intent) { 71 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 72 73 if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { 74 createNotificationChannel(context); 75 updateDeveloperOptionsWatcher(context, 76 prefs.getBoolean(context.getString(R.string.pref_key_quick_setting), false)); 77 updateTracing(context); 78 } else if (STOP_ACTION.equals(intent.getAction())) { 79 prefs.edit().putBoolean(context.getString(R.string.pref_key_tracing_on), false).apply(); 80 updateTracing(context); 81 } else if (OPEN_ACTION.equals(intent.getAction())) { 82 context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); 83 context.startActivity(new Intent(context, MainActivity.class) 84 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); 85 } 86 } 87 88 /* 89 * Updates the current tracing state based on the current state of preferences. 90 */ updateTracing(Context context)91 public static void updateTracing(Context context) { 92 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 93 boolean prefsTracingOn = 94 prefs.getBoolean(context.getString(R.string.pref_key_tracing_on), false); 95 96 if (prefsTracingOn != AtraceUtils.isTracingOn()) { 97 if (prefsTracingOn) { 98 // Show notification if the tags in preferences are not all actually available. 99 String activeAvailableTags = getActiveTags(context, prefs, true); 100 String activeTags = getActiveTags(context, prefs, false); 101 if (!TextUtils.equals(activeAvailableTags, activeTags)) { 102 postCategoryNotification(context, prefs); 103 } 104 105 int bufferSize = Integer.parseInt( 106 prefs.getString(context.getString(R.string.pref_key_buffer_size), 107 context.getString(R.string.default_buffer_size))); 108 109 boolean appTracing = prefs.getBoolean(context.getString(R.string.pref_key_apps), true); 110 111 AtraceService.startTracing(context, activeAvailableTags, bufferSize, appTracing); 112 } else { 113 AtraceService.stopTracing(context); 114 } 115 } 116 117 // Update the main UI and the QS tile. 118 context.sendBroadcast(new Intent(MainFragment.ACTION_REFRESH_TAGS)); 119 QsService.updateTile(); 120 } 121 122 /* 123 * Updates the current Quick Settings tile state based on the current state 124 * of preferences. 125 */ updateQuickSettings(Context context)126 public static void updateQuickSettings(Context context) { 127 boolean quickSettingsEnabled = 128 PreferenceManager.getDefaultSharedPreferences(context) 129 .getBoolean(context.getString(R.string.pref_key_quick_setting), false); 130 131 ComponentName name = new ComponentName(context, QsService.class); 132 context.getPackageManager().setComponentEnabledSetting(name, 133 quickSettingsEnabled 134 ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED 135 : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 136 PackageManager.DONT_KILL_APP); 137 138 IStatusBarService statusBarService = IStatusBarService.Stub.asInterface( 139 ServiceManager.checkService(Context.STATUS_BAR_SERVICE)); 140 141 try { 142 if (statusBarService != null) { 143 if (quickSettingsEnabled) { 144 statusBarService.addTile(name); 145 } else { 146 statusBarService.remTile(name); 147 } 148 } 149 } catch (RemoteException e) { 150 Log.e(TAG, "Failed to modify QS tile for Traceur.", e); 151 } 152 153 QsService.updateTile(); 154 155 updateDeveloperOptionsWatcher(context, quickSettingsEnabled); 156 } 157 158 /* 159 * When Developer Options are turned off, reset the Show Quick Settings Tile 160 * preference to false to hide the tile. The user will need to re-enable the 161 * preference if they decide to turn Developer Options back on again. 162 */ updateDeveloperOptionsWatcher(Context context, boolean quickSettingsEnabled)163 private static void updateDeveloperOptionsWatcher(Context context, 164 boolean quickSettingsEnabled) { 165 166 Uri settingUri = Settings.Global.getUriFor( 167 Settings.Global.DEVELOPMENT_SETTINGS_ENABLED); 168 169 if (quickSettingsEnabled) { 170 mDeveloperOptionsObserver = 171 new ContentObserver(new Handler()) { 172 @Override 173 public void onChange(boolean selfChange) { 174 super.onChange(selfChange); 175 176 boolean developerOptionsEnabled = (1 == 177 Settings.Global.getInt(context.getContentResolver(), 178 Settings.Global.DEVELOPMENT_SETTINGS_ENABLED , 0)); 179 180 if (!developerOptionsEnabled) { 181 SharedPreferences prefs = 182 PreferenceManager.getDefaultSharedPreferences(context); 183 prefs.edit().putBoolean( 184 context.getString(R.string.pref_key_quick_setting), false) 185 .apply(); 186 updateQuickSettings(context); 187 } 188 } 189 }; 190 191 context.getContentResolver().registerContentObserver(settingUri, 192 false, mDeveloperOptionsObserver); 193 194 } else if (mDeveloperOptionsObserver != null) { 195 context.getContentResolver().unregisterContentObserver( 196 mDeveloperOptionsObserver); 197 mDeveloperOptionsObserver = null; 198 } 199 } 200 postCategoryNotification(Context context, SharedPreferences prefs)201 private static void postCategoryNotification(Context context, SharedPreferences prefs) { 202 Intent sendIntent = new Intent(context, MainActivity.class); 203 204 String title = context.getString(R.string.tracing_categories_unavailable); 205 String msg = getActiveUnavailableTags(context, prefs); 206 final Notification.Builder builder = 207 new Notification.Builder(context, NOTIFICATION_CHANNEL) 208 .setSmallIcon(R.drawable.stat_sys_adb) 209 .setContentTitle(title) 210 .setTicker(title) 211 .setContentText(msg) 212 .setContentIntent(PendingIntent.getActivity( 213 context, 0, sendIntent, PendingIntent.FLAG_ONE_SHOT 214 | PendingIntent.FLAG_CANCEL_CURRENT)) 215 .setAutoCancel(true) 216 .setLocalOnly(true) 217 .setColor(context.getColor( 218 com.android.internal.R.color.system_notification_accent_color)); 219 220 context.getSystemService(NotificationManager.class) 221 .notify(Receiver.class.getName(), 0, builder.build()); 222 } 223 createNotificationChannel(Context context)224 private static void createNotificationChannel(Context context) { 225 NotificationChannel channel = new NotificationChannel( 226 NOTIFICATION_CHANNEL, context.getString(R.string.system_tracing), 227 NotificationManager.IMPORTANCE_HIGH); 228 channel.setBypassDnd(true); 229 channel.enableVibration(true); 230 channel.setSound(null, null); 231 232 NotificationManager notificationManager = 233 context.getSystemService(NotificationManager.class); 234 notificationManager.createNotificationChannel(channel); 235 } 236 getActiveTags(Context context, SharedPreferences prefs, boolean onlyAvailable)237 public static String getActiveTags(Context context, SharedPreferences prefs, boolean onlyAvailable) { 238 Set<String> tags = prefs.getStringSet(context.getString(R.string.pref_key_tags), 239 getDefaultTagList()); 240 StringBuilder sb = new StringBuilder(10 * tags.size()); 241 TreeMap<String, String> available = 242 onlyAvailable ? AtraceUtils.atraceListCategories() : null; 243 244 for (String s : tags) { 245 if (onlyAvailable && !available.containsKey(s)) continue; 246 if (sb.length() > 0) { 247 sb.append(' '); 248 } 249 sb.append(s); 250 } 251 String s = sb.toString(); 252 Log.v(TAG, "getActiveTags(onlyAvailable=" + onlyAvailable + ") = \"" + s + "\""); 253 return s; 254 } 255 getActiveUnavailableTags(Context context, SharedPreferences prefs)256 public static String getActiveUnavailableTags(Context context, SharedPreferences prefs) { 257 Set<String> tags = prefs.getStringSet(context.getString(R.string.pref_key_tags), 258 getDefaultTagList()); 259 StringBuilder sb = new StringBuilder(10 * tags.size()); 260 TreeMap<String, String> available = AtraceUtils.atraceListCategories(); 261 262 for (String s : tags) { 263 if (available.containsKey(s)) continue; 264 if (sb.length() > 0) { 265 sb.append(' '); 266 } 267 sb.append(s); 268 } 269 String s = sb.toString(); 270 Log.v(TAG, "getActiveUnavailableTags() = \"" + s + "\""); 271 return s; 272 } 273 getDefaultTagList()274 public static Set<String> getDefaultTagList() { 275 return Build.TYPE.equals("user") ? ATRACE_TAGS_USER : ATRACE_TAGS; 276 } 277 } 278