| Name | Size | Uploaded | Xet hash |
|---|---|---|---|
| ui-modules | 22 items | ||
| AGENTS.md | 1.81 kB xet | 998ace6a | |
| Architecture&ImplementationGuide.md | 32.4 kB xet | 22aeb351 | |
| CHANGELOG.md | 853 Bytes xet | c45c13e8 | |
| DEADME.md | 17.6 kB xet | c219c35f | |
| FileArchitecture.md | 29.1 kB xet | 45a9b585 | |
| LayersOfTheUI.md | 18.7 kB xet | 8dbf0d65 | |
| MODERNIZATION_SPEC.md | 10.8 kB xet | ef697f19 | |
| README.md | 55.5 kB xet | cf7e7335 | |
| Structure.md | 42.4 kB xet | 2ad5786a | |
| UIArchitecture.md | 21.1 kB xet | 21f6ed51 | |
| UI_UX.md | 0 Bytes xet | 00000000 | |
| breakdown.md | 5.18 kB xet | b606b0e9 |
Air Mouse Pro Android App โ Complete Documentation
Professionalโgrade smartphone remote control using motion sensors, custom gestures, proximity lock, and multiple connectivity protocols.
University of Tehran โ Embedded Systems Laboratory
๐ Table of Contents
- Air Mouse Pro Android App โ Complete Documentation
- ๐ Table of Contents
- ๐ฏ Overview & Motivation
- ๐๏ธ System Architecture
- ๐ฑ Detailed Feature Breakdown
- 3.1 Sensor Fusion & Orientation
- 3.2 Gesture Detection Engine
- 3.3 Calibration Procedures
- 3.4 Connectivity Modules
- 3.5 Proximity Lock/Unlock
- 3.6 Custom Gesture Recognition (TFLite)
- 3.7 Voice Commands (PocketSphinx)
- 3.8 Edge Gestures (Accessibility Service)
- 3.9 Touchpad Mode
- 3.10 Bluetooth HID Mouse
- 3.11 USB HID / Serial
- 3.12 USB Serial (CDC ACM / FTDI / CP210x / PL2303)
- ๐จ User Interface (Jetpack Compose)
- ๐พ Data Persistence
- ๐ Background Services & Foreground Notifications
- ๐ Permission Handling & Security
- โก Performance Optimizations
- ๐งช Testing Strategy
- ๐ ๏ธ Troubleshooting & Common Issues
- ๐ฆ Development & Contribution
- ๐ License
๐ฏ Overview & Motivation
The Air Mouse Pro Android app transforms any Android phone into a versatile, lowโlatency remote control for desktop computers. Unlike conventional remote apps that rely only on touch input, Air Mouse Pro leverages the phone's builtโin inertial sensors (gyroscope, accelerometer, magnetometer) to detect natural hand movements, allowing users to control the cursor simply by rotating or moving the phone in space.
๐ Core Use Cases
| Use Case | Description | Ideal For |
|---|---|---|
| Presentation Control | Navigate slides, highlight content, control media | Teachers, speakers, business presenters |
| Media Center Control | Play/pause, volume, navigation from couch | Home theatre, streaming, music playback |
| Accessibility | Mouse control without physical mouse | Users with mobility impairments |
| Gaming | Motion-controlled cursor for casual games | Gamers, entertainment |
| Remote Work | Control computer from phone during meetings | Remote workers, hybrid work |
๐ก Key Motivations
- Handsโfree interaction โ ideal for presentations, media centres, or when the keyboard/mouse is out of reach.
- Lowโcost alternative โ no need for specialised hardware; uses existing smartphone sensors.
- Customisability โ users can train their own gestures, adjust sensitivity, and choose from multiple connectivity protocols.
- Privacy โ all voice recognition is offline (PocketSphinx), and personal data stays onโdevice.
- CrossโPlatform โ works with Windows, Linux, and macOS servers.
๐๏ธ System Architecture
2.1 Layered Clean Architecture
The app is structured into four distinct layers, each with a clear responsibility and dependency direction (inward).
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ PRESENTATION LAYER โ
โ โข Jetpack Compose UI Screens โ
โ โข ViewModels (state holders with StateFlow) โ
โ โข Navigation (NavHost, Destinations) โ
โ โข Themes, animations, reusable composables โ
โ โข Material 3 Design System โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ DOMAIN LAYER โ
โ โข Entities (pure Kotlin data classes) โ
โ โข Use Cases (interactors with business logic) โ
โ โข Repository interfaces โ
โ โข Business logic (no Android dependencies) โ
โ โข Domain models (CalibrationData, SensorData, etc.) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ DATA LAYER โ
โ โข Repository implementations โ
โ โข Data sources (WebSocket, Bluetooth, USB) โ
โ โข Local databases (Room, DataStore) โ
โ โข DTOs (network models) โ
โ โข DAOs (Data Access Objects) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ INFRASTRUCTURE LAYER โ
โ โข SensorService (foreground service) โ
โ โข CalibrationHelper (bias removal, 6โpoint accel) โ
โ โข GestureDetector (thresholdโbased) โ
โ โข MadgwickAHRS (sensor fusion) โ
โ โข PocketSphinx recognizer โ
โ โข TFLite interpreter for custom gestures โ
โ โข Bluetooth HID, USB HID/serial โ
โ โข OkHttp, WebSocketManager โ
โ โข NetworkQualityMonitor (WiFi, Cellular, Ethernet) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Dependency Rule: The Domain layer knows nothing about the Data or Infrastructure layers. Dependencies point inward, making the core business logic independent of frameworks.
2.2 Dependency Injection with Hilt
All dependencies are injected at compile time using Dagger Hilt. This eliminates boilerplate and makes testing easier.
Key Hilt Components
| Component | Purpose |
|---|---|
@HiltAndroidApp |
Application class annotation |
@AndroidEntryPoint |
Activities, Fragments, Services, ViewModels |
@Module / @InstallIn |
Module definitions for specific components |
@Provides / @Singleton |
Provider functions with singleton scope |
Hilt Modules
| Module | Provides |
|---|---|
AppModule |
Context, Application, PreferencesManager |
NetworkModule |
OkHttpClient, WebSocketManager, TcpClient |
SensorModule |
SensorManager, SensorService |
DatabaseModule |
Room Database, DAOs |
RepositoryModule |
Repository implementations |
UseCaseModule |
Use case implementations |
ServiceModule |
Foreground services |
Example โ Providing the SensorManager
@Module
@InstallIn(SingletonComponent::class)
object SensorModule {
@Provides
@Singleton
fun provideSensorManager(@ApplicationContext context: Context): SensorManager =
context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
}
2.3 Data Flow Diagram
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ USER INTERACTION โ
โ โโโโโโโโโโโโโโ โโโโโโโโโโโโโโ โโโโโโโโโโโโโโ โโโโโโโโโโโโโโ โ
โ โ Phone โ โ Voice โ โ Touchpad โ โ Edge โ โ
โ โ Motion โ โ Commands โ โ Gestures โ โ Gestures โ โ
โ โโโโโโโโฌโโโโโโ โโโโโโโโฌโโโโโโ โโโโโโโโฌโโโโโโ โโโโโโโโฌโโโโโโ โ
โ โ โ โ โ โ
โ โผ โผ โผ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Sensor Processing Pipeline โ โ
โ โ โข Madgwick AHRS (Sensor Fusion) โ โ
โ โ โข Gesture Detection (Threshold-based) โ โ
โ โ โข TFLite Inference (Custom Gestures) โ โ
โ โ โข PocketSphinx (Voice Recognition) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Command Generation โ โ
โ โ โข Move (dx, dy) โข Click (left/right) โ โ
โ โ โข Scroll (delta) โข Gesture (name, confidence) โ โ
โ โ โข Proximity (near/far) โข Control (pause/resume) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Connection Manager โ โ
โ โ โข WebSocket (primary) โ โ
โ โ โข TCP (fallback) โ โ
โ โ โข UDP Discovery โ โ
โ โ โข Bluetooth HID / USB HID โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ DESKTOP SERVER โ
โ โข Receives JSON messages โ
โ โข Converts to mouse/keyboard events โ
โ โข Executes system actions (lock, volume, media) โ
โ โข Sends acknowledgements โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ฑ Detailed Feature Breakdown
3.1 Sensor Fusion & Orientation
Why fusion?
Raw gyroscope data drifts over time; accelerometer alone cannot distinguish gravity from linear acceleration; magnetometer is noisy. The Madgwick AHRS algorithm fuses all three to produce a stable, driftโfree quaternion, from which we extract Euler angles (roll, pitch, yaw).
Implementation โ MadgwickAHRS.kt
class MadgwickAHRS(private var beta: Float = 0.1f) {
private val quaternion = FloatArray(4).apply { this[0] = 1f }
fun updateGyro(gx: Float, gy: Float, gz: Float, dt: Float) { /* ... */ }
fun updateAccel(ax: Float, ay: Float, az: Float, dt: Float) { /* ... */ }
fun updateMag(mx: Float, my: Float, mz: Float, dt: Float) { /* ... */ }
fun getRollDegrees(): Float = Math.toDegrees(getRollRad().toDouble()).toFloat()
fun getPitchDegrees(): Float = Math.toDegrees(getPitchRad().toDouble()).toFloat()
fun getYawDegrees(): Float = Math.toDegrees(getYawRad().toDouble()).toFloat()
}
Algorithm Parameters:
- Beta = 0.1 (gain, controls filter convergence speed)
- Update rate = 50โ100 Hz (depends on sensor delay)
- Update steps:
updateGyro()โupdateAccel()โupdateMag() - Output: quaternion (w, x, y, z) โ converted to roll (X rotation) and yaw (Z rotation)
Cursor Mapping:
| Sensor | Mapping | Sensitivity Range |
|---|---|---|
| Roll (X rotation) | Vertical movement | 0.2 โ 2.0 multiplier |
| Yaw (Z rotation) | Horizontal movement | 0.2 โ 2.0 multiplier |
| Pitch (Y rotation) | Not used (disabled) | - |
Deadband: Ignore movements below 0.3ยฐ to prevent jitter.
Motion Smoothing:
- EMA (Exponential Moving Average) filter with configurable alpha (0.3 default).
- Velocityโbased smoothing: faster movements have less smoothing (perceptual motion blur).
3.2 Gesture Detection Engine
Threshold-Based Gestures
| Gesture | Sensor | Detection Logic | Default Threshold |
|---|---|---|---|
| Click | Gyroscope Y | Angular speed > threshold AND not within doubleโclick window | 5 rad/s |
| Doubleโclick | Gyroscope Y | Two clicks within double_click_interval (300โฏms) |
300โฏms |
| Rightโclick | Accelerometer roll | Tilt angle (roll) > threshold AND hold for > duration | 15ยฐ, 200โฏms |
| Scroll up | Accelerometer Y | Linear acceleration (positive) > threshold | 8โฏm/sยฒ |
| Scroll down | Accelerometer Y | Linear acceleration (negative) < -threshold | 8โฏm/sยฒ |
Cooldowns & Debounce
| Parameter | Value | Purpose |
|---|---|---|
| Click cooldown | 100โฏms | Prevent repeated triggers |
| Scroll debounce | 500โฏms | Prevent return movement from triggering reverse scroll |
| Gesture cooldown | 300โฏms | Prevent multiple gesture triggers |
Gesture Accuracy
Click Detection Accuracy: ~95% (after calibration)
Double-click Accuracy: ~88%
Right-click Accuracy: ~85%
Scroll Detection Accuracy: ~90%
3.3 Calibration Procedures
Gyroscope Bias Removal
Purpose: Eliminate zero-rate offset (static bias) that causes cursor drift even when the phone is stationary.
Process:
- User places phone on a flat, stationary surface.
- App collects 500 gyroscope samples at 50โฏHz (10 seconds).
- Computes average per axis โ this is the bias.
- Subtracts bias from all future readings.
Formula:
biasX = mean(gyroX_samples)
biasY = mean(gyroY_samples)
biasZ = mean(gyroZ_samples)
correctedGyroX = rawGyroX - biasX
correctedGyroY = rawGyroY - biasY
correctedGyroZ = rawGyroZ - biasZ
Quality Check: If the bias is too large (>1.0 rad/s), the calibration fails and prompts the user to try again.
Accelerometer 6โPoint Calibration
Purpose: Correct for offset and scale errors in accelerometer readings caused by manufacturing tolerances and temperature changes.
Process:
- User holds phone in six orientations (+X, -X, +Y, -Y, +Z, -Z).
- For each axis, measures the raw values when gravity aligns perfectly.
- Solves:
raw = scale * ideal + offsetfor each axis. - Resulting
offsetandscaleare stored.
Orientations:
| Position | Orientation | Ideal Gravity (g) |
|---|---|---|
| 1 | +X | (1, 0, 0) |
| 2 | -X | (-1, 0, 0) |
| 3 | +Y | (0, 1, 0) |
| 4 | -Y | (0, -1, 0) |
| 5 | +Z | (0, 0, 1) |
| 6 | -Z | (0, 0, -1) |
Formula:
For each axis:
offset = (raw_max + raw_min) / 2
scale = 2 / (raw_max - raw_min) // Assuming ยฑ1g range
Magnetometer HardโIron Calibration
Purpose: Correct for fixed magnetic offsets (hard-iron interference) from permanent magnets in the device or environment.
Process:
- User waves phone in a figureโ8 pattern for 10 seconds.
- App records min and max values for each axis.
- Offset =
(min + max)/2. - Scale =
(max - min)/2(optional, for soft-iron correction).
Formula:
offsetX = (minX + maxX) / 2
offsetY = (minY + maxY) / 2
offsetZ = (minZ + maxZ) / 2
correctedMagX = rawMagX - offsetX
correctedMagY = rawMagY - offsetY
correctedMagZ = rawMagZ - offsetZ
3.4 Connectivity Modules
Protocol Comparison
| Module | Protocol | Latency | Reliability | Use Case |
|---|---|---|---|---|
WebSocketManager |
WebSocket | ~10-20ms | High | Primary, real-time control |
DataSender |
TCP | ~5-15ms | Very High | Critical commands (clicks) |
TcpClient |
TCP | ~5-15ms | High | Touchpad mode |
BluetoothMouseService |
Bluetooth HID | ~20-40ms | Medium | Local connection |
UsbHidService |
USB HID | <5ms | Very High | Wired connection |
UsbSerialService |
USB CDC | <5ms | Very High | Debugging, custom apps |
WebSocket Features
| Feature | Implementation |
|---|---|
| Protocol | ws:// / wss:// |
| Port | 8081 (configurable) |
| Reconnection | Exponential backoff (1s, 2s, 4s, โฆ up to 30s) |
| Ping/Pong | 30-second interval |
| Message Queue | Buffer of 100 messages when disconnected |
| Authentication | JWT token via query parameter |
Reconnection Logic
private fun scheduleReconnect() {
if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
return
}
val delay = (reconnectAttempts + 1) * 2000L
reconnectAttempts++
// Schedule reconnect with delay
handler.postDelayed({ connect() }, delay)
}
3.5 Proximity Lock/Unlock
Principle โ RSSI (Received Signal Strength Indicator) of a paired Bluetooth device decreases with distance. Using a pathโloss model:
distance = 10^((txPower - rssi) / (10 * n))
txPower= calibrated RSSI at 1 metre (e.g., -59โฏdBm).n= environmental factor (2.5 for indoor office).
Flow
ProximityAwareServiceruns in foreground, reading RSSI every second.- Distance is fed into a hysteresis comparator:
- If already "near", switch to "far" only when distance >
far_threshold. - If already "far", switch to "near" only when distance <
near_threshold.
- On state change, the app sends a
proximitymessage to the server. - Server triggers screen lock/unlock via OS commands.
Calibration
| Step | Action |
|---|---|
| 1 | Place phone exactly 1m from computer |
| 2 | Tap "Calibrate" |
| 3 | App records RSSI at 1m โ adjusts txPower |
| 4 | Optional: Repeat at 2m, 3m, 4m, 5m for better accuracy |
Configuration
| Parameter | Default | Range |
|---|---|---|
| Near Threshold | 1.5m | 0.5m โ 3.0m |
| Far Threshold | 3.0m | 1.0m โ 5.0m |
| Tx Power | -59 dBm | -80 โ -30 dBm |
| Scan Interval | 1000ms | 500 โ 5000ms |
3.6 Custom Gesture Recognition (TFLite)
Pipeline
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ GESTURE RECORDING โ
โ โข User taps "Record" โ
โ โข App collects gyro + accel data at 50Hz (10 seconds) โ
โ โข Data saved as CSV โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ TRAINING (PC) โ
โ โข Python script loads CSV โ
โ โข Trains 1D-CNN / LSTM model โ
โ โข Exports gesture_model.tflite and gesture_labels.json โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ INFERENCE (Android) โ
โ โข GestureInferenceService loads model โ
โ โข Buffers 30 consecutive sensor samples โ
โ โข Runs inference every 500ms (with cooldown) โ
โ โข Sends gesture message when confidence > 0.7 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Model Architecture
Input: (30 ร 6)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Conv1D(64, kernel_size=3, activation=relu) โ
โ MaxPool1D(2) โ
โ Conv1D(128, kernel_size=3, activation=relu) โ
โ MaxPool1D(2) โ
โ Flatten โ
โ Dense(128, activation=relu) โ
โ Dropout(0.5) โ
โ Dense(num_classes, activation=softmax) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Supported Gestures (Pre-trained)
| Gesture | Confidence Threshold | Action |
|---|---|---|
| ThumbsUp | 0.7 | Play/Pause |
| ThumbsDown | 0.7 | Stop |
| SwipeLeft | 0.7 | Previous Track |
| SwipeRight | 0.7 | Next Track |
| CircleCW | 0.7 | Volume Up |
| CircleCCW | 0.7 | Volume Down |
| PinchIn | 0.7 | Zoom Out |
| PinchOut | 0.7 | Zoom In |
3.7 Voice Commands (PocketSphinx)
Why offline? Privacy and no internet dependency.
Setup
| Asset | Purpose |
|---|---|
en-us-ptm |
Acoustic model |
cmudict-en-us.dict |
Pronunciation dictionary |
commands.gram |
Command set grammar |
Commands
| Command | Network Message | Description |
|---|---|---|
| "click" | {"type":"click","button":"left"} |
Left click |
| "double click" | {"type":"doubleclick"} |
Double click |
| "right click" | {"type":"click","button":"right"} |
Right click |
| "scroll up" | {"type":"scroll","delta":3} |
Scroll up |
| "scroll down" | {"type":"scroll","delta":-3} |
Scroll down |
| "stop listening" | {"type":"control","command":"voice_stop"} |
Stop voice recognition |
Workflow
VoiceCommandServicestarts listening on button press.- Recogniser triggers on partial results (endโpoint detection).
- On full result, maps command to action.
- Sends appropriate network message.
- Optionally plays haptic feedback for recognition.
3.8 Edge Gestures (Accessibility Service)
Concept: Longโpress volume keys (up/down) trigger actions even when the app is in the background or screen off.
Implementation
Accessibility Service
โโโ onKeyEvent()
โ โโโ Volume Up Press
โ โ โโโ Start timer
โ โ โโโ If held > 1s โ Send control message
โ โโโ Volume Down Press
โ โโโ Start timer
โ โโโ If held > 1s โ Send control message
โโโ onAccessibilityEvent()
Configuration
| Action | Trigger | Result |
|---|---|---|
| Volume Up (short) | < 1s | Default volume behaviour |
| Volume Up (long) | > 1s | Configurable action (click, scroll, etc.) |
| Volume Down (short) | < 1s | Default volume behaviour |
| Volume Down (long) | > 1s | Configurable action |
Permissions Required
SYSTEM_ALERT_WINDOWโ Required for overlay detection.- Accessibility Service โ Must be enabled in system settings.
3.9 Touchpad Mode
When enabled, the screen becomes a fullโsurface touchpad.
Touch Gestures
| Gesture | Action | Network Message |
|---|---|---|
| Singleโfinger drag | Move cursor | {"type":"move","dx":x,"dy":y} |
| Tap | Left click | {"type":"click","button":"left"} |
| Twoโfinger drag | Scroll | {"type":"scroll","delta":d} |
| Twoโfinger tap | Right click | {"type":"click","button":"right"} |
| Threeโfinger tap | Back/Forward | Configurable |
Implementation
pointerInput(Unit) {
detectDragGestures(
onDragStart = { /* Start tracking */ },
onDrag = { change, dragAmount ->
// Convert touch drag to cursor movement
val dx = dragAmount.x * sensitivity
val dy = dragAmount.y * sensitivity
connectionManager.sendMove(dx, dy)
},
onDragEnd = { /* End tracking */ }
)
}
3.10 Bluetooth HID Mouse
How it works (Android 8+):
- App uses
BluetoothHidDevicesystem service. - Registers a HID application with a mouse report descriptor.
- When a computer pairs and connects, the app can send HID reports (
sendMouseReport). - The computer sees the phone as a standard Bluetooth mouse.
Report Format
Report Descriptor (8 bytes):
โโโโโโโโโโโโฌโโโโโโโโโโโฌโโโโโโโโโโโฌโโโโโโโโโโโฌโโโโโโโโโโโฌโโโโโโโโโโโฌโโโโโโโโโโโฌโโโโโโโโโโโ
โ Buttons โ X (low)โ X (high)โ Y (low)โ Y (high)โ Wheel โ Padding โ Padding โ
โ (1 byte) โ (1 byte)โ (1 byte)โ (1 byte)โ (1 byte)โ (1 byte)โ (1 byte)โ (1 byte)โ
โโโโโโโโโโโโดโโโโโโโโโโโดโโโโโโโโโโโดโโโโโโโโโโโดโโโโโโโโโโโดโโโโโโโโโโโดโโโโโโโโโโโดโโโโโโโโโโโ
Buttons:
- Bit 0: Left click
- Bit 1: Right click
- Bit 2: Middle click
Limitations
| Issue | Workaround |
|---|---|
Requires system permission BLUETOOTH_CONNECT |
Request at runtime |
| May not work on all devices (OEM implementation) | Fallback to USB HID mode |
| Android 8+ required | - |
3.11 USB HID / Serial
USB HID (UsbHidService)
- Phone appears as a USB mouse when connected via OTG.
- Sends mouse reports over the USB interrupt endpoint.
- Works on any OS (Linux, Windows, macOS) without special drivers.
Report Descriptor (HID 1.1)
val HID_REPORT_DESCRIPTOR = byteArrayOf(
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x02, // Usage (Mouse)
0xA1, 0x01, // Collection (Application)
// ... Full descriptor in code
)
USB Serial (UsbSerialService)
- Emulates a CDC serial port.
- Sends JSON commands over bulk endpoints.
- Useful for debugging or for custom applications.
Supported Chipsets:
- FTDI (FT232R, FT230X, FT231X)
- Silabs CP210x
- Prolific PL2303
- Arduino (CDC ACM)
- CH340/CH341
Baud Rates: 300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600
3.12 USB Serial (CDC ACM / FTDI / CP210x / PL2303)
This module provides communication over USB serial using the usb-serial-for-android library.
Features
| Feature | Status |
|---|---|
| Automatic device detection | โ |
| Baud rate configuration | โ |
| Data bits (5, 6, 7, 8) | โ |
| Stop bits (1, 1.5, 2) | โ |
| Parity (none, odd, even, mark, space) | โ |
| Flow control (RTS/CTS) | โ |
| JSON message parsing | โ |
| Raw text transmission | โ |
Message Format
// Move command
{"type":"move","payload":{"dx":12.5,"dy":-3.2}}
// Click command
{"type":"click","payload":{"button":"left"}}
// Hello command
{"type":"hello","payload":{"name":"Android Phone","version":"3.0"}}
๐จ User Interface (Jetpack Compose)
4.1 Navigation Graph
The app uses Compose Navigation with a sealed class Destinations. The NavHost defines all screens and transitions.
Destination Structure
sealed class Destinations(
val route: String,
val title: String,
val icon: ImageVector
) {
object Home : Destinations("home", "Home", Icons.Filled.Home)
object Statistics : Destinations("statistics", "Stats", Icons.Filled.BarChart)
object Settings : Destinations("settings", "Settings", Icons.Filled.Settings)
// ... 22+ destinations
}
Bottom Navigation
| Tab | Destination | Icon | Description |
|---|---|---|---|
| Home | Home | ๐ | Main dashboard |
| Stats | Statistics | ๐ | Usage statistics |
| Settings | Settings | โ๏ธ | App configuration |
| Help | Help | โ | Help & support |
Navigation Graph
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ NavHost โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ
โ โ Home โ โ Stats โ โ Settings โ โ Help โ โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ
โ โ โ โ โ โ
โ โผ โผ โผ โผ โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ
โ โCalibrate โ โ About โ โ Profiles โ โOnboardingโ โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ
โ โ โ โ โ โ
โ โผ โผ โผ โผ โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ
โ โ Touchpad โ โ Gestures โ โ Voice โ โ Themes โ โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
4.2 Theming & Dynamic Colors
Theme System
| Theme | Description | Use Case |
|---|---|---|
| System | Follows system theme | Default |
| Dark | Dark material theme | General use |
| Light | Light material theme | Bright environments |
| Pure Black | AMOLED-friendly | Battery saving |
| Ocean | Blue tones | Calm experience |
| Sunset | Orange tones | Warm feel |
| Forest | Green tones | Natural feel |
| Purple Haze | Purple tones | Mystical feel |
| Cherry | Pink tones | Soft feel |
| Neon | Cyan tones | Cyberpunk feel |
| Lavender | Light purple | Gentle feel |
| Mint | Light green | Fresh feel |
| Peach | Light orange | Warm feel |
| Sky | Light blue | Bright feel |
Accent Colors (14+)
| Color | Name | Code |
|---|---|---|
| Orange | 0xFFFF5722 | Default |
| Blue | 0xFF2196F3 | Calm |
| Green | 0xFF4CAF50 | Natural |
| Purple | 0xFF9C27B0 | Bold |
| Pink | 0xFFE91E63 | Soft |
| Red | 0xFFF44336 | Alert |
| Teal | 0xFF009688 | Cool |
| Indigo | 0xFF3F51B5 | Deep |
| Cyan | 0xFF00BCD4 | Cyber |
| Amber | 0xFFFFC107 | Warm |
| Rose | 0xFFE91E63 | Elegant |
| Lime | 0xFFCDDC39 | Fresh |
| Brown | 0xFF795548 | Earthy |
| Grey | 0xFF607D8B | Professional |
Dynamic Colors (Material You)
- Android 12+ support
- Extracts colors from wallpaper
- Applies to primary, secondary, tertiary, surface, and background
4.3 Reusable Components
| Component | Purpose | Used In |
|---|---|---|
ConnectionCard |
IP/port input, connect/disconnect | Home, Settings |
SensorDataCard |
Live yaw/pitch display | Home, Calibration |
GestureStatsCard |
Click, scroll, right-click, double-click counts | Home, Statistics |
CalibrationCard |
Progress bars, attempt counter | Calibration |
GestureChart |
Bar chart using MPAndroidChart | Statistics, Analytics |
StatusChip |
Colour-coded status indicator | Home, Devices |
QualityIndicator |
Signal quality display | Home, Connection |
ThemeSelector |
Theme picker grid | Settings, Themes |
GlassCard |
Glassmorphism card | About, Settings |
๐พ Data Persistence
5.1 Room Database
Entities
| Entity | Table Name | Columns | Purpose |
|---|---|---|---|
CalibrationEntity |
calibration | gyro_bias_x, gyro_bias_y, gyro_bias_z, accel_offset_x, ... | Calibration data |
GyroBias |
gyro_bias | id, offset_x, offset_y, offset_z, timestamp | Gyroscope bias |
AccelCalibration |
accel_calibration | id, offset_x, offset_y, offset_z, scale_x, scale_y, scale_z, timestamp | Accelerometer calibration |
MagCalibration |
mag_calibration | id, offset_x, offset_y, offset_z, scale_x, scale_y, scale_z, timestamp | Magnetometer calibration |
CustomGestureTemplate |
gesture_templates | id, name, data, created_at | Custom gesture templates |
Profile |
profiles | id, name, sensitivity, thresholds, theme, ai_settings | User profiles |
DAOs
@Dao
interface CalibrationDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertCalibration(calibration: CalibrationEntity)
@Query("SELECT * FROM calibration WHERE id = 'default'")
suspend fun getCalibration(): CalibrationEntity?
@Query("DELETE FROM calibration")
suspend fun deleteAll()
@Query("SELECT EXISTS(SELECT 1 FROM calibration WHERE id = 'default')")
suspend fun exists(): Boolean
}
5.2 DataStore (Preferences)
Preference Categories
| Category | Keys | Purpose |
|---|---|---|
| Connection | last_ip, last_port, protocol, auth_token | Connection settings |
| Calibration | gyro_bias_x, gyro_bias_y, gyro_bias_z, calibration_status | Calibration data |
| Mouse | sensitivity, smoothing, acceleration, invert_x, invert_y | Mouse settings |
| Gesture | click_threshold, scroll_threshold, double_click_interval | Gesture detection |
| AI | ai_smoothing, ai_blend_factor, predictive_movement | AI settings |
| Theme | theme, accent_color, dynamic_colors | Appearance |
| Proximity | proximity_enabled, near_threshold, far_threshold | Proximity lock |
| Voice | wake_word, wake_word_enabled, voice_haptic_feedback | Voice commands |
| Touchpad | touchpad_sensitivity, natural_scrolling, two_finger_scroll | Touchpad mode |
Observation
val preferencesFlow: Flow<Preferences> = dataStore.data
.catch { exception ->
if (exception is IOException) emit(emptyPreferences())
else throw exception
}
๐ Background Services & Foreground Notifications
Service Architecture
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ FOREGROUND SERVICES โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โ
โ โ SensorService โ โ VoiceCommand โ โ Proximity โ โ
โ โ (dataSync) โ โ (microphone) โ โ (location) โ โ
โ โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โ
โ โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โ
โ โ Bluetooth โ โ USB โ โ Gesture โ โ
โ โ HID Mouse โ โ HID/Serial โ โ Inference โ โ
โ โ (connectedDevice)โ โ (dataSync) โ โ (dataSync) โ โ
โ โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Service Details
| Service | Foreground Type | Purpose | Notification ID |
|---|---|---|---|
SensorService |
dataSync |
Sensor fusion, movement generation | 1001 |
GestureInferenceService |
dataSync |
TFLite custom gesture inference | 1002 |
VoiceCommandService |
microphone |
Offline speech recognition | 1003 |
ProximityAwareService |
location |
Bluetooth RSSI monitoring | 1004 |
BluetoothMouseService |
connectedDevice |
Bluetooth HID mouse emulation | 1005 |
UsbHidService |
dataSync |
USB HID mouse emulation | 1006 |
UsbSerialService |
dataSync |
USB serial communication | 1007 |
OrientationMonitorService |
dataSync |
Orientation monitoring | 1008 |
EdgeGestureService |
dataSync |
Edge gesture detection | 1009 |
DebugOverlayService |
dataSync |
Debug overlay (development) | 1010 |
Notification Channels
| Channel ID | Name | Importance | Sound | Vibration |
|---|---|---|---|---|
connection_channel |
Connection Status | LOW | No | No |
gesture_channel |
Gesture Detection | DEFAULT | Yes | Yes |
proximity_channel |
Proximity Lock | HIGH | Yes | Yes |
calibration_channel |
Calibration | DEFAULT | No | No |
voice_channel |
Voice Commands | LOW | No | No |
update_channel |
App Updates | DEFAULT | Yes | Yes |
sensor_channel |
Sensor Service | LOW | No | No |
bluetooth_channel |
Bluetooth HID | DEFAULT | No | No |
usb_channel |
USB Service | DEFAULT | No | No |
๐ Permission Handling & Security
Required Permissions
| Permission | Category | Purpose |
|---|---|---|
INTERNET |
Normal | Network communication |
ACCESS_NETWORK_STATE |
Normal | Network connectivity check |
CAMERA |
Dangerous | QR scanning |
VIBRATE |
Normal | Haptic feedback |
BLUETOOTH |
Dangerous | Bluetooth operations |
BLUETOOTH_SCAN |
Dangerous | Bluetooth discovery |
BLUETOOTH_CONNECT |
Dangerous | Bluetooth connection |
BLUETOOTH_ADVERTISE |
Dangerous | Bluetooth advertising |
ACCESS_FINE_LOCATION |
Dangerous | Bluetooth scanning (Android 10+) |
RECORD_AUDIO |
Dangerous | Voice commands |
BODY_SENSORS |
Dangerous | Accelerometer, gyroscope, magnetometer |
FOREGROUND_SERVICE |
Normal | Background services |
SYSTEM_ALERT_WINDOW |
Special | Debug overlay |
WRITE_EXTERNAL_STORAGE |
Dangerous | Exporting gesture datasets |
POST_NOTIFICATIONS |
Dangerous | Notification display (Android 13+) |
Permission Flow
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ PERMISSION REQUEST โ
โ โ
โ 1. Check if permission is granted โ
โ 2. If not, show rationale (optional) โ
โ 3. Request permission using ActivityResultContracts โ
โ 4. Handle result โ
โ 5. If denied, show explanation and retry โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Security Features
| Feature | Implementation | Status |
|---|---|---|
| JWT Authentication | Token in WebSocket URL | โ |
| TLS/SSL Support | WSS (WebSocket Secure) | โ |
| Data Encryption | AES (optional) | ๐ Planned |
| No Cloud Storage | All data stays on-device | โ |
| Offline Processing | All AI/ML on-device | โ |
| Permission Rationales | Shown before requesting | โ |
| Secure Communication | Local network only | โ |
โก Performance Optimizations
1. Sensor Sampling Optimization
- Dynamic rate adjustment:
- Stationary:
SENSOR_DELAY_NORMAL(200ms) - Movement:
SENSOR_DELAY_GAME(20ms)
- Stationary:
2. Network Optimization
- Event coalescing: Movement events batched at 60Hz
- Message queue: Buffer 100 messages when disconnected
- JSON pooling: Reuse JSON objects (planned)
3. UI Optimization
- Lazy loading:
LazyColumnfor large lists - Image caching: Coil for image loading
- Recomposition:
@Stableannotations,rememberkeys
4. Memory Optimization
- Bitmap caching:
LruCachefor bitmaps - Object pooling: Reuse sensor data objects
- Log trimming: Limit to 500 entries
5. Battery Optimization
| Feature | Description |
|---|---|
| Sensor rate adjustment | Lower rate when stationary |
| Background work | Coroutines on Dispatchers.IO |
| Wake locks | Released when screen off |
| Network | WebSocket ping interval (30s) |
| TFLite | Inference on Dispatchers.Default |
6. Performance Metrics
| Metric | Value |
|---|---|
| Sensor latency | ~5-10ms |
| Network latency (WiFi) | ~10-20ms |
| Network latency (Cellular) | ~30-50ms |
| UI frame rate | 60 FPS (target) |
| TFLite inference | ~10ms per batch |
| App startup time | <3 seconds |
| Memory usage | ~150-200 MB |
| Battery impact | ~5-10% per hour |
๐งช Testing Strategy
9.1 Unit Tests (JUnit + MockK + Kotlin Coroutines Test)
| Test Class | Purpose |
|---|---|
ValidationUtilsTest |
IP parsing, endpoint extraction |
PreferencesManagerTest |
Save/load, increment counters |
GestureDetectorTest |
Threshold detection, double-click window |
CalibrationHelperTest |
Bias calculation, 6-point formula |
MadgwickAHRSUnitTest |
Quaternion integration, Euler conversion |
SensorDataTest |
Sensor data processing |
ConnectionManagerTest |
Connection states, reconnection logic |
9.2 Instrumentation Tests (Espresso + Compose UI Test)
| Test Class | Purpose |
|---|---|
HomeScreenTest |
Connection card, start/stop button |
CalibrationScreenTest |
Step navigation, progress updates |
GestureStudioActivityTest |
Gesture recording, dataset export |
SettingsScreenTest |
Theme switching, sensitivity slider |
TouchpadScreenTest |
Touch gestures, scroll behaviour |
9.3 UI Tests (Compose UI Test)
@Test
fun testHomeScreenConnectionCard() {
composeTestRule.setContent {
HomeScreen()
}
composeTestRule
.onNodeWithTag("connection_card")
.assertIsDisplayed()
composeTestRule
.onNodeWithTag("connect_button")
.performClick()
composeTestRule
.onNodeWithText("Connected")
.assertIsDisplayed()
}
Continuous Integration
- GitHub Actions: Runs all tests on every push
- Code Coverage: Monitored by Codecov
- Static Analysis: Ktlint, Detekt
๐ ๏ธ Troubleshooting & Common Issues
Connection Issues
| Symptom | Likely Cause | Solution |
|---|---|---|
| Cannot connect | WiโFi mismatch, firewall | Verify same network; disable firewall temporarily |
| Connection drops | WiFi interference, signal weak | Move closer to router, use 5GHz |
| Slow response | High latency, packet loss | Reduce WiFi congestion, use Ethernet |
| Authentication failed | Invalid token | Regenerate token, check JWT expiry |
Sensor Issues
| Symptom | Likely Cause | Solution |
|---|---|---|
| Cursor jumps erratically | Gyroscope bias not calibrated | Run gyroscope calibration on a flat surface |
| No movement | Sensors not active, permissions denied | Check sensor permissions, restart service |
| Inverted movement | Invert axes enabled | Disable invert X/Y in Settings |
| Jittery cursor | High sensitivity, low deadband | Reduce sensitivity, increase deadband |
Gesture Issues
| Symptom | Likely Cause | Solution |
|---|---|---|
| Gestures not recognised | Threshold too high | Lower click/scroll thresholds in Settings |
| False positive gestures | Threshold too low | Increase click/scroll thresholds |
| Double-click not working | Interval too short/long | Adjust double-click interval (200-400ms) |
| Right-click not working | Tilt angle too high | Reduce right-click tilt threshold |
Voice Commands
| Symptom | Likely Cause | Solution |
|---|---|---|
| No response | Microphone permission denied | Grant permission |
| Wrong commands recognised | Background noise, poor mic quality | Move to quieter environment |
| Commands not registered | PocketSphinx assets missing | Reinstall app, check assets folder |
Bluetooth Issues
| Symptom | Likely Cause | Solution |
|---|---|---|
| Bluetooth mouse not pairing | Android version / OEM limitation | Use USB HID mode instead |
| HID device not discovered | Bluetooth not enabled, permissions | Enable Bluetooth, grant permissions |
| Pairing fails | Incompatible HID profile | Try USB HID or WebSocket mode |
Proximity Issues
| Symptom | Likely Cause | Solution |
|---|---|---|
| Never locks | Bluetooth not paired or RSSI too low | Pair phone with computer; calibrate distance |
| Unlocks immediately | Threshold too high | Adjust near/far thresholds |
| Frequent lock/unlock | Hysteresis too small | Increase gap between near/far thresholds |
App Crashes
| Symptom | Likely Cause | Solution |
|---|---|---|
| Crashes on opening | Missing TFLite model or labels | Ensure gesture_model.tflite and gesture_labels.json are in assets/ |
| Crashes on calibration | Sensor permission denied | Grant BODY_SENSORS permission |
| Out of memory | Memory leak | Restart app, reduce memory usage |
๐ฆ Development & Contribution
Building from Source
# Clone repository
git clone https://github.com/yourusername/airmouse-android.git
cd airmouse-android
# Build debug APK
./gradlew assembleDebug
# Build release APK
./gradlew assembleRelease
# Run tests
./gradlew test
# Run instrumentation tests
./gradlew connectedAndroidTest
Adding a New Screen
- Create screen file:
presentation/ui/mypackage/MyScreen.kt - Define composable:
@Composable fun MyScreen(...) - Create ViewModel:
MyViewModelextendingViewModel - Add destination: In
Destinations.ktobject MyScreen : Destinations("my_screen", "My Screen", Icons.Filled.Star) - Add navigation: In
AirMouseNavHost.ktcomposable(Destinations.MyScreen.route) { MyScreen(navigationActions = navigationActions) } - Add to bottom nav: In
AirMouseBottomBar.kt(if needed)
Code Style
# Format code
./gradlew ktlintFormat
# Check code style
./gradlew ktlintCheck
Pull Request Process
- Fork the repository.
- Create a feature branch:
git checkout -b feature/amazing-feature - Write tests for new functionality.
- Ensure all tests pass:
./gradlew test connectedAndroidTest - Commit changes:
git commit -m 'Add amazing feature' - Push to branch:
git push origin feature/amazing-feature - Open a Pull Request with clear description.
๐ License
MIT License โ Copyright (c) 2025 University of Tehran, Embedded Systems Laboratory.
See LICENSE for full text.
Built with Kotlin, Jetpack Compose, and Hilt โ turning your phone into a magic wand.
- Total size
- 5.54 GB
- Files
- 51,854
- Last updated
- Sep 12
- Pre-warmed CDN
- US EU US EU