В этом руководстве для разработчиков рассказывается, как улучшить приложение, чтобы оно могло использовать контактные данные из рабочего профиля. Если вы ранее не использовали API контактов Android, ознакомьтесь с документацией по поставщику контактов.
Обзор
На устройствах с рабочими профилями контакты хранятся в отдельных локальных каталогах для рабочего и личного профилей. По умолчанию, когда приложение запущено в личном профиле, оно не показывает рабочие контакты. Однако приложение может получить доступ к контактной информации из рабочего профиля. Например, приложение "Контакты" для Android показывает в результатах поиска контакты как из личного, так и из рабочего профиля.
Пользователи часто хотят использовать личные устройства и приложения для работы. Используя контакты из рабочего профиля, ваше приложение может стать частью рабочего дня пользователя.
Удобство использования
Подумайте, как ваше приложение может представлять контактную информацию из рабочего профиля. Выбор подхода зависит от характера приложения и причин, по которым люди его используют. Однако мы рекомендуем учитывать следующее:
- Должно ли приложение включать контакты из рабочего профиля по умолчанию или пользователю нужно дать на это согласие?
- Как объединение или разделение контактов из рабочего и личного профилей повлияет на работу пользователя?
- Что произойдет, если случайно нажать на контакт из рабочего профиля?
- Что происходит с интерфейсом приложения, когда контакты из рабочего профиля недоступны?
В приложении должно быть четко указано, что контакт относится к рабочему профилю. Возможно, вы можете пометить контакт с помощью знакомого значка работы, например портфеля.
Например, приложение "Google Контакты" (показано на рисунке 1) выполняет следующие действия, чтобы показать список контактов из личного и рабочего профилей:
- Вставляет подзаголовок, чтобы разделить рабочие и личные разделы списка.
- Отмечает рабочие контакты значком портфеля.
- При нажатии открывает рабочий контакт в рабочем профиле.
Если пользователь устройства отключит рабочий профиль, ваше приложение не сможет искать контактную информацию в рабочем профиле или удаленных каталогах контактов организации. В зависимости от того, как вы используете контакты из рабочего профиля, вы можете просто не включать их или отключить элементы управления пользовательским интерфейсом.
Разрешения
Если у приложения уже есть доступ к контактам пользователя, значит вы получили разрешение READ_CONTACTS (или WRITE_CONTACTS), которое запросили в файле манифеста приложения. Поскольку личный и рабочий профили принадлежат одному и тому же человеку, вам не нужно запрашивать дополнительное разрешение на доступ к контактным данным из рабочего профиля.
ИТ-администратор может запретить передачу контактной информации из рабочего профиля в личный профиль. Если системный администратор заблокирует доступ, результаты поиска контактов будут пустыми. Если пользователь отключил рабочий профиль, приложение не должно обрабатывать определенные ошибки. Поставщик контента каталога продолжает возвращать информацию о каталогах рабочих контактов пользователя (см. раздел Каталоги). Чтобы протестировать эти разрешения, ознакомьтесь с разделом Разработка и тестирование.
Поиск контактов
Вы можете получать контакты из рабочего профиля, используя те же API и процессы, которые ваше приложение использует для получения контактов в личном профиле. Корпоративный URI для контактов поддерживается в Android 7.0 (уровень API 24) и более поздних версиях. Вам нужно внести в URI следующие изменения:
- Установите URI поставщика контента на
Contacts.ENTERPRISE_CONTENT_FILTER_URIи укажите имя контакта в качестве строки запроса. - Укажите каталог контактов для поиска. Например,
ENTERPRISE_DEFAULTнаходит контакты в локальном хранилище рабочего профиля.
Изменение URI работает с любым механизмом поставщика контента, например CursorLoader. Это идеальный вариант для загрузки контактных данных в пользовательские интерфейсы, поскольку доступ к данным осуществляется в рабочем потоке. Для простоты в примерах этого руководства вызывается ContentResolver.query(). Вот как найти контакты в локальном каталоге рабочего профиля:
Kotlin
// First confirm the device user has given permission for the personal profile. // There isn't a separate work permission, but an IT admin can block access. val readContactsPermission = ContextCompat.checkSelfPermission(getBaseContext(), Manifest.permission.READ_CONTACTS) if (readContactsPermission != PackageManager.PERMISSION_GRANTED) { return } // Fetch Jackie, James, & Jason (and anyone else whose names begin with "ja"). val nameQuery = Uri.encode("ja") // Build the URI to look up work profile contacts whose name matches. Query // the default work profile directory which is the locally-stored contacts. val contentFilterUri = ContactsContract.Contacts.ENTERPRISE_CONTENT_FILTER_URI .buildUpon() .appendPath(nameQuery) .appendQueryParameter( ContactsContract.DIRECTORY_PARAM_KEY, ContactsContract.Directory.ENTERPRISE_DEFAULT.toString() ) .build() // Query the content provider using the generated URI. var cursor = getContentResolver() .query( contentFilterUri, arrayOf( ContactsContract.Contacts._ID, ContactsContract.Contacts.LOOKUP_KEY, ContactsContract.Contacts.DISPLAY_NAME_PRIMARY ), null, null, null ) // Print any results found using the work profile contacts' display name. cursor?.use { while (it.moveToNext()) { Log.i(TAG, "Work profile contact: ${it.getString(2)}") } }
Java
// First confirm the device user has given permission for the personal profile. // There isn't a separate work permission, but an IT admin can block access. int readContactsPermission = ContextCompat.checkSelfPermission( getBaseContext(), Manifest.permission.READ_CONTACTS); if (readContactsPermission != PackageManager.PERMISSION_GRANTED) { return; } // Fetch Jackie, James, & Jason (and anyone else whose names begin with "ja"). String nameQuery = Uri.encode("ja"); // Build the URI to look up work profile contacts whose name matches. Query // the default work profile directory which is the locally stored contacts. Uri contentFilterUri = ContactsContract.Contacts.ENTERPRISE_CONTENT_FILTER_URI .buildUpon() .appendPath(nameQuery) .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(ContactsContract.Directory.ENTERPRISE_DEFAULT)) .build(); // Query the content provider using the generated URI. Cursor cursor = getContentResolver().query( contentFilterUri, new String[] { ContactsContract.Contacts._ID, ContactsContract.Contacts.LOOKUP_KEY, ContactsContract.Contacts.DISPLAY_NAME_PRIMARY }, null, null, null); if (cursor == null) { return; } // Print any results found using the work profile contacts' display name. try { while (cursor.moveToNext()) { Log.i(TAG, "Work profile contact: " + cursor.getString(2)); } } finally { cursor.close(); }
Каталоги
Многие организации используют удаленные каталоги, например Microsoft Exchange или LDAP, в которых хранится контактная информация всех сотрудников. Ваше приложение может помогать пользователям общаться и делиться информацией с коллегами, найденными в каталоге организации. Обратите внимание, что в таких каталогах обычно содержатся тысячи контактов, а для поиска по ним требуется активное подключение к сети. Чтобы получить каталоги, используемые аккаунтами пользователя, и узнать больше об отдельном каталоге, можно использовать поставщик контента Directory.
Запросите поставщика контента Directory.ENTERPRISE_CONTENT_URI, чтобы получить каталоги из личного и рабочего профилей. Поиск в каталогах рабочего профиля поддерживается в Android 7.0 (уровень API 24) и более поздних версиях. Чтобы приложение могло работать с каталогами контактов, пользователь должен предоставить ему разрешения READ_CONTACTS.
Поскольку Android хранит контактную информацию в различных типах локальных и удаленных каталогов, класс Directory содержит методы, которые можно вызвать, чтобы узнать больше о каталоге:
isEnterpriseDirectoryId()- Вызовите этот метод, чтобы узнать, относится ли каталог к аккаунту рабочего профиля.
Помните, что поставщик контента
ENTERPRISE_CONTENT_URIвозвращает каталоги контактов для личного и рабочего профилей вместе. isRemoteDirectoryId()- Вызовите этот метод, чтобы узнать, является ли каталог удаленным. Удаленные каталоги могут быть корпоративными хранилищами контактов или социальными сетями пользователя.
В примере ниже показано, как использовать эти методы для фильтрации каталогов рабочего профиля.
Kotlin
// First, confirm the device user has given READ_CONTACTS permission. // This permission is still needed for directory listings ... // Query the content provider to get directories for BOTH the personal and // work profiles. val cursor = getContentResolver() .query( ContactsContract.Directory.ENTERPRISE_CONTENT_URI, arrayOf(ContactsContract.Directory._ID, ContactsContract.Directory.PACKAGE_NAME), null, null, null ) // Print the package name of the work profile's local or remote contact directories. cursor?.use { while (it.moveToNext()) { val directoryId = it.getLong(0) if (ContactsContract.Directory.isEnterpriseDirectoryId(directoryId)) { Log.i(TAG, "Directory: ${it.getString(1)}") } } }
Java
// First, confirm the device user has given READ_CONTACTS permission. // This permission is still needed for directory listings ... // Query the content provider to get directories for BOTH the personal and // work profiles. Cursor cursor = getContentResolver().query( ContactsContract.Directory.ENTERPRISE_CONTENT_URI, new String[]{ ContactsContract.Directory._ID, ContactsContract.Directory.PACKAGE_NAME }, null, null, null); if (cursor == null) { return; } // Print the package name of the work profile's local or remote contact directories. try { while (cursor.moveToNext()) { long directoryId = cursor.getLong(0); if (ContactsContract.Directory.isEnterpriseDirectoryId(directoryId)) { Log.i(TAG, "Directory: " + cursor.getString(1)); } } } finally { cursor.close(); }
В примере извлекается идентификатор и название пакета для каталога. Чтобы показать пользователю интерфейс, который поможет ему выбрать источник каталога контактов, вам может потребоваться получить больше информации о каталоге. Чтобы узнать, какие ещё поля метаданных доступны, ознакомьтесь со справочной информацией о классе Directory.
Поиск номеров телефонов
Приложения могут запрашивать PhoneLookup.CONTENT_FILTER_URI, чтобы эффективно находить контактные данные по номеру телефона. Чтобы получать результаты поиска из поставщиков контактов как личного, так и рабочего профиля, замените этот URI на PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI.
URI контента рабочего профиля доступен в Android 5.0 (уровень API 21) и более поздних версиях.
В примере ниже показано, как приложение запрашивает URI контента рабочего профиля, чтобы настроить пользовательский интерфейс для входящего вызова.
Kotlin
fun onCreateIncomingConnection( connectionManagerPhoneAccount: PhoneAccountHandle, request: ConnectionRequest ): Connection { var request = request // Get the telephone number from the incoming request URI. val phoneNumber = this.extractTelephoneNumber(request.address) var displayName = "Unknown caller" var isCallerInWorkProfile = false // Look up contact details for the caller in the personal and work profiles. val lookupUri = Uri.withAppendedPath( ContactsContract.PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, Uri.encode(phoneNumber) ) val cursor = getContentResolver() .query( lookupUri, arrayOf( ContactsContract.PhoneLookup._ID, ContactsContract.PhoneLookup.DISPLAY_NAME, ContactsContract.PhoneLookup.CUSTOM_RINGTONE ), null, null, null ) // Use the first contact found and check if they're from the work profile. cursor?.use { if (it.moveToFirst() == true) { displayName = it.getString(1) isCallerInWorkProfile = ContactsContract.Contacts.isEnterpriseContactId(it.getLong(0)) } } // Return a configured connection object for the incoming call. val connection = MyAudioConnection() connection.setCallerDisplayName(displayName, TelecomManager.PRESENTATION_ALLOWED) // Our app's activity uses this value to decide whether to show a work badge. connection.setIsCallerInWorkProfile(isCallerInWorkProfile) // Configure the connection further ... return connection }
Java
public Connection onCreateIncomingConnection ( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) { // Get the telephone number from the incoming request URI. String phoneNumber = this.extractTelephoneNumber(request.getAddress()); String displayName = "Unknown caller"; boolean isCallerInWorkProfile = false; // Look up contact details for the caller in the personal and work profiles. Uri lookupUri = Uri.withAppendedPath( ContactsContract.PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, Uri.encode(phoneNumber)); Cursor cursor = getContentResolver().query( lookupUri, new String[]{ ContactsContract.PhoneLookup._ID, ContactsContract.PhoneLookup.DISPLAY_NAME, ContactsContract.PhoneLookup.CUSTOM_RINGTONE }, null, null, null); // Use the first contact found and check if they're from the work profile. if (cursor != null) { try { if (cursor.moveToFirst() == true) { displayName = cursor.getString(1); isCallerInWorkProfile = ContactsContract.Contacts.isEnterpriseContactId(cursor.getLong(0)); } } finally { cursor.close(); } } // Return a configured connection object for the incoming call. MyConnection connection = new MyConnection(); connection.setCallerDisplayName(displayName, TelecomManager.PRESENTATION_ALLOWED); // Our app's activity uses this value to decide whether to show a work badge. connection.setIsCallerInWorkProfile(isCallerInWorkProfile); // Configure the connection further ... return connection; }
Поиск по электронной почте
Приложение может получить данные личных или рабочих контактов для адреса электронной почты, отправив запрос Email.ENTERPRISE_CONTENT_LOOKUP_URI.
При запросе этого URL сначала выполняется поиск точного совпадения в личных контактах. Если совпадений не найдено, поиск выполняется в рабочих контактах. Этот URI доступен в Android 6.0 (уровень API 23) и более поздних версиях.
Вот как найти контактную информацию по адресу электронной почты:
Kotlin
// Build the URI to look up contacts from the personal and work profiles that // are an exact (case-insensitive) match for the email address. val emailAddress = "somebody@example.com" val contentFilterUri = Uri.withAppendedPath( ContactsContract.CommonDataKinds.Email.ENTERPRISE_CONTENT_LOOKUP_URI, Uri.encode(emailAddress) ) // Query the content provider to first try to match personal contacts and, // if none are found, then try to match the work contacts. val cursor = contentResolver.query( contentFilterUri, arrayOf( ContactsContract.CommonDataKinds.Email.CONTACT_ID, ContactsContract.CommonDataKinds.Email.ADDRESS, ContactsContract.Contacts.DISPLAY_NAME ), null, null, null ) ?: return // Print the name of the matching contact. If we want to work-badge contacts, // we can call ContactsContract.Contacts.isEnterpriseContactId() with the ID. cursor.use { while (it.moveToNext()) { Log.i(TAG, "Matching contact: ${it.getString(2)}") } }
Java
// Build the URI to look up contacts from the personal and work profiles that // are an exact (case-insensitive) match for the email address. String emailAddress = "somebody@example.com"; Uri contentFilterUri = Uri.withAppendedPath( ContactsContract.CommonDataKinds.Email.ENTERPRISE_CONTENT_LOOKUP_URI, Uri.encode(emailAddress)); // Query the content provider to first try to match personal contacts and, // if none are found, then try to match the work contacts. Cursor cursor = getContentResolver().query( contentFilterUri, new String[]{ ContactsContract.CommonDataKinds.Email.CONTACT_ID, ContactsContract.CommonDataKinds.Email.ADDRESS, ContactsContract.Contacts.DISPLAY_NAME }, null, null, null); if (cursor == null) { return; } // Print the name of the matching contact. If we want to work-badge contacts, // we can call ContactsContract.Contacts.isEnterpriseContactId() with the ID. try { while (cursor.moveToNext()) { Log.i(TAG, "Matching contact: " + cursor.getString(2)); } } finally { cursor.close(); }
Показать рабочий контакт
Приложения, запущенные в личном профиле, могут показывать карточку контакта в рабочем профиле.
Вызовите ContactsContract.QuickContact.showQuickContact() в Android 5.0 или более поздней версии, чтобы запустить приложение "Контакты" в рабочем профиле и показать карточку контакта.
Чтобы сгенерировать правильный URI для рабочего профиля, вызовите ContactsContract.Contacts.getLookupUri() и передайте идентификатор контакта и ключ поиска. В примере ниже показано, как получить URI и показать карту.
Kotlin
// Query the content provider using the ENTERPRISE_CONTENT_FILTER_URI address. // We use the _ID and LOOKUP_KEY columns to generate a work-profile URI. val cursor = getContentResolver() .query( contentFilterUri, arrayOf(ContactsContract.Contacts._ID, ContactsContract.Contacts.LOOKUP_KEY), null, null ) // Show the contact details card in the work profile's Contacts app. The URI // must be created with getLookupUri(). cursor?.use { if (it.moveToFirst() == true) { val uri = ContactsContract.Contacts.getLookupUri(it.getLong(0), it.getString(1)) ContactsContract.QuickContact.showQuickContact( activity, Rect(20, 20, 100, 100), uri, ContactsContract.QuickContact.MODE_LARGE, null ) } }
Java
// Query the content provider using the ENTERPRISE_CONTENT_FILTER_URI address. // We use the _ID and LOOKUP_KEY columns to generate a work-profile URI. Cursor cursor = getContentResolver().query( contentFilterUri, new String[] { ContactsContract.Contacts._ID, ContactsContract.Contacts.LOOKUP_KEY, }, null, null, null); if (cursor == null) { return; } // Show the contact details card in the work profile's Contacts app. The URI // must be created with getLookupUri(). try { if (cursor.moveToFirst() == true) { Uri uri = ContactsContract.Contacts.getLookupUri( cursor.getLong(0), cursor.getString(1)); ContactsContract.QuickContact.showQuickContact( getActivity(), new Rect(20, 20, 100, 100), uri, ContactsContract.QuickContact.MODE_LARGE, null); } } finally { cursor.close(); }
Доступность
В таблице ниже указано, какие версии Android поддерживают доступ к контактным данным из рабочего профиля в личном профиле.
| Версия Android | Поддержка |
|---|---|
| 5.0 (уровень API 21) | Искать имена рабочих контактов по номерам телефонов с помощью PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI. |
| 6.0 (уровень API 23) | Искать имена рабочих контактов по адресам электронной почты с помощью Email.ENTERPRISE_CONTENT_LOOKUP_URI. |
| 7.0 (уровень API 24) | Запрашивать имена рабочих контактов из рабочих каталогов с помощью Contacts.ENTERPRISE_CONTENT_FILTER_URI.Выводить список всех каталогов в рабочем и личном профилях с помощью Directory.ENTERPRISE_CONTENT_URI. |
Разработка и тестирование
Чтобы создать рабочий профиль, выполните следующие действия:
- Установите приложение Test DPC.
- Откройте приложение Set up Test DPC (не значок приложения Test DPC).
- Следуйте инструкциям на экране, чтобы настроить управляемый профиль.
- В рабочем профиле откройте приложение Контакты и добавьте несколько контактов.
Чтобы смоделировать блокировку доступа к контактам рабочего профиля системным администратором, выполните следующие действия:
- В рабочем профиле откройте приложение Test DPC.
- Найдите параметр Отключить поиск контактов в разных профилях или Отключить идентификатор абонента в разных профилях.
- Установите переключатель в положение Вкл.
Подробнее о том, как тестировать приложения на совместимость с рабочими профилями…
Дополнительные ресурсы
Дополнительную информацию о контактах и рабочем профиле можно найти в следующих ресурсах:
- Дополнительные рекомендации по работе с рабочими профилями приведены в разделе Рабочие профили.
- Получение списка контактов В этом руководстве рассказывается, как получить список контактов в приложении.
- В статье Поставщик контактов объясняется структура базы данных контактов.