Permissions & manifest
Runtime permissions
| Permission | When requested | If denied |
|---|---|---|
CAMERA | First launch / before first call | No video; the app toasts and stays on Home |
RECORD_AUDIO | Same request as camera | No calls at all - both are required together |
POST_NOTIFICATIONS (API 33+) | Before starting a call | Call still works; no ongoing-call notification |
MainActivity builds the WebRtcClient after the camera + mic grant so the
Home self-preview lights up without an app restart.
Declared permissions
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />| Permission | Used for |
|---|---|
MODIFY_AUDIO_SETTINGS | CallAudioRouter - earpiece / speaker / routing |
BLUETOOTH / BLUETOOTH_CONNECT | BT headset detection & routing |
WAKE_LOCK | keepScreenOn during a call (via a Compose DisposableEffect, not a manual lock) |
ACCESS_NETWORK_STATE | WebRTC’s NetworkMonitor - detects Wi-Fi ↔ cellular changes for ICE |
FOREGROUND_SERVICE_* | The typed FGS - see below |
Foreground service
<service
android:name=".service.CallForegroundService"
android:foregroundServiceType="camera|microphone|mediaProjection"
android:exported="false" />- Started when a call connects; stopped on hang-up.
- Carries a
CallStylenotification with a Hang Up action. - The
mediaProjectiontype is required beforeMediaProjectionManager.getMediaProjection()on Android 14+ -CallForegroundService.onMediaProjectionReadyis the callback the screen-share flow waits for. onStartCommandreturnsSTART_NOT_STICKY- a killed process means the call is over, not something to resurrect.
Activity declaration
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTask"
android:supportsPictureInPicture="true"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboardHidden|uiMode|density|fontScale"
android:windowSoftInputMode="adjustResize">| Attribute | Why |
|---|---|
launchMode="singleTask" | Re-launching from the launcher while in PiP re-enters the existing instance instead of stacking a second one |
supportsPictureInPicture="true" | PiP on onUserLeaveHint |
configChanges=…uiMode|density|fontScale… | A rotation / dark-mode / font-scale change doesn’t recreate the Activity mid-call and tear down the PeerConnection |
Features
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.microphone" android:required="false" />Both required="false" so the app installs on devices without a camera (it just
can’t make video calls).
usesCleartextTraffic
android:usesCleartextTraffic="true" is set so a self-hoster can point the app
at a plain ws:// server on a trusted LAN during testing. Production
deployments use wss://.