Cómo administrar varios usuarios

En esta guía para desarrolladores, se explica cómo el controlador de políticas del dispositivo (DPC) puede administrar varios usuarios de Android en dispositivos dedicados.

Descripción general

Tu DPC puede ayudar a varias personas a compartir un solo dispositivo dedicado. Tu DPC que se ejecuta en un dispositivo completamente administrado puede crear y administrar dos tipos de usuarios:

  • Los usuarios secundarios son usuarios de Android con apps independientes y datos que se guardan entre sesiones. Administras el usuario con un componente de administrador. Estos usuarios son útiles en casos en los que se levanta un dispositivo al comienzo de un turno, como los repartidores o los trabajadores de seguridad.
  • Los usuarios efímeros son usuarios secundarios que el sistema borra cuando se detiene, cambia de dispositivo o se reinicia el dispositivo. Estos usuarios son útiles para casos en los que los datos se pueden borrar después de que finaliza la sesión, como los kioscos de Internet de acceso público.

Usas tu DPC existente para administrar el dispositivo dedicado y los usuarios secundarios. Un componente de administrador de tu DPC se establece como administrador para los nuevos usuarios secundarios cuando los creas.

Usuario principal y dos usuarios secundarios.
Figura 1: Usuarios principales y secundarios administrados por administradores desde el mismo DPC

Los administradores de un usuario secundario deben pertenecer al mismo paquete que el administrador del dispositivo completamente administrado. Para simplificar el desarrollo, te recomendamos que compartas un administrador entre el dispositivo y los usuarios secundarios.

La administración de varios usuarios en dispositivos dedicados suele requerir Android 9.0. Sin embargo, algunos de los métodos que se usan en esta guía para desarrolladores están disponibles en versiones anteriores de Android.

Usuarios secundarios

Los usuarios secundarios pueden conectarse a Wi-Fi y configurar redes nuevas. Sin embargo, no pueden editar ni borrar redes, ni siquiera las que crearon.

Cómo crear usuarios

Tu DPC puede crear usuarios adicionales en segundo plano y, luego, cambiarlos a primer plano. El proceso es casi el mismo para los usuarios secundarios y los efímeros. Implementa los siguientes pasos para los administradores del dispositivo completamente administrado y del usuario secundario:

  1. Llama a DevicePolicyManager.createAndManageUser(). Para crear un usuario efímero, incluye MAKE_USER_EPHEMERAL en el argumento de las marcas.
  2. Llama a DevicePolicyManager.startUserInBackground() para iniciar el usuario en segundo plano. El usuario comenzará a ejecutarse, pero deberás finalizar la configuración antes de llevarlo al primer plano y mostrárselo a la persona que usa el dispositivo.
  3. En el administrador del usuario secundario, llama a DevicePolicyManager.setAffiliationIds() para afiliar al usuario nuevo al usuario principal. Consulta Coordinación de DPC a continuación.
  4. En el administrador del dispositivo completamente administrado, llama a DevicePolicyManager.switchUser() para cambiar al usuario al primer plano.

En el siguiente ejemplo, se muestra cómo puedes agregar el paso 1 a tu 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...
  }
}

Cuando creas o inicias un usuario nuevo, puedes verificar el motivo de las fallas. Para ello, captura la excepción UserOperationException y llama a getUserOperationResult(). Superar los límites del usuario son motivos comunes de fallas:

Crear un usuario puede llevar algún tiempo. Si creas usuarios con frecuencia, puedes mejorar la experiencia del usuario si preparas a un usuario listo para usar en segundo plano. Es posible que debas equilibrar las ventajas de un usuario listo para usar con la cantidad máxima de usuarios permitidos en un dispositivo.

Identificación

Después de crear un usuario nuevo, debes dirigirlo con un número de serie persistente. No conserves el UserHandle, ya que el sistema los recicla a medida que creas y borras usuarios. Para obtener el número de serie, llama a 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  ...
}

Configuración del usuario

Según las necesidades de tus usuarios, puedes personalizar la configuración de los usuarios secundarios. Puedes incluir las siguientes marcas cuando llames a createAndManageUser():

SKIP_SETUP_WIZARD
Omite la ejecución del asistente de configuración para usuarios nuevos que busca e instala actualizaciones, le solicita al usuario que agregue una Cuenta de Google junto con los servicios de Google y establece un bloqueo de pantalla. Esto puede demorar un tiempo y podría no aplicarse a todos los usuarios, como los kioscos de Internet públicos.
LEAVE_ALL_SYSTEM_APPS_ENABLED
Deja habilitadas todas las apps del sistema en el usuario nuevo. Si no estableces esta marca, el usuario nuevo contendrá solo el conjunto mínimo de apps que el teléfono necesita para operar (por lo general, un navegador de archivos, un marcador telefónico, contactos y mensajes SMS).

Sigue el ciclo de vida del usuario

Es posible que tu DPC (si es un administrador del dispositivo completamente administrado) te resulte útil saber cuándo cambian los usuarios secundarios. Para ejecutar tareas de seguimiento después de los cambios, anula estos métodos de devolución de llamada en la subclase DeviceAdminReceiver de tu DPC:

onUserStarted()
Se invoca después de que el sistema inicia un usuario. Es posible que este usuario aún esté realizando la configuración o ejecutándose en segundo plano. Puedes obtener el usuario del argumento startedUser.
onUserSwitched()
Se llama después de que el sistema cambia a un usuario diferente. Puedes obtener el usuario nuevo que ahora se ejecuta en primer plano desde el argumento switchedUser.
onUserStopped()
Se llama después de que el sistema detiene a un usuario porque salió de su cuenta, cambió a un usuario nuevo (si el usuario es efímero) o tu DPC detuvo al usuario. Puedes obtener el usuario del argumento stoppedUser.
onUserAdded()
Se llama cuando el sistema agrega un usuario nuevo. Por lo general, los usuarios secundarios no están configurados por completo cuando tu DPC obtiene la devolución de llamada. Puedes obtener el usuario del argumento newUser.
onUserRemoved()
Se llama después de que el sistema borra un usuario. Como el usuario ya se borró, no puedes acceder al usuario representado por el argumento removedUser.

Para saber cuándo el sistema lleva a un usuario a primer plano o lo envía a segundo plano, las apps pueden registrar un receptor para las transmisiones ACTION_USER_FOREGROUND y ACTION_USER_BACKGROUND.

Descubrir usuarios

Para obtener todos los usuarios secundarios, un administrador de un dispositivo completamente administrado puede llamar a DevicePolicyManager.getSecondaryUsers(). Los resultados incluyen a todos los usuarios secundarios o efímeros que creó el administrador. Los resultados también incluyen los usuarios secundarios (o un usuario invitado) que una persona que usa el dispositivo pueda haber creado. Los resultados no incluyen perfiles de trabajo porque no son usuarios secundarios. En el siguiente ejemplo, se muestra cómo puedes usar este 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);
}

Estos son otros métodos a los que puedes llamar para averiguar el estado de los usuarios secundarios:

DevicePolicyManager.isEphemeralUser()
Llama a este método desde el administrador de un usuario secundario para averiguar si se trata de un usuario efímero.
DevicePolicyManager.isAffiliatedUser()
Llama a este método desde el administrador de un usuario secundario para averiguar si este está afiliado al usuario principal. Para obtener más información sobre la afiliación, consulta Coordinación con DPC a continuación.

Administración de usuarios

Si deseas administrar completamente el ciclo de vida del usuario, puedes llamar a las APIs para tener un control detallado de cuándo y cómo el dispositivo cambia de usuario. Por ejemplo, puedes borrar un usuario cuando no se usó un dispositivo durante un período o puedes enviar pedidos no enviados a un servidor antes de que termine el turno de una persona.

Salir

Android 9.0 agregó un botón de salida a la pantalla de bloqueo para que una persona que usa el dispositivo pueda finalizar su sesión. Después de presionar el botón, el sistema detiene al usuario secundario, borra al usuario si es efímero y el usuario principal regresa al primer plano. Android oculta el botón cuando el usuario principal está en primer plano porque este no puede salir de la cuenta.

Android no muestra el botón de finalización de la sesión de forma predeterminada, pero el administrador (de un dispositivo completamente administrado) puede habilitarlo llamando a DevicePolicyManager.setLogoutEnabled(). Si necesitas confirmar el estado actual del botón, llama a DevicePolicyManager.isLogoutEnabled().

El administrador de un usuario secundario puede salir de su sesión de manera programática y regresar al usuario principal. Primero, confirma que los usuarios secundarios y principales estén afiliados y, luego, llama a DevicePolicyManager.logoutUser(). Si el usuario que salió de su cuenta es efímero, el sistema se detiene y, luego, lo borra.

Cambiar usuarios

Para cambiar a un usuario secundario diferente, el administrador de un dispositivo completamente administrado puede llamar a DevicePolicyManager.switchUser(). Para tu comodidad, puedes pasar null a fin de cambiar al usuario principal.

Cómo detener un usuario

Para detener a un usuario secundario, un DPC que posee un dispositivo completamente administrado puede llamar a DevicePolicyManager.stopUser(). Si el usuario detenido es un usuario efímero, se detiene y, luego, se borra.

Recomendamos que detengas los usuarios siempre que sea posible para mantener un nivel inferior a la cantidad máxima de usuarios activos del dispositivo.

Eliminar un usuario

Para borrar de forma permanente un usuario secundario, un DPC puede llamar a uno de los siguientes métodos DevicePolicyManager:

  • Un administrador de un dispositivo completamente administrado puede llamar a removeUser().
  • Un administrador del usuario secundario puede llamar a wipeData().

El sistema borra los usuarios efímeros cuando salen de su cuenta, cuando se los detiene o cuando salen de ella.

Cómo inhabilitar la IU predeterminada

Si tu DPC proporciona una IU para administrar usuarios, puedes inhabilitar la interfaz multiusuario integrada de Android. Para ello, llama a DevicePolicyManager.setLogoutEnabled() y agrega la restricción DISALLOW_USER_SWITCH como se muestra en el siguiente ejemplo:

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

La persona que usa el dispositivo no puede agregar usuarios secundarios con la IU integrada de Android porque los administradores de dispositivos completamente administrados agregan automáticamente la restricción de usuarios de DISALLOW_ADD_USER.

Mensajes de la sesión

Cuando la persona que usa un dispositivo cambia a un usuario nuevo, Android muestra un panel para destacar el interruptor. Android muestra los siguientes mensajes:

  • Mensaje de inicio de sesión de usuario que se muestra cuando el dispositivo cambia a un usuario secundario del usuario principal
  • Mensaje de sesión de usuario final que se muestra cuando el dispositivo regresa al usuario principal desde un usuario secundario

El sistema no muestra los mensajes cuando se alterna entre dos usuarios secundarios.

Dado que los mensajes podrían no ser adecuados para todas las situaciones, puedes cambiar su texto. Por ejemplo, si tu solución usa sesiones de usuario efímeras, puedes reflejarlo en los mensajes como Stopping phone session and delete personal data... (Detén la sesión del navegador y borra datos personales...)

El sistema muestra el mensaje solo durante unos segundos, por lo que cada mensaje debe ser una frase corta y clara. Para personalizar los mensajes, el administrador puede llamar a los métodos setStartUserSessionMessage() y setEndUserSessionMessage() de DevicePolicyManager, como se muestra en el siguiente ejemplo:

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

Pasa null para borrar tus mensajes personalizados y volver a los mensajes predeterminados de Android. Si necesitas verificar el texto actual del mensaje, llama a getStartUserSessionMessage() o getEndUserSessionMessage().

Tu DPC debe establecer mensajes localizados para la configuración regional actual del usuario. También deberás actualizar los mensajes cuando cambie la configuración regional del usuario:

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

Coordinación de DPC

Por lo general, la administración de usuarios secundarios necesita dos instancias de tu DPC: una es la propietaria del dispositivo completamente administrado y la otra, del usuario secundario. Cuando se crea un usuario nuevo, el administrador del dispositivo completamente administrado establece otra instancia como administrador del usuario nuevo.

Usuarios afiliados

Algunas de las APIs de esta guía para desarrolladores solo funcionan cuando los usuarios secundarios están afiliados. Dado que Android inhabilita algunas funciones (por ejemplo, el registro de red) cuando agregas nuevos usuarios secundarios no afiliados al dispositivo, debes afiliar a los usuarios lo antes posible. Consulta el ejemplo en Configuración a continuación.

Configuración

Configura nuevos usuarios secundarios (desde el DPC que posee al usuario secundario) antes de permitir que las personas los usen. Puedes hacerlo desde la devolución de llamada DeviceAdminReceiver.onEnabled(). Si ya configuraste recursos adicionales del administrador en la llamada a createAndManageUser(), puedes obtener los valores del argumento intent. En el siguiente ejemplo, se muestra un DPC que afiliada a un nuevo usuario secundario en la devolución de llamada:

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

RPC entre DPC

Aunque las dos instancias de DPC se ejecutan con usuarios diferentes, los DPC que poseen el dispositivo y los usuarios secundarios pueden comunicarse entre sí. Debido a que llamar al servicio de otro DPC cruza los límites del usuario, tu DPC no puede llamar a bindService() como lo normalmente lo harías en Android. Para establecer una vinculación con un servicio que se ejecuta en otro usuario, llama a DevicePolicyManager.bindDeviceAdminServiceAsUser().

El usuario principal y dos usuarios secundarios afiliados que llaman a las RPC.
Figura 2: Administradores de usuarios principales y secundarios afiliados que llaman a los métodos de servicio

Tu DPC solo puede vincularse a servicios que se ejecutan en los usuarios que muestra DevicePolicyManager.getBindDeviceAdminTargetUsers(). En el siguiente ejemplo, se muestra el administrador de una vinculación de usuario secundaria al administrador del dispositivo completamente administrado:

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

Recursos adicionales

Para obtener más información sobre los dispositivos dedicados, consulta los siguientes documentos: