From 3097cc730572ef57e710e6cb501e0486402afba2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 9 Sep 2026 21:17:53 +0000 Subject: [PATCH] fix(android): handle location permission races and optional camera --- .../workflows/f6-1-apply-android-lint-fix.yml | 75 ------------------- android-app/app/src/main/AndroidManifest.xml | 1 + .../com/korexlabs/dhinspeccion/ui/DhApp.kt | 18 +++-- .../korexlabs/dhinspeccion/ui/F3VisitRoot.kt | 22 +++--- .../dhinspeccion/ui/FieldFindingScreen.kt | 18 +++-- .../dhinspeccion/ui/MobileActsScreen.kt | 20 +++-- 6 files changed, 48 insertions(+), 106 deletions(-) delete mode 100644 .github/workflows/f6-1-apply-android-lint-fix.yml diff --git a/.github/workflows/f6-1-apply-android-lint-fix.yml b/.github/workflows/f6-1-apply-android-lint-fix.yml deleted file mode 100644 index 229e3fc..0000000 --- a/.github/workflows/f6-1-apply-android-lint-fix.yml +++ /dev/null @@ -1,75 +0,0 @@ -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 diff --git a/android-app/app/src/main/AndroidManifest.xml b/android-app/app/src/main/AndroidManifest.xml index 73bfee0..9b26cae 100644 --- a/android-app/app/src/main/AndroidManifest.xml +++ b/android-app/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ + diff --git a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/DhApp.kt b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/DhApp.kt index 424be1e..7cc4bd8 100644 --- a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/DhApp.kt +++ b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/DhApp.kt @@ -547,13 +547,17 @@ private suspend fun currentGeo(context: Context): GeoSnapshot = suspendCancellab } val source = CancellationTokenSource() val client = LocationServices.getFusedLocationProviderClient(context) - client.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, source.token) - .addOnSuccessListener { location -> - if (!continuation.isActive) return@addOnSuccessListener - if (location == null) continuation.resumeWithException(IllegalStateException("No se pudo obtener una ubicación GPS actual.")) - else continuation.resume(GeoSnapshot(location.latitude, location.longitude, location.accuracy.toDouble())) - } - .addOnFailureListener { if (continuation.isActive) continuation.resumeWithException(it) } + try { + client.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, source.token) + .addOnSuccessListener { location -> + if (!continuation.isActive) return@addOnSuccessListener + if (location == null) continuation.resumeWithException(IllegalStateException("No se pudo obtener una ubicación GPS actual.")) + else continuation.resume(GeoSnapshot(location.latitude, location.longitude, location.accuracy.toDouble())) + } + .addOnFailureListener { if (continuation.isActive) continuation.resumeWithException(it) } + } catch (error: SecurityException) { + if (continuation.isActive) continuation.resumeWithException(error) + } continuation.invokeOnCancellation { source.cancel() } } diff --git a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/F3VisitRoot.kt b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/F3VisitRoot.kt index 2ca8670..7c28d8d 100644 --- a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/F3VisitRoot.kt +++ b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/F3VisitRoot.kt @@ -727,16 +727,20 @@ private suspend fun currentF3Geo(context: Context): F3GeoSnapshot = suspendCance } val source = CancellationTokenSource() val client = LocationServices.getFusedLocationProviderClient(context) - client.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, source.token) - .addOnSuccessListener { location -> - if (!continuation.isActive) return@addOnSuccessListener - if (location == null) { - continuation.resumeWithException(IllegalStateException("No se pudo obtener una ubicación GPS actual.")) - } else { - continuation.resume(F3GeoSnapshot(location.latitude, location.longitude, location.accuracy.toDouble())) + try { + client.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, source.token) + .addOnSuccessListener { location -> + if (!continuation.isActive) return@addOnSuccessListener + if (location == null) { + continuation.resumeWithException(IllegalStateException("No se pudo obtener una ubicación GPS actual.")) + } else { + continuation.resume(F3GeoSnapshot(location.latitude, location.longitude, location.accuracy.toDouble())) + } } - } - .addOnFailureListener { if (continuation.isActive) continuation.resumeWithException(it) } + .addOnFailureListener { if (continuation.isActive) continuation.resumeWithException(it) } + } catch (error: SecurityException) { + if (continuation.isActive) continuation.resumeWithException(error) + } continuation.invokeOnCancellation { source.cancel() } } diff --git a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/FieldFindingScreen.kt b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/FieldFindingScreen.kt index acc6c63..9b4b882 100644 --- a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/FieldFindingScreen.kt +++ b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/FieldFindingScreen.kt @@ -389,13 +389,17 @@ private suspend fun currentFindingGeo(context: Context): FindingGeoSnapshot = su } val source = CancellationTokenSource() val client = LocationServices.getFusedLocationProviderClient(context) - client.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, source.token) - .addOnSuccessListener { location -> - if (!continuation.isActive) return@addOnSuccessListener - if (location == null) continuation.resumeWithException(IllegalStateException("No se pudo obtener una ubicación GPS actual.")) - else continuation.resume(FindingGeoSnapshot(location.latitude, location.longitude, location.accuracy.toDouble())) - } - .addOnFailureListener { if (continuation.isActive) continuation.resumeWithException(it) } + try { + client.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, source.token) + .addOnSuccessListener { location -> + if (!continuation.isActive) return@addOnSuccessListener + if (location == null) continuation.resumeWithException(IllegalStateException("No se pudo obtener una ubicación GPS actual.")) + else continuation.resume(FindingGeoSnapshot(location.latitude, location.longitude, location.accuracy.toDouble())) + } + .addOnFailureListener { if (continuation.isActive) continuation.resumeWithException(it) } + } catch (error: SecurityException) { + if (continuation.isActive) continuation.resumeWithException(error) + } continuation.invokeOnCancellation { source.cancel() } } diff --git a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/MobileActsScreen.kt b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/MobileActsScreen.kt index 2f2d3d9..d1d0749 100644 --- a/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/MobileActsScreen.kt +++ b/android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/MobileActsScreen.kt @@ -431,13 +431,17 @@ private suspend fun currentActSignatureGeo(context: Context): ActSignatureGeo = return@suspendCancellableCoroutine } val source = CancellationTokenSource() - LocationServices.getFusedLocationProviderClient(context) - .getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, source.token) - .addOnSuccessListener { location -> - if (!continuation.isActive) return@addOnSuccessListener - if (location == null) continuation.resumeWithException(IllegalStateException("Ubicación no disponible")) - else continuation.resume(ActSignatureGeo(location.latitude, location.longitude, location.accuracy.toDouble())) - } - .addOnFailureListener { if (continuation.isActive) continuation.resumeWithException(it) } + try { + LocationServices.getFusedLocationProviderClient(context) + .getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, source.token) + .addOnSuccessListener { location -> + if (!continuation.isActive) return@addOnSuccessListener + if (location == null) continuation.resumeWithException(IllegalStateException("Ubicación no disponible")) + else continuation.resume(ActSignatureGeo(location.latitude, location.longitude, location.accuracy.toDouble())) + } + .addOnFailureListener { if (continuation.isActive) continuation.resumeWithException(it) } + } catch (error: SecurityException) { + if (continuation.isActive) continuation.resumeWithException(error) + } continuation.invokeOnCancellation { source.cancel() } }