Contatos do perfil de trabalho

Este guia para desenvolvedores explica como melhorar seu app para usar dados de contato do perfil de trabalho. Se você nunca usou as APIs de contatos do Android, leia Provedor de contatos para se familiarizar com as APIs.

Visão geral

Dispositivos com perfis de trabalho armazenam contatos em diretórios locais separados para os perfis de trabalho e pessoal. Por padrão, quando um app é executado no perfil pessoal, ele não mostra os contatos de trabalho. No entanto, um app pode acessar dados de contato no perfil de trabalho. Por exemplo, um app que faz isso é o app Contatos do Android do Google, que mostra os contatos pessoais e do diretório de trabalho nos resultados da pesquisa.

Os usuários geralmente querem usar dispositivos e apps pessoais no trabalho. Ao usar os contatos do perfil de trabalho, seu app pode se tornar parte do dia de trabalho do usuário.

Experiência do usuário

Pense em como o app pode apresentar dados de contato do perfil de trabalho. A melhor abordagem depende da natureza do seu app e do motivo pelo qual as pessoas o usam, mas pense no seguinte:

  • Seu app deve incluir os contatos do perfil de trabalho por padrão ou o usuário precisa aceitar?
  • Como a mistura ou separação dos contatos do perfil pessoal e de trabalho afetará o fluxo do usuário?
  • Qual é o impacto de tocar acidentalmente em um contato do perfil de trabalho?
  • O que acontece com a interface do app quando os contatos do perfil de trabalho não estão disponíveis?

O app precisa indicar claramente um contato do perfil de trabalho. Talvez você possa enviar o crachá para o contato usando um ícone de trabalho conhecido, como uma pasta.

Captura de tela mostrando os resultados da pesquisa em uma lista
Figura 1. Como o app Contatos do Google separa os contatos do perfil de trabalho

Por exemplo, o app Contatos do Google (mostrado na Figura 1) faz o seguinte para listar uma combinação de contatos do perfil pessoal e de trabalho:

  1. Insere um subtítulo para separar as seções pessoal e de trabalho da lista.
  2. Selos de contatos de trabalho com um ícone de maleta.
  3. Abre um contato de trabalho no perfil de trabalho quando o usuário toca nele.

Se o usuário do dispositivo desativar o perfil de trabalho, seu app não poderá procurar informações de contato no perfil de trabalho ou nos diretórios de contatos remotos da organização. Dependendo de como você usa os contatos do perfil de trabalho, é possível ignorar esses contatos silenciosamente ou talvez seja necessário desativar os controles da interface do usuário.

Permissões

Se o app já estiver funcionando com os contatos do usuário, você terá a permissão READ_CONTACTS (ou possivelmente WRITE_CONTACTS) solicitada no arquivo de manifesto do app. Como a mesma pessoa usa o perfil pessoal e o de trabalho, você não precisa de outras permissões para acessar os dados de contato do perfil de trabalho.

Um administrador de TI pode bloquear o perfil de trabalho que compartilha informações de contato com o perfil pessoal. Se um administrador de TI bloquear o acesso, suas pesquisas de contato serão retornadas como resultados vazios. O app não vai precisar processar erros específicos se o usuário tiver desativado o perfil de trabalho. O provedor de conteúdo do diretório continua retornando informações sobre os diretórios de contato de trabalho do usuário. Consulte a seção Diretórios. Para testar essas permissões, consulte a seção Desenvolvimento e testes.

Pesquisas de contatos

É possível receber contatos do perfil de trabalho usando as mesmas APIs e processos usados pelo app para acessar contatos no perfil pessoal. O URI corporativo para contatos tem suporte no Android 7.0 (nível 24 da API) ou versões mais recentes. É necessário fazer os seguintes ajustes no URI:

  1. Defina o URI do provedor de conteúdo como Contacts.ENTERPRISE_CONTENT_FILTER_URI e forneça o nome do contato como uma string de consulta.
  2. Defina um diretório de contatos para pesquisar. Por exemplo, ENTERPRISE_DEFAULT encontra contatos no armazenamento local do perfil de trabalho.

A mudança do URI funciona com qualquer mecanismo de provedor de conteúdo, como um CursorLoader, ideal para carregar dados de contato nas interfaces do usuário porque o acesso aos dados acontece em uma linha de execução de worker. Para simplificar, os exemplos neste guia chamam ContentResolver.query(). Veja como encontrar contatos no diretório local do perfil de trabalho:

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

Diretórios

Muitas organizações usam diretórios remotos, como Microsoft Exchange ou LDAP, que contêm informações de contato de toda a organização. Seu app pode ajudar os usuários a se comunicar e compartilhar com colegas de trabalho encontrados no diretório da organização. Observe que esses diretórios normalmente contêm milhares de contatos, e o app também precisa de uma conexão de rede ativa para procurá-los. Você pode usar o provedor de conteúdo Directory para acessar os diretórios usados pelas contas do usuário e saber mais sobre um diretório específico.

Consulte o provedor de conteúdo Directory.ENTERPRISE_CONTENT_URI para acessar diretórios do perfil pessoal e do perfil de trabalho retornados juntos. A pesquisa de diretórios do perfil de trabalho tem suporte no Android 7.0 (nível 24 da API) ou mais recente. O app ainda precisa que o usuário conceda permissões READ_CONTACTS para trabalhar com os diretórios de contatos.

Como o Android armazena dados de contato em diferentes tipos de diretórios locais e remotos, a classe Directory tem métodos que podem ser chamados para saber mais sobre um diretório:

isEnterpriseDirectoryId()
Chame esse método para descobrir se o diretório é de uma conta do perfil de trabalho. Lembre-se de que o provedor de conteúdo ENTERPRISE_CONTENT_URI retorna diretórios de contatos para os perfis pessoal e de trabalho juntos.
isRemoteDirectoryId()
Chame esse método para descobrir se o diretório é remoto. Os diretórios remotos podem ser repositórios de contatos corporativos ou redes sociais do usuário.

O exemplo abaixo mostra como usar esses métodos para filtrar diretórios de perfil de trabalho:

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

O exemplo busca o ID e o nome do pacote para o diretório. Para exibir uma interface do usuário que ajude os usuários a escolher uma origem do diretório de contatos, pode ser necessário buscar mais informações sobre o diretório. Para ver outros campos de metadados que podem estar disponíveis, leia a referência da classe Directory.

Pesquisas no smartphone

Os apps podem consultar PhoneLookup.CONTENT_FILTER_URI para pesquisar dados de contato de um número de telefone com eficiência. Você pode receber resultados da pesquisa do provedor de contatos do perfil pessoal e de trabalho se substituir esse URI por PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI. Esse URI de conteúdo do perfil de trabalho está disponível no Android 5.0 (API de nível 21) ou versões mais recentes.

O exemplo a seguir mostra um app que consulta o URI de conteúdo do perfil de trabalho para configurar a interface do usuário para uma chamada recebida:

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

Pesquisas de e-mail

Seu app pode receber dados de contato pessoais ou de trabalho de um endereço de e-mail consultando Email.ENTERPRISE_CONTENT_LOOKUP_URI. A consulta desse URL primeiro pesquisa os contatos pessoais em busca de uma correspondência exata. Se o provedor não corresponder a nenhum contato pessoal, ele vai procurar uma correspondência nos contatos de trabalho. Esse URI está disponível no Android 6.0 (API de nível 23) ou versões mais recentes.

Veja como procurar informações de contato para um endereço de e-mail:

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

Mostrar um contato de trabalho

Apps em execução no perfil pessoal podem mostrar um card de contato no perfil de trabalho. Chame ContactsContract.QuickContact.showQuickContact() no Android 5.0 ou versões mais recentes para iniciar o app Contatos no perfil de trabalho e mostrar o card do contato.

Para gerar um URI correto para o perfil de trabalho, é necessário chamar ContactsContract.Contacts.getLookupUri() e transmitir um ID de contato e uma chave de pesquisa. O exemplo a seguir mostra como acessar o URI e mostrar o card:

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

Disponibilidade

A tabela a seguir resume quais versões do Android oferecem suporte aos dados de contato do perfil de trabalho no perfil pessoal:

Versão do Android Suporte
5.0 (API de nível 21) Procure nomes de contatos de trabalho para números de telefone usando o PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI.
6.0 (API de nível 23) Procure nomes de contatos de trabalho para endereços de e-mail usando o Email.ENTERPRISE_CONTENT_LOOKUP_URI.
7.0 (API de nível 24) Consulte nomes de contatos de trabalho nos diretórios de trabalho usando o Contacts.ENTERPRISE_CONTENT_FILTER_URI.
Use o Directory.ENTERPRISE_CONTENT_URI para listar todos os diretórios nos perfis de trabalho e pessoal.

Desenvolvimento e teste

Para criar um perfil de trabalho, siga estas etapas:

  1. Instale o app Testar DPC.
  2. Abra o app Configurar DPC de teste, não o ícone dele.
  3. Siga as instruções na tela para configurar um perfil gerenciado.
  4. No perfil de trabalho, abra o app Contatos e adicione alguns exemplos de contatos.

Para simular um administrador de TI bloqueando o acesso aos contatos do perfil de trabalho, siga estas etapas:

  1. No perfil de trabalho, abra o app Test DPC.
  2. Procure a configuração Desativar a pesquisa de contatos entre perfis ou Desativar o identificador de chamadas entre perfis.
  3. Mude a configuração para Ativado.

Para saber mais sobre como testar seu app com perfis de trabalho, leia Testar a compatibilidade do seu app com perfis de trabalho.

Outros recursos

Para saber mais sobre contatos ou o perfil de trabalho, consulte estes recursos: