diff --git a/README.md b/README.md index d7ce63c..3dd061f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ UsbSerial ========= -Usb serial controller for Android. For more information, there is [a more complete description](http://felhr85.net/2014/11/11/usbserial-a-serial-port-driver-library-for-android-v2-0/) and [an example app](https://github.com/felHR85/SerialPortExample). +Usb serial controller for Android. For more information, there is [a more complete description](http://felhr85.net/2014/11/11/usbserial-a-serial-port-driver-library-for-android-v2-0/). Devices Supported -------------------------------------- diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 0000000..d115448 --- /dev/null +++ b/example/.gitignore @@ -0,0 +1,3 @@ +/build +*.jks +signing.properties \ No newline at end of file diff --git a/example/build.gradle b/example/build.gradle new file mode 100644 index 0000000..b1a4f10 --- /dev/null +++ b/example/build.gradle @@ -0,0 +1,29 @@ +apply plugin: 'com.android.application' + +android { + + compileSdkVersion Integer.parseInt(project.ANDROID_BUILD_SDK_VERSION) + buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION + + defaultConfig { + applicationId "com.felhr.serialportexample" + minSdkVersion Integer.parseInt(project.ANDROID_BUILD_MIN_SDK_VERSION) + targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION) + versionName project.VERSION_NAME + versionCode Integer.parseInt(project.VERSION_CODE) + } + + compileOptions { + encoding "UTF-8" + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 + } +} + +dependencies { + compile 'com.android.support:support-v4:23.1.1' + compile 'com.android.support:appcompat-v7:23.1.1' + compile 'com.android.support:design:23.1.1' + + compile project(':usbserial') +} diff --git a/example/proguard-rules.pro b/example/proguard-rules.pro new file mode 100644 index 0000000..b2fbbf2 --- /dev/null +++ b/example/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:\Program Files (x86)\Android\sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/example/src/main/AndroidManifest.xml b/example/src/main/AndroidManifest.xml new file mode 100644 index 0000000..e3ebd4c --- /dev/null +++ b/example/src/main/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + diff --git a/example/src/main/java/com/felhr/serialportexample/MainActivity.java b/example/src/main/java/com/felhr/serialportexample/MainActivity.java new file mode 100644 index 0000000..2a6b9d9 --- /dev/null +++ b/example/src/main/java/com/felhr/serialportexample/MainActivity.java @@ -0,0 +1,151 @@ +package com.felhr.serialportexample; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; + +import java.lang.ref.WeakReference; +import java.util.Set; + +public class MainActivity extends AppCompatActivity { + + /* + * Notifications from UsbService will be received here. + */ + private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + switch (intent.getAction()) { + case UsbService.ACTION_USB_PERMISSION_GRANTED: // USB PERMISSION GRANTED + Toast.makeText(context, "USB Ready", Toast.LENGTH_SHORT).show(); + break; + case UsbService.ACTION_USB_PERMISSION_NOT_GRANTED: // USB PERMISSION NOT GRANTED + Toast.makeText(context, "USB Permission not granted", Toast.LENGTH_SHORT).show(); + break; + case UsbService.ACTION_NO_USB: // NO USB CONNECTED + Toast.makeText(context, "No USB connected", Toast.LENGTH_SHORT).show(); + break; + case UsbService.ACTION_USB_DISCONNECTED: // USB DISCONNECTED + Toast.makeText(context, "USB disconnected", Toast.LENGTH_SHORT).show(); + break; + case UsbService.ACTION_USB_NOT_SUPPORTED: // USB NOT SUPPORTED + Toast.makeText(context, "USB device not supported", Toast.LENGTH_SHORT).show(); + break; + } + } + }; + private UsbService usbService; + private TextView display; + private EditText editText; + private MyHandler mHandler; + private final ServiceConnection usbConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName arg0, IBinder arg1) { + usbService = ((UsbService.UsbBinder) arg1).getService(); + usbService.setHandler(mHandler); + } + + @Override + public void onServiceDisconnected(ComponentName arg0) { + usbService = null; + } + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + mHandler = new MyHandler(this); + + display = (TextView) findViewById(R.id.textView1); + editText = (EditText) findViewById(R.id.editText1); + Button sendButton = (Button) findViewById(R.id.buttonSend); + sendButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (!editText.getText().toString().equals("")) { + String data = editText.getText().toString(); + if (usbService != null) { // if UsbService was correctly binded, Send data + display.append(data); + usbService.write(data.getBytes()); + } + } + } + }); + } + + @Override + public void onResume() { + super.onResume(); + setFilters(); // Start listening notifications from UsbService + startService(UsbService.class, usbConnection, null); // Start UsbService(if it was not started before) and Bind it + } + + @Override + public void onPause() { + super.onPause(); + unregisterReceiver(mUsbReceiver); + unbindService(usbConnection); + } + + private void startService(Class service, ServiceConnection serviceConnection, Bundle extras) { + if (!UsbService.SERVICE_CONNECTED) { + Intent startService = new Intent(this, service); + if (extras != null && !extras.isEmpty()) { + Set keys = extras.keySet(); + for (String key : keys) { + String extra = extras.getString(key); + startService.putExtra(key, extra); + } + } + startService(startService); + } + Intent bindingIntent = new Intent(this, service); + bindService(bindingIntent, serviceConnection, Context.BIND_AUTO_CREATE); + } + + private void setFilters() { + IntentFilter filter = new IntentFilter(); + filter.addAction(UsbService.ACTION_USB_PERMISSION_GRANTED); + filter.addAction(UsbService.ACTION_NO_USB); + filter.addAction(UsbService.ACTION_USB_DISCONNECTED); + filter.addAction(UsbService.ACTION_USB_NOT_SUPPORTED); + filter.addAction(UsbService.ACTION_USB_PERMISSION_NOT_GRANTED); + registerReceiver(mUsbReceiver, filter); + } + + /* + * This handler will be passed to UsbService. Data received from serial port is displayed through this handler + */ + private static class MyHandler extends Handler { + private final WeakReference mActivity; + + public MyHandler(MainActivity activity) { + mActivity = new WeakReference<>(activity); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case UsbService.MESSAGE_FROM_SERIAL_PORT: + String data = (String) msg.obj; + mActivity.get().display.append(data); + break; + } + } + } +} \ No newline at end of file diff --git a/example/src/main/java/com/felhr/serialportexample/UsbService.java b/example/src/main/java/com/felhr/serialportexample/UsbService.java new file mode 100644 index 0000000..49735e3 --- /dev/null +++ b/example/src/main/java/com/felhr/serialportexample/UsbService.java @@ -0,0 +1,242 @@ +package com.felhr.serialportexample; + +import android.app.PendingIntent; +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbManager; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; + +import com.felhr.usbserial.CDCSerialDevice; +import com.felhr.usbserial.UsbSerialDevice; +import com.felhr.usbserial.UsbSerialInterface; + +import java.io.UnsupportedEncodingException; +import java.util.HashMap; +import java.util.Map; + +public class UsbService extends Service { + + public static final String ACTION_USB_READY = "com.felhr.connectivityservices.USB_READY"; + public static final String ACTION_USB_ATTACHED = "android.hardware.usb.action.USB_DEVICE_ATTACHED"; + public static final String ACTION_USB_DETACHED = "android.hardware.usb.action.USB_DEVICE_DETACHED"; + public static final String ACTION_USB_NOT_SUPPORTED = "com.felhr.usbservice.USB_NOT_SUPPORTED"; + public static final String ACTION_NO_USB = "com.felhr.usbservice.NO_USB"; + public static final String ACTION_USB_PERMISSION_GRANTED = "com.felhr.usbservice.USB_PERMISSION_GRANTED"; + public static final String ACTION_USB_PERMISSION_NOT_GRANTED = "com.felhr.usbservice.USB_PERMISSION_NOT_GRANTED"; + public static final String ACTION_USB_DISCONNECTED = "com.felhr.usbservice.USB_DISCONNECTED"; + public static final String ACTION_CDC_DRIVER_NOT_WORKING = "com.felhr.connectivityservices.ACTION_CDC_DRIVER_NOT_WORKING"; + public static final String ACTION_USB_DEVICE_NOT_WORKING = "com.felhr.connectivityservices.ACTION_USB_DEVICE_NOT_WORKING"; + public static final int MESSAGE_FROM_SERIAL_PORT = 0; + private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION"; + private static final int BAUD_RATE = 9600; // BaudRate. Change this value if you need + public static boolean SERVICE_CONNECTED = false; + + private IBinder binder = new UsbBinder(); + + private Context context; + private Handler mHandler; + private UsbManager usbManager; + private UsbDevice device; + private UsbDeviceConnection connection; + private UsbSerialDevice serialPort; + + private boolean serialPortConnected; + /* + * Data received from serial port will be received here. Just populate onReceivedData with your code + * In this particular example. byte stream is converted to String and send to UI thread to + * be treated there. + */ + private UsbSerialInterface.UsbReadCallback mCallback = new UsbSerialInterface.UsbReadCallback() { + @Override + public void onReceivedData(byte[] arg0) { + try { + String data = new String(arg0, "UTF-8"); + if (mHandler != null) + mHandler.obtainMessage(MESSAGE_FROM_SERIAL_PORT, data).sendToTarget(); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + }; + /* + * Different notifications from OS will be received here (USB attached, detached, permission responses...) + * About BroadcastReceiver: http://developer.android.com/reference/android/content/BroadcastReceiver.html + */ + private final BroadcastReceiver usbReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context arg0, Intent arg1) { + if (arg1.getAction().equals(ACTION_USB_PERMISSION)) { + boolean granted = arg1.getExtras().getBoolean(UsbManager.EXTRA_PERMISSION_GRANTED); + if (granted) // User accepted our USB connection. Try to open the device as a serial port + { + Intent intent = new Intent(ACTION_USB_PERMISSION_GRANTED); + arg0.sendBroadcast(intent); + connection = usbManager.openDevice(device); + serialPortConnected = true; + new ConnectionThread().run(); + } else // User not accepted our USB connection. Send an Intent to the Main Activity + { + Intent intent = new Intent(ACTION_USB_PERMISSION_NOT_GRANTED); + arg0.sendBroadcast(intent); + } + } else if (arg1.getAction().equals(ACTION_USB_ATTACHED)) { + if (!serialPortConnected) + findSerialPortDevice(); // A USB device has been attached. Try to open it as a Serial port + } else if (arg1.getAction().equals(ACTION_USB_DETACHED)) { + // Usb device was disconnected. send an intent to the Main Activity + Intent intent = new Intent(ACTION_USB_DISCONNECTED); + arg0.sendBroadcast(intent); + serialPortConnected = false; + serialPort.close(); + } + } + }; + + /* + * onCreate will be executed when service is started. It configures an IntentFilter to listen for + * incoming Intents (USB ATTACHED, USB DETACHED...) and it tries to open a serial port. + */ + @Override + public void onCreate() { + this.context = this; + serialPortConnected = false; + UsbService.SERVICE_CONNECTED = true; + setFilter(); + usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); + findSerialPortDevice(); + } + + /* MUST READ about services + * http://developer.android.com/guide/components/services.html + * http://developer.android.com/guide/components/bound-services.html + */ + @Override + public IBinder onBind(Intent intent) { + return binder; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + return Service.START_NOT_STICKY; + } + + @Override + public void onDestroy() { + super.onDestroy(); + UsbService.SERVICE_CONNECTED = false; + } + + /* + * This function will be called from MainActivity to write data through Serial Port + */ + public void write(byte[] data) { + if (serialPort != null) + serialPort.write(data); + } + + public void setHandler(Handler mHandler) { + this.mHandler = mHandler; + } + + private void findSerialPortDevice() { + // This snippet will try to open the first encountered usb device connected, excluding usb root hubs + HashMap usbDevices = usbManager.getDeviceList(); + if (!usbDevices.isEmpty()) { + boolean keep = true; + for (Map.Entry entry : usbDevices.entrySet()) { + device = entry.getValue(); + int deviceVID = device.getVendorId(); + int devicePID = device.getProductId(); + + if (deviceVID != 0x1d6b && (devicePID != 0x0001 || devicePID != 0x0002 || devicePID != 0x0003)) { + // There is a device connected to our Android device. Try to open it as a Serial Port. + requestUserPermission(); + keep = false; + } else { + connection = null; + device = null; + } + + if (!keep) + break; + } + if (!keep) { + // There is no USB devices connected (but usb host were listed). Send an intent to MainActivity. + Intent intent = new Intent(ACTION_NO_USB); + sendBroadcast(intent); + } + } else { + // There is no USB devices connected. Send an intent to MainActivity + Intent intent = new Intent(ACTION_NO_USB); + sendBroadcast(intent); + } + } + + private void setFilter() { + IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_USB_PERMISSION); + filter.addAction(ACTION_USB_DETACHED); + filter.addAction(ACTION_USB_ATTACHED); + registerReceiver(usbReceiver, filter); + } + + /* + * Request user permission. The response will be received in the BroadcastReceiver + */ + private void requestUserPermission() { + PendingIntent mPendingIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0); + usbManager.requestPermission(device, mPendingIntent); + } + + public class UsbBinder extends Binder { + public UsbService getService() { + return UsbService.this; + } + } + + /* + * A simple thread to open a serial port. + * Although it should be a fast operation. moving usb operations away from UI thread is a good thing. + */ + private class ConnectionThread extends Thread { + @Override + public void run() { + serialPort = UsbSerialDevice.createUsbSerialDevice(device, connection); + if (serialPort != null) { + if (serialPort.open()) { + serialPort.setBaudRate(BAUD_RATE); + serialPort.setDataBits(UsbSerialInterface.DATA_BITS_8); + serialPort.setStopBits(UsbSerialInterface.STOP_BITS_1); + serialPort.setParity(UsbSerialInterface.PARITY_NONE); + serialPort.setFlowControl(UsbSerialInterface.FLOW_CONTROL_OFF); + serialPort.read(mCallback); + + // Everything went as expected. Send an intent to MainActivity + Intent intent = new Intent(ACTION_USB_READY); + context.sendBroadcast(intent); + } else { + // Serial port could not be opened, maybe an I/O error or if CDC driver was chosen, it does not really fit + // Send an Intent to Main Activity + if (serialPort instanceof CDCSerialDevice) { + Intent intent = new Intent(ACTION_CDC_DRIVER_NOT_WORKING); + context.sendBroadcast(intent); + } else { + Intent intent = new Intent(ACTION_USB_DEVICE_NOT_WORKING); + context.sendBroadcast(intent); + } + } + } else { + // No driver for given device, even generic CDC driver could not be loaded + Intent intent = new Intent(ACTION_USB_NOT_SUPPORTED); + context.sendBroadcast(intent); + } + } + } +} diff --git a/example/src/main/res/com/felhr/serialportexample/MainActivity.java b/example/src/main/res/com/felhr/serialportexample/MainActivity.java new file mode 100644 index 0000000..2f966ca --- /dev/null +++ b/example/src/main/res/com/felhr/serialportexample/MainActivity.java @@ -0,0 +1,199 @@ +package com.felhr.serialportexample; + +import java.lang.ref.WeakReference; +import java.util.Set; + +import android.support.v7.app.ActionBarActivity; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; + + +public class MainActivity extends ActionBarActivity implements View.OnClickListener +{ + private UsbService usbService; + + private TextView display; + private EditText editText; + private Button sendButton; + + private MyHandler mHandler; + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + mHandler = new MyHandler(this); + + display = (TextView) findViewById(R.id.textView1); + editText = (EditText) findViewById(R.id.editText1); + sendButton = (Button) findViewById(R.id.buttonSend); + sendButton.setOnClickListener(this); + } + + @Override + public void onResume() + { + super.onResume(); + setFilters(); // Start listening notifications from UsbService + startService(UsbService.class, usbConnection, null); // Start UsbService(if it was not started before) and Bind it + } + + @Override + public void onPause() + { + super.onPause(); + unregisterReceiver(mUsbReceiver); + unbindService(usbConnection); + } + + + @Override + public boolean onCreateOptionsMenu(Menu menu) + { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + if (id == R.id.action_settings) + { + return true; + } + return super.onOptionsItemSelected(item); + } + + + @Override + public void onClick(View v) + { + if(!editText.getText().toString().equals("")) + { + String data = editText.getText().toString(); + if(usbService != null) // if UsbService was correctly binded, Send data + usbService.write(data.getBytes()); + } + } + + private void startService(Class service, ServiceConnection serviceConnection, Bundle extras) + { + if(UsbService.SERVICE_CONNECTED == false) + { + Intent startService = new Intent(this, service); + if(extras != null && !extras.isEmpty()) + { + Set keys = extras.keySet(); + for(String key: keys) + { + String extra = extras.getString(key); + startService.putExtra(key, extra); + } + } + startService(startService); + } + Intent bindingIntent = new Intent(this, service); + bindService(bindingIntent, serviceConnection, Context.BIND_AUTO_CREATE); + } + + private void setFilters() + { + IntentFilter filter = new IntentFilter(); + filter.addAction(UsbService.ACTION_USB_PERMISSION_GRANTED); + filter.addAction(UsbService.ACTION_NO_USB); + filter.addAction(UsbService.ACTION_USB_DISCONNECTED); + filter.addAction(UsbService.ACTION_USB_NOT_SUPPORTED); + filter.addAction(UsbService.ACTION_USB_PERMISSION_NOT_GRANTED); + registerReceiver(mUsbReceiver, filter); + } + + /* + * This handler will be passed to UsbService. Dara received from serial port is displayed through this handler + */ + private static class MyHandler extends Handler + { + private final WeakReference mActivity; + + public MyHandler(MainActivity activity) + { + mActivity = new WeakReference(activity); + } + + @Override + public void handleMessage(Message msg) + { + switch(msg.what) + { + case UsbService.MESSAGE_FROM_SERIAL_PORT: + String data = (String) msg.obj; + mActivity.get().display.append(data); + break; + } + } + } + + /* + * Notifications from UsbService will be received here. + */ + private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() + { + @Override + public void onReceive(Context arg0, Intent arg1) + { + if(arg1.getAction().equals(UsbService.ACTION_USB_PERMISSION_GRANTED)) // USB PERMISSION GRANTED + { + Toast.makeText(arg0, "USB Ready", Toast.LENGTH_SHORT).show(); + }else if(arg1.getAction().equals(UsbService.ACTION_USB_PERMISSION_NOT_GRANTED)) // USB PERMISSION NOT GRANTED + { + Toast.makeText(arg0, "USB Permission not granted", Toast.LENGTH_SHORT).show(); + }else if(arg1.getAction().equals(UsbService.ACTION_NO_USB)) // NO USB CONNECTED + { + Toast.makeText(arg0, "No USB connected", Toast.LENGTH_SHORT).show(); + }else if(arg1.getAction().equals(UsbService.ACTION_USB_DISCONNECTED)) // USB DISCONNECTED + { + Toast.makeText(arg0, "USB disconnected", Toast.LENGTH_SHORT).show(); + }else if(arg1.getAction().equals(UsbService.ACTION_USB_NOT_SUPPORTED)) // USB NOT SUPPORTED + { + Toast.makeText(arg0, "USB device not supported", Toast.LENGTH_SHORT).show(); + } + } + }; + + private final ServiceConnection usbConnection = new ServiceConnection() + { + @Override + public void onServiceConnected(ComponentName arg0, IBinder arg1) + { + usbService = ((UsbService.UsbBinder) arg1).getService(); + usbService.setHandler(mHandler); + } + + @Override + public void onServiceDisconnected(ComponentName arg0) + { + usbService = null; + } + }; +} \ No newline at end of file diff --git a/example/src/main/res/com/felhr/serialportexample/UsbService.java b/example/src/main/res/com/felhr/serialportexample/UsbService.java new file mode 100644 index 0000000..1420ac0 --- /dev/null +++ b/example/src/main/res/com/felhr/serialportexample/UsbService.java @@ -0,0 +1,283 @@ +package com.felhr.serialportexample; + +import java.io.UnsupportedEncodingException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.felhr.usbserial.CDCSerialDevice; +import com.felhr.usbserial.UsbSerialDevice; +import com.felhr.usbserial.UsbSerialInterface; + +import android.app.PendingIntent; +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbManager; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.util.Log; + +public class UsbService extends Service +{ + public static final String ACTION_USB_READY = "com.felhr.connectivityservices.USB_READY"; + private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION"; + public static final String ACTION_USB_ATTACHED = "android.hardware.usb.action.USB_DEVICE_ATTACHED"; + public static final String ACTION_USB_DETACHED = "android.hardware.usb.action.USB_DEVICE_DETACHED"; + public static final String ACTION_USB_NOT_SUPPORTED = "com.felhr.usbservice.USB_NOT_SUPPORTED"; + public static final String ACTION_NO_USB = "com.felhr.usbservice.NO_USB"; + public static final String ACTION_USB_PERMISSION_GRANTED = "com.felhr.usbservice.USB_PERMISSION_GRANTED"; + public static final String ACTION_USB_PERMISSION_NOT_GRANTED = "com.felhr.usbservice.USB_PERMISSION_NOT_GRANTED"; + public static final String ACTION_USB_DISCONNECTED = "com.felhr.usbservice.USB_DISCONNECTED"; + public static final String ACTION_CDC_DRIVER_NOT_WORKING ="com.felhr.connectivityservices.ACTION_CDC_DRIVER_NOT_WORKING"; + public static final String ACTION_USB_DEVICE_NOT_WORKING = "com.felhr.connectivityservices.ACTION_USB_DEVICE_NOT_WORKING"; + + private static final int BAUD_RATE = 9600; // BaudRate. Change this value if you need + public static final int MESSAGE_FROM_SERIAL_PORT = 0; + + public static boolean SERVICE_CONNECTED = false; + + private IBinder binder = new UsbBinder(); + + private Context context; + private Handler mHandler; + private UsbManager usbManager; + private UsbDevice device; + private UsbDeviceConnection connection; + private UsbSerialDevice serialPort; + + private boolean serialPortConnected; + + /* + * onCreate will be executed when service is started. It configures an IntentFilter to listen for + * incoming Intents (USB ATTACHED, USB DETACHED...) and it tries to open a serial port. + */ + @Override + public void onCreate() + { + this.context = this; + serialPortConnected = false; + UsbService.SERVICE_CONNECTED = true; + setFilter(); + usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); + findSerialPortDevice(); + } + + /* MUST READ about services + * http://developer.android.com/guide/components/services.html + * http://developer.android.com/guide/components/bound-services.html + */ + @Override + public IBinder onBind(Intent intent) + { + return binder; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) + { + return Service.START_NOT_STICKY; + } + + @Override + public void onDestroy() + { + super.onDestroy(); + UsbService.SERVICE_CONNECTED = false; + } + + /* + * This function will be called from MainActivity to write data through Serial Port + */ + public void write(byte[] data) + { + if(serialPort != null) + serialPort.write(data); + } + + public void setHandler(Handler mHandler) + { + this.mHandler = mHandler; + } + + private void findSerialPortDevice() + { + // This snippet will try to open the first encountered usb device connected, excluding usb root hubs + HashMap usbDevices = usbManager.getDeviceList(); + if(!usbDevices.isEmpty()) + { + boolean keep = true; + for(Map.Entry entry : usbDevices.entrySet()) + { + device = entry.getValue(); + int deviceVID = device.getVendorId(); + int devicePID = device.getProductId(); + + if(deviceVID != 0x1d6b && (devicePID != 0x0001 || devicePID != 0x0002 || devicePID != 0x0003)) + { + // There is a device connected to our Android device. Try to open it as a Serial Port. + requestUserPermission(); + keep = false; + }else + { + connection = null; + device = null; + } + + if(!keep) + break; + } + if(!keep) + { + // There is no USB devices connected (but usb host were listed). Send an intent to MainActivity. + Intent intent = new Intent(ACTION_NO_USB); + sendBroadcast(intent); + } + }else + { + // There is no USB devices connected. Send an intent to MainActivity + Intent intent = new Intent(ACTION_NO_USB); + sendBroadcast(intent); + } + } + + private void setFilter() + { + IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_USB_PERMISSION); + filter.addAction(ACTION_USB_DETACHED); + filter.addAction(ACTION_USB_ATTACHED); + registerReceiver(usbReceiver , filter); + } + + /* + * Request user permission. The response will be received in the BroadcastReceiver + */ + private void requestUserPermission() + { + PendingIntent mPendingIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION),0); + usbManager.requestPermission(device, mPendingIntent); + } + + /* + * Data received from serial port will be received here. Just populate onReceivedData with your code + * In this particular example. byte stream is converted to String and send to UI thread to + * be treated there. + */ + private UsbSerialInterface.UsbReadCallback mCallback = new UsbSerialInterface.UsbReadCallback() + { + @Override + public void onReceivedData(byte[] arg0) + { + try + { + String data = new String(arg0, "UTF-8"); + if(mHandler != null) + mHandler.obtainMessage(MESSAGE_FROM_SERIAL_PORT,data).sendToTarget(); + } catch (UnsupportedEncodingException e) + { + e.printStackTrace(); + } + } + }; + + public class UsbBinder extends Binder + { + public UsbService getService() + { + return UsbService.this; + } + } + + /* + * Different notifications from OS will be received here (USB attached, detached, permission responses...) + * About BroadcastReceiver: http://developer.android.com/reference/android/content/BroadcastReceiver.html + */ + private final BroadcastReceiver usbReceiver = new BroadcastReceiver() + { + @Override + public void onReceive(Context arg0, Intent arg1) + { + if(arg1.getAction().equals(ACTION_USB_PERMISSION)) + { + boolean granted = arg1.getExtras().getBoolean(UsbManager.EXTRA_PERMISSION_GRANTED); + if(granted) // User accepted our USB connection. Try to open the device as a serial port + { + Intent intent = new Intent(ACTION_USB_PERMISSION_GRANTED); + arg0.sendBroadcast(intent); + connection = usbManager.openDevice(device); + serialPortConnected = true; + new ConnectionThread().run(); + }else // User not accepted our USB connection. Send an Intent to the Main Activity + { + Intent intent = new Intent(ACTION_USB_PERMISSION_NOT_GRANTED); + arg0.sendBroadcast(intent); + } + }else if(arg1.getAction().equals(ACTION_USB_ATTACHED)) + { + if(!serialPortConnected) + findSerialPortDevice(); // A USB device has been attached. Try to open it as a Serial port + }else if(arg1.getAction().equals(ACTION_USB_DETACHED)) + { + // Usb device was disconnected. send an intent to the Main Activity + Intent intent = new Intent(ACTION_USB_DISCONNECTED); + arg0.sendBroadcast(intent); + serialPortConnected = false; + serialPort.close(); + } + } + }; + + /* + * A simple thread to open a serial port. + * Although it should be a fast operation. moving usb operations away from UI thread is a good thing. + */ + private class ConnectionThread extends Thread + { + @Override + public void run() + { + serialPort = UsbSerialDevice.createUsbSerialDevice(device, connection); + if(serialPort != null) + { + if(serialPort.open()) + { + serialPort.setBaudRate(BAUD_RATE); + serialPort.setDataBits(UsbSerialInterface.DATA_BITS_8); + serialPort.setStopBits(UsbSerialInterface.STOP_BITS_1); + serialPort.setParity(UsbSerialInterface.PARITY_NONE); + serialPort.setFlowControl(UsbSerialInterface.FLOW_CONTROL_OFF); + serialPort.read(mCallback); + + // Everything went as expected. Send an intent to MainActivity + Intent intent = new Intent(ACTION_USB_READY); + context.sendBroadcast(intent); + }else + { + // Serial port could not be opened, maybe an I/O error or if CDC driver was chosen, it does not really fit + // Send an Intent to Main Activity + if(serialPort instanceof CDCSerialDevice) + { + Intent intent = new Intent(ACTION_CDC_DRIVER_NOT_WORKING); + context.sendBroadcast(intent); + }else + { + Intent intent = new Intent(ACTION_USB_DEVICE_NOT_WORKING); + context.sendBroadcast(intent); + } + } + }else + { + // No driver for given device, even generic CDC driver could not be loaded + Intent intent = new Intent(ACTION_USB_NOT_SUPPORTED); + context.sendBroadcast(intent); + } + } + } + +} diff --git a/example/src/main/res/layout/activity_main.xml b/example/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..07d57c6 --- /dev/null +++ b/example/src/main/res/layout/activity_main.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + +