From 3caa89d4fdad1c52be73937680e2d3ce92f6276b Mon Sep 17 00:00:00 2001 From: Shawn Jackson Date: Wed, 26 Aug 2026 11:00:40 -0700 Subject: [PATCH 1/2] RG-T133 Foregound service fix --- customManifest.plugin.js | 2 +- jest-setup.ts | 6 ++++++ src/__tests__/no-self-mocking-suites.test.ts | 3 ++- src/stores/app/__tests__/livekit-store-room-switch.test.ts | 3 ++- src/stores/app/livekit-store.ts | 5 ++++- 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/customManifest.plugin.js b/customManifest.plugin.js index 46d84240..a8c858e3 100644 --- a/customManifest.plugin.js +++ b/customManifest.plugin.js @@ -14,7 +14,7 @@ const withForegroundService = (config) => { mainApplication['service'].push({ $: { 'android:name': 'app.notifee.core.ForegroundService', - 'android:foregroundServiceType': 'microphone|mediaPlayback|connectedDevice', + 'android:foregroundServiceType': 'microphone|connectedDevice', 'tools:replace': 'android:foregroundServiceType', }, }); diff --git a/jest-setup.ts b/jest-setup.ts index 82915641..02f47a99 100644 --- a/jest-setup.ts +++ b/jest-setup.ts @@ -176,10 +176,16 @@ jest.mock('@notifee/react-native', () => { UNSPECIFIED: 'unspecified', }; + const AndroidForegroundServiceType = { + FOREGROUND_SERVICE_TYPE_MICROPHONE: 128, + FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE: 16, + }; + return { __esModule: true, default: mockNotifee, AndroidImportance, + AndroidForegroundServiceType, }; }); diff --git a/src/__tests__/no-self-mocking-suites.test.ts b/src/__tests__/no-self-mocking-suites.test.ts index 98cc8077..7aefe70a 100644 --- a/src/__tests__/no-self-mocking-suites.test.ts +++ b/src/__tests__/no-self-mocking-suites.test.ts @@ -67,7 +67,8 @@ const findSelfMock = (testFile: string): string | null => { const source = stripComments(fs.readFileSync(testFile, 'utf8')); // Match jest.mock('../') / jest.doMock("../"), with or without a factory. const selfMock = new RegExp(String.raw`jest\.(?:do)?[Mm]ock\(\s*['"\`]\.\./${subjectName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}['"\`]`); - return selfMock.test(source) ? path.relative(path.join(SRC, '..'), testFile) : null; + // Normalize to forward slashes so the known-debt list matches on Windows too. + return selfMock.test(source) ? path.relative(path.join(SRC, '..'), testFile).split(path.sep).join('/') : null; }; describe('test suites cover their real subject', () => { diff --git a/src/stores/app/__tests__/livekit-store-room-switch.test.ts b/src/stores/app/__tests__/livekit-store-room-switch.test.ts index a42b360b..4a5a984a 100644 --- a/src/stores/app/__tests__/livekit-store-room-switch.test.ts +++ b/src/stores/app/__tests__/livekit-store-room-switch.test.ts @@ -24,7 +24,8 @@ jest.mock('@notifee/react-native', () => ({ stopForegroundService: jest.fn(), }, AndroidForegroundServiceType: { - FOREGROUND_SERVICE_TYPE_MICROPHONE: 1, + FOREGROUND_SERVICE_TYPE_MICROPHONE: 128, + FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE: 16, }, AndroidImportance: { DEFAULT: 3, diff --git a/src/stores/app/livekit-store.ts b/src/stores/app/livekit-store.ts index 3c055457..10b62602 100644 --- a/src/stores/app/livekit-store.ts +++ b/src/stores/app/livekit-store.ts @@ -743,7 +743,10 @@ export const useLiveKitStore = create((set, get) => ({ android: { channelId: 'notif', asForegroundService: true, - foregroundServiceTypes: [AndroidForegroundServiceType.FOREGROUND_SERVICE_TYPE_MICROPHONE], + // microphone: keeps mic capture legal while backgrounded (Android 14+). + // connectedDevice: covers external bluetooth PTT handsets driving the call. + // Playback of remote audio needs no FGS type — any running FGS keeps the process alive. + foregroundServiceTypes: [AndroidForegroundServiceType.FOREGROUND_SERVICE_TYPE_MICROPHONE, AndroidForegroundServiceType.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE], smallIcon: 'ic_launcher', }, }); From 1dfcef3cf95748a292ee06e333bc797ddc42341b Mon Sep 17 00:00:00 2001 From: Shawn Jackson Date: Wed, 26 Aug 2026 13:26:12 -0700 Subject: [PATCH 2/2] RG-T133 PR#282 fixes --- src/services/location.ts | 11 ++++++++++- src/stores/app/livekit-store.ts | 13 +++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/services/location.ts b/src/services/location.ts index 17ce5f2e..56d61304 100644 --- a/src/services/location.ts +++ b/src/services/location.ts @@ -366,7 +366,16 @@ class LocationService { this.isRealtimeGeolocationEnabled = await loadRealtimeGeolocationState(); // Only request background permissions if the user has enabled background geolocation - const hasPermissions = await this.requestPermissions(this.isBackgroundGeolocationEnabled); + let hasPermissions: boolean; + try { + hasPermissions = await this.requestPermissions(this.isBackgroundGeolocationEnabled); + } catch (error) { + logger.error({ + message: 'Failed to request location permissions before starting updates', + context: { operation: 'startLocationUpdates', error }, + }); + throw error; + } if (!hasPermissions) { throw new Error('Location permissions not granted'); } diff --git a/src/stores/app/livekit-store.ts b/src/stores/app/livekit-store.ts index 10b62602..283818bb 100644 --- a/src/stores/app/livekit-store.ts +++ b/src/stores/app/livekit-store.ts @@ -737,6 +737,14 @@ export const useLiveKitStore = create((set, get) => ({ // that triggers the already-registered handler. if (Platform.OS === 'android') { try { + // connectedDevice type is only legal when a bluetooth PTT handset is actually + // connected AND a runtime prerequisite (BLUETOOTH_CONNECT) is held — Android 14+ + // validates both at FGS start and throws SecurityException otherwise, blocking + // the service. Manifest FOREGROUND_SERVICE_CONNECTED_DEVICE alone is not enough. + let bluetoothDeviceActive = useBluetoothAudioStore.getState().connectedDevice !== null; + if (bluetoothDeviceActive) { + bluetoothDeviceActive = await PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT); + } await notifee.displayNotification({ title: 'Active PTT Call', body: 'There is an active PTT call in progress.', @@ -744,9 +752,10 @@ export const useLiveKitStore = create((set, get) => ({ channelId: 'notif', asForegroundService: true, // microphone: keeps mic capture legal while backgrounded (Android 14+). - // connectedDevice: covers external bluetooth PTT handsets driving the call. // Playback of remote audio needs no FGS type — any running FGS keeps the process alive. - foregroundServiceTypes: [AndroidForegroundServiceType.FOREGROUND_SERVICE_TYPE_MICROPHONE, AndroidForegroundServiceType.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE], + foregroundServiceTypes: bluetoothDeviceActive + ? [AndroidForegroundServiceType.FOREGROUND_SERVICE_TYPE_MICROPHONE, AndroidForegroundServiceType.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE] + : [AndroidForegroundServiceType.FOREGROUND_SERVICE_TYPE_MICROPHONE], smallIcon: 'ic_launcher', }, });