El proveedor de contactos es un componente de Android potente y flexible que administra el repositorio central de datos sobre personas del dispositivo. El proveedor de contactos es la fuente de datos que ves en la aplicación de contactos del dispositivo, y también puedes acceder a sus datos en tu propia aplicación y transferir datos entre el dispositivo y los servicios en línea. El proveedor admite una amplia variedad de fuentes de datos y trata de administrar la mayor cantidad de datos posible para cada persona, lo que hace que su organización sea compleja. Por este motivo, la API del proveedor incluye un amplio conjunto de clases de contrato e interfaces que facilitan la recuperación y la modificación de datos.
Esta guía describe lo siguiente:
- Estructura de proveedor básica.
- Cómo recuperar datos del proveedor.
- Cómo modificar datos en el proveedor.
- Cómo escribir un adaptador de sincronización para sincronizar datos desde tu servidor con el Proveedor de contactos
Esta guía presupone que conoces los aspectos básicos de los proveedores de contenido de Android. Para obtener más información sobre los proveedores de contenido de Android, consulta la guía de Conceptos básicos sobre proveedores de contenido.
Organización del proveedor de contactos
El proveedor de contactos es un componente del proveedor de contenido de Android que Mantiene tres tipos de datos sobre una persona, cada uno de los cuales corresponde a una tabla que ofrece el proveedor, como se ilustra en la figura 1:

Figura 1: Estructura de las tablas del proveedor de contactos.
Generalmente, las tres tablas se denominan con los nombres de sus clases de contrato. Las clases definen constantes para los URI de contenido, los nombres de columna y los valores de columna que usan las tablas:
-
Tabla
ContactsContract.Contacts
- Filas que representan a diferentes personas, según las agregaciones de las filas de contactos sin procesar.
-
Tabla
ContactsContract.RawContacts
- Son filas que contienen un resumen de los datos de una persona, específicos para un tipo y una cuenta de usuario.
-
Tabla
ContactsContract.Data
- Son las filas que contienen los detalles del contacto sin procesar, como direcciones de correo electrónico o números de teléfono.
Las otras tablas representadas por clases de contratos en ContactsContract
son tablas auxiliares que el proveedor de contactos usa para administrar sus operaciones o admitir funciones específicas en las aplicaciones de telefonía o contactos del dispositivo.
Contactos sin procesar
Un contacto sin procesar representa los datos de una persona que provienen de un solo tipo de cuenta y nombre de cuenta. Dado que el Proveedor de contactos permite más de un servicio en línea como fuente de datos para una persona, el Proveedor de contactos permite varios contactos sin procesar para la misma persona. Varios contactos sin procesar también permiten que un usuario combine los datos de una persona de más de una cuenta del mismo tipo.
La mayoría de los datos de un contacto sin procesar no se almacenan en la tabla ContactsContract.RawContacts
. En su lugar, se almacena en una o más filas de la tabla ContactsContract.Data
. Cada fila de datos tiene una columna Data.RAW_CONTACT_ID
que contiene el valor de RawContacts._ID
de su fila ContactsContract.RawContacts
principal.
Columnas importantes de contactos sin procesar
Las columnas importantes de la tabla ContactsContract.RawContacts
se enumeran en la tabla 1. Lee las notas a continuación de la tabla:
Tabla 1: Columnas importantes de contactos sin procesar.
Nombre de la columna | Usar | Notas |
---|---|---|
ACCOUNT_NAME
|
Es el nombre de la cuenta para el tipo de cuenta que es la fuente de este contacto sin procesar.
Por ejemplo, el nombre de la cuenta de una Cuenta de Google es una de las direcciones de Gmail del propietario del dispositivo. Consulta la siguiente entrada para obtener más información sobre ACCOUNT_TYPE .
|
El formato de este nombre es específico para su tipo de cuenta. No es necesariamente una dirección de correo electrónico. |
ACCOUNT_TYPE
|
Es el tipo de cuenta que es la fuente de este contacto sin procesar. Por ejemplo, el tipo de cuenta de una Cuenta de Google es com.google . Siempre califica el tipo de tu cuenta
con un identificador de dominio para un dominio que poseas o controles. Esto garantizará que tu tipo de cuenta sea único.
|
Un tipo de cuenta que ofrece datos de contactos suele tener un adaptador de sincronización asociado que se sincroniza con el proveedor de contactos. |
DELETED
|
Es la marca "deleted" de un contacto sin procesar. | Este parámetro permite que el proveedor de contactos mantenga la fila de forma interna hasta que los adaptadores de sincronización puedan borrarla de sus servidores y, luego, borrarla del repositorio. |
Notas
A continuación, se incluyen notas importantes sobre la tabla ContactsContract.RawContacts
:
-
El nombre de un contacto sin procesar no se almacena en su fila en
ContactsContract.RawContacts
. En su lugar, se almacena en la tablaContactsContract.Data
, en una filaContactsContract.CommonDataKinds.StructuredName
. Un contacto sin procesar solo tiene una fila de este tipo en la tablaContactsContract.Data
. -
Precaución: Para usar los datos de tu propia cuenta en una fila de contacto sin procesar, primero debes registrarla con
AccountManager
. Para ello, pídeles a los usuarios que agreguen el tipo de cuenta y el nombre de su cuenta a la lista de cuentas. Si no lo haces, el proveedor de contactos borrará automáticamente la fila de contacto sin procesar.Por ejemplo, si deseas que tu app mantenga los datos de contactos de tu servicio basado en la Web con el dominio
com.example.dataservice
y la cuenta del usuario para tu servicio esbecky.sharp@dataservice.example.com
, el usuario primero debe agregar el "tipo" de cuenta (com.example.dataservice
) y el "nombre" de la cuenta (becky.smart@dataservice.example.com
) antes de que tu app pueda agregar filas de contactos sin procesar. Puedes explicarle este requisito al usuario en la documentación o solicitarle que agregue el tipo y el nombre, o ambos. Los tipos y nombres de cuentas se describen con más detalle en la siguiente sección.
Orígenes de los datos de contactos sin procesar
Para comprender cómo funcionan los contactos sin procesar, considera el usuario "Emily Dickinson", que tiene las siguientes tres cuentas de usuario definidas en su dispositivo:
emily.dickinson@gmail.com
emilyd@gmail.com
- Cuenta de Twitter "belle_of_amherst"
Este usuario habilitó la sincronización de contactos para las tres cuentas en la configuración de Cuentas.
Supongamos que Emily Dickinson abre una ventana del navegador, accede a Gmail como emily.dickinson@gmail.com
, abre Contactos y agrega a "Thomas Higginson". Más adelante, accede a Gmail como emilyd@gmail.com
y envía un correo electrónico a "Thomas Higginson", lo que lo agrega automáticamente como contacto. También sigue a "colonel_tom" (el ID de Twitter de Thomas Higginson) en Twitter.
Como resultado de estas acciones, el proveedor de contactos crea tres contactos sin procesar:
-
Es un contacto sin procesar de "Tomás Higginson" asociado con
emily.dickinson@gmail.com
. El tipo de cuenta del usuario es Google. -
Es un segundo contacto sin procesar de "Thomas Higginson" asociado con
emilyd@gmail.com
. El tipo de cuenta de usuario también es Google. Hay un segundo contacto sin procesar, aunque el nombre es idéntico a un nombre anterior, porque la persona se agregó para una cuenta de usuario diferente. - Es un tercer contacto sin procesar para "Thomas Higginson" asociado con "belle_of_amherst". El tipo de cuenta de usuario es Twitter.
Datos
Como se mencionó anteriormente, los datos de un contacto sin procesar se almacenan en una fila ContactsContract.Data
vinculada al valor _ID
del contacto sin procesar. Esto permite que un solo contacto sin procesar tenga varias instancias del mismo tipo de datos, como direcciones de correo electrónico o números de teléfono. Por ejemplo, si "Thomas Higginson" para emilyd@gmail.com
(la fila de contacto sin procesar para Thomas Higginson asociada a la Cuenta de Google emilyd@gmail.com
) tiene una dirección de correo electrónico particular de thigg@gmail.com
y una dirección de correo electrónico laboral de thomas.higginson@gmail.com
, el proveedor de Contactos almacena las dos filas de direcciones de correo electrónico y las vincula al contacto sin procesar.
Ten en cuenta que en esta tabla se guardan diferentes tipos de datos. Las filas de detalles del nombre visible, el número de teléfono, el correo electrónico, la dirección postal, la foto y el sitio web se encuentran en la tabla ContactsContract.Data
. Para ayudar a administrar esto, la tabla ContactsContract.Data
tiene algunas columnas con nombres descriptivos y otras con nombres genéricos. El contenido de una columna de nombre descriptivo tiene el mismo significado independientemente del tipo de datos de la fila, mientras que el contenido de una columna de nombre genérico tiene diferentes significados según el tipo de datos.
Columnas con nombre descriptivo
Algunos ejemplos de columnas con nombre descriptivo:
-
RAW_CONTACT_ID
-
Es el valor de la columna
_ID
del contacto sin procesar para estos datos. -
MIMETYPE
-
Tipo de datos almacenados en esta fila, expresado como un tipo de MIME personalizado. El proveedor de contactos usa los tipos de MIME definidos en las subclases de
ContactsContract.CommonDataKinds
. Estos tipos de MIME son de código abierto y los puede usar cualquier aplicación o adaptador de sincronización que funcione con el Proveedor de contactos. -
IS_PRIMARY
-
Si este tipo de fila de datos puede aparecer más de una vez para un contacto sin procesar, la columna
IS_PRIMARY
marca la fila de datos que contiene los datos principales del tipo. Por ejemplo, si el usuario mantiene presionado un número de teléfono de un contacto y selecciona Establecer como predeterminado, la filaContactsContract.Data
que contiene ese número tendrá su columnaIS_PRIMARY
establecida en un valor distinto de cero.
Columnas con nombre genérico
Hay 15 columnas genéricas llamadas DATA1
a DATA15
que están disponibles de forma general y cuatro columnas genéricas adicionales SYNC1
a SYNC4
que solo deben usar los adaptadores de sincronización. Las constantes de nombres de columnas genéricos siempre funcionan, independientemente del tipo de datos que contenga la fila.
La columna DATA1
está indexada. El proveedor de contactos siempre usa esta columna para los datos que espera que sean el destino más frecuente de una consulta. Por ejemplo, en una fila de correo electrónico, esta columna contiene la dirección de correo electrónico real.
Por convención, la columna DATA15
se reserva para almacenar datos de objetos binarios grandes (BLOB), como miniaturas de fotos.
Nombres de columnas específicos para los diferentes tipos
Para facilitar el trabajo con las columnas de un tipo de fila en particular, el proveedor de contactos también proporciona constantes de nombres de columna específicas del tipo, definidas en subclases de ContactsContract.CommonDataKinds
. Las constantes simplemente asignan un nombre diferente a la misma columna, lo que te ayuda a acceder a los datos de una fila de un tipo en particular.
Por ejemplo, la clase ContactsContract.CommonDataKinds.Email
define constantes de nombres de columna específicas del tipo para una fila ContactsContract.Data
que tiene el tipo de MIME Email.CONTENT_ITEM_TYPE
. La clase contiene la constante ADDRESS
para la columna de dirección de correo electrónico. El valor real de ADDRESS
es "data1", que es el mismo que el nombre genérico de la columna.
Precaución: No agregues tus propios datos personalizados a la tabla ContactsContract.Data
con una fila que tenga uno de los tipos de MIME predefinidos del proveedor. Si lo haces, es posible que pierdas los datos o que el proveedor
no funcione correctamente. Por ejemplo, no debes agregar una fila con el tipo MIME Email.CONTENT_ITEM_TYPE
que contenga un nombre de usuario en lugar de una dirección de correo electrónico en la columna DATA1
. Si usas tu propio tipo MIME personalizado para la fila, puedes definir tus propios nombres de columna específicos del tipo y usar las columnas como desees.
La figura 2 muestra cómo aparecen las columnas descriptivas y las columnas de datos en una fila de ContactsContract.Data
y cómo los nombres de las columnas específicos del tipo se "superponen" a los nombres de las columnas genéricas.

Figura 2: Nombres de columnas de tipo específico y nombres de columnas genéricos.
Clases de nombres de columnas de tipo específico
La tabla 2 indica las clases de nombres de columnas de tipo específico que se usan con más frecuencia:
Tabla 2: Clases de nombres de columnas de tipo específico
Clase de asignación | Tipo de dato | Notas |
---|---|---|
ContactsContract.CommonDataKinds.StructuredName |
Los datos del nombre para el contacto sin procesar asociado con esta fila de datos. | Un contacto sin procesar tiene una sola fila de este tipo. |
ContactsContract.CommonDataKinds.Photo |
La foto principal del contacto sin procesar asociado con esta fila de datos. | Un contacto sin procesar tiene una sola fila de este tipo. |
ContactsContract.CommonDataKinds.Email |
Una dirección de correo electrónico del contacto sin procesar asociado con esta fila de datos. | Un contacto sin procesar puede tener varias direcciones de correo electrónico. |
ContactsContract.CommonDataKinds.StructuredPostal |
Una dirección postal del contacto sin procesar asociado con esta fila de datos. | Un contacto sin procesar puede tener varias direcciones postales. |
ContactsContract.CommonDataKinds.GroupMembership |
Un identificador que vincula al contacto sin procesar con uno de los grupos en el proveedor de contactos. | Los grupos son una característica opcional para el tipo y el nombre de cuenta. Se describen con más detalle en la sección Grupos de contactos. |
Contactos
El Proveedor de contactos combina las filas de contactos sin procesar de todos los tipos de cuentas y nombres de cuentas para formar un contacto. Esto facilita la visualización y modificación de todos los datos que un usuario recopiló sobre una persona. El Proveedor de contactos administra la creación de filas de contactos nuevos y la agregación de contactos sin procesar con una fila de contacto existente. Ni las aplicaciones ni los adaptadores de sincronización pueden agregar contactos, y algunas columnas de una fila de contacto son de solo lectura.
Nota: Si intentas agregar un contacto al proveedor de contactos con un insert()
, recibirás una excepción de UnsupportedOperationException
. Si intentas actualizar una columna que aparece como "solo lectura", se ignorará la actualización.
El Proveedor de contactos crea un contacto nuevo en respuesta a la adición de un nuevo contacto sin procesar que no coincide con ningún contacto existente. El proveedor también hace esto si los datos de un contacto sin procesar existente cambian de tal manera que ya no coinciden con el contacto al que se adjuntaron anteriormente. Si una aplicación o un adaptador de sincronización crea un contacto sin procesar nuevo que sí coincide con un contacto existente, el contacto sin procesar nuevo se agrega al contacto existente.
El Proveedor de contactos vincula una fila de contacto a sus filas de contacto sin procesar con la columna _ID
de la fila de contacto en la tabla Contacts
. La columna CONTACT_ID
de la tabla de contactos sin procesar ContactsContract.RawContacts
contiene valores _ID
para la fila de contactos asociada con cada fila de contactos sin procesar.
La tabla ContactsContract.Contacts
también tiene la columna LOOKUP_KEY
, que es un vínculo "permanente" a la fila de contacto. Dado que el Proveedor de contactos mantiene los contactos automáticamente, puede cambiar el valor de _ID
de una fila de contacto en respuesta a una agregación o sincronización. Incluso si esto sucede, el URI de contenido CONTENT_LOOKUP_URI
combinado con el LOOKUP_KEY
del contacto seguirá apuntando a la fila del contacto, por lo que puedes usar LOOKUP_KEY
para mantener vínculos a los contactos "favoritos", etcétera. Esta columna tiene su propio formato, que no está relacionado con el formato de la columna _ID
.
En la figura 3, se ve cómo las tres tablas principales se relacionan entre sí.

Figura 3: Relaciones de la tabla de contactos, contactos sin procesar y detalles.
Precaución: Si publicas tu app en Google Play Store o si tu app se encuentra en un dispositivo con Android 10 (nivel de API 29) o versiones posteriores, ten en cuenta que un conjunto limitado de campos y métodos de datos de contactos están obsoletos.
En las condiciones mencionadas, el sistema borra periódicamente los valores escritos en estos campos de datos:
-
ContactsContract.ContactOptionsColumns.LAST_TIME_CONTACTED
-
ContactsContract.ContactOptionsColumns.TIMES_CONTACTED
-
ContactsContract.DataUsageStatColumns.LAST_TIME_USED
-
ContactsContract.DataUsageStatColumns.TIMES_USED
También caducan las API usadas para establecer los campos de datos anteriores:
Además, los siguientes campos ya no devuelven contactos frecuentes. Ten en cuenta que algunos de estos campos influyen en la clasificación de los contactos solo cuando estos forman parte de un tipo de datos específico.
-
ContactsContract.Contacts.CONTENT_FREQUENT_URI
-
ContactsContract.Contacts.CONTENT_STREQUENT_URI
-
ContactsContract.Contacts.CONTENT_STREQUENT_FILTER_URI
-
CONTENT_FILTER_URI
(afecta solo a los tipos de datos Email, Phone, Callable y Contactables) -
ENTERPRISE_CONTENT_FILTER_URI
(afecta solo a los tipos de datos Correo electrónico, Teléfono y Llamable)
Si tus apps acceden a estos campos o APIs, o los actualizan, usa métodos alternativos. Por ejemplo, puedes satisfacer ciertos casos de uso con proveedores de contenido privados o con otros datos almacenados en tu app o en los sistemas de backend.
Para verificar que la funcionalidad de tu app no se vea afectada por este cambio, puedes borrar manualmente estos campos de datos. Para ello, ejecuta el siguiente comando de ADB en un dispositivo con Android 4.1 (nivel de API 16) o una versión posterior:
adb shell content delete \ --uri content://com.android.contacts/contacts/delete_usage
Datos de los adaptadores de sincronización
Los usuarios ingresan los datos de los contactos directamente en el dispositivo, pero los datos también fluyen al proveedor de contactos desde los servicios web a través de adaptadores de sincronización, que automatizan la transferencia de datos entre el dispositivo y los servicios. Los adaptadores de sincronización se ejecutan en segundo plano bajo el control del sistema y llaman a los métodos ContentResolver
para administrar los datos.
En Android, el servicio web con el que trabaja un adaptador de sincronización se identifica mediante un tipo de cuenta. Cada adaptador de sincronización funciona con un tipo de cuenta, pero puede admitir varios nombres de cuenta para ese tipo. Los tipos y nombres de las cuentas se describen brevemente en la sección Fuentes de datos de contactos sin procesar. Las siguientes definiciones ofrecen más detalles y describen cómo el tipo y el nombre de la cuenta se relacionan con los adaptadores y servicios de sincronización.
- Tipo de cuenta
-
Identifica un servicio en el que el usuario almacenó datos. La mayoría de las veces, el usuario debe autenticarse con el servicio. Por ejemplo, los Contactos de Google son un tipo de cuenta, identificado por el código
google.com
. Este valor corresponde al tipo de cuenta que usaAccountManager
. - Nombre de la cuenta
- Identifica una cuenta o un acceso en particular para un tipo de cuenta. Las cuentas de Contactos de Google son las mismas que las Cuentas de Google, que tienen una dirección de correo electrónico como nombre de cuenta. Otros servicios pueden usar un nombre de usuario de una sola palabra o un identificador numérico.
Los tipos de cuenta no tienen que ser únicos. Un usuario puede configurar varias cuentas de Contactos de Google y descargar sus datos en el proveedor de contactos. Esto puede ocurrir si el usuario tiene un conjunto de contactos personales para un nombre de cuenta personal y otro conjunto para el trabajo. Los nombres de las cuentas suelen ser únicos. Juntos, identifican un flujo de datos específico entre el proveedor de contactos y un servicio externo.
Si deseas transferir los datos de tu servicio al proveedor de contactos, debes escribir tu propio adaptador de sincronización. Esto se describe con más detalle en la sección Adaptadores de sincronización del Proveedor de contactos.
En la figura 4, se muestra cómo el proveedor de contactos se ajusta al flujo de datos sobre las personas. En el cuadro marcado como "Adaptadores de sincronización", cada adaptador está etiquetado por su tipo de cuenta.

Figura 4: Flujo de datos del proveedor de contactos.
Permisos necesarios
Las aplicaciones que quieran acceder al Proveedor de contactos deben solicitar los siguientes permisos:
- Acceso de lectura a una o más tablas
-
READ_CONTACTS
, especificado enAndroidManifest.xml
con el elemento<uses-permission>
como<uses-permission android:name="android.permission.READ_CONTACTS">
. - Acceso de escritura a una o más tablas
-
WRITE_CONTACTS
, especificado enAndroidManifest.xml
con el elemento<uses-permission>
como<uses-permission android:name="android.permission.WRITE_CONTACTS">
.
Esos permisos no se extienden a los datos del perfil del usuario. El perfil del usuario y sus permisos requeridos se analizan en la siguiente sección, El perfil del usuario.
Recuerda que los datos de los contactos del usuario son personales y sensibles. Los usuarios se preocupan por su privacidad, por lo que no quieren que las aplicaciones recopilen datos sobre ellos o sus contactos. Si no es obvio por qué necesitas permiso para acceder a sus datos de contactos, es posible que le den a tu aplicación calificaciones bajas o que simplemente se nieguen a instalarla.
Perfil del usuario
La tabla ContactsContract.Contacts
tiene una sola fila que contiene datos del perfil del usuario del dispositivo. Estos datos describen el user
del dispositivo, no uno de los contactos del usuario. La fila de contactos del perfil está vinculada a una fila de contactos sin procesar para cada sistema que usa un perfil.
Cada fila de contacto sin procesar del perfil puede tener múltiples filas de datos. Las constantes para acceder al perfil del usuario están disponibles en la clase ContactsContract.Profile
.
El acceso al perfil del usuario requiere permisos especiales. Además de los permisos READ_CONTACTS
y WRITE_CONTACTS
necesarios para leer y escribir, el acceso al perfil del usuario requiere los permisos android.Manifest.permission#READ_PROFILE y android.Manifest.permission#WRITE_PROFILE para el acceso de lectura y escritura, respectivamente.
Recuerda que debes considerar que el perfil de un usuario es sensible. El permiso android.Manifest.permission#READ_PROFILE te permite acceder a los datos de identificación personal del usuario del dispositivo. Asegúrate de indicarle al usuario por qué necesitas permisos de acceso al perfil del usuario en la descripción de tu aplicación.
Para recuperar la fila de contacto que contiene el perfil del usuario, llama a ContentResolver.query()
. Establece el URI de contenido en CONTENT_URI
y no proporciones ningún criterio de selección. También puedes usar este URI de contenido como el URI base para recuperar contactos sin procesar o datos del perfil. Por ejemplo, este fragmento de código recupera datos para el perfil:
Kotlin
// Sets the columns to retrieve for the user profile projection = arrayOf( ContactsContract.Profile._ID, ContactsContract.Profile.DISPLAY_NAME_PRIMARY, ContactsContract.Profile.LOOKUP_KEY, ContactsContract.Profile.PHOTO_THUMBNAIL_URI ) // Retrieves the profile from the Contacts Provider profileCursor = contentResolver.query( ContactsContract.Profile.CONTENT_URI, projection, null, null, null )
Java
// Sets the columns to retrieve for the user profile projection = new String[] { Profile._ID, Profile.DISPLAY_NAME_PRIMARY, Profile.LOOKUP_KEY, Profile.PHOTO_THUMBNAIL_URI }; // Retrieves the profile from the Contacts Provider profileCursor = getContentResolver().query( Profile.CONTENT_URI, projection , null, null, null);
Nota: Si recuperas varias filas de contactos y quieres determinar si una de ellas es el perfil del usuario, prueba la columna IS_USER_PROFILE
de la fila. Esta columna se establece en "1" si el contacto es el perfil del usuario.
Metadatos del proveedor de contactos
El proveedor de contactos administra los datos que registran el estado de los datos de contactos en el repositorio. Estos metadatos sobre el repositorio se almacenan en varios lugares, incluidas las filas de las tablas Raw Contacts, Data y Contacts, la tabla ContactsContract.Settings
y la tabla ContactsContract.SyncState
. En la siguiente tabla, se muestra el efecto de cada uno de estos metadatos:
Tabla 3: Metadatos del proveedor de contactos
Tabla | Columna | Valores | Significado |
---|---|---|---|
ContactsContract.RawContacts |
DIRTY |
"0": No se modificó desde la última sincronización. |
Marca los contactos sin procesar que se cambiaron en el dispositivo y que se deben volver a sincronizar con el servidor. El proveedor de contactos establece el valor automáticamente cuando las aplicaciones para Android actualizan una fila.
Los adaptadores de sincronización que modifican las tablas de datos o de contactos sin procesar siempre deben agregar la cadena |
"1": Cambió desde la última sincronización y debe volver a sincronizarse con el servidor. | |||
ContactsContract.RawContacts |
VERSION |
El número de versión de esta fila. | El proveedor de contactos incrementa automáticamente este valor cada vez que cambia la fila o sus datos relacionados. |
ContactsContract.Data |
DATA_VERSION |
El número de versión de esta fila. | El proveedor de contactos incrementa automáticamente este valor cada vez que se cambia la fila de datos. |
ContactsContract.RawContacts |
SOURCE_ID |
Es un valor de cadena que identifica de forma única este contacto sin procesar para la cuenta en la que se creó. |
Cuando un adaptador de sincronización crea un contacto sin procesar nuevo, esta columna debe establecerse en el ID único del servidor para el contacto sin procesar. Cuando una aplicación para Android crea un nuevo contacto sin procesar, debe dejar esta columna vacía. Esto indica al adaptador de sincronización que debe crear un contacto sin procesar nuevo en el servidor y obtener un valor para SOURCE_ID .
En particular, el ID de la fuente debe ser único para cada tipo de cuenta y debe ser estable en todas las sincronizaciones:
|
ContactsContract.Groups |
GROUP_VISIBLE |
"0": Los contactos de este grupo no deben ser visibles en las IU de las aplicaciones para Android. | Esta columna es para la compatibilidad con servidores que permiten que un usuario oculte contactos en ciertos grupos. |
"1": Los contactos de este grupo pueden ser visibles en las IU de la aplicación. | |||
ContactsContract.Settings |
UNGROUPED_VISIBLE |
"0": Para esta cuenta y este tipo de cuenta, los contactos que no pertenecen a un grupo son invisibles para las IU de las aplicaciones para Android. |
De forma predeterminada, los contactos son invisibles si ninguno de sus contactos sin procesar pertenece a un grupo
(la pertenencia a un grupo de un contacto sin procesar se indica con una o más
filas de ContactsContract.CommonDataKinds.GroupMembership
en la tabla ContactsContract.Data ).
Si configuras esta marca en la fila de la tabla ContactsContract.Settings para un tipo de cuenta y una cuenta, puedes forzar la visibilidad de los contactos sin grupos.
Una forma de usar esta marca es mostrar contactos de servidores que no usan grupos.
|
"1": Para esta cuenta y tipo de cuenta, los contactos que no pertenecen a un grupo son visibles en las IU de la aplicación. | |||
ContactsContract.SyncState |
(todo) | Usa esta tabla para guardar metadatos de tu adaptador de sincronización. | Con esta tabla, puedes almacenar el estado de sincronización y otros datos relacionados con la sincronización de forma persistente en el dispositivo. |
Acceso al proveedor de contactos
En esta sección, se describen los lineamientos para acceder a los datos del proveedor de contactos, con un enfoque en lo siguiente:
- Consultas a entidades.
- Modificación por lotes.
- Recuperación y modificación con intents.
- Integridad de los datos.
En la sección Adaptadores de sincronización del Proveedor de contactos, también se explica con más detalle cómo realizar modificaciones desde un adaptador de sincronización.
Consulta de entidades
Dado que las tablas del proveedor de contactos se organizan en una jerarquía, a menudo es útil recuperar una fila y todas las filas "secundarias" que están vinculadas a ella. Por ejemplo, para mostrar toda la información de una persona, es posible que desees recuperar todas las filas ContactsContract.RawContacts
para una sola fila ContactsContract.Contacts
, o todas las filas ContactsContract.CommonDataKinds.Email
para una sola fila ContactsContract.RawContacts
. Para facilitar esto, el Proveedor de contactos ofrece construcciones de entidades, que actúan como uniones de bases de datos entre tablas.
Una entidad es como una tabla compuesta de columnas específicas de una tabla principal y su tabla secundaria.
Cuando consultas una entidad, proporcionas una proyección y criterios de búsqueda basados en las columnas disponibles de la entidad. El resultado es un Cursor
que contiene una fila para cada fila de la tabla secundaria que se recuperó. Por ejemplo, si consultas ContactsContract.Contacts.Entity
para obtener el nombre de un contacto y todas las filas de ContactsContract.CommonDataKinds.Email
para todos los contactos sin procesar de ese nombre, obtendrás un Cursor
que contiene una fila para cada fila de ContactsContract.CommonDataKinds.Email
.
Las entidades simplifican las consultas. Con una entidad, puedes recuperar todos los datos de un contacto o contacto sin procesar de una vez, en lugar de tener que consultar primero la tabla principal para obtener un ID y, luego, tener que consultar la tabla secundaria con ese ID. Además, el Proveedor de contactos procesa una consulta en relación con una entidad en una sola transacción, lo que garantiza que los datos recuperados sean coherentes de forma interna.
Nota: Por lo general, una entidad no contiene todas las columnas de la tabla principal y secundaria. Si intentas trabajar con un nombre de columna que no se encuentra en la lista de constantes de nombres de columna de la entidad, recibirás un Exception
.
El siguiente fragmento de código muestra cómo recuperar todas las filas de contacto sin procesar de un contacto. El fragmento es parte de una aplicación más grande que tiene dos actividades: "principal" y "detalle". La actividad principal muestra una lista de filas de contactos. Cuando el usuario selecciona una, la actividad envía su ID a la actividad de detalles. La actividad de detalles usa ContactsContract.Contacts.Entity
para mostrar todas las filas de datos de todos los contactos sin procesar asociados con el contacto seleccionado.
Este fragmento se extrae de la actividad "detalle":
Kotlin
... /* * Appends the entity path to the URI. In the case of the Contacts Provider, the * expected URI is content://com.google.contacts/#/entity (# is the ID value). */ contactUri = Uri.withAppendedPath( contactUri, ContactsContract.Contacts.Entity.CONTENT_DIRECTORY ) // Initializes the loader identified by LOADER_ID. loaderManager.initLoader( LOADER_ID, // The identifier of the loader to initialize null, // Arguments for the loader (in this case, none) this // The context of the activity ) // Creates a new cursor adapter to attach to the list view cursorAdapter = SimpleCursorAdapter( this, // the context of the activity R.layout.detail_list_item, // the view item containing the detail widgets mCursor, // the backing cursor fromColumns, // the columns in the cursor that provide the data toViews, // the views in the view item that display the data 0) // flags // Sets the ListView's backing adapter. rawContactList.adapter = cursorAdapter ... override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> { /* * Sets the columns to retrieve. * RAW_CONTACT_ID is included to identify the raw contact associated with the data row. * DATA1 contains the first column in the data row (usually the most important one). * MIMETYPE indicates the type of data in the data row. */ val projection: Array<String> = arrayOf( ContactsContract.Contacts.Entity.RAW_CONTACT_ID, ContactsContract.Contacts.Entity.DATA1, ContactsContract.Contacts.Entity.MIMETYPE ) /* * Sorts the retrieved cursor by raw contact id, to keep all data rows for a single raw * contact collated together. */ val sortOrder = "${ContactsContract.Contacts.Entity.RAW_CONTACT_ID} ASC" /* * Returns a new CursorLoader. The arguments are similar to * ContentResolver.query(), except for the Context argument, which supplies the location of * the ContentResolver to use. */ return CursorLoader( applicationContext, // The activity's context contactUri, // The entity content URI for a single contact projection, // The columns to retrieve null, // Retrieve all the raw contacts and their data rows. null, // sortOrder // Sort by the raw contact ID. ) }
Java
... /* * Appends the entity path to the URI. In the case of the Contacts Provider, the * expected URI is content://com.google.contacts/#/entity (# is the ID value). */ contactUri = Uri.withAppendedPath( contactUri, ContactsContract.Contacts.Entity.CONTENT_DIRECTORY); // Initializes the loader identified by LOADER_ID. getLoaderManager().initLoader( LOADER_ID, // The identifier of the loader to initialize null, // Arguments for the loader (in this case, none) this); // The context of the activity // Creates a new cursor adapter to attach to the list view cursorAdapter = new SimpleCursorAdapter( this, // the context of the activity R.layout.detail_list_item, // the view item containing the detail widgets mCursor, // the backing cursor fromColumns, // the columns in the cursor that provide the data toViews, // the views in the view item that display the data 0); // flags // Sets the ListView's backing adapter. rawContactList.setAdapter(cursorAdapter); ... @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { /* * Sets the columns to retrieve. * RAW_CONTACT_ID is included to identify the raw contact associated with the data row. * DATA1 contains the first column in the data row (usually the most important one). * MIMETYPE indicates the type of data in the data row. */ String[] projection = { ContactsContract.Contacts.Entity.RAW_CONTACT_ID, ContactsContract.Contacts.Entity.DATA1, ContactsContract.Contacts.Entity.MIMETYPE }; /* * Sorts the retrieved cursor by raw contact id, to keep all data rows for a single raw * contact collated together. */ String sortOrder = ContactsContract.Contacts.Entity.RAW_CONTACT_ID + " ASC"; /* * Returns a new CursorLoader. The arguments are similar to * ContentResolver.query(), except for the Context argument, which supplies the location of * the ContentResolver to use. */ return new CursorLoader( getApplicationContext(), // The activity's context contactUri, // The entity content URI for a single contact projection, // The columns to retrieve null, // Retrieve all the raw contacts and their data rows. null, // sortOrder); // Sort by the raw contact ID. }
Cuando finaliza la carga, LoaderManager
invoca una devolución de llamada a onLoadFinished()
. Uno de los argumentos entrantes de este método es un Cursor
con los resultados de la consulta. En tu propia app, puedes obtener los datos de este Cursor
para mostrarlos o trabajar con ellos más adelante.
Modificación por lotes
Siempre que sea posible, debes insertar, actualizar y borrar datos en el proveedor de contactos en "modo por lotes". Para ello, crea un ArrayList
de objetos ContentProviderOperation
y llama a applyBatch()
. Debido a que el proveedor de contactos realiza todas las operaciones en un applyBatch()
en una sola transacción, tus modificaciones nunca dejarán el repositorio de contactos en un estado incoherente. Una modificación por lotes también facilita la inserción de un contacto sin procesar y sus datos de detalle al mismo tiempo.
Nota: Para modificar un contacto sin procesar único, considera enviar un intent a la aplicación de contactos del dispositivo en lugar de controlar la modificación en tu app. Esto se describe con más detalle en la sección Recuperación y modificación con intents.
Puntos de productividad
Una modificación por lotes que contiene una gran cantidad de operaciones puede bloquear otros procesos, lo que genera una mala experiencia del usuario en general. Para organizar todas las modificaciones que deseas realizar en la menor cantidad posible de listas separadas y, al mismo tiempo, evitar que bloqueen el sistema, debes establecer puntos de rendimiento para una o más operaciones.
Un punto de rendimiento es un objeto ContentProviderOperation
que tiene su valor isYieldAllowed()
establecido en true
. Cuando el proveedor de contactos encuentra un punto de rendimiento, detiene su trabajo para permitir que se ejecuten otros procesos y cierra la transacción actual. Cuando el proveedor se reinicia, continúa con la siguiente operación en ArrayList
y comienza una nueva transacción.
Los puntos de rendimiento generan más de una transacción por llamada a applyBatch()
. Por este motivo, debes establecer un punto de rendimiento para la última operación de un conjunto de filas relacionadas.
Por ejemplo, debes establecer un punto de rendimiento para la última operación de un conjunto que agrega filas de contactos sin procesar y sus filas de datos asociadas, o la última operación para un conjunto de filas relacionadas con un solo contacto.
Los puntos de productividad también son una unidad de operación atómica. Todos los accesos entre dos puntos de rendimiento se completarán o fallarán como una sola unidad. Si no configuras ningún punto de rendimiento, la operación atómica más pequeña será todo el lote de operaciones. Si usas puntos de rendimiento, evitas que las operaciones degraden el rendimiento del sistema y, al mismo tiempo, te aseguras de que un subconjunto de operaciones sea atómico.
Referencias inversas de modificación
Cuando insertas una nueva fila de contacto sin procesar y sus filas de datos asociadas como un conjunto de objetos ContentProviderOperation
, debes vincular las filas de datos a la fila de contacto sin procesar insertando el valor _ID
del contacto sin procesar como el valor RAW_CONTACT_ID
. Sin embargo, este valor no está disponible cuando creas el ContentProviderOperation
para la fila de datos, ya que aún no aplicaste el ContentProviderOperation
para la fila de contacto sin procesar. Para solucionar este problema, la clase ContentProviderOperation.Builder
tiene el método withValueBackReference()
.
Este método te permite insertar o modificar una columna con el resultado de una operación anterior.
El método withValueBackReference()
tiene dos argumentos:
-
key
- Es la clave de un par clave-valor. El valor de este argumento debe ser el nombre de una columna en la tabla que modificas.
-
previousResult
-
Índice basado en 0 de un valor en el array de
objetos
ContentProviderResult
deapplyBatch()
. A medida que se aplican las operaciones por lotes, el resultado de cada operación se almacena en un array intermedio de resultados. El valor depreviousResult
es el índice de uno de estos resultados, que se recupera y almacena con el valor dekey
. Esto te permite insertar un nuevo registro de contacto sin procesar y recuperar su valor_ID
, y, luego, hacer una "referencia inversa" al valor cuando agregas una filaContactsContract.Data
.El array de resultados completo se crea cuando llamas a
applyBatch()
por primera vez, con un tamaño igual al deArrayList
de los objetosContentProviderOperation
que proporcionas. Sin embargo, todos los elementos del array de resultados se establecen ennull
y, si intentas hacer una referencia inversa a un resultado de una operación que aún no se aplicó,withValueBackReference()
arroja unException
.
Los siguientes fragmentos de código muestran cómo insertar un nuevo contacto sin procesar y datos por lotes. Incluyen código que establece un punto de rendimiento y usa una referencia inversa.
El primer fragmento de código devuelve datos de contacto desde la IU. En este punto, el usuario ya seleccionó la cuenta para la que se debe agregar el nuevo contacto sin procesar.
Kotlin
// Creates a contact entry from the current UI values, using the currently-selected account. private fun createContactEntry() { /* * Gets values from the UI */ val name = contactNameEditText.text.toString() val phone = contactPhoneEditText.text.toString() val email = contactEmailEditText.text.toString() val phoneType: String = contactPhoneTypes[mContactPhoneTypeSpinner.selectedItemPosition] val emailType: String = contactEmailTypes[mContactEmailTypeSpinner.selectedItemPosition]
Java
// Creates a contact entry from the current UI values, using the currently-selected account. protected void createContactEntry() { /* * Gets values from the UI */ String name = contactNameEditText.getText().toString(); String phone = contactPhoneEditText.getText().toString(); String email = contactEmailEditText.getText().toString(); int phoneType = contactPhoneTypes.get( contactPhoneTypeSpinner.getSelectedItemPosition()); int emailType = contactEmailTypes.get( contactEmailTypeSpinner.getSelectedItemPosition());
El siguiente fragmento crea una operación para insertar la fila de contacto sin procesar en la tabla ContactsContract.RawContacts
:
Kotlin
/* * Prepares the batch operation for inserting a new raw contact and its data. Even if * the Contacts Provider does not have any data for this person, you can't add a Contact, * only a raw contact. The Contacts Provider will then add a Contact automatically. */ // Creates a new array of ContentProviderOperation objects. val ops = arrayListOf<ContentProviderOperation>() /* * Creates a new raw contact with its account type (server type) and account name * (user's account). Remember that the display name is not stored in this row, but in a * StructuredName data row. No other data is required. */ var op: ContentProviderOperation.Builder = ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.name) .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.type) // Builds the operation and adds it to the array of operations ops.add(op.build())
Java
/* * Prepares the batch operation for inserting a new raw contact and its data. Even if * the Contacts Provider does not have any data for this person, you can't add a Contact, * only a raw contact. The Contacts Provider will then add a Contact automatically. */ // Creates a new array of ContentProviderOperation objects. ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); /* * Creates a new raw contact with its account type (server type) and account name * (user's account). Remember that the display name is not stored in this row, but in a * StructuredName data row. No other data is required. */ ContentProviderOperation.Builder op = ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.getType()) .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.getName()); // Builds the operation and adds it to the array of operations ops.add(op.build());
A continuación, el código crea filas de datos para el nombre para mostrar, el teléfono y el correo electrónico.
Cada objeto compilador de operaciones usa withValueBackReference()
para obtener el RAW_CONTACT_ID
. Los puntos de referencia
hacen referencia al objeto ContentProviderResult
de la primera operación,
que agrega la fila de contacto sin procesar y devuelve su nuevo valor de _ID
. Como resultado, cada fila de datos se vincula automáticamente por su
RAW_CONTACT_ID
a la nueva fila ContactsContract.RawContacts
a la que pertenece.
El objeto ContentProviderOperation.Builder
que agrega la fila de correo electrónico se marca con withYieldAllowed()
, lo que establece un punto de rendimiento:
Kotlin
// Creates the display name for the new raw contact, as a StructuredName data row. op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) /* * withValueBackReference sets the value of the first argument to the value of * the ContentProviderResult indexed by the second argument. In this particular * call, the raw contact ID column of the StructuredName data row is set to the * value of the result returned by the first operation, which is the one that * actually adds the raw contact row. */ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) // Sets the data row's MIME type to StructuredName .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) // Sets the data row's display name to the name in the UI. .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name) // Builds the operation and adds it to the array of operations ops.add(op.build()) // Inserts the specified phone number and type as a Phone data row op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) /* * Sets the value of the raw contact id column to the new raw contact ID returned * by the first operation in the batch. */ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) // Sets the data row's MIME type to Phone .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) // Sets the phone number and type .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone) .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType) // Builds the operation and adds it to the array of operations ops.add(op.build()) // Inserts the specified email and type as a Phone data row op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) /* * Sets the value of the raw contact id column to the new raw contact ID returned * by the first operation in the batch. */ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) // Sets the data row's MIME type to Email .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) // Sets the email address and type .withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email) .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType) /* * Demonstrates a yield point. At the end of this insert, the batch operation's thread * will yield priority to other threads. Use after every set of operations that affect a * single contact, to avoid degrading performance. */ op.withYieldAllowed(true) // Builds the operation and adds it to the array of operations ops.add(op.build())
Java
// Creates the display name for the new raw contact, as a StructuredName data row. op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) /* * withValueBackReference sets the value of the first argument to the value of * the ContentProviderResult indexed by the second argument. In this particular * call, the raw contact ID column of the StructuredName data row is set to the * value of the result returned by the first operation, which is the one that * actually adds the raw contact row. */ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) // Sets the data row's MIME type to StructuredName .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) // Sets the data row's display name to the name in the UI. .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name); // Builds the operation and adds it to the array of operations ops.add(op.build()); // Inserts the specified phone number and type as a Phone data row op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) /* * Sets the value of the raw contact id column to the new raw contact ID returned * by the first operation in the batch. */ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) // Sets the data row's MIME type to Phone .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) // Sets the phone number and type .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone) .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType); // Builds the operation and adds it to the array of operations ops.add(op.build()); // Inserts the specified email and type as a Phone data row op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) /* * Sets the value of the raw contact id column to the new raw contact ID returned * by the first operation in the batch. */ .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) // Sets the data row's MIME type to Email .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) // Sets the email address and type .withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email) .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType); /* * Demonstrates a yield point. At the end of this insert, the batch operation's thread * will yield priority to other threads. Use after every set of operations that affect a * single contact, to avoid degrading performance. */ op.withYieldAllowed(true); // Builds the operation and adds it to the array of operations ops.add(op.build());
El último fragmento muestra la llamada a applyBatch()
que inserta el nuevo contacto sin procesar y las filas de datos.
Kotlin
// Ask the Contacts Provider to create a new contact Log.d(TAG, "Selected account: ${mSelectedAccount.name} (${mSelectedAccount.type})") Log.d(TAG, "Creating contact: $name") /* * Applies the array of ContentProviderOperation objects in batch. The results are * discarded. */ try { contentResolver.applyBatch(ContactsContract.AUTHORITY, ops) } catch (e: Exception) { // Display a warning val txt: String = getString(R.string.contactCreationFailure) Toast.makeText(applicationContext, txt, Toast.LENGTH_SHORT).show() // Log exception Log.e(TAG, "Exception encountered while inserting contact: $e") } }
Java
// Ask the Contacts Provider to create a new contact Log.d(TAG,"Selected account: " + selectedAccount.getName() + " (" + selectedAccount.getType() + ")"); Log.d(TAG,"Creating contact: " + name); /* * Applies the array of ContentProviderOperation objects in batch. The results are * discarded. */ try { getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); } catch (Exception e) { // Display a warning Context ctx = getApplicationContext(); CharSequence txt = getString(R.string.contactCreationFailure); int duration = Toast.LENGTH_SHORT; Toast toast = Toast.makeText(ctx, txt, duration); toast.show(); // Log exception Log.e(TAG, "Exception encountered while inserting contact: " + e); } }
Las operaciones por lotes también te permiten implementar el control de simultaneidad optimista, un método para aplicar transacciones de modificación sin tener que bloquear el repositorio subyacente. Para usar este método, aplica la transacción y, luego, verifica si se realizaron otras modificaciones al mismo tiempo. Si detectas que se produjo una modificación incoherente, debes revertir la transacción y volver a intentarlo.
El control de simultaneidad optimista es útil para un dispositivo móvil, en el que solo hay un usuario a la vez y los accesos simultáneos a un repositorio de datos son poco frecuentes. Como no se usa el bloqueo, no se pierde tiempo en establecer bloqueos ni en esperar a que otras transacciones liberen sus bloqueos.
Para usar el control de simultaneidad optimista mientras actualizas una sola fila de ContactsContract.RawContacts
, sigue estos pasos:
-
Recupera la columna
VERSION
del contacto sin procesar junto con los demás datos que recuperes. -
Crea un objeto
ContentProviderOperation.Builder
adecuado para aplicar una restricción con el métodonewAssertQuery(Uri)
. Para el URI de contenido, usaRawContacts.CONTENT_URI
con el_ID
del contacto sin procesar anexado. -
Para el objeto
ContentProviderOperation.Builder
, llama awithValue()
para comparar la columnaVERSION
con el número de versión que acabas de recuperar. -
Para el mismo
ContentProviderOperation.Builder
, llama awithExpectedCount()
para asegurarte de que esta aserción solo pruebe una fila. -
Llama a
build()
para crear el objetoContentProviderOperation
y, luego, agrégalo como el primer objeto en elArrayList
que pasas aapplyBatch()
. - Aplica la transacción por lotes.
Si otra operación actualiza la fila de contacto sin procesar entre el momento en que lees la fila y el momento en que intentas modificarla, la "afirmación" ContentProviderOperation
fallará y se revertirá todo el lote de operaciones. Luego, puedes volver a intentarlo con el lote o realizar alguna otra acción.
En el siguiente fragmento, se muestra cómo crear una "afirmación" ContentProviderOperation
después de consultar un solo contacto sin procesar con un CursorLoader
:
Kotlin
/* * The application uses CursorLoader to query the raw contacts table. The system calls this method * when the load is finished. */ override fun onLoadFinished(loader: Loader<Cursor>, cursor: Cursor) { // Gets the raw contact's _ID and VERSION values rawContactID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID)) mVersion = cursor.getInt(cursor.getColumnIndex(SyncColumns.VERSION)) } ... // Sets up a Uri for the assert operation val rawContactUri: Uri = ContentUris.withAppendedId( ContactsContract.RawContacts.CONTENT_URI, rawContactID ) // Creates a builder for the assert operation val assertOp: ContentProviderOperation.Builder = ContentProviderOperation.newAssertQuery(rawContactUri).apply { // Adds the assertions to the assert operation: checks the version withValue(SyncColumns.VERSION, mVersion) // and count of rows tested withExpectedCount(1) } // Creates an ArrayList to hold the ContentProviderOperation objects val ops = arrayListOf<ContentProviderOperation>() ops.add(assertOp.build()) // You would add the rest of your batch operations to "ops" here ... // Applies the batch. If the assert fails, an Exception is thrown try { val results: Array<ContentProviderResult> = contentResolver.applyBatch(AUTHORITY, ops) } catch (e: OperationApplicationException) { // Actions you want to take if the assert operation fails go here }
Java
/* * The application uses CursorLoader to query the raw contacts table. The system calls this method * when the load is finished. */ public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { // Gets the raw contact's _ID and VERSION values rawContactID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID)); mVersion = cursor.getInt(cursor.getColumnIndex(SyncColumns.VERSION)); } ... // Sets up a Uri for the assert operation Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactID); // Creates a builder for the assert operation ContentProviderOperation.Builder assertOp = ContentProviderOperation.newAssertQuery(rawContactUri); // Adds the assertions to the assert operation: checks the version and count of rows tested assertOp.withValue(SyncColumns.VERSION, mVersion); assertOp.withExpectedCount(1); // Creates an ArrayList to hold the ContentProviderOperation objects ArrayList ops = new ArrayList<ContentProviderOperation>; ops.add(assertOp.build()); // You would add the rest of your batch operations to "ops" here ... // Applies the batch. If the assert fails, an Exception is thrown try { ContentProviderResult[] results = getContentResolver().applyBatch(AUTHORITY, ops); } catch (OperationApplicationException e) { // Actions you want to take if the assert operation fails go here }
Recuperación y modificación con intents
Enviar un intent a la aplicación de contactos del dispositivo te permite acceder al Proveedor de contactos de forma indirecta. El intent inicia la IU de la aplicación de contactos del dispositivo, en la que los usuarios pueden realizar tareas relacionadas con los contactos. Con este tipo de acceso, los usuarios pueden hacer lo siguiente:
- Seleccionar un contacto de una lista y hacer que se le devuelva a tu aplicación para continuar trabajando allí.
- Editar los datos de un contacto existente
- Insertar un nuevo contacto sin procesar para cualquiera de sus cuentas.
- Eliminar un contacto o datos de contacto.
Si el usuario inserta o actualiza datos, primero puedes recopilarlos y enviarlos como parte de la intención.
Cuando usas intents para acceder al proveedor de contactos a través de la aplicación de contactos del dispositivo, no tienes que escribir tu propia IU ni código para acceder al proveedor. Tampoco tienes que solicitar permiso para leer o escribir en el proveedor. La aplicación de contactos del dispositivo puede delegarte permiso de lectura para un contacto y, como realizas modificaciones en el proveedor a través de otra aplicación, no es necesario que tengas permisos de escritura.
El proceso general para enviar una intención de acceder a un proveedor se describe en detalle en la guía
Aspectos básicos sobre el proveedor de contenido, en la sección "Acceso a los datos a través de intents". En la tabla 4, se resumen la acción, el tipo de MIME y los valores de datos que usas para las tareas disponibles, mientras que los valores de elementos adicionales que puedes usar con putExtra()
se enumeran en la documentación de referencia de ContactsContract.Intents.Insert
:
Tabla 4. Intenciones del proveedor de contactos.
Tarea | Acción | Datos | tipo de MIME | Notas |
---|---|---|---|---|
Cómo elegir un contacto de una lista | ACTION_PICK |
Uno de los siguientes:
|
Sin uso |
Muestra una lista de contactos sin procesar o una lista de datos de un contacto sin procesar, según el tipo de URI de contenido que proporciones.
Llama a
|
Cómo insertar un contacto sin procesar nuevo | Insert.ACTION |
N/A |
RawContacts.CONTENT_TYPE , tipo de MIME para un conjunto de contactos sin procesar.
|
Muestra la pantalla Agregar contacto de la aplicación de contactos del dispositivo. Se muestran los valores de extras que agregas a la intención. Si se envía con startActivityForResult() , el URI de contenido del contacto sin procesar recién agregado se devuelve al método de devolución de llamada onActivityResult() de tu actividad en el argumento Intent , en el campo "data". Para obtener el valor, llama a getData() .
|
Cómo editar un contacto | ACTION_EDIT |
CONTENT_LOOKUP_URI para el contacto. La actividad del editor permitirá al usuario editar cualquiera de los datos asociados
con este contacto.
|
Contacts.CONTENT_ITEM_TYPE , un solo contacto. |
Muestra la pantalla "Editar contacto" de la aplicación de contactos. Se muestran los valores de extras que agregas al intent. Cuando el usuario hace clic en Listo para guardar los cambios, tu actividad vuelve al primer plano. |
Mostrar un selector que también puede agregar datos | ACTION_INSERT_OR_EDIT |
N/A |
CONTENT_ITEM_TYPE
|
Este intent siempre muestra la pantalla del selector de la app de Contactos. El usuario puede elegir un contacto para editarlo o agregar uno nuevo. Aparece la pantalla de edición o la de agregar, según la elección del usuario, y se muestran los datos adicionales que pasas en el intent. Si tu app muestra datos de contacto, como un correo electrónico o un número de teléfono, usa este intent para permitir que el usuario agregue los datos a un contacto existente.
contacto,
Nota: No es necesario enviar un valor de nombre en los extras de esta intención, ya que el usuario siempre elige un nombre existente o agrega uno nuevo. Además, si envías un nombre y el usuario elige editarlo, la app de Contactos mostrará el nombre que envíes y sobrescribirá el valor anterior. Si el usuario no lo nota y guarda la edición, se perderá el valor anterior. |
La app de contactos del dispositivo no te permite borrar un contacto sin procesar ni ninguno de sus datos con un intent. En su lugar, para borrar un contacto sin procesar, usa
ContentResolver.delete()
o ContentProviderOperation.newDelete()
.
En el siguiente fragmento, se muestra cómo construir y enviar un intent que inserta un nuevo contacto sin procesar y datos:
Kotlin
// Gets values from the UI val name = contactNameEditText.text.toString() val phone = contactPhoneEditText.text.toString() val email = contactEmailEditText.text.toString() val company = companyName.text.toString() val jobtitle = jobTitle.text.toString() /* * Demonstrates adding data rows as an array list associated with the DATA key */ // Defines an array list to contain the ContentValues objects for each row val contactData = arrayListOf<ContentValues>() /* * Defines the raw contact row */ // Sets up the row as a ContentValues object val rawContactRow = ContentValues().apply { // Adds the account type and name to the row put(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.type) put(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.name) } // Adds the row to the array contactData.add(rawContactRow) /* * Sets up the phone number data row */ // Sets up the row as a ContentValues object val phoneRow = ContentValues().apply { // Specifies the MIME type for this data row (all data rows must be marked by their type) put(ContactsContract.Data.MIMETYPE,ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) // Adds the phone number and its type to the row put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone) } // Adds the row to the array contactData.add(phoneRow) /* * Sets up the email data row */ // Sets up the row as a ContentValues object val emailRow = ContentValues().apply { // Specifies the MIME type for this data row (all data rows must be marked by their type) put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) // Adds the email address and its type to the row put(ContactsContract.CommonDataKinds.Email.ADDRESS, email) } // Adds the row to the array contactData.add(emailRow) // Creates a new intent for sending to the device's contacts application val insertIntent = Intent(ContactsContract.Intents.Insert.ACTION).apply { // Sets the MIME type to the one expected by the insertion activity type = ContactsContract.RawContacts.CONTENT_TYPE // Sets the new contact name putExtra(ContactsContract.Intents.Insert.NAME, name) // Sets the new company and job title putExtra(ContactsContract.Intents.Insert.COMPANY, company) putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobtitle) /* * Adds the array to the intent's extras. It must be a parcelable object in order to * travel between processes. The device's contacts app expects its key to be * Intents.Insert.DATA */ putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData) } // Send out the intent to start the device's contacts app in its add contact activity. startActivity(insertIntent)
Java
// Gets values from the UI String name = contactNameEditText.getText().toString(); String phone = contactPhoneEditText.getText().toString(); String email = contactEmailEditText.getText().toString(); String company = companyName.getText().toString(); String jobtitle = jobTitle.getText().toString(); // Creates a new intent for sending to the device's contacts application Intent insertIntent = new Intent(ContactsContract.Intents.Insert.ACTION); // Sets the MIME type to the one expected by the insertion activity insertIntent.setType(ContactsContract.RawContacts.CONTENT_TYPE); // Sets the new contact name insertIntent.putExtra(ContactsContract.Intents.Insert.NAME, name); // Sets the new company and job title insertIntent.putExtra(ContactsContract.Intents.Insert.COMPANY, company); insertIntent.putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobtitle); /* * Demonstrates adding data rows as an array list associated with the DATA key */ // Defines an array list to contain the ContentValues objects for each row ArrayList<ContentValues> contactData = new ArrayList<ContentValues>(); /* * Defines the raw contact row */ // Sets up the row as a ContentValues object ContentValues rawContactRow = new ContentValues(); // Adds the account type and name to the row rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_TYPE, selectedAccount.getType()); rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_NAME, selectedAccount.getName()); // Adds the row to the array contactData.add(rawContactRow); /* * Sets up the phone number data row */ // Sets up the row as a ContentValues object ContentValues phoneRow = new ContentValues(); // Specifies the MIME type for this data row (all data rows must be marked by their type) phoneRow.put( ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE ); // Adds the phone number and its type to the row phoneRow.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone); // Adds the row to the array contactData.add(phoneRow); /* * Sets up the email data row */ // Sets up the row as a ContentValues object ContentValues emailRow = new ContentValues(); // Specifies the MIME type for this data row (all data rows must be marked by their type) emailRow.put( ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE ); // Adds the email address and its type to the row emailRow.put(ContactsContract.CommonDataKinds.Email.ADDRESS, email); // Adds the row to the array contactData.add(emailRow); /* * Adds the array to the intent's extras. It must be a parcelable object in order to * travel between processes. The device's contacts app expects its key to be * Intents.Insert.DATA */ insertIntent.putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData); // Send out the intent to start the device's contacts app in its add contact activity. startActivity(insertIntent);
Integridad de los datos
Dado que el repositorio de contactos contiene datos importantes y sensibles que los usuarios esperan que sean correctos y estén actualizados, el proveedor de contactos tiene reglas bien definidas para la integridad de los datos. Es tu responsabilidad cumplir con estas reglas cuando modifiques los datos de los contactos. Las reglas importantes se enumeran aquí:
-
Siempre agrega una fila de
ContactsContract.CommonDataKinds.StructuredName
por cada fila deContactsContract.RawContacts
que agregues. -
Una fila
ContactsContract.RawContacts
sin una filaContactsContract.CommonDataKinds.StructuredName
en la tablaContactsContract.Data
puede causar problemas durante la agregación. -
Siempre vincula las filas de
ContactsContract.Data
nuevas a su fila deContactsContract.RawContacts
principal. -
Una fila de
ContactsContract.Data
que no esté vinculada a unContactsContract.RawContacts
no se verá en la aplicación de contactos del dispositivo y podría causar problemas con los adaptadores de sincronización. - Modifica datos solo para los contactos sin procesar que te pertenecen.
- Recuerda que el proveedor de contactos suele administrar datos de varios tipos de cuentas o servicios en línea diferentes. Debes asegurarte de que tu aplicación solo modifique o borre datos de las filas que te pertenecen y que solo inserte datos con un tipo y un nombre de cuenta que tú controles.
-
Siempre usa las constantes definidas en
ContactsContract
y sus subclases para las autoridades, los URIs de contenido, las rutas de URI, los nombres de columna, los tipos de MIME y los valores deTYPE
. - El uso de estas constantes te ayuda a evitar errores. También recibirás notificaciones con advertencias del compilador si alguna de las constantes está obsoleta.
Filas de datos personalizados
Si creas y usas tus propios tipos de MIME personalizados, puedes insertar, editar, borrar y recuperar tus propias filas de datos en la tabla ContactsContract.Data
. Tus filas se limitan a usar la columna definida en ContactsContract.DataColumns
, aunque puedes asignar tus propios nombres de columna específicos del tipo a los nombres de columna predeterminados. En la aplicación de contactos del dispositivo, se muestran los datos de tus filas, pero no se pueden editar ni borrar, y los usuarios no pueden agregar datos adicionales. Para permitir que los usuarios modifiquen tus filas de datos personalizados, debes proporcionar una actividad de editor en tu propia aplicación.
Para mostrar tus datos personalizados, proporciona un archivo contacts.xml
que contenga un elemento <ContactsAccountType>
y uno o más de sus elementos secundarios <ContactsDataKind>
. Esto se describe con más detalle en la sección <ContactsDataKind> element
.
Para obtener más información sobre los tipos de MIME personalizados, consulta la guía Cómo crear un proveedor de contenido.
Adaptadores de sincronización del proveedor de contactos
El proveedor de contactos está diseñado específicamente para controlar la sincronización de los datos de contactos entre un dispositivo y un servicio en línea. Esto permite que los usuarios descarguen datos existentes en un dispositivo nuevo y suban datos existentes a una cuenta nueva. La sincronización también garantiza que los usuarios tengan los datos más recientes a mano, independientemente de la fuente de las adiciones y los cambios. Otra ventaja de la sincronización es que hace que los datos de los contactos estén disponibles incluso cuando el dispositivo no está conectado a la red.
Si bien puedes implementar la sincronización de varias maneras, el sistema Android proporciona un framework de sincronización de complementos que automatiza las siguientes tareas:
- Comprobación de la disponibilidad de la red.
- Programación y ejecución de la sincronización en función de las preferencias del usuario.
- Reinicio de sincronizaciones detenidas.
Para usar este marco de trabajo, debes proporcionar un complemento de adaptador de sincronización. Cada adaptador de sincronización es único para un servicio y un proveedor de contenido, pero puede controlar varios nombres de cuentas para el mismo servicio. El marco de trabajo también permite varios adaptadores de sincronización para el mismo servicio y proveedor.
Clases y archivos del adaptador de sincronización
Implementas un adaptador de sincronización como una subclase de AbstractThreadedSyncAdapter
y lo instalas como parte de una aplicación para Android. El sistema aprende sobre el adaptador de sincronización a partir de los elementos del manifiesto de tu aplicación y de un archivo XML especial al que apunta el manifiesto. El archivo XML define el tipo de cuenta para el servicio en línea y la autoridad para el proveedor de contenido, que, en conjunto, identifican de forma única el adaptador. El adaptador de sincronización no se activa hasta que el usuario agrega una cuenta para el tipo de cuenta del adaptador de sincronización y habilita la sincronización para el proveedor de contenido con el que se sincroniza el adaptador de sincronización. En ese momento, el sistema comienza a administrar el adaptador y lo llama según sea necesario para sincronizar el proveedor de contenido y el servidor.
Nota: Usar un tipo de cuenta como parte de la identificación del adaptador de sincronización permite que el sistema detecte y agrupe los adaptadores de sincronización que acceden a diferentes servicios de la misma organización. Por ejemplo, todos los adaptadores de sincronización para los servicios en línea de Google tienen el mismo tipo de cuenta com.google
. Cuando los usuarios agregan una Cuenta de Google a sus dispositivos, se enumeran todos los adaptadores de sincronización instalados para los servicios de Google. Cada adaptador de sincronización enumerado se sincroniza con un proveedor de contenido diferente en el dispositivo.
Dado que la mayoría de los servicios requieren que los usuarios verifiquen su identidad antes de acceder a los datos, el sistema Android ofrece un marco de trabajo de autenticación similar al marco de trabajo del adaptador de sincronización, y que a menudo se usa en conjunto con este. El framework de autenticación usa autenticadores de complemento que son subclases de AbstractAccountAuthenticator
. Un autenticador verifica la identidad del usuario con los siguientes pasos:
- Recopila el nombre, la contraseña o información similar del usuario (las credenciales del usuario).
- Envía las credenciales al servicio.
- Examina la respuesta del servicio.
Si el servicio acepta las credenciales, el autenticador puede
almacenarlas para usarlas más adelante. Debido al marco de trabajo del autenticador de complementos, el objeto AccountManager
puede proporcionar acceso a cualquier token de autenticación que admita y elija exponer un autenticador, como los tokens de autenticación de OAuth2.
Si bien la autenticación no es obligatoria, la mayoría de los servicios de contacto la utilizan. Sin embargo, no es obligatorio que uses el framework de autenticación de Android para realizar la autenticación.
Implementación de un adaptador de sincronización
Para implementar un adaptador de sincronización para el Proveedor de contactos, primero debes crear una aplicación para Android que contenga lo siguiente:
-
Un componente
Service
que responde a las solicitudes del sistema para vincularse al adaptador de sincronización. -
Cuando el sistema quiere ejecutar una sincronización, llama al método
onBind()
del servicio para obtener unIBinder
para el adaptador de sincronización. Esto permite que el sistema realice llamadas entre procesos a los métodos del adaptador. -
El adaptador de sincronización real, implementado como una subclase concreta de
AbstractThreadedSyncAdapter
. -
Esta clase se encarga de descargar datos del servidor, subir datos del
dispositivo y resolver conflictos. El trabajo principal del adaptador se realiza en el método
onPerformSync()
. Esta clase se debe iniciar como un singleton. -
Es una subclase de
Application
. -
Esta clase actúa como una fábrica para el singleton del adaptador de sincronización. Usa el método
onCreate()
para crear una instancia del adaptador de sincronización y proporciona un método "getter" estático para devolver el singleton al métodoonBind()
del servicio del adaptador de sincronización. -
Opcional: Un componente
Service
que responde a las solicitudes del sistema para la autenticación del usuario. -
AccountManager
inicia este servicio para comenzar el proceso de autenticación. El métodoonCreate()
del servicio crea una instancia de un objeto autenticador. Cuando el sistema desea autenticar una cuenta de usuario para el adaptador de sincronización de la aplicación, llama al métodoonBind()
del servicio para obtener unIBinder
para el autenticador. Esto permite que el sistema realice llamadas entre procesos a los métodos del autenticador. -
Opcional: Es una subclase concreta de
AbstractAccountAuthenticator
que controla las solicitudes de autenticación. -
Esta clase proporciona métodos que invoca
AccountManager
para autenticar las credenciales del usuario con el servidor. Los detalles del proceso de autenticación varían mucho según la tecnología del servidor que se use. Consulta la documentación del software de tu servidor para obtener más información sobre la autenticación. - Archivos XML que definen el adaptador de sincronización y el autenticador en el sistema.
-
Los componentes del adaptador de sincronización y del servicio de autenticación descritos anteriormente se
definen en elementos
<service>
en el manifiesto de la aplicación. Estos elementos contienen elementos secundarios<meta-data>
que proporcionan datos específicos al sistema:-
El elemento
<meta-data>
para el servicio del adaptador de sincronización apunta al archivo XMLres/xml/syncadapter.xml
. A su vez, este archivo especifica un URI para el servicio web que se sincronizará con el proveedor de contactos y un tipo de cuenta para el servicio web. -
Opcional: El elemento
<meta-data>
del autenticador apunta al archivo XMLres/xml/authenticator.xml
. A su vez, este archivo especifica el tipo de cuenta que admite este autenticador, así como los recursos de la IU que aparecen durante el proceso de autenticación. El tipo de cuenta especificado en este elemento debe ser el mismo que el especificado para el adaptador de sincronización.
-
El elemento
Datos de redes sociales
Las tablas android.provider.ContactsContract.StreamItems y android.provider.ContactsContract.StreamItemPhotos administran los datos entrantes de las redes sociales. Puedes escribir un adaptador de sincronización que agregue datos de transmisión desde tu propia red a estas tablas, o bien puedes leer datos de transmisión desde estas tablas y mostrarlos en tu propia aplicación, o ambas cosas. Con estas funciones, tus servicios y aplicaciones de redes sociales se pueden integrar en la experiencia de redes sociales de Android.
Texto de redes sociales
Los elementos de la secuencia siempre están asociados con un contacto sin procesar. El campo android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID vincula al valor _ID
del contacto sin procesar. El tipo y el nombre de la cuenta del contacto sin procesar también se almacenan en la fila del elemento de transmisión.
Guarda los datos de la secuencia en las siguientes columnas:
- android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_TYPE
- Obligatorio. Es el tipo de cuenta del usuario para el contacto sin procesar asociado con este elemento de la transmisión. Recuerda establecer este valor cuando insertes un elemento de la secuencia.
- android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_NAME
- Obligatorio. Nombre de la cuenta del usuario para el contacto sin procesar asociado a este elemento de la secuencia. Recuerda establecer este valor cuando insertes un elemento de la secuencia.
- Columnas identificadoras
-
Obligatorio. Debes insertar las siguientes columnas de identificador cuando insertes un elemento de transmisión:
- android.provider.ContactsContract.StreamItemsColumns#CONTACT_ID: Es el valor de android.provider.BaseColumns#_ID del contacto con el que se asocia este elemento de transmisión.
- android.provider.ContactsContract.StreamItemsColumns#CONTACT_LOOKUP_KEY: Es el valor de android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY del contacto con el que se asocia este elemento de transmisión.
- android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID: Es el valor de android.provider.BaseColumns#_ID del contacto sin procesar con el que se asocia este elemento de transmisión.
- android.provider.ContactsContract.StreamItemsColumns#COMMENTS
- Opcional. Guarda información resumida que puedas mostrar al comienzo de un elemento de flujo.
- android.provider.ContactsContract.StreamItemsColumns#TEXT
-
Es el texto del elemento de la transmisión, ya sea el contenido que publicó la fuente del elemento,
o una descripción de alguna acción que generó el elemento de la transmisión. Esta columna puede contener cualquier formato y las imágenes de recursos incorporadas que
fromHtml()
puede renderizar. Es posible que el proveedor trunque o agregue puntos suspensivos al contenido largo, pero intentará evitar la interrupción de las etiquetas. - android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP
- Es una cadena de texto que contiene la hora en la que se insertó o actualizó el elemento de transmisión, en forma de milisegundos desde la época. Las aplicaciones que insertan o actualizan elementos de transmisión son responsables de mantener esta columna. El proveedor de contactos no la mantiene automáticamente.
Para mostrar información de identificación de los elementos de tu transmisión, usa android.provider.ContactsContract.StreamItemsColumns#RES_ICON, android.provider.ContactsContract.StreamItemsColumns#RES_LABEL y android.provider.ContactsContract.StreamItemsColumns#RES_PACKAGE para vincular recursos en tu aplicación.
La tabla android.provider.ContactsContract.StreamItems también contiene las columnas android.provider.ContactsContract.StreamItemsColumns#SYNC1 a android.provider.ContactsContract.StreamItemsColumns#SYNC4 para el uso exclusivo de los adaptadores de sincronización.
Fotos de redes sociales
La tabla android.provider.ContactsContract.StreamItemPhotos almacena fotos asociadas a un elemento de transmisión. La columna android.provider.ContactsContract.StreamItemPhotosColumns#STREAM_ITEM_ID de la tabla se vincula a los valores de la columna _ID
de la tabla android.provider.ContactsContract.StreamItems. Las referencias de fotos se almacenan en la tabla en estas columnas:
- Columna android.provider.ContactsContract.StreamItemPhotos#PHOTO (un BLOB).
- Es una representación binaria de la foto, cuyo tamaño ajustó el proveedor para su almacenamiento y visualización. Esta columna está disponible para la retrocompatibilidad con versiones anteriores de Contacts Provider que la usaban para almacenar fotos. Sin embargo, en la versión actual, no debes usar esta columna para almacenar fotos. En su lugar, usa android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID o android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI (ambos se describen en los siguientes puntos) para almacenar fotos en un archivo. Esta columna ahora contiene una miniatura de la foto, que está disponible para su lectura.
- android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID
-
Es un identificador numérico de una foto para un contacto sin procesar. Agrega este valor a la constante
DisplayPhoto.CONTENT_URI
para obtener un URI de contenido que apunte a un solo archivo de foto y, luego, llama aopenAssetFileDescriptor()
para obtener un identificador del archivo de foto. - android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI
-
Es un URI de contenido que apunta directamente al archivo de la foto representada por esta fila.
Llama a
openAssetFileDescriptor()
con este URI para obtener un identificador del archivo de la foto.
Uso de tablas de redes sociales
Estas tablas funcionan de la misma manera que las otras tablas principales en el proveedor de contactos, excepto por lo siguiente:
- Estas tablas requieren permisos de acceso adicionales. Para leerlos, tu aplicación debe tener el permiso android.Manifest.permission#READ_SOCIAL_STREAM. Para modificarlos, tu aplicación debe tener el permiso android.Manifest.permission#WRITE_SOCIAL_STREAM.
-
En el caso de la tabla android.provider.ContactsContract.StreamItems, la cantidad de filas
almacenadas para cada contacto sin procesar es limitada. Una vez que se alcanza este límite, el proveedor de contactos libera espacio para las filas de elementos de transmisión nuevos borrando automáticamente las filas que tienen el android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP más antiguo. Para obtener el límite, envía una consulta al URI de contenido android.provider.ContactsContract.StreamItems#CONTENT_LIMIT_URI. Puedes dejar todos los argumentos, excepto el URI de contenido, configurados como
null
. La consulta devuelve un Cursor que contiene una sola fila, con la única columna android.provider.ContactsContract.StreamItems#MAX_ITEMS.
La clase android.provider.ContactsContract.StreamItems.StreamItemPhotos define una subtabla de android.provider.ContactsContract.StreamItemPhotos que contiene las filas de fotos de un solo elemento de transmisión.
Interacciones con redes sociales
Los datos de transmisiones sociales que administra el proveedor de contactos, junto con la aplicación de contactos del dispositivo, ofrecen una forma eficaz de conectar tu sistema de redes sociales con los contactos existentes. Están disponibles las siguientes funciones:
- Si sincronizas tu servicio de redes sociales con el Proveedor de contactos a través de un adaptador de sincronización, puedes recuperar la actividad reciente de los contactos de un usuario y almacenarla en las tablas android.provider.ContactsContract.StreamItems y android.provider.ContactsContract.StreamItemPhotos para su uso posterior.
- Además de la sincronización normal, puedes activar tu adaptador de sincronización para recuperar datos adicionales cuando el usuario selecciona un contacto para verlo. Esto permite que tu adaptador de sincronización recupere fotos de alta resolución y los elementos de transmisión más recientes del contacto.
- Si registras una notificación con la aplicación de contactos del dispositivo y el proveedor de contactos, puedes recibir un intent cuando se vea un contacto y, en ese momento, actualizar el estado del contacto desde tu servicio. Este enfoque puede ser más rápido y usar menos ancho de banda que realizar una sincronización completa con un adaptador de sincronización.
- Los usuarios pueden agregar un contacto a tu servicio de redes sociales mientras lo ven en la aplicación de contactos del dispositivo. Puedes habilitar esta función con la función "Invitar a un contacto", que se habilita con una combinación de una actividad que agrega un contacto existente a tu red y un archivo XML que proporciona la aplicación de contactos del dispositivo y el proveedor de contactos con los detalles de tu aplicación.
La sincronización periódica de los elementos de la transmisión con el proveedor de contactos es igual a otras sincronizaciones. Para obtener más información sobre la sincronización, consulta la sección Adaptadores de sincronización del proveedor de contactos. El registro de notificaciones y la invitación de contactos se explican en las siguientes dos secciones.
Registro para administrar visualizaciones de redes sociales
Para registrar tu adaptador de sincronización para recibir notificaciones cuando el usuario vea un contacto que administra tu adaptador de sincronización, haz lo siguiente:
-
Crea un archivo llamado
contacts.xml
en el directoriores/xml/
de tu proyecto. Si ya tienes este archivo, puedes omitir este paso. -
En este archivo, agrega el elemento
<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">
. Si el elemento ya existe, puedes omitir este paso. -
Para registrar un servicio que recibe una notificación cuando el usuario abre la página de detalles de un contacto en la aplicación de contactos del dispositivo, agrega el atributo
viewContactNotifyService="serviceclass"
al elemento, dondeserviceclass
es el nombre de clase completamente calificado del servicio que debe recibir el intent de la aplicación de contactos del dispositivo. Para el servicio de notificaciones, usa una clase que extiendaIntentService
para permitir que el servicio reciba intents. Los datos de la intent entrante contienen el URI de contenido del contacto sin procesar en el que hizo clic el usuario. Desde el servicio de notificaciones, puedes vincularte y, luego, llamar a tu adaptador de sincronización para actualizar los datos del contacto sin procesar.
Para registrar una actividad a la que se llamará cuando el usuario haga clic en un elemento o una foto de la secuencia (o en ambos):
-
Crea un archivo llamado
contacts.xml
en el directoriores/xml/
de tu proyecto. Si ya tienes este archivo, puedes omitir este paso. -
En este archivo, agrega el elemento
<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">
. Si el elemento ya existe, puedes omitir este paso. -
Para registrar una de tus actividades para controlar el clic del usuario en un elemento de transmisión en la aplicación de contactos del dispositivo, agrega el atributo
viewStreamItemActivity="activityclass"
al elemento, dondeactivityclass
es el nombre de clase completo de la actividad que debe recibir el intent de la aplicación de contactos del dispositivo. -
Para registrar una de tus actividades para controlar el clic del usuario en una foto de transmisión en la aplicación de contactos del dispositivo, agrega el atributo
viewStreamItemPhotoActivity="activityclass"
al elemento, dondeactivityclass
es el nombre de clase completamente calificado de la actividad que debe recibir el intent de la aplicación de contactos del dispositivo.
El elemento <ContactsAccountType>
se describe con más detalle en la sección Elemento<ContactsAccountType>.
La intent entrante contiene el URI de contenido del elemento o la foto en los que el usuario hizo clic. Para tener actividades independientes para los elementos de texto y las fotos, usa ambos atributos en el mismo archivo.
Interacción con tu servicio de redes sociales
Los usuarios no tienen que salir de la aplicación de contactos del dispositivo para invitar a un contacto a tu sitio de redes sociales. En su lugar, puedes hacer que la app de contactos del dispositivo envíe un intent para invitar al contacto a una de tus actividades. Sigue estos pasos para configurar la función:
-
Crea un archivo llamado
contacts.xml
en el directoriores/xml/
de tu proyecto. Si ya tienes este archivo, puedes omitir este paso. -
En este archivo, agrega el elemento
<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android">
. Si el elemento ya existe, puedes omitir este paso. -
Agrega los siguientes atributos:
inviteContactActivity="activityclass"
-
inviteContactActionLabel="@string/invite_action_label"
activityclass
es el nombre de clase completamente calificado de la actividad que debe recibir el intent. El valorinvite_action_label
es una cadena de texto que se muestra en el menú Agregar conexión de la aplicación de contactos del dispositivo.
Nota: ContactsSource
es un nombre de etiqueta obsoleto para ContactsAccountType
.
Referencia contacts.xml
El archivo contacts.xml
contiene elementos XML que controlan la interacción de tu adaptador de sincronización y tu aplicación con la aplicación de Contactos y el proveedor de contactos. Estos elementos se describen en las siguientes secciones.
Elemento <ContactsAccountType>
El elemento <ContactsAccountType>
controla la interacción de tu aplicación con la aplicación de contactos. Tiene la siguiente sintaxis:
<ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android" inviteContactActivity="activity_name" inviteContactActionLabel="invite_command_text" viewContactNotifyService="view_notify_service" viewGroupActivity="group_view_activity" viewGroupActionLabel="group_action_text" viewStreamItemActivity="viewstream_activity_name" viewStreamItemPhotoActivity="viewphotostream_activity_name">
Contenido en:
res/xml/contacts.xml
puede contener lo siguiente:
<ContactsDataKind>
Description:
Declara componentes de Android y etiquetas de IU que permiten a los usuarios invitar a uno de sus contactos a una red social, notificar a los usuarios cuando se actualiza uno de sus feeds de redes sociales, etcétera.
Ten en cuenta que el prefijo del atributo android:
no es necesario para los atributos de <ContactsAccountType>
.
Atributos:
inviteContactActivity
- Es el nombre de clase completamente calificado de la actividad en tu aplicación que deseas activar cuando el usuario selecciona Agregar conexión en la aplicación de contactos del dispositivo.
inviteContactActionLabel
-
Es una cadena de texto que se muestra para la actividad especificada en
inviteContactActivity
, en el menú Agregar conexión. Por ejemplo, puedes usar la cadena "Seguir en mi red". Puedes usar un identificador de recursos de cadena para esta etiqueta. viewContactNotifyService
- Nombre de clase completamente calificado de un servicio en tu aplicación que debería recibir notificaciones cuando el usuario vea un contacto. Esta notificación la envía la aplicación de contactos del dispositivo y permite que tu aplicación posponga las operaciones que consumen muchos datos hasta que sean necesarias. Por ejemplo, tu aplicación puede responder a esta notificación leyendo y mostrando la foto de alta resolución del contacto y los elementos más recientes de su transmisión social. Esta función se describe con más detalle en la sección Interacciones en el flujo social.
viewGroupActivity
- Es el nombre de clase completo de una actividad en tu aplicación que puede mostrar información de grupos. Cuando el usuario hace clic en la etiqueta del grupo en la aplicación de contactos del dispositivo, se muestra la IU de esta actividad.
viewGroupActionLabel
-
Es la etiqueta que la aplicación de contactos muestra para un control de IU que permite
al usuario ver grupos en tu aplicación.
Se puede usar un identificador de recursos de cadena para este atributo.
viewStreamItemActivity
- Es el nombre de clase completamente calificado de una actividad en tu aplicación que la aplicación de contactos del dispositivo inicia cuando el usuario hace clic en un elemento de transmisión para un contacto sin procesar.
viewStreamItemPhotoActivity
- Es el nombre de clase completamente calificado de una actividad en tu aplicación de contactos del dispositivo que se inicia cuando el usuario hace clic en una foto en el elemento de transmisión de un contacto sin procesar.
Elemento <ContactsDataKind>
El elemento <ContactsDataKind>
controla la visualización de las filas de datos personalizados de tu aplicación en la IU de la aplicación de contactos. Tiene la siguiente sintaxis:
<ContactsDataKind android:mimeType="MIMEtype" android:icon="icon_resources" android:summaryColumn="column_name" android:detailColumn="column_name">
Contenido en:
<ContactsAccountType>
Description:
Usa este elemento para que la aplicación de contactos muestre el contenido de una fila de datos personalizados como parte de los detalles de un contacto sin procesar. Cada elemento secundario <ContactsDataKind>
de <ContactsAccountType>
representa un tipo de fila de datos personalizados que tu adaptador de sincronización agrega a la tabla ContactsContract.Data
. Agrega un elemento <ContactsDataKind>
para cada tipo de MIME personalizado que uses. No es necesario que agregues el elemento si tienes una fila de datos personalizada para la que no deseas mostrar datos.
Atributos:
android:mimeType
-
Es el tipo de MIME personalizado que definiste para uno de tus tipos de filas de datos personalizados en la tabla
ContactsContract.Data
. Por ejemplo, el valorvnd.android.cursor.item/vnd.example.locationstatus
podría ser un tipo de MIME personalizado para una fila de datos que registra la última ubicación conocida de un contacto. android:icon
- Un recurso de elementos gráficos de Android que la aplicación de contactos muestra junto a tus datos. Úsalo para indicarle al usuario que los datos provienen de tu servicio.
android:summaryColumn
- Nombre de la columna para el primero de los dos valores recuperados de la fila de datos. El valor se muestra como la primera línea de la entrada de esta fila de datos. La primera línea está diseñada para usarse como un resumen de los datos, pero es opcional. Consulta también android:detailColumn.
android:detailColumn
-
Nombre de la columna para el segundo de los dos valores recuperados de la fila de datos. El valor se muestra como la segunda línea de la entrada para esta fila de datos. Consulta también
android:summaryColumn
.
Otras funciones del proveedor de contactos
Además de las funciones principales que se describen en las secciones anteriores, el Proveedor de contactos ofrece estas funciones útiles para trabajar con datos de contactos:
- Grupos de contactos
- Funciones de fotografía
Grupos de contactos
El Proveedor de contactos puede etiquetar de forma opcional colecciones de contactos relacionados con datos de grupo. Si el servidor asociado a una cuenta de usuario desea mantener grupos, el adaptador de sincronización del tipo de cuenta de la cuenta debe transferir datos de grupos entre el proveedor de contactos y el servidor. Cuando los usuarios agregan un contacto nuevo al servidor y, luego, lo colocan en un grupo nuevo, el adaptador de sincronización debe agregar el grupo nuevo a la tabla ContactsContract.Groups
. El grupo o los grupos a los que pertenece un contacto sin procesar se almacenan en la tabla ContactsContract.Data
con el tipo de MIME ContactsContract.CommonDataKinds.GroupMembership
.
Si diseñas un adaptador de sincronización que agregará datos de contactos sin procesar del servidor al Proveedor de contactos y no usas grupos, debes indicarle al proveedor que haga visibles tus datos. En el código que se ejecuta cuando un usuario agrega una cuenta al dispositivo, actualiza la fila ContactsContract.Settings
que el proveedor de contactos agrega para la cuenta. En esta fila, establece el valor de la columna Settings.UNGROUPED_VISIBLE
en 1. Cuando lo hagas, el Proveedor de contactos siempre hará que tus datos de contactos sean visibles, incluso si no usas grupos.
Fotos de contactos
La tabla ContactsContract.Data
almacena fotos como filas con el tipo de MIME Photo.CONTENT_ITEM_TYPE
. La columna CONTACT_ID
de la fila está vinculada a la columna _ID
del contacto sin procesar al que pertenece.
La clase ContactsContract.Contacts.Photo
define una subtabla de ContactsContract.Contacts
que contiene información de la foto principal de un contacto, que es la foto principal del contacto sin procesar principal del contacto. Del mismo modo, la clase ContactsContract.RawContacts.DisplayPhoto
define una subtabla de ContactsContract.RawContacts
que contiene información de la foto principal de un contacto sin procesar.
La documentación de referencia para ContactsContract.Contacts.Photo
y ContactsContract.RawContacts.DisplayPhoto
contiene ejemplos de cómo recuperar información de fotos. No hay una clase de conveniencia para recuperar la miniatura principal de un contacto sin procesar, pero puedes enviar una consulta a la tabla ContactsContract.Data
, seleccionando el _ID
, el Photo.CONTENT_ITEM_TYPE
y la columna IS_PRIMARY
del contacto sin procesar para encontrar la fila de la foto principal del contacto sin procesar.
Los datos de redes sociales de una persona también pueden incluir fotos. Estos se almacenan en la tabla android.provider.ContactsContract.StreamItemPhotos, que se describe con más detalle en la sección Fotos de transmisiones sociales.