Ankit Gupta

November 19, 2020

By Ankit Gupta and Nikhil Punathil

Seamless provisioning is a key component of the device deployment process, designed to make what may have been a pain point in the past more accessible to developers and teams today. This article explores the reasons why you should choose seamless provisioning, and how someone with access to the AOSP source for a device can easily integrate this in their builds. With Esper seamless provisioning, devices are automatically provisioned on boot, requiring absolutely no user interaction throughout the process. This ends up being more operationally efficient than GMS specific provisioning options. 

By default, the Esper platform supports all Android 4.4+ devices through various different provisioning methods, all of which perform the task of loading the Esper agent onto the device and granting it the permission to be the device owner,  similar to the administrator role on Windows machines. GMS devices have streamlined provisioning methods, such as Google Zero-touch enrollment (and in some cases Samsung Knox Mobile Enrollment), Android for Work or 6 Tap QR code, all of which are supported by Esper. However, for purpose-built Android devices running Google-less Android Open Source Project (AOSP) OS images, the only way to provision the device is by using Android Debug Bridge (ADB). Esper provides an application that makes this process more streamlined, the Esper Device Provisioner, though it is important to note that customers with a high volume of devices might not find this to be a scalable solution. 

Advantages Over Other Provisioning Methods

While this method is mainly aimed at non-GMS devices, the advantages that seamless provisioning holds over the Esper Device Provisioner are also true for the other GMS-specific provisioning methods. Seamless provisioning is a highly scalable method of provisioning, since a fleet of similar devices can be provisioned collectively with a single OS image. The integration of the agent into the system also ensures that the provisioning survives factory resets, which can be beneficial for use cases that involve frequent data wipes. The only major requirements for seamless provisioning are access to the AOSP source for the target device, as well as the ability to flash custom images onto the device. 

Choosing to go with seamless provisioning also comes with the advantage of being able to configure the remote viewer completely remotely on older devices, without having the need to accept the permission manually. Additionally, with Original Equipment Manufacturer (OEM)/Original Device Manufacturer (ODM) support, you will also be able to optionally set up the Esper supervisor plugin, which allows the device to be controlled remotely. 

Acquiring the Esper Agent and Esper Helper

Before diving into the seamless provisioning setup process, it is important to note that this article assumes you have an AOSP tree set up in your build environment and have had at least one successful build, to ensure everything is in order. The first step is to download the Esper Agent and the Esper Helper, which are the two apps that need to be integrated into the source. Follow the below steps:

  1. Create a folder called esperdpc under <Android_Root>/packages/apps/
mkdir packages/apps/esperdpc
  1. Run the following command in the above folder: This will download the latest Esper Agent from the cloud. The Esper Agent is refreshed regularly at this location but is also programmed to update itself during provisioning, so it’s perfectly fine to have a stale version of the agent. 
wget -O esperdpc.apk https://s3.ap-south-1.amazonaws.com/shoonya-dpc/downloads/shoonyadpc-latest-master.apk
  1. Create a new Android.mk file in the above directory and copy the following contents. If you have any other custom launcher packages in the source, add them to the list of LOCAL_OVERRIDES_PACKAGES:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := esperdpc
LOCAL_SRC_FILES :=$(LOCAL_MODULE).apk
LOCAL_MODULE_CLASS := APPS
LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3QuickStep Launcher3QuickStepGo Launcher3Go
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
LOCAL_CERTIFICATE := PRESIGNED
include $(BUILD_PREBUILT)
  1. Create a folder called esperhelper under <Android_Root>/packages/apps/
 mkdir packages/apps/esperhelper 
  1. Run the following command in the above folder to download the latest Esper Agent Helper from the cloud. As with the Agent, the apk here is updated regularly but is also updated during provisioning. Be sure to run the commands for the agent and helper so that the versions match.
wget -O esperhelper.apk https://s3.ap-south-1.amazonaws.com/shoonya-dpc/downloads/shoonyadpc-helper-latest-master.apk
  1. Create a new Android.mk file in the same directory and copy the following contents. 
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := esperhelper
LOCAL_SRC_FILES :=$(LOCAL_MODULE).apk
LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
LOCAL_CERTIFICATE := PRESIGNED
include $(BUILD_PREBUILT)
  1. Add the following code into your device.mk or similar device tree file under <Android_root>/device/<manufacturer>/<model>/<device>.mk. This ensures that the new packages added to the source are added to the build for your particular device. 
PRODUCT_PACKAGES += esperdpc
PRODUCT_PACKAGES += esperhelperPRODUCT_PACKAGES += esperdpcPRODUCT_PACKAGES += esperhelper

Device Owners on Android

With the goal of making Android more enterprise-friendly, Google introduced the concept of device owners in Android 5.0. A device owner, contrary to what you would conclude from the name, is not (always) an actual person, it could also be an app. Device owners are the administrators of the device, which gives them more control over the various device properties. Esper utilizes this API in order to facilitate our featureset. This was designed so that device owners are set by the human who owns and uses the device. Since our goal is to provision the device without the need to be near it, we need to work around this requirement. For this, perform the following steps:

  1. Create two files called device_owner_2.xml and device_policies.xml and place them into a directory called <Android_Root>/device/<manufacturer>/<model>/
    The file device_owner_2.xml should look like this:
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<root>
    <device-owner package="io.shoonya.shoonyadpc"
                  name=""
                  component="io.shoonya.shoonyadpc/com.shoonyaos.shoonyadpc.receivers.AdminReceiver"
                  userRestrictionsMigrated="true" />
    <device-owner-context userId="0" />
</root>

The file device_policies.xml should look like this:

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
    <policies setup-complete="true" provisioning-state="3">
    <admin name="io.shoonya.shoonyadpc/com.shoonyaos.shoonyadpc.receivers.AdminReceiver">
        <policies flags="1023" />
        <strong-auth-unlock-timeout value="0" />
        <user-restrictions no_add_managed_profile="true" />
        <default-enabled-user-restrictions>
            <restriction value="no_add_managed_profile" />
      </default-enabled-user-restrictions>
  </admin>
  <password-validity value="true" />
  <lock-task-features value="16" />
</policies>
  1. Depending on the implementation of the OS, some devices may also require an additional device_owner.xml, in addition to the device_owner_2.xml. This is optional and should only be done on devices that are unable to grant device owner permissions solely from the previous steps. Create another file named device_owner.xml and ensure it looks like this:
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<device-owner package="io.shoonya.shoonyadpc" />
  1. (Optional) While Ethernet and mobile network devices can automatically connect to the internet on boot, if your device is expected to use Wi-Fi, an additional file needs to be created with the SSID and password of the network in question, so that the device may connect to said network automatically without user interaction. Fair warning: this does store the SSID and password in plaintext within the system, so if you are rooted, this could be a vulnerability worth considering. If you choose not to have the Wi-Fi credentials in the image, you will have to manually connect to a Wi-Fi network on the device before provisioning will continue. Create a file named config.json with the following contents:
{
    "android.app.extra.PROVISIONING_WIFI_SSID": "<ssid>",
    "android.app.extra.PROVISIONING_WIFI_PASSWORD": "<ap_password>"
}
  1. Now that we have the required files, we need to tell the Android build system to copy them to the right locations and make sure that they are included in the image. For this, add the following lines to the device.mk file within your device tree:
PRODUCT_COPY_FILES += \
    device/<manufacturer>/<model>/device_policies.xml:system/etc/device_policies.xml \
    device/<manufacturer>/<model>/device_owner_2.xml:system/etc/device_owner_2.xml \    device/<manufacturer>/<model>/device_owner.xml:system/etc/device_owner.xml \
  device/<manufacturer>/<model>/config.json:system/etc/config.json

Modifying Init to Set Device Owner On Boot

As mentioned earlier, the process of setting a device owner is something that Google expects will happen after the user has booted the device (either new or after a factory reset). However, we have only pushed the files so far to /system/etc/, not /data/system where it needs to be. For this, we will utilize and modify init. Android init is responsible for starting all the required processes in the right order and setting permissions as the device is booting. We add lines to the init library to make sure our device owner permissions and policies are pushed to /data during boot, which essentially simulates what happens with a GMS device that is being provisioned manually. 

For this, we need to modify <Android_root>/system/core/rootdir/init.rc by adding the following code below the post-fs-data section:

# enable esper agent to be device admin/owner
copy /system/etc/device_owner_2.xml /data/system/device_owner_2.xml
copy /system/etc/device_policies.xml /data/system/device_policies.xmlcopy /system/etc/device_owner.xml /data/system/device_owner.xml
chmod 0600 /data/system/device_owner_2.xmlchmod 0600 /data/system/device_owner.xml
chmod 0600 /data/system/device_policies.xml
chown system system /data/system/device_owner_2.xmlchown system system /data/system/device_owner.xml
chown system system /data/system/device_policies.xml

Remote Viewer Configuration

The Esper platform offers the ability to remotely view your device. While this should work out of the box for any device running Android 9+, there are still some devices running older versions of Android which require a slight workaround to avoid having to grant permissions in person every time you attempt to remotely view the device. If you are running < Android 9, follow the steps below.

Look at the following file:

<Android_Root>/frameworks/base/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java

If it is a MTK device, SystemUI from the above location is not used. You can find the SystemUI for MTK at:

<Android_Root>/vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java

It will look something like what is shown below:

public class MediaProjectionPermissionActivity extends Activity
    implements DialogInterface.OnClickListener, CheckBox.OnCheckedChangeListener,
    DialogInterface.OnCancelListener {
.
.
.

Here is the change that needs to be made:

.
.
try {
      // allow esper agent to start remote viewer automatically
      if (mPackageName.equals("io.shoonya.shoonyadpc") ||
          mService.hasProjectionPermission(mUid, mPackageName)) {
      setResult(RESULT_OK, getMediaProjectionIntent(mUid, mPackageName,
                      false /*permanentGrant*/));
              finish();

A diff of the changes looks something like this:

Remote Control

The APIs required for remote control are not exposed in Android to user apps, for valid security reasons. However, these may be accessed if the application in question is granted system permissions, which are only allowed by the system to apps in /system and signed with the same signature as the OEM/ODM image. To workaround this problem, we use a “supervisor” plugin, which essentially obtains the required system permissions and works together with the Agent/Helper to enable remote control. If Remote Control functionality is important to you, you need to:

  1. Contact us at Esper to generate a specific version of the supervisor targeted just for your device. 
  2. Add the apk to packages/apps, in the same way as was done for the agent and the helper.
  3. If you are building your own image, ensure that the app is signed with the same signature you would use for your release builds. If the OEM/ODM is building the image, ensure that they do the same. 

Esper Cloud Configuration

Configuring the Esper Cloud for seamlessly provisioned devices is very easy. Assuming you have an Esper endpoint created by signing up on esper.io and have already set up a Provisioning Template using our user-friendly interface, you’ll then need to perform the following steps to add devices to your template:

  1. First, obtain the serial number (or for a cellular device, the IMEI number) of the device in question. If you are working with a fleet of devices, you can also create a CSV file with a list of all the models and serial numbers to batch add devices. 
  1. In the Esper Console, click on Provisioning Templates found in the left menu bar.
  1. Find the Provision Template you wish to use for provisioning your device, and click the 3-dot ellipsis overflow menu, then click Edit. 
  1. Go to the “Add Devices” tab (you can directly click on it in the top menu bar to skip the other sections) and either individually add the serial number/IMEI or upload a previously generated CSV.
  1. Click on Next, then Update and that’s it! You’re all set for seamless provisioning. Now, when the device connects to the internet upon first boot, Esper will set up the device according to this template.

Conclusion

Through this article you’ve learned how you can set up your AOSP image to support seamless provisioning using Esper. The benefits are clear:

  • A very operationally efficient onboarding process, even more efficient than the available GMS enrollment methods.
  • The ability to employ less technical resources at deployment locations, thus saving cost and truck rolls.
  • Enabling just-in-time provisioning configuration providing more flexibility for onboarding in rapidly changing deployment situations.
  • No additional cost for Esper’s paid customers.

We encourage you to prototype with Esper’s seamless provisioning. It is available to any Esper user to give it a try. Note that when moving to production, we do require an Esper contract being in place.