Compare commits

...

5 Commits

Author SHA1 Message Date
e53c7be976 Ignore jar build dir 2026-08-21 20:34:57 -04:00
99598c14fe Request BLUETOOTH_SCAN/CONNECT at onAttach on API 31+
Activity.requestPermissions is unreliable from non-UI threads / late
timing on Android 17 (createDeviceContext garbage device id); the
fragment's onAttach request path works. Skip the legacy location
request on API 31+ (BLUETOOTH_SCAN is neverForLocation). Rebuild
Ble.jar.
2026-08-21 20:28:43 -04:00
876ea68622 Use commitNowAllowingStateLoss (API 24+) for fragment registration
The register() runnable can run after the activity has saved its
state (e.g. app started in the background), in which case commitNow
throws IllegalStateException and crashes the app. Rebuild Ble.jar.
2026-08-21 19:52:33 -04:00
9f32185b78 Merge branch 'fix/recents-wipe-jni-null-view'
Guard against Gio's null-view detach event (recents-wipe SIGABRT).
2026-08-20 23:35:08 -04:00
d70bbfed13 Guard against Gio's null-view detach event (recents-wipe SIGABRT)
Gio's GioView.onDestroyView sends ViewEvent{View: 0} as its detach
signal when the view is destroyed — i.e. the activity going away on a
recents-wipe. An app that forwards the event to Enable() passes a zero
view through to registerFragment, whose GetObjectClass(null) aborts the
process (JNI DETECTED ERROR: java_object == null in call to
GetObjectClass).

Enable() now returns early on a zero view so all callers are covered
even if they forward the event unfiltered, and registerFragment
null-checks as defense in depth. Same fix as pad (fdbffc9); verified
there on a Pixel 9 Pro / Android 17 where the recents swipe used to
crash the app.
2026-08-20 22:16:23 -04:00
5 changed files with 35 additions and 3 deletions

2
.gitignore vendored
View File

@ -1 +1,3 @@
classes/
cb.ast

BIN
Ble.jar

Binary file not shown.

View File

@ -35,6 +35,7 @@ public class BlessedConnect extends Fragment {
final int PERMISSION_REQUEST = 1;
final int REQUEST_ENABLE_BT = 2;
final int PERMISSION_REQUEST_BLE = 3;
public BlessedConnect() {
Log.d("gio", "BlessedConnect()");
@ -50,7 +51,11 @@ public class BlessedConnect extends Fragment {
Activity act = (Activity)ctx;
FragmentTransaction ft = act.getFragmentManager().beginTransaction();
ft.add(inst, "BlessedConnect");
ft.commitNow();
if (android.os.Build.VERSION.SDK_INT >= 24) {
ft.commitNowAllowingStateLoss();
} else {
ft.commitNow();
}
}
});
}
@ -59,7 +64,15 @@ public class BlessedConnect extends Fragment {
super.onAttach(ctx);
Log.d("gio", "BlessedConnect: onAttach()");
ctx.registerReceiver(receiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
if (ctx.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
if (android.os.Build.VERSION.SDK_INT >= 31) {
// BLE on Android 12+ needs the BLUETOOTH_SCAN/CONNECT runtime
// permissions. BLUETOOTH_SCAN is declared with
// usesPermissionFlags="neverForLocation", so no location
// permission is required for scanning.
requestPermissions(new String[]{
"android.permission.BLUETOOTH_SCAN",
"android.permission.BLUETOOTH_CONNECT"}, PERMISSION_REQUEST_BLE);
} else if (ctx.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSION_REQUEST);
}
central = new BluetoothCentral(ctx, centralCallback, null);
@ -264,7 +277,7 @@ public class BlessedConnect extends Fragment {
@Override
public void onRequestPermissionsResult (int requestCode, String[] permissions, int[] grantResults) {
Log.d("gio", "BlessedConnect: onRequestPermissionsResult");
if (requestCode == PERMISSION_REQUEST) {
if (requestCode == PERMISSION_REQUEST || requestCode == PERMISSION_REQUEST_BLE) {
boolean granted = true;
for (int x : grantResults) {
if (x == PackageManager.PERMISSION_DENIED) {

View File

@ -225,6 +225,15 @@ func NewBLE() *BLE {
//Enable
func (b *BLE) Enable(view uintptr) {
log.Printf("ble.Enable()")
// view == 0 is Gio's detach signal (GioView.onDestroyView sends
// ViewEvent{View: 0} when the view is destroyed, e.g. the activity
// going away on a recents-wipe). Nothing to register; passing the null
// ref on would abort in JNI (GetObjectClass on null). A re-attach
// arrives as a fresh event with a live view. Callers that forward the
// event unfiltered are protected by this guard.
if view == 0 {
return
}
setJVM(app.JavaVM())
runInJVM(func(env *JNIEnv) {
log.Printf("ble.Enable(): inside runInJVM()")

View File

@ -5,6 +5,14 @@
void
registerFragment(JNIEnv *env, jobject view) {
if (view == NULL) {
// Detach signal (Gio sends ViewEvent{View: 0} when the view is
// destroyed, e.g. the activity going away on a recents-wipe).
// The Go side already filters these; this guard keeps the JNI
// calls safe if one ever slips through (GetObjectClass on null
// aborts the process).
return;
}
jclass cls = (*env)->GetObjectClass(env, view);
jmethodID mid = (*env)->GetMethodID(env, cls, "getContext", "()Landroid/content/Context;");
jobject ctx = (*env)->CallObjectMethod(env, view, mid);