Servicios de la plataforma Android

Tamaño: px
Comenzar la demostración a partir de la página:

Download "Servicios de la plataforma Android"

Transcripción

1 Índice Librerías de compatibilidad y servicios Compatibilidad de la aplicación Fragmentos Loaders Librerías de compatibilidad Librerías de servicios Ejercicios de fragmentos y compatibilidad Lector de noticias (1,5 puntos) Carga de noticias (1 punto) Servicios de Google (0,5 puntos)...20 Agenda y calendario Agenda de contactos Calendario Ejercicios de agenda y calendario Acceso a la agenda de contactos (1 punto) Agregar contactos a la agenda (1,5 puntos) Creación de eventos del calendario (0,5 puntos) Servicios Servicios propios Broadcast receiver PendingIntents y servicios del sistema Comunicación entre procesos Ejercicios - Servicios Contador: Servicio con proceso en background (0.6 puntos) Broadcast Receiver: Captura de llamadas (0.6 puntos) Broadcast Receiver: Reenvío de datos (0.6 puntos)... 51

2 Arranque: Iniciar servicio al arrancar el móvil (0.6 puntos) Calculadora: Comunicación con el servicio (0.6 puntos) AppWidgets AppWidgets Crear un Widget Actualización del Widget Eventos de actualización Servicio de actualización Actividad de configuración...63 Ejercicios - AppWidgets IP AppWidget (1.5 puntos) StackWidget (1.5 puntos)...67 Notificaciones Notificaciones Toast Notificaciones de la Barra de Estado Cuadros de Diálogo Ejercicios - Notificaciones Notificaciones con Toast (1 punto) Servicio con notificaciones: Números primos (1 punto) Notificaciones mediante diálogos (1 punto) Depuración y pruebas Depuración con Eclipse Pruebas unitarias con JUnit para Android Pruebas de regresión con Robotium Pruebas de estrés con Monkey Ejercicios - Depuración y pruebas Caso de prueba con JUnit para Android (3 puntos)

3 1. Librerías de compatibilidad y servicios Vamos a ver algunas librerías adicionales que podemos añadir a nuestras aplicaciones, para conseguir compatibilidad con dispositivos antiguos o para acceder a servicios externos. Por ejemplo, una característica importante aparecida en versiones recientes de Android son los fragmentos. Es recomendable construir nuestras aplicaciones utilizando estos elementos para la interfaz, pero si lo hacemos nuestra aplicación ya no sería compatible con dispositivos Android 2.2 y 2.3, que son una parte importante del mercado actual. Vamos a ver cómo podemos resolver esto incluyendo librerías adicionales de compatibilidad. También veremos cómo incluir librerías adicionales que nos den acceso a los servicios de Google Play Compatibilidad de la aplicación A la hora de desarrollar una aplicación es importante decidir a qué versiones de la plataforma Android está destinada. Las últimas versiones nos dan muchas más facilidades para crear las aplicaciones, pero es importante dar soporte a versiones antiguas para abarcar a un mayor número de usuarios. Se recomienda que nuestras aplicaciones soporten al menos el 90% de los dispositivos que hay actualmente en uso. En el momento de la escritura de este texto, esto implicaría dar soporte al menos a partir de Android 2.2. Para especificar el rango de versiones a las que destinamos nuestra aplicación se utilizan los atributos minsdkversion y targetsdkversion de la etiqueta uses-sdk del AndroidManifest.xml: <uses-sdk android:minsdkversion="4" android:targetsdkversion="17" /> El atributo minsdkversion indica la versión mínima de Android necesaria para poder utilizar nuestra aplicación. Por debajo de dicha versión nuestra aplicación no funcionará. Sin embargo, este atributo no nos condiciona a utilizar sólo las características compatibles con la versión mínima. Podemos utilizar características de versiones mayores. La versión para la cual hemos diseñado la aplicación se indica con targetsdkversion. En el código podremos utilizar cualquier característica que soporte dicha versión. Pero, qué ocurre si utilizamos una característica de targetsdkversion no soportada en minsdkversion? En ese caso, cuando probemos la aplicación en un dispositivo con la versión mínima fallará. Por lo tanto, es importante que antes de utilizar dichas características comprobemos la versión de Android en la que se está ejecutando: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { // Utilizar características de Android 3.0 (Honeycomb) Nota En el caso de las propiedades de los ficheros XML, si utilizamos atributos que no estaban definidos en la versión mínima, cuando ejecutemos la aplicación en dicha versión simplemente 3

4 serán ignorados. Deberemos llevar cuidado de hacer siempre esta comprobación cuando estemos utilizando características no presentes en la versión mínima. Para asegurar el correcto funcionamiento deberemos probar la aplicación de forma exhaustiva con dispositivos que tengan tanto la versión mínima como la versión para la cual estamos desarrollando. Sin embargo, existen algunas características, como por ejemplo los fragmentos, que son fundamentales en la construcción de la aplicación, y no se pueden ignorar mediante la comprobación anterior. Para estas características veremos a continuación cómo utilizar una librería de compatibilidad que añada soporte para versiones previas de Android Fragmentos A partir de Android 3.0 aparece una característica importante a la hora de construir la interfaz: los fragmentos de actividades. Estos fragmentos nos permiten construir la interfaz de forma modular. Un fragmento se define como un panel, y una actividad puede estar compuesta por varios fragmentos. De esta forma podremos reutilizar los fragmentos en diferentes actividades, y podremos construir actividades complejas de forma sencilla. Una ventaja importante es que nos permiten adaptar de forma sencilla la interfaz a distintos tipos de pantallas, como tabletas, ya que distintos fragmentos que en un móvil están en distintas pantallas, en una tableta podrían incluirse en una única pantalla, reutilizando su código. Fragmentos Creación de fragmentos Los fragmentos se crean como una subclase de Fragment, casi de la misma forma en la que se define una actividad. Se utilizan los mismos métodos para controlar el ciclo de vida, y debemos definir un método adicional oncreateview que especifica la forma en la que se debe generar la interfaz del fragmento. La interfaz se generará normalmente a partir de un layout XML: 4

5 public class DetalleFragment extends Fragment { public View oncreateview(layoutinflater inflater, ViewGroup container, Bundle savedinstancestate) { return inflater.inflate(r.layout.fragmento, container, false); Además de este método anterior, tenemos disponibles todos los métodos que teníamos en el ciclo de vida de las actividades (oncreate, onstart, onresume, etc). Pero debemos tener en cuenta que un fragmento no es una actividad, por lo que por ejemplo no heredamos métodos como findviewbyid, y tampoco podemos utilizarlo en los métodos que nos piden pasar como parámetro el contexto (Context), donde normalmente siempre indicabamos nuestra actividad. Para poder hacer todo esto deberemos acceder a la actividad en la que está contenido nuestro fragmento. Esto lo haremos con el método getactivity(): Button boton = (Button)getActivity().findViewById(R.id.boton); Al igual que ocurría con las actividades, tenemos diferentes subclases de Fragment para definir tipos específicos de fragmentos, como ListFragment, DialogFragment, o PreferencesFragment. Por ejemplo, si queremos definir un fragmento de tipo lista, podemos heredar directamente de ListFragment: public class PrincipalFragment extends ListFragment {... Por otro lado, DialogFragment nos permitirá crear diálogos. En este caso el contenido del fragmento se mostrará dentro de la ventana del diálogo Ciclo de vida de los fragmentos Los fragmentos siempre estarán contenidos dentro de una actividad, por lo que su ciclo de vida quedará vinculado al estado de la actividad contenedora. En la siguiente figura mostramos los distintos eventos del ciclo de vida de los fragmentos, y la vinculación con el estado de la actividad a la que pertenecen: 5

6 Ciclo de vida En la anterior figura podemos observar que durante el estado de creación de la actividad, en el fragmento se producen diferentes eventos. En primer lugar el fragmento se vincula a la actividad (onattach). Tras esto se crea el fragmento (oncreate), pero debemos tener en cuenta que en este punto todavía no contamos con su interfaz, por lo que no podemos inicializarla. Este método será útil por ejemplo para inicializar el adapter que pueble de datos la lista de un ListFragment, en el que no es necesario crear la interfaz nosotros. Tras este evento, tenemos oncreateview, que es donde deberemos crear la interfaz como hemos visto anteriormente (excepto si estamos en un ListFragment, caso en el que la interfaz ya viene predefinida). Por último, se ejecutará onactivitycreated una vez haya terminado de ejecutarse el método oncreate de la actividad contenedora. Los eventos onstart, onresume, onpause y onstop están directamente vinculados con sus equivalentes en la actividad contenedora. La destrucción será similar a la inicialización pero en orden inverso. En primer lugar se elimina la vista (ondestroyview). Esto puede hacerse para liberar recursos, pero el 6

7 fragmento podría volver a utilizarse posteriormente. Si esto ocurriese, se volvería a llamar a oncreateview para volver a construir la vista. Si el fragmento ya no se va a utilizar más, se llamará a ondestroy, y tras esto a ondetach en el momento en el que se desvincula de la actividad Añadir el fragmento a una actividad Una vez definido el fragmento, podemos añadirlo a una o varias actividades. Existen dos formas de añadir los fragmentos: Estática: Se añaden directamente en el layout XML de la actividad. Si se hace de esta forma no podremos modificar el conjunto de fragmentos que se muestran en la actividad en tiempo de ejecución (la actividad siempre mostrará los mismos fragmentos dispuestos de la misma forma). Dinámica: Los fragmentos se añaden desde código. De esta forma podremos cambiar en cualquier momento el fragmento que se está mostrando en la actividad. Utilizaremos esta forma cuando queramos poder hacer transiciones entre fragmentos. La definición de fragmentos mediante el método estático se realizaría de la siguiente forma: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="fill_parent"> <fragment android:name="es.ua.jtech.fragments.principalfragment" android:layout_weight="1" android:layout_width="0dp" android:layout_height="match_parent" /> <fragment android:name="es.ua.jtech.fragments.detallefragment" android:layout_weight="2" android:layout_width="0dp" android:layout_height="match_parent" /> </LinearLayout> Si queremos dar soporte a varios tamaños de dispositivos, simplemente deberíamos definir diferentes layouts alternativos con distintos clasificadores (large, xlarge, etc). Pero, qué ocurre si queremos dar soporte a dispositivos más pequeños en los que los dos fragmentos no caben en la misma pantalla? En este caso podemos utilizar la método dinámico, para mostrar un único fragmento en pantalla y poder cambiar a otro de forma dinámica. Para hacer esto deberemos definir un layout alternativo para la actividad (por ejemplo con clasificador small o normal) en el que definamos un marco vacío donde podamos poner el fragmento que queramos directamente (FrameLayout): <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" 7

8 android:layout_height="match_parent" /> En nuestra actividad, en primer lugar deberemos comprobar si el layout cargado es el estático o el dinámico: public class MainActivity extends Activity { public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.news_articles); // Comprueba si estamos usando el layout dinámico if (findviewbyid(r.id.fragment_container)!= null) { // Si se está restaurando, no hace falta cargar el fragmento if (savedinstancestate!= null) { return; // Creamos el fragmento PrincipalFragment ppalfragment = new PrincipalFragment(); // Pasamos los extras del intent al fragmento ppalfragment.setarguments(getintent().getextras()); // Añadimos el fragmento al contenedor getfragmentmanager().begintransaction().add(r.id.fragment_container, ppalfragment).commit(); Como podemos observar, para modificar los fragmentos en pantalla debemos obtener un objeto FragmentManager y a partir de él abrir una transacción (FragmentTransaction). Una vez se haya hecho la operación oportuna (en este caso add), confirmaremos la transacción (commit) Transiciones entre fragmentos En el caso en el que hayamos añadido el fragmento de forma dinámica, podremos hacer una transición a otro fragmento utilizando el objeto FragmentTransaction. DetalleFragment detallefragment = new DetalleFragment(); // Pasamos parametros al nuevo fragmento Bundle args = new Bundle(); args.putint(param_posicion, posicionseleccionada); detallefragment.setarguments(args); FragmentTransaction transaction = getfragmentmanager().begintransaction(); transaction.replace(r.id.fragment_container, detallefragment); transaction.addtobackstack(null); transaction.commit(); En este caso vemos que tras reemplazar el fragmento, se añade la operación a la back stack, para así poder volver al fragmento anterior pulsando la tecla atrás. 8

9 Comunicación entre fragmentos Cuando hacemos una transición entre fragmentos, si un fragmento tiene que pasar datos a otro lo puede hacer en el momento de la transición, como hemos visto en el caso anterior. Pero si todos los fragmentos se muestran de forma estática, necesitaremos poder comunicarlos para que por ejemplo, cuando pulsemos un item de un fragmento lista, en otro fragmento veamos los detalles de dicho item. La comunicación entre fragmentos siempre lo haremos a través de la actividad a la que pertenecen. En uno de los fragmentos definiremos una interfaz que deba implementar la actividad: public class PrincipalFragment extends ListFragment { OnItemSelectedListener mcallback; // La actividad debe implementar esta interfaz public interface OnItemSelectedListener { public void onitemselected(int position); public void onattach(activity activity) { super.onattach(activity); // Comprueba que la actividad implemente la interfaz definida try { mcallback = (OnItemSelectedListener) activity; catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " debe implementar OnItemSelectedListener");... El método onattach se ejecutará cuando el fragmento se vincule a la actividad. Aquí nos guardamos una referencia a la actividad mediante un campo del tipo del listener recibido, con el que podremos notificar a la actividad cada vez que se haya seleccionado un item. En el fragmento anterior podemos utilizar esta referencia a la actividad para hacer un callback en el momento en el que se pulse sobre un item de la lista: public class PrincipalFragment extends ListFragment {... public void onlistitemclick(listview l, View v, int position, long id) { mcallback.onitemselected(position); En la actividad deberemos implementar el listener definido, y en él deberemos pasar el mensaje al fragmento con los detalles. Esto se hará de forma distinta según si utilizamos el método estático o dinámico. 9

10 public static class MainActivity extends Activity implements PrincipalFragment.OnItemSelectedListener {... public void onitemselected(int position) { DetalleFragment detallefragment = (DetalleFragment) getfragmentmanager().findfragmentbyid(r.id.detalle_fragment); if (detallefragment!= null) { // Tipo estático: actualizamos directamente el fragmento detallefragment.setdetalleitem(position); else { // Tipo dinámico: hacemos transición al nuevo fragmento detallefragment = new DetalleFragment(); Bundle args = new Bundle(); args.putint(param_posicion, position); detallefragment.setarguments(args); FragmentTransaction transaction = getfragmentmanager().begintransaction(); transaction.replace(r.id.fragment_container, detallefragment); transaction.addtobackstack(null); transaction.commit(); En el caso estático, basta con definir un método propio en el fragmento que nos permita actualizar su contenido. En el caso dinámico, tendremos que hacer una transición al nuevo fragmento pasándole los datos del item seleccionado Uso de diálogos Como hemos visto anteriormente, podemos crear diálogos utilizando fragmentos (de hecho, esta es la forma recomendada de hacerlo). Simplemente deberemos crear una clase que herede de DialogFragment. El fragmento se definirá de la misma forma que cualquiera de los anteriores, cargando su contenido en oncreateview. En este caso, además de cargar la vista, también podremos especificar un título para la ventana del diálogo con el método getdialog().settitle(): public View oncreateview(layoutinflater inflater, ViewGroup container, Bundle savedinstancestate) { View view = inflater.inflate(r.layout.dialog, container); getdialog().settitle("título "); return view; Donde encontramos mayores diferencias respecto a los fragmentos anteriores es la forma de mostrarlo. En este caso lo haremos de la siguiente forma: FragmentManager manager = getfragmentmanager(); 10

11 MiDialogFragment dialog = new MiDialogFragment(); dialog.setarguments(bundle); dialog.show(manager, "fragment_dialog"); Podemos observar que en este caso el propio fragmento tiene un método show que nos permite mostrarlo en pantalla (proporcionando como parámetro el fragment manager y una etiqueta identificativa opcional para el diálogo que nos permitirá localizarlo más adelante). Al igual que en casos anteriores, al fragmento se le pueden pasar datos en el momento de su creación proporcionando un bundle Loaders Otra características aparecida en Android 3.0 son los loaders. Esta característica está disponible en cualquier fragmento o actividad. Dichos fragmentos o actividades normalmente necesitan cargar datos al ejecutarse (por ejemplo de un servicio web, o de una base de datos SQlite). Los loaders están pensados para realizar esta tarea de forma asíncrona. Una ventaja de los loaders es que una vez han cargado los datos los retienen aunque la actividad se detenga (onstop), de forma que no será necesario volverlos a cargar cuando la actividad se vuelva a poner en marcha (onstart). Además, el loader estará pendiente de los cambios que se produzcan en su fuente de datos. Si detecta un cambio, obtendrá los nuevos datos y se los volverá a proporcionar a nuestra actividad o fragmento de forma automática. La clase principal que deberemos utilizar es LoaderManager, que nos permitirá manejar varios loaders. Normalmente pondremos en marcha los loaders en el método oncreate de nuestra actividad o en el método onactivitycreated de un fragmento: getloadermanager().initloader(0, null, this); El primer parámetro es un código identificador del loader que queremos poner en marcha. Si ya existe un loader activo con dicho código, se reutilizará dicho loader. Si no existe, se creará uno nuevo. Para crear un loader necesita que se le proporcione un objeto que implemente la interfaz LoaderManager.LoaderCallbacks (como tercer parámetro del método anterior debemos indicar dicho objeto). Normalmente haremos que sea la propia actividad o fragmento quien la implemente (this en el ejemplo anterior), lo cual nos obligará a definir los siguientes métodos: public class MiListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Tipo> {... public Loader<Tipo> oncreateloader(int id, Bundle args) {... public void onloadfinished(loader<tipo> loader, Cursor data) {... public void onloaderreset(loader<tipo> loader) {... 11

12 Podemos observar que en el callback hay que especificar el tipo de datos que maneja el loader, es decir, el tipo de datos que queremos cargar con él, mediante el uso de genéricos Creación de un loader Cuando solicitemos iniciar un loader que todavía no está en funcionamiento se llamará al método oncreateloader en el que deberemos especificar la forma de crearlo. Este método recibe como parámetro el identificador del loader que debemos crear. Podemos crear el loader mediante alguna clase derivada de Loader. Habitualmente utilizaremos CursorLoader para cargar datos de proveedores de contenidos, o AsyncTaskLoader para cargar datos de forma personalizada mediante una async task. Por ejemplo, para crear un loader de tipo cursor que lea los datos de un proveedor de contenidos, haremos lo siguiente: public Loader<Cursor> oncreateloader(int id, Bundle args) { return new CursorLoader(getActivity(), baseuri, proyeccion, seleccion, args, orden); Obtención de resultados Cuando el loader haya terminado de cargar los datos (puede que esto sea instantáneo si ya estaban cargados de antemano), se llamará al método onloaderfinished. En este método deberemos utilizar los datos obtenidos, por ejemplo para mostrarlos en pantalla. Por ejemplo, si contamos con un CursorAdapter podemos proporcionarle el nuevo cursor: public void onloadfinished(loader<cursor> loader, Cursor data) { madapter.swapcursor(data); Es importante no cerrar el cursor anterior, ya que de esto se encargará el loader Reinicio del loader Si queremos que el loader vuelva a cargar los datos (por ejemplo por haber cambiado algún criterio de búsqueda), podemos utilizar el método restartloader, de la misma forma en la que se utilizaba initloader: getloadermanager().restartloader(0, null, this); En este caso se deberán eliminar los datos cargados anteriormente, y comenzar una nueva carga. En el método onloadreset deberemos indicar la forma de eliminar estos datos 12

13 (por ejemplo, eliminando el cursor del CursorAdapter): public void onloaderreset(loader<cursor> loader) { madapter.swapcursor(null); Loader personalizado Si queremos realizar la carga de datos de forma personalizada, lo más sencillo será crear una subclase de AsyncTaskLoader en la que programaremos cómo se realiza la descarga en segundo plano. Se define de forma parecida a la clase AsyncTask. En este caso el método principal que deberemos definir es loadinbackground, que será el que se ejecutará en segundo plano para realizar la carga de datos, pero además deberemos sobrescribir otros métodos para controlar el proceso de descarga. A parte de loadinbackground, al menos deberemos definir onstartloading que deberá comprobar si ya tenemos disponibles los datos actualizados, o si por el contrario debemos volverlos a cagar. Si ya dispusiésemos de ellos, los devolveremos llamando a deliverresult(datos), y en caso contrario llamaremos a forceload() para hacer que se vuelvan a descargar (esto provocará la llamada a loadinbackground desde un hilo en segundo plano). Es importante destacar que si no sobrescribimos onstartloading, siempre considerará por defecto que ya dispone de los datos y nunca se realizará la descarga. En el siguiente ejemplo almacenamos los datos descargados en una variable de instancia, y con ella controlaremos si es necesario descargarlos (si es null) o si ya disponemos de ellos y podemos devolverlos. Para ello sobrescribimos también deliverresult, para que tras obtener los datos antes de devolverlos se retengan en dicha variable de instancia. También definimos el método onstoploading (que detiene el proceso de carga) y onreset (que detiene el proceso y además elimina los datos ya descargados para que la próxima vez se vuelvan a descargar). static class MiLoader extends AsyncTaskLoader<Tipo> { Tipo mdatos = null; public MiLoader(Context context) { super(context); public Tipo loadinbackground() { return cargardatos(); public void deliverresult(tipo data) { mdatos = data; super.deliverresult(data); protected void onstartloading() { super.onstartloading(); 13

14 if(mdatos!= null) { deliverresult(mdatos); else { forceload(); protected void onstoploading() { super.onstoploading(); cancelload(); protected void onreset() { super.onreset(); onstoploading(); mdatos = null; Una vez finalizada la carga de datos, le proporcionará al callback los datos obtenidos. Definiremos el callback de la siguiente forma: public class MiFragmento implements LoaderManager.LoaderCallbacks {... public Loader<Tipo> oncreateloader(int id, Bundle args) { return new MiLoader(getActivity()); public void onloadfinished(loader<tipo> loader, Tipo data) { madapter.clear(); for(item item: data) { madapter.add(item); public void onloaderreset(loader<tipo> loader) { madapter.clear(); Si detectásemos que ha habido algún cambio en nuestra fuente de datos, podemos llamar al método oncontentchanged() del objeto AsyncTaskLoader para forzar que vuelva a descargar los datos y se los proporcione a nuestra actividad o fragmento Librerías de compatibilidad Los fragmentos están disponibles sólo a partir de Android 3.0, y todavía existen en el mercado numerosos dispositivos con versiones anteriores. Sin embargo, el uso de fragmentos se ha convertido en una práctica muy recomendable en el diseño de aplicaciones, por lo que deberíamos utilizarlos. Para permitir que los desarrolladores utilicen esta nueva característica, pero no se pierda el soporte para dispositivos antiguos, se proporciona una librería de compatibilidad que incorpora las características más importantes de las nuevas versiones de Android a dispositivos con versiones anteriores 14

15 (da soporte a dispositivos desde Android 1.6). Vamos a ver cómo utilizar esta librería. En primer lugar, deberemos descargar la librería con el Android SDK Manager (si no la tenemos ya). La encontraremos en la carpeta Extras > Android Support. Librería de soporte Una vez descargada, podremos encontrarla en el directorio: $ANDROID_SDK/extras/android/support/v4/android-support-v4.jar Para incluirla en nuestro proyecto tendremos que crear en él un directorio libs y copiar ahí dicha librería. Android reconocerá de forma automática todas las librerías introducidad en libs. Con esta librería ya podemos indicar en el fichero AndroidManifest.xml que la versión mínima a la que damos soporte es la 1.6 (nivel 4): <uses-sdk android:minsdkversion="4" android:targetsdkversion="17" /> Ahora deberemos adaptar nuestra aplicación para que use la librería de compatibilidad para acceder a los fragmentos y loaders. Deberemos hacer una serie de modificaciones: Ahora todos los import referentes a fragmentos deberemos cogerlos del paquete android.support.v4.app correspondiente a la librería de compatibilidad. De esta forma tendremos: import import import import android.support.v4.app.fragment; android.support.v4.app.fragmentmanager; android.support.v4.app.fragmenttransaction; android.support.v4.app.loadermanager; La actividad contenedora de fragmentos ahora deberá heredar de FragmentActivity, en lugar de heredar simplemente de Activity: import android.support.v4.app.fragmentactivity; public class MainActivity extends FragmentActivity {... 15

16 Para obtener el objeto FragmentManager ahora utilizaremos el método getsupportfragmentmanager, y de la misma forma para obtener LoaderManager utilizaremos getsupportloadermanager: FragmentManager manager = getsupportfragmentmanager(); LoaderManager manager = getsupportloadermanager(); Con los cambios anteriores conseguiremos que nuestra aplicación funcione correctamente en cualquier versión de Android desde la Librerías de servicios Google proporciona una serie de servicios que pueden ser integrados en las aplicaciones Android, como Google Maps y Google+. En los dispositivos Android normalmente encontramos incluidas una serie de librerías de Google que nos permiten integrar Google Maps en nuestras aplicaciones. Incluir esta API en el propio sistema operativo del dispositivo hace que sea poco flexible a la hora de actualizar las aplicaciones de Google, ya que lo más común es que las actualizaciones del sistema operativo se proporcionen sólo durante un periodo limitado de tiempo. Por este motivo Google ha pasado a incluir sus librerías de forma externa. Ahora sus librerías deben descargarse de Google Play, y se actualizan continuamente desde esa tienda. De esta forma la mayor parte de los dispositivos van a tener soporte para las últimas versiones de estos servicios. Actualmente la librería de servicios de Google (que soporta Google Maps v2 y Google+) soporta todos los dispositivos Android a partir de la versión 2.2 (API de nivel 8). El único requerimiento es descargar las librerías de Google desde Google Play y mantenerlas actualizadas Configuración de las librerías de Google para el desarrollo Para utilizar las librerías de Google en nuestros proyectos, deberemos descargarlas y añadirlas como proyecto a Eclipse. Seguiremos los siguientes pasos: 1. Descargamos las librerías desde Android SDK Manager. Las encontraremos en la carpeta Extras > Google Play services. 2. Una vez descargadas las podremos encontrar en la siguiente ruta, que contiene un proyecto Eclipse con dichas librerías: $ANDROID_SDK/extras/google/google_play_services/ 3. Importamos en Eclipse el proyecto que se encuentra en la ruta anterior, con File > Import... > Android > Existing Android Code into Workspace Con esto tendremos las librerías de Android disponibles para ser utilizadas en nuestros proyectos. Para hacer que uno de nuestros proyectos las utilice deberemos añadir en él 16

17 una referencia al proyecto con las librerías. Para ello: 1. Pulsaremos con el botón derecho sobre el proyecto y seleccionaremos Properties. 2. Seleccionamos el apartado Android. 3. En el apartado Library pulsamos el botón Add Seleccionamos el proyecto con las librerías de Google y pulsamos Ok. 5. Pulsamos sobre el botón Apply y tras esto Ok para cerrar el diálogo. En nuestra aplicación deberemos tener en cuenta que es posible que los servicios de Google no estén disponibles en el dispositivo. Esto podemos comprobarlo con el siguiente método: int status = GooglePlayServicesUtil.isGooglePlayServicesAvailable(); if(status == ConnectionResult.SUCCESS) { // Los servicios están disponibles Configuración de la aplicación Una vez configurada la librería de servicios de Google, podemos utilizarla para integrar la versión 2 de los mapas. Para ello en primer lugar deberemos generar una clave de desarrollador asociada a nuestra aplicación y a nuestro certificado: https://developers.google.com/maps/documentation/android/start #the_google_maps_api_key Una vez obtenida la clave, deberemos configurar el fichero AndroidManifest.xml. En primer lugar añadiremos una serie de permisos necesarios: <uses-permission android:name="android.permission.internet"/> <uses-permission android:name= "android.permission.write_external_storage"/> <uses-permission android:name= "com.google.android.providers.gsf.permission.read_gservices"/> <uses-permission android:name= "android.permission.access_coarse_location"/> <uses-permission android:name= "android.permission.access_fine_location"/> Hay que añadir un permiso adicional propio (en lugar de es.ua.jtech deberemos poner el paquete de nuestra aplicación): <permission android:name="es.ua.jtech.permission.maps_receive" android:protectionlevel="signature"/> <uses-permission android:name="es.ua.jtech.permission.maps_receive"/> La versión 2 de los mapas de Google necesita que los dispositivos soporten Open GL ES 2.0, por lo que esto también debe ser indicado en el manifest: <uses-feature android:glesversion="0x " android:required="true"/> 17

18 También deberemos especificar la clave de desarrollador, introduciendo la siguiente etiqueta justo antes de </application>: <meta-data android:name="com.google.android.maps.v2.api_key" android:value="pon_aqui_tu_clave"/> Integración de los mapas Una vez tenemos configurada la aplicación, podremos integrar en ella los mapas de Google. Para ello podemos añadir un fragmento con el mapa a alguna de nuestras actividades: <?xml version="1.0" encoding="utf-8"?> <fragment xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" class="com.google.android.gms.maps.mapfragment"/> Con esto podremos ver el mapa integrado en nuestra actividad. En caso de que estuviésemos utilizando la librería de compatibilidad el código anterior no funcionará. En ese caso deberemos utilizar la clase SupportMapFragment: <?xml version="1.0" encoding="utf-8"?> <fragment xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" class="com.google.android.gms.maps.supportmapfragment"/> 18

19 2. Ejercicios de fragmentos y compatibilidad Antes de empezar a crear los proyectos, debes descargarte las plantillas desde bitbucket. Para ello: 1. Entraremos en nuestra cuenta de bitbucket.org, seleccionaremos el repositorio git expertomoviles/serv-android-expertomoviles (del que tendremos únicamente permisos de lectura), y haremos un Fork de dicho repositorio en nuestra cuenta, para así tener una copia propia del repositorio con permisos de administración. 2. Para evitar que bitbucket nos dé un error por sobrepasar el número de usuarios permitidos, debemos ir al apartado Access management de las preferencias del repositorio que acabamos de crear y eliminar los permisos de lectura para el grupo Estudiantes (tendremos estos permisos concedidos si al hacer el Fork hemos especificado que se hereden los permisos del proyecto original). Los únicos permisos que debe tener nuestro repositorio deben ser para el propietario (owner) y para el usuario Experto Moviles. 3. Una vez tenemos nuestra copia del repositorio con las plantillas correctamente configuradas en bitbucket, haremos un clone en nuestra máquina local: git clone https://[usr]:bitbucket.org/[usr]/serv-android-expertomoviles 4. De esta forma se crea en nuestro ordenador el directorio serv-android-expertomoviles y se descargan en él las plantillas para los ejercicios del módulo y un fichero.gitignore. Además, ya está completamente configurado y conectado con nuestro repositorio remoto, por lo que lo único que deberemos hacer será subir los cambios conforme realicemos los ejercicios, utilizando los siguientes comandos: git add. git commit -a -m "[Mensaje del commit]" git push origin master 2.1. Lector de noticias (1,5 puntos) Vamos a crear una aplicación que nos permita leer una serie de noticias obtenidas de un RSS. En primer lugar crearemos la interfaz utilizando fragmentos, para así adaptarla a teléfonos y tablets. En la aplicación encontramos dos fragmentos: MainFragment, que contiene una lista de noticias, y DetailFragment, que contiene los detalles de la noticia seleccionada. Deberemos: a) En primer lugar vamos a definir el contenido del fragmento DetailFragment. En su método oncreateview deberemos cargar su contenido del layout detail_fragment.xml. b) Vamos a crear el layout de la actividad para tablets. En layout-large editaremos el contenido de activity_main.xml para introducir en él los dos fragmentos definidos en la aplicación. El fragmento principal se introducirá al comienzo del LinearLayout 19

20 principal, con peso (layout-weight) 1. El fragmento con los detalles se introducirá en el LinearLayout secundario, con altura wrap_content. Con esto podremos probar la aplicación en un simulador de tipo tablet para ver la composición que hemos creado. c) Vamos a hacer ahora que la aplicación también funcione en móviles. Para ello, en el método oncreate de MainActivity comprobaremos si estamos utilizando el layout de móviles (miraremos si existe el contenedor R.id.fragment_container) y en tal caso crearemos el fragmento principal y lo añadiremos al contenedor. d) Por último, vamos a comunicar los dos fragmentos. La comunicación será distinta según el tipo de dispositivo. En un móvil haremos una transición, mientras que en tablets comunicaremos los dos fragmentos que ya se muestran en pantalla a través de la actividad. En el método onnoticiaselected de MainActivity comprobaremos en primer lugar si el fragmento con los detalles está accesible (en ese caso sabremos que estamos en un tablet). En caso de que exista, simplemente deberemos actualizar en él la noticia llamando a su método updatenoticia. En caso contrario, deberemos crear un nuevo fragmento de detalles, le pasaremos un parámetro "noticia" de tipo Serializable en el bundle con la noticia seleccionada, y lo mostraremos en pantalla Carga de noticias (1 punto) Vamos a actualizar el lector de noticias para que ahora lea las noticias a través de Internet utilizando para ello un loader. Deberemos: a) En MainFragment definimos una clase interna de tipo AsyncTaskLoader que se encargue de cargar las noticias (puede utilizar para ello el método DataSource.loadNoticias()). Este loader debe proporcionar objetos de tipo List<Noticia>. b) Hacer que dicha clase implemente el listener LoaderCallbacks, e implementar los métodos necesarios. Al crearse el loader se deberá proporcionar una instancia de la clase AsyncTaskLoader definida en el punto anterior. Al obtenerse datos tendremos que introducirlos en el adapter (vaciándolo previamente), y al reiniciar el loader simplemente vaciaremos el adapter. c) Por último, modificaremos el método oncreate de la clase anterior para que ya no introduzca ningún dato manualmente en el adapter, y que en su lugar lo que haga sea poner en marcha el loader. Con esto la aplicación deberá descargar las noticias del RSS proporcionado Servicios de Google (0,5 puntos) En este ejercicio vamos a añadir al proyecto anterior un mapa de Google utilizando los servicios de Google Play. Para ello deberemos: a) Descargar la librería de servicios de Google Play utilizando SDK Manager. Importar la 20

21 librería descargada en Eclipse, y añadir una dependencia de nuestro proyecto a ella. b) Obtenemos la clave de la API para acceder a Google Maps v2 accediendo a la siguiente dirección: https://developers.google.com/maps/documentation/android/start c) Introducimos en el fichero AndroidManifest.xml la información necesaria: Indicar que es necesario contar con OpenGLES 2.0. Solicitar los permisos necesarios. Indicar la clave de la API obtenida. d) Lo último que haremos será añadir el mapa a nuestra aplicación. Simplemente lo añadiremos como fragmento al fichero activity_main.xml de layout-large (utilizando la librería de soporte). En caso de usar una tablet, veremos el mapa bajo los detalles de la noticia. Nota Los servicios de Google Play no funcionan en el emulador, sólo en un móvil real. Lo único que podremos ver en el emulador es un botón que nos invita a instalarnos Google Play, pero no podremos hacerlo. 21

22 3. Agenda y calendario En esta sesión vamos a ver cómo utilizar proveedores de contenidos que nos den acceso a información personal del usuario manejada por el dispositivo, como es el caso de su agenda de contactos y sus calendarios. La forma de acceder a esta información ha ido variando a lo largo de las diferentes versiones de Android, hasta estandarizarse completamente en ICS (Ice Cream Sandwich, Android 4.0). Veremos cómo mantener la compatibilidad con versiones anteriores Agenda de contactos El proveedor de la agenda de contactos se encuentra estructurado en tres tablas de datos: Contacts: Contiene la lista de contactos únicos. Un contacto puede tener varias cuentas (Google, Twitter, etc). Esta tabla unifica todas esas cuentas en una única entrada. RawContacts: En esta tabla tenemos entradas para cada cuenta concreta de un contacto. Una única entrada en Contacts puede estar relacionada con varias entradas en RawContacts, para cada cuenta diferente del usuario. Data: Esta es la tabla donde realmente están almacenados los datos de cada cuenta de usuario. Para cada cuenta (almacenada en RawContacts) tendremos un conjunto de datos almacenados en la tabla Data. 22

23 Tablas de contactos El acceso al proveedor de la agenda de contactos se hace mediante una serie de constantes definidas en las siguientes subclases de ContactsContract, cada una de ellas referida a una de las tablas anteriores: ContactsContract.Contacts ContactsContract.RawContacts ContactsContract.Data En primer lugar, para que nuestra aplicación pueda acceder a los contactos (leerlos y/o modificarlos) deberemos solicitar el permiso correspondiente en el AndroidManifest.xml: <uses-permission android:name="android.permission.read_contacts"> <uses-permission android:name="android.permission.write_contacts"> Carga de contactos Para leer los contactos almacenados en el dispositivos utilizaremos la clase ContentsResolver, al igual que para cualquier otro tipo de contenidos. Por ejemplo, podríamos leer todos los contactos de la siguiente forma: ContentResolver cr = getcontentresolver(); Cursor cursor = cr.query(contactscontract.contacts.content_uri, null, null, null, null); 23

24 En lugar de seleccionar todos los datos podemos indicar la proyección o selección que nos interese mediante constantes de ContactsContract.Contacts. mprojection = new String[] { ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME_PRIMARY ; mprofilecursor = getcontentresolver().query(contactscontract.contacts.content_uri, mprojection, null, null, null); En lugar de cargar el cursor directamente, también podríamos utilizar un loader con un CursorAdapter para cargar los datos del proveedor de contenidos. public Loader<Cursor> oncreateloader(int id, Bundle args) { String[] projection = { ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME_PRIMARY ; String sortorder = ContactsContract.Contacts.DISPLAY_NAME_PRIMARY + " ASC"; return new CursorLoader(getApplicationContext(), ContactsContract.Contacts.CONTENT_URI, projection, null, null, sortorder); El loader CursorLoader se encarga de cargar los datos de un proveedor de contenidos de forma asíncrona (internamente utilizará ContentResolver). Nota En versiones anteriores a Android 2.0 (API 5) el acceso al proveedor de contenidos de la agenda de contactos se hacía mediante constantes de la clase Contacts.People. Si queremos hacer una aplicación compatible deberemos tener esto en cuenta y utilizar una u otra en función de la versión actual Acceso a datos de los contactos Una vez tenemos el identificador de un contacto, podríamos obtener todas sus cuentas asociadas (raw contacts). Utilizaremos para ello las constantes definidas en ContactsContract.RawContacts: Cursor c = getcontentresolver().query( ContactsContract.RawContacts.CONTENT_URI, new String[] { ContactsContract.RawContacts._ID, ContactsContract.RawContacts.ACCOUNT_TYPE, ContactsContract.RawContacts.ACCOUNT_NAME, ContactsContract.RawContacts.CONTACT_ID + "=?", new String[] { String.valueOf(contactId), null); 24

25 Podemos obtener los datos un contacto utilizando constantes de ContactsContract.Data para el acceso a la URI y a datos genéricos, y a constantes de clases internas de ContactsContract.CommonDataKinds para acceder a tipos de datos comunes. Por ejemplo, podemos leer los números de teléfono asociados a una cuenta dada: Cursor c = getcontentresolver().query( ContactsContract.Data.CONTENT_URI, new String[] { ContactsContract.Data._ID, ContactsContract.CommonDataKinds.Phone.NUMBER, ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.LABEL, ContactsContract.Data.RAW_CONTACT_ID + "=?" + " AND " + ContactsContract.Data.MIMETYPE + "='" + ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE + "'", new String[] { String.valueOf(rawContactId), null); También podemos obtener los teléfonos de todas las cuentas de un contacto dado: Cursor c = getcontentresolver().query( ContactsContract.Data.CONTENT_URI, new String[] { ContactsContract.Data._ID, ContactsContract.CommonDataKinds.Phone.NUMBER, ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.LABEL, ContactsContract.Data.CONTACT_ID + "=?" + " AND " + ContactsContract.Data.MIMETYPE + "='" + ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE + "'", new String[] { String.valueOf(contactId), null); Inserción de contactos No podemos añadir un contacto directamente, sino que deberemos añadir una cuenta (raw contact). Si al añadir la cuenta ya existe un contacto con el identificador proporcionado, la cuenta quedará asociada a dicho contacto. En caso de no ser así, el contacto se creará de forma automática. Podemos añadir una cuenta de la siguiente forma: ContentValues values = new ContentValues(); values.put(contactscontract.rawcontacts.account_type, tipo); values.put(contactscontract.rawcontacts.account_name, nombre); Uri rawcontacturi = getcontentresolver().insert(contactscontract.rawcontacts.content_uri, values); Tras crear la cuenta, obtendremos una URI que nos dará acceso a ella. Podemos extraer de ella el identificador de la cuenta que se acaba de insertar: long rawcontactid = ContentUris.parseId(rawContactUri); Tras insertar la cuenta, podremos añadir a ella distintos elementos de datos: values.clear(); values.put(contactscontract.data.raw_contact_id, rawcontactid); values.put( 25

26 ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE); values.put( ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, "Pepe García"); getcontentresolver().insert(contactscontract.data.content_uri, values); Sin embargo, la forma recomendada de realizar las inserciones es metiante una operación en batch que realice todas las operaciones de forma conjunta. Para ello crearemos una lista de objetos ContentProviderOperation, que definirán las operaciones que vamos a realizar en batch: ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); ops.add(contentprovideroperation.newinsert(contactscontract.rawcontacts.content_uri).withvalue(contactscontract.rawcontacts.account_type, accounttype).withvalue(contactscontract.rawcontacts.account_name, accountname).build()); ops.add(contentprovideroperation.newinsert(contactscontract.data.content_uri).withvaluebackreference(contactscontract.data.raw_contact_id, 0).withValue( ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE).withValue( ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, "Pepe García").build()); getcontentresolver().applybatch(contactscontract.authority, ops); Podemos ver que como la segunda operación depende del identificador generado por la primera (RAW_CONTACT_ID), este valor se lo pasamos con el método withvaluebackreference. Con ello le indicamos que como valor tome el generado por una operación anterior. Para indicar de qué operación queremos obtener el valor generado le proporcionamos como segundo parámetro el índice de dicha operación en la lista (en el caso anterior se le proporciona 0 porque nos interesa el resultado de la primera operación). En versiones anteriores a la 2.0 (API 5) la inserción de contactos se realiza de la siguiente forma: // Inserta contacto (previo a Android 2.0) ContentValues cv = new ContentValues(); cv.put(contacts.people.name, nombre); Uri uri = getcontentresolver().insert(contacts.people.content_uri, cv); // Añade un teléfono Uri phoneuri = Uri.withAppendedPath(uri, Contacts.People.Phones.CONTENT_DIRECTORY); cv.clear(); cv.put(contacts.people.phones.type, tipo); cv.put(contacts.people.phones.number, telefono); getcontentresolver().insert(phoneuri, cv); 26

27 // Añade un Uri uri = Uri.withAppendedPath(uri, Contacts.People.ContactMethods.CONTENT_DIRECTORY); cv.clear(); cv.put(contacts.people.contactmethods.kind, Contacts.KIND_ ); cv.put(contacts.people.contactmethods.data, ); cv.put(contacts.people.contactmethods.type, Contacts.People.ContactMethods.TYPE_WORK); getcontentresolver().insert( uri, cv); 3.2. Calendario El acceso al calendario no se ha estandarizado en Android hasta la versión 4.0 (Ice Cream Sandwich). Anteriormente se debían especificar las URI y los campos del proveedor sin ayuda de ninguna constante. Vamos a ver las dos formas de acceder, para poder mantener la compatibilidad con versiones anteriores. Con Android 4.0 el acceso a calendarios se realizará mediante clases internas de CalendarContract. En ellas podemos acceder a las distintas URIs que nos dan acceso a las tablas que contienen los datos de los calendarios. En versiones anteriores deberemos escribir las URIs directamente: Versión URI Hasta Android 2.1 "content://calendar/" A partir de Android 2.2 "content://com.android.calendar/" A partir de Android 4.0 CalendarContract.CONTENT_URI Para poder acceder a los calendarios antes deberemos solicitar los permisos correspondiente en AndroidManifest.xml: <uses-permission android:name="android.permission.read_calendar" /> <uses-permission android:name="android.permission.write_calendar" /> El proveedor de calendarios nos da acceso a multiples calendarios, almacenados en la tabla Calendars. Cada calendario contiene una serie de eventos, contenidos en la tabla Events, y estos eventos pueden contener recordatorios, que se almacenan en la tabla Reminders. 27

28 Tablas de calendarios Selección del calendario En el sistema podemos tener acceso a varios calendarios, por lo que lo primero que deberemos hacer es seleccionar el calendario con el que queramos trabajar. Para ello a partir de Android 4.0 tenemos la clase CalendarContract.Calendars, que contiene las constantes necesarias para acceder a la lista de calendario, como por ejemplo CONTENT_URI. En versiones anteriores deberemos especificar la URI manualmente: Versión URI Hasta Android 2.1 "content://calendar/calendars" A partir de Android 2.2 "content://com.android.calendar/calendars" A partir de Android 4.0 CalendarContract.Calendars.CONTENT_URI En Android 4.0 podremos acceder a los calendarios con: Cursor cursor = getcontentresolver().query(uri.parse(contenturi), new String[] { CalendarContract.Calendars._ID, CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, null, null, null); Sin embargo, en versiones anteriores deberemos especificar los campos manualmente también: 28

29 Cursor cursor = getcontentresolver().query(uri.parse(contenturi), new String[] { "_id", "displayname", null, null, null); Advertencia Hay que destacar que los nombres de los campos cambian en Android 4.0, por lo que el código de versiones antiguas dejará de funcionar. Por ejemplo, en lugar de "displayname" se utiliza "calendar_displayname". Si queremos conseguir una aplicación compatible con todas las versiones, deberemos detectar la versión que se está utilizando y en función de ésta ajustar los nombres de los campos: Uri calendarsuri; String calendarsid; String calendarsname; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { calendarsuri = CalendarContract.Calendars.CONTENT_URI; calendarsid = CalendarContract.Calendars._ID; calendarsname = CalendarContract.Calendars.CALENDAR_DISPLAY_NAME; else { if(build.version.sdk_int >= Build.VERSION_CODES.FROYO) { calendarsuri = Uri.parse("content://com.android.calendar/calendars"); else { calendarsuri = Uri.parse("content://calendar/calendars"); calendarsid = "_id"; calendarsname = "displayname"; De esta forma podemos obtener la lista de calendarios, y dejar que el usuario seleccione uno de ellos. Una vez seleccionado, podremos acceder a él y modificarlo, para por ejemplo añadir nuevos eventos Añadir eventos al calendario La tabla de eventos tiene las siguientes URIs, dependiendo de la versión de Android: Versión URI Hasta Android 2.1 "content://calendar/events" A partir de Android 2.2 "content://com.android.calendar/events" A partir de Android 4.0 CalendarContract.Events.CONTENT_URI Para crear un evento deberemos proporcionar la fecha y hora de inicio y de fin (en milisegundos), su título y descripción, la zona horaria, y el identificador del calendario al que lo queremos añadir: Date dtstart = // Fecha y hora de inicio Date dtend = // Fecha y hora de fin 29

30 ContentResolver cr = getcontentresolver(); ContentValues values = new ContentValues(); values.put(calendarcontract.events.dtstart, dtstart.gettime()); values.put(calendarcontract.events.dtend, dtend.gettime()); values.put(calendarcontract.events.title, "Reunión"); values.put(calendarcontract.events.description, "Preparación proyecto"); values.put(calendarcontract.events.calendar_id, calid); values.put(calendarcontract.events.event_timezone, "Europe/Madrid"); Uri uri = cr.insert(calendarcontract.events.content_uri, values); En caso de utilizar versiones anteriores de Android, se hará de la siguiente forma: Date dtstart = // Fecha y hora de inicio Date dtend = // Fecha y hora de fin ContentResolver cr = this.getcontentresolver(); ContentValues values = new ContentValues(); values.put("dtstart", dtstart.gettime()); values.put("dtend", dtend.gettime()); values.put("title", "Reunión"); values.put("description", "Preparación proyecto"); values.put("calendar_id", calid); values.put("eventtimezone", "Europe/Madrid"); Uri newevent = cr.insert(contenturi, values); Eventos recurrentes En muchas ocasiones nos interesa agregar un evento que se repite semanalmente durante un periodo de tiempo (por ejemplo las clases de una asignatura). En este caso será conveniente añadirlo como evento recurrente, en lugar de añadirlos como eventos independientes. De esta forma si queremos eliminarlo podremos eliminar la serie entera mediante una única operación. Para definir un evento recurrente deberemos: En DTSTART pondremos la fecha y la hora de inicio del primer evento de la serie, pero en este caso ya no utilizaremos DTEND. En lugar de DTEND, deberemos especificar la duración de los eventos de la serie en DURATION. Se especificará mediante el formato RFC5545. Por ejemplo, una duración de 90 minutos se especifica con "P90M", y una duración de de dos semanas con "P2W". Por último, deberemos especificar la regla de recurrencia en RRULE. En ella deberemos especificar la frecuencia con la que se repite y el número de repeticiones o fecha de finalización. Por ejemplo, si ponemos "FREQ=WEEKLY;COUNT=10" se repetirá 10 veces semanalmente. Si queremos fijar una fecha concreta de finalización, lo haremos con el formato "FREQ=WEEKLY;UNTIL= T235959Z". Más información sobre el formato de las reglas: A continuación mostramos un ejemplo en el que se añade un evento recurrente para las clases de una asignatura que se imparte semanalmente hasta el fin del cuatrimestre (24 de 30

31 mayo de 2013): Date dtstart = // Fecha y hora de inicio del primer evento Date dtend = // Fecha y hora de fin del primer evento // Duración de cada evento (formato RFC5545) long duracionmillis = dtend.gettime() - dtstart.gettime(); int duracionminutos = (int) (duracionmillis / (1000 * 60)); String duracion = "P" + duracionminutos + "M"; // Reglas de la serie de eventos String until = " T235959Z"; // Formato: yyyymmddthhmmssz String rrule = "FREQ=WEEKLY;UNTIL=" + until; ContentResolver cr = this.getcontentresolver(); ContentValues values = new ContentValues(); values.put(calendarcontract.events.calendar_id, calid); values.put(calendarcontract.events.title, "Programación I"); values.put(calendarcontract.events.description, "Asignatura troncal"); values.put(calendarcontract.events.event_location, "Aula L18"); values.put(calendarcontract.events.event_timezone, "Europe/Madrid"); values.put(calendarcontract.events.dtstart, dtstart.gettime()); values.put(calendarcontract.events.duration, duracion); values.put(calendarcontract.events.rrule, rrule); // Inserta el evento en el calendario Uri event = cr.insert(calendarcontract.events.content_uri, values); Atención Es importante no indicar el campo DTEND, ya que es incompatible con DURATION. Si ponemos los dos al mismo tiempo, obtendremos un error. Con versiones anteriores de Android esto mismo se haría de la siguiente forma: Date dtstart = // Fecha y hora de inicio del primer evento Date dtend = // Fecha y hora de fin del primer evento // Duración de cada evento (formato RFC5545) long duracionmillis = dtend.gettime() - dtstart.gettime(); int duracionminutos = (int) (duracionmillis / (1000 * 60)); String duracion = "P" + duracionminutos + "M"; // Reglas de la serie de eventos String until = " T235959Z"; // Formato: yyyymmddthhmmssz String rrule = "FREQ=WEEKLY;UNTIL=" + until; ContentResolver cr = this.getcontentresolver(); ContentValues values = new ContentValues(); values.put("calendar_id", idcalendario); values.put("title", "Programación I"); values.put("description", "Asignatura troncal"); values.put("eventlocation", "Aula L18"); values.put("eventtimezone", "Europe/Madrid"); values.put("dtstart", dtstart.gettime()); values.put("duration", duracion); values.put("rrule", rrule); // Inserta el evento en el calendario Uri event = cr.insert(contenturi, values); 31

32 Añadir recordatorios a los eventos Puede interesarnos también añadir recordatorios a los eventos, para recibir un aviso en el momento del evento o con cierta antelación. Estos recordatorios se añadirán a la tabla reminders. En primer lugar necesitaremos el identificador del evento al que vamos a añadir el recordatorio. Podemos extraerla de la URI obtenida al crear el evento: long id = ContentUris.parseId(event); Añadiremos el recordatorio proporcionando: Un método (alerta = 1, alarma = 2, = 3, sms = 4) Una antelación en minutos, respecto a la hora en la que está programado el evento. Las URIs para la tabla de recordatorios son los siguientes: Versión URI Hasta Android 2.1 "content://calendar/reminders" A partir de Android 2.2 "content://com.android.calendar/reminders" A partir de Android 4.0 CalendarContract.Reminders.CONTENT_URI Podemos añadir el recordatorio de la siguiente forma: ContentValues values = new ContentValues(); values.put(calendarcontract.reminders.event_id, id); values.put(calendarcontract.reminders.method, CalendarContract.Reminders.METHOD_ALERT); values.put(calendarcontract.reminders.minutes, 30); cr.insert(contenturi, values); La forma de hacer esto en versiones anteriores es la siguiente: ContentValues values = new ContentValues(); values.put("event_id", id); values.put("method", 1); values.put("minutes", 30); cr.insert(contenturi, values); 32

33 4. Ejercicios de agenda y calendario 4.1. Acceso a la agenda de contactos (1 punto) Vamos a crear una aplicación que lea nuestro listado de contactos y lo muestre en una lista utilizando para ello un loader. Podemos encontrar en las plantillas la aplicación AgendaContactos que utilizaremos como base. Deberemos: a) En el método oncreate de ContactosFragment creamos un adapter que muestre en la lista el nombre de los contactos (debemos especificar el nombre del campo adecuado de los contactos al crear el adapter). b) En el método oncreateloader creamos y devolvemos un CursorLoader que obtenga del proveedor de contactos el identificador y el nombre de todos los usuarios, y nos los devuelva ordenados por nombre. c) Cuando termine la carga de contactos, cambiamos el cursor del adapter por el cursor obtenido tras la carga. En caso de que se reinicie el loader, eliminamos el cursor del adapter Agregar contactos a la agenda (1,5 puntos) En la aplicación anterior, en MainActivity se define un menú de opciones con una única opción que nos permite añadir un contacto a la agenda. En el método onoptionsitemselected, al pulsar la opción anterior, vamos a hacer que añada el contacto a la agenda con los siguientes datos: Nombre: Especialista Móviles de tipo WORK (consultar las constantes de CommonDataKinds. ) Teléfono: , de tipo WORK (consultar las constantes de CommonDataKinds.Phone) Tipo de cuenta: Gmail Haremos que el contacto aparezca como favorito (Atributo STARRED de RawContact) Todas las operaciones de inserción se deben realizar en batch. Tras ejecutar el código anterior, si vamos a la aplicación de contactos podremos ver el contacto que acabamos de añadir en la sección Favoritos Creación de eventos del calendario (0,5 puntos) En el proyecto CalendarioAsignaturas tenemos una aplicación que muestra un listado de asignaturas, y nos permite añadir sus horarios a nuestro calendario. Vamos a añadir el 33

34 código que realiza esta tarea: a) En la clase DialogAsignaturaFragment tenemos el método agregarevento que se encargará de agregar un evento al calendario. En primer lugar deberemos seleccionar el calendario en el que añadir los eventos. Para ello obtendremos el listado de todos los calendarios, y cogeremos automáticamente el primero de la lista (si hay al menos uno). b) Una vez hayamos visto que selecciona el calendario correctamente, vamos a añadir a él un evento con la primera clase de la asignatura seleccionada. Por ahora añadiremos sólo un único evento sencillo con estos datos. c) Modificaremos el código anterior para que añada el evento como evento recurrente, con todas las clases de la asignatura (no solo la primera). d) Finalmente, haremos que los eventos de la asignatura tengan programado un recordatorio que nos avise con una alarma 30 minutos antes de cada clase. Nota Para poder probar el acceso a calendario necesitaremos contar con una cuenta Exchange con la que podamos sincronizar calendario en el móvil. En caso de dispositivos reales, nos servirá también una cuenta de Google en la que tengamos calendarios. En caso del emulador, necesitaremos utilizar una cuenta Exchange, y un emulador ICS o superior. 34

35 5. Servicios En Android un Service es un componente que se ejecuta en segundo plano, sin interactuar con el usuario. A parte de los servicios ya disponibles en la plataforma Android, cualquier desarrollador puede crear sus propios Service en su aplicación. Cuentan con soporte multitarea real en Android, ya que pueden ejecutar en su propio proceso, a diferencia de los hilos de las Activity, que hasta cierto punto están conectados con el ciclo de vida de las actividades de Android. Para comunicarse con los servicios se utiliza el mecanismo de comunicación entre procesos de Android, Inter-Process Communication (IPC). La interfaz pública de esta comunicación se describe en el lenguaje AIDL Servicios propios Podemos crear un servicio propio para realizar tareas largas que no requieran la interacción con el usuario, o bien para proveer de determinado tipo de funcionalidad a otras aplicaciones. Los servicios propios se deben declarar en el AndroidManifest.xml de la aplicación: <service android:name=".miservicio" android:process=":mi_proceso" > </service> donde "MiServicio" es el nombre de la clase Java que crearemos con el código del servicio. Para pedir permiso para utilizar un servicio desde una clase diferente a la que se declaró el servicio tenemos que definir el atributo uses-permission. Se puede especificar que un servicio debe ejecutarse en un proceso aparte a través del atributo android:process. De esta manera cuenta con su propio hilo y memoria y cualquier operación costosa que pueda tener no afectará al hilo principal donde estaría la interfaz gráfica. Si no lo declaramos con el atributo android:process, debemos utilizar hilos o AsyncTask para las tareas con procesamiento intensivo. En el código java se debe crear una clase que herede de la clase Service de forma directa o a través de alguna clase hija. Una actividad puede iniciar un servicio a través del método startservice() y detenerlo a través de stopservice(). Si la actividad necesita interactuar con el servicio se utiliza el método bindservice() del servicio. Esto requiere un objeto ServiceConnection que permite conectar con el servicio y que devuelve un objeto IBinder. Este objeto puede ser utilizado por la actividad para comunicarse con el servicio. 35

36 A continuación se muestra el ciclo de ejecución de una actividad iniciada con startservice() y de otra creada utilizando bindservice(): El ciclo de vida de un servicio de Android. Al iniciar el servicio se invoca su método oncreate(), y a continuación se invoca el método onstartcommand(intent, int, int) con la información del Intent proporcionado por la actividad. Para los servicios que se lanzan desde la aplicación principal, el método onstartcommand() se ejecuta en ese mismo hilo, el de la interfaz gráfica. Es bastante común crear un hilo nuevo y lanzarlo desde el onstartcommand() para realizar el procesamiento en segundo plano. Si se lanza ese hilo, la ejecución se devuelve rápidamente al método onstartcommand() dejándolo terminar en muy poco tiempo. A través del valor retorno de onstartcommand() podemos controlar el comportamiento de reinicio. El método startservice() también permite definir una bandera para determinar el comportamiento del ciclo de vida de los servicios. De esta forma, START_STICKY se utiliza para los servicios que se inician o detienen de forma explícita. Los servicios iniciados con START_NOT_STICKY se finalizan automáticamente después de que se termina de ejecutar el método onstartcommand(). O START_REDELIVER_INTENT, que es 36

37 una combinación de los dos anteriores. Debes tomar en cuenta que un servicio se inicia en el hilo (thread) principal de la aplicación, por lo tanto, todas las tareas en tiempo de ejecución deben llevarse a cabo en segundo plano. A continuación se detalla el funcionamiento de cada uno de estos flags: Service.START_STICKY es el comportamiento estándar, en este caso el método onstartcommand() será invocado cada vez que el servicio sea reiniciado tras ser terminado por la máquina virtual. Nótese que en este caso, al reiniciarlo, el Intent que se le pasa por parámetro será null. Service.START_STICKY se utiliza típicamente en servicios que controlan sus propios estados y que se inician y terminan de manera explícita con startservice() y stopservice(). Por ejemplo, servicios que reproduzcan música u otras tareas de fondo. Service.START_NOT_STICKY se usa para servicios que se inician para procesar acciones específicas o comandos. Normalmente utilizarán stopself() para terminarse una vez completada la tarea a realizar. Cuando la máquina virtual termine este tipo de servicios, éstos serán reiniciados sólo si hay llamadas de starservice() pendientes, de lo contrario el servicio terminará sin pasar por onstartcommand(). Este modo es adecuado para servicios que manejen peticiones específicas, tales como actualizaciones de red o polling de red. Service.START_REDELIVER_INTENT es una combinación de los dos anteriores de manera que si el servicio es terminado por la máquina virtual, se reiniciará solamente si hay llamadas pendientes a starservice() o bien si el proceso fue matado antes de hacer la llamada a stopself(). En este último caso se llamará a onstartcommand() pasándole el valor inicial del Intent cuyo procesamiento no fue completado. Con Service.START_REDELIVER_INTENT nos aseguramos de que el comando cuya ejecución se ha solicitado al servicio sea completado hasta el final. En versiones anteriores a la 2.0 del Android SDK (nivel 5 de API) había que implementar el método onstart() y era equivalente a sobrecargar onstartcommand() y devolver START_STICKY. El segundo parámetro del método onstarcommand(intent, int, int) es un entero que contiene flags. Éstos se utilizan para saber cómo ha sido iniciado el servicio, sus valores pueden ser: Service.START_FLAG_REDELIVERY indica que el Intent pasado por parámetro es un reenvío porque la máquina virtual ha matado el servicio antes de ocurrir la llamada a stopself(). Service.START_FLAG_RETRY indica que el servicio ha sido reiniciado tras una terminación anormal. Sólo ocurre si el servicio había sido puesto en el modo Service.START_STICKY. De forma que nos sirven para por ejemplo comprobar si el servicio había sido reiniciado: public class MiServicio extends Service { public void oncreate() { 37

38 public void ondestroy() { public int onstartcommand(intent intent, int flags, int startid) { if((flags & START_FLAG_RETRY)!= 0){ // El servicio se ha reiniciado else { // Iniciar el proceso de fondo return Service.START_STICKY; public IBinder onbind(intent arg0) { return null; El método onbind() debe ser sobrecargado obligatoriamente y se utiliza para la comunicación entre procesos Iniciar un servicio Como hemos mencionado, para iniciar un servicio de forma explícita se utiliza el método startservice() con un Intent, y para detenerlo se utiliza stopservice(). A continuación se muestra un ejemplo: ComponentName servicio = startservice( new Intent(getApplicationContext(), MiServicio.class)); //... stopservice(new Intent(getApplicationContext(), servicio.getclass())); donde la clase ComponentName es una clase genérica que guarda un identificador de un componente dado (que puede ser de diferente tipo: Activity, Service, BroadcastReceiver, o ContentProvider). Este identificador posteriormente es utilizado para detener el servicio. A continuación vamos a ver otras dos formas de iniciar servicios: al arrancar el sistema operativo o arrancarlos como servicios prioritarios Iniciar al arrancar Hay aplicaciones que necesitan registrar un servicio para que se inicie al arrancar el sistema operativo. Para ello hay que registrar un BroadcastReceiver al evento del sistema android.intent.action.boot_completed. En el AndroidManifest tendríamos: <uses-permission android:name="android.permission.receive_boot_completed" /> 38

39 <application <receiver android:name=".onbootreceiver"> <intent-filter> <action android:name="android.intent.action.boot_completed" /> </intent-filter> </receiver> </application> La actividad sobrecargaría el método onreceive(): public class MiReceiver extends BroadcastReceiver { public void onreceive(context context, Intent intent) { Intent servicio = new Intent(context, MiServicio.class); context.startservice(servicio); Si la aplicación está instalada en la tarjeta SD, entonces no estará disponible en cuanto arranque el sistema. En este caso hay que registrarse para el evento android.intent.action.action_external_applications_available. A partir de Android 3.0 el usuario debe haber ejecutado la aplicación al menos una vez antes de que ésta pueda recibir el evento BOOT_COMPLETED Servicios prioritarios Es posible arrancar servicios con la misma prioridad que una actividad que esté en el foreground para evitar que Android pueda matarlos por necesidad de recursos. Esto es peligroso porque si hay muchos servicios en foreground se degrada el rendimiento del sistema. Por esta razón al iniciar un servicio en el foreground se debe avisar al usuario. Este tipo de servicios se suelen utilizar solamente cuando el servicio es crítico y no se puede detener. Por ejemplo, para una aplicación de reproductor de música que siga reproduciendo el sonido aun cuando se cambie a otra aplicación. Los servicios de foreground se inician realizando desde dentro del servicio una llamada al método startforeground(): // 1º Creamos la notificación para avisar al usuario int NOTIFICATION_ID = 1; Intent intent = new Intent(this, MiActividad.class); PendingIntent pendingintent = PendingIntent.getActivity(this, 1, intent, 0); Notification notification = new Notification(R.drawable.icon, "Servicio prioritario iniciado", System.currentTimeMillis()); notification.setlatesteventinfo(this, "Servicio", "Servicio iniciado", pendingintent); notification.flags = notification.flags Notification.FLAG_ONGOING_EVENT; //Mientras dure // 2ª Pasamos el servicio al foreground y avisamos al usuario startforeground(notification_id, notification); 39

40 Esta notificación debe durar mientras el servicio esté en ejecución. Se puede volver del foreground con el método stopforeground(). En general los servicios no deben ser iniciados en el foreground Servicios y AsyncTask Las operaciones lentas de un servicio deben realizarse en un hilo aparte. Esto evitará ralentizar la actividad o hilo principal. La manera más cómoda suele ser a través de una AsyncTask. En el siguiente ejemplo un servicio utiliza una AsyncTask para realizar una cuenta desde 1 hasta 100. public class MiCuentaServicio extends Service { MiTarea mitarea; // Puntero al AsyncTask public int onstartcommand(intent intent, int flags, int startid) { Log.i("SRV", "onstartcommand"); mitarea.execute(); // Iniciamos la tarea return Service.START_STICKY; public void oncreate() { super.oncreate(); Toast.makeText(this,"Servicio creado...", Toast.LENGTH_LONG).show(); Log.i("SRV","onCreate"); mitarea = new MiTarea(); // Creamos el AsyncTask public void ondestroy() { super.ondestroy(); Toast.makeText(this,"Servicio destruido...", Toast.LENGTH_LONG).show(); Log.i("SRV","Servicio destruido"); mitarea.cancel(true); // Detenemos la tarea public IBinder onbind(intent arg0) { return null; // Clase privada para la tarea... // param type: [input, progress report, result type] private class MiTarea extends AsyncTask<String, String, String> { private int i; boolean cancelado; protected void onpreexecute() { super.onpreexecute(); i = 1; cancelado = false; 40

41 protected String doinbackground(string... params) { for(; i<100; i++){ Log.i("SRV", "AsyncTask: Cuento hasta "+i); publishprogress(""+i); //para actualizar onprogressupdate try { Thread.sleep(5000); catch (InterruptedException e) { e.printstacktrace(); if(cancelado) break; return null; protected void onprogressupdate(string... values) { // Actualizar las notificaciones UI Toast.makeText(getApplicationContext(), "Cuento hasta "+values[0], Toast.LENGTH_SHORT).show(); protected void oncancelled() { super.oncancelled(); cancelado = true; Para completar el ejemplo, el código de una actividad con dos botones que inicien y detenga este servicio tendría el siguiente aspecto: public class Main extends Activity { Main main; // Puntero al contexto public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); main = this; ((Button)findViewById(R.id.ButtonStart)).setOnClickListener( new OnClickListener() { public void onclick(view v) { startservice(new Intent(main, MiCuentaServicio.class)); ); ((Button)findViewById(R.id.ButtonStop)).setOnClickListener( new OnClickListener() { public void onclick(view v) { stopservice(new Intent(main, MiCuentaServicio.class)); 41

42 ); 5.2. Broadcast receiver Un BroadcastReceiver es una especie de receptor de los eventos que produce el sistema operativo Android. Típicamente, un Broadcast Receiver se utiliza para mostrar notificaciones de los eventos que ocurren en nuestro teléfono móvil, como por ejemplo cuando se encuentra una red wifi, se agota la batería, etc Declaración y registro en el Manifest Un receptor de broadcast es una clase que recibe Intents generados a través del método Context.sendBroadcast(). La clase debe heredar de BroadcastReceiver y debe implementar el método onreceive(). Sólo durante la ejecución de este método el objeto estará activo, por lo tanto no se puede utilizar para hacer ninguna operación asíncrona. Concretamente, no se podría mostrar un diálogo ni realizar un bind a un servicio. Las alternativas serían usar el NotificationManager en el primer caso y Context.startService() en el segundo. La clase que herede de BroadcastReceiver tiene que estar declarada en el AndroidManifest.xml. También se declaran los Intent que la clase recibirá: <application> <!-- [...] --> <receiver android:name=".paquete.mibroadcastreceiver"> <intent-filter> <action android:name="android.intent.action_timezone_changed" /> <action android:name="android.intent.action_time" /> </intent-filter> </receiver> </application> Siguiendo con el ejemplo, el código de la clase Java "MiBroadcastReceiver" que hemos añadido al manifest, recibiría un intent al cambiar la hora (debido a un ajuste del reloj) o al cambiar la zona horaria: public class MiBroadcastReceiver extends BroadcastReceiver { public void onreceive(context context, Intent intent) { String action = intent.getaction(); if (action.equals(intent.action_timezone_changed) action.equals(intent.action_time_changed)) { // Ejemplo: Actualizar nuestro // Widget dependiente de la hora 42

43 Otro ejemplo típico es el de recibir el intent de una llamada de teléfono entrante. Habría que declarar el intent android.intent.action.phone_state en el AndroidManifest.xml y declarar la clase Java BroadcastReceiver con el siguiente código: public class LlamadaReceiver extends BroadcastReceiver { public void onreceive(context context, Intent intent) { Bundle extras = intent.getextras(); if (extras!= null) { String state = extras.getstring(telephonymanager.extra_state); if (state.equals(telephonymanager.extra_state_ringing)) { String phonenumber = extras.getstring( TelephonyManager.EXTRA_INCOMING_NUMBER); Log.i("DEBUG", phonenumber); Para probar este tipo de eventos en el emulador podemos abrir la vista "Emulator Control" (que está en Window > Show view > Other..., y en la ventana que se abre elegimos "Emulator Control" dentro de la sección "Android"). Este nos permite, entre otras opciones, realizar llamadas, enviar mensajes al emulador, o indicarle la red o la posición GPS actual. Hay intents para toda clase de eventos, como por ejemplo pulsar el botón de la cámara, batería baja, o incluso cuando se instala una nueva aplicación. Los componentes propios también pueden enviar intents de tipo Broadcast. A continuación se incluyen algunos de los eventos que podemos capturar: android.provider.telephony.sms_received Evento de mensaje recibido. android.intent.action.phone_state Evento de llamadas recibidas. android.intent.action.airplane_mode Evento modo vuelo. android.intent.action.battery_low Evento batería baja. android.intent.action.boot_completed Evento de inicio del sistema operativo. android.intent.action.screen_off Evento bloqueo de pantalla. android.intent.action.screen_on Evento desbloqueo de pantalla. android.bluetooth.intent.action.discovery_started Evento comienzo de escáner Bluetooth. android.bluetooth.intent.action.enabled Evento Bluetooth habilitado Registro dinámico Un BroadcastReceiver también se puede registrar para capturar un determinado tipo de intent de forma dinámica, en lugar de hacerlo a través del AndroidManifest.xml 43

44 utilizando la sección <intent-filter>. La forma de realizar esto es la siguiente: MiBroadcastReceiver intentreceiver = new MiBroadcastReceiver(); IntentFilter intentfilter = new IntentFilter(Intent.ACTION_CAMERA_BUTTON); intentfilter.addaction(intent.action_package_added); // Añadimos otro evento // Activamos el registro registerreceiver(intentreceiver, intentfilter); // Posteriormente para eliminar el registro... unregisterreceiver(intentreceiver); Es recomendable registrar el receiver en el método onresume() y desregistrarlo en el método onpause() de la actividad PendingIntents y servicios del sistema Al pasar un PendingIntent a otra aplicación, por ejemplo, el Notification Manager, el Alarm Manager o aplicaciones de terceros, se le está dando permiso para ejecutar determinado código que nosotros definimos en nuestra propia aplicación. Por ejemplo, vamos a colocar el código a ejecutar en un BroadcastReceiver: public class MiBroadcastReceiver extends BroadcastReceiver { public void onreceive(context context, Intent intent) { Toast.makeText(context, "Otra aplicación es la causa de esta tostada.", Toast.LENGTH_LONG).show(); // Código que queremos ejecutar // a petición de la otra aplicación //... Sin olvidar declararlo en el AndroidManifest.xml, antes de cerrar la etiqueta de </application>: <receiver android:name="mibroadcastreceiver"> En este ejemplo se utiliza un PendingIntent para que el Alarm Manager pueda ejecutar el código de nuestro BroadcastReceiver. Este código pertenecería a algún método de la Activity: Intent intent = new Intent(this, MiBroadcastReceiver.class); PendingIntent pendingintent = PendingIntent.getBroadcast( this.getapplicationcontext(), 0, intent, 0); AlarmManager alarmmanager = (AlarmManager) getsystemservice(alarm_service); alarmmanager.set(alarmmanager.rtc_wakeup, System.currentTimeMillis()+5000, pendingintent); Android ofrece una serie de servicios predefinidos a los que se accede a través del método getsystemservice(string). La cadena debe ser una de las siguientes constantes: 44

45 WINDOW_SERVICE, LAYOUT_INFLATER_SERVICE, ACTIVITY_SERVICE, POWER_SERVICE, ALARM_SERVICE, NOTIFICATION_SERVICE, KEYGUARD_SERVICE, LOCATION_SERVICE, SEARCH_SERVICE, VIBRATOR_SERVICE, CONNECTIVITY_SERVICE, WIFI_SERVICE, INPUT_METHOD_SERVICE, UI_MODE_SERVICE, DOWNLOAD_SERVICE. En general los servicios obtenidos a través de esta API pueden estar muy relacionados con el contexto (Context) en el cuál fueron obtenidos, por tanto no conviene compartirlos con contextos diferentes (actividades, servicios, aplicaciones, proveedores) AlarmManager para programar servicios no sólo se puede utilizar para programar otros servicios sino que en la mayoría de los casos se debe utilizar. Un ejemplo sería el de programar un servicio que comprueba si hay correo electrónico o RSS. Se trata de una tarea periódica y el servicio no tiene por qué estar en ejecución todo el tiempo. De hecho un servicio que sólo se inicia con startservice() y nunca se finaliza con stopservice() se considera un "antipatrón de diseño" en Android. AlarmManager Si un servicio cumple ese antipatrón, es posible que Android lo mate en algún momento. Si un servicio de verdad requiere estar todo el tiempo en ejecución, como por ejemplo, uno de voz IP (require estar conectado y a la escucha todo el tiempo), entonces habría que iniciarlo como servicio foreground para que Android no lo mate nunca. El AlarmManager es la analogía del cron de Unix. La diferencia importante es que cron siempre continúa con su anterior estado mientras que AlarmManager empieza en blanco cada vez que arranca. Por tanto estamos obligados a volver a registrar nuestros servicios durante el arranque Comunicación entre procesos La comunicación entre procesos se puede realizar tanto invocando métodos como pasando objetos con información. Para pasar información de una actividad a un servicio se pueden utilizar los BroadcastReceiver, pasando información extra (Bundle) en el Intent que se utiliza para iniciar un servicio, o bien haciendo un binding al servicio. Vamos a empezar por el binding de una actividad a un servicio: Atar actividades a servicios (binding) Un tipo posible de comunicación es el de invocar directamente los métodos de un servicio a través de una referencia a este. Atar o enlazar (to bind) una actividad a un servicio consiste en mantener una referencia a la instancia del servicio, permitiendo a la actividad realizar llamadas a métodos del servicio igual que se harían a cualquier otra clase accesible desde la actividad. 45

46 Para que un servicio de soporte a binding hay que implementar su método onbind(). Éste devolverá una clase binder que debe implementar la interfaz IBinder. La implementación nos obliga a definir el método getservice() del binder. En este método devolveremos la instancia al servicio. A continuación se incluye un ejemplo de un servicio de este tipo: public class MiServicio extends Service { private final IBinder binder = new MiBinder(); public IBinder onbind(intent intent){ return binder; public class MiBinder extends Binder { MiServicio getservice() { return MiServicio.this; //... La conexión entre el servicio y la actividad es representada por un objeto de clase ServiceConnection. En la actividad que va a realizar el binding, hay que implementar una nueva clase hija, sobrecargando el método onserviceconnected() y onservicedisconnected() para poder obtener la referencia al servicio una vez que se ha establecido la conexión: private MiServicio servicio; //La referencia al servicio private ServiceConnection serviceconnection = new ServiceConnection() { public void onserviceconnected( ComponentName classname, IBinder service) { servicio = ((MiServicio.MiBinder)service).getService(); public void onservicedisconnected(componentname classname) { servicio = null; ; Para realizar el binding hay que pasarle el intent correspondiente al método bindservice(). El intent servirá para poder seleccionar qué servicio devolver. Tendríamos el siguiente código en el método oncreate() de nuestra actividad: Intent intent = new Intent(MiActividad.this, MiServicio.class); bindservice(intent, serviceconnection, Context.BIND_AUTO_CREATE); Una vez establecido el binding, todos los métodos y propiedades públicos del servicio estarán disponibles a través del objeto servicio del ejemplo. Podríamos realizar lo que queramos con él, bien iniciarlo con startservice y controlarlo con métodos a través del binding, o simplemente llamarlo para utilizar sus métodos. 46

47 Para finalizar tendríamos que "desatar" el servicio al finalizar (en el método "ondestroy") pasándole la variable que hemos utilizado para la conexión, en nuestro ejemplo sería "serviceconnection", de la forma: protected void ondestroy() { unbindservice(connection); Inter Process Communication (IPC) En Android cada aplicación se ejecuta en su propia "caja de arena" y no comparte la memoria con otras aplicaciones o procesos. Para comunicarse entre procesos Android implementa el mecanismo IPC (Inter Process Communication). El protocolo IPC requiere codificar y descodificar los distintos tipos de datos. Para facilitar esta parte, Android ofrece la posibilidad de definir los tipos de datos con AIDL (Android Interface Definition Language). De esta manera para comunicarse entre dos aplicaciones o procesos hay que definir el AIDL, a partir de éste implementar un Stub para la comunicación con un servicio remoto y por último dar al cliente acceso al servicio remoto. La interfaz de datos se define en un archivo ".aidl". Por ejemplo, el siguiente archivo de código estaría almacenado en "/src/es/ua/jtech/iservicio.aidl": package es.ua.jtech; interface IServicio { // Los valores pueden ser: in, out, inout. String saludo(in String nombre, in String apellidos); Una vez creado el anterior archivo, la herramienta AIDL generará un archivo ".java", que en nuestro ejemplo sería "/src/es/ua/jtech/iservicio.java". Esta herramienta se encuentra en la carpeta "platform-tools" del SDK. Es posible que el compilador nos diga que la clase AIDL ya existe (pues está en el fichero.aidl y el.java), para solucionar esto simplemente podemos renombrar el fichero ".aidl" a ".aidl.backup", puesto que ya no nos hace falta. En el servicio tenemos que implementar el método onbind() para que devuelva un stub que cumpla la interfaz anteriormente definida (en el IServicio), por ejemplo: public class Servicio extends Service { public IBinder onbind(intent intent) { return new IServicio.Stub() { public int saludo(string nombre, String apellidos) throws RemoteException { return "Hola " + nombre + " " + apellidos; ; 47

48 //... Ahora hay que obtener acceso al servicio desde la aplicación a través de ServiceConnection. Dentro del código de nuestra Activity, tendríamos: public class MiActividad extends Activity { IServicio servicio; MiServicioConnection connection; class MiServicioConnection implements ServiceConnection { public void onserviceconnected(componentname name, IBinder service) { servicio = IServicio.Stub.asInterface( (IBinder) service); public void onservicedisconnected( ComponentName name) { servicio = null; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); connection = new MiServicioConnection(); // El intent se crea así porque la clase // podría estar en otra aplicación: Intent intent = new Intent(); intent.setclassname("es.ua.jtech", es.ua.jtech.servicio.class.getname()); bindservice(intent, connection, Context.BIND_AUTO_CREATE); String saludo = servicio.saludo( "MiNombre", "MiApellido"); //... protected void ondestroy() { unbindservice(connection); Los tipos que se permiten en AIDL son: Valores primitivos como int, float, double, boolean, etc. String y CharSequence java.util.list y java.util.map Intefaces definidos en AIDL, requiere definir el import. Clases Java que implementen la interfaz Parcelable que Android usa para permitir la 48

49 serialización. También requiere definir el import. Si el servicio está en otro proyecto necesitaremos declararlo con un intent-filter en su Manifest: <service android:export="true" name="es.ua.jtech.servicio"> <intent-filter> <action android:name="es.ua.jtech.iservicio"/> </intent-filter> </service> y crear el intent con ese nombre de acción: Intent intent = new Intent("es.ua.jtech.IServicio"); Otras formas de comunicación Broadcast privados Enviar un Intent a través del método sendbroadcast() es sencillo pero por defecto cualquier aplicación podría tener un BroadcastReceiver que obtenga la información de nuestro intent. Para evitarlo se puede utilizar el método Intent.setPackage() que restringirá el broadcast a determinado paquete PendingResult Otra manera de que un servicio envíe información a una actividad es a través del método creatependingresult(). Se trata de un método que permite a un PendingIntent disparar el método Activity.onActivityResult. Es decir, el servicio remoto llamaría a send() con un PendingIntent con la información, de manera análoga al setresult() que ejecuta una actividad que fue llamada con startactivityforresult(). Dentro de Activity.onActivityResult se procesaría la información del Intent. Este es un mecanismo que sólo funciona con actividades. 49

50 6. Ejercicios - Servicios 6.1. Contador: Servicio con proceso en background (0.6 puntos) Los servicios se utilizan para ejecutar algún tipo de procesamiento en background. En este ejercicio vamos a crear nuestro propio proceso asociado a un servicio que ejecutará una determinada tarea. En este caso el proceso asociado contará desde 1 hasta 100 deteniéndose 5 segundos antes de cada incremento. Cada vez que se cambie de valor se mostrará un Toast que nos informe de la cuenta. En las plantillas tenemos el proyecto android-av-serviciocontador que ya incluye la declaración del servicio en el manifest, la actividad que inicia y detiene el servicio, y el esqueleto del servicio MiCuentaServicio. En el esqueleto que se proporciona, viene definida una extensión de AsyncTask llamada MiTarea. Los métodos onpreexecute, doinbackground, onprogressupdate y oncancelled están sobrecargados pero están vacíos. Se pide implementarlos, el primero de ellos tiene que inicializar el campo i que se utiliza para la cuenta, el segundo tiene que ejecutar un bucle desde 1 hasta 100, y en cada iteración pedir que se muestre el progreso (publishprogress) y hacer una pausa de 5 segundos con Thread.sleep(5000). El tercer método, onprogressupdate mostrará el Toast con el progreso, y por último el método de cancelación pondrá el valor máximo de la cuenta para que se salga del bucle. En los métodos del servicio, oncreate, onstartcommand y ondestroy, introduciremos la creación de la nueva MiTarea, su ejecución (método execute() de la tarea) y la cancelación de su ejecución (método cancel() de la tarea). El servicio deberá seguir funcionando aunque se salga de la aplicación y podrá ser parado entrando de nuevo en la aplicación y pulsando Stop Broadcast Receiver: Captura de llamadas (0.6 puntos) Se pide crear un proyecto android-av-broadcast que capture los números de teléfono de las llamadas entrantes y los muestre a través del LogCat. El proyecto tendrá un único fichero de código Java (llamado MiBroadcastReceiver) que implementará un BroadcastReceiver. En primer lugar añadiremos esta clase al Manifest, y le asignaremos un intent-filter que capture las acciones de tipo: "android.intent.action.phone_state". Además es necesario solicitar en el Manifest permisos para leer el estado del teléfono, para esto tenemos que añadir: "<uses-permission android:name="android.permission.read_phone_state"/>". En el método "onreceive" de nuestra clase "MiBroadcastReceiver" comprobaremos que el evento recibido es del tipo esperado: 50

51 String action = intent.getaction(); if(action.equals("android.intent.action.phone_state") { \\... Si es de este tipo obtendremos el número de teléfono de la llamada a partir de los extras que recibimos en el intent (revisar la sección correspondiente de teoría) y lo mostraremos en el Log. Al ejecutar esta aplicación Eclipse nos mostrará los siguientes avisos: "No Launcher activity found!", "The launch will only sync the application package on the device!". Esto es normal debido a que no hay una actividad principal que pueda mostrar, sin embargo sí que instalará la aplicación. Para comprobar que tu aplicación funciona correctamente abre la vista "Emulator Control" (Window > Show view > Other... > "Emulator Control") y realiza una llamada de prueba. Nota A partir de la versión 3.1 de Android es necesario que se incluya una actividad visible (además de la clase con el BroadcastReceiver) para que sea efectuado el registro del receiver. En esta actividad podríamos poner algún texto explicativo como: "Se ha registrado el servicio" Broadcast Receiver: Reenvío de datos (0.6 puntos) Vamos a ampliar el ejercicio anterior para que al recibir una llamada envíe datos mediante un SMS a un número de teléfono. El servicio podría, por ejemplo, enviar la posición GPS cuando recibe la llamada de un número específico. En este ejercicio, por simplificar, enviaremos un SMS cuando se reciba una llamada desde cualquier número y como texto del SMS se incluirá el propio número que ha llamado. En primer lugar modificamos el Manifest de la aplicación anterior para solicitar permisos para el envío de SMSs: <uses-permission android:name="android.permission.send_sms"/> En el fichero de código del broadcast ("MiBroadcastReceiver") añadimos la siguiente función para el envío de SMSs: public void send( String phonenumber, String message ) { SmsManager sms = SmsManager.getDefault(); sms.sendtextmessage( phonenumber, null, message, null, null ); Log.i( "SendSMS", "Enviando SMS a " + phonenumber + ": \"" + message + "\"" ); Por último modificamos el código del método "onreceive" para que además de mostrar el número de la llamada por el Log lo envíe por SMS. 51

52 Para probar este ejercicio tenemos que abrir dos emuladores. En uno de ellos instalamos el servicio y utilizamos el segundo emulador para realizar la llamada y comprobar que se recibe el SMS correctamente. Para realizar una llamada de un emulador a otro simplemente hay que marcar el número del puerto que aparece en el título de la ventana de cada emulador Arranque: Iniciar servicio al arrancar el móvil (0.6 puntos) En el proyecto android-av-onboot tenemos un servicio que se lanza al iniciar la actividad, mostrando Toasts del 1 al 5 y finalizando. Puedes comprobar ejecutando este proyecto como al arrancar o instalar la actividad se muestran estos Toast de aviso. Se pide modificar el código para que el servicio se inicie cuando el móvil haya terminado de arrancar. Para ello añadiremos el siguiente permiso al AndroidManifest.xml: <uses-permission android:name="android.permission.receive_boot_completed" /> También registraremos un receptor de broadcast MiBroadcastReceiver con un intent-filter para android.intent.action.boot_completed. A continuación implementaremos un BroadcastReceiver (llamado MiBroadcastReceiver) y en su método onreceive() introduciremos el código: if( "android.intent.action.boot_completed".equals(intent.getaction())) { ComponentName comp = new ComponentName(context.getPackageName(), MiServicio.class.getName()); ComponentName service = context.startservice( new Intent().setComponent(comp)); if (null == service){ Log.e(MiBroadcastReceiver.class.toString(), "Servicio no iniciado: " + comp.tostring()); else { Log.e(MiBroadcastReceiver.class.toString(), "Intent no esperado " + intent.tostring()); Tras ejecutar el proyecto desde Eclipse habrá que reiniciar el emulador para observar que el servicio se inicia al finalizar el arranque. El botón de apagar del emulador no funcionará, lo mejor es cerrar el emulador y volver a abrirlo Calculadora: Comunicación con el servicio (0.6 puntos) En este ejercicio vamos a practicar con los métodos de comunicación con un servicio. Para esto vamos a implementar una calculadora que a partir de un par de números de entrada realice su suma, resta, multiplicación o división utilizando un servicio. 52

53 En primer lugar creamos el proyecto y en la interfaz de la aplicación insertamos dos campos de edición (donde el usuario escribirá los operandos), un campo de texto vacío (donde se mostrará el resultado) y cuatro botones (para la suma, resta, multiplicación y división). A continuación añadiremos un servicio, llamado "MiServicioBind", en el cual implementaremos la clase onbind para poder realizar el binding. Además le añadiremos cuatro métodos públicos que a partir de dos números decimales devuelvan su suma, resta, multiplicación o división respectivamente. Además recordad que tenéis que añadir el servicio al Manifest del proyecto. En el código de la actividad principal, además de obtener referencias a los campos de edición y los botones e implementar sus métodos onclick, tendremos que atar el servicio que hemos creado. En los listener de los botones llamaremos al método correspondiente y mostraremos el resultado en el textview. Por último, tendremos que desatar el servicio al destruir la actividad. 53

54 7. AppWidgets En los escritorios de Android es posible colocar un tipo especial de aplicaciones denominadas Widgets. Estos Widgets permiten mostrar al usuario una información (o una especie de interfaz reducida) sin necesidad de abrir una aplicación. Ocupan un determinado tamaño en el escritorio, por lo que es posible tener en el mismo escritorio colocados a la vez varios de estos elementos. Son muy útiles para mostrar información rápida al usuario, como por ejemplo la hora, los últimos correos, últimos eventos del calendario, etc., y que al pulsar sobre ellos se abra la aplicación completa para poder interactuar AppWidgets Los widgets, que desde el punto de vista del programador son AppWidgets, son pequeños interfaces de programas Android que permanecen en el escritorio del dispositivo móvil. Para añadir alguno sobra con hacer una pulsación larga sobre un área vacía del escritorio y seleccionar la opción "widget", para que aparezca la lista de todos los que hay instalados y listos para añadir. En la versión 4 de Android para añadir widgets hay que ir al menú de aplicaciones y desde la sección de widgets seleccionar uno y pulsarlo prolongadamente para arrastrarlo a la zona deseada de la pantalla de inicio. Seleccionar un (App) Widget Este es un ejemplo de un widget en el que se muestra un reloj, incluido de serie con Android: 54

55 AppWidget reloj de Android 7.2. Crear un Widget Para crear un widget se necesitan varios elementos: Una definición o configuración de los metadatos del Widget en XML. Un layout para definir su interfaz gráfica. La implementación de la funcionalidad del Widget (un IntentReceiver). Declaración del Widget en el Android Manifest de la aplicación Definición XML del Widget Los AppWidgets ocupan un determinado tamaño y se refrescan con una determinada frecuencia, estos son metadatos que hay que declarar en el XML que define el widget. Se puede añadir como nuevo recurso XML de Android, y seleccionar el tipo Widget. Se coloca en la carpeta res/xml/. A continuación se incluye la definición XML guardada en "res/xml/miwidget_conf.xml" de nuestro ejemplo: <?xml version="1.0" encoding="utf-8"? > <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:minwidth="146dip" android:minheight="72dip" android:updateperiodmillis=" " android:label="mi Widget" /> Las propiedades que hemos definido en este XML son las siguientes: minwidth y minheight: ancho y alto mínimo que ocupará el Widget en el escritorio. updateperiodmillis: frecuencia de actualización del Widget en milisegundos. label: nombre del Widget que se mostrará en el menú de selección de Android. initiallayout: referencia al layout XML con la interfaz del Widget. La pantalla de inicio de Android por defecto está dividida en una matriz de 4x4 celdas donde se pueden colocar aplicaciones, accesos directos y Widgets. Los Widgets, como hemos dicho, pueden ocupar más de una celda. Estas celdas tienen una dimensión de 74x74 dip (píxeles independientes del dispositivo). Para indicar el número de celdas que va a ocupar el Widget tenemos que calcular el número de "dip" que va a ocupar. La fórmula para calcular esto es muy sencilla: ancho_minimo = (num_celdas_ancho * 74) - 2 alto_minimo = (num_celdas_alto * 74)

56 Los 2 dip que se le restan son por el borde. Por ejemplo, en nuestro caso, para que ocupe 2 celdas de ancho por 1 celda de alto tenemos que indicar una dimensiones de 146dip x 72dip. En los dispositivos donde las dimensiones mínimas que establezcamos no coincidan exactamente con las celdas de la pantalla, el tamaño de nuestro widget será extendido para llenar lo que queda libre de las celdas. La frecuencia de actualización del Widget (updateperiodmillis) tiene un mínimo permitido: 30 minutos ( milisegundos). Además, si el dispositivo está en reposo cuando se realiza la actualización éste se despertará, por lo que puede que se incremente el gasto de batería si se realiza con mucha frecuencia. Es aconsejable, para este tipo de prácticas, utilizar en su lugar intents programados mediante un AlarmManager (como veremos más adelante) Layout del Widget En el XML de configuración de ejemplo, en el campo "initiallayout", se indica que el layout del widget se encuentra en "res/layout/miwidget_layout.xml". Este es el layout que define la interfaz gráfica del Widget, su diseño es análogo al del interfaz de cualquier aplicación normal, pero con unas pequeñas restricciones. Por cuestiones de diseño de Android, relacionadas con seguridad y eficiencia, los únicos componentes gráficos permitidos en el layout del widget son: Los layouts: FrameLayout, LinearLayout y RelativeLayout. Los views: TextView, Button, ImageButton, ImageView, ProgressBar, AnalogClock y Chronometer. Es interesante el hecho de que los EditText no están permitidos en el layout del widget. Para conseguir este efecto lo que se suele hacer es utilizar una imagen que imite este view y al hacer click, abrir una actividad semi-transparente por encima que sea la que nos permita introducir el texto. Desde la versión 3.0 de Android los widget cuentan con nuevas características y mayor interactividad. Los nuevos componentes que se pueden utilizar son GridView, ListView, StackView, ViewFlipper, AdapterViewFlipper. Por ejemplo StackView no estaba presente entre los componentes de las versiones anteriores y es un view que permite mostrar "tarjetas" e ir pasándolas hacia delante o hacia atrás, como se muestra a continuación: 56

57 Widget con StackView Un ejemplo de layout podría ser el siguiente, en el que se coloca un reloj analógico y un campo de texto al lado: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content"> <AnalogClock android:layout_width="45dp" android:layout_height="45dp" /> <TextView android:text="" android:textsize="9dp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:shadowcolor="#000000" android:shadowradius="1.2" android:shadowdx="1" android:shadowdy="1"> </TextView> </LinearLayout> Implementación de la funcionalidad del Widget Los AppWidgets no necesitan ninguna actividad, sino que se implementan como IntentReceivers con IntentFilters que detectan intents con acciones de actualización de widget como AppWidget.ACTION_APPWIDGET_UPDATE, DELETED, ENABLED, DISABLED, así como con acciones personalizadas. 57

58 Android provee la clase AppWidgetProvider (clase derivada de BroadcastReceiver) que es una alternativa proporcionada por Android para encapsular el procesamiento de los Intent y proveer de handlers para la actualización, borrado, habilitación y deshabilitación del widget. En esta clase deberemos implementar los mensajes a los que vamos a responder desde nuestro widget, entre los que destacan: onenabled(): lanzado cuando se añade al escritorio la primera instancia de un widget. onupdate(): lanzado periódicamente cada vez que se debe actualizar un widget. ondeleted(): lanzado cuando se elimina del escritorio una instancia de un widget. ondisabled(): lanzado cuando se elimina del escritorio la última instancia de un widget. En la mayoría de los casos, tendremos que implementar como mínimo el evento onupdate(). El resto de métodos dependerán de la funcionalidad de nuestro widget. En nuestro ejemplo de momento solo vamos a implementar el método "onupdate", aunque lo dejaremos vacío. A continuación se incluye un ejemplo del código de nuestro Widget: public class MiWidget extends AppWidgetProvider { public void onupdate(context context, AppWidgetManager appwidgetmanager, int[] appwidgetids) { // Actualizar el Widget Manifest El último paso es declarar el Widget dentro del AndroidManifest.xml de nuestra aplicación. No necesitamos declarar una actividad principal, sino que solamente tendremos que declarar el receptor de intents de nuestro widget: <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versioncode="1" android:versionname="1.0" package="es.ua.jtech.av.appwidget"> <uses-sdk android:minsdkversion="7" /> <application <receiver android:name=".miwidget" android:label="frases Widget"> <intent-filter> <action android:name="android.appwidget.action.appwidget_update" /> </intent-filter> <intent-filter> <action android:name="es.ua.jtech.av.action_widget_click" /> </intent-filter> <meta-data android:name="android.appwidget.provider" 58

59 /> </receiver> </application> </manifest> En el ejemplo anterior de manifest se declara nuestro Widget como un elemento "receiver" dentro de la sección "application". En la etiqueta "name" se indica el nombre de la clase java con el código del receptor. Además se añaden dos secciones importantes, una sección "meta-data" donde en "resource" se tiene que indicar el fichero XML que contiene la descripción del Widget; y otra sección de "intent-filter" para la captura de eventos. En este ejemplo se capturará el evento de actualización del widget y un evento personalizado "es.ua.jtech.av.action_widget_click" (que veremos más adelante). Llegados a este punto ya es posible probar el Widget, añadir instancias al escritorio, desplazarlo por la pantalla o eliminarlo Actualización del Widget Para realizar la actualización del Widget tenemos que modificar su fichero de código para completar el método "onupdate" que habíamos dejado vacío en el ejemplo anterior. Es importante tener en cuenta que es posible que hayan varias instancias de un mismo widget añadidas al escritorio, por lo que tendremos que actualizarlas todas. El método "onupdate" recibe como parámetro un array con la lista de Widgets que hay que actualizar. Por lo que de forma genérica podríamos realizar la actualización de la forma: public void onupdate(context context, AppWidgetManager appwidgetmanager, int[] appwidgetids) { //Iteramos la lista de widgets en ejecución for (int i = 0; i < appwidgetids.length; i++) { //ID del widget actual int widgetid = appwidgetids[i]; //Actualizamos el widget actual actualizarwidget(context, appwidgetmanager, widgetid); private void actualizarwidget(context context, AppWidgetManager appwidgetmanager, int widgetid) { // Actualizar un Widget... Al extraer la actualización a un método estático independiente podemos reutilizar este código para llamarlo desde otras partes de la aplicación, como por ejemplo para realizar la actualización inicial. Para realizar la actualización de las vistas de nuestro Widget necesitamos utilizar una clase especial llamada RemoteViews. A continuación se explica este proceso de 59

60 actualización RemoteViews La clase RemoteViews se utiliza para definir y manipular una jerarquía de views que se encuentra en el proceso de una aplicación diferente. Permite cambiar propiedades y ejecutar métodos. En el caso de los widgets, los views están alojados en un proceso (normalmente perteneciente a la pantalla del Home) y hay que actualizarlos desde el IntentReceiver que definimos para controlar el widget. La actualización de los valores de los views se realiza por medio de los métodos de la clase RemoteViews, tales como.settextviewtext(),.setimageviewbitmap(), etc. En primer lugar crearemos un objeto de tipo RemoteViews al que le pasaremos el identificador del layout de nuestro Widget, con este objeto ya podemos actualizar las vistas de este layout, por ejemplo: RemoteViews updateviews = new RemoteViews( context.getpackagename(), R.layout.miwidget_layout); updateviews.settextviewtext(r.id.textview01, "Mensaje de prueba"); RemoteViews proporciona métodos para acceder a propiedades como setint(...), setboolean(...), setbitmap(...), etc, y métodos para acceder a Views específicos, como settextviewtext(...), settextcolor(...), setimageviewbitmap(...), setchronometer(...), setprogressbar(...), setviewvisibility(...). Una vez definido el cambio a realizar por medio de RemoteViews, tenemos que aplicar estas modificaciones utilizando el appwidgetmanager que recibimos como parámetro. Esto es importante, ya que de no hacerlo la actualización de los controles no se reflejará correctamente en la interfaz del widget. En el siguiente código se muestra un ejemplo completo en el que se actualiza el campo de texto del Widget con la hora actual: private void actualizarwidget(context context, AppWidgetManager appwidgetmanager, int widgetid) { RemoteViews updateviews = new RemoteViews( context.getpackagename(), R.layout.miwidget_layout); //Obtenemos la hora actual Calendar calendario = new GregorianCalendar(); String hora = calendario.gettime().tolocalestring(); //Actualizamos la hora en el control del widget updateviews.settextviewtext(r.id.textview01, hora); //Notificamos al manager de la actualización del widget actual appwidgetmanager.updateappwidget(widgetid, updateviews); 7.4. Eventos de actualización 60

61 A los controles utilizados en los widgets de Android, que ya sabemos que son del tipo RemoteView, no podemos asociar eventos de la forma tradicional. Sin embargo, tenemos la posibilidad de asociar a un evento (por ejemplo la realización de un click) una determinada acción de tipo broadcast (PendingIntent) que será lanzada cada vez que se produzca dicho evento. Podemos configurar el propio widget para que capture esos mensajes (ya que como indicamos no es más que un componente de tipo broadcast receiver). Para esto tenemos que implementar su método "onreceive()" con las acciones necesarias a ejecutar tras capturar el mensaje. De esta forma "simulamos" la captura de eventos sobre controles de un widget. En primer lugar hacemos que se lance un intent broadcast cada vez que se pulse sobre el widget o algún elemento del mismo. Para ello, en el método "actualizarwidget()" construiremos un nuevo Intent asociándole una acción personalizada, que en nuestro caso llamaremos "es.ua.jtech.av.action_widget_click". Como parámetro del nuevo Intent insertaremos mediante putextra() el ID del widget actual de forma que más tarde podamos saber el widget concreto que ha lanzado el mensaje. Por último crearemos el PendingIntent mediante el método getbroadcast() y lo asociaremos al evento onclick del control utilizando setonclickpendingintent() y pasándole el ID del control. Veamos cómo queda todo esto: // Asociamos los 'eventos' al widget Intent intent = new Intent("es.ua.jtech.av.ACTION_WIDGET_CLICK"); intent.putextra(appwidgetmanager.extra_appwidget_id, widgetid); PendingIntent pendingintent = PendingIntent.getBroadcast(context, widgetid, intent, PendingIntent.FLAG_UPDATE_CURRENT); updateviews.setonclickpendingintent(r.id.miwidget, pendingintent); Es importante que la acción de este intent se añada al Manifest, dentro de la sección "receiver" del Widget, tendremos que declarar otro "intent-filter" para que capture este tipo de eventos y responda a ellos: <intent-filter> <action android:name="es.ua.jtech.av.action_widget_click" /> </intent-filter> Por último tenemos que implementar el evento "onreceive()" del widget para actuar en caso de recibir nuestro mensaje de actualización personalizado. Dentro de este evento comprobaremos si la acción del mensaje recibido es la nuestra, y en ese caso recuperaremos el ID del widget que lo ha lanzado, obtendremos una referencia al "widget manager", y por último llamamos a nuestro método estático de actualización pasándole estos datos: public void onreceive(context context, Intent intent) { if (intent.getaction().equals("es.ua.jtech.av.action_widget_click")) 61

62 { //Obtenemos el ID del widget a actualizar int widgetid = intent.getintextra( AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); //Obtenemos el widget manager de nuestro contexto AppWidgetManager widgetmanager = AppWidgetManager.getInstance(context); //Actualizamos el widget if (widgetid!= AppWidgetManager.INVALID_APPWIDGET_ID) actualizarwidget(context, widgetmanager, widgetid); Con esto ya hemos finalizado la emisión y captura de eventos al pulsar sobre el Widget (o algún view de este Widget). Dentro del método "actualizarwidget" realizaremos las acciones de actualización, que dependerán del tipo de widget en cuestión que estemos implementando. Para actualizaciones periódicas (con una frecuencia inferior a 30 min) se puede utilizar el AlarmManager que podría ser programado para forzar la actualización del widget, enviando periódicamente un broadcast con la acción que hayamos definido para tal fin Abrir una aplicación Al pulsar sobre un widget, en lugar de lanzar un intent tipo broadcast, también podríamos lanzar un intent que abra un actividad determinada, simplemente realizando: //Comportamiento al pulsar el widget: lanzar alguna actividad Intent defineintent = new Intent(context, Actividad.class); PendingIntent pendingintent = PendingIntent.getActivity( context, 0, defineintent, 0); updateviews.setonclickpendingintent(r.id.miwidget, pendingintent); 7.5. Servicio de actualización Otra alternativa para refrescar la interfaz gráfica es el uso de un servicio que actualice el widget. Para ello habría que declararlo en el manifest y declarar la clase del servicio UpdateService extends Service dentro de la clase MiWidget. A continuación se muestra un ejemplo de la clase MiWidget que implementa un widget: public class MiWidget extends AppWidgetProvider { public void onupdate(context context, AppWidgetManager appwidgetmanager, int[] appwidgetids) { // Inicio de nuestro servicio de actualización: context.startservice( new Intent(context, UpdateService.class)); 62

63 public static class UpdateService extends Service { public int onstartcommand(intent intent, int flags, int startid) { RemoteViews updateviews = new RemoteViews( getpackagename(), R.layout.miwidget_layout); //Aqui se actualizarían todos los tipos de Views: updateviews.settextviewtext(r.id.textview01, "Valor con el que refrescamos"); //... //Y la actualización a través del updateviews creado: ComponentName thiswidget = new ComponentName( this, MiWidget.class); AppWidgetManager.getInstance(this).updateAppWidget( thiswidget, updateviews); return Service.START_STICKY; public IBinder onbind(intent intent) { return null; 7.6. Actividad de configuración Algunos widgets muestran una actividad de configuración la primera vez que son añadidos a la pantalla. Cuando el sistema solicita la configuración de un widget se recibe un broadcast intent con la acción android.appwidget.action.appwidget_configure, por tanto definiremos en el manifest nuestra actividad con el intent-filter correspondiente, que la ejecutará al recibir la acción: <activity android:name=".miconfigurationactivity"> <intent-filter> <action android:name="android.appwidget.action.appwidget_configure"/> </intent-filter> </activity> Esta actividad es una actividad normal, que podemos crear utilizando todos los conceptos que hemos visto, recoger los datos del usuario y almacenarlos de alguna forma (por ejemplo utilizando "SharedPreferences"). Para finalizar la actividad podemos colocar botones para aceptar o cancelar. En el caso de que se cancele tendríamos que realizar lo siguiente: setresult(result_canceled); finish(); 63

64 Si se presiona el botón de aceptar la actividad debe devolver un Intent que incluya un extra con el identificador del App Widget, usando la constante EXTRA_APPWIDGET_ID (se puede obtener a partir del Intent que lanza la actividad). A continuación se muestra el código que realizaría esta acción: //Obtenemos el Intent que ha lanzado esta ventana //y recuperamos sus parámetros Intent intentorigen = getintent(); Bundle params = intentorigen.getextras(); //Obtenemos el ID del widget que se está configurando int widgetid = params.getint( AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); //Desde el botón aceptar... Intent resultado = new Intent(); resultado.putextra(appwidgetmanager.extra_appwidget_id, widgetid); setresult(result_ok, resultado); finish(); Esta actividad de configuración también debe ser declarada en el xml que contiene los metadatos del widget: <appwidget-provider... android:updateperiodmillis=" " android:configure="es.ua.jtech.av.appwidget.miconfigurationactivity" /> La actividad será mostrada solamente al añadir el widget a la pantalla por primera vez. 64

65 8. Ejercicios - AppWidgets 8.1. IP AppWidget (1.5 puntos) Vamos abrir el proyecto android-av-appwidget para construir un AppWidget de Android, que nos muestre una frase célebre y la hora. En el proyecto pulsamos con el botón derecho y añadimos un nuevo "Android XML File", de tipo "AppWidget Provider", que se llame miwidget_conf.xml. El editor nos permite pulsar sobre el "AppWidget Provider" y editar sus atributos. Ponemos los siguientes: android:minwidth="146dip" android:minheight="72dip" android:updateperiodmillis=" " El layout miwidget_layout.xml no lo tenemos que crear porque ya está incluido en el proyecto. Creamos una clase Java llamada MiWidget que herede de AppWidgetProvider, en el paquete es.ua.jtech.av.appwidget. Sobrecargamos su método onupdate(...) y su método onreceive(...). En este último comprobaremos si se ha recibido un intent con la acción personalizada "es.ua.jtech.av.action_widget_click": public class MiWidget extends AppWidgetProvider { public static final String ACTION_WIDGET_CLICK = "es.ua.jtech.av.action_widget_click"; public void onupdate(context context, AppWidgetManager appwidgetmanager, int[] appwidgetids) { //TODO actualizar public void onreceive(context context, Intent intent) { super.onreceive(context, intent); if(intent.getaction().equals(action_widget_click)){ //TODO actualizar Tanto desde el método "onupdate()" como desde "onreceive()" tenemos que actualizar la vista de la misma forma, por lo que vamos a crear un método privado de la clase "private void actualizarwidget(context context, AppWidgetManager appwidgetmanager, int widgetid)" que llamaremos desde ambos métodos tal y como hemos visto en la teoría. 65

66 El método privado utilizará los RemoteViews para cambiar el campo de texto. Lo actualizará con una frase aleatoria de entre las definidas dentro de un array de strings en el recurso strings.xml que viene incluido en el proyecto de las plantillas. También se asignará un comportamiento al hacer click sobre el widget que consistirá en enviar un broadcast intent con la acción que hemos definido: RemoteViews updateviews = new RemoteViews( context.getpackagename(), R.layout.miwidget_layout); //Seleccionar frase aleatoria String frases[] = context.getresources().getstringarray(r.array.frases); updateviews.settextviewtext(r.id.textview01, frases[(int)(math.random()*frases.length)]); //Comportamiento del botón //On-click listener que envía un broadcast intent Intent intent = new Intent(ACTION_WIDGET_CLICK); PendingIntent pendingintent = PendingIntent.getBroadcast( context, widgetid, intent, PendingIntent.FLAG_UPDATE_CURRENT); updateviews.setonclickpendingintent(r.id.miwidget, pendingintent); //Notificamos al manager de la actualización del widget actual appwidgetmanager.updateappwidget(widgetid, updateviews); En el AndroidManifest.xml, dentro de <application> declararemos el "receiver" de nuestro widget con dos "intent filters", uno para la acción del sistema android.appwidget.action.appwidget_update, y otro para la que utilizamos para forzar la actualización desde la clase MiWidget: <receiver android:name=".miwidget" android:label="frases Widget"> <intent-filter> <action android:name="android.appwidget.action.appwidget_update" /> </intent-filter> <intent-filter> <action android:name="es.ua.jtech.av.action_widget_click" /> </intent-filter> <meta-data android:name="android.appwidget.provider" /> </receiver> Ejecutamos el widget desde Eclipse, como aplicación Android, y comprobamos que no ocurra ningún error en la consola de Eclipse. Ya se puede añadir el widget en el escritorio, efectuando una pulsación larga sobre una porción de área libre del escritorio, y seleccionando nuestro widget. En Android 4.0 la pulsación larga no nos muestra la opción de añadir widget sino que hay que ir al menú de aplicaciones y seleccionar la pestaña de widgets. Para insertar uno se tiene que pulsar prolongadamente para arrastrarlo a una zona libre del escritorio. Añadimos el widget y observamos el resultado: 66

67 Widget que muestra una frase aleatoria La frase se tiene que actualizar al pulsar sobre el Widget. Prueba a añadir dos Widgets del mismo tipo y a pulsar sobre cada uno de ellos para ver como cambian de forma independiente StackWidget (1.5 puntos) Vamos a probar una de las características novedosas de los widgets a partir de Android 3.0, se trata del StackView que nos permite pasar "tarjetas" hacia delante y hacia atrás. Ejecuta el proyecto android-av-stackwidget de las plantillas y comprueba que funciona correctamente en un emulador con alguna de las últimas versiones de Android. Si probamos a ejecutarlo en una versión antigua nos aparecerá un error. Vamos a modificar el widget para que muestre las frases del ejercicio anterior. Para ello tenemos que copiar y pegar el array de strings del recurso strings.xml del ejercicio anterior. A continuación editamos la clase StackWidgetService y le añadimos un campo "private String [] frases;". Este array lo podemos inicializar en en el constructor: frases = context.getresources().getstringarray(r.array.frases); En el método oncreate sustituiremos el mwidgetitems.add(new WidgetItem(...)) por un de frases. El resultado debe quedar así: bucle con las llamadas a bucle que añada todas las cadenas 67

68 StackWidget con frases 68

69 9. Notificaciones En esta sección vamos a tratar las distintas formas que tenemos de enviar notificaciones a los usuarios, desde notificaciones en la misma pantalla mediante ventanas de diálogo o avisos denominados Toast, hasta notificaciones más avanzadas que serán mostradas en la barra de estado del sistema (o bandeja del sistema) y que podrán ser emitidas aun cuando la aplicación no esté en primer plano Notificaciones Toast Un toast es un mensaje que se muestra en pantalla durante unos segundos al usuario para luego volver a desaparecer automáticamente sin requerir ningún tipo de actuación por su parte, y sin recibir el foco en ningún momento (o dicho de otra forma, sin interferir en las acciones que esté realizando el usuario en ese momento). Aunque son personalizables, aparecen por defecto en la parte inferior de la pantalla, sobre un rectángulo gris ligeramente translúcido. Por sus propias características, este tipo de notificaciones son ideales para mostrar mensajes rápidos y sencillos al usuario, pero por el contrario, al no requerir confirmación por su parte, no deberían utilizarse para hacer notificaciones demasiado importantes. Para crear un mensaje de este tipo haremos uso de la clase Toast. Esta clase dispone de un método estático maketext() al que deberemos pasar como parámetro el contexto de la actividad, el texto a mostrar, y la duración del mensaje, que puede tomar los valores LENGTH_LONG (3.5 segundos) o LENGTH_SHORT (2 segundos), dependiendo del tiempo que queramos que la notificación aparezca en pantalla. Tras obtener una referencia al objeto Toast a través de este método, ya sólo nos quedaría mostrarlo en pantalla mediante el método show(). A continuación se incluye un ejemplo de uso: btndefecto.setonclicklistener(new OnClickListener() { public void onclick(view arg0) { Toast toast1 = Toast.makeText(getApplicationContext(), "Toast por defecto", Toast.LENGTH_SHORT); toast1.show(); ); Si ejecutamos esta sencilla aplicación en el emulador y pulsamos el botón veremos como en la parte inferior de la pantalla aparece el mensaje "Toast por defecto", que tras varios segundos desaparecerá automáticamente. 69

70 Toast Como hemos comentado, éste es el comportamiento por defecto de las notificaciones toast, sin embargo también podemos personalizarlo cambiando su posición en la pantalla. Para esto utilizaremos el método setgravity(), al que podremos indicar en que zona deseamos que aparezca la notificación. La zona deberemos indicarla con alguna de las constantes definidas en la clase Gravity: CENTER, LEFT, BOTTOM,... o con alguna combinación de éstas. Como ejemplo vamos a colocar la notificación en la zona central izquierda de la pantalla. Para ello, añadamos un segundo botón a la aplicación de ejemplo que muestre un toast con estas características: btngravity.setonclicklistener(new OnClickListener() { public void onclick(view arg0) { Toast toast2 = Toast.makeText(getApplicationContext(), "Toast con gravity", Toast.LENGTH_SHORT); toast2.setgravity(gravity.center Gravity.LEFT,0,0); toast2.show(); ); Si volvemos a ejecutar la aplicación y pulsamos el nuevo botón veremos como el toast aparece en la zona indicada de la pantalla: 70

71 Toast Android nos permite personalizar por completo el aspecto de la notificación definiendo un layout XML para el toast. En este layout podremos incluir todos los elementos necesarios para adaptar la notificación a nuestras necesidades. Vamos a ampliar el ejemplo incluyendo un layout sencillo, con una imagen y una etiqueta de texto sobre un rectángulo gris: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="horizontal" android:background="#555555" android:padding="5dip" > <ImageView android:layout_height="wrap_content" android:layout_width="wrap_content" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:textcolor="#ffffff" android:paddingleft="10dip" /> </LinearLayout> 71

72 Guardaremos este layout con el nombre "toast_layout.xml" en la carpeta "res\layout" de nuestro proyecto. Para asignar este layout a nuestro toast en primer lugar tenemos que inflar el layout mediante un objeto LayoutInflater. Una vez construido el layout modificaremos los valores de los distintos controles para mostrar la información que queramos. En nuestro caso, tan sólo modificaremos el mensaje de la etiqueta de texto, ya que la imagen ya la asignamos de forma estática en el layout XML mediante el atributo android:src. Tras esto, sólo nos quedará establecer la duración de la notificación con setduration() y asignar el layout personalizado al toast mediante el método setview(). A continuación se muestra como queda el código incluyendo todos estos cambios: btnlayout.setonclicklistener(new OnClickListener() { public void onclick(view arg0) { Toast toast3 = new Toast(getApplicationContext()); LayoutInflater inflater = getlayoutinflater(); View layout = inflater.inflate(r.layout.toast_layout, (ViewGroup) findviewbyid(r.id.lytlayout)); TextView txtmsg = (TextView)layout.findViewById(R.id.txtMensaje); txtmsg.settext("toast Personalizado"); toast3.setduration(toast.length_short); toast3.setview(layout); toast3.show(); ); Si ejecutamos ahora la aplicación de ejemplo y pulsamos el nuevo botón, veremos como el toast aparece con la estructura definida en nuestro layout personalizado. 72

73 Toast 9.2. Notificaciones de la Barra de Estado Otro mecanismo de comunicación con el usuario son las notificaciones de la barra de estado o de la bandeja del sistema. Estas notificaciones son las que se muestran cuando recibimos un mensaje SMS, cuando tenemos actualizaciones disponibles, cuando tenemos el reproductor de música abierto en segundo plano, etc. Constan de un icono y un texto mostrado en la barra de estado superior, y adicionalmente un mensaje algo más descriptivo y una marca de fecha/hora que podemos consultar desplegando la bandeja del sistema. Por ejemplo, cuando tenemos una llamada perdida en nuestro terminal se nos muestra por un lado un icono en la barra de estado: Notificación Y cuando desplegamos la bandeja del sistema podemos ver un mensaje con más información, donde en este caso concreto se nos informa del evento que se ha producido ("Missed call"), el número de teléfono asociado, y la fecha/hora del evento. Además, al pulsar sobre la notificación se nos dirige automáticamente al historial de llamadas. 73

74 Notificación Como ejemplo vamos a construir una aplicación lo más sencilla posible para poder centrarnos en lo importante, nuestra aplicación tendrá un único botón que generará una notificación de ejemplo en la barra de estado, con todos los elementos comentados y con la posibilidad de dirigirnos a la propia aplicación de ejemplo cuando se pulse sobre ella. Para generar notificaciones en la barra de estado del sistema, lo primero que debemos hacer es obtener una referencia al servicio de notificaciones de Android, a través de la clase NotificationManager. Utilizaremos para ello el método getsystemservice() indicando como parámetro el identificador del servicio correspondiente, en este caso Context.NOTIFICATION_SERVICE. Seguidamente configuraremos las características de nuestra notificación. En primer lugar estableceremos el icono y el texto a mostrar en la barra de estado, y registraremos la fecha/hora asociada a nuestra notificación. Con estos datos construiremos un objeto Notification. En nuestro caso de ejemplo, vamos a utilizar un icono predefinido de Android, el mensaje de ejemplo "Alerta!", y registraremos la fecha/hora actual, devuelta por el método System.currentTimeMillis(): //Obtenemos una referencia al servicio de notificaciones String ns = Context.NOTIFICATION_SERVICE; NotificationManager notmanager = (NotificationManager) getsystemservice(ns); //Configuramos la notificación int icono = android.r.drawable.stat_sys_warning; long hora = System.currentTimeMillis(); Notification notif = new Notification(icono, "Alerta!", hora); El segundo paso será utilizar el método setlatesteventinfo() para asociar a nuestra notificación la información a mostrar al desplegar la bandeja del sistema (título y descripción) e indicar la actividad a la cual debemos dirigir al usuario automáticamente si éste pulsa sobre la notificación. Los dos primeros datos son simples cadenas de texto, para el tercer dato, la actividad a la que se redirigirá, tenemos que utilizar un objeto PendingIntent. En primer lugar definimos un objeto Intent, indicando la clase de la actividad concreta a lanzar, que en nuestro caso será la propia actividad principal de ejemplo (AndroidNotificacionesActivity.class). Este intent lo utilizaremos para construir el PendingIntent final mediante el método PendingIntent.getActivity(). 74

75 Veamos cómo quedaría el código de esta última parte: //Configuramos el Intent Context contexto = getapplicationcontext(); CharSequence titulo = "Mensaje de Alerta"; CharSequence descripcion = "Ejemplo de notificación."; Intent notifintent = new Intent(contexto, AndroidNotificacionesActivity.class); PendingIntent contintent = PendingIntent.getActivity( contexto, 0, notifintent, 0); notif.setlatesteventinfo( contexto, titulo, descripcion, contintent); Como opciones adicionales, también podemos indicar por ejemplo que nuestra notificación desaparezca automáticamente de la bandeja del sistema cuando se pulsa sobre ella. Esto lo hacemos asignando al atributo flags de nuestra notificación el valor Notification.FLAG_AUTO_CANCEL. También podríamos indicar que al generarse la notificación el dispositivo debe emitir un sonido, vibrar o encender el LED de estado presente en muchos terminales. Esto lo conseguiríamos añadiendo al atributo defaults de la notificación los valores DEFAULT_SOUND, DEFAULT_VIBRATE o DEFAULT_LIGHTS. //AutoCancel: cuando se pulsa la notificación ésta desaparece notif.flags = Notification.FLAG_AUTO_CANCEL; //Añadir sonido, //notif.defaults //notif.defaults //notif.defaults vibración y luces = Notification.DEFAULT_SOUND; = Notification.DEFAULT_VIBRATE; = Notification.DEFAULT_LIGHTS; En algunos casos tendremos que añadir permisos y configurar el emulador correctamente para que funcione, como en el caso de la vibración que tendremos que añadir permisos en el manifest (<uses-permission android:name="android.permission.vibrate" />) Existen otras muchas opciones que se pueden consultar en la documentación oficial de la clase Notification de Android (atributos flags y defaults). Por último, una vez tenemos completamente configuradas las opciones de nuestra notificación podemos emitirla llamando al método notify(), que recibe como parámetros un identificador único definido por nosotros y el objeto Notification construido. //Enviar notificación notmanager.notify(notif_alerta_id, notif); El identificador sirve para actualizar la notificación en un futuro (con un nuevo aviso de notificación al usuario). Si se necesita añadir una notificación más, manteniendo la anterior, hay que indicar un nuevo ID. Ya estamos en disposición de probar nuestra aplicación de ejemplo. Para ello vamos a ejecutarla en el emulador de Android y pulsamos el botón que hemos implementado con 75

76 todo el código anterior. Si todo va bien, debería aparecer en ese mismo momento nuestra notificación en la barra de estado, con el icono y texto definidos. Notificación Si desplegamos la bandeja del sistema podremos verificar el resto de información de la notificación tal como muestra la siguiente imagen: Notificación Por último, si pulsamos sobre la notificación se debería abrir automáticamente la aplicación de ejemplo. Además, la notificación también debería desaparecer de la bandeja del sistema, tal y como lo habíamos configurado en el código con la opción FLAG_AUTO_CANCEL. Este cierre de la notificación también lo podríamos haber implementado en el método onresume() de la actividad, de la forma: 76

77 protected void onresume() { super.onresume(); notmanager.cancel(notif_alerta_id); A continuación se muestra un ejemplo completo de notificaciones usadas por una tipo AsyncTask. Este ejemplo se podría extender fácilmente a un Service: Sólo falta crear una nueva MiTarea en Service.onCreate(), arrancarla mitarea.execute() desde Service.onStartCommand(...) y detenerla mitarea.cancel() desde Service.onDestroy(). tarea haría con con private class MiTarea extends AsyncTask<String, String, String>{ public static final int NOTIF1_ID = 1; Notification notification; NotificationManager notificationmanager; protected void onpreexecute() { super.onpreexecute(); notificationmanager = (NotificationManager)getSystemService( Context.NOTIFICATION_SERVICE); notification = new Notification(R.drawable.icon, "Mensaje evento", System.currentTimeMillis()); protected String doinbackground(string... params) { while(condicionseguirejecutando){ if(condicionevento){ publishprogress("información del evento"); return null; protected void onprogressupdate(string... values) { Intent notificationintent = new Intent( getapplicationcontext(), MiActividadPrincipal.class); PendingIntent contentintent = PendingIntent.getActivity( getapplicationcontext(), 0, notificationintent, 0); notification.setlatesteventinfo(getapplicationcontext(), values[0], contentintent); notificationmanager.notify(notif_id, notification); 9.3. Cuadros de Diálogo Otra posible forma de notificación son los cuadros de diálogo, los cuales los podremos utilizar con distintos fines: Mostrar un mensaje o aviso. Pedir una confirmación rápida. 77

78 Solicitar al usuario una elección (simple o múltiple) entre varias alternativas. A parte de estas opciones también podremos personalizar completamente un diálogo para adaptarlo a cualquier otra necesidad. La forma de construir cada diálogo dependerá de la información y funcionalidad que necesitemos. A continuación se muestran algunas de las formas más habituales Diálogo de Alerta Este tipo de diálogo se limita a mostrar un mensaje sencillo al usuario, y un único botón para confirma su lectura. Este tipo de diálogos los construiremos mediante la clase AlertDialog, y más concretamente su subclase AlertDialog.Builder. Su utilización es muy sencilla, bastará con crear un objeto de tipo AlertDialog.Builder y establecer las propiedades del diálogo mediante sus métodos correspondientes: título [settitle()], mensaje [setmessage()], icono [seticon()], y el texto y comportamiento del botón [setpositivebutton()]. Veamos un ejemplo: private void mostrardialogoalerta() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.settitle("información"); builder.setmessage("esto es un mensaje de alerta."); builder.seticon(r.drawable.dialog_icon); builder.setpositivebutton("aceptar", new DialogInterface.OnClickListener() { public void onclick(dialoginterface dialog, int which) { Log.i("Dialogos", "Botón aceptar pulsado"); ); builder.show(); El método setpositivebutton() recibe como argumentos el texto a mostrar en el botón y la implementación del evento onclick en forma de objeto DialogInterface.OnClickListener. El diálogo se cerrará automáticamente al pulsar el botón, por lo que no es necesario realizar ninguna acción en esta función. Si no queremos asignarle ninguna función podemos pasarle el valor null como segundo parámetro. Si no llamamos a esta función (setpositivebutton) el diálogo aparecerá sin ningún botón y el usuario tendrá que cerrarlo pulsando el botón atrás. El aspecto de nuestro diálogo de alerta sería el siguiente: 78

79 Diálogo de alerta Diálogo de Confirmación Un diálogo de confirmación es muy similar al anterior, con la diferencia de que lo utilizaremos para solicitar al usuario que nos confirme una determinada acción. La implementación de estos diálogos es prácticamente igual, salvo que en esta ocasión tendremos dos botones, uno de ellos para la respuesta afirmativa (setpositivebutton()), y el segundo para la respuesta negativa (setnegativebutton()). Veamos un ejemplo: private void mostrardialogoconfirmacion() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.settitle("confirmacion"); builder.setmessage(" Confirma la accion seleccionada?"); builder.setpositivebutton("aceptar", new DialogInterface.OnClickListener() { public void onclick(dialoginterface dialog, int which) { Log.i("Dialogos", "Confirmacion Aceptada."); ); builder.setnegativebutton("cancelar", new DialogInterface.OnClickListener() { public void onclick(dialoginterface dialog, int which) { Log.i("Dialogos", "Confirmacion Cancelada."); ); builder.show(); En este caso, generamos a modo de ejemplo dos mensajes de log para poder verificar que botón pulsamos en el diálogo. El aspecto visual de nuestro diálogo de confirmación sería el siguiente: 79

80 Diálogo de confirmación Diálogo de Selección Cuando las opciones a seleccionar por el usuario no son sólo dos, sino que el conjunto es mayor podemos utilizar los diálogos de selección para mostrar una lista de opciones entre las que el usuario pueda elegir. Para ello también utilizaremos la clase AlertDialog, pero indicando directamente la lista de opciones a mostrar (mediante el método setitems()) y proporcionando la implementación del evento onclick() sobre dicha lista (mediante un listener de tipo DialogInterface.OnClickListener), evento en el que realizaremos las acciones oportunas según la opción elegida. La lista de opciones la definiremos como un array tradicional, veamos cómo: private void mostrardialogoseleccion() { final String[] items = {"Español", "Inglés", "Francés"; AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.settitle("selecciona tu idioma"); builder.setitems(items, new DialogInterface.OnClickListener() { public void onclick(dialoginterface dialog, int item) { Log.i("Dialogos", "Opción elegida: " + items[item]); ); builder.setnegativebutton("cancelar", new DialogInterface.OnClickListener() { public void onclick(dialoginterface dialog, int which) { Log.i("Dialogos", "Selección Cancelada."); ); builder.show(); En este caso el diálogo tendrá un aspecto similar a la interfaz mostrada para los controles Spinner. Además hemos añadido un botón "Cancelar" para poder cerrar el diálogo sin pulsar ninguna opción, aunque esto también se podría conseguir simplemente pulsando el botón atrás. 80

81 Diálogo de selección Para más információn sobre diálogos podéis consultar la dirección 81

82 10. Ejercicios - Notificaciones Notificaciones con Toast (1 punto) En este primer ejercicio vamos a crear una aplicación de ejemplo que muestre un toast personalizado cada vez que se pulse un botón. Empezamos creando un proyecto Android vacío y añadiendo un campo de edición y un botón en la actividad principal. Cada vez que se pulse el botón se mostrará un toast por pantalla con el texto que se haya escrito en el campo de edición. En caso de que el campo esté vacío se mostrará el aviso: "Escribe un texto". Después de cada pulsación del botón se borrará el contenido del campo de edición. Por último personalizaremos el aspecto y características del toast de la forma: El aviso deberá de aparecer en el centro de la pantalla, tanto vertical como horizontalmente. Tendrá una duración corta de 2 segundos. Modificaremos su aspecto visual asignándole un layout personalizado con las siguientes características: Creamos un linear layout horizontal que ocupe todo el ancho y alto, y tenga un padding de 5dip. El layout contendrá una imagen (androide.png) en la parte izquierda y un texto a continuación (con un padding izquierdo y derecho de 10dip, color de texto de #FFFFFF y un layout_gravity con valor center_vertical). Por último asignaremos como background del layout que acabamos de crear un elemento drawable XML que también crearemos nosotros (repasar la sesión de "Drawables, estilos y temas"). Este drawable será de tipo shape, en concreto un rectángulo, con un color de fondo "#f0cccccc", un borde de 3dp con color "#FF0000" y esquinas redondeadas con un radio de "10dp" Servicio con notificaciones: Números primos (1 punto) El proyecto android-av-notificaciones de la plantilla tiene un servicio con una tarea que va calculando números primos a un ritmo lento. Vamos a mostrar una Notification en la barra de tareas cada vez que se descubra un primo y la iremos actualizando con la llegada de cada nuevo número. Si salimos de la aplicación sin parar el servicio, seguirán apareciendo notificaciones, y si pulsamos sobre la notificación, volverá a lanzarse la actividad, cerrándose la notificación que hemos pulsado. Dentro del servicio MiNumerosPrimosServicio se encuentra declarada la AsyncTask llamada MiTarea. En ella tenemos como campos de la clase una Notification y un NotificationManager. Hay que darles valores en el método onpreexecute(). 82

83 El método doinbackground(...) ejecutará un bucle que irá incrementando i mientras su valor sea menor de MAXCOUNT. En cada iteración, si el número es primo (función incluida en la plantilla), pedirá que se muestre el progreso, pasándole como parámetro el nuevo primo encontrado. Implementar el método onprogressupdate(...) para que muestre la notificación. Para ello habrá que actualizar la notificación con el método setlatesteventinfo, al cuál le pasaremos en un String la información del último primo descubierto y le pasaremos un PendingIntent para que al pulsar sobre la notificación, nos devuelva a la actividad de la aplicación, por si la hemos cerrado. Para crear el PendingIntent utilizaremos el método PendingIntent.getActivity(...) al cuál le tenemos que pasar un new Intent(getApplicationContext(),Main.class). La aplicación debería funcionar en este punto, mostrando las notificaciones y relanzando la aplicación si son pulsadas, pero no cerrándolas al pulsarlas. Para ello simplemente tenemos que llamar al método cancel(id) del notificationmanager y pasarle la constante NOTIF_ID para que la notificación no se muestre como una nueva, sino como actualización de la que ya habíamos puesto. Una manera de hacerlo es en un método estático del MiNumerosPrimosServicio, que ya está creado en las plantillas del ejercicio y se llama cerrarminotificacion(notificationmanager nm). Debes invocar este método desde el Main.onResume(). Notificación del servicio de números primos Notificaciones mediante diálogos (1 punto) Para practicar con las notificaciones vamos a crear una aplicación que muestre un texto y nos permita aumentar y reducir el tamaño de la letra, y modificar los colores en los que se muestra el texto. 83

84 Empezamos creando una aplicación y diseñando el layout, el cual tendrá una apariencia similar a la imagen inferior: Toast Para poder realizar scroll cuando el texto no quepa en la pantalla vamos a incluir un ScrollView al layout. Sustituimos el layout principal por uno tipo scroll, el cual a su vez contendrá un LinearLayout vertical. Es importante que el ScrollView contenga un único elemento, sino nos aparecerá un error. En la parte superior colocaremos un botón del tipo ZoomControls para aumentar y disminuir el tamaño del texto, y otro botón para cambiar los colores. Justo debajo añadiremos un campo TextView con un trozo del lorem ipsum: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." Para disminuir y aumentar el tamaño del texto tenemos que implementar los listeners setonzoomoutclicklistener y setonzoominclicklistener del botón de Zoom respectivamente. En ambos casos tendremos que obtener el tamaño actual del texto en píxeles (gettextsize()), sumar o restar 0.5 píxels, y volver a asignarlo como tamaño: settextsize(typedvalue.complex_unit_px, newtam). En caso de que el tamaño a asignar sea inferior a 10 píxeles o superior a 40 píxeles mostraremos un cuadro de diálogo indicando que se ha superado el tamaño mínimo/máximo. Para cambiar los colores mostraremos un cuadro de diálogo tipo selección al pulsar el botón "Color", las posibles opciones serán tres: "Blanco/Negro, Negro/Blanco, Negro/Verde", siendo el primer color el color de fondo a asignar (que asignaremos a todo el ScrollView), y el segundo color el que asignaremos como color de texto. 84

85 85

86 11. Depuración y pruebas Depuración con Eclipse Log y LogCat Sin duda alguna el sistema más utilizado para depuraciones sencillas es la salida estándar y los logs. En Android podemos imprimir mensajes al log tras importar android.util.log. Los mensajes de Log van clasificados por una etiqueta que les asignamos, aparte de otra información implícita, como la aplicación que los lanza, el PID, el tiempo y el nivel de debug. Se recomienda crear una constante con la etiqueta: private static final String TAG = "MiActivity"; Para añadir al log un mensaje de nivel de información utilizaríamos Log.i(): Log.i(TAG, "indice=" + i); Según el nivel de depuración utilizaremos una llamada de las siguientes: Log.v(): Verbose Log.d(): Debug Log.i(): Info Log.w(): Warning Log.e(): Error Con esta información el Log podrá ser mostrado filtrando los mensajes menos importantes, por ejemplo si establecemos la vista del Log a nivel de Info, los de Debug y los Verbose no se mostrarían, pero sí los de Warning y Error que son más graves que Info. En Eclipse contamos con la vista LogCat (si no se muestra por defecto se puede añadir) donde podemos realizar dicho filtrado. También podemos realizar filtrado por etiquetas para ver sólo los mensajes que nos interesan. Los mensajes van apareciendo en tiempo real, tanto si estamos con un emulador como si estamos con un dispositivo móvil conectado por USB. Se recomienda eliminar todas las llamadas a Log cuando se publica un programa en el Android Market, a pesar de que en los dispositivos de los usuarios no se vería ningún log ni salida estándar Dalvik Debug Monitor Server (DDMS) Android viene con el servidor de depuración DDMS que puede ser ejecutado desde la 86

87 terminal o desde Eclipse. Desde la terminal del sistema se tendría que ejecutar./ddms que se encuentra en la carpeta tools. No es necesario si utilizamos Eclipse, que cuenta con una vista DDMS para interactuar y visualizar resultados de depuración. En Android cada aplicación se ejecuta en su propia máquina virtual (VM) y cada VM tiene un puerto al que el debugger se conecta. Cuando se inicia DDMS, éste se conecta a adb. Cuando conectamos un dispositivo se crea un servicio de monitorización entre adb y DDMS, que notifica a DDMS cuando una VM del dispositivo arranca o termina. Cuando una VM arranca, DDMS recoge su PID a través de adb y abre una conexión con el debugger de la VM. Aunque cada depurador se puede conectar a un único puerto, DDMS maneja múltiples depuradores conectados. Vista de DDMS en Eclipse Como se puede observar en la figura, DDMS nos permite ver los hilos que están en ejecución en dada máquina virtual. También se permiten todas las características de depuración que proporciona Eclipse para Java, como puntos de ruptura, visualización de variables y evaluación de expresiones. DDMS cuenta con las siguientes funcionalidades y controles: Visualización del uso de memoria heap Seguimiento de reservas de memoria para objetos 87

88 Trabajar con el sistema de ficheros del emulador o del dispositivo Examinar la información de hilos Profiling de métodos: seguimiento de medidas tales como número de llamadas, tiempo de ejecución, etc. LogCat Emulación de operaciones de telefonía y localización Cambiar el estado de red y simular red lenta Pruebas unitarias con JUnit para Android Las pruebas unitarias consisten en probar métodos y partes concretas del código, intentando independizarlas del resto del código lo máximo posible. Los proyectos de Test de Android se construyen sobre JUnit y nos proveen de herramientas que permiten realizar no sólo pruebas unitarias sino también pruebas más amplias. Cuando creamos un proyecto de Android con el asistente de Eclipse tenemos la opción de incluir casos de prueba en el propio proyecto. Otra manera muy común es la de separar las pruebas en un proyecto aparte. Para ello creamos un nuevo proyecto Android Test Project y seleccionamos el proyecto de nuestro workspace que queremos probar. A continuación creamos casos de prueba en el mismo paquete donde se encuentra el proyecto original, o en subpaquetes de éste. Se crean con New / JUnit Test Case pero en lugar de que la clase padre sea la de JUnit, utilizaremos ActivityInstrumentationTestCase2, que es la versión actualizada de ActivityInstrumentationTestCase cuyo uso ya está desaconsejado. ActivityInstrumentationTestCase2 se parametriza con la clase de la actividad que vamos a probar. Por ejemplo, si vamos a probar MainActivity, tendremos: package es.ua.jtech.av.suma.test; import android.test.activityinstrumentationtestcase2; import es.ua.jtech.av.suma.mainactivity; public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> { public MainActivityTest() { super("es.ua.jtech.av.suma", MainActivity.class); protected void setup() throws Exception { super.setup(); public void test1(){ // asserts public void test2(){ // asserts 88

89 //... protected void teardown() throws Exception { super.teardown(); En el código anterior se llama al constructor de la clase padre con el paquete "es.ua.jtech.av.suma" y la clase MainActivity de la actividad a probar. Con esta clase ya se pueden escribir métodos con pruebas. Sus nombres deben comenzar por "test", por ejemplo testmiprueba1(). Dentro de estos métodos de pruebas podemos utilizar los métodos assert de JUnit que provocan el fallo si la aserción resulta falsa. Por ejemplo, assertequals(a,b) compara los dos parámetros y si son distintos la prueba falla. Otro ejemplo es asserttrue(c) que comprueba si es verdadero el parámetro que se le pasa. Con JUnit se pueden probar métodos de las clases del código pero en Android podemos necesitar probar partes de la interfaz gráfica. Para acceder a los views de la actividad lo normal sería declarar referencias a éstos como campos de la clase e inicializarlos en el método setup(). Para obtener las referencias hay que acceder a ellos con getactivity().findviewbyid(id)): private Button bt; protected void setup() throws Exception { super.setup(); MainActivity activity = getactivity(); bt = (Button)activity.findViewById(es.ua.jtech.av.suma.R.id.button1); Una vez obtenidas las referencias se pueden comprobar los valores que contienen. assertequals("32.3", mitextview.gettext().tostring()); Otra necesidad es la de simular eventos de introducción de texto, de selección, de pulsación. Para ello se utiliza la clase TouchUtils TouchUtils.tapView(this, miedittext); sendkeys("s"); sendkeys("i"); sendkeys( KeyEvent.KEYCODE_PERIOD ); TouchUtils.clickView(this, bt); Podéis encontrar más información en Aparte de ActivityInstrumentationTestCase2 hay otras alternativas que también heredan de las clases de JUnit: AndroidTestCase que sólo ofrece el contexto local y no el de la aplicación. 89

90 ServiceTestCase que se usa para probar servicios. ActivityUnitTestCase que crea la actividad pero no la conecta al entorno, de manera que se puede utilizar un contexto o aplicación mock. ApplicationTestCase para probar subclases propias de Application. Se pueden utilizar todas las características de JUnit, tales como crear una Test Suite para agrupar distintos casos de pruebas Pruebas de regresión con Robotium "Robotium es como Selenium pero para Android" es el eslogan de este software de pruebas que no está afiliado ni con Android ni con Selenium. Selenium es un software para pruebas de aplicaciones web que permite grabar una secuencia de acciones sobre una página web y después reproducirla. Una batería de pruebas de este tipo permite comprobar que tras realizar cambios en el software, éste sigue comportándose de manera idéntica en su interacción con el usuario. Robotium no permite grabar las acciones del usuario sino que la secuencia de acciones debe ser programada a través de sencillas llamadas a los métodos de Robotium. Da soporte completo para los componentes Activity, Dialog, Toast, Menu y ContextMenu. Usar una herramienta como Robotium nos proporciona las siguientes ventajas: Desarrollar casos de prueba sin necesidad de conocer el funcionamiento interno de la aplicación probada. Automatizar el manejo múltiples actividades de Android. Pruebas realistas, al realizarse sobre los componentes GUI en tiempo de ejecución Integración con Maven y Ant para ejecutar pruebas como parte de una integración continua. Para crear un conjunto de pruebas con Robotium debemos crear un nuevo proyecto de test de Android desde Eclipse: New / Android Test Project. Es importante crear los casos de prueba dentro del mismo paquete que el proyecto original, así que desde el asistente de creación del proyecto podemos introducir dicho paquete. También puede ser un subpaquete del original. Otra cuestión importante para evitar errores es indicar una versión actual del SDK de Android que sea compatible con la versión de Robotium que utilicemos. Añadiremos el JAR de Robotium en el "build path" del proyecto. Éste puede ser descargado de y tendrá un nombre similar a robotium-solo-3.1.jar (según la versión). Lo añadiremos como "jar" tras incluirlo dentro del proyecto, en una carpeta lib/, por ejemplo. También será necesario marcarlo en "Order and Export" en la misma pantalla de configuración del "Java Build Path". 90

91 Incluir Robotium en el Buildpath del proyecto de pruebas. Una vez creado el proyecto de pruebas debemos crear un nuevo caso de prueba en el paquete correcto: New / JUnit Test Case en cuyo asistente pondremos el nombre del test que deseemos y cambiaremos la superclase del test por ActivityInstrumentationTestCase2<Activity> en lugar de junit.framework.testcase. La Activity que pasamos como tipo puede ser directamente la clase de la actividad que vayamos a probar, por ejemplo MainActivity. Aunque pertenezca a otro proyecto Eclipse nos permite incluirla configurando automáticamente el path para referenciar al otro proyecto. En el caso de prueba creado declaramos Solo como campo privado de la clase y, si no lo están ya, sobrecargamos los métodos setup(), teardown y el constructor. package es.ua.jtech.av.miproyecto.test; import android.test.activityinstrumentationtestcase2; import es.ua.jtech.av.miproyecto.mainactivity; import com.jayway.android.robotium.solo.solo; public class TestMainActivity extends ActivityInstrumentationTestCase2<MainActivity> { private Solo solo; public TestMainActivity(){ super("es.ua.jtech.av.miproyecto", MainActivity.class); protected void setup() throws Exception { super.setup(); 91

92 solo = new Solo(getInstrumentation(), getactivity()); public void test1(){ //... //asserttrue(comprobacion); //... protected void teardown() throws Exception { solo.finishopenedactivities(); Para ejecutar las pruebas programadas hay que hacer click sobre TestMain y en el menú contextual seleccionar Run as / Android JUnit Test. El emulador arrancará si no está arrancado, la aplicación a probar será instalada y ejecutada, y las acciones programadas en las pruebas serán realizadas sobre la actividad. Los resultados serán reconocidos por las pruebas y mostradas en el resumen de resultados de JUnit. Un ejemplo de test podría ser el de una sencilla calculadora que cuenta con dos EditText y con un botón de sumar. El código de la prueba sería el siguiente: public void test1(){ solo.entertext(0,"10"); solo.entertext(1,"22.4"); solo.clickonbutton("+"); asserttrue(solo.searchtext("32.4")); Los EditText de la actividad se corresponen (por orden) con los parámetros 0 y 1. El botón se corresponde con el que contiene la cadena indicada como etiqueta. Finalmente se busca el resultado en toda la actividad. Existen diferentes métodos con los que Solo da soporte a los componentes gráficos. Algunos ejemplos son: getview(id) getcurrenttextviews(textview) setactivityorientation(solo.landscape) sendkey(solo.menu) clickonbutton(text) clickontext(text) clickonedittext(text) cleartext(text) entertext(text) goback() sleep(millis) La información sobre todos los métodos está en 92

93 11.4. Pruebas de estrés con Monkey Monkey es un programa para pruebas que simula input aleatorio del usuario. Su propósito es realizar una prueba de estrés de la aplicación para confirmar que, haga lo que haga el usuario con la GUI, la aplicación no tendrá un comportamiento inesperado. El input que realiza Monkey no tiene por qué tener sentido alguno. A continuación solicitamos 1000 eventos simulados cada 100 milisegundos obteniendo la lista de ellos (opción -v) y afectará a las aplicaciones del paquete es.ua.jtech.av. adb shell monkey -p es.ua.jtech.av -v --throttle Monkey simulará eventos de teclado, tanto qwerty como teclas hardware especializadas, movimientos de trackball, apertura y cierre del teclado, rotaciones de la pantalla. Si deseamos poder reproducir una serie de acciones aleatorias, podemos fijar la semilla de inicialización aleatoria con una propia, por medio de la opción -s. Así cada vez que ejecutemos con esta semilla, la secuencia de eventos aleatorios será la misma y podremos reproducir un problema tantas veces como haga falta hasta dar con la solución. 93

94 12. Ejercicios - Depuración y pruebas Caso de prueba con JUnit para Android (3 puntos) En las plantillas contamos con el proyecto android-av-suma que consiste en una actividad con dos campos de texto para introducir dos números y a continuación pulsar el botón para visualizar el resultado en un TextView. A continuación se explica cómo crear un proyecto de pruebas JUnit. Crea un nuevo proyecto de tipo Android Test Project y deja marcada la opción de Create new project in workspace, llamándolo android-av-suma-test. Crear nuevo proyecto de test de Android Crea un nuevo caso de prueba con New / JUnit Test Case y cambia la clase de la que hereda a ActivityInstrumentationTestCase2. El nombre de la clase será MainActivityTest y el paquete el mismo que el del proyecto que estamos probando 94

95 pero terminado en.test. También podemos sustituir el tipo <T> por el de la clase de la actividad a probar, <MainActivity>: Asistente para la creación de un caso de prueba Dará error por la falta del import de MainActivity, pero se puede importar porque el otro proyecto está en el build path del proyecto de pruebas. También debemos cambiar el constructor para constuir el caso de prueba con el paquete y la actividad a probar: public MainActivityTest() { super("es.ua.jtech.av.suma", MainActivity.class); Vamos a añadir como campos de la clase objetos que referencien los views de nuestra actividad: private EditText et1, et2; private TextView tv; private Button bt; Los inicializaremos en el método setup(), a partir de la actividad que se obtiene con getactivity(): protected void setup() throws Exception { 95

96 super.setup(); MainActivity activity = getactivity(); et1 = (EditText)activity.findViewById(es.ua.jtech.av.suma.R.id.editText1); et2 = (EditText)activity.findViewById(es.ua.jtech.av.suma.R.id.editText2); tv = (TextView)activity.findViewById(es.ua.jtech.av.suma.R.id.textView3); bt = (Button) activity.findviewbyid(es.ua.jtech.av.suma.r.id.button1); Vamos a añadir dos métodos de test, uno de ellos va a comprobar que los componentes gráficos estén bien inicializados y el otro va a introducir unos datos en la interfaz y va a comprobar que el resultado es el public void testviewscreados(){ assertnotnull(et1); assertnotnull(et2); assertnotnull(tv); assertnotnull(bt); assertequals("", et1.gettext().tostring()); assertequals("", et2.gettext().tostring()); assertequals("...", public void testsuma(){ TouchUtils.tapView(this, et1); sendkeys("1"); sendkeys("0"); TouchUtils.tapView(this, et2); sendkeys("2"); sendkeys("2"); sendkeys( KeyEvent.KEYCODE_PERIOD ); sendkeys("3"); TouchUtils.clickView(this, bt); assertequals("32.3", tv.gettext().tostring()); Para ejecutar la prueba hay que hacer click en Run as/ Android JUnit Test. El resultado debe salir en verde: Resultado de JUnit en verde 96

Desarrollo de apps para móviles Android. Conceptos básicos de las aplicaciones Android

Desarrollo de apps para móviles Android. Conceptos básicos de las aplicaciones Android Desarrollo de apps para móviles Android Conceptos básicos de las aplicaciones Android Modelo vista controlador (MVC) En Android se utiliza el patrón de arquitectura llamado modelo vista controlador. Este

Más detalles

INTRODUCCIÓN. paco@portadaalta.es

INTRODUCCIÓN. paco@portadaalta.es INTRODUCCIÓN paco@portadaalta.es Índice Entorno de desarrollo Estructura de un proyecto Android Componentes de una aplicación Android Ejemplos: Mi primera aplicación Divisas 2 actividades Contador de cafés

Más detalles

Con este tutorial podrás aprender a cómo empezar a crear apps en Android, empezaremos con algo muy simple para que poco a poco vayas conociendo.

Con este tutorial podrás aprender a cómo empezar a crear apps en Android, empezaremos con algo muy simple para que poco a poco vayas conociendo. Cómo crear una app simple en ANDROID Con este tutorial podrás aprender a cómo empezar a crear apps en Android, empezaremos con algo muy simple para que poco a poco vayas conociendo. Aprenderás cómo crear

Más detalles

Android Guía de desarrollo de aplicaciones para Smartphones y Tabletas

Android Guía de desarrollo de aplicaciones para Smartphones y Tabletas Prólogo 1. Introducción 11 2. A quién se dirige este libro? 12 3. Conocimientos previos necesarios para abordar este libro 12 4. Objetivos a alcanzar 13 5. Descarga 14 6. Información complementaria 14

Más detalles

Programación Android. Alejandro Alcalde. elbauldelprogramador.com

Programación Android. Alejandro Alcalde. elbauldelprogramador.com Programación Android Alejandro Alcalde elbauldelprogramador.com Copyright c 2013 Alejandro Alcalde P L A TEX. Programación Android por Alejandro Alcalde se encuentra bajo una Licencia Creative Commons

Más detalles

Curso de Android con Java

Curso de Android con Java Todos los Derechos Reservados Global Mentoring Experiencia y Conocimiento para tu Vida 1 Una aplicación Android consiste en una serie de componentes poco acoplados y perfectamente estructurados, los cuales

Más detalles

Hola Android. Introducción al desarrollo de aplicaciones para Android

Hola Android. Introducción al desarrollo de aplicaciones para Android Hola Android. Introducción al desarrollo de aplicaciones para Android Las aplicaciones para el sistema operativo móvil Android son desarrolladas en el lenguaje de programación Java en conjunto con el SDK

Más detalles

Servicios Avanzados. Índice. 1 Servicios en segundo plano... 2 2 Notificaciones...3 3 AppWidgets...5 4 Publicación de software...8

Servicios Avanzados. Índice. 1 Servicios en segundo plano... 2 2 Notificaciones...3 3 AppWidgets...5 4 Publicación de software...8 Índice 1 Servicios en segundo plano... 2 2 Notificaciones...3 3 AppWidgets...5 4 Publicación de software...8 1. Servicios en segundo plano Los servicios en segundo plano, Services son similares a los demonios

Más detalles

SESIÓN 5 MANEJO DE BASES DE DATOS SQLITE

SESIÓN 5 MANEJO DE BASES DE DATOS SQLITE SESIÓN 5 MANEJO DE BASES DE DATOS SQLITE Contenidos Resumen...1 Práctica guiada 7: ListaCompra...1 Implementación de la base de datos...2 Actividad principal: ListaCompraActivity...6 Actividad secundaria:

Más detalles

Capitulo 4: Componentes Android

Capitulo 4: Componentes Android Capitulo 4: Componentes Android Elaborado por: Gary Briceño http://gary.pe http://gary.pe 1 http://gary.pe 2 1. COMPONENTES DE LA APLICACIÓN http://gary.pe 3 Componentes Android permite a los desarrolladores

Más detalles

Curso PUDE. Desarrollo de Aplicaciones Móviles en Android

Curso PUDE. Desarrollo de Aplicaciones Móviles en Android Curso PUDE Ejercicio Avanzado A: Bases de Datos y conexión con un ListView utilizando vistas A. Descripción Con este ejercicio, veremos cómo funcionan las bases de datos SQLite en Android, crearemos una,

Más detalles

Guía de instalación del software de la asignatura

Guía de instalación del software de la asignatura Guía de instalación del software de la asignatura Eclipse + Android SDK 1. La forma más sencilla de instalar Eclipse con el plugin ADT para desarrollo de aplicaciones Android es descargar el paquete preconfigurado

Más detalles

Interfaz de usuario Layout Vistas Adaptadores Eventos de interacción Estilos y temas

Interfaz de usuario Layout Vistas Adaptadores Eventos de interacción Estilos y temas Interfaz de usuario Layout Vistas Adaptadores Eventos de interacción Estilos y temas Interfaz de usuario Layout Vistas Adaptadores Eventos de interacción Estilos y temas Activity [Form] Pantalla que se

Más detalles

PRACTICAS DE ANDROID 12 - Lanzar un segundo "Activity" y pasar parámetros Problema:

PRACTICAS DE ANDROID 12 - Lanzar un segundo Activity y pasar parámetros Problema: PRACTICAS DE ANDROID 12 - Lanzar un segundo "Activity" y pasar parámetros Hemos visto en el concepto anterior que un programa puede tener más de una ventana representando cada ventana con una clase que

Más detalles

Servicios - Ejercicios

Servicios - Ejercicios Índice 1 Servicio con proceso en background. Contador...2 2 Dialer. Iniciar una actividad con un evento broadcast (*)...2 3 Arranque. Iniciar servicio con evento broadcast... 3 4 Localizador de móvil desaparecido...4

Más detalles

Ejercicio 18. Configuración de Widgets en Android. Android Con Java. Ejercicio 18. Configuración de Widgets en Android. Curso de Android con Java

Ejercicio 18. Configuración de Widgets en Android. Android Con Java. Ejercicio 18. Configuración de Widgets en Android. Curso de Android con Java Android Con Java Ejercicio 18 Configuración de Widgets en Android Objetivo del Ejercicio El objetivo del ejercicio crear un Widget que antes de utilizarlo nos permita configurarlo. En este caso, solicitar

Más detalles

CURSO INTERMEDIO DE PROGRAMACIÓN EN ANDROID

CURSO INTERMEDIO DE PROGRAMACIÓN EN ANDROID CURSO INTERMEDIO DE PROGRAMACIÓN EN ANDROID 1. Entorno de desarrollo y primera aplicación 1. El mundo Android 1.1 Android y las versiones 1.2 Personalizaciones del sistema operativo. 1.3 Dispositivos Android

Más detalles

Ejercicios - Persistencia en Android: ficheros y SQLite

Ejercicios - Persistencia en Android: ficheros y SQLite Ejercicios - Persistencia en Android: ficheros y SQLite Índice 1 Uso de ficheros (0.5 puntos)...2 2 Persistencia con ficheros (0.5 puntos)...3 3 Base de datos: SQLiteOpenHelper (0.5 puntos)... 3 4 Base

Más detalles

Crear una Activity en Android. Paso por paso

Crear una Activity en Android. Paso por paso 19-03-2014 Crear una Activity en Android. Paso por paso Una breve descripción de qué hacer cuando las herramientas de desarrollo de Android se convierten en nuestros enemigos. A veces, puede pasar que

Más detalles

Ejercicios - Persistencia en Android: proveedores de contenidos y SharedPreferences

Ejercicios - Persistencia en Android: proveedores de contenidos y SharedPreferences Ejercicios - Persistencia en Android: proveedores de contenidos y SharedPreferences Índice 1 Compartir datos entre actividades con Shared Preferences (0.75 puntos)... 2 2 Actividad de preferencias (0.75

Más detalles

Ejercicios de pantalla táctil

Ejercicios de pantalla táctil Índice 1 Pantalla táctil (1 punto)... 2 2 Gestos (1 punto)... 3 3 Gestos personalizados (0,5 puntos)... 3 4 Acelerómetro (0,5 puntos)...4 Antes de empezar a crear los proyectos, debes descargarte las plantillas

Más detalles

ALMACENAMIENTOS DE DATOS EN ANDROID CON SQLITE

ALMACENAMIENTOS DE DATOS EN ANDROID CON SQLITE SQLite M.Sc. Reynaldo Zeballos ALMACENAMIENTOS DE DATOS EN ANDROID CON SQLITE En este ejercicio vamos a crear una tabla TELEFONOS para la base de datos GUIATEL. Para lo cual utilizaremos SQLite que viene

Más detalles

La funcionalidad básica es la del proyecto 1 (Pacman III). Sobre ella reemplazamos la interfaz de usuario para adaptarla al nuevo entorno

La funcionalidad básica es la del proyecto 1 (Pacman III). Sobre ella reemplazamos la interfaz de usuario para adaptarla al nuevo entorno Pacman en android 14.5.2015 1 Objetivos Pasar el juego del proyecto 1 (Pacman III) a una plataforma Android. movimiento del jugador dirigido por el dedo programación de menús contextuales gestión de threads

Más detalles

CODIGO PROYECTO: AppPixelproServicioWeb Proyecto Android - Servicio Web

CODIGO PROYECTO: AppPixelproServicioWeb Proyecto Android - Servicio Web CODIGO PROYECTO: AppPixelproServicioWeb Proyecto Android - Servicio Web I. Alcances del Proyecto a. Ide Eclipse Indigo 3.7 b. Api 10-17, Versión Android 2.3.3 A 4.2.2 c. Jdk 1.6 II. Conceptos Relacionados

Más detalles

Desarrollo de apps para móviles Android. Entorno de desarrollo

Desarrollo de apps para móviles Android. Entorno de desarrollo Desarrollo de apps para móviles Android Entorno de desarrollo Entorno de desarrollo Las aplicaciones Android van a ser implementadas en Java usando Android SDK. El software necesario para realizarlas es

Más detalles

Ejercicios - Servicios

Ejercicios - Servicios Índice 1 Contador: Servicio con proceso en background (0.6 puntos)... 2 2 Broadcast Receiver: Captura de llamadas (0.6 puntos)...2 3 Broadcast Receiver: Reenvío de datos (0.6 puntos)...3 4 Arranque: Iniciar

Más detalles

Android 4 Principios del desarrollo de aplicaciones Java

Android 4 Principios del desarrollo de aplicaciones Java La plataforma Android 1. Presentación 13 2. Origen 14 3. Google Play 15 3.1. Creación de una cuenta de desarrollador 16 3.2 Publicación de una aplicación 16 3.3. Seguimiento y actualización de una aplicación

Más detalles

online Programación para dispositivos Android 4.x

online Programación para dispositivos Android 4.x online Programación para dispositivos Android 4.x Objetivos Conocer los distintos componentes que permiten interactuar con el dispositivo con la voz. Saber cómo reproducir sonido en base a un contenido

Más detalles

Curso PUDE. Desarrollo de Aplicaciones Móviles en Android

Curso PUDE. Desarrollo de Aplicaciones Móviles en Android Curso PUDE A. Descripción En este ejercicio, introduciremos cómo funcionan los servicios de localización en Android y explicaremos cómo conseguir que la API de Android nos indique nuestra posición GPS.

Más detalles

Ejercicio 4. Manejo de Layouts en Android. Android Con Java. Ejercicio 4. Manejo de Layouts en Android. Curso de Android con Java

Ejercicio 4. Manejo de Layouts en Android. Android Con Java. Ejercicio 4. Manejo de Layouts en Android. Curso de Android con Java Android Con Java Ejercicio 4 Manejo de Layouts en Android Objetivo del Ejercicio El objetivo del ejercicio es entender el manejo de Layout y los Adaptadores en Android. Al finalizar deberemos observar

Más detalles

2.4. BASES DE DATOS SQLITE

2.4. BASES DE DATOS SQLITE 2.4. BASES DE DATOS SQLITE SQLite es un potente motor de base de datos, creado en el año 2000 por el Dr. Richard Hipp. Podríamos decir, que se trata del motor más usado en el mundo, ya que se encuentra

Más detalles

Cookbook Creando un Proyecto Android (ADT-Eclipse)

Cookbook Creando un Proyecto Android (ADT-Eclipse) Cookbook Creando un Proyecto Android (ADT-Eclipse) ALONSO PARRA CESAR VIELMA FREDDY RONDON JOSE MARQUEZ Alienx9889 * cesarvielma * spantons * joseangel2212 * * @gmail.com Universidad de Los Andes Escuela

Más detalles

Lista de siglas y acrónimos... xvii. Cómo leer este libro?... xix

Lista de siglas y acrónimos... xvii. Cómo leer este libro?... xix Índice general Lista de siglas y acrónimos... xvii Cómo leer este libro?... xix CAPÍTULO 1. Visión general y entorno de desarrollo... 23 1.1. Qué hace que Android sea especial?... 24 1.2. Los orígenes...

Más detalles

MANUAL DE USO SERVICIOS DE BACKUP ONLINE (Backup remoto software novanet-web)

MANUAL DE USO SERVICIOS DE BACKUP ONLINE (Backup remoto software novanet-web) MANUAL DE USO SERVICIOS DE BACKUP ONLINE (Backup remoto software novanet-web) 1 ÍNDICE 1. INSTALACIÓN DEL PROGRAMA... 4 1.1 PASOS PREVIOS... 4 1.2 INSTALACIÓN... 4 1.3 CONFIGURACIÓN DEL ACCESO... 5 1.3.1

Más detalles

Lo que necesitaremos para programar en Java, será un editor de texto o IDE y la JDK.

Lo que necesitaremos para programar en Java, será un editor de texto o IDE y la JDK. Introducción Java surgió en 1991 dentro de la empresa Sun Microsystems como un lenguaje de programación sencillo y universal destinado a electrodomésticos. La reducida potencia de cálculo y memoria de

Más detalles

Cómo unir un equipo al dominio de las aulas tecnológicas

Cómo unir un equipo al dominio de las aulas tecnológicas Cómo unir un equipo al dominio de las aulas tecnológicas Es requisito indispensable unir el equipo primero en MAX y luego en Windows, si se hace al revés Windows dará un error cuando intentemos iniciar

Más detalles

Programación Android Completo

Programación Android Completo Programación Android Completo Duración: 50.00 horas Descripción Este curso pretende formar al alumno en el desarrollo de aplicaciones para dispositivos Android. Se estudiarán con detalle todos aquellos

Más detalles

Android. Content Providers

Android. Content Providers Android Content Providers Content-Providers Persistencia: Manejar datos y exponerlos a otras aplicaciones. Interfaz con un conjunto de métodos estándar. Único modo de compartir datos entre aplicaciones.

Más detalles

Android 5 Principios del desarrollo de aplicaciones Java

Android 5 Principios del desarrollo de aplicaciones Java La plataforma Android 1. Presentación 13 2. Origen 14 3. Google Play 15 3.1 Creación de una Cuenta de desarrollador 16 3.2 Publicación de una aplicación 17 3.3 Seguimiento y actualización de una aplicación

Más detalles

Android Programming. Código: S15 Duración: 25 horas OBJETIVOS

Android Programming. Código: S15 Duración: 25 horas OBJETIVOS Código: S15 Duración: 25 horas Este curso intensivo prepara a programadores para desarrollar aplicaciones para la plataforma Android. Los alumnos aprenderán a configurar un entorno de desarrollo para Android,

Más detalles

CAPÍTULO 1. Visión general y entorno de desarrollo... 21

CAPÍTULO 1. Visión general y entorno de desarrollo... 21 Índice general Lista de acrónimos... xv Cómo leer este libro?... xvii CAPÍTULO 1. Visión general y entorno de desarrollo... 21 1.1. Qué hace que Android sea especial?... 22 1.2. Los orígenes... 23 1.3.

Más detalles

Manual TeamViewer Manager 6.0

Manual TeamViewer Manager 6.0 Manual TeamViewer Manager 6.0 Revision TeamViewer 6.0-954 Índice 1 Resumen... 2 1.1 Acerca de TeamViewer Manager... 2 1.2 Acerca de este manual... 2 2 Instalación y arranque inicial... 3 2.1 Instalación...

Más detalles

Kosmo Desktop Manual de desarrollo Instalación y configuración del código fuente de Kosmo en Eclipse

Kosmo Desktop Manual de desarrollo Instalación y configuración del código fuente de Kosmo en Eclipse Kosmo Desktop Manual de desarrollo Instalación y configuración del código fuente de Kosmo en Eclipse Versión 3.0 02/12/2010 2 3 1 Control de versiones VERSIÓN AUTOR FECHA CAMBIO 1.0 SAIG, S.L. 22/03/2007

Más detalles

Manual de gestión de contenidos web en entorno Drupal. Versión sitio maestro para servicios 1.0

Manual de gestión de contenidos web en entorno Drupal. Versión sitio maestro para servicios 1.0 Manual de gestión de contenidos web en entorno Drupal Versión sitio maestro para servicios 1.0 Contenido Gestión de contenidos... 5 Crear contenidos... 5 Tipos de contenido... 5 Página básica o basic

Más detalles

Tema 2: Diseño de servicios para móviles

Tema 2: Diseño de servicios para móviles Tema 2: Diseño de servicios para móviles Listas y menús 2013-2014 Depto. Ciencia de la Computación e IA Puntos a tratar Spinners Listas Adaptadores Menús de opciones Menús contextuales 2 Spinner Cuadro

Más detalles

MODELO DE IMPLEMENTACIÓN

MODELO DE IMPLEMENTACIÓN Capítulo 4 MODELO DE IMPLEMENTACIÓN 4.1 Introducción El Modelo de implementación utiliza el resultado del Modelo de diseño para generar el código final en el lenguaje de programación elegido [10]. Aunque

Más detalles

Cursos El cerdito feliz. Programación de dispositivos Móviles con Android.

Cursos El cerdito feliz. Programación de dispositivos Móviles con Android. Cursos El cerdito feliz Programación de dispositivos Móviles con Android. Objetivo(s) del curso: El asistente desarrollará las habilidades y conocimientos necesarios para la programación de dispositivos

Más detalles

Programación en Android LSUB, GSYC, URJC

Programación en Android LSUB, GSYC, URJC Programación en Android LSUB, GSYC, URJC Introducción Teléfono o Emulador Android Studio Introducción Activity Mgr App! Dialer Dalvik JVM SQLite WebKit... OpenGL Linux kernel Dispositivo móvil Aplicaciones

Más detalles

www.android-spa.com Android Creación de una aplicación sencilla: Forwarding - Página 1 -

www.android-spa.com Android Creación de una aplicación sencilla: Forwarding - Página 1 - Android Creación de una aplicación sencilla: Forwarding - Página 1 - Realización de la aplicación Forwarding en Android Este es un pequeño tutorial con el que se realizará un pequeño programa para Android

Más detalles

Manual CMS Mobincube

Manual CMS Mobincube Manual CMS Mobincube CMS Mobincube Qué es? El CMS (Sistema de Gestión de Contenidos) es un completo website que permite la creación y actualización de contenido remoto. De esta forma, una vez creada una

Más detalles

Cursos de orientación profesional

Cursos de orientación profesional Cursos de orientación profesional ACCIONES COFINANCIADAS CON FONDOS COMUNITARIOS DEL FONDO SOCIAL EUROPEO, A TRAVÉS DEL PROGRAMA OPERATIVO FONDO SOCIAL EUROPEO DE CANARIAS 20072013 CON UN PORCENTAJE DE

Más detalles

GUIA APLICACIÓN DE SOLICITUDES POR INTERNET. Gestión de Cursos, Certificados de Aptitud Profesional y Tarjetas de Cualificación de Conductores ÍNDICE

GUIA APLICACIÓN DE SOLICITUDES POR INTERNET. Gestión de Cursos, Certificados de Aptitud Profesional y Tarjetas de Cualificación de Conductores ÍNDICE ÍNDICE ACCESO A LA APLICACIÓN... 2 1.- HOMOLOGACIÓN DE CURSOS... 4 1.1.- INICIAR EXPEDIENTE... 4 1.2.- CONSULTA DE EXPEDIENTES... 13 1.3.- RENUNCIA A LA HOMOLOGACIÓN... 16 2.- MECÁNICA DE CURSOS... 19

Más detalles

Instalación del Admin CFDI

Instalación del Admin CFDI Instalación del Admin CFDI Importante!!!Antes de comenzar verifique los requerimientos de equipo esto podrá verlo en la guía 517 en nuestro portal www.control2000.com.mx en el apartado de soporte, ahí

Más detalles

http://www.oracle.com/technetwork/es/java/javase/downloads/index.html

http://www.oracle.com/technetwork/es/java/javase/downloads/index.html Introducción En esta primera parte del taller iremos viendo paso a paso como poner a punto todo tu entorno de trabajo para poder empezar cuanto antes a desarrollar tu primera aplicación Android para luego

Más detalles

Vamos a comenzar creando un nuevo proyecto de Android utilizando los siguientes parámetros en el cuadro de diálogo:

Vamos a comenzar creando un nuevo proyecto de Android utilizando los siguientes parámetros en el cuadro de diálogo: 2.3. MULTIMEDIA En este capítulo vamos a mostrarle cómo añadir multimedia a nuestras aplicaciones Android. 2.3.1. REPRODUCCIÓN DE AUDIO Android es compatible con salidas de sonido y música a través de

Más detalles

1.1. Instalación del entorno de desarrollo

1.1. Instalación del entorno de desarrollo 1.1. Instalación del entorno de desarrollo Para el desarrollo de las aplicaciones vamos a poder utilizar un potente y moderno entorno de desarrollo. Al igual que Android, todas las herramientas están basadas

Más detalles

SIOM-Interfaz AM Manual de Usuario

SIOM-Interfaz AM Manual de Usuario SIOM-Interfaz AM Manual de Usuario Alfonso XI, 6 28014 Madrid F(+34) 91 524 03 96 www.omie.es Ref. MU_InterfazAM.doc Versión 5.0 Fecha: 2014-09-10 ÍNDICE 1 INTRODUCCIÓN 3 2 REQUISITOS PREVIOS 4 2.1 COMPONENTES

Más detalles

MANUAL DE USUARIO Guía de Gestión de la Configuración con Subversion

MANUAL DE USUARIO Guía de Gestión de la Configuración con Subversion MANUAL DE USUARIO Guía de Gestión de la Configuración con Subversion Versión 1.8 Área de Integración y Arquitectura de Aplicaciones Hoja de Control Título Documento de Referencia Responsable Guía de Gestión

Más detalles

Almacenamiento en Android

Almacenamiento en Android Almacenamiento en Android LSUB, GYSC, URJC Todo lo que hay que saber http://developer.android.com/guide/topics/ data/data-storage.html Dentro de una Aplicación Ya hemos visto, que se puede pasar con el

Más detalles

Entorno de desarrollo Instalación y configuración

Entorno de desarrollo Instalación y configuración Entorno de desarrollo Instalación y configuración GExCALL Formación http://gexcall.unex.es/formacion El plugin ADT (Android Development Tools) extiende al IDE Eclipse. Te permite crear y depurar aplicaciones

Más detalles

Manual de Instrucciones

Manual de Instrucciones Manual de Instrucciones Audi connect (myaudi) Manual de Instrucciones Audi connect (myaudi) Spanisch 11.2014 152566AMH60 www.audi.com Audi Vorsprung durch Technik 2014 AUDI AG AUDI AG trabaja constantemente

Más detalles

Características del cliente en Outlook Web Access

Características del cliente en Outlook Web Access Exchange 2007 Características del cliente en Outlook Web Access En este tema se explican las nuevas y mejoradas características del cliente en Outlook Web Access en Microsoft Exchange Server 2007. Estas

Más detalles

Guía para proveedores de contenido. LiLa Portal Guía para proveedores de contenido. Crear Experimentos

Guía para proveedores de contenido. LiLa Portal Guía para proveedores de contenido. Crear Experimentos Library of Labs Content Provider s Guide Guía para proveedores de contenido LiLa Portal Guía para proveedores de contenido En el entorno de LiLa, los proveedores de contenido son los responsables de crear

Más detalles

Mi Primer Proyecto en Android Studio

Mi Primer Proyecto en Android Studio Mi Primer Proyecto en Android Studio Para crear un nuevo proyecto ejecutaremos Android Studio y desde la pantalla de bienvenida pulsaremos la opción Start a new Android Studio project para iniciar el asistente

Más detalles

Interfaces de usuario [Desarrollo de aplicaciones para Android]

Interfaces de usuario [Desarrollo de aplicaciones para Android] Interfaces de usuario [Desarrollo de aplicaciones para Android] M. en C. Sergio Luis Pérez Pérez UAM CUAJIMALPA, MÉXICO, D. F. Trimestre 14-P Sergio Luis Pérez (UAM CUAJIMALPA) Curso de Interfaces de Usuario

Más detalles

Guía administración Intelligent Watcher

Guía administración Intelligent Watcher Guía administración Intelligent Watcher Enero de 2015 1 Contenido 1. Acceso... 3 2. Sectores y categorías... 3 3. Usuarios... 8 4. Campos, tipos y plantillas de contenido... 11 5. Gestión de informaciones...

Más detalles

Look!: Framework para Aplicaciones de Realidad Aumentada en Android

Look!: Framework para Aplicaciones de Realidad Aumentada en Android Look!: Framework para Aplicaciones de Realidad Aumentada en Android Diseño de aplicaciones con Look! Sergio Bellón Alcarazo Jorge Creixell Rojo Ángel Serrano Laguna En este tutorial se proponen los pasos

Más detalles

Microsoft Outlook 2003

Microsoft Outlook 2003 Elementos básicos de Outlook... 3 Panel de exploración... 3 Outlook para Hoy... 3 Personalizar Outlook para hoy... 4 Carpetas de correo... 5 Bandeja de salida... 5 Borrador... 5 Correo electrónico no deseado...

Más detalles

Escuela Superior de Ingeniería

Escuela Superior de Ingeniería Escuela Superior de Ingeniería Programación en Internet Grado en Ingeniería Informática Invocación de un servicio web REST desde una aplicación Android Autores: Javier Montes Cumbrera y Salvador Carmona

Más detalles

Manual del Webmail Correo Corporativo y Groupware

Manual del Webmail Correo Corporativo y Groupware Manual del Webmail Correo Corporativo y Groupware Sogo es el webmail desde el que se controlan todos los servicios del Correo Corporativo y Groupware, un novedoso producto de comunicación corporativa de

Más detalles

ebox: Servidor de dominio Windows libre y gratuito

ebox: Servidor de dominio Windows libre y gratuito ebox: Servidor de dominio Windows libre y gratuito Guía de instalación y configuración Manuel Morán Vaquero mmv@edu.xunta.es Febrero 2010 Esta guía está basada en la versión 1.2 de ebox Índice 1 Introducción

Más detalles

Doli Caldav. Calendarios remotos en Dolibarr

Doli Caldav. Calendarios remotos en Dolibarr Doli Caldav Calendarios remotos en Dolibarr 1 Índice de contenidos 1. Instalación y configuración del módulo 1. Instalación 2. Descarga de librerías externas (necesario) 2. Creación de calendarios 1. Listado

Más detalles

TUTORIAL GOOGLE DOCS

TUTORIAL GOOGLE DOCS TUTORIAL GOOGLE DOCS Las principales ventajas de Google Docs son: Nuestros documentos se almacenan en línea: esto nos permite acceder a ellos desde cualquier ordenador con conexión a internet, y compartirlos

Más detalles

U2. Introducción al desarrollo de aplicaciones móviles Smartphone y Android Desarrollo de Aplicaciones III TIC-UTSV Enero 2015 I.S.C.

U2. Introducción al desarrollo de aplicaciones móviles Smartphone y Android Desarrollo de Aplicaciones III TIC-UTSV Enero 2015 I.S.C. U2. Introducción al desarrollo de aplicaciones móviles Smartphone y Android Desarrollo de Aplicaciones III TIC-UTSV Enero 2015 I.S.C. Rogelio Vázquez Hernández Smartphone Termino utilizado para referirse

Más detalles

Manual de uso de webmail-pronto

Manual de uso de webmail-pronto Manual de uso de webmail-pronto Tipo de documento: Manual de uso de webmail-pronto : Manual de usuario webmail - v..2.doc Elaborado por: Redabogacia Modificaciones respecto a la revisión anterior 2 Nº

Más detalles

Sophos Mobile Control Guía de inicio

Sophos Mobile Control Guía de inicio Sophos Mobile Control Guía de inicio Versión: 5 Edición: abril 2015 Contenido 1 Acerca de esta guía...3 1.1 Terminología...3 2 Licencias de Sophos Mobile Control...5 2.1 Licencias de evaluación...5 3 Pasos

Más detalles

Inicio rápido de Novell Messenger 3.0.1 para móviles

Inicio rápido de Novell Messenger 3.0.1 para móviles Inicio rápido de Novell Messenger 3.0.1 para móviles Mayo de 2015 Novell Messenger 3.0.1 y versiones posteriores están disponibles para dispositivos móviles ios, Android o BlackBerry. Dado que puede entrar

Más detalles

MANUAL DE CREACIÓN DE CARPETAS PARA ACCESO POR FTP DE CLIENTES EN UN NAS

MANUAL DE CREACIÓN DE CARPETAS PARA ACCESO POR FTP DE CLIENTES EN UN NAS MANUAL DE CREACIÓN DE CARPETAS PARA ACCESO POR FTP DE CLIENTES EN UN NAS Vamos a explicar en varios pasos cómo crear una carpeta para que un cliente concreto con un usuario y una contraseña acceda sólo

Más detalles

Taller Desarrollo Mobile. Tecnólogo Informática - 6to Semestre Montevideo

Taller Desarrollo Mobile. Tecnólogo Informática - 6to Semestre Montevideo Taller Desarrollo Mobile Tecnólogo Informática - 6to Semestre Montevideo Android Databases Android DBs La información estructurada es accesible a través de SQLite y los Content Providers. Cada aplicacion

Más detalles

Pruebas de unidad con JUnit

Pruebas de unidad con JUnit Pruebas de unidad con JUnit Cuando se implementa software, resulta recomendable comprobar que el código que hemos escrito funciona correctamente. Para ello, implementamos pruebas que verifican que nuestro

Más detalles

Android avanzado. Sesión 6: Depuración y pruebas. Experto en Desarrollo de Aplicaciones para Dispositivos Móviles

Android avanzado. Sesión 6: Depuración y pruebas. Experto en Desarrollo de Aplicaciones para Dispositivos Móviles Android avanzado Sesión 6: Depuración y pruebas 2012-2013 Depto. Ciencia de la Computación e IA Puntos a tratar Conectar un dispositivo Hardware Depuración con Eclipse Log y LogCat Dalvik Debug Monitor

Más detalles

Tabla de contenido. Manual de referencias para el Usuario Webmail UNE

Tabla de contenido. Manual de referencias para el Usuario Webmail UNE Manual de usuario Buzón de correo UNE 02 03 Tabla de contenido 1. Ingresar por primera vez 4 1.1 Cambiar su clave al entrar por primera vez 5 2. Preferencias 7 2.1 Iniciar sesión como 8 2.2 Cambiar contraseña

Más detalles

TELEFÓNICA MÓVILES ESPAÑA, S.A.U. Software para Soporte Unificado de Facturación

TELEFÓNICA MÓVILES ESPAÑA, S.A.U. Software para Soporte Unificado de Facturación TELEFÓNICA MÓVILES ESPAÑA, S.A.U. Software para Soporte Unificado de Facturación Manual de Usuario SOFIA GESTIÓN V.5 Pág. 2 de 300 S O F T W A R E P A R A S O P O R T E U N I F I C A D O D E F A C T U

Más detalles

Curso Online de Programación Android

Curso Online de Programación Android Curso Online de Programación Android Presentación Android es el sistema operativo más usado en dispositivos móviles como teléfonos inteligentes o tablets. El sistema es actualmente desarrollado por Google

Más detalles

SQL Data Export for PS/PSS

SQL Data Export for PS/PSS Version 2.3.5 MANUAL DE INSTRUCCIONES (M98232701-01-13B) CIRCUTOR, SA ÍNDICE 1.- INSTALACIÓN DEL SOFTWARE SQL DATA EXPORT... 3 1.1.- HABILITAR CONEXIONES REMOTAS DEL SERVIDOR SQL SERVER... 14 1.2.- DESINSTALAR

Más detalles

MANUAL. J. Enrique Durán Colaborador TIC Huesca

MANUAL. J. Enrique Durán Colaborador TIC Huesca MANUAL ÍNDICE 1.- QUÉ ES DROPBOX. 2.- DESCARGA DE DROPBOX 3.- INTRODUCCIÓN 4.- ARCHIVOS 4.1.- INVITAR A CARPETA 4.2.- COMPARTIR VÍNCULO 4.3.- DESCARGAR 4.4.- ELIMINAR 4.5.- CAMBIAR NOMBRE 4.6.- MOVER 4.7.-

Más detalles

Manual de usuario Terminal control de Rondas CONTROL DE RONDAS GS. Manual de usuario para el sistema de control de rondas versión 3.

Manual de usuario Terminal control de Rondas CONTROL DE RONDAS GS. Manual de usuario para el sistema de control de rondas versión 3. Manual de usuario Terminal control de Rondas CONTROL DE RONDAS GS 1 Lea el manual para entender la estructura básica del producto, rendimiento, función y conocimientos básicos acerca de la instalación,

Más detalles

1. Como empezar 1.1. Arquitectura Android 1.2. Dalvik VM 1.3. Componentes de Android 1.4. Entorno de Desarrollo Android 1.5. Una Aplicación sencilla

1. Como empezar 1.1. Arquitectura Android 1.2. Dalvik VM 1.3. Componentes de Android 1.4. Entorno de Desarrollo Android 1.5. Una Aplicación sencilla 1. Como empezar 1.1. Arquitectura Android 1.2. Dalvik VM 1.3. Componentes de Android 1.4. Entorno de Desarrollo Android 1.5. Una Aplicación sencilla en Android 1.6. El Emulador Android 1.7. Formatos de

Más detalles

PECO-GRAPH Manual de Usuario

PECO-GRAPH Manual de Usuario ESPAÑOL PECO-GRAPH Manual de Usuario Software para la gestión gráfica de datos de conteo ÍNDICE 1 INTRODUCCIÓN...3 2 INSTALACIÓN...4 2.1 REQUISITOS...4 2.2 INSTALACIÓN Y EJECUCIÓN...4 3 FUNCIONALIDAD Y

Más detalles

Programación 2 Curso 2013 2014. Guía de desarrollo C/C++ con Eclipse

Programación 2 Curso 2013 2014. Guía de desarrollo C/C++ con Eclipse Programación 2 Curso 2013 2014 Introducción Guía de desarrollo C/C++ con Eclipse Eclipse 1 es un entorno de programación que permite el desarrollo de aplicaciones en diferentes lenguajes. Consta de un

Más detalles

Guía de actualización del sistema nómina red internet

Guía de actualización del sistema nómina red internet Guía de actualización del sistema nómina red internet Requerimientos del equipo Importante!!! Antes de empezar a realizar la actualización de su sistema es necesario considerar lo siguiente: configuraciones

Más detalles

3.4. Reload Editor ( Guía de Uso).

3.4. Reload Editor ( Guía de Uso). 3.4. Reload Editor ( Guía de Uso). Anterior 3. Lors Management Siguiente 3.4. Reload Editor ( Guía de Uso). 3.4.1. Preguntas básicas sobre Reload Editor. - Qué hace el programa Reload Editor? RELOAD Editor

Más detalles

Índice. Herramientas de desarrollo. Historia Qué es Android? Arquitectura del sistema. Componentes Android Modelos de Negocio

Índice. Herramientas de desarrollo. Historia Qué es Android? Arquitectura del sistema. Componentes Android Modelos de Negocio 1 Introducción a Android Índice Historia Qué es Android? Arquitectura del sistema Herramientas de desarrollo Componentes Android Modelos de Negocio 2 Objetivos Herramientas de desarrollo Conocer las herramientas

Más detalles

AVD, el emulador de Smartphone y Tablets Android que incluye el SDK de Google

AVD, el emulador de Smartphone y Tablets Android que incluye el SDK de Google AVD, el emulador de Smartphone y Tablets Android que incluye el SDK de Google Para probar nuestras aplicaciones Google nos proporciona un emulador de dispositivos conocido como AVD (Android Virtual Devices).

Más detalles

Arsys Backup Online Manual de Usuario

Arsys Backup Online Manual de Usuario Arsys Backup Online Manual de Usuario 1 Contenido 1. Instalación del Programa Cliente... 3 Pasos previos... 3 Instalación... 3 Configuración del acceso... 6 Ubicación del servidor de seguridad... 6 Datos

Más detalles

Persistencia. Mecanismos de persistencia. Preferencias. Curso 12/13

Persistencia. Mecanismos de persistencia. Preferencias. Curso 12/13 Curso 12/13 Aplicaciones Persistencia Mecanismos de persistencia Sistema de ficheros (privado) Bases de datos SQLite Almacenamiento externo (público) Conexiones de red 2 Las preferencias son una forma

Más detalles

HOW TO SOBRE REMOTE ACCESS VPN MODE EN LINUX

HOW TO SOBRE REMOTE ACCESS VPN MODE EN LINUX HOW TO SOBRE REMOTE ACCESS VPN MODE EN LINUX 1- En este how to realizaremos una conexión remota mediante vpn; lo que haremos es comprobar primero que las maquinas que vamos a conectar, se puedan ver y

Más detalles

www.apliqr.com Manual de Usuario - v0.1 Manual de usuario v0.1

www.apliqr.com Manual de Usuario - v0.1 Manual de usuario v0.1 Manual de usuario v0.1 1 Índice de contenidos 1.Iniciar sesión...3 2.Crear una cuenta de usuario...4 3.Elegir un plan de servicio...5 4.Pasar a un plan de pago...7 5.Ver el plan contratado...8 6.Códigos

Más detalles

Guía de inicio rápido a

Guía de inicio rápido a Guía de inicio rápido a Office 365 para pequeñas empresas La experiencia web La experiencia de aplicaciones de escritorio La experiencia móvil Ayuda y comunidad de Office 365 Microsoft Office 365 para

Más detalles

Curso 12/13. Desarrollo de Aplicaciones Android. Persistencia

Curso 12/13. Desarrollo de Aplicaciones Android. Persistencia Curso 12/13 Desarrollo de Aplicaciones Persistencia Mecanismos de persistencia Preferencias Sistema de ficheros (privado) Bases de datos SQLite Almacenamiento externo (público) Conexiones de red 2 Preferencias

Más detalles