Definir configurações gerenciadas

Se você estiver desenvolvendo apps para o mercado corporativo, talvez seja necessário atender a requisitos específicos definidos pelas políticas de uma organização. As configurações gerenciadas, anteriormente conhecidas como restrições de aplicativo, permitem que o administrador de TI da organização especifique remotamente as configurações dos apps. Esse recurso é particularmente útil para apps aprovados pela organização implantados em um perfil de trabalho.

Por exemplo, uma organização pode exigir que os apps aprovados permitam que o administrador de TI:

  • Permitir ou bloquear URLs para um navegador da Web
  • Configurar se um app pode sincronizar conteúdo por rede celular ou apenas por Wi-Fi
  • Definir as configurações de e-mail do app

Este guia mostra como implementar definições de configurações gerenciadas no app. Para acessar apps de exemplo com uma configuração gerenciada, consulte ManagedConfigurations. Se você é um desenvolvedor de gerenciamento de mobilidade empresarial (EMM, na sigla em inglês), consulte o guia da API Android Management.

Observação:por motivos históricos, essas definições de configuração são conhecidas como restrições e são implementadas com arquivos e classes que usam esse termo (como RestrictionsManager). No entanto, essas restrições podem implementar uma ampla variedade de opções de configuração, não apenas restrições à funcionalidade do app.

Visão geral da Configuração remota

Os apps definem as opções de configuração gerenciada que podem ser definidas remotamente por um administrador de TI. São configurações arbitrárias que podem ser alteradas por um provedor de configuração gerenciado. Se o app estiver sendo executado em um perfil de trabalho, o administrador de TI poderá alterar a configuração gerenciada dele.

O provedor de configurações gerenciadas é outro app em execução no mesmo dispositivo. Esse app geralmente é controlado pelo administrador de TI. O administrador de TI comunica as mudanças de configuração ao app do provedor de configuração gerenciada. Esse app, por sua vez, altera as configurações do app.

Para fornecer configurações gerenciadas externamente:

  • Declare as configurações gerenciadas no manifesto do app. Isso permite que o administrador de TI leia as configurações do app nas APIs do Google Play.
  • Sempre que o app for retomado, use o objeto RestrictionsManager para verificar as configurações gerenciadas atuais e mude a interface e o comportamento do app para se adequar a essas configurações.
  • Detecte o intent ACTION_APPLICATION_RESTRICTIONS_CHANGED. Quando você receber essa transmissão, verifique o RestrictionsManager para ver quais são as configurações gerenciadas atuais e faça as mudanças necessárias no comportamento do app.

Definir configurações gerenciadas

O app oferece suporte a qualquer configuração gerenciada que você quiser definir. Você declara as configurações gerenciadas do app em um arquivo de configurações gerenciadas e o arquivo de configurações no manifesto. A criação de um arquivo de configurações permite que outros apps examinem as configurações gerenciadas que seu app oferece. Os parceiros de EMM podem ler as configurações do seu app usando as APIs do Google Play.

Para definir as opções de configuração remota do seu app, coloque o seguinte elemento no elemento <application> do seu manifesto:

<meta-data android:name="android.content.APP_RESTRICTIONS"
    android:resource="@xml/app_restrictions" />

Crie um arquivo chamado app_restrictions.xml no diretório res/xml do app. A estrutura desse arquivo está descrita na referência de RestrictionsManager. O arquivo tem um único elemento <restrictions> de nível superior, que contém um elemento filho <restriction> para cada opção de configuração do app.

Observação:não crie versões localizadas do arquivo de configuração gerenciada. Seu app só pode ter um único arquivo de configurações gerenciado. Portanto, as configurações serão consistentes para o app em todas as localidades.

Em um ambiente corporativo, um EMM normalmente usa o esquema de configuração gerenciada para gerar um console remoto para administradores de TI, permitindo que eles configurem o aplicativo remotamente.

O provedor de configuração gerenciada pode consultar o app para encontrar detalhes sobre as configurações disponíveis, incluindo o texto de descrição. O provedor de configurações e o administrador de TI podem mudar as configurações gerenciadas do app a qualquer momento, mesmo quando ele não está em execução.

Por exemplo, suponha que seu app possa ser configurado remotamente para permitir ou proibir o download de dados por uma conexão celular. Seu app pode ter um elemento <restriction> como este:

<?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>

Use o atributo android:key de cada configuração para ler o valor de um pacote de configurações gerenciado. Por esse motivo, cada configuração precisa ter uma string de chave exclusiva, e essa string não pode ser localizada. Ele precisa ser especificado com um literal de string.

Observação:em um app de produção, android:title e android:description precisam ser desenhados de um arquivo de recurso localizado, conforme descrito em Localizar com recursos.

Um app define restrições usando pacotes em uma bundle_array. Por exemplo, um app com várias opções de conexão de VPN pode definir cada configuração de servidor de VPN em uma bundle, com vários pacotes agrupados em uma matriz:

<?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>

Os tipos compatíveis com o elemento android:restrictionType estão listados na Tabela 1 e documentados na referência de RestrictionsManager e RestrictionEntry.

Tabela 1. Uso e tipos de entrada de restrição.

Tipo android:restrictionType Uso normal
TYPE_BOOLEAN "bool" Um valor booleano, verdadeiro ou falso.
TYPE_STRING "string" Um valor de string, como um nome.
TYPE_INTEGER "integer" Um número inteiro com um valor de MIN_VALUE a MAX_VALUE.
TYPE_CHOICE "choice" Um valor de string selecionado de android:entryValues, normalmente apresentado como uma lista de seleção única.
TYPE_MULTI_SELECT "multi-select" Uma matriz de strings com valores selecionados de android:entryValues. Use para apresentar uma lista de seleção múltipla em que mais de uma entrada pode ser selecionada, por exemplo, para escolher títulos específicos para a lista de permissões.
TYPE_NULL "hidden" Tipo de restrição oculto. Use esse tipo para informações que precisam ser transferidas, mas não devem ser apresentadas ao usuário na interface. Armazena um único valor de string.
TYPE_BUNDLE_ARRAY "bundle_array" Use-o para armazenar matrizes de restrição bundles. Disponível no Android 6.0 (API de nível 23).

Observação:android:entryValues são legíveis por máquina e não podem ser localizados. Use android:entries para apresentar valores legíveis que podem ser localizados. Cada entrada precisa ter um índice correspondente em android:entryValues.

Verificar as configurações gerenciadas

Seu app não recebe uma notificação automática quando outros apps mudam as definições de configuração. Em vez disso, você precisa verificar quais são as configurações gerenciadas quando seu app é iniciado ou retomado e detectar uma intent do sistema para descobrir se as configurações mudam enquanto o app está em execução.

Para descobrir as definições de configuração atuais, o app usa um objeto RestrictionsManager. Seu app precisa verificar as configurações gerenciadas atuais nos seguintes momentos:

Para receber um objeto RestrictionsManager, acesse a atividade atual com getActivity() e chame o método Activity.getSystemService() dessa atividade:

Kotlin

var myRestrictionsMgr =
        activity?.getSystemService(Context.RESTRICTIONS_SERVICE) as RestrictionsManager

Java

RestrictionsManager myRestrictionsMgr =
    (RestrictionsManager) getActivity()
        .getSystemService(Context.RESTRICTIONS_SERVICE);

Quando você tiver um RestrictionsManager, será possível ver as definições de configuração atuais chamando o método getApplicationRestrictions() dele:

Kotlin

var appRestrictions: Bundle = myRestrictionsMgr.applicationRestrictions

Java

Bundle appRestrictions = myRestrictionsMgr.getApplicationRestrictions();

Observação:por conveniência, você também pode buscar as configurações atuais com um UserManager chamando UserManager.getApplicationRestrictions(). Esse método se comporta exatamente da mesma forma que RestrictionsManager.getApplicationRestrictions().

O método getApplicationRestrictions() requer a leitura do armazenamento de dados, portanto, isso deve ser feito com moderação. Não chame esse método sempre que você precisar saber a configuração atual. Em vez disso, é necessário chamá-lo uma vez quando o app for iniciado ou retomado e armazenar em cache o pacote de configurações gerenciadas buscado. Em seguida, detecte o intent ACTION_APPLICATION_RESTRICTIONS_CHANGED para descobrir se a configuração muda enquanto o app está ativo, conforme descrito em Detectar mudanças de configuração gerenciadas.

Ler e aplicar configurações gerenciadas

O método getApplicationRestrictions() retorna um Bundle contendo um par de chave-valor para cada configuração definida. Os valores são todos do tipo Boolean, int, String e String[]. Assim que tiver as configurações gerenciadas Bundle, será possível verificar as definições atuais com os métodos Bundle padrão para esses tipos de dados, como getBoolean() ou getString().

Observação:as configurações gerenciadas Bundle contêm um item para cada configuração que foi definida explicitamente por um provedor de configurações gerenciadas. No entanto, não é possível presumir que uma configuração estará presente no pacote só porque você definiu um valor padrão no arquivo XML de configurações gerenciadas.

Cabe ao seu app tomar as medidas apropriadas com base nas configurações gerenciadas atuais. Por exemplo, se o app tiver uma configuração que especifica se pode fazer o download de dados por uma conexão celular e você descobrir que a configuração está definida como false, desative o download de dados, exceto quando o dispositivo tiver uma conexão Wi-Fi, conforme mostrado no código de exemplo a seguir:

Kotlin

val appCanUseCellular: Boolean =
        if (appRestrictions.containsKey("downloadOnCellular")) {
            appRestrictions.getBoolean("downloadOnCellular")
        } else {
            // cellularDefault is a boolean using the restriction's default value
            cellularDefault
        }

if (!appCanUseCellular) {
    // ...turn off app's cellular-download functionality
    // ...show appropriate notices to user
}

Java

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
}

Para aplicar várias restrições aninhadas, leia a entrada de restrição bundle_array como uma coleção de objetos Parcelable e transmita como uma Bundle. Neste exemplo, os dados de configuração de cada VPN são analisados e usados para criar uma lista de opções de conexão do servidor:

Kotlin

// VpnConfig is a sample class used store config data, not defined
val vpnConfigs = mutableListOf<VpnConfig>()

val parcelables: Array<out Parcelable>? =
        appRestrictions.getParcelableArray("vpn_configuration_list")

if (parcelables?.isNotEmpty() == true) {
    // iterate parcelables and cast as bundle
    parcelables.map { it as Bundle }.forEach { vpnConfigBundle ->
        // parse bundle data and store in VpnConfig array
        vpnConfigs.add(VpnConfig()
                .setServer(vpnConfigBundle.getString("vpn_server"))
                .setUsername(vpnConfigBundle.getString("vpn_username"))
                .setPassword(vpnConfigBundle.getString("vpn_password")))
    }
}

if (vpnConfigs.isNotEmpty()) {
    // ...choose a VPN configuration or prompt user to select from list
}

Java

// 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
}

Detectar mudanças de configuração gerenciada

Sempre que as configurações gerenciadas de um app são alteradas, o sistema dispara o intent ACTION_APPLICATION_RESTRICTIONS_CHANGED. Seu app precisa detectar esse intent para que você possa mudar o comportamento do app quando as definições de configuração forem alteradas.

Observação:a intent ACTION_APPLICATION_RESTRICTIONS_CHANGED é enviada somente aos listeners registrados dinamicamente, não aos que foram declarados no manifesto do app.

O código a seguir mostra como registrar dinamicamente um broadcast receiver para essa intent:

Kotlin

val restrictionsFilter = IntentFilter(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED)

val restrictionsReceiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {

        // Get the current configuration bundle
        val appRestrictions = myRestrictionsMgr.applicationRestrictions

        // Check current configuration settings, change your app's UI and
        // functionality as necessary.
    }
}

registerReceiver(restrictionsReceiver, restrictionsFilter)

Java

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);

Observação:normalmente, seu app não precisa ser notificado sobre mudanças de configuração quando está pausado. Em vez disso, cancele o registro do broadcast receiver quando o app estiver pausado. Quando o app for retomado, você primeiro vai verificar as configurações gerenciadas atuais (conforme discutido em Verificar configurações gerenciadas) e, em seguida, registrar o broadcast receiver para garantir que você seja notificado sobre as mudanças de configuração que ocorrem enquanto o app está ativo.

Enviar feedback sobre a configuração gerenciada para EMMs

Depois de aplicar as mudanças de configuração gerenciada ao app, é recomendado notificar os EMMs sobre o status da mudança. O Android oferece suporte a um recurso chamado estados de apps com chaves, que você pode usar para enviar feedback sempre que o app tentar aplicar mudanças de configuração gerenciadas. Esse feedback pode servir como confirmação de que seu app definiu as configurações gerenciadas corretamente ou pode incluir uma mensagem de erro se o app não aplicar as mudanças especificadas.

Os provedores de EMM podem recuperar esse feedback e exibi-lo nos consoles para os administradores de TI. Consulte Enviar feedback do app para EMMs para mais informações sobre o tópico, incluindo um guia detalhado sobre como adicionar suporte a feedback ao seu app.

Outros exemplos de código

A amostra ManagedConfigurations demonstra melhor o uso das APIs abordadas nesta página.