Reported from the phone's diag log: status: crashed: SecurityException: WifiService: Neither user 10519 nor current process has android.permission.ACCESS_WIFI_STATE. at android.os.Parcel.createExceptionOrNull `PhoneSensors.wifiSsid` read `WifiManager.connectionInfo` behind nothing but the location grant, but WifiService gates every WifiManager call on ACCESS_WIFI_STATE — and it enforces that by throwing SecurityException, not by answering null. The permission was never declared, so the read always threw. The cost is larger than the missing SSID: the throw travels out of `readSnapshot` into the worker's catch, which logs "status: crashed" and returns failure — so one undeclared permission took the battery, network, DND and location topics down with it, and the fleet's rows went stale while the app looked healthy. Two halves, both needed: - the manifest declares ACCESS_WIFI_STATE (a normal permission: declaring it is the whole grant, no runtime prompt). An install that predates the line keeps the grant set it was installed with, so this alone does not stop the crash on an existing phone; - `wifiSsid` returns null unless the permission is actually held, and wraps the read in `runCatching`, so the status report degrades to "no wifi name" instead of dying whole. The check is a `hasWifiState` beside the existing `hasLocation`, same idiom.
76 lines
3.7 KiB
XML
76 lines
3.7 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
xmlns:tools="http://schemas.android.com/tools">
|
|
|
|
<!-- Network, posting the agent's notifications, and re-kicking the
|
|
workers after a reboot. Location is the status source's optional
|
|
extra: a runtime permission, and without it the location topic
|
|
simply never reports. -->
|
|
<uses-permission android:name="android.permission.INTERNET" />
|
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
|
<!-- Every WifiManager call — including the connectionInfo that
|
|
names the joined network — is gated on ACCESS_WIFI_STATE, which
|
|
WifiService enforces by throwing SecurityException, not by
|
|
answering null. A normal permission, so declaring it is the
|
|
whole grant: an install that predates this line keeps the grant
|
|
set it was installed with. -->
|
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
|
<!-- DND control is a special-app grant, and this declaration is what
|
|
puts the app on the "Do Not Disturb access" screen's list: an
|
|
undeclared app opens that screen and finds nothing to flip. -->
|
|
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
|
|
<!-- App usage is not a runtime permission but a special-app access
|
|
(Settings → Apps → Special app access → Usage access); declaring
|
|
it is what makes the toggle able to grant it to this app. -->
|
|
<uses-permission
|
|
android:name="android.permission.PACKAGE_USAGE_STATS"
|
|
tools:ignore="ProtectedPermissions" />
|
|
|
|
<application
|
|
android:name=".RhemaApp"
|
|
android:allowBackup="false"
|
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
|
android:fullBackupContent="@xml/backup_rules"
|
|
android:icon="@drawable/ic_rhema"
|
|
android:label="@string/app_name"
|
|
android:networkSecurityConfig="@xml/network_security_config"
|
|
android:theme="@android:style/Theme.Material.NoActionBar">
|
|
|
|
<activity
|
|
android:name=".MainActivity"
|
|
android:exported="true">
|
|
<intent-filter>
|
|
<action android:name="android.intent.action.MAIN" />
|
|
<category android:name="android.intent.category.LAUNCHER" />
|
|
</intent-filter>
|
|
</activity>
|
|
|
|
<!-- WorkManager already re-enqueues its own periodic work after reboot;
|
|
this receiver is the explicit boot kick. It only calls the same
|
|
idempotent (KEEP) schedule call used at app start. -->
|
|
<receiver
|
|
android:name=".work.BootReceiver"
|
|
android:exported="true">
|
|
<intent-filter>
|
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
|
</intent-filter>
|
|
</receiver>
|
|
|
|
<!-- Attachments saved into the cache open in other apps only through
|
|
this provider's content:// mapping; a raw cache path is
|
|
unreadable to them. -->
|
|
<provider
|
|
android:name="androidx.core.content.FileProvider"
|
|
android:authorities="${applicationId}.fileprovider"
|
|
android:exported="false"
|
|
android:grantUriPermissions="true">
|
|
<meta-data
|
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
|
android:resource="@xml/file_paths" />
|
|
</provider>
|
|
</application>
|
|
</manifest>
|