From 86e724cc8c4067ed3ba5f3a1a3f048936ff9c02d Mon Sep 17 00:00:00 2001 From: Greg Date: Fri, 22 Nov 2019 16:15:02 -0500 Subject: [PATCH] Partial android implementation, including Enable(), readyToScan(), scan(), stopScan(), connectPeripheral(), cancelConnection(), and DiscoverServices(). --- .gitignore | 1 + BleConnect.java | 180 ++++++++++++++++++++++++++++++++ BleUtil.jar | Bin 2223 -> 0 bytes BleUtil.java | 35 ------- ble.go | 2 + ble_android.go | 241 ++++++++++++++++++++++++++++++++++--------- ble_darwin.go | 12 +-- gatt/gatt_android.go | 5 + gatt/gatt_darwin.go | 5 + jni_android.c | 92 +++++++++++++++++ jni_android.go | 219 ++------------------------------------- jni_android.h | 12 +++ 12 files changed, 507 insertions(+), 297 deletions(-) create mode 100644 BleConnect.java delete mode 100644 BleUtil.jar delete mode 100644 BleUtil.java create mode 100644 gatt/gatt_android.go create mode 100644 gatt/gatt_darwin.go create mode 100644 jni_android.c create mode 100644 jni_android.h diff --git a/.gitignore b/.gitignore index e69de29..302266c 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1 @@ +Ble.jar diff --git a/BleConnect.java b/BleConnect.java new file mode 100644 index 0000000..72b9b2b --- /dev/null +++ b/BleConnect.java @@ -0,0 +1,180 @@ +package st.wow.git.ble; + +import java.lang.Runnable; +import android.util.Log; +import java.lang.Class; +import java.lang.ClassLoader; +import java.lang.reflect.Constructor; +import android.app.Activity; +import android.app.Fragment; +import android.os.Handler; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCallback; +import android.bluetooth.BluetoothGattService; +import android.bluetooth.BluetoothManager; +import android.bluetooth.BluetoothProfile; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.Manifest; + +public class BleConnect extends Fragment { + BluetoothManager manager; + BluetoothAdapter adapter; + + final int PERMISSION_REQUEST = 1; + + final int REQUEST_ENABLE_BT = 1; + + public BleConnect() { + Log.d("gio", "BleConnect()"); + } + + @Override public void onAttach(Context ctx) { + super.onAttach(ctx); + Log.d("gio", "BleConnect: onAttach()"); + ctx.registerReceiver(receiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); + manager = (BluetoothManager) ctx.getSystemService(ctx.BLUETOOTH_SERVICE); + Log.d("gio", "BleUtil Enable: adapter"); + adapter = manager.getAdapter(); + if (!enabled()) { + Log.d("gio", "BleConnect: enabling adapter"); + Handler handler = new Handler(ctx.getMainLooper()); + Intent enableBtIntent = new Intent(adapter.ACTION_REQUEST_ENABLE); + Log.d("gio", "BleConnect: handler.post"); + handler.post(new Runnable() { + public void run() { + startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); + } + }); + } else { + Log.d("gio", "BleConnect: adapter is enabled"); + } + if (ctx.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSION_REQUEST); + } + installComplete(this); + } + + public boolean enabled() { + return (adapter != null && adapter.isEnabled()); + } + + private final BroadcastReceiver receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + Log.d("gio", "Received broadcast"); + if (intent.getAction().equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { + updateState(intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)); + } + } + }; + + @Override + public void onDestroy() { + Log.d("gio","onDestroy()"); + stopScan(); + getContext().unregisterReceiver(receiver); + super.onDestroy(); + } + + private final BluetoothAdapter.LeScanCallback scanCallback = new BluetoothAdapter.LeScanCallback() { + public void onLeScan(final BluetoothDevice dev, int rssi, byte[] scanRecord) { + Log.d("gio","onLeScan(): " + dev.getName()); + onScan(dev.getName(), dev.getAddress(), rssi, dev); + } + }; + + public void scan() { + if (!enabled()) { + return; + } + adapter.startLeScan(scanCallback); + } + + public void stopScan() { + if (!enabled()) { + return; + } + Log.d("gio", "Stop scan"); + adapter.stopLeScan(scanCallback); + } + + private BluetoothDevice device; + private BluetoothGatt bluetoothGatt; + + private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() { +@Override + public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { + if (newState == BluetoothProfile.STATE_CONNECTED) { + Log.d("gio", "Connected"); + String addr = device.getAddress(); + Log.d("gio", "Address = " + addr); + onConnect(device.getAddress()); + } + } + public void onServicesDiscovered(BluetoothGatt gatt, int status) { + for (BluetoothGattService serv : gatt.getServices()) { + onDiscoverService(device.getAddress(), serv.getUuid().toString()); + } + } + }; + + + public void connect(BluetoothDevice dev) { + if (dev == null) { + return; + } + Log.d("gio","BleConnect: connect"); + device = dev; + bluetoothGatt = dev.connectGatt(getContext(), false, gattCallback); + } + + public void disconnect() { + device = null; + if (bluetoothGatt == null) { + return; + } + Log.d("gio","BleConnect: disconnect"); + bluetoothGatt.disconnect(); + } + + public void discoverServices(BluetoothDevice dev) { + if (bluetoothGatt == null || device == null) { + return; + } + if (!dev.getAddress().equals(device.getAddress())) { + disconnect(); + return; + } + bluetoothGatt.discoverServices(); + } + + @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { + Log.d("gio", "BleConnect: onActivityResult()"); + if (requestCode == REQUEST_ENABLE_BT) { + Log.d("gio", "BleConnect: onActivityResult() REQUEST_ENABLE_BT"); + switch (resultCode) { + case Activity.RESULT_OK: { + Log.d("gio", "BleConnect: onActivityResult() -- OK"); + break; + } + case Activity.RESULT_CANCELED: { + Log.d("gio", "BleConnect: onActivityResult() -- Cancelled"); + break; + } + } + } + } + + static private native void installComplete(BleConnect p); + static private native void updateState(int s); + static private native void onScan(String name, String id, int rssi, BluetoothDevice dev); + static private native void onConnect(String id); + static private native void onDiscoverService(String id, String uuid); +} + diff --git a/BleUtil.jar b/BleUtil.jar deleted file mode 100644 index a3bd6b1857b9f91a60d47ed2646a1224eb394bc6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2223 zcmWIWW@Zs#;Nak3h$>I?XFvj+3@i-3t|5-Po_=on|4uP5Ff#;rvvYt{FhP|C;M6Pv zQ~}rQ>*(j{<{BKL=j-;__snS@Z(Y5MyxzK6=gyqp9At3C_`%a6JuhD!Pv48Bt5`TA zUPvC1o+YW7@k3ly?fDWdi|68^PsCNHFH8%0ovgT#2{C2F)#pK z_O=Yw3(P>-;u3w_3fLhE%Ja+dDB?yaO3%b+8b7MSq?}Z+sn~;822-(9PHJdLW{zHR zPGWI!Ye;nP~&vdEegj-=DvqVaD-UEsl4@3>A}toERJh zgN2-Ch#4p=={PnhJ>*MsJ;S-zdqaA)oBms<-0uW zA!pA_*HvEsMCbA~-{{WIU-!yBWRaDOKE2mFQ&qiBg}dUILh70<%jNo#a~%6bzw+MJ zX#2i}+4opz%;L-3ZAT3Pw)gC4*X?l^gkOUBh!Dp@<}j z>=%|>H8&p9dn#nI@Y<)Slh;lg#U;!%Ipy&vRd{=>l710W{9}>Ti*$A`=m`&~WYTQ= z_qow?xmk)u{Mt6hmC}cnZ#rD+o2Z*EJxwysdrh8-(d&HSml;03ADS#VH=fc_lg_f?E$(ov)?tlK~|8;p+u6h^p zF4wxKu_`nnp|b5{!>{QE$;EbA`Ys}_PBtNx#FU39CrEBf}HV(o1!S9Yo|m0Xq;sp@yNyshij!B)*p_7_;5 zZC|v0X40Xn4Y&S;yXfsr5$DOXw=9v{&dzpj-nKB)xhYBqb9<5+(zxrR4*WQ}_`5-E z?DDA>Z<&?yl(|T|2|Or0;8%73z}a1|%boUanO38_@0$OI6z7F&)~{uIAI_n9$feKs z`h|J(ezIjQV|%GQjbs0zkHROS7fM<6UEKLVal>iW-P_YP?VFeUL+lpEo2L&ZJZJg0 zcIAbD+lIfgUYwV+T(onQ<*c*?oa&3(+h+HNd<{yf^I6=#EZu(5lq4bN8%tR32Xu)= zy3etRs{5#DrIqkT-FLFsog$0lC&edd*zXUP`ojn+Yu=ujS_dq47`j*?xg2{=lEayk zR16_`sWd3o|F(m`oWqK2#S<7b;~AwCbdRy}2<3-N^L%&r~t5-@KFazuFz1;>anwMX7K>|7NYjccc$_w|;P~ z)x7+x@8FVK`#bah^j)#r__x_(PPU}vnedfM{vEqe^4Vdh@KeqF7y}Wh!f1mjvw}J= zzbo}t*?;B^PuQhUkyz%Ux?^`$MBFcl{BX$st@VQSh)qbM$nv04zm|PHx6NM3%>OVm zSw>i9ae>9L1D0MZZbUpU*yuQW-+t%X>A&Q)RVTTLELrIxdCKirKhxEZ=cB@vRJ51J z_1Be5t(sZ>>~G(bI}aH)zIbMpGHuRTO{1RWRvurz2b`XFWxwRAeCfaCBBr}+%p}|r zrk+l#Z7{N&kou`5@3z2R)hV2&p7FE4#4m{XxbMrsJ!kFO_N=wjdC%BtU*g9dtP{kx zN@RJUq(N-btl)Qd1)uX>vu%r)^jo&s<<{v99~3XoJ*e}P>6ts8&J%BUFu$Itn#l zk#ilWmO_AdOwA~D7J4`%M;|DcBEU*ud?C3X!&B_K+&jEz*f%dKf^8hGl B^^^br diff --git a/BleUtil.java b/BleUtil.java deleted file mode 100644 index 8b582d3..0000000 --- a/BleUtil.java +++ /dev/null @@ -1,35 +0,0 @@ -package st.wow.git.ble; - -import java.lang.Runnable; -import android.util.Log; -import java.lang.Class; -import java.lang.ClassLoader; -import java.lang.reflect.Constructor; -import android.os.Handler; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothManager; -import android.content.Context; -import android.content.Intent; - -public class BleUtil { - public void Enable(Context ctx) { - Log.d("gio", "BleUtil Enable: manager"); - BluetoothManager manager = (BluetoothManager) ctx.getSystemService(ctx.BLUETOOTH_SERVICE); - Log.d("gio", "BleUtil Enable: adapter"); - BluetoothAdapter ad = (BluetoothAdapter) manager.getAdapter(); - if (ad == null || !ad.isEnabled()) { - Log.d("gio", "BleUtil Enable: handler"); - Handler handler = new Handler(ctx.getMainLooper()); - //BleEnabler en = new BleEnabler(ctx, ad); - Log.d("gio", "BleUtil Enable: Intent"); - Intent enableBtIntent = new Intent(ad.ACTION_REQUEST_ENABLE); - Log.d("gio", "BleUtil Enable: handler.post"); - handler.post(new Runnable() { - public void run() { - ctx.startActivity(enableBtIntent); - } - }); - } - } -} - diff --git a/ble.go b/ble.go index 11eabe7..306dd66 100644 --- a/ble.go +++ b/ble.go @@ -2,6 +2,7 @@ package ble import ( "fmt" + "log" "sync" "time" @@ -152,6 +153,7 @@ func (b *BLE) Scan() { b.Lock() defer b.Unlock() if b.readyToScan() { + log.Printf("ready to scan") b.peripherals.Lock() b.peripherals.items = b.peripherals.items[:0] b.peripherals.Unlock() diff --git a/ble_android.go b/ble_android.go index 41f046e..51f9c0c 100644 --- a/ble_android.go +++ b/ble_android.go @@ -1,34 +1,39 @@ //go:generate mkdir -p classes -//go:generate javac -bootclasspath $ANDROID_HOME/platforms/android-29/android.jar -d classes BleUtil.java -//go:generate jar cf BleUtil.jar -C classes . +//go:generate javac -bootclasspath $ANDROID_HOME/platforms/android-29/android.jar -d classes BleConnect.java +//go:generate jar cf Ble.jar -C classes . //go:generate rm -rf classes package ble import ( "log" + "sync" + "gioui.org/app" _ "gioui.org/app/permission/bluetooth_le" + + "git.wow.st/gmp/ble/gatt" ) /* -#include +#include "jni_android.h" */ import "C" // Types required for ble.go -type bleState string - type bleHandle struct { - adapter C.jobject - class C.jclass + BleConnect C.jobject + state int } +type bleState string + type Peripheral struct { Name string RSSI int Identifier string + device C.jobject } type Service string @@ -36,32 +41,51 @@ type Characteristic string // Internal global variables -var jvmContext uintptr +var ( + gBLE *BLE // FIXME: move to lookup tables as in ble_darwin.go? + installCompleteOnce sync.Once + waitch chan struct{} +) + +const ( + STATE_OFF int = 10 + STATE_TURNING_ON = 11 + STATE_ON = 12 + STATE_TURNING_OFF = 13 +) + +func init() { + waitch = make(chan struct{}) +} + +func connect() { + <-waitch +} // Functions required by API -//Init needs to be called before the BLE library can be used. On Android we -//need to set up the JVM by calling setJVM in jni_android.go. -func Init() { - log.Print("ble.Init()") - h := app.PlatformHandle() - jvmContext = h.Context - setJVM(h.JVM, h.Context) -} - //peripheralLookup returns a pointer to a BLE struct related to the given //Peripheral. func peripheralLookup(p Peripheral) *BLE { - return &BLE{} + return gBLE } //newPeripheral creates a new Peripheral struct -func newPeripheral() Peripheral { - return Peripheral{} +func newPeripheral(name, id string, rssi int, dev C.jobject) Peripheral { + return Peripheral{ + Name: name, + RSSI: rssi, + Identifier: id, + device: dev, + } } func (p Peripheral) IsIncomplete() bool { - return false + if p.device == 0 { + return true + } else { + return false + } } func (p Peripheral) Retain() { @@ -69,38 +93,76 @@ func (p Peripheral) Retain() { //stringState returns a string version of the BLE state func (b *BLE) stringState() string { - return "" + switch b.handle.state { + case STATE_OFF: + return "powered off" + case STATE_TURNING_ON: + return "turning on" + case STATE_ON: + return "powered on" + case STATE_TURNING_OFF: + return "turning off" + default: + return "no state" + } } //readyToScan returns true if the hardware is ready to initiate a scan func (b *BLE) readyToScan() bool { - return true + connect() + var ret bool + runInJVM(func(env *C.JNIEnv) { + ret = C.enabled(env, b.handle.BleConnect) == C.JNI_TRUE + }) + return ret } //scan puts the BLE hardware into scanning mode func (b *BLE) scan() { + connect() + runInJVM(func(env *C.JNIEnv) { + C.scan(env, b.handle.BleConnect); + }) } //stopScan stops a scan in progress func (b *BLE) stopScan() { + connect() + runInJVM(func(env *C.JNIEnv) { + C.stopScan(env, b.handle.BleConnect); + }) } //connectPeripheral attempts to connect to a Peripheral func (b *BLE) connectPeripheral(x Peripheral) { + connect() + runInJVM(func(env *C.JNIEnv) { + C.connect(env, b.handle.BleConnect, x.device) + }) } //cancelConnection cancels an in-progress connection attempt func (b *BLE) cancelConnection(p Peripheral) { + connect() + runInJVM(func(env *C.JNIEnv) { + C.disconnect(env, b.handle.BleConnect) + }) } //knownPeripheral returns a Peripheral that is known to the system without //scanning +//Not implemented for Android func (b *BLE) knownPeripheral(p Peripheral) (Peripheral, bool) { return Peripheral{}, false } //DiscoverServices asks a Peripheral for its Services func (x Peripheral) DiscoverServices() { + connect() + log.Printf("discovering services") + runInJVM(func(env *C.JNIEnv) { + C.discoverServices(env, gBLE.handle.BleConnect, x.device) + }) } //DiscoverCharacteristics asks a Peripheral for the Characteristics related @@ -116,31 +178,118 @@ func (p Peripheral) SetNotifyValue(c Characteristic) { //Bluetooth API. func NewBLE() *BLE { ps := Peripherals{items: make([]PeripheralListItem, 0)} - h := bleHandle{} - RunInJVM(func(env *JNIEnv) { - h.adapter = GetBluetoothAdapter(env, jvmContext) - h.class = JniGetObjectClass(env, h.adapter) - }) - - ret := &BLE{ + gBLE = &BLE{ events: make(chan interface{}), peripherals: ps, - handle: h, } - - //check if Bluetooth is enabled and if so, send an UpdateStateEvent - go func() { - RunInJVM(func(env *JNIEnv) { - mid := JniGetMethodID(env, h.class, "isEnabled", "()Z") - en := JniCallBooleanMethod(env, h.adapter, mid) - if en { - log.Print("It's enabled!") - ret.events <-UpdateStateEvent{"powered on"} - } else { - log.Print("It's not enabled") - } - }) - }() - return ret + return gBLE } +//Enable +func (b *BLE) Enable(w *app.Window) { + log.Printf("ble.Enable()") + err := w.RegisterFragment("st/wow/git/ble/BleConnect") + log.Printf("ble.Enable() RegisterFragment() returned") + if err != nil { + log.Printf("Error! %s", err) + } +} + +// Go callbacks from Java + +//export Java_st_wow_git_ble_BleConnect_installComplete +func Java_st_wow_git_ble_BleConnect_installComplete(env *C.JNIEnv, class C.jclass, b C.jobject) { + log.Printf("installComplete()") + if (b == 0) { + log.Printf("BleConnect object is nil!") + } + gBLE.handle.BleConnect = (C.NewGlobalRef)(env,b) + h := app.PlatformHandle() + setJVM(h.JVM) + if C.enabled(env, gBLE.handle.BleConnect) == C.JNI_TRUE { + gBLE.handle.state = STATE_ON + gBLE.ready = true + gBLE.events <- UpdateStateEvent{State: gBLE.stringState()} + } + installCompleteOnce.Do(func() { + close(waitch) + }) + log.Printf("installComplete() returning") +} + +//export Java_st_wow_git_ble_BleConnect_updateState +func Java_st_wow_git_ble_BleConnect_updateState(env *C.JNIEnv, class C.jclass, s C.jint) { + log.Printf("UpdateState: %d", s) + gBLE.handle.state = (int)(s) + if gBLE.handle.state == STATE_ON { + gBLE.ready = true + } + gBLE.events <- UpdateStateEvent{State: gBLE.stringState()} +} + +//export goOnScan +func goOnScan(cname, cid *C.char, rssi C.int, dev C.jobject) { + name := C.GoString(cname); + id := C.GoString(cid); + if name == "" { + return + } + peripheral := newPeripheral(name, id, (int)(rssi), dev) + if ok := gBLE.peripherals.Add(peripheral); ok { + gBLE.events <- DiscoverPeripheralEvent{Peripheral: peripheral} + } +} + +//export goOnConnect +func goOnConnect(cid *C.char) { + id := C.GoString(cid) + + var peripheral Peripheral + found := false + + gBLE.peripherals.Lock() + for _, item := range gBLE.peripherals.items { + if item.p.Identifier == id { + peripheral = item.p + found = true + break + } + } + gBLE.peripherals.Unlock() + + if !found { + log.Printf("Go: peripheral not found!") + } + + gBLE.connections.UpdateState(peripheral, "connected") + gBLE.events <- ConnectEvent{peripheral} + log.Printf("Go: goOnConnect returning\n") +} + +//export goOnDiscoverService +func goOnDiscoverService(cid *C.char, cuuid *C.char) { + id := C.GoString(cid) + uuid := C.GoString(cuuid) + + var peripheral Peripheral + found := false + + gBLE.peripherals.Lock() + for _, item := range gBLE.peripherals.items { + if item.p.Identifier == id { + peripheral = item.p + found = true + break + } + } + gBLE.peripherals.Unlock() + + if !found { + log.Printf("Go: peripheral not found!") + } + + gBLE.events <- DiscoverServiceEvent{ + Peripheral: peripheral, + Gatt: gatt.Service{uuid}, + } +} diff --git a/ble_darwin.go b/ble_darwin.go index 0831372..396af9d 100644 --- a/ble_darwin.go +++ b/ble_darwin.go @@ -8,6 +8,8 @@ import ( "time" "unsafe" + "gioui.org/app" + "git.wow.st/gmp/ble/gatt" "git.wow.st/gmp/ble/ns" ) @@ -40,12 +42,6 @@ var pcache map[unsafe.Pointer]*Peripheral // Functions required by API -//Init needs to be called before the BLE library can be used. No setup needed -//on Darwin. -func Init() { - -} - //peripheralLookup returns a pointer to a BLE struct related to the given //Peripheral. func peripheralLookup(p Peripheral) *BLE { @@ -206,6 +202,10 @@ func NewBLE() *BLE { return ble } +//Enable is not required for Darwin. +func (b *BLE) Enable(w *app.Window) { +} + // Core Bluetooth callback functions func didUpdateState(c *ns.CBCentralManager) { diff --git a/gatt/gatt_android.go b/gatt/gatt_android.go new file mode 100644 index 0000000..ba575f2 --- /dev/null +++ b/gatt/gatt_android.go @@ -0,0 +1,5 @@ +package gatt + +func (s Service) IsHRM() bool { + return len(s.UUID) >= 8 && s.UUID[:8] == "0000180d" +} diff --git a/gatt/gatt_darwin.go b/gatt/gatt_darwin.go new file mode 100644 index 0000000..e941f47 --- /dev/null +++ b/gatt/gatt_darwin.go @@ -0,0 +1,5 @@ +package gatt + +func (s Service) IsHRM() bool { + return s.UUID == "180D" +} diff --git a/jni_android.c b/jni_android.c new file mode 100644 index 0000000..dc50263 --- /dev/null +++ b/jni_android.c @@ -0,0 +1,92 @@ +#include +#include +#include +#include "_cgo_export.h" + +jboolean +enabled(JNIEnv *env, jobject b) { + jclass cls = (*env)->GetObjectClass(env, b); + jmethodID mid = (*env)->GetMethodID(env, cls, "enabled", "()Z"); + return (*env)->CallBooleanMethod(env, b, mid); +} + +void +scan(JNIEnv *env, jobject b) { + jclass cls = (*env)->GetObjectClass(env, b); + jmethodID mid = (*env)->GetMethodID(env, cls, "scan", "()V"); + (*env)->CallVoidMethod(env, b, mid); +} + +void +stopScan(JNIEnv *env, jobject b) { + jclass cls = (*env)->GetObjectClass(env, b); + jmethodID mid = (*env)->GetMethodID(env, cls, "stopScan", "()V"); + (*env)->CallVoidMethod(env, b, mid); +} + +void +connect(JNIEnv *env, jobject b, jobject d) { + jclass cls = (*env)->GetObjectClass(env, b); + jmethodID mid = (*env)->GetMethodID(env, cls, "connect", "(Landroid/bluetooth/BluetoothDevice;)V"); + (*env)->CallVoidMethod(env, b, mid, d); +} + +void +disconnect(JNIEnv *env, jobject b) { + jclass cls = (*env)->GetObjectClass(env, b); + jmethodID mid = (*env)->GetMethodID(env, cls, "disconnect", "()V"); + (*env)->CallVoidMethod(env, b, mid); +} + +void +discoverServices(JNIEnv *env, jobject b, jobject p) { + jclass cls = (*env)->GetObjectClass(env, b); + jmethodID mid = (*env)->GetMethodID(env, cls, "discoverServices", "(Landroid/bluetooth/BluetoothDevice;)V"); + (*env)->CallVoidMethod(env, b, mid, p); +} + +jint +GetEnv(JavaVM *vm, JNIEnv **env, jint version) { + return (*vm)->GetEnv(vm, (void **)env, version); +} + +jint +AttachCurrentThread(JavaVM *vm, JNIEnv **p_env, void *thr_args) { + return (*vm)->AttachCurrentThread(vm, p_env, thr_args); +} + +jint +DetachCurrentThread(JavaVM *vm) { + return (*vm)->DetachCurrentThread(vm); +} + +jobject +NewGlobalRef(JNIEnv *env, jobject o) { + return (*env)->NewGlobalRef(env, o); +} + +void +Java_st_wow_git_ble_BleConnect_onScan(JNIEnv *env, jclass class, jstring jname, jstring jid, jint jrssi, jobject dev) { + char* name = (*env)->GetStringUTFChars(env, jname, NULL); + char* id = (*env)->GetStringUTFChars(env, jid, NULL); + jobject gdev = (*env)->NewGlobalRef(env, dev); + goOnScan(name, id, (int)jrssi, gdev); + (*env)->ReleaseStringUTFChars(env, jname, name); + (*env)->ReleaseStringUTFChars(env, jid, id); +} + +void +Java_st_wow_git_ble_BleConnect_onConnect(JNIEnv *env, jclass class, jstring jid) { + char* id = (*env)->GetStringUTFChars(env, jid, NULL); + goOnConnect(id); + (*env)->ReleaseStringUTFChars(env, jid, id); +} + +void +Java_st_wow_git_ble_BleConnect_onDiscoverService(JNIEnv *env, jclass class, jstring jid, jstring juuid) { + char* id = (*env)->GetStringUTFChars(env, jid, NULL); + char* uuid = (*env)->GetStringUTFChars(env, juuid, NULL); + goOnDiscoverService(id, uuid); + (*env)->ReleaseStringUTFChars(env, jid, id); + (*env)->ReleaseStringUTFChars(env, juuid, uuid); +} diff --git a/jni_android.go b/jni_android.go index 4d1b362..e8fbcec 100644 --- a/jni_android.go +++ b/jni_android.go @@ -3,165 +3,7 @@ package ble /* #cgo LDFLAGS: -landroid -llog -#include -#include -#include - -static jobject gClassLoader; -static jmethodID gFindClassMethod; - -jclass -GetObjectClass(JNIEnv* env, jobject obj) { - jclass cls = (*env)->GetObjectClass(env, obj); - cls = (*env)->NewGlobalRef(env, cls); - return cls; -} - -void -SetLoader(JNIEnv* env, jobject context) { - jclass cclass = (*env)->GetObjectClass(env, context); - jmethodID gcl_id = (*env)->GetMethodID(env, cclass, "getClassLoader", "()Ljava/lang/ClassLoader;"); - jobject loader = (*env)->CallObjectMethod(env, context, gcl_id); - gClassLoader = (*env)->NewGlobalRef(env, loader); - jclass lclass = (*env)->GetObjectClass(env, loader); - gFindClassMethod = (*env)->GetMethodID(env, lclass, "findClass", "(Ljava/lang/String;)Ljava/lang/Class;"); -} - -jclass -FindClass(JNIEnv* env, char* name) { - jstring strClassName = (*env)->NewStringUTF(env, name); - return (*env)->CallObjectMethod(env, gClassLoader, gFindClassMethod, strClassName); -} - -void -EnableBluetooth(JNIEnv* env, jobject ctx, jobject ad) { - __android_log_write(ANDROID_LOG_DEBUG, "gio", "Finding BleUtil"); - jclass cls = FindClass(env, "st/wow/git/ble/BleUtil"); - if (cls == NULL) { - __android_log_write(ANDROID_LOG_DEBUG, "gio", "not found"); - return; - } - __android_log_write(ANDROID_LOG_DEBUG, "gio", "getting "); - jmethodID mid = (*env)->GetMethodID(env, cls, "", "()V"); - if (mid == NULL) { - __android_log_write(ANDROID_LOG_DEBUG, "gio", "not found"); - return; - } - __android_log_write(ANDROID_LOG_DEBUG, "gio", "calling NewObject"); - jobject bleutil = (*env)->NewObject(env, cls, mid); - if (bleutil == NULL) { - __android_log_write(ANDROID_LOG_DEBUG, "gio", "failed"); - return; - } - __android_log_write(ANDROID_LOG_DEBUG, "gio", "Getting Enable()"); - mid = (*env)->GetMethodID(env, cls, "Enable", "(Landroid/content/Context;)V"); - if (mid == NULL) { - __android_log_write(ANDROID_LOG_DEBUG, "gio", "failed"); - return; - } - __android_log_write(ANDROID_LOG_DEBUG, "gio", "Calling Enable()"); - (*env)->CallVoidMethod(env, bleutil, mid, ctx); -} - -jobject -GetBluetoothAdapter(JNIEnv* env, jobject ctx) { - jclass cls = (*env)->GetObjectClass(env, ctx); - if (cls == NULL) { - __android_log_write(ANDROID_LOG_DEBUG, "gio", "GetBluetoothAdapter(): Can't get context class"); - return NULL; - } - jfieldID id = (*env)->GetStaticFieldID(env, cls, "BLUETOOTH_SERVICE", "Ljava/lang/String;"); - if (id == NULL) { - __android_log_write(ANDROID_LOG_DEBUG, "gio", "GetBluetoothAdapter(): Can't get field ID"); - return NULL; - } - jstring btsrv = (jstring)(*env)->GetStaticObjectField(env, cls, id); - if (btsrv == NULL) { - __android_log_write(ANDROID_LOG_DEBUG, "gio", "GetBluetoothAdapter(): Can't get static object field"); - return NULL; - } - jmethodID mid = (*env)->GetMethodID(env, cls, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"); - if (mid == NULL) { - __android_log_write(ANDROID_LOG_DEBUG, "gio", "GetBluetoothAdapter(): Can't get method ID"); - return NULL; - } - jobject manager = (*env)->CallObjectMethod(env, ctx, mid, btsrv); - if (manager == NULL) { - __android_log_write(ANDROID_LOG_DEBUG, "gio", "GetBluetoothAdapter(): Can't get bluetooth manager"); - return NULL; - } - cls = (*env)->GetObjectClass(env, manager); - if (cls == NULL) { - __android_log_write(ANDROID_LOG_DEBUG, "gio", "GetBluetoothAdapter(): Can't get class of bluetooth manager"); - return NULL; - } - mid = (*env)->GetMethodID(env, cls, "getAdapter", "()Landroid/bluetooth/BluetoothAdapter;"); - if (mid == NULL) { - __android_log_write(ANDROID_LOG_DEBUG, "gio", "GetBluetoothAdapter(): Can't get methodID for getAdapter()"); - return NULL; - } - jobject ad = (*env)->CallObjectMethod(env, manager, mid); - if (ad == NULL) { - __android_log_write(ANDROID_LOG_DEBUG, "gio", "GetBluetoothAdapter(): Can't get adapter"); - } else { - __android_log_write(ANDROID_LOG_DEBUG, "gio", "GetBluetoothAdapter(): Found"); - } - - ad = (*env)->NewGlobalRef(env, ad); - - EnableBluetooth(env, ctx, ad); - return ad; -} - -jstring -GetObjectStaticStringField(JNIEnv* env, jobject obj, char* fld) { - jclass cls = (*env)->GetObjectClass(env, obj); - if (cls == NULL) { - __android_log_write(ANDROID_LOG_DEBUG, "gio", "GetObjectStaticStringField(): Can't get class"); - return NULL; - } - jfieldID id = (*env)->GetStaticFieldID(env, cls, fld, "Ljava/lang/String;"); - if (id == NULL) { - __android_log_write(ANDROID_LOG_DEBUG, "gio", "GetObjectStaticStringField(): Can't find FieldID"); - return NULL; - } - return (jstring)(*env)->GetStaticObjectField(env, cls, id); -} - -JNIEXPORT jobject -CreateObject(JNIEnv* env, jclass cls) { - jmethodID init = (*env)->GetMethodID(env, cls, "", "()V"); - return (*env)->NewObject(env, cls, init); -} - -void CallVoidMethod(JNIEnv *env, jobject obj, jmethodID methodID) { - (*env)->CallVoidMethod(env, obj, methodID); -} - -jboolean CallBooleanMethod(JNIEnv *env, jobject obj, jmethodID mid) { - return (*env)->CallBooleanMethod(env, obj, mid); -} - -jint CallIntMethod(JNIEnv *env, jobject obj, jmethodID methodID) { - return (*env)->CallIntMethod(env, obj, methodID); -} - -jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig) { - return (*env)->GetMethodID(env, clazz, name, sig); -} - -jint GetEnv(JavaVM *vm, JNIEnv **env, jint version) { - return (*vm)->GetEnv(vm, (void **)env, version); -} - -jint AttachCurrentThread(JavaVM *vm, JNIEnv **p_env, void *thr_args) { - return (*vm)->AttachCurrentThread(vm, p_env, thr_args); -} - -jint DetachCurrentThread(JavaVM *vm) { - return (*vm)->DetachCurrentThread(vm); -} - +#import "jni_android.h" */ import "C" @@ -177,60 +19,17 @@ var theJVM *C.JavaVM type JNIEnv = C.JNIEnv -func setJVM(jvm, context uintptr) { - log.Print("set theJVM") +func setJVM(jvm uintptr) { + log.Printf("set theJVM") theJVM = (*C.JavaVM)(unsafe.Pointer(jvm)) - //log.Printf("theJVM = %d", uintptr(unsafe.Pointer(theJVM))) - RunInJVM(func(env *C.JNIEnv) { - C.SetLoader(env, (C.jobject)(context)) - }) + if theJVM == nil { + log.Printf("theJVM is nil!") + } else { + log.Printf("theJVM is not nil") + } } -func FindClass(env *C.JNIEnv, name string) C.jclass { - cname := C.CString(name) - defer C.free(unsafe.Pointer(cname)) - return C.FindClass((*C.JNIEnv)(env), cname) -} - -func JniGetObjectClass(env *C.JNIEnv, obj C.jobject) C.jclass { - return C.GetObjectClass(env, obj) -} - -func JniGetObjectStaticStringField(env *C.JNIEnv, obj C.jobject, fld string) C.jstring { - cstring := C.CString(fld) - defer C.free(unsafe.Pointer(cstring)) - return C.GetObjectStaticStringField(env, obj, cstring) -} - -func GetBluetoothAdapter(env *C.JNIEnv, ctx uintptr) C.jobject { - return C.GetBluetoothAdapter(env, (C.jobject)(ctx)) -} - -func JniCallVoidMethod(env *C.JNIEnv, obj C.jobject, methodID C.jmethodID) { - C.CallVoidMethod(env, obj, methodID) -} - -func JniCallBooleanMethod(env *C.JNIEnv, obj C.jobject, methodID C.jmethodID) bool { - return C.CallBooleanMethod(env, obj, methodID) == C.JNI_TRUE -} - -func JniCallIntMethod(env *C.JNIEnv, obj C.jobject, methodID C.jmethodID) int { - return (int)(C.CallIntMethod(env, obj, methodID)) -} - -func JniGetMethodID(env *C.JNIEnv, cls C.jclass, name, sig string) C.jmethodID { - cname := C.CString(name) - defer C.free(unsafe.Pointer(cname)) - csig := C.CString(sig) - defer C.free(unsafe.Pointer(csig)) - return C.GetMethodID(env, cls, cname, csig) -} - -func CreateObject(env *C.JNIEnv, cls C.jclass) C.jobject { - return C.CreateObject(env, (C.jclass)(cls)) -} - -func RunInJVM(f func(env *C.JNIEnv)) { +func runInJVM(f func(env *C.JNIEnv)) { runtime.LockOSThread() defer runtime.UnlockOSThread() var env *C.JNIEnv diff --git a/jni_android.h b/jni_android.h new file mode 100644 index 0000000..e88cae8 --- /dev/null +++ b/jni_android.h @@ -0,0 +1,12 @@ +#include + +jboolean enabled(JNIEnv *env, jobject p); +void scan(JNIEnv *env, jobject b); +void stopScan(JNIEnv *env, jobject b); +void connect(JNIEnv *env, jobject b, jobject d); +void disconnect(JNIEnv *env, jobject b); +void discoverServices(JNIEnv *env, jobject b, jobject p); +jint GetEnv(JavaVM *vm, JNIEnv **env, jint version); +jint AttachCurrentThread(JavaVM *vm, JNIEnv **p_env, void *thr_args); +jint DetachCurrentThread(JavaVM *vm); +jobject NewGlobalRef(JNIEnv *env, jobject o);