Acceder A Los Contactos De Android Con El Contacts Provider

Este artículo ha sido creado con el fin de administrar los datos nativos de nuestros Contactos en Android a través del componente Contacts Provider.

A lo largo del temario, aprenderás de qué forma está estructurada la información entre aplicaciones.

Verás cómo obtener acceso a dicha información y finalmente tendrás acceso a un ejemplo completo para consultar la información de los contactos.

¿Qué es un Content Provider?

Recuerda que cuando viste los componentes de una aplicación Android, se estudió el concepto de Content Provider (“Proveedor de Contenido”). Se había dicho que es un mecanismo de almacenamiento de datos, usado para compartir información persistente entre procesos de forma aislada. Esto permite al programador dotar de un buen nivel de seguridad en las bases de datos de sus aplicaciones.

En Android existen varios Content Providers predeterminados cuyo propósito es intercambiar información entre sus aplicaciones nativas. El más común es el Contacts Provider, el cual permite obtener los datos de la aplicación de contactos de nuestro teléfono. No obstante, existen diferentes tipos para compartir imágenes, audio, video, etc.

Acceder a los datos de un ContentProvider

Para acceder a los datos soportados por un Content Provider se debe usar un componente llamado ContentResolver (“Resolutor de Contenido”).

Un Content Resolver es un objeto que permite consultar, insertar, eliminar y modificar los datos de un Content Provider.

Debido a que varias aplicaciones pueden acceder a un mismo Content Provider a la vez, el Content resolver gestiona por nosotros una petición estilo cliente hacia la instancia de la aplicación proveedora, para obtener la información.

Para acceder a los datos de un ContentProvider debes asigna un permiso tu aplicación. Esto se hace añadiendo un elemento <uses-permission> a tu AndroidManifest.xml con la orden correspondiente según como haya sido construido el provider.

Al igual que cuando usábamos la clase SQLiteDataBase con bases de datos en SQLite, un ContentResolver te permite implementar métodos query(), insert(), update() y delete() para operar los tados del ContentProvider.

Por ejemplo…

Si deseas consultar los registros de tus contactos telefónicos, lo primero que debes hacer es obtener el Content Resolver asociado a tu aplicación con el método getContentResolver().

Luego de ello puedes emplear el método query() especificando el ContentProvider a consultar, el nombre de las columnas, la condición y argumentos de la sentencia WHERE y hasta la forma en que se ordenarán los resultados finales.

contactsCursor = getContentResolver().query(
    ContactsContract.Contacts.CONTENT_URI,   // URI de contenido para los contactos
    projection,                        // Columnas a seleccionar
    selectionClause                    // Condición del WHERE
    selectionArgs,                     // Valores de la condición
    sortOrder);                        // ORDER BY columna [ASC|DESC]

El primer parámetro hace referencia al nombre de la tabla que vamos a consultar dentro del ContentProvider de los contactos, cuyo tipo es Uri.

Pero… ¿Por qué usar una URI para especificar el nombre de la tabla?…y…¿Qué es ContactsContract?

Usando URIs de contenido

Las URIs de contenido le indican a los Content Resolvers en que Content Provider debe buscar una tabla determinada, a través de esquema “content://”. Por ejemplo, la URI de contenido de la anterior consulta se ve de esta forma:

content://com.android.contacts/contacts

Normalmente la estructura de una URI de contenido está dividida en tres partes significativas como se ve en el siguiente ejemplo:
URI de contenido para los contactos de Android

  • Esquema: La cadena “content://” es una constante para establecer que el origen de datos vendrá de un Content Provider.
  • autoridad: Es una cadena única que identifica al Content Provider para su uso. Cuando creas tus propios Content Providers normalmente se recomienda usar el nombre del paquete al que pertenece para diferenciarlos de los demas.
  • ruta: Es un conjunto de segmentos separados por barras oblicuas (‘/’) que guían al Content Resolver hacia la ubicación lógica de los datos dentro del Content Provider.
  • id: Identificador numérico (en la mayoría de los casos es la llave primaria) asociado a un solo registro que se encuentra en la ruta establecida del Content Provider. Es muy útil cuando deseamos acceder a una sola fila.

Como ves, el uso de URIs de contenido te facilita todo el trabajo a la hora de acceder a la información de otras aplicaciones, evitándonos el desarrollo de mecanismos complejos.

Esta es la razón por la cual se le denomina Resolutor de Contenido a este objeto, ya que es el encargado de parsear la URI y darse a la búsqueda de la información. ¡Una grandiosa labor!

Pero de nada nos serviría esta información si no conocemos la forma en que están estructuradas las bases de datos de los contactos. Por esta razón es importante comprender el siguiente tema.

¿Cuál es la utilidad del Contacts Provider en Android?

Construir el esquema de una base de datos es de suma importancia para acceder a la información de forma significativa. En nuestra caso, deseamos saber cómo están organizados los datos de la aplicación de contacto en Android.

Este esquema está definido a través de un componente llamado Contacts Provider, el cual es un Content Provider que alberga toda la información de las personas que tenemos guardadas en nuestro dispositivo.

El Contacts Provider utiliza la clase ContactsContract para definir las constantes que modelan la base de datos de los contactos. Podría llegar a pensarse que esta clase contiene solo la definición de una sola tabla con todos los datos, pero no es así. Muchos programadores se confunden debido a que la información de una sola persona está fragmentada en un modelo de tres capas.

Veamos un pequeño diagrama:
Modelo Relacional del Contacts Provider en Android
La anterior ilustración te indica que los datos de un contacto esta distribuida en tres tablas con un propósito distinto. Veamos de qué se trata:

  • Contacts: Dentro de esta tabla están definidas las constantes necesarias para representar los datos una persona basados en diferentes cuentas.
  • RawContacts: Aquí se añaden los datos asociados de un contacto con una cuenta de algún servicio.
  • Data: Almacena información mucho más detallada del contacto como direcciones de email, teléfonos, direcciones, campos personalizados, etc.

Las relaciones entre las tres tablas se basan en una cardinalidad 1:N. Donde un contacto puede tener una o más cuentas de servicios. Y a su vez, cada cuenta puede tener asociada uno o más tipos de datos.

Consultar los datos de un Contacto en Android

Antes de consultar los datos de los contactos, es necesario que agregues el siguiente permiso a tu aplicación:

 <uses-permission android:name="android.permission.READ_CONTACTS"/>

Supongo que intuyes que para obtener los datos más relevantes de un contacto te debes enfocar en la tabla ContactsContract.Data. Así que solo debes encontrar el nombre de las columnas que vas a consultar y listo.

Pero existe una característica un poco confusa. La tabla Data almacena información de distinta naturaleza. Es decir, puede tener un registro almacene el teléfono del contacto y otra que almacene el nombre del contacto.

Esta rara particularidad es posible gracias a un conjunto de 15 columnas genéricas que guardan cualquier tipo de información. Observa una simplificación personalizada de como se vería la tabla Data con algunos datos de ejemplo:

_ID MYMETYPE DATA1 DATA2 DATA15
1 NOMBRE Carlos Jimenez Mr.
2 TELEFONO 3294831 DOMICILIO
3 EMAIL carlangas@mail.com TRABAJO

La tabla anterior no es un fiel reflejo del verdadero aspecto de Data, pero es un buen ejemplo para comprender su esencia. Si te fijas, existe un identificador (_ID) que diferencia cada fila, también está el tipo de registro (MYMETYPE) de la fila y luego sigue un conjunto de 15 filas genéricas con los datos específicos.

A simple vista se nota que los registros no son homogéneos, porque es una tabla que almacena información variada en las columnas. Por ejemplo, el registro tipo NOMBRE guarda el nombre del contacto en DATA1, pero el registro tipo EMAIL guarda la dirección de correo electrónico en esa misma columna.

Tú te preguntarás cómo hacer para manejar esta extraña manera de almacenamiento de datos, ¿no es cierto?, pues veamos la respuesta.

Recuerda que antes de ejecutar una consulta con el método query(), debemos construir la sentencia SQL. Así que lo primero es identificar cuáles son las columnas a consultar. Pero, debido a que no todos los tipos de registros asociados a un contacto usan la misma cantidad de datos para representar la información, entonces te ves en la obligación de consultar las 15 columnas:

  String projection =
            {
                Data._ID,
                Data.MIMETYPE,
                Data.DATA1,
                Data.DATA2,
                Data.DATA3,
                Data.DATA4,
                Data.DATA5,
                Data.DATA6,
                Data.DATA7,
                Data.DATA8,
                Data.DATA9,
                Data.DATA10,
                Data.DATA11,
                Data.DATA12,
                Data.DATA13,
                Data.DATA14,
                Data.DATA15
            };

Luego debemos especificar de qué usuario vamos a consultar dicha información. Eso se logra condicionando con un WHERE los registros, de tal forma que elijamos solo los registros relacionados al identificador del contacto deseado.

String selectionClause = Contacts.LOOKUP_KEY+" = ? ";
String selectionArgs[] = new String[]{ contactId };

Aunque la tabla Contacts tiene una llave primaria llamada _ID, la columna Contacts.LOOKUP_KEY también puede usarse como llave primaria. La diferencia está en que LOOKUP_KEY permite hallar el contacto incluso si se modifica en tiempo real como consecuencia de una agregación o sincronización de productos Google.

Se usa el placeholder ‘?’ para generalizar la sentencia y reemplazarlo luego por los argumentos. En este caso el argumento se compone del valor arbitrario de una variable hipotética llamada contactId.

Y luego si deseas puedes establecer la forma en que se ordenarán los registros.

Veamos un ejemplo de cómo organizarlos por tipo:

String sortOrder = Data.MIMETYPE;

Finalmente juntas todas las piezas y las empleas en la consulta junto al URI de la tabla Data:

contactsCursor = getContentResolver().query(
    ContactsContract.Data.CONTENT_URI,  
    projection,                        
    selectionClause                    
    selectionArgs,                     
    sortOrder);

Consultas al Contacts Provider con la clase CommonDataKinds

Existe una forma mucho más cómoda de elegir las columnas del Contacts Provider que deseamos consultar. Se trata de la clase ContactsContract.CommonDataKinds. Esta clase te provee varias clases anidadas que asignan un alias a las columnas de cada tipo de registro.

Por ejemplo, si quieres referirte solo al nombre de tu contacto, entonces usas el alias DISPLAY_NAME de la clase CommonDataKinds.StructuredName o si deseas saber el email usas ADDRESS de CommonDataKinds.Email. Ambas columnas representan el DATA1 de un registro tipo nombre y tipo email.

Usar estas clases te facilita la escritura y compresión de las consultas a realizar sobre la tabla Data (Aprender más sobre la clase CommonDataKinds).

Veamos un ejemplo para consultar el nombre y el teléfono de los contactos. Lo primero es crear la proyección de aquellas columnas que vamos a necesitar:

  String[] projection =
            {
                Phone._ID,
              Phone.NUMBER
            };

Ahora se deben establecer los criterios de selección para buscar solo aquellos registros telefónicos. Para ello realizamos una comparación entre MIMETYPE y la constante CONTENT_ITEM_TYPE de Phone que representa un registro con datos telefónicos.

String selectionClause =
            Data.LOOKUP_KEY + " = ?" +
            " AND " +
            Data.MIMETYPE + " = " +
            "'" + Phone.CONTENT_ITEM_TYPE + "'";

String[] selectionArgs = { contactId };

La clase Data tiene un acceso a la columna LOOKUP_KEY de la tabla Contacts. Esto se debe a que las consultas de información requieren joins para obtener los datos correctos, por lo que ContactsContract implementa todas estas operaciones de forma implícita y así ahorrar al programador el cruce entre tablas.

Finalmente implementas un tipo de orden, en este caso es descendente:

String sortOrder = Phone.TYPE + " DESC ";

Aunque la clase CommonDataKinds nos provee sobrenombres para las columnas, consultar tipos mezclados es confuso. Es preferible consultar cada tipo de información individualmente, es decir, si deseas manipular los datos del nombre y del email, entonces primero haz una selección individual para los datos del nombre y luego realiza otra para los del email.

Insertar datos de un Contacto

Para insertar un contacto usaremos el método insert() de ContentResolver. Primero crea un contenedor de valores estructurados y luego añade los valores relacionados a los nombres de las columnas de la tabla Data. A continuación se muestra un ejemplo de inserción de un email:

 ContentValues values = new ContentValues();
 values.put(Data.RAW_CONTACT_ID, rawContactId);
 values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
 values.put(Email.ADDRESS, "carlangas@mail.com");
 values.put(Email.TYPE, Phone.TYPE_PHONE);
 Uri uri = getContentResolver().insert(Data.CONTENT_URI, values);

Es importante referenciar la inserción a través de la URI de contenido de la tabla Data y además consultar previamente el identificador (Data.RAW_CONTACT_ID) de aquella cuenta del contacto en la que se insertará el email.

Si en algún momento deseas insertar, modificar o eliminar varios registros al tiempo emplea la clase ContentProviderOperation. Esta clase permite abrir transacciones para procesos largos junto al método applyBatch() de ContentResolver.

El uso de este mecanismo aumenta la velocidad de la operación y además mantiene la consistencia de datos evitando la perdida de información (ver Propiedades de una transacción).

Veamos un ejemplo de inserción:

ArrayList<ContentProviderOperation> ops =
          new ArrayList<ContentProviderOperation>();

 ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI)
          .withValue(Data.RAW_CONTACT_ID, rawContactId)
          .withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE)
          .withValue(Email.ADDRESS, "carlangas@mail.com")
          .withValue(Phone.TYPE, Phone.TYPE_HOME)
          .build());
 getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);

La manera de crear la operación es muy parecida a el método insert(). Con el método newInsert() se le indica al ContentProviderOperation que se creará una inserción. Luego añades al espacio los valores con sus respectivas claves a través del método withValue().

Lo siguiente es hacer efectiva la sentencia con el método build() y finalmente ejecutas el método applyBatch() para iniciar la transacción. applyBatch() recibe la autoridad del ContentProvider a modificar y una instancia de la operación.

Modificar datos un Contacto

Aunque podemos emplear el método update(), es preferible seguir realizando las operaciones con la clase ContentProviderOperation. Por lo que debes emplear el método newUpdate(), el cual recibe los valores nuevos relacionados con la clave y las características condicionales de la sentencia WHERE, para modificar los registros necesarios.

Por ejemplo, si quisiéramos cambiar el nombre de un contacto haríamos lo siguiente:

 ArrayList<ContentProviderOperation> ops =
          new ArrayList<ContentProviderOperation>();

 ops.add(ContentProviderOperation.newUpdate(Data.CONTENT_URI)
          .withSelection(Data._ID + "=?", new String[]{String.valueOf(rowId)})
          .withValue(StructuredName.DISPLAY_NAME, "Roberto gonzales")
          .build());
 getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);

Usa el método withSelection() para implementar la condición, donde el primer valor es la clausula y el segundo son los argumentos a reemplazar. El ejemplo muestra que se modificarán la fila cuyo atributo _ID sea igual al valor de la variable rowId.

Eliminar datos de un Contacto

Eliminar un contacto es muy parecido a modificarlo, solo que esta vez no establecemos valores, si no que especificamos el identificador de aquella fila a descartar del Contacts Provider. Usa el método newDelete() acompañado de withSelection():

 ArrayList<ContentProviderOperation> ops =
          new ArrayList<ContentProviderOperation>();

 ops.add(ContentProviderOperation.newDelete(Data.CONTENT_URI)
          .withSelection(Data._ID + "=?", new String[]{String.valueOf(rowId)})
          .build());
 getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);

Ejemplo: Enviar Un Mensaje De Texto A Un Contacto

En esta sección veremos cómo se construye una aplicación que envíe un Mensaje de texto a un contacto en Android. La idea es utilizar lo aprendido a lo largo del artículo para solucionar los requerimientos. La siguiente es una imagen que muestra el funcionamiento de la aplicación, a la cual se le denominó “ReportMe”:

Aplicación Android que accede a los contactos
ReportMe tiene un sola actividad principal denominada Main. Su diseño es sencillo, tiene dos TextViews para mostrar el nombre y teléfono móvil del contacto. Además un ImageView que proyecta la foto del contacto, si es que tiene una.

Para desbloquear el link de descarga del código completo, sigue estas instrucciones:

La selección del contacto se realiza a través del botón “Seleccionar Contacto”, el cual inicia la aplicación Contactos para la respectiva selección. Una vez seleccionado, con el botón “Enviar”, enviamos el siguiente mensaje de texto: “Estamos aprendiendo a Desarrollar en Android” al número móvil del contacto.

Comencemos por comprender como se inicia la actividad de la aplicación Contactos.

Usar un Intent para seleccionar Contactos de Android

Si recuerdas el tutorial sobre Intents en Android, tendrás claro que a través de ellos podemos iniciar actividades de aplicaciones externas. De momento necesitamos iniciar la actividad de selección de la aplicación contactos. Y que al ser seleccionado un contacto, se obtenga la información de dicho contacto para procesarla en ReportMe.

Toda esto debe suceder cuando presiones el botón “Seleccionar Contacto”, por lo que se debe implementar un método para el procesamiento de la escucha de clics. Como bien sabes, esto se hace con la interfaz OnClickListener y sobrescribiendo el método callback onClick(). Pero esta vez seremos más prácticos.

Usaremos el atributo android:onClick para el botón selectionButton . Su función es relacionar al View con un método que se ejecutará cuando este sea presionado. Esta propiedad te permite ahorrarte la implementación de la escucha y evitar complicaciones.

<Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Seleccionar Contacto"
        android:id="@+id/selectionButton"
        <strong>android:onClick="initPickContacts"</strong>
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="20dp" />

android:onClick tiene asignado un método llamado initPickContacts() para la escucha de clicks. Para que funcione como se desea, el método debe recibir un parámetro de tipo View y así recibir la instancia del botón seleccionado.

Dentro de initPickContacts() crearemos un Intent explícito a través de la URI de contenido de la tabla ContactsContract.Contacts. El objetivo es enviar una acción de tipo ACTION_PICK, para que posibilite iniciar la actividad de selección de contactos. Luego con la creación del Intent ejecutaremos la actividad con la espera del resultado de vuelta a través del método startActivityForResult():

public void initPickContacts(View v){
        /*
        Crear un intent para seleccionar un contacto del dispositivo
         */
        Intent i = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);

        /*
        Iniciar la actividad esperando respuesta a través
        del canal PICK_CONTACT_REQUEST
         */
        startActivityForResult(i, PICK_CONTACT_REQUEST);
    }

El siguiente paso es extraer los datos del intent de respuesta dentro del método onActivityResult(), luego de que sea seleccionado el contacto. Para ello usaremos el método getData() del intent de respuesta. Este método retorna en la URI de contenido específica para el contacto seleccionado, por lo que tendría una forma como esta:

content://com.android.contacts/contacts/lookup/0r1-39273F2F4B492F512F3D43/1

La anterior Uri te provee la LOOKUP_KEY relacionada al _ID del contacto. Evita perder este valor en el método y almacénalo en una variable global de tipo Uri:

protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        if (requestCode == PICK_CONTACT_REQUEST) {
            if (resultCode == RESULT_OK) {
                /*
                Capturar el valor de la Uri
                 */
                contactUri = intent.getData();
                <strong>/*
                Procesar la Uri
                 */
                renderContact(contactUri);</strong>
            }
        }
    }

El método renderContact() que ves en el código anterior es el encargado de procesar la URI de contenido y asignar el nombre, el teléfono y la foto a cada view de la actividad Main. Su definición se muestra a continuación:

private void renderContact(Uri uri) {

        TextView contactName = (TextView)findViewById(R.id.contactName);
        TextView contactPhone = (TextView)findViewById(R.id.contactPhone);
        ImageView contactPic = (ImageView)findViewById(R.id.contactPic);

        contactName.setText(<strong>getName(uri)</strong>);
        contactPhone.setText(<strong>getPhone(uri)</strong>);
        contactPic.setImageBitmap(<strong>getPic(uri)</strong>);
    }

Ahora solo queda resolver la obtención del nombre, el teléfono y la foto del contacto con los conocimientos que ya has aprendido en este artículo.

Obtener el nombre de un Contacto

La obtención del nombre se realiza a través de getName(). Este método recibe como parámetro la URI de contenido que nos entregó la actividad de selección en los contactos. El propósito es consultar la tabla Contacts el Contacts Provider y obtener el nombre del contacto, que es representado por la columna DISPLAY_NAME:

 private String getName(Uri uri) {

        /*
        Valor a retornar
         */
        String name = null;

         /*
        Obtener una instancia del Content Resolver
         */
        ContentResolver contentResolver = getContentResolver();

        /*
        Cursor para recorrer los datos de la consulta
         */
        Cursor c = contentResolver.query(
     uri, Contacts.DISPLAY_NAME, null, null, null);

        /*
        Consultando el primer y único resultado elegido
         */
        if(c.moveToFirst()){            
            name = c.getString(0);
        }

        /*
        Cerramos el cursor
         */
        c.close();

        return name;
    }

Al usar query() para consultar la URI de contenido, el cursor (Aprende más sobre cursores) resultante tendrá acceso a solo un registro particular, por lo que empleamos el método moveToFirst() para situarnos sobre él. En seguida usamos getString() y obtenemos el nombre del contacto para retornarlo.

Es posible consultar el nombre a través de la clase CommonDataKinds.StructuredName, pero como resulta un proceso mucho más interno, es mejor opción consultar DISPLAY_NAME de la tabla Contacts, ya que también guarda una instancia simple del nombre.

Obtener el teléfono de un Contacto

El método getPhone() retorna el teléfono móvil del contacto. A diferencia del nombre, la tabla Contacts no trae una instancia rápida del teléfono, por lo que es necesario realizar un JOIN implícito.

Lo primero que se realiza es una consulta del identificador del contacto retornado. Luego se consulta el teléfono a través de CommonDataKinds.Phone, usando una condición para solo obtener la fila precisa del contacto seleccionado. Además de ello debemos asegurarnos que el número telefónico es de un móvil (TYPE_MOBILE):

 private String getPhone(Uri uri) {
        /*
        Variables temporales para el id y el teléfono
         */
        String id = null;
        String phone = null;

        /************* PRIMERA CONSULTA ************/
        /*
        Obtener el _ID del contacto
         */
        Cursor contactCursor = getContentResolver().query(
                uri, new String[]{ContactsContract.Contacts._ID},
                null, null, null);


        if (contactCursor.moveToFirst()) {
            id = contactCursor.getString(0);
        }
        contactCursor.close();

        /************* SEGUNDA CONSULTA ************/
        /*
        Sentencia WHERE para especificar que solo deseamos
        números de telefonía móvil
         */
        String selectionArgs =
                        Phone.CONTACT_ID + " = ? AND " +
                        Phone.TYPE+"= " +
                        Phone.TYPE_MOBILE;

        /*
        Obtener el número telefónico
         */
        Cursor phoneCursor = getContentResolver().query(
                Phone.CONTENT_URI,
                new String[] { Phone.NUMBER },
                selectionArgs
                ,
                new String[] { id },
                null
        );
        if (phoneCursor.moveToFirst()) {
            phone = phoneCursor.getString(0);
        }
        phoneCursor.close();

        return phone;
    }

Obtener la foto de un Contacto

Y finalmente se obtiene la miniatura que tiene el contacto asignado. Este proceso se puede realizar de varias formas alternas debido a la riqueza del esquema del Contacts Provider. Una de las formas es usar la clase interna CommonDataKinds.Photo para obtener la secuencia de bytes que representa la foto a través de una doble consulta.

Otra forma es usar una clase llamada ContactsContract.Contacts.Photo. Esta clase representa un subdirectorio cuyo fin es guardar datos de la foto. Lo primero que debes hacer es obtener el identificador del contacto y luego crear una URI que represente el origen de la imagen a través del directorio (Ver más sobre este forma de obtener la foto de un contacto).

En nuestro caso usaremos otro camino. Para ello usaremos el método openContactPhotoInputStream() de la clase Contacts, el cual obtiene los datos de la foto del contacto con el CONTENT_URI concatenado a su identificador. Observa:

private Bitmap getPhoto(Uri uri) {
        /*
        Foto del contacto y su id
         */
        Bitmap photo = null;
        String id = null;

        /************* CONSULTA ************/
        Cursor contactCursor = getContentResolver().query(
                uri, new String[]{ContactsContract.Contacts._ID}, null, null, null);

        if (contactCursor.moveToFirst()) {
            id = contactCursor.getString(0);
        }
        contactCursor.close();

        /*
        Usar el método de clase openContactPhotoInputStream()
         */
        try {
            InputStream input =
                    ContactsContract.Contacts.openContactPhotoInputStream(
                            getContentResolver(),
                            ContentUris.withAppendedId(
                                    ContactsContract.Contacts.CONTENT_URI,
                                    Long.parseLong(id))
                    );
            if (input != null) {
                /*
                Dar formato tipo Bitmap a los bytes del BLOB
                correspondiente a la foto
                 */
                photo = BitmapFactory.decodeStream(input);
                input.close();
            }

        } catch (IOException iox) { /* Manejo de errores */ }
        
        return photo;
    }

El método openContactPhotoInputStream() retorna en una secuencia de bytes, así que lo guardamos en una nueva instancia InputStream. Este método recibe la instancia del ContentResolver de la actividad y la URI de contenido concatenada al identificador del contacto. Para concatenar usa el método estático withAppendedId() de la clase auxiliar ContentUris.

Luego das formato de Bitmap al BLOB que representa la foto. Para ello usa el método decodeStream() de la clase auxiliar BitmapFactory.

Enviar mensajes de texto con MsgManager

El MsgManager es un componente que nos permitirá enviar texto hacia otro teléfono móvil. Para acceder a él debes usar el método estático getDefault():

/*
Creando nuestro gestor de mensajes
 */
SmsManager smsManager = SmsManager.getDefault();

Luego usas el método sendTextMessage() junto a la URI de contenido del teléfono del contacto y la cadena que representa al mensaje:

/*
Enviando el mensaje
 */
smsManager.sendTextMessage(
        getPhone(contactUri),
        null,
        "¡Estamos aprendiendo a Desarrollar en Android!",
        null,
        null);

Ahora habilita el envío con el siguiente permiso:

<uses-permission android:name="android.permission.SEND_SMS"/>

Finalmente juntamos estas instrucciones en un método llamado sendMessage() y se lo asignamos al botón de envío sendButton a traves del archivo de diseño:

public void sendMessage(View v){

        /*
        Creando nuestro gestor de mensajes
         */
        SmsManager smsManager = SmsManager.getDefault();

        /*
        Enviando el mensaje
         */
        smsManager.sendTextMessage(
                getPhone(contactUri),
                null,
                "¡Estamos aprendiendo a Desarrollar en Android!",
                null,
                null);

        Toast.makeText(this, "Mensaje Enviado", Toast.LENGTH_LONG).show();

    }

Y finalmente modifica la propiedad onClick del botón de envío:

android:onClick="sendMessage"