An Android automation framework that compares ride prices across multiple ride-hailing apps in real-time and books the best option — all without touching a single API.
Built entirely with Accessibility Services and on-device OCR, Automata interacts with ride-hailing apps the same way a human would: by reading the screen, tapping buttons, and making decisions.
Disclaimer: This is an educational project built to explore Android accessibility services, on-device OCR, and cross-app automation. It is not intended for commercial use or distribution. Use responsibly and at your own risk.
- Opens ride-hailing apps (PickMe, Uber) one by one
- Enters your destination and navigates to the pricing screen
- Reads prices using OCR (ML Kit text recognition on screenshots)
- Compares prices (or ETAs) and picks the best option
- Books the winning ride automatically
- Shows results via a floating overlay and notification
All of this happens in about 30–60 seconds with no manual input after you tap "Go".
Automata doesn't use any ride-hailing APIs. Instead, it uses two Android system features:
Android's AccessibilityService gives apps the ability to observe and interact with the UI of other apps. Automata uses this to:
- Read UI element text and properties
- Tap buttons, enter text, scroll, and navigate
- Detect which screen is currently displayed
Some apps (especially Flutter-based ones) don't expose useful text through accessibility nodes. For these, Automata takes a screenshot and runs ML Kit Text Recognition locally on the device to read prices, labels, and buttons directly from the screen pixels.
Every automation task is broken down into a sequence of steps. Each step has:
- A wait condition — polls the accessibility tree until the expected screen appears
- An action — what to do once the condition is met (tap, read, enter text)
- Timeout and retry logic — handles slow loading, unexpected popups, and errors
Force-close apps
→ Launch PickMe → Enter destination → Read price
→ Launch Uber → Enter destination → Read price
→ Compare prices → Pick winner
→ Re-open winner → Book the ride
┌─────────────────────────────────────┐
│ UI (Jetpack Compose) │
│ Dashboard · Task Config · Settings│
└──────────────┬──────────────────────┘
│
┌───────▼────────┐
│ MainViewModel │
└───┬─────────┬───┘
│ │
┌───────▼───┐ ┌──▼──────────────┐
│ Engine │ │ Data Layer │
│ + Scripts │ │ Room · SharedPrefs│
└───────┬───┘ └─────────────────┘
│
┌───────▼──────────────┐
│ Accessibility Service │
│ + ML Kit OCR │
└───────────────────────┘
| Component | What it does |
|---|---|
| AutomationEngine | Manages the step sequencer, app lifecycle, and accessibility integration |
| StepSequencer | Executes steps sequentially with polling, timeouts, and retries |
| ActionExecutor | Low-level gestures — tap, type, scroll, press home/back |
| ScreenReader | Takes screenshots and runs OCR to extract text and positions |
| NodeFinder | Traverses the accessibility tree to find UI elements |
| RideOrchestrator | Composes app-specific scripts into a full comparison workflow |
| PickMeScript / UberScript | App-specific step sequences for navigating each ride app |
User taps "Go"
→ ViewModel validates config (destination, network, apps installed)
→ RideOrchestrator builds step list from TaskConfig
→ AutomationEngine runs steps via StepSequencer
→ Each step polls accessibility tree → executes action → returns result
→ Collected data (prices, ETAs) flows through StepContext
→ Orchestrator compares and decides winner
→ Conditional booking steps run for the winning app only
→ Result shown via overlay + notification
| Category | Technology |
|---|---|
| Language | Kotlin |
| UI | Jetpack Compose + Material 3 |
| Database | Room (SQLite) |
| OCR | Google ML Kit Text Recognition (on-device) |
| System | Android Accessibility Service |
| Architecture | MVVM + StateFlow |
| Build | Gradle with KSP |
| Min SDK | 26 (Android 8.0) |
| Target SDK | 36 |
com.jayathu.automata/
├── engine/ # Core automation engine
│ ├── AutomationEngine # Main orchestrator
│ ├── StepSequencer # Step execution with polling and retries
│ ├── AutomationStep # Step definition (wait + action)
│ ├── ActionExecutor # Gestures (tap, type, scroll)
│ ├── ScreenReader # Screenshot capture + OCR
│ ├── NodeFinder # Accessibility tree traversal
│ └── ErrorMapper # User-friendly error messages
│
├── scripts/ # App-specific automation scripts
│ ├── PickMeScript # PickMe navigation and price reading
│ ├── UberScript # Uber navigation and price reading
│ └── RideOrchestrator # Multi-app comparison workflow
│
├── service/ # Android system services
│ └── AutomataAccessibilityService
│
├── notification/ # Overlays and notifications
│ ├── AutomationNotificationManager # Progress and result notifications
│ ├── ComparisonOverlay # Floating price comparison card
│ └── AutomationControlOverlay # Floating timer + stop button
│
├── data/ # Persistence
│ ├── db/ # Room database, DAOs, entities
│ ├── model/ # TaskConfig, SavedLocation, RideApp, DecisionMode
│ ├── repository/ # Data access layer
│ └── PreferencesManager # SharedPreferences wrapper
│
└── ui/ # Presentation
├── screens/ # Compose screens (Dashboard, TaskConfig, Settings)
├── navigation/ # Navigation graph
├── theme/ # Colors, typography
└── MainViewModel # Central state management
- Multi-app price comparison — reads prices from PickMe and Uber
- Two decision modes — Cheapest (lowest price) or Fastest (shortest ETA)
- Automatic booking — books the winning ride without manual intervention
- Floating control overlay — always-visible timer and stop button during automation
- Price comparison popup — overlay showing both prices and savings
- OCR price sanitization — handles comma-as-period misreads, dropped decimals
- Conditional booking — only the winning app's booking steps execute
- Saved tasks — store destination/pickup/preferences for one-tap automation
- Settings — configurable overlay duration, notification sound, auto-close apps, debug mode
Building this project involved solving several non-trivial problems:
- Flutter apps and accessibility — PickMe is built with Flutter, which has limited accessibility node support. OCR-based coordinate tapping was the only reliable way to interact with it.
- OCR accuracy — Commas read as periods (
7,733→7.733), missing decimals, and digit misreads (l→1,O→0) required a multi-layered sanitization pipeline. - Cross-app state management — Passing data (prices, winner decision) across steps that span two different apps, with the automation engine as the only shared context.
- Overlay permissions —
TYPE_ACCESSIBILITY_OVERLAYlets you draw over other apps without requestingSYSTEM_ALERT_WINDOW, but touch handling (touchable vs pass-through) needs careful flag management. - Timing and reliability — Apps load at different speeds, popups appear unpredictably, and UI layouts change between updates. The polling + retry + timeout pattern handles this gracefully.
- Go to the Releases page
- Download the latest
automata-v*.apkfile - Transfer the APK to your Android device (or download directly on the device)
- Open the APK file on your device
- If prompted, allow installation from unknown sources: Settings → Apps → Special access → Install unknown apps → enable for your browser or file manager
- Tap Install
Installing apps from outside the Play Store triggers several Android security features. Here's how to get past them:
Google Play Protect may flag the APK as unverified and block installation.
Fix:
- Open the Google Play Store app
- Tap your profile icon (top right) → Play Protect
- Tap the gear icon (top right)
- Turn off Scan apps with Play Protect
- Install the APK, then re-enable Play Protect afterwards
Samsung devices have an additional security feature called Auto Blocker that blocks sideloaded apps.
Fix:
- Go to Settings → Security and privacy → Auto Blocker
- Turn it off
- Install the APK, then re-enable it afterwards
Android restricts sideloaded apps from using accessibility services by default. You need to allow restricted settings first.
Fix:
- Go to Settings → Apps
- Find and tap Automata
- Tap the three-dot menu (top right corner)
- Tap Allow restricted settings
- You may need to confirm with your PIN, pattern, or fingerprint
- Now go to Settings → Accessibility → Automata and turn it on
These restrictions exist because accessibility services have deep access to your device. They apply to all sideloaded apps, not just Automata.
If you prefer to build it yourself:
git clone https://github.com/jayathuc/Automata.git
cd Automata
./gradlew assembleDebugThe APK will be at app/build/outputs/apk/debug/app-debug.apk. Transfer it to your device and install.
Requirements: Android Studio, JDK 11+, Android SDK 26+
- Android 8.0 or higher
- Both PickMe and Uber must be installed and logged in on the device
- Open Automata after installing
- Enable the accessibility service — the app will prompt you. Go to Settings → Accessibility → Automata and turn it on
- Grant notification permission when prompted (Android 13+)
- Create a task — tap the + button and enter:
- Destination address (as you'd type it in the ride app)
- Pickup address (optional — uses current location if left empty)
- Which apps to compare (PickMe, Uber, or both)
- Decision mode: Cheapest or Fastest
- Tap "Go" on your saved task
- Switch to your home screen — Automata will take over from here
- Watch it work — a floating pill shows elapsed time and a stop button
- See results — a popup overlay shows the price comparison and which app was booked
Open Settings from the top bar to configure:
| Setting | What it does |
|---|---|
| Auto-enable location | Turns on GPS automatically if it's off when automation starts |
| Auto-bypass "someone else" prompt | Automatically taps "No, it's for me" if Uber asks |
| Show comparison overlay | Floating card showing both prices after comparison |
| Overlay duration | How long the comparison popup stays visible (5–15 seconds) |
| Notification sound | Play sound when results are ready |
| Auto-close apps | Close ride apps after booking is complete |
| Default decision mode | Cheapest or Fastest |
| Preferred app | Tiebreaker when both price and ETA are the same |
- Tap the STOP button on the floating pill (visible on any screen), or
- Open Automata and tap the stop button on the dashboard
This project is built purely for educational and research purposes — to explore what's possible with Android accessibility services, on-device ML, and cross-app automation.
- It is not affiliated with Uber, PickMe, or any ride-hailing company
- It may violate the Terms of Service of the apps it interacts with
- Do not use this for any commercial purpose
- Use at your own risk — automated interactions may result in unintended bookings
The code is shared to demonstrate Android development techniques. If you're a developer interested in accessibility services, OCR, or automation — this is a reference implementation, not a product.
This project is provided as-is for educational purposes. See LICENSE for details.