FaceDown
Showcase repository for FaceDown: A sensor-based focus timer. Demonstrating Jetpack Compose, Material 3 Expressive, Room, and raw SensorManager integration.
Install / Use
/learn @arekbauer/FaceDownREADME
<p align="center"> <a href="https://www.youtube.com/watch?v=iY3FBMTA15A&t=327s"> <img src="https://img.youtube.com/vi/iY3FBMTA15A/0.jpg" width="600px" alt="FaceDown featured on HowToMen" /> </a> <br> <i>Featured in <b>HowToMen's</b> "Top 15 Best Android Apps" - "Honestly a really great app to help you stay more productive."</i> </p>Note: This repository is a technical showcase for a private productivity application called "FaceDown". It contains sanitized snippets of the core engineering challenges to demonstrate architectural and technical capabilities.
FaceDown is a minimal timer that enforces focus by requiring the user to place their phone face down on a table. If the phone is picked up or flipped over, the timer pauses, ensuring a distraction-free environment.
<p align="center"> <img src="assets/home3.png" width="30%" alt="Timer Face Up" /> <img src="assets/stats.png" width="30%" alt="Weekly Stats" /> <img src="assets/settings2.png" width="30%" alt="Dial Setup" /> </p>Engineering Highlights
1. Robust Foreground Service & State Bridging
The core timer loop runs within an Android Foreground Service (using the new SPECIAL_USE type) to guarantee precision execution even when the device is dozing.
- In-Memory State Bus: To communicate seamlessly with the UI, a singleton
TimerRepositorymanages aMutableStateFlowthat bridges the Service and any observing ViewModels. - Grace Period Logic: The service reacts to a combined flow of the timer ticker and physical orientation, automatically triggering a 10-second recovery window when the user picks up the phone.
- WakeLock Management: Carefully manages
PARTIAL_WAKE_LOCKacross all pause, failure, and completion states to prevent battery drain.
2. Sensor Fusion & Coroutines
The core physical mechanic relies on the device's accelerometer. I implemented a clean Flow-based repository that bridges the legacy SensorManager callback API with modern Kotlin Coroutines.
- Debouncing:
distinctUntilChanged()ensures minor vibrations don't trigger state updates. - Battery Efficiency: The listener is registered as a cold
callbackFlow, automatically unregistering when no longer collected (e.g., when the service stops).
(See SensorLogic.kt for the full implementation)
3. Glance AppWidgets & Lifecycle Sync
To provide quick access to focus stats without opening the app, I built home screen widgets using Jetpack Glance.
- Reactive Syncing: Uses a
ProcessLifecycleOwnerobserver to trigger a customSyncWidgetsUseCaseonly when the app enters the foreground, ensuring widget data (daily minutes, current streak) stays perfectly in sync with the Room database without unnecessary background processing.
4. Custom Compose Drawing
Instead of relying on heavy charting libraries for the weekly statistics, I built custom Jetpack Compose Canvas components.
- Performance: Draws the entire chart geometry dynamically in a single pass based on available screen width.
- Animation: Uses independent
Animatablestates for each bar to create a staggered "wave" entrance effect.
(See WeeklyStatsChart.kt for the drawing logic)
5. Comprehensive Testing Infrastructure
The app's complex state rules are heavily tested across unit and integration layers:
- Unit Testing: Pure business logic (streak calculation, UseCases) and ViewModels are verified using
JUnit 4,MockK, andkotlinx-coroutines-test. - UI & E2E Testing: Composable screens and critical flows are tested using
ComposeTestRuleandHiltTestRunneron Android instrumentation. - Database Mocking: Utilizes a custom
MockSessionInjectorfor programmatic local database seeding during development and structured DB tests.
Architecture
The app follows the recommended Clean Architecture guidelines and MVVM pattern, strictly separating concerns:
com.arekb.facedown
├── data # Data Layer
│ ├── database # Room Entities, DAOs, & Mocking Injector
│ ├── sensor # Accelerometer callbackFlow wrappers
│ ├── timer # ForegroundService & Repository State Bridges
│ └── widget # DataStore Preferences for Widgets
├── domain # Domain Layer
│ ├── model # Sealed Interfaces for State (TimerState)
│ └── usecase # Coordinated logic (e.g., SyncWidgetsUseCase)
├── ui # Presentation Layer (Jetpack Compose)
│ ├── home # Timer UI & State Sub-components
│ ├── stats # Statistics, History Paging, Canvas Charts
│ ├── settings # User Preferences
│ └── widget # Glance AppWidget Implementations
└── di # Hilt Dependency Injection Modules
Tech Stack
- Language: Kotlin
- UI: Jetpack Compose (Material 3 Expressive)
- Widgets: Jetpack Glance AppWidgets
- Architecture: MVVM, Clean Architecture
- DI: Hilt (Dagger) with KSP
- Async: Coroutines & Flow (
StateFlow/callbackFlow) - Local Data: Room Database (with Paging 3), Jetpack DataStore
- Testing: JUnit 4, MockK, Compose UI Tests, Hilt Android Testing
- Hardware/System: Android
SensorManager(Accelerometer), Foreground Services, WakeLocks,NotificationManager(DnD)
App Showcase
<details> <summary>View Full Screenshot Gallery</summary> <br> <p align="center"> <img src="assets/home0.png" width="30%" /> <img src="assets/history.png" width="30%" /> <img src="assets/timer_options.png" width="30%" /> <img src="assets/warning.png" width="30%" /> <img src="assets/stats_dark.png" width="30%" /> <img src="assets/settings_dark.png" width="30%" /> </p> </details>Credits & Contributiors
- UI/UX inspired by the Tomato App.
- Babrr: New icon design
