A polished, commercially-mature freemium Quran app built on React Native + Hermes. Its headline "AI" — real-time recitation mistake detection — is server-side cloud speech-to-text: the phone only captures and streams audio. Crucially it grades which words you recite, not how you pronounce them — Tarteel's own UI says it "does not detect Tajweed mistakes … in development." No on-device model, no genuine LLM. It ships no anti-tamper and a client-trusted premium flag, but the flagship AI and synced data stay server-enforced.
.so librariesTarteel (com.mmmoussa.iqra, v5.78.0/394) is an AI-powered Quran recitation, reading and memorization (hifz) app built entirely on React Native with the New Architecture (Fabric + TurboModules + Bridgeless) running Hermes bytecode v96 — not Flutter, not native Kotlin, and not Expo.
Its defining feature is real-time recitation mistake detection, and — contrary to a common assumption — this is server-side cloud ASR: the proprietary libNitroTarteel.so is a microphone capture-and-stream client that pushes raw audio over a WebSocket to wss://voice-v2.tarteel.io and receives transcripts/mistakes back. No ML model ships on-device, and the apparent "OpenAI" usage is Sentry SDK auto-instrumentation, not a real LLM call.
The decisive competitive fact is what that AI evaluates: it is a Quran-specialized speech-to-text recognizer that judges which words were recited (missed / extra / wrong word, and in Beta wrong tashkeel) — it does not acoustically grade how the letters were pronounced. On-screen "tajweed" colouring is rule visualization, not a score.
Commercially it is a mature RevenueCat freemium across Google Play and Amazon, with the AI feature paywalled. The app ships no anti-tamper (repackaging is Easy) and a client-trusted premium flag (Moderate to flip), but the flagship AI and all synced data are server-enforced, so a local crack yields an unlocked shell, not a free recognizer.
Tarteel's moat is its server-side Quran ASR plus a deep FSRS hifz engine — not the app. The open gap the category leader has not shipped is genuine acoustic tajweed grading; that, and an on-device recognizer for offline use, is where a competitor can attack.
index.android.bundle (17.5 MB Hermes) + libhermesvm.so (reports 0.14.0, bytecode v96) + libreactnative.so → React Native on the New Architecture (Fabric + TurboModules + Bridgeless).PackageList.java registers 50 native packages, none Expo; no expo-modules-core and 0 Expo native libs → bare RN (Community CLI), not Expo.NitroMmkv/SQLite, Notifee+FCM, react-native-bootsplash/localize/audio-api.libapp.so/libflutter.so), not native Kotlin (logic is Hermes bytecode).Core loop: read a full 604-page King Fahd (QCF) Mushaf → recite aloud with real-time cloud-AI mistake detection → memorize via FSRS spaced repetition ("Wird" daily tasks + "Spot Testing" active recall, incl. multiplayer). Layered with tajweed, multi-reciter audio, translations/tafsir, search, gamification, social and widgets.
Four questions, in order: is the AI real and where does it run; what does it actually evaluate (transcription or genuine tajweed); how good can it be; and how would you measure that. Load-bearing claims were re-checked by adversarial agents.
The recitation AI is real and runs on Tarteel's servers. The decompiled Kotlin client (ai.tarteel.shared.mic.SharedMicStream) is a thin audio-up / message-down pipe: it opens a WebSocket to wss://voice-v2.tarteel.io ("Connecting to VS" = Voice Server), sends a JSON START_STREAM handshake, streams raw PCM as binary WS frames (sendBinaryMessage), and forwards inbound text results to a single onMessageListener. There is no on-device recognizer or model; libNitroTarteel.so is a capture/stream client. verified · high
The AI is a Quran-specialized speech-to-text engine: it recognizes the word sequence you recite and matches it to the expected ayah, flagging which words are wrong / missed / extra (and, in Beta, wrong tashkeel). It does not acoustically grade how you pronounce — makharij, sifaat, madd duration, ghunnah or qalqalah are not scored. This is stated in Tarteel's own UI:
"Tarteel does not detect Tajweed mistakes but this feature is in development inshaAllah."
"Unannounced Pronunciation (tajweed) mistakes not yet supported."
On-screen tajweed colouring is rule visualization, and even "Tajweed Guided Review" is driven by mistake history (the word/tashkeel log), not an acoustic grade. verified · high
| AI / algorithm | Type | Provider / mechanism | Where it runs | Conf. |
|---|---|---|---|---|
| Recitation mistake detection | Streaming speech-to-text (word recognition) | Tarteel's own voice backend cloud ASR | Server · wss://voice-v2 | high |
| Tashkeel (diacritic) mistakes | Same transcript pipeline, text-tier | Server-produced, gated by isDiacritized — self-labeled Beta |
Server | high |
| FSRS spaced repetition | Scheduling algorithm (not ML) | FSRSAlgorithm/FsrsScheduler — hifz review timing |
On device | high |
| Tajweed colour-coding | Rule visualization (not acoustic grading) | colorTajweedMadd/Ghunnah/Qalqalah over mushaf text |
On device | high |
| "OpenAI / LLM" | Not present — false positive | Sentry SDK auto-instrumentation strings, no key / endpoint / model | — | refuted |
Oboe + react-native-audio-api capture; Recorder emits raw PCM. A local RMS volume meter (calculateVolume) drives the UI only — no on-device VAD or recognition.
{"event":"START_STREAM","data":<ClientConfig>} — target ayah range, modelName, recitationMode, the routing flags isDiacritized / isDualModel / isNewSttServer, and the user's JWT.
Raw PCM chunks sent as binary WebSocket frames (sendBinaryMessage); muting simply drops frames client-side.
voice-v2.tarteel.io runs the Quran-specialized recognizer (dual fast+accurate and/or preview model per flags), aligns to the expected text, and emits PARTIAL_TRANSCRIPT / FINAL_RESULT_DETAILS / RESULT messages. Internals are not statically inspectable.
JS maps results to 5 mistake types + per-word scores. The recognized-word → expected-text alignment locus (client vs server) is not statically determinable — the server is given the expected range, and the client also ships per-word logic.
Each mistake row (expectedTranscript, receivedTranscript, positions, time offsets) → local SQLite → synced to /v1/profile/mistakes.
| Recitation dimension | Graded by the AI? | Mechanism / evidence |
|---|---|---|
| Word substitution / omission / insertion | Yes — core | ASR word-sequence match vs expected ayah → mistakeTypeIncorrectWord / MissedWord / ExtraWord. What the engine is built & tuned for. |
| Tashkeel / harakat (diacritics) | Yes — Beta | Server-produced, text/transcription-tier (mistakeTypeIncorrectTashkeel); requested via isDiacritized. UI: "a new feature and may miss some of your tashkeel mistakes." |
| Memorization peek | Yes — client | mistakeTypePeekedWord — a UI reveal the server can't observe, yet shares the mistake schema ⇒ taxonomy finalized client-side. |
| Tajweed quality (makharij, madd timing, ghunnah, qalqalah) | No | Explicitly absent: "does not detect Tajweed mistakes … in development"; colorTajweed* = visualization only. |
| Voice / accent / maqam quality | No | Out of model scope — a correct word sequence in any accent passes; no acoustic-quality score field in the persisted schema. |
Net: a reciter who says the right words with poor tajweed generally passes. The product is a word-verifier, not a pronunciation coach.
The recognizer runs entirely on voice-v2.tarteel.io; its confidence thresholds, N-best collapse and true error rates are server-internal and not observable statically. Below is an evidence-bounded ceiling & failure-mode assessment, plus the controlled test we would run to actually measure it (and why we did not).
isDualModel flag's own description: "use 2 models simultaneously (fast + accurate) to improve speed without compromising accuracy."beta-model; copy: "Old preview model unavailable. Switched to a new preview model.", "The Preview Model has no guaranteed uptime."isNewSttServer / GrowthBook key new-stt-server — accuracy iterated server-side, flag-gated.shouldCollectAudio / shouldLabelAudio ⇒ user recitations can be harvested & labelled to improve the model.Should be strong: a closed Quranic corpus with the expected ayah range supplied a priori (startPosition/endPosition) makes word substitution/omission/insertion the easy case — plausibly high-90s% word accuracy on clear murattal in good audio (inferred).
Must degrade on: tashkeel minimal pairs (Beta), near-homophones, accents / melodic maqam, fast ḥadr tempo, and noisy or packet-dropped audio. Tajweed quality is a hard blind spot.
"did the Preview Model detect mistakes incorrectly?" → False-Reject"did the Preview Model miss intentional mistakes?" → False-AcceptThe user-facing "Adaptive Mode" mistake threshold is exactly the FAR↔FRR dial.
A controlled minimal-pair test set would isolate each failure mode:
Incorrect/Missed/ExtraWord at that position.isDiacritized=true) ⇒ probes the Beta diacritic path.Metrics: WER/CER from the engine's own receivedTranscript vs expectedTranscript; per-mistake-type False-Accept and False-Reject rates; the FA/FR tradeoff curve across the threshold.
Not executed — out of scope. The recognizer is behind wss://voice-v2.tarteel.io, a competitor's paid endpoint, and START_STREAM requires a valid Tarteel JWT tied to the paid Mistake-Detection entitlement (we hold no account). Per the workflow's Authorization & Scope, accuracy/bypass feasibility is assessed from the code, never by hitting their live paid service. The ceiling above therefore stays explicitly inferred.
SharedMicStreamKt.startStreamJson() serializes the config into {"event":"START_STREAM","data":…}; onAudioData → websocket.sendBinaryMessage; onMessageReceived forwards inbound text to the JS listener. No transcribe()/recognize()/loadModel()/infer()..onnx/.tflite/.pt/.gguf/.mlmodel anywhere; none of the 41 native libs is an inference runtime (no ONNX/TFLite/Whisper/Vosk/Kaldi).SharedSTTConfig carries Google-Cloud-Speech-style fields (sampleRate, channels, fileFormat, modelName, languageCode, enableInterimResults, enableWordTimeOffsets, maxAlternatives); SharedClientConfig adds authToken, userId, deviceId, recitationMode, startPosition/endPosition, isDiacritized, isDualModel, isNewSttServer, shouldCollectAudio, shouldLabelAudio.instrumentOpenAiClient strings are vendored Sentry JS SDK auto-instrumentation (shipped alongside identical instrumentAnthropicAiClient / GoogleGenAI / LangChain hooks). No api.openai.com, no /v1/chat/completions, no gpt-* model id, no sk- key.mistakeTypeMissedWord, ExtraWord, IncorrectWord, IncorrectTashkeel (Beta), PeekedWord (memorization). Modes: recitationMode = DETECTION (flag as you go) or CORRECTION (track correctedUpToWordNumber).mistakeType, expectedTranscript, receivedTranscript, positions, ayah, correctedUpToWordNumber, startTimeMs, endTimeMs, syncedAt — stores both expected and received transcript (a word-sequence diff) + per-word time offsets that power Mistake-Playback.mistakeThreshold ("Adaptive Mode"); isStartPositionStrict / isInitialTargetRangeStrict + MUSHAF_MARGIN_OF_ERROR_SECONDS tune start/timing strictness.A bare RN/Hermes app with zero app-authored anti-tamper: no signature self-check, no Play Integrity/SafetyNet, no certificate pinning, low obfuscation. Repackage + re-sign is trivial. The only thing stopping a free "paid-AI" mod is that the cloud ASR and premium entitlements are gated server-side by a Tarteel JWT + RevenueCat receipt — not by the APK. A mod yields an unlocked shell, not working paid AI.
| Defensive control | Status | What we found | Conf. |
|---|---|---|---|
| Signing scheme | v2 + v3 | APK Signing Block present (IDs 0x7109871a v2, 0xf05368c0 v3, + Play source-stamp); zero .RSA/.SF v1 files. Any tamper invalidates the signature → attacker must fully re-sign with their own key. |
high |
| Signature / integrity self-check | Absent | App never reads its own signatures[0] to compare a known hash. Only GET_SIGNATURES/getSigningInfo hits are GMS (GoogleSignatureVerifier) and Adjust (fingerprinting Facebook's cert) — neither gates Tarteel. A re-signed mod is not detected. |
high |
| Play Integrity / attestation | Absent | No play.core.integrity, no IntegrityManager, no SafetyNet/DeviceCheck. The only "attestation" hits are GMS FIDO2/WebAuthn (passkeys), unrelated. |
high |
| Certificate pinning | Absent | No networkSecurityConfig/<pin-set>; OkHttp CertificatePinner ships but is never instantiated with pins; no TrustKit. Traffic to api/voice/RevenueCat is MITM-inspectable with a user CA on a rooted device. |
high |
| Root / debug detection | Dormant | A full native root toolkit ships inside react-native-nitro-device-info (isDeviceCompromised(): Magisk/KernelSU/APatch/su/busybox/test-keys), but the JS layer never calls it → capability present, not in force. App not declared debuggable. |
high |
| Code obfuscation | Low | App Kotlin/Java keep descriptive names (ai.tarteel.shared.mic.*); ~2 single-letter classes total → R8 mangling effectively off. No string encryption. Hermes bytecode is the only mild speed-bump; JS identifiers (isPremium, mistakeDetection) are intact and patchable. |
med |
| Capability | Outcome | Why |
|---|---|---|
| Cloud AI mistake-detection | Not unlocked | App still runs (no integrity check), but the STT session needs a valid JWT + paid entitlement at voice-v2 — a free mod has neither → 401/403. Modding alone does not grant paid AI. |
| Premium UI gates | Cosmetic only | Local isPremium flag can be flipped to show premium UI, but any server-validated feature re-checks and fails (see Paywall). |
| Reciter audio playback | Survives | Streamed from unauthenticated CDN (audio-cdn.tarteel.ai/quran/…mp3) — no token/pinning → plays unchanged (free content anyway). |
| Offline Mushaf reading | Survives | Fully local (bundled fonts + SQLite via DBManager); no auth/signature dependency. |
| FCM push / Google Sign-In | Breaks | Bound to package + signing-cert SHA registered with the Firebase/Google project; re-signing with an attacker key fails closed. |
| Analytics / attribution | Survives | Sentry/Mixpanel/Adjust/GrowthBook use client-embedded DSNs/keys that don't verify the APK signature → keep sending (Adjust may mis-attribute). |
# signing scheme — v2/v3 block, no v1 JAR sig strings -a com.mmmoussa.iqra-394.apk | grep -q "APK Sig Block 42" # => present unzip -l com.mmmoussa.iqra-394.apk | grep -ciE '^META-INF/[^/]+\.(RSA|SF)$' # => 0 # pinning / attestation / root-detection / integrity signals (app code) grep -raEi "certificatePinner|network_security_config|play.core.integrity|SafetyNet|IntegrityManager" \ decompiled/sources # => only GMS/OkHttp boilerplate, ZERO app-authored # root toolkit exists but is never invoked from JS grep -ac "isDeviceCompromised\|verifyDeviceIntegrity\|checkMagisk" analysis/bundle_strings.txt # => 0 invocations => dormant capability # obfuscation: app symbols retained => unobfuscated grep -ac "ai/tarteel/shared/mic/SharedMicStream\|isPremium\|mistakeDetection" analysis/bundle_strings.txt # => many hits => R8 mangling effectively off for app code
The paywall UI is client-trusted: a single Redux boolean (selectIsPremiumUser) derived from an unverified, cached RevenueCat CustomerInfo. Flipping it unlocks locally-bundled / UI-only perks (no-ads, on-device translations/tafsir/word-by-word, tajweed colours). But the flagship cloud AI and all synced data authenticate against Tarteel's servers (which even expose /v1/account/verify/sub/) — so a local crack yields an unlocked shell, not a free working recognizer.
| Bypass factor | Status | Detail | Conf. |
|---|---|---|---|
| Entitlement location | Mixed | UI gate = client-trusted boolean from cached CustomerInfo (held entirely in JS/Redux; libNitroTarteel.so has zero purchase logic). Feature value = server-enforced (JWT-authed API + voice WS). |
high |
| Local-flag tamper | Easy | One well-named selector selectIsPremiumUser survives in the Hermes string table → quick to locate and force true (or seed MMKV with a premium-looking CustomerInfo). |
med |
| Entitlement verification mode | Disabled | RevenueCat Trusted Entitlements SDK ships (entitlementVerificationMode token present) but the app never opts in → cached CustomerInfo consumed without signature verification. |
med |
| Offline unlock | Partial | UI/local-only perks unlock offline; anything server-validated or server-stored cannot be conjured by a flag flip. | med |
| Server-side resistance | Holds | Cloud AI session + profile/mistakes/wirds endpoints re-derive trust from the account JWT, not the client flag. Single unknown: whether voice-v2 enforces entitlement per session is not statically verifiable. |
med |
isPremium flip| Premium feature | Result | Why / gatekeeper |
|---|---|---|
| No-ads | Unlocks | Pure client decision off the premium boolean; no ad-network gate → unlocks fully offline. |
| Word-by-word / tafsir / translations / tajweed colours | Partial | Already-bundled content renders once the flag flips (UI gate only); additional packs fetched from download/pl-cms.tarteel.ai may be entitlement-gated at download. |
| Cloud AI mistake-detection (+ tajweed/tashkeel) | Server-gated | UI/entry unlocks, but the session needs voice-v2 to accept a START_STREAM carrying the JWT/userId; tashkeel is just the isDiacritized param of that session. |
| Memorization planning · advanced spot-testing · challenges | Partial | Local toggles flip; but synced plans (/v1/profile/manual_memorization_entries), recitation-scored spot-tests (reuse the cloud ASR) and multiplayer need the server. |
| Mistake history / frequency | Stays locked | Server-stored, fetched from /v1/profile/mistakes; a local flag can't fabricate server-side history. |
| Cross-device sync | Stays locked | Inherently server-authoritative (JWT-authed profile endpoints); the backend decides what to sync per entitlement. |
react-native-purchases) over Google Play Billing 7.1.1 and Amazon Appstore IAP (ProxyAmazonBillingActivity) — dual-store. Bridge events RNPurchases-CustomerInfoUpdated; full CustomerInfo surface (getCustomerInfo, addCustomerInfoUpdateListener, invalidateCustomerInfoCache, restorePurchases, getOfferings).selectIsPremiumUser derived from the cached entitlements map; gating components subscribe to it. No per-action server re-check found on the client side.MistakeDetectionUpsellModal, MemorizationModeUpsellModal, compareFeatures* — UI gated on the boolean./v1/account/activate_trial_offer (a Cloudflare Worker) and win-back / referral loops. Exact SKUs/prices are runtime-fetched from RevenueCat Offerings (not in the bundle).A hardened manifest baseline and correct secrets hygiene (cleartext off, backup off, not debuggable, only publishable client keys), undermined chiefly by missing certificate pinning, missing device attestation, and a leaked dev voice endpoint. No genuinely sensitive server secret was found in the bundle or native libraries.
| Permission | Justification | Assessment |
|---|---|---|
RECORD_AUDIO / FOREGROUND_SERVICE_MICROPHONE | Recitation listening / STT capture (MicRecordingService) | justified |
FOREGROUND_SERVICE_MEDIA_PLAYBACK | Reciter audio player (TrackPlayer) | justified |
INTERNET / ACCESS_NETWORK_STATE | API + voice WS streaming | justified |
SCHEDULE_EXACT_ALARM / RECEIVE_BOOT_COMPLETED / POST_NOTIFICATIONS | Prayer/streak reminder notifications (Notifee) | justified |
com.android.vending.BILLING / gms…AD_ID | RevenueCat purchases / Adjust + Mixpanel attribution | justified |
READ/WRITE_EXTERNAL_STORAGE | Legacy storage — no maxSdkVersion cap | minor hygiene |
No SMS / CONTACTS / LOCATION / CAMERA / READ_PHONE_STATE — proportionate, no over-reach. Exported surface minimal (MainActivity deep-link handler, widget providers, BootReceiver, guarded SDK receivers); the app's own services & FileProviders are exported=false.
usesCleartextTraffic="false")allowBackup="false")sk_live / AKIA / PEM / client_secretlibsigner.so = Adjust signer (not Tarteel)| Key | Type | Risk |
|---|---|---|
goog_… / appl_… | RevenueCat public SDK keys | publishable · low |
Sentry DSN (o298228.ingest.sentry.io/5216068) | Ingest-only, write-only | publishable · low |
prod_EIZy… | GrowthBook client key (read-only) | publishable · low |
AIzaSy… | Firebase/google-services client API key | restrict to package+SHA-1 |
| Google OAuth client id | Public identifier | publishable · low |
libsigner.so is NOT a Tarteel signer claim refuted — it is the third-party Adjust SDK signature library (com.adjust.sdk.sig), building an authorization: Signature … header for Adjust attribution anti-fraud. Zero references to "tarteel", "voice", "hmac" or "bearer". Tarteel API auth is JWT-based in the JS layer.
A first-party REST API on api.tarteel.io (versioned /v1/), a dedicated real-time WebSocket voice cluster, a Payload CMS, and a multi-host CDN tier. Auth is social sign-in exchanged for a server-issued JWT.
| Host | Role | Notable paths / notes | Conf. |
|---|---|---|---|
| api.tarteel.io | Primary product REST API (/v1/) | account/auth, profile, mistakes, srs, leaderboards, groups, alim, aad | high |
| wss://voice-v2.tarteel.io | Real-time STT / mistake detection (prod) | Streaming recitation recognition; dev variant voice-v2-dev leaked into prod | high |
| api.tarteel.ai | Infra / version gate | /v1/mobile/minimum-supported-version/ | med |
| audio-cdn / static-cdn / download.tarteel.ai | Reciter audio & asset CDN | /quran/<reciter>/<sssaaa>.mp3; asset-manifests | high |
| pl-cms.tarteel.ai | Payload CMS — app-driven content | "pl" = Payload | med |
| srs-onboarding.worker.tarteel.ai | Cloudflare Worker — trial activation | /v1/account/activate_trial_offer/ | high |
/v1/account/authenticate/authenticate_with_token/social_auth_create_account/validate_social_auth_sign_in/activate_trial_offer/v1/profile/mistakes/srs_settings, /wirds/manual_memorization_entries/recordings, /bookmarks/canny_sso, /hydrate/v1/leaderboards/…/profile/community_group/join/profile/family_group/invite/v1/alim/apply/status/v1/aad/schedule (Ayah-a-Day)Social sign-in (Apple/Google/Facebook/email) is exchanged server-side for a JWT bearer token stored client-side via MMKV. Open question: whether the JWT is short-lived with refresh, and whether it sits in MMKV vs Android Keystore. Third-party backends are cleanly separable: api.mixpanel.com, Sentry ingest, cdn.growthbook.io, errors.rev.cat, tarteel-app.firebaseio.com, tanzil.network (canonical Quran text).
| Layer | Technology | Evidence |
|---|---|---|
| Runtime / framework | React Native New Arch · Hermes bytecode v96 | libhermesvm.so (0.14.0) + libreactnative.so |
| Proprietary core | Margelo Nitro module — 14 manager specs | libNitroTarteel.so (MicStream, Session, SRS, Wird, Widget…) |
| Margelo Nitro stack | NitroModules, NitroMmkv, NitroHaptics, RNNitroSQLite, NitroDeviceInfo | Replaces Expo equivalents |
| UI / graphics | Skia (~11 MB), Reanimated 4 + Worklets, Moti, Gesture Handler, SVG | librnskia.so, libreanimated.so |
| Audio | react-native-audio-api (Oboe + bundled FFmpeg) capture; track-player (Media3/ExoPlayer) playback | liboboe.so, libav*.so |
| State / storage | Redux Toolkit + redux-persist (MMKV) + TanStack Query; MMKV + Nitro SQLite + AsyncStorage + DataStore | ReduxMMKV; libRNNitroSQLite.so |
| Payments | RevenueCat — dual-store (Play + Amazon) | react-native-purchases; ProxyAmazonBillingActivity |
| Analytics / growth | Mixpanel · Adjust (+SKAdNetwork) · GrowthBook · Sentry (JS+NDK) · Firebase (FCM) · Canny · Shake | 12+ third-party SDKs |
| Backend | First-party REST + real-time WS voice cluster + Payload CMS + CDN tier | api.tarteel.io, wss://voice-v2 |
libNitroTarteel.so (2.1 MB, 14 Nitro managers; no ML/crypto/TLS libs).libhermesvm / libreactnative / libjsi / libfbjni / libhermestooling (hosts the voice WS transport).librnskia (~11 MB), libreanimated / libworklets, libreact-native-audio-api, liboboe, libavcodec/avformat/avutil/swresample (FFmpeg, playback decode — no Opus → not mic encoding).libNitroModules / libNitroMmkv / libmmkv / libRNNitroSQLite / libNitroHaptics / libnitrodeviceinfo.libsentry(+android), libsigner (= Adjust signer), 8× libreact_codegen_*, Fresco image pipeline, DataStore, libc++_shared.AmazonPurchaseScreen) — unusual reach.startNDayFreeTrial, intro pricing, server-side activate_trial_offer.purchasePackageWithWinBackOffer, exit-survey deep link.shabanMistakeDetectionUpsell).useIsDualModelEnabled, useIsNewSttServerEnabled, useIsSpotTestingMultiplayerEnabled).recordUpsellTriggered, purchaseSuccess).| Capability | Premium-gated signal |
|---|---|
| AI Mistake Detection (flagship) | MistakeDetectionUpsellModal, compareFeaturesMistakeDetectionExplanation |
| Mistake History / Frequency / Playback | compareFeaturesMistakeHistory / MistakeFrequency |
| Tajweed & Tashkeel mistakes | compareFeaturesTajweedMistakes / TashkeelMistakes |
| Memorization planning · advanced spot testing | memorizationModeUpsell, spotTestingUpsellSelfAssessment |
| Ads removal · cross-device sync | compareFeaturesNoAds, compareFeaturesCrossDeviceSyncing |
| Word-by-word · tafsir · translations | compareFeaturesWordByWord / Tafsir / VariousRecitations |
Exact SKUs, durations and price points are not in the bundle (runtime-fetched from RevenueCat Offerings). The flagship AI mistake-detection is the correct paywall anchor.
/v1/profile/mistakesaudio-cdn.tarteel.ai (Alafasy, Husary, Abdul Basit, Sudais…)The following are interpretation and recommendation (not reverse-engineered facts) — framed for product and engineering decisions.
Tarteel's AI grades word transcription, not pronunciation/tajweed quality — it says so itself ("in development"), and tashkeel is Beta. For a tajweed-first builder (e.g. LearnQuran's own com.bi.learnquran), genuine acoustic tajweed feedback — scoring madd duration, ghunnah, qalqalah and makharij, ideally on-device for offline + privacy — is the differentiator the category leader has not shipped.
Tarteel's defensible asset is its server-side Quran ASR (the voice-v2 cluster + recognition models), not client code. Cloning the RN app gains nothing without the recognizer. Conversely the cloud dependency means recitation only works online — a real UX gap a competitor could attack with a (harder-to-build) on-device recognizer for offline practice.
The hifz system — FSRS spaced repetition, Wird daily tasks, Spot Testing active recall, multiple Mushaf scripts — is well beyond a typical "read + play audio" app. This is the structural reason a user stays for months; a new entrant competing on reading alone will struggle. The bar is now "structured, scheduled memorization with AI feedback."
Correct secrets hygiene and a hardened manifest, but no pinning and no attestation means the server must bear all abuse/entitlement enforcement. For a paid AI API, requests can be replayed/MITM'd from a rooted device. Adding certificate pinning + Play Integrity would be low-effort, high-signal hardening — a lesson for our own app.
Net read: Tarteel sets the bar on cloud word-recognition and structured memorization, but leaves genuine tajweed grading and offline recitation wide open. The takeaway is a niche to own (pronunciation quality) and a discipline to keep (server-side enforcement + client hardening).
com.mmmoussa.iqra-394.apk (base 184 MB; ~275 MB installed) + config splitscom.mmmoussa.iqra · 5.78.0 (build 394)jadx, unzip, strings, grep, plus a multi-agent analysis with adversarial verification of high-stakes claims. No dynamic / runtime analysis, no traffic capture.by-claude-tarteel-ai/ — decompiled/, decompiled-with-res/, assets/, native/, analysis/Hermes string run-on caveat: the Hermes string table packs literals back-to-back, so strings output frequently concatenates unrelated literals (e.g. download.tarteel.ainstrumentOpenAiClient); substring matches were treated as fuzzy and corroborated. Adversarial verification re-checked the load-bearing claims, and the corrected statements override the source analyses: (1) recitation ASR is cloud, not on-device; (2) "OpenAI" = Sentry instrumentation; (3) only publishable keys; (4) libsigner.so = Adjust signer. AI-evaluation re-verification: "evaluates transcription not tajweed" CONFIRMED; recognizePrefixSuffix is a Reanimated false friend (dropped); tashkeel is server-produced (not client-derived) and Beta; the alignment locus (client vs server) is not statically determinable; the AI accuracy ceiling is inferred, not measured (the recognizer is a paid, out-of-scope endpoint).
Provenance & ethics. All findings derive from static analysis of a publicly distributed application binary for authorized competitive-research and defensive-security purposes. No active testing, traffic capture, or disruption of any live service was performed. "Strategic notes" reflect analyst interpretation, not statements of fact about the subject app.
Note: the raw endpoints.txt is dominated by Hermes string-table run-on artifacts (e.g. https://download.tarteel.ainstrumentOpenAiClient); only cleanly de-concatenated hosts are treated as real.
# control messages are JSON; audio is binary frames startStreamJson(config) => {"event":"START_STREAM","data":<ClientConfig>} endStreamJson() => {"event":"END_STREAM"} onAudioData(byte[] data) => websocket.sendBinaryMessage(data) # raw PCM onMessageReceived(String message) => onMessageListener.invoke(message) calculateVolume(data) = 20*log10(RMS) # local meter only, no VAD LogKt.d: "Connecting to VS..." / "Connected to VS" / "Started voice session" # SharedClientConfig — the START_STREAM handshake fields appVersion, audioConfig(=SharedSTTConfig), authToken, deviceId, devicePlatform, isMemorization, isDiacritized, isDualModel, isInitialTargetRangeStrict, isNewSttServer, isStartPositionStrict, startPosition, endPosition, recitationMode, sessionId, userId, shouldCollectAudio, shouldLabelAudio # SharedSTTConfig — Google-Cloud-Speech-style streaming ASR request fileFormat, channels, sampleRate, modelName, languageCode, enableInterimResults, enableWordTimeOffsets, maxAlternatives
# what it evaluates — Tarteel's own disclaimers "Tarteel does not detect Tajweed mistakes but this feature is in development inshaAllah." "Unannounced Pronunciation (tajweed) mistakes not yet supported." "Tashkeel mistake detection is a new feature and may miss some of your tashkeel mistakes." # mistake taxonomy (word/diacritic-level, not acoustic) mistakeTypeMissedWord · ExtraWord · IncorrectWord · IncorrectTashkeel · PeekedWord expectedTranscript · receivedTranscript · recitationMode = DETECTION|CORRECTION # model iteration — Tarteel measures FAR/FRR itself "use 2 models simultaneously (fast + accurate)..." # isDualModel "did the Preview Model detect mistakes incorrectly?" # False-Reject "did the Preview Model miss intentional mistakes?" # False-Accept "The Preview Model has no guaranteed uptime." new-stt-server (GrowthBook)
# framework fingerprint — React Native + Hermes ls native/lib/arm64-v8a/ # => libhermesvm.so + libreactnative.so => RN/Hermes ls assets/flutter_assets 2>/dev/null # => absent => not Flutter # on-device ML model check (ABSENCE is the finding) find assets native \( -name "*.tflite" -o -name "*.onnx" -o -name "*.gguf" \) # => none => no on-device model ls native/lib/arm64-v8a/ | grep -iE "tflite|tensorflow|onnx|whisper|vosk" # => none => no inference runtime linked # cloud ASR endpoint + OpenAI=Sentry check grep -aoE "wss://[a-z0-9.-]+" analysis/bundle_strings.txt | sort -u # => wss://voice-v2.tarteel.io (+ -dev) grep -ac "api.openai.com\|/v1/chat/completions\|sk-[A-Za-z0-9]" analysis/bundle_strings.txt # => 0 => "OpenAI" strings are Sentry instrumentation, not an LLM call
# uses-permission <uses-permission android:name="android.permission.RECORD_AUDIO"/> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE"/> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="com.android.vending.BILLING"/> <uses-permission android:name="com.google.android.gms.permission.AD_ID"/> <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/> <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> # legacy, no maxSdkVersion # application flags android:debuggable => ABSENT (false) android:usesCleartextTraffic => "false" android:allowBackup => "false" # signing: v2 + v3 APK Signing Block; no v1 .RSA/.SF