Repository navigation
Read text in images without Google Play services - #195
jpavonabian wants to merge 6 commits into
Conversation
Switch OCR from play-services-mlkit-text-recognition, which delegates to Google Play services, to the bundled com.google.mlkit:text-recognition. The API and package names are the same, so no code changes are needed. On devices without Play services (e.g. GrapheneOS without sandboxed Google Play), Describe image can now read text in images. The model adds a few MB to the APK. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YSnPtHkzLK7bBGAiHzZz9h
|
Thanks, being able to read text without Play services is worth having. Some things to sort out first:
|
The model adds about 30 MB to each APK, not a few megabytes: the phone APK goes from 106 MB to 135 MB and the watch APK from 80 MB to 110 MB.
The bundled text recognition library records usage events and uploads them to firebaselogging.googleapis.com through Firebase datatransport, which now runs inside the app. Remove the datatransport service and receiver from the merged manifest so there is no backend to upload to. Text recognition still works.
Image descriptions are turned off on watches, so nothing there can reach text recognition. Bundle ML Kit only in the phone build, and keep it on the compile classpath of the watch build so the code still compiles. The watch APK goes back to its size before the bundled model.
|
@trypsynth thanks for the review. I went through each point. Size. It's more than a few MB, you're right. I measured debug builds before and after: the phone APK goes from 106 MiB to 135 MiB (+29.5 MiB). It's about 30 MB and not 10 to 12 because the native OCR library is bigger than 4 MB per ABI: The watch build was growing by the same amount, but nothing in the watch interface can reach text recognition (Describe image and automatic descriptions are turned off there, and Describe screen only sends the screenshot to Gemini), so f493373 bundles ML Kit only in the phone build and keeps it compile-only for the watch. The watch APK is back to 80 MiB. I don't have a watch, so that part is not tested on real hardware. It builds, the app starts on an emulator, and Telemetry. It was there. Firebase datatransport ( Phones with Play services. I couldn't find a way to do this with a single APK. The Play services artifact and the bundled one have the same classes ( The only option I see is two build flavors, with and without the bundled model. That means two phone APKs and the updater choosing between them, which is a bigger change in how releases are built, so I didn't want to do it without asking. Would you like me to leave it as it is, or do the flavors? |
The watch APK leaves out the model because Describe image and automatic descriptions are off on watches, not all image descriptions: Describe screen is still there. Also say that Backtalk doesn't send ML Kit's usage log to Google, which TalkBack does.
|
@trypsynth one more thing on telemetry. With 0722722 nothing is sent and nothing is stored: without a registered backend, datatransport's The code that could send it is still in the APK, though, and ML Kit still creates the events in memory before they are dropped. The way to remove it entirely would be to exclude Do you want me to try it, or is the manifest change enough? |
Problem
Describe image reads text in images with ML Kit, using
play-services-mlkit-text-recognition. That artifact delegates to the ML Kit module inside Google Play services. On phones without Play services, such as GrapheneOS without sandboxed Google Play, text recognition is unavailable, so Describe image can't read any text.Change
Switch the dependency to the bundled
com.google.mlkit:text-recognition:16.0.1. The text recognition model now ships inside the app, so it works whether or not Play services is installed.The API and package names are the same, so the Java code is unchanged. The change is in
gradle/libs.versions.toml,shared.gradle, a section indifferences.md, andtalkback/src/main/AndroidManifest.xml(see Trade-off).The model is bundled only in the phone build. Describe image and automatic descriptions are turned off on watches (
supportsImageCaption()needssupportDynamicFeatures(), which is false there), and Describe screen, which watches do have, only sends the screenshot to Gemini without text recognition. So nothing in the watch interface reaches text recognition. The watch build keeps ML Kit on its compile classpath only (wearCompileOnly), so the code still compiles, and the APK doesn't include it.Trade-off
The phone APK grows by about 30 MB, because the model is built for all three ABIs (armeabi-v7a, arm64-v8a, x86_64). The watch APK doesn't change. Measured on debug builds, before and after this change:
Almost all of it is
libmlkit_google_ocr_pipeline.so(11 MB arm64-v8a, 6.8 MB armeabi-v7a, 11.6 MB x86_64).differences.mdgives these numbers.The phone release build (
assemblePhoneRelease, unsigned) grows the same: 108.8 MiB to 138.3 MiB (+29.5 MiB). Backtalk doesn't minify, so release builds are only about 3 MiB larger than debug builds. The debug APKs are the ones published.ML Kit records usage events and uploads them to
firebaselogging.googleapis.comthrough Firebase datatransport. The datatransport components were already in the merged manifest before this change, throughplay-services-mlkit-text-recognition-common, so the upload already came from Backtalk itself. On an x86_64 emulator, with this change alone, the app storedFIREBASE_ML_SDKevents, scheduled two upload jobs and tried to connect tofirebaselogging.googleapis.com/v0cc/log/batch.The second commit removes
TransportBackendDiscovery,JobInfoSchedulerServiceandAlarmManagerSchedulerBroadcastReceiverfrom the manifest. With that, the emulator logsTransport backend 'cct' is not registered, schedules no upload jobs and makes no request, and text recognition still reads the same test image. I also ran the build before this change (withplay-services-mlkit-text-recognition) on the same emulator: it stores the same events, schedules the same two upload jobs and tries to connect to the same address. So the upload is not new, but this change stops it for everyone.What is left is the datatransport code in the APK, and ML Kit still creating the events in memory. Nothing is sent and nothing is stored: without a registered backend,
DefaultScheduler.scheduleintransport-runtime2.2.6 logsTransport backend 'cct' is not registeredand returns before it saves the event (I checked the bytecode). Removing the code too would mean excludingcom.google.android.datatransport:transport-backend-cctin Gradle. I haven't tried that, because ML Kit may crash when it starts if it refers to that class directly. Left for the maintainer to decide.Testing
./gradlew assembleDebugbuilds successfully.PipelineManager: Start process bitmap, thenOCR process succeeded),Transport backend 'cct' is not registeredshows there is no backend to upload usage events to, and no upload jobs are scheduled.testPhoneDebugUnitTestand:lintPhoneDebugpass.🤖 Generated with Claude Code