14 KiB
14 KiB
Flash Call OTP System — Permissions Guide
Table of Contents
- Android Caller App Permissions
- Flutter Receiver App Permissions
- Server-Side Security
- iOS-Specific Notes
- Permission Request Flow
1. Android Caller App Permissions
CALL_PHONE
- Permission String:
android.permission.CALL_PHONE - Protection Level: Dangerous
- Required Since: API 1
- Purpose: Required to programmatically place phone calls without user interaction. The app uses
ACTION_CALLwithUri.parse("tel:$phone")to automatically dial the target number and hang up after 2.5 seconds. - Why Not ACTION_DIAL:
ACTION_DIALonly opens the dialer and requires the user to tap the call button. Flash call verification must be completely automatic — no user interaction. - Android Version Notes:
- API 26-27 (Android 8.0-8.1): Standard dangerous permission, requested at runtime
- API 28+ (Android 9+): Works as standard dangerous permission
- If denied: The app cannot make flash calls. The service will report "failed" for all call tasks.
READ_PHONE_STATE
- Permission String:
android.permission.READ_PHONE_STATE - Protection Level: Dangerous
- Required Since: API 1
- Purpose: Required to read telephony state information including call state, network type, and SIM information. Used to detect when a call is active so the app knows when to hang up.
- Android Version Notes:
- API 26-28 (Android 8-9): Standard dangerous permission
- API 29+ (Android 10+): More restricted; the app only needs this for call state detection
- If denied: Call state detection may not work, but the 2.5-second timer still hangs up reliably
SEND_SMS
- Permission String:
android.permission.SEND_SMS - Protection Level: Dangerous
- Required Since: API 1
- Purpose: Required to send SMS messages containing OTP codes to iOS users. When the backend has an SMS delivery task, the Caller App sends the OTP via SMS using
SmsManager. - Why Needed: iOS devices cannot receive flash calls for automatic OTP extraction. The system falls back to SMS delivery, and the Caller Android device acts as the SMS sender.
- Android Version Notes:
- API 26-30 (Android 8-11): Uses
SmsManager.getDefault() - API 31+ (Android 12+): Uses
context.getSystemService(SmsManager::class.java)for per-subscription SMS handling - If denied: SMS tasks will fail. Flash calls for Android devices still work.
- API 26-30 (Android 8-11): Uses
FOREGROUND_SERVICE
- Permission String:
android.permission.FOREGROUND_SERVICE - Protection Level: Normal
- Required Since: API 26 (Android 8.0)
- Purpose: Required to run a foreground service that continuously polls the backend for pending tasks. The service must be a foreground service (not a background service) to avoid being killed by the system.
- Android Version Notes:
- API 26+ (Android 8.0+): Mandatory for any long-running service
- Without this: The service would be killed within a few minutes by the system
FOREGROUND_SERVICE_PHONE_CALL
- Permission String:
android.permission.FOREGROUND_SERVICE_PHONE_CALL - Protection Level: Normal
- Required Since: API 34 (Android 14)
- Purpose: Specifies the foreground service type as "phone call". Required on Android 14+ for services that place phone calls.
- Android Version Notes:
- API 34+ (Android 14): Must be declared in manifest AND specified in
startForeground()call - API 26-33: Not required, but harmless to declare
- Without this on Android 14: The foreground service will crash with SecurityException
- API 34+ (Android 14): Must be declared in manifest AND specified in
RECEIVE_BOOT_COMPLETED
- Permission String:
android.permission.RECEIVE_BOOT_COMPLETED - Protection Level: Normal
- Required Since: API 1
- Purpose: Allows the app to receive the
BOOT_COMPLETEDbroadcast and automatically restart the CallerService after the device reboots. Without this, the user would have to manually open the app and start the service after every restart. - Android Version Notes:
- All versions: Works as a normal permission (auto-granted)
- Note: Some OEMs (Xiaomi, Huawei, Oppo) restrict auto-start by default. Users must enable "Auto-start" in security settings.
REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
- Permission String:
android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS - Protection Level: Normal
- Required Since: API 23 (Android 6.0)
- Purpose: Prompts the user to add the app to the battery optimization whitelist. This is critical because Android's Doze mode and app standby can kill the foreground service, preventing the device from receiving OTP tasks.
- How It Works: The app sends an
ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONSintent with the app's package URI. The system shows a dialog asking the user to allow the app to ignore battery optimization. - Android Version Notes:
- API 23+ (Android 6.0+): Works as normal permission with system dialog
- Some OEMs: May require additional steps (e.g., Xiaomi: Security → Autostart; Samsung: Device Care → Battery → App Power Management)
- Without this: The service may be killed after 10-30 minutes of screen-off time
INTERNET
- Permission String:
android.permission.INTERNET - Protection Level: Normal
- Required Since: API 1
- Purpose: Required for all network communication — polling the backend API and reporting call/SMS results.
- Note: Automatically granted on all Android versions.
2. Flutter Receiver App Permissions
READ_CALL_LOG (Android Only)
- Permission String:
android.permission.READ_CALL_LOG - Protection Level: Dangerous
- Required Since: API 16
- Purpose: Required to read the device's call log and detect incoming missed calls from the flash call. The app queries the call log every 1.5 seconds looking for missed calls since the OTP was requested.
- How OTP Extraction Works: When a flash call comes in, it appears as a missed call in the call log. The app reads the caller's phone number (e.g., +962791234829) and extracts the last 4 digits (4829) as the OTP code.
- Android Version Notes:
- API 16-22 (Android 4.1-5.1): Granted at install time
- API 23+ (Android 6.0+): Must be requested at runtime
- If denied: Automatic OTP detection will not work. User must manually enter the OTP code via the fallback input.
READ_PHONE_STATE (Android Only)
- Permission String:
android.permission.READ_PHONE_STATE - Protection Level: Dangerous
- Required Since: API 1
- Purpose: Companion permission for READ_CALL_LOG. Some Android versions require both permissions to properly access call log data.
- Android Version Notes:
- API 29+ (Android 10+): More restricted; may need to request both READ_CALL_LOG and READ_PHONE_STATE together
- If denied: Call log access may fail on some devices
No Extra Permissions on iOS
- iOS does not allow reading call logs or SMS messages programmatically
- SMS AutoFill works automatically through
AutofillHints.oneTimeCodewithout any special permissions - The only requirement is that the SMS follows Apple's format with
@domain #codeat the end
3. Server-Side Security
API Key Authentication
- APP_KEY: Used by the Flutter Receiver App for
request-otpandverify-otpendpoints - DEVICE_KEY: Used by the Android Caller App for
pending-call,pending-sms,call-done,sms-done, andregister-deviceendpoints - Implementation: Uses
hash_equals()for timing-safe comparison to prevent timing attacks - Key Generation: Use
openssl rand -hex 32to generate cryptographically secure keys
Rate Limiting
- Per-Phone Limit: Max 3 OTP requests per phone number per 10-minute window
- Per-IP Limit: Max 30 requests per IP per minute for
request-otp - General API: Max 60 requests per IP per minute across all endpoints
- Implementation: Redis-based with atomic increment operations and TTL-based expiration
Input Validation
- Phone Format: E.164 format validation (e.g., +9627XXXXXXXX) — regex:
/^\+[1-9]\d{6,14}$/ - OTP Format: Exactly 4 digits — regex:
/^\d{4}$/ - Device ID: Length between 5-50 characters
- SQL Injection: All queries use PDO prepared statements
- XSS: JSON-only responses (no HTML rendering)
Data Protection
- OTP codes stored in Redis with 120-second TTL (auto-expire)
- OTP codes in MySQL are for audit purposes only
- Failed verification attempts tracked in Redis (max 5 attempts)
- Request logging masks sensitive fields (app_key, otp, password)
- API logs auto-cleaned after 30 days
4. iOS-Specific Notes
Why Flash Call Doesn't Work on iOS
Apple restricts access to the call log through CoreTelephony framework. There is no public API to:
- Read incoming/outgoing call history
- Get caller ID from recent calls programmatically
- Detect missed calls in real-time
This is a deliberate privacy decision by Apple. The workaround is to use SMS delivery with AutoFill.
SMS AutoFill Mechanism
iOS can automatically suggest OTP codes from SMS messages in the keyboard suggestion bar. This requires:
- Correct SMS format: The message must include the domain and code at the end:
رمز التحقق في Intaleq هو 4829 لا تشاركه مع أحد. @otp.intaleqapp.com #4829 - Associated Domains: The app must have
otp.intaleqapp.comas an associated domain in its entitlements (for the#codeto be recognized) - Text field configuration: The OTP input must have
autofillHints: [AutofillHints.oneTimeCode]andkeyboardType: TextInputType.number
iOS AutoFill Requirements
| Requirement | Details |
|---|---|
| SMS format | Must end with @domain #code |
| Associated domain | Configured in Xcode → Signing & Capabilities |
| Text field | AutofillHints.oneTimeCode + number keyboard |
| Real device | AutoFill does not work on simulators |
| SMS sender | Must come from a phone number (not alphanumeric) |
5. Permission Request Flow
Android Caller App — First Launch
App Launched
│
▼
┌─────────────────────────┐
│ PermissionHelper │
│ .requestAllPermissions()│
└──────────┬──────────────┘
│
▼
┌──────────────┐
│ CALL_PHONE │──── Denied ──→ Show rationale dialog
│ │ │
│ │──── Granted ──→ Continue
└──────┬───────┘
│
▼
┌──────────────┐
│READ_PHONE_STATE│── Denied ──→ Show rationale dialog
│ │ │
│ │── Granted ──→ Continue
└──────┬───────┘
│
▼
┌──────────────┐
│ SEND_SMS │──── Denied ──→ Show rationale dialog
│ │ │
│ │──── Granted ──→ Continue
└──────┬───────┘
│
▼
┌──────────────────────────────────┐
│ Battery Optimization Exemption │
│ (Intent to system settings) │
│ │
│ User selects "Don't optimize" │
└──────────────────────────────────┘
│
▼
Ready for Registration
Flutter Receiver App — OTP Flow
User taps "Send OTP"
│
▼
┌─────────────────────────┐
│ Platform check │
└──────┬──────────────────┘
│
├──── Android ──→ Check READ_CALL_LOG permission
│ │
│ ├── Granted ──→ Start call log polling
│ │
│ ├── Denied ──→ Show rationale:
│ │ "نحتاج صلاحية قراءة سجل المكالمات
│ │ للتحقق التلقائي دون الحاجة لإدخال أي كود"
│ │
│ ├── Permanently Denied ──→ Redirect to Settings
│ │ + Show manual OTP input fallback
│ │
│ └── Still Denied ──→ Show manual OTP input
│
└──── iOS ──→ Skip call log entirely
│
Show manual 4-digit input
with AutofillHints.oneTimeCode
│
iOS keyboard shows SMS code
suggestion automatically
Arabic Permission Explanations
| Permission | Arabic Explanation |
|---|---|
| CALL_PHONE | "صلاحية إجراء المكالمات مطلوبة للاتصال التلقائي بأرقام التحقق" |
| READ_PHONE_STATE | "صلاحية قراءة حالة الهاتف مطلوبة لإدارة المكالمات" |
| SEND_SMS | "صلاحية إرسال الرسائل مطلوبة للتحقق من مستخدمي iOS" |
| READ_CALL_LOG | "نحتاج صلاحية قراءة سجل المكالمات للتحقق التلقائي دون الحاجة لإدخال أي كود" |
| Battery Optimization | "يجب تعطيل تحسين البطارية لضمان عمل الخدمة بشكل مستمر" |
Appendix: Permission Summary by Component
| Permission | Caller App | Receiver App | Type | Min API |
|---|---|---|---|---|
| CALL_PHONE | ✅ | ❌ | Dangerous | 1 |
| READ_PHONE_STATE | ✅ | ✅ | Dangerous | 1 |
| SEND_SMS | ✅ | ❌ | Dangerous | 1 |
| READ_CALL_LOG | ❌ | ✅ | Dangerous | 16 |
| FOREGROUND_SERVICE | ✅ | ❌ | Normal | 26 |
| FOREGROUND_SERVICE_PHONE_CALL | ✅ | ❌ | Normal | 34 |
| RECEIVE_BOOT_COMPLETED | ✅ | ❌ | Normal | 1 |
| REQUEST_IGNORE_BATTERY_OPTIMIZATIONS | ✅ | ❌ | Normal | 23 |
| INTERNET | ✅ | ✅ | Normal | 1 |