/****************************************************************************
* Copyright (C) 2012 HS Coburg.
* All rights reserved.
* Contact: ecsec GmbH (info@ecsec.de)
*
* This file is part of the Open eCard App.
*
* GNU General Public License Usage
* This file may be used under the terms of the GNU General Public
* License version 3.0 as published by the Free Software Foundation
* and appearing in the file LICENSE.GPL included in the packaging of
* this file. Please review the following information to ensure the
* GNU General Public License version 3.0 requirements will be met:
* http://www.gnu.org/copyleft/gpl.html.
*
* Other Usage
* Alternatively, this file may be used in accordance with the terms
* and conditions contained in a signed written agreement between
* you and ecsec GmbH.
*
***************************************************************************/
package org.openecard.android.activities;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.XmlResourceParser;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Xml;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import org.openecard.android.ApplicationContext;
import org.openecard.android.R;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
/**
* This Activity finds and opens usb devices and starts the socket communication with libusb.
*
* @author Dirk Petrautzki <petrautzki@hs-coburg.de>
*/
public class DeviceOpenActivity extends Activity {
private static Logger logger = LoggerFactory.getLogger(DeviceOpenActivity.class);
private static final String USB_PERMISSION = "com.android.example.USB_PERMISSION";
private static final String PRODUCT_ID = "product-id";
private static final String VENDOR_ID = "vendor-id";
private static Intent intent;
private static HashMap<String, Integer> fileDescriptors = new HashMap<String, Integer>();
private static Thread t;
private static PendingIntent permissionIntent;
private static ApplicationContext applicationContext;
private final BroadcastReceiver mUsbReceiver = new UsbPermissionReceiver();
private String fdSocket;
private String pathSocket;
static {
System.loadLibrary("LibusbSocketCommunicator");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
logger.debug("onCreate");
intent = getIntent();
fdSocket = getFilesDir().getAbsolutePath() + "/socket";
pathSocket = getFilesDir().getAbsolutePath() + "/socket2";
applicationContext = (ApplicationContext) this.getApplicationContext();
if (t == null) {
t = new Thread(new SocketCommunicationRunnable());
t.start();
}
}
@Override
protected void onStart() {
super.onStart();
logger.debug("onStart");
permissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(USB_PERMISSION), 0);
registerReceiver(mUsbReceiver, new IntentFilter(USB_PERMISSION));
findDevice(DeviceOpenActivity.this);
}
@Override
protected void onDestroy() {
logger.debug("onDestroy");
super.onDestroy();
try {
unregisterReceiver(mUsbReceiver);
} catch (IllegalArgumentException e) {
logger.error(e.getMessage(), e);
}
}
/**
* Opens a connection to a certain usb device and stores its file descriptor.
*
* @param device the usb device to open
*/
public void openDevice(final UsbDevice device) {
final UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
if (device != null && !manager.hasPermission(device)) {
logger.debug("Requesting permission for device {}", device.getDeviceName());
manager.requestPermission(device, permissionIntent);
return;
}
if (device == null || !manager.hasPermission(device)) {
return;
}
final UsbDeviceConnection connection = manager.openDevice(device);
if (connection != null) {
logger.debug("Adding file descriptor {} for {}", connection.getFileDescriptor(), device.getDeviceName());
fileDescriptors.put(device.getDeviceName(), connection.getFileDescriptor());
}
}
/**
* Gets a usb device via intent or the first one returned from the usb service and opens it.
*
* @param ctx Context of the DeviceOpenActivity
*/
public static void findDevice(final DeviceOpenActivity ctx) {
logger.debug("findDevice");
final UsbManager manager = (UsbManager) ctx.getSystemService(Context.USB_SERVICE);
UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (device == null) {
final HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
HashSet<String> allowed = getDeviceData(ctx);
logger.debug("No device in intent; iterating through device list.");
for (String desc : deviceList.keySet()) {
UsbDevice usbDevice = deviceList.get(desc);
String candstr = VENDOR_ID + usbDevice.getVendorId() + PRODUCT_ID
+ usbDevice.getProductId();
if (allowed.contains(candstr)) {
logger.debug("opening device {}", desc);
ctx.openDevice(usbDevice);
}
}
} else {
ctx.openDevice(device);
}
// all available devices added, start next activity
if (intent.getAction() == Intent.ACTION_MAIN || (! applicationContext.isInitialized())) {
// user started the app manually OR attached a supported usb device and the app is closed
logger.debug("Starting AboutActivity.");
Intent i = new Intent(ctx, AboutActivity.class);
ctx.startActivityForResult(i, 1);
} else {
// app is running and a usb device has been attached
ctx.finish();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
logger.debug("onActivityResult; resultCode: " + requestCode);
applicationContext.shutdown();
if (data != null) {
logger.debug("Starting Activity with refresh URL Intent.");
startActivity(data);
} else {
logger.debug("No refresh URL Intent in onActivityResult.");
}
finish();
}
/**
* Parses the device_filter.xml-file and returns a set containing all allowed devices.
*
* @param ctx ApplicationContext to get the xml resource
* @return a HashSet containing the allowed devices from the device list file
*/
private static HashSet<String> getDeviceData(final Context ctx) {
HashSet<String> allowed = new HashSet<String>();
XmlResourceParser xml = ctx.getResources().getXml(R.xml.device_filter);
try {
xml.next();
int eventType;
while ((eventType = xml.getEventType()) != XmlPullParser.END_DOCUMENT) {
switch (eventType) {
case XmlPullParser.START_TAG:
if (xml.getName().equals("usb-device")) {
AttributeSet as = Xml.asAttributeSet(xml);
String vendorId = as.getAttributeValue(null, VENDOR_ID);
String productId = as.getAttributeValue(null, PRODUCT_ID);
allowed.add(VENDOR_ID + vendorId + PRODUCT_ID + productId);
}
break;
}
xml.next();
}
} catch (XmlPullParserException e) {
logger.error("Error parsing device filter xml.", e);
} catch (IOException e) {
logger.error("Error parsing device filter xml.", e);
}
return allowed;
}
/**
* Starts a socket for sending a usb device file descriptor.
*
* @param location path of the socket
* @param fd the file descriptor to pass
*/
public static native void startUnixSocketServer(final String location, int fd);
/**
* Listens on a socket for an incoming usb device path.
*
* @param location path of the socket
* @return recieved device path
*/
public static native String listenUnixSocketServer(final String location);
/**
* Opens an usb device after the user grants permission to use it.
*
* @author Dirk Petrautzki <petrautzki@hs-coburg.de>
*/
private final class UsbPermissionReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (USB_PERMISSION.equals(action)) {
synchronized (this) {
final UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
if (device != null) {
openDevice(device);
} else {
logger.error("Device is null");
}
} else {
logger.error("Usb permission was not granted.");
}
}
}
}
}
/**
* Never ending Runnable implementing the socket communication with libusb.
*
* @author Dirk Petrautzki <petrautzki@hs-coburg.de>
*/
private final class SocketCommunicationRunnable implements Runnable {
@Override
public void run() {
while (true) {
String devicePath = listenUnixSocketServer(pathSocket);
logger.debug("Received a file descriptor request for device {}", devicePath);
Integer deviceDescriptor = null;
for (int i = 0; i < 10; i++) {
deviceDescriptor = fileDescriptors.get(devicePath);
if (deviceDescriptor != null) {
logger.debug("returning device descriptor: {}", deviceDescriptor);
startUnixSocketServer(fdSocket, deviceDescriptor);
break;
}
try {
logger.debug("No matching device descriptor recorded yet; waiting 1 second.");
Thread.sleep(1000);
} catch (InterruptedException e) {
// ignore
}
}
if (deviceDescriptor == null) {
logger.error("No matching device descriptor, returnning -1");
startUnixSocketServer(fdSocket, -1);
}
}
}
}
}