Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 2 additions & 2 deletions .github/workflows/build-apk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ jobs:
- name: Setup Gradle
uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
with:
# 8.11.1 = mínimo para AGP 8.9 (compileSdk 36, Appodeal adapters)
gradle-version: '8.11.1'
# 8.11.1 = mínimo para AGP 8.9 (compileSdk 36)
gradle-version: '8.13'

# android-actions/setup-android@v3 puede fallar por timeouts transitorios
# del runner. Se prueba primero; si falla, se asume que ubuntu-latest ya
Expand Down
151 changes: 151 additions & 0 deletions .github/workflows/echo-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
name: Echo module (rama)

# Workflow de iteración para el injerto del reproductor Echo Nightly.
# Solo compila (no publica release) y se dispara en ramas de trabajo, para
# poder iterar sin tocar main ni romper el pipeline de APK.
on:
push:
branches:
- 'feat/**'
- 'echo/**'
workflow_dispatch:

permissions:
contents: read

jobs:
build:
name: Compilar :echo + :app (debug)
runs-on: ubuntu-latest
timeout-minutes: 45

steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Setup Java JDK 17
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
with:
java-version: '17'
distribution: 'temurin'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
with:
gradle-version: '8.13'

- name: Setup Android SDK
id: setup-android
uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1
continue-on-error: true

- name: Accept SDK licenses
run: |
SDKMANAGER="$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager"
if [ -f "$SDKMANAGER" ]; then
yes | "$SDKMANAGER" --licenses > /dev/null 2>&1 || true
fi

- name: Compile :echo (debug)
id: echo
working-directory: apk/android
run: |
set +e
gradle :echo:assembleDebug --stacktrace > "$RUNNER_TEMP/echo-build.log" 2>&1
rc=$?
if [ $rc -eq 0 ]; then : > "$RUNNER_TEMP/echo-ok"; fi
echo "== :echo:assembleDebug rc=$rc =="
if [ -f "$RUNNER_TEMP/echo-build.log" ]; then tail -n 40 "$RUNNER_TEMP/echo-build.log"; fi
exit $rc

- name: Compile :app (debug, con :echo)
working-directory: apk/android
run: |
set +e
gradle :app:assembleDebug --stacktrace > "$RUNNER_TEMP/app-build.log" 2>&1
rc=$?
echo "gradle_rc=$rc" >> "$GITHUB_ENV"
if [ $rc -eq 0 ]; then : > "$RUNNER_TEMP/app-build-ok"; fi
exit $rc

- name: Resumen de errores
if: always()
run: |
ERRPAT="^e: |error:|Error:|FAILURE:|What went wrong|Execution failed for task|Caused by:|Could not (resolve|find|determine|download|open)|Manifest merger|is also present at|needs to be explicitly specified|Suggestion:|Duplicate class|Duplicate resources|Duplicate entry|AAPT|Android resource linking failed"
MR="app/build/outputs/logs/manifest-merger-debug-report.txt"
for L in "$RUNNER_TEMP/app-build.log" "$RUNNER_TEMP/echo-build.log"; do
[ -f "$L" ] || continue
echo "===== $L (ERRORES) ====="
grep -nE -A2 "$ERRPAT" "$L" | head -n 80 || true
done
if [ -f "$MR" ]; then
echo "===== $MR (ERRORES) ====="
grep -nE "Error|error|ERROR|also present|Suggestion|needs to be explicitly" "$MR" | head -n 60 || true
fi
# Anotaciones de check públicas (leíbles por la API sin admin), app
# primero y sin warnings, para poder diagnosticar sin acceso al log.
{
for L in "$RUNNER_TEMP/app-build.log" "$RUNNER_TEMP/echo-build.log"; do
[ -f "$L" ] || continue
grep -nE "$ERRPAT" "$L" 2>/dev/null | sed "s|^|${L##*/}: |"
done
if [ -f "$MR" ]; then
grep -nE "Error:|is also present at|needs to be explicitly specified|Suggestion:" "$MR" 2>/dev/null | sed 's|^|merger: |'
fi
} > "$RUNNER_TEMP/errs.txt" || true
if [ -f "$RUNNER_TEMP/errs.txt" ]; then
head -n 14 "$RUNNER_TEMP/errs.txt" | while IFS= read -r line; do
clean=$(printf '%s' "$line" | tr -d '\r' | sed 's/%/%25/g' | cut -c1-500)
echo "::error::$clean"
done
fi
exit 0

- name: Fail si :app no compila
if: always()
run: |
if [ -f "$RUNNER_TEMP/app-build-ok" ]; then echo "compile :app OK"; exit 0; fi
echo "::error:::app:assembleDebug falló (ver resumen/logs)"
exit 1

- name: Verificar componentes media3 unicos
if: always()
run: |
# media3 exige EXACTAMENTE un receptor ACTION_MEDIA_BUTTON
# (MediaSessionLegacyStub) y un servicio MediaLibraryService
# (MediaButtonReceiver). Con dos de cualquiera arranca el crash:
# "Expected 1 ... found 2". Se valida sobre el manifiesto fusionado
# para no depender de probar en dispositivo.
MM=$(find apk/android/app/build/intermediates -name AndroidManifest.xml 2>/dev/null | grep -iE 'merged_manifest' | head -n 1)
if [ -z "$MM" ]; then
echo "::warning::No se encontró el manifiesto fusionado; se omite la verificación."
exit 0
fi
echo "Manifiesto fusionado: $MM"
RECV=$(grep -c '<action android:name="android.intent.action.MEDIA_BUTTON"' "$MM" || true)
SVC=$(grep -c '<action android:name="androidx.media3.session.MediaLibraryService"' "$MM" || true)
echo "receptores MEDIA_BUTTON=$RECV servicios MediaLibraryService=$SVC"
rc=0
[ "$RECV" = "1" ] || { echo "::error::media3 necesita exactamente 1 receptor ACTION_MEDIA_BUTTON (hay $RECV); PlayerService crashea al crearse"; rc=1; }
[ "$SVC" = "1" ] || { echo "::error::media3 necesita exactamente 1 servicio MediaLibraryService (hay $SVC); crash al pulsar boton de medios"; rc=1; }
exit $rc

- name: Subir logs
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: echo-build-logs
path: |
/home/runner/work/_temp/echo-build.log
/home/runner/work/_temp/app-build.log
if-no-files-found: ignore
retention-days: 5

- name: Subir APK debug (prueba en dispositivo)
if: success()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: codehub-echo-debug-apk
path: apk/android/app/build/outputs/apk/debug/app-debug.apk
if-no-files-found: error
retention-days: 14
9 changes: 5 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,9 @@ Toda edición relacionada con publicidad (AdSense web / AdMob app) debe seguir
usar `gtag('config', 'ca-pub-3780093322926832')`.
2. **No quitar** el Consent Mode v2 (default `denied` + `wait_for_update`) ni el
guard `CodeHubNative`/`__apkNative` que impide AdSense dentro del WebView/APK.
3. La app monetiza con **Appodeal 4.4.0 (Mediation Only)** bajo el gate de UMP
(`ConsentManager.java` → `AppodealManager.init`): AdMob (adaptador), AppLovin,
Unity Ads, Vungle/Liftoff y BidMachine. NUNCA inyectar AdSense en el WebView.
3. La app monetiza con **Unity Ads directo** bajo el gate de UMP
(`ConsentManager.java` → `UnityAdsManager.init`): banner, interstitial y
rewarded. Game ID y placements (`BP_*`) viven en `UnityAdsManager.java`.
NUNCA inyectar AdSense en el WebView.
4. Mantener `ads.txt` y `app-ads.txt` servidos por el host real (añadir el
seller de Appodeal en el `app-ads.txt` de la app).
seller de Unity Ads en el `app-ads.txt` de la app).
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,18 @@ B2_BUCKET_NAME # Backblaze B2
TELEGRAM_BOT_TOKEN # Bot Telegram (canónico; alias legacy: TELEGRAM_TOKEN)
TELEGRAM_CHAT_ID # Tu chat ID
BACKEND_URL # https://codehub-98s6.onrender.com

# ── Recompensas (rewarded ads, ver docs/REWARDED-ADS.md) ──
REWARD_POSTBACK_SECRET # Secreto único del postback (cualquier red)
# REWARD_POSTBACK_SECRETS # JSON por red: {"monetag":"<s>","offerwall":"<s>"}
# MONETAG_POSTBACK_SECRET # atajo por red (también ADGEM_/AYET_/BITLABS_)
REWARD_HMAC_SECRET # Firma de nonces (def. ADMIN_KEY)
REWARD_AMOUNTS # JSON {"default":1,"bonus":5}
REWARD_DAILY_CAP # Tope de créditos/usuario/día (def. 50)
REWARD_AD_UNIT # Unidad por defecto (def. "default")
REWARD_USER_ID_SOURCES # CSV redes vía user_id: "adgem,bitlabs"
# App: Unity Ads — Game ID y placements en
# apk/android/app/src/main/java/com/codehub/app/UnityAdsManager.java
```

## 🤖 AutoScript
Expand Down
29 changes: 11 additions & 18 deletions apk/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
// minSdk 24 < 26 → necesario para las APIs `java.time` que usa el
// adaptador de Appodeal/AdMob en runtime sin subir el minSdk.
// minSdk 24 < 26 → necesario para las APIs `java.time` que usan
// (en runtime) las librerías del stack de anuncios/analytics sin subir
// el minSdk (ver AlexParser/AutoLauncher y el fork de Echo).
coreLibraryDesugaringEnabled true
}
}
Expand All @@ -59,6 +60,9 @@ kotlin {
}

dependencies {
// Reproductor completo de Echo Nightly (see apk/android/echo). Se lanza
// desde el panel Música de CodeHub (CodeHubBridge.openMusic).
implementation project(':echo')
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.webkit:webkit:1.9.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
Expand All @@ -73,20 +77,9 @@ dependencies {
// SDK de Google (com.google.android.ump), distinto de play-services-ads.
// Debe consultarse antes de pedir anuncios en regiones exigidas (EEE/UK/Canadá).
implementation 'com.google.android.ump:user-messaging-platform:3.2.0'
// Appodeal 4.4.0 (Mediation Only) — sustituye las llamadas directas a
// AdMob (BannerAdManager/RewardedAdManager/InterstitialAdManager). El
// adaptador de AdMob trae play-services-ads y exige compileSdk 36; las
// unidades de banner/rewarded/interstitial se configuran en el panel de
// Appodeal (APP_KEY en AppodealManager). Repo: artifactory.appodeal.com.
implementation 'com.appodeal.ads.sdk:core:4.4.0'
implementation 'com.appodeal.ads.sdk.adapters:admob:25.2.0.0'
implementation 'com.appodeal.ads.sdk.adapters:applovin:13.6.3.0'
implementation 'com.appodeal.ads.sdk.adapters:unity_ads:4.17.0.0'
implementation 'com.appodeal.ads.sdk.adapters:vungle:7.7.4.0'
implementation 'com.appodeal.ads.sdk.adapters:bidmachine:3.7.1.0'
// iab — puente TCF: Appodeal lee los strings IABTCF que escribe UMP
// (ConsentManager.java) para respetar la decisión del usuario.
implementation 'com.appodeal.ads.sdk.adapters:iab:1.8.1.1'
// Unity Ads (SDK directo, sin mediación). Game ID y placements se declaran
// en UnityAdsManager.java; el banner vive en la franja reservada del layout.
implementation 'com.unity3d.ads:unity-ads:4.20.0'
// Desugaring de librerías java.time (ver compileOptions arriba).
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
implementation 'com.google.android.gms:play-services-location:21.3.0'
Expand All @@ -109,8 +102,8 @@ dependencies {
// Licencia de las extensiones: UPL-1.0 / GPLv3 - ver nota en apk/README.md.
// Se excluye protobuf-java: echo common lo declara en su POM pero el AAR
// common-android (282 clases, verificado) NO referencia com.google.protobuf
// en su bytecode; el stack Appodeal/AdMob trae protobuf-javalite:3.25.6 y
// ambos artefactos comparten clases com.google.protobuf.* → duplicadas.
// en su bytecode. El exclude es defensivo para no arrastrar una copia
// duplicada de com.google.protobuf.* junto a otras dependencias.
implementation('dev.brahmkshatriya.echo:common:1.0.0') {
exclude group: 'com.google.protobuf', module: 'protobuf-java'
}
Expand Down
25 changes: 12 additions & 13 deletions apk/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -111,28 +111,27 @@
android:windowSoftInputMode="adjustResize">
</activity>

<!-- Servicio de reproducción en segundo plano (media3 MediaLibraryService).
Mantiene el MusicPlayer singleton con notificación de medios,
control desde bloqueo/headset y browse para Android Auto. -->
<!-- Servicio de reproducción en segundo plano del player nativo.
Se arranca SIEMPRE de forma explícita (MusicPlayer.ensureService →
startForegroundService(Intent(...))) y MusicPlayerActivity lee el
singleton, así que NO declara androidx.media3.session.MediaLibraryService.

media3 sólo admite UN MediaSessionService por app: su
MediaButtonReceiver resuelve el servicio por esa acción y lanza
"Expected 1 service that handles …, found 2" si hay dos; y
MediaSessionLegacyStub exige exactamente un receptor de
ACTION_MEDIA_BUTTON. El reproductor Echo (:echo, PlayerService) es
ahora el principal y aporta ambos componentes; CodeHub solo
mantiene el servicio nativo, arrancado explícitamente. -->
<service
android:name=".music.MusicPlaybackService"
android:exported="true"
android:foregroundServiceType="mediaPlayback">
<intent-filter>
<action android:name="androidx.media3.session.MediaLibraryService" />
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>

<!-- Botones de medios globales (headset / bluetooth) → MediaSessionService. -->
<receiver
android:name="androidx.media3.session.MediaButtonReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>

<!-- Widget (4x1) del reproductor de música. -->
<receiver
android:name=".music.MusicWidget"
Expand Down
Loading
Loading