Skip to content

Commit 00c0628

Browse files
xaionaro@dx.centerxaionaro@dx.center
authored andcommitted
docs: add topic-focused guides for Context7 documentation coverage
Context7 only indexes markdown files, not Go source. The project had 57 working examples in .go files that were invisible to Context7, resulting in a benchmark score of 57.1/100 with gaps in field access, method calls, BLE, location updates, content providers, broadcast receivers, and telephony documentation. Add 9 focused markdown guides in docs/ covering: getting-started, field-access, method-calls, bluetooth, location, content-resolver, broadcast-receiver, telephony, and notifications. All code examples verified against actual source. Upgrade context7.json with full schema: projectTitle, description, folders, excludeFolders, and rules.
1 parent 8bae639 commit 00c0628

10 files changed

Lines changed: 1536 additions & 1 deletion

context7.json

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,33 @@
11
{
2+
"$schema": "https://context7.com/schema/context7.json",
23
"url": "https://context7.com/androidgolab/jni",
3-
"public_key": "pk_ajlVZor26lzvjspOfyEJd"
4+
"public_key": "pk_ajlVZor26lzvjspOfyEJd",
5+
"projectTitle": "go-jni: Android Java API Bindings for Go",
6+
"description": "Idiomatic Go bindings for 53 Android Java API packages via JNI, with typed wrappers for Bluetooth, Location, WiFi, Telephony, ContentResolver, Notifications, and more",
7+
"folders": [
8+
"docs",
9+
"examples/gio",
10+
"examples/gomobile"
11+
],
12+
"excludeFolders": [
13+
"ref",
14+
"raw",
15+
"tools",
16+
"spec",
17+
"templates",
18+
"capi",
19+
"internal",
20+
"proofs",
21+
"tests"
22+
],
23+
"rules": [
24+
"All JNI operations must run inside vm.Do(func(env *jni.Env) error { ... }) for thread safety",
25+
"Local references are only valid within a vm.Do() scope; convert to GlobalRef with env.NewGlobalRef() if needed outside",
26+
"Always defer mgr.Close() after creating any Manager to release the Java GlobalRef",
27+
"Use env.NewStringUTF() to convert Go strings to JNI, and env.GoString() for JNI to Go",
28+
"Generated packages cache method IDs via ensureInit(); prefer typed wrappers over raw JNI calls",
29+
"Java exceptions are automatically converted to Go errors by all Call*Method functions",
30+
"For callbacks on Java interfaces, use env.NewProxy() to create a java.lang.reflect.Proxy",
31+
"Create a HandlerThread with its own Looper when registering listener callbacks"
32+
]
433
}

docs/bluetooth.md

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
# Bluetooth API
2+
3+
The `bluetooth` package wraps `android.bluetooth.BluetoothAdapter`, GATT client/server, and device data classes. BLE scanning and advertising live in `bluetooth/le`.
4+
5+
## Adapter: Query State and Bonded Devices
6+
7+
```go
8+
import "github.com/AndroidGoLab/jni/bluetooth"
9+
10+
adapter, err := bluetooth.NewAdapter(ctx)
11+
if err != nil {
12+
return fmt.Errorf("bluetooth.NewAdapter: %w", err)
13+
}
14+
defer adapter.Close()
15+
16+
// Check if Bluetooth is enabled
17+
enabled, err := adapter.IsEnabled()
18+
19+
// Get adapter name and MAC address
20+
name, err := adapter.GetName()
21+
addr, err := adapter.GetAddress()
22+
23+
// Get bonded (paired) devices - returns raw Java Set object
24+
bonded, err := adapter.GetBondedDevices()
25+
```
26+
27+
## Device Data Class
28+
29+
Each `BluetoothDevice` is wrapped as a data class with typed accessor methods:
30+
31+
```go
32+
// Device wraps android.bluetooth.BluetoothDevice
33+
type Device struct {
34+
VM *jni.VM
35+
Obj *jni.GlobalRef
36+
}
37+
38+
// Access device fields via getter methods
39+
name, err := device.GetName() // String
40+
addr, err := device.GetAddress() // String
41+
devType, err := device.GetType() // int32 (DeviceTypeClassic, DeviceTypeLe, DeviceTypeDual)
42+
bondState, err := device.GetBondState() // int32 (BondNone, BondBonding, BondBonded)
43+
uuids, err := device.GetUuids() // raw Java ParcelUuid[] object
44+
```
45+
46+
## Classic Discovery
47+
48+
Start and stop Bluetooth device discovery:
49+
50+
```go
51+
// Start classic inquiry-based discovery
52+
started, err := adapter.StartDiscovery()
53+
54+
// Cancel ongoing discovery
55+
canceled, err := adapter.CancelDiscovery()
56+
```
57+
58+
Discovery results are delivered via Android's `ACTION_FOUND` broadcast.
59+
60+
## BLE Scanning
61+
62+
The LE scanner is obtained from the adapter. It returns a raw JNI object that wraps into the `bluetooth/le` package types:
63+
64+
```go
65+
import (
66+
"github.com/AndroidGoLab/jni/bluetooth"
67+
"github.com/AndroidGoLab/jni/bluetooth/le"
68+
)
69+
70+
// Get the BLE scanner from the adapter
71+
scannerObj, err := adapter.GetBluetoothLeScanner()
72+
scanner := le.BluetoothLeScanner{VM: vm, Obj: scannerObj}
73+
74+
// Start scanning with a callback (raw JNI object)
75+
scanner.StartScan1(callbackProxy)
76+
77+
// Or with filters and settings (raw JNI objects)
78+
scanner.StartScan3_1(filtersObj, settingsObj, callbackProxy)
79+
80+
// Stop scanning
81+
scanner.StopScan1(callbackProxy)
82+
```
83+
84+
## BLE Advertising
85+
86+
```go
87+
import "github.com/AndroidGoLab/jni/bluetooth/le"
88+
89+
// Get the BLE advertiser from the adapter
90+
advertiserObj, err := adapter.GetBluetoothLeAdvertiser()
91+
advertiser := le.BluetoothLeAdvertiser{VM: vm, Obj: advertiserObj}
92+
93+
// Start advertising (settings, data, callback are raw JNI objects)
94+
advertiser.StartAdvertising3(settingsObj, dataObj, callbackProxy)
95+
96+
// Stop advertising
97+
advertiser.StopAdvertising(callbackProxy)
98+
```
99+
100+
## GATT Client
101+
102+
Connect to a remote device and interact with its GATT services:
103+
104+
```go
105+
// Gatt wraps android.bluetooth.BluetoothGatt
106+
// Obtained by calling device.ConnectGatt(...)
107+
108+
// Discover services (triggers onServicesDiscovered callback)
109+
gatt.DiscoverServices()
110+
111+
// Read available services
112+
services, _ := gatt.GetServices()
113+
114+
// Read/write characteristics
115+
gatt.ReadCharacteristic(characteristicObj)
116+
gatt.WriteCharacteristic1(characteristicObj)
117+
118+
// Enable notifications for a characteristic
119+
gatt.SetCharacteristicNotification(characteristic, true)
120+
121+
// Request MTU change
122+
gatt.RequestMtu(512)
123+
124+
// Read remote RSSI
125+
gatt.ReadRemoteRssi()
126+
127+
// Clean up
128+
gatt.Close()
129+
```
130+
131+
## GATT Server
132+
133+
Host GATT services on this device:
134+
135+
```go
136+
// GattServer wraps android.bluetooth.BluetoothGattServer
137+
server.AddService(service)
138+
server.NotifyCharacteristicChanged3(device, characteristic, confirm)
139+
server.Close()
140+
```
141+
142+
## Constants
143+
144+
The bluetooth package exports all Android Bluetooth constants as typed Go values:
145+
146+
```go
147+
// Device types
148+
bluetooth.DeviceTypeClassic // 1
149+
bluetooth.DeviceTypeLe // 2
150+
bluetooth.DeviceTypeDual // 3
151+
152+
// Bond states
153+
bluetooth.BondNone // 10
154+
bluetooth.BondBonding // 11
155+
bluetooth.BondBonded // 12
156+
157+
// Scan modes (BluetoothAdapter)
158+
bluetooth.ScanModeNone // 20
159+
bluetooth.ScanModeConnectable // 21
160+
bluetooth.ScanModeConnectableDiscoverable // 23
161+
162+
// GATT characteristic properties
163+
bluetooth.PropertyRead // 2
164+
bluetooth.PropertyWrite // 8
165+
bluetooth.PropertyNotify // 16
166+
bluetooth.PropertyIndicate // 32
167+
168+
// GATT status / connection state
169+
bluetooth.GattSuccess // 0
170+
bluetooth.StateDisconnected // 0
171+
bluetooth.StateConnected // 2
172+
```
173+
174+
## Required Permissions (Android 12+)
175+
176+
```xml
177+
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
178+
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
179+
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
180+
```

docs/broadcast-receiver.md

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
# BroadcastReceiver and IntentFilter
2+
3+
This guide shows how to register a `BroadcastReceiver` from Go to listen for system events like WiFi state changes, battery updates, and Bluetooth discovery results.
4+
5+
## Creating a BroadcastReceiver via JNI Proxy
6+
7+
Since `BroadcastReceiver` is an abstract class (not an interface), the approach uses `java.lang.reflect.Proxy` with a wrapper interface or a direct subclass. The most practical method is using `env.NewProxy` with the `InvocationHandler` pattern.
8+
9+
### Pattern: WiFi State Change Listener
10+
11+
```go
12+
import (
13+
"github.com/AndroidGoLab/jni"
14+
"github.com/AndroidGoLab/jni/net/wifi"
15+
)
16+
17+
func listenForWifiChanges(vm *jni.VM, ctx *app.Context) error {
18+
mgr, err := wifi.NewManager(ctx)
19+
if err != nil {
20+
return err
21+
}
22+
defer mgr.Close()
23+
24+
// Check current WiFi state
25+
enabled, _ := mgr.IsWifiEnabled()
26+
fmt.Printf("WiFi enabled: %v\n", enabled)
27+
28+
// For real-time state changes, register via raw JNI:
29+
return vm.Do(func(env *jni.Env) error {
30+
// 1. Create an IntentFilter
31+
ifClass, _ := env.FindClass("android/content/IntentFilter")
32+
ifInit, _ := env.GetMethodID(ifClass, "<init>", "(Ljava/lang/String;)V")
33+
action, _ := env.NewStringUTF("android.net.wifi.WIFI_STATE_CHANGED")
34+
intentFilter, _ := env.NewObject(ifClass, ifInit, jni.ObjectValue(&action.Object))
35+
36+
// 2. Create a BroadcastReceiver via proxy
37+
// BroadcastReceiver is abstract, so we use a dynamic subclass.
38+
// Alternatively, define a minimal Java helper class.
39+
// The env.NewProxy approach works for interfaces only.
40+
41+
// For BroadcastReceiver (abstract class), use registerCallback-style APIs
42+
// when available (Android 13+ has registerNetworkCallback, etc.)
43+
44+
// See the "Modern Callback APIs" section below for the recommended approach.
45+
_ = intentFilter
46+
return nil
47+
})
48+
}
49+
```
50+
51+
## Modern Callback APIs (Recommended)
52+
53+
Android provides callback-based APIs that work better with JNI proxy:
54+
55+
### Network Connectivity Callback
56+
57+
```go
58+
import "github.com/AndroidGoLab/jni/net"
59+
60+
mgr, _ := net.NewConnectivityManager(ctx)
61+
defer mgr.Close()
62+
63+
// ConnectivityManager provides callback registration
64+
// that doesn't require BroadcastReceiver
65+
activeNet, _ := mgr.GetActiveNetwork()
66+
caps, _ := mgr.GetNetworkCapabilities(activeNet)
67+
```
68+
69+
### WiFi Scan Results (Post-Query Pattern)
70+
71+
```go
72+
import "github.com/AndroidGoLab/jni/net/wifi"
73+
74+
mgr, _ := wifi.NewManager(ctx)
75+
defer mgr.Close()
76+
77+
// Query current state directly instead of waiting for broadcasts
78+
enabled, _ := mgr.IsWifiEnabled()
79+
// Get scan results (populated by the OS)
80+
scanResults, _ := mgr.GetScanResults()
81+
```
82+
83+
## JNI Proxy for Interface-Based Callbacks
84+
85+
When the callback is a Java interface (not abstract class), `env.NewProxy` works directly:
86+
87+
```go
88+
vm.Do(func(env *jni.Env) error {
89+
// Example: LocationListener (interface)
90+
listenerClass, _ := env.FindClass("android/location/LocationListener")
91+
92+
proxy, cleanup, err := env.NewProxy(
93+
[]*jni.Class{listenerClass},
94+
func(env *jni.Env, methodName string, args []*jni.Object) (*jni.Object, error) {
95+
switch methodName {
96+
case "onLocationChanged":
97+
// Handle location update
98+
case "onProviderEnabled":
99+
// Handle provider enabled
100+
case "onProviderDisabled":
101+
// Handle provider disabled
102+
}
103+
return nil, nil
104+
},
105+
)
106+
if err != nil {
107+
return err
108+
}
109+
defer cleanup()
110+
111+
// Register the proxy with a manager method
112+
// ...
113+
return nil
114+
})
115+
```
116+
117+
## HandlerThread for Callback Delivery
118+
119+
Callbacks need a `Looper` thread to be delivered. Create a `HandlerThread`:
120+
121+
```go
122+
vm.Do(func(env *jni.Env) error {
123+
// Create HandlerThread
124+
htClass, _ := env.FindClass("android/os/HandlerThread")
125+
htInit, _ := env.GetMethodID(htClass, "<init>", "(Ljava/lang/String;)V")
126+
name, _ := env.NewStringUTF("CallbackThread")
127+
ht, _ := env.NewObject(htClass, htInit, jni.ObjectValue(&name.Object))
128+
handlerThread := env.NewGlobalRef(ht)
129+
130+
// Start the thread
131+
startMid, _ := env.GetMethodID(htClass, "start", "()V")
132+
env.CallVoidMethod(handlerThread, startMid)
133+
134+
// Get its Looper for registering callbacks
135+
getLooperMid, _ := env.GetMethodID(htClass, "getLooper", "()Landroid/os/Looper;")
136+
looper, _ := env.CallObjectMethod(handlerThread, getLooperMid)
137+
138+
// Use looper when registering listeners (e.g., requestLocationUpdates)
139+
_ = looper
140+
141+
// Later: quit the thread
142+
// quitMid, _ := env.GetMethodID(htClass, "quit", "()Z")
143+
// env.CallBooleanMethod(handlerThread, quitMid)
144+
// env.DeleteGlobalRef(handlerThread)
145+
146+
return nil
147+
})
148+
```
149+
150+
## WiFi P2P (Wi-Fi Direct)
151+
152+
The `net/wifi/p2p` package wraps WiFi Direct functionality:
153+
154+
```go
155+
import "github.com/AndroidGoLab/jni/net/wifi/p2p"
156+
157+
mgr, _ := p2p.NewWifiP2pManager(ctx)
158+
defer mgr.Close()
159+
160+
// WiFi P2P uses callback interfaces that work with env.NewProxy
161+
```
162+
163+
## Content Wrappers for BroadcastReceiver
164+
165+
The `content` package provides a generated `BroadcastReceiver` wrapper type:
166+
167+
```go
168+
import "github.com/AndroidGoLab/jni/content"
169+
170+
// BroadcastReceiver wraps android.content.BroadcastReceiver
171+
receiver := content.BroadcastReceiver{VM: vm, Obj: receiverObj}
172+
receiver.AbortBroadcast()
173+
result, _ := receiver.GetAbortBroadcast()
174+
```
175+
176+
Note: instantiating a `BroadcastReceiver` from Go requires a Java subclass or dynamic proxy. The wrapper is for interacting with existing receiver instances passed via JNI.

0 commit comments

Comments
 (0)