The RECORD_AUDIO permission was declared in the manifest but never requested at runtime, causing WebRTC to fail on Android 6+. Now requests microphone permission on app startup before initializing the WebView. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
TWP Softphone — Mobile App
Flutter-based VoIP softphone client for the Twilio WordPress Plugin. Uses the Twilio Voice SDK (WebRTC) to make and receive calls via the Android Telecom framework.
Requirements
- Flutter 3.29+ (tested with 3.41.4)
- Android device/tablet (API 26+)
- TWP WordPress plugin installed and configured on server
- Twilio account with Voice capability
Quick Start
cd mobile
flutter pub get
flutter build apk --debug
adb install build/app/outputs/flutter-apk/app-debug.apk
Server Setup
The app connects to your WordPress site running the TWP plugin. The server must have:
- TWP Plugin installed and activated
- Twilio credentials configured (Account SID, Auth Token)
- At least one Twilio phone number purchased
- A WordPress user with agent permissions
SSE (Server-Sent Events) — Apache + PHP-FPM
The app uses SSE for real-time updates (queue changes, agent status). On Apache with PHP-FPM, mod_proxy_fcgi buffers output by default, which breaks SSE streaming.
Fix — Create a config file on the web server:
echo 'ProxyPassMatch "^/wp-json/twilio-mobile/v1/stream/events$" "unix:/run/php-fpm/www.sock|fcgi://localhost/path/to/wordpress/index.php" flushpackets=on' > /etc/httpd/conf.d/twp-sse.conf
httpd -t && systemctl restart httpd
Adjust the paths:
- Socket path must match your PHP-FPM config (check
grep fcgi /etc/httpd/conf.d/php.conf)- Document root must match your WordPress installation path
Diagnosis — If the green connection dot stays red:
# Check current PHP-FPM proxy config
grep -r "fcgi\|php-fpm" /etc/httpd/conf.d/
# Check if flushpackets is configured
grep -r "flushpackets" /etc/httpd/conf.d/
# Test SSE endpoint (should stream data continuously, not hang)
curl -N -H "Authorization: Bearer YOUR_TOKEN" \
https://your-site.com/wp-json/twilio-mobile/v1/stream/events
Notes:
flushpackets=onis aProxyPassMatchdirective — it cannot go in.htaccess- If using nginx instead of Apache, the
X-Accel-Buffering: noheader (already in the PHP code) handles this automatically - The app automatically falls back to 5-second polling if SSE fails, so the app still works without this config — just with higher latency
App Setup (Android)
First Launch
- Open the app and enter your server URL (e.g.,
https://phone.cloud-hosting.io) - Log in with your WordPress credentials
- Grant permissions when prompted:
- Microphone (required for calls)
- Phone/Call (required for Android Telecom integration)
Phone Account
Android requires a registered and enabled phone account for VoIP apps. The app registers automatically, but enabling must be done manually:
- If prompted, tap "Open Settings" to go to Android's Phone Account settings
- Find "TWP Softphone" in the list and toggle it ON
- Return to the app
If you skipped this step, tap the orange warning card on the dashboard.
Path: Settings → Apps → Default apps → Phone → Calling accounts → TWP Softphone
Making Calls
- Tap the phone FAB (bottom right) to open the dialer
- Enter the phone number
- Caller ID is auto-selected from your Twilio numbers
- Tap Call — the Android system call screen (InCallUI) handles the active call
Receiving Calls
Incoming calls appear via Android's native call UI. Answer/reject using the standard Android interface.
Note: FCM push notifications are required for receiving calls when the app is in the background. This requires
google-services.jsoninandroid/app/.
Queue Management
- View assigned queues on the dashboard
- Tap a queue with waiting calls to see callers
- Tap Accept to take a call from the queue
Agent Status
Toggle between Available, Busy, and Offline using the status bar at the top of the dashboard.
Development
Project Structure
lib/
├── config/ # App configuration
├── models/ # Data models (CallInfo, QueueState, AgentStatus, User)
├── providers/ # State management (AuthProvider, CallProvider, AgentProvider)
├── screens/ # UI screens (Login, Dashboard, Settings, ActiveCall)
├── services/ # API/SDK services (VoiceService, SseService, ApiClient, AuthService)
├── widgets/ # Reusable widgets (Dialpad, QueueCard, AgentStatusToggle)
└── main.dart # App entry point
Running Tests
flutter test
34 tests covering CallInfo, QueueState, and CallProvider.
Building
# Debug APK
flutter build apk --debug
# Release APK (requires signing config)
flutter build apk --release
ADB Deployment (WiFi)
# Connect to device
adb connect DEVICE_IP:PORT
# Install
adb install -r build/app/outputs/flutter-apk/app-debug.apk
# Launch
adb shell am start -n io.cloudhosting.twp.twp_softphone/.MainActivity
# View logs
adb logcat -s flutter
Key Dependencies
| Package | Purpose |
|---|---|
twilio_voice |
Twilio Voice SDK (WebRTC calling) |
provider |
State management |
dio |
HTTP client (REST API, SSE) |
firebase_messaging |
FCM push for incoming calls |
flutter_secure_storage |
Secure token storage |
Troubleshooting
| Problem | Solution |
|---|---|
| Green dot stays red | SSE buffering — see Server Setup |
| "No registered phone account" | Enable phone account in Android Settings (see Phone Account) |
| Calls fail with "Invalid callerId" | Server webhook needs phone number validation — check handle_browser_voice in class-twp-webhooks.php |
| App hangs on login | Check server is reachable: curl https://your-site.com/wp-json/twilio-mobile/v1/auth/login |
| No incoming calls | Ensure FCM is configured (google-services.json) and phone account is enabled |