diff --git a/.github/workflows/f6-1-apply-android-lint-fix.yml b/.github/workflows/f6-1-apply-android-lint-fix.yml
new file mode 100644
index 0000000..229e3fc
--- /dev/null
+++ b/.github/workflows/f6-1-apply-android-lint-fix.yml
@@ -0,0 +1,75 @@
+name: F6.1 apply Android lint fix
+
+on:
+ push:
+ branches:
+ - 'release/f6-1-presentation-ready'
+ paths:
+ - '.github/workflows/f6-1-apply-android-lint-fix.yml'
+
+permissions:
+ contents: write
+
+jobs:
+ apply:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: release/f6-1-presentation-ready
+ fetch-depth: 0
+
+ - name: Apply targeted runtime fixes
+ run: |
+ python - <<'PY'
+ from pathlib import Path
+
+ replacements = {
+ 'android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/DhApp.kt': (
+ ''' val source = CancellationTokenSource()\n val client = LocationServices.getFusedLocationProviderClient(context)\n client.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, source.token)\n .addOnSuccessListener { location ->\n if (!continuation.isActive) return@addOnSuccessListener\n if (location == null) continuation.resumeWithException(IllegalStateException("No se pudo obtener una ubicación GPS actual."))\n else continuation.resume(GeoSnapshot(location.latitude, location.longitude, location.accuracy.toDouble()))\n }\n .addOnFailureListener { if (continuation.isActive) continuation.resumeWithException(it) }\n continuation.invokeOnCancellation { source.cancel() }''',
+ ''' val source = CancellationTokenSource()\n val client = LocationServices.getFusedLocationProviderClient(context)\n try {\n client.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, source.token)\n .addOnSuccessListener { location ->\n if (!continuation.isActive) return@addOnSuccessListener\n if (location == null) continuation.resumeWithException(IllegalStateException("No se pudo obtener una ubicación GPS actual."))\n else continuation.resume(GeoSnapshot(location.latitude, location.longitude, location.accuracy.toDouble()))\n }\n .addOnFailureListener { if (continuation.isActive) continuation.resumeWithException(it) }\n } catch (error: SecurityException) {\n if (continuation.isActive) continuation.resumeWithException(error)\n }\n continuation.invokeOnCancellation { source.cancel() }''',
+ ),
+ 'android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/F3VisitRoot.kt': (
+ ''' val source = CancellationTokenSource()\n val client = LocationServices.getFusedLocationProviderClient(context)\n client.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, source.token)\n .addOnSuccessListener { location ->\n if (!continuation.isActive) return@addOnSuccessListener\n if (location == null) {\n continuation.resumeWithException(IllegalStateException("No se pudo obtener una ubicación GPS actual."))\n } else {\n continuation.resume(F3GeoSnapshot(location.latitude, location.longitude, location.accuracy.toDouble()))\n }\n }\n .addOnFailureListener { if (continuation.isActive) continuation.resumeWithException(it) }\n continuation.invokeOnCancellation { source.cancel() }''',
+ ''' val source = CancellationTokenSource()\n val client = LocationServices.getFusedLocationProviderClient(context)\n try {\n client.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, source.token)\n .addOnSuccessListener { location ->\n if (!continuation.isActive) return@addOnSuccessListener\n if (location == null) {\n continuation.resumeWithException(IllegalStateException("No se pudo obtener una ubicación GPS actual."))\n } else {\n continuation.resume(F3GeoSnapshot(location.latitude, location.longitude, location.accuracy.toDouble()))\n }\n }\n .addOnFailureListener { if (continuation.isActive) continuation.resumeWithException(it) }\n } catch (error: SecurityException) {\n if (continuation.isActive) continuation.resumeWithException(error)\n }\n continuation.invokeOnCancellation { source.cancel() }''',
+ ),
+ 'android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/FieldFindingScreen.kt': (
+ ''' val source = CancellationTokenSource()\n val client = LocationServices.getFusedLocationProviderClient(context)\n client.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, source.token)\n .addOnSuccessListener { location ->\n if (!continuation.isActive) return@addOnSuccessListener\n if (location == null) continuation.resumeWithException(IllegalStateException("No se pudo obtener una ubicación GPS actual."))\n else continuation.resume(FindingGeoSnapshot(location.latitude, location.longitude, location.accuracy.toDouble()))\n }\n .addOnFailureListener { if (continuation.isActive) continuation.resumeWithException(it) }\n continuation.invokeOnCancellation { source.cancel() }''',
+ ''' val source = CancellationTokenSource()\n val client = LocationServices.getFusedLocationProviderClient(context)\n try {\n client.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, source.token)\n .addOnSuccessListener { location ->\n if (!continuation.isActive) return@addOnSuccessListener\n if (location == null) continuation.resumeWithException(IllegalStateException("No se pudo obtener una ubicación GPS actual."))\n else continuation.resume(FindingGeoSnapshot(location.latitude, location.longitude, location.accuracy.toDouble()))\n }\n .addOnFailureListener { if (continuation.isActive) continuation.resumeWithException(it) }\n } catch (error: SecurityException) {\n if (continuation.isActive) continuation.resumeWithException(error)\n }\n continuation.invokeOnCancellation { source.cancel() }''',
+ ),
+ 'android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/MobileActsScreen.kt': (
+ ''' val source = CancellationTokenSource()\n LocationServices.getFusedLocationProviderClient(context)\n .getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, source.token)\n .addOnSuccessListener { location ->\n if (!continuation.isActive) return@addOnSuccessListener\n if (location == null) continuation.resumeWithException(IllegalStateException("Ubicación no disponible"))\n else continuation.resume(ActSignatureGeo(location.latitude, location.longitude, location.accuracy.toDouble()))\n }\n .addOnFailureListener { if (continuation.isActive) continuation.resumeWithException(it) }\n continuation.invokeOnCancellation { source.cancel() }''',
+ ''' val source = CancellationTokenSource()\n try {\n LocationServices.getFusedLocationProviderClient(context)\n .getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, source.token)\n .addOnSuccessListener { location ->\n if (!continuation.isActive) return@addOnSuccessListener\n if (location == null) continuation.resumeWithException(IllegalStateException("Ubicación no disponible"))\n else continuation.resume(ActSignatureGeo(location.latitude, location.longitude, location.accuracy.toDouble()))\n }\n .addOnFailureListener { if (continuation.isActive) continuation.resumeWithException(it) }\n } catch (error: SecurityException) {\n if (continuation.isActive) continuation.resumeWithException(error)\n }\n continuation.invokeOnCancellation { source.cancel() }''',
+ ),
+ }
+
+ for file_name, (before, after) in replacements.items():
+ path = Path(file_name)
+ text = path.read_text()
+ count = text.count(before)
+ if count != 1:
+ raise SystemExit(f'{file_name}: expected exactly one GPS block, found {count}')
+ path.write_text(text.replace(before, after))
+
+ manifest = Path('android-app/app/src/main/AndroidManifest.xml')
+ text = manifest.read_text()
+ before = ' \n'
+ after = before + ' \n'
+ if text.count(before) != 1:
+ raise SystemExit('AndroidManifest.xml: CAMERA permission block not unique')
+ if 'android.hardware.camera' not in text:
+ manifest.write_text(text.replace(before, after))
+
+ # Self-delete so this one-shot workflow never ships in the release branch.
+ Path('.github/workflows/f6-1-apply-android-lint-fix.yml').unlink()
+ PY
+
+ - name: Commit targeted fixes
+ run: |
+ set -Eeuo pipefail
+ git config user.name 'github-actions[bot]'
+ git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
+ git add android-app .github/workflows/f6-1-apply-android-lint-fix.yml
+ git diff --cached --check
+ git commit -m 'fix(android): handle location permission races and optional camera'
+ git push origin HEAD:release/f6-1-presentation-ready