Discover the challenges and solutions in building an Android Device Owner MDM application. Learn about provisioning methods, device policies, kiosk mode, and enterprise deployment strategies.
Building an MDM (Mobile Device Management) application with Device Owner capabilities is one of the most challenging Android development projects. Here's what I learned building HK AIR IoT Controls.
Device Owner is a special privilege level in Android that grants an app complete control over a device. Unlike regular apps, a Device Owner can:
Getting Device Owner status isn't straightforward. Here are the available methods:
The most reliable method for production:
{
"android.app.extra.PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME":
"com.yourapp/.AdminReceiver",
"android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION":
"https://your-server.com/app.apk",
"android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM":
"base64-encoded-sha256-checksum",
"android.app.extra.PROVISIONING_SKIP_ENCRYPTION": false
}
For automated provisioning in manufacturing:
class NfcProvisioningActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
val props = Properties().apply {
setProperty(
EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,
ComponentName(packageName, AdminReceiver::class.java.name).flattenToString()
)
}
val nfcAdapter = NfcAdapter.getDefaultAdapter(this)
// Create NFC record with provisioning data
}
}
adb shell dpm set-device-owner com.yourapp/.AdminReceiver
The heart of your MDM app:
class AdminReceiver : DeviceAdminReceiver() {
override fun onEnabled(context: Context, intent: Intent) {
super.onEnabled(context, intent)
Log.d("MDM", "Device admin enabled")
}
override fun onProfileProvisioningComplete(context: Context, intent: Intent) {
super.onProfileProvisioningComplete(context, intent)
val manager = context.getSystemService(Context.DEVICE_POLICY_SERVICE)
as DevicePolicyManager
val componentName = ComponentName(context, AdminReceiver::class.java)
// Set up initial policies
manager.setLockTaskPackages(componentName, arrayOf(context.packageName))
}
}
Lock users into your app:
class KioskActivity : Activity() {
private lateinit var dpm: DevicePolicyManager
private lateinit var adminComponent: ComponentName
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
dpm = getSystemService(DEVICE_POLICY_SERVICE) as DevicePolicyManager
adminComponent = ComponentName(this, AdminReceiver::class.java)
// Enable kiosk mode
if (dpm.isDeviceOwnerApp(packageName)) {
dpm.setLockTaskPackages(adminComponent, arrayOf(packageName))
startLockTask()
}
}
fun exitKiosk(pin: String): Boolean {
if (verifyAdminPin(pin)) {
stopLockTask()
return true
}
return false
}
}
class PolicyManager(
private val context: Context,
private val dpm: DevicePolicyManager,
private val admin: ComponentName
) {
fun enforcePasswordPolicy() {
dpm.setPasswordQuality(admin, PASSWORD_QUALITY_COMPLEX)
dpm.setPasswordMinimumLength(admin, 8)
}
fun disableCamera() {
dpm.setCameraDisabled(admin, true)
}
fun restrictApps(allowedPackages: List<String>) {
// Hide all apps except allowed ones
val pm = context.packageManager
pm.getInstalledApplications(0).forEach { app ->
if (app.packageName !in allowedPackages) {
dpm.setApplicationHidden(admin, app.packageName, true)
}
}
}
fun configureWifi(ssid: String, password: String) {
val config = WifiConfiguration().apply {
this.SSID = "\"$ssid\""
this.preSharedKey = "\"$password\""
}
dpm.addNetworkConfiguration(admin, config)
}
}
Connect to your backend for remote control:
class RemoteCommandService : Service() {
private val socket = SocketIO("wss://your-mdm-server.com")
override fun onCreate() {
socket.on("command") { data ->
when (data.getString("type")) {
"LOCK" -> lockDevice()
"WIPE" -> wipeDevice()
"INSTALL_APP" -> installApp(data.getString("url"))
"UPDATE_POLICY" -> updatePolicy(data.getJSONObject("policy"))
}
}
socket.connect()
}
private fun wipeDevice() {
dpm.wipeData(DevicePolicyManager.WIPE_EXTERNAL_STORAGE)
}
}
Solution: Use setFactoryResetProtectionPolicy() to control who can reset the device.
Solution: Use setSystemUpdatePolicy() to control when updates install.
Solution: Implement multiple layers - disable buttons, intercept intents, monitor foreground app.
Building a Device Owner MDM app requires deep understanding of Android's enterprise APIs. The power comes with responsibility - these apps have complete control over devices.
If you're building MDM solutions, start small, test thoroughly, and always have a way to recover devices during development!
Get the latest articles, tutorials, and updates delivered straight to your inbox. No spam, unsubscribe at any time.