summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAustin Kirk <askirk@umich.edu>2018-04-04 17:58:05 -0400
committerAustin Kirk <askirk@umich.edu>2018-04-04 17:58:05 -0400
commite45d20eb38f00aa5b3c1ec99549bf6421a0fbfad (patch)
tree6b970dddab88e78786e4dcf72da3eb500c9cbdb8
parent905be8ef1976352746f6d56cfa8dde97482c40e1 (diff)
downloadsdl_android-feature/rservice_aoa.tar.gz
-rw-r--r--sdl_android/src/main/java/com/smartdevicelink/transport/MyUSBTransport.java671
-rw-r--r--sdl_android/src/main/java/com/smartdevicelink/transport/MyUsbTransferProvider.java147
-rw-r--r--sdl_android/src/main/java/com/smartdevicelink/transport/SdlBroadcastReceiver.java25
-rw-r--r--sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterService.java48
-rw-r--r--sdl_android/src/main/java/com/smartdevicelink/transport/TransportConstants.java5
-rw-r--r--sdl_android/src/main/java/com/smartdevicelink/transport/USBTransportConfig.java15
-rw-r--r--sdl_android/src/main/java/com/smartdevicelink/transport/UsbTransferProvider.java163
7 files changed, 1055 insertions, 19 deletions
diff --git a/sdl_android/src/main/java/com/smartdevicelink/transport/MyUSBTransport.java b/sdl_android/src/main/java/com/smartdevicelink/transport/MyUSBTransport.java
new file mode 100644
index 000000000..ec592142f
--- /dev/null
+++ b/sdl_android/src/main/java/com/smartdevicelink/transport/MyUSBTransport.java
@@ -0,0 +1,671 @@
+package com.smartdevicelink.transport;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+
+import com.smartdevicelink.exception.SdlException;
+import com.smartdevicelink.exception.SdlExceptionCause;
+import com.smartdevicelink.protocol.SdlPacket;
+import com.smartdevicelink.trace.SdlTrace;
+import com.smartdevicelink.trace.enums.InterfaceActivityDirection;
+import com.smartdevicelink.transport.enums.TransportType;
+import com.smartdevicelink.util.DebugTool;
+
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Class that implements USB transport.
+ *
+ * A note about USB Accessory protocol. If the device is already in the USB
+ * accessory mode, any side (computer or Android) can open connection even if
+ * the other side is not connected. Conversely, if one side simply disconnects,
+ * the other side will NOT be notified and unblocked from reading data until
+ * some data is sent again or the USB is physically disconnected.
+ */
+@SuppressLint("NewApi")
+public class MyUSBTransport extends SdlTransport {
+
+ // Boolean to monitor if the transport is in a disconnecting state
+ private boolean _disconnecting = false;
+ /**
+ * Broadcast action: sent when a USB accessory is attached.
+ *
+ * UsbManager.EXTRA_ACCESSORY extra contains UsbAccessory object that has
+ * been attached.
+ */
+ public static final String ACTION_USB_ACCESSORY_ATTACHED =
+ "com.smartdevicelink.USB_ACCESSORY_ATTACHED";
+ /**
+ * String tag for logging.
+ */
+ private static final String TAG = MyUSBTransport.class.getSimpleName();
+ /**
+ * Key for SdlTrace.
+ */
+ private static final String SDL_LIB_TRACE_KEY =
+ "42baba60-eb57-11df-98cf-0800200c9a66";
+
+ private static final String DEBUG_PREFIX = "DEBUG: ";
+ /**
+ * String to prefix exception output.
+ */
+ private static final String EXCEPTION_STRING = " Exception String: ";
+ /**
+ * Broadcast receiver that receives different USB-related intents: USB
+ * accessory connected, disconnected, and permission granted.
+ */
+ /**
+ * USB config object.
+ */
+ private USBTransportConfig mConfig = null;
+ /**
+ * Current state of transport.
+ *
+ * Use setter and getter to access it.
+ */
+ private State mState = State.IDLE;
+ /**
+ * FileDescriptor that owns the input and output streams. We have to keep
+ * it, otherwise it will be garbage collected and the streams will become
+ * invalid.
+ */
+ private ParcelFileDescriptor mParcelFD = null;
+ /**
+ * Data input stream to read data from USB accessory.
+ */
+ private InputStream mInputStream = null;
+ /**
+ * Data output stream to write data to USB accessory.
+ */
+ private OutputStream mOutputStream = null;
+ /**
+ * Thread that connects and reads data from USB accessory.
+ *
+ * @see USBTransportReader
+ */
+ private Thread mReaderThread = null;
+
+ /**
+ * Constructs the USBTransport instance.
+ *
+ * @param usbTransportConfig Config object for the USB transport
+ * @param transportListener Listener that gets notified on different
+ * transport events
+ */
+ public MyUSBTransport(USBTransportConfig usbTransportConfig,
+ ITransportListener transportListener) {
+ super(transportListener);
+ this.mConfig = usbTransportConfig;
+ this.mParcelFD = usbTransportConfig.getParcelFileDescriptor();
+ }
+
+ /**
+ * Returns the current state of transport.
+ *
+ * @return Current state of transport
+ */
+ public State getState() {
+ return this.mState;
+ }
+
+ /**
+ * Changes current state of transport.
+ *
+ * @param state New state
+ */
+ private void setState(State state) {
+ logD("Changing state " + this.mState + " to " + state);
+ this.mState = state;
+ }
+
+ /**
+ * Sends the array of bytes over USB.
+ *
+ * @param packet The packet that is to be written out on the USB transport
+ * @return true if the bytes are sent successfully
+ */
+ @Override
+ protected boolean sendBytesOverTransport(SdlPacket packet) {
+ return sendBytesOverTransport(packet.constructPacket());
+ }
+
+
+ protected boolean sendBytesOverTransport(byte[] msgBytes){
+ logD("SendBytes: array size " + msgBytes.length + ", offset " + 0 +
+ ", length " + msgBytes.length);
+
+ boolean result = false;
+ final State state = getState();
+ switch (state) {
+ case CONNECTED:
+ if (mOutputStream != null) {
+ try {
+ mOutputStream.write(msgBytes, 0, msgBytes.length);
+ result = true;
+
+ logI("Bytes successfully sent");
+ SdlTrace.logTransportEvent(TAG + ": bytes sent",
+ null, InterfaceActivityDirection.Transmit,
+ msgBytes, 0, msgBytes.length,
+ SDL_LIB_TRACE_KEY);
+ } catch (IOException e) {
+ final String msg = "Failed to send bytes over USB";
+ logW(msg, e);
+ disconnect(msg, e);
+ }
+ } else {
+ final String msg =
+ "Can't send bytes when output stream is null";
+ logW(msg);
+ disconnect(msg, null);
+ }
+ break;
+
+ default:
+ logW("Can't send bytes from " + state + " state");
+ break;
+ }
+
+ return result;
+ }
+
+ /**
+ * Opens a USB connection if not open yet.
+ *
+ * @throws com.smartdevicelink.exception.SdlException
+ */
+ @Override
+ public void openConnection() throws SdlException {
+ final State state = getState();
+ switch (state) {
+ case IDLE:
+ synchronized (this) {
+ logI("openConnection()");
+ setState(State.LISTENING);
+ }
+ try {
+ openAccessory();
+ }catch(Exception e){
+ String msg = "Couldn't start opening connection";
+ logE(msg, e);
+ throw new SdlException(msg, e, SdlExceptionCause.SDL_CONNECTION_FAILED);
+ }
+ break;
+
+ default:
+ logW("openConnection() called from state " + state +
+ "; doing nothing");
+ break;
+ }
+ }
+
+ /**
+ * Closes the USB connection if open.
+ */
+ @Override
+ public void disconnect() {
+ disconnect(null, null);
+ }
+
+ /**
+ * Asks the reader thread to stop while it's possible. If it's blocked on
+ * read(), there is no way to stop it except for physical USB disconnect.
+ */
+ //@Override
+ public void stopReading() {
+ DebugTool.logInfo("USBTransport: stop reading requested, doing nothing");
+ // TODO - put back stopUSBReading(); @see <a href="https://adc.luxoft.com/jira/browse/SmartDeviceLink-3450">SmartDeviceLink-3450</a>
+ }
+
+ @SuppressWarnings("unused")
+ private void stopUSBReading() {
+ final State state = getState();
+ switch (state) {
+ case CONNECTED:
+ logI("Stopping reading");
+ synchronized (this) {
+ stopReaderThread();
+ }
+ break;
+
+ default:
+ logW("Stopping reading called from state " + state +
+ "; doing nothing");
+ break;
+ }
+ }
+
+ /**
+ * Actually asks the reader thread to interrupt.
+ */
+ private void stopReaderThread() {
+ if (mReaderThread != null) {
+ logI("Interrupting USB reader");
+ mReaderThread.interrupt();
+ // don't join() now
+ mReaderThread = null;
+ } else {
+ logD("USB reader is null");
+ }
+ }
+
+ /**
+ * Closes the USB connection from inside the transport with some extra info.
+ *
+ * @param msg Disconnect reason message, if any
+ * @param ex Disconnect exception, if any
+ */
+ private void disconnect(String msg, Exception ex) {
+
+ // If already disconnecting, return
+ if (_disconnecting) {
+ // No need to recursively call
+ return;
+ }
+ _disconnecting = true;
+
+ mConfig.setUsbAccessory(null);
+
+ final State state = getState();
+ switch (state) {
+ case LISTENING:
+ case CONNECTED:
+ synchronized (this) {
+ logI("Disconnect from state " + getState() + "; message: " +
+ msg + "; exception: " + ex);
+ setState(State.IDLE);
+
+ SdlTrace.logTransportEvent(TAG + ": disconnect", null,
+ InterfaceActivityDirection.None, null, 0,
+ SDL_LIB_TRACE_KEY);
+
+ stopReaderThread();
+
+ if (mOutputStream != null) {
+ try {
+ mOutputStream.close();
+ mOutputStream = null;
+ } catch (IOException e) {
+ logW("Can't close output stream", e);
+ mOutputStream = null;
+ }
+ }
+ if (mInputStream != null) {
+ try {
+ mInputStream.close();
+ mInputStream = null;
+ } catch (IOException e) {
+ logW("Can't close input stream", e);
+ mInputStream = null;
+ }
+ }
+ if (mParcelFD != null) {
+ try {
+ mParcelFD.close();
+ mParcelFD = null;
+ } catch (IOException e) {
+ logW("Can't close file descriptor", e);
+ mParcelFD = null;
+ }
+ }
+ }
+
+ logD("Unregistering receiver");
+
+ String disconnectMsg = (msg == null ? "" : msg);
+ if (ex != null) {
+ disconnectMsg += ", " + ex.toString();
+
+ if(ex instanceof SdlException
+ && SdlExceptionCause.SDL_USB_PERMISSION_DENIED.equals(((SdlException)ex).getSdlExceptionCause())){
+ //If the USB device disconnected we don't want to cycle the proxy ourselves
+ ex = null;
+ }
+ }
+
+ if (ex == null) {
+ // This disconnect was not caused by an error, notify the
+ // proxy that the transport has been disconnected.
+ logI("Disconnect is correct. Handling it");
+ handleTransportDisconnected(disconnectMsg);
+ } else {
+ // This disconnect was caused by an error, notify the proxy
+ // that there was a transport error.
+ logI("Disconnect is incorrect. Handling it as error");
+ handleTransportError(disconnectMsg, ex);
+ }
+ break;
+
+ default:
+ logW("Disconnect called from state " + state +
+ "; doing nothing");
+ break;
+ }
+ _disconnecting = false;
+ }
+
+ /**
+ * Returns the type of the transport.
+ *
+ * @return TransportType.USB
+ * @see com.smartdevicelink.transport.enums.TransportType
+ */
+ @Override
+ public TransportType getTransportType() {
+ return TransportType.USB;
+ }
+
+ /**
+ * Opens a connection to the accessory.
+ *
+ * When this function is called, the permission to use it must have already
+ * been granted.
+ */
+ private void openAccessory() {
+ final State state = getState();
+ switch (state) {
+ case LISTENING:
+ synchronized (this) {
+ mReaderThread = new Thread(new USBTransportReader());
+ mReaderThread.setDaemon(true);
+ mReaderThread
+ .setName(USBTransportReader.class.getSimpleName());
+ mReaderThread.start();
+
+ // Initialize the SiphonServer
+ if (SiphonServer.getSiphonEnabledStatus()) {
+ SiphonServer.init();
+ }
+ }
+
+ break;
+
+ default:
+ logW("openAccessory() called from state " + state +
+ "; doing nothing");
+ }
+ }
+
+ /**
+ * Logs the string and the throwable with ERROR level.
+ *
+ * @param s string to log
+ * @param tr throwable to log
+ */
+ private void logE(String s, Throwable tr) {
+ DebugTool.logError(s, tr);
+ }
+
+ /**
+ * Logs the string with WARN level.
+ *
+ * @param s string to log
+ */
+ private void logW(String s) {
+ DebugTool.logWarning(s);
+ }
+
+ /**
+ * Logs the string and the throwable with WARN level.
+ *
+ * @param s string to log
+ * @param tr throwable to log
+ */
+ private void logW(String s, Throwable tr) {
+ StringBuilder res = new StringBuilder(s);
+ if (tr != null) {
+ res.append(EXCEPTION_STRING);
+ res.append(tr.toString());
+ }
+ logW(res.toString());
+ }
+
+ /**
+ * Logs the string with INFO level.
+ *
+ * @param s string to log
+ */
+ private void logI(String s) {
+ DebugTool.logInfo(s);
+ }
+
+ /**
+ * Logs the string with DEBUG level.
+ *
+ * @param s string to log
+ */
+ private void logD(String s) {
+ // DebugTool doesn't support DEBUG level, so we use INFO instead
+ DebugTool.logInfo(DEBUG_PREFIX + s);
+ }
+
+ /**
+ * Returns Context to communicate with the OS.
+ *
+ * @return current context to be used by the USB transport
+ */
+ private Context getContext() {
+ return mConfig.getUSBContext();
+ }
+
+ /**
+ * Possible states of the USB transport.
+ */
+ private enum State {
+ /**
+ * Transport initialized; no connections.
+ */
+ IDLE,
+
+ /**
+ * USB accessory not attached; SdlProxy wants connection as soon as
+ * accessory is attached.
+ */
+ LISTENING,
+
+ /**
+ * USB accessory attached; permission granted; data IO in progress.
+ */
+ CONNECTED
+ }
+
+ /**
+ * Internal task that connects to and reads data from a USB accessory.
+ *
+ * Since the class has to have access to the parent class' variables,
+ * synchronization must be taken in consideration! For now, all access
+ * to variables of USBTransport must be surrounded with
+ * synchronized (USBTransport.this) { … }
+ */
+ private class USBTransportReader implements Runnable {
+ /**
+ * String tag for logging inside the task.
+ */
+ private final String TAG = USBTransportReader.class.getSimpleName();
+
+ SdlPsm psm;
+
+ /**
+ * Checks if the thread has been interrupted.
+ *
+ * @return true if the thread has been interrupted
+ */
+ private boolean isInterrupted() {
+ return Thread.interrupted();
+ }
+
+ /**
+ * Entry function that is called when the task is started. It attempts
+ * to connect to the accessory, then starts a read loop until
+ * interrupted.
+ */
+ @Override
+ public void run() {
+ logD("USB reader started!");
+ psm = new SdlPsm();
+ psm.reset();
+ if (connect()) {
+ readFromTransport();
+ }
+
+ logD("USB reader finished!");
+ }
+
+ /**
+ * Attemps to open connection to USB accessory.
+ *
+ * @return true if connected successfully
+ */
+ private boolean connect() {
+ if (isInterrupted()) {
+ logI("Thread is interrupted, not connecting");
+ return false;
+ }
+
+ final State state = getState();
+ switch (state) {
+ case LISTENING:
+ synchronized (MyUSBTransport.this) {
+ if (mParcelFD == null) {
+ if (isInterrupted()) {
+ logW("Can't open accessory, and thread is interrupted");
+ } else {
+ logW("Can't open accessory, disconnecting!");
+ String msg = "Failed to open USB accessory";
+ disconnect(msg, new SdlException(msg,
+ SdlExceptionCause.SDL_CONNECTION_FAILED));
+ }
+ return false;
+ }
+ FileDescriptor fd = mParcelFD.getFileDescriptor();
+ mInputStream = new FileInputStream(fd);
+ mOutputStream = new FileOutputStream(fd);
+ }
+
+ logI("Accessory opened!");
+
+ synchronized (MyUSBTransport.this) {
+ setState(State.CONNECTED);
+ handleTransportConnected();
+ }
+ break;
+
+ default:
+ logW("connect() called from state " + state +
+ ", will not try to connect");
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Continuously reads data from the transport's input stream, blocking
+ * when no data is available.
+ */
+ private void readFromTransport() {
+ final int READ_BUFFER_SIZE = 4096;
+ byte[] buffer = new byte[READ_BUFFER_SIZE];
+ int bytesRead;
+ // byte input;
+ boolean stateProgress = false;
+
+ // read loop
+ while (!isInterrupted()) {
+ try {
+ if (mInputStream == null)
+ continue;
+
+ bytesRead = mInputStream.read(buffer);
+ if (bytesRead == -1) {
+ if (isInterrupted()) {
+ logI("EOF reached, and thread is interrupted");
+ } else {
+ logI("EOF reached, disconnecting!");
+ disconnect("EOF reached", null);
+ }
+ return;
+ }
+ } catch (IOException e) {
+ if (isInterrupted()) {
+ logW("Can't read data, and thread is interrupted", e);
+ } else {
+ logW("Can't read data, disconnecting!", e);
+ disconnect("Can't read data from USB", e);
+ }
+ return;
+ }
+
+ logD("Read " + bytesRead + " bytes");
+ //FIXME SdlTrace.logTransportEvent(TAG + ": read bytes", null,
+ // InterfaceActivityDirection.Receive, buffer, bytesRead,
+ // SDL_LIB_TRACE_KEY);
+
+ if (isInterrupted()) {
+ logI("Read some data, but thread is interrupted");
+ return;
+ }
+ byte input;
+ for(int i=0;i<bytesRead; i++){
+ input=buffer[i];
+ stateProgress = psm.handleByte(input);
+ if(!stateProgress){//We are trying to weed through the bad packet info until we get something
+ //Log.w(TAG, "Packet State Machine did not move forward from state - "+ psm.getState()+". PSM being Reset.");
+ psm.reset();
+ buffer = new byte[READ_BUFFER_SIZE];
+ }
+
+ if(psm.getState() == SdlPsm.FINISHED_STATE){
+ synchronized (MyUSBTransport.this) {
+ //Log.d(TAG, "Packet formed, sending off");
+ handleReceivedPacket((SdlPacket)psm.getFormedPacket());
+ }
+ //We put a trace statement in the message read so we can avoid all the extra bytes
+ psm.reset();
+ buffer = new byte[READ_BUFFER_SIZE]; //FIXME just do an array copy and send off
+
+ }
+ }
+ }
+ }
+
+ // Log functions
+
+ private void logD(String s) {
+ DebugTool.logInfo(DEBUG_PREFIX + s);
+ }
+
+ private void logI(String s) {
+ DebugTool.logInfo(s);
+ }
+
+ private void logW(String s) {
+ DebugTool.logWarning(s);
+ }
+
+ private void logW(String s, Throwable tr) {
+ StringBuilder res = new StringBuilder(s);
+ if (tr != null) {
+ res.append(EXCEPTION_STRING);
+ res.append(tr.toString());
+ }
+ logW(res.toString());
+ }
+
+ private void logE(String s, Throwable tr) {
+ DebugTool.logError(s, tr);
+ }
+ }
+
+ @Override
+ public String getBroadcastComment() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+}
diff --git a/sdl_android/src/main/java/com/smartdevicelink/transport/MyUsbTransferProvider.java b/sdl_android/src/main/java/com/smartdevicelink/transport/MyUsbTransferProvider.java
new file mode 100644
index 000000000..5958cbc91
--- /dev/null
+++ b/sdl_android/src/main/java/com/smartdevicelink/transport/MyUsbTransferProvider.java
@@ -0,0 +1,147 @@
+package com.smartdevicelink.transport;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.smartdevicelink.util.AndroidTools;
+
+import java.lang.ref.WeakReference;
+
+public class MyUsbTransferProvider {
+ private static final String TAG = "SdlRouterParcelProvider";
+
+ private Context context = null;
+ private boolean isBound = false;
+ MyUsbTransferProvider.ConnectedStatusCallback cb = null;
+ Messenger routerServiceMessenger = null;
+ private ComponentName routerService = null;
+
+ final Messenger clientMessenger;
+ final ParcelFileDescriptor parcelFileDescriptor;
+
+ private ServiceConnection routerConnection= new ServiceConnection() {
+
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ Log.d(TAG, "Bound to service " + className.toString());
+ routerServiceMessenger = new Messenger(service);
+ isBound = true;
+ //So we just established our connection
+ //Register with router service
+ Message msg = Message.obtain();
+ msg.what = TransportConstants.ROUTER_REQUEST_SEND_PFD;
+ msg.obj = parcelFileDescriptor;
+ msg.replyTo = clientMessenger;
+ try {
+ routerServiceMessenger.send(msg);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ if(cb!=null){
+ cb.onConnectionStatusUpdate(false, routerService, context);
+ }
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ Log.d(TAG, "UN-Bound from service " + className.getClassName());
+ routerServiceMessenger = null;
+ isBound = false;
+ }
+ };
+
+ public MyUsbTransferProvider(Context context, ComponentName service, ParcelFileDescriptor parcelFileDescriptor, MyUsbTransferProvider.ConnectedStatusCallback callback){
+ if(context == null || service == null || callback == null){
+ throw new IllegalStateException("Supplied params are not correct. Context == null? "+ (context==null) + " ComponentName == null? " + (service == null) + " ConnectedStatusListener == null? " + callback);
+ }
+ this.context = context;
+ this.routerService = service;
+ this.parcelFileDescriptor = parcelFileDescriptor;
+ this.cb = callback;
+ this.clientMessenger = new Messenger(new MyUsbTransferProvider.ClientHandler(this));
+ }
+ public void checkIsConnected(){
+ if(!AndroidTools.isServiceExported(context,routerService) || !bindToService()){
+ //We are unable to bind to service
+ cb.onConnectionStatusUpdate(false, routerService, context);
+ unBindFromService();
+ }
+ }
+
+ public void cancel(){
+ if(isBound){
+ unBindFromService();
+ }
+ }
+
+ private boolean bindToService(){
+ if(isBound){
+ return true;
+ }
+ if(clientMessenger == null){
+ return false;
+ }
+ Intent bindingIntent = new Intent();
+ bindingIntent.setClassName(this.routerService.getPackageName(), this.routerService.getClassName());//This sets an explicit intent
+ //Quickly make sure it's just up and running
+ context.startService(bindingIntent);
+ bindingIntent.setAction( TransportConstants.BIND_REQUEST_TYPE_USB_TRANSPORT);
+ return context.bindService(bindingIntent, routerConnection, Context.BIND_AUTO_CREATE);
+ }
+
+ private void unBindFromService(){
+ try{
+ if(context!=null && routerConnection!=null){
+ context.unbindService(routerConnection);
+ }else{
+ Log.w(TAG, "Unable to unbind from router service, context was null");
+ }
+
+ }catch(IllegalArgumentException e){
+ //This is ok
+ }
+ }
+
+ private void handleRouterParcelRcvdResponse(int connectedStatus){
+ if(cb!=null){
+ cb.onConnectionStatusUpdate(connectedStatus == 1, routerService,context);
+ }
+ unBindFromService();
+ routerServiceMessenger =null;
+ }
+
+ static class ClientHandler extends Handler {
+ final WeakReference<MyUsbTransferProvider> provider;
+
+ public ClientHandler(MyUsbTransferProvider provider){
+ super(Looper.getMainLooper());
+ this.provider = new WeakReference<MyUsbTransferProvider>(provider);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if(provider.get()==null){
+ return;
+ }
+ switch (msg.what) {
+ case TransportConstants.ROUTER_RESPONSE_RCVD_PFD:
+ provider.get().handleRouterParcelRcvdResponse(msg.arg1);
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ public interface ConnectedStatusCallback{
+ public void onConnectionStatusUpdate(boolean connected, ComponentName service, Context context);
+ }
+}
diff --git a/sdl_android/src/main/java/com/smartdevicelink/transport/SdlBroadcastReceiver.java b/sdl_android/src/main/java/com/smartdevicelink/transport/SdlBroadcastReceiver.java
index ad950fa45..47ac17aa4 100644
--- a/sdl_android/src/main/java/com/smartdevicelink/transport/SdlBroadcastReceiver.java
+++ b/sdl_android/src/main/java/com/smartdevicelink/transport/SdlBroadcastReceiver.java
@@ -66,7 +66,7 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver{
private static final Object QUEUED_SERVICE_LOCK = new Object();
private static ComponentName queuedService = null;
- private UsbAccessory usbAccessory = null;
+ private ParcelFileDescriptor parcelFileDescriptor = null;
public int getRouterServiceVersion(){
return SdlRouterService.ROUTER_SERVICE_VERSION_NUMBER;
@@ -84,6 +84,15 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver{
RouterServiceValidator.invalidateList(context);
return;
}
+
+ if(action.equalsIgnoreCase(USBTransport.ACTION_USB_ACCESSORY_ATTACHED)){
+ Log.d(TAG, "Usb connected");
+ Toast.makeText(context, "Usb Connected, Grabbing Accessory", Toast.LENGTH_SHORT).show();
+ if(intent.hasExtra(UsbManager.EXTRA_ACCESSORY)){
+ usbAccessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
+ wakeUpRouterService(context,false,true);
+ }
+ }
if(!(action.equalsIgnoreCase(BOOT_COMPLETE)
|| action.equalsIgnoreCase(ACL_CONNECTED)
@@ -95,12 +104,6 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver{
return;
}
- if(action.equalsIgnoreCase(USBTransport.ACTION_USB_ACCESSORY_ATTACHED)){
- Log.d(TAG, "Usb connected");
- Toast.makeText(context, "Usb Connected, Grabbing Accessory", Toast.LENGTH_SHORT).show();
- usbAccessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
- }
-
boolean didStart = false;
if (localRouterClass == null){
localRouterClass = defineLocalSdlRouterClass();
@@ -143,7 +146,6 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver{
//We were told to wake up our router services
boolean altServiceWake = intent.getBooleanExtra(TransportConstants.BIND_REQUEST_TYPE_ALT_TRANSPORT, false);
didStart = wakeUpRouterService(context, false, usbAccessory);
-
}
}
@@ -163,7 +165,7 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver{
if(localRouterClass!=null){ //If there is a supplied router service lets run some logic regarding starting one
if(!didStart){Log.d(TAG, "attempting to wake up router service");
- didStart = wakeUpRouterService(context, true, usbAccessory);
+ didStart = wakeUpRouterService(context, true);
}
//So even though we started our own version, on some older phones we find that two services are started up so we want to make sure we send our version that we are working with
@@ -179,7 +181,7 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver{
}
@TargetApi(Build.VERSION_CODES.O)
- private boolean wakeUpRouterService(final Context context, final boolean ping, final UsbAccessory usbAccessory){
+ private boolean wakeUpRouterService(final Context context, final boolean ping, final boolean altTransportWake){
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
if (!isRouterServiceRunning(context, ping)) {
//If there isn't a service running we should try to start one
@@ -255,14 +257,13 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver{
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
- private void wakeRouterServiceAltTransport(Context context, UsbAccessory usbAccessory) {
+ private void wakeRouterServiceAltTransport(Context context) {
Intent serviceIntent = new Intent();
serviceIntent.setAction(TransportConstants.BIND_REQUEST_TYPE_ALT_TRANSPORT);
serviceIntent.putExtra(UsbManager.EXTRA_ACCESSORY, usbAccessory);
for (ComponentName compName : runningBluetoothServicePackage) {
serviceIntent.setComponent(compName);
context.startService(serviceIntent);
-
}
}
diff --git a/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterService.java b/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterService.java
index af352be2e..7b03fcfc9 100644
--- a/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterService.java
+++ b/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterService.java
@@ -158,6 +158,8 @@ public class SdlRouterService extends Service{
private ExecutorService packetExecutor = null;
PacketWriteTaskMaster packetWriteTaskMaster = null;
+ private ParcelFileDescriptor parcelFileDescriptor = null;
+
/**
* This flag is to keep track of if we are currently acting as a foreground service
@@ -648,6 +650,24 @@ public class SdlRouterService extends Service{
AndroidTools.sendExplicitBroadcast(service.getApplicationContext(),service.pingIntent, null);
}
break;
+ case TransportConstants.ROUTER_REQUEST_SEND_PFD:
+ ParcelFileDescriptor pfd = (ParcelFileDescriptor) msg.obj;
+ boolean success = false;
+ if(this.provider.get().getParcelFileDescriptor() == null){
+ this.provider.get().setParcelFileDescriptor(pfd);
+ success = true;
+ }
+ if(msg.replyTo!=null){
+ Message message = Message.obtain();
+ message.what = TransportConstants.ROUTER_REQUEST_SEND_PFD;
+ message.arg1 = success ? 1 : 0;
+ try {
+ msg.replyTo.send(message);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ break;
default:
Log.w(TAG, "Unsupported request: " + msg.what);
break;
@@ -672,6 +692,8 @@ public class SdlRouterService extends Service{
if(0 != (getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE)){ //Only allow alt transport in debug mode
return this.altTransportMessenger.getBinder();
}
+ }else if(TransportConstants.BIND_REQUEST_TYPE_USB_TRANSPORT.equals(requestType)){
+ return this.routerStatusMessenger.getBinder();
}else if(TransportConstants.BIND_REQUEST_TYPE_CLIENT.equals(requestType)){
return this.routerMessenger.getBinder();
}else if(TransportConstants.BIND_REQUEST_TYPE_STATUS.equals(requestType)){
@@ -919,11 +941,9 @@ public class SdlRouterService extends Service{
}
}
if(intent.hasExtra(UsbManager.EXTRA_ACCESSORY)){
- UsbAccessory accessory =
- intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
- if(accessory != null){
+ if(mAOAService == null){
toasty("Accessory recv'd in Router Service!");
- mAOAService = new USBTransport(new USBTransportConfig(this, accessory), new ITransportListener() {
+ mAOAService = new USBTransport(new USBTransportConfig(this, parcelFileDescriptor), new ITransportListener() {
@Override
public void onTransportPacketReceived(SdlPacket packet) {
sendPacketToRegisteredApp(packet);
@@ -1244,8 +1264,13 @@ public class SdlRouterService extends Service{
Log.i(TAG, "No AOA transport to use");
}else{
try {
- mAOAService.openConnection();
- } catch (SdlException e) {
+ if(!mAOAService.getIsConnected()) {
+ Log.d(TAG, "Attempting to open USB connection");
+ mAOAService.openConnection();
+ }else{
+ Log.d(TAG, "USB connection is already open");
+ }
+ } catch (Exception e) {
e.printStackTrace();
}
}
@@ -1301,6 +1326,10 @@ public class SdlRouterService extends Service{
if(altTransportService!=null){ //If we still have an alt transport open, then we don't need to tell the clients to close
return;
}
+ if(type == TransportType.USB){
+ mAOAService.disconnect();
+ mAOAService = null;
+ }
Log.e(TAG, "Notifying client service of hardware disconnect.");
connectedTransportType = null;
isTransportConnected = false;
@@ -2732,5 +2761,12 @@ public class SdlRouterService extends Service{
Log.d(TAG, s);
}
+ public void setParcelFileDescriptor(ParcelFileDescriptor pfd){
+ parcelFileDescriptor = pfd;
+ }
+
+ public ParcelFileDescriptor getParcelFileDescriptor(){
+ return parcelFileDescriptor;
+ }
}
diff --git a/sdl_android/src/main/java/com/smartdevicelink/transport/TransportConstants.java b/sdl_android/src/main/java/com/smartdevicelink/transport/TransportConstants.java
index 5d86bc87b..7583f26aa 100644
--- a/sdl_android/src/main/java/com/smartdevicelink/transport/TransportConstants.java
+++ b/sdl_android/src/main/java/com/smartdevicelink/transport/TransportConstants.java
@@ -51,6 +51,7 @@ public class TransportConstants {
public static final String BIND_REQUEST_TYPE_CLIENT = "BIND_REQUEST_TYPE_CLIENT";
public static final String BIND_REQUEST_TYPE_ALT_TRANSPORT = "BIND_REQUEST_TYPE_ALT_TRANSPORT";
+ public static final String BIND_REQUEST_TYPE_USB_TRANSPORT = "BIND_REQUEST_TYPE_USB_TRANSPORT";
public static final String BIND_REQUEST_TYPE_STATUS = "BIND_REQUEST_TYPE_STATUS";
@@ -173,7 +174,9 @@ public class TransportConstants {
* Command to have router service to send a packet
*/
public static final int ROUTER_SEND_PACKET = 0x20;
-
+
+ public static final int ROUTER_REQUEST_SEND_PFD = 0x21;
+ public static final int ROUTER_RESPONSE_RCVD_PFD = 0x22;
//response
diff --git a/sdl_android/src/main/java/com/smartdevicelink/transport/USBTransportConfig.java b/sdl_android/src/main/java/com/smartdevicelink/transport/USBTransportConfig.java
index 4e82807cb..7b02e4e9f 100644
--- a/sdl_android/src/main/java/com/smartdevicelink/transport/USBTransportConfig.java
+++ b/sdl_android/src/main/java/com/smartdevicelink/transport/USBTransportConfig.java
@@ -4,17 +4,24 @@ import com.smartdevicelink.transport.enums.TransportType;
import android.content.Context;
import android.hardware.usb.UsbAccessory;
+import android.os.ParcelFileDescriptor;
public class USBTransportConfig extends BaseTransportConfig {
private Context mainActivity = null;
private UsbAccessory usbAccessory = null;
private Boolean queryUsbAcc = true;
+ private ParcelFileDescriptor parcelFileDescriptor = null;
public USBTransportConfig (Context mainActivity) {
this.mainActivity = mainActivity;
}
+ public USBTransportConfig (Context mainActivity, ParcelFileDescriptor parcelFileDescriptor) {
+ this.mainActivity = mainActivity;
+ this.parcelFileDescriptor = parcelFileDescriptor;
+ }
+
public USBTransportConfig (Context mainActivity, UsbAccessory usbAccessory) {
this.mainActivity = mainActivity;
this.usbAccessory = usbAccessory;
@@ -50,4 +57,12 @@ public class USBTransportConfig extends BaseTransportConfig {
public TransportType getTransportType() {
return TransportType.USB;
}
+
+ public void setParcelFileDescriptor(ParcelFileDescriptor parcelFileDescriptor){
+ this.parcelFileDescriptor = parcelFileDescriptor;
+ }
+
+ public ParcelFileDescriptor getParcelFileDescriptor() {
+ return parcelFileDescriptor;
+ }
} \ No newline at end of file
diff --git a/sdl_android/src/main/java/com/smartdevicelink/transport/UsbTransferProvider.java b/sdl_android/src/main/java/com/smartdevicelink/transport/UsbTransferProvider.java
new file mode 100644
index 000000000..b860da4b7
--- /dev/null
+++ b/sdl_android/src/main/java/com/smartdevicelink/transport/UsbTransferProvider.java
@@ -0,0 +1,163 @@
+package com.smartdevicelink.transport;
+
+import android.annotation.SuppressLint;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.hardware.usb.UsbAccessory;
+import android.hardware.usb.UsbManager;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.smartdevicelink.util.AndroidTools;
+
+import java.lang.ref.WeakReference;
+
+public class UsbTransferProvider {
+ private static final String TAG = "UsbTransferProvider";
+
+ private Context context = null;
+ private boolean isBound = false;
+ Messenger routerServiceMessenger = null;
+ private ComponentName routerService = null;
+ private int flags = 0;
+
+ final Messenger clientMessenger;
+
+ ParcelFileDescriptor usbPfd;
+
+ private ServiceConnection routerConnection= new ServiceConnection() {
+
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ Log.d(TAG, "Bound to service " + className.toString());
+ routerServiceMessenger = new Messenger(service);
+ isBound = true;
+ //So we just established our connection
+ //Register with router service
+ Message msg = Message.obtain();
+ msg.what = 5555; //TransportConstants.USB_CONNECTED_WITH_DEVICE;
+ msg.arg1 = flags;
+ msg.replyTo = clientMessenger;
+ msg.obj = usbPfd;
+ try {
+ routerServiceMessenger.send(msg);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ Log.d(TAG, "UN-Bound from service " + className.getClassName());
+ routerServiceMessenger = null;
+ isBound = false;
+ }
+ };
+
+ public UsbTransferProvider(Context context, ComponentName service, UsbAccessory usbAccessory){
+ if(context == null || service == null || usbAccessory == null){
+ throw new IllegalStateException("Supplied params are not correct. Context == null? "+ (context==null) + " ComponentName == null? " + (service == null) + " Usb Accessory == null? " + usbAccessory);
+ }
+ this.context = context;
+ this.routerService = service;
+ this.clientMessenger = new Messenger(new ClientHandler(this));
+ usbPfd = getFileDescriptor(usbAccessory);
+ if(usbPfd != null){
+ checkIsConnected();
+ }
+
+ }
+
+ @SuppressLint("NewApi")
+ private ParcelFileDescriptor getFileDescriptor(UsbAccessory accessory){
+ UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
+ if(manager != null){
+ return manager.openAccessory(accessory);
+ }
+ return null;
+ }
+
+ public void setFlags(int flags){
+ this.flags = flags;
+ }
+
+ public void checkIsConnected(){
+ if(!AndroidTools.isServiceExported(context,routerService) || !bindToService()){
+ //We are unable to bind to service
+ Log.e(TAG, "Unable to bind to servicec");
+ unBindFromService();
+ }
+ }
+
+ public void cancel(){
+ if(isBound){
+ unBindFromService();
+ }
+ }
+
+ private boolean bindToService(){
+ if(isBound){
+ return true;
+ }
+ if(clientMessenger == null){
+ return false;
+ }
+ Intent bindingIntent = new Intent();
+ bindingIntent.setClassName(this.routerService.getPackageName(), this.routerService.getClassName());//This sets an explicit intent
+ //Quickly make sure it's just up and running
+ context.startService(bindingIntent);
+ bindingIntent.setAction( "TransportConstants.BIND_REQUEST_TYPE_USB");
+ return context.bindService(bindingIntent, routerConnection, Context.BIND_AUTO_CREATE);
+ }
+
+ private void unBindFromService(){
+ try{
+ if(context!=null && routerConnection!=null){
+ context.unbindService(routerConnection);
+ }else{
+ Log.w(TAG, "Unable to unbind from router service, context was null");
+ }
+
+ }catch(IllegalArgumentException e){
+ //This is ok
+ }
+ }
+
+ private void finish(){
+ unBindFromService();
+ routerServiceMessenger =null;
+ }
+
+ static class ClientHandler extends Handler {
+ final WeakReference<UsbTransferProvider> provider;
+
+ public ClientHandler(UsbTransferProvider provider){
+ super(Looper.getMainLooper());
+ this.provider = new WeakReference<UsbTransferProvider>(provider);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if(provider.get()==null){
+ return;
+ }
+ switch (msg.what) {
+ case 5556: //TransportConstants.USB_ACC_RECEIVED:
+ Log.d(TAG, "Successful USB transfer");
+ provider.get().finish();
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+
+}