All checks were successful
PR Checks / android-build (pull_request) Successful in 24m52s
PushService was a dataSync foreground service. Android 15 stops dataSync after six hours a day and expects the service to stop itself on onTimeout; it never did, so with target 36 the app would crash. A held-open push connection is not a transfer either, which is what Play reserves dataSync for. - The service is now specialUse, with the PROPERTY_SPECIAL_USE_FGS_SUBTYPE reason Play reviews (ntfy does the same on Play). No daily limit. - PushBootReceiver restarts it after BOOT_COMPLETED and MY_PACKAGE_REPLACED when push was on and a registration is saved (a sign-out clears it). It starts the service from the saved topic instead of re-registering, since the network is often not up at boot. PLAN.md M8, decided by the org lead 2026-10-29. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
102 lines
5.5 KiB
XML
102 lines
5.5 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
|
<!-- SPDX-License-Identifier: GPL-3.0-or-later -->
|
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
|
|
<!-- The app is purely an HTTPS API client of a shard's website backend. -->
|
|
<uses-permission android:name="android.permission.INTERNET" />
|
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
|
|
|
<!-- Opt-in push notifications (M7): the runtime notification permission (API 33+)
|
|
and a foreground service that holds the persistent ntfy connection open — the
|
|
embedded UnifiedPush distributor, so no separate app is needed (PLAN.md §11).
|
|
specialUse, not dataSync (PLAN.md M8, 2026-10-29): a held-open connection is not a
|
|
transfer, and Android 15 stops dataSync after six hours a day. RECEIVE_BOOT_COMPLETED
|
|
restarts it after a reboot or an update. -->
|
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
|
|
|
<application
|
|
android:name=".RunicGatewayApp"
|
|
android:allowBackup="true"
|
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
|
android:fullBackupContent="@xml/backup_rules"
|
|
android:icon="@mipmap/ic_launcher"
|
|
android:label="@string/app_name"
|
|
android:networkSecurityConfig="@xml/network_security_config"
|
|
android:roundIcon="@mipmap/ic_launcher_round"
|
|
android:supportsRtl="true"
|
|
android:theme="@style/Theme.RunicGateway">
|
|
|
|
<!-- singleTop so the SSO Custom Tab returning via the deep link reuses the
|
|
running task (onNewIntent) instead of stacking a second activity. -->
|
|
<activity
|
|
android:name=".MainActivity"
|
|
android:exported="true"
|
|
android:launchMode="singleTop"
|
|
android:theme="@style/Theme.RunicGateway">
|
|
<intent-filter>
|
|
<action android:name="android.intent.action.MAIN" />
|
|
<category android:name="android.intent.category.LAUNCHER" />
|
|
</intent-filter>
|
|
|
|
<!-- Native SSO callback (M9, PLAN.md §4.2). The bridge deep-links the
|
|
one-time authorization code back to this fixed, app-owned custom
|
|
scheme; it must match SsoAuthManager.REDIRECT_URI and the backend's
|
|
MOBILE_AUTH_REDIRECT_URIS allowlist exactly. This is the permanent
|
|
fallback on every build (docs/android/APP_LINKS.md). -->
|
|
<intent-filter>
|
|
<action android:name="android.intent.action.VIEW" />
|
|
<category android:name="android.intent.category.DEFAULT" />
|
|
<category android:name="android.intent.category.BROWSABLE" />
|
|
<data
|
|
android:scheme="runicgateway"
|
|
android:host="auth"
|
|
android:path="/callback" />
|
|
</intent-filter>
|
|
|
|
<!-- App Links hardening (docs/android/APP_LINKS.md): a verified https
|
|
callback that only the domain's real owner can claim. autoVerify
|
|
needs a literal host, so ${appLinkHost} is baked at build time
|
|
(build.gradle.kts). The generic build leaves it as the reserved
|
|
runic-gateway.invalid sentinel — the filter then matches no real
|
|
link and never verifies. A white-label build sets -PappLinkHost. -->
|
|
<intent-filter android:autoVerify="true">
|
|
<action android:name="android.intent.action.VIEW" />
|
|
<category android:name="android.intent.category.DEFAULT" />
|
|
<category android:name="android.intent.category.BROWSABLE" />
|
|
<data
|
|
android:scheme="https"
|
|
android:host="${appLinkHost}"
|
|
android:path="/mobile/callback" />
|
|
</intent-filter>
|
|
</activity>
|
|
|
|
<!-- The embedded distributor's persistent ntfy connection (M7, PLAN.md §11).
|
|
specialUse foreground type, with the reason Play reviews in the property below;
|
|
not exported — started only by PushManager and PushBootReceiver. -->
|
|
<service
|
|
android:name=".core.push.PushService"
|
|
android:exported="false"
|
|
android:foregroundServiceType="specialUse">
|
|
<property
|
|
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
|
android:value="Instant push notifications without Google services. When the user turns notifications on, this service holds one connection open to the push relay their community's server names, and shows a notification for each message from it. The ongoing notification shows the service is running; turning notifications off or signing out stops it." />
|
|
</service>
|
|
|
|
<!-- Restarts push after a reboot or an app update, if the member had it on and was
|
|
signed in when the phone went down (PLAN.md M8). Not exported: only the system
|
|
delivers these broadcasts. -->
|
|
<receiver
|
|
android:name=".core.push.PushBootReceiver"
|
|
android:exported="false">
|
|
<intent-filter>
|
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
|
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
|
</intent-filter>
|
|
</receiver>
|
|
</application>
|
|
|
|
</manifest>
|