How to implement Android Open Accessory mode as a service? - android

I've been playing around with the Android Open Accessory Development Kit. By following the DemoKit example provided by Google, I've had no trouble in adapting the solution to my application. I can detect, communicate, and detach the accessory just fine.
However, I would need to run the whole thing as a service. I have a base activity which is launched by the USB_ACCESSORY_ATTACHED intent (that is, when the accessory is connected), and that works fine. But as soon as I start my service and run identical code in it compared to my working solution within a regular activity, I'm receiving an IOException ("no such device") whenever I'm trying to communicate with the accessory (monitoring arduino side shows a successful USB connection). This happens even though I've specified the correct BroadcastReceiver within the service, registered it in the onStartCommand callback method, and set up the communication endpoints with openAccessory(). Relevant code is as follows.
#Override
public void onCreate() {
Log.d(TAG, "ONCREATE");
manager = UsbManager.getInstance(this);
mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(
ACTION_USB_PERMISSION), 0);
// Register broadcastreceiver for filtering accessory events
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
filter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED);
registerReceiver(mUsbReceiver,filter);
super.onCreate();
}
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "ONSTARTCOMMAND METHOD ACCESSED");
if (mInputStream != null && mOutputStream != null) {
return START_NOT_STICKY;
}
UsbAccessory[] accessories = manager.getAccessoryList();
mAccessory = (accessories == null ? null : accessories[0]);
if (mAccessory != null) {
if (manager.hasPermission(mAccessory)) {
openAccessory();
} else {
synchronized (mUsbReceiver) {
if (!mPermissionRequestPending) {
manager.requestPermission(mAccessory,
mPermissionIntent);
mPermissionRequestPending = true;
}
}
}
} else {
Log.d(TAG, "mAccessory is null");
}
return START_NOT_STICKY;
}
openAccessory method:
/**
* Open the accessory
*/
private void openAccessory() {
Log.d(TAG, "openAccessory: "+mAccessory);
mFileDescriptor = manager.openAccessory(mAccessory);
if (mFileDescriptor != null) {
FileDescriptor fd = mFileDescriptor.getFileDescriptor();
mInputStream = new FileInputStream(fd);
mOutputStream = new FileOutputStream(fd);
Thread thread = new Thread(null,this,"AccessoryThread");
thread.start();
}
}
Any ideas for a possible solution?

The solution was simple.
if (intent.getAction().equals(USB_ACCESSORY_ATTACHED)) {
Intent i = new Intent(this, YourServiceName.class);
i.putExtras(intent);
startService(i);
}
Basically, copy the intent that you received when starting your activity that you use to launch the service, because the intent contains the details of the accessory that the ADK implementation needs.
Then, in the service proceed to implement the rest of ADK exactly as before.

At the moment I can't give you a matching solution to your problem. But may be this
github example shows you how to solve your problem:
I am going to analyse the code given on github to implement just the same thing you're going to do.

Related

What is the event listener for when UsbManager Request Permission is called to execute a routine when a permission is granted?

I'm stuck on figuring out how to ask for permission to access a USB com device, wait for the user input, then proceed accordingly if permission granted. I can't figure out what the "onRequestPermissionsResult" is for when UsbManager asks for permissions. I noticed that listener never gets called when UsbManager requests permission, so its not used in the way I originally thought.
This code is all in the MainActivity.
Here I'm setting my Intent for when my USB device is connected or disconnected, and initializing UsbManager.
Note I'm not using LOGCAT to log debug messages because my Android device has to be disconnected from Android Studio to plug in the USB com device I'm developing the app for. Instead I'm logging to the app UI.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DoIntent();
m_manager = (UsbManager) getSystemService(Context.USB_SERVICE);
}
private void DoIntent () {
m_usbReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action) || UsbManager.ACTION_USB_ACCESSORY_ATTACHED.equals(action)) {
try {
OnDeviceConnected();
// m_textViewDebug.setText("USB Connected");
} catch (Exception e) {
m_textViewDebug.setText(e.getMessage());
}
} else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action) || UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) {
m_port = null;
m_serialIoManager = null;
m_isInitialized = false;
m_textViewDebug.setText("USB Disconnected");
}
}
};
IntentFilter filter = new IntentFilter();
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
registerReceiver(m_usbReceiver , filter);
Then here is what happens when a device is connected. I want to establish permission as soon as its connected.
private void OnDeviceConnected () throws Exception {
ProbeTable customTable = new ProbeTable();
customTable.addProduct(0x239a, 0x800c, CdcAcmSerialDriver.class);
UsbSerialProber prober = new UsbSerialProber(customTable);
List<UsbSerialDriver> drivers = prober.findAllDrivers(m_manager);
UsbDeviceConnection connection = null;
UsbSerialDriver driver = drivers.get(0);
PendingIntent usbPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(INTENT_ACTION_GRANT_USB), 0);
m_manager.requestPermission(driver.getDevice(), usbPermissionIntent);
/// Need some kind of pause or check for permissions here before executing forward.or
/// handle everything after on a different routine called after permission has been selected.
/// Continues to execute before user has time to respond to permissions.
try {
connection = m_manager.openDevice(driver.getDevice());
} catch (Exception e) {
throw new Exception(e.getMessage());
}
if (connection == null) {
throw new Exception ("Could not open device.");
}
m_port = driver.getPorts().get(0);
try {
m_port.open(connection);
m_port.setParameters(115200, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE);
m_port.setDTR(true);
} catch (Exception e) {
throw new Exception(e.getMessage());
}
m_serialIoManager = new SerialInputOutputManager(m_port, m_listener);
m_executor.submit(m_serialIoManager);
m_isInitialized = true;
}
Then here is what I'm originally trying to do once permission has been granted.
I can't get any logging message to appear from this scope, so I believe it's never being called and I'm using it incorrectly.
#Override
public void onRequestPermissionsResult(final int requestCode, String[] permissions, int[] grantResults) {
MainActivity.this.runOnUiThread(new Runnable() {
public void run() {
/// Never gets called :/
m_textViewDebug.setText(Integer.toString(requestCode));
}
});
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
/// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
/// permission was granted, yay! Do the
/// contacts-related task you need to do.
} else {
/// permission denied, boo! Disable the
/// functionality that depends on this permission.
}
return;
case USB_PERMISSION_GRANTED: { /// Not the real enum because I'm not sure where to find what it is.
try {
/// Need to somehow pass driver from OnDeviceConnected to this scope, or make it class property.
connection = m_manager.openDevice(driver.getDevice());
} catch (Exception e) {
throw new Exception(e.getMessage());
}
if (connection == null) {
throw new Exception ("Could not open device.");
}
m_port = driver.getPorts().get(0);
try {
m_port.open(connection);
m_port.setParameters(115200, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE);
m_port.setDTR(true);
} catch (Exception e) {
throw new Exception(e.getMessage());
}
m_serialIoManager = new SerialInputOutputManager(m_port, m_listener);
m_executor.submit(m_serialIoManager);
m_isInitialized = true
}
/// other 'case' lines to check for other
/// permissions this app might request.
}
}
I'm trying to log what requestCode is so I can write a case for whatever the USB permission code is. I can't find a compiled list anywhere in the docs of what all the options are that requestCode could be.
MY_PERMISSIONS_REQUEST_READ_CONTACTS actually throws a compile error because I have no idea where it comes from. This guide isn't very detail with USB specifically. That guide is also where I got the switch statement in my above routine.
EDIT:
I tried messing around with UsbManager.EXTRA_PERMISSION_GRANTED to see if that could work. I added it as an action to my intent filter.
IntentFilter filter = new IntentFilter();
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
filter.addAction(UsbManager.EXTRA_PERMISSION_GRANTED);
registerReceiver(m_usbReceiver, filter);
Then I'm logging what the action of the intent is coming into my Broadcast Receiver, but nothing happens when a USB permission is granted or denied.
What kind of action or event is triggered when "OK" is tapped in this above image? Been scratching my head at this for a few days poking at the API.
I finally figured it out taking a closer look at this.
I have this before trying to establish an intent filter.
public static final String INTENT_ACTION_GRANT_USB = BuildConfig.APPLICATION_ID + ".GRANT_USB";
PendingIntent usbPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(INTENT_ACTION_GRANT_USB), 0);
IntentFilter filter = new IntentFilter();
And I changed it to this.
private static final String ACTION_USB_PERMISSION = BuildConfig.APPLICATION_ID + ".USB_PERMISSION";
m_permissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
The key difference was that new IntentFilter needed that ACTION_USB_PERMISSION string.
Now in my Broadcast Receiver, I have this condition that is being called as expected.
else if (ACTION_USB_PERMISSION.equals(action)) {
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
m_textViewDebug.setText("USB Permission Granted");
try {
OnDevicePermissionGranted();
} catch (Exception e) {
m_textViewDebug.setText(e.getMessage());
}
}
else {
m_textViewDebug.setText("USB Permission Denied");
}
}
Took me a while to figure out how to use EXTRA_PERMISSION_GRANTED.
When this here says "EXTRA_PERMISSION_GRANTED containing boolean indicating whether permission was granted by the user" I'm thinking this whole time I'm trying to find a boolean flag on some object to verify permission. I didn't realize I had to call a special method on the intent and supply that string to get my true or false. Seems very counter intuitive to me.
I realize the biggest mistake was not supplying the correct string when making the new intent filter. I found a bunch of other examples that had it without any arguments.

App asks for USB permission on every Phone restart

I'm using an USB-Receiver to handle the Communication with a Temperature Sensor attached via USB to the Phone.
Everything is working fine so far, but if i restart the Phone, the App throws an USB-Permission pop-up directly after restarting, even if there isn't any USB-Device attached to the Phone at that Moment.
Has anyone an Idea of what's causing this strange Problem?
[ EDIT: I'm Sorry, the App isn't asking for USB Permission, the popup asks if i want to Open the app if "this device is connected" but there's obviously no device attached.]
Here is the Code of the USB-Receiver:
//Initial USB Settings Receiver
private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
#Override
public void onReceive(final Context context, final Intent intent) {
final String action = intent.getAction();
if (ACTION_USB_PERMISSION.equals(action)) {
synchronized (this) {
final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
// is usb permission has been granted, try to open a connection
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
if (device != null) {
// call method to set up device communication
Constants result = mcp2221.open();
if (result != Constants.SUCCESS) {
//nothing by now
} else {
openConnectionToMCP2221();
}
}
}
}
}
if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
// close the connection and release all resources
closeAllResources();
// leave a bit of time for the COM thread to close
try {
Thread.sleep(20);
}catch(InterruptedException e){e.printStackTrace();}
mcp2221Comm = null;
Toast.makeText(getApplicationContext(), "Device detached",
Toast.LENGTH_SHORT).show();
}
if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (device != null) {
Toast.makeText(getApplicationContext(), "Device attached",
Toast.LENGTH_SHORT).show();
mStopUSBThread=false;
// only try to connect if an MCP2221 is attached
if (device.getVendorId() == MCP2221_VID && device.getProductId() == MCP2221_PID) {
Constants result = mcp2221.open();
switch (result) {
case SUCCESS:
openConnectionToMCP2221();
break;
case CONNECTION_FAILED:
Toast.makeText(getApplicationContext(), "ERROR: connection failed", Toast.LENGTH_SHORT).show();
break;
case NO_USB_PERMISSION:
Toast.makeText(getApplicationContext(), "ERROR: no USB permission", Toast.LENGTH_SHORT).show();
mcp2221.requestUsbPermission(mPermissionIntent);
break;
default:
break;
}
}
}
}
}
};
And here is the onCreate() part:
//USB Connection
mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
final IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
registerReceiver(mUsbReceiver, filter);
//Checking if theres a Device already connected
.......
Got it!
It was an
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"/>
</intent-filter>
that was declared in AndroidManifest.xml under <activity>.
I don't fully understand, why it caused this Bug, but removing it kept the functionality of my App while getting rid of the Problem.
Only 'negative' aspect might be, that now the App doesn't ask to open if the Sensor is attached to the phone.

Play Video using connected USB via OTG cable in Android?

I want to ask that there are application available in which user can connect USB to Android via OTG cable device and play the media (specially videos) contained by it.
i have made a Broadcast Receiver to detect the attached USB, i want to read the content also. I am using this code snippet.
private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
synchronized (this) {
UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if(device != null){
//
Log.d("1","DEATTCHED-" + device);
}
}
}
//
if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
synchronized (this) {
UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
if(device != null){
//
Log.d("1","ATTACHED-" + device);
}
}
else {
PendingIntent mPermissionIntent;
mPermissionIntent = PendingIntent.getBroadcast(MainActivity.this, 0, new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_ONE_SHOT);
mUsbManager.requestPermission(device, mPermissionIntent);
}
}
}
//
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){
//
Log.d("1","PERMISSION-" + device);
}
}
}
}
}
};
I want to make such kind of application.
Do anyone have some idea about that?
After researches of many days i have found the solution
https://github.com/1hakr/AnExplorer
Dividing the video in multiple sub videos may work for you.
What I suggest : instead of saving full video in one temp path, divide that video in multiple sub videos and then replaced AVPlayerItem property of AVPlayer accordingly.
So now functionality is working same as video streaming . :)
You can also convert the CMSampleBuffer that the AVAssetReader returns into a CGImage and then a UIImage and display that in a UIImageView, to render the frames as they are pulled out of the original video file.
There is example code inside the AVFoundation Programming Guide that shows how to do this conversion.

monitor file receiving via bluetooth

I'm now trying to transfer files between Android devices via bluetooth. I already implemented my sender side. I'm not using InputStream/OutputStream. I'm using Intent.ACTION_SEND. Everything in there sender side works fine, but when it comes to the receiver side, I'm facing two problems.
There's the pop out notification saying "Do you want to receive this file?". Is there any way I can avoid this thing?
How can I know that there's a file coming in and the file transfer is finished or stopped at the receiver side?
It seems that these two problems can be solved using InputStream/OutputStream, but I don't really want to use them. Maybe a listener that monitors Bluetooth, or some functions in BluetoothAdapter/BluetoothDevice can do this?
Thanks for help. My code is like below: (in my MainActivity.java)
public void beginBT() {
if (isSender) {
File file = new File(Environment.getExternalStorageDirectory().getPath()+"/log.txt");
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
if (!findBluetoothForIntent(intent)){
Toast.makeText(this, "Bluetooth Not Found.", Toast.LENGTH_SHORT).show();
} else {
//intent will send the file via bluetooth
startActivity(intent);
}
} else { //receiver side
//make device be discoverable
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);
}
}
public boolean findBluetoothForIntent(Intent intent){
List appsList = getPackageManager().queryIntentActivities(intent, 0);
String packageName = null;
String className = null;
for (Object info: appsList){
if (info instanceof ResolveInfo) {
packageName = ((ResolveInfo) info).activityInfo.packageName;
if (packageName.equals("com.android.bluetooth")){
className = ((ResolveInfo) info).activityInfo.name;
break;
}
}
}
if (className != null) {
intent.setClassName(packageName, className);
return true;
} else {
return false;
}
}
Answering my own question is always so much fun!!
About the pop out notification, I can't do anything about it, unless I use InStream/OutStream.
For the receiver side, use BroadcastReceiver to monitor actions of the device. Here I monitor the disconnecting action of bluetooth. Because there will be a connecting action when the device begin to receive file, and when it finishes, there will be a disconnecting action.
Don't know if the following code would help anybody, :)
MainActivity.java
private static final String BT_DISCONNECTED = "android.bluetooth.device.action.ACL_DISCONNECTED";
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action == BT_DISCONNECTED) {
//now file transmitting has finished, can do something to the file
//if you know the file name, better to check if the file is actually there
// - make sure this disconnection not initiated by any other reason.
}
}
IntentFileter filter = new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED);
this.registerReceiver(mReceiver, filter);
Warning: remember to unregister this receiver when you exit the activity, or simply when you don't need it

Long running ADK Accessory (Service?)

I've spent days trying to get the android ADK connection running in a service rather then activity..... Anyone know if its even possible?
I would like to have the service handled inputStream and outputStream so I can read my Arduino for extended periods in the background.
When the activity returns to focus I will bind with the service and update the GUI. If this is possible I would eventually like to update a website with the live data from the service for remote monitoring.
Any help if appreciated. I'm new to programming and can't seem to find much info on this topic.
Thank you in advace for the help.
I'm also trying it and I've found out this.
http://robotgrrl.com/blog/2011/11/29/android-adk-background-service/
I was able to get a ADK connection running in the following way (not complete code. Only the basic building blocks):
First I have an activity that the receives adk intent broadcasts (android system service bases on the adk meta data and the manifest).
private static final String USB_ACCESSORY_ATTACHED = "android.hardware.usb.action.USB_ACCESSORY_ATTACHED";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getIntent().getAction() != null && getIntent().getAction().equals(USB_ACCESSORY_ATTACHED)) {
Intent service = new Intent(this, ADKservice.class);
service.putExtras(getIntent());
startService(service);
Intent launch = new Intent(this, MainActivity.class);
launch.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(launch);
}
this.finish();
}
If the intent matches the adk string it will start the adk service and pass the intent information to the service, launch the user interface activity and finish itself.
The user interface (MainActivity) now binds to the service just like any other service so it can call public methods and/or receive data via the service callbacks (local broadcast's can also be used).
The ADKservice extends Runnable to monitor the usb connection. It also registers a receiver for adk disconnect so it can stop if the device gets disconnected:
#Override
public void onCreate() {
IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_ACCESSORY_DETACHED);
registerReceiver(mUsbReceiver, filter);
mNotificationManager =(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_launcher)
.setContentTitle("ADK Service")
.setContentText("Started");
startForeground(notifyID, mBuilder.build());
super.onCreate();
}
After onCreate has finished the service will call onStartCommand where the adk initialization starts.
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartup " + mAccessory );
mAccessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
if (mAccessory != null) {
openAccessory(mAccessory);
}
return super.onStartCommand(intent, flags, startId);
}
private void openAccessory(UsbAccessory accessory) {
Log.d(TAG, "openAccessory: " + accessory);
UsbManager mUsbManager = (UsbManager) getApplicationContext().getSystemService(Context.USB_SERVICE);
mFileDescriptor = mUsbManager.openAccessory(accessory);
if (mFileDescriptor != null) {
FileDescriptor fd = mFileDescriptor.getFileDescriptor();
mInputStream = new FileInputStream(fd);
mOutputStream = new FileOutputStream(fd);
thread = new Thread(null, this, "ADKserviceThread");
thread.start(); // start runnable
}
public void run() {
// handle adk "usb" messages here
}
#Override
public void onDestroy() {
closeAccessory();
stopForeground(true);
super.onDestroy();
}
private void closeAccessory() {
try {
if (mFileDescriptor != null) {
mFileDescriptor.close();
}
} catch (IOException e) {
} finally {
mFileDescriptor = null;
mAccessory = null;
}
}
private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if(UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) {
closeAccessory();
stopSelf();
}
}
};
The handling of the connection might need some tweaks but the overall concept seems to work. I hope that helps everyone! It seems easy now but it took me a long time to here (I'm repetitively new to programming)
You should handle the loading of the accessory in the main activity like here, and then pass a reference to the accessory object to a service.
You can now bind the service from any activity and get access to the input/output streams.
You can also close the activity, and when you disconnect the accessory, the activity should catch the intent broadcast and relaunch to perform the closeAccessory routine.

Categories

Resources