Volver > SOFTWARE Curso online sencillo Desarrollo de Aplicaciones en Android | |



Curso online sencillo DESARROLLO DE APLICACIONES ANDROID.








4 CONTACTMAP: LOCALIZADOR DE CONTACTOS

En este capítulo se explica paso a paso el desarrollo completo de ContactMap, una aplicación cuyo objetivo es localizar y mostrar en un mapa los contactos almacenados en el dispositivo, añadiendo además otra serie de funcionalidades.
Mediante este desarrollo se busca ilustrar de una forma más práctica las características principales que ofrece Android y pretende además servir como ejemplo para la creación de otras aplicaciones.
Algunos de los aspectos más interesantes de Android y que se explican con esta aplicación son los siguientes:

- Uso de componentes Activity.
- Uso de componentes Service.
- Solicitudes a través de Intents.
- Utilización de los servicios de Google Maps.
- Comunicaciones por HTTP.
- Uso de la base de datos SQLite.
- Composición del archivo “AndroidManifest.xml”.
- Acceso a la información de los contactos.
- Acceso a información sobre el propio dispositivo.
- Uso de interfaces remotas con AIDL.
- Composición de interfaces de usuario, tanto con código como con XML.
- Declaración y uso de recursos externos.
- Composición gráfica de elementos en pantalla.
- Gestión de opciones de menú.
- Control del GPS.
- Control de la conexión Wi-Fi.
- Llamadas telefónicas.
- Envío de SMS.
- Envío de correo electrónico.

4.1 Análisis y diseño de la aplicación

4.1.1 Introducción a ContactMap

El objetivo básico de ContactMap es mostrar al usuario la ubicación geográfica de los contactos que tenga almacenados en el propio dispositivo móvil. Para ello utiliza fundamentalmente tres servicios:

- Google Maps, para mostrar al usuario las localizaciones de los contactos
- La señal GPS del propio dispositivo móvil, para conocer la propia localización
- Una conexión a Internet, con el fin de poder intercambiar información de localización con los demás usuarios.

El usuario visualiza la situación geográfica de sus contactos gracias a los mapas y servicios de Google Maps, que son ofrecidos por Android a través de una serie de API específicas. El uso de esta potente herramienta permite tener acceso a miles de imágenes y mapas, convirtiéndose en uno de los elementos de Android que más juego está dando en todo tipo de aplicaciones dentro de la recién creada comunidad de desarrolladores.
Así mismo, la aplicación utiliza el componente GPS presente en el mismo dispositivo móvil para conocer la propia ubicación, pudiendo de este modo tanto mostrársela al usuario en el mapa como comunicársela al resto de usuarios. El servicio de GPS, aunque todavía hoy no es de los accesorios más extendidos en los dispositivos móviles, sí que lo será a corto plazo al estar incluido en muchos de los nuevos modelos de smartphones a la venta (el primer teléfono con Android, el modelo HTC G1, incluye GPS de serie) y todo parece indicar que en pocos años su incorporación será tan frecuente como lo es hoy la cámara digital o el reproductor de música.
ContactMap no requiere la presencia de GPS para poder funcionar, pero su ausencia significa que el usuario no podrá conocer su propia ubicación ni, por tanto, comunicársela a los demás usuarios.
Por otro lado, mediante el establecimiento de una conexión a Internet a través de la Wi-Fi, la aplicación ContactMap realiza intercambios de información sobre localizaciones con los demás usuarios. Este intercambio no se realiza nodo a nodo, es decir, entre cada usuario, sino que existe un servidor central donde cada uno de los usuarios consulta las localizaciones de sus contactos, además de actualizar su propia localización. Este intercambio se realiza utilizando el estándar XML y sobre el protocolo HTTP, aunque el funcionamiento mas detallado de esta comunicación y el del servidor se detalla en apartados posteriores.
Al igual que con el GPS, no es imprescindible disponer de manera constante de una conexión a Internet para que ContactMap pueda mostrar las ubicaciones de los contactos. Por defecto, ContactMap intenta mostrar la ubicación más reciente de cada usuario, pero en caso de no poder conectarse mostrará siempre la última ubicación conocida. Por ello sí es necesario conectarse al menos una vez para obtener las localizaciones correspondientes
Además de mostrar ubicaciones geográficas, ContactMap permite también realizar otra serie de acciones asociadas a cada uno de los contactos como, por ejemplo, realizar una llamada telefónica, enviar un mensaje de texto o escribir un correo electrónico.

4.1.2 Casos de uso

En este apartado se utilizan los casos de uso como forma de acercar al lector las funcionalidades que la aplicación ContactMap va a desempeñar frente al usuario. Sin embargo, no se va a profundizar demasiado en las posibilidades que ofrece esta técnica, sino que se limitará solamente en aquellos aspectos que son más ilustrativos y concisos para el lector y que le ayuden a comprender mejor qué es lo que realiza la aplicación.
Como recordatorio se dirá que, en ingeniería del software, un caso de uso representa un uso típico que se le da al sistema. La técnica de los casos de uso permite capturar y definir los requisitos que debe cumplir una aplicación, y describe las típicas interacciones que hay entre el usuario y esta. Dicha técnica es utilizada con frecuencia por los ingenieros del software para, entre otras cosas, mostrar al cliente de forma clara y sencilla qué tipo de acciones podrá realizar su futuro sistema.
A continuación se muestra un diagrama con los casos de uso asociados a ContactMap, utilizando el estándar UML 2.0.

Aplicaciones Android
Figura 16. Diagrama de casos de uso de ContactMap.

En diagrama mostrado en la Figura 16, el sistema, (es decir, la aplicación ContactMap) está representado por una caja que contiene los casos de uso. Cada caso de uso consiste en un óvalo con un nombre descriptivo en su interior. Fuera del sistema se encuentra los actores que pueden interactuar con él. En este caso, a pesar de estar representado por dos figuras por motivos de espacio y legibilidad, existe un único actor de nombre “usuario” que es el que realiza todos los casos de uso.
A continuación se ofrece una breve descripción del cometido de cada uno de los casos de uso mostrados en el diagrama:

- Mostrar ubicación propia: mostrar en el mapa la ubicación actual del usuario.
- Mostrar ubicación contactos: mostrar en el mapa las ubicaciones de los contactos del usuario, siempre y cuando se disponga de información de localización.
- Cambiar tipo de mapa: alternar la imagen entre la vista de satélite (fotografía aérea) y vista de callejero.
- Desplazar mapa: desplazar el mapa en el sentido pulsado por el usuario.
- Hacer zoom in/out: acerca o alejar el nivel de zoom del mapa.
- Listar contactos: listar en orden alfabéticos todos aquellos contactos para los que se disponga de información de localización.
- Seleccionar un contacto: elegir un contacto, centrando en él el mapa y haciéndole objeto de las diferentes acciones.
- Seleccionar contacto siguiente: elegir al contacto siguiente por orden alfabético al actual, centrando en él el mapa y haciéndole objeto de las diferentes acciones.
- Seleccionar contacto anterior: elegir al contacto anterior por orden alfabético al actual, centrando en él el mapa y haciéndole objeto de las diferentes acciones.
- Llamar a contacto: realizar una llamada telefónica al contacto actual.
- Enviar SMS a contacto: enviar un mensaje de texto al contacto actual.
- Enviar correo electrónico a contacto: enviar un correo electrónico al contacto actual.
- Ver información de contacto: visualizar la fecha y hora de la última vez que el contacto actualizó su localización.

Siguiendo el mismo criterio, el siguiente diagrama expresa los casos de uso asociados al servidor al que se conecta la aplicación para gestionar la información de localización:

Aplicaciones Android
Figura 17. Diagrama de casos de uso del servidor de ContactMap


En el diagrama de la Figura 17, el único actor que realiza los casos de uso es la propia aplicación ContactMap, ya que es la que establece la conexión con el servidor. Los casos de uso considerados son:

-Actualizar localización: actualizar los datos de localización del usuario.
-Conocer localización: obtener los datos de localización del usuario solicitado.

Es importante recalcar que los casos de uso simplemente expresan el punto de vista del usuario sobre cómo debe funcionar la aplicación y qué puede realizar a través de ella, y son una forma de facilitar su comprensión. No tiene porqué existir ninguna correspondencia entre los casos de uso y la clases finalmente implementadas, más allá de que las clases en su conjunto, como sistema completo, realizan aquello que los casos de uso expresan.

4.1.3 Intercambio de información con XML

ContactMap obtiene la información de localización de los contactos del usuario conectándose a un servidor, donde además actualiza su propia ubicación para que esté disponible para otros usuarios. Estos intercambios de información entre aplicación y servidor se realizan utilizando documentos XML. Como seguramente conocerá el lector, el lenguaje de marcado XML permite expresar de forma sencilla y abierta diferentes estructuras de datos, manteniendo intacta su semántica y contenidos.
El uso de XML entre otras alternativas responde principalmente a dos criterios. Por un lado, se trata de un estándar regulado y ampliamente utilizado, lo que facilita su comprensión y la posible ampliación o interconexión con otros servicios en un futuro. Por otro, es una tecnología muy fácil de usar y cuenta con diferentes API en Java que convierten en algo automático la lectura y composición de este tipo de documentos. En concreto, tanto en el lado servidor como en el lado de la aplicación en Android, se utiliza la biblioteca SAX.
Cuando se desea utilizar documentos XML que cuenten siempre con una misma estructura, como es el caso que nos ocupa, se suelen definir unas plantillas que expresan la forma en la que debe ser construido este documento XML para ser considerado válido. Una de estas plantillas es la que se denomina DTD, y expresa de forma muy clara qué tipo de elementos e información puede contener un determinado documento XML.
En ContactMap se definen dos tipos distintos de documentos XML: el de petición y el de respuesta. El siguiente DTD define un documento de petición:

< !ELEMENT contactmap-request (me, contact*)>
< !ELEMENT me (number, latitude, longitude, date)>
< !ELEMENT number (#PCDATA)>
< !ELEMENT latitude (#PCDATA)>
< !ELEMENT longitude (#PCDATA)>
< !ELEMENT date (#PCDATA)>
< !ELEMENT contact (#PCDATA)>

Código 14. DTD para peticiones

Un documento de petición consta de dos partes. En la primera, de carácter obligatorio, el usuario notifica sus datos para ser actualizados en el servidor: el número de teléfono que lo identifica, los datos de localización expresados en una latitud y una longitud, y la fecha actual consistente en día, mes, año, hora, minutos y segundos. En la segunda parte del documento de petición, de carácter opcional, se incluyen los números de teléfono de aquellos contactos para los que solicita conocer su ubicación.
El Código 15 muestra un documento de petición válido y aceptado por el servidor. En él, el usuario actualiza sus datos de localización y además solicita la localización de tres de sus contactos:

< ?xml version="1.0" encoding="UTF-8"?>
< !D OCTYPE contactmap-request SYSTEM "contactmap-request.dtd">
< contactmap-request>
< me>
< number>660854377
< latitude>40.425970
< longitude>-3.696010
< date>2008-01-04 18:30:42
< /me>
< contact>647889034
< contact>688403241
< contact>654312526
< /contactmap-request>

Código 15. Ejemplo de petición XML válida

Los documentos de respuesta deben, por su parte, cumplir lo definido en la plantilla DTD mostrada en el Código 16:

< !ELEMENT contactmap-response (contact+ | error)>
< !ELEMENT contact (number, latitude, longitude, date)>
< !ELEMENT error (#PCDATA)>
< !ELEMENT number (#PCDATA)>
< !ELEMENT latitude (#PCDATA)>
< !ELEMENT longitude (#PCDATA)>
< !ELEMENT date (#PCDATA)>

Código 16. DTD para respuestas

En el documento de respuesta viajan los datos de localización de aquellos contactos que hayan actualizado en algún momento su ubicación en el servidor. Dicho de otro modo, pueden existir contactos solicitados por la aplicación ContactMap en su documento de petición para los cuales no existan datos de localización disponibles, por lo que no serán incluidos en el documento de respuesta. En caso de haber ocurrido algún error no se adjunta información de ningún contacto, sino que se describe el error acaecido.
El siguiente documento de respuesta es un documento válido según la plantilla DTD anteriormente mostrada. En él, se comunican a la aplicación los datos de dos de los tres contactos solicitados según la petición del Código 15, al ser los únicos disponibles. El primer contacto probablemente está utilizando en este momento ContactMap, ya que la hora de actualización es casi contemporánea a la comunicada por el usuario de la petición (ver Código 15). El segundo contacto actualizó por última vez su posición el día anterior.

< ?xml version="1.0" encoding="UTF-8"?>
< !D OCTYPE contactmap-response SYSTEM "contactmap-response.dtd">
< contactmap-response>
< contact>
< number>647889034
< latitude>40.425968
< longitude>-3.976010
< date>2008-01-04 18:29:15
< /contact>
< contact>
< number>688403241
< latitude>40.428974
< longitude>-3.717503
< date>2008-01-03 11:49:00
< /contact>
< /contactmap-response>

Código 17. Ejemplo de respuesta XML válida

4.1.4 Servidor

El servidor al que se conecta ContactMap realiza dos funciones principales: actualizar información de localización del usuario y enviarle la información de localización de los usuarios (esto es, sus contactos) solicitados. Mediante la actualización, un usuario cualquier envía sus datos (latitud, longitud y fecha, además de su número de teléfono identificativo) y estos son almacenados en una base de datos. En la consulta, el servidor lee los contactos solicitados y devuelve la información de todos aquellos que estén presentes en esta misma base de datos.
En la composición del servidor funcionan en realidad tres componentes básicos:

- Una base de datos, que almacena toda la información de localización que envían los usuarios.
- Un servlet, que atiende la petición recibida, la procesa y envía la respuesta correspondiente.
- Un servidor web, donde reside y se ejecuta el servlet, y que permanece a la espera de conexiones HTTP entrantes.

Para la base de datos se utiliza el gestor MySQL 5.0, usándolo bajo la licencia gratuita que permite su explotación para desarrollos no lucrativos, como es la aplicación que ocupa este proyecto. En él se ha implementado una sencilla base de datos llamada ContactMapBD que cuenta con una única tabla; en esta se almacena para cada usuario el número de teléfono que lo identifica, su latitud y longitud, así como la fecha completa de actualización. El siguiente script es el utilizado para generar dicha base de datos.

--
-- Crear la base de datos `ContactMapBD`
--
DROP DATABASE `ContactMapBD`;
CREATE DATABASE `ContactMapBD`
DEFAULT CHARACTER SET latin1
COLLATE latin1_spanish_ci;
USE `ContactMapBD`;
-- --------------------------------------------------------
--
-- Estructura de tabla para la única tabla `friend`
--
CREATE TABLE IF NOT EXISTS `friend` (
`number` bigint NOT NULL,
`latitude` float (8,6) NOT NULL,
`longitude` float (8,6) NOT NULL,
`date` datetime NOT NULL,
PRIMARY KEY (`number`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_spanish_ci;

Código 18. Script SQL de la base de datos del servidor

El servlet es el componente que procesa las peticiones que envían los usuarios. Actualiza la información del usuario, accede a la base de datos en busca de los contactos solicitados, y devuelve la respuesta. Como información para el lector, se dirá que un servlet no es más que un componente Java, generalmente pequeño e independiente de la plataforma, que se ejecuta en un servidor web al que extiende su funcionalidad. El servlet implementado recibe el nombre de ContactMapServlet y es al que van dirigidas las conexiones.
Cuando recibe una petición, el servlet utiliza la librería SAX de Java para leer el documento XML completo. En primer lugar, actualiza en la base de datos la localización comunicada por el usuario. A continuación, consulta en la base de datos todos aquellos contactos que el usuario ha indicado en la petición recibida. Esta consulta la realiza gracias a las librerías de JDBC y MySQL de Java. Por último, el servlet compone y envía a su vez una respuesta en un nuevo documento XML, donde incluirá los datos de localización de todos aquellos contactos que estén presentes en la base de datos.
Por su parte, el servidor web utilizado es Apache Tomcat (también en su distribución gratuita) y es el entorno donde se ejecuta el servlet, quedando siempre a la espera de recibir conexiones HTTP. Todas las comunicaciones entre la aplicación ContactMap y el servidor se envían a través de dicho protocolo. En las peticiones, el método HTTP utilizado es POST, correspondiente al envío de datos, y se encapsula el documento XML en una variable de nombre “xml”. En las respuestas, se envía el documento XML correspondiente en el campo de datos.

4.1.5 Modelo de clases

Las clases utilizadas para resolver un determinado problema, sus atributos y métodos, la visibilidad que de estos tienen las demás clases, así como las relaciones que existen entre ellas y sus colaboraciones, constituyen el modelo de clases. Mediante este tipo de modelos se expresa, con mayor o menor nivel de detalle, la futura implementación del sistema, así como permite dar una idea bastante cercana a la forma en la que se ha abordado el problema.
En el presente apartado se ofrece al lector una breve descripción de cuál es el modelo de clases utilizado en la aplicación ContactMap. Nuevamente, el objetivo principal es ayudar a comprender cómo se soluciona el problema planteado y pretende servir de anticipo a la explicación más detallada de la implementación de ContactMap, que se da en secciones posteriores. Por todo ello, el modelo de clases ofrecido no incluye más que clases, atributos, métodos, y la visibilidad de estos, con vistas a no perder al lector con aspectos demasiado complejos.
En la Figura 18 se enseña el diagrama correspondiente al modelo de clases utilizado, siguiendo el estándar UML 2.0.

Aplicaciones Android
Figura 18. Diagrama de clases de ContactMap

Las clases presentes en el anterior diagrama, así como su naturaleza y cometido principal dentro de la aplicación, se resumen brevemente en las siguientes líneas:

- ContactMap : es la clase principal y la que coordina la mayor parte de la ejecución de la aplicación. Mediante ContactMap , que extiende la clase Activity (uno de los componentes básicos de cualquier aplicación para Android), se muestra al usuario el mapa con la ubicación de los contactos, se gestiona la conexión con el servidor, se procesa la información recibida y enviada, así como controla las demás funcionalidades disponibles para el usuario.
- MapOverlay : esta clase se encarga de los aspectos relacionados con dibujar y actualizar los diferentes elementos de la pantalla, principalmente el mapa y los contactos.
- XMLExchange : con esta clase se escriben y leen los documentos XML que forman, respectivamente, las peticiones y respuestas del servidor.
- MyXMLHandler : esta clase ayuda a procesar las respuestas recibidas, construyendo en memoria las estructuras necesarias para representar a los contactos y sus correspondientes localizaciones.
- Update : es la clase encargada de conectar periódicamente con el servidor para actualizar la información propia y la de los contactos. Extiende la clase Service , otro de los componentes básicos de una aplicación Android, ejecutándose siempre en background.
- HTTPConnection : esta clase gestiona los aspectos de más bajo nivel de las conexiones HTTP con el servidor.
- Friend : la clase que representa a un contacto que puede ser representado en el mapa, ya que se cuenta con su información de localización.
- FriendDataBase : clase que gestiona el acceso a la base de datos SQLite para almacenar o consultar los datos de localización de los contactos. Se utiliza en caso de no poder conectar con el servidor.
- Contacts : mediante esta clase se accede a la información de contactos almacenados en el dispositivo móvil.
- FriendListViewer : esta clase extiende también la clase Activity , y muestra una lista de los contactos permitiendo así al usuario seleccionar uno.
- SMSWriter : clase que extiende la clase Activity , y muestra una interfaz con la que el usuario puede escribir un SMS.
- IRemoteRegister : interfaz remota que permite comunicarse con la clase Update , donde está implementada. Se utiliza para comunicar la Activity principal (clase ContactMap ) con el Service activo en background (clase Update ).
- IRemoteCallback : interfaz remota que permite comunicarse con la clase ContactMap , donde está implementada. Se utiliza para comunicar el Service activo en background (clase Update ) con la Activity principal (clase ContactMap ).

4.1.6 Arquitectura

Una vez descrito el funcionamiento general de ContactMap, las funcionalidades ofrecidas al usuario, el comportamiento del lado servidor y el intercambio de información mediante XML, a continuación se ofrece un diagrama con la arquitectura general del sistema completo:

Aplicaciones Android
Figura 19. Arquitectura del sistema completo de ContactMap

En la Figura 19 el lector puede apreciar los siguientes elementos:

- Dispositivo móvil: dispositivo con el sistema Android corriendo y donde está instalada la aplicación ContactMap. Mediante el uso del GPS, el dispositivo conoce la ubicación actual del usuario y a través de la conexión Wi-Fi realiza intercambios de información con el servidor web: notifica sus datos de localización y solicita la ubicación de sus contactos.
- Documentos XML: el dispositivo móvil realiza intercambios de documentos XML con el servidor utilizando el protocolo HTTP. El documento de petición comunica al servidor la ubicación actual del usuario y solicita a su vez las ubicaciones de sus contactos, mientras que el documento de respuesta comunica al dispositivo móvil dichas localizaciones.
- Servidor web: permanece siempre a la espera de conexiones por parte de los diferentes usuarios de ContactMap. Aloja el servlet que procesa las peticiones y respuestas sobre XML.
- Servlet: pequeño programa en Java que recibe peticiones, consulta la base de datos, y construye y envía las respuestas XML de vuelta al dispositivo móvil.
- Base de datos: la base de datos almacena toda la información de localización de los usuarios de ContactMap. Es consultada por el servlet.

4.2 Desarrollo e implementación

Una vez expuestos los objetivos y características principales de la aplicación, así como las decisiones tomadas en cuanto a su diseño, a continuación se desgranan en este apartado los aspectos relacionados con su implementación.
El fin perseguido en las siguientes líneas no es sólo mostrar el código más relevante de ContactMap, sino ofrecer también una explicación general sobre cómo funcionan las aplicaciones para Android, de forma que a su vez se consigan mostrar las peculiaridades de este nuevo sistema. Así mismo, los detalles de implementación mencionados esperan poder ayudar a otros desarrolladores a ampliar en un futuro las capacidades de ContactMap, o incluso inspirar la creación de aplicaciones con servicios similares.
Se advierte al lector de que los fragmentos de código fuente a continuación mostrados no son una copia literal del código fuente de ContactMap, sino que en algunos casos se reducen o modifican por motivos de limitación de espacio, pero sobre todo por simplificar y facilitar su comprensión.

4.2.1 Acceso a Google Maps

La utilización del popular servicio Google Maps es una de las posibilidades más atractivas de Android. En efecto, un gran número de las aplicaciones presentadas al concurso de desarrolladores propuesto por Google utilizan estas bibliotecas con fines muy distintos.
El paquete que incluye todas las clases relacionadas con la carga y manejo de mapas directamente desde Google Maps es com.google.android.maps . Dentro de este paquete podemos encontrar clases como las siguientes:

- MapView : obtiene el mapa solicitado y lo muestra en pantalla.
MapController : gestiona el manejo de un mapa, como desplazamientos o zoom.
- GeoPoint : clase que representa un punto geográfico determinado del mapa, según su latitud y longitud.
- Overlay : permite dibujar y representar elementos sobre el mapa.
- MapActivity : clase muy relevante que extiende la clase base Activity , y permite crear una Activity específica para gestionar mapas.

Un paso previo a la utilización de este API para mostrar y manejar mapas es el registro en Google Maps y su aceptación de los términos y condiciones de uso. Este registro se realiza a través de una clave denominada en Android como API key.
Una vez se ha procedido al registro, la utilización de mapas en nuestra aplicación no requiere más que unas cuantas llamadas a las clases pertinentes. Todo ello se puede comprobar en los dos siguientes apartados.

Obtención de API Key

Tal y como se ha mencionado, para utilizar los servicios de Google Maps en una aplicación Android es imprescindible realizar unos pasos previos que implican el registro del desarrollador y la aceptación de las condiciones de uso. De esta forma, Google quiere asegurar que se hará en todo momento un uso adecuado y apropiado de los servicios y datos que nos va proporcionar desde entonces con Google Maps.
Toda aplicación en Android está acompañada de un certificado que asegura su autoría y la vincula con su desarrollador. Si se utiliza el plug-in de Android para Eclipse, todas las aplicaciones están firmadas por un cerificado al que se puede calificar de prueba y que permiten a los desarrolladores poder crear y probar sus aplicaciones sin más esperas. Este certificado se obtiene a través del fichero de claves de prueba “debug.keystore”, presente por defecto en el SDK de Android.
Recuérdese, sin embargo, que si un desarrollador desea distribuir su aplicación de forma pública o hacerla accesible desde servicios de descarga, como por ejemplo Android Market, es necesario que se cree su propio fichero e individual de claves “.keystore” con el que crear un certificado y firmar su aplicación.
El registro para tener acceso a Google Maps se hace a través de dicho certificado. En concreto el proceso es el siguiente, utilizando el fichero “debug.store”. En caso de disponer un fichero de claves propio, el proceso sería el mismo cambiando los parámetros necesarios.

1. En primer lugar, se ha de obtener un resumen MD5 del certificado que se va a usar para firma la aplicación donde se hará uso de mapas. El resumen puede generarse, por ejemplo, con la herramienta keytool presente en el SDK de Java. La llamada correspondiente se muestra en la Figura 20.

>keytool -list -alias androiddebugkey –keystore
C:\debug.keystore -storepass android -keypass android

Huella digital de certificado (MD5):
0B:41:31:DC:4B:89:22:8E:A5:45:79:C6:13:DA:7E:D4

Figura 20. Ejemplo de uso de la herramienta keytool para generar resumen MD5

2. A continuación, se debe registrar el resumen MD5 obtenido en Google Maps. Para ello, se visita la web de registro [37], se aceptan los términos de uso, se envía el resumen. Es necesario disponer de una cuenta Google activa.

3. Google Maps proporciona entonces una clave alfanumérica, llamada API key. Esta es la que se debe utilizar para obtener y manejar mapas, siendo además única y exclusiva para la aplicación firmada con certificado utilizado.

Tu clave es:

0sO8gCzgaeJ3QpNmuPTX3pwzQ7m6vD3_tV2CY4w

Esta clave es válida para todas las aplicaciones firmadas con el
certificado cuya huella dactilar sea:

0B:41:31:DC:4B:89:22:8E:A5:45:79:C6:13:DA:7E:D4 Incluimos un diseño xml de ejemplo para que puedas iniciarte por
los senderos de la creación de mapas:

< com.google.android.maps.MapView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:apiKey="0sO8gCzgaeJ3QpNmuPTX3pwzQ7m6vD3_tV2CY4w"
/>
Figura 21. Ejemplo de API key proporcionada por Google

Mostrar un mapa

Una vez se dispone de una API key con la que poder acceder a los mapas y servicios de Google Maps, no resta más que hacer las llamadas pertinentes a las clases del paquete com.google.android.maps .
La clase ContactMap , como se recordará, es la clase principal y la que coordina la mayor parte de las demás clases de la aplicación. Esta clase deriva de la clase Activity , como ya se advirtió, pero lo hace de forma indirecta. A quien extiende en realidad es a la clase MapActivity , que incluye las funciones estándares de Activity más aquellas otras relacionadas con la visión de mapas.
Para poder visualizar un mapa obtenido desde Google Maps, es necesario por lo menos dos objetos básicos:

- Un objeto MapView , que obtiene y representa el mapa.
- Un objeto MapController , que controla el mapa representado.

Ambos objetos, encargados de contener y controlar el mapa que se está mostrando, se encargan dentro de la clase ContactMap a través de dos variables globales, llamadas mMapView y mMapControl , respectivamente. Ambas variables serán creadas y configuradas inicialmente en esta clase, pero también son accedidas por la clase MapOverlay , cuya misión es ir actualizando el mapa con diferentes elementos.
En primer lugar, se crea el objeto mMapView . En su constructor es necesario especificar un contexto válido de aplicación (la propia clase ContactMap ) y además, incluir la API key que previamente se ha generado.
El objeto mMapControl ha de vincularse a un objeto de la clase MapView , para indicar que es en esa vista de mapa donde debe ejercer las labores de control. Para generar el objeto mMapControl , se utiliza el método getController() del objeto mMapView .

Aplicaciones Android
Figura 22. Mapa obtenido desde Google Maps

Llegados a este punto, ya se tienen los dos elementos básicos, es decir, un mapa y su controlador. Sin embargo, es necesario especificar ahora otros aspectos, como por ejemplo, qué zona se desea mostrar, con qué tipo de mapa o el nivel de zoom.
La clase GeoPoint , también incluida en el paquete com.google.android.maps , será una de las que más se utilizarán si se trabaja con mapas. Esta clase representa un punto concreto, una localización determinada a través de sus coordenadas de latitud y longitud. Para construir uno de estos objetos, es necesario proporcionar en su constructor dichas coordenadas, siempre multiplicadas por la potencia 10 6 .
Como curiosidad, se mencionan formas rápidas de conocer las coordenadas de un punto concreto:

- En el propio Google Maps, al buscar una dirección se ofrece la posibilidad de generar automáticamente código HTML para publicar esta en una página web. Dentro de este código HTML se encuentra la etiqueta “ll”, que contiene el valor de la latitud y longitud, respectivamente.
- Existen algunas páginas web que utilizan Google Maps para buscar e informar al usuario de las coordenadas del punto geográfico indicado, como la indicada en la referencia.

Una vez generado un punto mediante coordenadas, es posible centrar el mapa en él. Para llevarlo a cabo, se utiliza el método setCenter() de mMapControl . Además, en este mismo objeto se tienen los métodos setSatellite() , que cambia el tipo de mapa entre vista de satélite o vista de callejero, y setZoom() que establece el nivel de zoom deseado para el mapa.
Como paso final para poder generar el mapa, ha de tenerse en cuenta la configuración del “AndroidManifest.xml” de ContactMap. En este fichero deben declararse los componentes básicos que actúan, sus características, así como los permisos de usuario. Para poder utilizar mapas, es necesario declarar de forma explícita que se va a utilizar la biblioteca com.google.android.maps mediante el elemento :

< uses-library android:name="com.google.android.maps" />

Código 19. Declaración en el manifiesto del API de Google Maps

También se ha de conceder a la aplicación permiso para conectarse a Internet y poder así traer los mapas necesarios:

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

Código 20. Declaración en el manifiesto del permiso de conexión a Internet

En el Código 21 se muestra el código implementado para mostrar un mapa en pantalla. Solamente se incluye el código relevante para tal fin dentro del método onCreate() , que es el método que se llama la primera vez que se crea una Activity, en este caso nada más cargar la aplicación.

Aplicaciones Android
Aplicaciones Android
Código 21. Mostrar un mapa de Google Maps

Se puede comprobar que es necesario indicar a la actividad ContactMap qué es lo que ha de mostrar como vista principal al usuario, es decir, el objeto mMapView con el mapa cargado. El método setContentView() heredado de la clase Activity realiza esta función.

4.2.2 Representación de los contactos en memoria

Para cada contacto, la aplicación necesita conocer su nombre, número de teléfono, su dirección de correo electrónico, sus datos de localización, esto es, latitud y longitud, y la fecha en la que fue actualizada su ubicación. Toda esta información se obtiene desde diferentes fuentes: los contactos almacenados en el dispositivo móvil, el servidor o la base de datos SQLite.
Sin embargo, una vez se han reunido todos estos datos, se representan en memoria a través de una lista llamada mFriendList y que es una variable global de la clase ContactMap . El reunir todos los datos en una misma estructura, que es la que se consulta a la hora de representar los contactos en el mapa, permite un acceso mucho más rápido y limpio que si se obtuviera por separado de forma directa desde cada una de las fuentes.
La clase Friend representa a un contacto que puede ser mostrado en el mapa, y constituye cada uno de los miembros de la lista mFriendList . Tiene cinco atributos declarados:

- mNumber , para el número de teléfono,
- mName , para el nombre,
- mEmail , para la dirección de correo electrónico,
- mGeo , para los datos de localización. Es una instancia de la clase GeoPoint .
- mDate , para la fecha en la que fueron obtenidos los datos de localización.

mFriendList se actualiza cada vez que se recibe una respuesta del servidor. La excepción se produce cuando no es posible disponer de Wi-Fi, en cuyo caso se actualiza una única vez desde la base de datos SQLite nada más arrancar la aplicación y detectar tal circunstancia. Este comportamiento se explica en el apartado 4.2.12.

4.2.3 Dibujar y gestionar elementos en el mapa

La interfaz principal de ContactMap consiste en la vista completa de un mapa donde el usuario visualiza la ubicación de los contactos, puede desplazarlo libremente, realizar cambios en el nivel de zoom, o bien pulsar en un contacto para centrar el foco en él. Además, al tocar sobre el contacto este muestra un bocadillo con información acerca de la fecha en la que notificó su ubicación actual.
Por lo tanto, los elementos que se deben dibujar sobre el mapa visto por el usuario son los siguientes:

- Un punto de color, que representa a un determinado contacto.
- El nombre del contacto.
- Un círculo concéntrico de ese mismo color, en caso de que el contacto sea el que centra el foco de aplicación en ese momento.
- Un bocadillo con la fecha de actualización.
El último elemento dibujable, el bocadillo con información, debe ser pintado en caso de que el usuario pulse sobre el contacto. Por lo tanto, es necesario también tomar el control de los elementos dibujados a fin de saber cuándo el usuario ha pulsado sobre uno de ellos.

Dibujar ubicación de los contactos

Como ya se explicó en el apartado 4.2.1, el paquete com.google.android.maps es el que permite trabajar con los mapas y servicios ofrecidos desde Google Maps. En este paquete existe una clase, de nombre Overlay , que es la que permite dibujar elementos sobre el mapa.
Esta clase se diferencia notablemente de otras clases utilizadas por Android para dibujar elementos en pantalla, clases que se verán más adelante. La clase Overlay es específica para mapas y asocia a cualquier objeto dibujado los movimientos y cambios que se realicen sobre dichos mapas. Dicho de otro modo: lo habitual es que si el desarrollador desea situar elementos en un mapa, también desee que estos mismos elementos se desplacen coherentemente según el usuario desplace el mapa, o aparezcan o desaparezcan de la pantalla según el nivel de zoom que se haga sobre él. Es, por ejemplo, lo que debe ocurrir en ContactMap con los contactos y elementos derivados que se dibujan. Todos estos detalles de desplazamiento conjunto quedan cubiertos por la clase Overlay .
En esta clase se encuentra un método fundamental llamado draw() . Este método es constantemente invocado cada vez que ocurre algún cambio en el mapa; por ejemplo, cuando el usuario pulsa en él, cuando lo desplaza o cuando realiza cambios en el nivel de zoom. El método draw() debe contener aquellos elementos que se deseen dibujar de forma que, si lo elementos están asociados a coordenadas, solamente serán dibujados si dado el mapa mostrado en pantalla deben ser realmente visibles para el usuario.
La única forma de implementar a medida este método es extendiendo la clase Overlay mediante otra clase. En ContactMap, la clase MapOverlay extiende a dicha clase y dibuja, con su correspondiente método draw() , los contactos y demás elementos asociados.
A continuación se detalla, en el Código 22, el fragmento del método draw() de MapOverlay que dibuja en el mapa los contactos, representándolos mediante un punto de color y acompañándolos de su nombre respectivo.

Aplicaciones Android
Aplicaciones Android
Código 22. Dibujado de contactos en el mapa

En este código se utilizan algunas clases del paquete android.graphics . Este paquete alberga clases de todo tipo que resultan muy útiles para configurar y dibujar elementos en pantalla, desde figuras geométricas a textos o efectos. En concreto, en ContactMap se utilizan las siguientes clases:

- Canvas : clase a través de la cual se dibujan todos los demás elementos.
- Paint : define las características de un dibujo, como el color, el tamaño, tipo de letra para el texto, ciertos efectos, etc.
- Color : gestiona la manipulación de colores.
- Point : representa un punto mediante dos coordenadas (x, y).
- RectF : crea una figura con forma de rectángulo, según las coordenadas facilitadas.

Cada vez que se dibuja un elemento a través de Canvas , este debe estar asociado a un objeto Paint que configura el estilo del dibujo. Por ello, al principio del método draw() se definen tres objetos Paint que se asociarán, respectivamente, al punto de color, a la circunferencia concéntrica y al nombre del texto.
En el Código 22 solamente se muestra la configuración del objeto Paint llamado paintText , que representa el estilo deseado para dibujar el nombre del contacto al lado de su punto de color. Como se puede apreciar, con método como setColor() , setTextSize() , o setFakeBoldText() se establece el color deseado, el tamaño de texto o la presencia de negrita. Los otros objetos Paint obtienen sus configuraciones con métodos similares.
A continuación se recorren uno a uno los contactos presentes en la lista. El objetivo en cada recorrido es sencillo: transformar sus coordenadas de latitud y longitud en un punto de coordenadas (x, y) de la pantalla, dibujar ese punto con un color y escribir el nombre del contacto cerca del mismo.
La clase MapView (que, como se recordará, representa la vista de un mapa) tiene un método muy útil que permite proyectar unas coordenadas geográficas en coordenadas de un plano de dos dimensiones (x, y): el método getProjection() . De esta forma se obtiene un sencillo objeto Point que representa en qué punto de la pantalla se ubica el contacto, dadas su latitud y longitud actuales.
Después de elegir el color para el contacto de la lista mColors , se dibuja un punto de 6 píxeles de radio en su ubicación geográfica actual, transformada ya en unas coordenadas (x, y) que están presentes en el objeto Point . La clase Canvas permite dibujar un círculo con el método drawCircle() , donde simplemente se debe indicar las coordenadas de su centro y el radio deseado.

Aplicaciones Android
Figura 23. Mapa con los contactos dibujados

El foco de la aplicación ContactMap está siempre sobre alguno de los contactos mostrados, haciendo a éste objeto de todas las acciones posibles asociadas, como mandar un SMS, realizar una llamada o escribir un correo electrónico. En caso de que el contacto recién pintado sea el contacto actual, según marca el objeto mCurrent (que es una instancia de la clase Friend) , se dibuja entonces una circunferencia entorno al punto con un radio amplio de 30 píxeles y del mismo color que el propio punto; así se indica que dicho usuario tiene el foco de la aplicación.
Por último, se debe escribir el nombre del contacto. El método drawText() de la clase Canvas permite dibujar textos como si de cualquier otro elemento se tratara. En este caso, el texto se sitúa en el área superior derecha con respecto al centro del punto dibujado.
Como información al lector, se dirá que el otro elemento dibujado, el bocadillo con información, no representa mayor dificultad de la vista hasta ahora. Simplemente consiste en una combinación de figuras geométricas que forman la silueta de un bocadillo cuyo centro se sitúa en el contacto y en el que se escribe dicha información.
Más adelante se describe el proceso mediante el cual se controlan la aparición o no de este bocadillo informativo.
Una vez explicado el funcionamiento de MapOverlay y como a través de una clase derivada Overlay se pueden pintar elementos en el mapa, es necesario mencionar ahora cómo se vincula un determinado Overlay a un mapa para dibujar elementos en él.
En la clase ContactMap que, como se ha dicho anteriormente, lleva el mayor peso de la ejecución y coordina las demás clases, se vincula al mapa mostrado el Overlay representado por la clase MapOverlay . En el Código 23 se enseña este proceso:

Aplicaciones Android
Código 23. Vincular un MapView con un Overlay

Después de crear un objeto MapOverlay , éste se asocia al mapa representado por mMapView utilizando los métodos getOverlays().add() . Se puede intuir que un mismo mapa permite tener varios objetos Overlay asociados, de forma que se pueden pintar elementos de forma independiente a través de diferentes clases que se implementen. En este caso, el mapa mostrado por ContactMap solamente tendrá un Overlay asociado, encargado de dibujar los contactos y sus elementos asociados.

Controlar los elementos dibujados

La clase Overlay no sólo da acceso a dibujar elementos asociados a un determinado mapa, sino que también permite controlar los eventos relacionados con ellos como, por ejemplo, que sean pulsados por el usuario.
El método onTouchEvent() se llama cada vez que el usuario pulsa en la pantalla. Este método se utiliza en MapOverlay (que extiende Overlay ) para conocer si el usuario ha pulsado sobre el contacto y si es, por ello, necesario cambiar el foco, centrar el mapa o pintar un bocadillo con la fecha de actualización del contacto.
Existe un atributo en MapOverlay de carácter global llamado mBubble , que no es más que un tipo string donde se almacena el número de teléfono del usuario que ha sido pulsado o, en su defecto, almacena una cadena vacía como indicador de que ningún usuario debe mostrar un bocadillo.
El siguiente código muestra la parte más importante del método onTouchEvent() :

Aplicaciones Android
Aplicaciones Android
Código 24. Control de pulsaciones sobre un contacto

La estrategia seguida es muy sencilla. Se recorren todos los contactos dibujados en el mapa y se crea una zona en forma de rectángulo que abarque cada contacto recorrido. Después, se comprueba si el punto tocado por el usuario se encuentra ubicado dentro de esta zona, en cuyo caso será necesario dibujar el bocadillo o cambiar el contacto con el foco.
En primer lugar, de nuevo se obtiene una proyección de las coordenadas de latitud y longitud del contacto en coordenadas (x, y) de la pantalla. A continuación, se crea un rectángulo, representado por la clase RectF , cuyo centro sea el propio contacto. El rectángulo crea un área mayor que el propio punto, de forma que no sólo se activa el bocadillo si se pulsa en el propio contacto, sino que también lo hace si se pulsa en una zona relativamente próxima, lo que repercute en la comodidad del usuario.
Es importante hacer notar que el rectángulo no se dibuja, sino que simplemente se crea. Es un objeto más situado en ciertas coordenadas del mapa, pero no ha sido dibujado en él puesto que no se ha utilizado ningún objeto Canvas para ello. Existe, pero el usuario ni lo sabe ni lo puede percibir.
El método onTouchEvent() facilita las coordenadas (x, y) del punto donde el usuario ha pulsado. Conociendo las coordenadas de los contactos y las coordenadas del punto pulsado, simplemente es necesario comparar ambos; salvo que, en realidad, no se compara con el punto exacto del contacto sino que se hace con un área completa entorno a él, representada en el rectángulo creado.
La clase RectF permite saber si un punto dado se encuentra dentro del área del rectángulo mediante el método nativo contains() . Así pues, en caso de que el usuario haya pulsado en esta área que representa el contacto, se almacena su número de teléfono en mBubble , se convierte en el contacto que posee el foco a partir de ese momento y se centra el mapa en él. La próxima vez que el método draw() sea llamado (cosa que ocurrirá a continuación), todos estos cambios serán visibles para el usuario.

4.2.4 Control del mapa: movimiento y zoom.

En este apartado se enseña como tomar el control de mapa en dos vertientes: el control del nivel de zoom, y el libre desplazamiento mediante el sistema de pinchar y arrastrar.
Android cuenta con un paquete muy completo para la composición de elementos de interfaz, también llamado widgets, como botones, imágenes, cajas de texto, etc. Este paquete, denominado android.widget contiene clases como las siguientes:

- Button : representa un botón que puede ser pulsado por el usuario.
- CheckBox : permite incluir elementos checkbox en formularios.
- DigitalClock : muestra un reloj digital. La clase AnalogClock hace lo propio, pero con un reloj analógico.
- EditText : clase que muestra una caja de texto donde el usuario puede escribir.
- FrameLayout : clase que representa un área de la pantalla donde colgar otros elementos visuales.
- ImageButton : representa un botón al que se le puede asociar una imagen.
- MediaController : clase que ofrece un conjunto de controles para elementos multimedia.
- ProgressBar : muestra una barra de progreso.
- RadioButton : permite introducir elementos radiobutton en formularios.
- TextView : clase que ofrece un área de texto no editable; por ejemplo, títulos para campos de formularios.
- VideoView : posibilita introducir elementos de video.
- ZoomControls : ofrece elementos de control de zoom.

Android permite asociar vistas predefinidas a los elementos mostrados en la pantalla, de forma que estos se agrupan siguiendo determinado patrones de diseño; estos patrones predefinidos representan los más comúnmente utilizados en ciertos tipos de aplicaciones. Por ejemplo, existen en este paquete android.widget diversas clases como Gallery , GridView , LinearLayout , ListView o TableLayout .
En ContactMap, la interfaz mostrada no incluye solamente el mapa con los contactos dibujados en él; también se deben incluir ciertos widgets a través de los cuales el usuario pueda realizar algunas de las acciones contempladas en la aplicación. Una de estas acciones es el control del nivel de zoom del mapa.
La clase FrameLayout del paquete android.widget puede utilizarse para construir interfaces de usuario en las cuales existen diversos elementos. Esta clase define un área, que puede ser toda la pantalla o una parte de ella, donde colgar los elementos que componen la interfaz. Cada elemento deseado se añade a FrameLayout pudiendo además especificar ciertas características del mismo, como su posición relativa o su comportamiento frente a otros elementos presentes en el mismo FrameLayout o frente a ciertos eventos, como pulsaciones del usuario. Utilizando la clase FrameLayout es como se ofrece al usuario de ContactMap el control del nivel de zoom del mapa.
El siguiente código muestra los pasos a seguir para poner a disposición del usuario estos controles.

Aplicaciones Android
Código 25. Control del nivel de zoom

Tras crear un nuevo objeto FrameLayout , ya se le pueden asociar distintos elementos de vista. El primero de ellos en ser colgado en el nuevo FrameLayout es el propio mapa. Como no se indica ningún parámetro de configuración, el mapa ocupará por defecto todo el área de este FrameLayout , es decir, la pantalla completa.
A continuación, se crea un objeto ZoomControls , clase que también pertenece al paquete android.widget . Sin embargo, es necesario que este control del zoom esté asociado al mapa, ya que es el objeto que debe ser controlado. Para ello, se utiliza el método getZoomControls() de la clase MapView , que directamente devuelve un control de zoom asociados al mismo mapa.
Para los controles de zoom, sí se desea crear una configuración que matice su comportamiento dentro del FrameLayout , por lo que se añadirán a este incluyendo un objeto FrameLayoutParams con determinados valores. Con la constante LayoutParams.WRAP_CONTENT , se especifica que el objeto ocupe tanto espacio como necesite, y con Gravity.BOTTOM y Gravity.CENTER_HORIZONTAL se establece que el objeto se sitúe en la parte inferior del objeto FrameLayout y centrado, respectivamente.

Aplicaciones Android
Figura 24. Controles del nivel de zoom

Por defecto, el control del nivel de zoom se hace visible al usuario cuando este pulsa en la pantalla, y se vuelven a ocultar tras unos pocos segundos de inactividad.
Para permitir al usuario que pueda desplazarse por el mapa libremente simplemente arrastrando el dedo por la pantalla, es tan simple como invocar el método setClickable() de la clase MapView y establecer el valor true . De este modo, además se permite capturar los eventos relacionados con pulsaciones en la pantalla.

4.2.5 Cambiar al contacto anterior y siguiente

El contacto que mantiene el foco de la aplicación es aquel que es objeto de algunas de las acciones que ContactMap ofrece, como realizar una llamada, enviar un SMS o un correo electrónico. El contacto que tiene el foco está representado por un círculo que rodea al punto de color que lo representa en el mapa.
El usuario puede cambiar el foco pulsando sobre otro contacto, eligiéndolo de un listado de contactos o, tal y como se explica en este apartado, utilizando unos botones para desplazarse al contacto anterior o al siguiente, según su orden alfabético. Estos botones han de estar siempre presentes en la pantalla y ubicados en las esquinas inferiores de la interfaz principal, es decir, sobre el mapa con los contactos dibujados.

Aplicaciones Android
Figura 25. Botones de anterior y siguiente

Android permite asociar a una imagen cualquiera de las propiedades de un botón, entre las que se encuentra principalmente el que pueda ser pulsada por el usuario y asociar tal evento a una determinada acción. La clase que permite este comportamiento es ImageButton , del paquete android.widget .
A continuación se enseña al lector el fragmento del método onCreate() de la clase ContactMap que crea el botón que permite mover al siguiente contacto, siguiendo un orden alfabético.

Aplicaciones Android
Código 26. Mostrar botón de siguiente contacto

En primer lugar, se instancia un objeto de la clase ImageButton , pasando únicamente como parámetro un objeto Context válido, que representa el contexto de la aplicación. La misma clase ContactMap sirve como parámetro de este tipo.
Para asociar una imagen a un objeto ImageButton no es necesario más que invocar el método setImageResource() . La imagen que se utiliza para representar este botón se encuentra ubicada en la carpeta de recursos “\res\drawable” bajo el nombre de “next.png”.
Para configurar la ubicación de la imagen, se utiliza la clase ya anteriormente vista FramLayout.LayouParams . En este caso, se especifica que la imagen ocupe todo el espacio que necesite (es una imagen muy pequeña) y en la parte inferior derecha de la pantalla.
El lector probablemente recuerde la clase FrameLayout que permitía definir un área donde poder colgar elementos para componer la interfaz de usuario. En el apartado 4.2.4 se utilizaba dicha clase para incluir botones para el control del nivel de zoom. En esta ocasión, se utiliza el mismo objeto FramLayout para incluir, mediante su método nativo addView() , un nuevo elemento a la interfaz: el botón de siguiente.
Tras mostrar el botón en la interfaz de usuario, es necesario asociarle el comportamiento deseado. Utilizando un listener vinculado a dicho botón, se implementa el método onClick() , que provocará que se llama a moveNext() cada vez que el usuario pulse este botón. El método moveNext() , también definido en ContactMap , únicamente recorre la lista de contactos y establece el foco en el contacto que es siguiente al actual.
El proceso aquí descrito es exactamente igual para el botón que permite al usuario desplazarse al contacto anterior.



Beneficiese adquiriendo software original.