Está en la página 1de 128

Proveedores de contenidos.

Mensajes y
Networking. Multimedia

Proveedores de contenidos
En la unidad anterior vimos distintas formas de acceder y guardar datos: preferencias del usuario, ficheros
en la memoria local y en tarjetas de memoria externas y el acceso a bases de datos SQL.

Aunque nos parezca suficiente, todavía nos falta algo más, y es el acceso a otros entornos de datos
distintos.

Compartir datos en Android


Ahora vamos a compartir datos entre aplicaciones Android. La mejor forma de realizar esta operación es
mediante proveedores de contenido. No nos importará la forma en la que está almacenada, sino cómo
podremos acceder a esa información.

Un proveedor de contenido es muy similar al concepto de base de datos: podremos realizar consultas,
editar y borrar su contenido. Sin embargo, el soporte de almacenamiento no tiene que ser necesariamente
una base de datos. Puede ser también un fichero o un soporte al que accederemos a través de Internet. Los
proveedores de contenido que nos aporta Android son:

Browser

Almacena datos como marcadores de páginas, historial de exploración, ...

CallLog

Almacena por ejemplo las llamadas perdidas. Es, por tanto, un registro de llamadas.

Contacts

Accederemos la lista de contactos.

MediaStore

Almacena formatos multimedia: vídeo, audio, imágenes, ...

Settings

Almacena configuraciones del dispositivo y preferencias.

Como vemos, ampliamos el concepto del acceso a datos del tema anterior para acceder a todas las áreas
de nuestro dispositivo en el que tenemos datos almacenados. Además de estos proveedores de contenido,
podremos crear los que nosotros queramos.
Un proveedor de contenidos proporciona acceso a datos estructurados de diferentes aplicaciones de
nuestro dispositivo. La forma en la que se almacenan es similar a lo que vimos con SQLLite, pero el
sentido es más amplio. No sólo se reduce a tablas, sino que nos abstraemos un poco del origen de datos
para utilizar el mismo modelo de objetos para el acceso.

Veamos las partes en la que se compone el acceso a un proveedor de contenidos.

Proveedor de contenidos

Un proveedor de contenidos se crea como una subclase de "android.content.ContentProvider". Lo normal


es que la aplicación responsable de administrar los datos compartidos incluya un proveedor de contenidos
para poder compartir los datos con otras aplicaciones.

Los métodos que sobrescribiremos de la clase "Content Provider" para poder realizar esas acciones, son:

onCreate()

Llamaremos a este método para iniciar el proveedor de contenido.

query()

Este método recibe una petición desde un cliente y devuelve un objeto "Cursor".

insert()

Inserta un registro en el proveedor de contenido.

delete()

Elimina un registro del proveedor de contenido.

update()

Actualiza un registro.

getType()

Devuelve el tipo "MIME" de los datos proporcionados en la URI.

Excepto el método "onCreate", los demás métodos pueden ser llamados por otros procesos de forma
simultánea, por lo que tendremos que tener en cuenta el acceso seguro.

Contenido URI
1

Para consultar un proveedor de contenidos, especificaremos la consulta en forma de URI. El formato es


el siguiente:

<prefijo_estándar>://<autority>/<data_path>/<id>

Que se refieren a estos conceptos:

El prefijo estándar es siempre "content://".


Autority especifica el nombre del proveedor de contenido. Por ejemplo "contacts". Otros
proveedores de otros fabricantes se indican su nombre completo: "net.learning.android".
data_path indica el tipo de datos solicitado. Por ejemplo, si estamos consultando contactos, la
ruta será "people", de esta forma: content://contacts/people.
Por último "id" identifica el registro solicitado. Por ejemplo: content://contacts/people/2.

Vemos algunos ejemplos de consultas:

Para acceder a todas las imágenes del dispositivo y de la memoria interna

content://media/internal/images

content://media/external/images

Acceder a la lista de llamadas del registro de llamadas


content: //call_logs/calls

Lista de marcadores del navegador


content://browser/bookmarks

Mi propio proveedor
content://com.example.mibbdd.miproveedor

Una tabla de mi propio proveedor


content://com.example.mibbdd.miproveedor/catalogo

Una fila de una tabla de mi propio proveedor


content://com.example.mibbdd.miproveedor/catalogo/1
3

Recordemos la llamada al "intent" que nos mostraba la lista de contactos:

Por tanto, esta forma de consulta o de iteración con distintas bases de datos, pretende realizar todas las
operaciones de una forma similar. Los pasos deben ser los siguientes:

Lo primero de todo crearemos una clase "Content Provider" que se extenderá de


ContentProviderbase.
Después, definiremos una dirección "URI" con el proveedor de contenido que nos permitirá
acceder al contenido.
Después ya podemos utilizar la sintaxis de bases de datos para mantener el contenido a través
de SQL Lite. Por ejemplo, con "onCreate" obtendremos el acceso a los datos.
A continuación, implementaremos nuestras propias consultas para realizar las operaciones que
necesitemos en nuestra aplicación.
Finalmente, registraremos nuestro proveedor de contenido en la aplicación utilizando "
<provider>".

Además de utilizar los proveedores de contenido que hemos mencionado antes, podemos crear los
nuestros propios. Dando un paso más para tener unasintaxiscoherentepara el acceso a cualquier dato.

Internet y nuestro dispositivo Android es una fuente de inmensos recursos. La forma


de localizar estos recursos es a través de URI. Por ejemplo, conocemos la forma de
acceder a recursos de Internet con: http://, ftp://. En Android utilizaremos esta notación
para acceder a los distintos recursos que nos ofrece nuestro dispositivo.

Más información de los URI

El "Content Resolver"
Prefiero dejarlo sin traducir, pero lo que realiza este paso es acceder al proveedor de contenido a través de
un objeto "ContentResolver". Por tanto, una aplicación, obtendrá una referencia a este contenido
realizando una llamada al método "getContentReolver" del contexto de la aplicación.

El objeto "content resolver" contiene una serie de métodos similares a los que proporciona el proveedor de
contenidos (insert, query, ...). La aplicación simplemente hace una llamada a estos métodos especificando
la URI del origen de los datos. Entonces los objetos "content resolver" y "content provider" se encargan
de comunicarse con los datos para realizar las peticiones.

El "<provider>" Manifest Element

Para que un proveedor de contenidos sea visible en el dispositivo, para el caso de que queramos crear el
nuestro propio, debemos declararlo en el archivo de configuración "androidmanifest". Se declara utilizando
la etiqueta "<provider>" y debe tener estos elementos:

android:authority

El nombre completo URI del proveedor de contenido. Por ejemplo:


com.example.miaplicacion.mibasedatos.miproveedor.

android:name

Nombre de la clase que implementa el proveedor de contenidos. La mayoría de las veces coincide con
el valor anterior.

Además, utilizaremos esta sección "<provider>" para proporcionar permisos. Sino se especifica nada todas
las aplicaciones pueden acceder al contenido. Los permisos pueden cubrir desde el proveedor completo,
acceso a las tablas o incluso a los registros.

Utilizar un proveedor de contenido


Vamos a trabajar con un proveedor de contenido y así veremos que también se trata de una técnica
sencilla. Sólo tendremos que saber los objetos adecuados para trabajar con estos proveedores de
contenido.

Consultar la base de datos de contactos de nuestro dispositivo


1

Vamos a consultar la base de datos de contactos de nuestro dispositivo:


2

En el caso de la base de datos de contactos tenemos varias tablas. En una de ellas "raw contact"
tenemos digamos, los datos principales y enlaces a otros. Si nos fijamos en este ejemplo:

Podemos ver las tres tablas de las que se compone. "Contacts" tiene los datos básicos de las fichas
de contactos. "RawContacts" tiene toda la colección de datos, por ejemplo, sus teléfonos,
direcciones de correo, ... En "Data" tenemos los datos asociados a cada una de las filas anteriores.
3

Crearemos una aplicación que tenga en el layout una sola etiqueta donde escribiremos los contactos
de nuestro dispositivo:

Ahora vamos con la actividad principal. Por un lado, recogemos esa etiqueta y llamamos a un método
que es el que realizará la consulta:
5

Sigue código (2):


6

Sigue código (y 3):


7

Antes de ejecutar la aplicación proporcionaremos permisos para poder leer los contactos:

Si hubiéramos intentado ejecutar la aplicación habríamos obtenido un error en tiempo de ejecución


porque no tendríamos permisos para acceder a leer esa zona. El registro de ejecución nos habría
dado esta pista:
9

Antes de ejecutar la aplicación debemos añadir algunos contactos:

Realizar cambios para ejecutar en Android 6

Ahora tenemos que recordar lo que vimos en la unidad 4 sobre la nueva gestión de permisos en
Android 6.0. En aquellos ejemplos de llamadas a los Intents, vimos que con Android 5 funcionaban,
pero con Android 6 no. Esto se debe a que los permisos antes se aceptaban en la instalación de la
aplicación en el dispositivo, sin embargo, ahora se realiza de forma dinámica según se utiliza. Así que
la línea que hemos puesto en el "androidmanifest" para que la aplicación pudiera leer contactos no va
a funcionar en los dispositivos con Android 6.0 y la aplicación dejará de funcionar.

Vamos a realizar los cambios para que podamos ejecutarlo en nuestro Android 6. Primero vamos a
añadir una constante con el permiso que vamos a solicitar y la identificamos con un número
cualquiera, por ejemplo, el 100:
2

Ahora en el método que hemos visto antes comprobamos si la versión es inferior a la 6.0
(Build.VERSION_CODES.M) o ya tiene permiso (PERMISSION_GRANTED)

Si es inferior o ya tiene permiso ejecutamos el código de consulta de contactos con normalidad. Si


no tiene permisos hacemos la petición al usuario. El resultado se pasa a través del método
"onRequestPermissionResult". En este método, es necesario comprobar si el usuario otorga el
permiso o no y cambiarremo el comportamiento de la aplicación en función del resultado:

Si concedemos permisos, perfecto, llamamos otra vez al método "cargar_contactos". Como éste
tiene la comprobación al principio de si tienen ya los permisos aceptados, podremos ejecutarlo sin
problemas. Si el usuario indica que no, mostraremos un mensaje.

Esta nueva gestión de permisos nos aporta algo de complejidad, pero es siempre el
mismo método. Con la versión 6 y posteriores se pretende que el usuario sea más
consciente de que la aplicación va a acceder a datos potencialmente peligrosos o
privados.
5

Antes de ejecutar la aplicación, vamos con la explicación de cómo estamos realizando la consulta.
Hemos comentado que la base de datos de contactos es algo más complicada porque tiene varias
tablas, por tanto, tendremos que acceder a ellas de forma distinta para poder recoger todos los
valores que queremos mostrar. Para empezar:

Hemos creado debajo un cursor con "CONTENT_URI" que es una "URI" que apunta a
"ContactsContract.COntacts.CONTENT_URI". El cursor se ha creado llamando al método "query"
del objeto "contentResolver". Por tanto, este objeto nos ayuda a abrir una conexión con el URI que le
estamos indicando, tal y como hemos comentado al principio.

Ahora recorremos ese cursor de la forma que ya conocemos de la unidad anterior, comprobando
primero que haya registros en la consulta con el método "getCount" del cursor:

Extraer los datos


1

Ahora vamos a extraer los datos que queremos mostrar:

Los datos del identificador y nombre "_ID","DISPLAY_NAME" que son las constantes que
creamos arriba y que apuntan a "ContactsContract.Contacts._ID;" y
"ContactsContract.Contacts.DISPLAY_NAME" ya los podemos extraer directamente. La variable
numérica "hasPhoneNumber" nos indica si tiene teléfonos en la base de datos. Si es así vamos a
consultarlos. Puesto que debemos hacer otra consulta a la base de datos indicamos el siguiente URI:
3

La consulta será ahora la URI "ContactsContract.CommonDataKinds.Phone.CONTENT_URI". Por


tanto, creamos un cursos, pero esta vez debemos pasar un parámetro que es el identificador del
registro. La forma de pasar los datos es similar a lo que vimos en la unidad anterior donde poníamos
el campo y el dato:

Una vez tenemos el cursor, hacemos un bucle "while" concatenando el "String" que hemos declarado
antes para escribir en él todo el contenido de nuestra base de datos de contactos.

Para el correo electrónico hacemos de forma parecida, pero consultando a la URI:

Ejecutar
1

Al ejecutarlo, y como nuestro dispositivo es un Android 6.0 nos pedirá permiso para acceder a los
contactos:

Si no damos permisos no podremos acceder a leerlos:


3

Pero si aceptamos, podremos leerlos sin problemas. El resultado será que podemos recorrer la base
de datos de nuestros contactos:

En el segundo registro se han definido dos teléfonos y dos direcciones de correo para comprobar
que nuestros bucles funcionan...

Ver o modificar los permisos


1

Para ver o modificar los permisos otorgados a las aplicaciones con este nuevo sistema, debemos ir a
la configuración del dispositivo y localizar la aplicación en la lista de aplicaciones instaladas:

Una vez seleccionada, podemos ver ya los permisos que tiene otorgados:
3

Pulsando en esta opción podremos activar o desactivarlos:

Proveedor de contenidos: contactos

Ejemplo utilizando un listView


1

Vamos a realizar un ejemplo algo más sencillo pero enlazado a una lista "ListView". Además, vamos a
utilizar un método para recorrer todo el cursor y mostrar los elementos de una forma muy similar a la
anterior. La actividad principal quedaría:

Veamos ahora el método para explorar el cursor"Escribe_contactos (c)":


3

(Sigue método:)

Lo recorremos de una forma que ya vimos antes. Comenzando colocándonos en el primero registro con
"moveFirst" y realizando un "moveNext" al final del bucle. Además, y para realizar un seguimiento en
nuestro programa y ayudarnos en la depuración, hemos puesto que nos escriba en la ventana de registro
"LogCat":
5

La ejecución de la aplicación se hace ahora de una forma más controlada en la escritura de los registros
ya que en lugar de estar enlazada directamente, lo vamos escribiendo fila a fila:

Muestra en pantalla el identificador y el nombre del contacto almacenado en la aplicación "Contacts".


Podemos mejorar la aplicación creando un layout aparte para mostrar la lista como hicimos en la unidad
anterior.

Si queremos mostrar además el número de teléfono, tendremos que añadirlo en la visualización del
cursor. Como no se trata de un campo simple, sino que se trata de una lista, tendremos que tratarlo
como un segundo cursor. Es decir, el número de teléfono de un contacto no es solo un campo. Por eso
hemos añadido ese segundo bucle localizando la lista de teléfonos de cada contacto.

Primero vemos si tenemos algún teléfono en la lista, consultando la propiedad


"HAS_PHONE_NUMBER":

Si devuelve un "1" quiere decir que hay teléfonos en la lista de ese contacto. Así que volvemos a
consultar con un cursor, pero esta vez la lista de teléfonos, que mostramos en la ventana de registro.
7

Proveedor de contenido: permite acceder a datos de otras aplicaciones. En este caso


hemos accedido a la aplicación "Contacts" del sistema operativo y que almacena,
como ya sabemos, la lista de contactos de nuestro dispositivo.

Proyecciones

El segundo parámetro del método "managedQuery" controla el número de columnas que devuelve la
consulta. Es un parámetro que no habíamos utilizado antes:

Este parámetro se conoce como "proyección". Podemos especificar el número de columnas devuelto
creando una matriz o array con los nombres de las columnas que queremos obtener. Por ejemplo:

En este caso, la consulta es más sencilla y además obtenemos de una vez las tres columnas.

Filtros

Los parámetros tercero y cuarto del método "managedQuery" nos permite habilitar la cláusula SQL
"where" en la consulta de los contactos. De esta forma podremos filtrar el contenido. Por ejemplo, esta
consulta recuperaría solo los registros con nombre "jose":

También podemos poner esta consulta con un cuarto parámetro, parametrizando la consulta:

Sustituimos el "?" por el cuarto parámetros que es un string con el filtro de consulta.

Ordenación
El quinto y último parámetro permite ordenar el conjunto de resultados, habilitando la instrucción SQL
"ORDER BY". Por ejemplo, para ordenar de forma ascendente:

Si quisiéramos en orden descendente pondríamos la instrucción "DESC".

Mensajes SMS
En esta sección aprenderemos a trabajar con mensajes SMS. Android incorpora una aplicación para la
gestión de los mensajes SMS. Sin embargo, y como en otros casos que hemos visto a lo largo del curso,
queremos integrar capacidades de SMS en nuestros programas.

Por ejemplo, queremos enviar mensajes SMS repetitivamente o cuando se dé un suceso. O, como hacen
algunas aplicaciones que envíe cada cierto tiempo las coordenadas GPS donde se encuentre el terminal.
Vamos a comenzar enviando mensajes SMS.

Enviar mensajes SMS mediante programación


Introducción

Esta sección no es más sencilla ni compleja que otras, simplemente todo es distinto. Los objetos, los
métodos, las propiedades... todo lo que vamos a ver ahora es nuevo ya que no hemos aplicado nada
parecido a lo largo del curso.

Es lo habitual en la programación con Android. Necesitaremos trabajar por ejemplo con los sensores
que incorpora. ¿hay que estudiarlos a fondo? Sí y no. Sí un poco para ver que objetos son y no porque
no se puede profundizar en todos los ámbitos sin convertirse este curso de introducción en algo
interminable. Una vez conocidos los objetos de los sensores, o de SMS, como es este caso, solo
debemos ver código de ejemplo y la referencia de los métodos que podemos utilizar. De ahí que cada
área que veamos sea todo nuevo, mejor dicho, ya sabemos programar y lo que es nuevo son los
objetos.

Lo que vamos a ver en esta sección es todo el código necesario para realizar las operaciones de gestión
de los mensajes SMS por lo que será un ejemplo perfecto para seguir aumentando nuestra colección de
código.
1

Vamos a crear un sencillo ejemplo que envíe un SMS. Nuestro programa tendrá solo un botón:

Añadimos los permisos necesarios en el fichero de configuración del proyecto:


3

Este permiso es muy importante. Puesto que para el envío de SMS necesita acceso al hardware, al
instalarse la aplicación le indicará al usuario que la aplicación va a enviar mensajes SMS, así queda a
confirmación del usuario si va a querer que se instale.

Muchas aplicaciones de dudoso origen ejecutan el código que vas a ver a continuación
para realizar subscripciones a servicios SMS de pago sin que se entere el usuario. Sí se
ha podido enterar, pero lo ha pasado por alto. ¿Por qué una aplicación como un
salvapantallas necesita permisos de envío de SMS? No los necesita, si lo pide al
instalar la aplicación debemos sospechar inmediatamente. El "malware" o el software
que hace otras cosas distintas a las que pensamos es el gran problema en la seguridad.

Esto es lo que nos ayuda a controlar el nuevo sistema de permisos que vimos en el
ejemplo anterior de la lectura de contenidos.

Ahora vamos con el código de la actividad principal, incluyendo el gestor de permisos de la versión
Android 6.
5

Y a h o r a el método para enviar el mensaje con la comprobación de si el permiso


"Manifest.permission.SEND_SMS" está activo:

Para probar el envío de mensajes vamos a hacerlo entre dos emuladores Android. Así al enviar de uno a
otro podremos ver los resultados. Ejecutaremos la aplicación y lanzaremos un nuevo emulador, para
tener dos en ejecución desde el desplegable de dispositivos virtuales:
7

De los dos lo instalamos en uno de ellos nada más, pero podríamos lanzarlo en varios de ellos de esta
forma:

No podemos lanzar dos veces el mismo emulador, así que tendremos otro creado. En los emuladores
veremos en la parte superior su número de identificación.
Vemos en el emulador principal en el título "Android_10_API_29:5554". Ese número "5556" es el
número de teléfono al que responderá. El otro emulador no lo muestra por la máscara gráfica que
incluye el emulador. Podemos ver su número con las propiedades del emulador:
Por eso al enviar un SMS desde el ejemplo que hemos hecho veremos que lo recibe este segundo
emulador, tal y como vemos en el área de notificaciones.

Lógicamente la primera vez nos ha pedido permisos para enviar el mensaje:

10

Nuestro ejemplo ha funcionado. Hemos conseguido enviar un mensaje SMS por código. Veamos ahora
con detalle el código que hemos utilizado. Básicamente es una actividad con un botón y un "listener"
para el evento clic del botón. El código nuevo lo tenemos aquí:

11

Hay una clase que hará todo el trabajo del envío de SMS: SmsManager. Al contrario que otras clases,
no es necesario que la "instanciemos", es decir que creemos una instancia de ella con "new()". Sino que
basta con llamar al método "getDefault" para obtener una referencia de ella en un objeto y así poder
utilizarlo en el programa.

Con este objeto basta con utilizar el método "sendTextMessage" para enviar un SMS. Los parámetros
que acepta con:

Número de teléfono de destino.


Dirección del centro de servicio de mensajes.
Texto o contenido del mensaje.
setIntent. Lo veremos enseguida. Intent a invocar cuando se ha enviado un mensaje.
deliveryIntent. Lo veremos enseguida. Intent a invocar cuando se ha entregado un mensaje.

Los dos últimos parámetros tratan de los acuses de recibo. Hay dos: uno de envío del mensaje al
servidor y otro de confirmación de entrega al usuario.

Acuse de recibo del envío del mensaje


Ya hemos enviado un mensaje, pero no sabemos si se ha enviado correctamente, así que tendremos que
añadir a nuestro código lo necesario para obtener este resultado. Para esto vamos a crear dos objetos
"PendingIntent" y así monitorizar el estado del envió del mensaje.
1

Se trata de los dos últimos parámetros que vimos antes y que no habíamos utilizado en el ejemplo.
Vamos a ampliar el código de nuestro procedimiento de envío:

Por simplificar el código la parte de los códigos de respuesta devueltos en los dos "registerReceiver"
los veremos a continuación. Hemos creado dos procesos de espera, o dos objetos "PendingIntent".
Este tipo de objetos es un "listener" especial a un mensaje del sistema operativo. Como es él quien envía
el SMS, debemos esperar el mensaje de él. Así que creamos dos escuchadores de tipo
"BroadcastReceiver". Es decir, escucha eventos del sistema operativo y si dos de ellos son
"SMS_SENT" o "SMS_DELIVERED" ejecuta estas instrucciones.

Broadcast Receiver

Un Broadcast Receiver es un receptor de eventos que produce el sistema operativo


Android. Habitualmente, un Broadcast Receiver se utiliza para mostrar notificaciones
de los eventos que ocurren en nuestro dispositivo, como por ejemplo el
descubrimiento de una red wifi o el agotamiento de la batería.
Hay decenas de eventos del sistema operativo, por ejemplo:

android.provider.Telephony.SMS_RECEIVED Evento de mensaje recibido.


android.intent.action.PHONE_STATE Evento de llamadas recibidas.
android.intent.action.BATTERY_LOW Evento batería baja.
android.intent.action.SCREEN_ON Evento desbloqueo de pantalla.
android.bluetooth.intent.action.DISCOVERY_STARTED Evento comienzo
de escáner Bluetooth.
android.bluetooth.intent.action.ENABLED Evento Bluetooth habilitado.

Veremos ejemplos de cómo crear y registrar "BroadcastReceiver".

Si vemos de forma aislada su definición, lo veremos más sencillo:

Primero tenemos la definición de un "PendingIntent.getBroadCast" donde definimos que se escuche


este evento del sistema operativo. Luego definimos este objeto "broadcast" para poder acceder a su
evento "onReceive" y así detectar cuando se ha producido. En ese momento sabremos ya el estado de
los dos procesos. En ambos casos haremos un "switch" para, dependiendo del resultado obtenido,
escribamos un "Toast" con este estado devuelto.
4

Para el proceso de envío sería:

Y para el proceso de entrega:


6

Ahora el código para el envío de mensajes SMS quedará completo con estos dos "PendingIntent".
Ejecutamos el programa y al enviar de forma correcta un mensaje:

Obtenemos el código devuelto en el proceso de envío del mensaje y lo mostramos con un "Toast".

Envío de mensajes mediante "Intents"


Al principio del curso vimos la definición de las actividades y de los "intents" como dos de los pilares de
las aplicaciones Android. Ahora retomamos la ejecución de los Intents para implementar este envío de
mensajes con simples llamadas a la aplicación del sistema operativo que realiza estas operaciones en lugar
de hacerlo de forma manual con los objetos. El código se reduce radicalmente ya que estas llamadas eran
procesos realmente sencillos y solo debíamos aprender la sintaxis adecuada.
1

Vamos a una aplicación. Creamos una nueva similar a las anteriores con un botón para enviar el mensaje
y un listener de este botón para atender el evento clic:
2

(sigue listener):

Si repasamos el código y recordamos el funcionamiento de las llamadas a "Intents" el código no puede


ser más sencillo. Solo debemos definir un "Intent" nuevo y asignarle los parámetros adecuados:

Parámetro de dirección destino: "Uri.parse ("sms:" + telefonos).


Parámetro de contenido del mensaje "sms_body".

"sms:" identifica para Android la aplicación con la que debe inciarse.


4

Ejecutamos la aplicación, pulsamos en el botón de enviar y se pondrá en marcha automáticamente la


aplicación del sistema operativo que envía mensajes SMS. Además, mostrará los dos parámetros de
dirección destino, que en el caso de los mensajes SMS es el teléfono destino, y el contenido del
mensaje:
5

Si pulsamos en enviar, se mandarán a los dos números de teléfono que hemos indicado:

Tenemos menos control sobre el proceso, pero a veces podremos necesitar implementar de forma
rápida un envío de mensajes SMS. Con una llamada al programa del sistema operativo en 4 líneas de
código, podemos hacer todo el proceso.

Recibir mensajes SMS


Además de enviar mensajes, lógicamente también podemos recibir mensajes. Debemos escuchar el sistema
operativo para detectar mensajes SMS entrantes, por tanto, parece claro que debemos implementar otra vez
un objeto "BroadCastReceiver".

Esta funcionalidad no parece interesante en principio, pero nos abre un abanico de opciones que sí que
pueden sernos de utilidad en el futuro. Por ejemplo, la recepción y tratamiento de mensajes que incorporan
la geolocalización. Vamos un ejemplo.

Partiremos del ejemplo que hicimos antes de los Intents. Donde añadiremos una nueva actividad en la que
implementaremos este "escuchador" de eventos del sistema operativo. Añadimos un nuevo fichero que va
a definir por sí mismo un "BroadcastReceived". Es decir, en lugar de "extends activity" será "extends
BroadCastReceived".

Implementar un proceso de escucha


1

La idea es implementar un proceso que esté escuchando permanentemente. Así que debemos
implementarlo en el proyecto. Para esto, como hemos dicho añadimos un nuevo fichero "java" a
nuestro proyecto:
2

En este segundo fichero pondremos este código:


3

Antes de ejecutarlo vamos a completar el fichero de configuración de proyecto para añadir este
"receiver":

El fichero "Manifest" tiene ahora los dos permisos: enviar y recibir mensajes. Además, hemos
definido un "receiver" para escuchar estos eventos. Para escuchar la entrada de mensajes SMS
hemos creado una clase "BroadCastReceiver". Esta clase habilita a nuestra aplicación a recibir
"intents" de otras aplicaciones utilizando el método "sendBroadcast".

Como los "intens" ya sabemos que son las comunicaciones entre aplicaciones, estamos
implementando eso, que nuestra aplicación también esté escuchando las llamadas de otras
aplicaciones (como es el caso de la aplicación que gestiona los SMS).
5

Cuando llegue "Intent" a nuestra aplicación, se invoca al método "onReceive", así que será el que
hagamos un "override" para implementar nuestro código:

Al dispararse este método podremos acceder al mensaje SMS que está contenido dentro del objeto
"Intent" mediante un objeto "Bundle", que era utilizado para mover datos entre los "intents".

El mensaje está almacenado en un objeto "array" en formato PDU y debemos extraerlo. Para extraer
cada mensaje utilizaremos el método estático "createFromPdu()" de la clase "SmsMessage". Luego
lo mostramos con un "Toast":
7

Anotación: Modo PDU


El modo PDU trata el SMS como una cadena de caracteres en octetos
hexadecimales de cuya codificación resulta el SMS en modo texto. La ventaja de
modo PDU respecto al modo texto es que en modo texto la aplicación queda
limitada a la opción de codificación que se haya preestablecido, en modo PDU se
puede implementar cualquier codificación.

La cadena PDU no solo contiene el mensaje, sino que lleva información del centro
de servicio SMS (¿AT+CSCA?), hora de llegada, tipo de mensaje, información
sobre el que envía el SMS, vigencia del SMS, nº de caracteres del SMS, tipo de
llamada (nacional ó internacional), tipo de alfabeto usado…

Puesta en marcha

Lo ponemos en marcha y luego nos vamos a la vista del dispositivo y así acceder a las opciones del
emulador:
2

Lanzar el mensaje desde el emulador:

Podemos indicar que le envíe a nuestro dispositivo un mensaje SMS, así simularemos perfectamente
esta acción. Enviamos uno y la parte del "receiver", es decir la que escucha los mensajes, detectará
este SMS y nos lo avisará en pantalla con un "Toast":
4

Abrimos el mensaje y lo tendremos en la aplicación habitual de mensajes SMS del dispositivo.

Una característica interesante del uso de "BroadcastReceiver" es que podemos seguir escuchando la
entrada de nuevos mensajes SMS incluso si la aplicación no se está ejecutando. Una vez que la
aplicación se ha instalado en el dispositivo todos los mensajes entrantes pasarán por este programa.

Podemos hacer la prueba moviéndonos a la pantalla principal de nuestro Android, donde veremos
que salta nuestro programa interceptando el mensaje entrante. Son cosas de las comunicaciones entre
los "intents".
5

En Android Studio tenemos la forma de añadir los "broadcastreceiver":


6

Le ponemos un nombre:

Vemos además que nos va a añadir este "broadcastreceiver" en el fichero de AndroidManifest.


Veamos lo que nos ha creado:
8

Y además el obligatorio registro en el fichero de configuración:

Aunque sin completar, pero por lo menos tenemos la sintaxis de ambas partes. Como hemos visto en
el código, si la versión de Android es la 6 o superior debemos poner un parámetro adicional de
formato. Este nuevo añadido es para dar soporte a nuevos formatos de los mensajes: "3gpp" para
GSM/UMTS/LTE en formato 3GPP o "3gpp2" para CDMA/LTE en formato 3GPP2.

Envío de mensajes SMS

Enviar mensajes de correo electrónico


Después del ejemplo anterior, el envío de mensajes se convierte en una tarea trivial. Crearemos una nueva
aplicación utilizando "intents" para enviar mensajes de correo electrónico.
1

Creamos una aplicación con un botón:


2

Ahora creamos como en el caso anterior el marco de la aplicación creando un "listener" para el evento
clic del botón:

El código no tiene nada de especial todavía. Se trata de una actividad normal. Para los mensajes
necesitaremos dos matrices o "arrays" de "String" para poner la lista de destinatarios del correo y los
que figurarán en conocimiento (cc). Como vemos, se han construido en el evento clic del botón. Luego
hemos hecho una llamada al método "sendEmail" con los parámetros de destino, copia, asunto y cuerpo
del mensaje.
3

Ahora solo debemos crear un nuevo "Intent" con los parámetros necesarios para construir un mensaje
de correo electrónico:

Los parámetros "putExtra" van alimentando la llamada al "Intent". Con setType identificamos que tipo
de llamada vamos a realizar y, por fin, "startActivity" hace la llamada final al gesto de correo electrónico
de Android.
4

Pulsamos para enviar y obtendremos un mensaje de que este tipo de acción la podemos finalizar con
dos aplicaciones:
5

Seleccionamos Gmail y, sino tenemos una cuenta configurada, nos pedirá que lo hagamos. Una vez
configurada:

podremos enviar los mensajes con normalidad.

Networking
En los puntos anteriores vimos cómo enviar correo electrónico y una gestión completa de los SMS. Otro
de los protocolos que nos interesa controlar es el HTTP, que como sabes, es el protocolo para las páginas
Web.

Gestionando este protocolo podremos realizar tareas a través de él, ya sea para realizar descargas de
ficheros, páginas web u otro contenido. Vamos a crear un proyecto para ver cómo podemos controlar
estas operaciones.
1

En el proyecto pondremos una imagen:

Ahora comencemos dando permisos para que nuestro programa acceda a Internet. Y además una línea
de permiso adicional para poder leer de internet:
3

Importante: Obligatoriamente asíncronas

Hasta hace poco estas operaciones, como la de cargar en línea un gráfico por Internet
se podían realizar directamente. Desde hace algunas API's estas tareas deben ser
obligatoriamente asíncronas. Así que debemos crear una tarea asíncrona y lanzar
nuestro proceso de lectura del gráfico de Internet.

En la unidad 12 veremos con detalle las tareas.

En la práctica estos procesos se dejan a procesos asíncronos. Nosotros lanzamos la petición y en


cuanto se pueda se realizará y se mostrará el resultado. Si nos encontramos con una red lenta, no es
buena técnica esperar de forma "síncrona" porque seguramente obtengamos un error de que se ha
excedido el tiempo de espera "timeout".

Si estamos descargando datos de gran volumen, tampoco es buena técnica que dejemos la aplicación
"congelada" mientras se descargan los datos. Lo ideal es lanzar la descarga de fondo, es decir, de forma
"asíncrona".

Este nuevo concepto es muy interesante y abre muchas posibilidades a nuestros programas que
requieren conexiones remotas. El trabajo recaerá en la clase AsyncTask. Esta clase permitirá realizar
tareas de fondo en un "hilo" distinto al principal. Es decir, vamos a poder realizar otras aplicaciones de
fondo en nuestro programa sin tener que realizar complicados diseños multitarea.

Vamos a modificar el ejemplo de la descarga de la imagen para hacer una prueba. El programa principal
se reduce ahora a una simple llamada a ejecutar la tarea de fondo, mandando como parámetro la
dirección URL del gráfico.
4

Este será el esquema del programa:

(Continúa esquema):
6

Definimos una clase "extendiéndola" de "AsyncTask". Dentro de esta definición de clase tenemos dos
métodos: uno para el proceso que debe ejecutar con "doInBackGround" y otro cuando lo haya
terminado "onPostExecute".

En la primera parte hemos descargado la imagen. Mientras está realizándose esta tarea, los usuarios
tienen completo control en la aplicación.

Por ejemplo, una disposición en pantalla de cuadrícula con muchas imágenes de internet sería
impensable que se hiciera de forma síncrona. Podemos, mediante esta técnica, lanzar la carga de todas
esas imágenes y así el usuario puede tener control de la aplicación mientras se van mostrando en
pantalla.

El segundo método se ejecuta cuando la tarea se ha completado. En el programa principal hemos creado
un objeto de tipo "descarga_imagen" y llamado a su método "execute". Este método es nuestra tarea
asíncrona:
7

Al invocar al método estamos ejecutando "doInBackGround", es decir, "ejecuta en segundo plano el


método 'descargar' con el parámetro 'url'":
8

Así que llamamos a método "descargar" con la URL de la imagen a descargar. Este método de tipo
"Bitmap" devolverá el gráfico que luego asignaremos en el layout. Para descargar el gráfico utilizaremos
el método "getHttpConnection" que es el que realiza realmente el proceso del "GET" o coger de Internet:
9

Hemos definido un método de tipo "InputStream", es decir una corriente de datos que será en este caso
HTTP. En lugar de una corriente de bytes que sería un fichero, en este caso sería a través de Internet y
leyendo el protocolo HTTP.

Este método "getHttpConnection()" acepta un "string" que será la "URL" a la que debe acceder y
devuelve un InputStream. Si reducimos un poco para que sea más legible vemos que el código es:

Vemos que el método tiene la cláusula "throws IOException". Esta sentencia, "throws" lo que hace es
generar una excepción que la hemos llamado "IOException" si se produce un error en la ejecución.
Dentro del método hemos definido el "InputStream" que devolverá si todo es correcto en el final con
"return in". Creamos un objeto de tipo URL estableciendo en él la dirección URL y abrimos la
conexión con "openConnection".
10

Si no hemos podido realizar esa conexión generamos la excepción y sino, quiere decir que al menos
podemos acceder al destino, intentamos la lectura con dentro del "try/catch".

La similitud con el tratamiento de un fichero es mucha, sólo debemos conocer los objetos adecuados.
Recuerda que no hay que aprenderse nada de memoria, sino tener una buena documentación y
ejemplos. Cuando ya hemos establecido la conexión y todo es correcto procedemos a la descarga de
los datos:

11

HttpURLConnection es un objeto para abrir la conexión HTTP de la URL. Si vemos la definición:

Estamos definiendo un objeto httpConn de tipo "HttpURLConnection y le estamos asignando la


conexión "conn" que hemos creado antes. Después de esta definición le indicamos unos cuantos
parámetros para definir el tipo de descarga que vamos a realizar.

Una vez definidos establecemos la conexión final con "httpConn.connect()". Conectamos con el
servidor y hacemos un "get" de los datos. SI se establece la conexión la respuesta será un "HTTP_OK"
y si no se disparará una excepción. Con la conexión correcta finalmente tenemos en "in" el
"InputStream" con los datos descargados.

Este es el marco de la aplicación. Ahora lo iremos personalizando para trabajar con varios tipos de
descargas.

Descargar datos binarios


Uno de los procesos más habituales en las conexiones a la red o a Internet es la descarga de datos. Por
ejemplo, la descarga de una imagen o la descarga de un documento PDF. En el ejemplo anterior pusimos
en el fichero de recursos de la actividad un control de tipo imagen. Así que vamos a descargar ahí una
imagen desde Internet.
1

Además del código anterior vamos a añadir un código para pasarle los datos necesarios al método
anterior y para mostrar la imagen resultado en la pantalla. El código será:

Además de este código hemos definido a nivel de clase un objeto de tipo imagen llamado "bitmap". El
funcionamiento es sencillo. En el método "oncreate" de la actividad vemos tres sencillo pasos:
definimos la URL, recogemos con "findViewById" la referencia del fichero gráfico que hemos puesto
en el diseño de recursos y lanzamos la tarea asíncrona de la lectura por Internet.

Finalmente, el evento que se dispara al terminarse la tarea asíncrona, tendremos el bitmap que
asociaremos a nuestro gráfico:
4

El resultado es:

Bueno, es interesante. Nos hemos conectado a Internet, hemos descargado un fichero binario, una
imagen, y la hemos mostrado en pantalla en un control de la pantalla de tipo "ImageView". El proceso
está en el método "Downloadimage", así que vamos a analizarlo.

Este método tiene como parámetro de entrada la dirección URL de donde tiene que descargar la imagen.
Definimos internamente dos variables: un "bitmap" para que devolvamos ahí la imagen resultante y un
"InputStream" para descargarla.

Una vez definidas hacemos una llamada al método anterior dentro de un "try/catch" para descargar la
imagen:

Si todo es correcto asignamos a "bitmap" el "stream" descargado dando con el método


"BitmapFactory.decodeStream" el formato adecuado. Finalmente cerramos el flujo de datos "in". SI se
produce un erro lo mostramos con un "toast". Con "return bitmap" devolvemos al método principal del
programa el gráfico leído.

Como siempre, la novedad la tenemos en los objetos utilizados, no en la dificultad.


Descargar ficheros de texto
La descarga de ficheros de texto también es interesante. El ejemplo típico es el de un lector de noticias
RSS. Se trata de formatos de texto plano donde se incluyen las noticias publicadas mediante este formato.

RSS

RSS son las siglas de Really Simple Syndication, un formato XML para compartir
contenido en la web. Se utiliza para difundir información actualizada frecuentemente a
usuarios que se han suscrito a la fuente de contenidos. El formato permite distribuir
contenidos sin necesidad de un navegador, utilizando un software diseñado para leer estos
contenidos RSS. A pesar de eso, es posible utilizar el mismo navegador para ver los
contenidos RSS. Las últimas versiones de los principales navegadores permiten leer los
RSS sin necesidad de software adicional. RSS es parte de la familia de los formatos XML
desarrollado específicamente para todo tipo de sitios que se actualicen con frecuencia y
por medio del cual se puede compartir la información y usarla en otros sitios web o
programas. A esto se le conoce como redifusión web.

Partiendo del ejemplo anterior ampliamos el programa para leer un fichero de texto plano que
pondremos en nuestra actividad de ejemplo. El programa principal para la lectura del fichero de texto
quedará:

Sólo definimos un "String". Llamamos al método para la descarga del fichero RSS y mostramos el
contenido con un "Toast". Ahora vemos como hemos la llamada asíncrona como antes:
3

La llamada al método es en este caso con parámetros de tipo "String" ya que es lo que devolverá. El
método "descargar" es el que realiza el proceso abriendo una conexión con "getHttpConection" de la
misma forma que hicimos antes. Finalmente, al devolver un String este método de "descargar" lo
mostramos con un "Toast" al finalizarla tarea con el "onPostExecute".

Veamos ahora el método que se encarga realmente de la descarga de los datos:

En este caso, al ser un torrente de caracteres de texto el proceso será más sencillo. No tendremos que
acudir a ningún objeto para codificar el fichero imagen. Una vez realizada la descarga con la llamada al
método "GetHttpConnection" debemos darle el formato adecuado. Esto lo hacemos con un
"InputStreamReader", objeto que ya vimos en los ficheros.

Haremos un bucle para leer todos los caracteres del flujo y meterlos en la variable de retorno "str". Si
recordamos o repasamos la lectura de ficheros de texto, el proceso es idéntico. En el caso de la lectura
del fichero se trata de un fichero físico también asignado en un "stream".

Todo protegido con un "try/catch" para que, como en todos los procesos de lectura y escritura de
dispositivos, podamos interceptar los errores.
5

Si ejecutamos la aplicación podremos ver el fichero de texto resultante:

Multimedia
Para "descansar" un poco de estos contenidos complejos, vamos a hacer un pequeño repaso por los
paquetes que tenemos para el control multimedia de audio y vídeo. Ambos son muy similares, así que
profundizaremos en el audio y luego mostraremos las diferencias en el vídeo.

Paquetes

Disponemos de dos tipos de paquetes para la grabación y reproducción de audio:

Mediaplayer/Mediarecorder

Es el método estándar para manejar el sonido. Debe estar basado en un fichero o un "streamer"
(fuente de sonido, como una radio). Crea un "thread" para el proceso.

AudioTrack/AudioRecorder

Proporciona acceso directo al audio. Es el indicado si queremos modificar audio en memoria,


escribir en un búfer mientras se está reproduciendo o cualquier otro proceso independiente de un
fichero o "stream". No crea proceso como el anterior.

En estos ejemplos veremos el uso de los primeros métodos: MediaPlayer y MediaRecoder.


Documentación

Podemos acceder a la documentación desde este enlace:

http://developer.android.com/reference/android/media/MediaPlayer.html
Carpeta "Music"

Vamos con los ejemplos, pero antes, vamos a copiar unos archivos de prueba en nuestro emulador.

Dentro de la tarjeta de almacenamiento tenemos las carpetas estándar del perfil del usuario:
"storage/emulated/0/". Copiaremos en "Music" algunos ficheros MP3 para probar nuestra aplicación:

Preparación de un ListActivity con retorno de selección


Antes de hacer el ejemplo, comentaremos como va a funcionar para explicar lo que sería la aplicación
estándar. La idea es que al ejecutar la aplicación nos muestre una segunda actividad con un "listView"
mostrando todos los ficheros que tengamos en la carpeta anterior.

Cuando el usuario seleccione uno de ellos, volveremos y tendremos la opción de reproducirlo o


detenerlo. Lógicamente esto ejemplo es ampliamente aplicable ya que sería igual si simplemente
queremos reproducir unos sonidos personalizados en nuestras aplicaciones.

Para hacer esta parte utilizaremos un método similar al del ejemplo del final de la unidad 3, donde
lanzamos una segunda actividad esperando su ejecución. Al presentarse la segunda actividad y elegir un
fichero, lo devolveremos para reproducirlo.

Por tanto, definimos dos actividades, la principal con un botón:


2

Añadimos una segunda actividad llamada "Lista_ficheros" con un "listview", de esta forma:
3

Y recordando cómo se creaban estas actividades de tipo lista, ponemos el formato de una fila con su
"row", como vemos en la pantalla anterior, a la que hemos llamado "fila.xml" y que contiene sólo el
cuadro de texto donde mostraremos los elementos:
4

Por tanto, hay cinco ficheros de layout: la actividad principal con el botón y su contenido, la actividad
del listview con su contenido y el de la "fila" para mostrar los datos de la lista que lo hemos creado
como un nuevo fichero de recursos de la zona de "layouts":
5

Ahora debemos cargar la lista de ficheros, o, mejor dicho, de canciones que tenemos en la ruta que
hemos preparado "mi_musica", así que, como novedad, vamos a cargar los ficheros de una carpeta y
ponerlos en nuestra lista.

Lo haremos con los objetos dedicados a trabajar con el sistema de ficheros. No son muchos y son
sencillos de utilizar. Partiremos de una lista o matiz que completaremos realizando un bucle en la
carpeta que ya conocemos:

Definimos una matriz "lista_ficheros" y recogemos el dato de entrada de la actividad llamado


"directorio", que es la carpeta a explorar. Así que tenemos una matriz y una carpeta a explorar.

Si esa carpeta existe y es correcta (directorio.isDirectory) podremos recorrerla entera, así que
creamos la matriz "ficheros" automáticamente con el método "directorio.listfiles". Ahora
recorreremos esa matriz para insertar los elementos en nuestra matriz principal.

Con esta matriz, por último, crearemos el habitual adaptador para rellenar la lista de la actividad.
Perfecto, ya hemos mostrado la lista de ficheros de la carpeta que pasamos como dato.

Acceso a ficheros
Documentación del acceso a ficheros, enmarcados dentro de la clase Java "io"
(input/output).

http://developer.android.com/reference/java/io/File.html

Una vez creada la lista, debemos implementar el evento clic de la lista para devolver el fichero
seleccionado a la actividad principal. Esto lo haremos con el correspondiente evento y con el objeto
donde devolveremos el valor seleccionado:

Si seleccionamos un elemento lo pondremos con "i.putExtra" en el paquete de devolución de la


llamada al Intent. Como con "pos" sabemos el índice del elemento seleccionado, sólo debemos
extraerlo con "lista_ficheros.get(pos)", siendo de tipo de datos "File". Finalmente con el método
"toString()" lo convertimos a "string" para devolverlo a la actividad principal. Recuerda lo que vimos
al final de la unidad 3 para finalizar la actividad con "setResult (RESULT_OK,1)" y "finish()".

Finalmente, en la actividad principal podremos recoger ese valor ya que la ejecución de esta actividad
la realizamos "esperando respuesta", que, en este caso, es el fichero seleccionado:
9

Recordemos que "onActivityResult" contiene ese valor devuelto que podemos recoger con el
"getExtras". Recuerda también que la llamada a esta segunda actividad se realiza de forma algo
distinta a la habitual para habilitar la espera de este evento:

10

En lugar de un "startActivity" tenemos un "startActivityForResult", para que podamos esperar ese


evento de retorno, donde hemos puesto un "toast" con el fichero seleccionado. Iniciamos la
aplicación y se lanza directamente la segunda actividad con la lista de ficheros:
11

Seleccionamos uno y provocamos el retorno a la actividad principal, donde ya operaremos con él.

Ya tenemos nuestra aplicación preparada para añadirle la parte multimedia.

Audio con MediaPlayer/MediaRecorder


1

Una vez que tenemos nuestro marco de aplicación nos falta ahora trabajar con los objetos multimedia.
Así que, con el fichero MP3 localizado vamos a ver los procesos para lanzar un fichero de audio.

Todo se basa en "Mediaplayer", como vimos en el enlace de la documentación, así que creamos este
objeto "multimedia" a partir de "Mediaplayer". Posteriormente hemos llamado a la actividad del
"listView" para mostrar la lista de ficheros. Así que el código de reproducción lo pondremos al volver
de la actividad de la lista, es decir, en el "onActivityResult", ya que ahí tenemos el fichero MP33
seleccionado.
3

En este método preparamos el entorno multimedia con los métodos "setDataSource" para indicarle el
fichero que tiene que reproducir y con "prepare" para que esté ya todo listo para poder reproducirse.

Todo esto se engloba, como otros procesos de entrada/salida, en un control de errores "try/catch" para
poder interceptar, por ejemplo, a inexistencia de un reproductor multimedia en el dispositivo y así no
provocar un error en tiempo de ejecución.

Tenemos ya todo listo:

Un fichero multimedia preparado.


El reproductor cargado con el fichero y preparado.
Un botón para activar el sonido.

Así que ya sólo jugaremos con el evento clic del botón que hemos añadido.

Tenemos un botón para iniciar y parar la música y tenemos un método que nos indica si se está
reproduciendo el sonido. Así que si "IsPlaying" significa que está en marcha y pararemos la música con
"multimedia_pausa". Y si no se está reproduciendo (isPlaying=false) entonces iniciaremos el fichero
multimedia con "multimedia_inicia".

Estos dos métodos lógicamente actuarán sobre nuestro objeto "multimedia" que ya tenemos preparado
con el sonido:
"start" y "pause" son los métodos para iniciar y detener la reproducción multimedia. Como sólo
tenemos un botón, cambiamos el texto que muestra al usuario para que pueda hacer una "Pause" o
"Play" dependiendo del estado en que se encuentre.

Este ejemplo, es más sencillo de lo que parece. Si extraemos lo que necesitamos para reproducir un
fichero cuya ubicación y existencia conozcamos, se reduce a media docena de filas. Lo veremos en el
siguiente ejemplo, donde activaremos la grabación de audio y posteriormente reproduciremos como en
este caso que hemos visto.

Recuerda que debemos dar acceso a lectura de la tarjeta de memoria a nuestra aplicación:

Puesto que es un permiso no agresivo, no necesitamos implementar el nuevo sistema de permisos de


Android 6.0.
Reproducir audio

Grabar audio
La grabación es un proceso sencillo, una vez que conocemos nuestro objeto multimedia "Mediaplayer", ya
que la técnica es muy parecida. En esta ocasión, para todo lo referente a la grabación de sonidos,
utilizaremos el objeto "MediaRecorder”.

Origen de la grabación

Tenemos dos parámetros importantes: el origen o fuente de la grabación y su formato. El origen de la


grabación (MediaRecorder.AudioSource) es decir, cuál va a ser la fuente del sonido puede ser:

MIC
Micrófono incorporado.

VOICE_UPLINK

Audio transmitido durante una llamada de voz.

VOICE_DOWNLINK

Audio recibido en una llamada de voz.

VOICE_CALL

El audio completo de la llamada, es decir, los dos anteriores.

CAMCORDER

Micrófono asociado a la cámara, si existe.

VOICE_RECOGNITION

Micrófono configurado como reconocimiento de voz, si está disponible.


Formato para grabar el audio

A su vez, podremos grabar el audio en tres formatos distintos (MediaRecorder.OutputFormat):

THREE_GRP
Formato multimedia 3GPP.

MPEG_4

Formato MPEG4.

AMR_NB

Formato de baja resolución habitualmente utilizado para grabar voz en calidad media.

Formato codificador de audio

Como formato codificador de audio tenemos (MediaRecorder.AudioEncoder):

AAC
Codificación de baja complejidad.

AAC_ELD
Codificación como la anterior y de baja latencia.

HE_AAC
Codificador de alta eficiencia. Es el más moderno y eficaz.

AMR_WB

Codificador de banda ancha.

AMR_NB

Similar al anterior y que se aplica en la grabación.

Lectura

Este mundillo de formatos es algo complejo, así que no vendría mal una buena lectura:
http://en.wikipedia.org/wiki/MPEG-4_Part_3
1

Vamos con el ejemplo. Será parecido al anterior con un botón donde activaremos y detendremos la
grabación. En el layout pondremos tres botones: iniciar la grabación, detenerla y reproducir el fichero
grabado:
2

En la primera parte definiremos los botones y los objetos multimedia de reproducción "MediaPlayer" y
grabación “ MediaRecorder":
3

Como son tres "listener", uno para cada botón vamos a ver cada uno de ellos y la llamada al método
para poder grabar. En este caso necesitamos otorgar permisos de escritura en la tarjeta de memoria
"WRITE_EXTERNAL_STORAGE" y grabación de audio "RECORD_AUDIO", por eso hemos
creado las constantes para poder recoger los permisos del usuario. Comenzamos con la grabación en el
mótodo "incia_grabacion", comprobando primero que tengamos los dos permisos:
4

La primera instrucción es crear un objeto "MediaRecorder" y a continuación le indicamos la carpeta de


grabación con nombre de fichero. Después, protegiendo la operación con un "try" definimos los tres
parámetros para la grabación: origen (micrófono), formato (mp4) y codificador de audio (AMR_NB).

Ya tenemos todo listo, así que solo necesitamos activarla con el método prepare(). Puesto que se pone
en comunicación con el hardware para saber si tenemos lo necesario para la grabación (existencia de
micrófono, por ejemplo), debemos ponerlo al igual que la reproducción con un control de errores
Try/catch. Una vez preparado, basta con iniciar la grabación con el método "start".

Antes de terminar veamos cómo hemos recogido la solicitud de los dos permisos:

Así que, de acuerdo con la nueva forma de solicitar permisos, preguntaremos al usuario si otroga
permisos en la aplicación para acceder al micrófono y al almacenamiento externo.
6

Una vez aceptados los permisos a nuestra aplicación, quedarán almacenados en la configuración:

Con esto, ya tenemos nuestro programa grabando, así que ahora debemos detenerlo cuando lo indique
el usuario. El método es fácilmente imaginable:
8

Primero lo detenemos con un "stop" y luego liberemos los recursos con "release". Una vez grabado
vamos con la reproducción. Para esto haremos lo mismo que antes, pero puesto que no necesitaremos
tanto programa al tener ya en la variable "fichero_grabacion", lo que tenemos que reproducir, el proceso
será muy sencillo:

1. Creamos el objeto multimedia.


2. Indicamos el origen de datos.
3. Preparamos el objeto multimedia para la reproducción.
4. Iniciamos la reproducción con el método "start".
9

Como vemos, todo sigue una sencilla y repetitiva lógica. Ejecutamos el programa y pulsamos en el
botón de grabación. Primero aceptaremos los dos permisos:

10

Y luego podremos ver que la operación de grabación se inicia correctamente:


11

Comenzará el proceso y tendremos nuestro fichero completo al pulsar en el botón de "Terminar


grabación". Este fichero de grabación, lo podemos ver desde la vista del dispositivo. La carpeta
"sdcard" es un acceso directo a "storage/emulated/0". De ahí que podamos verla en dos sitios:

Desde la última API, es necesario añadir esta línea en el "manifest.xml" para el correcto acceso al
almacenamiento externo:

Ejemplos con vídeo


Permiso especial

Para la reproducción y grabación de vídeo no hay ninguna diferencia importante. Para el caso de la
grabación debemos añadir un permiso especial:

<uses permision android:name="android.permission.RECORD_VIDEO">

Origen del vídeo

En cuanto a los orígenes de datos, en este caso, el origen del vídeo (MediaRecorder.VideoSource),
tenemos:

CAMERA. Cámara incorporada.

Formato de salida

Para el formato de salida (MediaRecorder.OutFormat):

THREE_GPP.
MPEG_4.

Codificador de vídeo

Y como codificador de vídeo (MediaRecorder.VideoEncoder):

H263.
H264.
MPEG_4_SP.

Puesto que el proceso es idéntico al anterior, vamos a resumir los pasos de los dos procesos.

Grabación

Los pasos para la grabación serían estos:

Creamos el objeto multimedia de grabación:

mediarecorder= m_recorder=new mediarecorder();


2

Indicamos un origen de datos:

m_recorder.setvideoSource (MediaRecorder.VideoSource.CAMERA);

Indicamos la salida y el codificador:

setoutputformat
audioencoder

Indicamos la carpeta de salida:

setoutputfile

Finalmente invocamos a los métodos habituales:

m_recorder.prepare()
m_recorder.star();

Reproducir vídeo

Para reproducir, haremos el mismo proceso que vimos en el audio, pero con las particularidades del vídeo.

Creamos el objeto:

mediaplayer m_mediaplayer=new mediaplayer();

Si es un fichero de vídeo de nuestro depósito de recursos del proyecto, se lo indicamos:

m_mediaplayer=Mediaplayer.create (this, R.raw.my_video);

y si es el que hemos grabado antes, lo cargamos de igual forma que el audio:

m_mediaplayer.sertDatasource (ruta);
4

Preparamos el hardware para la reproducción:

m_mediaplayer.prepare();

Y llamamos al método "start" para que inicie:

m_mediaplayer.start();

Finalmente, para detener la reproducción y liberar los recursos:

m_mediaplayer.stop();
m_mediaplayer.release();

Como ves, el proceso es idéntico que en el audio.

Cámara
1

El último ejemplo que vamos a ver es cómo hacer una foto de la cámara y almacenarla en nuestra tarjeta.
En este caso no utilizaremos objetos multimedia sino la llamada al Intent que nos permite acceder a la
cámara. Esta llamada requerirá permisos ya que accederemos a una parte del dispositivo, así que se lo
indicaremos en el fichero de configuración:
2

El código sería, por una parte, la llamada al intent, comprobando previamente que tenemos los dos
permisos y sino los pedimo:

Al pulsar en la imagen la veremos a mayor tamaño.


3

Y en la gestión de la petición de permisos se irán solicitando si no los tiene:

El parámetro "mifoto.jpg" se lo proporcionamos a la llamada para que nos guarde el fichero de la


imagen. Puesto que es una llamada con retorno, implementamos el método correspondiente:
5

En él, mostraremos un "toast" de que ha sido correcto. Al ejecutar el programa y después de otorgar los
permisos para acceso a la cámara y almacenamiento:

nos emulará la cámara:


7

Hacemos una foto y podremos almacenarla:


8

Veremos en la carpeta que tenemos la foto almacenada:


9

Si obtenemos algún error en la ejecución puede ser porque en el emulador no tengamos activada la
cámara. Para esto debemos comprobar que el modelo de emulador dispone de cámara y está activada,
o con el emulador activado:

Con esto finalizamos esta entretenida (y sencilla) parte multimedia.

Estilos
Ya que estamos con esta parte más sencilla de multimedia vamos a tocar un poco el tema de los estilos en
Android. Algo sencillo y que hemos tocado en algún ejemplo, pero sin saber muy bien lo que era.

Creamos un proyecto nuevo y veamos la vista de carpetas "Package":


Android Studio ofrecía en versiones anteriores las carpetas de recursos Como se puede observar en la
imagen de la izquierda, en algunas carpetas incluía un sufijo adicional, como por ejemplo “values-w820dp”.
Estos, y otros sufijos, se empleaban para definir recursos independientes para determinados dispositivos
según sus características. De esta forma, por ejemplo, los recursos incluidos en la carpeta “values-
w820dp” se aplicarían sólo a pantallas con más de 820dp de ancho, o los incluidos en una carpeta llamada
“values-v11” se aplicarían tan sólo a dispositivos cuya versión de Android sea la 3.0 (API 11) o superior.
Al igual que los sufijos “-w” y “–v” existen otros muchos para referirse a otras características del terminal.
Puede consultarse la lista completa en la documentación oficial del Android.

En la nueva API ya no verás esta nomenclatura y aparece sólo la vista de la derecha donde se definen todos
los valores en la misma carpeta "values"

Definir nuevos estilos


Definir nuevos estilos
1

Vamos a definir nuevos estilos y utilizar los que tenemos ya predefinidos. Los estilos son muy
parecidos al mundo Web. Podemos definirlos en un fichero XML y luego los podemos asociarlos a
los controles que queramos: botones, cuadros de texto o actividades. Cuando afecta al estilo de la
aplicación completa se llama "tema".
2

Los estilos de los controles los podemos cambiar habitualmente con sus propiedades. Por ejemplo,
para los botones:

Lo que pasa es que no es práctico si queremos tener un estilo homogéneo en nuestra aplicación. Y,
además, si queremos actualizar el estilo completo de nuestro programa bastaría con cambiar los
estilos definidos de forma global ya que están aplicados a todos los controles.

La barra de progreso la hemos puesto porque ya tiene incorporado un estilo de los predefinidos por
Android:
4

El prefijo "?android:attr" indica este estilo predefinido que podemos ver si pulsamos las teclas de
control+spacio:

Las etiquetas o "TextView" muestran un tamaño u otro de texto porque también tienen asociado uno
de los estilos predefinidos de Android:
6

Indica que el tamaño del texto sea grande "Large". Ahora crearemos un estilo propio y se lo
aplicaremos a los "TextView" de nuestra aplicación. Veamos el fichero dedicado para esto:

Ejecutar la aplicación
1

Este fichero lo único que está definiendo de momento es el estilo completo de la aplicación o tema.
Si ejecutamos la aplicación tendremos el aspecto habitual:

Si cambiamos el estilo a este:


3

donde el aspecto es como de un cuadro de diálogo "Dialog.Alert":

Así que vemos los temas "appTheme" afecta a toda la aplicación. Vemos que el estilo tiene un
nombre "appTheme" y que se hereda de un padre o "parent" de uno ya existente nativo de Android
llamado "Them.AppCompat.Light.DarkActionBar". Sigamos con nuestro ejemplo para poner estilo a
las etiquetas "textView".

Definir nuestros estilos


1

Dentro de la zona "xml" definiremos nuestros estilos. Así que de momento creamos nuestro estilo:

Le ponemos un nombre y vamos a heredar del estilo ya existente y que da formato a los textos de
tamaño mediano. Vamos a escribir el primer estilo escribiendo como nombre el de la propiedad que
queremos modificar:
3

Definimos como anchura "match_parent", por ejemplo. Para los colores vamos a fijarnos en el editor
cuando definíamos un color para algún elemento de pantalla:
4

Nos dejaba elegir un color ya predefinido o podíamos indicar uno personalizado, si hacemos clic en
el color en lugar del desplagable derecho:

Vemos el equivalente en hexadecimal que sería el que le indicaríamos en el estilo. Vamos a elegir
algunos ya predefinidos para el color del texto y del fondo, además de alguna propiedad más como
el tipo de letra y tamaño. Recuerda el atajo de presionar control+espacio para la auyuda.
6

Si queremos definir nuestros propios colores, podemos hacerlo creando un nuevo color como estilo
y luego aplicándolo de esta forma:

Hemos definido un color llamado "verde_claro" con una cantidad de color "RGB" con el valor inicial
de transparencia o "canal alpha". Este valor es el que vemos en el editor de colores anterior.

En la barra lateral izquierda podemos ver el resultado del color seleccionado. Ahora nos vamos al
editor a indicarle nuestro estilo a las etiquetas "TexView":
8

Al aplicarlo podemos ver ya el efecto en la vista del dispositivo:

Aplicar estilo al layout


1

Se lo aplicamos al resto de etiquetas y tendremos nuestro estilo aplicado al layout. La ventaja de


trabajar con estilos es la facilidad para cambiar de aspecto nuestra aplicación entera.

En una pantalla estándar de "Material Design" esos son los componentes de la pantalla y cuyos
colores podremos modificar de la misma forma que hemos visto:

Todo lo referente a los estilos lo tenemos en la Web: https://material.io/develop/android/


3

Respecto a la utilización de los dos mundos: la interfaz "Holo" de Android 4.x y "Material Design"
de Android 5.x, tenemos que los estilos:

@android:style/Theme.Holo.
@android:style/Theme.Holo.Light.
@android:style/Theme.Holo.Light.DarkActionBar.

Se convierten ahora a partir de la API 21 en:

@android:style/Theme.Material (dark version).


@android:style/Theme.Material.Light (light version).
@android:style/Theme.Material.Light.DarkActionBar.

La biblioteca de soporte "appcompat” proporciona compatibilidad de esos estilos para los


dispositivos anteriores a Android 5.x.

Recuerda que la biblioteca de compatibilidad se incluye en nuestros proyectos de forma


predeterminada:

Estilos con herencia


1

Podemos definir estilos que hereden de los que hemos creado. Es interesante su uso porque nos
simplifica la escritura de otros estilos. Para la nomenclatura se escribe el nombre del estilo que hereda
y luego un nombre que indique el aspecto que lo diferencia del padre. Por ejemplo:

Define un nuevo estilo llamado "Estilo.letramini" que tiene las propiedades de "Estilo" y además un
tamaño de letra de 20, que lógicamente sobrescribe al anterior de 30.

Sigamos con nuestro estilo de la aplicación para poner un dibujo de fondo a nuestros botones.
Cargamos un gráfico de fondo, por ejemplo, uno descargado de Internet:
3

Y creamos un nuevo estilo indicando que el fondo lo queremos con este gráfico:

Y se lo aplicamos de la misma forma a los dos botones:

En internet podremos encontrar multitud de estilos para personalizar nuestras aplicaciones sin tener
que diseñar cada elemento.

Definir formas
1

Además de los estilos que hemos creado podemos definir capas para nuestros fondos en nuestros
controles, en lugar de utilizar los fondos gráficos que hemos visto antes. Añadimos un fichero XML en
nuestra zona "drawable":

Dentro de este XML modificamos la definición para que sea un "layer-list" que es la definición de las
capas que queremos aplicar a nuestro control:
3

En estas etiquetas podemos definir totalmente los estilos de nuestros controles. Por ejemplo, vamos a
poner un degradado de color de blanco a negro pasando por el color verde que pusimos antes y los
cantos redondeados:

"corners" indica los bordes de la capa y con un radio de 15dp. Para el degradado ponemos tres colores:
el inicial (blanco), el central (verde) y el final (negro). Podemos indicar sólo dos colores, uno de inicio y
otro de final, pero así vemos el efecto de utilizar los tres colores. Al ir escribiendo ya hemos visto las
opciones que podíamos poner en cada sección:

El gradiente, o degradado de color, se lo podemos aplicar en cualquier dirección. El valor del ángulo es
el que define hacia qué sentido se aplicará ese degradado. Vamos a aplicar este estilo al botón. Así que
sustituimos el fondo gráfico anterior por este estilo:
6

Veamos el resultado con esos 45º:

Ahora con 180º:

Vamos a indicarle al degradado que la posición central es la mitad del botón en altura, es decir y=0,5y
además 270º para cambiar la posición:
9

El resultado es:

Recuerda que tenemos la vista gráfica/código en el editor:

Estilos genéricos
1

Veamos ahora como podemos aplicar estilos a una actividad completa. Algo ya hemos visto con los
temas, pero veamos ahora algún detalle más. Si abrimos el archivo de configuración "androidmanifest"
podemos ver que ya tenemos aplicado un tema por defecto a la aplicación:

Este estilo no es ni más ni menos que el definido en el fichero principal y que heredaba de:
3

Ya vamos encajando todas las piezas. Vemos que ya tenemos colores predefinidos para
"colorPrimary", "colorPrimaryDark" o "colorAccent”. Vamos a añadir el color para los textos de todo
el proyecto

Si al primer "TextBox" le quitamos el estilo que tenía antes y lo dejamos sin ninguno, le aplicará este
global que hemos creado donde el color de los textos es rojo:

Documentación

Como siempre, es muy importante tener la documentación oficial a mano para tener una
amplia descripción de cada una de las partes que estamos tratando:

http://developer.android.com/guide/topics/ui/themes.html
Estilos de Holo

Si leemos los enlaces a los estilos de Holo, podremos ver todos los estilos de este tema. Podríamos
duplicarlo o coger conceptos o ideas para nuestro estilo:

Estilos según los estados


1

Podemos crear estilos para aplicar según tengamos un estado y otro en nuestro botón. Es lo que se
llama un "selector" que no es más que un "drawable" que cambia según un estado. Creamos dos
botones para los dos estados: presionado o normal:

Creamos u n nuevo fichero XML con el nombre "button_selector.xml" dentro de la carpeta


"res\drawable\ folder". Con este código:
<?xml version="1.0" encoding="utf-8"?>

<selector xmlns:android="http://schemas.android.com/apk/res/android">

<item android:drawable="@drawable/button_focused" android:state_pressed="true"/>

<item android:drawable="@drawable/button" android:state_pressed="false"/>

<item android:drawable="@drawable/button_focused" android:state_focused="true"/>

<item android:drawable="@drawable/button" android:state_focused="false"/>

</selector>

Será cuando esté con el enfoque y presionado (pressed y focused) cuando le mostremos la segunda
imagen.

Ahora ponemos el botón en un layout donde le indicaremos que el fondo "backgroud" es ahora nuestro
selector:

<Button
android:id="@+id/button1"
android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="Button"

android:textColor="@android:color/white"

android:textSize="28dp"

android:textStyle="bold"

android:layout_centerInParent="true"

android:background="@drawable/button_selector"
/>
4

Ejecutamos la aplicación y veremos los fondos de los botones, cambiando de fondo cuando lo estemos
pulsando.

Hasta hace poco, Android Studio disponía de una herramienta para editar estilos:

Ahora mismo esta opción está desactivada pero seguramente la añadan en posteriores revisiones. Desde
aquí podremos editar más fácilmente los valores de los colores y estilos predefinidos:
También podemos ver el resultado con las distintas API's y dispositivos.
importante: permisos

A lo largo de estos temas y los siguentes seguiremos solicitando permisos de forma dinámica al usuario.
Es decir, como hemos estado viendo y según Android 6 indica, debemos pedirlos en el momento de su
uso. Los permisos, los llamados peligrosos, que debemos controlar mediante este modelo son:

De estos, ya hemos visto los de acceso al micrófono, almacenamiento externo, cámara o lectura de
contactos. En la unidad siguiente volveremos a utilizarlos para la geolocalización, por ejemplo.

También podría gustarte