Search every screenshot you've ever taken using natural language. Fully offline. No cloud. No data leaves your device.
Recall automatically indexes all screenshots on your device using on-device OCR and vector embeddings, then lets you find them by describing what you remember, not by scrolling endlessly through a grid.
| Home | Search | Detail | Settings |
|---|---|---|---|
| coming soon | coming soon | coming soon | coming soon |
- Hybrid search — combines semantic similarity (HNSW vector index) with full-text search (FTS4) for the best of both worlds
- On-device OCR — ML Kit Text Recognition extracts text from every screenshot automatically
- Vector embeddings —
bge-small-en-v1.5ONNX model generates 384-dimensional sentence embeddings for semantic understanding - Fully offline & private — no network calls for search or indexing; the AI model is downloaded once over Wi-Fi and then never needs the internet again
- Timeline view — home gallery groups screenshots by Today / Yesterday / This Week / This Month
- Filter chips — quickly narrow to All, Recent, By App, or Summarized screenshots
- Detail screen — view extracted text, edit it, copy it, share the screenshot, or delete it
- Dark / Light / System theme — full Material 3 dynamic colour support
- Background indexing —
WorkManagerworkers run OCR and embedding generation in the background without blocking the UI - Memory-aware — automatically selects quantized (34 MB) or full FP32 (133 MB) model based on device RAM
| Layer | Technology |
|---|---|
| Language | Kotlin 1.9.22 |
| UI | Jetpack Compose + Material 3 |
| Architecture | Clean Architecture (Data / Domain / Presentation) |
| DI | Hilt 2.50 |
| Database | Room 2.6.1 (FTS4, version 5) |
| Background work | WorkManager 2.9.0 |
| OCR | ML Kit Text Recognition 16.0.0 |
| AI Embeddings | ONNX Runtime 1.16.0 (bge-small-en-v1.5) |
| Image loading | Coil 2.6.0 |
| Preferences | Jetpack DataStore |
| Networking | OkHttp 4.12.0 (model download only) |
| Navigation | Navigation Compose 2.7.6 |
| Min SDK | 26 (Android 8.0 Oreo) |
| Target SDK | 34 (Android 14) |
app/src/main/java/com/recall/app/
├── data/
│ ├── di/ Hilt modules (Database, DataStore, OCR, Repository)
│ ├── local/ Room database, DAOs, entities, migrations, UserPreferences
│ ├── nlp/ ONNX embedding generator, vector index, tokenizer, model selector
│ ├── ocr/ ML Kit OCR processor
│ ├── repository/ Repository implementations
│ ├── service/ ScreenshotContentObserver (real-time detection)
│ └── worker/ WorkManager workers (OCR, scan, model download)
├── domain/
│ ├── model/ Pure Kotlin domain models (Screenshot, ProcessingState, …)
│ ├── repository/ Repository interfaces
│ └── usecase/ Business logic (SearchScreenshotsUseCase, GetAllScreenshotsUseCase, …)
└── presentation/
└── ui/
├── components/ Shared composables
├── detail/ Detail screen + ViewModel
├── home/ Home screen + ViewModel + timeline utilities
├── navigation/ NavGraph
├── permissions/ Onboarding / permission screen
├── search/ Search screen
├── settings/ Settings screen + ViewModel
└── theme/ RecallTheme, colours, typography
New screenshot saved
│
▼
ScreenshotContentObserver (real-time, 1 s debounce per URI)
│
▼
ScreenshotProcessingWorker (enqueued immediately, KEEP policy)
│
├─► MlKitOcrProcessor → extract text
└─► OnnxEmbeddingGenerator → 384-dim float vector
│
▼
Room DB (ScreenshotEntity) + FTS4 index + VectorIndexOptimized
On cold launch: ScanExistingWorker → BackgroundOcrWorker (catches up missed files)
Every 6 hours: BackgroundOcrWorker (two-pass: OCR-pending → embedding-pending)
User query
│
▼
SearchScreenshotsUseCase
├─ async ──► EmbeddingGenerator.generate(query)
│ └─► VectorIndexOptimized.search() (HNSW, threshold 0.3, timeout 1.5 s)
└─ async ──► ScreenshotDao.searchFts() (FTS4 + JOIN)
│
▼
Merge: AI results first, then FTS-only, deduplicated by ID
- Android Studio Hedgehog (2023.1.1) or newer
- JDK 17 — the project pins Java 17 via
.java-version - Android device or emulator running API 26+
git clone https://github.com/mux032/Recall.git
cd Recall
./gradlew assembleDebugInstall on a connected device/emulator:
./gradlew installDebug- Grant storage / media images permission when prompted.
- The app will begin scanning existing screenshots automatically.
- Go to Settings → AI Model and tap Download to get the embedding model (Wi-Fi + battery-not-low required). Without it, search falls back to keyword-only mode.
| Command | Output |
|---|---|
./gradlew assembleDebug |
Debug APK (battery constraint skipped) |
./gradlew assembleRelease |
Release APK (requires signing config) |
./gradlew clean build |
Full clean build |
| Branch | Purpose |
|---|---|
main |
Stable, always-releasable |
fix/<issue-number>-<short-description> |
Bug fixes |
feat/<issue-number>-<short-description> |
New features |
Example: feat/107-processing-status-banner
- Find or open a GitHub Issue for the work. Every branch should map to an issue.
- Branch off
main:git checkout main && git pull git checkout -b fix/123-your-description - Make your changes. Follow the existing layer boundaries:
- Data concerns (DB, workers, network) →
data/ - Business rules →
domain/ - UI / ViewModel →
presentation/ui/
- Data concerns (DB, workers, network) →
- Add or update tests (see Testing below).
- Open a PR targeting
mainand link the issue withCloses #NNN.
Room uses destructive migration (fallbackToDestructiveMigration) during development. Before any production release:
- Add a proper
Migrationobject inDatabaseMigrations.kt - Bump
versioninRecallDatabase.kt - Update the version comment in the
@Databaseannotation
Current DB version: 5
- Create a
HiltWorkersubclass indata/worker/. - Tag it with
RecallApplication.INDEXING_TAGif it performs indexing — this makes it visible to the pause/cancel controls. - Register the
HiltWorkerFactorybinding is already global viaRecallApplication; no extra setup needed.
- Add a route object to
Screensealed class inNavGraph.kt. - Add a
composable(Screen.YourScreen.route)block inRecallNavGraph. - Create
YourScreen.ktandYourViewModel.ktunderpresentation/ui/yourscreen/.
Run all unit tests:
./gradlew test
# or for the debug variant specifically:
./gradlew :app:testDebugUnitTestKey test files and what they cover:
| Test file | Coverage |
|---|---|
SearchScreenshotsUseCaseTest |
Hybrid search merge logic, AI timeout fallback |
VectorIndexTest / VectorIndexOptimizedTest |
In-memory HNSW search, LRU eviction |
WordPieceTokenizerTest |
Trie-based tokenizer, edge cases |
OnnxEmbeddingGeneratorTest |
ONNX session lifecycle, null-model fallback |
BackgroundOcrWorkerConstantsTest |
Two-pass slot allocation, retry limits |
BackgroundOcrWorkerPass2Test |
Embedding-only retry path |
ModelDownloadWorkerTest |
SHA-256 verification, OkHttp streaming (MockWebServer) |
HomeViewModelTest |
Pagination, reactive DB count refresh |
ModelSelectorTest |
RAM-based model selection |
DeviceProfilerTest |
Memory class detection |
./gradlew connectedAndroidTestKey instrumented tests:
| Test file | Coverage |
|---|---|
ScreenshotDaoTest |
Room DAO operations, FTS JOIN correctness |
DetailScreenTest |
Compose UI — share, delete, OCR edit |
DarkModeThemeTest |
Theme switching |
ExtractedTextSectionTest |
OCR text section composable |
- Unit test →
app/src/test/java/com/recall/app/
Use JUnit 4 + Mockito-Kotlin. For@AndroidEntryPoint/ Android API surface, extend with Robolectric (@RunWith(RobolectricTestRunner::class)). - Instrumented test →
app/src/androidTest/java/com/recall/app/
Use Compose test rules (createComposeRule()) or Room in-memory DB (Room.inMemoryDatabaseBuilder).
| Permission | Why |
|---|---|
READ_MEDIA_IMAGES (API 33+) |
Read screenshots from MediaStore |
READ_EXTERNAL_STORAGE (API ≤ 32) |
Read screenshots on older devices |
INTERNET |
Download the ONNX model from HuggingFace (one-time) |
POST_NOTIFICATIONS |
Worker progress notifications |
FOREGROUND_SERVICE_DATA_SYNC |
Reserved for future foreground indexing service |
Recall uses BAAI/bge-small-en-v1.5 exported to ONNX.
| Variant | Size | Used when |
|---|---|---|
| Quantized INT8 | ~34 MB | Device RAM < 4 GB |
| Full FP32 | ~133 MB | Device RAM ≥ 4 GB |
The model is not bundled in the APK. It is downloaded once from HuggingFace via Settings when the user is on Wi-Fi with sufficient battery. SHA-256 integrity is verified after download.
- Check the open issues — issues are labelled by
phase,layer,type, andpriority. - Comment on the issue you want to work on so we don't duplicate effort.
- Follow the Making a change workflow above.
- Keep PRs focused: one issue per PR.
- All new code must include tests.
| Label | Meaning |
|---|---|
layer: data |
Data layer (DB, workers, NLP) |
layer: domain |
Domain models and use cases |
layer: presentation |
UI and ViewModels |
type: feature |
New functionality |
type: bug |
Something broken |
type: refactor |
Code quality, no behaviour change |
priority: high |
Blocking or critical |
priority: medium |
Important but not blocking |
phase: N |
Development phase grouping |
Active development is tracked in the Recall Roadmap project board under milestone v1.0 — AI Search.
Current phase: Phase 11 — UI & UX Improvements
Copyright (C) 2026 mux032
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License v3.0 as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose. See the LICENSE file for the full terms.
Privacy note: Recall processes all data on-device. No screenshots, OCR text, or embeddings are ever transmitted to any server.