Posted by: Ramit Pahwa
Introduction
With ever-growing mobile usage and with the recent advent of work from home scenarios, many enterprises are in the market for secure applications which can allow their employees to have access to sensitive data and prevent data leaks. Therefore, mobile applications which can securely enable the usage of sensitive data are the need of the hour in the enterprise ecosystem.
Mobile apps drive employee productivity and IT must move beyond static app distribution to meet security, management, and productivity. Enterprise Mobility Management (referred to as EMM) allows enterprises to enroll their employees in either a Bring-Your-Own-Device (BYOD) or Company-Owned-Personally-Enabled (COPE) device programs.
EMM addresses the challenges associated with mobile security by providing a simplified, efficient way to view and manage all devices from the central admin console. It allows enterprise IT administrators to prevent data leakage and remotely configure the application on these devices as per the company’s enterprise policy. Employees benefit from instant mobile productivity and a seamless out-of-the-box experience, and enterprises benefit from secure work-ready apps with minimal setup.
Android Mobile Applications has moved to a single application model which supports both personal and work environments from within the same application in a seamless and secure manner, thus enabling productivity. We showcase Android Application in both personal and Work mode (Notice the briefcase icon at the bottom right of the application icon) [1]
Setting up work profile
Different EMM vendors have a different workflow to help you create a work profile, or one can be provided to you by your organization.
Provision Work Profile
Here I will demonstrate the process to create a work profile using the TestDPC [2] application which can be used to simulate the Work Profile and Restrictions which can be applied to a work-managed android application. The creation, once you have downloaded the APK from the link, is straightforward. Once this provision is complete now you will see personal and work applications (for that application that supports Work Mode) separately on your device.
Note: You can access both personal and work profiles from setting application
Work mode in action
I will be using the enterprise sample application to demonstrate how the enterprise version of the application can behave in work mode. This basically gives the IT admin of the organization to control features of the application they want to restrict.
Manged Application
The application demonstrates how we can restrict the say hello button in a managed application.
The IT Administrator for the organization can specify the restrictions for the application and those functionalities will then be restricted for all the users of the application.
This allows companies to have a single application with these restrictions in place instead of the different distribution of APK and allows the organization to deploy according to their restrictions, implementation of this is explained in detail below.
Checking if the Application is Work Application?
Android provides us with DevicePolicyManager class, which lists down admin available for the device which can then be used to get the package name. The isProfileOwnerApp () or isDeviceOwnerApp () API helps us identify whether the application in question is managed or other the entire device is managed.
public boolean isAFWManagedApp(Context context) | |
{ | |
boolean isAFWManaged = false; | |
DevicePolicyManager devicePolicyManager = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); | |
List<ComponentName> activeAdmins = devicePolicyManager.getActiveAdmins(); | |
if (activeAdmins != null) | |
{ | |
for (ComponentName admin : activeAdmins) | |
{ | |
String packageName= admin.getPackageName(); | |
isAFWManaged = devicePolicyManager.isProfileOwnerApp(packageName); | |
if (isAFWManaged) | |
{ | |
break; | |
} | |
} | |
} | |
return isAFWManaged; | |
} | |
public boolean isAFWManagedDevice(Context context) | |
{ | |
boolean isAFWManaged = false; | |
DevicePolicyManager devicePolicyManager = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); | |
isAFWManaged = devicePolicyManager.isDeviceOwnerApp(context.getPackageName()); | |
return isAFWManaged; | |
} |
The restriction to be imposed are added as an XML wherein the default values for these restrictions are specified. The different types of restrictions which can be imposed are enumerated in the XML below, here I will be showcasing how one of the listed restrictions can be used to control application behavior.
<restrictions xmlns:android="http://schemas.android.com/apk/res/android"> | |
<!-- | |
Refer to the javadoc of RestrictionsManager for detail of this file. | |
https://developer.android.com/reference/android/content/RestrictionsManager.html | |
--> | |
<!-- Boolean restriction --> | |
<restriction | |
android:defaultValue="@bool/default_can_say_hello" | |
android:description="@string/description_can_say_hello" | |
android:key="can_say_hello" | |
android:restrictionType="bool" | |
android:title="@string/title_can_say_hello"/> | |
<!-- String restriction --> | |
<restriction | |
android:defaultValue="@string/default_message" | |
android:description="@string/description_message" | |
android:key="message" | |
android:restrictionType="string" | |
android:title="@string/title_message"/> | |
<!-- Integer restriction --> | |
<restriction | |
android:defaultValue="@integer/default_number" | |
android:description="@string/description_number" | |
android:key="number" | |
android:restrictionType="integer" | |
android:title="@string/title_number"/> | |
<!-- Choice restriction --> | |
<restriction | |
android:defaultValue="@string/default_rank" | |
android:description="@string/description_rank" | |
android:entries="@array/entries_rank" | |
android:entryValues="@array/entry_values_rank" | |
android:key="rank" | |
android:restrictionType="choice" | |
android:title="@string/title_rank"/> | |
<!-- Multi-select restriction --> | |
<restriction | |
android:defaultValue="@array/default_approvals" | |
android:description="@string/description_approvals" | |
android:entries="@array/entries_approvals" | |
android:entryValues="@array/entry_values_approvals" | |
android:key="approvals" | |
android:restrictionType="multi-select" | |
android:title="@string/title_approvals"/> | |
<!-- Hidden restriction --> | |
<restriction | |
android:defaultValue="@string/default_secret_code" | |
android:description="@string/description_secret_code" | |
android:key="secret_code" | |
android:restrictionType="hidden" | |
android:title="@string/title_secret_code"/> | |
<!-- Bundle array restriction --> | |
<restriction | |
android:description="@string/description_items" | |
android:key="items" | |
android:restrictionType="bundle_array" | |
android:title="@string/title_items"> | |
<!-- Bundle array must have one bundle restriction --> | |
<restriction | |
android:key="item" | |
android:restrictionType="bundle" | |
android:title="@string/title_item"> | |
<restriction | |
android:key="key" | |
android:restrictionType="string" | |
android:title="@string/title_key"/> | |
<restriction | |
android:key="value" | |
android:restrictionType="string" | |
android:title="@string/title_value"/> | |
</restriction> | |
</restriction> |
These restrictions can be stored in SharedPreference so that they can be accessed across application sessions. We have the RestrictionsManager class which can be used to get all getManifestRestrictions() which returns a list of restrictions. We then store the preference for the restrictions to be utilized to check the availability of a particular feature i.e. if the feature is allowed by the IT admin for your organization.
public static final String AFW_RESTRICION_PREFERENCES = "com.adobe.reader.restrictions"; | |
public static final String ENABLE_HELLO_PREFS_KEY = "enablePrintingKey"; | |
private static final String KEY_CAN_SAY_HELLO = "can_say_hello"; | |
private boolean getPreferenceValue(Context context, Bundle restrictions, String keyValue, @BoolRes int id) | |
{ | |
boolean enableKey = context.getResources().getBoolean(id); | |
if (restrictions != null && restrictions.containsKey(keyValue)) | |
{ | |
enableKey = restrictions.getBoolean(keyValue); | |
} | |
return enableKey; | |
} | |
private boolean isEnabledInAFW(Context context, String preferenceKey, @BoolRes int id) | |
{ | |
boolean isEnabled = false; | |
if(context != null) | |
{ | |
SharedPreferences preferences = context.getSharedPreferences(AFW_RESTRICION_PREFERENCES, Context.MODE_PRIVATE); | |
isEnabled = preferences.getBoolean(preferenceKey, context.getResources().getBoolean(id)); | |
} | |
return isEnabled; | |
} | |
private void resolveRestrictions() | |
{ | |
RestrictionsManager manager = (RestrictionsManager) getActivity().getSystemService(Context.RESTRICTIONS_SERVICE); | |
Bundle restrictions = manager.getApplicationRestrictions(); | |
List<RestrictionEntry> entries = manager.getManifestRestrictions(getActivity().getApplicationContext().getPackageName()); | |
SharedPreferences preferences = getContext().getSharedPreferences(AFW_RESTRICION_PREFERENCES, Context.MODE_PRIVATE); | |
for (RestrictionEntry entry : entries) | |
{ | |
String key = entry.getKey(); | |
String preferencesKey = null; | |
boolean preferencesValue = false; | |
Log.d(TAG, "key: " + key); | |
if (key.equals(KEY_CAN_SAY_HELLO)) | |
{ | |
preferencesKey = ENABLE_HELLO_PREFS_KEY; | |
preferencesValue = getPreferenceValue(getContext(), restrictions, KEY_CAN_SAY_HELLO, R.bool.default_can_say_hello); | |
} | |
if (preferencesKey != null) | |
{ | |
SharedPreferences.Editor editor = preferences.edit(); | |
editor.putBoolean(preferencesKey, preferencesValue); | |
editor.apply(); | |
} | |
} | |
} | |
Job Offers
tired of reading
Take a break an watch a video
No results found.
Finally …
The above code can be used to control the feature inside the work application, thus providing the additional functionality for the organization to restrict some features while allowing others in the work-managed application. We need to create a broadcast receiver for ACTION_APPLICATION_RESTRICTIONS_CHANGED which sends out a broadcast when restrictions change.
public class AppRestrictionSchemaFragment extends Fragment implements View.OnClickListener | |
{ | |
private static final String KEY_CAN_SAY_HELLO = "can_say_hello"; | |
@Override | |
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) | |
{ | |
mTextSayHello = (TextView) view.findViewById(R.id.say_hello_explanation); | |
mButtonSayHello.setOnClickListener(this); | |
} | |
@Override | |
public void onResume() { | |
super.onResume(); | |
resolveRestrictions(); | |
updateCanSayHello(); | |
} | |
@Override | |
public void onStart() | |
{ | |
super.onStart(); | |
// We need to create a broadcast receiver for ACTION_APPLICATION_RESTRICTIONS_CHANGED which send out a broadcast when restrictions change | |
mBroadcastReceiver = new BroadcastReceiver() { | |
@Override | |
public void onReceive(Context context, Intent intent) | |
{ | |
resolveRestrictions(); | |
updateCanSayHello(); | |
} | |
}; | |
getActivity().registerReceiver(mBroadcastReceiver,new IntentFilter(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED)); | |
} | |
@Override | |
public void onStop() | |
{ | |
super.onStop(); | |
if (mBroadcastReceiver != null) { | |
getActivity().unregisterReceiver(mBroadcastReceiver); | |
mBroadcastReceiver = null; | |
} | |
} | |
@Override | |
public void onClick(View view) | |
{ | |
switch (view.getId()) | |
{ | |
case R.id.say_hello: | |
{ | |
Toast.makeText(getActivity(), getString(R.string.message, mMessage), | |
Toast.LENGTH_SHORT).show(); | |
break; | |
} | |
} | |
} | |
} |
The canUpdateSayHello() will use these restrictions to determine if the button needs to be disabled or not.
private void updateCanSayHello() | |
{ | |
boolean canSayHello = false; | |
if(isAFWManagedApp(getContext()) || isAFWManagedDevice(getContext())) | |
{ | |
canSayHello = isAFWCanSayHello(getContext()); | |
} | |
mTextSayHello.setText(canSayHello ? | |
R.string.explanation_can_say_hello_true : | |
R.string.explanation_can_say_hello_false); | |
mButtonSayHello.setEnabled(canSayHello); | |
} |
I hope the above article can help you understand some of the basics of managed apps and give you insights on how you can implement Restrictions for your Application, and make it enterprise-ready.
Useful Links
[1]https://developers.google.com/android/work/overview
[2]https://github.com/googlesamples/android-testdpc
[3] https://github.com/android/enterprise-samples
[4] https://www.miradore.com/blog/separate-work-time-from-free-time-with-android-work-profile/