package com.openxc.enabler;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.bluetooth.BluetoothAdapter;
import android.content.ComponentName;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import android.provider.DocumentsContract;
import android.util.Log;
import android.widget.Toast;
import com.openxc.enabler.preferences.PreferenceManagerService;
import com.openxc.sinks.UploaderSink;
import com.openxcplatform.enabler.R;
/**
* Initialize and display all preferences for the OpenXC Enabler application.
*
* In order to select a trace file to use as a data source, the device must have
* a file manager application installed that responds to the GET_CONTENT intent,
* e.g. OI File Manager.
*/
@TargetApi(12)
public class SettingsActivity extends PreferenceActivity {
private static String TAG = "SettingsActivity";
private final static String RECORDING_PREFERENCE =
"com.openxc.enabler.preferences.RECORDING";
private final static String DATA_SOURCE_PREFERENCE =
"com.openxc.enabler.preferences.DATA_SOURCE";
private final static String OUTPUT_PREFERENCE =
"com.openxc.enabler.preferences.OUTPUT";
private final static String ABOUT_PREFERENCE =
"com.openxc.enabler.preferences.ABOUT";
private final static int FILE_SELECTOR_RESULT = 100;
private ListPreference mVehicleInterfaceListPreference;
private ListPreference mBluetoothDeviceListPreference;
private CheckBoxPreference mUploadingPreference;
private Preference mTraceFilePreference;
private EditTextPreference mNetworkHostPreference;
private EditTextPreference mNetworkPortPreference;
private Preference mAboutVersionPreference;
private PreferenceManagerService mPreferenceManager;
private PreferenceCategory mBluetoothPreferences;
private PreferenceCategory mNetworkPreferences;
private PreferenceCategory mTracePreferences;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initializeLegacyLayout();
}
@Override
public void onPause() {
super.onPause();
if(mPreferenceManager != null) {
unbindService(mConnection);
mPreferenceManager = null;
}
}
protected boolean isValidFragment(String fragmentName){
return RecordingPreferences.class.getName().equals(fragmentName) ||
OutputPreferences.class.getName().equals(fragmentName) ||
DataSourcePreferences.class.getName().equals(fragmentName) ||
AboutPreferences.class.getName().equals(fragmentName);
}
@SuppressWarnings("deprecation")
private void initializeLegacyLayout() {
String action = getIntent().getAction();
if(action != null) {
if(action.equals(RECORDING_PREFERENCE)) {
addPreferencesFromResource(R.xml.recording_preferences);
initializeUploadingPreferences(getPreferenceManager());
} else if(action.equals(DATA_SOURCE_PREFERENCE)) {
addPreferencesFromResource(R.xml.data_source_preferences);
initializeDataSourcePreferences(getPreferenceManager());
} else if(action.equals(OUTPUT_PREFERENCE)) {
addPreferencesFromResource(R.xml.output_preferences);
} else if(action.equals(ABOUT_PREFERENCE)) {
addPreferencesFromResource(R.xml.about_preferences);
initializeAboutPreferences(getPreferenceManager());
}
} else if(Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
addPreferencesFromResource(R.xml.preference_headers_legacy);
}
}
// Thanks to Paul Burke on Stack Overflow
@TargetApi(Build.VERSION_CODES.KITKAT)
// (http://stackoverflow.com/questions/19834842/android-gallery-on-kitkat-returns-different-uri-for-intent-action-get-content)
@SuppressLint("NewApi")
public static String getPath(final Context context, final Uri uri) {
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// DocumentProvider
if(isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if(isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}
} else if(isDownloadsDocument(uri)) {
final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
}
} else if ("file".equalsIgnoreCase(uri.getScheme()) ||
"content".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
public static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
public static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
/**
* Get the value of the data column for this Uri. This is useful for
* MediaStore Uris, and other file-based ContentProviders.
*
* @param context The context.
* @param uri The Uri to query.
* @param selection (Optional) Filter used in the query.
* @param selectionArgs (Optional) Selection arguments used in the query.
* @return The value of the _data column, which is typically a file path.
*/
public static String getDataColumn(Context context, Uri uri, String selection,
String[] selectionArgs) {
Cursor cursor = null;
final String column = "_data";
final String[] projection = {
column
};
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
null);
if (cursor != null && cursor.moveToFirst()) {
final int index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(index);
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if(requestCode == FILE_SELECTOR_RESULT && resultCode == RESULT_OK) {
String newValue = getPath(this, data.getData());
SharedPreferences.Editor editor =
PreferenceManager.getDefaultSharedPreferences(this).edit();
editor.putString(getString(R.string.trace_source_file_key), newValue);
editor.commit();
updateSummary(mTraceFilePreference, newValue);
}
}
@Override
public void onBuildHeaders(List<Header> target) {
loadHeadersFromResource(R.xml.preference_headers, target);
}
public static class RecordingPreferences extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.recording_preferences);
((SettingsActivity)getActivity()).initializeUploadingPreferences(
getPreferenceManager());
}
}
public static class OutputPreferences extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.output_preferences);
}
}
public static class DataSourcePreferences extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.data_source_preferences);
((SettingsActivity)getActivity()).initializeDataSourcePreferences(
getPreferenceManager());
}
}
public static class AboutPreferences extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.about_preferences);
((SettingsActivity)getActivity()).initializeAboutPreferences(
getPreferenceManager());
}
}
protected void initializeTracePreferences(PreferenceManager manager) {
mTraceFilePreference = manager.findPreference(
getString(R.string.trace_source_file_key));
mTraceFilePreference.setOnPreferenceClickListener(
mTraceFileClickListener);
mTraceFilePreference.setOnPreferenceChangeListener(
mUpdateSummaryListener);
SharedPreferences preferences =
PreferenceManager.getDefaultSharedPreferences(this);
updateSummary(mTraceFilePreference,
preferences.getString(
getString(R.string.trace_source_file_key), null));
}
protected void initializeUploadingPreferences(PreferenceManager manager) {
mUploadingPreference = (CheckBoxPreference) manager.findPreference(
getString(R.string.uploading_checkbox_key));
Preference uploadingPathPreference = manager.findPreference(
getString(R.string.uploading_path_key));
uploadingPathPreference.setOnPreferenceChangeListener(
mUploadingPathPreferenceListener);
SharedPreferences preferences =
PreferenceManager.getDefaultSharedPreferences(this);
updateSummary(uploadingPathPreference,
preferences.getString(
getString(R.string.uploading_path_key), null));
}
protected void initializeVehicleInterfacePreference(PreferenceManager manager) {
mVehicleInterfaceListPreference = (ListPreference)
manager.findPreference(getString(
R.string.vehicle_interface_key));
PreferenceManager.setDefaultValues(this, R.xml.data_source_preferences, false);
mVehicleInterfaceListPreference.setOnPreferenceChangeListener(
mVehicleInterfaceUpdatedListener);
PreferenceScreen screen = (PreferenceScreen)
manager.findPreference("preference_screen");
mBluetoothPreferences = (PreferenceCategory) screen.findPreference(
getString(R.string.bluetooth_settings));
mNetworkPreferences = (PreferenceCategory) screen.findPreference(
getString(R.string.network_settings));
mTracePreferences = (PreferenceCategory) screen.findPreference(
getString(R.string.trace_source_settings));
List<String> entries = new ArrayList<>(Arrays.asList(getResources().
getStringArray(R.array.vehicle_interface_types)));
List<String> values = new ArrayList<>(Arrays.asList(getResources().
getStringArray(R.array.vehicle_interface_type_aliases)));
if(android.os.Build.VERSION.SDK_INT <
android.os.Build.VERSION_CODES.HONEYCOMB) {
// USB not supported, so re-load entries without that option
entries.remove(getString(R.string.usb_interface_option));
values.remove(getString(R.string.usb_interface_option_value));
}
if(BluetoothAdapter.getDefaultAdapter() == null) {
// No Bluetooth adapter, so remove those entries too
entries.remove(getString(R.string.bluetooth_interface_option));
values.remove(getString(R.string.bluetooth_interface_option_value));
screen.removePreference(mBluetoothPreferences);
// Bluetooth is the default, so we need to force it to None if the
// device has no adapter
if(mVehicleInterfaceListPreference.getValue().equals(
getString(R.string.bluetooth_interface_option_value))) {
mVehicleInterfaceListPreference.setValueIndex(
mVehicleInterfaceListPreference.findIndexOfValue(
getString(
R.string.disabled_interface_option_value)));
}
}
CharSequence[] prototype = {};
mVehicleInterfaceListPreference.setEntries(entries.toArray(prototype));
mVehicleInterfaceListPreference.setEntryValues(values.toArray(prototype));
mVehicleInterfaceListPreference.setSummary(
mVehicleInterfaceListPreference.getEntry());
mBluetoothPreferences.setEnabled(mVehicleInterfaceListPreference.
getValue().equals(
getString(R.string.bluetooth_interface_option_value)));
mNetworkPreferences.setEnabled(mVehicleInterfaceListPreference.
getValue().equals(
getString(R.string.network_interface_option_value)));
mTracePreferences.setEnabled(mVehicleInterfaceListPreference.
getValue().equals(
getString(R.string.trace_interface_option_value)));
}
protected void initializeDataSourcePreferences(PreferenceManager manager) {
initializeVehicleInterfacePreference(manager);
initializeBluetoothPreferences(manager);
initializeNetwork(manager);
initializeTracePreferences(manager);
}
protected void initializeBluetoothPreferences(PreferenceManager manager) {
mBluetoothDeviceListPreference = (ListPreference)
manager.findPreference(getString(R.string.bluetooth_mac_key));
// If the device doesn't have BT, we removed these preferences earlier
// in the initialization
if(mBluetoothDeviceListPreference != null) {
mBluetoothDeviceListPreference.setOnPreferenceChangeListener(
mUpdateSummaryListener);
bindService(new Intent(SettingsActivity.this,
PreferenceManagerService.class), mConnection,
Context.BIND_AUTO_CREATE);
List<String> entries = new ArrayList<String>();
entries.add(getString(R.string.bluetooth_mac_automatic_option));
List<String> values = new ArrayList<String>();
values.add(getString(R.string.bluetooth_mac_automatic_summary));
CharSequence[] prototype = {};
mBluetoothDeviceListPreference.setEntries(entries.toArray(prototype));
mBluetoothDeviceListPreference.setEntryValues(values.toArray(prototype));
SharedPreferences preferences =
PreferenceManager.getDefaultSharedPreferences(this);
updateSummary(mBluetoothDeviceListPreference,
preferences.getString(getString(
R.string.bluetooth_mac_key), null));
}
}
protected void initializeNetwork(PreferenceManager manager) {
mNetworkHostPreference = (EditTextPreference)
manager.findPreference(getString(R.string.network_host_key));
mNetworkHostPreference.setOnPreferenceChangeListener(
mUpdateSummaryListener);
mNetworkPortPreference = (EditTextPreference)
manager.findPreference(getString(R.string.network_port_key));
mNetworkPortPreference.setOnPreferenceChangeListener(
mUpdateSummaryListener);
SharedPreferences preferences =
PreferenceManager.getDefaultSharedPreferences(this);
updateSummary(mNetworkHostPreference,
preferences.getString(getString(
R.string.network_host_key), null));
updateSummary(mNetworkPortPreference,
preferences.getString(getString(
R.string.network_port_key), null));
}
protected void initializeAboutPreferences(PreferenceManager manager) {
try {
mAboutVersionPreference = manager.findPreference(
getString(R.string.application_version_key));
String versionNumber = getPackageManager().getPackageInfo(
getPackageName(), 0).versionName;
updateSummary(mAboutVersionPreference, versionNumber);
} catch (NameNotFoundException e) {
Log.e(TAG, "Could not get application version.", e);
}
}
protected void updateSummary(Preference preference, Object currentValue) {
String summary = null;
if(currentValue != null) {
summary = currentValue.toString();
} else {
summary = "No value set";
}
preference.setSummary(summary);
}
private OnPreferenceChangeListener mVehicleInterfaceUpdatedListener =
new OnPreferenceChangeListener() {
public boolean onPreferenceChange(Preference preference,
Object newValue) {
// Can't just call preference.getSummary() because this callback
// happens bofore newValue is actually set.
ListPreference listPreference = (ListPreference) preference;
String newSummary = listPreference.getEntries()[
listPreference.findIndexOfValue(
newValue.toString())].toString();
preference.setSummary(newSummary);
mNetworkPreferences.setEnabled(newValue.equals(
getString(R.string.network_interface_option_value)));
mBluetoothPreferences.setEnabled(newValue.equals(
getString(R.string.bluetooth_interface_option_value)));
mTracePreferences.setEnabled(newValue.equals(
getString(R.string.trace_interface_option_value)));
return true;
}
};
private OnPreferenceChangeListener mUpdateSummaryListener =
new OnPreferenceChangeListener() {
public boolean onPreferenceChange(Preference preference,
Object newValue) {
updateSummary(preference, newValue);
return true;
}
};
private OnPreferenceChangeListener mUploadingPathPreferenceListener =
new OnPreferenceChangeListener() {
public boolean onPreferenceChange(Preference preference,
Object newValue) {
String path = (String) newValue;
if(!UploaderSink.validatePath(path)) {
String error = "Invalid target URL \"" + path +
"\" -- must be an absolute URL " +
"with http:// prefix";
Toast.makeText(getApplicationContext(), error,
Toast.LENGTH_SHORT).show();
Log.w(TAG, error);
mUploadingPreference.setChecked(false);
}
updateSummary(preference, newValue);
return true;
}
};
private Preference.OnPreferenceClickListener mTraceFileClickListener =
new Preference.OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*");
intent.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(intent, FILE_SELECTOR_RESULT);
return true;
}
};
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
IBinder service) {
Log.i(TAG, "Bound to PreferenceManagerService");
mPreferenceManager = ((PreferenceManagerService.PreferenceBinder)service
).getService();
List<String> entries = new ArrayList<String>();
entries.add(getString(R.string.bluetooth_mac_automatic_summary));
List<String> values = new ArrayList<String>();
values.add(getString(R.string.bluetooth_mac_automatic_option));
Map<String, String> discoveredDevices =
mPreferenceManager.getBluetoothDevices();
values.addAll(discoveredDevices.keySet());
entries.addAll(discoveredDevices.values());
CharSequence[] prototype = {};
mBluetoothDeviceListPreference.setEntries(
entries.toArray(prototype));
mBluetoothDeviceListPreference.setEntryValues(
values.toArray(prototype));
}
public void onServiceDisconnected(ComponentName className) {
Log.w(TAG, "PreferenceMangerService disconnected unexpectedly");
mPreferenceManager = null;
}
};
}