From c31532ebbceb042fa2c010e1c09de33f3efd0871 Mon Sep 17 00:00:00 2001 From: SimpleTest Date: Mon, 3 Aug 2026 23:53:14 +0300 Subject: [PATCH] Enforce Play location declaration contract --- .../location-and-fgs-declaration.md | 112 ++++++++++++++++++ android/play-store/release-checklist.md | 17 ++- ...google-play-pre-upload-audit-2026-08-03.md | 9 +- ...oogle-play-release-candidate-2026-08-03.md | 4 +- scripts/android-play-policy-check.sh | 98 +++++++++++++++ scripts/android-release-build.sh | 1 + scripts/quality.sh | 1 + 7 files changed, 234 insertions(+), 8 deletions(-) create mode 100644 android/play-store/location-and-fgs-declaration.md create mode 100755 scripts/android-play-policy-check.sh diff --git a/android/play-store/location-and-fgs-declaration.md b/android/play-store/location-and-fgs-declaration.md new file mode 100644 index 0000000..05b42a1 --- /dev/null +++ b/android/play-store/location-and-fgs-declaration.md @@ -0,0 +1,112 @@ +# Google Play location and foreground-service declaration + +This file is the source copy for Play Console. It describes the exact current +Android behavior; it is not evidence that Google Play has approved the feature. +Reconcile every answer with the AAB selected for release. + +## Manifest and permission facts + +- Package: `org.whoneedhelp.mobile` +- Target SDK: `37` +- Foreground service type: `location` +- Foreground-service permissions: `FOREGROUND_SERVICE` and + `FOREGROUND_SERVICE_LOCATION` +- Runtime location permissions: `ACCESS_COARSE_LOCATION` and + `ACCESS_FINE_LOCATION` +- The app does not request `ACCESS_BACKGROUND_LOCATION`. +- The app also declares `USE_LOCATION_BUTTON` for the separate one-time + foreground action that places a request or activity point. It must not use + `onlyForLocationButton`, because the distinct live-location flow also needs + precise location after the user starts the foreground service. + +## Foreground-service declaration copy + +**Use case:** User-initiated location sharing. + +**Feature using the service:** + +> During an active mutual-aid assignment, an accepted requester or helper can +> explicitly start live location sharing with the matched participant. Who Need +> Help starts a location foreground service only after the user opens the active +> assignment, taps Share live location, reads the prominent disclosure, and +> grants Android location permission. A persistent notification remains visible +> for the full session and includes a Stop sharing action. + +**Why the task must start immediately:** + +> The participant starts sharing to coordinate an active, time-sensitive handoff. +> Deferring the first update would show the matched participant stale or missing +> position information at the moment the user deliberately requested sharing. + +**Impact if Android interrupts the task:** + +> New location updates stop. The app does not silently restart sharing. The user +> must return to the active assignment and start it again. The matched participant +> no longer receives a current position. + +**How it ends:** + +- The user taps Stop sharing in the app or in the persistent notification. +- Cancelling, withdrawing from, completing, or otherwise leaving the active + assignment stops the native service through the server-driven terminal state. +- The service also stops on an authorization or missing-assignment response. +- Stopping removes the current raw location from the server. Limited derived + safety evidence can remain as stated in the Privacy Policy. +- Sharing is never started from boot, a background receiver, a push notification, + or an unattended scheduled task. + +## Play Console answers + +Use these only when the current Console wording matches the stated fact: + +- Foreground service type: **Location** +- Closest preset use case: **Background Location Updates — User-initiated + location sharing** +- Core user benefit: safe coordination between the two people already matched + for an active help request +- Persistent notification: shown after the explicit in-app start and prominent + disclosure, with a user-visible Stop action +- Background-location runtime permission declared: **No** +- Location foreground service declared: **Yes** + +Google Play requires a foreground-service declaration for apps targeting +Android 14 or newer. Do not describe this feature as passive, continuous, +always-on, emergency, medical, or hidden tracking. + +## Video evidence script + +Record one short, unlisted video from the exact Play candidate. Keep the phone +screen readable and show the complete trigger path without cuts that hide a +permission or disclosure screen. + +1. Start on an active synthetic request in which the signed-in reviewer is an + accepted requester or helper. +2. Scroll to live-location controls and tap **Share live location**. +3. Pause on the prominent disclosure long enough to read what is collected, + who receives it, minimized-app use, deletion, and the Stop action. +4. Tap **Continue and share** and grant the Android location permission. +5. Show the persistent **Sharing live location** system notification. +6. Press Home so the app is minimized; show that the notification remains + visible and that the matched browser receives a current synthetic position. +7. Tap **Stop sharing** in the notification. +8. Return to the request and show that sharing is stopped and the live marker is + no longer available. + +Do not use a real home address, real medical information, chat text, email, +handover code, access token, or another person's location in the recording. + +## Pre-submission evidence + +- Run `./scripts/android-play-policy-check.sh`. +- Run the signed release build and retain its manifest/package/signing reports. +- Repeat start, Home/minimize, notification, Stop, and raw-position deletion on + a Play-delivered internal-test install after Play App Signing is available. +- Confirm the Privacy Policy, Data Safety form, store listing, disclosure, and + Play Console declaration all describe the same behavior. + +Official references checked on 2026-08-03: + +- https://support.google.com/googleplay/android-developer/answer/13392821 +- https://support.google.com/googleplay/android-developer/answer/9799150 +- https://support.google.com/googleplay/android-developer/answer/16909972 +- https://developer.android.com/develop/background-work/services/fgs/service-types diff --git a/android/play-store/release-checklist.md b/android/play-store/release-checklist.md index d2b75db..13c8559 100644 --- a/android/play-store/release-checklist.md +++ b/android/play-store/release-checklist.md @@ -58,11 +58,18 @@ - [ ] Account deletion questions and external URL completed. - [ ] Government/news/financial/health declarations answered from actual app behavior; do not describe the app as a medical service. -- [ ] Foreground-service/location declarations completed from the exact AAB if - Play Console asks. The current source requests coarse/fine foreground - location and a location foreground service; it does not declare - `ACCESS_BACKGROUND_LOCATION`. Re-check the uploaded artifact rather than - inferring the Console form from this note. +- [ ] Complete the mandatory Play Console foreground-service declaration for + the `location` service used by the exact AAB. +- [ ] Upload the unlisted demonstration video showing the user-triggered start, + prominent disclosure, Android permission, persistent notification, + minimized-app operation, and Stop action. +- [ ] Reconcile any target-SDK-37 persistent precise-location declaration shown + by Play Console with the exact artifact. The app uses a user-started + location foreground service and does not declare + `ACCESS_BACKGROUND_LOCATION`; do not answer that it requests the + background-location runtime permission. +- [ ] Use and verify the prepared declaration copy in + `location-and-fgs-declaration.md`. ## Testing diff --git a/docs/google-play-pre-upload-audit-2026-08-03.md b/docs/google-play-pre-upload-audit-2026-08-03.md index 601e76b..8bc79b0 100644 --- a/docs/google-play-pre-upload-audit-2026-08-03.md +++ b/docs/google-play-pre-upload-audit-2026-08-03.md @@ -52,8 +52,13 @@ require Play Console. It contains no account credentials or signing keys. `scripts/prepare-play-review.sh`. Verify both roles and every reviewer instruction from a clean Play-delivered installation. - Complete App content: App access, Ads, Content rating, Target audience, - News-app declaration, Data Safety, background-location declaration if Play - presents it, and the account-deletion URL. + News-app declaration, Data Safety, foreground-service location declaration, + any target-SDK-37 persistent precise-location declaration actually presented + by Play, and the account-deletion URL. Use + `android/play-store/location-and-fgs-declaration.md`; do not claim the app + requests `ACCESS_BACKGROUND_LOCATION`. +- Record and upload the foreground-service demonstration video from the exact + candidate using the prepared evidence script. - Recheck the store listing, screenshots, support contact, and privacy-policy URL in Play Console against the prepared files under `android/play-store/`. diff --git a/docs/google-play-release-candidate-2026-08-03.md b/docs/google-play-release-candidate-2026-08-03.md index fda0907..9108096 100644 --- a/docs/google-play-release-candidate-2026-08-03.md +++ b/docs/google-play-release-candidate-2026-08-03.md @@ -86,7 +86,9 @@ The remaining Console sequence is: 2. Save/publish the internal release and obtain the Play App Signing SHA-1 and SHA-256 from **Test and release → Setup → App signing**. 3. Complete the prepared store listing and App content sections using - `android/play-store/` and `android/store-assets/`. + `android/play-store/` and `android/store-assets/`, including the mandatory + location foreground-service declaration and demonstration video described + in `android/play-store/location-and-fgs-declaration.md`. 4. Install the Play-delivered build from the internal-test opt-in link and repeat the production-origin, sign-in, notification, location, and App Link smoke tests. diff --git a/scripts/android-play-policy-check.sh b/scripts/android-play-policy-check.sh new file mode 100755 index 0000000..46149d4 --- /dev/null +++ b/scripts/android-play-policy-check.sh @@ -0,0 +1,98 @@ +#!/bin/sh +set -eu + +ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) +manifest="$ROOT/android/app/src/main/AndroidManifest.xml" +english_strings="$ROOT/android/app/src/main/res/values/strings.xml" +russian_strings="$ROOT/android/app/src/main/res/values-ru/strings.xml" +ukrainian_strings="$ROOT/android/app/src/main/res/values-uk/strings.xml" +english_listing="$ROOT/android/play-store/store-listing-en-US.md" +russian_listing="$ROOT/android/play-store/store-listing-ru-RU.md" +ukrainian_listing="$ROOT/android/play-store/store-listing-uk-UA.md" +declaration="$ROOT/android/play-store/location-and-fgs-declaration.md" + +require_literal() { + file=$1 + value=$2 + description=$3 + + if ! grep -Fq "$value" "$file"; then + echo "Android Play policy check failed: $description" >&2 + echo "Missing from ${file#"$ROOT/"}: $value" >&2 + exit 1 + fi +} + +for permission in \ + android.permission.ACCESS_COARSE_LOCATION \ + android.permission.ACCESS_FINE_LOCATION \ + android.permission.USE_LOCATION_BUTTON \ + android.permission.FOREGROUND_SERVICE \ + android.permission.FOREGROUND_SERVICE_LOCATION; do + require_literal \ + "$manifest" \ + "android:name=\"$permission\"" \ + "the manifest no longer declares $permission" +done + +require_literal \ + "$manifest" \ + 'android:name=".TrackingService"' \ + 'the native live-location service is missing' +require_literal \ + "$manifest" \ + 'android:foregroundServiceType="location"' \ + 'TrackingService is not declared as a location foreground service' + +if grep -Fq 'android.permission.ACCESS_BACKGROUND_LOCATION' "$manifest"; then + echo "Android Play policy check failed: ACCESS_BACKGROUND_LOCATION was added." >&2 + echo "The reviewed flow starts a user-visible location foreground service from the foreground; adding background permission requires a new policy and product review." >&2 + exit 1 +fi + +if grep -Fq 'onlyForLocationButton' "$manifest"; then + echo "Android Play policy check failed: onlyForLocationButton is incompatible with the separate live-location foreground-service flow." >&2 + exit 1 +fi + +for strings in "$english_strings" "$russian_strings" "$ukrainian_strings"; do + require_literal "$strings" 'name="tracking_disclosure_title"' \ + 'a locale is missing the live-location disclosure title' + require_literal "$strings" 'name="tracking_disclosure_detail"' \ + 'a locale is missing the live-location disclosure detail' + require_literal "$strings" 'name="tracking_disclosure_continue"' \ + 'a locale is missing the affirmative live-location action' + require_literal "$strings" 'name="tracking_stop_action"' \ + 'a locale is missing the persistent-notification Stop action' +done + +for phrase in \ + 'collects and sends your precise location' \ + 'matched requester or helper' \ + 'background when the app is minimized or not in use' \ + 'persistent notification remains visible' \ + 'current raw position is then deleted'; do + require_literal "$english_strings" "$phrase" \ + "the English prominent disclosure no longer states: $phrase" +done + +require_literal "$english_listing" 'including in the background while the app is minimized or not in use' \ + 'the English store listing no longer discloses minimized-app location sharing' +require_literal "$russian_listing" 'в том числе в фоновом режиме' \ + 'the Russian store listing no longer discloses background location sharing' +require_literal "$ukrainian_listing" 'зокрема у фоновому режимі' \ + 'the Ukrainian store listing no longer discloses background location sharing' + +# These are literal Markdown fragments; the backticks are not shell syntax. +# shellcheck disable=SC2016 +for phrase in \ + 'Foreground service type: `location`' \ + 'does not request `ACCESS_BACKGROUND_LOCATION`' \ + 'User-initiated location sharing' \ + 'Persistent notification' \ + 'Video evidence script'; do + require_literal "$declaration" "$phrase" \ + "the Play Console declaration guide is incomplete: $phrase" +done + +echo "Android Play location/foreground-service policy contract passed." diff --git a/scripts/android-release-build.sh b/scripts/android-release-build.sh index 31aa41b..8bdc272 100755 --- a/scripts/android-release-build.sh +++ b/scripts/android-release-build.sh @@ -25,6 +25,7 @@ set -a set +a "$ROOT/scripts/validate-android-environment.sh" "$ENV_FILE" production +"$ROOT/scripts/android-play-policy-check.sh" : "${WNH_BASE_URL:?Set WNH_BASE_URL in the selected environment file}" : "${WNH_TRACKING_MIN_TIME_MS:?Set WNH_TRACKING_MIN_TIME_MS in the selected environment file}" diff --git a/scripts/quality.sh b/scripts/quality.sh index 285d490..3670288 100755 --- a/scripts/quality.sh +++ b/scripts/quality.sh @@ -400,6 +400,7 @@ fi test "$(sha256sum "$credential_env" | awk '{print $1}')" = "$credential_hash" echo "Checking Android environment isolation" +./scripts/android-play-policy-check.sh >/dev/null android_fingerprint=D7:C4:F1:12:4D:F4:68:E5:B3:54:DE:D8:96:E8:01:51:29:41:F1:8A:71:0C:18:B0:E7:98:AA:2B:81:DA:11:DF android_env="$scan_dir/android-development.env" printf '%s\n' \