Gérer plusieurs utilisateurs

Ce guide du développeur explique comment votre outil de contrôle des règles relatives aux appareils (DPC) peut gérer plusieurs utilisateurs Android sur des appareils dédiés.

Présentation

Votre DPC peut permettre à plusieurs personnes de partager un même appareil dédié. Votre DPC s'exécutant sur un appareil entièrement géré peut créer et gérer deux types d'utilisateurs:

  • Les utilisateurs secondaires sont des utilisateurs Android dont les applications et les données sont enregistrées entre les sessions. Vous pouvez gérer les utilisateurs à l'aide d'un composant d'administration. Ces utilisateurs sont utiles dans les cas où un appareil est récupéré au début d'une équipe, comme les livreurs ou les agents de sécurité.
  • Les utilisateurs éphémères sont des utilisateurs secondaires que le système supprime lorsque l'utilisateur s'arrête, quitte ou lorsque l'appareil redémarre. Ces utilisateurs sont utiles dans les cas où les données peuvent être supprimées à la fin de la session, par exemple dans les cas de kiosques Internet en accès public.

Vous utilisez votre DPC existant pour gérer l'appareil dédié et les utilisateurs secondaires. Un composant d'administration de votre DPC se définit comme l'administrateur des nouveaux utilisateurs secondaires lorsque vous les créez.

Utilisateur principal et deux utilisateurs secondaires.
Figure 1. Utilisateurs principaux et secondaires gérés par des administrateurs issus du même DPC

Les administrateurs d'un utilisateur secondaire doivent appartenir au même package que l'administrateur de l'appareil entièrement géré. Pour simplifier le développement, nous vous recommandons de partager un administrateur entre l'appareil et les utilisateurs secondaires.

La gestion de plusieurs utilisateurs sur des appareils dédiés nécessite généralement Android 9.0, mais certaines des méthodes utilisées dans ce guide du développeur sont disponibles dans les versions antérieures d'Android.

Créer des utilisateurs

Votre DPC peut créer des utilisateurs supplémentaires en arrière-plan, puis les faire passer au premier plan. Le processus est presque le même pour les utilisateurs secondaires et éphémères. Procédez comme suit pour les administrateurs de l'appareil entièrement géré et de l'utilisateur secondaire:

  1. Appelez DevicePolicyManager.createAndManageUser(). Pour créer un utilisateur éphémère, incluez MAKE_USER_EPHEMERAL dans l'argument "flags".
  2. Appelez DevicePolicyManager.startUserInBackground() pour démarrer l'utilisateur en arrière-plan. L'utilisateur commence à s'exécuter, mais vous devez terminer la configuration avant de le mettre au premier plan et de le montrer à l'utilisateur de l'appareil.
  3. Dans l'administrateur de l'utilisateur secondaire, appelez DevicePolicyManager.setAffiliationIds() pour associer le nouvel utilisateur à l'utilisateur principal. Consultez la section Coordination DPC ci-dessous.
  4. Revenez dans l'administrateur de l'appareil entièrement géré, appelez DevicePolicyManager.switchUser() pour faire passer l'utilisateur au premier plan.

L'exemple suivant montre comment ajouter l'étape 1 à votre 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...
  }
}

Lorsque vous créez ou démarrez un utilisateur, vous pouvez vérifier la raison de tout échec en interceptant l'exception UserOperationException et en appelant getUserOperationResult(). Le dépassement des limites d'utilisateurs est l'une des raisons courantes d'un échec:

La création d'un utilisateur peut prendre un certain temps. Si vous créez fréquemment des utilisateurs, vous pouvez améliorer l'expérience utilisateur en préparant un utilisateur prêt à l'emploi en arrière-plan. Vous devrez peut-être trouver un équilibre entre les avantages d'un utilisateur prêt à l'emploi et le nombre maximal d'utilisateurs autorisés sur un appareil.

Identification

Après avoir créé un utilisateur, vous devez le désigner avec un numéro de série persistant. Ne conservez pas les UserHandle, car le système les recycle lorsque vous créez et supprimez des utilisateurs. Obtenez le numéro de série en appelant 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  ...
}

Configuration utilisateur

En fonction des besoins de vos utilisateurs, vous pouvez personnaliser la configuration des utilisateurs secondaires. Vous pouvez inclure les indicateurs suivants lorsque vous appelez createAndManageUser():

SKIP_SETUP_WIZARD
Passe l'exécution de l'assistant de configuration pour les nouveaux utilisateurs, qui recherche et installe les mises à jour, invite l'utilisateur à ajouter un compte Google avec les services Google et définit un verrouillage de l'écran. Cette opération peut prendre un certain temps et ne pas s'appliquer à tous les utilisateurs (bornes Internet publiques, par exemple).
LEAVE_ALL_SYSTEM_APPS_ENABLED
Laisse toutes les applications système activées pour le nouvel utilisateur. Si vous ne définissez pas cet indicateur, le nouvel utilisateur ne contient que l'ensemble minimal d'applications nécessaires au fonctionnement du téléphone (généralement un explorateur de fichiers, un clavier téléphonique, des contacts et des SMS).

Suivre le cycle de vie des utilisateurs

Votre DPC (s'il s'agit d'un administrateur de l'appareil entièrement géré) peut trouver utile de savoir quand les utilisateurs secondaires changent. Pour exécuter des tâches complémentaires après des modifications, remplacez ces méthodes de rappel dans la sous-classe DeviceAdminReceiver de votre DPC:

onUserStarted()
Appelée après le démarrage d'un utilisateur par le système. Cet utilisateur est peut-être encore en cours de configuration ou d'exécution en arrière-plan. Vous pouvez obtenir l'utilisateur à partir de l'argument startedUser.
onUserSwitched()
Appelée après que le système passe à un autre utilisateur. Vous pouvez obtenir le nouvel utilisateur qui s'exécute désormais au premier plan à partir de l'argument switchedUser.
onUserStopped()
Appelée après que le système a arrêté un utilisateur parce qu'il s'est déconnecté, est passé à un nouvel utilisateur (si l'utilisateur est éphémère) ou que votre DPC l'a arrêté. Vous pouvez obtenir l'utilisateur à partir de l'argument stoppedUser.
onUserAdded()
Appelée lorsque le système ajoute un utilisateur. En général, les utilisateurs secondaires ne sont pas entièrement configurés lorsque votre DPC reçoit le rappel. Vous pouvez obtenir l'utilisateur à partir de l'argument newUser.
onUserRemoved()
Appelée après la suppression d'un utilisateur par le système. Comme l'utilisateur a déjà été supprimé, vous ne pouvez pas accéder à l'utilisateur représenté par l'argument removedUser.

Pour savoir quand le système met un utilisateur au premier plan ou l'envoie en arrière-plan, les applications peuvent enregistrer un récepteur pour les diffusions ACTION_USER_FOREGROUND et ACTION_USER_BACKGROUND.

Découvrir les utilisateurs

Pour obtenir tous les utilisateurs secondaires, l'administrateur d'un appareil entièrement géré peut appeler DevicePolicyManager.getSecondaryUsers(). Les résultats incluent tous les utilisateurs secondaires ou éphémères créés par l'administrateur. Les résultats incluent également tous les utilisateurs secondaires (ou invités) qu'une personne utilisant l'appareil aurait pu créer. Les résultats n'incluent pas les profils professionnels, car il ne s'agit pas d'utilisateurs secondaires. L'exemple suivant montre comment utiliser cette méthode:

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

Voici d'autres méthodes que vous pouvez appeler pour vérifier l'état des utilisateurs secondaires:

DevicePolicyManager.isEphemeralUser()
Appelez cette méthode à partir de l'administrateur d'un utilisateur secondaire pour savoir s'il s'agit d'un utilisateur éphémère.
DevicePolicyManager.isAffiliatedUser()
Appelez cette méthode à partir de l'administrateur d'un utilisateur secondaire pour savoir si cet utilisateur est affilié à l'utilisateur principal. Pour en savoir plus sur l'affiliation, consultez la section Coordination de l'outil DPC ci-dessous.

Gestion des utilisateurs

Si vous souhaitez gérer entièrement le cycle de vie des utilisateurs, vous pouvez appeler des API pour contrôler précisément quand et comment l'appareil change d'utilisateur. Par exemple, vous pouvez supprimer un utilisateur lorsque son appareil n'a pas été utilisé depuis un certain temps ou envoyer des commandes non envoyées à un serveur avant la fin de son quart de travail.

Se déconnecter

Android 9.0 a ajouté un bouton de déconnexion à l'écran de verrouillage afin qu'une personne utilisant l'appareil puisse mettre fin à sa session. Après avoir appuyé sur le bouton, le système arrête l'utilisateur secondaire, le supprime s'il est éphémère, et l'utilisateur principal revient au premier plan. Android masque le bouton lorsque l'utilisateur principal est au premier plan, car celui-ci ne peut pas se déconnecter.

Android n'affiche pas le bouton de fin de session par défaut, mais votre administrateur (d'un appareil entièrement géré) peut l'activer en appelant DevicePolicyManager.setLogoutEnabled(). Si vous devez confirmer l'état actuel du bouton, appelez DevicePolicyManager.isLogoutEnabled().

L'administrateur d'un utilisateur secondaire peut déconnecter l'utilisateur par programmation et revenir à l'utilisateur principal. Commencez par vérifier que les utilisateurs principaux et secondaires sont affiliés, puis appelez DevicePolicyManager.logoutUser(). Si l'utilisateur déconnecté est éphémère, le système l'arrête, puis le supprime.

Changer d'utilisateur

Pour passer à un autre utilisateur secondaire, l'administrateur d'un appareil entièrement géré peut appeler DevicePolicyManager.switchUser(). Pour plus de commodité, vous pouvez transmettre null pour passer à l'utilisateur principal.

Interrompre l'accès d'un utilisateur

Pour arrêter un utilisateur secondaire, un DPC propriétaire d'un appareil entièrement géré peut appeler DevicePolicyManager.stopUser(). Si l'utilisateur arrêté est éphémère, il est arrêté, puis supprimé.

Nous vous recommandons d'arrêter les utilisateurs dans la mesure du possible afin de rester en dessous du nombre maximal d'utilisateurs en cours d'exécution sur l'appareil.

Supprimer un espace utilisateur

Pour supprimer définitivement un utilisateur secondaire, un DPC peut appeler l'une des méthodes DevicePolicyManager suivantes:

  • Un administrateur d'un appareil entièrement géré peut appeler removeUser().
  • Un administrateur de l'utilisateur secondaire peut appeler wipeData().

Le système supprime les utilisateurs éphémères lorsqu'ils sont déconnectés, arrêtés ou déconnectés.

Désactiver l'interface utilisateur par défaut

Si votre DPC fournit une UI pour gérer les utilisateurs, vous pouvez désactiver l'interface multi-utilisateur intégrée d'Android. Pour ce faire, appelez DevicePolicyManager.setLogoutEnabled() et ajoutez la restriction DISALLOW_USER_SWITCH comme indiqué dans l'exemple suivant:

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

L'utilisateur de l'appareil ne peut pas ajouter d'utilisateurs secondaires avec l'interface utilisateur intégrée à Android, car les administrateurs d'appareils entièrement gérés ajoutent automatiquement la restriction utilisateur DISALLOW_ADD_USER.

Messages de session

Lorsque l'utilisateur d'un appareil passe à un nouvel utilisateur, Android affiche un panneau permettant de mettre le commutateur en surbrillance. Android affiche les messages suivants:

  • Message de démarrage de l'utilisateur-session affiché lorsque l'appareil passe à un utilisateur secondaire au lieu de l'utilisateur principal.
  • Message de session utilisateur final affiché lorsque l'appareil revient à l'utilisateur principal à partir d'un utilisateur secondaire.

Le système n'affiche pas ces messages lors du passage d'un compte utilisateur secondaire à un autre.

Étant donné que les messages peuvent ne pas convenir à toutes les situations, vous pouvez en modifier le texte. Par exemple, si votre solution utilise des sessions utilisateur éphémères, vous pouvez le refléter dans les messages suivants: Arrêt de la session de navigateur et suppression des données personnelles...

Le système affiche le message pendant quelques secondes seulement. Chaque message doit donc être une phrase courte et claire. Pour personnaliser les messages, votre administrateur peut appeler les méthodes DevicePolicyManager setStartUserSessionMessage() et setEndUserSessionMessage(), comme illustré dans l'exemple suivant:

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

Transmettez null pour supprimer vos messages personnalisés et revenir aux messages par défaut d'Android. Si vous devez vérifier le texte actuel du message, appelez getStartUserSessionMessage() ou getEndUserSessionMessage().

Votre DPC doit définir des messages localisés en fonction des paramètres régionaux actuels de l'utilisateur. Vous devez également mettre à jour les messages lorsque les paramètres régionaux de l'utilisateur changent:

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

Coordination DPC

La gestion des utilisateurs secondaires a généralement besoin de deux instances de votre DPC : l'une qui possède l'appareil entièrement géré, et l'autre, l'utilisateur secondaire. Lors de la création d'un utilisateur, l'administrateur de l'appareil entièrement géré définit une autre instance de lui-même comme administrateur du nouvel utilisateur.

Utilisateurs affiliés

Certaines API décrites dans ce guide du développeur ne fonctionnent que lorsque les utilisateurs secondaires sont affiliés. Étant donné qu'Android désactive certaines fonctionnalités (la journalisation réseau, par exemple) lorsque vous ajoutez de nouveaux utilisateurs secondaires non affiliés à l'appareil, vous devez les associer dès que possible. Consultez l'exemple dans la section Configuration ci-dessous.

Configurer

Configurez de nouveaux utilisateurs secondaires (à partir du DPC auquel appartient l'utilisateur secondaire) avant de les laisser les utiliser. Vous pouvez effectuer cette configuration à partir du rappel DeviceAdminReceiver.onEnabled(). Si vous avez déjà défini des extras d'administrateur dans l'appel de createAndManageUser(), vous pouvez obtenir les valeurs de l'argument intent. L'exemple suivant montre un DPC affilié à un nouvel utilisateur secondaire dans le rappel:

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

Même si les deux instances DPC s'exécutent sous des utilisateurs distincts, les DPC propriétaires de l'appareil et les utilisateurs secondaires peuvent communiquer entre eux. Étant donné que l'appel d'un autre service DPC dépasse les limites de l'utilisateur, votre DPC ne peut pas appeler bindService() comme vous le feriez normalement dans Android. Pour créer une liaison avec un service exécuté par un autre utilisateur, appelez DevicePolicyManager.bindDeviceAdminServiceAsUser().

Utilisateur principal et deux utilisateurs secondaires affiliés appelant des RPC.
Figure 2 : Administrateurs d'utilisateurs principaux et secondaires affiliés appelant des méthodes de service

Votre DPC ne peut s'associer qu'aux services exécutés chez les utilisateurs renvoyés par DevicePolicyManager.getBindDeviceAdminTargetUsers(). L'exemple suivant montre l'administrateur d'une liaison utilisateur secondaire à l'administrateur de l'appareil entièrement géré:

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

Ressources supplémentaires

Pour en savoir plus sur les appareils dédiés, consultez les documents suivants: