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