Android app that protects sensitive PIN input during USSD sessions from shoulder-surfing.
Interactive USSD sessions (M-Pesa *334#, Safaricom *444#, etc.) display PIN prompts in plain text through the system's MMIDialogActivity. Anyone looking at your screen can see your PIN as you type it.
USSD Shield uses an AccessibilityService to monitor the system USSD dialog. When a PIN prompt is detected (e.g. "Enter M-PESA PIN"), a masked overlay appears on top of the system dialog. The user enters their PIN into the overlay (shown as dots), and the app fills the system dialog's input field and submits automatically.
Non-sensitive prompts (menus, confirmations) pass through to the system dialog normally.
User dials *334# → System MMIDialogActivity opens
→ AccessibilityService detects the USSD dialog
→ Traverses accessibility node tree, extracts prompt text
→ HeuristicClassifier determines if prompt is sensitive
→ If sensitive: masked PIN overlay appears
→ User enters PIN (shown as •••)
→ Service fills system EditText + clicks Send
→ Session continues / completes
- PIN masking — overlay with immediate dot masking (no brief character reveal)
- Heuristic classification — regex-based detection of PIN/password/OTP prompts in English, Swahili, and French
- Session-scoped — only intercepts USSD sessions started by the app, never interferes with other apps
- Fail-secure — aggressive fallback regex masks anything requesting digit/code input
- Termination detection — detects "transaction complete", "balance is", etc. to end sessions
- FLAG_SECURE — prevents screenshots and screen recording of the app
| Permission | Purpose |
|---|---|
CALL_PHONE |
Initiate USSD requests |
READ_PHONE_STATE |
Access telephony for USSD |
SYSTEM_ALERT_WINDOW |
Overlay permission (fallback) |
BIND_ACCESSIBILITY_SERVICE |
Monitor system USSD dialog |
- Build and install the app
- Grant phone permissions when prompted
- Enable "USSD Shield" in Settings → Accessibility
- Grant overlay permission if prompted
- Dial a USSD code through the app
./gradlew assembleDebug
./gradlew installDebugRequires Android SDK with compileSdk 36 and minSdk 26.
app/src/main/java/com/example/ussdshield/
├── MainActivity.kt # Permission checks, navigation
├── accessibility/
│ ├── UssdAccessibilityService.kt # Monitors com.android.phone, classifies prompts
│ ├── UssdOverlayManager.kt # WindowManager overlay for masked PIN input
│ └── UssdInterceptBus.kt # LiveData event bus (Service ↔ ViewModel)
├── ml/
│ └── HeuristicClassifier.kt # Regex-based prompt classification
└── ui/
├── dialer/DialerFragment.kt # USSD code entry keypad
├── session/SessionFragment.kt # Session state display
└── ussd/UssdSessionViewModel.kt # USSD state machine, TelecomManager fallback