Контакты рабочего профиля

В этом руководстве разработчика объясняется, как можно улучшить свое приложение, чтобы оно использовало контактные данные из рабочего профиля. Если вы раньше не использовали API-интерфейсы контактов Android, прочтите «Поставщик контактов» , чтобы ознакомиться с API-интерфейсами.

Обзор

Устройства с рабочими профилями хранят контакты в отдельных локальных каталогах для рабочего и личного профилей. По умолчанию, когда приложение запускается в личном профиле, оно не отображает рабочие контакты. Однако приложение может получить доступ к контактной информации из рабочего профиля. Например, приложение Google «Контакты» для Android, которое показывает в результатах поиска как личные контакты, так и контакты из рабочего каталога.

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

Пользовательский опыт

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

  • Должно ли ваше приложение включать контакты рабочего профиля по умолчанию или пользователь должен дать свое согласие?
  • Как смешивание или разделение контактов рабочего и личного профиля повлияет на поток пользователей?
  • Каковы последствия случайного нажатия на контакт рабочего профиля?
  • Что происходит с интерфейсом вашего приложения, если контакты рабочего профиля недоступны?

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

Снимок экрана, показывающий результаты поиска в списке
Рисунок 1. Как приложение Google Контакты разделяет контакты рабочего профиля

Например, приложение Google Contacts (показанное на рисунке 1) делает следующее, чтобы вывести список контактов рабочего и личного профиля:

  1. Вставляет подзаголовок, чтобы разделить рабочий и личный разделы списка.
  2. Значки рабочих контактов со значком портфеля.
  3. При нажатии открывает рабочий контакт в рабочем профиле.

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

Разрешения

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

ИТ-администратор может заблокировать обмен контактной информацией рабочего профиля с личным профилем. Если ИТ-администратор заблокирует доступ, результаты поиска контактов будут пустыми. Вашему приложению не нужно обрабатывать определенные ошибки, если пользователь отключил рабочий профиль. Поставщик содержимого справочника продолжает возвращать информацию о справочниках рабочих контактов пользователя (см. раздел Справочники ). Чтобы проверить эти разрешения, см. раздел «Разработка и тестирование» .

Поиск контактов

Вы можете получить контакты из рабочего профиля, используя те же API и процессы, которые ваше приложение использует для получения контактов в личном профиле. Корпоративный URI для контактов поддерживается в Android 7.0 (уровень API 24) или более поздних версиях. Вам необходимо внести следующие изменения в URI:

  1. Задайте для URI поставщика контента значение Contacts.ENTERPRISE_CONTENT_FILTER_URI и укажите имя контакта в виде строки запроса.
  2. Установите каталог контактов для поиска. Например, ENTERPRISE_DEFAULT находит контакты в локальном хранилище рабочего профиля.

Изменение URI работает с любым механизмом поставщика контента, таким как CursorLoader — идеально подходит для загрузки контактных данных в пользовательские интерфейсы, поскольку доступ к данным происходит в рабочем потоке. Для простоты примеры в этом руководстве вызывают ContentResolver.query() . Вот как можно найти контакты в локальном каталоге контактов рабочего профиля:

Котлин

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

Ява

// 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()
Вызовите этот метод, чтобы узнать, является ли каталог удаленным. Удаленные каталоги могут быть корпоративными хранилищами контактов или социальными сетями пользователя.

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

Котлин

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

Ява

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

Котлин

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
}

Ява

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) или более поздних версиях.

Вот как можно найти контактную информацию по адресу электронной почты:

Котлин

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

Ява

// 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, а затем показать карточку:

Котлин

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

Ява

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

Разработка и тестирование

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

  1. Установите наше приложение Test DPC .
  2. Откройте приложение «Настройка тестового ЦОД» (а не значок приложения «Тестовый ЦОД»).
  3. Следуйте инструкциям на экране, чтобы настроить управляемый профиль.
  4. В рабочем профиле откройте приложение «Контакты» и добавьте несколько примеров контактов.

Чтобы имитировать блокировку ИТ-администратором доступа к контактам рабочего профиля, выполните следующие действия:

  1. В рабочем профиле откройте приложение Test DPC .
  2. Найдите параметр «Отключить межпрофильный поиск контактов» или параметр «Отключить межпрофильный идентификатор вызывающего абонента» .
  3. Переключите настройку на Вкл .

Чтобы узнать больше о тестировании вашего приложения с рабочими профилями, прочтите статью «Проверьте свое приложение на совместимость с рабочими профилями» .

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

Чтобы узнать больше о контактах или рабочем профиле, посетите эти ресурсы: