Skip to content

fix: prevent crash on Android devices without telecom feature#7334

Open
diegolmello wants to merge 2 commits into
developfrom
fix/callkeep-telecom-unsupported
Open

fix: prevent crash on Android devices without telecom feature#7334
diegolmello wants to merge 2 commits into
developfrom
fix/callkeep-telecom-unsupported

Conversation

@diegolmello
Copy link
Copy Markdown
Member

@diegolmello diegolmello commented May 20, 2026

Proposed changes

RNCallKeep.setup() runs unconditionally from index.js on every Android boot and ends up calling TelecomManager.registerPhoneAccount. On devices that ship without the android.software.telecom system feature — e.g. Zebra TC21 and other rugged enterprise SKUs — that call throws UnsupportedOperationException. The exception lands on the RN bridge thread (mqt_v_native), so the .catch(...) attached to the JS-side setup() promise cannot intercept it and the process crashes immediately at startup.

Two layers of defense:

  1. JS-side gate (index.js) — skip RNCallKeep.setup() when the device lacks android.software.telecom. Uses react-native-device-info's hasSystemFeatureSync (a true blocking-synchronous bridge method, sub-millisecond), so the cold-start path that accepts FCM-driven calls is unaffected on telecom-capable devices. No new native module needed; react-native-device-info is already a dependency.
  2. Native patch (patches/react-native-callkeep+4.3.16.patch)RNCallKeepModule.registerPhoneAccount now checks PackageManager.FEATURE_TELECOM and returns early, with a try/catch around the underlying telecomManager.registerPhoneAccount(...) call. Kept as defense-in-depth for OEMs that misreport the feature and for any future call site that bypasses the JS gate.

The existing native VoipNotification.kt path already catches Exception around its own registerPhoneAccount, so no change is needed there. VoIP is not usable on these devices anyway — this just stops the boot crash so the rest of the app continues to function.

java.lang.UnsupportedOperationException: System does not support feature android.software.telecom
    at android.telecom.TelecomManager.registerPhoneAccount(TelecomManager.java:1530)
    at io.wazo.callkeep.RNCallKeepModule.registerPhoneAccount(RNCallKeepModule.java:1138)
    at io.wazo.callkeep.RNCallKeepModule.setup(RNCallKeepModule.java:390)

Issue(s)

How to test or reproduce

Reproduces deterministically on any Android device whose adb shell pm list features output does not include android.software.telecom. Confirmed on the Zebra TC21 (Android 13) in the linked ticket. After the fix, the app should boot cleanly on that device — no Telecom call is attempted from JS, and even if something else were to reach RNCallKeepModule.registerPhoneAccount, the native guard logs [RNCallKeepModule] registerPhoneAccount skipped: device lacks FEATURE_TELECOM instead of crashing.

A standard Android emulator (Pixel images) does include the telecom feature, so emulator builds are not a substitute repro — they only verify the no-regression case on telecom-capable devices.

Screenshots

N/A — boot crash fix, no UI change.

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • Improvement (non-breaking change which improves a current function)
  • New feature (non-breaking change which adds functionality)
  • Documentation update (if none of the other choices apply)

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works (if applicable)
  • I have added necessary documentation (if applicable)
  • Any dependent changes have been merged and published in downstream modules

Further comments

No JS unit test added: the gate is a single boolean check against a native-platform predicate, with no good seam to test in isolation that would be more meaningful than just reading the code. The native patch re-applies cleanly against an unmodified react-native-callkeep@4.3.16 tarball (verified with patch -p1 --dry-run).

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced error handling and stability for incoming and outgoing call operations.
    • Prevented crashes when registering phone accounts on certain Android devices by handling unsupported or security failures.
    • Android call setup now runs only on devices with telecom support, avoiding erroneous initialization on unsupported devices.
    • Fixed delayed event initialization on iOS to prevent potential event loss.
    • Strengthened null-safety across call connection flows.

Review Change Stack

RNCallKeep.setup() runs unconditionally on Android boot and calls
TelecomManager.registerPhoneAccount, which throws
UnsupportedOperationException on devices that ship without the
android.software.telecom feature (e.g. Zebra TC21 and other rugged
enterprise SKUs). The exception lands on the RN bridge thread, so the
.catch() in JS cannot intercept it and the process crashes at boot.

Patch react-native-callkeep so registerPhoneAccount checks
PackageManager.FEATURE_TELECOM up front and returns early, with a
try/catch around the underlying TelecomManager call as a second line of
defense for OEMs that lie about the feature.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

Walkthrough

Defensive updates to react-native-callkeep: Android phone-account registration and availability checks are wrapped and gated by FEATURE_TELECOM; two React bridge overloads were removed; VoiceConnectionService access to RNCallKeep is null-guarded and Android O+ connections set self-managed; iOS delayed events array is initialized when nil.

Changes

react-native-callkeep Robustness and API Updates

Layer / File(s) Summary
Phone account registration and availability checks
patches/react-native-callkeep+4.3.16.patch
PhoneAccountHandle construction uses context.getPackageName(); registerPhoneAccount skips registration when PackageManager.FEATURE_TELECOM is absent and wraps telecomManager.registerPhoneAccount(...) in try/catch for UnsupportedOperationException and SecurityException; hasPhoneAccount catches SecurityException and returns false on failure.
React Native bridge method simplification
patches/react-native-callkeep+4.3.16.patch
Removed two @ReactMethod overloads: displayIncomingCall(String uuid, String number, String callerName) and startCall(String uuid, String number, String callerName); only variants that include hasVideo remain.
Connection service initialization robustness
patches/react-native-callkeep+4.3.16.patch
Guarded retrieval of current React Activity via RNCallKeepModule.instance; for Android O+ flows, Connection.PROPERTY_SELF_MANAGED is set directly without querying TelecomManager/PhoneAccount.
Android RNCallKeep setup runtime gating
index.js
Import react-native-device-info and initialize RNCallKeep on Android only when DeviceInfo.hasSystemFeatureSync('android.software.telecom') returns true.
iOS delayed events initialization fix
patches/react-native-callkeep+4.3.16.patch
Ensure _delayedEvents is non-nil by initializing to an empty mutable array when required before appending delayed event dictionaries.

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

type: bug

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately and concisely summarizes the main change: preventing crashes on Android devices lacking the telecom feature.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • SUP-1044: Request failed with status code 401

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@diegolmello diegolmello temporarily deployed to experimental_android_build May 20, 2026 14:05 — with GitHub Actions Inactive
@diegolmello diegolmello had a problem deploying to experimental_ios_build May 20, 2026 14:05 — with GitHub Actions Error
@diegolmello diegolmello had a problem deploying to official_android_build May 20, 2026 14:05 — with GitHub Actions Error
Belt-and-suspenders to the native patch: skip the JS-side setup entirely
on devices that lack android.software.telecom so the RN bridge thread
never reaches the Telecom API. Uses react-native-device-info's
hasSystemFeatureSync (a true blocking-synchronous bridge method), so
cold-start cost is sub-millisecond — the FCM-driven call accept path
is unaffected on telecom-capable devices.

The native patch is kept as defense-in-depth for OEMs that misreport
the feature and for any future call site that bypasses this gate.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
index.js (1)

26-26: ⚡ Quick win

The hasSystemFeatureSync API is documented and available in react-native-device-info 11.1.0—no blocking issue.

Verification confirms that DeviceInfo.hasSystemFeatureSync(feature) is a documented synchronous method in react-native-device-info 11.1.0 that returns a boolean directly. The method exists and is safe for use during app initialization.

If you want additional robustness, consider wrapping the feature check in try-catch as a secondary safeguard:

-	if (Platform.OS === 'android' && DeviceInfo.hasSystemFeatureSync('android.software.telecom')) {
+	let hasTelecomFeature = false;
+	try {
+		hasTelecomFeature = Platform.OS === 'android' && DeviceInfo.hasSystemFeatureSync('android.software.telecom');
+	} catch (error) {
+		console.warn('Failed to check telecom feature availability:', error);
+	}
+
+	if (hasTelecomFeature) {

This is optional but recommended for defense-in-depth on synchronous bridge calls.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@index.js` at line 26, The DeviceInfo.hasSystemFeatureSync call can throw in
rare cases even though it exists; wrap the Android-only check (where Platform.OS
=== 'android' and DeviceInfo.hasSystemFeatureSync(...)) in a try/catch, store
the result to a local boolean (e.g., hasTelecomFeature) and default to false on
error, then use that boolean in the conditional; reference
DeviceInfo.hasSystemFeatureSync and the Platform.OS check so the change is made
exactly around that expression.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@index.js`:
- Line 26: The DeviceInfo.hasSystemFeatureSync call can throw in rare cases even
though it exists; wrap the Android-only check (where Platform.OS === 'android'
and DeviceInfo.hasSystemFeatureSync(...)) in a try/catch, store the result to a
local boolean (e.g., hasTelecomFeature) and default to false on error, then use
that boolean in the conditional; reference DeviceInfo.hasSystemFeatureSync and
the Platform.OS check so the change is made exactly around that expression.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e94bda7b-8356-47e8-a914-0fb6488ee3c0

📥 Commits

Reviewing files that changed from the base of the PR and between 390cb4b and b34302b.

📒 Files selected for processing (1)
  • index.js
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Use descriptive names for functions, variables, and classes that clearly convey their purpose
Write comments that explain the 'why' behind code decisions, not the 'what'
Keep functions small and focused on a single responsibility
Use const by default, let when reassignment is needed, and avoid var
Prefer async/await over .then() chains for handling asynchronous operations
Use explicit error handling with try/catch blocks for async operations
Avoid deeply nested code; refactor complex logic into helper functions

Files:

  • index.js
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Use Prettier with tabs, single quotes, 130 char width, no trailing commas, arrow parens avoid, bracket same line
Use @rocket.chat/eslint-config base with React, React Native, TypeScript, Jest plugins

Files:

  • index.js
index.js

📄 CodeRabbit inference engine (CLAUDE.md)

App registration and conditional Storybook loading should be in index.js

Files:

  • index.js
🔇 Additional comments (1)
index.js (1)

5-5: LGTM!

@diegolmello diegolmello temporarily deployed to approve_e2e_testing May 20, 2026 14:46 — with GitHub Actions Inactive
@diegolmello diegolmello had a problem deploying to official_android_build May 20, 2026 14:49 — with GitHub Actions Error
@diegolmello diegolmello deployed to experimental_android_build May 20, 2026 14:49 — with GitHub Actions Active
@diegolmello diegolmello had a problem deploying to experimental_ios_build May 20, 2026 14:49 — with GitHub Actions Error
@diegolmello diegolmello had a problem deploying to upload_experimental_android May 20, 2026 15:29 — with GitHub Actions Error
@github-actions
Copy link
Copy Markdown

Android Build Available

Rocket.Chat Experimental 4.73.0.108919

Internal App Sharing: https://play.google.com/apps/test/RQVpXLytHNc/ahAO29uNR9FQXgQEJmPasQWObsdHEn6yAQj-Q50XoPlgUd5yid1soVhTMQd786BQ_BdZRxx_w_ZabIPP5ck-Majq3F

@diegolmello diegolmello requested a deployment to official_android_build May 20, 2026 18:47 — with GitHub Actions Waiting
@diegolmello diegolmello requested a deployment to experimental_ios_build May 20, 2026 18:47 — with GitHub Actions Waiting
@diegolmello diegolmello requested a deployment to official_ios_build May 20, 2026 18:47 — with GitHub Actions Waiting
@diegolmello diegolmello requested a deployment to upload_experimental_android May 20, 2026 18:47 — with GitHub Actions Waiting
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant