Manual sobre dispositivos dedicados

Este manual ajuda desenvolvedores e integradores de sistemas a melhorar a solução de dispositivo dedicada. Siga nossas receitas para encontrar soluções para comportamentos de dispositivos dedicados. Este livro de receitas funciona melhor para desenvolvedores que já têm um app de dispositivo dedicado. Se você está apenas começando, leia Visão geral de dispositivos dedicados.

Apps personalizados do Google Home

Essas receitas são úteis se você estiver desenvolvendo um app para substituir a tela inicial e o acesso rápido do Android.

Seja o app de início

É possível configurar seu app como o app de início do dispositivo para que ele seja iniciado automaticamente quando o dispositivo for iniciado. Você também pode ativar o botão home, que coloca o app na lista de permissões em primeiro plano no modo de bloqueio de tarefas.

Todos os apps de casa processam a categoria de intent CATEGORY_HOME. É assim que o sistema reconhece um app de início. Para se tornar o app de início padrão, defina uma das atividades do app como o gerenciador de intent preferido da página inicial, chamando DevicePolicyManager.addPersistentPreferredActivity(), conforme mostrado no exemplo a seguir:

Kotlin

// Create an intent filter to specify the Home category.
val filter = IntentFilter(Intent.ACTION_MAIN)
filter.addCategory(Intent.CATEGORY_HOME)
filter.addCategory(Intent.CATEGORY_DEFAULT)

// Set the activity as the preferred option for the device.
val activity = ComponentName(context, KioskModeActivity::class.java)
val dpm = context.getSystemService(Context.DEVICE_POLICY_SERVICE)
        as DevicePolicyManager
dpm.addPersistentPreferredActivity(adminName, filter, activity)

Java

// Create an intent filter to specify the Home category.
IntentFilter filter = new IntentFilter(Intent.ACTION_MAIN);
filter.addCategory(Intent.CATEGORY_HOME);
filter.addCategory(Intent.CATEGORY_DEFAULT);

// Set the activity as the preferred option for the device.
ComponentName activity = new ComponentName(context, KioskModeActivity.class);
DevicePolicyManager dpm =
    (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
dpm.addPersistentPreferredActivity(adminName, filter, activity);

Ainda é necessário declarar o filtro de intent no arquivo de manifesto do app, conforme mostrado no snippet XML a seguir:

<activity
        android:name=".KioskModeActivity"
        android:label="@string/kiosk_mode"
        android:launchMode="singleInstance"
        android:excludeFromRecents="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.HOME"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>

Normalmente, você não quer que o app de acesso rápido apareça na tela "Visão geral". No entanto, não é necessário adicionar excludeFromRecents à declaração de atividade, porque a tela de início do Android oculta a atividade iniciada inicialmente quando o sistema está em execução no modo de bloqueio de tarefas.

Mostrar tarefas separadas

A FLAG_ACTIVITY_NEW_TASK pode ser uma sinalização útil para apps do tipo de tela de início, porque cada nova tarefa aparece como um item separado na tela "Visão geral". Para saber mais sobre tarefas na tela "Visão geral", leia Tela "Recentes".

Quiosques públicos

Essas receitas são ótimas para dispositivos autônomos em espaços públicos, mas também podem ajudar muitos usuários de dispositivos dedicados a se concentrar nas tarefas.

Bloquear o dispositivo

Para garantir que os dispositivos sejam usados para a finalidade pretendida, adicione as restrições de usuário listadas na Tabela 1.

Tabela 1. Restrições de usuário para dispositivos de quiosque
Restrição de usuário Descrição
DISALLOW_FACTORY_RESET Impede que um usuário redefina o dispositivo para a configuração original. Os administradores de dispositivos totalmente gerenciados e o usuário principal podem definir essa restrição.
DISALLOW_SAFE_BOOT Impede que um usuário inicie o dispositivo no modo de segurança, o que impede a inicialização automática do app pelo sistema. Os administradores de dispositivos totalmente gerenciados e o usuário principal podem definir essa restrição.
DISALLOW_MOUNT_PHYSICAL_MEDIA Impede que o usuário do dispositivo monte volumes de armazenamento que possam ser conectados ao dispositivo. Os administradores de dispositivos totalmente gerenciados e o usuário principal podem definir essa restrição.
DISALLOW_ADJUST_VOLUME Silencia o dispositivo e impede que o usuário mude as configurações de volume de som e vibração. Verifique se o quiosque não precisa de áudio para reprodução de mídia ou recursos de acessibilidade. Os administradores de dispositivos totalmente gerenciados, o usuário principal, os usuários secundários e os perfis de trabalho podem definir essa restrição.
DISALLOW_ADD_USER Impede que o usuário do dispositivo adicione novos usuários, como secundários ou restritos. O sistema adiciona automaticamente essa restrição de usuário a dispositivos totalmente gerenciados, mas ela pode ter sido removida. Os administradores de dispositivos totalmente gerenciados e o usuário principal podem definir essa restrição.

O snippet a seguir mostra como definir as restrições:

Kotlin

// If the system is running in lock task mode, set the user restrictions
// for a kiosk after launching the activity.
arrayOf(
        UserManager.DISALLOW_FACTORY_RESET,
        UserManager.DISALLOW_SAFE_BOOT,
        UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA,
        UserManager.DISALLOW_ADJUST_VOLUME,
        UserManager.DISALLOW_ADD_USER).forEach { dpm.addUserRestriction(adminName, it) }

Java

// If the system is running in lock task mode, set the user restrictions
// for a kiosk after launching the activity.
String[] restrictions = {
    UserManager.DISALLOW_FACTORY_RESET,
    UserManager.DISALLOW_SAFE_BOOT,
    UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA,
    UserManager.DISALLOW_ADJUST_VOLUME,
    UserManager.DISALLOW_ADD_USER};

for (String restriction: restrictions) dpm.addUserRestriction(adminName, restriction);

Você pode remover essas restrições quando o app estiver no modo de administrador para que um administrador de TI ainda possa usar esses recursos para a manutenção do dispositivo. Para limpar a restrição, chame DevicePolicyManager.clearUserRestriction().

Suprimir caixas de diálogo de erro

Em alguns ambientes, como demonstrações na loja ou exibições de informações públicas, talvez você não queira mostrar caixas de diálogo de erro aos usuários. No Android 9.0 (API de nível 28) ou versões mais recentes, é possível suprimir caixas de diálogo de erro do sistema para apps com falha ou que não respondem adicionando a restrição de usuário DISALLOW_SYSTEM_ERROR_DIALOGS. O sistema reinicia apps que não respondem como se o usuário do dispositivo tivesse fechado o app na caixa de diálogo. O exemplo a seguir mostra como fazer isso:

Kotlin

override fun onEnabled(context: Context, intent: Intent) {
    val dpm = getManager(context)
    val adminName = getWho(context)

    dpm.addUserRestriction(adminName, UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS)
}

Java

public void onEnabled(Context context, Intent intent) {
  DevicePolicyManager dpm = getManager(context);
  ComponentName adminName = getWho(context);

  dpm.addUserRestriction(adminName, UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS);
}

Se um administrador do usuário principal ou secundário definir essa restrição, o sistema suprime as caixas de diálogo de erro apenas para esse usuário. Se um administrador de um dispositivo totalmente gerenciado definir essa restrição, o sistema vai suprimir as caixas de diálogo de todos os usuários.

Manter a tela ativada

Se você estiver construindo um quiosque, poderá interromper um dispositivo que entrará em suspensão quando ele estiver executando a atividade do app. Adicione a sinalização de layout FLAG_KEEP_SCREEN_ON à janela do app, conforme mostrado no exemplo a seguir:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    // Keep the screen on and bright while this kiosk activity is running.
    window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  // Keep the screen on and bright while this kiosk activity is running.
  getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}

Confira se o dispositivo está conectado a um carregador CA, USB ou sem fio. Registre-se para transmissões de mudança de bateria e use valores de BatteryManager para descobrir o estado de carregamento. É possível até enviar alertas remotos para um administrador de TI se o dispositivo ficar desconectado. Para instruções passo a passo, leia Monitorar o nível da bateria e o estado de carregamento.

Você também pode definir a configuração global STAY_ON_WHILE_PLUGGED_IN para manter o dispositivo ativado enquanto estiver conectado a uma fonte de energia. Os administradores de dispositivos totalmente gerenciados no Android 6.0 (API de nível 23) ou versões mais recentes podem chamar DevicePolicyManager.setGlobalSetting(), conforme mostrado neste exemplo:

Kotlin

val pluggedInto = BatteryManager.BATTERY_PLUGGED_AC or
        BatteryManager.BATTERY_PLUGGED_USB or
        BatteryManager.BATTERY_PLUGGED_WIRELESS
dpm.setGlobalSetting(adminName,
        Settings.Global.STAY_ON_WHILE_PLUGGED_IN, pluggedInto.toString())

Java

int pluggedInto = BatteryManager.BATTERY_PLUGGED_AC |
    BatteryManager.BATTERY_PLUGGED_USB |
    BatteryManager.BATTERY_PLUGGED_WIRELESS;
dpm.setGlobalSetting( adminName,
    Settings.Global.STAY_ON_WHILE_PLUGGED_IN, String.valueOf(pluggedInto));

Pacotes de apps

Esta seção contém roteiros para instalar apps de forma eficiente em dispositivos dedicados.

Armazenar pacotes de apps em cache

Se os usuários de um dispositivo compartilhado compartilham um conjunto comum de apps, faz sentido evitar o download de apps sempre que possível. Para simplificar o provisionamento de usuários em dispositivos compartilhados com um conjunto fixo de usuários, como dispositivos para funcionários de turno, no Android 9.0 (API de nível 28) ou versões mais recentes, é possível armazenar em cache os pacotes de apps (APKs) necessários para sessões multiusuário.

A instalação de um APK armazenado em cache (que já está instalado no dispositivo) acontece em duas etapas:

  1. O componente de administrador de um dispositivo totalmente gerenciado (ou um delegado, veja a seguir) define a lista de APKs a serem mantidos no dispositivo.
  2. Os componentes de administrador de usuários secundários afiliados (ou delegados) podem instalar o APK armazenado em cache em nome do usuário. Os administradores do dispositivo totalmente gerenciado, o usuário principal ou um perfil de trabalho afiliado (ou os delegados) também podem instalar o app armazenado em cache, se necessário.

Para definir a lista de APKs que vão ser mantidos no dispositivo, o administrador chama DevicePolicyManager.setKeepUninstalledPackages(). Esse método não verifica se o APK está instalado no dispositivo. Isso é útil se você quiser instalar um app pouco antes de precisar dele para um usuário. Para ver uma lista de pacotes definidos anteriormente, chame DevicePolicyManager.getKeepUninstalledPackages(). Depois de chamar setKeepUninstalledPackages() com mudanças ou quando um usuário secundário é excluído, o sistema exclui todos os APKs armazenados em cache que não são mais necessários.

Para instalar um APK armazenado em cache, chame DevicePolicyManager.installExistingPackage(). Esse método só pode instalar um app que o sistema já tenha armazenado em cache. Sua solução de dispositivo dedicada (ou o usuário de um dispositivo) precisa primeiro instalar o app no dispositivo antes que você possa chamar esse método.

O exemplo a seguir mostra como você pode usar essas chamadas de API no administrador de um dispositivo totalmente gerenciado e um usuário secundário:

Kotlin

// Set the package to keep. This method assumes that the package is already
// installed on the device by managed Google Play.
val cachedAppPackageName = "com.example.android.myapp"
dpm.setKeepUninstalledPackages(adminName, listOf(cachedAppPackageName))

// ...

// The admin of a secondary user installs the app.
val success = dpm.installExistingPackage(adminName, cachedAppPackageName)

Java

// Set the package to keep. This method assumes that the package is already
// installed on the device by managed Google Play.
String cachedAppPackageName = "com.example.android.myapp";
List<String> packages = new ArrayList<String>();
packages.add(cachedAppPackageName);
dpm.setKeepUninstalledPackages(adminName, packages);

// ...

// The admin of a secondary user installs the app.
boolean success = dpm.installExistingPackage(adminName, cachedAppPackageName);

Delegar apps

Você pode delegar o gerenciamento do armazenamento em cache de outro app. Isso pode ser feito para separar os recursos da solução ou oferecer a capacidade de administradores de TI usarem os próprios aplicativos. O app delegado recebe as mesmas permissões que o componente administrador. Por exemplo, um delegado do app de um administrador de usuário secundário pode chamar installExistingPackage(), mas não pode chamar setKeepUninstalledPackages().

Para fazer uma chamada delegada DevicePolicyManager.setDelegatedScopes() e incluir DELEGATION_KEEP_UNINSTALLED_PACKAGES no argumento de escopos. O exemplo abaixo mostra como você pode tornar outro app o delegado:

Kotlin

var delegatePackageName = "com.example.tools.kept_app_assist"

// Check that the package is installed before delegating.
try {
    context.packageManager.getPackageInfo(delegatePackageName, 0)
    dpm.setDelegatedScopes(
            adminName,
            delegatePackageName,
            listOf(DevicePolicyManager.DELEGATION_KEEP_UNINSTALLED_PACKAGES))
} catch (e: PackageManager.NameNotFoundException) {
    // The delegate app isn't installed. Send a report to the IT admin ...
}

Java

String delegatePackageName = "com.example.tools.kept_app_assist";

// Check that the package is installed before delegating.
try {
  context.getPackageManager().getPackageInfo(delegatePackageName, 0);
  dpm.setDelegatedScopes(
      adminName,
      delegatePackageName,
      Arrays.asList(DevicePolicyManager.DELEGATION_KEEP_UNINSTALLED_PACKAGES));
} catch (PackageManager.NameNotFoundException e) {
  // The delegate app isn't installed. Send a report to the IT admin ...
}

Se tudo correr bem, o app delegado receberá a transmissão ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED e se tornará o delegado. O app pode chamar os métodos apresentados neste guia como se fosse o proprietário do dispositivo ou do perfil. Ao chamar métodos DevicePolicyManager, o delegado passa null para o argumento do componente de administrador.

Instalar pacotes de apps

Às vezes, é útil instalar um app personalizado armazenado em cache localmente em um dispositivo dedicado. Por exemplo, dispositivos dedicados são frequentemente implantados em ambientes com largura de banda limitada ou em áreas sem conexão de Internet. Sua solução de dispositivo dedicada precisa considerar a largura de banda dos seus clientes. O app pode iniciar a instalação de outro pacote de apps (APK) usando as classes PackageInstaller.

Embora qualquer app possa instalar APKs, os administradores em dispositivos totalmente gerenciados podem instalar (ou desinstalar) pacotes sem interação do usuário. O administrador pode gerenciar o dispositivo, um usuário secundário afiliado ou um perfil de trabalho afiliado. Depois de concluir a instalação, o sistema publica uma notificação que aparece para todos os usuários do dispositivo. A notificação informa aos usuários do dispositivo que o app foi instalado (ou atualizado) pelo administrador.

Tabela 2. Versões do Android compatíveis com a instalação de pacotes sem interação do usuário
Versão do Android Componente "Administrador" para instalação e desinstalação
Android 9.0 (API de nível 28) ou mais recente Usuários secundários afiliados e perfis de trabalho, ambos em dispositivos totalmente gerenciados
Ter o Android 6.0 (API de nível 23) ou versões mais recentes Dispositivos totalmente gerenciados

A forma como você distribui uma ou mais cópias do APK para dispositivos dedicados depende da distância entre os dispositivos e, possivelmente, da distância entre eles. Sua solução precisa seguir as práticas recomendadas de segurança antes de instalar APKs em dispositivos dedicados.

Use PackageInstaller.Session para criar uma sessão que enfileira um ou mais APKs para instalação. No exemplo a seguir, recebemos feedback de status em nossa atividade (modo singleTop), mas você pode usar um serviço ou broadcast receiver:

Kotlin

// First, create a package installer session.
val packageInstaller = context.packageManager.packageInstaller
val params = PackageInstaller.SessionParams(
        PackageInstaller.SessionParams.MODE_FULL_INSTALL)
val sessionId = packageInstaller.createSession(params)
val session = packageInstaller.openSession(sessionId)

// Add the APK binary to the session. The APK is included in our app binary
// and is read from res/raw but file storage is a more typical location.
// The I/O streams can't be open when installation begins.
session.openWrite("apk", 0, -1).use { output ->
    getContext().resources.openRawResource(R.raw.app).use { input ->
        input.copyTo(output, 2048)
    }
}

// Create a status receiver to report progress of the installation.
// We'll use the current activity.
// Here we're requesting status feedback to our Activity but this can be a
// service or broadcast receiver.
val intent = Intent(context, activity.javaClass)
intent.action = "com.android.example.APK_INSTALLATION_ACTION"
val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
val statusReceiver = pendingIntent.intentSender

// Start the installation. Because we're an admin of a fully managed device,
// there isn't any user interaction.
session.commit(statusReceiver)

Java

// First, create a package installer session.
PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
    PackageInstaller.SessionParams.MODE_FULL_INSTALL);
int sessionId = packageInstaller.createSession(params);
PackageInstaller.Session session = packageInstaller.openSession(sessionId);

// Add the APK binary to the session. The APK is included in our app binary
// and is read from res/raw but file storage is a more typical location.
try (
    // These I/O streams can't be open when installation begins.
    OutputStream output = session.openWrite("apk", 0, -1);
    InputStream input = getContext().getResources().openRawResource(R.raw.app);
) {
  byte[] buffer = new byte[2048];
  int n;
  while ((n = input.read(buffer)) >= 0) {
    output.write(buffer, 0, n);
  }
}

// Create a status receiver to report progress of the installation.
// We'll use the current activity.
// Here we're requesting status feedback to our Activity but this can be a
// service or broadcast receiver.
Intent intent = new Intent(context, getActivity().getClass());
intent.setAction("com.android.example.APK_INSTALLATION_ACTION");
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
IntentSender statusReceiver = pendingIntent.getIntentSender();

// Start the installation. Because we're an admin of a fully managed device,
// there isn't any user interaction.
session.commit(statusReceiver);

A sessão envia feedback de status sobre a instalação usando intents. Verifique o campo EXTRA_STATUS de cada intent para saber o status. Lembre-se de que os administradores não recebem a atualização de status STATUS_PENDING_USER_ACTION porque o usuário do dispositivo não precisa aprovar a instalação.

Para desinstalar apps, chame PackageInstaller.uninstall. Os administradores de dispositivos, usuários e perfis de trabalho totalmente gerenciados podem desinstalar pacotes sem interação do usuário executando versões do Android com suporte. Consulte a Tabela 2.

Congelar atualizações do sistema

Os dispositivos Android recebem atualizações over the air (OTA) para o software do sistema e do aplicativo. Para congelar a versão do SO em períodos críticos, como feriados ou outros horários de pico, os dispositivos dedicados podem suspender as atualizações do sistema OTA por até 90 dias. Para saber mais, leia Gerenciar atualizações do sistema.

Configuração remota

As configurações gerenciadas do Android permitem que os administradores de TI configurem seu app remotamente. É recomendável expor configurações como listas de permissões, hosts de rede ou URLs de conteúdo para tornar o app mais útil para os administradores de TI.

Se o app expõe a própria configuração, lembre-se de incluí-las na documentação. Para saber mais sobre como expor as configurações do app e reagir a mudanças nas configurações, leia Definir configurações gerenciadas.

Configuração de desenvolvimento

Ao desenvolver uma solução para dispositivos dedicados, às vezes é útil configurar seu app como o administrador de um dispositivo totalmente gerenciado sem uma redefinição para a configuração original. Para definir o administrador de um dispositivo totalmente gerenciado, siga estas etapas:

  1. Crie e instale o app controlador de política de dispositivo (DPC) no dispositivo.
  2. Verifique se não há contas no dispositivo.
  3. Execute o comando a seguir no shell do Android Debug Bridge (adb). É necessário substituir com.example.dpc/.MyDeviceAdminReceiver no exemplo pelo nome do componente de administrador do app:

    adb shell dpm set-device-owner com.example.dpc/.MyDeviceAdminReceiver

Para ajudar os clientes a implantar a solução, analise outros métodos de registro. Recomendamos o registro de código QR para dispositivos dedicados.

Outros recursos

Para saber mais sobre dispositivos dedicados, leia os seguintes documentos: