Home MundoTec Software Código fuente Tutorial / pdf Minijuegos
Cerrar

Tutorial Desarrollo de Aplicaciones en Android

Tutorial DESARROLLO DE APLICACIONES ANDROID.







4.2.14 Mostrar listado de contactos

Acciones como mandar un SMS, un correo electrónico o realizar una llamada se realizan sobre el contacto que en ese momento tenga el foco de la aplicación. El usuario puede cambiar el foco de un contacto a otro de diferentes maneras: pulsando sobre ellos, utilizando los botones de anterior y siguiente (se verán más adelante) o a través de un listado con todos los contactos ordenados alfabéticamente. Éste último caso es el que se explica en este apartado.
Cualquier aplicación en Android está formada por una serie de componentes básicos entre los que se cuenta Activity. Una clase Activity , como ya sabrá el lector, representa una funcionalidad importante, con entidad propia, y que está asociada generalmente a una interfaz con la que el usuario puede interactuar. La clase ContactMap , por ejemplo, es una Activity que muestra un mapa y permite al usuario realizar una serie de acciones. Una Activity también puede invocar a su vez a otros componentes Activity.
El listado de contactos consiste en una lista a pantalla completa donde se ordenan alfabéticamente todos los contactos y donde el usuario pulsa sobre uno de ellos; se vuelve entonces de forma inmediata al mapa, donde dicho contacto adquiere el foco de la aplicación. Toda esta acción se enmarca dentro de un nuevo componente Activity, ya que representa una funcionalidad importante con su propia y diferenciada interfaz.

Lanzar una nueva Activity

La clase FriendListViewer extiende a la clase Activity y es la encargada de componer la lista de contactos y capturar cuál de ellos es pulsado. En primer lugar, a través del Código 45 se enseña cómo se lanza esta nueva Activity desde el método onMenuItemSelected() que, como se recordará, es el método invocado al seleccionar una opción del menú principal.

Aplicaciones Android
Código 45. Lanzar Activity para el listado de contactos

Toda nueva Activity debe ser lanzada a través de un Intent, que especifica qué es lo que se quiere hacer y con qué datos debe hacer. Habitualmente, un Intent es una forma de delegar determinadas acciones en otras aplicaciones instaladas en el sistema y que, obviamente, tengan la capacidad para llevarlas a cabo. Sin embargo, también es posible especificar que un Intent debe ser realizado por una determinada instancia de una clase. Es lo que se pretende hacer en este caso con el listado de contactos.
Al crear un objeto de la clase Intent se especifica en el constructor que dicho Intent debe ser realizado en la clase FriendListViewer . De esta forma, ya se ha configurado qué es lo que se quiere hacer (ejecutar la clase FriendListViewer ). Ahora se debe indicar con qué datos se desea hacer. Ya que el objetivo final es mostrar un listado con los contactos, lo que se va a pasar como datos asociados a dicho Intent es el nombre de cada uno de los contactos presentes en la lista mFriendList .
La clase Intent permite asociar muchos tipos básicos de datos para poder ser procesados en la clase o aplicación donde vaya a ser atendido el Intent. Por ejemplo, en esta situación concreta se ha utilizado el método putExtra() para asociar cadenas de texto correspondientes a cada uno de los nombres. Una peculiaridad común a cada dato asociado a un Intent, cualquiera que sea su tipo, es que debe ir acompañado de una etiqueta textual que lo identifique. Por ello, al método putExtra() se le asigna tanto el nombre del contacto como la etiqueta identificativa “contactoX”, donde la X se sustituye por el orden que debe ocupar en la lista.
El Intent ya ha sido construido: representa una acción específica (ejecutar la clase FriendListViewer ) y unos datos asociados (los nombres de los contactos a listar). Únicamente resta lanzar la Activity.
Existen dos formas para lanzar una nueva actividad en Android. Si se quiere lanzar la Activity sin más, sin esperar ningún resultado final, se utiliza el método startActivity() . Si se espera un valor devuelto por la Activity, entonces se lanza con el método startActivityForResult() , de forma que al terminar dicha actividad se invocará el método onActivityResult() , que permite manejar el resultado obtenido. Como en este caso se desea conocer qué contacto ha sido seleccionado en la lista, se utilizará el segundo de los métodos posibles.
Invocar el método startActivityForResult() es muy sencillo. Simplemente se indica el Intent que sea desea lanzar y se le asocia un código identificativo para poder diferenciar en startActivityForResult() cuál es la Activity que ha finalizado. Es decir, todas las actividades lanzadas con startActivityForResult() acaban en el mismo método onActivityResult() , que ha de poder diferenciarlas de algún modo.

Mostrar el listado de contactos

Al lanzar el Intent explicado anteriormente, la clase ContactMap deja de ser la actividad en curso para entrar en un estado onPause() primero y onStop() después, ya que no es visible para el usuario. La clase FriendListViewer , que también extiende a Activity , toma toda la pantalla ofreciendo al usuario un listado de contactos.
Esta clase debe extraer los datos asociados al Intent con el que ha sido lanzada, componer la lista de nombres y capturar cuál de ellos ha sido pulsado, para poder retornar ese resultado a la clase ContactMap (que despertará y pasará a un estado onResume() ) y su método onActivityResult() .
En primer lugar, se muestra el método onCreate() de FrienListViewer , que extraerá los datos asociados al Intent y compondrá la lista de contactos. El Código 46 enseña el proceso.

Aplicaciones Android
Código 46. Mostrar listado de contactos

Lo primero de todo es mencionar que, tal y como muestra el código anterior, la clase FriendListViewer no extiende directamente a la clase Activity , sino que extiende a ListActivity . Esta clase, al igual que ocurría en la clase ContactMap con MapActivity , es derivada de Activity y, si la primera estaba específicamente adaptada para gestionar mapas, la segunda lo está para mostrar listados a pantalla completa donde el usuario ha de elegir uno de los elementos. Además, la clase ListActivity tiene asociado un objeto ListView , que representa uno de los muchos diseños para interfaces de usuario predefinidas en Android y que ayuda en la tarea de componer y mostrar un listado vertical de elementos.
La clase Bundle forma parte del paquete android.os , un pequeño pero relevante paquete que ofrece varios servicios asociados al sistema operativo, paso de mensajes o comunicaciones entre procesos. Esta clase representa una colección de valores y suele utilizarse para recuperar los datos asociados a un Intent, tal y como se hace en FriendListViewer .
Mediante el método getIntet() de la clase Activity se obtiene el objeto Intent con el que fue lanzada dicha actividad (si es el caso). A su vez, este objeto Intent ofrece un método llamado getExtras() que devuelve el conjunto total de valores asociados a él. A través del objeto Bundle , que contendrá todos los valores del Intent, se van recorriendo uno a uno y se recuperan individualmente accediendo a ellos a través de su etiqueta identificativa. Al mismo tiempo que se recuperan, se van almacenando en una nueva lista de tipo string llamada mFriendShowList . En esta lista es donde estarán guardados finalmente todos los nombres de contactos que se van a mostrar en el listado al usuario.
ListActivity tiene un método exclusivo al resto de clases derivadas de Activity llamado setListAdapter() que construye con los valores dados el listado final que será visible para el usuario a pantalla completa. Para ello, es necesario transformar antes la lista mFriendShowList en un objeto de la clase ListAdapter .
Por último, con setSelection() de ListActivity se especifica cuál de los elementos de la lista debe aparecer como seleccionado por defecto al usuario; en este caso, es el primero de ellos.

Aplicaciones Android
Figura 28. Listado de contactos

Ya se ha construido y mostrado el listado al usuario, pero ahora es necesario saber qué hacer cuando el usuario seleccione uno de los nombres. El método onListItemClick() de ListActivity se activa cuando tal evento sucede, y debe devolver a ContactMap la posición del usuario seleccionado con setResult() , además de dar por terminada con el método finish() la presente actividad FriendListViewer .

public class FriendListViewer extends ListActivity {
@Override
protected void onListItemClick(ListView l, View v, int position, long
id) {
super.onListItemClick(l, v, position, id);
// Devuelve la posición del elemento seleccionado
this.setResult(position);
// Terminar la presente Activity
this.finish();
}
}

Código 47. Devolver elemento seleccionado en la lista de contactos

Manejar el resultado de una Activity

Ya que la Activity encarnada en la clase FriendListViewer se lanzó con el método startActivityForResult() , cuando esta termina es invocado de inmediato el método onActivityResult() declarado en la clase ContactMap .
De esta forma, es posible gestionar el resultado proporcionado por una Activity. Una vez finalizada, dicho método es llamado proporcionando acceso tanto al resultado final como al código identificativo con el que fue lanzado la actividad, ya que es el que va a permitir a este método conocer cuál es la actividad finalizada.
Una vez el usuario ha seleccionado un contacto de la lista mostrada, la aplicación debe establecer este contacto como el poseedor del foco de aplicación, centrando además el mapa en él y haciéndole objeto de otras acciones posterior, como el envío de un SMS, una llamada telefónica, o el envío de un correo electrónico.
El Código 48 muestra este proceso:

public class ContactMap extends MapActivity {
// Contacto con el foco de la aplicación
public Friend mCurrent = null;
// Lista de contactos
public ArrayList mFriendList = new ArrayList();
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// Código correspondiente a la actividad FriendListViewer
if (requestCode==0){
// Centrar mapa en el contacto seleccionado
mMapControl.animateTo(
mFriendList.get(resultCode).getGeo());
// Cambiar contacto con el foco
mCurrent=mFriendList.get(resultCode);
}
// (...)
}
}

Código 48. Contacto seleccionado por el usuario

Un último apunte que se debe mencionar con respecto a la clase FriendListViewer es que, como Activity que es, debe ser declarada explícitamente en el fichero “AndroidManifest.xml”. Recuérdese que cualquiera de los componentes básicos de Android que se utilicen en una aplicación debe ser declarado para poder utilizarlo. Así pues, la actividad queda declarada de la siguiente manera dentro del elemento :

< activity android:name=".FriendListViewer">

Código 49. Declaración en el manifiesto de la Activity FriendListViewer

4.2.15 Llamar a un contacto

Entre las acciones que el usuario puede realizar sobre un contacto está la de realizar una llamada telefónica. Para tal fin se utilizará un Intent y se lanzará una nueva Activity, pero de una forma algo diferente a lo recientemente visto con el listado de los contactos.
Como ya se ha explicado en anteriores ocasiones, mediante un Intent se delega una determinada acción en el sistema, de forma que este encuentra la aplicación más apropiada de entre todas las instaladas para llevarla a cabo. Un Intent consiste en la especificación de la acción a realizar, y los datos con los que debe realizarse. Android cuenta por defecto con un gestor de llamadas telefónicas. Esto permite poder lanzar el Intent específico de una llamada sin tener que preocuparse de nada más, puesto que el sistema la derivará a dicho gestor de llamadas de Android.
El siguiente código, Código 50, expone los pasos a seguir para lanzar un Intent de llamada telefónica. La acción se lleva a cabo en el método onMenuItemSelected() de la clase ContactMap , una vez que el usuario ha seleccionado la opción correspondiente en el menú principal.

Aplicaciones Android
Código 50. Intent para realizar una llamada telefónica.

Al crear el objeto Intent se especifica el tipo de acción que se desea realizar. En el caso del listado de contactos se indicaba que se quería ejecutar una clase concreta que llevaba a cabo dicha acción (la clase FriendListViewer ). Aquí también se indica la acción a realizar, pero no diciendo directamente quién debe realizarla, sino diciendo qué se quiere realizar. El sistema ya se encargará de buscar la aplicación más indicada para ello.
Existen acciones frecuentes y definidas para indicar en un Intent. La correspondiente a la llamada telefónica es Intent.ACTION_CALL . Una vez creado el objeto Intent es necesario añadir como datos el número de teléfono al que se desea llamar; este debe ser adjuntado como un objeto Uri . Las URI relacionadas con recursos telefónicos han de tener el formato “tel:numero” para poder funcionar correctamente con la acción Intent.ACTION_CALL .
Tras la creación y configuración del objeto Intent ya se puede lanzar la actividad. Como en este caso no se espera que esta retorne ningún valor que deba ser capturado, la Activity se lanza sin más con el método startActivity() . En ese momento el sistema abrirá el gestor de llamadas y marcará el número indicado.

Aplicaciones Android
Figura 29. Llamada a un contacto

El acceso a las funciones telefónicas del dispositivo móvil requiere de su correspondiente permiso en la declaración del manifiesto. El permiso es el siguiente:

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

Código 51. Declaración en el manifiesto del permiso de llamada telefónica

4.2.16 Enviar un correo electrónico

Otras de las funcionalidades ofrecidas por ContactMap es la de enviar un correo electrónico a un contacto. Al igual que ocurre con la llamada telefónica, esta acción se llevará a cabo a través de un Intent de forma que sea el sistema el que se haga cargo a través de la aplicación más adecuada.

Aplicaciones Android
Código 52. Intent para enviar un correo electrónico

El Código 52 enseña la parte del método onMenuItemSelected() de la clase ContactMap que permite enviar un correo electrónico. En primer lugar se crea el objeto Intent correspondiente, en este caso asociándole la acción Intent.ACTION_SEND .
Después, como datos adjuntos a este Intent se incluyen tanto la dirección de correo electrónico del contacto actualmente con el foco, como el tipo MIME del correo. A continuación se lanza la Activity correspondiente con el método startActivity() , ya que no se precisa manejar ningún resultado devuelto por ésta.
Una peculiaridad de esta llamada a starActivity() es que se utiliza como parámetro el método createChooser() de la clase Intent . Este permite que, en caso de existir varias aplicaciones capaces de hacerse cargo de dicho Intent, el sistema muestre al usuario un menú donde puede elegir la aplicación para desarrollar la acción.
Si en el caso de las llamadas telefónicas existe por defecto un gestor de las mismas en Android, para el correo electrónico no hay instalado ningún cliente. Es por ello que, en la versión considerada de Android para este proyecto, este Intent no podrá ser atendido por ninguna aplicación, hecho que el sistema comunicará al usuario mediante el correspondiente mensaje.

Aplicaciones Android
Figura 30. Mensaje tras intentar enviar un correo electrónico

4.2.17 Enviar un SMS

El proceso de envío de un SMS es bastante similar al visto en el listado de contactos, en el apartado 4.2.14. No se utilizará un Intent para delegar en el sistema dicha operación, sino que nuevamente se utiliza para lanzar una nueva Activity que se corresponde con una clase ya implementada en la propia aplicación ContactMap, de nombre SMSWriter .
Esta clase muestra al usuario una nueva interfaz donde poder escribir el mensaje de texto, tal y como exhibe la Figura 31. La interfaz consta de los siguientes elementos:

- Una caja de texto para introducir el número, acompañada de su correspondiente etiqueta textual.
- Una caja de texto para introducir el mensaje, acompañada igualmente de su etiqueta textual.
- Un botón para enviar.
- Un botón para borrar.

Aplicaciones Android
Figura 31. Interfaz para enviar un SMS

Lanzar una nueva Activity

El lanzamiento de una nueva Activity sigue siendo igual al visto en anteriores ocasiones. En el método onMenuItemSelected() de la clase ContactMap , que es llamado cada vez que el usuario selecciona una opción del menú principal, se crea un nuevo Intent y se lanza la Activity correspondiente a este.
El objeto de la clase Intent está asociado a la clase SMSWriter , debido a que no se quiere que el sistema se haga cargo y busque la aplicación instalada más adecuada, sino que se desea explícitamente que se ejecute en esta clase implementada en la propia aplicación ContactMap.
Como datos asociados al Intent (recuérdese, un Intent consta de una acción y unos datos) se encuentra únicamente el número de teléfono del contacto que cuenta en ese momento con el foco, de forma que ya aparezca ese campo rellenado en la interfaz del usuario.
Es necesario que la nueva Activity, una vez finalizada, devuelva a la Activity principal, ContactMap el texto introducido por el usuario para poder enviar el mensaje. Por ello se lanza con el método startActivityForResult() , que llamará a onActivityResult() cuando la actividad termine.
En el siguiente código se muestra cómo lanzar la nueva Activity desde el método onMenuItemSelected() :

Aplicaciones Android
Código 53. Lanzar Activity para enviar un SMS

Crear la interfaz a partir de XML

Android ofrece la posibilidad, ya vista en anteriores apartados, de utilizar en el código fuente los recursos externos declarados dentro de la carpeta “\res”. Hasta ahora se han visto casos donde se declaraban externamente cadenas de texto para las opciones del menú principal o ciertos avisos al usuario, facilitando así su utilización en otros idiomas; también se ha enseñado como utilizar imágenes o iconos que después pueden ser referenciadas en el código con una simple declaración que apunte a esta carpeta de recursos externos.
Sin embargo, quizás la mayor utilidad de estas declaraciones externas al código resida en la posibilidad de definir, de nuevo a través de documentos XML, interfaces de usuario completas. Dentro de la carpeta “\res\layout” pueden definirse cuantos documentos XML se desee, cada uno de ellos representando una determinada interfaz que se quiera implementar, o incluso diferentes versiones de la misma para según qué casos.
Para la interfaz de usuario que debe mostrarse con la clase SMSWriter se utilizan estas declaraciones externas, ubicadas en el fichero de nombre “sms.xml”. Dada la Figura 31 anteriormente enseñada, sabemos que hemos de utilizar los siguientes elementos del paquete android.widget que, como se vio anteriormente, contiene numerosas clases para construir interfaces de usuario:

- Para las etiquetas textuales, la clase TextView .
- Para las cajas de texto, la clase EditText .
- Para los botones, la clase Button .

Cada una de estas clases, ya sean instanciadas desde un documento XML externo o en el código fuente de forma tradicional, tienen una serie de atributos que han de ser configurados y que matizan su aspecto, colocación o comportamiento dentro de la interfaz.
El siguiente ejemplo escrito en XML define dos de los elementos presentes en la interfaz para escribir un SMS. Son la etiqueta y la caja de texto correspondiente a la parte donde el usuario puede introducir el contenido del mensaje que desea escribir.

android:id="@+id/txt2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5px"
android:text="Mensaje"
android:textSize="18sp"
/>
android:id="@+id/message"
android:layout_width="300sp"
android:layout_height="250sp"
android:layout_marginTop="5px"
android:layout_marginLeft="10px"
android:textSize="18sp"
android:maxLength="160"
/>
Código 54. Construcción con XML de elementos de interfaz de usuario

Cada elemento declarado tiene como nombre el propio de la clase a la que pretende instanciar, en este caso y . Dependiendo de dicha clase, los elementos podrán declarar unos atributos u otros, aunque aquí los atributos son muy similares entre ambos. Se pueden observar los siguientes atributos:

- android:id : todo elemento declarado debe tener un identificador único, escrito en el atributo android:id . Este identificador es a través del cual se referencia el elemento en el código fuente, y es el nombre con el que aparece declarado el recurso dentro del fichero “R.java” (se recordará que dicho fichero mantiene una sincronización con cualquier elemento declarado como recurso en la carpeta “\res”, de forma que permite a las demás clases de la aplicación Android poder utilizar el recursos deseado simplemente nombrándolo).
- android:layout_width : indica la anchura que debe tener este elemento. Si se especifica wrap_content , entonces ocupará tanto espacio como necesite; por otra parte, una configuración como fill_parent indica al widget que ocupe tanto espacio como tenga disponible. Otra alternativa, como ocurre en es especificar exactamente el tamaño deseado, ya sea en píxeles ( px ), en píxeles escalados ( ps ), en pulgadas ( in ), milímetros ( mm ) u otros. - android:layout_height : indica la altura del elemento. Se comporta igual que el atributo layout_width .
- android:layout_marginTop : especifica el margen superior que se desea establecer para el elemento. Se comporta igual que el atributo layout_width .
- android:layout_marginLeft : especifica el margen izquierdo que se desea establecer para el elemento. Se comporta igual que el atributo layout_width .
- android:Text : el texto que, por defecto, debe mostrar el elemento.
- android:textSize : el tamaño del texto.
- android:maxLength : el número máximo de caracteres.

Cada elemento de interfaz cuenta con su propia familia de atributos, dando así un alto grado de libertad al desarrollador para configurar sus widgets de la forma que considere más oportuna.
Además de elementos de interfaz, Android también contempla algunas vistas, también llamados diseños, que agrupan los distintos widgets y los ordenan siguiendo unos patrones frecuentemente utilizados en las aplicaciones. En ocasiones anteriores hemos nombrado Gallery , para los visores de fotografías, o ListView , utilizado aquí para mostrar un listado de contactos. Para ordenar adecuadamente los elementos de la interfaz de SMSWriter se usará el patrón llamado LinearLayout .
La clase LinearLayout permite colocar los elementos de una interfaz de forma consecutiva, ya sea imaginando para ellos unas columnas en la pantalla (orden horizontal) o unas filas (orden vertical). Este patrón se utiliza en dos ocasiones dentro del fichero “sms.xml”, donde estamos definiendo los widgets: en una ocasión para la totalidad de los elementos, siguiendo un orden vertical, y en otra ocasión para colocar los dos botones de envío y borrado siguiendo un orden horizontal. La declaración de este patrón es la mostrada en el Código 55, donde se indican sólo a través de su nombre los elementos completos de la interfaz:

< LinearLayout
android:id="@+id/linearlayout1"
android:orientation="vertical"
>
< TextView android:id="@+id/txt1" ... />
< EditText android:id="@+id/number" ... />
< TextView android:id="@+id/txt2" ... />
< EditText android:id="@+id/message" ... />
< LinearLayout
android:id="@+id/linearlayout1"
android:orientation="horizontal"
>
< Bu tton android:id="@+id/button1" ... />
< Bu tton android:id="@+id/button2" ... />
< /LinearLayout>
< /LinearLayout>

Código 55. Uso del diseño LinearLayout

Mostrar la interfaz desde la clase SMSWriter Una vez declarada completamente la interfaz de usuario en un documento XML dentro de la carpeta “\res\layout”, solamente es necesario referenciarla dentro del código fuente de SMSWriter . El método setContentView() de la clase Activity es la única llamada que es necesaria hacer para construir y mostrar completamente al usuario la interfaz. El elemento que la referencia, en el caso aquí descrito, es R.layout.sms .
Sin embargo, si se desea hacer un control adicional de algunos de los elementos como, por ejemplo, captar sus eventos asociados, es necesario instanciarlos individualmente para tener un objeto que poder controlar. Esta instanciación es tan simple como la anterior. El siguiente código muestra cómo se referencia la interfaz completa y se instancia alguno de sus elementos para poder controlarlos:

Aplicaciones Android
Código 56. Utilizar recursos XML para instanciar la interfaz de usuario

El método findViewById() , de la clase Activity , permite instanciar de forma inmediata cualquier elemento de interfaz declarado en la carpeta de recursos. Cada instancia se realiza haciendo un casting a la clase correspondiente y utilizando el identificador declarado en el XML. De esta forma, ya se puede trabajar con los objetos obtenidos.
Mediante getIntent().getExtras() se obtienen los datos asociados al Intent que ha lanzado la presente Activity; el único dato asociado aquí es el número de teléfono del contacto al que se va a enviar el SMS. Este número se muestra en la caja de texto correspondiente con setText() de la clase EditText , y además se configura para que no pueda ser modificado, mediante el método setFocusable() y el valor false .
El siguiente control de elementos que se desea hacer es establecer los comportamientos para los dos botones mostrados, el de enviar y el de borrar. Al pulsar el botón de enviar, la Activity debe finalizar y retornar a la clase ContactMap el texto introducido por el usuario. Por su parte, al pulsar el botón de borrar, la caja de texto debe vaciarse por completo.
Para configurar estos comportamientos se implementa el método onClick() de cada botón. Esta implementación se hace, tal y como muestra el Código 57, a través de un listener llamado setOnClickListener() .

Aplicaciones Android
Código 57. Comportamiento de los botones en la clase SMSWriter

Para devolver a ContactMap el texto que ha introducido el usuario, se añade este como datos asociados al Intent. Además, se invoca el método finish() para dar por terminada la presente actividad. En caso de pulsar el botón de borrar, el texto introducido hasta el momento se borra utilizando setText() y la cadena vacía.

Enviar el contenido del mensaje

Al termina la actividad SMSWriter , el control vuelve a ContactMap desde el método onActivityResult() . Desde esta clase, una vez recibido el texto que el usuario ha introducido, se envía el SMS de la forma que sigue:

Aplicaciones Android
Aplicaciones Android
Código 58. Enviar SMS

La clase SmsManager , presente en el paquete android.telephony.gsm , representa un gestor para los mensajes de texto. En particular, dispone del método sendTextMessage() para poder enviar mensaje de texto de tipo estándar. En este método, se especifican el número destino, el cuerpo del mensaje y el proveedor de servicios SMS, entre otros. Para indicar el número y mensaje, se obtienen del Intent los datos asociados. El proveedor de servicios SMS se especifica como null , lo cual significa que se utilizará el proveedor por defecto asociado a la tarjeta SIM presente en el dispositivo.
Para comunicar al usuario el resultado de tal operación, se utiliza la clase Dialog del paquete android.app . Mediante un objeto de esta clase, se advierte al usuario del éxito del envío o de algún error que haya tenido lugar durante el mismo. Como se puede comprobar, una vez más se utilizan los recursos textuales declarados en la carpeta “\res\values”, dentro del fichero “strings.xml”.
No hay que olvidar que para que la clase SMSWriter y el envío del SMS funcionen correctamente, es necesario hacer antes algunas declaraciones en el fichero del manifiesto. Por un lado, debe adjuntarse un permiso específico para los mensajes de texto:

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

Código 59. Declaración en el manifiesto del permiso para enviar SMS

Además, ya que la clase SMSWriter es una Activity, esta debe ser declarada en el mismo “AndroidManifest.xml” como componente básico de la aplicación que es. La declaración mostrada en el debe hacerse dentro del elemento .

< activity android:name=".SMSWriter">

Código 60. Declaración en el manifiesto de la Activity SMSWriter

4.2.18 Actualización periódica en el servidor

Anteriormente se ha explicado cómo la clase HTTPConnection se encarga de los aspectos de más bajo nivel del protocolo HTTP relacionados con las conexiones con el servidor; también se ha mencionado que las clases XMLExchange y MyXMLHandler se utilizan para procesar las peticiones enviadas y las respuestas recibidas en formato XML. Sin embargo, hasta ahora no se ha mencionado nada con respecto a cómo ContactMap conecta periódicamente con el servidor utilizando la clase Update .
La clase Update extiende la clase Service que, al igual que Activity, representa uno de los componentes básicos que pueden construir una aplicación Android. Un Service, tal y como se ha dicho en anteriores ocasiones, representa una tarea sin interfaz que se realiza en background sin conocimiento del usuario, y que puede formar parte del mismo proceso que la aplicación que lo lanza o de otro distinto.
Para actualizar los datos, la clase Update conecta de forma periódica con el servidor, utilizando la clase HTTPConnection y su método connect() , que se encarga de enviar al servidor la petición presente en su atributo mDataOut y escribir la respuesta en su otro atributo mDataIn (ver apartado 4.2.8). Update únicamente llama cada cierto tiempo a connect() , mientras que la clase ContactMap , también periódicamente, lee la respuesta recibida y escribe la petición que se ha de enviar en la próxima conexión que haga Update .
En la Figura 32 se muestra un diagrama de secuencia utilizando el estándar UML 2.0 [35] donde se exponen los pasos seguidos para realizar una conexión con el servidor. Nótese que la clase ContactMap utiliza las clases XMLExchange y MyXMLHandler para construir la petición y leer la respuesta, pero para simplificar dicho diagrama se han omitido esos pasos.

Aplicaciones Android
Figura 32. Diagrama de secuencia para las conexiones con el servidor

Lanzar el servicio es tan sencillo como llamar al método startService() de la clase Activity . Este hecho se produce en el método onCreate() de la clase ContactMap , justo cuando se ha comprobado que existe una conexión Wi-Fi disponible. El Código 61 muestra cómo se lanza el Service Update .

public class ContactMap extends MapActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
// (...)
// Wi-Fi disponible
if (wifiEnabled==true){
// Crear un Intent asociado a la clase Update
Intent UpdateService = new Intent(this, Update.class);
// Lanzar Service
this.startService(UpdateService);
// Wi-Fi no disponible
}else{
// Lanzar aviso
// Obtener localizaciones desde SQLite
}
// (...)
}
}

Código 61. Lanzar el Service Update

Al igual que ocurre cuando se lanza una nueva Activity, para lanzar un Service es necesario utiliza un objeto de la clase Intent . Este objeto se crea vinculado a la propia clase Update , que es el componente Service que se desea comenzar. Si existe Wi-Fi disponible, se lanza el servicio. Si no, se comunica tal situación al usuario y se actualizan los contactos utilizando la información guardada en la base de datos SQLite del propio dispositivo móvil (consultar apartados 4.2.7 y 4.2.12).
Generalmente, los elementos Service empleados en una aplicación Android repiten de forma cíclica una determinada tarea que debe ser simultánea a las otras actividades llevadas a cabo por la aplicación. Por ello, casi siempre un Service implementa un hilo o thread donde desarrolla alguna acción. Update lanza un hilo en su método onCreate() , tal y como se ve en el Código 62, que conecta con el servidor mediante HTTPConnection y se duerme un cierto tiempo mediante un sleep() .

public class Update extends Service{
// dirección del servidor
private String mServer = getResources().getString(R.string.server);
// temporizador de conexión
private int mTime = getResources().getInteger(R.integer.time_update);
// connexion HTTP
private HTTPConnection mHTTPConnection = new HTTPConnection(mServer);
// hilo que conectará periódicamente
public Thread mThread = new Thread(mTask);
@Override
public void onCreate() {
super.onCreate();
mThread.start();
}
Runnable mTask = new Runnable() {
public void run(){
while (true){
// Conectar con el servidor
mHTTPConnection.connect();
// Dormir el hilo 10 segundos
Thread.sleep(mTime);
}
}
};
}

Código 62. Conexión periódica mediante la clase Update

La clase Update cuenta además con atributo de nombre mService que contiene el nombre del servidor donde debe conectar ContactMap. Este es el único lugar donde se hace referencia a tal dirección, ya que en la construcción del objeto HTTPConnection se pasa como parámetro dicho valor y es el que utiliza el método connect() para lanzar su conexión HTTP. Para conocer la dirección del servidor, se utiliza el recurso de texto definido en el archivo “\res\values\strings.xml” de nombre “server”. Mediante los métodos getResources().getString() , es posible obtener directamente una cadena de texto del recurso especificado.
Por otro lado, el atributo de mTime representa el intervalo de tiempo que debe esperar Update entre cada conexión. Este valor, representado en milisegundos, se obtiene también de los recursos externos, pero en este caso del fichero “\res\values\integers.xml”, donde hay un elemento declarado con el nombre “update_timer”.

4.2.19 Utilización de interfaces remotas mediante AIDL

En el apartado anterior se ha explicado como ContactMap actualiza de forma periódica su información con la del servidor, utilizando para ello un componente Service encarnado en la clase Update . Esta clase simplemente lanza el método connect() de HTTPConnection , que recoge en sus atributos tanto la petición que se ha de enviar como la respuesta que se recibe. La clase ContactMap es la que debe escribir y leer esos datos antes y después de la conexión, respectivamente.
Para actuar de forma coordinada, Update debe comunicar a la clase ContactMap cuándo se va a conectar, para que así pueda preparar la petición que se ha de enviar. Así mismo, Update también debe anunciar a ContactMap que la conexión ya se ha producido y que existe disponible una respuesta del servidor que puede ser leída y procesada.
Estas comunicaciones han sido omitidas en el apartado 4.2.18 para centrar la atención del lector en el funcionamiento de un elemento Service, pero existen. Android permite cierto grado de comunicación entre una Activity y un Service lanzado por esta. Sin embargo, la comunicación en el otro sentido, tal y como se desea aquí, es algo más complejo y requiere el uso de interfaces remotas.
Una interfaz remota representa un mecanismo mediante el cual dos procesos separados pueden utilizar métodos declarados en el otro proceso. Cada uno de los extremos desconoce la implementación del otro, simplemente conoce a través de una interfaz qué métodos se ofrecen, pudiendo utilizarlos como si de cualquier otra clase local se tratase. Algunas tecnologías de interfaces remotas más utilizados son RMI de Java o CORBA. Android también cuenta con su propio mecanismo de interfaces remotas.
Antes de usar este tipo de comunicaciones en Android, es necesario realizar los siguientes pasos:

1. Declarar la interfaz remota mediante el lenguaje AIDL (Android Interface Definition Language). La declaración se realiza con una sintaxis muy básica e incluye los nombres de los métodos, los parámetros que éstos aceptan y los posibles valores devueltos. Con esta declaración se genera un resguardo o stub para ser utilizado por el otro extremo.
2. Implementar dicha interfaz.
3. Obtener el objeto remoto que permita el acceso a los métodos declarados.

Declaración de las interfaces con AIDL y obtención de los stubs

El lenguaje AIDL de Android permite poder declarar interfaces remotas, generando además un resguardo para que el extremo que las va utilizar pueda conocer su naturaleza, aunque ignore su implementación. AIDL consiste en una simplísima sintaxis que se limita a anunciar métodos y tipos de datos básicos, además de tipos de cualquier clase perteneciente al paquete actual.
La clase Update necesita comunicar a ContactMap tanto el momento previo a la conexión con el servidor como el momento inmediatamente posterior, para que dicha clase puede escribir y leer los correspondientes documentos XML. Es decir, ContactMap necesita ofrecer dos métodos remotos a los que Update pueda acceder:

- notifyPreConnection() : donde se escribirá la petición.
- notifyPostConnection() : donde se leerá la respuesta.

Para que la clase Update pueda invocar estos métodos implementados en ContactMap , ha de tener un objeto que represente la interfaz remota de ContactMap . Se concluye, por tanto, que Update también ha de ofrecer otros métodos remotos que permitan a ContactMap pasarle un instancia de su interfaz remota con la que pueda invocar a sus métodos, es decir, hacer lo que se denomina callback. Update deberá implementar, pues, los siguientes métodos remotos:

- register() : guarda un objeto de la interfaz remota.
- unRegister() : elimina un objeto de la interfaz remota.

La pregunta que puede surgir ahora es que si Update necesita tener una instancia de la interfaz remota de ContactMap y registrarla para acceder a ella, ¿por qué a la inversa no se realiza el mismo proceso?; es decir, por qué ContactMap no necesita tener una instancia de la interfaz remota de Update y, por tanto, registrarla igualmente. La respuesta es que ContactMap sí necesita, como es lógico, una instancia de la interfaz remota de Update a la que pretende acceder, pero no necesita registrarla porque la obtiene automáticamente gracias al método bindService() . Esto se detalla más adelante. Como se ha dicho, es más simple comunicar una Activity con su Service que a la inversa.
Una vez conocidas las interfaces que se necesitan, el primer paso es declararlas formalmente a través de AIDL. El siguiente código muestra la declaración de la interfaz remota de ContactMap , llamada IRemoteCallback . En ella, el método notifyPreConnection() devuelve un valor string, que consistirá en la petición XML que se ha de enviar al servidor. Por su parte, el método notifyPostConnection() recibe como parámetro otro tipo string que representa la respuesta XML recibida desde el servidor.

package com.android.contactmap;
interface IRemoteCallback {
String notifyPreConnection();
void notifyPostConnection(String data);
}

Código 63. Interfaz remota IRemoteCallback

A continuación se muestra la interfaz remota implementada en la clase Update , que recibe el nombre de IRemoteRegister . Los métodos register() y unRegister() únicamente guardan y eliminan, respectivamente, una instancia de la interfaz remota IRemoteCallback .

package com.android.contactmap;
import com.android.contactmap.IRemoteCallback;
interface IRemoteRegister {
void register(IRemoteCallback regService);
void unRegister(IRemoteCallback regService);
}

Código 64. Interfaz remota IRemoteRegister

Estas declaraciones deben ser guardadas en ficheros con el mismo nombre que el de la interfaz y con la extensión “.aidl”. Si se está trabajando con el plug-in de Eclipse, el simple hecho de colocarlas en el mismo proyecto donde van a ser utilizadas hará que se generen de forma automática dos ficheros Java, uno por cada interfaz, que representan el resguardo o stub que el otro extremo debe conocer para poder utilizarlas. En este caso, como ambos extremos ContactMap y Update están en el mismo proyecto, no hace falta trasladar ninguno de los resguardos.

Implementación de las interfaces

Tras haber declarado las interfaces con el lenguaje AIDL, el siguiente paso es implementar los métodos declarados en ellas. La interfaz IRemoteCallback se corresponde con las llamadas que hará la clase Update a la clase ContactMap para avisarle tanto de que va a conectar con el servidor y necesita una petición, como que ha terminado dicha conexión y ya dispone de una respuesta.
Para implementar la interfaz IRemoteCallback en la clase ContactMap se declara un nuevo atributo de este tipo y se construyen los dos métodos anunciados. En el Código 65 se expone la implementación realizada.

public class ContactMap extends MapActivity {
// Lista de contactos
public ArrayList mFriendList = new ArrayList();
// Controlador del localizador GPS
private LocationManager mLocation = null;
// Gestor de intercambios con XML
private XMLExchange mXmlExchange = new XMLExchange();
private IRemoteCallback.Stub mCallback = new IRemoteCallback.Stub() {
public String notifyPreConnection() {
// Devolver petición XML
return mXmlExchange.writeXMLUpdate(
getContentResolver(),
(TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE),
mLocation.getLastKnownLocation(LocationManager.GPS_PROVIDER));
}
public void notifyPostConnection(String data) {
// Procesar respuesta XML
mXmlExchange.readXMLUpdate(
data, mFriendList, getContentResolver());
}
};
}

Código 65. Implementación de la interfaz IRemoteCallback

Tal y como se ve en el Código 65, el método notifyPreConnection() utiliza la clase XMLExchange para construir un documento XML que represente una petición válida, donde se informe al servidor de la posición del usuario y se solicite las posiciones de los contactos. Por el contrario, el método notifyPostConnection() usa también dicha clase pero para leer la respuesta XML recibida del servidor, donde se encontrará la información de localización de los contactos.
Seguidamente, se muestra la implementación de la interfaz IRemoteRegister en la clase Update .

public class Update extends Service{
// Lista con interfaces remotas
private RemoteCallbackList mCallbacks =
new RemoteCallbackList();
private IRemoteRegister.Stub mRegister = new
IRemoteRegister.Stub(){
public void register(IRemoteCallback interface) {
// Guardar interfaz remota
if (interface!= null) mCallbacks.register(interface);
}
public void unRegister(IRemoteCallback interface) {
// Eliminar interfaz remota
if (interface!= null) mCallbacks.unregister(interface);
}
};
}

Código 66. Implementación de la interfaz IRemoteRegister

Antes de implementar la interfaz, se declara una instancia de la clase RemoteCallbackList , perteneciente al paquete android.os . Esta clase permite manejar más fácilmente un conjunto de interfaces remotas especialmente pensadas para hacer callback desde un Service. En el caso aquí explicado, a priori solamente habrá un único elemento en esta lista: la instancia de la interfaz IRemoteCallback .
Los métodos remotos register() y unRegister() simplemente añaden o eliminan una interfaz remota a la lista. De esta forma, una vez está incluida en dicha lista, se puede acceder sin mayor complicación a los métodos remotos ofrecidos por esa interfaz.

Utilización de las interfaces

Llegado este punto, las interfaces remotas ya han sido declaradas mediante AIDL, se ha generado automáticamente su stub o resguardo y además se han implementado los métodos que cada interfaz ofrece: la interfaz IRemoteCallback está implementada dentro de la clase ContactMap , y la interfaz IRemoteRegister lo está en la clase Update . Ahora es necesario proporcionar a cada clase una instancia de la interfaz remota que necesita, esto es, ContactMap precisa de una instancia de IRemoteRegister y Update requiere una instancia de IRemoteCallback .
Para ofrecer a ContactMap una instancia de IRemoteRegister es necesario utilizar el método bindService() de la clase Context . . La clase Service tiene también un ciclo de vida bien definido, es decir, cuenta con métodos como onCreate() , onStart() , onDestroy() , etc. Cuando se lanza un elemento Service, por defecto este es independiente al ciclo de vida de la Activity que lo ha lanzado, lo que implica que el Service seguirá ejecutándose aunque la Activity que lo ha lanzado muera, y solamente finalizará cuando la Activity lo haga de forma explícita.
En esta situación no se desea que el Service sea independiente. Por un lado, interesa vincular el ciclo de vida de la clase ContactMap y la clase Update para que cuando la aplicación ContactMap finalice, la conexión periódica con el servidor también lo haga; por otra, la utilización de bindService() va a proporcionar a la clase ContactMap una instancia de la interfaz remota que necesita, IRemoteRegister .
En el siguiente código, el Código 67, se muestra al lector cómo se utiliza bindService() para poder lanzar correctamente el servicio Update de la forma que aquí interesa.

public class ContactMap extends MapActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
// (...)
// Wi-Fi disponible
if (wifiEnabled==true){
// Vincular el Service y la Activity actual
bindService(new Intent(IRemoteRegister.class.getName()),
mConnection, Context.BIND_AUTO_CREATE);
// Crear un Intent asociado a la clase Update
Intent UpdateService = new Intent(this, Update.class);
// Lanzar Service
this.startService(UpdateService);
// Wi-Fi no disponible
}else{
// Lanzar aviso
// Obtener localizaciones desde SQLite
}
// (...)
}
}

Código 67. Lanzar el Service Update usando interfaces remotas

Este código es íntimamente similar al mostrado en el Código 61, salvo que antes de lanzar el servicio con startService() se utiliza el método bindServicer() , vinculando así la clase ContactMap y la clase Update . Se pueden apreciar tres parámetros en este método:

- Un objeto de la clase Intent .
- Un objeto de la clase ServiceConnection , llamado aquí mConnection .
- Un flag de valor Context.BIND_AUTO_CREATE para la creación del elemento Service.

Con respecto al primer parámetro de bindServicer() , el lector ya sabrá que un Intent siempre representa una acción que se desea realizar. Al crear un Intent, o bien se especifica qué acción se desea llevar a cabo utilizando alguna de las constantes que ofrece Android (ver los apartados 4.2.15 ó 4.2.16) o bien se especifica directamente la clase que se quiere lanzar (repásense los casos descritos en los apartados 4.2.14 ó 4.2.17). Aquí se opta por el segundo caso. Lo que se está diciendo a través del Intent es que se desea ejecutar la interfaz IRemoteRegister . Ya que la clase IRemoteRegister presente en el proyecto no es una clase ejecutable, sino un simple stub o resguardo, debe existir algún otro elemento que indique explícitamente que puede hacerse cargo de Intents de ese tipo. Dicho esto, se comprederá ahora la necesidad de que en el fichero “AndroidManifest.xml”, el Service ya declarado Update anuncie ahora que puede atender este tipo de Intents:

< service android:name=".Update">
< intent-filter>
< action android:name="com.android.contactmap.IRemoteRegister" />
< /intent-filter>
< /service>

Código 68. Declaración en el manifiesto de los Intents para el Service Update

Con la etiqueta < intent-filter> cualquier componente de una aplicación Android puede anunciar que es capaz de hacerse cargo de unos determinados Intents. De hecho, esta etiqueta es consultada por Android una por una en las aplicaciones instaladas cuando alguien lanza un Intent, a fin de encontrar a la aplicación más adecuada para hacerse cargo. Así pues, cuando alguien lance un Intent asociado a IRemoteRegister , la clase Update se hará cargo de ello.
El segundo parámetro de bindService() es un objeto de la clase ServiceConnection . Esta clase pertenece al paquete android.content y es en realidad una interfaz que debe ser implementada por el desarrollador. Ya que bindService() permite vincular al Service y a la Activity, debe existir un objeto que monitorice cuál es el estado del Service. Este es el cometido del objeto ServiceConnection . En concreto, ofrece dos métodos: onServiceConnected() y onServiceDesconnected() . Como se podrá intuir, el primero es llamado cuando el Service y la Activity se conectan, mientras que el segundo lo hace cuando se desconectan.
La llamada a bindService() invoca en el lado del componente Service un método denominado onBind() . La finalidad de este método está enfocada casi en exclusiva a la utilización con interfaces remotas, ya que su objetivo es devolver a la Activity un objeto mediante el cual pueda comunicarse con el Service. Este objeto ha de ser una instancia de la interfaz IRemoteRegister , tal y como se ve en el siguiente código.

public class Update extends Service{
private IRemoteRegister.Stub mRegister = new
IRemoteRegister.Stub(){
// Aquí se implementan los métodos register() y unRegister()
}
@Override
public IBinder onBind(Intent intent) {
return mRegister;
}
}

Código 69. Método onBind() de la clase Update

Así pues, la clase ContactMap , tras invocar al método bindService() y este al método onBind() , obtiene una instancia de la interfaz IRemoteRegister . Este objeto se recibe a través del método onServiceConnected() , que forma parte de la interfaz de ServiceConnection y debe ser implementado por el desarrollador. La finalidad de tener una instacia de IRemoteRegister , como se recordará, es permitir a la clase ContactMap registrar en la clase Update una instancia de IRemoteCallback , de forma que el círculo de comunicación ya quede cerrado.

public class ContactMap extends MapActivity {
// Interfaz remota de la clase Update
private IRemoteRegister mService = null;
// Interfaz remota de la clase ContactMap
private IRemoteCallback.Stub mCallback = new IRemoteCallback.Stub() {
// Aquí se implementan los método notifyPreConnection() y
// notifyPostConnection()
};
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
IBinder service) {
// Obtener instacia gracias al stub
mService = IRemoteRegister.Stub.asInterface(service);
// Hacer llamada remota: registrar instancia de IRemoteCallback
mService.register(mCallback);
}
public void onServiceDisconnected(ComponentName className) {
mService = null;
}
};
}

Código 70. Implementación de la interfaz ServiceConnection

Cuando se llama a bindService() , los métodos onBind() y onServiceConnected() se invocan. El primero devuelve una instancia de la interfaz IRemoteRegister y el segundo permite recogerla y transformarla correctamente. Esta transformación se realiza gracias al stub o resguardo de IRemoteRegister . Desde el momento que ya se tiene una instacia correcta de IRemoteRegister , encarnada en el atributo mService , ya se pueden utilizar los métodos remotos ofrecidos. Por ello, ContactMap registra de inmediato una instancia de su interfaz remota IRemoteCallback , permitiendo desde ese instante que la clase Update pueda comunicarse con ContactMap .
La clase Update tiene que llamar al método notifyPreConnection() justo antes de realizar una conexión, para que ContactMap escriba la petición a enviar, e invocar a notifyPosConnection() justo depués para que ContactMap lea la respuesta. Todo este proceso se realiza en el hilo que la clase Update crea para realizar las conexiones de forma periódica. Véase el Código 71.

public class Update extends Service{
// connexion HTTP
private HTTPConnection mHTTPConnection = new HTTPConnection(mServer);
// hilo que conectará periódicamente
public Thread mThread = new Thread(mTask);
Runnable mTask = new Runnable() {
public void run()
{
// Mientras dure el Service
while (true){
// Activar mCallbacks
mCallbacks.beginBroadcast();
// Notificar a la clase ContactMap
mHTTPConnection.setDataOut(
mCallbacks.getBroadcastItem(0).notifyPreConnection());
// Conectar con el servidor
mHTTPConnection.connect();
// Notificar a la clase ContactMap
mCallbacks.getBroadcastItem(0).notifyPostConnection(
mHTTPConnection.getDataIn());
// Desactivar mCallbacks
mCallbacks.finishBroadcast();
// Dormir el hilo
Thread.sleep(mTime);
}
}
};
}

Código 71. Notificaciones desde la clase Update a la clase ContactMap

Este código es muy similar al mostrado en el Código 62, exceptuando que aquí se incluyen las notificaciones enviadas a ContactMap antes y después de la conexión con el servidor. La clase RemoteCallbackList , que representa una lista con los objetos remotos disponibles para hacer callback, requiere ser activado antes de las llamadas remotas y desactivado después. Este requisito se cumple con los métodos beginBroadcast() y finishBroadcast() , respectivamente. Las notificaciones se envían justo antes de la conexión, obteniendo de ella la petición a enviar, así como después de la misma, donde se envía por parámetro la respuesta recibida.

4.2.20 Manifiesto final

En este apartado se ofrece al lector el aspecto final del fichero “AndroidManifest.xml” utilizado en la aplicación ContactMap.

1. < ?xml version="1.0" encoding="utf-8"?>
2. < manifest xmlns:android="http://schemas.android.com/apk/res/android"
3. package="com.android.contactmap"
4. android:versionCode="1"
5. android:versionName="1.0.0">
6.
7. < uses-permission android:name="android.permission.READ_PHONE_STATE" />
8. < uses-permission android:name="android.permission.INTERNET" />
9. < uses-permission android:name="android.permission.READ_CONTACTS" />
10. < uses-permission android:name="android.permission.CALL_PHONE" />
11. < uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
12. < uses-permission android:name="android.permission.SEND_SMS" />
13. < uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
14.
15.
16.
17.
18. < application android:icon="@drawable/icon" android:label="@string/app_name">
19.
20. < uses-library android:name="com.google.android.maps" />
21.
22. < activity android:name=".ContactMap" android:label="@string/app_name">
23. < intent-filter>
24. < action android:name="android.intent.action.MAIN" />
25. < category android:name="android.intent.category.LAUNCHER" />
26. < /intent-filter>
27. < /activity>
28.
29. < activity android:name=".FriendListViewer">
30.
31. < activity android:name=".SMSWriter">
32.
33. < service android:name=".Update">
34. < intent-filter>
35. < action android:name="com.android.contactmap.IRemoteRegister" />
36. < /intent-filter>
37. < /service>
38.
39. < /application>
40.
41. < /manifest>

Código 72. Manifiesto final de ContactMap

Las principales declaraciones de este manifiesto son las siguientes:

- Líneas 7-13: permisos para la aplicación. Por orden de aparición, están los permisos para acceder a la información del dispositivo móvil, acceder a Internet, leer los contactos del dispositivo, realizar llamadas, control del elemento de localización, enviar SMS y control de la Wi-Fi.
- Líneas 18-39: declaración de los componentes que forman la aplicación.
- Línea 20: uso de la librería de Google Maps.
- Líneas 22-27: declaración de la Activity principal, la clase ContactMap .
- Línea 24: la clase ContactMap es la clase principal.
- Línea 25: la clase ContactMap debe ejecutarse nada más ejecutar la aplicación.
- Línea 29: declaración de la Activity FriendListViewer .
- Línea 31: declaración de la Activity SMSWriter .
- Líneas 33-37: declaración del Service Update .
- Línea 35: interfaz remota ofrecida por el Service Update .