Contactos del perfil de trabajo

En esta guía para desarrolladores, se explica cómo puedes mejorar tu app para usar datos de contacto del perfil de trabajo. Si nunca usaste las APIs de contactos de Android, lee Proveedor de contactos para familiarizarte con las APIs.

Descripción general

Los dispositivos con perfiles de trabajo almacenan contactos en directorios locales independientes para el perfil de trabajo y el personal. De forma predeterminada, cuando una app se ejecuta en el perfil personal, no muestra los contactos de trabajo. Sin embargo, una app puede acceder a la información de contacto desde el perfil de trabajo. Por ejemplo, una app que hace esto es la app de Contactos de Google para Android, que muestra los contactos personales y los del directorio de trabajo en los resultados de la búsqueda.

A menudo, los usuarios quieren usar sus apps y dispositivos personales para trabajar. Si usas los contactos del perfil de trabajo, tu app puede convertirse en parte de la jornada laboral de tu usuario.

Experiencia del usuario

Considera cómo tu app podría presentar la información de contacto del perfil de trabajo. El mejor enfoque depende de la naturaleza de tu app y del motivo por el que las personas la usan, pero piensa en lo siguiente:

  • ¿Tu app debería incluir contactos del perfil de trabajo de forma predeterminada o el usuario debería habilitarlo?
  • ¿Cómo afectará el flujo del usuario mezclar o separar los contactos del perfil personal y de trabajo?
  • ¿Cuál es el impacto de presionar accidentalmente un contacto del perfil de trabajo?
  • ¿Qué sucede con la interfaz de tu app cuando los contactos del perfil de trabajo no están disponibles?

Tu app debe indicar claramente un contacto del perfil de trabajo. Tal vez puedas asignar una insignia al contacto con un ícono de trabajo conocido, como un maletín.

Captura de pantalla que muestra los resultados de la búsqueda en una lista
Figura 1: Cómo separa la app de Contactos de Google los contactos del perfil de trabajo

A modo de ejemplo, la app de Contactos de Google (que se muestra en la figura 1) hace lo siguiente para enumerar una combinación de contactos de perfiles personales y de trabajo:

  1. Inserta un subtítulo para separar las secciones personal y laboral de la lista.
  2. Insignias de contactos de trabajo con un ícono de maletín.
  3. Abre un contacto de trabajo en el perfil de trabajo cuando se presiona.

Si la persona que usa el dispositivo desactiva el perfil de trabajo, la app no podrá buscar información de contacto del perfil de trabajo ni de los directorios de contactos remotos de la organización. Según cómo uses los contactos del perfil de trabajo, puedes dejar esos contactos de forma silenciosa o es posible que debas inhabilitar los controles de la interfaz de usuario.

Permisos

Si tu app ya funciona con los contactos del usuario, tendrás el permiso READ_CONTACTS (o posiblemente WRITE_CONTACTS) que solicitaste en el archivo de manifiesto de la app. Como la misma persona utiliza el perfil personal y el perfil de trabajo, no necesitas más permisos para acceder a los datos de contacto del perfil de trabajo.

Un administrador de TI puede bloquear el perfil de trabajo que comparta información de contacto con el perfil personal. Si un administrador de TI bloquea el acceso, las búsquedas de contactos se mostrarán como resultados vacíos. Tu app no necesita controlar errores específicos si el usuario desactivó el perfil de trabajo. El proveedor de contenido del directorio continúa mostrando información sobre los directorios de contacto de trabajo del usuario (consulta la sección Directorios). Para probar estos permisos, consulta la sección Desarrollo y pruebas.

Búsquedas de contactos

Puedes obtener contactos del perfil de trabajo con las mismas APIs y procesos que usa tu app para obtener contactos en el perfil personal. El URI empresarial para contactos es compatible con Android 7.0 (nivel de API 24) o versiones posteriores. Debes realizar los siguientes ajustes en el URI:

  1. Establece el URI del proveedor de contenido en Contacts.ENTERPRISE_CONTENT_FILTER_URI y proporciona el nombre del contacto como una cadena de consulta.
  2. Establece un directorio de contactos para realizar la búsqueda. Por ejemplo, ENTERPRISE_DEFAULT encuentra contactos en la tienda local del perfil de trabajo.

El cambio de URI funciona con cualquier mecanismo de proveedor de contenido, como un CursorLoader. Es ideal para cargar datos de contacto a las interfaces de usuario, ya que el acceso a los datos se realiza en un subproceso de trabajo. Para simplificar, los ejemplos de esta guía llaman a ContentResolver.query(). A continuación, te mostramos cómo puedes encontrar contactos en el directorio local de contactos del perfil de trabajo:

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

Directorios

Muchas organizaciones usan directorios remotos, como Microsoft Exchange o LDAP, que contienen información de contacto de toda la organización. Tu app puede ayudar a los usuarios a comunicarse y compartir contenido con colegas del trabajo que se encuentran en el directorio de su organización. Ten en cuenta que, por lo general, estos directorios contienen miles de contactos y que tu app también necesita una conexión de red activa para realizar búsquedas en ellos. Puedes usar el proveedor de contenido Directory para obtener los directorios que usan las cuentas del usuario y obtener más información sobre un directorio individual.

Consulta el proveedor de contenido Directory.ENTERPRISE_CONTENT_URI para obtener directorios del perfil personal y el perfil de trabajo que se muestra juntos. La búsqueda en directorios del perfil de trabajo se admite en Android 7.0 (nivel de API 24) o versiones posteriores. Tu app aún necesita que el usuario otorgue permisos READ_CONTACTS para funcionar con sus directorios de contacto.

Como Android almacena la información de contacto en diferentes tipos de directorios locales y remotos, la clase Directory tiene métodos que puedes llamar para obtener más información sobre un directorio:

isEnterpriseDirectoryId()
Llama a este método para averiguar si el directorio proviene de una cuenta de perfil de trabajo. Recuerda que el proveedor de contenido ENTERPRISE_CONTENT_URI muestra directorios de contacto juntos para el perfil personal y el de trabajo.
isRemoteDirectoryId()
Llama a este método para averiguar si el directorio es remoto. Los directorios remotos pueden ser tiendas de contactos empresariales o las redes sociales del usuario.

En el siguiente ejemplo, se muestra cómo puedes usar estos métodos para filtrar directorios de perfiles de trabajo:

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

En el ejemplo, se recupera el ID y el nombre de paquete del directorio. Para mostrar una interfaz de usuario que ayude a los usuarios a elegir una fuente del directorio de contacto, es posible que debas obtener más información sobre el directorio. Para ver otros campos de metadatos que podrían estar disponibles, lee la referencia de la clase Directory.

Búsquedas en teléfonos

Las apps pueden consultar PhoneLookup.CONTENT_FILTER_URI para buscar de manera eficiente los datos de contacto de un número de teléfono. Puedes obtener resultados de búsqueda del proveedor de contactos del perfil personal y de trabajo si reemplazas este URI por PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI. Este URI de contenido del perfil de trabajo está disponible en Android 5.0 (nivel de API 21) o versiones posteriores.

En el siguiente ejemplo, se muestra una app que consulta el URI de contenido del perfil de trabajo para configurar la interfaz de usuario de una llamada entrante:

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

Búsquedas de correo electrónico

Tu app puede obtener datos de contacto personales o laborales de una dirección de correo electrónico consultando a Email.ENTERPRISE_CONTENT_LOOKUP_URI. Cuando se consulta esta URL, primero se busca una concordancia exacta en los contactos personales. Si el proveedor no establece coincidencias con ningún contacto personal, busca una coincidencia en los contactos laborales. Este URI está disponible en Android 6.0 (nivel de API 23) o versiones posteriores.

A continuación, te indicamos cómo buscar la información de contacto de una dirección de correo electrónico:

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

Cómo mostrar un contacto laboral

Las apps que se ejecutan en el perfil personal pueden mostrar una tarjeta de contacto en el perfil de trabajo. Llama a ContactsContract.QuickContact.showQuickContact() en Android 5.0 o versiones posteriores para iniciar la app de Contactos en el perfil de trabajo y mostrar la tarjeta del contacto.

A fin de generar un URI correcto para el perfil de trabajo, debes llamar a ContactsContract.Contacts.getLookupUri() y pasar un ID de contacto y una clave de búsqueda. En el siguiente ejemplo, se muestra cómo obtener el URI y, luego, mostrar la tarjeta:

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

Disponibilidad

En la siguiente tabla, se resume qué versiones de Android admiten datos de contactos del perfil de trabajo en el perfil personal:

Versión de Android Asistencia
5.0 (nivel de API 21) Busca los nombres de los contactos laborales para encontrar los números de teléfono con PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI.
6.0 (nivel de API 23) Busca direcciones de correo electrónico de contactos laborales con Email.ENTERPRISE_CONTENT_LOOKUP_URI.
7.0 (nivel de API 24) Consulta los nombres de los contactos laborales de los directorios de trabajo con Contacts.ENTERPRISE_CONTENT_FILTER_URI.
Usa Directory.ENTERPRISE_CONTENT_URI para enumerar todos los directorios de los perfiles personal y de trabajo.

Desarrollo y pruebas

Para crear un perfil de trabajo, sigue estos pasos:

  1. Instala la app de Test DPC.
  2. Abre la app Set up Test DPC (no el ícono de la app de Test DPC).
  3. Sigue las instrucciones en pantalla para configurar un perfil administrado.
  4. En el perfil de trabajo, abre la app de Contactos y agrega algunos contactos de muestra.

Para simular que un administrador de TI bloquea el acceso a los contactos del perfil de trabajo, sigue estos pasos:

  1. En el perfil de trabajo, abre la app de Test DPC.
  2. Busca las opciones de configuración Inhabilitar la búsqueda de los contactos del perfil sincronizado o Inhabilitar el identificador de llamadas del perfil sincronizado.
  3. Cambia la configuración a Activado.

Si quieres obtener más información para probar tu app con perfiles de trabajo, lee Prueba la compatibilidad de tu app con los perfiles de trabajo.

Recursos adicionales

Para obtener más información sobre los contactos o el perfil de trabajo, consulta estos recursos: