First introduced in Android 2.2, the Device Admin policies grant abilities to apps to gain a greater level of device control. These features are primarily aimed at enterprise app developers given their controlling, restrictive, and potentially destructive nature, and offer an alternative to a third-party Mobile Device Management (MDM) solution. In general, this is not aimed at consumer apps unless a trust relationship already exists, for example, a bank and a banking app.
This recipe will define two device policies designed to strengthen the device that could be part of an enterprise's mobile security policy:
Although device encryption is no replacement for ensuring that the app data is encrypted properly, it does add to the overall device security. Reducing the maximum screen lock timeout helps protect the device if left unattended.
There is no restriction on the number of apps enforcing device policies. If there is a conflict on policy, the system defaults to the most secure policy. For example, if there was a conflict on the password strength requirement's policy, the strongest policy would be applied to satisfy all policies.
The Device Admin policies were added in Version 2.2; however, this feature and the specific restriction for device encryption were not added until Android 3.0. Therefore, for this recipe, ensure that you are building against a SDK above API 11.
Let's get started.
.xml
file called admin_policy_encryption_and_lock_timeout.xml
in the res/xml
folder with the following content:<device-admin xmlns:android="http://schemas.android.com/apk/res/android" > <uses-policies> <force-lock /> <encrypted-storage /> </uses-policies> </device-admin>
DeviceAdminReceiver
class. This is the app entry point for system broadcasts relating to device administration:public class AppPolicyReceiver extends DeviceAdminReceiver { // Called when the app is about to be deactivated as a device administrator. @Override public void onDisabled(Context context, Intent intent) { // depending on your requirements, you may want to disable the // app or wipe stored data e.g clear prefs context.getSharedPreferences(context.getPackageName(), Context.MODE_PRIVATE).edit().clear().apply(); super.onDisabled(context, intent); } @Override public void onEnabled(Context context, Intent intent) { super.onEnabled(context, intent); // once enabled enforce AppPolicyController controller = new AppPolicyController(); controller.enforceTimeToLock(context); controller.shouldPromptToEnableDeviceEncrpytion(context); } @Override public CharSequence onDisableRequested(Context context, Intent intent) { // issue warning to the user before disable e.g. app prefs // will be wiped return context.getText(R.string.device_admin_disable_policy); } }
<receiver android:name="YOUR_APP_PGK.AppPolicyReceiver" android:permission="android.permission.BIND_DEVICE_ADMIN" > <meta-data android:name="android.app.device_admin" android:resource="@xml/admin_policy_encryption_and_lock_timeout" /> <intent-filter> <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" /> <action android:name="android.app.action.DEVICE_ADMIN_DISABLED" /> <action android:name="android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED" /> </intent-filter> </receiver>
Defining the receiver allows AppPolicyReceiver
to receive system broadcast intents to disable/request disabling of the admin settings. You should also notice that this is where we reference the policy XML file in the metadata via the filename admin_policy_encryption_and_lock_timeout
.
DevicePolicyManager
with any additional application-specific logic. The first method that we defined is for other application components (such as an activity) to validate device admin status and to get intents that are specific to device admin:public class AppPolicyController { public boolean isDeviceAdminActive(Context context) { DevicePolicyManager devicePolicyManager = (DevicePolicyManager) context .getSystemService(Context.DEVICE_POLICY_SERVICE); ComponentName appPolicyReceiver = new ComponentName(context, AppPolicyReceiver.class); return devicePolicyManager.isAdminActive(appPolicyReceiver); } public Intent getEnableDeviceAdminIntent(Context context) { ComponentName appPolicyReceiver = new ComponentName(context, AppPolicyReceiver.class); Intent activateDeviceAdminIntent = new Intent( DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN); activateDeviceAdminIntent.putExtra( DevicePolicyManager.EXTRA_DEVICE_ADMIN, appPolicyReceiver); // include optional explanation message activateDeviceAdminIntent.putExtra( DevicePolicyManager.EXTRA_ADD_EXPLANATION, context.getString(R.string.device_admin_activation_ message)); return activateDeviceAdminIntent; } public Intent getEnableDeviceEncryptionIntent() { return new Intent(DevicePolicyManager.ACTION_START_ENCRYPTION); }
AppPolicyController
, we now define the method that actually enforces the lock screen timeout. We've arbitrarily chosen a maximum lock time of 3
minutes, but this should align with an enterprise's security policy:private static final long MAX_TIME_TILL_LOCK = 3 * 60 * 1000; public void enforceTimeToLock(Context context) { DevicePolicyManager devicePolicyManager = (DevicePolicyManager) context .getSystemService(Context.DEVICE_POLICY_SERVICE); ComponentName appPolicyReceiver = new ComponentName(context, AppPolicyReceiver.class); devicePolicyManager.setMaximumTimeToLock(appPolicyReceiver, MAX_TIME_TILL_LOCK); }
public boolean shouldPromptToEnableDeviceEncryption(Context context) { DevicePolicyManager devicePolicyManager = (DevicePolicyManager) context .getSystemService(Context.DEVICE_POLICY_SERVICE); int currentStatus = devicePolicyManager.getStorageEncryptionStatus(); if (currentStatus == DevicePolicyManager.ENCRYPTION_STATUS_INACTIVE) { return true; } return false; } }
AppPolicyController
to help direct the user to enable system settings and handle the responses:public class AppPolicyDemoActivity extends Activity { private static final int ENABLE_DEVICE_ADMIN_REQUEST_CODE = 11; private static final int ENABLE_DEVICE_ENCRYPT_REQUEST_CODE = 12; private AppPolicyController controller; private TextView mStatusTextView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_app_policy); mStatusTextView = (TextView) findViewById(R.id.deviceAdminStatus); controller = new AppPolicyController(); if (!controller.isDeviceAdminActive(getApplicationContext())) { // Launch the activity to have the user enable our admin. startActivityForResult( controller .getEnableDeviceAdminIntent(getApplicationContext()), ENABLE_DEVICE_ADMIN_REQUEST_CODE); } else { mStatusTextView.setText("Device admin enabled, yay!"); // admin is already activated so ensure policies are set controller.enforceTimeToLock(getApplicationContext()); if (controller.shouldPromptToEnableDeviceEncrpytion(this)) { startActivityForResult( controller.getEnableDeviceEncrpytionIntent(), ENABLE_DEVICE_ENCRYPT_REQUEST_CODE); } } }
onActivityResult(…)
activity lifecycle method to handle the results from the system activities when enabling device administration and encryption:@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == ENABLE_DEVICE_ADMIN_REQUEST_CODE) { if (resultCode != RESULT_OK) { handleDevicePolicyNotActive(); } else { mStatusTextView.setText("Device admin enabled"); if (controller.shouldPromptToEnableDeviceEncrpytion(this)) { startActivityForResult( controller.getEnableDeviceEncryptionIntent(), ENABLE_DEVICE_ENCRYPT_REQUEST_CODE); } } } else if (requestCode == ENABLE_DEVICE_ENCRYPT_REQUEST_CODE && resultCode != RESULT_OK) { handleDevicePolicyNotActive(); } }
private void handleDevicePolicyNotActive() { Toast.makeText(this, R.string.device_admin_policy_breach_message, Toast.LENGTH_SHORT).show(); } }
AppPolicyDemoActivity
shows an example of handling user interactions and callbacks in onActivityResult(…)
from the system activities for enabling the device administration and device encryption.
AppPolicyController
encapsulates interactions with DevicePolicyManager
and contains the logic to apply the policies. You could locate this code in your activity or fragment, but it's a better practice to keep it separate.
Defining the policies is as simple as defining them in the <uses-policies>
element of the device admin file. This is referenced in the metadata element of the AppPolicyReceiver
XML declaration in the Android manifest:
<meta-data android:name="android.app.device_admin" android:resource="@xml/admin_policy_encryption_and_lock_timeout" />
Given the elevated privileges of being a device administrator, apps are not enabled as device administrators on installation as a security precaution. This is achieved post install by using a build-in system activity that is requested using an intent with a special action AppPolicyController.getEnableDeviceAdminIntent()
as shown. This activity is started with startActivityForResult()
. This returns a callback to onActivityResult(…)
where the users choose to activate or cancel. Nonactivation of the device administration could count as being in breach of the enterprise security policy. Therefore, if the user doesn't activate it, it might be enough to simply prevent the user from using the app until it is activated.
We use the DevicePolicyManager.isActive(…)
method to check if the app is active as a device administrator. Typically, this check should be performed on the entry points to the application, such as the first activity.
The job of AppPolicyReceiver
is to listen for device administration system events. To receive these events, firstly you have to extend DeviceAdminReceiver
and define Receiver
in the Android manifest file. The OnEnabled()
callback is where we enforce the lock screen timeout as it requires no additional user input. Enabling device encryption requires user confirmation; therefore, we initiate this from the activity.
AppPolicyReceiver
will also receive an onDisabled
event if the user disables this application as a device administrator. What to do when a user disables your app as device administrator will vary between apps, as mentioned earlier it depends on enterprise security policy. There is also an onDisableRequested
callback method that allows us to display a specific message to the user, detailing the consequences of disabling the application. In this example, we wipe the SharedPreferences to ensure that data is not at risk while the device is noncompliant.
In addition to the policies used in this recipe, the device admin can enforce the following:
Users cannot uninstall apps that are active device administrators. To uninstall, they must first deactivate the app as a device administrator, and then uninstall it. This allows you to perform any necessary functions in DeviceAdminReceiver.onDisabled()
, for example, reporting an incident to a remote server.
Android 4.4 saw the introduction of an optional device admin feature constant to be used in the <uses-feature>
tag in the app's manifest.xml
file. This declares that the app requires device admin feature and ensures correct filtering on the Google Play store.
An interesting feature added in Android 4.0 was the ability to disable camera use. This can be useful for organizations looking to limit data leakage. The following code snippet shows the policy to enable an app to disable camera use:
<device-admin xmlns:android="http://schemas.android.com/apk/res/android" > <uses-policies> <disable-camera /> </uses-policies> </device-admin>
FEATURE_DEVICE_ADMIN
in the Android Developers Reference guide at https://developer.android.com/reference/android/content/pm/PackageManager.html#FEATURE_DEVICE_ADMIN3.147.78.145