/*****************************************************************
BioZen
Copyright (C) 2011 The National Center for Telehealth and
Technology
Eclipse Public License 1.0 (EPL-1.0)
This library is free software; you can redistribute it and/or
modify it under the terms of the Eclipse Public License as
published by the Free Software Foundation, version 1.0 of the
License.
The Eclipse Public License is a reciprocal license, under
Section 3. REQUIREMENTS iv) states that source code for the
Program is available from such Contributor, and informs licensees
how to obtain it in a reasonable manner on or through a medium
customarily used for software exchange.
Post your updates and modifications to our GitHub or email to
t2@tee2.org.
This library is distributed WITHOUT ANY WARRANTY; without
the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the Eclipse Public License 1.0 (EPL-1.0)
for more details.
You should have received a copy of the Eclipse Public License
along with this library; if not,
visit http://www.opensource.org/licenses/EPL-1.0
*****************************************************************/
package com.t2.compassionMeditation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Vector;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import spine.SPINEFactory;
import spine.SPINEListener;
import spine.SPINEManager;
import spine.datamodel.Data;
import spine.datamodel.Node;
import spine.datamodel.ServiceMessage;
import spine.datamodel.functions.SpineServiceCommand;
import android.app.Activity;
import android.app.AlertDialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.os.Bundle;
import android.os.Messenger;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.ToggleButton;
import com.t2.SpineReceiver;
import com.t2.SpineReceiver.BioFeedbackStatus;
import com.t2.SpineReceiver.OnBioFeedbackMessageRecievedListener;
import com.t2.biofeedback.BioFeedbackService;
import com.t2.R;
import com.t2.compassionUtils.BioSensor;
import com.t2.compassionUtils.Util;
/**
* Activity used to manually administer list of available devices.
* This is instantiated in two conditions
* 1. When the user starts the service manually (without, or more accurately before the Spine server).
* 2. When the user selects "Manage Devices" from the Spine Server main menu.
*
* @author scott.coleman
*
*/
public class DeviceManagerActivity extends Activity
implements OnClickListener, SPINEListener, OnBioFeedbackMessageRecievedListener {
private static final String TAG = "BFDemo";
private static final int BLUETOOTH_SETTINGS = 987;
private ListView deviceList;
private ManagerItemAdapter deviceListAdapter;
private AlertDialog bluetoothDisabledDialog;
ArrayList<Messenger> mServerListeners = new ArrayList<Messenger>();
String mVersionName = "";
Boolean mBluetoothEnabled;
protected SharedPreferences sharedPref;
/**
* List of all currently PAIRED BioSensors
*/
private ArrayList<BioSensor> mBioSensors = new ArrayList<BioSensor>();
/**
* The Spine manager contains the bulk of the Spine server.
*/
private static SPINEManager mSpineManager;
/**
* This is a broadcast receiver. Note that this is used ONLY for command/status messages from the AndroidBTService
* All data from the service goes through the mail SPINE mechanism (received(Data data)).
*/
private SpineReceiver mCommandReceiver;
private DeviceManagerActivity mInstance;
String[] measureNames = {"OPne", "two"};
boolean toggleArray[] = new boolean[2];
AlertDialog mParamChooserAlert;
/**
* Handles UI button clicks
* @param v
*/
public void onButtonClick(final View v)
{
final int id = v.getId();
switch (id) {
// Present a list of all parameter types to allow the user
// to associate a specific device (address) to a specific parameter
// This association will be used in the main activities so that they
// know the BT address of particular sensors to tell them to start/stop
// The associations are stored in Shared pref
// key = param name (See R.array.parameter_names)
// value = BT address
case R.id.parameter:
final String address = (String)v.getTag();
AlertDialog.Builder alertParamChooser = new AlertDialog.Builder(mInstance);
alertParamChooser.setTitle("Select parameter for this device");
// mParamChooserAlert = alertParamChooser.create();
alertParamChooser.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
}
});
alertParamChooser.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
}
});
alertParamChooser.setSingleChoiceItems(R.array.parameter_names,-1, new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, final int which) {
String[] paramNamesStringArray = getResources().getStringArray(R.array.parameter_names);
final String name = paramNamesStringArray[which];
// See which pamrameter is associated with this name
String device = SharedPref.getDeviceForParam(mInstance, name);
if (device != null && which != 0) {
AlertDialog.Builder alertWarning = new AlertDialog.Builder(mInstance);
String message = String.format("Another sensor (%s) currently feeds this parameter. Please change this previous mapping before trying again", device );
alertWarning.setMessage(message);
alertWarning.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
mParamChooserAlert.dismiss();
}
});
alertWarning.setNegativeButton("Select Anyway", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
Log.d(TAG, "param name = " + name + ", address = " + address);
if (which != 0) {
SharedPref.setParamForDevice(mInstance, name, address);
v.getBackground().setColorFilter(Color.GREEN, PorterDuff.Mode.MULTIPLY);
((Button)v.findViewById(R.id.parameter)).setText(name);
}
else {
SharedPref.setParamForDevice(mInstance, name, address);
v.getBackground().setColorFilter(Color.LTGRAY, PorterDuff.Mode.MULTIPLY);
((Button)v.findViewById(R.id.parameter)).setText("Parameter");
}
}
});
alertWarning.show();
}
else {
// The parameter is currently not mapped to a sensor, so we can go ahead and map it
Log.d(TAG, "param name = " + name + ", address = " + address);
if (which != 0) {
SharedPref.setParamForDevice(mInstance, name, address);
v.getBackground().setColorFilter(Color.GREEN, PorterDuff.Mode.MULTIPLY);
((Button)v.findViewById(R.id.parameter)).setText(name);
}
else {
SharedPref.setParamForDevice(mInstance, name, address);
v.getBackground().setColorFilter(Color.LTGRAY, PorterDuff.Mode.MULTIPLY);
((Button)v.findViewById(R.id.parameter)).setText("Parameter");
}
}
}
});
// alert.show();
mParamChooserAlert = alertParamChooser.create();
mParamChooserAlert.show();
break;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mInstance = this;
sharedPref = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
//this.sendBroadcast(new Intent(BioFeedbackService.ACTION_SERVICE_START));
this.setContentView(R.layout.device_manager_activity_layout);
this.findViewById(R.id.bluetoothSettingsButton).setOnClickListener(this);
Resources resources = this.getResources();
AssetManager assetManager = resources.getAssets();
try {
mSpineManager = SPINEFactory.createSPINEManager("SPINETestApp.properties", resources);
} catch (InstantiationException e) {
Log.e(TAG, this.getClass().getSimpleName() + " Exception creating SPINE manager: " + e.toString());
e.printStackTrace();
}
// ... then we need to register a SPINEListener implementation to the SPINE manager instance
// to receive sensor node data from the Spine server
// (I register myself since I'm a SPINEListener implementation!)
mSpineManager.addListener(this);
// Create a broadcast receiver. Note that this is used ONLY for command messages from the service
// All data from the service goes through the mail SPINE mechanism (received(Data data)).
// See public void received(Data data)
this.mCommandReceiver = new SpineReceiver(this);
// Set up filter intents so we can receive broadcasts
IntentFilter filter = new IntentFilter();
filter.addAction("com.t2.biofeedback.service.status.BROADCAST");
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
filter.addAction(BioFeedbackService.ACTION_STATUS_BROADCAST);
this.registerReceiver(this.mCommandReceiver,filter);
// Tell the bluetooth service to send us a list of bluetooth devices and system status
// Response comes in public void onStatusReceived(BioFeedbackStatus bfs) STATUS_PAIRED_DEVICES
mSpineManager.pollBluetoothDevices();
// this.deviceManager = DeviceManager.getInstance(this.getBaseContext(), null);
this.deviceList = (ListView)this.findViewById(R.id.list);
this.deviceListAdapter = new ManagerItemAdapter(
this,
R.layout.manager_item
);
this.deviceList.setAdapter(this.deviceListAdapter);
this.bluetoothDisabledDialog = new AlertDialog.Builder(this)
.setMessage("Bluetooth is not enabled.")
.setOnCancelListener(new OnCancelListener(){
@Override
public void onCancel(DialogInterface dialog) {
finish();
}
})
.setPositiveButton("Setup Bluetooth", new DialogInterface.OnClickListener () {
@Override
public void onClick(DialogInterface dialog, int which) {
startBluetoothSettings();
}
})
.create();
try {
PackageManager packageManager = this.getPackageManager();
PackageInfo info = packageManager.getPackageInfo(this.getPackageName(), 0);
mVersionName = info.versionName;
Log.i(TAG, this.getClass().getSimpleName() + " Spine server Test Application Version " + mVersionName);
}
catch (NameNotFoundException e) {
Log.e(TAG, this.getClass().getSimpleName() + e.toString());
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (deviceListAdapter != null) {
deviceListAdapter.reloadItems();
}
switch(requestCode) {
case BLUETOOTH_SETTINGS:
break;
}
}
private void startBluetoothSettings() {
this.startActivityForResult(new Intent(android.provider.Settings.ACTION_BLUETOOTH_SETTINGS), BLUETOOTH_SETTINGS);
}
@Override
protected void onResume() {
super.onResume();
if(!BluetoothAdapter.getDefaultAdapter().isEnabled()) {
this.bluetoothDisabledDialog.show();
}
if (deviceListAdapter != null) {
deviceListAdapter.reloadItems();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
mSpineManager.removeListener(this);
this.unregisterReceiver(this.mCommandReceiver);
}
@Override
public void onClick(View v) {
switch(v.getId()) {
case R.id.bluetoothSettingsButton:
this.startBluetoothSettings();
break;
}
}
private class GeneralReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (deviceListAdapter != null)
deviceListAdapter.reloadItems();
}
}
/**
* Adapter class for sensor list
* @author scott.coleman
*
*/
private class ManagerItemAdapter extends ArrayAdapter<BioSensor> implements OnCheckedChangeListener {
private int layoutId;
private LayoutInflater layoutInflater;
private HashMap<View,String> viewDeviceMap = new HashMap<View,String>();
public ManagerItemAdapter(Context context, int textViewResourceId) {
super(context, textViewResourceId, new ArrayList<BioSensor>());
this.layoutId = textViewResourceId;
layoutInflater = (LayoutInflater)this.getContext().getSystemService(LAYOUT_INFLATER_SERVICE);
this.setNotifyOnChange(false);
this.reloadItems();
}
public void reloadItems() {
this.reloadItems(true);
}
private void reloadItems(boolean notify) {
this.clear();
for (BioSensor bioSensor: mBioSensors) {
this.add(bioSensor);
}
if(notify) {
this.notifyDataSetChanged();
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
BioSensor bioSensor = this.getItem(position);
View v = layoutInflater.inflate(this.layoutId, null);
if(bioSensor == null) {
return v;
}
String statusString = Util.connectionStatusToString(bioSensor.mConnectionStatus);
((TextView)v.findViewById(R.id.text1)).setText(bioSensor.mBTName);
((TextView)v.findViewById(R.id.status)).setText(statusString);
((ToggleButton)v.findViewById(R.id.enabled)).setChecked(bioSensor.mEnabled);
((ToggleButton)v.findViewById(R.id.enabled)).setOnCheckedChangeListener(this);
((ToggleButton)v.findViewById(R.id.enabled)).setTag(bioSensor.mBTAddress);
String param = SharedPref.getParamForDevice(mInstance, bioSensor.mBTAddress);
if (param != null) {
((Button)v.findViewById(R.id.parameter)).getBackground().setColorFilter(Color.GREEN, PorterDuff.Mode.MULTIPLY);
((Button)v.findViewById(R.id.parameter)).setText(param);
}
else {
((Button)v.findViewById(R.id.parameter)).getBackground().setColorFilter(Color.LTGRAY, PorterDuff.Mode.MULTIPLY);
((Button)v.findViewById(R.id.parameter)).setText("Parameter");
}
((Button)v.findViewById(R.id.parameter)).setTag(bioSensor.mBTAddress);
viewDeviceMap.put(v, bioSensor.mBTAddress);
return v;
}
@Override
public void onCheckedChanged(final CompoundButton buttonView,
boolean isChecked) {
if (buttonView.getId() == R.id.enabled) {
String address = (String)buttonView.getTag();
SpineServiceCommand serviceCommand = null;
serviceCommand = new SpineServiceCommand();
serviceCommand.setBtAddress(address);
if(isChecked) {
serviceCommand.setCommand(SpineServiceCommand.COMMAND_ENABLED);
// We technically don't have do to this because we'll receive a STATUS_PAIRED_DEVICES
// command from the service as a result of the service commena we send
// but we do it so the button updates immediately
// as opposed to waiting for a round trip to/from the service
updateBioSensorEnabled(address, true);
} else {
serviceCommand.setCommand(SpineServiceCommand.COMMAND_DISABLED);
updateBioSensorEnabled(address, false);
}
// We need to tell the service to enable or disable this sensor
mSpineManager.serviceCommand(serviceCommand);
this.reloadItems();
}
else if (buttonView.getId() == R.id.parameter) {
}
}
}
@Override
public void newNodeDiscovered(Node newNode) {
}
@Override
public void received(ServiceMessage msg) {
}
@Override
public void received(Data data) {
}
@Override
public void discoveryCompleted(Vector activeNodes) {
}
@Override
public void onStatusReceived(BioFeedbackStatus bfs) {
Log.d(TAG, this.getClass().getSimpleName() + ".onStatusReceived(" + bfs.messageId + ")");
String name = bfs.name;
if (name == null ) name = "sensor node";
if(bfs.messageId.equals("CONN_CONNECTING")) {
Log.i(TAG, this.getClass().getSimpleName() + " Received command : " + bfs.messageId + " to " + name );
updateBioSensorConnectionStatus(name, BioSensor.CONN_CONNECTING);
}
else if(bfs.messageId.equals("CONN_ANY_CONNECTED")) {
Log.i(TAG, this.getClass().getSimpleName() + " Received command : " + bfs.messageId + " to " + name );
updateBioSensorConnectionStatus(name, BioSensor.CONN_CONNECTED);
}
else if(bfs.messageId.equals("CONN_CONNECTION_LOST")) {
Log.i(TAG, this.getClass().getSimpleName() + " Received command : " + bfs.messageId + " to " + name );
updateBioSensorConnectionStatus(name, BioSensor.CONN_PAIRED);
}
else if(bfs.messageId.equals("STATUS_PAIRED_DEVICES")) {
// bfs.address contains a json arrary containing status of all BT Devices
Log.i(TAG, this.getClass().getSimpleName() + " Received command : " + bfs.messageId + " to " + name );
Log.i(TAG, this.getClass().getSimpleName() + bfs.address );
// Populate the mSensors array with a list of all paired sensors
populateBioSensors(bfs.address);
}
if (deviceListAdapter != null)
deviceListAdapter.reloadItems();
}
/**
* Receives a json string containing data about all of the paired sensors
* the adds a new BioSensor for each one to the mBioSensors collection
*
* @param jsonString String containing info on all paired devices
*/
private void populateBioSensors(String jsonString) {
Log.d(TAG, this.getClass().getSimpleName() + " populateBioSensors");
// Now clear it out and Re-populate it.
mBioSensors.clear();
try {
JSONArray jsonArray = new JSONArray(jsonString);
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
Boolean enabled = jsonObject.getBoolean("enabled");
String name = jsonObject.getString("name");
String address = jsonObject.getString("address");
int connectionStatus = jsonObject.getInt("connectionStatus");
if (name.equalsIgnoreCase("system")) {
mBluetoothEnabled = enabled;
}
else {
Log.i(TAG, "Adding sensor " + name + ", " + address + (enabled ? ", enabled":", disabled") + " : " + Util.connectionStatusToString(connectionStatus));
Log.i(TAG, this.getClass().getSimpleName() + " Adding sensor " + name + ", " + address + (enabled ? ", enabled":", disabled"));
BioSensor bioSensor = new BioSensor(name, address, enabled);
bioSensor.mConnectionStatus = connectionStatus;
mBioSensors.add(bioSensor);
}
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
}
}
void updateBioSensorConnectionStatus(String name, int status) {
for (BioSensor bioSensor: mBioSensors) {
if (bioSensor.mBTName.equalsIgnoreCase(name)) {
bioSensor.mConnectionStatus = status;
}
}
}
void updateBioSensorEnabled(String address,Boolean enabled) {
for (BioSensor bioSensor: mBioSensors) {
if (bioSensor.mBTAddress.equalsIgnoreCase(address)) {
bioSensor.mEnabled = enabled;
}
}
}
}