Rebounder Tech Blog

Written by the people who actually run these systems in production.

INSTALL_FAILED_UPDATE_INCOMPATIBLE Locks a Device Owner

Published About 5 min readBy the Rebounder engineering team — the people who operate these systems

This article may contain affiliate links. Its content is not affected by advertising.

In short

A Device Owner kiosk's signing-key switch makes install -r safely fail with INSTALL_FAILED_UPDATE_INCOMPATIBLE, but uninstalling then strips Device Owner, leaving only a factory reset to recover.

Conclusion

An Android device locked down as a Device Owner kiosk hits a dead end the moment its APK signing key changes: it can’t be updated, and uninstalling to work around that can’t be undone.

  • An APK signed with a different key safely fails adb install -r (INSTALL_FAILED_UPDATE_INCOMPATIBLE). The existing app is left untouched.
  • Deciding to “just uninstall and reinstall” at that point strips the device of its Device Owner admin privilege itself.
  • Device Owner can normally only be restored by reprovisioning from a full factory reset. On site, that’s a dead end.

This came up when switching the signing key for a kiosk app flashed onto signage devices (Google TV) from a debug key used for verification to a dedicated release key.

Symptom

Field devices had been running APKs signed with a debug key (0951ef53…) carried over from the PoC period. Ahead of production operation, a dedicated release key — separate from the debug key — was created, and the build switched to use it.

// app/build.gradle.kts
// Release key (from 2026-10-28). Not checked into the repo; lives in ~/.kimiteras-signing/.
// Environments without it (e.g. CI) fall back to the debug key.
// Existing devices (signed with the 0951 key) can't be overwritten — signatures don't match.
// See dist/APK-MANIFEST.md.
val releaseKs = file(System.getProperty("user.home") + "/.kimiteras-signing/kimiteras-tvbridge-release.p12")
val releasePass = file(System.getProperty("user.home") + "/.kimiteras-signing/storepass.txt")
if (releaseKs.exists() && releasePass.exists()) {
    signingConfigs.create("kimiteras") {
        storeFile = releaseKs
        storeType = "pkcs12"
        storePassword = releasePass.readText().trim()
        keyAlias = "kimiteras-tvbridge"
        keyPassword = storePassword
    }
}
val signing = signingConfigs.findByName("kimiteras") ?: signingConfigs.getByName("debug")

Trying to overwrite an APK already running as Device Owner in the field with a build signed by this new key, via adb install -r, makes Android refuse the update because the signatures don’t match. The app’s own manifest spells this out too.

Signed only with the release key kimiteras-tvbridge (cert SHA-256 B2:D5:99:38:…:C1:97:DC, v2+v3). The key lives in ~/.kimiteras-signing/ (outside the knowledge base, not in the repo). ⚠ Won’t install -r onto existing devices on the 0951 key (signature mismatch) — move them to this version when they’re rebuilt. Until then, use a90d6a95… for recovery (setup-monitor).

— dist/APK-MANIFEST.md

Android’s package manager requires the signing certificate to match when overwriting an app with the same applicationId. If it doesn’t match, install -r safely fails (without corrupting data) and returns the error code INSTALL_FAILED_UPDATE_INCOMPATIBLE. Up to this point, this is something that happens with ordinary Android apps too, and a straightforward reading ends at “just uninstall and reinstall.”

Cause

Device Owner is different. This is exactly the safety rule already spelled out in a 2026-06-15 on-site update procedure, written against this very failure code.

## 0. Safety rules (read first, most important)

- Never run `adb uninstall`. Uninstalling removes Device Owner, and without a factory reset there is no recovery.
- This build's signing key `0951ef53…` matches the current production key (FCM build, 2026-06-11). So devices with 6/11 installed can be updated via `install -r`.
- `install -r` safely fails if the signature doesn't match, leaving the existing app in place (`INSTALL_FAILED_UPDATE_INCOMPATIBLE`).
  If it fails, leave that device alone — record the model and IP and pull back (devices on an old key are a separate matter). Do not uninstall even if it fails.

— dist/UPDATE-2026-06-15-night-off-recovery.md

The assumption that Android’s Device Owner — built on the premise that an organization manages the whole device — is designed so it can’t simply be removed the way an ordinary app can with adb uninstall, is only half right. It’s actually the opposite: uninstalling the Device Owner app itself removes the device-admin privilege along with it. On most models, the only way to re-enable a Device Owner that’s been removed is to reprovision from a factory-reset state with no accounts added at all; running dpm set-device-owner again on site does not bring it back.

In other words, for a Device Owner device, INSTALL_FAILED_UPDATE_INCOMPATIBLE isn’t a “one level safer” failure — it’s the last branch point where things should stop. Uninstalling before re-matching the signature means losing the foundation — the device-admin privilege — before ever solving the signature problem. Back in June, this was insurance against the rare case of a device ending up with a build signed by a different debug key. Switching to a dedicated release key in October made every existing device fall on this branch.

Response

What actually got decided wasn’t a one-shot switch of every existing device to the release key — it was accepting that two keys would coexist.

  • Newly built devices are flashed with release-key-signed APKs from the start.
  • Existing devices (running on the 0951 key) keep running builds signed with that key.
  • Existing devices move to the release key only when they’re rebuilt anyway — i.e., when they break and need reprovisioning.

A one-shot switch would converge everything onto a single key, but doing it would require factory-resetting every existing device on site. Right now, that cost is judged higher than the cost of leaving the signing keys split.

Operating rule

Before updating APKs on a fleet of Device Owner devices, there’s exactly one thing to check first: is the APK about to be flashed signed with the same key as the one already on that device? If not, install -r will fail regardless, so checking first means never seeing INSTALL_FAILED_UPDATE_INCOMPATIBLE at all.

If INSTALL_FAILED_UPDATE_INCOMPATIBLE does show up on site, there’s exactly one thing to do.

  • Don’t run adb uninstall.
  • Leave that device alone — record its model and IP and pull back.
  • Either flash an APK signed with the matching key, or defer it to the next time that device gets rebuilt anyway.

“The signature doesn’t match” is Android safely telling you something, and stopping there leaves the device untouched. The damage comes from the next move — the one that reads this error as a signal to “just reinstall then.”

Frequently asked questions

Q1What should I do when I hit INSTALL_FAILED_UPDATE_INCOMPATIBLE?

Don't run adb uninstall. When the signature doesn't match, install -r safely fails and the existing app stays in place. Leave that device alone, record its model and IP, and come back to it with a matching key, or wait until it's due for a rebuild anyway.

Q2Why can't I just uninstall and reinstall?

A device running as Device Owner loses that admin privilege the moment it's uninstalled, and getting it back requires reprovisioning from a full factory reset. That's a much bigger loss than leaving a signing-key mismatch unresolved.

Q3How are the existing devices handled now?

Devices already in the field keep running builds signed with the old key (0951). The new dedicated release key is used only for devices built from scratch, and for existing ones only once they break and need reprovisioning anyway.

Environment verified

  • Android TV / compileSdk 34, minSdk 26, targetSdk 34 (Kotlin, Gradle Kotlin DSL)
  • Dedicated release signing key (kimiteras-tvbridge) introduced 2026-10-28. Existing devices still run on the earlier debug key (0951ef53…)

What this article is based on

  • file lines 27-40commit 650002e
  • Markdown file lines 40-42commit 650002e
  • Markdown file lines 9-15commit 650002e

Every claim in this article comes from the records above. The repositories we operate are private so we cannot link to them, but which file, which lines, and at which commit we read them is recorded for every article. Nothing here is written from guesswork.