{"id":50363977,"url":"https://github.com/codecsrayo/quiz-gate","last_synced_at":"2026-05-30T03:02:05.153Z","repository":{"id":358371601,"uuid":"1241141683","full_name":"codecsrayo/quiz-gate","owner":"codecsrayo","description":"aws practitioner guard android quiz","archived":false,"fork":false,"pushed_at":"2026-05-17T02:39:06.000Z","size":132,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-17T04:41:55.758Z","etag":null,"topics":["android","aws-practice","quiz"],"latest_commit_sha":null,"homepage":"https://codecsrayo.com/quiz/#aws-practitioner","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/codecsrayo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-17T02:24:05.000Z","updated_at":"2026-05-17T02:39:09.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/codecsrayo/quiz-gate","commit_stats":null,"previous_names":["codecsrayo/quiz-gate"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/codecsrayo/quiz-gate","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codecsrayo%2Fquiz-gate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codecsrayo%2Fquiz-gate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codecsrayo%2Fquiz-gate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codecsrayo%2Fquiz-gate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/codecsrayo","download_url":"https://codeload.github.com/codecsrayo/quiz-gate/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codecsrayo%2Fquiz-gate/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33678271,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-30T02:00:06.278Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","aws-practice","quiz"],"created_at":"2026-05-30T03:02:04.350Z","updated_at":"2026-05-30T03:02:05.136Z","avatar_url":"https://github.com/codecsrayo.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Quiz Gate\n\nBloquea WhatsApp, Facebook e Instagram detrás de preguntas del banco de **AWS Cloud Practitioner**. Cada vez que abres una app bloqueada (entrada fresca o expirada la sesión) tienes que acertar una pregunta antes de poder usarla. Pensado para HyperOS / MIUI / Android stock (API 24+).\n\n- **Origen del banco**: API HTTP (default `https://codecsrayo.com/api/quiz/practitioner`)\n- **UI nativa**: Jetpack Compose + Material 3\n- **Bilingüe**: ES / EN\n- **Sin dependencias pesadas**: `org.json` + `HttpURLConnection`\n\n---\n\n## Cómo funciona (alto nivel)\n\n```mermaid\nflowchart LR\n    User([Usuario]) --\u003e|abre WhatsApp/FB/IG| Foreground[Window state change]\n    Foreground --\u003e AS[BlockerAccessibilityService]\n    AS --\u003e|sesión activa o llamada| Allow[Deja pasar]\n    AS --\u003e|sesión expirada| Launch[startActivity QuizActivity]\n    Launch --\u003e Quiz[QuizActivity Compose]\n    Quiz --\u003e|acierta| Unlock[setPendingUnlock + launch app]\n    Quiz --\u003e|cancela| Home[Intent CATEGORY_HOME]\n    Unlock --\u003e Target[App desbloqueada]\n    AS -.-\u003e WD[WatchdogService FG]\n    WD -.-\u003e|mantiene proceso vivo| AS\n```\n\n`BlockerAccessibilityService` observa transiciones de foreground. `WatchdogService` es un foreground service con notificación silenciosa que mantiene el proceso vivo — sin él HyperOS mata el AccessibilityService o bloquea `startActivity` por **Background Activity Launch**.\n\n---\n\n## Reglas de sesión y desbloqueo\n\nEl service decide para cada `TYPE_WINDOW_STATE_CHANGED` qué hacer:\n\n```mermaid\nflowchart TD\n    Evt[Evento foreground pkg] --\u003e Sys{¿systemui / IME?}\n    Sys -- sí --\u003e Skip[Ignorar evento]\n    Sys -- no --\u003e Track[lastForegroundPkg = pkg]\n    Track --\u003e LeavingBlocked{¿prev era app bloqueada\u003cbr/\u003ey pkg no es transient?}\n    LeavingBlocked -- sí --\u003e EndSess[clearLastSeen + clearSessionStart de prev]\n    LeavingBlocked -- no --\u003e CheckPkg{¿pkg es app bloqueada?}\n    EndSess --\u003e CheckPkg\n    CheckPkg -- no, y es real --\u003e EndAll[Cierra TODAS las sesiones activas]\n    CheckPkg -- no --\u003e Done[Return]\n    EndAll --\u003e Done\n    CheckPkg -- sí --\u003e EndOthers[Cierra sesión de las demás bloqueadas]\n    EndOthers --\u003e InCall{¿llamada activa?\u003cbr/\u003eAudioManager.mode}\n    InCall -- sí --\u003e Bypass[touchLastSeen + return]\n    InCall -- no --\u003e PendingUnlock{¿pending unlock\u003cbr/\u003epara este pkg?}\n    PendingUnlock -- sí --\u003e Consume[Consume token\u003cbr/\u003esetSessionStart\u003cbr/\u003eschedule timer]\n    PendingUnlock -- no --\u003e InSession{¿isWithinSessionWindow?\u003cbr/\u003e5 min desde last seen}\n    InSession -- sí --\u003e Continue[touchLastSeen\u003cbr/\u003echequea max in-app\u003cbr/\u003eschedule timer]\n    InSession -- no --\u003e Quiz[triggerQuiz: lanza QuizActivity]\n```\n\n### Conceptos\n\n| Concepto | TTL | Significado |\n|---|---|---|\n| `pendingUnlock[pkg]` | 60 s | Token de un solo uso emitido por QuizActivity al acertar. Consumido por el service en el próximo foreground del pkg. |\n| `lastSeen[pkg]` | 5 min (SESSION_WINDOW) | Última vez que pkg estuvo en foreground. Si vuelves dentro de la ventana, sin quiz. |\n| `sessionStart[pkg]` | hasta reset | Cuándo empezó la sesión actual. Se compara contra `maxInAppMs` para el cuestionario por tiempo. |\n\n### Apps \"transient\" (no rompen sesión)\n`systemui`, `miui.systemui.plugin`, `com.miui.home` y otros launchers conocidos. Cuando deslizas notificaciones o pasas brevemente por el launcher, la sesión sigue activa.\n\n### Llamadas\nSi `AudioManager.mode` es `MODE_RINGTONE`, `MODE_IN_COMMUNICATION` o `MODE_IN_CALL`, el quiz se omite — puedes contestar la llamada sin acertar primero.\n\n### WhatsApp y el timer in-app\nWhatsApp está en `IN_APP_TIMER_EXEMPT` — el cuestionario por tiempo prolongado no se aplica (sólo al entrar). Conversaciones largas no se interrumpen.\n\n---\n\n## Anatomía de un desbloqueo\n\n```mermaid\nsequenceDiagram\n    participant U as Usuario\n    participant W as WhatsApp\n    participant AS as AccessibilityService\n    participant QA as QuizActivity\n    participant P as Prefs\n\n    U-\u003e\u003eW: tap icono\n    W--\u003e\u003eAS: WINDOW_STATE_CHANGED pkg=com.whatsapp\n    AS-\u003e\u003eP: isWithinSessionWindow(whatsapp) → false\n    AS-\u003e\u003eP: setLastBlockedPackage(whatsapp)\n    AS-\u003e\u003eQA: startActivity(QuizActivity)\n    QA-\u003e\u003eU: muestra pregunta\n    U-\u003e\u003eQA: selecciona + Comprobar + Continuar\n    QA-\u003e\u003eP: setPendingUnlock(whatsapp, 60s TTL)\n    QA-\u003e\u003eW: startActivity(launch intent)\n    QA-\u003e\u003eQA: finishAndRemoveTask\n    W--\u003e\u003eAS: WINDOW_STATE_CHANGED pkg=com.whatsapp\n    AS-\u003e\u003eP: consumePendingUnlock(whatsapp) → true\n    AS-\u003e\u003eP: touchLastSeen + setSessionStart\n    Note over AS: schedule postDelayed para max in-app\n```\n\n---\n\n## Permisos requeridos\n\n| Permiso | Por qué | Cómo se concede |\n|---|---|---|\n| `BIND_ACCESSIBILITY_SERVICE` | Detectar app en foreground | Ajustes → Accesibilidad → Quiz Gate |\n| `SYSTEM_ALERT_WINDOW` | Reforzar la activity del quiz | Diálogo Android estándar (botón en MainActivity) |\n| `REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` | Que HyperOS no mate el service | Diálogo Android estándar |\n| `FOREGROUND_SERVICE_SPECIAL_USE` | Para el WatchdogService | Manifest, no requiere acción del usuario |\n| `POST_NOTIFICATIONS` | Notificación del Watchdog | Diálogo en primera apertura (Android 13+) |\n| **MIUI \"Otros permisos\"** | **Crítico**: \"Mostrar ventanas emergentes en segundo plano\" + \"Mostrar en pantalla de bloqueo\" | Botón en MainActivity → editor de permisos MIUI |\n\n\u003e ⚠️ Sin el último (MIUI), `startActivity` desde el AccessibilityService es **denegado silenciosamente** por HyperOS, la activity nunca llega a `onCreate`. Síntoma: ves un parpadeo y nada más.\n\n---\n\n## Configuración (Settings card)\n\n| Setting | Default | Notas |\n|---|---|---|\n| Apps bloqueadas | FB, FB Lite, WhatsApp, Instagram | Checkboxes |\n| Tiempo máximo en la app | 10 min | Segmented `0 · 5 · 10 · 20 · 30 · 60`. `0` = ∞ |\n| Idioma | ES | ES / EN, aplica a las preguntas |\n| URL de la API | `https://codecsrayo.com/api/quiz/practitioner` | Editable |\n| Sincronizar ahora | — | Descarga y cachea el JSON |\n\n---\n\n## Contrato del JSON\n\n```json\n[\n  {\n    \"id\": \"AWS-CHAT-001\",\n    \"domain\": \"technology\",\n    \"domainLabel\": { \"es\": \"Tecnología\", \"en\": \"Technology\" },\n    \"questionType\": \"multiple-choice\",\n    \"scenarioBased\": true,\n    \"q\": { \"es\": \"¿…?\", \"en\": \"…?\" },\n    \"options\": {\n      \"es\": [\"CloudFront\", \"Route53\", \"ELB\", \"NAT Gateway\"],\n      \"en\": [\"CloudFront\", \"Route53\", \"ELB\", \"NAT Gateway\"]\n    },\n    \"answer\": 1,\n    \"answers\": [0, 1],\n    \"explanation\": { \"es\": \"…\", \"en\": \"…\" },\n    \"tip\": { \"es\": \"…\", \"en\": \"…\" }\n  }\n]\n```\n\n- `questionType` = `\"multiple-choice\"` (radio, una respuesta) o `\"multiple-response\"` (checkbox, varias).\n- `answer` (int) = índice correcto para multiple-choice.\n- `answers` (array de int) = índices correctos para multiple-response. Si está presente, prevalece sobre `answer`.\n- Las preguntas en caché viven en `filesDir/quiz_practitioner.json`.\n\n---\n\n## Estructura del proyecto\n\n```\napp/src/main/\n├── AndroidManifest.xml\n├── java/com/example/ask/\n│   ├── MainActivity.kt              # UI de configuración (Compose)\n│   ├── QuizActivity.kt              # UI del cuestionario (Compose, fullscreen)\n│   ├── BlockerAccessibilityService.kt  # detecta foreground + dispara quiz\n│   ├── WatchdogService.kt           # foreground service para mantener proceso vivo\n│   ├── Permissions.kt               # helpers para abrir Settings de cada permiso\n│   ├── Prefs.kt                     # SharedPreferences wrapper\n│   ├── Question.kt                  # modelo + parser JSON\n│   ├── QuizRepository.kt            # descarga y cachea preguntas\n│   └── ui/theme/                    # tema Compose (DayNight)\n└── res/\n    ├── values/themes.xml            # Material Light\n    ├── values-night/themes.xml      # Material Dark\n    ├── values/strings.xml           # textos ES\n    └── xml/accessibility_service_config.xml\n```\n\n---\n\n## Build \u0026 install\n\n```powershell\n$env:JAVA_HOME = \"C:\\Program Files\\Android\\Android Studio\\jbr\"\n.\\gradlew :app:assembleDebug\n\n$adb = \"$env:LOCALAPPDATA\\Android\\Sdk\\platform-tools\\adb.exe\"\n\u0026 $adb install -r app\\build\\outputs\\apk\\debug\\app-debug.apk\n```\n\nRequisitos:\n- Android Studio Ladybug+ (compileSdk 36)\n- min SDK 24, target 36\n- JDK 11+\n\n---\n\n## Debugging\n\nLos logs relevantes se emiten con tag `QuizGate`:\n\n```powershell\n\u0026 \"$env:LOCALAPPDATA\\Android\\Sdk\\platform-tools\\adb.exe\" logcat -s QuizGate\n```\n\nEventos clave:\n- `AccessibilityService connected` — accesibilidad activa\n- `blocking \u003cpkg\u003e → launching QuizActivity` — disparo del quiz\n- `consumed pending unlock for \u003cpkg\u003e` — desbloqueo exitoso\n- `in active session for \u003cpkg\u003e, skipping quiz` — sesión vigente, sin quiz\n- `leaving \u003cpkg\u003e → real app \u003cother\u003e, ending session` — sesión cerrada por cambio de app\n- `active call detected, bypassing quiz for \u003cpkg\u003e` — llamada en curso\n- `QuizActivity onCreate/onResume/onPause/onDestroy isFinishing=...` — ciclo de vida (útil si HyperOS está matando la activity: `isFinishing=false` = el sistema la mató)\n\n---\n\n## Notas sobre HyperOS / MIUI\n\n1. **Background Activity Launch**: WatchdogService + permiso \"popup en segundo plano\" son ambos necesarios. Faltar cualquiera de los dos hace que el quiz nunca aparezca o se cierre solo.\n2. **Auto-start**: si el service muere tras reiniciar el teléfono o tras unos minutos en idle, activa el auto-inicio en *Seguridad → Permisos → Autostart*.\n3. **Optimización de batería**: HyperOS reaplica restricciones agresivas. Si el comportamiento se degrada con el tiempo, revisa *Ajustes → Batería → Quiz Gate → Sin restricciones*.\n4. **WRITE_SECURE_SETTINGS** (para activar accesibilidad por adb) está bloqueado en HyperOS salvo que tengas la cuenta Mi vinculada y \"Security Settings\" en debugging habilitado.\n\n---\n\n## Limitaciones conocidas\n\n- Selección de preguntas es **random uniforme** del pool — no hay anti-repetición ni ponderación por fallos.\n- No hay persistencia de estadísticas de aciertos / fallos.\n- El listado de \"apps bloqueables\" es fijo (`KNOWN_APPS` en MainActivity); para añadir otra hay que tocar código.\n- Sin pruebas automatizadas.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodecsrayo%2Fquiz-gate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodecsrayo%2Fquiz-gate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodecsrayo%2Fquiz-gate/lists"}