Troubleshooting
The failures you’ll actually hit, grouped by symptom.
Enforcement
The agent runs but nothing is enforced; observed state is “n/a”
The app isn’t a Device Owner, so isDeviceOwner() is false and the enforce/observe step is skipped. Set it on an emulator with no accounts:
adb shell dpm set-device-owner com.arganaemre.mdmcore/.MdmDeviceAdminReceiver
If that command itself fails, the device already has an account (or an existing owner). Use a fresh Google APIs emulator with nothing signed in. See agent.md.
SecurityException from a DevicePolicyManager call
The policy you’re invoking isn’t declared in device_admin.xml. Every policy a DPC calls must have a matching <uses-policies> tag. (Camera works because <disable-camera/> is declared.) See agent.md.
Networking
CLEARTEXT communication ... not permitted
targetSdk 36 blocks plain HTTP by default. Only 10.0.2.2 and localhost are re-permitted (network_security_config.xml). The agent must talk to http://10.0.2.2:8081 (the emulator’s alias for the host), not localhost and not a LAN IP. To use another host, add it to the network security config.
The agent can’t reach the server
- From an emulator the host is
10.0.2.2, not127.0.0.1/localhost. - The server binds
0.0.0.0:8081— confirm it’s actually running (./gradlew :server:run) and the port matches.
CORS errors in the web console’s browser devtools
The server’s CORS is anyHost() (dev-only) and allows GET/POST/PUT + Content-Type. If you see CORS failures, the server probably isn’t running, or you changed the allowed methods/headers. A JSON body triggers a preflight, so Content-Type and PUT must stay allowed. See server.md.
Push (FCM doorbell)
Boot log says FCM doorbell: NOT configured
No service-account credential was found (or it failed to load). Push wake-ups are disabled, but everything else still works — agents sync over HTTP on their schedule. To enable push, configure the key per firebase-setup.md. The boot log flips to FCM doorbell: ready once it loads.
Policy changed, but the device didn’t wake
The doorbell is best-effort; the device will still catch up on its next sync. If you need the push specifically:
- The server must log
FCM doorbell: ready(a key is configured). - The agent must have registered a token (it does so every sync — make sure it has synced at least once after enrolling).
- The emulator needs Google Play services (a Google APIs image); a bare AOSP image can’t receive FCM. See firebase-setup.md.
- A force-stopped app won’t receive messages until it’s launched again.
Build
A ./gradlew command just hangs
A long-lived task (:server:run, a console dev-run) is holding the Gradle project lock. Stop it, or run the blocked command from a separate checkout. See development.md.
Agent build fails complaining about google-services.json
The google-services plugin requires agent/google-services.json. Download it from the Firebase console for application id com.arganaemre.mdmcore and place it there. It’s not secret. See firebase-setup.md.
Server state
A device gets 404 and re-enrolls with a new id
DeviceStore is in-memory — restarting the server forgets every device. The agent treats a 404 on policy-pull as “the server forgot me” and re-enrolls, receiving a fresh id (device-2, …). This is expected with the current non-persistent store. See server.md.
A device shows STALE and won’t clear
STALE means the last report applied an older policy version than the current desired one. It clears when the agent re-syncs (pull → enforce → report) on the new version. Trigger a sync (relaunch the agent, or let the doorbell/WorkManager fire). If it then shows NON_COMPLIANT, the device is on the right version but genuinely drifted. See the loop.