Книга рецептов для специализированных устройств

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

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

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

Будьте домашним приложением

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

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

Котлин

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

Ява

// 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 Launcher скрывает первоначально запущенное действие, когда система работает в режиме блокировки задач.

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

FLAG_ACTIVITY_NEW_TASK может быть полезным флагом для приложений типа средства запуска, поскольку каждая новая задача отображается как отдельный элемент на экране «Обзор». Чтобы узнать больше о задачах на экране «Обзор», прочтите «Экран последних задач» .

Общественные киоски

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

Заблокируйте устройство

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

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

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

Котлин

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

Ява

// 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 . Система перезапускает не отвечающие приложения, как если бы пользователь устройства закрыл приложение из диалогового окна. В следующем примере показано, как это можно сделать:

Котлин

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

    dpm.addUserRestriction(adminName, UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS)
}

Ява

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 в окно вашего приложения, как показано в следующем примере:

Котлин

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

Ява

@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() как показано в следующем примере:

Котлин

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

Ява

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 в администраторе полностью управляемого устройства и вторичного пользователя:

Котлин

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

Ява

// 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 в аргумент областей. В следующем примере показано, как сделать делегатом другое приложение:

Котлин

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

Ява

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 ), но вы можете использовать сервис или широковещательный приемник:

Котлин

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

Ява

// 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 получают обновления системного и прикладного программного обеспечения по беспроводной сети (OTA). Чтобы заморозить версию ОС в критические периоды, например, в праздники или в другое загруженное время, выделенные устройства могут приостанавливать обновления системы OTA на срок до 90 дней. Дополнительные сведения см. в разделе Управление обновлениями системы .

Удаленная настройка

Управляемые конфигурации 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-кода для выделенных устройств.

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

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