Руководство по специализированным устройствам

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

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

Эти рецепты полезны, если вы разрабатываете приложение, которое заменит главный экран Android и панель запуска.

Быть приложением Home

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

Все приложения Home обрабатывают категорию намерений CATEGORY_HOME. Именно так система распознает приложение Home. Чтобы сделать приложение Home по умолчанию, установите одно из его действий в качестве предпочтительного обработчика намерений Home, вызвав DevicePolicyManager.addPersistentPreferredActivity(), как показано в следующем примере:

Kotlin

// Create an intent filter to specify the Home category.
val filter = IntentFilter(Intent.ACTION_MAIN)
filter.addCategory(Intent.CATEGORY_HOME)
filter.addCategory(Intent.CATEGORY_DEFAULT)

// Set the activity as the preferred option for the device.
val activity = ComponentName(context, KioskModeActivity::class.java)
val dpm = context.getSystemService(Context.DEVICE_POLICY_SERVICE)
        as DevicePolicyManager
dpm.addPersistentPreferredActivity(adminName, filter, activity)

Java

// Create an intent filter to specify the Home category.
IntentFilter filter = new IntentFilter(Intent.ACTION_MAIN);
filter.addCategory(Intent.CATEGORY_HOME);
filter.addCategory(Intent.CATEGORY_DEFAULT);

// Set the activity as the preferred option for the device.
ComponentName activity = new ComponentName(context, KioskModeActivity.class);
DevicePolicyManager dpm =
    (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
dpm.addPersistentPreferredActivity(adminName, filter, activity);

Вам по-прежнему нужно объявить фильтр интентов в файле манифеста приложения, как показано в следующем фрагменте XML:

<activity
        android:name=".KioskModeActivity"
        android:label="@string/kiosk_mode"
        android:launchMode="singleInstance"
        android:excludeFromRecents="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.HOME"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>

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

Показать отдельные задачи

FLAG_ACTIVITY_NEW_TASK может быть полезным флагом для приложений-лаунчеров, поскольку каждая новая задача отображается как отдельный элемент на экране "Обзор". Подробнее об экране "Обзор"…

Общедоступные киоски

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

Блокировка устройства

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

Таблица 1. Ограничения для пользователей устройств в режиме киоска
Ограничение для пользователей Описание
DISALLOW_FACTORY_RESET Запрещает пользователю устройства сбрасывать его настройки до заводских. Это ограничение могут задавать администраторы полностью управляемых устройств и основной пользователь.
DISALLOW_SAFE_BOOT Запрещает пользователю запускать устройство в безопасном режиме, в котором система не запускает приложение автоматически. Это ограничение могут задать администраторы полностью управляемых устройств и основной пользователь.
DISALLOW_MOUNT_PHYSICAL_MEDIA Запрещает пользователю устройства подключать любые тома хранилища, которые он может подключить к устройству. Это ограничение могут задать администраторы полностью управляемых устройств и основной пользователь.
DISALLOW_ADJUST_VOLUME Отключает звук на устройстве и запрещает пользователю менять настройки громкости и вибрации. Убедитесь, что в киоске не требуется воспроизведение аудио или специальные возможности. Это ограничение могут задавать администраторы полностью управляемых устройств, основные и дополнительные пользователи, а также администраторы рабочих профилей.
DISALLOW_ADD_USER Запрещает пользователю устройства добавлять новых пользователей, например дополнительных или с ограниченным доступом. Система автоматически добавляет это ограничение для полностью управляемых устройств, но оно может быть снято. Это ограничение могут задать администраторы полностью управляемых устройств и основной пользователь.

В следующем фрагменте кода показано, как задать ограничения:

Kotlin

// If the system is running in lock task mode, set the user restrictions
// for a kiosk after launching the activity.
arrayOf(
        UserManager.DISALLOW_FACTORY_RESET,
        UserManager.DISALLOW_SAFE_BOOT,
        UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA,
        UserManager.DISALLOW_ADJUST_VOLUME,
        UserManager.DISALLOW_ADD_USER).forEach { dpm.addUserRestriction(adminName, it) }

Java

// If the system is running in lock task mode, set the user restrictions
// for a kiosk after launching the activity.
String[] restrictions = {
    UserManager.DISALLOW_FACTORY_RESET,
    UserManager.DISALLOW_SAFE_BOOT,
    UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA,
    UserManager.DISALLOW_ADJUST_VOLUME,
    UserManager.DISALLOW_ADD_USER};

for (String restriction: restrictions) dpm.addUserRestriction(adminName, restriction);

Вы можете снять эти ограничения, когда приложение находится в режиме администратора, чтобы ИТ-администратор мог использовать эти функции для обслуживания устройства. Чтобы снять ограничение, позвоните по номеру DevicePolicyManager.clearUserRestriction().

Блокировать диалоговые окна с ошибками

В некоторых случаях, например при демонстрации в магазине или на информационном экране, диалоговые окна с ошибками не должны показываться пользователям. В Android 9.0 (уровень API 28) и более поздних версиях можно отключить системные диалоговые окна об ошибках в приложениях, которые не отвечают или работают со сбоями. Для этого добавьте ограничение для пользователей DISALLOW_SYSTEM_ERROR_DIALOGS. Система перезапускает неотвечающие приложения, как если бы пользователь закрыл их в диалоговом окне. Ниже приведен пример.

Kotlin

override fun onEnabled(context: Context, intent: Intent) {
    val dpm = getManager(context)
    val adminName = getWho(context)

    dpm.addUserRestriction(adminName, UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS)
}

Java

public void onEnabled(Context context, Intent intent) {
  DevicePolicyManager dpm = getManager(context);
  ComponentName adminName = getWho(context);

  dpm.addUserRestriction(adminName, UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS);
}

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

Не выключать экран

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

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    // Keep the screen on and bright while this kiosk activity is running.
    window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  // Keep the screen on and bright while this kiosk activity is running.
  getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}

Убедитесь, что устройство подключено к зарядному устройству (беспроводному, USB или переменного тока). Зарегистрируйтесь для получения уведомлений об изменении заряда батареи и используйте значения BatteryManager, чтобы узнать состояние зарядки. Вы даже можете отправлять удаленные оповещения ИТ-администратору, если устройство отключено. Пошаговые инструкции вы найдете в статье Как отслеживать уровень заряда батареи и состояние зарядки.

Вы также можете задать глобальную настройку STAY_ON_WHILE_PLUGGED_IN, чтобы устройство не переходило в спящий режим, когда оно подключено к источнику питания. Администраторы полностью управляемых устройств с Android 6.0 (уровень API 23) или более поздней версии могут вызывать DevicePolicyManager.setGlobalSetting(), как показано в примере ниже.

Kotlin

val pluggedInto = BatteryManager.BATTERY_PLUGGED_AC or
        BatteryManager.BATTERY_PLUGGED_USB or
        BatteryManager.BATTERY_PLUGGED_WIRELESS
dpm.setGlobalSetting(adminName,
        Settings.Global.STAY_ON_WHILE_PLUGGED_IN, pluggedInto.toString())

Java

int pluggedInto = BatteryManager.BATTERY_PLUGGED_AC |
    BatteryManager.BATTERY_PLUGGED_USB |
    BatteryManager.BATTERY_PLUGGED_WIRELESS;
dpm.setGlobalSetting( adminName,
    Settings.Global.STAY_ON_WHILE_PLUGGED_IN, String.valueOf(pluggedInto));

Пакеты приложений

В этом разделе приведены рецепты для эффективной установки приложений на специализированные устройства.

Кешировать пакеты приложений

Если пользователи общего устройства используют один и тот же набор приложений, имеет смысл по возможности избегать скачивания приложений. Чтобы упростить инициализацию пользователей на общих устройствах с фиксированным набором пользователей, например на устройствах для посменных работников, в Android 9.0 (уровень API 28) или более поздней версии можно кешировать пакеты приложений (APK), необходимые для добавления пользователей.

Установка кешированного APK-файла (который уже установлен на устройстве) происходит в два этапа:

  1. Административный компонент полностью управляемого устройства (или делегат – см. ниже) задает список APK, которые нужно сохранить на устройстве.
  2. Компоненты администратора аффилированных дополнительных пользователей (или их представителей) могут устанавливать кешированный APK от имени пользователя. Администраторы полностью управляемого устройства, основной пользователь или аффилированный рабочий профиль (или их представители) также могут при необходимости установить кешированное приложение.

Чтобы задать список APK-файлов, которые нужно сохранить на устройстве, администратор вызывает DevicePolicyManager.setKeepUninstalledPackages(). Этот метод не проверяет, установлен ли APK на устройстве. Он полезен, если вы хотите установить приложение непосредственно перед тем, как оно понадобится пользователю. Чтобы получить список ранее заданных пакетов, вызовите DevicePolicyManager.getKeepUninstalledPackages(). После вызова setKeepUninstalledPackages() с изменениями или удаления дополнительного пользователя система удаляет все кешированные APK-файлы, которые больше не нужны.

Чтобы установить кешированный APK-файл, вызовите DevicePolicyManager.installExistingPackage(). Этот метод может установить только приложение, которое система уже кэшировала. Ваше решение для специализированных устройств (или пользователь устройства) должно сначала установить приложение на устройство, прежде чем вы сможете вызвать этот метод.

В примере ниже показано, как использовать эти вызовы API в администраторе полностью управляемого устройства и дополнительного пользователя.

Kotlin

// Set the package to keep. This method assumes that the package is already
// installed on the device by managed Google Play.
val cachedAppPackageName = "com.example.android.myapp"
dpm.setKeepUninstalledPackages(adminName, listOf(cachedAppPackageName))

// ...

// The admin of a secondary user installs the app.
val success = dpm.installExistingPackage(adminName, cachedAppPackageName)

Java

// Set the package to keep. This method assumes that the package is already
// installed on the device by managed Google Play.
String cachedAppPackageName = "com.example.android.myapp";
List<String> packages = new ArrayList<String>();
packages.add(cachedAppPackageName);
dpm.setKeepUninstalledPackages(adminName, packages);

// ...

// The admin of a secondary user installs the app.
boolean success = dpm.installExistingPackage(adminName, cachedAppPackageName);

Делегирование приложений

Вы можете делегировать управление кешированием другому приложению. Это может быть полезно, чтобы разделить функции вашего решения или дать администраторам возможность использовать собственные приложения. Приложение-делегат получает те же разрешения, что и компонент администратора. Например, делегат приложения администратора дополнительного пользователя может вызвать installExistingPackage(), но не может вызвать setKeepUninstalledPackages().

Чтобы выполнить вызов делегата, используйте DevicePolicyManager.setDelegatedScopes() и включите DELEGATION_KEEP_UNINSTALLED_PACKAGES в аргумент scopes. В примере ниже показано, как назначить делегатом другое приложение.

Kotlin

var delegatePackageName = "com.example.tools.kept_app_assist"

// Check that the package is installed before delegating.
try {
    context.packageManager.getPackageInfo(delegatePackageName, 0)
    dpm.setDelegatedScopes(
            adminName,
            delegatePackageName,
            listOf(DevicePolicyManager.DELEGATION_KEEP_UNINSTALLED_PACKAGES))
} catch (e: PackageManager.NameNotFoundException) {
    // The delegate app isn't installed. Send a report to the IT admin ...
}

Java

String delegatePackageName = "com.example.tools.kept_app_assist";

// Check that the package is installed before delegating.
try {
  context.getPackageManager().getPackageInfo(delegatePackageName, 0);
  dpm.setDelegatedScopes(
      adminName,
      delegatePackageName,
      Arrays.asList(DevicePolicyManager.DELEGATION_KEEP_UNINSTALLED_PACKAGES));
} catch (PackageManager.NameNotFoundException e) {
  // The delegate app isn't installed. Send a report to the IT admin ...
}

Если все пройдет успешно, приложение-делегат получит трансляцию ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED и станет делегатом. Приложение может вызывать методы, описанные в этом руководстве, как если бы оно было владельцем устройства или профиля. При вызове методов DevicePolicyManager представитель передает null в качестве аргумента компонента администратора.

Установка пакетов приложений

Иногда бывает полезно установить на специализированное устройство кешированное локально пользовательское приложение. Например, специализированные устройства часто развертываются в средах с ограниченной пропускной способностью или в областях без подключения к интернету. Решение для специализированных устройств должно учитывать пропускную способность сети клиентов. Ваше приложение может начать установку другого пакета приложения (APK) с помощью классов PackageInstaller.

Любое приложение может устанавливать APK-файлы, но администраторы полностью управляемых устройств могут устанавливать и удалять пакеты без участия пользователя. Администратор может управлять устройством, аффилированным дополнительным пользователем или аффилированным рабочим профилем. После завершения установки система отправляет уведомление, которое видят все пользователи устройства. Уведомление сообщает пользователям устройств, что приложение было установлено (или обновлено) администратором.

Таблица 2. Версии Android, поддерживающие установку пакетов без участия пользователя
Версию Android. Компонент администратора для установки и удаления
Android 9.0 (уровень API 28) или более поздняя версия аффилированные дополнительные пользователи и рабочие профили – и те, и другие на полностью управляемых устройствах;
Android 6.0 (уровень API 23) или более поздней версии; Полностью управляемые устройства

Способ распространения одной или нескольких копий APK на специализированные устройства зависит от того, насколько удалены устройства и, возможно, от того, насколько далеко они находятся друг от друга. Перед установкой APK-файлов на специализированные устройства ваше решение должно соответствовать рекомендациям по безопасности.

С помощью PackageInstaller.Session можно создать сеанс, который ставит в очередь на установку один или несколько APK-файлов. В примере ниже мы получаем отзыв о статусе в нашем действии (режим singleTop), но вы можете использовать сервис или широковещательный приемник:

Kotlin

// First, create a package installer session.
val packageInstaller = context.packageManager.packageInstaller
val params = PackageInstaller.SessionParams(
        PackageInstaller.SessionParams.MODE_FULL_INSTALL)
val sessionId = packageInstaller.createSession(params)
val session = packageInstaller.openSession(sessionId)

// Add the APK binary to the session. The APK is included in our app binary
// and is read from res/raw but file storage is a more typical location.
// The I/O streams can't be open when installation begins.
session.openWrite("apk", 0, -1).use { output ->
    getContext().resources.openRawResource(R.raw.app).use { input ->
        input.copyTo(output, 2048)
    }
}

// Create a status receiver to report progress of the installation.
// We'll use the current activity.
// Here we're requesting status feedback to our Activity but this can be a
// service or broadcast receiver.
val intent = Intent(context, activity.javaClass)
intent.action = "com.android.example.APK_INSTALLATION_ACTION"
val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
val statusReceiver = pendingIntent.intentSender

// Start the installation. Because we're an admin of a fully managed device,
// there isn't any user interaction.
session.commit(statusReceiver)

Java

// First, create a package installer session.
PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
    PackageInstaller.SessionParams.MODE_FULL_INSTALL);
int sessionId = packageInstaller.createSession(params);
PackageInstaller.Session session = packageInstaller.openSession(sessionId);

// Add the APK binary to the session. The APK is included in our app binary
// and is read from res/raw but file storage is a more typical location.
try (
    // These I/O streams can't be open when installation begins.
    OutputStream output = session.openWrite("apk", 0, -1);
    InputStream input = getContext().getResources().openRawResource(R.raw.app);
) {
  byte[] buffer = new byte[2048];
  int n;
  while ((n = input.read(buffer)) >= 0) {
    output.write(buffer, 0, n);
  }
}

// Create a status receiver to report progress of the installation.
// We'll use the current activity.
// Here we're requesting status feedback to our Activity but this can be a
// service or broadcast receiver.
Intent intent = new Intent(context, getActivity().getClass());
intent.setAction("com.android.example.APK_INSTALLATION_ACTION");
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
IntentSender statusReceiver = pendingIntent.getIntentSender();

// Start the installation. Because we're an admin of a fully managed device,
// there isn't any user interaction.
session.commit(statusReceiver);

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

Чтобы удалить приложения, вызовите PackageInstaller.uninstall. Администраторы полностью управляемых устройств, пользователей и рабочих профилей могут удалять пакеты без участия пользователя на поддерживаемых версиях Android (см. таблицу 2).

Заморозить обновления системы

Устройства Android получают беспроводные обновления системы и приложений. Чтобы зафиксировать версию ОС на критические периоды, например праздники или другие периоды высокой загрузки, на специализированных устройствах можно приостановить беспроводные обновления системы на срок до 90 дней. Подробнее о том, как управлять обновлениями системы…

Remote Config

Управляемые конфигурации Android позволяют системным администраторам удаленно настраивать ваше приложение. Чтобы сделать его более полезным для администраторов, вы можете предоставить им доступ к таким настройкам, как белые списки, сетевые хосты или URL контента.

Если ваше приложение предоставляет доступ к своей конфигурации, не забудьте включить настройки в документацию. Подробнее о том, как настроить управляемые конфигурации…

Настройка при разработке

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

  1. Создайте и установите на устройство приложение – контроллер правил для устройств (DPC).
  2. Убедитесь, что на устройстве нет аккаунтов.
  3. Выполните следующую команду в оболочке Android Debug Bridge (adb). Вам нужно заменить com.example.dpc/.MyDeviceAdminReceiver в примере на название компонента администратора вашего приложения:

    adb shell dpm set-device-owner com.example.dpc/.MyDeviceAdminReceiver

Чтобы помочь клиентам развернуть ваше решение, вам нужно будет изучить другие способы регистрации. Для специализированных устройств мы рекомендуем регистрацию с помощью QR-кода.

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

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