Рабочие профили

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

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

Обзор

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

Чтобы решить эту проблему, в Android 5.0 (уровень API 21) была добавлена возможность создавать рабочие профили. Если на устройстве есть рабочий профиль, его настройки контролирует системный администратор. Администратор может выбрать, какие приложения и функции устройства будут доступны в этом профиле.

Если на устройстве есть рабочий профиль, это влияет на работу приложений, независимо от того, в каком профиле они запущены:

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

Предотвращение неудачных намерений

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

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

Прежде чем запускать действие, убедитесь, что оно будет выполнено корректно. Чтобы убедиться, что разрешение приемлемо, вызовите Intent.resolveActivity(). Если намерение не удается обработать, метод возвращает значение null. Если метод возвращает значение, отличное от null, то существует по крайней мере один способ обработать намерение и его можно безопасно запустить. В этом случае намерение может быть разрешено, поскольку в текущем профиле есть обработчик или намерение может быть передано обработчику в другом профиле. Подробнее о распространенных намерениях…

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

Kotlin

fun startTimer(message: String, seconds: Int) {

    // Build the "set timer" intent
    val timerIntent = Intent(AlarmClock.ACTION_SET_TIMER).apply {
        putExtra(AlarmClock.EXTRA_MESSAGE, message)
        putExtra(AlarmClock.EXTRA_LENGTH, seconds)
        putExtra(AlarmClock.EXTRA_SKIP_UI, true)
    }

    // Check if there's a handler for the intent
    if (timerIntent.resolveActivity(packageManager) == null) {

        // Can't resolve the intent! Fail this operation cleanly
        // (perhaps by showing an error message)

    } else {
        // Intent resolves, it's safe to fire it off
        startActivity(timerIntent)

    }
}

Java

public void startTimer(String message, int seconds) {

    // Build the "set timer" intent
    Intent timerIntent = new Intent(AlarmClock.ACTION_SET_TIMER)
            .putExtra(AlarmClock.EXTRA_MESSAGE, message)
            .putExtra(AlarmClock.EXTRA_LENGTH, seconds)
            .putExtra(AlarmClock.EXTRA_SKIP_UI, true);

    // Check if there's a handler for the intent
    if (timerIntent.resolveActivity(getPackageManager()) == null) {

        // Can't resolve the intent! Fail this operation cleanly
        // (perhaps by showing an error message)

    } else {
        // Intent resolves, it's safe to fire it off
        startActivity(timerIntent);

    }
}

Как обмениваться файлами между профилями

Иногда приложению нужно предоставить другим приложениям доступ к своим файлам. Например, приложение галереи изображений может захотеть поделиться своими изображениями с редакторами изображений. Обычно для этого используется URI файла или URI контента.

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

Вместо этого следует использовать URI контента. URI контента идентифицируют файл более безопасным и доступным для совместного использования способом. URI контента содержит путь к файлу, а также орган, предоставляющий файл, и идентификационный номер, идентифицирующий файл. Вы можете сгенерировать идентификатор контента для любого файла с помощью FileProvider. Затем вы можете поделиться этим идентификатором с другими приложениями (даже в другом профиле). Получатель может использовать идентификатор контента, чтобы получить доступ к файлу.

Например, вот как можно получить URI контента для определенного URI файла:

Kotlin

// Open File object from its file URI
val fileToShare = File(fileUriToShare)

val contentUriToShare: Uri = FileProvider.getUriForFile(
        context,
        "com.example.myapp.fileprovider",
        fileToShare
)

Java

// Open File object from its file URI
File fileToShare = new File(fileUriToShare);

Uri contentUriToShare = FileProvider.getUriForFile(getContext(),
        "com.example.myapp.fileprovider", fileToShare);

При вызове метода getUriForFile() необходимо указать полномочия поставщика файлов (в этом примере – "com.example.myapp.fileprovider"), которые задаются в элементе <provider> манифеста приложения. Подробнее о том, как предоставлять доступ к файлам с помощью URI контента…

Прослушивание уведомлений

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

В рабочем профиле

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

В личном профиле

Когда приложение запущено в личном профиле, вы можете не получать уведомления от приложений, запущенных в рабочем профиле. По умолчанию все приложения в личном профиле получают обратные вызовы, но системный администратор может добавить в белый список одно или несколько приложений, которым разрешено отслеживать изменения уведомлений. После этого система заблокирует приложения, не входящие в белый список. В Android 8.0 (уровень API 26) и более поздних версиях контроллер правил для устройств (DPC), управляющий рабочим профилем, может запретить приложению прослушивать уведомления рабочего профиля с помощью метода DevicePolicyManagersetPermittedCrossProfileNotificationListeners(). Приложение по-прежнему получает обратные вызовы об уведомлениях, опубликованных в личном профиле.

Проверьте совместимость приложения с рабочими профилями

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

Мы создали пример приложения TestDPC, которое можно использовать для настройки рабочего профиля на устройстве Android 5.0 (API уровня 21) или более поздней версии. Это приложение позволяет легко протестировать ваше приложение в среде рабочего профиля. Вы также можете использовать это приложение, чтобы настроить рабочий профиль следующим образом:

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

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

  • Если намерение обычно обрабатывается приложением по умолчанию (например, приложением камеры), попробуйте отключить это приложение по умолчанию в рабочем профиле и убедитесь, что приложение обрабатывает это соответствующим образом.
  • Если вы запускаете намерение, которое должно обрабатываться другим приложением, попробуйте включить и отключить разрешение на передачу этого намерения из одного профиля в другой. Убедитесь, что приложение работает правильно в обоих случаях. Если намерение не может передаваться между профилями, проверьте поведение приложения в двух случаях: когда в профиле приложения есть подходящий обработчик и когда его нет. Например, если ваше приложение запускает намерение, связанное с картой, попробуйте каждый из следующих сценариев:
    • Устройство позволяет намерениям карт переходить из одного профиля в другой, и в другом профиле (в котором приложение не запущено) есть подходящий обработчик.
    • Устройство не позволяет передавать намерения, связанные с картами, между профилями, но в профиле приложения есть подходящий обработчик.
    • Устройство не позволяет передавать намерения, связанные с картами, между профилями, и в профиле устройства нет подходящего обработчика для таких намерений.
  • Если вы прикрепляете контент к намерению, убедитесь, что намерение работает правильно как при обработке в профиле приложения, так и при переходе между профилями.

Тестирование рабочих профилей: советы и рекомендации

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

  • Как отмечалось выше, при неопубликованной загрузке приложения на устройство с рабочим профилем оно устанавливается в оба профиля. При желании вы можете удалить приложение из одного профиля и оставить в другом.
  • Большинство команд менеджера действий, доступных в оболочке Android Debug Bridge (adb), поддерживают флаг --user, который позволяет указать, от имени какого пользователя нужно выполнить команду. Указав пользователя, вы можете выбрать, запускать ли приложение от имени основного пользователя без управления или в рабочем профиле. Дополнительную информацию можно найти в статье Команды ADB Shell.
  • Чтобы найти активных пользователей на устройстве, используйте команду list users менеджера пакетов adb. Первое число в выходной строке – это идентификатор пользователя, который можно использовать с флагом --user. Дополнительную информацию можно найти в статье Команды ADB Shell.

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

$ adb shell pm list users
UserInfo{0:Drew:13} running
UserInfo{10:Work profile:30} running

В этом случае основной пользователь("Drew") имеет идентификатор пользователя 0, а рабочий профиль – идентификатор пользователя 10. Чтобы запустить приложение в рабочем профиле, используйте следующую команду:

$ adb shell am start --user 10 \
-n "com.example.myapp/com.example.myapp.testactivity" \
-a android.intent.action.MAIN -c android.intent.category.LAUNCHER