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.pm.ApplicationInfo;
28 import android.content.pm.PackageManager;
29 import android.content.SharedPreferences;
30 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
31 import android.net.Uri;
32 import android.os.Build;
33 import android.os.Bundle;
34 import androidx.preference.MultiSelectListPreference;
35 import androidx.preference.ListPreference;
36 import androidx.preference.Preference;
37 import androidx.preference.PreferenceFragment;
38 import androidx.preference.PreferenceManager;
39 import androidx.preference.PreferenceScreen;
40 import androidx.preference.SwitchPreference;
41 import android.view.LayoutInflater;
42 import android.view.View;
43 import android.view.ViewGroup;
44 import android.view.Menu;
45 import android.view.MenuInflater;
46 import android.widget.Toast;
47 
48 import com.android.settingslib.HelpUtils;
49 
50 import java.util.ArrayList;
51 import java.util.Collections;
52 import java.util.Comparator;
53 import java.util.Iterator;
54 import java.util.List;
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 
73     private AlertDialog mAlertDialog;
74     private SharedPreferences mPrefs;
75 
76     private MultiSelectListPreference mTags;
77 
78     private boolean mRefreshing;
79 
80     private BroadcastReceiver mRefreshReceiver;
81 
82     OnSharedPreferenceChangeListener mSharedPreferenceChangeListener =
83         new OnSharedPreferenceChangeListener () {
84               public void onSharedPreferenceChanged(
85                       SharedPreferences sharedPreferences, String key) {
86                   refreshUi();
87               }
88         };
89 
90     @Override
onCreate(@ullable Bundle savedInstanceState)91     public void onCreate(@Nullable Bundle savedInstanceState) {
92         super.onCreate(savedInstanceState);
93 
94         Receiver.updateDeveloperOptionsWatcher(getContext());
95 
96         mPrefs = PreferenceManager.getDefaultSharedPreferences(
97                 getActivity().getApplicationContext());
98 
99         mTracingOn = (SwitchPreference) findPreference(getActivity().getString(R.string.pref_key_tracing_on));
100         mTracingOn.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
101             @Override
102             public boolean onPreferenceClick(Preference preference) {
103               Receiver.updateTracing(getContext());
104               return true;
105             }
106         });
107 
108         mTags = (MultiSelectListPreference) findPreference(getContext().getString(R.string.pref_key_tags));
109         mTags.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
110             @Override
111             public boolean onPreferenceChange(Preference preference, Object newValue) {
112                 if (mRefreshing) {
113                     return true;
114                 }
115                 Set<String> set = (Set<String>) newValue;
116                 TreeMap<String, String> available = TraceUtils.listCategories();
117                 ArrayList<String> clean = new ArrayList<>(set.size());
118 
119                 for (String s : set) {
120                     if (available.containsKey(s)) {
121                         clean.add(s);
122                     }
123                 }
124                 set.clear();
125                 set.addAll(clean);
126                 return true;
127             }
128         });
129 
130         findPreference("restore_default_tags").setOnPreferenceClickListener(
131                 new Preference.OnPreferenceClickListener() {
132                     @Override
133                     public boolean onPreferenceClick(Preference preference) {
134                         refreshUi(/* restoreDefaultTags =*/ true);
135                         Toast.makeText(getContext(),
136                             getContext().getString(R.string.default_categories_restored),
137                                 Toast.LENGTH_SHORT).show();
138                         return true;
139                     }
140                 });
141 
142         findPreference(getString(R.string.pref_key_quick_setting))
143             .setOnPreferenceClickListener(
144                 new Preference.OnPreferenceClickListener() {
145                     @Override
146                     public boolean onPreferenceClick(Preference preference) {
147                         Receiver.updateQuickSettings(getContext());
148                         return true;
149                     }
150                 });
151 
152         findPreference("clear_saved_traces").setOnPreferenceClickListener(
153                 new Preference.OnPreferenceClickListener() {
154                     @Override
155                     public boolean onPreferenceClick(Preference preference) {
156                         new AlertDialog.Builder(getContext())
157                             .setTitle(R.string.clear_saved_traces_question)
158                             .setMessage(R.string.all_traces_will_be_deleted)
159                             .setPositiveButton(R.string.clear,
160                                 new DialogInterface.OnClickListener() {
161                                     public void onClick(DialogInterface dialog, int which) {
162                                         TraceUtils.clearSavedTraces();
163                                     }
164                                 })
165                             .setNegativeButton(android.R.string.no,
166                                 new DialogInterface.OnClickListener() {
167                                     public void onClick(DialogInterface dialog, int which) {
168                                         dialog.dismiss();
169                                     }
170                                 })
171                             .create()
172                             .show();
173                         return true;
174                     }
175                 });
176 
177         findPreference("trace_link_button")
178             .setOnPreferenceClickListener(
179                 new Preference.OnPreferenceClickListener() {
180                     @Override
181                     public boolean onPreferenceClick(Preference preference) {
182                         Intent intent = buildTraceFileViewIntent();
183                         try {
184                             startActivity(intent);
185                         } catch (ActivityNotFoundException e) {
186                             return false;
187                         }
188                         return true;
189                     }
190                 });
191 
192         // This disables "Attach to bugreports" when long traces are enabled. This cannot be done in
193         // main.xml because there are some other settings there that are enabled with long traces.
194         SwitchPreference attachToBugreport = findPreference(
195             getString(R.string.pref_key_attach_to_bugreport));
196         findPreference(getString(R.string.pref_key_long_traces))
197             .setOnPreferenceClickListener(
198                 new Preference.OnPreferenceClickListener() {
199                     @Override
200                     public boolean onPreferenceClick(Preference preference) {
201                         if (((SwitchPreference) preference).isChecked()) {
202                             attachToBugreport.setEnabled(false);
203                         } else {
204                             attachToBugreport.setEnabled(true);
205                         }
206                         return true;
207                     }
208                 });
209 
210         refreshUi();
211 
212         mRefreshReceiver = new BroadcastReceiver() {
213             @Override
214             public void onReceive(Context context, Intent intent) {
215                 refreshUi();
216             }
217         };
218 
219     }
220 
221     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)222     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
223         setHasOptionsMenu(true);
224         return super.onCreateView(inflater, container, savedInstanceState);
225     }
226 
227     @Override
onStart()228     public void onStart() {
229         super.onStart();
230         getPreferenceScreen().getSharedPreferences()
231             .registerOnSharedPreferenceChangeListener(mSharedPreferenceChangeListener);
232         getActivity().registerReceiver(mRefreshReceiver, new IntentFilter(ACTION_REFRESH_TAGS));
233         Receiver.updateTracing(getContext());
234     }
235 
236     @Override
onStop()237     public void onStop() {
238         getPreferenceScreen().getSharedPreferences()
239             .unregisterOnSharedPreferenceChangeListener(mSharedPreferenceChangeListener);
240         getActivity().unregisterReceiver(mRefreshReceiver);
241 
242         if (mAlertDialog != null) {
243             mAlertDialog.cancel();
244             mAlertDialog = null;
245         }
246 
247         super.onStop();
248     }
249 
250     @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)251     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
252         addPreferencesFromResource(R.xml.main);
253     }
254 
255     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)256     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
257         HelpUtils.prepareHelpMenuItem(getActivity(), menu, R.string.help_url,
258             this.getClass().getName());
259     }
260 
buildTraceFileViewIntent()261     private Intent buildTraceFileViewIntent() {
262         Intent intent = new Intent(Intent.ACTION_VIEW);
263         intent.setDataAndType(Uri.parse(STORAGE_URI), ROOT_MIME_TYPE);
264         return intent;
265     }
266 
refreshUi()267     private void refreshUi() {
268         refreshUi(/* restoreDefaultTags =*/ false);
269     }
270 
271     /*
272      * Refresh the preferences UI to make sure it reflects the current state of the preferences and
273      * system.
274      */
refreshUi(boolean restoreDefaultTags)275     private void refreshUi(boolean restoreDefaultTags) {
276         Context context = getContext();
277 
278         // Make sure the Record Trace toggle matches the preference value.
279         mTracingOn.setChecked(mTracingOn.getPreferenceManager().getSharedPreferences().getBoolean(
280                 mTracingOn.getKey(), false));
281 
282         SwitchPreference stopOnReport =
283                 (SwitchPreference) findPreference(getString(R.string.pref_key_stop_on_bugreport));
284         stopOnReport.setChecked(mPrefs.getBoolean(stopOnReport.getKey(), false));
285 
286         // Update category list to match the categories available on the system.
287         Set<Entry<String, String>> availableTags = TraceUtils.listCategories().entrySet();
288         ArrayList<String> entries = new ArrayList<String>(availableTags.size());
289         ArrayList<String> values = new ArrayList<String>(availableTags.size());
290         for (Entry<String, String> entry : availableTags) {
291             entries.add(entry.getKey() + ": " + entry.getValue());
292             values.add(entry.getKey());
293         }
294 
295         mRefreshing = true;
296         try {
297             mTags.setEntries(entries.toArray(new String[0]));
298             mTags.setEntryValues(values.toArray(new String[0]));
299             if (restoreDefaultTags || !mPrefs.contains(context.getString(R.string.pref_key_tags))) {
300                 mTags.setValues(Receiver.getDefaultTagList());
301             }
302         } finally {
303             mRefreshing = false;
304         }
305 
306         // Update subtitles on this screen.
307         Set<String> categories = mTags.getValues();
308         mTags.setSummary(Receiver.getDefaultTagList().equals(categories)
309                          ? context.getString(R.string.default_categories)
310                          : context.getResources().getQuantityString(R.plurals.num_categories_selected,
311                               categories.size(), categories.size()));
312 
313         ListPreference bufferSize = (ListPreference)findPreference(
314                 context.getString(R.string.pref_key_buffer_size));
315         bufferSize.setSummary(bufferSize.getEntry());
316 
317         // If we are not using the Perfetto trace backend,
318         // hide the unsupported preferences.
319         if (TraceUtils.currentTraceEngine().equals(PerfettoUtils.NAME)) {
320             ListPreference maxLongTraceSize = (ListPreference)findPreference(
321                     context.getString(R.string.pref_key_max_long_trace_size));
322             maxLongTraceSize.setSummary(maxLongTraceSize.getEntry());
323 
324             ListPreference maxLongTraceDuration = (ListPreference)findPreference(
325                     context.getString(R.string.pref_key_max_long_trace_duration));
326             maxLongTraceDuration.setSummary(maxLongTraceDuration.getEntry());
327         } else {
328             Preference longTraceCategory = findPreference("long_trace_category");
329             if (longTraceCategory != null) {
330                 getPreferenceScreen().removePreference(longTraceCategory);
331             }
332         }
333 
334         // Check if BetterBug is installed to see if Traceur should display either the toggle for
335         // 'attach_to_bugreport' or 'stop_on_bugreport'.
336         try {
337             context.getPackageManager().getPackageInfo(BETTERBUG_PACKAGE_NAME,
338                     PackageManager.MATCH_SYSTEM_ONLY);
339             findPreference(getString(R.string.pref_key_attach_to_bugreport)).setVisible(true);
340             findPreference(getString(R.string.pref_key_stop_on_bugreport)).setVisible(false);
341             // TODO(b/188898919): Update summary with a warning that long traces are not
342             // automatically attached to bug reports.
343         } catch (PackageManager.NameNotFoundException e) {
344             // attach_to_bugreport must be disabled here because it's true by default.
345             mPrefs.edit().putBoolean(
346                     getString(R.string.pref_key_attach_to_bugreport), false).commit();
347             findPreference(getString(R.string.pref_key_attach_to_bugreport)).setVisible(false);
348             findPreference(getString(R.string.pref_key_stop_on_bugreport)).setVisible(true);
349         }
350 
351         // Check if an activity exists to handle the trace_link_button intent. If not, hide the UI
352         // element
353         PackageManager packageManager = context.getPackageManager();
354         Intent intent = buildTraceFileViewIntent();
355         if (intent.resolveActivity(packageManager) == null) {
356             findPreference("trace_link_button").setVisible(false);
357         }
358     }
359 }
360