/*
Copyright (C) 2004-2006 Nokia Corporation
Copyright (C) 2008-2011, Dirk Trossen, airs@dirk-trossen.de
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation as version 2.1 of the License.
This program 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 Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this library; if not, write to the Free Software Foundation, Inc.,
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.airs.handlers;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Semaphore;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import com.airs.helper.SerialPortLogger;
import com.airs.helper.Waker;
import com.airs.platform.HandlerManager;
import com.airs.platform.History;
import com.airs.platform.SensorRepository;
import com.airs.*;
/**
* Class to read audio-related sensors, specifically the BN, BD, BT sensor
* @see Handler
*/
public class BeaconHandler implements Handler, Runnable
{
// BT stuff
private Context nors;
private BluetoothAdapter mBtAdapter = null;
private int no_devices = 0;
private byte[] no_readings = new byte[6];
private List<String> device_list;
// beacon data
private StringBuffer reading = null;
private boolean bt_enabled = true;
private boolean bt_ask = true;
private boolean bt_registered = false;
private boolean bt_registered2 = false;
private boolean bt_first = false;
private Thread runnable = null;
private boolean running = false, shutdown = false;
private Semaphore BT_semaphore = new Semaphore(1);
private Semaphore BN_semaphore = new Semaphore(1);
private Semaphore finished_semaphore = new Semaphore(1);
// private char EOL = 13;
// config data
private int polltime = 15000;
private long oldtime = 0;
/**
* Sleep function
* @param millis
*/
private void sleep(long millis)
{
Waker.sleep(millis);
}
private void wait(Semaphore sema)
{
try
{
sema.acquire();
}
catch(Exception e)
{
}
}
/**
* Method to acquire sensor data
* Here, start BT discovery thread in case it hasn't run yet
* @param sensor String of the sensor symbol
* @param query String of the query to be fulfilled - not used here
* @see com.airs.handlers.Handler#Acquire(java.lang.String, java.lang.String)
*/
public synchronized byte[] Acquire(String sensor, String query)
{
int i;
StringBuffer devices = null;
// are we shutting down?
if (shutdown == true)
return null;
// Discovery thread started?
if (runnable == null)
{
running = true;
runnable = new Thread(this);
runnable.start();
}
switch(sensor.charAt(1))
{
case 'T' :
// wait until new reading available
wait(BT_semaphore);
if (reading != null)
return reading.toString().getBytes();
else
return null;
case 'N' :
// wait until new reading available
wait(BN_semaphore);
no_readings[0] = (byte)sensor.charAt(0);
no_readings[1] = (byte)sensor.charAt(1);
no_readings[2] = (byte)((no_devices>>24) & 0xff);
no_readings[3] = (byte)((no_devices>>16) & 0xff);
no_readings[4] = (byte)((no_devices>>8) & 0xff);
no_readings[5] = (byte)(no_devices & 0xff);
return no_readings;
case 'D':
// otherwise read list and create reading buffer
devices = new StringBuffer("BD");
// if there's no device connected, we're finished!
if (device_list.size() == 0)
return devices.toString().getBytes();
synchronized(device_list)
{
boolean first_one = true;
for (i=0;i<device_list.size();i++)
{
// first device? -> then no \n at the end of it!
if (first_one == true)
first_one = false;
else
devices.append("\n");
// append the BluetoothDevice object from the list
devices.append(device_list.get(i));
}
return devices.toString().getBytes();
}
}
return null;
}
/**
* Method to share the last value of the given sensor
* @param sensor String of the sensor symbol to be shared
* @return human-readable string of the last sensor value
* @see com.airs.handlers.Handler#Share(java.lang.String)
*/
public String Share(String sensor)
{
switch(sensor.charAt(1))
{
case 'T' :
case 'N' :
return "There are currently " + String.valueOf(no_devices) + " BT devices around me!";
}
return null;
}
/**
* Method to view historical chart of the given sensor symbol
* @param sensor String of the symbol for which the history is being requested
* @see com.airs.handlers.Handler#History(java.lang.String)
*/
public void History(String sensor)
{
if (sensor.charAt(1) == 'N')
History.timelineView(nors, "BT devices [#]", "BN");
}
/**
* BT Discovery thread, being executed every polltime seconds, using the sleep() function to wait
* @see java.lang.Runnable#run()
*/
public void run()
{
long now;
while(running==true)
{
now = System.currentTimeMillis();
// try to discover when it's time to do so
if (oldtime+polltime<=now)
{
oldtime = now;
discover();
}
else
sleep(polltime - (now - oldtime));
}
}
/**
* Method to discover the sensor symbols support by this handler
* As the result of the discovery, appropriate {@link com.airs.platform.Sensor} entries will be added to the {@link com.airs.platform.SensorRepository}
* Here, we also check if the BT adapter is available and also enable it, in case it is disabled and it is configured (by the user) to switch BT on
* Furthermore, we register the receivers for getting BT connect and disconnects messages
* @see com.airs.handlers.Handler#Discover()
* @see com.airs.platform.Sensor
* @see com.airs.platform.SensorRepository
*/
public void Discover()
{
try
{
// Get the local Bluetooth adapter
mBtAdapter = BluetoothAdapter.getDefaultAdapter();
// if there's no BT adapter, return without putting sensors in repository
if (mBtAdapter == null)
return;
// and if there's no BT enabled, see if it is to be turned on automatically
if (mBtAdapter.isEnabled()==false)
{
// does user wants BT to be enabled?
if (bt_enabled == false)
return;
// shall we ask before enabling?
if (bt_ask==true)
{
Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
nors.startActivity(enableIntent);
}
else
mBtAdapter.enable();
}
// if it's there, add sensor
SensorRepository.insertSensor(new String("BT"), new String("MAC"), nors.getString(R.string.BT_d), nors.getString(R.string.BT_e), new String("txt"), 0, 0, 1, false, 0, this);
SensorRepository.insertSensor(new String("BN"), new String("#"), nors.getString(R.string.BN_d), nors.getString(R.string.BN_e), new String("int"), 0, 0, 50, true, 0, this);
SensorRepository.insertSensor(new String("BD"), new String("MAC"), nors.getString(R.string.BD_d), nors.getString(R.string.BD_e), new String("txt"), 0, 0, 50, true, polltime, this);
// now register for the connected device intents
IntentFilter filter1 = new IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED);
IntentFilter filter2 = new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED);
IntentFilter filter3 = new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED);
nors.registerReceiver(mReceiver2, filter1);
nors.registerReceiver(mReceiver2, filter2);
nors.registerReceiver(mReceiver2, filter3);
}
catch(Exception e)
{
SerialPortLogger.debug("BeaconHandler::cannot get localDevice()");
bt_enabled = false;
}
}
/**
* Constructor, allocating all necessary resources for the handler
* Here, reading the various RMS values of the preferences and arming the semaphores
* @param nors Reference to the calling {@link android.content.Context}
*/
public BeaconHandler(Context nors)
{
// store for later
this.nors = nors;
// read whether or not we need to enable Beacon
bt_enabled = HandlerManager.readRMS_b("BeaconHandler::BTON", false);
// should ask before enabling?
bt_ask = HandlerManager.readRMS_b("BeaconHandler::BTONAsk", false);
polltime = HandlerManager.readRMS_i("BeaconHandler::Poll", 30) * 1000;
// save current time and set so that first Acquire() will discover but substract a bit more to give BT time to fire up
oldtime = System.currentTimeMillis();
// create device list
device_list = new ArrayList<String>();
// arm the semaphores now
wait(BT_semaphore);
wait(BN_semaphore);
wait(finished_semaphore);
}
/**
* Method to release all handler resources
* Here, we interrupt the discovery thread and stop any ongoing BT discovery
* @see com.airs.handlers.Handler#destroyHandler()
*/
public void destroyHandler()
{
// we are shutting down !
shutdown = true;
// release all semaphores for unlocking the Acquire() threads
BT_semaphore.release();
BN_semaphore.release();
// signal thread to close down
if (running == true)
{
running = false;
runnable.interrupt();
}
// cancel any discovery
if (mBtAdapter != null)
if (mBtAdapter.isDiscovering() == true)
mBtAdapter.cancelDiscovery();
// cancel any discovery wait!
finished_semaphore.release();
// if (bt_registered == true)
// {
// try
// {
// nors.unregisterReceiver(mReceiver);
// }
// catch(Exception e)
// {
// bt_registered = false;
// }
// }
// are we listening to connected devices?
if (bt_registered2 == true)
nors.unregisterReceiver(mReceiver2);
}
private void discover()
{
if (bt_registered == false)
{
// Register for broadcasts when a device is discovered
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
nors.registerReceiver(mReceiver, filter);
// Register for broadcasts when discovery has finished
filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
nors.registerReceiver(mReceiver, filter);
bt_registered = true;
}
bt_first = true;
no_devices = 0;
reading = new StringBuffer("BT");
// start discovery
try
{
// Request discover from BluetoothAdapter
mBtAdapter.startDiscovery();
}
catch (Exception e)
{
return;
}
// sleep until finished
wait(finished_semaphore);
// signal availability of data
BT_semaphore.release();
BN_semaphore.release();
// unregister broadcast receiver
if (bt_registered == true)
{
try
{
nors.unregisterReceiver(mReceiver);
}
catch(Exception e)
{
}
}
// cancel any discovery that might be running due to multi-threading
if (mBtAdapter != null)
if (mBtAdapter.isDiscovering() == true)
mBtAdapter.cancelDiscovery();
bt_registered = false;
}
// The BroadcastReceiver that listens for discovered devices and
// changes the title when discovery is finished
private final BroadcastReceiver mReceiver = new BroadcastReceiver()
{
@Override
public void onReceive(Context context, Intent intent)
{
String action = intent.getAction();
// When discovery finds a device
if (BluetoothDevice.ACTION_FOUND.equals(action))
{
// first device? -> then no \n at the end of it!
if (bt_first == true)
bt_first = false;
else
reading.append("\n");
// Get the BluetoothDevice object from the Intent
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// If it's already paired, skip it, because it's been listed already
if (device.getName()!=null)
reading.append(device.getAddress() + "::" + device.getName().replaceAll("'","''"));
else
reading.append(device.getAddress() + ":: ");
no_devices ++;
}
if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action))
finished_semaphore.release();
}
};
// The BroadcastReceiver that listens for connected devices and
// changes the title when discovery is finished
private final BroadcastReceiver mReceiver2 = new BroadcastReceiver()
{
@Override
public void onReceive(Context context, Intent intent)
{
String action = intent.getAction();
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
int i;
String entry;
// If it's already paired, skip it, because it's been listed already
if (device.getName()!=null)
entry = new String(device.getAddress() + "::" + device.getName());
else
entry = new String(device.getAddress() + ":: ");
if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action))
{
// add connected device to list
device_list.add(entry);
}
if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action))
{
// remove disconnected device from list
for (i=0;i<device_list.size();i++)
if (device_list.get(i).compareTo(entry) == 0)
device_list.remove(i);
}
}
};
}