Gestire più utenti

Questa guida per gli sviluppatori spiega in che modo il controller dei criteri dei dispositivi (DPC) può gestire più utenti Android su dispositivi dedicati.

Panoramica

Il DPC può aiutare più persone a condividere un unico dispositivo dedicato. Il tuo DPC in esecuzione su un dispositivo completamente gestito può creare e gestire due tipi di utenti:

  • Gli utenti secondari sono utenti Android con app e dati separati salvati tra le sessioni. Puoi gestire l'utente con un componente di amministrazione. Questi utenti sono utili nei casi in cui un dispositivo viene preso in considerazione all'inizio di un turno, come i fattorini o gli addetti alla sicurezza.
  • Gli utenti temporanei sono utenti secondari che il sistema elimina quando l'utente si arresta, si allontana o il dispositivo si riavvia. Questi utenti sono utili nei casi in cui è possibile eliminare i dati al termine della sessione, ad esempio i kiosk Internet con accesso pubblico.

Il DPC esistente viene utilizzato per gestire il dispositivo dedicato e gli utenti secondari. Un componente di amministrazione nel DPC si imposta come amministratore per i nuovi utenti secondari quando li crei.

Utente principale e due utenti secondari.
Figura 1. Utenti principali e secondari gestiti da amministratori dallo stesso DPC

Gli amministratori di un utente secondario devono appartenere allo stesso pacchetto dell'amministratore del dispositivo completamente gestito. Per semplificare lo sviluppo, ti consigliamo di condividere un amministratore tra il dispositivo e gli utenti secondari.

La gestione di più utenti su dispositivi dedicati richiede in genere Android 9.0, tuttavia alcuni dei metodi utilizzati in questa guida per gli sviluppatori sono disponibili nelle versioni precedenti di Android.

Creare utenti

Il DPC può creare utenti aggiuntivi in background e poi spostarli in primo piano. La procedura è quasi la stessa per gli utenti secondari e temporanei. Implementa i seguenti passaggi per gli amministratori del dispositivo e dell'utente secondario completamente gestiti:

  1. Chiama il numero DevicePolicyManager.createAndManageUser(). Per creare un utente temporaneo, includi MAKE_USER_EPHEMERAL nell'argomento flags.
  2. Chiama DevicePolicyManager.startUserInBackground() per avviare l'utente in background. L'utente inizia a essere eseguito, ma dovrai completare la configurazione prima di portarlo in primo piano e mostrarlo alla persona che utilizza il dispositivo.
  3. Nell'amministratore dell'utente secondario, chiama DevicePolicyManager.setAffiliationIds() per affiliare il nuovo utente all'utente principale. Consulta la sezione Coordinamento DPC di seguito.
  4. Torna all'amministratore del dispositivo completamente gestito e chiama DevicePolicyManager.switchUser() per portare l'utente in primo piano.

Il seguente esempio mostra come aggiungere il passaggio 1 al tuo 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...
  }
}

Quando crei o avvii un nuovo utente, puoi controllare il motivo di eventuali errori rilevando l'eccezione UserOperationException e chiamando getUserOperationResult(). Il superamento dei limiti utente è uno dei motivi più comuni per l'errore:

La creazione di un utente può richiedere del tempo. Se crei spesso utenti, puoi migliorare l'esperienza utente preparando in background un utente pronto all'uso. Potrebbe essere necessario trovare un equilibrio tra i vantaggi di un utente pronto all'uso con il numero massimo di utenti consentiti su un dispositivo.

Identificazione

Dopo aver creato un nuovo utente, devi fare riferimento all'utente con un numero di serie permanente. Non salvare UserHandle in modo permanente perché il sistema li ricicla man mano che crei ed elimini gli utenti. Recupera il numero di serie chiamando 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  ...
}

Configurazione utente

A seconda delle esigenze dei tuoi utenti, puoi personalizzare la configurazione degli utenti secondari. Puoi includere i seguenti flag quando chiami createAndManageUser():

SKIP_SETUP_WIZARD
Salta l'esecuzione della configurazione guidata per i nuovi utenti, che controlla la presenza di aggiornamenti e installali, richiede all'utente di aggiungere un Account Google insieme ai servizi Google e imposta un blocco schermo. Questa operazione può richiedere del tempo e potrebbe non essere applicabile a tutti gli utenti, ad esempio i kiosk internet pubblici.
LEAVE_ALL_SYSTEM_APPS_ENABLED
Tutte le app di sistema rimangono attive nel nuovo utente. Se non imposti questo flag, il nuovo utente conterrà solo l'insieme minimo di app necessarie per il funzionamento del telefono, generalmente un browser di file, una tastiera telefonica, i contatti e gli SMS.

Segui il ciclo di vita dell'utente

Il tuo DPC (se è un amministratore del dispositivo completamente gestito) potrebbe trovare utile sapere quando cambiano gli utenti secondari. Per eseguire attività successive dopo le modifiche, sostituisci questi metodi di callback nella sottoclasse DeviceAdminReceiver del tuo DPC:

onUserStarted()
Richiamato dopo l'avvio del sistema di un utente. L'utente potrebbe ancora eseguire la configurazione o essere in esecuzione in background. Puoi recuperare l'utente dall'argomento startedUser.
onUserSwitched()
Richiamato dopo che il sistema è passato a un altro utente. Puoi ottenere il nuovo utente che è ora in esecuzione in primo piano dall'argomento switchedUser.
onUserStopped()
Richiamato dopo che il sistema arresta un utente perché quest'ultimo si è disconnesso, è passato a un nuovo utente (se l'utente è temporaneo) o il DPC ha arrestato l'utente. Puoi ottenere l'utente dall'argomento stoppedUser.
onUserAdded()
Richiamato quando il sistema aggiunge un nuovo utente. In genere, gli utenti secondari non sono completamente configurati quando il DPC riceve il callback. Puoi recuperare l'utente dall'argomento newUser.
onUserRemoved()
Richiamato dopo l'eliminazione di un utente da parte del sistema. Poiché l'utente è già stato eliminato, non puoi accedere all'utente rappresentato dall'argomento removedUser.

Per sapere quando il sistema porta un utente in primo piano o rimanda un utente in background, le app possono registrare un ricevitore per le trasmissioni ACTION_USER_FOREGROUND e ACTION_USER_BACKGROUND.

Scoprire gli utenti

Per accedere a tutti gli utenti secondari, l'amministratore di un dispositivo completamente gestito può chiamare DevicePolicyManager.getSecondaryUsers(). I risultati includono eventuali utenti secondari o temporanei creati dall'amministratore. I risultati includono anche eventuali utenti secondari (o un utente ospite) che una persona che utilizza il dispositivo potrebbe aver creato. I risultati non includono i profili di lavoro perché non sono utenti secondari. Il seguente esempio mostra come utilizzare questo metodo:

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

Ecco altri metodi che puoi chiamare per conoscere lo stato degli utenti secondari:

DevicePolicyManager.isEphemeralUser()
Chiama questo metodo dall'amministratore di un utente secondario per scoprire se si tratta di un utente temporaneo.
DevicePolicyManager.isAffiliatedUser()
Chiama questo metodo dall'amministratore di un utente secondario per scoprire se questo utente è affiliato all'utente principale. Per scoprire di più sull'affiliazione, consulta la sezione Coordinamento DPC di seguito.

Gestione utenti

Se vuoi gestire completamente il ciclo di vita degli utenti, puoi chiamare le API per avere un controllo granulare su quando e come il dispositivo cambia gli utenti. Ad esempio, puoi eliminare un utente quando il dispositivo non è stato utilizzato per un determinato periodo di tempo o puoi inviare ordini non inviati a un server prima che finisca il turno di una persona.

Esci

Android 9.0 ha aggiunto un pulsante di disconnessione alla schermata di blocco per consentire a una persona che utilizza il dispositivo di terminare la sessione. Dopo aver toccato il pulsante, il sistema interrompe l'utente secondario, lo elimina se è temporaneo e l'utente principale torna in primo piano. Android nasconde il pulsante quando l'utente principale è in primo piano perché l'utente principale non può uscire.

Android non mostra il pulsante Termina sessione per impostazione predefinita, ma l'amministratore (di un dispositivo completamente gestito) può attivarlo chiamando DevicePolicyManager.setLogoutEnabled(). Se devi confermare lo stato attuale del pulsante, chiama DevicePolicyManager.isLogoutEnabled().

L'amministratore di un utente secondario può disconnetterlo in modo programmatico e tornare all'utente principale. Verifica che gli utenti secondari e principali siano affiliati, quindi chiama DevicePolicyManager.logoutUser(). Se l'utente che non ha eseguito l'accesso è un utente temporaneo, il sistema si arresta ed elimina l'utente.

Cambia utente

Per passare a un altro utente secondario, l'amministratore di un dispositivo completamente gestito può chiamare DevicePolicyManager.switchUser(). Per praticità, puoi passare null per passare all'utente principale.

Interrompere un utente

Per interrompere un utente secondario, un DPC proprietario di un dispositivo completamente gestito può chiamare DevicePolicyManager.stopUser(). Se l'utente interrotto è un utente temporaneo, l'utente viene arrestato e poi eliminato.

Ti consigliamo di bloccare gli utenti quando possibile per rimanere al di sotto del numero massimo di utenti in esecuzione del dispositivo.

Eliminazione di un utente

Per eliminare definitivamente un utente secondario, un DPC può chiamare uno dei seguenti DevicePolicyManager metodi:

  • L'amministratore di un dispositivo completamente gestito può chiamare removeUser().
  • Un amministratore dell'utente secondario può chiamare wipeData().

Il sistema elimina gli utenti temporanei quando sono disconnessi, si sono arrestati o sono usciti dall'account.

Disattivare l'interfaccia utente predefinita

Se il tuo DPC fornisce un'interfaccia utente per la gestione degli utenti, puoi disattivare l'interfaccia multiutente integrata di Android. Per farlo, chiama DevicePolicyManager.setLogoutEnabled() e aggiungi la limitazione DISALLOW_USER_SWITCH, come mostrato nell'esempio seguente:

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 che utilizza il dispositivo non può aggiungere utenti secondari con l'interfaccia utente integrata di Android perché gli amministratori dei dispositivi completamente gestiti aggiungono automaticamente la limitazione utente di DISALLOW_ADD_USER.

Messaggi relativi alle sessioni

Quando la persona che utilizza un dispositivo passa a un nuovo utente, Android mostra un riquadro per evidenziare l'opzione. Android mostra i seguenti messaggi:

  • Messaggio sessione-utente-avvio visualizzato quando il dispositivo passa a un utente secondario dell'utente principale.
  • Messaggio sessione utente finale visualizzato quando il dispositivo torna all'utente principale da un utente secondario.

Il sistema non mostra i messaggi quando si passa da un utente secondario all'altro.

Poiché i messaggi potrebbero non essere adatti a tutte le situazioni, puoi modificare il loro testo. Ad esempio, se la tua soluzione utilizza sessioni utente temporanee, puoi indicarlo nei messaggi come Interruzione della sessione del browser ed eliminazione dei dati personali...

Il sistema mostra il messaggio solo per un paio di secondi, quindi ogni messaggio deve essere una frase breve e chiara. Per personalizzare i messaggi, l'amministratore può chiamare i metodi DevicePolicyManager setStartUserSessionMessage() e setEndUserSessionMessage() come mostrato nell'esempio seguente:

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

Passa null per eliminare i tuoi messaggi personalizzati e tornare ai messaggi predefiniti di Android. Se devi controllare il testo del messaggio corrente, chiama getStartUserSessionMessage() o getEndUserSessionMessage().

Il DPC deve impostare i messaggi localizzati per le impostazioni internazionali correnti dell'utente. Devi aggiornare i messaggi anche quando le impostazioni internazionali dell'utente cambiano:

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

Coordinamento DPC

La gestione degli utenti secondari in genere richiede due istanze del DPC: una proprietaria del dispositivo completamente gestito e l'altra proprietaria dell'utente secondario. Quando crei un nuovo utente, l'amministratore del dispositivo completamente gestito imposta un'altra istanza come amministratore del nuovo utente.

Utenti affiliati

Alcune API in questa guida per gli sviluppatori funzionano solo quando gli utenti secondari sono affiliati. Poiché Android disabilita alcune funzionalità (ad esempio il logging della rete) quando aggiungi al dispositivo nuovi utenti secondari non affiliati, dovresti affiliare gli utenti appena possibile. Vedi l'esempio in Configurazione di seguito.

Configurazione

Configura i nuovi utenti secondari (dal DPC proprietario dell'utente secondario) prima di consentirne l'utilizzo. Puoi eseguire questa configurazione dal callback DeviceAdminReceiver.onEnabled(). Se in precedenza hai impostato eventuali extra amministrativi nella chiamata a createAndManageUser(), puoi recuperare i valori dall'argomento intent. L'esempio seguente mostra un DPC che affilia un nuovo utente secondario nel 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 ...
}

RPC tra DPC

Anche se le due istanze DPC vengono eseguite con utenti separati, i DPC proprietari del dispositivo e gli utenti secondari possono comunicare tra loro. Poiché le chiamate a un servizio di un altro DPC superano i confini dell'utente, il tuo DPC non può chiamare bindService() come normalmente in Android. Per eseguire l'associazione a un servizio in esecuzione in un altro utente, chiama DevicePolicyManager.bindDeviceAdminServiceAsUser().

L&#39;utente principale e due utenti secondari affiliati che chiamano RPC.
Figura 2. Amministratori di utenti principali e secondari affiliati che chiamano metodi di servizio

Il tuo DPC può essere associato solo ai servizi in esecuzione negli utenti restituiti da DevicePolicyManager.getBindDeviceAdminTargetUsers(). L'esempio seguente mostra l'amministratore di un'associazione utente secondaria con l'amministratore del dispositivo completamente gestito:

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

Risorse aggiuntive

Per scoprire di più sui dispositivi dedicati, leggi i seguenti documenti: