package org.droidplanner.android.fragments;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.os.Handler;
import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.preference.PreferenceFragment;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.util.Log;
import com.google.android.gms.analytics.GoogleAnalytics;
import com.google.android.gms.analytics.HitBuilders;
import com.o3dr.android.client.Drone;
import com.o3dr.services.android.lib.drone.attribute.AttributeEvent;
import com.o3dr.services.android.lib.drone.attribute.AttributeEventExtra;
import com.o3dr.services.android.lib.drone.attribute.AttributeType;
import com.o3dr.services.android.lib.drone.connection.ConnectionType;
import com.o3dr.services.android.lib.drone.property.State;
import com.o3dr.services.android.lib.drone.property.Type;
import org.droidplanner.android.DroidPlannerApp;
import org.droidplanner.android.R;
import org.droidplanner.android.activities.helpers.MapPreferencesActivity;
import org.droidplanner.android.dialogs.ClearBTDialogPreference;
import org.droidplanner.android.maps.providers.DPMapProvider;
import org.droidplanner.android.utils.Utils;
import org.droidplanner.android.utils.analytics.GAUtils;
import org.droidplanner.android.utils.file.DirectoryPath;
import org.droidplanner.android.utils.prefs.DroidPlannerPrefs;
import java.util.HashSet;
import java.util.Locale;
/**
* Implements the application settings screen.
*/
public class SettingsFragment extends PreferenceFragment implements OnSharedPreferenceChangeListener,
DroidPlannerApp.ApiListener {
/**
* Used as tag for logging.
*/
private final static String TAG = SettingsFragment.class.getSimpleName();
private static final String PACKAGE_NAME = Utils.PACKAGE_NAME;
/**
* Action used to broadcast updates to the period for the spoken status
* summary.
*/
public static final String ACTION_UPDATED_STATUS_PERIOD = PACKAGE_NAME + ".ACTION_UPDATED_STATUS_PERIOD";
/**
* Action used to broadcast updates to the gps hdop display preference.
*/
public static final String ACTION_PREF_HDOP_UPDATE = PACKAGE_NAME + ".ACTION_PREF_HDOP_UPDATE";
/**
* Action used to broadcast updates to the unit system.
*/
public static final String ACTION_PREF_UNIT_SYSTEM_UPDATE = PACKAGE_NAME + ".ACTION_PREF_UNIT_SYSTEM_UPDATE";
/**
* Used to retrieve the new period for the spoken status summary.
*/
public static final String EXTRA_UPDATED_STATUS_PERIOD = "extra_updated_status_period";
public static final String ACTION_LOCATION_SETTINGS_UPDATED = PACKAGE_NAME + ".action.LOCATION_SETTINGS_UPDATED";
public static final String EXTRA_RESULT_CODE = "extra_result_code";
public static final String ACTION_ADVANCED_MENU_UPDATED = PACKAGE_NAME + ".action.ADVANCED_MENU_UPDATED";
/**
* Used to notify of an update to the map rotation preference.
*/
public static final String ACTION_MAP_ROTATION_PREFERENCE_UPDATED = PACKAGE_NAME +
".ACTION_MAP_ROTATION_PREFERENCE_UPDATED";
private static final IntentFilter intentFilter = new IntentFilter();
static {
intentFilter.addAction(AttributeEvent.STATE_DISCONNECTED);
intentFilter.addAction(AttributeEvent.STATE_CONNECTED);
intentFilter.addAction(AttributeEvent.STATE_UPDATED);
intentFilter.addAction(AttributeEvent.HEARTBEAT_FIRST);
intentFilter.addAction(AttributeEvent.HEARTBEAT_RESTORED);
intentFilter.addAction(AttributeEvent.TYPE_UPDATED);
}
private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final Activity activity = getActivity();
if(activity == null)
return;
final String action = intent.getAction();
switch (action) {
case AttributeEvent.STATE_DISCONNECTED:
updateMavlinkVersionPreference(null);
updateFirmwareVersionPreference(null);
break;
case AttributeEvent.HEARTBEAT_FIRST:
case AttributeEvent.HEARTBEAT_RESTORED:
int mavlinkVersion = intent.getIntExtra(AttributeEventExtra.EXTRA_MAVLINK_VERSION, -1);
if (mavlinkVersion == -1)
updateMavlinkVersionPreference(null);
else
updateMavlinkVersionPreference(String.valueOf(mavlinkVersion));
break;
case AttributeEvent.STATE_CONNECTED:
case AttributeEvent.TYPE_UPDATED:
Drone drone = dpApp.getDrone();
if (drone.isConnected()) {
Type droneType = drone.getAttribute(AttributeType.TYPE);
updateFirmwareVersionPreference(droneType.getFirmwareVersion());
} else
updateFirmwareVersionPreference(null);
break;
}
}
};
/**
* Keep track of which preferences' summary need to be updated.
*/
private final HashSet<String> mDefaultSummaryPrefs = new HashSet<String>();
private final Handler mHandler = new Handler();
private DroidPlannerApp dpApp;
private DroidPlannerPrefs dpPrefs;
private LocalBroadcastManager lbm;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
dpApp = (DroidPlannerApp) activity.getApplication();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
initSummaryPerPrefs();
final Context context = getActivity().getApplicationContext();
dpPrefs = new DroidPlannerPrefs(context);
lbm = LocalBroadcastManager.getInstance(context);
final SharedPreferences sharedPref = dpPrefs.prefs;
// Populate the map preference category
final String mapsProvidersPrefKey = getString(R.string.pref_maps_providers_key);
final ListPreference mapsProvidersPref = (ListPreference) findPreference(mapsProvidersPrefKey);
if (mapsProvidersPref != null) {
final DPMapProvider[] providers = DPMapProvider.values();
final int providersCount = providers.length;
final CharSequence[] providersNames = new CharSequence[providersCount];
final CharSequence[] providersNamesValues = new CharSequence[providersCount];
for (int i = 0; i < providersCount; i++) {
final String providerName = providers[i].name();
providersNamesValues[i] = providerName;
providersNames[i] = providerName.toLowerCase(Locale.ENGLISH).replace('_', ' ');
}
final String defaultProviderName = sharedPref.getString(mapsProvidersPrefKey,
DPMapProvider.DEFAULT_MAP_PROVIDER.name());
mapsProvidersPref.setEntries(providersNames);
mapsProvidersPref.setEntryValues(providersNamesValues);
mapsProvidersPref.setValue(defaultProviderName);
mapsProvidersPref
.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
// Update the map provider settings preference.
final String mapProviderName = newValue.toString();
return updateMapSettingsPreference(mapProviderName);
}
});
updateMapSettingsPreference(defaultProviderName);
}
// update the summary for the preferences in the mDefaultSummaryPrefs hash table.
for (String prefKey : mDefaultSummaryPrefs) {
final Preference pref = findPreference(prefKey);
if (pref != null) {
pref.setSummary(sharedPref.getString(prefKey, ""));
}
}
// Set the usage statistics preference
final String usageStatKey = getString(R.string.pref_usage_statistics_key);
final CheckBoxPreference usageStatPref = (CheckBoxPreference) findPreference(usageStatKey);
if (usageStatPref != null) {
usageStatPref
.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
// Update the google analytics singleton.
final boolean optIn = (Boolean) newValue;
final GoogleAnalytics analytics = GoogleAnalytics.getInstance(context);
analytics.setAppOptOut(!optIn);
return true;
}
});
}
final Preference storagePref = findPreference(getString(R.string.pref_storage_key));
if (storagePref != null) {
storagePref.setSummary(DirectoryPath.getPublicDataPath());
}
try {
Preference versionPref = findPreference("pref_version");
if (versionPref != null) {
String version = context.getPackageManager().getPackageInfo(
context.getPackageName(), 0).versionName;
versionPref.setSummary(version);
}
} catch (NameNotFoundException e) {
Log.e(TAG, "Unable to retrieve version name.", e);
}
updateMavlinkVersionPreference(null);
setupPeriodicControls();
setupConnectionPreferences();
setupAdvancedMenu();
setupUnitSystemPreferences();
setupBluetoothDevicePreferences();
setupImminentGroundCollisionWarningPreference();
setupMapPreferences();
}
private void setupAdvancedMenu(){
final CheckBoxPreference hdopToggle = (CheckBoxPreference) findPreference(getString(R.string
.pref_ui_gps_hdop_key));
if(hdopToggle != null) {
hdopToggle.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
lbm.sendBroadcast(new Intent(ACTION_PREF_HDOP_UPDATE));
return true;
}
});
}
final CheckBoxPreference killSwitch = (CheckBoxPreference) findPreference(getString(R.string
.pref_enable_kill_switch_key));
if(killSwitch != null) {
killSwitch.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
lbm.sendBroadcast(new Intent(ACTION_ADVANCED_MENU_UPDATED));
return true;
}
});
}
}
private void setupUnitSystemPreferences(){
ListPreference unitSystemPref = (ListPreference) findPreference(getString(R.string.pref_unit_system_key));
if(unitSystemPref != null){
int defaultUnitSystem = dpPrefs.getUnitSystemType();
updateUnitSystemSummary(unitSystemPref, defaultUnitSystem);
unitSystemPref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
int unitSystem = Integer.parseInt((String) newValue);
updateUnitSystemSummary(preference, unitSystem);
lbm.sendBroadcast(new Intent(ACTION_PREF_UNIT_SYSTEM_UPDATE));
return true;
}
});
}
}
private void setupMapPreferences(){
final CheckBoxPreference mapRotation = (CheckBoxPreference) findPreference(getString(R.string
.pref_map_enable_rotation_key));
mapRotation.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
lbm.sendBroadcast(new Intent(ACTION_MAP_ROTATION_PREFERENCE_UPDATED));
return true;
}
});
}
private void setupImminentGroundCollisionWarningPreference(){
final CheckBoxPreference collisionWarn = (CheckBoxPreference) findPreference(getString(R.string
.pref_ground_collision_warning_key));
if(collisionWarn != null){
collisionWarn.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final boolean isEnabled = (Boolean) newValue;
if(!isEnabled){
lbm.sendBroadcast(new Intent(Drone.ACTION_GROUND_COLLISION_IMMINENT)
.putExtra(Drone.EXTRA_IS_GROUND_COLLISION_IMMINENT, false));
}
return true;
}
});
}
}
private void updateUnitSystemSummary(Preference preference, int unitSystemType){
final int summaryResId;
switch(unitSystemType){
case 0:
default:
summaryResId = R.string.unit_system_entry_auto;
break;
case 1:
summaryResId = R.string.unit_system_entry_metric;
break;
case 2:
summaryResId = R.string.unit_system_entry_imperial;
break;
}
preference.setSummary(summaryResId);
}
private void setupConnectionPreferences() {
ListPreference connectionTypePref = (ListPreference) findPreference(getString(R.string.pref_connection_type_key));
if (connectionTypePref != null) {
int defaultConnectionType = dpPrefs.getConnectionParameterType();
updateConnectionPreferenceSummary(connectionTypePref, defaultConnectionType);
connectionTypePref
.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
int connectionType = Integer.parseInt((String) newValue);
updateConnectionPreferenceSummary(preference, connectionType);
return true;
}
});
}
}
private void setupBluetoothDevicePreferences(){
final ClearBTDialogPreference preference = (ClearBTDialogPreference) findPreference(getString(R.string
.pref_bluetooth_device_address_key));
if(preference != null){
updateBluetoothDevicePreference(preference, dpPrefs.getBluetoothDeviceAddress());
preference.setOnResultListener(new ClearBTDialogPreference.OnResultListener() {
@Override
public void onResult(boolean result) {
if(result){
updateBluetoothDevicePreference(preference, dpPrefs.getBluetoothDeviceAddress());
}
}
});
}
}
private void updateBluetoothDevicePreference(Preference preference, String deviceAddress){
if(TextUtils.isEmpty(deviceAddress)) {
preference.setEnabled(false);
preference.setTitle(R.string.pref_no_saved_bluetooth_device_title);
preference.setSummary("");
}
else{
preference.setEnabled(true);
preference.setSummary(deviceAddress);
final String deviceName = dpPrefs.getBluetoothDeviceName();
if(deviceName != null){
preference.setTitle(getString(R.string.pref_forget_bluetooth_device_title, deviceName));
}
else
preference.setTitle(getString(R.string.pref_forget_bluetooth_device_address));
}
}
private void updateConnectionPreferenceSummary(Preference preference, int connectionType) {
String connectionName;
switch (connectionType) {
case ConnectionType.TYPE_USB:
connectionName = "USB";
break;
case ConnectionType.TYPE_UDP:
connectionName = "UDP";
break;
case ConnectionType.TYPE_TCP:
connectionName = "TCP";
break;
case ConnectionType.TYPE_BLUETOOTH:
connectionName = "BLUETOOTH";
break;
default:
connectionName = null;
break;
}
if (connectionName != null)
preference.setSummary(connectionName);
}
private void initSummaryPerPrefs() {
mDefaultSummaryPrefs.clear();
mDefaultSummaryPrefs.add(getString(R.string.pref_baud_type_key));
mDefaultSummaryPrefs.add(getString(R.string.pref_server_port_key));
mDefaultSummaryPrefs.add(getString(R.string.pref_server_ip_key));
mDefaultSummaryPrefs.add(getString(R.string.pref_udp_server_port_key));
mDefaultSummaryPrefs.add(getString(R.string.pref_rc_quickmode_left_key));
mDefaultSummaryPrefs.add(getString(R.string.pref_rc_quickmode_right_key));
mDefaultSummaryPrefs.add(getString(R.string.pref_udp_ping_receiver_ip_key));
mDefaultSummaryPrefs.add(getString(R.string.pref_udp_ping_receiver_port_key));
}
/**
* This is used to update the mavlink version preference.
*
* @param version mavlink version
*/
private void updateMavlinkVersionPreference(String version) {
final Preference mavlinkVersionPref = findPreference(getString(R.string.pref_mavlink_version_key));
if (mavlinkVersionPref != null) {
final HitBuilders.EventBuilder mavlinkEvent = new HitBuilders.EventBuilder()
.setCategory(GAUtils.Category.MAVLINK_CONNECTION);
if (version == null) {
mavlinkVersionPref.setSummary(getString(R.string.empty_content));
mavlinkEvent.setAction("Mavlink version unset");
} else {
mavlinkVersionPref.setSummary('v' + version);
mavlinkEvent.setAction("Mavlink version set").setLabel(version);
}
// Record the mavlink version
GAUtils.sendEvent(mavlinkEvent);
}
}
private void updateFirmwareVersionPreference(String firmwareVersion) {
final Preference firmwareVersionPref = findPreference(getString(R.string.pref_firmware_version_key));
if (firmwareVersionPref != null) {
final HitBuilders.EventBuilder firmwareEvent = new HitBuilders.EventBuilder()
.setCategory(GAUtils.Category.MAVLINK_CONNECTION);
if (firmwareVersion == null) {
firmwareVersionPref.setSummary(getString(R.string.empty_content));
firmwareEvent.setAction("Firmware version unset");
} else {
firmwareVersionPref.setSummary(firmwareVersion);
firmwareEvent.setAction("Firmware version set").setLabel(firmwareVersion);
}
// Record the firmware version.
GAUtils.sendEvent(firmwareEvent);
}
}
private boolean updateMapSettingsPreference(final String mapProviderName) {
final DPMapProvider mapProvider = DPMapProvider.getMapProvider(mapProviderName);
if (mapProvider == null)
return false;
final Preference providerPrefs = findPreference(getText(R.string.pref_map_provider_settings_key));
if (providerPrefs != null) {
providerPrefs.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
startActivity(new Intent(getActivity(), MapPreferencesActivity.class).putExtra(
MapPreferencesActivity.EXTRA_MAP_PROVIDER_NAME, mapProviderName));
return true;
}
});
}
return true;
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
final Preference preference = findPreference(key);
if (preference == null) {
return;
}
if (mDefaultSummaryPrefs.contains(key)) {
preference.setSummary(sharedPreferences.getString(key, ""));
}
}
private void setupPeriodicControls() {
final PreferenceCategory periodicSpeechPrefs = (PreferenceCategory) findPreference(getString(R.string.pref_tts_periodic_key));
ListPreference periodic = ((ListPreference) periodicSpeechPrefs.getPreference(0));
periodic.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, final Object newValue) {
// Broadcast the event locally on update.
// A handler is used to that the current action has the time to
// return, and store the value in the preferences.
mHandler.post(new Runnable() {
@Override
public void run() {
lbm.sendBroadcast(new Intent(ACTION_UPDATED_STATUS_PERIOD)
.putExtra(EXTRA_UPDATED_STATUS_PERIOD, (String) newValue));
setupPeriodicControls();
}
});
return true;
}
});
int val = Integer.parseInt(periodic.getValue());
final boolean isEnabled = val != 0;
if (isEnabled) {
periodic.setSummary(getString(R.string.pref_tts_status_every) + " " + val + " "
+ getString(R.string.pref_tts_seconds));
} else {
periodic.setSummary(R.string.pref_tts_periodic_status_disabled);
}
for (int i = 1; i < periodicSpeechPrefs.getPreferenceCount(); i++) {
periodicSpeechPrefs.getPreference(i).setEnabled(isEnabled);
}
}
@Override
public void onStart() {
super.onStart();
dpApp.addApiListener(this);
}
@Override
public void onStop() {
super.onStop();
dpApp.removeApiListener(this);
}
@Override
public void onResume() {
super.onResume();
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
}
@Override
public void onPause() {
super.onPause();
getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(
this);
}
@Override
public void onApiConnected() {
Drone drone = dpApp.getDrone();
State droneState = drone.getAttribute(AttributeType.STATE);
Type droneType = drone.getAttribute(AttributeType.TYPE);
final int mavlinkVersion = droneState == null
? State.INVALID_MAVLINK_VERSION
: droneState.getMavlinkVersion();
if (mavlinkVersion != State.INVALID_MAVLINK_VERSION) {
updateMavlinkVersionPreference(String.valueOf(mavlinkVersion));
} else {
updateMavlinkVersionPreference(null);
}
String firmwareVersion = droneType == null ? null : droneType.getFirmwareVersion();
updateFirmwareVersionPreference(firmwareVersion);
lbm.registerReceiver(broadcastReceiver, intentFilter);
}
@Override
public void onApiDisconnected() {
lbm.unregisterReceiver(broadcastReceiver);
}
}