Skip to content

Commit fbaf567

Browse files
committed
IOIO: updates for android module
1 parent 357fbc8 commit fbaf567

13 files changed

Lines changed: 500 additions & 22 deletions

ioio/ioio/src/main/java/ioio/lib/AndroidUtil.java renamed to ioio/ioio/src/main/java/ioio/lib/android/AndroidUtil.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package ioio.lib;
1+
package ioio.lib.android;
22

33
public class AndroidUtil {
44
private static final boolean isRunningOnAndroid;
Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
/*
2+
* Copyright 2015 Ytai Ben-Tsvi. All rights reserved.
3+
*
4+
*
5+
* Redistribution and use in source and binary forms, with or without modification, are
6+
* permitted provided that the following conditions are met:
7+
*
8+
* 1. Redistributions of source code must retain the above copyright notice, this list of
9+
* conditions and the following disclaimer.
10+
*
11+
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
12+
* of conditions and the following disclaimer in the documentation and/or other materials
13+
* provided with the distribution.
14+
*
15+
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
16+
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
17+
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ARSHAN POURSOHI OR
18+
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
21+
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
22+
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
23+
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24+
*
25+
* The views and conclusions contained in the software and documentation are those of the
26+
* authors and should not be interpreted as representing official policies, either expressed
27+
* or implied.
28+
*/
29+
30+
package ioio.lib.android.accessory;
31+
32+
import ioio.lib.android.accessory.Adapter.UsbAccessoryInterface;
33+
import ioio.lib.api.IOIOConnection;
34+
import ioio.lib.api.exception.ConnectionLostException;
35+
import ioio.lib.impl.FixedReadBufferedInputStream;
36+
import ioio.lib.spi.IOIOConnectionBootstrap;
37+
import ioio.lib.spi.IOIOConnectionFactory;
38+
import ioio.lib.spi.NoRuntimeSupportException;
39+
40+
import java.io.BufferedOutputStream;
41+
import java.io.FileDescriptor;
42+
import java.io.FileInputStream;
43+
import java.io.FileOutputStream;
44+
import java.io.IOException;
45+
import java.io.InputStream;
46+
import java.io.OutputStream;
47+
import java.util.Collection;
48+
49+
import android.app.PendingIntent;
50+
import android.content.BroadcastReceiver;
51+
import android.content.Context;
52+
import android.content.ContextWrapper;
53+
import android.content.Intent;
54+
import android.content.IntentFilter;
55+
import android.os.ParcelFileDescriptor;
56+
import android.util.Log;
57+
58+
public class AccessoryConnectionBootstrap extends BroadcastReceiver implements IOIOConnectionBootstrap, IOIOConnectionFactory {
59+
private static final String TAG = AccessoryConnectionBootstrap.class.getSimpleName();
60+
private static final String ACTION_USB_PERMISSION = "ioio.lib.accessory.action.USB_PERMISSION";
61+
62+
private ContextWrapper activity;
63+
private final Adapter adapter;
64+
private Adapter.AbstractUsbManager usbManager;
65+
private boolean shouldTryOpen = false;
66+
private PendingIntent pendingIntent;
67+
private ParcelFileDescriptor fileDescriptor;
68+
private InputStream inputStream;
69+
private OutputStream outputStream;
70+
71+
public AccessoryConnectionBootstrap() throws NoRuntimeSupportException {
72+
adapter = new Adapter();
73+
}
74+
75+
//@Override
76+
public void onCreate(ContextWrapper wrapper) {
77+
activity = wrapper;
78+
usbManager = adapter.getManager(wrapper);
79+
registerReceiver();
80+
}
81+
82+
//@Override
83+
public void onDestroy() {
84+
unregisterReceiver();
85+
}
86+
87+
@Override
88+
public synchronized void onReceive(Context context, Intent intent) {
89+
final String action = intent.getAction();
90+
if (ACTION_USB_PERMISSION.equals(action)) {
91+
pendingIntent = null;
92+
if (intent.getBooleanExtra(usbManager.EXTRA_PERMISSION_GRANTED, false)) {
93+
notifyAll();
94+
} else {
95+
Log.e(TAG, "Permission denied");
96+
}
97+
}
98+
}
99+
100+
//@Override
101+
public synchronized void open() {
102+
notifyAll();
103+
}
104+
105+
//@Override
106+
public synchronized void reopen() {
107+
notifyAll();
108+
}
109+
110+
//@Override
111+
public synchronized void close() {
112+
}
113+
114+
private synchronized void disconnect() {
115+
// This should abort any current open attempt.
116+
shouldTryOpen = false;
117+
notifyAll();
118+
119+
// And this should kill any ongoing connections.
120+
if (fileDescriptor != null) {
121+
try {
122+
fileDescriptor.close();
123+
} catch (IOException e) {
124+
Log.e(TAG, "Failed to close file descriptor.", e);
125+
}
126+
fileDescriptor = null;
127+
}
128+
129+
if (pendingIntent != null) {
130+
pendingIntent.cancel();
131+
pendingIntent = null;
132+
}
133+
}
134+
135+
@Override
136+
public IOIOConnection createConnection() {
137+
return new Connection();
138+
}
139+
140+
@Override
141+
public void getFactories(Collection<IOIOConnectionFactory> result) {
142+
result.add(this);
143+
}
144+
145+
@Override
146+
public String getType() {
147+
return Connection.class.getCanonicalName();
148+
}
149+
150+
@Override
151+
public Object getExtra() {
152+
return null;
153+
}
154+
155+
private synchronized void waitForConnect(Connection connection) throws ConnectionLostException {
156+
// In order to simplify the connection process in face of many different sequences of events
157+
// that might occur, we collapsed the entire sequence into one non-blocking method,
158+
// tryOpen(), which tries the entire process from the beginning, undoes everything if
159+
// something along the way fails and always returns immediately.
160+
// This method, simply calls tryOpen() in a loop until it succeeds or until we're no longer
161+
// interested. Between attempts, it waits until "something interesting" has happened, which
162+
// may be permission granted, the client telling us to try again (via reopen()) or stop
163+
// trying, etc.
164+
shouldTryOpen = true;
165+
while (shouldTryOpen) {
166+
if (tryOpen()) {
167+
// Success!
168+
return;
169+
}
170+
forceWait();
171+
}
172+
throw new ConnectionLostException();
173+
}
174+
175+
private void forceWait() {
176+
try {
177+
wait();
178+
} catch (InterruptedException e) {
179+
Log.e(TAG, "Do not interrupt me!");
180+
}
181+
}
182+
183+
private boolean tryOpen() {
184+
// Find the accessory.
185+
UsbAccessoryInterface[] accessories = usbManager.getAccessoryList();
186+
UsbAccessoryInterface accessory = (accessories == null ? null : accessories[0]);
187+
188+
if (accessory == null) {
189+
Log.v(TAG, "No accessory found.");
190+
return false;
191+
}
192+
193+
// Check for permission to access the accessory.
194+
if (!usbManager.hasPermission(accessory)) {
195+
if (pendingIntent == null) {
196+
Log.v(TAG, "Requesting permission.");
197+
pendingIntent = PendingIntent.getBroadcast(activity, 0, new Intent(
198+
ACTION_USB_PERMISSION), 0);
199+
usbManager.requestPermission(accessory, pendingIntent);
200+
}
201+
return false;
202+
}
203+
204+
boolean success = false;
205+
206+
// From this point on, if anything goes wrong, we're responsible for canceling the intent.
207+
try {
208+
// Obtain a file descriptor.
209+
fileDescriptor = usbManager.openAccessory(accessory);
210+
if (fileDescriptor == null) {
211+
Log.v(TAG, "Failed to open file descriptor.");
212+
return false;
213+
}
214+
215+
// From this point on, if anything goes wrong, we're responsible for closing the file
216+
// descriptor.
217+
try {
218+
FileDescriptor fd = fileDescriptor.getFileDescriptor();
219+
// Apparently, some Android devices (e.g. Nexus 5) only support read operations of
220+
// multiples of the endpoint buffer size. So there you have it!
221+
inputStream = new FixedReadBufferedInputStream(new FileInputStream(fd), 1024);
222+
outputStream = new BufferedOutputStream(new FileOutputStream(fd), 1024);
223+
224+
// Soft-open the connection
225+
outputStream.write(0x00);
226+
outputStream.flush();
227+
228+
// We're going to block now. We're counting on the IOIO to
229+
// write back a byte, or otherwise we're locked until
230+
// physical disconnection. This is a known OpenAccessory
231+
// bug:
232+
// http://code.google.com/p/android/issues/detail?id=20545
233+
while (inputStream.read() != 1) {
234+
trySleep(1000);
235+
}
236+
237+
success = true;
238+
return true;
239+
} catch (IOException e) {
240+
Log.v(TAG, "Failed to open streams", e);
241+
return false;
242+
} finally {
243+
if (!success) {
244+
try {
245+
fileDescriptor.close();
246+
} catch (IOException e) {
247+
Log.e(TAG, "Failed to close file descriptor.", e);
248+
}
249+
fileDescriptor = null;
250+
}
251+
}
252+
} finally {
253+
if (!success && pendingIntent != null) {
254+
pendingIntent.cancel();
255+
pendingIntent = null;
256+
}
257+
}
258+
}
259+
260+
private void registerReceiver() {
261+
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
262+
activity.registerReceiver(this, filter);
263+
}
264+
265+
private void unregisterReceiver() {
266+
activity.unregisterReceiver(this);
267+
}
268+
269+
private void trySleep(long time) {
270+
synchronized (AccessoryConnectionBootstrap.this) {
271+
try {
272+
AccessoryConnectionBootstrap.this.wait(time);
273+
} catch (InterruptedException e) {
274+
}
275+
}
276+
}
277+
278+
private static enum InstanceState {
279+
INIT, CONNECTED, DEAD
280+
};
281+
282+
private class Connection implements IOIOConnection {
283+
private InstanceState instanceState_ = InstanceState.INIT;
284+
285+
@Override
286+
public InputStream getInputStream() throws ConnectionLostException {
287+
return inputStream;
288+
}
289+
290+
@Override
291+
public OutputStream getOutputStream() throws ConnectionLostException {
292+
return outputStream;
293+
}
294+
295+
@Override
296+
public boolean canClose() {
297+
return false;
298+
}
299+
300+
@Override
301+
public void waitForConnect() throws ConnectionLostException {
302+
synchronized(AccessoryConnectionBootstrap.this) {
303+
if (instanceState_ != InstanceState.INIT) {
304+
throw new IllegalStateException("waitForConnect() may only be called once");
305+
}
306+
307+
try {
308+
AccessoryConnectionBootstrap.this.waitForConnect(this);
309+
instanceState_ = InstanceState.CONNECTED;
310+
} catch (ConnectionLostException e) {
311+
instanceState_ = InstanceState.DEAD;
312+
throw e;
313+
}
314+
}
315+
}
316+
317+
@Override
318+
public void disconnect() {
319+
synchronized(AccessoryConnectionBootstrap.this) {
320+
if (instanceState_ != InstanceState.DEAD) {
321+
AccessoryConnectionBootstrap.this.disconnect();
322+
instanceState_ = InstanceState.DEAD;
323+
}
324+
}
325+
}
326+
327+
@Override
328+
protected void finalize() throws Throwable {
329+
disconnect();
330+
}
331+
}
332+
}

0 commit comments

Comments
 (0)