blob: 536e7ddae015897d28f705b6c65a86875dc1cbe7 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.traceur;
import android.annotation.Nullable;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager;
import android.icu.text.MessageFormat;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.preference.ListPreference;
import androidx.preference.MultiSelectListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragment;
import androidx.preference.PreferenceManager;
import androidx.preference.SwitchPreference;
import com.android.settingslib.HelpUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
public class MainFragment extends PreferenceFragment {
static final String TAG = TraceUtils.TAG;
public static final String ACTION_REFRESH_TAGS = "com.android.traceur.REFRESH_TAGS";
private static final String BETTERBUG_PACKAGE_NAME =
"com.google.android.apps.internal.betterbug";
private static final String ROOT_MIME_TYPE = "vnd.android.document/root";
private static final String STORAGE_URI = "content://com.android.traceur.documents/root";
private SwitchPreference mTracingOn;
private SwitchPreference mStackSamplingOn;
private SwitchPreference mHeapDumpOn;
private AlertDialog mAlertDialog;
private SharedPreferences mPrefs;
private MultiSelectListPreference mTags;
private MultiSelectListPreference mHeapDumpProcesses;
private boolean mRefreshing;
private BroadcastReceiver mRefreshReceiver;
OnSharedPreferenceChangeListener mSharedPreferenceChangeListener =
new OnSharedPreferenceChangeListener () {
public void onSharedPreferenceChanged(
SharedPreferences sharedPreferences, String key) {
refreshUi();
}
};
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Receiver.updateDeveloperOptionsWatcher(getContext(), /* fromBootIntent */ false);
mPrefs = PreferenceManager.getDefaultSharedPreferences(
getActivity().getApplicationContext());
mTracingOn = (SwitchPreference) findPreference(
getActivity().getString(R.string.pref_key_tracing_on));
mStackSamplingOn = (SwitchPreference) findPreference(
getActivity().getString(R.string.pref_key_stack_sampling_on));
mHeapDumpOn = (SwitchPreference) findPreference(
getActivity().getString(R.string.pref_key_heap_dump_on));
mTracingOn.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
Receiver.updateTracing(getContext());
// Disable the stack sampling and heap dump toggles if the trace toggle is enabled.
mStackSamplingOn.setEnabled(!((SwitchPreference) preference).isChecked());
mHeapDumpOn.setEnabled(!((SwitchPreference) preference).isChecked());
return true;
}
});
mStackSamplingOn.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
Receiver.updateTracing(getContext());
// Disable the trace and heap dump toggles if the stack sampling toggle is enabled.
mTracingOn.setEnabled(!((SwitchPreference) preference).isChecked());
mHeapDumpOn.setEnabled(!((SwitchPreference) preference).isChecked());
return true;
}
});
mHeapDumpOn.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
Receiver.updateTracing(getContext());
// Disable the trace and stack sampling toggles if the heap dump toggle is enabled.
mTracingOn.setEnabled(!((SwitchPreference) preference).isChecked());
mStackSamplingOn.setEnabled(!((SwitchPreference) preference).isChecked());
return true;
}
});
mHeapDumpProcesses = (MultiSelectListPreference) findPreference(
getContext().getString(R.string.pref_key_heap_dump_processes));
mTags = (MultiSelectListPreference) findPreference(getContext().getString(R.string.pref_key_tags));
mTags.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (mRefreshing) {
return true;
}
Set<String> set = (Set<String>) newValue;
TreeMap<String, String> available = TraceUtils.listCategories();
ArrayList<String> clean = new ArrayList<>(set.size());
for (String s : set) {
if (available.containsKey(s)) {
clean.add(s);
}
}
set.clear();
set.addAll(clean);
return true;
}
});
findPreference("restore_default_tags").setOnPreferenceClickListener(
new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
refreshUi(/* restoreDefaultTags =*/ true,
/* clearHeapDumpProcesses =*/ false);
Toast.makeText(getContext(),
getContext().getString(R.string.default_categories_restored),
Toast.LENGTH_SHORT).show();
return true;
}
});
findPreference("clear_heap_dump_processes").setOnPreferenceClickListener(
new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
refreshUi(/* restoreDefaultTags =*/ false,
/* clearHeapDumpProcesses =*/ true);
Toast.makeText(getContext(),
getContext().getString(R.string.clear_heap_dump_processes_toast),
Toast.LENGTH_SHORT).show();
return true;
}
});
findPreference(getString(R.string.pref_key_tracing_quick_setting))
.setOnPreferenceClickListener(
new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
Receiver.updateTracingQuickSettings(getContext());
return true;
}
});
findPreference(getString(R.string.pref_key_stack_sampling_quick_setting))
.setOnPreferenceClickListener(
new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
Receiver.updateStackSamplingQuickSettings(getContext());
return true;
}
});
findPreference("clear_saved_files").setOnPreferenceClickListener(
new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
new AlertDialog.Builder(getContext())
.setTitle(R.string.clear_saved_files_question)
.setMessage(R.string.all_recordings_will_be_deleted)
.setPositiveButton(R.string.clear,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
TraceUtils.clearSavedTraces();
}
})
.setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})
.create()
.show();
return true;
}
});
findPreference("trace_link_button")
.setOnPreferenceClickListener(
new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
Intent intent = buildTraceFileViewIntent();
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
return false;
}
return true;
}
});
// This disables "Attach to bugreports" when long traces are enabled. This cannot be done in
// main.xml because there are some other settings there that are enabled with long traces.
SwitchPreference attachToBugreport = findPreference(
getString(R.string.pref_key_attach_to_bugreport));
findPreference(getString(R.string.pref_key_long_traces))
.setOnPreferenceClickListener(
new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
if (((SwitchPreference) preference).isChecked()) {
attachToBugreport.setEnabled(false);
} else {
attachToBugreport.setEnabled(true);
}
return true;
}
});
refreshUi();
mRefreshReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
refreshUi();
}
};
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
setHasOptionsMenu(true);
return super.onCreateView(inflater, container, savedInstanceState);
}
@Override
public void onStart() {
super.onStart();
getPreferenceScreen().getSharedPreferences()
.registerOnSharedPreferenceChangeListener(mSharedPreferenceChangeListener);
getActivity().registerReceiver(mRefreshReceiver, new IntentFilter(ACTION_REFRESH_TAGS),
Context.RECEIVER_NOT_EXPORTED);
Receiver.updateTracing(getContext());
}
@Override
public void onStop() {
getPreferenceScreen().getSharedPreferences()
.unregisterOnSharedPreferenceChangeListener(mSharedPreferenceChangeListener);
getActivity().unregisterReceiver(mRefreshReceiver);
if (mAlertDialog != null) {
mAlertDialog.cancel();
mAlertDialog = null;
}
super.onStop();
}
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.main);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
HelpUtils.prepareHelpMenuItem(getActivity(), menu, R.string.help_url,
this.getClass().getName());
}
private Intent buildTraceFileViewIntent() {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse(STORAGE_URI), ROOT_MIME_TYPE);
return intent;
}
private void refreshUi() {
refreshUi(/* restoreDefaultTags =*/ false, /* clearHeapDumpProcesses =*/ false);
}
/*
* Refresh the preferences UI to make sure it reflects the current state of the preferences and
* system.
*/
private void refreshUi(boolean restoreDefaultTags, boolean clearHeapDumpProcesses) {
Context context = getContext();
// Make sure the Record trace, Record CPU profile, and Record heap dump toggles match their
// preference values.
mTracingOn.setChecked(mPrefs.getBoolean(mTracingOn.getKey(), false));
mStackSamplingOn.setChecked(mPrefs.getBoolean(mStackSamplingOn.getKey(), false));
mHeapDumpOn.setChecked(mPrefs.getBoolean(mHeapDumpOn.getKey(), false));
SwitchPreference stopOnReport =
(SwitchPreference) findPreference(getString(R.string.pref_key_stop_on_bugreport));
stopOnReport.setChecked(mPrefs.getBoolean(stopOnReport.getKey(), false));
SwitchPreference continuousHeapDump = (SwitchPreference) findPreference(
getString(R.string.pref_key_continuous_heap_dump));
continuousHeapDump.setChecked(mPrefs.getBoolean(continuousHeapDump.getKey(), false));
// Update category list to match the categories available on the system.
Set<Entry<String, String>> availableTags = TraceUtils.listCategories().entrySet();
ArrayList<String> entries = new ArrayList<String>(availableTags.size());
ArrayList<String> values = new ArrayList<String>(availableTags.size());
for (Entry<String, String> entry : availableTags) {
entries.add(entry.getKey() + ": " + entry.getValue());
values.add(entry.getKey());
}
// We keep selected processes in the list in case a user is interested in a process that AM
// is not yet aware of (e.g. an app that hasn't started up).
Set<String> runningProcesses = TraceUtils.getRunningAppProcesses(context);
Set<String> selectedProcesses = mHeapDumpProcesses.getValues();
runningProcesses.addAll(selectedProcesses);
List<String> sortedProcesses = new ArrayList<>(runningProcesses);
Collections.sort(sortedProcesses);
mRefreshing = true;
try {
mTags.setEntries(entries.toArray(new String[0]));
mTags.setEntryValues(values.toArray(new String[0]));
if (restoreDefaultTags || !mPrefs.contains(context.getString(R.string.pref_key_tags))) {
mTags.setValues(PresetTraceConfigs.getDefaultTags());
}
mHeapDumpProcesses.setEntries(sortedProcesses.toArray(new String[0]));
mHeapDumpProcesses.setEntryValues(sortedProcesses.toArray(new String[0]));
if (clearHeapDumpProcesses ||
!mPrefs.contains(context.getString(R.string.pref_key_heap_dump_processes))) {
mHeapDumpProcesses.setValues(new HashSet<String>());
}
} finally {
mRefreshing = false;
}
// Enable or disable each toggle based on the state of the others. This path exists in case
// the tracing state was updated with the QS tile or the ongoing-trace notification, which
// would not call the toggles' OnClickListeners.
mTracingOn.setEnabled(!(mStackSamplingOn.isChecked() || mHeapDumpOn.isChecked()));
mStackSamplingOn.setEnabled(!(mTracingOn.isChecked() || mHeapDumpOn.isChecked()));
// Disallow heap dumps if no process is selected, or if tracing/stack sampling is active.
boolean heapDumpProcessSelected = mHeapDumpProcesses.getValues().size() > 0;
mHeapDumpOn.setEnabled(heapDumpProcessSelected &&
!(mTracingOn.isChecked() || mStackSamplingOn.isChecked()));
mHeapDumpOn.setSummary(heapDumpProcessSelected
? context.getString(R.string.record_heap_dump_summary_enabled)
: context.getString(R.string.record_heap_dump_summary_disabled));
// Update subtitles on this screen.
Set<String> categories = mTags.getValues();
MessageFormat msgFormat = new MessageFormat(
getResources().getString(R.string.num_categories_selected),
Locale.getDefault());
Map<String, Object> arguments = new HashMap<>();
arguments.put("count", categories.size());
mTags.setSummary(PresetTraceConfigs.getDefaultTags().equals(categories)
? context.getString(R.string.default_categories)
: msgFormat.format(arguments));
ListPreference bufferSize = (ListPreference)findPreference(
context.getString(R.string.pref_key_buffer_size));
bufferSize.setSummary(bufferSize.getEntry());
ListPreference maxLongTraceSize = (ListPreference)findPreference(
context.getString(R.string.pref_key_max_long_trace_size));
maxLongTraceSize.setSummary(maxLongTraceSize.getEntry());
ListPreference maxLongTraceDuration = (ListPreference)findPreference(
context.getString(R.string.pref_key_max_long_trace_duration));
maxLongTraceDuration.setSummary(maxLongTraceDuration.getEntry());
ListPreference continuousHeapDumpInterval = (ListPreference)findPreference(
context.getString(R.string.pref_key_continuous_heap_dump_interval));
continuousHeapDumpInterval.setSummary(continuousHeapDumpInterval.getEntry());
// Check if BetterBug is installed to see if Traceur should display either the toggle for
// 'attach_to_bugreport' or 'stop_on_bugreport'.
try {
context.getPackageManager().getPackageInfo(BETTERBUG_PACKAGE_NAME,
PackageManager.MATCH_SYSTEM_ONLY);
findPreference(getString(R.string.pref_key_attach_to_bugreport)).setVisible(true);
findPreference(getString(R.string.pref_key_stop_on_bugreport)).setVisible(false);
// Changes the long traces summary to add that they cannot be attached to bugreports.
findPreference(getString(R.string.pref_key_long_traces))
.setSummary(getString(R.string.long_traces_summary_betterbug));
} catch (PackageManager.NameNotFoundException e) {
// attach_to_bugreport must be disabled here because it's true by default.
mPrefs.edit().putBoolean(
getString(R.string.pref_key_attach_to_bugreport), false).commit();
findPreference(getString(R.string.pref_key_attach_to_bugreport)).setVisible(false);
findPreference(getString(R.string.pref_key_stop_on_bugreport)).setVisible(true);
// Sets long traces summary to the default in case Betterbug was removed.
findPreference(getString(R.string.pref_key_long_traces))
.setSummary(getString(R.string.long_traces_summary));
}
// Check if an activity exists to handle the trace_link_button intent. If not, hide the UI
// element
PackageManager packageManager = context.getPackageManager();
Intent intent = buildTraceFileViewIntent();
if (intent.resolveActivity(packageManager) == null) {
findPreference("trace_link_button").setVisible(false);
}
}
}