If you are developing apps for the enterprise market, you may need to satisfy particular requirements set by a company's policies. Managed configurations, previously known as application restrictions, allow the enterprise administrator to remotely specify settings for apps. This capability is particularly useful for enterprise-approved apps deployed to a managed profile.
For example, an enterprise might require that approved apps allow the enterprise administrator to:
- Whitelist or blacklist URLs for a web browser
- Configure whether an app is allowed to sync content via cellular, or just by Wi-Fi
- Configure the app's email settings
This guide shows how to implement managed configuration settings in your app. If you're an EMM developer, refer to the Build a Device Policy Controller guide.
Note: For historical reasons, these configuration settings are known as
restrictions, and are implemented with files and classes that use this
term (such as RestrictionsManager
). However, these
restrictions can actually implement a wide range of configuration options,
not just restrictions on app functionality.
Remote Configuration Overview
Apps define the managed configuration options that can be remotely set by an administrator. These are arbitrary settings that can be changed by a managed configuration provider. If your app is running on an enterprise device's managed profile, the enterprise administrator can change your app's managed configuration.
The managed configurations provider is another app running on the same device. This app is typically controlled by the enterprise administrator. The enterprise administrator communicates configuration changes to the managed configuration provider app. That app, in turn, changes the configurations on your app.
To provide externally managed configurations:
- Declare the managed configurations in your app manifest. Doing so allows the enterprise administrator to read the app's configurations through Google Play APIs.
- Whenever the app resumes, use the
RestrictionsManager
object to check the current managed configurations, and change your app's UI and behavior to conform with those configurations. - Listen for the
ACTION_APPLICATION_RESTRICTIONS_CHANGED
intent. When you receive this broadcast, check theRestrictionsManager
to see what the current managed configurations are, and make any necessary changes to your app's behavior.
Define Managed Configurations
Your app can support any managed configuration you want to define. You declare the app's managed configurations in a managed configurations file, and declare the configurations file in the manifest. Creating a configurations file allows other apps to examine the managed configurations your app provides. Enterprise Mobility Management (EMM) partners can read your app's configurations by using Google Play APIs.
To define your app's remote configuration options, put the following element
in your manifest's
<application>
element:
<meta-data android:name="android.content.APP_RESTRICTIONS" android:resource="@xml/app_restrictions" />
Create a file named app_restrictions.xml
in your app's
res/xml
directory. The structure of that file is described in
the reference for RestrictionsManager
. The file has a
single top-level <restrictions>
element, which contains
one <restriction>
child element for every configuration
option the app has.
Note: Do not create localized versions of the managed configuration file. Your app is only allowed to have a single managed configurations file, so configurations will be consistent for your app in all locales.
In an enterprise environment, an EMM will typically use the managed configuration schema to generate a remote console for IT administrators, so the administrators can remotely configure your application.
The managed configuration provider can query the app to find details on the app's available configurations, including their description text. The configurations provider and enterprise administrator can change your app's managed configurations at any time, even when the app is not running.
For example, suppose your app can be remotely configured to allow or forbid
it to download data over a cellular connection. Your app could have a
<restriction>
element like this:
<?xml version="1.0" encoding="utf-8"?> <restrictions xmlns:android="http://schemas.android.com/apk/res/android"> <restriction android:key="downloadOnCellular" android:title="@string/download_on_cell_title" android:restrictionType="bool" android:description="@string/download_on_cell_description" android:defaultValue="true" /> </restrictions>
You use each configuration's android:key
attribute to
read its value from a managed configuration bundle. For this reason,
each configuration must have a unique key string, and the string
cannot be localized. It must be specified with a string literal.
Note: In a production app, android:title
and
android:description
should be drawn from a localized resource
file, as described in
Localizing with Resources.
An app can define one or multiple nested restriction elements using
the restriction types
bundle
and
bundle_array
.
For example, an app with multiple VPN connection options could define
each VPN server configuration in a bundle, with multiple bundles grouped
together in a bundle array:
<?xml version="1.0" encoding="utf-8"?> <restrictions xmlns:android="http://schemas.android.com/apk/res/android" > <restriction android:key="vpn_configuration_list" android:restrictionType="bundle_array"> <restriction android:key="vpn_configuration" android:restrictionType="bundle"> <restriction android:key="vpn_server" android:restrictionType="string"/> <restriction android:key="vpn_username" android:restrictionType="string"/> <restriction android:key="vpn_password" android:restrictionType="string"/> </restriction> </restriction> </restrictions>
The supported types for the android:restrictionType
element
are listed in Table 1 and documented in
the reference for RestrictionsManager
and
RestrictionEntry
.
Type | android:restrictionType | Typical usage |
---|---|---|
TYPE_BOOLEAN
|
"bool" |
A boolean value, true or false. |
TYPE_STRING
|
"string" |
A string value, such as a name. |
TYPE_INTEGER
|
"integer" |
An integer with a value from
MIN_VALUE to
MAX_VALUE .
|
TYPE_CHOICE
|
"choice" |
A string value, typically presented as a single-select list. |
TYPE_MULTI_SELECT
|
"multi-select" |
Use this for presenting a multi-select list where more than one entry can be selected, such as for choosing specific titles to white-list. |
TYPE_NULL
|
"hidden" |
Hidden restriction type. Use this type for information that needs to be transferred across but shouldn't be presented to the user in the UI. Stores a single string value. |
TYPE_BUNDLE |
"bundle" |
Use this for storing bundles of
restrictions. Available in Android 6.0 (API level 23).
|
TYPE_BUNDLE_ARRAY
|
"bundle_array" |
Use this for storing arrays of restriction bundles. Available in Android 6.0 (API level 23). |
Check Managed Configurations
Your app is not automatically notified when other apps change its configuration settings. Instead, you need to check what the managed configurations are when your app starts or resumes, and listen for a system intent to find out if the configurations change while your app is running.
To find out the current configuration settings, your app uses a
RestrictionsManager
object. Your app should
check for the current managed configurations at the following times:
- When the app starts or resumes, in its
onResume()
method - When the app is notified of a configuration change, as described in Listen for Managed Configuration Changes
To get a RestrictionsManager
object, get the current
activity with getActivity()
, then
call that activity's Activity.getSystemService()
method:
RestrictionsManager myRestrictionsMgr = (RestrictionsManager) getActivity() .getSystemService(Context.RESTRICTIONS_SERVICE);
Once you have a RestrictionsManager
, you can get the
current configuration settings by calling its
getApplicationRestrictions()
method:
Bundle appRestrictions = myRestrictionsMgr.getApplicationRestrictions();
Note: For convenience, you can also fetch the current
configurations with a UserManager
, by calling
UserManager.getApplicationRestrictions()
. This method behaves exactly the
same as RestrictionsManager.getApplicationRestrictions()
.
The getApplicationRestrictions()
method requires reading from data storage, so
it should be done sparingly. Do not call this method every time you need to
know the current configuration. Instead, you should call it once when your app
starts or resumes, and cache the fetched managed configurations bundle. Then listen
for the ACTION_APPLICATION_RESTRICTIONS_CHANGED
intent to find out if the configuration
change while your app is active, as described in
Listen for Managed Configuration Changes.
Reading and applying managed configurations
The getApplicationRestrictions()
method returns a Bundle
containing a key-value pair for each configuration that has been set. The
values are all of type Boolean
, int
,
String
, and String[]
. Once you have the
managed configurations Bundle
, you can check the current
configuration settings with the standard Bundle
methods for
those data types, such as getBoolean()
or
getString()
.
Note: The managed configurations Bundle
contains one item for every configuration that has been explicitly set by a
managed configurations provider. However, you cannot assume that a
configuration will be present in the bundle just because you defined a default
value in the managed configurations XML file.
It is up to your app to take appropriate action based on the current
managed configuration settings. For example, if your app has a
configuration specifying whether it can download data over a
cellular connection, and you find that the configuration is set to
false
, you would have to disable data download except when
the device has a Wi-Fi connection, as shown in the following example code:
boolean appCanUseCellular; if (appRestrictions.containsKey("downloadOnCellular")) { appCanUseCellular = appRestrictions.getBoolean("downloadOnCellular"); } else { // cellularDefault is a boolean using the restriction's default value appCanUseCellular = cellularDefault; } if (!appCanUseCellular) { // ...turn off app's cellular-download functionality // ...show appropriate notices to user }
To apply multiple nested restrictions, read
the bundle_array
restriction entry as a collection of Parcelable
objects
and cast as a Bundle
. In this example, each VPN's configuration
data is parsed and used to build a list of server connection choices:
// VpnConfig is a sample class used store config data, not defined List<VpnConfig> vpnConfigs = new ArrayList<>(); Parcelable[] parcelables = appRestrictions.getParcelableArray("vpn_configuration_list"); if (parcelables != null && parcelables.length > 0) { // iterate parcelables and cast as bundle for (int i = 0; i < parcelables.length; i++) { Bundle vpnConfigBundle = (Bundle) parcelables[i]; // parse bundle data and store in VpnConfig array vpnConfigs.add(new VpnConfig() .setServer(vpnConfigBundle.getString("vpn_server")) .setUsername(vpnConfigBundle.getString("vpn_username")) .setPassword(vpnConfigBundle.getString("vpn_password"))); } } if (!vpnConfigs.isEmpty()) { // ...choose a VPN configuration or prompt user to select from list }
Listen for Managed Configuration Changes
Whenever an app's managed configurations are changed, the system fires the
ACTION_APPLICATION_RESTRICTIONS_CHANGED
intent. Your app has to listen for
this intent so you can change the app's behavior when the configuration settings
change.
Note: The ACTION_APPLICATION_RESTRICTIONS_CHANGED
intent is sent only to listeners
that are dynamically registered, not to listeners that are declared
in the app manifest.
The following code shows how to dynamically register a broadcast receiver for this intent:
IntentFilter restrictionsFilter = new IntentFilter(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED); BroadcastReceiver restrictionsReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // Get the current configuration bundle Bundle appRestrictions = myRestrictionsMgr.getApplicationRestrictions(); // Check current configuration settings, change your app's UI and // functionality as necessary. } }; registerReceiver(restrictionsReceiver, restrictionsFilter);
Note: Ordinarily, your app does not need to be notified about configuration changes when it is paused. Instead, you should unregister your broadcast receiver when the app is paused. When the app resumes, you first check for the current managed configurations (as discussed in Check Managed Configurations), then register your broadcast receiver to make sure you're notified about configuration changes that happen while the app is active.