androidandroid-for-work

How to validate results of Android DevicePolicyManager#isAdminActive vs failure to activate device manager


I'm facing an interesting situation which I don't know how to resolve. When a user is signing into my app for the first time as an Android for Work user, I am obliged to make sure that the app is registered as a device manager. I check whether this is the case by calling DevicePolicyManager#isAdminActive, and if this returns false, then I launch an Intent with action=DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN, in order to launch the Android control which will enable device management. Once this has happened, when my app is launched again (or when it returns from the device management flow), the value of DevicePolicyManager#isAdminActive is checked again. If the user enabled device management, then everything is fine, and the app continues on its way.

Interestingly, this works exactly as expected the first time the user goes through our flow. Unfortunately, after a reboot, when the user starts up my app, it checks to make sure device management is still on, via DevicePolicyManager#isAdminActive, and here it gets interesting. DevicePolicyManager#isAdminActive will report false, which is verified by looking at the device security settings. Even worse, however, attempting to enable device management will result in the following exception:

W/DeviceAdminAdd: Exception trying to activate admin ComponentInfo{com.mysoft.myapp/com.mysoft.core.receivers.MyAppAdminReceiver} java.lang.IllegalArgumentException: Admin is already added at android.os.Parcel.readException(Parcel.java:1550) at android.os.Parcel.readException(Parcel.java:1499) at android.app.enterprise.IEnterpriseDeviceManager$Stub$Proxy.setActiveAdmin(IEnterpriseDeviceManager.java:867) at android.app.enterprise.EnterpriseDeviceManager.setActiveAdmin(EnterpriseDeviceManager.java:720) at com.android.settings.DeviceAdminAdd.addAndFinish(DeviceAdminAdd.java:346) at com.android.settings.DeviceAdminAdd$3.onClick(DeviceAdminAdd.java:313) at android.view.View.performClick(View.java:5242) at android.widget.TextView.performClick(TextView.java:10571) at android.view.View$PerformClick.run(View.java:21196) at android.os.Handler.handleCallback(Handler.java:739) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:145) at android.app.ActivityThread.main(ActivityThread.java:6938) at java.lang.reflect.Method.invoke(Native Method) at java.lang.reflect.Method.invoke(Method.java:372) at ...

This exact exception appears on my logcat console even when I attempt to manually enable device management via the system security settings page, so I don't think the original Intent is malformed.

So, here's my problem: One call to the Android device management API tells me that my device administrator is not activated, but another call to the same API tells me that it is. I believe that the 2nd one is, in fact, in error, but without being able to activate the administration, my user is stuck in a loop and unable to use my app.

Has anyone else encountered this error, and if so, how do you code around it?


Solution

  • I have analyzed AOSP source of DevicePolicyManagerService.java. The function which is failing in this case is called setActiveAdmin(), and it is throwing an exception because it claims that our application has already been added as a device administrator. This exception is being thrown from setActiveAdmin after a call to getActiveAdminUncheckedLocked() returns a valid object, indicating that the requested device administrator is active:

    final ActiveAdmin existingAdmin
        = getActiveAdminUncheckedLocked(adminReceiver, userHandle);
    if (!refreshing && existingAdmin != null) {
        throw new IllegalArgumentException("Admin is already added");
    }
    if (policy.mRemovingAdmins.contains(adminReceiver)) {
        throw new IllegalArgumentException(
                "Trying to set an admin which is being removed");
    }
    

    However, before this, we have already called DevicePolicyManager.isAdminActive, which contains this logic:

    return getActiveAdminUncheckedLocked(adminReceiver, userHandle) != null;
    

    This is fine; this is exactly what we want. However, this is AOSP code. I theorized that there might be a different implementation on affected devices, in which there is an error in the logic of DevicePolicyManager#isAdminActive(), so that the call is returning false when it should be returning true.

    After deploying instrumentation to catch this condition in production, I have verified that this hypothesis is correct. Inferring from my metrics, implementations exist in the Android device population such that the value returned by isAdminActive(X) will return false, even though X will be found in the list of enumerated active administrators returned by getActiveAdmins(). This is, fortunately, not a common case, but it does exist. I would advise any coders who rely upon these calls to harden their code against this scenario.