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 singolo dispositivo dedicato. Un 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. Per gestire l'utente, devi usare 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 conducenti per le consegne 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 per i casi in cui i dati possono essere eliminati al termine della sessione, ad esempio per i kiosk internet con accesso pubblico.

Puoi utilizzare il DPC esistente per gestire il dispositivo dedicato e gli utenti secondari. Un componente amministratore nel DPC si autodefinisce come amministratore per i nuovi utenti secondari quando li crei.

Utente principale e due utenti secondari.
Figura 1. Utenti principali e secondari gestiti dagli 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.

Utenti secondari

Gli utenti secondari possono connettersi al Wi-Fi e configurare nuove reti. Tuttavia, non possono modificare o eliminare le reti, nemmeno quelle che hanno creato.

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 passaggi seguenti negli 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 impostare 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 verificare la causa di eventuali errori rilevando l'eccezione UserOperationException e chiamando getUserOperationResult(). Il superamento dei limiti utente è uno dei motivi di errore più comuni:

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. Potresti dover trovare il giusto equilibrio tra i vantaggi di un utente pronto all'uso e 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 mantenere UserHandle perché il sistema li ricicla quando crei ed elimini gli utenti. Per ottenere il numero di serie, chiama 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. Quando chiami createAndManageUser() puoi includere i seguenti flag:

SKIP_SETUP_WIZARD
Salta l'esecuzione della configurazione guidata per i nuovi utenti, che controlla e installa gli aggiornamenti, viene chiesto all'utente di aggiungere un Account Google oltre 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 del nuovo utente rimangono attivate. Se non imposti questo flag, il nuovo utente conterrà solo l'insieme minimo di app che il telefono dovrà utilizzare, in genere un browser di file, una tastiera del telefono, contatti e SMS.

Seguire il ciclo di vita dell'utente

Il 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 di un utente nel sistema. Questo utente potrebbe essere ancora in fase di configurazione o in esecuzione in background. Puoi recuperare l'utente dall'argomento startedUser.
onUserSwitched()
Chiamata dopo che il sistema è passato a un altro utente. Puoi recuperare il nuovo utente che ora è in esecuzione in primo piano dall'argomento switchedUser.
onUserStopped()
Richiamato dopo che il sistema ha arrestato 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()
Chiamato 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 che il sistema ha eliminato un utente. 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 lo reindirizza in background, le app possono registrare un ricevitore per le trasmissioni ACTION_USER_FOREGROUND e ACTION_USER_BACKGROUND.

Scoprire gli utenti

Per acquisire tutti gli utenti secondari, un 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. Nell'esempio seguente viene illustrato 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 modifica 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 in modo che chi utilizza il dispositivo possa 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 di fine sessione per impostazione predefinita, ma l'amministratore (di un dispositivo completamente gestito) può abilitarlo chiamando il numero DevicePolicyManager.setLogoutEnabled(). Se devi confermare lo stato attuale del pulsante, chiama il numero DevicePolicyManager.isLogoutEnabled().

L'amministratore di un utente secondario può disconnettere in modo programmatico l'utente e tornare all'utente principale. Verifica innanzitutto 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 e poi 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, 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:

  • Un 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, arrestati o disconnessi.

Disattiva l'interfaccia utente predefinita

Se il DPC fornisce un'interfaccia utente per la gestione degli utenti, puoi disattivare l'interfaccia multiutente integrata di Android. Puoi farlo chiamando DevicePolicyManager.setLogoutEnabled() e aggiungendo 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 di dispositivi completamente gestiti aggiungono automaticamente la limitazione utenti di DISALLOW_ADD_USER.

Messaggi sessione

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 di avvio della sessione utente visualizzato quando il dispositivo passa a un utente secondario dell'utente principale.
  • Messaggio relativo alla sessione utente finale mostrato 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 nel seguente esempio:

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 messaggi personalizzati e tornare a quelli predefiniti di Android. Se devi controllare il testo del messaggio corrente, chiama il numero 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 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 riportate in questa guida per gli sviluppatori funzionano solo quando gli utenti secondari sono affiliati. Poiché Android disabilita alcune funzionalità (ad esempio il logging di rete) quando aggiungi nuovi utenti secondari non affiliati al dispositivo, dovresti creare gli utenti affiliati il prima possibile. Vedi l'esempio in Configurazione di seguito.

Configura

Configura nuovi utenti secondari (dal DPC proprietario dell'utente secondario) prima di consentire agli utenti di utilizzarli. Puoi eseguire questa configurazione dal callback DeviceAdminReceiver.onEnabled(). Se in precedenza hai impostato eventuali extra amministrativi nella chiamata a createAndManageUser(), puoi ottenere 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 da utenti separati, i DPC proprietari del dispositivo e gli utenti secondari possono comunicare tra loro. Poiché le chiamate a un altro servizio DPC superano i confini degli utenti, il tuo DPC non può chiamare bindService() come normalmente in Android. Per eseguire l'associazione a un servizio in esecuzione su un altro utente, chiama DevicePolicyManager.bindDeviceAdminServiceAsUser().

Utente principale e due utenti secondari affiliati che chiamano le RPC.
Figura 2. Amministratori di utenti affiliati principali e secondari che chiamano i metodi di servizio

Il DPC può essere associato solo ai servizi in esecuzione negli utenti restituiti da DevicePolicyManager.getBindDeviceAdminTargetUsers(). L'esempio seguente mostra l'amministratore di un'associazione di utenti secondaria all'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 saperne di più sui dispositivi dedicati, leggi i seguenti documenti: