Enterprises today are increasing the efficiency of their remote workforce, while at the same time ensuring corporate information is protected, by leveraging Device Management (DM) technologies. Enterprise IT departments are now able to control and enforce policies "over the air", to remotely configure, wipe, and even lock devices, increasing the productivity or their workforce. Motorola's Enterprise Device Management (EDM) SDK provides additional Device Management capabilities by extending the Android™ platform's DM capabilities on supported Motorola devices. For businesses such as yours, this means increased efficiency and security for your employees, your services and your IP.
Now, let's look more closely at how you as a developer can create your own Android client as a component of your corporate IT Device Management system. An Android DM client and its design are key to successful DM systems on Motorola devices. This guide gives you an understanding of the basics required of your application to enable it as a device manager and also helps you get started designing and developing your Android DM client.
Supported devices
- ELECTRIFY™ 2 XT881
- XT626 XT626
- RAZR™ V XT885
- ATRIX™ HD MB886
- RAZR™ V XT889
- RAZR™ V MT887
- RAZR™ MAXX XT910
- RAZR™ XT909
- MOTO™ XT928
- DROID 4 by Motorola, XT894
- DROID RAZR™ MAXX by Motorola, XT912
- MOTO™ MT917
- MOTO™ ME632
- RAZR™ XT910
- ADMIRAL™ XT603
- MOTO™ ME525+
- ... and more
Installing the EDM SDK
The EDM SDK is not a "stand-alone" SDK. Rather, it is a component of the SDK add-ons for the supported Motorola devices listed above. Developers wanting to create DM Android client applications using the EDM API only need to download and install an add-on for their target device.
AndroidManifest.xml
The AndroidManifest.xml file is mandatory in all Android applications. It resides at the root of your Android project and identifies your application to the Android platform on your device. We'll assume that you're using MOTODEV Core Plugins. When you create a New Android project from the MOTODEV menu, the application's AndroidManifest.xml file is created automatically and you'll only need to modify the file as necessary.
Let's start off with a look at the boilerplate AndroidManifest.xml file you'll get when starting a new project with MOTODEV Core Plugins built targeting Android 2.2 Software Development Kit (SDK).
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="<your package here>" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".BlankProjectActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> <uses-sdk android:minSdkVersion="8" /> </manifest>
Linking to the EDM library
The first addition to the manifest is the addition of the <uses-library> element to tell the system to include the EDM library in the memory when the application is run. The change is simple and involves adding the following line inside the manifest's <application> element.
<uses-library android:name="com.motorola.app.admin" />
This seemingly innocent addition of the <uses-library> element to the manifest actually has far-reaching effects that need to be examined further. Applications that specify the <uses-library> element cannot be installed on devices that do not have the specified library. In fact, the application, if available on Android Market, is hidden from devices that do not include the specified library. This may or may not be the behavior you want for your application, though. If you want your application to install on devices that don't have the EDM Library, that is still possible. Introduced in API Level 4 is the android:required flag. Setting the flag to 'false' would still allow the application to install on devices that do not have the specified library and would load the library into memory for the application to use on devices that did have the library. We strongly encourage you to read Google's recommendations when considering whether or not to include the android:required flag in your manifest for this purpose.
Declaring your DeviceAdminReceiver
The next manifest change we'll examine is the addition of the <receiver> element to declare the application's DeviceAdminReceiver. A DeviceAdminReceiver is mandatory for applications using the DevicePolicyManager interface. This means it's also mandatory for you to use Motorola's EDM SDK, because in the background, the SDK uses the basic core Android DPM features. Your receiver must handle the Broadcast Action android.app.action.DEVICE_ADMIN_ENABLED and request the android.permission.BIND_DEVICE_ADMIM permission. This allows the Android platform to interact with the application as a device administrator. Here is how a typical receiver declaration looks.
<receiver android:name=".DeviceManagementReceiver" android:label="@string/sample_device_admin" android:description="@string/sample_device_admin_description" android:permission="android.permission.BIND_DEVICE_ADMIN"> <meta-data android:name="android.app.device_admin" android:resource="@xml/device_admin_sample" > </meta-data> <intent-filter> <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" /> </intent-filter> </receiver>
Adding permissions
The <uses-permission> element in the manifest requests permissions from the system so that the application can behave correctly. There are no new Motorola-specific permissions required to use the EDM API. Some of the API do require that the application communicate with an enterprise server over HTTP. Therefore, inside the manifest's application you should specify at least the following permission for your application.
<uses-permission android:name="android.permission.INTERNET" />
The following section shows the revised version of the AndroidManifest.xml file with the above additions.
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="<your package here>" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <!-- ================== Revision start =================== --> <uses-library android:name="com.motorola.app.admin" /> <!-- =================== Revision end ==================== --> <activity android:name=".BlankProjectActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <!-- ================== Revision start =================== --> <receiver android:name=".DeviceManagementReceiver" android:label="@string/sample_device_admin" android:description="@string/sample_device_admin_description" android:permission="android.permission.BIND_DEVICE_ADMIN"> <meta-data android:name="android.app.device_admin" android:resource="@xml/device_admin" > </meta-data> <intent-filter> <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" /> </intent-filter> </receiver> <!-- =================== Revision end ==================== --> </application> <uses-sdk android:minSdkVersion="8" /> <!-- ================== Revision start =================== --> <uses-permission android:name="android.permission.INTERNET" /> <!-- =================== Revision end ==================== --> </manifest>
Intents and receivers
As mentioned above, Device Management on Android currently requires that the application publish a DeviceAdminReceiver that the user has enabled. This holds true for applications leveraging the EDM API as well. Let's start by examining how you can implement a DeviceAdminReceiver in your application. To further improve the cleanliness and readability of your code, you may want to consider separating the DeviceAdminReceiver from your applications main activities. One advantage of this approach becomes clear as your project increases in size. The following simplified code snippet is taken from the EDM API sample application and illustrates how you can extend the DeviceAdminReceiver class and override its convenience methods.
public class DeviceManagementReceiver extends DeviceAdminReceiver {
private static String TAG = "DeviceManagementSample";
void showToast(Context context, CharSequence msg) {
Log.v( TAG, msg.toString() );
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
}
@Override
public void onEnabled( Context context, Intent intent ) {
showToast( context, "Sample Device Admin: enabled" );
}
@Override
public CharSequence onDisableRequested( Context context, Intent intent ) {
return "Thank you for using our Device Management app!";
}
***
} // end DeviceManagementReceiver class
onReceive() method is for the DeviceManagementReceiver implementation above. Of course, you are free to override the onReceive() method for your receiver. Be aware though that doing so will prevent any of the other DeviceAdminReceiver convenience methods from being called.
Your application's DeviceAdminReceiver needs to be enabled on the device by the end user as mentioned above. This requires your application to launch the Device Administrator Activity on the device, which prompts the user to enable or cancel the request from your application to set it as a device administrator. This can be achieved using an Intent object and the startActivityForResult() method as shown in the code snippet below taken from the EDM API sample application.
private CheckBox checkBox = null;
static final int RESULT_ENABLE = 1;
***
checkBox = ( CheckBox ) findViewById( R.id.enable_disable_checkbox );
checkBox.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() {
public void onCheckedChanged( CompoundButton buttonView, boolean isChecked ) {
if ( isChecked ) {
Log.v( TAG, " onCheckedChanged isChecked : " + isChecked );
// Launch 'Activate device administrator?' screen
Intent intent = new Intent( DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN );
intent.putExtra( DevicePolicyManager.EXTRA_DEVICE_ADMIN, mDeviceManagementSample );
startActivityForResult( intent, RESULT_ENABLE );
} else {
mDPM.removeActiveAdmin( mDeviceManagementSample );
updateButtonStates();
}
}
});
Intent objects? Learn more by reading the Android documentation on intent filters.
Because the code above launches the Device Administrator window via the startActivityForResult() method, the user operation in the Device Administrator window – enable or cancel – can be retrieved in the calling Activity if we override the onActivityResult() method and checking the resultCode as shown below.
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case RESULT_ENABLE:
if (resultCode == Activity.RESULT_OK) {
// handle case where user accepted request to enable DeviceAdminReceiver
} else {
// handle case where user canceled the request
}
return;
}
super.onActivityResult( requestCode, resultCode, data );
}
Broadcast receivers and EDM asynchronous methods
Some of the Motorola EDM API calls are asynchronous. After the method call is made, execution continues even when control is passed to the next line of code. Let's look at some EDM API asynchronous methods and how to design your application to use them. If you want to retrieve the Exchange ActiveSync Device ID for the device, you can do so through the asynchronous DevicePolicyManagerExt.getActiveSyncDeviceId(DevicePolicyManager dpm). The EDM SDK documentation specifies that the method has the following signature:
public void getActiveSyncDeviceId(DevicePolicyManager dpm)
getActiveSyncDeviceId() has a void return value as you can see. The Exchange ActiveSync Device ID is retrieved asynchronously via a BroadcastReceiver set up to listen for a specific Broadcast Action. The onReceive() method shown in this code snippet below is taken from the EDM SDK sample application and illustrates how the ActiveSync Device ID can be extracted from the intent bundle extra.
public class SyncIdReceiver extends BroadcastReceiver {
public static final String ACTIVE_SYNC_ID = "android.active.intent.action.SYNC_ID";
@Override
public void onReceive( Context context, Intent intent ) {
if ( ACTIVE_SYNC_ID.equals( intent.getAction() ) ) {
Bundle b = intent.getBundleExtra( "bundle" );
String syncID = b.getString( "activeSyncID" );
Toast.makeText( context, syncID, Toast.LENGTH_LONG).show();
***
The BroadcastReceiver in this case, SyncIdReceiver, is declared and registers the intent it is interested in the application's manifest file. Here is the section of the sample application's manifest that handles the registration for the "android.active.intent.action.SYNC_ID" Broadcast Action.
<receiver android:name=".SyncIdReceiver" android:permission="android.permission.BIND_DEVICE_ADMIN" > <intent-filter> <action android:name="android.active.intent.action.SYNC_ID" /> </intent-filter> </receiver>
"mot.app.admin.edm.ERROR_STATUS" Broadcast Action. We'll examine error handling more closely later in this article.
Bundled into the "mot.app.admin.edm.ERROR_STATUS" intent is the result of the asynchronous DevicePolicyManagerExt.getVpnById(VpnConfig vpnConfig, DevicePolicyManager dpm) method. Applications calling this method should register a BroadcastReceiver to listen for the "mot.app.admin.edm.ERROR_STATUS"Action and query the Intent's extra Bundle using the "EXTRA_ARGS" label. This is typically done in the receiver's onReceive() method. The following is a snippet from EDM SDK's sample application's manifest and illustrates the BroadcastReceiver can be registered.
<receiver android:name=".ResultReceiver" android:permission="android.permission.BIND_DEVICE_ADMIN" > <intent-filter> <action android:name="mot.app.admin.edm.ERROR_STATUS" /> </intent-filter> </receiver>
A successful call to the DevicePolicyManagerExt.getVpnById() method will result in the interested receiver's onReceive() method being called. The VPN values can then be extracted from the Intent passed to the onReceive() method. This is illustrated in this code snippet taken from the EDM SDK sample application.
@Override
public void onReceive( Context context, Intent intent ) {
String intentString = intent.getAction();
if ( intentString.equals( EdmErrorCode.ACTION_EDM_ERROR_STATUS.toString() )) {
// use this for VPN Config Strings
Bundle extraArgs = intent.getBundleExtra( "EXTRA_ARGS" );
if ( extraArgs != null ) {
Log.d( TAG,"VPN_ID = " + extraArgs.getString("VPN_ID") );
Log.d( TAG,"VPN_TYPE = " + extraArgs.getString("VPN_TYPE") );
Log.d( TAG,"VPN_NAME = " + extraArgs.getString("VPN_NAME" );
Log.d( TAG,"VPN_SERVER_NAME = " + extraArgs.getString("VPN_SERVER_NAME") );
Log.d( TAG,"VPN_DOMAIN_SUFFICES = " + extraArgs.getString("VPN_DOMAIN_SUFFICES") );
Log.d( TAG,"L2TP_SECRET_ENABLED = " + extraArgs.getString("L2TP_SECRET_ENABLED") );
Log.d( TAG,"L2TP_SECRET = " + extraArgs.getString("L2TP_SECRET") );
Log.d( TAG,"CA_CERT = " + extraArgs.getString("CA_CERT") );
Log.d( TAG,"USER_CERT = " + extraArgs.getString("USER_CERT") );
Log.d( TAG,"PSK = " + extraArgs.getString("PSK") );
Log.d( TAG,"ENCRYPT_ENABLED = " + extraArgs.getString( "ENCRYPT_ENABLED") );
}
***
EDM API examined
Now, let's get a high-level view of EDM API method calls and how the components comprising a typical Enterprise Device Management system relate to each other. For specific EDM API method signatures and details, consult the API documentation available in the /doc directory in the EDM SDK.
Figure 1 illustrates good practice for any EDM calls to be made. It is essential that the standard Android (v2.2 and later) call DevicePolicyManager.isAdminActive() be made to ensure Device Administration is enabled on the device before making any Motorola EDM API method calls. We also recommend that you disable any buttons, checkboxes, or other UI components that when clicked invoke EDM API method calls. Disabling UI components in this way produces more pleasant end-user experience.
Enterprises need to control the device, wiping critical data from devices when needed. To perform this, Android provides an API, DevicePolicyManager.wipeData(), which clears the user data on the device. However, enterprises may want to clear the content stored on the SD card as well. Motorola's EDM API provides a method to wipe the SD card content and also provides a feature to selectively clear data on the SD card based on a certain pattern through the DevicePolicyManagerExt.wipePattern(DevicePolicyManager dpm, java.lang.String pattern) method. Figure 2 illustrates remote SD card wiping.
Enterprises prefer to set up corporate email on device remotely. This is possible on Motorola's devices through an API exposed by the Motorola EDM API. The email address, user name, password, profile can all be set using the API addEmailAccount(). It takes an EnterpriseActiveSync(EasConfig) settings object as a parameter. EasConfig allows for setting the profile ID, email address, username, password, host server, and whether or not SSL is used for the account. The account setup can also be removed using the API removeEmailAccount(). It takes an EnterpriseActiveSync(EasConfig) settings object as a parameter. Figures 3 and 4 illustrate graphically adding and removing enterprise email accounts on the device.
Enterprises may prefer to have an Android client on their devices that can configure devices for their workforce. The configuration may require certificates to be installed on the device for added security, which can be done with the DevicePolicyManagerExt.installCertificate(CertificateConfig certConfig, DevicePolicyManager dpm) method. This API takes a CertificateConfig object as a parameter, which can be configured with the certificate ID, certificate type, password and the certificate's raw data. Figure 5 illustrates the call to installCertificate() graphically.
Figures 6 and 7 illustrate calls to add and remove VPN accounts on the device. Enterprises may prefer to communicate with their remote workforce via secure VPN connections on the device. Motorola devices supporting the EDM API allow this VPN communication to be controlled remotely. There are two APIs exposed for this purpose. VPN account configuration (adding a VPN account) is controlled via the DevicePolicyManagerExt.configureVpn(VpnConfig vpnConfig, DevicePolicyManager dpm) method. Removing VPN accounts is done via the DevicePolicyManagerExt.deleteVpn(VpnConfig vpnConfig, DevicePolicyManager dpm) method. Both of these methods take a VpnConfig object as a parameter. Two VpnConfig subclasses are available for configuring accounts for L2TP- and PPTP-type VPNs.
Handling errors
Anticipating what errors may occur during the lifetime of an application is an arduous task. Network failures or lack of memory on the device are outside of your control and could prevent your application from behaving as expected. Handling your errors elegantly is an integral part of your application design. Let's examine how error codes in the EDM API are returned to the calling application.
First, let's examine the high-level view of EDM API error handling illustrated in Figure 8. Enterprises need to know when and if errors occur on one of their devices. EDM API method call results and failures are broadcast back to the client application asynchronously and can be retrieved by a broadcast receiver registered to listen for the "mot.app.admin.edm.ERROR_STATUS" action (We saw this same broadcast action earlier when introducing the DevicePolicyManagerExt.getVpnById() method.).
A receiver interested in the "mot.app.admin.edm.ERROR_STATUS" broadcast action receives result codes via an intent passed to its onReceive() method. Individual method results can be extracted as shown below. All possible result codes returned to the calling application for EDM API method calls are declared in the EdmErrorCode class. Consult the EDM API documentation for the result code details.
@Override
public void onReceive( Context context, Intent intent ) {
// Check error code
int code = (int)intent.getIntExtra( EdmErrorCode.ERROR_CODE.toString(), 0 );
switch( code ) {
case EdmErrorCode.EDM_ERROR_STATUS_SUCCESS:
Log.v( TAG, "EDM_ERROR_STATUS_SUCCESS" );
break;
case EdmErrorCode.EDM_ERROR_STATUS_CERT_CANCELLED:
Log.v( TAG, "EDM_ERROR_STATUS_CERT_CANCELLED" );
break;
***
}
}
EDM SDK and unsupported devices
We've already discussed how the <uses-library> tag will prevent users from installing your application on devices that do not have the EDM library. Don't think that as a developer you'll need to develop separate versions of your application, though. There is a tried and true way to design your application so that it installs on both EDM SDK supported and unsupported devices. In this case, your application could call the EDM API when running on a supported device and would handle unsupported devices in some proprietary way of your choice. Next, we'll look at how Java reflection can be used to inspect classes and methods at runtime, giving you the opportunity to do one thing when the class or method is available and another when it is not.
Let's imagine that you have been tasked with coding the Android client for a company's DM system. Your boss has made the very wise decision to leverage the EDM SDK but only wants one version of the application available. The same application must target both devices that support the EDM SDK and for those that don't. What does this mean to you? Well, among many other things, you are going to first need to make sure that the application can even install on the unsupported devices. Plus, you are going to need a way to check first if an EDM API exists on the device before making a call to it. The first issue you already know how to solve. You can use the android:required="false" attribute in the <users-library> element to allow the application to install on devices that do not support the EDM API. Declared this way, the <uses-library> element also makes sure Android loads the library in memory when your application runs on supported devices. Here is how the element looks in your manifest file.
<uses-library android:name="com.motorola.app.admin" android:required="false" />
Now, your second priority is what to do when your application wishes to make an EDM API method call. This is where Java reflection comes in. First, we'll show you some simple code that will first check if a method (in our example we'll use the DevicePolicyManagerExt.addEmailAccount() method) even exists on the device before calling it and what to do in cases when the API does not exist. We'll then explain what is happening in the code to give you an idea how a similar approach can work for your application as well.
***
private static Method edm_EDMExt_getEdmMethod = null;
***
/*
* Check that a EDM API method exists on the device.
* Handling cases where no method exists elegantly.
*/
private void checkForMethod( String methodName ) {
Log.v( TAG, " checkForMethod methodName: " + methodName );
try {
edm_EDMExt_getEdmMethod = DevicePolicyManagerExt.class.getMethod( methodName, null );
// If we reach here with no exception then we know that there is a method called
// 'methodName'. We can safely invoke the method
if( edm_EDMExt_getEdmMethod != null ) {
if( managerExt != null ) {
if( methodName.equals( "addEmailAccount" ) == true ) {
// Make call to DevicePolicyManagerExt.addEmailAccount
} else if( methodName.equals( "configureVpn" ) == true ) {
// Make call to DevicePolicyManagerExt.configureVpn();
} else {
// Do something here
}
}
}
} catch ( NoSuchMethodException nsme ) {
// Handle cases where the API does not exist here.
Toast.makeText( this, "No " + methodName + " on device", Toast.LENGTH_LONG).show();
}
}
Instances of the Java Method class provides you with a way to get information about and a way to access a particular method. In line 02 above, we declare just such a private member in our class.
private static Method edm_EDMExt_getEdmMethod = null;
The Class.getMethod() method takes as its first parameter a String representing the name of the method you are interested in. In our case we'll be passing in the String "addEmailAccount". getMethod() will throw a java.lang.NoSuchMethodException exception if the method does not exist on the device. If a method that matches the first parameter does exist, then getMethod() returns a Method object, through which we can access the method on the device and even call it dynamically if we want. In our example, we'll call the method as long as there is no java.lang.NoSuchMethodException thrown. Line 12 illustrates how we check if the "addEmailAccount()" method exists on the device.
edm_EDMExt_getEdmMethod = DevicePolicyManagerExt.class.getMethod( methodName, null );
If there is no java.lang.NoSuchMethodException thrown, we can go ahead and call the method as shown in lines 16 to 24. If the method does not exist then we catch the exception and handle that case in some proprietary way, as shown in lines 26 to 29.
Conclusion
Whether you are an enterprise IT manager responsible for the entire corporate network and its data or a member of a remote workforce dependent on your mobile device when in the field, Device Management systems are becoming an indispensable segment for the overall security of corporate data and the efficiency of its remote workforce. This article outlined the first steps required to develop an Android application that uses the Motorola EDM API to make up the client component of a Device Management system. You should follow the guidelines set forth in the article to ensure you application can be enabled as a Device Administrator and receive the results of various asynchronous API method calls. For more information and help on the Motorola EDM API SDK and its usage, join in the discussion on the MOTODEV Discussion Boards – just click Comment on this page below.
ECCN 5D992.a: In accordance with United States Export Administration Regulations (EAR), and specifically the Commerce Control List (CCL), this item has been classified 5D992.a. Export or re-export of this commodity and compliance with the U.S. Export Administration Regulations is ultimately the responsibility of the exporter. For more detailed information related to export or re-export of this item, please consult the EAR at http://www.access.gpo.gov/bis/ear/ear_data.html.
Copyright © 2010, Motorola Mobility, Inc. All rights reserved unless otherwise explicitly indicated. Sample source code written by Motorola Mobility, Inc. is provided to you under the conditions of the Motorola Modified BSD License.









