AppWidget Android: Ejemplo usando BroadcastReceiver y Localización

0
18613

AppWidget Android: Ejemplo usando BroadcastReceiver y Localización

Nota: Carlos García impartió una charla de dos horas sobre el desarrollo de aplicaciones en Android, puedes verla haciendo clic aquí.

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
de manera que cuando hagamos clic en el mismo se nos abrirá un navegador Web con la información que nos provee la wikipedia sobre
la persona a la cual se le dedicó la calle.

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
estará en color rojo cuando el GPS esté apagado o fuera de cobertura y en negro en caso contrario cambiándose el texto del botón por el nombre
de la calle en la cual nos ubicamos al desplazarnos.

Este ejemplo servirá para conocer algunos de los conceptos y posibilidades que nos ofrece Android, como por ejemplo:

  • Configuración y arquitectura de las aplicaciones en Android
  • BroadcastReceiver
  • Widget
  • Localización
  • RemoteViews

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:

  1. 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.
  2. 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.
  3. 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).
  4. 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 variable widgetView apuntará a la ventana widget_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ón manager.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; } }

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad