/** * Copyright (C) 2013 - 2015 the enviroCar community * * This file is part of the enviroCar app. * * The enviroCar app is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The enviroCar app is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. * * You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ package org.envirocar.app.view.preferences; import android.app.AlertDialog; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.content.DialogInterface; import android.preference.DialogPreference; import android.support.v7.widget.Toolbar; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import com.squareup.otto.Bus; import com.squareup.otto.Subscribe; import org.envirocar.app.R; import org.envirocar.app.handler.BluetoothHandler; import org.envirocar.app.view.preferences.bluetooth.BluetoothDeviceListAdapter; import org.envirocar.core.events.bluetooth.BluetoothPairingChangedEvent; import org.envirocar.core.events.bluetooth.BluetoothStateChangedEvent; import org.envirocar.core.injection.Injector; import org.envirocar.core.logging.Logger; import java.util.Set; import javax.inject.Inject; import butterknife.ButterKnife; import butterknife.InjectView; import rx.Subscriber; import rx.Subscription; import rx.android.schedulers.AndroidSchedulers; import rx.schedulers.Schedulers; /** * @author dewall */ public class BluetoothPairingPreference extends DialogPreference { private static final Logger LOGGER = Logger.getLogger(BluetoothPairingPreference.class); // Views for the already paired devices. @InjectView(R.id.bluetooth_pairing_preference_paired_devices_text) public TextView mPairedDevicesTextView; @InjectView(R.id.bluetooth_pairing_preference_paired_devices_list) public ListView mPairedDevicesListView; // Views for the newly discovered devices. @InjectView(R.id.bluetooth_pairing_preference_available_devices_text) public TextView mNewDevicesTextView; @InjectView(R.id.bluetooth_pairing_preference_available_devices_list) public ListView mNewDevicesListView; // No device found. @InjectView(R.id.bluetooth_pairing_preference_available_devices_info) public TextView mNewDevicesInfoTextView; @InjectView(R.id.bluetooth_pairing_preference_search_devices_progressbar) public ProgressBar mProgressBar; // Injected variables. @Inject protected Bus mBus; @Inject protected BluetoothHandler mBluetoothHandler; // Main parent view for the content. @InjectView(R.id.bluetooth_pairing_preference_content) protected LinearLayout mContentView; // ArrayAdapter for the two different list views. private BluetoothDeviceListAdapter mNewDevicesArrayAdapter; private BluetoothDeviceListAdapter mPairedDevicesAdapter; /** * Constructor. * * @param context the Context of the current scope. * @param attrs the attribute set. */ public BluetoothPairingPreference(Context context, AttributeSet attrs) { super(context, attrs); // Inject fields. ((Injector) context.getApplicationContext()).injectObjects(this); // Set the layout of the dialog to show. setDialogLayoutResource(R.layout.bluetooth_pairing_preference); } /** * Binds views in the content View of the dialog to data. * * @param view the content view of the dialog. */ @Override protected void onBindDialogView(final View view) { super.onBindDialogView(view); // Inject all views. ButterKnife.inject(this, view); // Initialize the array adapter for both list views mNewDevicesArrayAdapter = new BluetoothDeviceListAdapter(getContext(), R.layout.bluetooth_pairing_preference_device_name, false); mPairedDevicesAdapter = new BluetoothDeviceListAdapter(getContext(), R.layout.bluetooth_pairing_preference_device_name, true); // Set the adapter for both list views mNewDevicesListView.setAdapter(mNewDevicesArrayAdapter); mPairedDevicesListView.setAdapter(mPairedDevicesAdapter); // Initialize the toolbar and the menu entry. Toolbar toolbar = (Toolbar) view.findViewById(R.id.bluetooth_pairing_preference_toolbar); toolbar.setTitle(R.string.bluetooth_pairing_preference_toolbar_title); toolbar.setNavigationIcon(R.drawable.ic_bluetooth_white_24dp); toolbar.inflateMenu(R.menu.menu_select_bluetooth_preference); toolbar.setTitleTextColor(getContext().getResources().getColor(R.color .white_cario)); toolbar.setOnMenuItemClickListener(item -> { switch (item.getItemId()) { case R.id.menu_action_search_bluetooth_devices: startBluetoothDiscovery(); return true; default: break; } return false; }); // Updates the list of already paired devices. updatePairedDevicesList(); mNewDevicesListView.setOnItemClickListener((parent, view1, position, id) -> { final BluetoothDevice device = mNewDevicesArrayAdapter.getItem(position); View contentView = LayoutInflater.from(getContext()).inflate(R.layout .bluetooth_pairing_preference_device_pairing_dialog, null, false); // Set toolbar style Toolbar toolbar1 = (Toolbar) contentView.findViewById(R.id .bluetooth_selection_preference_pairing_dialog_toolbar); toolbar1.setTitle(R.string.bluetooth_pairing_preference_toolbar_title); toolbar1.setNavigationIcon(R.drawable.ic_bluetooth_white_24dp); toolbar1.setTitleTextColor(getContext().getResources().getColor(R.color .white_cario)); // Set text view TextView textview = (TextView) contentView.findViewById(R.id .bluetooth_selection_preference_pairing_dialog_text); textview.setText(String.format("Do you want to pair with %s?", device.getName())); // Create the Dialog new AlertDialog.Builder(getContext()) .setView(contentView) .setPositiveButton("Pair Device", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // If this button is clicked, pair with the given device view1.setClickable(false); pairDevice(device, view1); } }) .setNegativeButton("Cancel", null) // Nothing to do on cancel .create() .show(); }); // Set an onClickListener for items in the paired devices list. mPairedDevicesListView.setOnItemClickListener((parent, view1, position, id) -> { final BluetoothDevice device = mPairedDevicesAdapter.getItem(position); View contentView = LayoutInflater.from(getContext()).inflate(R.layout .bluetooth_pairing_preference_device_pairing_dialog, null, false); // Set toolbar style Toolbar toolbar1 = (Toolbar) contentView.findViewById(R.id .bluetooth_selection_preference_pairing_dialog_toolbar); toolbar1.setTitle("Bluetooth Device"); toolbar1.setNavigationIcon(R.drawable.ic_bluetooth_white_24dp); toolbar1.setTitleTextColor(getContext().getResources().getColor(R.color .white_cario)); // Set text view TextView textview = (TextView) contentView.findViewById(R.id .bluetooth_selection_preference_pairing_dialog_text); textview.setText(String.format("Do you want to remove the pairing with %s?", device .getName())); // Create the AlertDialog. new AlertDialog.Builder(getContext()) .setView(contentView) .setPositiveButton(R.string.bluetooth_pairing_preference_dialog_remove_pairing, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { LOGGER.debug("OnPositiveButton clicked for remove pairing."); unpairDevice(device); } }) .setNegativeButton(R.string.menu_cancel, null) // Nothing to do on // cancel. .create() .show(); }); // Register this object on the event bus mBus.register(this); // Start the discovery of bluetooth devices. updateContentView(); } @Override public void onDismiss(DialogInterface dialog) { // Stop the discovery if active. mBluetoothHandler.stopBluetoothDeviceDiscovery(); // Unregister this instance from the eventBus mBus.unregister(this); super.onDismiss(dialog); } @Subscribe public void onBluetoothStateChangedEvent(BluetoothStateChangedEvent event) { LOGGER.debug("onBluetoothStateChangedEvent(): " + event.toString()); updateContentView(); } /** * Updates the content view. */ private void updateContentView() { if (!mBluetoothHandler.isBluetoothEnabled()) { // Bluetooth is not enabled. Disable the content view and update the info text view. mContentView.setVisibility(View.GONE); mNewDevicesArrayAdapter.clear(); mPairedDevicesAdapter.clear(); mNewDevicesInfoTextView.setText("Bluetooth is disabled."); } else { // Bluetooth is enabled. Show the content view, update the list, and start the // discovery of Bluetooth devices. mNewDevicesArrayAdapter.clear(); mPairedDevicesAdapter.clear(); mContentView.setVisibility(View.VISIBLE); updatePairedDevicesList(); startBluetoothDiscovery(); } } /** * Updates the list of already paired devices. */ private void updatePairedDevicesList() { // Get the set of paired devices. Set<BluetoothDevice> pairedDevices = mBluetoothHandler.getPairedBluetoothDevices(); // For each device, add an entry to the list view. mPairedDevicesAdapter.addAll(pairedDevices); // Make the paired devices textview visible if there are paired devices if (!pairedDevices.isEmpty()) { mPairedDevicesTextView.setVisibility(View.VISIBLE); } } /** * Initiates the discovery of other Bluetooth devices. */ private void startBluetoothDiscovery() { // If bluetooth is not enabled, skip the discovery and show a toast. if (!mBluetoothHandler.isBluetoothEnabled()) { LOGGER.debug("startBluetoothDiscovery(): Bluetooth is disabled!"); Toast.makeText(getContext(), "Bluetooth is disabled. Please enable Bluetooth before " + "discovering for other devices.", Toast.LENGTH_LONG).show(); return; } // Before starting a fresh discovery of bluetooth devices, clear // the current adapter. mNewDevicesArrayAdapter.clear(); // Subscription sub = mBluetoothHandler.startBluetoothDeviceDiscoveryObservable(true) Subscription sub = mBluetoothHandler.startBluetoothDiscoveryOnlyUnpaired() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( new Subscriber<BluetoothDevice>() { @Override public void onStart() { LOGGER.info("Blutooth discovery started."); // Show the progressbar mProgressBar.setVisibility(View.VISIBLE); // Set info view to "searching...". mNewDevicesInfoTextView.setText(R.string .bluetooth_pairing_preference_info_searching_devices); Toast.makeText(getContext(), "Discovery Started!", Toast .LENGTH_LONG).show(); } @Override public void onCompleted() { LOGGER.info("Bluetooth discovery finished."); // Dismiss the progressbar. mProgressBar.setVisibility(View.GONE); // If no devices found, set the corresponding textview to visibile. if (mNewDevicesArrayAdapter.isEmpty()) { mNewDevicesInfoTextView.setText(R.string .select_bluetooth_preference_info_no_device_found); } else if (mNewDevicesArrayAdapter.getCount() == 1) { mNewDevicesInfoTextView.setText(R.string .bluetooth_pairing_preference_info_device_found); } else { String string = getContext().getString(R.string .bluetooth_pairing_preference_info_devices_found); mNewDevicesInfoTextView.setText(String.format(string, "" + mNewDevicesArrayAdapter.getCount())); } Toast.makeText(getContext(), "Discovery Finished!", Toast .LENGTH_LONG).show(); } @Override public void onError(Throwable e) { LOGGER.error("Error while discovering bluetooth devices", e); } @Override public void onNext(BluetoothDevice device) { LOGGER.info(String.format("Bluetooth device detected: [name=%s, address=%s]", device.getName(), device.getAddress())); // if the discovered device is not already part of the list, then // add it to the list and add an entry to the array adapter. if (!mPairedDevicesAdapter.contains(device) && !mNewDevicesArrayAdapter.contains(device)) { mNewDevicesArrayAdapter.add(device); // } } } } ); } /** * Initiates the pairing process to a given device. * * @param device the device to pair to. * @param view the view of the listview entry. */ private void pairDevice(BluetoothDevice device, final View view) { final TextView text = (TextView) view.findViewById(R.id .bluetooth_selection_preference_device_list_entry_text); mBluetoothHandler.pairDevice(device, new BluetoothHandler.BluetoothDevicePairingCallback() { @Override public void onPairingStarted(BluetoothDevice device) { Toast.makeText(getContext(), "Pairing Started", Toast.LENGTH_LONG).show(); if (text != null) { text.setText(device.getName() + " (Pairing started...)"); } } @Override public void onPairingError(BluetoothDevice device) { Toast.makeText(getContext(), "Pairing Error", Toast.LENGTH_LONG).show(); if (text != null) text.setText(device.getName()); } @Override public void onDevicePaired(BluetoothDevice device) { // Device is paired. Add it to the array adapter for paired devices and // remove it from the adapter for new devices. Toast.makeText(getContext(), "Paired", Toast.LENGTH_LONG).show(); mNewDevicesArrayAdapter.remove(device); mPairedDevicesAdapter.add(device); // Post an event to all registered handlers. mBus.post(new BluetoothPairingChangedEvent(device, true)); } }); } /** * @param device */ private void unpairDevice(BluetoothDevice device) { LOGGER.debug("unpairDevice(): remove the pairing for device " + device.getName()); // Call the unpairing procedure at the bluetoothhandler with a respective callback. mBluetoothHandler.unpairDevice(device, new BluetoothHandler .BluetoothDeviceUnpairingCallback() { @Override public void onDeviceUnpaired(BluetoothDevice device) { LOGGER.debug(String.format("unpairDevice(): %s successfully unpaired", device .getName())); // Remove the unpaired device if it is contained in the adapter. if (mPairedDevicesAdapter.contains(device)) { mPairedDevicesAdapter.remove(device); } // Posts an event to all registered handlers. mBus.post(new BluetoothPairingChangedEvent(device, false)); } @Override public void onUnpairingError(BluetoothDevice device) { LOGGER.debug(String.format("unpairDevice(): error while unpairing device %s", device.getName())); } }); } @Override protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { super.onPrepareDialogBuilder(builder); // Remove default preference title. builder.setTitle(null); } }