Enforce Play location declaration contract

This commit is contained in:
SimpleTest 2026-08-03 23:53:14 +03:00
parent de60e6e54c
commit c31532ebbc
7 changed files with 234 additions and 8 deletions

View File

@ -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

View File

@ -58,11 +58,18 @@
- [ ] Account deletion questions and external URL completed. - [ ] Account deletion questions and external URL completed.
- [ ] Government/news/financial/health declarations answered from actual app - [ ] Government/news/financial/health declarations answered from actual app
behavior; do not describe the app as a medical service. behavior; do not describe the app as a medical service.
- [ ] Foreground-service/location declarations completed from the exact AAB if - [ ] Complete the mandatory Play Console foreground-service declaration for
Play Console asks. The current source requests coarse/fine foreground the `location` service used by the exact AAB.
location and a location foreground service; it does not declare - [ ] Upload the unlisted demonstration video showing the user-triggered start,
`ACCESS_BACKGROUND_LOCATION`. Re-check the uploaded artifact rather than prominent disclosure, Android permission, persistent notification,
inferring the Console form from this note. 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 ## Testing

View File

@ -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 `scripts/prepare-play-review.sh`. Verify both roles and every reviewer
instruction from a clean Play-delivered installation. instruction from a clean Play-delivered installation.
- Complete App content: App access, Ads, Content rating, Target audience, - Complete App content: App access, Ads, Content rating, Target audience,
News-app declaration, Data Safety, background-location declaration if Play News-app declaration, Data Safety, foreground-service location declaration,
presents it, and the account-deletion URL. 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 - Recheck the store listing, screenshots, support contact, and privacy-policy
URL in Play Console against the prepared files under `android/play-store/`. URL in Play Console against the prepared files under `android/play-store/`.

View File

@ -86,7 +86,9 @@ The remaining Console sequence is:
2. Save/publish the internal release and obtain the Play App Signing SHA-1 and 2. Save/publish the internal release and obtain the Play App Signing SHA-1 and
SHA-256 from **Test and release → Setup → App signing**. SHA-256 from **Test and release → Setup → App signing**.
3. Complete the prepared store listing and App content sections using 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 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 repeat the production-origin, sign-in, notification, location, and App Link
smoke tests. smoke tests.

View File

@ -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."

View File

@ -25,6 +25,7 @@ set -a
set +a set +a
"$ROOT/scripts/validate-android-environment.sh" "$ENV_FILE" production "$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_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}" : "${WNH_TRACKING_MIN_TIME_MS:?Set WNH_TRACKING_MIN_TIME_MS in the selected environment file}"

View File

@ -400,6 +400,7 @@ fi
test "$(sha256sum "$credential_env" | awk '{print $1}')" = "$credential_hash" test "$(sha256sum "$credential_env" | awk '{print $1}')" = "$credential_hash"
echo "Checking Android environment isolation" 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_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" android_env="$scan_dir/android-development.env"
printf '%s\n' \ printf '%s\n' \