package com.openxc.enabler.preferences; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import android.bluetooth.BluetoothDevice; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.util.Log; import com.openxc.interfaces.bluetooth.BluetoothException; import com.openxc.interfaces.bluetooth.BluetoothVehicleInterface; import com.openxc.interfaces.bluetooth.DeviceManager; import com.openxc.remote.VehicleServiceException; import com.openxc.util.SupportSettingsUtils; import com.openxcplatform.enabler.R; /** * Enable or disable receiving vehicle data from a Bluetooth vehicle interface. * * When you enable the Bluetooth vehicle interface option, this is what happens: * * * The BT discovery process is started to find any nearby VIs that are * powered up - they don't have to be previously paired. * * The Bluetooth device list in preferences is populated with a list of * all paired and discovered devices * * A socket is open and will accept incoming Bluetooth connections, e.g. * a connection initated by a VI acting as Bluetooth master. This * socket remains open as long as the Bluetooth vehicle interface is * enabled and is not currently connected. * * If the "use background polling" option is enabled (the default), we * try and initiate a connection to a discovered or previously selected * Bluetooth device. By default, the device preference is set to automatic * mode. In that mode, the * service will scan for any paired or unpaired Bluetooth device with a * name beginning with "OpenXC-VI-". For each device found, it attempts * to connect and read data. This automatic scan is rather resource * intensive, so it only happens when manuaully initialized with a UI * button *or* once when the service first starts up if no VI has even * been previously connected. If a VI has been previously connected, in * the future it will only poll for a connection to that device. * * You can also explicitly select a device from the list, even one that * doesn't have the OpenXC-VI name prefix. Requesting an automatic scan * will reset this preference back to automatic mode. * * Device discovery is only kicked off once, when the Bluetooth option is first * enabled or the OpenXC service first starts, to avoid draining the battery. */ public class BluetoothPreferenceManager extends VehiclePreferenceManager { private final static String TAG = "BluetoothPreferenceManager"; private DeviceManager mBluetoothDeviceManager; private HashMap<String, String> mDiscoveredDevices = new HashMap<String, String>(); public BluetoothPreferenceManager(Context context) { super(context); IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); context.registerReceiver(mDiscoveryReceiver, filter); try { mBluetoothDeviceManager = new DeviceManager(context); fillBluetoothDeviceList(); } catch(BluetoothException e) { Log.w(TAG, "This device most likely does not have " + "a Bluetooth adapter"); } } @SuppressWarnings("unchecked") public Map<String, String> getDiscoveredDevices() { return (Map<String, String>) mDiscoveredDevices.clone(); } @Override public void close() { super.close(); getContext().unregisterReceiver(mDiscoveryReceiver); mBluetoothDeviceManager.stop(); } protected PreferenceListener createPreferenceListener() { return new PreferenceListener() { private int[] WATCHED_PREFERENCE_KEY_IDS = { R.string.vehicle_interface_key, R.string.bluetooth_polling_key, R.string.bluetooth_mac_key, }; protected int[] getWatchedPreferenceKeyIds() { return WATCHED_PREFERENCE_KEY_IDS; } public void readStoredPreferences() { setBluetoothStatus(getPreferences().getString( getString(R.string.vehicle_interface_key), "").equals( getString(R.string.bluetooth_interface_option_value))); getVehicleManager().setBluetoothPollingStatus( getPreferences().getBoolean( getString(R.string.bluetooth_polling_key), true)); } }; } private synchronized void setBluetoothStatus(boolean enabled) { if(enabled) { Log.i(TAG, "Enabling the Bluetooth vehicle interface"); String deviceAddress = getPreferenceString( R.string.bluetooth_mac_key); if(deviceAddress == null || deviceAddress.equals( getString(R.string.bluetooth_mac_automatic_option))) { deviceAddress = null; Log.d(TAG, "No Bluetooth vehicle interface selected -- " + "starting in automatic mode"); } try { getVehicleManager().setVehicleInterface( BluetoothVehicleInterface.class, deviceAddress); } catch(VehicleServiceException e) { Log.e(TAG, "Unable to start Bluetooth interface", e); } } } private void fillBluetoothDeviceList() { for(BluetoothDevice device : mBluetoothDeviceManager.getPairedDevices()) { mDiscoveredDevices.put(device.getAddress(), device.getName() + " (" + device.getAddress() + ")"); } persistCandidateDiscoveredDevices(); mBluetoothDeviceManager.startDiscovery(); } private BroadcastReceiver mDiscoveryReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { if(BluetoothDevice.ACTION_FOUND.equals(intent.getAction())) { BluetoothDevice device = intent.getParcelableExtra( BluetoothDevice.EXTRA_DEVICE); if(device.getBondState() != BluetoothDevice.BOND_BONDED) { String summary = device.getName() + " (" + device.getAddress() + ")"; Log.d(TAG, "Found unpaired device: " + summary); mDiscoveredDevices.put(device.getAddress(), summary); persistCandidateDiscoveredDevices(); } } } }; private void persistCandidateDiscoveredDevices() { // TODO I don't think the MULTI_PROCESS flag is necessary SharedPreferences.Editor editor = getContext().getSharedPreferences( DeviceManager.KNOWN_BLUETOOTH_DEVICE_PREFERENCES, Context.MODE_MULTI_PROCESS).edit(); Set<String> candidates = new HashSet<String>(); for(Map.Entry<String, String> device : mDiscoveredDevices.entrySet()) { if(device.getValue().startsWith( BluetoothVehicleInterface.DEVICE_NAME_PREFIX)) { candidates.add(device.getKey()); } } SupportSettingsUtils.putStringSet(editor, DeviceManager.KNOWN_BLUETOOTH_DEVICE_PREF_KEY, candidates); editor.commit(); } }