So in my android application, I have an intent service which pings devices and finds whether they are online/offline.
When I start my IntentService my UI freezes(Debug points to when ping commands are being executed) in the Service.
Service is started from the parent activity after I get the response of a network call
loadFragment(printersFrag, Constants.CONTAINER_ACT_DASHBOARD, PrintersListingFragment.class.getSimpleName(), false, false, false);
serviceIntent = new Intent(this, PrinterPingIntentService.class);
serviceIntent.putExtra("PrinterList", printersResponse);
this.startService(serviceIntent);
The code for my IntentService is as follows:
public class PrinterPingIntentService extends IntentService {
/**
* The IP Address to ping
*/
private String msIPAddressToPing = null;
/**
* Countdown latch instance to decrement after the thread is done
*/
private CountDownLatch mCountDownLatch;
/**
* Handler to handle ping threads
*/
private PingHandler mPingThreadHandler = null;
/**
* Volatile count variable to manage the ping thread count
*/
private volatile int mnPingThreadCount = 0;
/**
* The currently list of valid IP Addresses
*/
private ConcurrentHashMap<String, Device> mPrinterMap = new ConcurrentHashMap<String, Device>();
public PrinterPingIntentService() {
super(PrinterPingIntentService.class.getName());
}
#Override
protected void onHandleIntent(#Nullable Intent intent) {
Bundle bundle = intent.getExtras();
PrintersResponseBean printerResponse = bundle.getParcelable("PrinterList");
for (int i = 0; i < printerResponse.getDevices().size(); i++) {
mPrinterMap.put(printerResponse.getDevices().get(i).getDeviceIP(), printerResponse.getDevices().get(i));
}
validatePrinterIP();
}
#Override
public void onCreate() {
super.onCreate();
/*
* Fire up the Ping handler
*/
mPingThreadHandler = new PingHandler();
}
/**
* Validate the PrinterIPs by pinging them
*
* #author
*/
private void validatePrinterIP() {
try {
mnPingThreadCount = 0;
mCountDownLatch = new CountDownLatch(mPrinterMap.size());
for (String sIP : mPrinterMap.keySet()) {
PingRunnable runnable = new PingRunnable(sIP, mCountDownLatch);
Thread thread = new Thread(runnable);
++mnPingThreadCount;
Log.d("BAT", "validatePrinterIP - Thread count - " + mnPingThreadCount);
thread.start();
}
} catch (Exception e) {
Log.d("BAT", "Exception validatePrinterIP - " + e.getMessage());
}
}
/**
* Runnable to make a ping to the given Ip Address
*
* #author
*/
public class PingRunnable implements Runnable {
////////////////////////////////// CLASS MEMBERS ///////////////////////////////////////////
/**
* The IP Address to ping
*/
private String msIPAddressToPing = null;
/**
* Countdown latch instance to decrement after the thread is done
*/
private CountDownLatch mCountDownLatch;
////////////////////////////////// CLASS METHODS ///////////////////////////////////////////
public PingRunnable(String sIPAddress, CountDownLatch latch) {
msIPAddressToPing = sIPAddress;
mCountDownLatch = latch;
}
#Override
public void run() {
try {
/*
* If the destination is not reachable, remove the IP address
* from the printer map and set the bundle value accordingly
*/
if (!pingURL(msIPAddressToPing)) {
Log.d("BAT", "Could not ping " + msIPAddressToPing + ". Removing from Map");
mPrinterMap.remove(msIPAddressToPing);
} else {
Log.d("BAT", "Could ping " + msIPAddressToPing + ". Present in Map");
}
} catch (Exception e) {
Log.d("BAT", "Exception in Ping Runnable - " + e.getMessage());
} finally {
mPingThreadHandler.sendEmptyMessage(0);
mCountDownLatch.countDown();
}
}
}
/**
* Static Handler class to handle messsages.
* Reduce the count by one each time we receive a message to keep
* track that all threads have returned
*
* #author
*/
public class PingHandler extends Handler {
#Override
public void handleMessage(Message msg) {
Log.d("BAT", "Returning thread..");
if (msg.what == 0) {
mnPingThreadCount--;
Log.d("BAT", "Thread Return count - " + mnPingThreadCount);
}
/*
Await Latch
*/
try {
mCountDownLatch.await();
} catch (InterruptedException e) {
Log.d("BAT", "InterruptedException PingHandler - " + e.getMessage());
}
if (mnPingThreadCount == 0) {
//////TEMP
Log.d("BAT", "All threads accounted for. Final Printer List...");
ArrayList<Device> onlinePrinters = new ArrayList<>();
for (String sIP : mPrinterMap.keySet()) {
onlinePrinters.add(mPrinterMap.get(sIP));
Log.d("BAT", "Printers Active " + sIP);
}
//send data back to fragment via localBroadcastReceiver
Intent localBroadcast = new Intent();
localBroadcast.putParcelableArrayListExtra("onlinePrinters", onlinePrinters);
localBroadcast.setAction("printer");
sendBroadcast(localBroadcast);
}
}
}
/**
* Ping a device. First we try the usual isReachable method. If that does not work,
* we go with the Ping command execution
*
* #param sURL THe uRL / IP Address to ping
* #author
*/
public boolean pingURL(String sURL) {
try {
Log.d("BAT", "Pinging IP sURL");
//First try with isReachable
if (Inet4Address.getByName(sURL).isReachable(1000)) {
Log.d("BAT", "Host Reachable by InetAddress " + sURL);
return true;
}
//else try and ping. If neither works, we return false
else {
Log.d("BAT", "Host Not Reachable by InetAddress. Pinging IP with RunTime... " + sURL);
StringBuffer echo = new StringBuffer();
Runtime runtime = Runtime.getRuntime();
Process proc = runtime.exec("ping -c 1 " + sURL);
// "/system/bin/ping -c 8 " + sURL
int nReturnVal = proc.waitFor();
Log.d("BAT", "Done Pinging - " + sURL + ((nReturnVal == 0) ? " Successful" : " Unsuccessful"));
return (nReturnVal == 0);
}
} catch (IOException e) {
Log.d("BAT", "IOEXception in pingURL - " + e.getMessage().toString());
} catch (InterruptedException e) {
Log.d("BAT", "InterruptedException in pingURL - " + e.getMessage());
} catch (Exception e) {
Log.d("BAT", "EXception in pingURL - " + e.getMessage());
}
return false;
}
}
From my intent service, I send back the data of active devices to my Fragment using:
//send data back to fragment via localBroadcastReceiver
Intent localBroadcast = new Intent();
localBroadcast.putParcelableArrayListExtra("onlinePrinters", onlinePrinters);
localBroadcast.setAction("printer");
sendBroadcast(localBroadcast);
and extract this info in my Fragment using:
IntentFilter filter = new IntentFilter();
filter.addAction("printer");
updateUIReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
//UI update here
Bundle bundle = intent.getExtras();
if (bundle.get("onlinePrinters") != null) {
onlinePrinters = (ArrayList) bundle.get("onlinePrinters");
setPrinterStatus();
}
}
};
As I'm using an IntentService a UI freeze should be unlikely as the task is performed on a worker thread and not in the Main Thread.
Not able to figure out the root cause of the UI freeze
onCreate() method of your service is called on the main thread.
PingHandler instance that you're creating there is associated with the main thread.
So handleMessage for this handler is also executed on the main thread. You seem to have blocking operations there which may be the cause of your problem.
Related
Currently I've been using this reference http://blog.lemberg.co.uk/how-guide-obdii-reader-app-development but I am still confused on what the next steps are for my Android app to be able to connect to the OBDII. I found code online for a simple bluetooth app. The app is able to form bluetooth connections with other Android phones but not with the OBDII.
public class MainActivity extends Activity {
Button b1,b2,b3,b4;
private BluetoothAdapter BA;
private Set<BluetoothDevice>pairedDevices;
ListView lv;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
b1 = (Button) findViewById(R.id.button);
b2=(Button)findViewById(R.id.button2);
b3=(Button)findViewById(R.id.button3);
b4=(Button)findViewById(R.id.button4);
BA = BluetoothAdapter.getDefaultAdapter();
lv = (ListView)findViewById(R.id.listView);
}
public void on(View v) {
if (!BA.isEnabled()) {
Intent turnOn = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(turnOn, 0);
Toast.makeText(getApplicationContext(), "Turned on", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(getApplicationContext(), "Already on", Toast.LENGTH_LONG).show();
}
}
public void off(View v) {
BA.disable();
Toast.makeText(getApplicationContext(), "Turned off", Toast.LENGTH_LONG).show();
}
public void visible(View v) {
Intent getVisible = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
startActivityForResult(getVisible, 0);
}
public void list(View v) {
pairedDevices = BA.getBondedDevices();
ArrayList list = new ArrayList();
for (BluetoothDevice bt : pairedDevices) list.add(bt.getName());
Toast.makeText(getApplicationContext(), "Showing Paired Devices", Toast.LENGTH_SHORT).show();
final ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, list);
lv.setAdapter(adapter);
}
Moving forward, what must be done in order for the app to be able to successfully establish and maintain a bluetooth connection with the OBDII.
Check android-obd-reader OBDII example, which helps you to connect OBDII with your device.
It is using OBD-II Java API to connect and read data from obd.
Using below classes you can manage connection and read data from obd.
BluetoothManager.java : It's helps you to connect device
public class BluetoothManager {
private static final String TAG = BluetoothManager.class.getName();
/*
* http://developer.android.com/reference/android/bluetooth/BluetoothDevice.html
* #createRfcommSocketToServiceRecord(java.util.UUID)
*
* "Hint: If you are connecting to a Bluetooth serial board then try using the
* well-known SPP UUID 00001101-0000-1000-8000-00805F9B34FB. However if you
* are connecting to an Android peer then please generate your own unique
* UUID."
*/
private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
/**
* Instantiates a BluetoothSocket for the remote device and connects it.
* <p/>
* See http://stackoverflow.com/questions/18657427/ioexception-read-failed-socket-might-closed-bluetooth-on-android-4-3/18786701#18786701
*
* #param dev The remote device to connect to
* #return The BluetoothSocket
* #throws IOException
*/
public static BluetoothSocket connect(BluetoothDevice dev) throws IOException {
BluetoothSocket sock = null;
BluetoothSocket sockFallback = null;
Log.d(TAG, "Starting Bluetooth connection..");
try {
sock = dev.createRfcommSocketToServiceRecord(MY_UUID);
sock.connect();
} catch (Exception e1) {
Log.e(TAG, "There was an error while establishing Bluetooth connection. Falling back..", e1);
Class<?> clazz = sock.getRemoteDevice().getClass();
Class<?>[] paramTypes = new Class<?>[]{Integer.TYPE};
try {
Method m = clazz.getMethod("createRfcommSocket", paramTypes);
Object[] params = new Object[]{Integer.valueOf(1)};
sockFallback = (BluetoothSocket) m.invoke(sock.getRemoteDevice(), params);
sockFallback.connect();
sock = sockFallback;
} catch (Exception e2) {
Log.e(TAG, "Couldn't fallback while establishing Bluetooth connection.", e2);
throw new IOException(e2.getMessage());
}
}
return sock;
}
}
ObdGatewayService.java : It have connection code and reading data from obd.
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;
import com.github.pires.obd.commands.protocol.EchoOffCommand;
import com.github.pires.obd.commands.protocol.LineFeedOffCommand;
import com.github.pires.obd.commands.protocol.ObdResetCommand;
import com.github.pires.obd.commands.protocol.SelectProtocolCommand;
import com.github.pires.obd.commands.protocol.TimeoutCommand;
import com.github.pires.obd.commands.temperature.AmbientAirTemperatureCommand;
import com.github.pires.obd.enums.ObdProtocols;
import com.github.pires.obd.exceptions.UnsupportedCommandException;
import com.github.pires.obd.reader.R;
import com.github.pires.obd.reader.activity.ConfigActivity;
import com.github.pires.obd.reader.activity.MainActivity;
import com.github.pires.obd.reader.io.ObdCommandJob.ObdCommandJobState;
import com.google.inject.Inject;
import java.io.File;
import java.io.IOException;
/**
* This service is primarily responsible for establishing and maintaining a
* permanent connection between the device where the application runs and a more
* OBD Bluetooth interface.
* <p/>
* Secondarily, it will serve as a repository of ObdCommandJobs and at the same
* time the application state-machine.
*/
public class ObdGatewayService extends AbstractGatewayService {
private static final String TAG = ObdGatewayService.class.getName();
#Inject
SharedPreferences prefs;
private BluetoothDevice dev = null;
private BluetoothSocket sock = null;
public void startService() throws IOException {
Log.d(TAG, "Starting service..");
// get the remote Bluetooth device
final String remoteDevice = prefs.getString(ConfigActivity.BLUETOOTH_LIST_KEY, null);
if (remoteDevice == null || "".equals(remoteDevice)) {
Toast.makeText(ctx, getString(R.string.text_bluetooth_nodevice), Toast.LENGTH_LONG).show();
// log error
Log.e(TAG, "No Bluetooth device has been selected.");
// TODO kill this service gracefully
stopService();
throw new IOException();
} else {
final BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
dev = btAdapter.getRemoteDevice(remoteDevice);
/*
* Establish Bluetooth connection
*
* Because discovery is a heavyweight procedure for the Bluetooth adapter,
* this method should always be called before attempting to connect to a
* remote device with connect(). Discovery is not managed by the Activity,
* but is run as a system service, so an application should always call
* cancel discovery even if it did not directly request a discovery, just to
* be sure. If Bluetooth state is not STATE_ON, this API will return false.
*
* see
* http://developer.android.com/reference/android/bluetooth/BluetoothAdapter
* .html#cancelDiscovery()
*/
Log.d(TAG, "Stopping Bluetooth discovery.");
btAdapter.cancelDiscovery();
showNotification(getString(R.string.notification_action), getString(R.string.service_starting), R.drawable.ic_btcar, true, true, false);
try {
startObdConnection();
} catch (Exception e) {
Log.e(
TAG,
"There was an error while establishing connection. -> "
+ e.getMessage()
);
// in case of failure, stop this service.
stopService();
throw new IOException();
}
showNotification(getString(R.string.notification_action), getString(R.string.service_started), R.drawable.ic_btcar, true, true, false);
}
}
/**
* Start and configure the connection to the OBD interface.
* <p/>
* See http://stackoverflow.com/questions/18657427/ioexception-read-failed-socket-might-closed-bluetooth-on-android-4-3/18786701#18786701
*
* #throws IOException
*/
private void startObdConnection() throws IOException {
Log.d(TAG, "Starting OBD connection..");
isRunning = true;
try {
sock = BluetoothManager.connect(dev);
} catch (Exception e2) {
Log.e(TAG, "There was an error while establishing Bluetooth connection. Stopping app..", e2);
stopService();
throw new IOException();
}
// Let's configure the connection.
Log.d(TAG, "Queueing jobs for connection configuration..");
queueJob(new ObdCommandJob(new ObdResetCommand()));
//Below is to give the adapter enough time to reset before sending the commands, otherwise the first startup commands could be ignored.
try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
queueJob(new ObdCommandJob(new EchoOffCommand()));
/*
* Will send second-time based on tests.
*
* TODO this can be done w/o having to queue jobs by just issuing
* command.run(), command.getResult() and validate the result.
*/
queueJob(new ObdCommandJob(new EchoOffCommand()));
queueJob(new ObdCommandJob(new LineFeedOffCommand()));
queueJob(new ObdCommandJob(new TimeoutCommand(62)));
// Get protocol from preferences
final String protocol = prefs.getString(ConfigActivity.PROTOCOLS_LIST_KEY, "AUTO");
queueJob(new ObdCommandJob(new SelectProtocolCommand(ObdProtocols.valueOf(protocol))));
// Job for returning dummy data
queueJob(new ObdCommandJob(new AmbientAirTemperatureCommand()));
queueCounter = 0L;
Log.d(TAG, "Initialization jobs queued.");
}
/**
* This method will add a job to the queue while setting its ID to the
* internal queue counter.
*
* #param job the job to queue.
*/
#Override
public void queueJob(ObdCommandJob job) {
// This is a good place to enforce the imperial units option
job.getCommand().useImperialUnits(prefs.getBoolean(ConfigActivity.IMPERIAL_UNITS_KEY, false));
// Now we can pass it along
super.queueJob(job);
}
/**
* Runs the queue until the service is stopped
*/
protected void executeQueue() throws InterruptedException {
Log.d(TAG, "Executing queue..");
while (!Thread.currentThread().isInterrupted()) {
ObdCommandJob job = null;
try {
job = jobsQueue.take();
// log job
Log.d(TAG, "Taking job[" + job.getId() + "] from queue..");
if (job.getState().equals(ObdCommandJobState.NEW)) {
Log.d(TAG, "Job state is NEW. Run it..");
job.setState(ObdCommandJobState.RUNNING);
if (sock.isConnected()) {
job.getCommand().run(sock.getInputStream(), sock.getOutputStream());
} else {
job.setState(ObdCommandJobState.EXECUTION_ERROR);
Log.e(TAG, "Can't run command on a closed socket.");
}
} else
// log not new job
Log.e(TAG,
"Job state was not new, so it shouldn't be in queue. BUG ALERT!");
} catch (InterruptedException i) {
Thread.currentThread().interrupt();
} catch (UnsupportedCommandException u) {
if (job != null) {
job.setState(ObdCommandJobState.NOT_SUPPORTED);
}
Log.d(TAG, "Command not supported. -> " + u.getMessage());
} catch (IOException io) {
if (job != null) {
if(io.getMessage().contains("Broken pipe"))
job.setState(ObdCommandJobState.BROKEN_PIPE);
else
job.setState(ObdCommandJobState.EXECUTION_ERROR);
}
Log.e(TAG, "IO error. -> " + io.getMessage());
} catch (Exception e) {
if (job != null) {
job.setState(ObdCommandJobState.EXECUTION_ERROR);
}
Log.e(TAG, "Failed to run command. -> " + e.getMessage());
}
if (job != null) {
final ObdCommandJob job2 = job;
((MainActivity) ctx).runOnUiThread(new Runnable() {
#Override
public void run() {
((MainActivity) ctx).stateUpdate(job2);
}
});
}
}
}
/**
* Stop OBD connection and queue processing.
*/
public void stopService() {
Log.d(TAG, "Stopping service..");
notificationManager.cancel(NOTIFICATION_ID);
jobsQueue.clear();
isRunning = false;
if (sock != null)
// close socket
try {
sock.close();
} catch (IOException e) {
Log.e(TAG, e.getMessage());
}
// kill service
stopSelf();
}
public boolean isRunning() {
return isRunning;
}
public static void saveLogcatToFile(Context context, String devemail) {
Intent emailIntent = new Intent(Intent.ACTION_SEND);
emailIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
emailIntent.setType("text/plain");
emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{devemail});
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "OBD2 Reader Debug Logs");
StringBuilder sb = new StringBuilder();
sb.append("\nManufacturer: ").append(Build.MANUFACTURER);
sb.append("\nModel: ").append(Build.MODEL);
sb.append("\nRelease: ").append(Build.VERSION.RELEASE);
emailIntent.putExtra(Intent.EXTRA_TEXT, sb.toString());
String fileName = "OBDReader_logcat_" + System.currentTimeMillis() + ".txt";
File sdCard = Environment.getExternalStorageDirectory();
File dir = new File(sdCard.getAbsolutePath() + File.separator + "OBD2Logs");
if (dir.mkdirs()) {
File outputFile = new File(dir, fileName);
Uri uri = Uri.fromFile(outputFile);
emailIntent.putExtra(Intent.EXTRA_STREAM, uri);
Log.d("savingFile", "Going to save logcat to " + outputFile);
//emailIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(Intent.createChooser(emailIntent, "Pick an Email provider").addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
try {
#SuppressWarnings("unused")
Process process = Runtime.getRuntime().exec("logcat -f " + outputFile.getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
I hope it helps you. Please leave comment or edit your question if more help require.
I'am able to connect to device and ask device specifications:
specs:
Model: /dev/bus/usb/001/002
ID: 1002
Class: 0
Protocol: 0
Vendor ID 1155
Product ID: 22352
Interface count: 1
---------------------------------------
***** *****
Interface index: 0
Interface ID: 0
Inteface class: 3 USB_CLASS_HID
Interface protocol: 0
Endpoint count: 2
++++ ++++ ++++
Endpoint index: 0
Attributes: 3
Direction: 128 (device to host)
Number: 1
Interval: 1
Packet size: 64
Type: 3 USB_ENDPOINT_XFER_INT (interrupt endpoint)
++++ ++++ ++++
Endpoint index: 1
Attributes: 3
Direction: 0 (host to device)
Number: 1
Interval: 1
Packet size: 64
Type: 3 USB_ENDPOINT_XFER_INT (interrupt endpoint)
No more devices connected.
I'am also able to send data from host to device, using this method:
int bufferDataLength = mEndpointOut.getMaxPacketSize();
ByteBuffer buffer = ByteBuffer.allocate(bufferDataLength + 1);
UsbRequest request = new UsbRequest();
buffer.put(DataToSend);
request.initialize(mDeviceConnection, mEndpointOut);
request.queue(buffer, bufferDataLength);
try
{
if (request.equals(mDeviceConnection.requestWait()))
{
// Read an analyze the incoming data here
byte[] byteBuffer = new byte[buffer.remaining()];
buffer.get(byteBuffer, 0, buffer.remaining());
return true;
}
}
catch (Exception ex)
{
Log.e(TAG, "Error sending data: " + ex.toString());
}
return false;
But i'am not able to read from device, i've tested several methods without success..
for example this doesn't work:
int bufferDataLength = mEndpointIn.getMaxPacketSize();
ByteBuffer buffer = ByteBuffer.allocate(bufferDataLength + 1);
//Make a request
UsbRequest request = new UsbRequest();
request.initialize(mDeviceConnection, mEndpointIn);
ByteBuffer buffer = ByteBuffer.allocate(bufferDataLength);
//For IN endpoints, data is read into the buffer
request.queue(buffer, bufferDataLength);
//This blocks until the request is successful
//Make sure the request that finished is the one you need
if (mDeviceConnection.requestWait() == request) {
// Read an analyze the incoming data here
byte[] byteBuffer = new byte[buffer.remaining()];
buffer.get(byteBuffer, 0, buffer.remaining());
}
Suggestions?
I resolved my problems using this class, i'm answering to my my question hoping it can help someone else:
/**
* This class is used for talking to hid of the dongle, connecting, disconnencting and enumerating the devices.
* #author gai
*/
#TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
public class HidBridge {
private Context _context;
private int _productId;
private int _vendorId;
//private HidBridgeLogSupporter _logSupporter = new HidBridgeLogSupporter();
private static final String ACTION_USB_PERMISSION =
"com.example.company.app.testhid.USB_PERMISSION";
// Locker object that is responsible for locking read/write thread.
private final Object _locker = new Object();
private Thread _readingThread = null;
private boolean _runReadingThread = false;
private String _deviceName;
private UsbManager _usbManager;
private UsbDevice _usbDevice;
// The queue that contains the read data.
private Queue<byte[]> _receivedQueue;
/**
* Creates a hid bridge to the dongle. Should be created once.
* #param context is the UI context of Android.
* #param productId of the device.
* #param vendorId of the device.
*/
public HidBridge(Context context, int productId, int vendorId) {
_context = context;
_productId = productId;
_vendorId = vendorId;
_receivedQueue = new LinkedList<byte[]>();
}
/**
* Searches for the device and opens it if successful
* #return true, if connection was successful
*/
public boolean OpenDevice() {
_usbManager = (UsbManager) _context.getSystemService(Context.USB_SERVICE);
HashMap<String, UsbDevice> deviceList = _usbManager.getDeviceList();
Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
_usbDevice = null;
// Iterate all the available devices and find ours.
while(deviceIterator.hasNext()){
UsbDevice device = deviceIterator.next();
if (device.getProductId() == _productId && device.getVendorId() == _vendorId) {
_usbDevice = device;
_deviceName = _usbDevice.getDeviceName();
}
}
if (_usbDevice == null) {
Log("Cannot find the device. Did you forgot to plug it?");
Log(String.format("\t I search for VendorId: %s and ProductId: %s", _vendorId, _productId));
return false;
}
// Create and intent and request a permission.
PendingIntent mPermissionIntent = PendingIntent.getBroadcast(_context, 0, new Intent(ACTION_USB_PERMISSION), 0);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
_context.registerReceiver(mUsbReceiver, filter);
_usbManager.requestPermission(_usbDevice, mPermissionIntent);
Log("Found the device");
return true;
}
/**
* Closes the reading thread of the device.
*/
public void CloseTheDevice() {
try
{
StopReadingThread();
_context.unregisterReceiver(mUsbReceiver);
}
catch(RuntimeException e)
{
Log("Error happend while closing device. Usb reciver not connected.");
}
}
/**
* Starts the thread that continuously reads the data from the device.
* Should be called in order to be able to talk with the device.
*/
public void StartReadingThread() {
if (_readingThread == null) {
_runReadingThread = true;
_readingThread = new Thread(readerReceiver);
_readingThread.start();
} else {
Log("Reading thread already started");
}
}
/**
* Stops the thread that continuously reads the data from the device.
* If it is stopped - talking to the device would be impossible.
*/
public void StopReadingThread() {
if (_readingThread != null) {
// Just kill the thread. It is better to do that fast if we need that asap.
_runReadingThread = false;
_readingThread = null;
} else {
Log("No reading thread to stop");
}
}
/**
* Write data to the usb hid. Data is written as-is, so calling method is responsible for adding header data.
* #param bytes is the data to be written.
* #return true if succeed.
*/
public boolean WriteData(byte[] bytes) {
try
{
// Lock that is common for read/write methods.
synchronized (_locker) {
UsbInterface writeIntf = _usbDevice.getInterface(0);
UsbEndpoint writeEp = writeIntf.getEndpoint(1);
UsbDeviceConnection writeConnection = _usbManager.openDevice(_usbDevice);
// Lock the usb interface.
writeConnection.claimInterface(writeIntf, true);
// Write the data as a bulk transfer with defined data length.
int r = writeConnection.bulkTransfer(writeEp, bytes, bytes.length, 0);
if (r != -1) {
Log(String.format("Written %s bytes to the dongle. Data written: %s", r, composeString(bytes)));
} else {
Log("Error happened while writing data. No ACK");
}
// Release the usb interface.
writeConnection.releaseInterface(writeIntf);
writeConnection.close();
}
} catch(NullPointerException e)
{
Log("Error happend while writing. Could not connect to the device or interface is busy?");
Log.e("HidBridge", Log.getStackTraceString(e));
return false;
}
return true;
}
/**
* #return true if there are any data in the queue to be read.
*/
public boolean IsThereAnyReceivedData() {
synchronized(_locker) {
return !_receivedQueue.isEmpty();
}
}
/**
* Queue the data from the read queue.
* #return queued data.
*/
public byte[] GetReceivedDataFromQueue() {
synchronized(_locker) {
return _receivedQueue.poll();
}
}
// The thread that continuously receives data from the dongle and put it to the queue.
private Runnable readerReceiver = new Runnable() {
public void run() {
if (_usbDevice == null) {
Log("No device to read from");
return;
}
UsbEndpoint readEp;
UsbDeviceConnection readConnection = null;
UsbInterface readIntf = null;
boolean readerStartedMsgWasShown = false;
// We will continuously ask for the data from the device and store it in the queue.
while (_runReadingThread) {
// Lock that is common for read/write methods.
synchronized (_locker) {
try
{
if (_usbDevice == null) {
OpenDevice();
Log("No device. Recheking in 10 sec...");
Sleep(10000);
continue;
}
readIntf = _usbDevice.getInterface(0);
readEp = readIntf.getEndpoint(0);
if (!_usbManager.getDeviceList().containsKey(_deviceName)) {
Log("Failed to connect to the device. Retrying to acquire it.");
OpenDevice();
if (!_usbManager.getDeviceList().containsKey(_deviceName)) {
Log("No device. Recheking in 10 sec...");
Sleep(10000);
continue;
}
}
try
{
readConnection = _usbManager.openDevice(_usbDevice);
if (readConnection == null) {
Log("Cannot start reader because the user didn't gave me permissions or the device is not present. Retrying in 2 sec...");
Sleep(2000);
continue;
}
// Claim and lock the interface in the android system.
readConnection.claimInterface(readIntf, true);
}
catch (SecurityException e) {
Log("Cannot start reader because the user didn't gave me permissions. Retrying in 2 sec...");
Sleep(2000);
continue;
}
// Show the reader started message once.
if (!readerStartedMsgWasShown) {
Log("!!! Reader was started !!!");
readerStartedMsgWasShown = true;
}
// Read the data as a bulk transfer with the size = MaxPacketSize
int packetSize = readEp.getMaxPacketSize();
byte[] bytes = new byte[packetSize];
int r = readConnection.bulkTransfer(readEp, bytes, packetSize, 50);
if (r >= 0) {
byte[] trancatedBytes = new byte[r - 1]; // Truncate bytes in the honor of r
int i=0;
for (byte b : bytes) {
if (i > 0)
trancatedBytes[i - 1] = b;
i++;
}
_receivedQueue.add(trancatedBytes); // Store received data
Log(String.format("Message received of lengths %s and content: %s", r, composeString(bytes)));
}
// Release the interface lock.
readConnection.releaseInterface(readIntf);
readConnection.close();
}
catch (NullPointerException e) {
Log("Error happened while reading. No device or the connection is busy");
Log.e("HidBridge", Log.getStackTraceString(e));
}
catch (ThreadDeath e) {
if (readConnection != null) {
readConnection.releaseInterface(readIntf);
readConnection.close();
}
throw e;
}
}
// Sleep for 10 ms to pause, so other thread can write data or anything.
// As both read and write data methods lock each other - they cannot be run in parallel.
// Looks like Android is not so smart in planning the threads, so we need to give it a small time
// to switch the thread context.
Sleep(10);
}
}
};
private void Sleep(int milliseconds) {
try {
Thread.sleep(milliseconds);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (ACTION_USB_PERMISSION.equals(action)) {
synchronized (this) {
UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
if(device != null){
//call method to set up device communication
}
}
else {
Log.d("TAG", "permission denied for the device " + device);
}
}
}
}
};
/**
* Logs the message from HidBridge.
* #param message to log.
*/
private void Log(String message) {
//LogHandler logHandler = LogHandler.getInstance();
//logHandler.WriteMessage("HidBridge: " + message, LogHandler.GetNormalColor());
Log.i("HidBridge: ", message);
}
/**
* Composes a string from byte array.
*/
private String composeString(byte[] bytes) {
StringBuilder builder = new StringBuilder();
for (byte b: bytes) {
builder.append(b);
builder.append(" ");
}
return builder.toString();
}
}
I am building an Android app that communicates with an Arduino board via bluetooth, I have the bluetooth code in a class of it's own called BlueComms. To connect to the device I use the following methord:
public boolean connectDevice() {
CheckBt();
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
Log.d(TAG, "Connecting to ... " + device);
mBluetoothAdapter.cancelDiscovery();
try {
btSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
btSocket.connect();
outStream = btSocket.getOutputStream();
Log.d(TAG, "Connection made.");
return true;
} catch (IOException e) {
try {
btSocket.close();
} catch (IOException e2) {
Log.d(TAG, "Unable to end the connection");
return false;
}
Log.d(TAG, "Socket creation failed");
}
return false;
}
private void CheckBt() {
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (!mBluetoothAdapter.isEnabled()) {
System.out.println("Bt dsbld");
}
if (mBluetoothAdapter == null) {
System.out.println("Bt null");
}
}
This connects fine but as soon as I leave the activity I connected through it drops the connection, showing this through LogCat,
D/dalvikvm(21623): GC_CONCURRENT freed 103K, 10% free 2776K/3056K, paused 5ms+2ms, total 35ms
I can no longer connect to the device, but if I call killBt() it throws a fatal error and if I try to send data I get a 'Socket creation failed' error. My send message code is as follows:
public void sendData(String data, int recvAct) {
try {
outStream = btSocket.getOutputStream();
} catch (IOException e) {
Log.d(TAG, "Bug BEFORE Sending stuff", e);
}
String message = data;
byte[] msgBuffer = message.getBytes();
try {
outStream.write(msgBuffer);
} catch (IOException e) {
Log.d(TAG, "Bug while sending stuff", e);
}
}
How should I go about preventing the connection from being paused by the activity I connect with when I switch a different activity, I am switching activities with this code:
Intent myIntent = new Intent(v.getContext(), Timelapse.class);
startActivityForResult(myIntent, 0);
Many Thanks,
Rozz
Where did you store the instance of your BlueComms class? If you put it in the first activity then the class instance would have been killed when that activity was destroyed as you left it and moved to the next activity (NB activities also get destroyed on screen rotation)
So you need to find a way to keep the instance of BlueComms class alive for as long as you need it. You could pass it between activities via public properties and store it in onRetainNonConfigurationInstance() during rotations.
An easier trick is to create a class that extends Application use it as the application delegate for your app and add public property to it to store the instance of BlueComms class within it. That way the instance of BlueComms class would be alive for the lifetime of you app.
Extend Application
import android.app.Application;
public class cBaseApplication extends Application {
public BlueComms myBlueComms;
#Override
public void onCreate()
{
super.onCreate();
myBlueComms = new BlueComms();
}
}
Make your class the application delegate in the app manifest
<application
android:name="your.app.namespace.cBaseApplication"
android:icon="#drawable/icon"
android:label="#string/app_name" >
Access the base app from any of your Activities like this
((cBaseApplication)this.getApplicationContext()).myBlueComms.SomeMethod();
What I have done is, Created a singleton class for BluetoothConnection.
So socket creation happens only for one time.
When onCreate method of any activity is created, it first fetch instance of BluetoothConnection class.
Handler is used to send messages from thread in BluetoothConnection class to the corresponding activity by settings Handler.
Like:
Class MyBTConnection{
private static MyBTConnection connectionObj;
private Handler mHandler;
public MyBTConnection() { //constructor }
public static MyBTConnection getInstance() {
if(connectionObj == null) {
connectionObj = new MyBTConnection();
}
return connectionObj;
}
}
public void setHandler(Handler handler) {
mHandler = handler;
}
..... Code for Bluetooth Connection ....
to send message :
mHandler.obtainMessage(what).sendToTarget();
}
// in first activity
class MainActivity extends Activity {
private MyBTConnection connectionObj;
public onCreate(....) {
/*
* Since this is first call for getInstance. A new object
* of MyBTConnection will be created and a connection to
* remote bluetooth device will be established.
*/
connectionObj = MyBTConnection.getInstance();
connectionObj.setHandler(mHandler);
}
private Handler mHandler = new Handler(){
public void onReceive(...) {
/// handle received messages here
}
};
}
// in second activity
class SecondActivity extends Activity {
private MyBTConnection connectionObj;
public onCreate(....) {
/*
* Since this is second call for getInstance.
* Object for MyBTConnection was already created in previous
* activity. So getInstance will return that previously
* created object and in that object, connection to remote
* bluetooth device is already established so you can
* continue your work here.
*/
connectionObj = MyBTConnection.getInstance();
connectionObj.setHandler(mHandler);
}
private Handler mHandler = new Handler(){
public void onReceive(...) {
/// handle received messages here
}
};
}
I'm currently having exactly the same issue and I was thinking of opening/closing the Bluetooth socket each time an Activity asks for it. Each Activity has it's own BlueComms instance.
Because my application will became a bit complex and there will be Bluetooth threaded requests from different activities, I'm thinking that this way will become very difficult to use and troubleshoot.
Another way I came across by reading here...
https://developer.android.com/guide/components/services.html
A Service can be created on the background having a Bluetooth socket always on. All Bluetooth requests can be made using Intent towards this service. This also creates some fair amount of complexity but feels a lot more tidy and organized.
I'm currently having this dilemma, either to use a thread for each activity or use a service. I don't know which way is actually better.
When you are Selecting A device to connect and when you are click on the device list item for requesting a connection to the device use AsyncTask
and put the connect method inside the AsyncTask like this :-
AsyncTask.execute(new Runnable() {
#Override
public void run() {
try {
bluetoothSocket = Globals.bluetoothDevice.createRfcommSocketToServiceRecord(Globals.DEFAULT_SPP_UUID);
bluetoothSocket.connect();
// After successful connect you can open InputStream
} catch (IOException e) {
e.printStackTrace();
}
**Here is the full code for the same problem that i have cracked :-**
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
lablelexconnected.setText("Connecting ...");
bdDevice = arrayListBluetoothDevices.get(position);
//bdClass = arrayListBluetoothDevices.get(position)
// Toast.makeText(getApplicationContext()," " + bdDevice.getAddress(),Toast.LENGTH_SHORT).show();
Log.i("Log", "The dvice : " + bdDevice.toString());
bdDevice = bluetoothAdapter.getRemoteDevice(bdDevice.getAddress());
Globals.bluetoothDevice = bluetoothAdapter.getRemoteDevice(bdDevice.getAddress());
System.out.println("Device in GPS Settings : " + bdDevice);
// startService(new Intent(getApplicationContext(),MyService.class));
/* Intent i = new Intent(GpsSettings.this, MyService.class);
startService(i);*/
// finish();
// connectDevice();
AsyncTask.execute(new Runnable() {
#Override
public void run() {
try {
bluetoothSocket = Globals.bluetoothDevice.createRfcommSocketToServiceRecord(Globals.DEFAULT_SPP_UUID);
bluetoothSocket.connect();
// After successful connect you can open InputStream
InputStream in = null;
in = bluetoothSocket.getInputStream();
InputStreamReader isr = new InputStreamReader(in);
br = new BufferedReader(isr);
while (found == 0) {
String nmeaMessage = br.readLine();
Log.d("NMEA", nmeaMessage);
// parse NMEA messages
sentence = nmeaMessage;
System.out.println("Sentence : " + sentence);
if (sentence.startsWith("$GPRMC")) {
String[] strValues = sentence.split(",");
System.out.println("StrValues : " + strValues[3] + " " + strValues[5] + " " + strValues[8]);
if (strValues[3].equals("") && strValues[5].equals("") && strValues[8].equals("")) {
Toast.makeText(getApplicationContext(), "Location Not Found !!! ", Toast.LENGTH_SHORT).show();
} else {
latitude = Double.parseDouble(strValues[3]);
if (strValues[4].charAt(0) == 'S') {
latitude = -latitude;
}
longitude = Double.parseDouble(strValues[5]);
if (strValues[6].charAt(0) == 'W') {
longitude = -longitude;
}
course = Double.parseDouble(strValues[8]);
// Toast.makeText(getApplicationContext(), "latitude=" + latitude + " ; longitude=" + longitude + " ; course = " + course, Toast.LENGTH_SHORT).show();
System.out.println("latitude=" + latitude + " ; longitude=" + longitude + " ; course = " + course);
// found = 1;
NMEAToDecimalConverter(latitude, longitude);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
});
I am trying to measure the wifi signal strength 5 times (after every second) & display it in a TextView. I simultaneously write it to external storage as well. Everything runs fine except that I am NOT able to see the results in real time. The app would run for 5 secs with a blank screen and then show up the results (which are correct btw, i.e 5 different readings after each second).
I'd want to see the results updating as soon the new value is calculated in each iteration of the for loop.
Thanks
Here is the code
public class WifiDemo extends Activity implements OnClickListener {
private static final String TAG = "WiFiDemo";
WifiManager wifi;
TextView textStatus;
Button buttonScan;
/** Called when the activity is first created. */
/*
* (non-Javadoc)
*
* #see android.app.Activity#onCreate(android.os.Bundle)
*/
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Setup UI
textStatus = (TextView) findViewById(R.id.textStatus);
buttonScan = (Button) findViewById(R.id.buttonScan);
buttonScan.setOnClickListener(this);
// Setup WiFi
wifi = (WifiManager) getSystemService(Context.WIFI_SERVICE);
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
// Get WiFi status
runOnUiThread(new Runnable() {
public void run() {
try {
FileWriter fw = new FileWriter(
Environment.getExternalStorageDirectory()
+ "/bluetooth/wifi.txt");
for (int i = 0; i < 5; i++) {
WifiInfo info = wifi.getConnectionInfo();
Date d = new Date(System.currentTimeMillis());
String stat = "\n\nWiFi Status: " + info.getRssi()
+ " " + d.getHours() + ":" + d.getMinutes()
+ ":" + d.getSeconds();
textStatus.append(stat);
fw.write(stat);
fw.flush();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
fw.close();
}
catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
textStatus.append("something wrong");
}
}
});
}
}
}
You can try to create a handler to handle UI update tasks in the main thread. Do not update UI in your thread, instead, do it by passing handler messages to make sure this job is handled in the main thread. It works fine for me. I've modified some of your code here (I removed the write file part),
public class WifiDemo extends Activity implements OnClickListener {
private static final String TAG = "WiFiDemo";
private static final int WifiDetectStart = 0;
private static final int WifiDetectStop = 1;
private String stat;
WifiManager wifi;
TextView textStatus;
Button buttonScan;
Handler handler;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Setup UI
textStatus = (TextView) findViewById(R.id.textStatus);
buttonScan = (Button) findViewById(R.id.buttonScan);
buttonScan.setOnClickListener(this);
//setup handler
handler = new Handler(){
#Override
public void handleMessage(Message msg) {
if(msg.what == WifiDetectStart)
{
textStatus.append(stat);
}
if(msg.what == WifiDetectStop)
{
Thread.currentThread().interrupt();
}
super.handleMessage(msg);
}
};
// Setup WiFi
wifi = (WifiManager) getSystemService(Context.WIFI_SERVICE);
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
// Get WiFi status
Thread myThread = new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 5; i++) {
WifiInfo info = wifi.getConnectionInfo();
Date d = new Date(System.currentTimeMillis());
stat = "\n\nWiFi Status: " + info.getRssi()
+ " " + d.getHours() + ":" + d.getMinutes()
+ ":" + d.getSeconds();
Message msg = new Message();
msg.what = WifiDetectStart;
handler.sendMessage(msg);
// textStatus.append(stat);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//finish this operation
Message msg = new Message();
msg.what = WifiDetectStop;
handler.sendMessage(msg);
}
});
myThread.start();
}
}
The problem is you're doing something right by trying to do your updating in a separate Runnable... however, your Runnable is running in the UI thread and therefore causing the UI thread to sit in the loop (including the Thread.sleep()). You're not getting your updates because you're causing the UI to wait on you.
If your processing is reasonably heavy, you might wish to break it out into a separate thread and send messages to a handler. Otherwise, it might be easiest to do something like the following (untested, but something similar):
if (Environment.MEDIA_MOUNTED.equals(state)) {
textStatus.postDelayed(new Runnable() {
public void run() {
WifiInfo info = wifi.getConnectionInfo();
Date d = new Date(System.currentTimeMillis());
String stat = "\n\nWiFi Status: " + info.getRssi()
+ " " + d.getHours() + ":" + d.getMinutes()
+ ":" + d.getSeconds();
textStatus.append(stat);
// relaunch if we're not through with our number of iterations.
// mCount is a new field.
if(mCount++ < 5) {
textStatus.postDelayed(this, 1000);
}
}
}, 1000);
}
Weird problem that seems to have been mentioned a few times on here. I have a thread, have used AsyncTask also, and I am trying to make it stop running, on the users request.
So, naturally, I use a boolean in the while loop. The thread always sees that boolean as true, even when it prints false elsewhere.
Code is below and any help is appreciated!
/**
* Opens new socket and listens on the specified port until a user stops the thread, the logview is updated with messages
*/
public void run() {
byte[] receiveData = new byte[1024];
byte[] sendData = new byte[1024];
Log.e("Text2Server", "Starting Server");
this.running = true;
while(this.running) {
Log.i("Text2Server", "Server should be running: " + running);
try {
serverSocket = new DatagramSocket(port);
} catch (SocketException e1) {
e1.printStackTrace();
}
try {
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
serverSocket.receive(receivePacket);
final String fromIP = new String(receivePacket.getAddress().toString());
final int fromPort = receivePacket.getPort();
final String received = new String(receivePacket.getData());
Date now = new Date();
final String logput = DateFormat.getDateTimeInstance().format(now) + " - Message from: " + fromIP + " through Port: " + fromPort + " with Message: " + received;
Log.i("Text2Server", logput);
//All UI Operations are done in this thread
uiHandler.post(new Runnable(){
#Override
public void run() {
logTextView.append(logput);
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
serverSocket.close();
}
public void stopThread() {
this.running = false;
Log.i("Text2Server", "Stopping Server, value is now: " + running);
}
Calling stopThread() makes it false, but the thread still goes into the while and prints out true. Any thoughts?
Thanks!
Possible reasons:
1) You have spawned more than one thread and have closed only one of them.
2) You call stopThread before the thread starts running.
3) The thread has queued up many logTextView.append(logput) calls on the UI thread and therefore appears to be still running afterwards.