Управление несколькими пользователями

В этом руководстве разработчика объясняется, как ваш контроллер политики устройств (DPC) может управлять несколькими пользователями Android на выделенных устройствах .

Обзор

Ваш ЦОД может помочь нескольким людям использовать одно выделенное устройство. Ваш ЦОД, работающий на полностью управляемом устройстве, может создавать и управлять двумя типами пользователей:

  • Вторичные пользователи — это пользователи Android с отдельными приложениями и данными, сохраняемыми между сеансами. Вы управляете пользователем с помощью компонента администратора. Эти пользователи полезны в тех случаях, когда устройство забирают в начале смены, например, водители-курьеры или сотрудники службы безопасности.
  • Эфемерные пользователи — это второстепенные пользователи, которых система удаляет, когда пользователь останавливается, отключается или перезагружается устройство. Эти пользователи полезны в случаях, когда данные могут быть удалены после завершения сеанса, например, в интернет-киосках общего доступа.

Вы используете существующий ЦОД для управления выделенным устройством и дополнительными пользователями. Компонент администратора в вашем ЦОД назначает себя администратором для новых дополнительных пользователей при их создании.

Основной пользователь и два дополнительных пользователя.
Рисунок 1. Основные и дополнительные пользователи, управляемые администраторами из одного ЦОД.

Администраторы дополнительного пользователя должны принадлежать к тому же пакету, что и администратор полностью управляемого устройства. Чтобы упростить разработку, мы рекомендуем разделить администратора между устройством и дополнительными пользователями.

Для управления несколькими пользователями на выделенных устройствах обычно требуется Android 9.0, однако некоторые методы, используемые в этом руководстве разработчика, доступны и в более ранних версиях Android.

Вторичные пользователи

Вторичные пользователи могут подключаться к Wi-Fi и настраивать новые сети. Однако они не могут редактировать или удалять сети, даже те, которые они создали.

Создание пользователей

Ваш ЦОД может создавать дополнительных пользователей в фоновом режиме, а затем переключать их на передний план. Процесс практически одинаков как для второстепенных, так и для эфемерных пользователей. Выполните следующие шаги в администраторах полностью управляемого устройства и вторичного пользователя:

  1. Вызовите DevicePolicyManager.createAndManageUser() . Чтобы создать временного пользователя, включите MAKE_USER_EPHEMERAL в аргумент flags.
  2. Вызовите DevicePolicyManager.startUserInBackground() чтобы запустить пользователя в фоновом режиме. Пользователь начинает работать, но вам необходимо завершить настройку, прежде чем вывести пользователя на передний план и показать его пользователю, использующему устройство.
  3. В администраторе дополнительного пользователя вызовите DevicePolicyManager.setAffiliationIds() чтобы связать нового пользователя с основным пользователем. См. координацию ЦОД ниже.
  4. Вернувшись в административную панель полностью управляемого устройства, вызовите DevicePolicyManager.switchUser() , чтобы переключить пользователя на передний план.

В следующем примере показано, как можно добавить шаг 1 в свой ЦОД:

Котлин

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

Ява

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

Когда вы создаете или запускаете нового пользователя, вы можете проверить причину любых сбоев, перехватив исключение UserOperationException и вызвав getUserOperationResult() . Превышение пользовательских лимитов является распространенной причиной сбоя:

Создание пользователя может занять некоторое время. Если вы часто создаете пользователей, вы можете улучшить взаимодействие с ними, подготовив готового пользователя в фоновом режиме. Возможно, вам придется сбалансировать преимущества готового пользователя с максимальным количеством пользователей, разрешенных на устройстве.

Идентификация

После создания нового пользователя вам следует обратиться к пользователю с постоянным серийным номером. Не сохраняйте UserHandle , поскольку система перерабатывает их при создании и удалении пользователей. Получите серийный номер, вызвав UserManager.getSerialNumberForUser() :

Котлин

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

Ява

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

Конфигурация пользователя

В зависимости от потребностей ваших пользователей вы можете настроить настройку дополнительных пользователей. Вы можете включить следующие флаги при вызове createAndManageUser() :

SKIP_SETUP_WIZARD
Пропускает запуск мастера настройки нового пользователя, который проверяет и устанавливает обновления, предлагает пользователю добавить учетную запись Google вместе со службами Google и устанавливает блокировку экрана. Это может занять некоторое время и может быть неприменимо для всех пользователей — например, для общедоступных интернет-киосков.
LEAVE_ALL_SYSTEM_APPS_ENABLED
Оставляет все системные приложения включенными для нового пользователя. Если вы не установите этот флаг, новый пользователь будет содержать только минимальный набор приложений, необходимых телефону для работы — обычно файловый браузер, телефонный номеронабиратель, контакты и SMS-сообщения.

Следите за жизненным циклом пользователя

Вашему ЦОДу (если он является администратором полностью управляемого устройства) может быть полезно знать, когда меняются дополнительные пользователи. Чтобы запускать последующие задачи после изменений, переопределите эти методы обратного вызова в подклассе DeviceAdminReceiver вашего ЦОД:

onUserStarted()
Вызывается после того, как система запускает пользователя. Возможно, этот пользователь все еще выполняет настройку или работает в фоновом режиме. Вы можете получить пользователя из аргумента startedUser .
onUserSwitched()
Вызывается после переключения системы на другого пользователя. Вы можете получить нового пользователя, который сейчас работает на переднем плане, из switchedUser .
onUserStopped()
Вызывается после того, как система останавливает пользователя, поскольку он вышел из системы, переключился на нового пользователя (если пользователь является временным) или ваш ЦОД остановил пользователя. Вы можете получить пользователя из аргумента stoppedUser .
onUserAdded()
Вызывается, когда система добавляет нового пользователя. Обычно дополнительные пользователи не полностью настроены, когда ваш ЦОД получает обратный вызов. Вы можете получить пользователя из аргумента newUser .
onUserRemoved()
Вызывается после того, как система удаляет пользователя. Поскольку пользователь уже удален, вы не можете получить доступ к пользователю, представленному аргументом removedUser .

Чтобы знать, когда система переводит пользователя на передний план или отправляет его на задний план, приложения могут зарегистрировать приемник для широковещательных рассылок ACTION_USER_FOREGROUND и ACTION_USER_BACKGROUND .

Откройте для себя пользователей

Чтобы получить всех дополнительных пользователей, администратор полностью управляемого устройства может вызвать DevicePolicyManager.getSecondaryUsers() . Результаты включают в себя всех второстепенных или временных пользователей, созданных администратором. Результаты также включают любых дополнительных пользователей (или гостевых пользователей), которых мог создать пользователь, использующий устройство. Результаты не включают рабочие профили, поскольку они не являются дополнительными пользователями. В следующем примере показано, как можно использовать этот метод:

Котлин

// The device is stored for the night. Stop all running secondary users.
dpm.getSecondaryUsers(adminName).forEach {
    dpm.stopUser(adminName, it)
}

Ява

// The device is stored for the night. Stop all running secondary users.
for (UserHandle user : dpm.getSecondaryUsers(adminName)) {
  dpm.stopUser(adminName, user);
}

Вот другие методы, которые вы можете вызвать, чтобы узнать статус дополнительных пользователей:

DevicePolicyManager.isEphemeralUser()
Вызовите этот метод у администратора дополнительного пользователя, чтобы узнать, является ли это временным пользователем.
DevicePolicyManager.isAffiliatedUser()
Вызовите этот метод у администратора дополнительного пользователя, чтобы узнать, связан ли этот пользователь с основным пользователем. Чтобы узнать больше о присоединении, см. раздел «Координация DPC» ниже.

Управление пользователями

Если вы хотите полностью управлять жизненным циклом пользователя, вы можете вызывать API для детального контроля того, когда и как устройство меняет пользователей. Например, вы можете удалить пользователя, если устройство не использовалось в течение определенного периода времени, или отправить все неотправленные заказы на сервер до окончания смены человека.

Выход из системы

В Android 9.0 на экран блокировки добавлена ​​кнопка выхода из системы, чтобы человек, использующий устройство, мог завершить сеанс. После нажатия кнопки система останавливает вторичного пользователя, удаляет пользователя, если он эфемерный, и основной пользователь возвращается на передний план. Android скрывает кнопку, когда основной пользователь находится на переднем плане, поскольку основной пользователь не может выйти из системы.

Android не отображает кнопку завершения сеанса по умолчанию, но ваш администратор (полностью управляемого устройства) может включить ее, вызвав DevicePolicyManager.setLogoutEnabled() . Если вам нужно подтвердить текущее состояние кнопки, вызовите DevicePolicyManager.isLogoutEnabled() .

Администратор дополнительного пользователя может программно выйти из системы и вернуться к основному пользователю. Сначала подтвердите, что дополнительный и основной пользователи связаны, затем вызовите DevicePolicyManager.logoutUser() . Если вышедший из системы пользователь является временным пользователем, система останавливается, а затем удаляет пользователя.

Сменить пользователя

Чтобы переключиться на другого вторичного пользователя, администратор полностью управляемого устройства может вызвать DevicePolicyManager.switchUser() . Для удобства вы можете передать null , чтобы переключиться на основного пользователя.

Остановить пользователя

Чтобы остановить вторичного пользователя, ЦОД, владеющий полностью управляемым устройством, может вызвать DevicePolicyManager.stopUser() . Если остановленный пользователь является временным пользователем, он останавливается, а затем удаляется.

Мы рекомендуем останавливать пользователей, когда это возможно, чтобы не превышать максимальное количество работающих пользователей на устройстве.

Удаление пользователя

Чтобы окончательно удалить вторичного пользователя, ЦОД может вызвать один из следующих методов DevicePolicyManager :

  • Администратор полностью управляемого устройства может вызвать removeUser() .
  • Администратор вторичного пользователя может вызвать wipeData() .

Система удаляет временных пользователей, когда они выходят из системы, останавливаются или отключаются от них.

Отключить пользовательский интерфейс по умолчанию

Если ваш ЦОД предоставляет пользовательский интерфейс для управления пользователями, вы можете отключить встроенный многопользовательский интерфейс Android. Это можно сделать, вызвав DevicePolicyManager.setLogoutEnabled() и добавив ограничение DISALLOW_USER_SWITCH , как показано в следующем примере:

Котлин

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

Ява

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

Пользователь, использующий устройство, не может добавлять дополнительных пользователей с помощью встроенного пользовательского интерфейса Android, поскольку администраторы полностью управляемых устройств автоматически добавляют пользовательское ограничение DISALLOW_ADD_USER .

Сообщения сеанса

Когда человек, использующий устройство, переключается на нового пользователя, Android отображает панель, чтобы выделить этот переключатель. Android показывает следующие сообщения:

  • Сообщение о запуске сеанса пользователя, отображаемое, когда устройство переключается на вторичного пользователя с основного пользователя.
  • Сообщение о сеансе конечного пользователя, отображаемое, когда устройство возвращается к основному пользователю от вторичного пользователя.

Система не показывает сообщения при переключении между двумя дополнительными пользователями.

Поскольку сообщения могут подходить не для всех ситуаций, вы можете изменить текст этих сообщений. Например, если ваше решение использует временные пользовательские сеансы, вы можете отразить это в таких сообщениях, как: Остановка сеанса браузера и удаление личных данных…

Система показывает сообщение всего пару секунд, поэтому каждое сообщение должно представлять собой короткую четкую фразу. Чтобы настроить сообщения, ваш администратор может вызвать методы DevicePolicyManager setStartUserSessionMessage() и setEndUserSessionMessage() как показано в следующем примере:

Котлин

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

Ява

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

Передайте null , чтобы удалить пользовательские сообщения и вернуться к сообщениям Android по умолчанию. Если вам нужно проверить текст текущего сообщения, вызовите getStartUserSessionMessage() или getEndUserSessionMessage() .

Ваш ЦОД должен установить локализованные сообщения для текущего языкового стандарта пользователя. Вам также необходимо обновлять сообщения при изменении языкового стандарта пользователя:

Котлин

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

Ява

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

Координация ЦОД

Для управления дополнительными пользователями обычно требуется два экземпляра вашего ЦОД: один владеет полностью управляемым устройством, а другой — дополнительным пользователем. При создании нового пользователя администратор полностью управляемого устройства назначает другой экземпляр себя администратором нового пользователя.

Аффилированные пользователи

Некоторые API-интерфейсы в этом руководстве для разработчиков работают только в том случае, если второстепенные пользователи являются аффилированными лицами . Поскольку Android отключает некоторые функции (например, ведение журнала сети), когда вы добавляете на устройство новых неаффилированных вторичных пользователей, вам следует присоединить пользователей как можно скорее. См. пример в разделе «Настройка» ниже.

Настраивать

Настройте новых дополнительных пользователей (из ЦОД, которому принадлежит дополнительный пользователь), прежде чем разрешить другим пользователям их использовать. Эту настройку можно выполнить с помощью обратного вызова DeviceAdminReceiver.onEnabled() . Если вы ранее установили какие-либо дополнительные возможности администратора при вызове createAndManageUser() , вы можете получить значения из аргумента intent . В следующем примере показан DPC, присоединяющий нового вторичного пользователя в обратном вызове:

Котлин

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

Ява

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 между ЦОДами

Несмотря на то, что два экземпляра ЦОД работают под разными пользователями, ЦОД, которым принадлежит устройство, и дополнительные пользователи могут взаимодействовать друг с другом. Поскольку вызов службы другого центра обработки данных пересекает границы пользователя, ваш центр обработки данных не может вызывать bindService() как это обычно делается в Android . Чтобы привязаться к службе, работающей у другого пользователя, вызовите DevicePolicyManager.bindDeviceAdminServiceAsUser() .

Основной пользователь и два аффилированных вторичных пользователя, вызывающие RPC.
Рисунок 2. Администраторы аффилированных основных и дополнительных пользователей, вызывающие методы обслуживания

Ваш ЦОД может привязываться только к службам, работающим у пользователей, возвращаемых DevicePolicyManager.getBindDeviceAdminTargetUsers() . В следующем примере показана привязка администратора дополнительного пользователя к администратору полностью управляемого устройства:

Котлин

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

Ява

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

Дополнительные ресурсы

Чтобы узнать больше о выделенных устройствах, прочитайте следующие документы: