AppWidget Android: Ejemplo usando BroadcastReceiver y Localización
Introducción
En este tutorial vamos a realizar un Widget embebido en el escritorio (AppWidget) cuya funcionalidad será la de mostrar la calle actual en la que estamos ubicados En el medio de la imagen que se muestra a la derecha se muestra la apariencia visual que tendrá el Widget, de manera que el texto del botón Este ejemplo servirá para conocer algunos de los conceptos y posibilidades que nos ofrece Android, como por ejemplo:
|
Si quieres trastear, puedes descargarte el código fuente desde clic aquí. Si quieres probarlo directamente
en tu dispositivo puedes descargarte la aplicación desde clic aquí
Se presupone que el lector ya tiene ciertos conocimientos sobre Android.
Manos a la obra… el ejemplo paso a paso:
AndroidManifest.xml
A continuación puede observar:
- Lineas 30 y 31: La aplicación necesita permisos para conectarse a Internet así como tener acceso a un método de localización exacto como el GPS.
- La aplicación está basada completamente en eventos pues sólo estará viva cuando el sistema lance eventos relacionados con el ciclo de vida de un Widget (APPWIDGET_UPDATE, APPWIDGET_ENABLED, APPWIDGET_DISABLED) o cuando
se generen notificaciones relacionadas con el posicionamiento. En este segundo caso, nos hemos creado una acción propia (com.autentia.intent.GPS_LOCATION_CHANGED) y posteriormente le diremos a Android
que genere una notificación con ese action cuando existan eventos de localización. Nos hemos tenido que crear una acción propia, porque no existe ninguna definida en Android para este tipo de notificaciones. - Linea 27: La versión mínima de SDK que debe tener el dispositivo es la versión 3 que es la que tienen la gran mayoría (Android 1.5).
- Linea 14: Indicamos al WidgetProvider las propiedades del Widget (tiempo de refresco, dimensiones que debe reservar para el mismo, ventana GUI inicial, etc.)
res/xml/widget_properties.xml
Después de dias enteros acostándome a horas poco normales no he conseguido activar/desactivar el GPS desde programación. Nadie en
ningún foro contesta esta pregunta… por favor, si tú sabes como hacerlo, compártelo con todos 😉
Por cierto, creo que es de las pocas cosas que no puedes controlar desde programación… el resto no he visto limitaciones hasta la fecha.
Bueno, la cuestión es que al colocar el Widget en el escritorio, es posible que el GPS no esté activo, y si no está activo no
puedes registrar un oyente de eventos de localización… por lo que cada 20 segundos hago que salte el evento WIDGET_UPDATE y así solvento este problema.
res/layout/widget_main.xml
La ventana principal del Widget que consta de una imagen con el Logo de Autentia y
un botón en donde se mostrará la calle en la que nos ubicamos.
Si te fijas, en ambos controles gráficos uso estilos (tag style) para configurar la apariencia.
Ojo!!, en los GUI de este tipo de Widgets (AppWidgets) no puedes colocar cualquier control gráfico de Android (Spinner, etc) están muy limitados!! (como es lógico pues se supone que es para ver información… que no quita que al hacer clic se abra una ventana de configuración, etc.)
res/values/styles.xml
Definición de los estilos… recuerda que esto es un tutorial… tiene miga todo esto, pueden hederarse, etc.
res/values/colors.xml
Definición de colores (por supuesto todo esto no es obligatorio…)
#FF0000 #000000
res/values/strings.xml
En este archivo definimos los recursos de cadenas de caracteres… internacionalización por medio del típico mecanismo de prefijos _es, etc..
Widget StreetInfo Ubicación desconocida Ubicación actual http://www.google.com/search?domains=wikipedia.org&hl=es&q=
res/values/arrays.xml
Definición de un array que nos servirá para posteriormente en el código fuente quitar
de los resultados de ubicación que nos de el GPS las palabras que contenga este array,
por ejemplo si el GPS nos dice que estamos en la calle «Plaza de Manuel Becerra» en el botón mostraremos «Manuel Becerra».
- Calle de la
- Avenida de la
- Plaza de la
- Calle del
- Avenida del
- Plaza del
- Plaza de
- Calle de
- Avenida de
- Av. de
src/com.autentia.android.widget.streetinfo.StreetInfoWidget.java
El siguiente BroadcastReceiver (Receptor de eventos o notificaciones) será invocado cuando se produzcan los eventos
que fueron definidos anteriormente en el archivo de configuración AndroidManifest.xml
Si el evento es ACTION_APPWIDGET_UPDATE o ACTION_APPWIDGET_ENABLED, solicitaremos el LocationManager que lance
la notificación «com.autentia.intent.GPS_LOCATION_CHANGED»… que tratará nuestro siguiente BroadcastReceiver…
En caso de que no sea ninguno de esos dos eventos, será el evento ACTION_APPWIDGET_DISABLED (el usuario ha quitado el Widget de la pantalla u otros motivos) y
desregistraremos la notificación de eventos.
Los valores 0,0 de la instrucción requestLocationUpdates....
indican el tiempo y los metros transcurridos entre evento y evento.. dejándolos a 0 ahorramos energía, ya que dejamos que el sistema lo gestione y lance eventos cuando crea necesario (si estamos parados no lanzará).
package com.autentia.android.widget.streetinfo; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.content.Intent; import android.content.Context; import android.location.LocationManager; /** * Clase principal del Widget * @author Carlos García. Autentia */ public class StreetInfoWidget extends android.content.BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); PendingIntent locationIntent = PendingIntent.getBroadcast(context, 0, new Intent("com.autentia.intent.GPS_LOCATION_CHANGED"), PendingIntent.FLAG_UPDATE_CURRENT); LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); locationManager.removeUpdates(locationIntent); if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action) || AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)){ locationManager.requestLocationUpdates (LocationManager.GPS_PROVIDER, 0, 0, locationIntent); } } }
src/com.autentia.android.widget.streetinfo.LocationUpdatesBroadcastReceiver.java
Bueno, en esta clase hay más chicha… empecemos
- Esta clase tratará los eventos de localización: GPS habilitado, deshabilitado, temporalmente fuera de servicio y cambio de localización.
- Cada evento tiene unos datos asociados (Bundle).
- Para modificar la vista del Widget y sus controles desde este proceso, usaremos RemoteView.
La variablewidgetView
apuntará a la ventanawidget_main
y luego con instrucciones como ésta:
widgetView.setTextColor(R.id.street, context.getResources().getColor(R.color.enabled));
modificaremos uno de los controles que contiene (en este caso modificaremos el color del botón) y
posteriormente con la instrucciónmanager.updateAppWidget(streetInfoWidget, widgetView)
le diremos al administrador de Widget que lo repinte. - La clase
android.location.Geocoder
nos servirá para traducir puntos geográficos (latitud, longitud) en direcciones legibles para las personas (calle, pais, ciudad, etc).
package com.autentia.android.widget.streetinfo; import java.util.List; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.content.ComponentName; import android.content.Intent; import android.content.Context; import android.location.Address; import android.location.Geocoder; import android.location.Location; import android.location.LocationManager; import android.location.LocationProvider; import android.net.Uri; import android.os.Bundle; import android.widget.RemoteViews; /** * Recivirá notificaciones de localización * @author Carlos García. Autentia */ public class LocationUpdatesBroadcastReceiver extends android.content.BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Bundle data = intent.getExtras(); RemoteViews widgetView = new RemoteViews(context.getPackageName(), R.layout.widget_main); boolean isGPSProviderEnabled = true; try { // Analizamos los valores que nos entregan (dependerá del evento) if (data.containsKey(LocationManager.KEY_LOCATION_CHANGED)){ Location point = (Location) data.get(LocationManager.KEY_LOCATION_CHANGED); Geocoder geocoder = new Geocoder(context); List addrs = geocoder.getFromLocation(point.getLatitude(), point.getLongitude(), 1); String street = this.parseAddress(context, addrs.get(0).getAddressLine(0)); // Por ejemplo: Calle de Pedró de alarcón, 30 // Modificamos el TextView con el contenido de la calle widgetView.setTextViewText(R.id.street, street); // Modificamos el comportamiento del Button para que cuando hagamos clic en el // se abra un Activity con capacidad de mostrar el contenido de una URL String domain = context.getResources().getString(R.string.wikiDomain); PendingIntent pendingIntent = PendingIntent.getActivity(context, 0 , new Intent(Intent.ACTION_VIEW, Uri.parse(domain + street)), 0); widgetView.setOnClickPendingIntent(R.id.street, pendingIntent); } else if (data.containsKey(LocationManager.KEY_STATUS_CHANGED)){ isGPSProviderEnabled = (data.getInt(LocationManager.KEY_STATUS_CHANGED) == LocationProvider.AVAILABLE); } else if (data.containsKey(LocationManager.KEY_PROVIDER_ENABLED)){ isGPSProviderEnabled = data.getBoolean(LocationManager.KEY_PROVIDER_ENABLED); } } catch (java.io.IOException e) { // No se dará } // Cuando el GPS esté en On y haya cobertura el botón tendrá un estilo u otro if (isGPSProviderEnabled){ widgetView.setTextColor(R.id.street, context.getResources().getColor(R.color.enabled)); } else { widgetView.setTextColor(R.id.street, context.getResources().getColor(R.color.disabled)); } // Actualizamos la ventana del widget ComponentName streetInfoWidget = new ComponentName(context, StreetInfoWidget.class); AppWidgetManager manager = AppWidgetManager.getInstance(context); manager.updateAppWidget(streetInfoWidget, widgetView); } /** * @return Dada una dirección con el formato "Calle de Pedro de alarcón, 30" devuelve una cadena "Pedro de alarcón" */ private String parseAddress(Context context, String addressLine) { int pos = addressLine.indexOf(","); if (pos != -1){ addressLine = addressLine.substring(0, pos); } String[] addrTokens = context.getResources().getStringArray(R.array.invalidTokensAddress); for (int i = 0, count = addrTokens.length; i < count; i++){ addressLine = addressLine.replaceAll(addrTokens[i], ""); } return addressLine; } }