/**
* Copyright (C) 2013 - 2015 the enviroCar community
* <p>
* This file is part of the enviroCar app.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.obdselection;
import android.app.AlertDialog;
import android.bluetooth.BluetoothDevice;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.afollestad.materialdialogs.MaterialDialog;
import com.squareup.otto.Subscribe;
import org.envirocar.app.R;
import org.envirocar.app.handler.BluetoothHandler;
import org.envirocar.core.events.bluetooth.BluetoothPairingChangedEvent;
import org.envirocar.core.events.bluetooth.BluetoothStateChangedEvent;
import org.envirocar.core.injection.BaseInjectorFragment;
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.functions.Action0;
import rx.schedulers.Schedulers;
/**
* TODO JavaDoc
*
* @author dewall
*/
public class OBDSelectionFragment extends BaseInjectorFragment {
private static final Logger LOGGER = Logger.getLogger(OBDSelectionFragment.class);
/**
*
*/
public interface ShowSnackbarListener {
void showSnackbar(String text);
}
@Inject
protected BluetoothHandler mBluetoothHandler;
@InjectView(R.id.activity_obd_selection_layout_content)
protected View mContentView;
@InjectView(R.id.activity_obd_selection_layout_paired_devices_text)
protected TextView mPairedDevicesTextView;
@InjectView(R.id.activity_obd_selection_layout_paired_devices_list)
protected ListView mPairedDevicesListView;
@InjectView(R.id.activity_obd_selection_layout_available_devices_list)
protected ListView mNewDevicesListView;
@InjectView(R.id.activity_obd_selection_layout_search_devices_progressbar)
protected ProgressBar mProgressBar;
@InjectView(R.id.activity_obd_selection_layout_available_devices_info)
protected TextView mNewDevicesInfoTextView;
// ArrayAdapter for the two different list views.
private OBDDeviceListAdapter mNewDevicesArrayAdapter;
private OBDDeviceListAdapter mPairedDevicesAdapter;
private Subscription mBTDiscoverySubscription;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
savedInstanceState) {
// infalte the content view of this activity.
View contentView = inflater.inflate(R.layout.activity_obd_selection_fragment,
container, false);
// Inject all annotated views.
ButterKnife.inject(this, contentView);
// Setup the listviews, its adapters, and its onClick listener.
setupListViews();
// Setup the paired devices.
updatePairedDevicesList();
// Start the discovery of bluetooth devices.
updateContentView();
// // TODO: very ugly... Instead a dynamic LinearLayout should be used.
// setDynamicListHeight(mNewDevicesListView);
// setDynamicListHeight(mPairedDevicesListView);
return contentView;
}
@Override
public void onDestroy() {
if (mBTDiscoverySubscription != null && !mBTDiscoverySubscription.isUnsubscribed()) {
mBTDiscoverySubscription.unsubscribe();
}
super.onDestroy();
}
@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(R.string.obd_selection_bluetooth_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();
}
}
/**
* 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!");
showSnackbar(getString(R.string.obd_selection_bluetooth_disabled_snackbar));
return;
}
// Before starting a fresh discovery of bluetooth devices, clear
// the current adapter.
mNewDevicesArrayAdapter.clear();
mBTDiscoverySubscription = mBluetoothHandler.startBluetoothDiscoveryOnlyUnpaired()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnUnsubscribe(new Action0() {
@Override
public void call() {
LOGGER.info("Canceling bluetooth device discovery");
mBluetoothHandler.stopBluetoothDeviceDiscovery();
}
})
.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);
showSnackbar(getString(R.string.obd_selection_discovery_started));
}
@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 = getString(R.string
.bluetooth_pairing_preference_info_devices_found);
mNewDevicesInfoTextView.setText(String.format(string,
Integer.toString(mNewDevicesArrayAdapter.getCount())));
}
showSnackbar("Discovery Finished!");
}
@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);
}
}
}
);
}
private void setupListViews() {
BluetoothDevice selectedBTDevice = mBluetoothHandler.getSelectedBluetoothDevice();
// Initialize the array adapter for both list views
mNewDevicesArrayAdapter = new OBDDeviceListAdapter(getActivity(), false);
mPairedDevicesAdapter = new OBDDeviceListAdapter(getActivity(), true, new
OBDDeviceListAdapter.OnOBDListActionCallback() {
@Override
public void onOBDDeviceSelected(BluetoothDevice device) {
LOGGER.info(String.format("onOBDDeviceSelected(%s)", device.getName()));
// Set the bluetooth device as the selected device in shared preferences.
mBluetoothHandler.setSelectedBluetoothDevice(device);
// Show a snackbar
showSnackbar(String.format(getString(
R.string.obd_selection_is_selected_template), device.getName()));
}
@Override
public void onDeleteOBDDevice(BluetoothDevice device) {
LOGGER.info(String.format("onDeleteOBDDevice(%s)", device.getName()));
showUnpairingDialig(device);
}
}, selectedBTDevice);
// Set the adapter for both list views
mNewDevicesListView.setAdapter(mNewDevicesArrayAdapter);
mPairedDevicesListView.setAdapter(mPairedDevicesAdapter);
mNewDevicesListView.setOnItemClickListener((parent, view1, position, id) -> {
final BluetoothDevice device = mNewDevicesArrayAdapter.getItem(position);
View contentView = LayoutInflater.from(getActivity()).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(getActivity().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(getString(
R.string.obd_selection_dialog_pairing_content_template), device.getName()));
// Create the Dialog
new AlertDialog.Builder(getActivity())
.setView(contentView)
.setPositiveButton(R.string.obd_selection_dialog_pairing_title,
(dialog, which) -> {
// If this button is clicked, pair with the given device
view1.setClickable(false);
pairDevice(device, view1);
})
.setNegativeButton(R.string.cancel, null) // Nothing to do on cancel
.create()
.show();
});
}
private void showUnpairingDialig(BluetoothDevice device) {
View contentView = LayoutInflater.from(getActivity())
.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.obd_selection_dialog_delete_pairing_title);
toolbar1.setNavigationIcon(R.drawable.ic_bluetooth_white_24dp);
toolbar1.setTitleTextColor(
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(
getString(R.string.obd_selection_dialog_delete_pairing_content_template),
device.getName()));
// Create the AlertDialog.
new MaterialDialog.Builder(getActivity())
.customView(contentView, false)
.positiveText(R.string.bluetooth_pairing_preference_dialog_remove_pairing)
.negativeText(R.string.menu_cancel)
.callback(new MaterialDialog.ButtonCallback() {
@Override
public void onPositive(MaterialDialog dialog) {
LOGGER.debug("OnPositiveButton clicked to remove pairing.");
unpairDevice(device);
}
})
.show();
}
private void unpairDevice(BluetoothDevice device) {
// Try to unpair the device
mBluetoothHandler.unpairDevice(device,
new BluetoothHandler.BluetoothDeviceUnpairingCallback() {
@Override
public void onDeviceUnpaired(BluetoothDevice device) {
showSnackbar(String.format(
getString(R.string.obd_selection_device_unpaired_template),
device.getName() + " (" + device.getAddress() + ")"));
mPairedDevicesAdapter.remove(device);
}
@Override
public void onUnpairingError(BluetoothDevice device) {
showSnackbar(String.format(
getString(R.string.obd_selection_unpairing_error_template),
device.getName() + " (" + device.getAddress() + ")"));
}
});
}
/**
* 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);
mPairedDevicesAdapter.setSelectedBluetoothDevice(mBluetoothHandler
.getSelectedBluetoothDevice());
// Make the paired devices textview visible if there are paired devices
if (!pairedDevices.isEmpty()) {
mPairedDevicesTextView.setVisibility(View.VISIBLE);
}
}
/**
* 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) {
showSnackbar(getString(R.string.obd_selection_pairing_started));
if (text != null) {
text.setText(device.getName() + " (Pairing started...)");
}
}
@Override
public void onPairingError(BluetoothDevice device) {
if (getActivity() != null) {
Toast.makeText(getActivity(),
R.string.obd_selection_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.
showSnackbar(getString(R.string.obd_selection_pairing_success_template,
device.getName()));
mNewDevicesArrayAdapter.remove(device);
mPairedDevicesAdapter.add(device);
// Post an event to all registered handlers.
mBus.post(new BluetoothPairingChangedEvent(device, true));
}
});
}
private void setDynamicListHeight(ListView listView) {
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null) {
// pre-condition
return;
}
int totalHeight = 0;
for (int i = 0; i < listAdapter.getCount(); i++) {
View listItem = listAdapter.getView(i, null, listView);
listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight();
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
listView.setLayoutParams(params);
}
/**
* Shows a snackbar with a given text.
*
* @param text the text to show in the snackbar.
*/
private void showSnackbar(String text) {
if (getActivity() instanceof OBDSelectionFragment.ShowSnackbarListener)
((OBDSelectionFragment.ShowSnackbarListener) getActivity()).showSnackbar(text);
// else if(mContentView != null && mContentView.getContext() != null)
// Snackbar.make(mContentView, text, Snackbar.LENGTH_LONG).show();
}
}