diff --git a/.agent/rules/rules.md b/.agent/rules/rules.md index 7168799..443301e 100644 --- a/.agent/rules/rules.md +++ b/.agent/rules/rules.md @@ -1,3 +1,7 @@ +--- +trigger: always_on +--- + You are an expert in TypeScript, React Native, Expo, and Mobile App Development. Code Style and Structure: @@ -64,3 +68,27 @@ Additional Rules: - Use `@rnmapbox/maps` for maps, mapping or vehicle navigation - Use `lucide-react-native` for icons and use those components directly in the markup and don't use the gluestack-ui icon component - Use ? : for conditional rendering and not && + + +Be more strict about planning. + + +Do not say things or provide incorrect information just to be polite; certainty is required. + + +When solving problems, always analyze them through first principles thinking. Break every challenge down to its basic, fundamental truths and build your solutions from the ground up rather than relying on analogies or common practices. + + +When debugging, always investigate whether legacy code or previous implementations are interfering with new logic before assuming the new code is inherently broken. + + +**Anti-Repetition Protocol** +: If a previously suggested fix is reported as failed, do not attempt to "patch" the broken logic or repeat the same suggestion. Instead, explicitly discard your previous assumptions, re-verify the data flow from first principles, and propose a fundamentally different architectural path. Avoid repetition bias at all costs. + + +**Token Efficiency Protocol** +: Be extremely concise. Prioritize code and technical facts over conversational filler. + + +**Pre-Flight Verification** +: Always verify the current state of relevant files, imports, and the specific environment (e.g., Windows paths, Node version) BEFORE proposing a change. The goal is to maximize the success rate of the first attempt. diff --git a/src/services/callkeep.service.android.ts b/src/services/callkeep.service.android.ts index 69e303e..7f6f73b 100644 --- a/src/services/callkeep.service.android.ts +++ b/src/services/callkeep.service.android.ts @@ -74,14 +74,10 @@ export class CallKeepService { await RNCallKeep.setup(options); - // On Android, we might need to ask for permissions explicitly if not handled by setup + // Set available for Android 23+ + // Note: Phone permissions are now requested on-demand when connecting to a voice call + // to avoid prompting users who don't need voice features if (Platform.Version >= 23) { - if (Platform.Version >= 30) { - const hasPermission = await PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.READ_PHONE_NUMBERS); - if (!hasPermission) { - await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.READ_PHONE_NUMBERS); - } - } RNCallKeep.setAvailable(true); } @@ -101,6 +97,59 @@ export class CallKeepService { } } + /** + * Request phone permissions required for CallKeep to manage calls + * This should be called just before connecting to a voice call + * Returns true if permissions are granted or not required + */ + async requestPhonePermissions(): Promise { + if (Platform.OS !== 'android') { + return true; + } + + // READ_PHONE_NUMBERS is only required on Android 11+ (API 30+) + if (Platform.Version < 30) { + return true; + } + + try { + const hasPermission = await PermissionsAndroid.check( + PermissionsAndroid.PERMISSIONS.READ_PHONE_NUMBERS + ); + + if (hasPermission) { + logger.debug({ + message: 'READ_PHONE_NUMBERS permission already granted', + }); + return true; + } + + const result = await PermissionsAndroid.request( + PermissionsAndroid.PERMISSIONS.READ_PHONE_NUMBERS, + { + title: 'Phone Permission Required', + message: 'This app needs phone access to manage voice calls with your headset', + buttonPositive: 'Grant', + buttonNegative: 'Deny', + } + ); + + const granted = result === PermissionsAndroid.RESULTS.GRANTED; + logger.info({ + message: 'READ_PHONE_NUMBERS permission request result', + context: { granted, result }, + }); + + return granted; + } catch (error) { + logger.error({ + message: 'Failed to request phone permissions', + context: { error }, + }); + return false; + } + } + /** * Start a CallKit call to keep the app alive in the background * This should be called when connecting to a LiveKit room diff --git a/src/services/callkeep.service.ios.ts b/src/services/callkeep.service.ios.ts index ebec457..9a9fb8e 100644 --- a/src/services/callkeep.service.ios.ts +++ b/src/services/callkeep.service.ios.ts @@ -98,6 +98,16 @@ export class CallKeepService { } } + /** + * Request phone permissions (iOS implementation - no-op) + * This permission is only required on Android for CallKeep headset controls + * On iOS, we always return true as no additional permissions are needed + */ + async requestPhonePermissions(): Promise { + // iOS does not require READ_PHONE_NUMBERS permission + return true; + } + /** * Start a CallKit call to keep the app alive in the background * This should be called when connecting to a LiveKit room diff --git a/src/stores/app/livekit-store.ts b/src/stores/app/livekit-store.ts index e567248..7e41808 100644 --- a/src/stores/app/livekit-store.ts +++ b/src/stores/app/livekit-store.ts @@ -340,6 +340,17 @@ export const useLiveKitStore = create((set, get) => ({ // Start CallKeep call (iOS & Android) if (Platform.OS !== 'web') { + // Request phone permissions for CallKeep headset controls (Android only) + if (Platform.OS === 'android') { + const hasPhonePermission = await callKeepService.requestPhonePermissions(); + if (!hasPhonePermission) { + logger.warn({ + message: 'Phone permission not granted - headset controls may not work properly', + }); + // Don't block connection - continue without phone permission + } + } + // Using a generic handle or room name const roomName = roomInfo?.Name || 'Voice Channel'; callKeepService