Enforce Play location declaration contract
This commit is contained in:
parent
de60e6e54c
commit
c31532ebbc
112
android/play-store/location-and-fgs-declaration.md
Normal file
112
android/play-store/location-and-fgs-declaration.md
Normal 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
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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/`.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
98
scripts/android-play-policy-check.sh
Executable file
98
scripts/android-play-policy-check.sh
Executable 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."
|
||||||
|
|
@ -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}"
|
||||||
|
|
|
||||||
|
|
@ -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' \
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user