Gerenciar vários usuários

Este guia para desenvolvedores explica como o controlador de política de dispositivo (DPC, na sigla em inglês) pode gerenciar vários usuários do Android em dispositivos dedicados.

Informações gerais

Seu DPC pode ajudar várias pessoas a compartilhar um único dispositivo dedicado. O DPC executado em um dispositivo totalmente gerenciado pode criar e gerenciar dois tipos de usuários:

  • Os usuários secundários são usuários do Android com apps e dados separados salvos entre sessões. Você gerencia o usuário com um componente de administrador. Esses usuários são úteis nos casos em que um dispositivo é retirado no início de um turno, como motoristas de entrega ou funcionários de segurança.
  • Usuários temporários são secundários que o sistema exclui quando o usuário para, sai da conta ou reinicia o dispositivo. Esses usuários são úteis para casos em que os dados podem ser excluídos após o término da sessão, como em quiosques da Internet de acesso público.

Use seu DPC atual para gerenciar o dispositivo dedicado e os usuários secundários. Um componente de administrador no DPC define a si mesmo como administrador para novos usuários secundários quando você os cria.

Usuário principal e dois usuários secundários.
Figura 1. Usuários principais e secundários gerenciados por administradores no mesmo DPC

Os administradores de um usuário secundário precisam pertencer ao mesmo pacote que o administrador do dispositivo totalmente gerenciado. Para simplificar o desenvolvimento, recomendamos compartilhar um administrador entre o dispositivo e os usuários secundários.

Normalmente, gerenciar vários usuários em dispositivos dedicados requer o Android 9.0. No entanto, alguns dos métodos usados neste guia para desenvolvedores estão disponíveis em versões anteriores do Android.

Usuários secundários

Os usuários secundários podem se conectar ao Wi-Fi e configurar novas redes. No entanto, não é possível editar ou excluir redes, nem mesmo as redes que criaram.

Criar usuários

Seu DPC pode criar outros usuários em segundo plano e depois trocá-los para o primeiro plano. O processo é quase o mesmo para usuários secundários e temporários. Implemente as etapas a seguir nos administradores do dispositivo totalmente gerenciado e do usuário secundário:

  1. Chame DevicePolicyManager.createAndManageUser(). Para criar um usuário temporário, inclua MAKE_USER_EPHEMERAL no argumento de flags.
  2. Chame DevicePolicyManager.startUserInBackground() para iniciar o usuário em segundo plano. O usuário começa a correr, mas é recomendável concluir a configuração antes de colocar o usuário em primeiro plano e mostrá-lo para a pessoa que está usando o dispositivo.
  3. No administrador do usuário secundário, chame DevicePolicyManager.setAffiliationIds() para afiliar o novo usuário ao usuário principal. Consulte Coordenação de DPC abaixo.
  4. No administrador do dispositivo totalmente gerenciado, chame DevicePolicyManager.switchUser() para colocar o usuário em primeiro plano.

O exemplo a seguir mostra como adicionar a etapa 1 ao seu DPC:

Kotlin

val dpm = getContext().getSystemService(Context.DEVICE_POLICY_SERVICE)
        as DevicePolicyManager

// If possible, reuse an existing affiliation ID across the
// primary user and (later) the ephemeral user.
val identifiers = dpm.getAffiliationIds(adminName)
if (identifiers.isEmpty()) {
    identifiers.add(UUID.randomUUID().toString())
    dpm.setAffiliationIds(adminName, identifiers)
}

// Pass an affiliation ID to the ephemeral user in the admin extras.
val adminExtras = PersistableBundle()
adminExtras.putString(AFFILIATION_ID_KEY, identifiers.first())
// Include any other config for the new user here ...

// Create the ephemeral user, using this component as the admin.
try {
    val ephemeralUser = dpm.createAndManageUser(
            adminName,
            "tmp_user",
            adminName,
            adminExtras,
            DevicePolicyManager.MAKE_USER_EPHEMERAL or
                    DevicePolicyManager.SKIP_SETUP_WIZARD)

} catch (e: UserManager.UserOperationException) {
    if (e.userOperationResult ==
            UserManager.USER_OPERATION_ERROR_MAX_USERS) {
        // Find a way to free up users...
    }
}

Java

DevicePolicyManager dpm = (DevicePolicyManager)
    getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);

// If possible, reuse an existing affiliation ID across the
// primary user and (later) the ephemeral user.
Set<String> identifiers = dpm.getAffiliationIds(adminName);
if (identifiers.isEmpty()) {
  identifiers.add(UUID.randomUUID().toString());
  dpm.setAffiliationIds(adminName, identifiers);
}

// Pass an affiliation ID to the ephemeral user in the admin extras.
PersistableBundle adminExtras = new PersistableBundle();
adminExtras.putString(AFFILIATION_ID_KEY, identifiers.iterator().next());
// Include any other config for the new user here ...

// Create the ephemeral user, using this component as the admin.
try {
  UserHandle ephemeralUser = dpm.createAndManageUser(
      adminName,
      "tmp_user",
      adminName,
      adminExtras,
      DevicePolicyManager.MAKE_USER_EPHEMERAL |
          DevicePolicyManager.SKIP_SETUP_WIZARD);

} catch (UserManager.UserOperationException e) {
  if (e.getUserOperationResult() ==
      UserManager.USER_OPERATION_ERROR_MAX_USERS) {
    // Find a way to free up users...
  }
}

Ao criar ou iniciar um novo usuário, é possível verificar o motivo das falhas capturando a exceção UserOperationException e chamando getUserOperationResult(). Exceder os limites de usuários são motivos comuns para falhas:

A criação de um usuário pode levar algum tempo. Se você cria usuários com frequência, é possível melhorar a experiência deles preparando um usuário pronto para uso em segundo plano. Pode ser necessário equilibrar as vantagens de um usuário pronto para usar com o número máximo de usuários permitidos em um dispositivo.

Identificação

Depois de criar um novo usuário, você deve consultá-lo com um número de série persistente. Não mantenha as UserHandle, porque o sistema as recicla quando você cria e exclui usuários. Para saber o número de série, chame UserManager.getSerialNumberForUser():

Kotlin

// After calling createAndManageUser() use a device-unique serial number
// (that isn’t recycled) to identify the new user.
secondaryUser?.let {
    val userManager = getContext().getSystemService(UserManager::class.java)
    val ephemeralUserId = userManager!!.getSerialNumberForUser(it)
    // Save the serial number to storage  ...
}

Java

// After calling createAndManageUser() use a device-unique serial number
// (that isn’t recycled) to identify the new user.
if (secondaryUser != null) {
  UserManager userManager = getContext().getSystemService(UserManager.class);
  long ephemeralUserId = userManager.getSerialNumberForUser(secondaryUser);
  // Save the serial number to storage  ...
}

Configuração do usuário

Dependendo das necessidades dos usuários, você pode personalizar a configuração dos usuários secundários. Você pode incluir as seguintes flags ao chamar createAndManageUser():

SKIP_SETUP_WIZARD
Ignora a execução do assistente de configuração para novo usuário que verifica e instala atualizações, solicita que o usuário adicione uma Conta do Google com os serviços do Google e define um bloqueio de tela. Isso pode levar algum tempo e pode não ser aplicável a todos os usuários, como quiosques de Internet públicos.
LEAVE_ALL_SYSTEM_APPS_ENABLED
Deixa todos os apps do sistema ativados para o novo usuário. Se você não definir essa flag, o novo usuário terá apenas o conjunto mínimo de apps necessários para o smartphone funcionar, normalmente um navegador de arquivos, um discador telefônico, contatos e mensagens SMS.

Seguir o ciclo de vida do usuário

Seu DPC (se for um administrador do dispositivo totalmente gerenciado) pode ser útil saber quando usuários secundários mudam. Para executar tarefas subsequentes após mudanças, substitua estes métodos de callback na subclasse DeviceAdminReceiver do DPC:

onUserStarted()
Chamado após o sistema iniciar um usuário. Esse usuário ainda pode estar configurando ou executando em segundo plano. Você pode identificar o usuário do argumento startedUser.
onUserSwitched()
Chamado depois que o sistema alterna para um usuário diferente. Você pode usar o argumento switchedUser para definir o novo usuário que está sendo executado em primeiro plano.
onUserStopped()
Chamado após o sistema interromper um usuário porque ele saiu, mudou para um novo usuário (se for temporário) ou seu DPC interrompeu o usuário. Consiga o usuário do argumento stoppedUser.
onUserAdded()
Chamado quando o sistema adiciona um novo usuário. Normalmente, os usuários secundários não estão totalmente configurados quando o DPC recebe o callback. Consiga o usuário do argumento newUser.
onUserRemoved()
Chamado após o sistema excluir um usuário. Como o usuário já foi excluído, não é possível acessar o usuário representado pelo argumento removedUser.

Para saber quando o sistema leva um usuário ao primeiro plano ou o envia ao segundo plano, os apps podem registrar um receptor para as transmissões ACTION_USER_FOREGROUND e ACTION_USER_BACKGROUND.

Descobrir usuários

Para acessar todos os usuários secundários, o administrador de um dispositivo totalmente gerenciado pode chamar DevicePolicyManager.getSecondaryUsers(). Os resultados incluem qualquer usuário secundário ou temporário criado pelo administrador. Os resultados também incluem qualquer usuário secundário (ou convidado) que uma pessoa que usa o dispositivo possa ter criado. Os resultados não incluem perfis de trabalho porque não são usuários secundários. O exemplo abaixo mostra como usar esse método:

Kotlin

// The device is stored for the night. Stop all running secondary users.
dpm.getSecondaryUsers(adminName).forEach {
    dpm.stopUser(adminName, it)
}

Java

// The device is stored for the night. Stop all running secondary users.
for (UserHandle user : dpm.getSecondaryUsers(adminName)) {
  dpm.stopUser(adminName, user);
}

Veja outros métodos que você pode chamar para descobrir o status de usuários secundários:

DevicePolicyManager.isEphemeralUser()
Chame este método do administrador de um usuário secundário para descobrir se esse é um usuário temporário.
DevicePolicyManager.isAffiliatedUser()
Chame esse método do administrador de um usuário secundário para descobrir se esse usuário está afiliado ao usuário principal. Para saber mais sobre afiliação, consulte a coordenação de DPC abaixo.

Gerenciamento de usuários

Se você quiser gerenciar completamente o ciclo de vida do usuário, chame APIs para ter um controle refinado de quando e como o dispositivo muda de usuário. Por exemplo, você pode excluir um usuário quando um dispositivo não estiver sendo usado por um período ou enviar pedidos não enviados a um servidor antes que o turno de uma pessoa termine.

Sair

O Android 9.0 adicionou um botão de logout à tela de bloqueio para que o usuário do dispositivo possa encerrar a sessão. Depois de tocar no botão, o sistema interrompe o usuário secundário, exclui o usuário se ele for temporário, e o usuário principal retorna ao primeiro plano. O Android oculta o botão quando o usuário principal está em primeiro plano, porque ele não pode sair.

O Android não mostra o botão de encerrar sessão por padrão, mas o administrador (de um dispositivo totalmente gerenciado) pode ativá-lo chamando DevicePolicyManager.setLogoutEnabled(). Se você precisar confirmar o estado atual do botão, chame DevicePolicyManager.isLogoutEnabled().

O administrador de um usuário secundário pode desconectar o usuário de maneira programática e retornar ao usuário principal. Primeiro, confirme se os usuários secundários e principais estão afiliados. Em seguida, chame DevicePolicyManager.logoutUser(). Se o usuário desconectado for um usuário temporário, o sistema será interrompido e excluirá o usuário.

Trocar de usuário

Para alternar para um usuário secundário diferente, o administrador de um dispositivo totalmente gerenciado pode chamar DevicePolicyManager.switchUser(). Por conveniência, você pode transmitir null para alternar para o usuário principal.

Interromper um usuário

Para interromper um usuário secundário, um DPC proprietário de um dispositivo totalmente gerenciado pode chamar DevicePolicyManager.stopUser(). Se o usuário interrompido for um usuário temporário, ele será interrompido e excluído.

Recomendamos interromper os usuários sempre que possível para permanecer abaixo do número máximo de usuários em execução no dispositivo.

Excluir um usuário

Para excluir permanentemente um usuário secundário, um DPC pode chamar um destes métodos DevicePolicyManager:

  • O administrador de um dispositivo totalmente gerenciado pode chamar removeUser().
  • Um administrador do usuário secundário pode chamar wipeData().

O sistema exclui usuários temporários quando eles não estão conectados, interrompidos ou foram desligados.

Desativar a interface padrão

Se o DPC oferecer uma interface para gerenciar usuários, você poderá desativar a interface multiusuário integrada do Android. Para fazer isso, chame DevicePolicyManager.setLogoutEnabled() e adicione a restrição DISALLOW_USER_SWITCH, conforme mostrado no exemplo a seguir:

Kotlin

// Explicitly disallow logging out using Android UI (disabled by default).
dpm.setLogoutEnabled(adminName, false)

// Disallow switching users in Android's UI. This DPC can still
// call switchUser() to manage users.
dpm.addUserRestriction(adminName, UserManager.DISALLOW_USER_SWITCH)

Java

// Explicitly disallow logging out using Android UI (disabled by default).
dpm.setLogoutEnabled(adminName, false);

// Disallow switching users in Android's UI. This DPC can still
// call switchUser() to manage users.
dpm.addUserRestriction(adminName, UserManager.DISALLOW_USER_SWITCH);

O usuário do dispositivo não pode adicionar usuários secundários com a interface integrada do Android porque os administradores de dispositivos totalmente gerenciados adicionam automaticamente a restrição de usuário DISALLOW_ADD_USER.

Mensagens da sessão

Quando a pessoa que está usando um dispositivo muda para um novo usuário, o Android mostra um painel para destacar a chave. O Android mostra as seguintes mensagens:

  • Mensagem de início de sessão do usuário mostrada quando o dispositivo muda do usuário principal para um secundário.
  • Mensagem da sessão do usuário final mostrada quando o dispositivo retorna ao usuário principal de um usuário secundário.

O sistema não mostra as mensagens ao alternar entre dois usuários secundários.

Como as mensagens podem não ser adequadas para todas as situações, você pode alterar o texto delas. Por exemplo, se a solução usar sessões de usuário temporárias, você poderá refletir isso em mensagens como: Interrompendo a sessão do navegador e excluindo dados pessoais...

O sistema mostra a mensagem por apenas alguns segundos, então cada mensagem precisa ser uma frase curta e clara. Para personalizar as mensagens, o administrador pode chamar os métodos DevicePolicyManager setStartUserSessionMessage() e setEndUserSessionMessage(), conforme mostrado no exemplo a seguir:

Kotlin

// Short, easy-to-read messages shown at the start and end of a session.
// In your app, store these strings in a localizable resource.
internal val START_USER_SESSION_MESSAGE = "Starting guest session…"
internal val END_USER_SESSION_MESSAGE = "Stopping & clearing data…"

// ...
dpm.setStartUserSessionMessage(adminName, START_USER_SESSION_MESSAGE)
dpm.setEndUserSessionMessage(adminName, END_USER_SESSION_MESSAGE)

Java

// Short, easy-to-read messages shown at the start and end of a session.
// In your app, store these strings in a localizable resource.
private static final String START_USER_SESSION_MESSAGE = "Starting guest session…";
private static final String END_USER_SESSION_MESSAGE = "Stopping & clearing data…";

// ...
dpm.setStartUserSessionMessage(adminName, START_USER_SESSION_MESSAGE);
dpm.setEndUserSessionMessage(adminName, END_USER_SESSION_MESSAGE);

Transmita null para excluir suas mensagens personalizadas e retornar às mensagens padrão do Android. Se você precisar verificar o texto da mensagem atual, chame getStartUserSessionMessage() ou getEndUserSessionMessage().

Seu DPC precisa definir mensagens localizadas para a localidade atual do usuário. Também é necessário atualizar as mensagens quando a localidade do usuário muda:

Kotlin

override fun onReceive(context: Context?, intent: Intent?) {
    // Added the <action android:name="android.intent.action.LOCALE_CHANGED" />
    // intent filter for our DeviceAdminReceiver subclass in the app manifest file.
    if (intent?.action === ACTION_LOCALE_CHANGED) {

        // Android's resources return a string suitable for the new locale.
        getManager(context).setStartUserSessionMessage(
                getWho(context),
                context?.getString(R.string.start_user_session_message))

        getManager(context).setEndUserSessionMessage(
                getWho(context),
                context?.getString(R.string.end_user_session_message))
    }
    super.onReceive(context, intent)
}

Java

public void onReceive(Context context, Intent intent) {
  // Added the <action android:name="android.intent.action.LOCALE_CHANGED" />
  // intent filter for our DeviceAdminReceiver subclass in the app manifest file.
  if (intent.getAction().equals(ACTION_LOCALE_CHANGED)) {

    // Android's resources return a string suitable for the new locale.
    getManager(context).setStartUserSessionMessage(
        getWho(context),
        context.getString(R.string.start_user_session_message));

    getManager(context).setEndUserSessionMessage(
        getWho(context),
        context.getString(R.string.end_user_session_message));
  }
  super.onReceive(context, intent);
}

Coordenação de DPC

O gerenciamento de usuários secundários normalmente precisa de duas instâncias do DPC: uma que tenha o dispositivo totalmente gerenciado e outra que seja proprietária do usuário secundário. Ao criar um novo usuário, o administrador do dispositivo totalmente gerenciado define outra instância de si mesmo como administrador do novo usuário.

Usuários afiliados

Algumas das APIs deste guia para desenvolvedores só funcionam quando os usuários secundários são afiliados. Como o Android desativa alguns recursos (por exemplo, registro de rede) quando você adiciona novos usuários secundários não afiliados ao dispositivo, é necessário afiliar os usuários o mais rápido possível. Consulte o exemplo em Configuração abaixo.

Configurar

Configure novos usuários secundários (do DPC proprietário do usuário secundário) antes de permitir que as pessoas os usem. É possível fazer essa configuração no callback DeviceAdminReceiver.onEnabled(). Se você definiu anteriormente algum extra de administrador na chamada para createAndManageUser(), pode receber os valores do argumento intent. O exemplo a seguir mostra um DPC filiando um novo usuário secundário no callback:

Kotlin

override fun onEnabled(context: Context?, intent: Intent?) {
    super.onEnabled(context, intent)

    // Get the affiliation ID (our DPC previously put in the extras) and
    // set the ID for this new secondary user.
    intent?.getStringExtra(AFFILIATION_ID_KEY)?.let {
        val dpm = getManager(context)
        dpm.setAffiliationIds(getWho(context), setOf(it))
    }
    // Continue setup of the new secondary user ...
}

Java

public void onEnabled(Context context, Intent intent) {
  // Get the affiliation ID (our DPC previously put in the extras) and
  // set the ID for this new secondary user.
  String affiliationId = intent.getStringExtra(AFFILIATION_ID_KEY);
  if (affiliationId != null) {
    DevicePolicyManager dpm = getManager(context);
    dpm.setAffiliationIds(getWho(context),
        new HashSet<String>(Arrays.asList(affiliationId)));
  }
  // Continue setup of the new secondary user ...
}

RPCs entre DPCs

Mesmo que as duas instâncias de DPC estejam em execução em usuários separados, os DPCs que pertencem ao dispositivo e os usuários secundários podem se comunicar entre si. Como a chamada do serviço de outro DPC ultrapassa os limites do usuário, seu DPC não pode chamar bindService() como você normalmente faria no Android. Para vincular um serviço em execução em outro usuário, chame DevicePolicyManager.bindDeviceAdminServiceAsUser().

Usuário principal e dois usuários secundários afiliados que chamam RPCs.
Figura 2. Administradores de usuários primários e secundários afiliados que chamam métodos de serviço.

Seu DPC só pode ser vinculado a serviços em execução nos usuários retornados por DevicePolicyManager.getBindDeviceAdminTargetUsers(). O exemplo a seguir mostra o administrador de uma vinculação secundária de usuário com o administrador do dispositivo totalmente gerenciado:

Kotlin

// From a secondary user, the list contains just the primary user.
dpm.getBindDeviceAdminTargetUsers(adminName).forEach {

    // Set up the callbacks for the service connection.
    val intent = Intent(mContext, FullyManagedDeviceService::class.java)
    val serviceconnection = object : ServiceConnection {
        override fun onServiceConnected(componentName: ComponentName,
                                        iBinder: IBinder) {
            // Call methods on service ...
        }
        override fun onServiceDisconnected(componentName: ComponentName) {
            // Clean up or reconnect if needed ...
        }
    }

    // Bind to the service as the primary user [it].
    val bindSuccessful = dpm.bindDeviceAdminServiceAsUser(adminName,
            intent,
            serviceconnection,
            Context.BIND_AUTO_CREATE,
            it)
}

Java

// From a secondary user, the list contains just the primary user.
List<UserHandle> targetUsers = dpm.getBindDeviceAdminTargetUsers(adminName);
if (targetUsers.isEmpty()) {
  // If the users aren't affiliated, the list doesn't contain any users.
  return;
}

// Set up the callbacks for the service connection.
Intent intent = new Intent(mContext, FullyManagedDeviceService.class);
ServiceConnection serviceconnection = new ServiceConnection() {
  @Override
  public void onServiceConnected(
      ComponentName componentName, IBinder iBinder) {
    // Call methods on service ...
  }

  @Override
  public void onServiceDisconnected(ComponentName componentName) {
    // Clean up or reconnect if needed ...
  }
};

// Bind to the service as the primary user.
UserHandle primaryUser = targetUsers.get(0);
boolean bindSuccessful = dpm.bindDeviceAdminServiceAsUser(
    adminName,
    intent,
    serviceconnection,
    Context.BIND_AUTO_CREATE,
    primaryUser);

Outros recursos

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