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