Cómo administrar varios usuarios

En esta guía para desarrolladores, se explica cómo tu controlador de política de 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. Si tu DPC se ejecuta en un dispositivo completamente administrado, puedes crear y administrar dos tipos de usuarios:

  • Los usuarios secundarios son usuarios de Android que tienen apps independientes y datos guardados entre sesiones. Administras al usuario con un componente de administrador. Estos usuarios son útiles en los casos en los que se toma 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.

Usarás tu DPC existente para administrar el dispositivo dedicado y los usuarios secundarios. Un componente de administrador en tu DPC se establece como administrador de 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.

Cómo crear usuarios

Tu DPC puede crear usuarios adicionales en segundo plano y, luego, cambiarlos al 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 el 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 comienza a ejecutarse, pero querrá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 el usuario nuevo con el 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 si detectas la excepción UserOperationException y llamas a getUserOperationResult(). Superar los límites de usuario son motivos comunes de fallas:

Crear un usuario puede llevar un tiempo. Si creas usuarios con frecuencia, puedes mejorar la experiencia 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 recomendarle un número de serie persistente. No conserves el UserHandle, ya que el sistema los recicla a medida que creas y borras usuarios. Llama a UserManager.getSerialNumberForUser() para obtener el número de serie:

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 actualizaciones y las instala, le solicita al usuario que agregue una Cuenta de Google junto con los servicios de Google y establece un bloqueo de pantalla. Esto puede tardar un poco y es posible que no se aplique 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 configuras esta marca, el usuario nuevo solo contendrá el conjunto mínimo de apps que necesita el teléfono para operar, por lo general, un navegador de archivos, un marcador telefónico, contactos y mensajes SMS.

Sigue el ciclo de vida del usuario

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

onUserStarted()
Se llama después de que el sistema inicia un usuario. Es posible que este usuario aún esté configurando 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 lo detuvo. 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 recibe la devolución de llamada. Puedes obtener el usuario del argumento newUser.
onUserRemoved()
Se llama después de que el sistema borra un usuario. Debido a que 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 al 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.

Descubre usuarios

Para obtener todos los usuarios secundarios, un administrador de un dispositivo completamente administrado puede llamar a DevicePolicyManager.getSecondaryUsers(). Los resultados incluyen a los usuarios secundarios o efímeros que creó el administrador. Los resultados también incluyen a los usuarios secundarios (o los usuarios invitados) que una persona que usa el dispositivo podría 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 del administrador de un usuario secundario para averiguar si se trata de un usuario efímero.
DevicePolicyManager.isAffiliatedUser()
Llama a este método del 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 la sección Coordinación de DPC a continuación.

Administración de usuarios

Si quieres administrar por completo el ciclo de vida del usuario, puedes llamar a las APIs para controlar en detalle cuándo y cómo cambia el dispositivo de los usuarios. 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 finalice 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, luego, 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.

Android no muestra el botón de finalización de 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 cerrar la sesión del usuario 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 detendrá y, luego, lo borrará.

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 para cambiar al usuario principal.

Cómo detener a 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 detener a los usuarios siempre que sea posible para ayudar a no superar 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, se detienen o se alejan de ellos.

Inhabilita 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 la sesión del 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 el texto de estos mensajes. Por ejemplo, si tu solución usa sesiones de usuario efímeras, puedes reflejar esto en mensajes como los siguientes: Stoping navegador session and delete personal data... (Detener la sesión del navegador y borrar los datos personales).

El sistema muestra el mensaje durante solo un par de 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 del mensaje actual, llama a getStartUserSessionMessage() o getEndUserSessionMessage().

Tu DPC debe configurar 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 del DPC

Por lo general, la administración de usuarios secundarios necesita dos instancias de tu DPC: una que posee el dispositivo completamente administrado y la otra, al usuario secundario. Cuando se crea un usuario nuevo, el administrador del dispositivo completamente administrado configura otra instancia de sí mismo 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. Debido a que Android inhabilita algunas funciones (por ejemplo, el registro de red) cuando agregas nuevos usuarios secundarios no afiliados al dispositivo, debes afiliar los usuarios lo antes posible. Consulta el ejemplo en Configuración a continuación.

Configuración

Configura nuevos usuarios secundarios (del DPC que posee al usuario secundario) antes de permitir que las personas los usen. Puedes realizar esta configuración desde la devolución de llamada DeviceAdminReceiver.onEnabled(). Si ya configuraste recursos adicionales de administrador en la llamada a createAndManageUser(), puedes obtener los valores del argumento intent. En el siguiente ejemplo, se muestra un DPC que afiliado 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 supera los límites del usuario, tu DPC no puede llamar a bindService() como lo harías normalmente en Android. Para realizar la vinculación a 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 los 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 con el 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 exclusivos, consulta los siguientes documentos: