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