USBUART
A library for reading/wring data via USB-UART adapters
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Modules
UsbUartService.java
Go to the documentation of this file.
1 
9 /* This file is part of USBUART Library. http://usbuart.info/
10  *
11  * Copyright © 2016 Eugene Hutorny <eugene@hutorny.in.ua>
12  *
13  * The USBUART Library is free software; you can redistribute it and/or
14  * modify it under the terms of the GNU Lesser General Public License v2
15  * as published by the Free Software Foundation;
16  *
17  * The USBUART Library is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
20  * See the GNU Lesser General Public License for more details.
21  *
22  * You should have received a copy of the GNU Lesser General Public License
23  * along with the USBUART Library; if not, see
24  * <http://www.gnu.org/licenses/gpl-2.0.html>.
25  */
26 
27 package info.usbuart.service;
28 
29 import android.annotation.TargetApi;
30 import android.app.PendingIntent;
31 import android.app.Service;
32 import android.content.BroadcastReceiver;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.IntentFilter;
36 import android.hardware.usb.*;
37 import android.os.Binder;
38 import android.os.Build;
39 import android.os.IBinder;
40 import android.system.ErrnoException;
41 import android.system.Os;
42 import android.text.TextUtils;
43 import android.util.Log;
44 import info.usbuart.api.Channel;
46 import info.usbuart.api.UsbUartContext;
47 
48 import java.io.File;
49 import java.io.FileDescriptor;
50 import java.io.IOException;
51 import java.util.Collection;
52 import java.util.HashMap;
53 import java.util.HashSet;
54 
55 import static android.system.OsConstants.*;
56 
57 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
58 public class UsbUartService extends Service implements IService {
59 
60  @Override
61  public void onCreate() {
62  super.onCreate();
63  Log.d(TAG, "onCreate");
64  binder.attachInterface(this, IService.class.getCanonicalName());
65  try {
66  IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
67  filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
68  filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
69  registerReceiver(usbReceiver, filter);
70  } catch (Exception e) {
71  Log.e(TAG, e.getMessage());
72  }
73  usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
74  requestPermissions();
75  }
76 
77  @Override
78  public void onDestroy() {
79  Log.d(TAG, "onDestroy");
80  unregisterReceiver(usbReceiver);
81  stop();
82  super.onDestroy();
83  }
84 
85  @Override
86  public IBinder onBind(Intent intent) {
87  Log.d(TAG, "onBind");
88  return binder;
89  }
90 
91  @Override
92  public IBinder asBinder() {
93  return binder;
94  }
95 
96  @Override
97  public boolean onUnbind(Intent intent) {
98  Log.d(TAG, "onUnbind");
99  //TODO detect if there are open devices and stop if none
100  stopSelf();
101  return false;
102  }
103 
104  /****************************************************************************************************************/
105 
106  public void attached(UsbDevice device) {
107  if( acceptable(device) && requestPermissions(device) && broadcastAttached(device) || options.autoOpen ) {
108  open(device);
109  }
110  }
111 
112  private void open(UsbDevice device) {
113  for(int i = 0; i < device.getInterfaceCount(); ++i )
114  try { makeFifo(device, i, options.protocol);
115  } catch (UsbUartContext.Error error) {
116  Log.e(TAG, error.getMessage());
117  } catch (ErrnoException error) {
118  Log.e(TAG, error.getMessage());
119  }
120  }
121 
122  private boolean broadcastAttached(UsbDevice device) {
123  boolean result = false;
124  for(BusListener i : listeners) {
125  result = i.attached(device) || result;
126  }
127  return result;
128  }
129 
130  private void broadcastDetached(UsbDevice device) {
131  for(BusListener i : listeners) {
132  i.detached(device);
133  }
134  }
135 
136  @Override
137  public Collection<UsbDevice> getList() {
138  return devices.values();
139  }
140 
141  @Override
142  public String[] getFifo(String deviceName, int intrface, EIA_TIA_232_Info settings) throws ErrnoException, UsbUartContext.Error {
143  UsbDevice device = devices.get(deviceName);
144  if( device == null ) return null;
145  String[] fifo = fifos.get(deviceName);
146  if( fifo == null )
147  fifo = makeFifo(device, intrface, settings);
148  return fifo;
149  }
150 
151  @Override
152  public void setOptions(Options options) {
153  this.options = options;
154  }
155 
156  @Override
157  public void addBusListener(BusListener listener) {
158  if( listeners.add(listener) ) {
159  for(UsbDevice i : devices.values() ) {
160  if( listener.attached(i) && fifos.get(i.getDeviceName()) == null ) {
161  open(i);
162  }
163  }
164  }
165  }
166 
167  @Override
168  public void removeBusListener(BusListener listener) {
169  listeners.remove(listener);
170  }
171 
172 
173  /****************************************************************************************************************/
174  static public String formatDevice(UsbDevice device) {
175  return String.format("%04x:%04x %s", device.getVendorId(), device.getProductId(), device.getProductName());
176  }
177 
178  private void stop() {
179  for(Channel c : channels.values() ) c.close();
180  channels.clear();
181  for(UsbDevice d: devices.values() ) detached(d);
182  listeners.clear();
183  if(runner != null ) runner.interrupt();
184  }
185 
186  private void detached(UsbDevice device) {
187  fifos.remove(device.getDeviceName());
188  devices.remove(device.getDeviceName());
189  Thread waiter = waiters.get(device.getDeviceName());
190  if( waiter != null ) waiter.interrupt();
191  Channel channel = channels.get(device.getDeviceName());
192  if( channel != null ) channel.close();
193  cleanupFilesFor(device);
194  broadcastDetached(device);
195  }
196 
197  private boolean acceptable(UsbDevice device) {
198  if( device.getDeviceClass() == UsbConstants.USB_CLASS_MASS_STORAGE ) return false;
199  if( device.getInterfaceCount() == 0 ) return false;
200  return !(device.getInterface(0).getInterfaceClass() == UsbConstants.USB_CLASS_MASS_STORAGE);
201  }
202 
203  private static String append(String base, int i, String ext) {
204  return String.format("%s/%d.%s", base, i, ext);
205  }
206 
207  private String deviceName2Path(UsbDevice device) {
208  String fmt = "%s/fifo";
209  switch (options.naming) {
210  case BusIDDevID:
211  fmt = "%s/%4$s/%5$s";
212  break;
213  case VIDPID:
214  fmt = "%s/%04X:%04X";
215  break;
216  case Static:
217  }
218  String[] parts = TextUtils.split(device.getDeviceName(),"/");
219  return String.format(fmt,getFilesDir().getAbsolutePath(),
220  device.getVendorId(), device.getProductId(), /* VIDPID */
221  parts[parts.length - 2], parts[parts.length - 1] /* BusIDDevID */
222  );
223  }
224 
225  private void cleanupFilesFor(UsbDevice device) {
226  String path = deviceName2Path(device);
227  if ( new File(path).exists() ) {
228  path = "rm -rf " + path;
229  try { Runtime.getRuntime().exec(path);
230  } catch (IOException ignore) { }
231  }
232  }
233 
234  private String[] makeFifo(final UsbDevice device, final int ifc, final EIA_TIA_232_Info settings)
235  throws ErrnoException, UsbUartContext.Error {
236  int n = device.getInterfaceCount();
237  if( n == 0 ) return null;
238 
239  String[] res = fifos.get(device.getDeviceName());
240  if( res != null ) return res;
241 
242  context();
243 
244  File dir = new File(deviceName2Path(device));
245  dir.mkdirs();
246  Os.chmod(dir.getAbsolutePath(), options.d_mod);
247 
248  final String rd = append(dir.getAbsolutePath(), ifc, "r");
249  if(! new File(rd).exists() ) {
250  Os.mkfifo(rd, options.f_mod & ~ S_IWOTH);
251  Os.chmod(rd, options.f_mod & ~ S_IWOTH);
252  }
253  final String wr = append(dir.getAbsolutePath(), ifc, "w");
254  if(! new File(wr).exists() ) {
255  Os.mkfifo(wr, options.f_mod & ~ S_IROTH);
256  Os.chmod(wr, options.f_mod & ~ S_IROTH);
257  }
258  /*
259  A process can open a FIFO in non-blocking mode. In this case, opening for read only will succeed even if no
260  one has opened on the write side yet; opening for write only will fail with ENXIO (no such device or address)
261  unless the other end has already been opened.
262  */
263  String[] fifo = new String[] {rd, wr};
264  Log.d(TAG, "opening " + wr);
265  final FileDescriptor fdrd = Os.open(wr, O_RDONLY | O_NONBLOCK, options.f_mod);
266  Thread waiter = new Thread(new Runnable() {
267  @Override
268  public void run() {
269  Log.d(TAG, "opening " + rd);
270  FileDescriptor fdwr = BAD_FD;
271  try {
272  fdwr = Os.open(rd, O_WRONLY, options.f_mod);
273  Log.d(TAG, "opened " + rd);
274  UsbDeviceConnection deviceConnection = usbManager.openDevice(device);
275  Log.d(TAG, "opened " + device.getDeviceName());
276  Channel channel = context().attach(deviceConnection, ifc, fdrd, fdwr, settings);
277  synchronized (channels) {
278  channels.put(device.getDeviceName(), channel);
279  }
280  Log.i(TAG, "attached " + device.getDeviceName() + " via " + rd);
281  } catch (ErrnoException e) {
282  if( fdwr != BAD_FD ) try { Os.close(fdwr); } catch (Throwable ignore) {}
283  try { Os.close(fdrd); } catch (Throwable ignore) {}
284  Log.i(TAG, e.getMessage());
285  } catch (UsbUartContext.Error e) {
286  if( fdwr != BAD_FD ) try { Os.close(fdwr); } catch (Throwable ignore) {}
287  try { Os.close(fdrd); } catch (Throwable ignore) {}
288  Log.i(TAG, e.getMessage());
289  }
290  fifos.remove(device.getDeviceName());
291  }
292  });
293  waiters.put(device.getDeviceName(), waiter);
294  waiter.start();
295 
296  fifos.put(device.getDeviceName(), fifo);
297  return fifo;
298  }
299 
300  private boolean requestPermissions(UsbDevice device) {
301  if( ! usbManager.hasPermission(device) ) {
302  PendingIntent intent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
303  usbManager.requestPermission(device, intent);
304  return false;
305  }
306  devices.put(device.getDeviceName(), device);
307  return true;
308  }
309 
310  private void requestPermissions() {
311  HashMap<String, UsbDevice> devices = usbManager.getDeviceList();
312  for(UsbDevice device : devices.values()) {
313  if( acceptable(device) )
314  requestPermissions(device);
315  }
316  }
317 
318  /**************************************************************************************************************/
319  private final BroadcastReceiver usbReceiver = new BroadcastReceiver() {
320  public void onReceive(Context context, Intent intent) {
321  String action = intent.getAction();
322  if( ACTION_USB_PERMISSION.equals(action) ) {
323  synchronized (this) {
324  final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
325  if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
326  if(device != null){
327  Log.d(TAG, "! " + formatDevice(device));
328  context().hotplug(usbManager.openDevice(device));
329  }
330  } else {
331  Log.d(TAG, "? Permission denied on " + formatDevice(device));
332  Log.d(TAG, "Class: "+ device.getDeviceClass());
333  }
334  }
335  } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
336  UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
337  if( device != null ) {
338  Log.d(TAG, "- " + formatDevice(device));
339  detached(device);
340  }
341  } else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
342  final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
343  if( device != null ){
344  if (usbManager.hasPermission(device)) {
345  Log.d(TAG, "+ " + formatDevice(device));
346  attached(device);
347  } else {
348  Log.w(TAG, "? " + formatDevice(device));
349  usbManager.requestPermission(device,
350  PendingIntent.getBroadcast(UsbUartService.this, 0, new Intent(ACTION_USB_PERMISSION), 0));
351  }
352  }
353  }
354  }
355  };
356 
357 
358  private UsbUartContext context() {
359  if( ctx == null )
360  synchronized (this) {
361  if( ctx == null )
362  ctx = new UsbUartContext();
363  runner = new Thread(ctx);
364  runner.start();
365  }
366  return ctx;
367 
368  }
369 
370  private final Binder binder = new Binder();
371 
372  private Thread runner;
373  private UsbUartContext ctx;
374  private UsbManager usbManager;
375  private HashMap<String, UsbDevice> devices = new HashMap<>();
376  private HashMap<String, String[]> fifos = new HashMap<>();
377  private HashMap<String, Channel> channels = new HashMap<>();
378  private Options options = new Options(EIA_TIA_232_Info._115200_8N1n());
379  private final HashSet<BusListener> listeners = new HashSet<>();
380  private final HashMap<String, Thread> waiters = new HashMap<>();
381 
382  private static final String TAG = "UsbUartService";
383  private static final FileDescriptor BAD_FD = new FileDescriptor();
384 }
An interface for accessing a USB-UART device via I/O streams.
Definition: Channel.java:31