Granting Permission to Access Serial & IMEI Numbers on Mobile Computers Running A10

Introduction

The sample application referenced in this blog is available here: https://github.com/ZebraDevs/OEMIdentifiers-Java-Sample

In our first article, my colleague Darryn Campbell, describes the process of accessing the Serial & IMEI Numbers on our mobile computers running Android 10. As Darryn explains, your application needs to be explicitly granted permission to access these devices identifiers which can be done in one of three ways:

  1. Via StageNow
  2. Via your EMM
  3. Via your application, using the EMDK Profile Manager.

This blog will walk-trough option number 3: Granting permissions to access the devices identifiers via your application, using the EMDK Profile Manager.

This is a three-step process:

  1. Build MX XML (using package name & programmatically generated package signature)
  2. Initialise ProfileManager from EMDK
  3. Process XML using ProfileManager

In our sample application, there’s a few key classes we use to achieve this functionality:

  • XML.class
    • Create MX XML using package name & signature
  • PackageManagerHelper.class
    • Used to generate the Base64 encoding of our application signature
  • PermissionsActivity.class
    • Process XML using ProfileManager APIs.

Building MX XML

The first thing we’ll walk through is generating the XML to be passed through to the ProfileManager APIs which will grant the necessary permissions to your application.

The core XML looks like this, with placeholders for the Package Name & Signature, respectfully:

<characteristic type="Profile">
  <parm name="ProfileName" value="SerialPermission"/>
  <characteristic version="8.3" type="AccessMgr">
    <parm name="OperationMode" value="1" />
    <parm name="ServiceAccessAction" value="4" />
    <parm name="ServiceIdentifier" value="content://oem_info/oem.zebra.secure/build_serial" />
    <parm name="CallerPackageName" value="YOUR_PACKAGE_NAME" />
    <parm name="CallerSignature" value="YOUR_PACKAGE_SIGNATURE" />
  </characteristic>
</characteristic>

The above is the XML required to grant the serial permission, which is denoted under the “ServiceIdentifier” tag. The below is the XML required to grant the IMEI permission:

<characteristic type="Profile">
  <parm name="ProfileName" value="ImeiPermission"/>
  <characteristic version="8.3" type="AccessMgr">
    <parm name="OperationMode" value="1" />
    <parm name="ServiceAccessAction" value="4" />
    <parm name="ServiceIdentifier" value="content://oem_info/wan/imei" />
    <parm name="CallerPackageName" value="YOUR_PACKAGE_NAME" />
    <parm name="CallerSignature" value="YOUR_PACKAGE_SIGNATURE" />
  </characteristic>
</characteristic>

Before we can apply this XML, we need to fill in the gaps for our Package Name & Signature.

Generating the package name is straight forward, simply call getPackageName() from a valid Context.

Getting the package signature is slightly more involved: First, we grab the APK signatures using getApkContentsSigners(), and then secondly, we convert this to Base64 – I leverage the Apache Commons Hex & Base64 libraries to do this by converting the signatures to a char array, decoding this into a byte array, and finally encoding into Base64:

public static String getSigningCertBase64(Context cx) throws PackageManager.NameNotFoundException, DecoderException {
    //convert String to char array (1st step)
    char[] charArray = getSigningCertificateHex(cx)[0].toChars();

    // decode the char array to byte[] (2nd step)
    byte[] decodedHex = Hex.decodeHex(charArray);

    // The String decoded to Base64 (3rd step)
    return Base64.encodeBase64String(decodedHex);
}

@SuppressWarnings("deprecation")
@SuppressLint("PackageManagerGetSignatures")
public static Signature[] getSigningCertificateHex(Context cx)
        throws PackageManager.NameNotFoundException {
    Signature[] sigs;
    SigningInfo signingInfo;
    signingInfo = cx.getPackageManager().getPackageInfo(cx.getPackageName(), PackageManager.GET_SIGNING_CERTIFICATES).signingInfo;
    sigs = signingInfo.getApkContentsSigners();
    return sigs;
}

We put all this functionality together inside the XML Class – Inside the constructor we make two calls to the aforementioned APIs to grab the package signature & name. We then store these values as member variables in the class:

// Holders
private String mPackageSignatureHex;
private String mPackageName;

public XML(Context context) throws PackageManager.NameNotFoundException, DecoderException {
    mPackageSignatureHex = PackageManagerHelper.getSigningCertBase64(context);
    mPackageName = context.getPackageName();
}

When our application needs the XML to grant the permissions (Serial & IMEI), we can simply call the getSerialPermissionXml() & getImeiPermissionXml() methods, and the values will be populated for us, like so:

public String getSerialPermissionXml() {
    return  "<wap-provisioningdoc>\n" +
            "  <characteristic type=\"Profile\">\n" +
            "    <parm name=\"ProfileName\" value=\"SerialPermission\"/>\n" +
            "    <characteristic version=\"8.3\" type=\"AccessMgr\">\n" +
            "      <parm name=\"OperationMode\" value=\"1\" />\n" +
            "      <parm name=\"ServiceAccessAction\" value=\"4\" />\n" +
            "      <parm name=\"ServiceIdentifier\" value=\"content://oem_info/oem.zebra.secure/build_serial\" />\n" +
            "      <parm name=\"CallerPackageName\" value=" + '"' + mPackageName + '"' + " />\n" +
            "      <parm name=\"CallerSignature\" value=" + '"' + mPackageSignatureHex + '"' + "  />\n" +
            "    </characteristic>\n" +
            "  </characteristic>\n" +
            "</wap-provisioningdoc>";
}

public String getImeiPermissionXml() {
    return  "<wap-provisioningdoc>\n" +
            "  <characteristic type=\"Profile\">\n" +
            "    <parm name=\"ProfileName\" value=\"ImeiPermission\"/>\n" +
            "    <characteristic version=\"8.3\" type=\"AccessMgr\">\n" +
            "      <parm name=\"OperationMode\" value=\"1\" />\n" +
            "      <parm name=\"ServiceAccessAction\" value=\"4\" />\n" +
            "      <parm name=\"ServiceIdentifier\" value=\"content://oem_info/wan/imei\" />\n" +
            "      <parm name=\"CallerPackageName\" value=" + '"' + mPackageName + '"' + " />\n" +
            "      <parm name=\"CallerSignature\" value=" + '"' + mPackageSignatureHex + '"' + "  />\n" +
            "    </characteristic>\n" +
            "  </characteristic>\n" +
            "</wap-provisioningdoc>";
}

At this point, we have two sets of XML strings ready to be passed through to the ProfileManager APIs to grant these permissions to our application.

Initialise Profile Manager Instance

Before we can apply any XML, we first need to initialise an instance of the ProfileManager from the EMDK. In the sample app this is done in the PermissionsActivity class. We launch this activity from the MainActivity using startActivityForResult() so we can be notified of the success / failure of granting the permission.

Before proceeding, please make sure you’ve added the EMDK to your build.gradle file:

// EMDK
implementation 'com.symbol:emdk:7.6.10'

Now, we can initialise the EMDK in our activity:

// Init EMDK
EMDKResults emdkManagerResults = EMDKManager.getEMDKManager(this, this);

The two parameters are the context, and an EMDKListener callback, which we have implemented in our PermissionsActivity.class. When the EMDK instance is ready, we’re notified in the onOpened() callback, where we can then obtain an instance of the ProfileManager object:

@Override
public void onOpened(EMDKManager emdkManager) {
    // Assign EMDK Reference
    mEmdkManager = emdkManager;

    // Get Profile & Version Manager Instances
    mProfileManager = (ProfileManager) mEmdkManager.getInstance(EMDKManager.FEATURE_TYPE.PROFILE);

    // Apply Profile
    if (mProfileManager != null) {
        try {
            // Init XML
            XML permissionXml = new XML(this);

            // Process
            new ProcessProfile(XML.GRANT_SERIAL_PERMISSION_NAME, mProfileManager, onProfileApplied)
                    .execute(permissionXml.getSerialPermissionXml());

            // Process
            new ProcessProfile(XML.GRANT_IMEI_PERMISSION_NAME, mProfileManager, onProfileApplied)
                    .execute(permissionXml.getImeiPermissionXml());

        } catch (PackageManager.NameNotFoundException | DecoderException e) {
            e.printStackTrace();
        }
    } else {
        Log.e(TAG, "Error Obtaining ProfileManager!");
        Toast.makeText(this, "Error Obtaining ProfileManager!", Toast.LENGTH_LONG)
                .show();
    }
}

As you can see in the code sample above, we get an instance of the ProfileManager from the emdkManager by calling the getInstance API, and passing through FEATURE_TYPE.PROFILE as the only parameter.

Once we’ve obtained an instance of the ProfileManager, we can leverage a utility class called ProcessProfile which will asynchronously process (using an AsyncTask) this profile for us, and notify completion in our onProfileApplied callback. Note, we have to perform this operation twice – once to grant permission to the IMEI number and once to the Serial number, so we need to perform a check in our callback to make sure both profiles are processed:

private OnProfileApplied onProfileApplied = new OnProfileApplied() {

    // Holder - this is needed because we can't apply two access manager permissions in a single profile
    int numberOfResults = 0;
    int numberOfPermissionsToGrant = 2;

    // Return Intent for StartActivityForResult
    Intent resultIntent = new Intent();

    @Override
    public void profileApplied(String statusCode, String extendedStatusCode) {
        // Update Results Holder
        if (++numberOfResults == numberOfPermissionsToGrant) {
            resultIntent.putExtra(PERMISSIONS_GRANTED_EXTRA, true);
            resultIntent.putExtra(PERMISSIONS_STATUS_CODE, statusCode);
            resultIntent.putExtra(PERMISSIONS_EXTENDED_STATUS_CODE, extendedStatusCode);
            setResult(RESULT_OK, resultIntent);
            finish();
        }
    }

    @Override
    public void profileError(String statusCode, String extendedStatusCode) {
        resultIntent.putExtra(PERMISSIONS_GRANTED_EXTRA, false);
        resultIntent.putExtra(PERMISSIONS_STATUS_CODE, statusCode);
        resultIntent.putExtra(PERMISSIONS_EXTENDED_STATUS_CODE, extendedStatusCode);
        setResult(RESULT_OK, resultIntent);
        finish();
    }
};

Once we’ve been notified in our callback that both permissions have been granted, we notify the calling activity and finish the permissions activity.

We can now return to the MainActivity and access the Serial & IMEI Number Permissions – if you’d like to read more detail about how to access these permissions using our Content Provider, please refer to the RetrieveOEMInfo.class, or a blog from our colleague Darryn Campbell: https://developer.zebra.com/blog/access-serial-number-and-imei-mobile-computers-running-android-10