• 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.annotation.Nullable;
20 import android.app.AlertDialog;
21 import android.content.ActivityNotFoundException;
22 import android.content.BroadcastReceiver;
23 import android.content.Context;
24 import android.content.DialogInterface;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.content.SharedPreferences;
28 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
29 import android.content.pm.PackageManager;
30 import android.icu.text.MessageFormat;
31 import android.net.Uri;
32 import android.os.Bundle;
33 import android.view.LayoutInflater;
34 import android.view.Menu;
35 import android.view.MenuInflater;
36 import android.view.View;
37 import android.view.ViewGroup;
38 import android.widget.Toast;
39 import androidx.preference.ListPreference;
40 import androidx.preference.MultiSelectListPreference;
41 import androidx.preference.Preference;
42 import androidx.preference.PreferenceFragment;
43 import androidx.preference.PreferenceManager;
44 import androidx.preference.SwitchPreference;
45 
46 import com.android.settingslib.HelpUtils;
47 
48 import java.util.ArrayList;
49 import java.util.Collections;
50 import java.util.HashMap;
51 import java.util.HashSet;
52 import java.util.Locale;
53 import java.util.List;
54 import java.util.Map;
55 import java.util.Map.Entry;
56 import java.util.Set;
57 import java.util.TreeMap;
58 
59 public class MainFragment extends PreferenceFragment {
60 
61     static final String TAG = TraceUtils.TAG;
62 
63     public static final String ACTION_REFRESH_TAGS = "com.android.traceur.REFRESH_TAGS";
64 
65     private static final String BETTERBUG_PACKAGE_NAME =
66             "com.google.android.apps.internal.betterbug";
67 
68     private static final String ROOT_MIME_TYPE = "vnd.android.document/root";
69     private static final String STORAGE_URI = "content://com.android.traceur.documents/root";
70 
71     private SwitchPreference mTracingOn;
72     private SwitchPreference mStackSamplingOn;
73     private SwitchPreference mHeapDumpOn;
74 
75     private AlertDialog mAlertDialog;
76     private SharedPreferences mPrefs;
77 
78     private MultiSelectListPreference mTags;
79     private MultiSelectListPreference mHeapDumpProcesses;
80 
81     private boolean mRefreshing;
82 
83     private BroadcastReceiver mRefreshReceiver;
84 
85     OnSharedPreferenceChangeListener mSharedPreferenceChangeListener =
86         new OnSharedPreferenceChangeListener () {
87               public void onSharedPreferenceChanged(
88                       SharedPreferences sharedPreferences, String key) {
89                   refreshUi();
90               }
91         };
92 
93     @Override
onCreate(@ullable Bundle savedInstanceState)94     public void onCreate(@Nullable Bundle savedInstanceState) {
95         super.onCreate(savedInstanceState);
96 
97         Receiver.updateDeveloperOptionsWatcher(getContext(), /* fromBootIntent */ false);
98 
99         mPrefs = PreferenceManager.getDefaultSharedPreferences(
100                 getActivity().getApplicationContext());
101 
102         mTracingOn = (SwitchPreference) findPreference(
103                 getActivity().getString(R.string.pref_key_tracing_on));
104         mStackSamplingOn = (SwitchPreference) findPreference(
105                 getActivity().getString(R.string.pref_key_stack_sampling_on));
106         mHeapDumpOn = (SwitchPreference) findPreference(
107                 getActivity().getString(R.string.pref_key_heap_dump_on));
108 
109         mTracingOn.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
110             @Override
111             public boolean onPreferenceClick(Preference preference) {
112                 Receiver.updateTracing(getContext());
113                 // Disable the stack sampling and heap dump toggles if the trace toggle is enabled.
114                 mStackSamplingOn.setEnabled(!((SwitchPreference) preference).isChecked());
115                 mHeapDumpOn.setEnabled(!((SwitchPreference) preference).isChecked());
116                 return true;
117             }
118         });
119 
120         mStackSamplingOn.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
121             @Override
122             public boolean onPreferenceClick(Preference preference) {
123                 Receiver.updateTracing(getContext());
124                 // Disable the trace and heap dump toggles if the stack sampling toggle is enabled.
125                 mTracingOn.setEnabled(!((SwitchPreference) preference).isChecked());
126                 mHeapDumpOn.setEnabled(!((SwitchPreference) preference).isChecked());
127                 return true;
128             }
129         });
130 
131         mHeapDumpOn.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
132             @Override
133             public boolean onPreferenceClick(Preference preference) {
134                 Receiver.updateTracing(getContext());
135                 // Disable the trace and stack sampling toggles if the heap dump toggle is enabled.
136                 mTracingOn.setEnabled(!((SwitchPreference) preference).isChecked());
137                 mStackSamplingOn.setEnabled(!((SwitchPreference) preference).isChecked());
138                 return true;
139             }
140         });
141 
142         mHeapDumpProcesses = (MultiSelectListPreference) findPreference(
143                 getContext().getString(R.string.pref_key_heap_dump_processes));
144 
145         mTags = (MultiSelectListPreference) findPreference(getContext().getString(R.string.pref_key_tags));
146         mTags.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
147             @Override
148             public boolean onPreferenceChange(Preference preference, Object newValue) {
149                 if (mRefreshing) {
150                     return true;
151                 }
152                 Set<String> set = (Set<String>) newValue;
153                 TreeMap<String, String> available = TraceUtils.listCategories();
154                 ArrayList<String> clean = new ArrayList<>(set.size());
155 
156                 for (String s : set) {
157                     if (available.containsKey(s)) {
158                         clean.add(s);
159                     }
160                 }
161                 set.clear();
162                 set.addAll(clean);
163                 return true;
164             }
165         });
166 
167         findPreference("restore_default_tags").setOnPreferenceClickListener(
168                 new Preference.OnPreferenceClickListener() {
169                     @Override
170                     public boolean onPreferenceClick(Preference preference) {
171                         refreshUi(/* restoreDefaultTags =*/ true,
172                                 /* clearHeapDumpProcesses =*/ false);
173                         Toast.makeText(getContext(),
174                             getContext().getString(R.string.default_categories_restored),
175                                 Toast.LENGTH_SHORT).show();
176                         return true;
177                     }
178                 });
179 
180         findPreference("clear_heap_dump_processes").setOnPreferenceClickListener(
181                 new Preference.OnPreferenceClickListener() {
182                     @Override
183                     public boolean onPreferenceClick(Preference preference) {
184                         refreshUi(/* restoreDefaultTags =*/ false,
185                                 /* clearHeapDumpProcesses =*/ true);
186                         Toast.makeText(getContext(),
187                             getContext().getString(R.string.clear_heap_dump_processes_toast),
188                                 Toast.LENGTH_SHORT).show();
189                         return true;
190                     }
191                 });
192 
193         findPreference(getString(R.string.pref_key_tracing_quick_setting))
194             .setOnPreferenceClickListener(
195                 new Preference.OnPreferenceClickListener() {
196                     @Override
197                     public boolean onPreferenceClick(Preference preference) {
198                         Receiver.updateTracingQuickSettings(getContext());
199                         return true;
200                     }
201                 });
202 
203         findPreference(getString(R.string.pref_key_stack_sampling_quick_setting))
204             .setOnPreferenceClickListener(
205                 new Preference.OnPreferenceClickListener() {
206                     @Override
207                     public boolean onPreferenceClick(Preference preference) {
208                         Receiver.updateStackSamplingQuickSettings(getContext());
209                         return true;
210                     }
211                 });
212 
213         findPreference("clear_saved_files").setOnPreferenceClickListener(
214                 new Preference.OnPreferenceClickListener() {
215                     @Override
216                     public boolean onPreferenceClick(Preference preference) {
217                         new AlertDialog.Builder(getContext())
218                             .setTitle(R.string.clear_saved_files_question)
219                             .setMessage(R.string.all_recordings_will_be_deleted)
220                             .setPositiveButton(R.string.clear,
221                                 new DialogInterface.OnClickListener() {
222                                     public void onClick(DialogInterface dialog, int which) {
223                                         TraceUtils.clearSavedTraces();
224                                     }
225                                 })
226                             .setNegativeButton(android.R.string.cancel,
227                                 new DialogInterface.OnClickListener() {
228                                     public void onClick(DialogInterface dialog, int which) {
229                                         dialog.dismiss();
230                                     }
231                                 })
232                             .create()
233                             .show();
234                         return true;
235                     }
236                 });
237 
238         findPreference("trace_link_button")
239             .setOnPreferenceClickListener(
240                 new Preference.OnPreferenceClickListener() {
241                     @Override
242                     public boolean onPreferenceClick(Preference preference) {
243                         Intent intent = buildTraceFileViewIntent();
244                         try {
245                             startActivity(intent);
246                         } catch (ActivityNotFoundException e) {
247                             return false;
248                         }
249                         return true;
250                     }
251                 });
252 
253         // This disables "Attach to bugreports" when long traces are enabled. This cannot be done in
254         // main.xml because there are some other settings there that are enabled with long traces.
255         SwitchPreference attachToBugreport = findPreference(
256             getString(R.string.pref_key_attach_to_bugreport));
257         findPreference(getString(R.string.pref_key_long_traces))
258             .setOnPreferenceClickListener(
259                 new Preference.OnPreferenceClickListener() {
260                     @Override
261                     public boolean onPreferenceClick(Preference preference) {
262                         if (((SwitchPreference) preference).isChecked()) {
263                             attachToBugreport.setEnabled(false);
264                         } else {
265                             attachToBugreport.setEnabled(true);
266                         }
267                         return true;
268                     }
269                 });
270 
271         refreshUi();
272 
273         mRefreshReceiver = new BroadcastReceiver() {
274             @Override
275             public void onReceive(Context context, Intent intent) {
276                 refreshUi();
277             }
278         };
279 
280     }
281 
282     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)283     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
284         setHasOptionsMenu(true);
285         return super.onCreateView(inflater, container, savedInstanceState);
286     }
287 
288     @Override
onStart()289     public void onStart() {
290         super.onStart();
291         getPreferenceScreen().getSharedPreferences()
292             .registerOnSharedPreferenceChangeListener(mSharedPreferenceChangeListener);
293         getActivity().registerReceiver(mRefreshReceiver, new IntentFilter(ACTION_REFRESH_TAGS),
294                 Context.RECEIVER_NOT_EXPORTED);
295         TraceUtils.cleanupOlderFiles();
296         Receiver.updateTracing(getContext());
297     }
298 
299     @Override
onStop()300     public void onStop() {
301         getPreferenceScreen().getSharedPreferences()
302             .unregisterOnSharedPreferenceChangeListener(mSharedPreferenceChangeListener);
303         getActivity().unregisterReceiver(mRefreshReceiver);
304 
305         if (mAlertDialog != null) {
306             mAlertDialog.cancel();
307             mAlertDialog = null;
308         }
309 
310         super.onStop();
311     }
312 
313     @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)314     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
315         addPreferencesFromResource(R.xml.main);
316     }
317 
318     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)319     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
320         HelpUtils.prepareHelpMenuItem(getActivity(), menu, R.string.help_url,
321             this.getClass().getName());
322     }
323 
buildTraceFileViewIntent()324     private Intent buildTraceFileViewIntent() {
325         Intent intent = new Intent(Intent.ACTION_VIEW);
326         intent.setDataAndType(Uri.parse(STORAGE_URI), ROOT_MIME_TYPE);
327         return intent;
328     }
329 
refreshUi()330     private void refreshUi() {
331         refreshUi(/* restoreDefaultTags =*/ false, /* clearHeapDumpProcesses =*/ false);
332     }
333 
334     /*
335      * Refresh the preferences UI to make sure it reflects the current state of the preferences and
336      * system.
337      */
refreshUi(boolean restoreDefaultTags, boolean clearHeapDumpProcesses)338     private void refreshUi(boolean restoreDefaultTags, boolean clearHeapDumpProcesses) {
339         Context context = getContext();
340 
341         // Make sure the Record trace, Record CPU profile, and Record heap dump toggles match their
342         // preference values.
343         mTracingOn.setChecked(mPrefs.getBoolean(mTracingOn.getKey(), false));
344         mStackSamplingOn.setChecked(mPrefs.getBoolean(mStackSamplingOn.getKey(), false));
345         mHeapDumpOn.setChecked(mPrefs.getBoolean(mHeapDumpOn.getKey(), false));
346 
347         SwitchPreference stopOnReport =
348                 (SwitchPreference) findPreference(getString(R.string.pref_key_stop_on_bugreport));
349         stopOnReport.setChecked(mPrefs.getBoolean(stopOnReport.getKey(), false));
350 
351         SwitchPreference continuousHeapDump = (SwitchPreference) findPreference(
352                 getString(R.string.pref_key_continuous_heap_dump));
353         continuousHeapDump.setChecked(mPrefs.getBoolean(continuousHeapDump.getKey(), false));
354 
355         // Update category list to match the categories available on the system.
356         Set<Entry<String, String>> availableTags = TraceUtils.listCategories().entrySet();
357         ArrayList<String> entries = new ArrayList<String>(availableTags.size());
358         ArrayList<String> values = new ArrayList<String>(availableTags.size());
359         for (Entry<String, String> entry : availableTags) {
360             entries.add(entry.getKey() + ": " + entry.getValue());
361             values.add(entry.getKey());
362         }
363 
364         // We keep selected processes in the list in case a user is interested in a process that AM
365         // is not yet aware of (e.g. an app that hasn't started up).
366         Set<String> runningProcesses = TraceUtils.getRunningAppProcesses(context);
367         Set<String> selectedProcesses = mHeapDumpProcesses.getValues();
368         runningProcesses.addAll(selectedProcesses);
369 
370         List<String> sortedProcesses = new ArrayList<>(runningProcesses);
371         Collections.sort(sortedProcesses);
372 
373         mRefreshing = true;
374         try {
375             mTags.setEntries(entries.toArray(new String[0]));
376             mTags.setEntryValues(values.toArray(new String[0]));
377             if (restoreDefaultTags || !mPrefs.contains(context.getString(R.string.pref_key_tags))) {
378                 mTags.setValues(PresetTraceConfigs.getDefaultConfig().getTags());
379             }
380             mHeapDumpProcesses.setEntries(sortedProcesses.toArray(new String[0]));
381             mHeapDumpProcesses.setEntryValues(sortedProcesses.toArray(new String[0]));
382             if (clearHeapDumpProcesses ||
383                     !mPrefs.contains(context.getString(R.string.pref_key_heap_dump_processes))) {
384                 mHeapDumpProcesses.setValues(new HashSet<String>());
385             }
386         } finally {
387             mRefreshing = false;
388         }
389 
390         // Enable or disable each toggle based on the state of the others. This path exists in case
391         // the tracing state was updated with the QS tile or the ongoing-trace notification, which
392         // would not call the toggles' OnClickListeners.
393         mTracingOn.setEnabled(!(mStackSamplingOn.isChecked() || mHeapDumpOn.isChecked()));
394         mStackSamplingOn.setEnabled(!(mTracingOn.isChecked() || mHeapDumpOn.isChecked()));
395 
396         // Disallow heap dumps if no process is selected, or if tracing/stack sampling is active.
397         boolean heapDumpProcessSelected = mHeapDumpProcesses.getValues().size() > 0;
398         mHeapDumpOn.setEnabled(heapDumpProcessSelected &&
399                 !(mTracingOn.isChecked() || mStackSamplingOn.isChecked()));
400         mHeapDumpOn.setSummary(heapDumpProcessSelected
401                 ? context.getString(R.string.record_heap_dump_summary_enabled)
402                 : context.getString(R.string.record_heap_dump_summary_disabled));
403 
404         // Update subtitles on this screen.
405         Set<String> categories = mTags.getValues();
406         MessageFormat msgFormat = new MessageFormat(
407                 getResources().getString(R.string.num_categories_selected),
408                 Locale.getDefault());
409         Map<String, Object> arguments = new HashMap<>();
410         arguments.put("count", categories.size());
411         mTags.setSummary(PresetTraceConfigs.getDefaultConfig().getTags().equals(categories)
412                          ? context.getString(R.string.default_categories)
413                          : msgFormat.format(arguments));
414 
415         ListPreference bufferSize = (ListPreference)findPreference(
416                 context.getString(R.string.pref_key_buffer_size));
417         bufferSize.setSummary(bufferSize.getEntry());
418 
419         ListPreference maxLongTraceSize = (ListPreference)findPreference(
420                 context.getString(R.string.pref_key_max_long_trace_size));
421         maxLongTraceSize.setSummary(maxLongTraceSize.getEntry());
422 
423         ListPreference maxLongTraceDuration = (ListPreference)findPreference(
424                 context.getString(R.string.pref_key_max_long_trace_duration));
425         maxLongTraceDuration.setSummary(maxLongTraceDuration.getEntry());
426 
427         ListPreference continuousHeapDumpInterval = (ListPreference)findPreference(
428                 context.getString(R.string.pref_key_continuous_heap_dump_interval));
429         continuousHeapDumpInterval.setSummary(continuousHeapDumpInterval.getEntry());
430 
431         // Check if BetterBug is installed to see if Traceur should display either the toggle for
432         // 'attach_to_bugreport' or 'stop_on_bugreport'.
433         try {
434             context.getPackageManager().getPackageInfo(BETTERBUG_PACKAGE_NAME,
435                     PackageManager.MATCH_SYSTEM_ONLY);
436             findPreference(getString(R.string.pref_key_attach_to_bugreport)).setVisible(true);
437             findPreference(getString(R.string.pref_key_stop_on_bugreport)).setVisible(false);
438             // Changes the long traces summary to add that they cannot be attached to bugreports.
439             findPreference(getString(R.string.pref_key_long_traces))
440                     .setSummary(getString(R.string.long_traces_summary_betterbug));
441         } catch (PackageManager.NameNotFoundException e) {
442             // attach_to_bugreport must be disabled here because it's true by default.
443             mPrefs.edit().putBoolean(
444                     getString(R.string.pref_key_attach_to_bugreport), false).commit();
445             findPreference(getString(R.string.pref_key_attach_to_bugreport)).setVisible(false);
446             findPreference(getString(R.string.pref_key_stop_on_bugreport)).setVisible(true);
447             // Sets long traces summary to the default in case Betterbug was removed.
448             findPreference(getString(R.string.pref_key_long_traces))
449                     .setSummary(getString(R.string.long_traces_summary));
450         }
451 
452         // Check if an activity exists to handle the trace_link_button intent. If not, hide the UI
453         // element
454         PackageManager packageManager = context.getPackageManager();
455         Intent intent = buildTraceFileViewIntent();
456         if (intent.resolveActivity(packageManager) == null) {
457             findPreference("trace_link_button").setVisible(false);
458         }
459     }
460 }
461