Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .agent/rules/rules.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
trigger: always_on
---

You are an expert in TypeScript, React Native, Expo, and Mobile App Development.

Code Style and Structure:
Expand Down Expand Up @@ -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.
63 changes: 56 additions & 7 deletions src/services/callkeep.service.android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
alertDescription: 'This application needs to access your phone accounts',
cancelButton: 'Cancel',
okButton: 'OK',
additionalPermissions: [

Check warning on line 60 in src/services/callkeep.service.android.ts

View workflow job for this annotation

GitHub Actions / test

Replace `⏎············PermissionsAndroid.PERMISSIONS.READ_PHONE_STATE,⏎············...(Platform.Version·>=·30·?·[PermissionsAndroid.PERMISSIONS.READ_PHONE_NUMBERS]·:·[]),⏎··········` with `PermissionsAndroid.PERMISSIONS.READ_PHONE_STATE,·...(Platform.Version·>=·30·?·[PermissionsAndroid.PERMISSIONS.READ_PHONE_NUMBERS]·:·[])`
PermissionsAndroid.PERMISSIONS.READ_PHONE_STATE,
...(Platform.Version >= 30 ? [PermissionsAndroid.PERMISSIONS.READ_PHONE_NUMBERS] : []),
],
Expand All @@ -74,14 +74,10 @@

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);
}

Expand All @@ -101,6 +97,59 @@
}
}

/**
* 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<boolean> {
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(

Check warning on line 116 in src/services/callkeep.service.android.ts

View workflow job for this annotation

GitHub Actions / test

Replace `⏎········PermissionsAndroid.PERMISSIONS.READ_PHONE_NUMBERS⏎······` with `PermissionsAndroid.PERMISSIONS.READ_PHONE_NUMBERS`
PermissionsAndroid.PERMISSIONS.READ_PHONE_NUMBERS
);

if (hasPermission) {
logger.debug({
message: 'READ_PHONE_NUMBERS permission already granted',
});
return true;
}

const result = await PermissionsAndroid.request(

Check warning on line 127 in src/services/callkeep.service.android.ts

View workflow job for this annotation

GitHub Actions / test

Replace `⏎········PermissionsAndroid.PERMISSIONS.READ_PHONE_NUMBERS,⏎·······` with `PermissionsAndroid.PERMISSIONS.READ_PHONE_NUMBERS,`
PermissionsAndroid.PERMISSIONS.READ_PHONE_NUMBERS,
{
title: 'Phone Permission Required',

Check warning on line 130 in src/services/callkeep.service.android.ts

View workflow job for this annotation

GitHub Actions / test

Delete `··`
message: 'This app needs phone access to manage voice calls with your headset',

Check warning on line 131 in src/services/callkeep.service.android.ts

View workflow job for this annotation

GitHub Actions / test

Delete `··`
buttonPositive: 'Grant',

Check warning on line 132 in src/services/callkeep.service.android.ts

View workflow job for this annotation

GitHub Actions / test

Replace `··········` with `········`
buttonNegative: 'Deny',

Check warning on line 133 in src/services/callkeep.service.android.ts

View workflow job for this annotation

GitHub Actions / test

Delete `··`
}

Check warning on line 134 in src/services/callkeep.service.android.ts

View workflow job for this annotation

GitHub Actions / test

Replace `··}⏎······` with `}`
);

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
Expand Down
10 changes: 10 additions & 0 deletions src/services/callkeep.service.ios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<boolean> {
// 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
Expand Down
11 changes: 11 additions & 0 deletions src/stores/app/livekit-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,17 @@ export const useLiveKitStore = create<LiveKitState>((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
Expand Down
Loading