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=" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="fill_parent"> <fragment android:name="es.ua.jtech.fragments.principalfragment" android:id="@+id/principal_fragment" android:layout_weight="1" android:layout_width="0dp" android:layout_height="match_parent" /> <fragment android:name="es.ua.jtech.fragments.detallefragment" android:id="@+id/detalle_fragment" 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=" android:id="@+id/fragment_container" 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: #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=" android:id="@+id/map" 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=" android:id="@+id/map" 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 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: 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 especialistamoviles@gmail.com, 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" android:icon="@drawable/icon" android:label="@string/service_name" > </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=" android:minwidth="146dip" android:minheight="72dip" android:updateperiodmillis=" " android:label="mi Widget" android:initiallayout="@layout/miwidget_layout" /> 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 android:id="@+id/miwidget" xmlns:android=" android:layout_width="wrap_content" android:layout_height="wrap_content"> <AnalogClock android:id="@+id/analogclock1" android:layout_width="45dp" android:layout_height="45dp" /> <TextView android:text="" android:id="@+id/textview01" 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=" android:versioncode="1" android:versionname="1.0" package="es.ua.jtech.av.appwidget"> <uses-sdk android:minsdkversion="7" /> <application android:icon="@drawable/icon" android:label="@string/app_name"> <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=" " android:initiallayout="@layout/miwidget_layout" 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" android:resource="@xml/miwidget_conf" /> </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=" android:id="@+id/lytlayout" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="horizontal" android:background="#555555" android:padding="5dip" > <ImageView android:id="@+id/imgicono" android:layout_height="wrap_content" android:layout_width="wrap_content" android:src="@drawable/marcador" /> <TextView android:id="@+id/txtmensaje" 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

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

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

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

CONCEPTOS BASICOS. Febrero 2003 Página - 1/10

CONCEPTOS BASICOS. Febrero 2003 Página - 1/10 CONCEPTOS BASICOS Febrero 2003 Página - 1/10 EL ESCRITORIO DE WINDOWS Se conoce como escritorio la zona habitual de trabajo con windows, cuando iniciamos windows entramos directamente dentro del escritorio,

Más detalles

Notas para la instalación de un lector de tarjetas inteligentes.

Notas para la instalación de un lector de tarjetas inteligentes. Notas para la instalación de un lector de tarjetas inteligentes. Índice 0. Obtención de todo lo necesario para la instalación. 3 1. Comprobación del estado del servicio Tarjeta inteligente. 4 2. Instalación

Más detalles

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

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

Curso Internet Básico - Aularagon

Curso Internet Básico - Aularagon Antes de empezar es necesario que tengas claro algunas cosas: para configurar esta cuenta de correo, debes saber que el POP y el SMTP en este caso son mail.aragon.es; esta cuenta de correo hay que solicitarla

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

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

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

MANUAL DE AYUDA TAREA PROGRAMADA COPIAS DE SEGURIDAD

MANUAL DE AYUDA TAREA PROGRAMADA COPIAS DE SEGURIDAD MANUAL DE AYUDA TAREA PROGRAMADA COPIAS DE SEGURIDAD Fecha última revisión: Diciembre 2010 Tareas Programadas TAREAS PROGRAMADAS... 3 LAS TAREAS PROGRAMADAS EN GOTELGEST.NET... 4 A) DAR DE ALTA UN USUARIO...

Más detalles

Manual de uso de la plataforma para monitores. CENTRO DE APOYO TECNOLÓGICO A EMPRENDEDORES -bilib

Manual de uso de la plataforma para monitores. CENTRO DE APOYO TECNOLÓGICO A EMPRENDEDORES -bilib Manual de uso de la plataforma para monitores CENTRO DE APOYO TECNOLÓGICO A EMPRENDEDORES -bilib [Manual de uso de la plataforma para monitores] 1. Licencia Autor del documento: Centro de Apoyo Tecnológico

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

Servicios de la plataforma Android

Servicios de la plataforma Android Servicios de la plataforma Android Sesión 1: Librerías de compatibilidad y servicios 2012-2013 Depto. Ciencia de la Computación e IA Puntos a tratar Compatibilidad de versiones Fragmentos Loaders Librería

Más detalles

Cómo creo las bandejas del Registro de Entrada /Salida y de Gestión de Expedientes?

Cómo creo las bandejas del Registro de Entrada /Salida y de Gestión de Expedientes? Preguntas frecuentes Cómo creo las bandejas del Registro de Entrada /Salida y de Gestión de Expedientes? Atención! Esta opción es de configuración y solamente la prodrá realizar el administrador de la

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

CIF-KM. GUÍA DE LOS PRIMEROS PASOS

CIF-KM. GUÍA DE LOS PRIMEROS PASOS CIF-KM. GUÍA DE LOS PRIMEROS PASOS Secciones 1. CONCEPTOS PREVIOS. 2. INSTALAR CIF-KM. 2.1 Descargar e instalar CIF-KM. 2.2 Configuración de CIF-KM. 2.3 Acceso externo al servidor de CIF-KM. 3. PRIMERA

Más detalles

Combinar correspondencia (I)

Combinar correspondencia (I) Combinar correspondencia (I) Mediante la opción Combinar correspondencia Word2007 nos permite incluir en un documento, datos almacenados en otro sitio. De esta forma podremos obtener copias de un mismo

Más detalles

NORMA 34.14(SEPA) 05/11/2013

NORMA 34.14(SEPA) 05/11/2013 NORMA 34.14(SEPA) 05/11/2013 1. Descripción La aplicación de generación de ficheros de transferencias permite generar fácilmente órdenes para que se efectúe el pago de transferencias a los beneficiarios

Más detalles

Escudo Movistar Guía Rápida de Instalación Dispositivos Symbian

Escudo Movistar Guía Rápida de Instalación Dispositivos Symbian Escudo Movistar Guía Rápida de Instalación Dispositivos Symbian Guía de Instalación Página 1 Índice ESCUDO MOVISTAR.... 3 1. INSTALACIÓN DEL SERVICIO ESCUDO MOVISTAR... 3 1.1. VERSIONES SOPORTADAS... 3

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

INSTALACIÓN DE ORACLE 8i (8.1.7) SOBRE NT

INSTALACIÓN DE ORACLE 8i (8.1.7) SOBRE NT INSTALACIÓN DE ORACLE 8i (8.1.7) SOBRE NT Versión 1. Mayo de 2001 Luis Vinuesa Martínez. Departamento de Informática Universidad de Oviedo vinuesa@correo.uniovi.es www.di.uniovi.es/~vinuesa ÍNDICE. Introducción...

Más detalles

Guía Rápida de Inicio

Guía Rápida de Inicio Guía Rápida de Inicio 1. Acerca de esta Guía Esta guía le ayudará a instalar y dar los primeros pasos con BitDefender Security for SharePoint. Para disponer de instrucciones detalladas, por favor, diríjase

Más detalles

GENERACIÓN DE ANTICIPOS DE CRÉDITO

GENERACIÓN DE ANTICIPOS DE CRÉDITO GENERACIÓN DE ANTICIPOS DE CRÉDITO 1 INFORMACIÓN BÁSICA La aplicación de generación de ficheros de anticipos de crédito permite generar fácilmente órdenes para que la Caja anticipe el cobro de créditos

Más detalles

Proceso de cifrado. La fortaleza de los algoritmos es que son públicos, es decir, se conocen todas las transformaciones que se aplican al documento

Proceso de cifrado. La fortaleza de los algoritmos es que son públicos, es decir, se conocen todas las transformaciones que se aplican al documento Qué es AT-Encrypt nos permitirá dotar de contraseña a cualquier documento o carpeta. Este documento o carpeta sólo será legible por aquel que conozca la contraseña El funcionamiento del cifrado (o encriptación)

Más detalles

En términos generales, un foro es un espacio de debate donde pueden expresarse ideas o comentarios sobre uno o varios temas.

En términos generales, un foro es un espacio de debate donde pueden expresarse ideas o comentarios sobre uno o varios temas. 1 de 18 Inicio Qué es un foro En términos generales, un foro es un espacio de debate donde pueden expresarse ideas o comentarios sobre uno o varios temas. En el campus virtual, el foro es una herramienta

Más detalles

Toda base de datos relacional se basa en dos objetos

Toda base de datos relacional se basa en dos objetos 1. INTRODUCCIÓN Toda base de datos relacional se basa en dos objetos fundamentales: las tablas y las relaciones. Sin embargo, en SQL Server, una base de datos puede contener otros objetos también importantes.

Más detalles

CÓMO CREAR NUESTRO CATÁLOGO

CÓMO CREAR NUESTRO CATÁLOGO CÓMO CREAR NUESTRO CATÁLOGO Mediante la aplicación (http://www.prensasoft.com/programas/conline) podemos crear nuestros propios catálogos. Para crear un catálogo necesitamos: - Varios productos que mostrar,

Más detalles

Adaptación al NPGC. Introducción. NPGC.doc. Qué cambios hay en el NPGC? Telf.: 93.410.92.92 Fax.: 93.419.86.49 e-mail:atcliente@websie.

Adaptación al NPGC. Introducción. NPGC.doc. Qué cambios hay en el NPGC? Telf.: 93.410.92.92 Fax.: 93.419.86.49 e-mail:atcliente@websie. Adaptación al NPGC Introducción Nexus 620, ya recoge el Nuevo Plan General Contable, que entrará en vigor el 1 de Enero de 2008. Este documento mostrará que debemos hacer a partir de esa fecha, según nuestra

Más detalles

Person IP CRM Manual MOBILE

Person IP CRM Manual MOBILE Manual MOBILE División Informática BuscPerson Telecomunicaciones : Manual MOBILE 0.- Introducción 3 0.1 Configuración de los terminales 3 0.2 Acceso de Usuarios 3 1.- Funcionalidades CRM 5 1.1 Agenda del

Más detalles

WINDOWS 2008 5: TERMINAL SERVER

WINDOWS 2008 5: TERMINAL SERVER WINDOWS 2008 5: TERMINAL SERVER 1.- INTRODUCCION: Terminal Server proporciona una interfaz de usuario gráfica de Windows a equipos remotos a través de conexiones en una red local o a través de Internet.

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

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

MANUAL COPIAS DE SEGURIDAD

MANUAL COPIAS DE SEGURIDAD MANUAL COPIAS DE SEGURIDAD Índice de contenido Ventajas del nuevo sistema de copia de seguridad...2 Actualización de la configuración...2 Pantalla de configuración...3 Configuración de las rutas...4 Carpeta

Más detalles

Programa diseñado y creado por 2014 - Art-Tronic Promotora Audiovisual, S.L.

Programa diseñado y creado por 2014 - Art-Tronic Promotora Audiovisual, S.L. Manual de Usuario Programa diseñado y creado por Contenido 1. Acceso al programa... 3 2. Opciones del programa... 3 3. Inicio... 4 4. Empresa... 4 4.2. Impuestos... 5 4.3. Series de facturación... 5 4.4.

Más detalles

GVisualPDA Módulo de Almacén

GVisualPDA Módulo de Almacén GVisualPDA Módulo de Almacén GVisualPDA es una aplicación para Windows Mobile 5/6 que amplía más aún las posibilidades de integración del software de gestión GVisualRec permitiendo estar conectados en

Más detalles

Accesibilidad web GUÍA FUNCIONAL

Accesibilidad web GUÍA FUNCIONAL Accesibilidad web GUÍA FUNCIONAL 0 _ ÍNDICE 01_Introducción 02_Primeros pasos 03_Conceptos 04_Navegación por voz 05_Navegación por teclado 06_Navegación por sonido 07_Compatibilidad con lectores de pantalla

Más detalles

Manual de usuario de Parda Programa de Almacenamiento y Recuperación de Datos Automático

Manual de usuario de Parda Programa de Almacenamiento y Recuperación de Datos Automático Programa de Almacenamiento y Recuperación de Datos Automático CONSEJERÍA DE EDUCACIÓN Dirección General de Participación e Innovación Educativa Centro de Gestión Avanzado de Centros TIC Fecha: 20/04/10

Más detalles

SICAE S.L.L. Almansa nº 4.28224.Pozuelo de Alarcón (Madrid). www.sicae.es. 91 799 07 66.Manual general de C. de Inventario/Ordenador V.1 Pag.

SICAE S.L.L. Almansa nº 4.28224.Pozuelo de Alarcón (Madrid). www.sicae.es. 91 799 07 66.Manual general de C. de Inventario/Ordenador V.1 Pag. SICAE S.L.L. Almansa nº 4.28224.Pozuelo de Alarcón (Madrid). www.sicae.es. 91 799 07 66.Manual general de C. de Inventario/Ordenador V.1 Pag. 1 INDICE Entrada y Menú principal.... 2 Configuración de la

Más detalles

CATÁLOGO CATÁLOGO CATÁLOGO CATÁLOGO CATÁLOGO

CATÁLOGO CATÁLOGO CATÁLOGO CATÁLOGO CATÁLOGO CATÁLOGO MANUAL DE USUARIO CATÁLOGO MANUAL DE USUARIO CATÁLOGO MANUAL DE USUARIO 1. CATÁLOGO MANUAL DE USUARIO CATÁLOGO AHORA CATÁLOGO MANUAL DE USUARIO 1 1. Introducción AHORA Catálogo es una aplicación

Más detalles

Manual hosting acens

Manual hosting acens Manual hosting acens Contenido Acceso al panel de control de cliente... 3 Asociar un dominio a mi Hosting... 5 Acceso al panel de administración del hosting... 7 INICIO - Visión general del estado de nuestro

Más detalles

MANUAL DE LA APLICACIÓN HELP DESK

MANUAL DE LA APLICACIÓN HELP DESK CASAMOTOR MANUAL DE LA APLICACIÓN HELP DESK Desarrollado por: NOVIEMBRE, 2012 BOGOTÁ D.C. - COLOMBIA INTRODUCCIÓN Este documento es el manual de la aplicación de Help Desk de Casamotor, producto desarrollado

Más detalles

INDICE. 1. Introducción... 4. 2. El panel Entities view... 5. 3. El panel grafico... 6. 4. Barra de botones... 6. 4.1. Botones de Behavior...

INDICE. 1. Introducción... 4. 2. El panel Entities view... 5. 3. El panel grafico... 6. 4. Barra de botones... 6. 4.1. Botones de Behavior... MANUAL DE USUARIO INDICE 1. Introducción... 4 2. El panel Entities view... 5 3. El panel grafico... 6 4. Barra de botones... 6 4.1. Botones de Behavior... 7 4.2. Botones de In-agents... 8 4.3. Botones

Más detalles

port@firmas V.2.3.1 Manual de Portafirmas V.2.3.1

port@firmas V.2.3.1 Manual de Portafirmas V.2.3.1 Manual de Portafirmas V.2.3.1 1 1.- Introducción 2.- Acceso 3.- Interfaz 4.- Bandejas de peticiones 5.- Etiquetas 6.- Búsquedas 7.- Petición de firma 8.- Redactar petición 9.- Firma 10.- Devolución de

Más detalles

SMS Gestión. manual de uso

SMS Gestión. manual de uso SMS Gestión manual de uso índice qué es SMS Gestión 2 acceso al servicio 3 01 acceso con la clave de servicios de Orange 4 02 acceso personalizado 6 02.1 cómo personalizar su acceso a la aplicación 7 02.2

Más detalles

Guía rápida de la Oficina Virtual (Solicit@V5) Área Web y Administración Electrónica

Guía rápida de la Oficina Virtual (Solicit@V5) Área Web y Administración Electrónica Guía rápida de la Oficina Virtual (Solicit@V5) Área Web y Administración Electrónica HOJA DE CONTROL Título Nombre del Fichero Autores Guía rápida de la Oficina Virtual (Solicit@V5) UHU_GuiaRapidaSolicita_V5.pdf

Más detalles

GENERACIÓN DE TRANSFERENCIAS

GENERACIÓN DE TRANSFERENCIAS GENERACIÓN DE TRANSFERENCIAS 1 INFORMACIÓN BÁSICA La aplicación de generación de ficheros de transferencias permite generar fácilmente órdenes para que la Caja efectúe transferencias, creando una base

Más detalles

SEPARAR Y ADJUNTAR UNA BASE DE DATOS. Separar una base de datos

SEPARAR Y ADJUNTAR UNA BASE DE DATOS. Separar una base de datos SEPARAR Y ADJUNTAR UNA BASE DE DATOS Separar una base de datos Al separar una base de datos la está eliminando de la instancia de SQL Server, pero la deja intacta en sus archivos de datos y en los archivos

Más detalles

MANUAL PARA GESTIÓN DE INCIDENCIAS INFORMÁTICAS

MANUAL PARA GESTIÓN DE INCIDENCIAS INFORMÁTICAS MANUAL PARA GESTIÓN DE INCIDENCIAS INFORMÁTICAS En este manual aprenderemos a introducir un Ticket de Soporte (Incidencia Informática) y ver todo el proceso hasta que se resuelve. Para poder escribir Tickets

Más detalles

Gestión de Retales WhitePaper Noviembre de 2009

Gestión de Retales WhitePaper Noviembre de 2009 Gestión de Retales WhitePaper Noviembre de 2009 Contenidos 1. Introducción 3 2. Almacén de retales 4 3. Propiedades de los materiales 6 4. Alta de retales 8 5. Utilización de retales en un lote de producción

Más detalles

CASO PRÁCTICO. ANÁLISIS DE DATOS EN TABLAS DINÁMICAS

CASO PRÁCTICO. ANÁLISIS DE DATOS EN TABLAS DINÁMICAS CASO PRÁCTICO. ANÁLISIS DE DATOS EN TABLAS DINÁMICAS Nuestra empresa es una pequeña editorial que maneja habitualmente su lista de ventas en una hoja de cálculo y desea poder realizar un análisis de sus

Más detalles

Una vez que tengamos el padrón de un determinado tributo con todos sus datos actualizados, podemos generar los recibos de ese padrón.

Una vez que tengamos el padrón de un determinado tributo con todos sus datos actualizados, podemos generar los recibos de ese padrón. 11. RECIBOS. Desde esta opción de Menú vamos a completar el proceso de gestión de los diferentes tributos, generando recibos, informes de situación, impresiones, etc. 11.1. GENERACIÓN DE RECIBOS. Una vez

Más detalles

Ejecución del programa de instalación de Windows XP

Ejecución del programa de instalación de Windows XP Ejecución del programa de instalación de Windows XP Productos: Windows XP Professional Al instalar Windows XP Professional debe proporcionar información acerca de cómo desea instalar el sistema operativo.

Más detalles

Instalación y Registro Versiones Educativas 2013

Instalación y Registro Versiones Educativas 2013 Instalación y Registro Versiones Educativas 2013 Octubre 2012 Instalación y Registro Online página 2 Índice Introducción... 4 Instalación Versión Educativa Aula... 6 1. Setup... 6 2. Instalación... 7 3.

Más detalles

MANUAL DE USUARIO DE LA APLICACIÓN DE ACREDITACION DE ACTIVIDADES DE FORMACION CONTINUADA. Perfil Entidad Proveedora

MANUAL DE USUARIO DE LA APLICACIÓN DE ACREDITACION DE ACTIVIDADES DE FORMACION CONTINUADA. Perfil Entidad Proveedora MANUAL DE USUARIO DE LA APLICACIÓN DE ACREDITACION DE ACTIVIDADES DE FORMACION CONTINUADA Perfil Entidad Proveedora El objetivo del módulo de Gestión de Solicitudes vía Internet es facilitar el trabajo

Más detalles

Apéndice 5 Manual de usuario de ColeXión. ColeXión 1.0. Manual de usuario

Apéndice 5 Manual de usuario de ColeXión. ColeXión 1.0. Manual de usuario Apéndice 5 Manual de usuario de ColeXión ColeXión 1.0 Manual de usuario Índice 1. Qué es ColeXión?... 2 2. Requerimientos del sistema... 3 3. Instalación de ColeXión... 3 4. Creación de un nuevo esquema...

Más detalles

Herramienta Encuestas. MiAulario

Herramienta Encuestas. MiAulario Herramienta Encuestas MiAulario Introducción... 2 Menú de la herramienta... 3 Panel de encuestas... 3 Mis encuestas... 4 Añadir encuesta... 4 Mis plantillas... 7 Añadir elemento: pregunta o cabecera...

Más detalles

UNIDAD DIDACTICA 3 USUARIOS Y GRUPOS EN REDES WINDOWS 2003 SERVER II

UNIDAD DIDACTICA 3 USUARIOS Y GRUPOS EN REDES WINDOWS 2003 SERVER II UNIDAD DIDACTICA 3 USUARIOS Y GRUPOS EN REDES WINDOWS 2003 SERVER II Eduard Lara 1 1. USUARIOS DE ACTIVE DIRECTORY Las cuentas de usuario en el Active Directory tienen la catalogación de cuentas DNS. Cada

Más detalles

PLANTILLAS EN MICROSOFT WORD

PLANTILLAS EN MICROSOFT WORD PLANTILLAS EN MICROSOFT WORD Una plantilla es un modelo o patrón para crear nuevos documentos. En una plantilla se guarda internamente el formato utilizado, es decir, el estilo de la fuente, el tamaño,

Más detalles

Manual de usuario de Windows Live Writer

Manual de usuario de Windows Live Writer Manual de usuario de Windows Live Writer Índice 0.- Introducción. 3 1.- Descarga e Instalación. 4 2.- Conexión a un blog. 7 3.- Interfaz de Windows Live Writer. 12 4.- Creación de un Post. 13 5.- Creación

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

Content Manager 2 Guía del usuario

Content Manager 2 Guía del usuario Content Manager 2 Guía del usuario Lea esta guía para aprender a utilizar Content Manager 2 para buscar, adquirir, descargar e instalar actualizaciones y contenido extra en su Becker PND. 1.) Cómo instalar

Más detalles

CÓMO CONFIGURAR DHCP EN SUSE LINUX

CÓMO CONFIGURAR DHCP EN SUSE LINUX CÓMO CONFIGURAR DHCP EN SUSE LINUX Pedro Manuel Lemus Sánchez Profesor de Informática en el IES Suárez de Figueroa En ocasiones cuando oímos hablar de un Sistema Operativo (S.O.) de libre distribución,

Más detalles

AGREGAR UN EQUIPO A UNA RED Y COMPARTIR ARCHIVOS CON WINDOWS 7

AGREGAR UN EQUIPO A UNA RED Y COMPARTIR ARCHIVOS CON WINDOWS 7 Tutoriales de ayuda e información para todos los niveles AGREGAR UN EQUIPO A UNA RED Y COMPARTIR ARCHIVOS CON WINDOWS 7 Como agregar a una red existente un equipo con Windows 7 y compartir sus archivos

Más detalles

UNIDESYS UNIVERSAL BUSINESS SYSTEMS INSTALACIÓN NUEVO PUESTO DE TRABAJO

UNIDESYS UNIVERSAL BUSINESS SYSTEMS INSTALACIÓN NUEVO PUESTO DE TRABAJO www.ubs-systems.com Teléfono: 91 3681185 UNIDESYS UNIVERSAL BUSINESS SYSTEMS INSTALACIÓN NUEVO PUESTO DE TRABAJO Unidesys Versión 2011 1 CONTENIDO 1 INTRODUCCIÓN 3 2 FUENTES DE DATOS 4 3 INSTALACIÓN DEL

Más detalles

Seminario de Informática

Seminario de Informática Unidad II: Operaciones Básicas de Sistemas Operativos sobre base Windows 11. Herramientas del Sistema INTRODUCCION Este apunte está basado en Windows XP por ser el que estamos utilizando en el gabinete

Más detalles

Acronis License Server. Guía del usuario

Acronis License Server. Guía del usuario Acronis License Server Guía del usuario TABLA DE CONTENIDO 1. INTRODUCCIÓN... 3 1.1 Generalidades... 3 1.2 Política de licencias... 3 2. SISTEMAS OPERATIVOS COMPATIBLES... 4 3. INSTALACIÓN DE ACRONIS LICENSE

Más detalles

19 4.1.1.0 4 04/05/2009

19 4.1.1.0 4 04/05/2009 Soluciones Informáticas Descripción: Como utilizar la Agenda de Visitas Objetivos: Al finalizar este tutorial el usuario será capaz de utilizar la Agenda de Visitas con sus diferentes opciones: asignar

Más detalles

Manual de iniciación a

Manual de iniciación a DOCUMENTACIÓN Picasa y otras nubes Manual de iniciación a DROPBOX 1 Últimamente se ha hablado mucho de la nube y de cómo es el futuro de la Web. También se han presentado servicios y aplicaciones que ya

Más detalles

TÉCNICAS DE GESTIÓN ADMINISTRATIVA PARA PEQUEÑAS EMPRESAS

TÉCNICAS DE GESTIÓN ADMINISTRATIVA PARA PEQUEÑAS EMPRESAS COMBINAR CORRESPONDENCIA CON OFFICE 2003 Combinar correspondencia nos permite incluir en un documento datos almacenados en otro lugar. De esta forma podremos obtener copias de un mismo documento pero con

Más detalles

PANEL DE CONTROL (Zona de Administración) MANUAL DE USO Por conexanet. Revisión 1.1 Fecha 2006-08

PANEL DE CONTROL (Zona de Administración) MANUAL DE USO Por conexanet. Revisión 1.1 Fecha 2006-08 PANEL DE CONTROL (Zona de Administración) MANUAL DE USO Por conexanet Revisión 1.1 Fecha 2006-08 Índice 1. Acceder 2. Menú 3. Gestión Básica 3.1 Añadir 3.2 Editar 3.3 Eliminar 3.4 Eliminación de registros

Más detalles

Departamento CERES Área de Tarjetas Inteligentes Manual de Usuario

Departamento CERES Área de Tarjetas Inteligentes Manual de Usuario 14 CORREO SEGURO. Hay aplicaciones de correo que permiten enviar y recibir correos cifrados y firmados digitalmente utilizando criptografía. Estas operaciones garantizan el intercambio seguro de información,

Más detalles

Manual de operación Tausend Monitor

Manual de operación Tausend Monitor Manual de operación Tausend Monitor Luego de haber realizado satisfactoriamente el proceso de instalación, al iniciar el programa le aparecerá la siguiente ventana: El usuario principal y con el primero

Más detalles

MANUAL DE AYUDA MODULO TALLAS Y COLORES

MANUAL DE AYUDA MODULO TALLAS Y COLORES MANUAL DE AYUDA MODULO TALLAS Y COLORES Fecha última revisión: Enero 2010 Índice TALLAS Y COLORES... 3 1. Introducción... 3 CONFIGURACIÓN PARÁMETROS TC (Tallas y Colores)... 3 2. Módulos Visibles... 3

Más detalles

GESTIÓN DOCUMENTAL PARA EL SISTEMA DE CALIDAD

GESTIÓN DOCUMENTAL PARA EL SISTEMA DE CALIDAD GESTIÓN DOCUMENTAL PARA EL SISTEMA DE CALIDAD Manual de usuario 1 - ÍNDICE 1 - ÍNDICE... 2 2 - INTRODUCCIÓN... 3 3 - SELECCIÓN CARPETA TRABAJO... 4 3.1 CÓMO CAMBIAR DE EMPRESA O DE CARPETA DE TRABAJO?...

Más detalles

Sistema Integrado de Control de Presencia Dactilar

Sistema Integrado de Control de Presencia Dactilar Sistema Integrado de Control de Presencia Dactilar Índice Índice... 1 Ventana Principal de la aplicación... 2 Zona de Administración... 7 Mantenimiento de trabajadores... 9 Parámetros... 12 1 Ventana Principal

Más detalles

Contenido. Email: capacitacion@u cursos.cl / Teléfono: 9782450

Contenido. Email: capacitacion@u cursos.cl / Teléfono: 9782450 GMI Contenido PUBLICAR AVISO... 3 CREAR PROCESO DE SELECCIÓN... 6 VER/ELIMINAR AVISOS PUBLICADOS... 8 ETAPAS DE UN PROCESO DE SELECCIÓN... 10 SECCIONES DE LOS PROCESOS DE SELECCIÓN (GPS)... 21 PERSONALIZAR

Más detalles

WINDOWS 2008 7: COPIAS DE SEGURIDAD

WINDOWS 2008 7: COPIAS DE SEGURIDAD 1.- INTRODUCCION: WINDOWS 2008 7: COPIAS DE SEGURIDAD Las copias de seguridad son un elemento fundamental para que el trabajo que realizamos se pueda proteger de aquellos problemas o desastres que pueden

Más detalles

Sitios remotos. Configurar un Sitio Remoto

Sitios remotos. Configurar un Sitio Remoto Sitios remotos Definir un sitio remoto significa establecer una configuración de modo que Dreamweaver sea capaz de comunicarse directamente con un servidor en Internet (por eso se llama remoto) y así poder

Más detalles

Instalar protocolo, cliente o servicio nuevo. Seleccionar ubicación de red. Práctica - Compartir y conectar una carpeta

Instalar protocolo, cliente o servicio nuevo. Seleccionar ubicación de red. Práctica - Compartir y conectar una carpeta Configuración de una red con Windows Aunque existen múltiples sistemas operativos, el más utilizado en todo el mundo sigue siendo Windows de Microsoft. Por este motivo, vamos a aprender los pasos para

Más detalles

HOOTSUITE: GESTOR DE CUENTAS EN REDES SOCIALES

HOOTSUITE: GESTOR DE CUENTAS EN REDES SOCIALES HOOTSUITE: GESTOR DE CUENTAS EN REDES SOCIALES Índice del curso 1. HootSuite Qué es?... 3 QUÉ ES?... 3 2. HootSuite Por qué?... 5 POR QUÉ?... 5 3. Registro... 6 REGISTRO... 6 4. Interfaz... 7 INTERFAZ...

Más detalles

MANUAL DE AYUDA HERRAMIENTA DE APROVISIONAMIENTO

MANUAL DE AYUDA HERRAMIENTA DE APROVISIONAMIENTO MANUAL DE AYUDA HERRAMIENTA DE APROVISIONAMIENTO Fecha última revisión: Junio 2011 INDICE DE CONTENIDOS HERRAMIENTA DE APROVISIONAMIENTO... 3 1. QUÉ ES LA HERRAMIENTA DE APROVISIONAMIENTO... 3 HERRAMIENTA

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

GESTINLIB GESTIÓN PARA LIBRERÍAS, PAPELERÍAS Y KIOSCOS DESCRIPCIÓN DEL MÓDULO DE KIOSCOS

GESTINLIB GESTIÓN PARA LIBRERÍAS, PAPELERÍAS Y KIOSCOS DESCRIPCIÓN DEL MÓDULO DE KIOSCOS GESTINLIB GESTIÓN PARA LIBRERÍAS, PAPELERÍAS Y KIOSCOS DESCRIPCIÓN DEL MÓDULO DE KIOSCOS 1.- PLANTILLA DE PUBLICACIONES En este maestro crearemos la publicación base sobre la cual el programa generará

Más detalles

10. El entorno de publicación web (Publiweb)

10. El entorno de publicación web (Publiweb) 10. El entorno de publicación web (Publiweb) 10.1. Introducción El entorno de publicación Web es una herramienta que permite la gestión de nuestras páginas Web de una forma visual. Algunos ejemplos de

Más detalles

Manual para la utilización de PrestaShop

Manual para la utilización de PrestaShop Manual para la utilización de PrestaShop En este manual mostraremos de forma sencilla y práctica la utilización del Gestor de su Tienda Online mediante Prestashop 1.6, explicaremos todo lo necesario para

Más detalles

Anexo A Diagramas de Navegación

Anexo A Diagramas de Navegación Anexo A Diagramas de Navegación Figura D.1: Diagrama de navegación de la pantalla principal. 43 Figura D.2: Diagrama de navegación del apartado Crear Encuesta. 44 Figura D.3: Diagrama de navegación del

Más detalles

GUÍA DE USUARIO DEL CORREO

GUÍA DE USUARIO DEL CORREO REPÚBLICA BOLIVARIANA DE VENEZUELA MINISTERIO DEL PODER POPULAR PARA LA EDUCACIÓN DIRECCIÓN GENERAL DE LA OFICINA DE ADMINISTRACIÓN Y SERVICIOS DIVISIÓN DE SOPORTE TÉCNICO Y FORMACIÓN AL USUARIO GUÍA DE

Más detalles

Traslado de Copias y Presentación de Escritos. Manual de Usuario V.3.1

Traslado de Copias y Presentación de Escritos. Manual de Usuario V.3.1 Traslado de Copias y Presentación de Escritos Manual de Usuario V.3.1 Página: 2 45 INDICE INTRODUCCIÓN... 3 1 ACCESO A LA APLICACIÓN... 3 2 PROCESO DE FIRMA... 4 3 TRASLADOS PENDIENTES DE ACEPTAR POR EL

Más detalles

Presentaciones. Con el estudio de esta Unidad pretendemos alcanzar los siguientes objetivos:

Presentaciones. Con el estudio de esta Unidad pretendemos alcanzar los siguientes objetivos: UNIDAD 8 Presentaciones Reunión. (ITE. Banco de imágenes) as presentaciones son documentos formados por una sucesión de páginas, llamadas diapositivas, que transmiten información estructurada de manera

Más detalles

Manual Oficina Web de Clubes - Federaciones Autono micas y Delegaciones

Manual Oficina Web de Clubes - Federaciones Autono micas y Delegaciones Manual Oficina Web de Clubes - Federaciones Autono micas y Delegaciones Este manual muestra el funcionamiento de una Federación Autonómica o Delegación en el uso de Intrafeb, todos los pasos que a continuación

Más detalles

GIT Dinahosting 3. Hola!

GIT Dinahosting 3. Hola! GIT Manual de uso GIT Dinahosting 2 Contenidos Sobre Git...3 Software...3 Cómo conectarse?...3 Volcar un repositorio remoto...4 Manejar el repositorio...5 Trabajando con ramificaciones...6 Fusionando ramificaciones...6

Más detalles

Cómo instalar fácilmente tu WordPress tras contratar un hosting en Hostalia

Cómo instalar fácilmente tu WordPress tras contratar un hosting en Hostalia Cómo instalar fácilmente tu WordPress tras contratar un hosting en Hostalia Cardenal Gardoki, 1 48008 BILBAO (Vizcaya) Teléfono: 902 012 199 www.hostalia.com La puesta en marcha de WordPress es muy sencilla,

Más detalles

5. Composer: Publicar sus páginas en la web

5. Composer: Publicar sus páginas en la web 5. Composer: Publicar sus páginas en la web Si nuestras páginas existen únicamente en el disco duro local, sólo nosotros podremos navegar por ellas, pero nadie más podrá hacerlo. Composer nos permite publicarlas

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

UNIDAD DIDACTICA 4 INTEGRACIÓN DE CLIENTES WINDOWS EN UN DOMINIO

UNIDAD DIDACTICA 4 INTEGRACIÓN DE CLIENTES WINDOWS EN UN DOMINIO UNIDAD DIDACTICA 4 INTEGRACIÓN DE CLIENTES Eduard Lara 1 1. CONFIGURACIÓN PREVIA DE LOS CLIENTES WINDOWS Objetivo: Configurar los clientes Windows XP/Vista en red para posteriormente poderlos integrar

Más detalles

Manualillo Italc 1.9.5 3/3/2010 Página 1

Manualillo Italc 1.9.5 3/3/2010 Página 1 Manual básico de Italc Iniciamos Italc mediante el icono del escritorio en el ordenador del profesor: Nos va a pedir un nombre de usuario y contraseña. Como usuario: alumno-clase y contraseña: alumno Una

Más detalles