Skip to content

Commit 3fe97c5

Browse files
committed
IOIO: android integration
1 parent 52a3d19 commit 3fe97c5

7 files changed

Lines changed: 100 additions & 84 deletions

File tree

ioio/Makefile.am

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,7 @@ android:
3535
(cd ioio/build/libs && zip ../outputs/aar/ioio-debug.aar classes.jar) && \
3636
(cd ioio/build/libs && zip ../outputs/aar/ioio-release.aar classes.jar) && \
3737
cp ioio/build/outputs/aar/ioio-* ~/src/SmallBASIC/src/platform/android/app/libs/
38+
39+
desktop:
40+
@(cd ioio && mvn clean package && cp target/ioio-1.0-jar-with-dependencies.jar ..)
41+

ioio/ioio/src/main/java/ioio/lib/android/accessory/AccessoryConnectionBootstrap.java renamed to ioio/ioio/src/main/java/ioio/lib/android/AccessoryConnectionBootstrap.java

Lines changed: 38 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,19 @@
2727
* or implied.
2828
*/
2929

30-
package ioio.lib.android.accessory;
30+
package ioio.lib.android;
3131

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;
32+
import android.annotation.TargetApi;
33+
import android.app.PendingIntent;
34+
import android.content.BroadcastReceiver;
35+
import android.content.Context;
36+
import android.content.Intent;
37+
import android.content.IntentFilter;
38+
import android.os.Build;
39+
import android.os.ParcelFileDescriptor;
40+
import android.util.Log;
41+
42+
import net.sourceforge.smallbasic.ioio.IOIOLoader;
3943

4044
import java.io.BufferedOutputStream;
4145
import java.io.FileDescriptor;
@@ -46,42 +50,37 @@
4650
import java.io.OutputStream;
4751
import java.util.Collection;
4852

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;
53+
import ioio.lib.android.UsbManagerAdapter.UsbAccessoryInterface;
54+
import ioio.lib.api.IOIOConnection;
55+
import ioio.lib.api.exception.ConnectionLostException;
56+
import ioio.lib.impl.FixedReadBufferedInputStream;
57+
import ioio.lib.spi.IOIOConnectionBootstrap;
58+
import ioio.lib.spi.IOIOConnectionFactory;
59+
import ioio.lib.spi.NoRuntimeSupportException;
5760

5861
public class AccessoryConnectionBootstrap extends BroadcastReceiver implements IOIOConnectionBootstrap, IOIOConnectionFactory {
5962
private static final String TAG = AccessoryConnectionBootstrap.class.getSimpleName();
6063
private static final String ACTION_USB_PERMISSION = "ioio.lib.accessory.action.USB_PERMISSION";
6164

62-
private ContextWrapper activity;
63-
private final Adapter adapter;
64-
private Adapter.AbstractUsbManager usbManager;
65+
private final Context activity;
66+
private final UsbManagerAdapter.AbstractUsbManager usbManager;
6567
private boolean shouldTryOpen = false;
6668
private PendingIntent pendingIntent;
6769
private ParcelFileDescriptor fileDescriptor;
6870
private InputStream inputStream;
6971
private OutputStream outputStream;
7072

7173
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);
74+
Log.d(TAG, "creating AccessoryConnectionBootstrap");
75+
UsbManagerAdapter usbManagerAdapter = new UsbManagerAdapter();
76+
activity = IOIOLoader.getContext();
77+
usbManager = usbManagerAdapter.getManager(activity);
7978
registerReceiver();
8079
}
8180

8281
//@Override
8382
public void onDestroy() {
84-
unregisterReceiver();
83+
activity.unregisterReceiver(this);
8584
}
8685

8786
@Override
@@ -107,12 +106,9 @@ public synchronized void reopen() {
107106
notifyAll();
108107
}
109108

110-
//@Override
111-
public synchronized void close() {
112-
}
113-
114109
private synchronized void disconnect() {
115110
// This should abort any current open attempt.
111+
Log.d(TAG, "private disconnect");
116112
shouldTryOpen = false;
117113
notifyAll();
118114

@@ -130,6 +126,7 @@ private synchronized void disconnect() {
130126
pendingIntent.cancel();
131127
pendingIntent = null;
132128
}
129+
Log.d(TAG, "leaving private disconnect");
133130
}
134131

135132
@Override
@@ -257,38 +254,38 @@ private boolean tryOpen() {
257254
}
258255
}
259256

257+
@TargetApi(Build.VERSION_CODES.TIRAMISU)
260258
private void registerReceiver() {
261259
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
262-
activity.registerReceiver(this, filter);
263-
}
264-
265-
private void unregisterReceiver() {
266-
activity.unregisterReceiver(this);
260+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
261+
activity.registerReceiver(this, filter, Context.RECEIVER_NOT_EXPORTED);
262+
}
267263
}
268264

269265
private void trySleep(long time) {
270266
synchronized (AccessoryConnectionBootstrap.this) {
271267
try {
272268
AccessoryConnectionBootstrap.this.wait(time);
273269
} catch (InterruptedException e) {
270+
Log.e(TAG, e.toString());
274271
}
275272
}
276273
}
277274

278-
private static enum InstanceState {
275+
private enum InstanceState {
279276
INIT, CONNECTED, DEAD
280-
};
277+
}
281278

282279
private class Connection implements IOIOConnection {
283280
private InstanceState instanceState_ = InstanceState.INIT;
284281

285282
@Override
286-
public InputStream getInputStream() throws ConnectionLostException {
283+
public InputStream getInputStream() {
287284
return inputStream;
288285
}
289286

290287
@Override
291-
public OutputStream getOutputStream() throws ConnectionLostException {
288+
public OutputStream getOutputStream() {
292289
return outputStream;
293290
}
294291

@@ -316,6 +313,7 @@ public void waitForConnect() throws ConnectionLostException {
316313

317314
@Override
318315
public void disconnect() {
316+
Log.d(TAG, "disconnect");
319317
synchronized(AccessoryConnectionBootstrap.this) {
320318
if (instanceState_ != InstanceState.DEAD) {
321319
AccessoryConnectionBootstrap.this.disconnect();

ioio/ioio/src/main/java/ioio/lib/android/accessory/Adapter.java renamed to ioio/ioio/src/main/java/ioio/lib/android/UsbManagerAdapter.java

Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -27,50 +27,44 @@
2727
* or implied.
2828
*/
2929

30-
package ioio.lib.android.accessory;
30+
package ioio.lib.android;
3131

32-
import ioio.lib.spi.NoRuntimeSupportException;
3332
import android.app.PendingIntent;
3433
import android.content.Context;
35-
import android.content.ContextWrapper;
36-
3734
import android.hardware.usb.UsbAccessory;
3835
import android.hardware.usb.UsbManager;
3936
import android.os.ParcelFileDescriptor;
4037

38+
import ioio.lib.spi.Log;
39+
import ioio.lib.spi.NoRuntimeSupportException;
40+
4141
/**
42-
* This class serves as a common interface for USB accessory for either com.android.future.usb or
43-
*
44-
* When this class is instantiated, it will throw a {@link NoRuntimeSupportException} if none of the
45-
* underlying implementations exist. Otherwise, the client may call
46-
* {@link #getManager(ContextWrapper)}, which returns an interface that is equivalent to UsbManager
47-
* on either API. Likewise, the {@link UsbAccessoryInterface} interface is a wrapper around
48-
* UsbAccessory.
42+
* This class serves as a common interface for USB accessory for android.hardware.usb.UsbManager
4943
*
44+
* When this class is instantiated, it will throw a {@link NoRuntimeSupportException} if the
45+
* underlying implementation does not exist.
5046
*/
51-
class Adapter {
52-
Adapter() throws NoRuntimeSupportException {
47+
class UsbManagerAdapter {
48+
private static final String TAG = UsbManagerAdapter.class.getSimpleName();
49+
50+
UsbManagerAdapter() throws NoRuntimeSupportException {
5351
try {
54-
Class.forName("UsbManager");
52+
Class.forName("android.hardware.usb.UsbManager");
5553
} catch (ClassNotFoundException e) {
56-
throw new NoRuntimeSupportException("No support for USB accesory mode.");
54+
Log.d(TAG, e.toString());
55+
throw new NoRuntimeSupportException("No support for USB accessory mode.");
5756
}
5857
}
5958

60-
AbstractUsbManager getManager(ContextWrapper wrapper) {
61-
return getManagerNew(wrapper);
62-
}
63-
64-
private AbstractUsbManager getManagerNew(ContextWrapper wrapper) {
59+
AbstractUsbManager getManager(Context wrapper) {
6560
final UsbManager manager = (UsbManager) wrapper.getSystemService(Context.USB_SERVICE);
66-
return new NewUsbManager(manager);
61+
return new UsbManagerImpl(manager);
6762
}
6863

6964
static abstract class AbstractUsbManager {
7065
final String EXTRA_PERMISSION_GRANTED;
71-
protected AbstractUsbManager(String action_usb_accessory_detached,
72-
String extra_permission_granted) {
73-
EXTRA_PERMISSION_GRANTED = extra_permission_granted;
66+
protected AbstractUsbManager() {
67+
EXTRA_PERMISSION_GRANTED = UsbManager.EXTRA_PERMISSION_GRANTED;
7468
}
7569

7670
abstract UsbAccessoryInterface[] getAccessoryList();
@@ -79,12 +73,11 @@ protected AbstractUsbManager(String action_usb_accessory_detached,
7973
abstract void requestPermission(UsbAccessoryInterface accessory, PendingIntent pendingIntent);
8074
}
8175

82-
private static final class NewUsbManager extends AbstractUsbManager {
76+
private static final class UsbManagerImpl extends AbstractUsbManager {
8377
private final UsbManager manager;
8478

85-
private NewUsbManager(UsbManager manager) {
86-
super(UsbManager.ACTION_USB_ACCESSORY_DETACHED,
87-
UsbManager.EXTRA_PERMISSION_GRANTED);
79+
private UsbManagerImpl(UsbManager manager) {
80+
super();
8881
this.manager = manager;
8982
}
9083

@@ -127,6 +120,6 @@ private static class UsbAccessoryAdapter<T> implements UsbAccessoryInterface {
127120
}
128121
}
129122

130-
static interface UsbAccessoryInterface {
123+
interface UsbAccessoryInterface {
131124
}
132125
}

ioio/ioio/src/main/java/net/sourceforge/smallbasic/ioio/ConnectionController.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,9 @@ public class ConnectionController extends IOIOBaseApplicationHelper {
1111

1212
static {
1313
if (AndroidUtil.isAndroid()) {
14-
IOIOConnectionRegistry.addBootstraps(new String[]{
15-
"ioio.lib.android.accessory.AccessoryConnectionBootstrap"});
14+
IOIOConnectionRegistry.addBootstraps(new String[]{"ioio.lib.android.AccessoryConnectionBootstrap"});
1615
} else {
17-
IOIOConnectionRegistry.addBootstraps(new String[]{
18-
"ioio.lib.pc.SerialPortIOIOConnectionBootstrap"
19-
});
16+
IOIOConnectionRegistry.addBootstraps(new String[]{"ioio.lib.pc.SerialPortIOIOConnectionBootstrap"});
2017
}
2118
}
2219

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
11
package net.sourceforge.smallbasic.ioio;
22

3+
import android.content.Context;
4+
5+
import java.lang.ref.WeakReference;
6+
37
import ioio.lib.spi.Log;
48

59
public class IOIOLoader {
610
private static final String TAG = "IOIOLoader";
711
public static native void init(Long app);
812

9-
public IOIOLoader(Long activity) {
13+
private static WeakReference<Context> context;
14+
15+
public IOIOLoader(Long activity, Context context) {
1016
super();
1117
Log.d(TAG, "IOIOLoader: " + activity);
18+
IOIOLoader.context = new WeakReference<>(context);
1219
init(activity);
1320
}
21+
22+
public static Context getContext() {
23+
return context.get();
24+
}
1425
}

ioio/ioio/src/main/java/net/sourceforge/smallbasic/ioio/IOLock.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package net.sourceforge.smallbasic.ioio;
22

33
import java.util.concurrent.CountDownLatch;
4+
import java.util.concurrent.TimeUnit;
45
import java.util.concurrent.atomic.AtomicInteger;
56
import java.util.concurrent.atomic.AtomicLong;
67
import java.util.concurrent.atomic.AtomicReference;
@@ -10,6 +11,7 @@
1011

1112
public class IOLock<I> {
1213
private final Object mutex = new Object();
14+
private static final int TIMEOUT_SECS = 5;
1315
private Consumer<I> consumer;
1416

1517
public void invoke(Consumer<I> consumer) {
@@ -87,7 +89,9 @@ private CountDownLatch beginLatch() {
8789
*/
8890
private void endLatch(CountDownLatch latch) {
8991
try {
90-
latch.await();
92+
if (!latch.await(TIMEOUT_SECS, TimeUnit.SECONDS)) {
93+
throw new RuntimeException("Timeout waiting for latch");
94+
}
9195
} catch (InterruptedException e) {
9296
throw new RuntimeException(e);
9397
}

ioio/main.cpp

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -134,12 +134,18 @@ struct IOTask {
134134
auto exc = g_env->ExceptionOccurred();
135135
if (exc) {
136136
if (retval) {
137+
#if defined(ANDROID_MODULE)
138+
// avoid: JNI DETECTED ERROR IN APPLICATION: JNI FindClass called with pending exception
139+
error(retval, "Java exception - see adb logcat");
140+
g_env->ExceptionClear();
141+
#else
137142
jclass clazz = g_env->FindClass("java/lang/Object");
138143
jmethodID methodId = g_env->GetMethodID(clazz, "toString", "()Ljava/lang/String;");
139144
jstring jstr = (jstring) g_env->CallObjectMethod(exc, methodId);
140145
const char *message = g_env->GetStringUTFChars(jstr, JNI_FALSE);
141146
error(retval, message);
142147
g_env->ReleaseStringUTFChars(jstr, message);
148+
#endif
143149
} else {
144150
g_env->ExceptionDescribe();
145151
g_env->ExceptionClear();
@@ -513,20 +519,23 @@ int sblib_init(const char *sourceFile) {
513519
#else
514520
int result = 1;
515521
#endif
516-
517-
g_ioioTask = new IOTask();
518-
var_t retval;
519-
if (!g_ioioTask || !g_ioioTask->create(CLASS_IOIO, &retval)) {
520-
fprintf(stderr, "Failed to IOIOTask: %s\n", v_getstr(&retval));
522+
if (g_jvm == nullptr) {
521523
result = 0;
524+
} else {
525+
g_ioioTask = new IOTask();
526+
var_t retval;
527+
if (!g_ioioTask || !g_ioioTask->create(CLASS_IOIO, &retval)) {
528+
fprintf(stderr, "Failed to IOIOTask: %s\n", v_getstr(&retval));
529+
result = 0;
530+
}
522531
}
523532
return result;
524533
}
525534

526535
#if defined(ANDROID_MODULE)
527536
//
528537
// Stores the Android JavaVM reference
529-
//
538+
//
530539
extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void* reserved) {
531540
logEntered();
532541
g_jvm = vm;
@@ -583,13 +592,13 @@ void sblib_close(void) {
583592
fprintf(stderr, "IOTask leak detected\n");
584593
g_ioTaskMap.clear();
585594
}
586-
#if defined(DESKTOP_MODULE)
595+
#if defined(DESKTOP_MODULE)
587596
if (g_jvm) {
588597
g_jvm->DetachCurrentThread();
589598
}
590599
// calling this hangs
591600
//jvm->DestroyJavaVM();
592601
g_env = nullptr;
593602
g_jvm = nullptr;
594-
#endif
603+
#endif
595604
}

0 commit comments

Comments
 (0)