Proyecto final de curso Android: Programación de aplicaciones

Anuncio
Proyecto final de curso
Android: Programación de aplicaciones (edición online, febrero 2011)
Nombre aplicación: AvisoSMS - “detección de cambio brusco avisando con mensajes
de un posible accidente”
Autor: Jose Parra Salort
Licencia: Autorizo la difusión del código fuente con fines educativos siempre que se haga
referencia al autor bajo los términos generales de la licencia “Academic Free License v.3.0”.
Motivación
Este proyecto pretende servir de utilidad a aquellas personas que por la realización de ciertas
actividades como ciclismo, senderismo, escaladores, albañiles que trabajen en fachadas, etcétera,
puedan estar expuestas a caídas, así como también a personas ancianas que necesiten un cuidado
más atento.
El funcionamiento de la aplicación desarrollada se basa en el uso del sensor “acelerómetro”, en el de
mecanismos localización (por red Wi-fi, telefónica móvil, o por GPS), en el de servicios y
notificaciones Android, y en el envío de SMS y de e-mails usando el protocolo SMTP.
Funcionalidad
La aplicación presenta las siguientes funcionalidades:



La aplicación se ejecutará como un servicio en segundo plano, aunque tendrá una simple
ventana desde donde se arrancará o detendrá el servicio comentado, y desde la que se
accederá a la configuración. Ver gráfico de ventanas en el siguiente apartado (nº 2). Además
al encender el teléfono, se arrancará el servicio, apareciendo la notificación correspondiente
en la barra de notificaciones.
Una vez en marcha el servicio, cuando el sensor “acelerómetro” detecte un cambio brusco, el
cual representará una caída repentina del dispositivo móvil, se localizará la posición del
dispositivo con el proveedor más adecuado en función de donde se encuentre aquel (dentro
de un edificio mediante wi-fi, o en el exterior mediante GPS, o con la red telefónica móvil).
Una vez establecidas las coordenadas de la posición, se enviará un mensaje de alerta bien por
SMS o bien por e-mail, pero no ambos, al número de teléfono o dirección electrónica
respectivamente, indicadas en la configuración.
 En el caso de envío por e-mail, la aplicación cargará un mapa de Google ubicando la posición del
dispositivo, hará una captura de pantalla y la enviará en el e-mail.
 Cuando el mensaje sea enviado por cualquiera de los dos modos, se actualizará la notificación
en la barra de notificaciones y se reproducirá un sonido: el de una campana (fichero
campana.mp3 que debe de existir en la carpeta /sdcard/avisos).


A partir de este momento no se volverá a considerar cualquier otro cambio brusco, pues el
servicio está implementado de forma que solo se detecte un cambio brusco. Si se desea que
la aplicación continúe monitorizando, hay que detener el servicio y volverlo a arrancar.
La aplicación en lo que se refiere a los textos de la interfaz gráfica como a los distintos
mensajes de información, está preparada tanto para el idioma español como para inglés.
Parámetros que se pueden configurar en las preferencias de la aplicación.
 Mostrar el mensaje de bienvenida: pues al principio es recomendable que la primera vez que
se ejecute se informe al usuario de que es importante que configure los parámetros.
 Tipo de mensaje: SMS o e-mail (SMTP).
 Dirección origen (para el caso de e-mail): indica la dirección desde la que se manda el e-mail
de alerta. NOTA: debe ser una cuenta de GMail.
 Password de la dirección origen (para el caso de e-mail): es el password de la dirección
anterior. Es necesario incluirlo pues de lo contrario, no se podrá enviar el e-mail de alerta.
 Dirección destino (para el caso de e-mail): indica la dirección a la que se manda el e-mail, en
este caso no es necesario que sea de GMail.
 Asunto (para el caso de e-mail): permite especificar el asunto del e-mail de alerta.
 Número de teléfono (para el caso de SMS): permite especificar el nº de teléfono de la
persona a la que se envía el SMS de alerta.
 Texto del mensaje (tanto para SMS como e-mail): permite especificar el texto que se enviará
ya sea mediante SMS o mediante e-mail. A dicho texto se le añadirán a continuación las
coordenadas de la ubicación del dispositivo en el momento del cambio brusco.
o Es recomendable que si se piensa utilizar en el modo SMS, el texto sea más corto de
65 caracteres ya que la aplicación le concatena las coordenadas, con lo cual se evitaría
que sobrepase los 160 caracteres, en cuyo caso se envía más de un SMS.
A destacar









Estilos aplicados tanto al layout principal como al de preferencias.
Servicios y notificaciones.
Receptores de anuncios: en particular arranque desde la carga del sistema operativo.
Uso de sensores: acelerómetro.
Envío de SMS y comprobación del envío.
Proveedores de localización.
Google Maps. Además se ha utilizado la clase OverLay para pintar un marcador la posición
del dispositivo.
Animaciones Tween: se ha usado una animación como truco para que no aparezca la
pantalla en negro mientras se carga el mapa.
Envío de e-mail con SMTP. En primer lugar se probó a implementar el mecanismo de envío
proporcionado por Android mediante las intenciones predeterminadas de envío.
Pero como dicho mecanismo requería
la interacción del usuario (lo cual resulta
incompatible con el objetivo de la
aplicación, pues el envío debe ser
totalmente automático), se optó por el
envío del correo electrónico utilizando
unas librerías encontradas en Internet
(http://mobiledevtuts.com/android/androi
d-sdk-smtp-email-tutorial/).
Dichas
librerías permiten el envío de correos
electrónicos usando SMTP. Además en
el proyecto Android se ha incluido una
clase envoltorio (Mail.java) de dichas
librerías para facilitar su uso desde el
servicio
de
monitorización
(ServicioAvisos).
 Imagen ejemplo enviada en el e-mail.
Esquema de pantallas
A continuación se explica en un gráfico, la interacción con la aplicación y los posibles estados de ésta:
2
1
3
4
¿Cómo se ha implementado? Lo más básico.
Existe una actividad principal (AvisosSMS.java) que es la que maneja el layout principal (Main.xml)
de acceso a la configuración y a los botones de arrancar y detener.
Pero las clases java más importantes son:
1. La clase ServicioAvisos.java
Representa un servicio que ejecutándose en segundo plano se encarga de detectar el
cambio brusco. Por lo tanto hereda de Service e implementa la interfaz SensorEventListener
para el manejo de eventos de sensores, en este caso para el sensor acelerómetro. Es por tanto el
manejador onSensorChanged(SensorEvent event), el centro del servicio, pues es donde se
detecta si ha habido un cambio brusco. A continuación se muestra su código, en el que destaca la
utilización de un filtro paso-alto para eliminar el efecto de la gravedad en el sensor.
Para detectar cambios bruscos, se han tenido en cuenta dos parámetros que se han
ajustado haciendo pruebas con el dispositivo móvil. El primero de ellos es factorDeAjuste, que se
usa en el filtro paso-alto y que se ha fijado en 0.3, y el segundo es topeCambioBrusco, que se usa en
el filtro paso-alto y que se ha fijado en 0.8. Estos valores se han obtenido probando con un
teléfono móvil Samsung Galaxy S 2.
@Override
public void onSensorChanged(SensorEvent event) {
synchronized (this) {
if (EJECUTADO)
return;
// El acelarometro servirá para detectar el movimiento brusco.
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
// Recoger posiciones x, y, z.
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
// Implementación de un filtro Paso-Alto para eliminar
// el efecto de la gravedad, que está presente aun cuando
// el dispositivo está quieto sobre la mesa.
float
lastX
lastY
lastZ
xPA =
yPA =
zPA =
xPA, yPA, zPA; // x,
= x * factorDeAjuste
= y * factorDeAjuste
= z * factorDeAjuste
Math.abs(x - lastX);
Math.abs(y - lastY);
Math.abs(z - lastZ);
y, z después del filtro.
+ lastX * (1.0f - factorDeAjuste);
+ lastY * (1.0f - factorDeAjuste);
+ lastZ * (1.0f - factorDeAjuste);
if (xPA > topeCambioBrusco || yPA > topeCambioBrusco
|| zPA > topeCambioBrusco) {
EJECUTADO = true;
// Busca la ubicación del Dispositivo.
AvisosSMS.UBICACION = DevolverPosicion();
if (AvisosSMS.UBICACION == null) {
Toast.makeText(this,
getText(R.string.infoPosicionNoEncontrada),
Toast.LENGTH_SHORT).show();
}
Integer tipoDeEnvio = Integer.parseInt(PREF_desdeServicio
.getString("tipoMensaje", "0"));
// Abrir un layout u otro según el tipo de envio.
switch (tipoDeEnvio) {
case 0:
Intent intencionSMS = new Intent(this, SMS.class);
intencionSMS.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
this.startActivity(intencionSMS);
break;
case 1:
Intent intencionEMailMapa = new Intent(this,
EMailMapa.class);
intencionEMailMapa
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
this.startActivity(intencionEMailMapa);
break;
}
}
}
}
}
El resto de métodos de ServicioAvisos son: para obtener la posición del dispositivo y para el
manejo de las notificaciones de la barra de notificaciones.
2. La clase SMS.java
Es una actividad que gestiona el envío de SMS, se lanza desde el evento ‘ onSensorChanged’
del servicio anterior. Implementa la interfaz EnvioDeAyuda la cual define los métodos enviar y
notificar. En el envío del SMS utiliza la clase SmsManager y también usa un receptor broadcast para
la acción “ACTION_SMS_SENT”, en la que se comprueba si el mensaje ha sido enviado o ha ocurrido
algún error.
3. La clase EMailMapa.java
Es una actividad que gestiona el envío de e-mail, se lanza desde el evento
‘onSensorChanged’ del servicio ServicioAvisos. Implementa la interfaz ‘EnvioDeAyuda’ la cual define
los métodos enviar y notificar. En este caso para el envío del e-mail, antes se debe de obtener
una imagen con la posición en un mapa Google Maps. Para ello abre un layout con un mapa, se
captura la pantalla, se guarda la captura en un fichero jpeg y finalmente construye un e-mail
donde adjunta dicho fichero. A continuación se muestra el código que realiza dicha captura de
pantalla y el almacenamiento en un fichero jpeg.
public void CapturaPantallaMapa() {
Bitmap imagenCapturada = null;
View vistaCapturada = null;
try {
vistaCapturada = findViewById(R.id.mapa);
vistaCapturada.setDrawingCacheEnabled(true);
imagenCapturada = Bitmap.createBitmap(vistaCapturada.getWidth(),
vistaCapturada.getHeight(), Bitmap.Config.ARGB_8888);
imagenCapturada = vistaCapturada.getDrawingCache();
} catch (Exception e) {
// Log.i("PANTALLA", "Exc_msg: " + e.getMessage());
// Log.i("PANTALLA", "Exc: " + e.toString());
}
try {
// Comprobando que el almacenamiento
// externo esté montado y disponible.
String estadoSD = Environment.getExternalStorageState();
File sd = Environment.getExternalStorageDirectory();
if (!estadoSD.equals(Environment.MEDIA_MOUNTED)
&& !estadoSD.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
Toast.makeText(this,
"It's not possible to access \' " + FICHERO + "\'",
Toast.LENGTH_LONG).show();
}
// El archivo que contendrá la captura.
File f = new File(sd, FICHERO);
if (sd.canWrite()) {
f.createNewFile();
OutputStream os = new FileOutputStream(f);
imagenCapturada.compress(Bitmap.CompressFormat.JPEG, 80, os);
os.close();
}
} catch (FileNotFoundException e) {
// Log.i("INFOAVISOS", "traza FileNotFoundException");
} catch (IOException e) {
// Log.i("INFOAVISOS", "traza IOException");
}
vistaCapturada.setDrawingCacheEnabled(false);
}
Además para asegurarse de que el mapa está completamente cargado (lo cual depende de la
conexión a Internet) se ha hecho uso del evento ‘onWindowFocusChanged’ para detectar un cambio de
foco en la pantalla y también de una animación Tween (hacer aparecer el mapa en 5 segundos de
forma progresiva).
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
// Se utiliza este evento, porque en este momento
// el mapa ya está pintado y se puede capturar la pantalla.
if (!CapturaREALIZADA) {
CapturaPantallaMapa();
// Enviar el mensaje.
enviar();
// Cierra la actividad
EMailMapa.this.finish();
CapturaREALIZADA = true;
}
}
El siguiente código, en el método onCreate de la actividad, ejecuta la animación y cuando termina
oculta el reloj.
// Animar el mapa. Animación 'Alpha' -> se hace visible en 5 segundos.
Animation animacion = AnimationUtils.loadAnimation(this,
R.anim.tweenanimation);
mapView.startAnimation(animacion);
animacion.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationEnd(Animation animation) {
// Al terminar la animación oculto el reloj de espera.
progressDialog.dismiss();
textViewAux.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationStart(Animation animation) {
}
});
También se ha hecho uso de un ProgressDialog para mostrar un reloj de sistema ocupado,
mientras se carga el mapa. Una vez cargado el mapa se esconde el reloj y se hace la captura.
4. La clase Mail.java
Es una clase envoltorio de las librerías de envío de SMTP. Destacan los métodos send,
addAttachment y los constructores. Por simplificar, se ha configurado en esta clase que el servidor
de envío sea el de GMail (smtp.gmail.com).
Las otras clases del proyecto son meramente auxiliares, una para las preferencias de configuración y
otra (ReceptorArranque.java) para que el servicio sea arrancado cuando se termina la carga del
sistema operativo.
Permisos solicitados por la aplicación (en el manifiesto):
-
Para permitir la localización del dispositivo:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
-
Para permitir el envío de SMSs:
<uses-permission android:name="android.permission.SEND_SMS" />
-
Para permitir el acceso a Internet:
<uses-permission android:name="android.permission.INTERNET" />
-
Para permitir guardar ficheros en el almacenamiento externo: guarda el jpeg que tiene que
adjuntar.
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-
Para permitir la notificación de que se ha cargado el Sistema Operativo.
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
Observaciones

Para probar la aplicación se puede lanzar el móvil sobre una superficie banda (ejemplo un
sofá o la cama) o bien agitarlo fuertemente con el brazo, es decir, dando una buena sacudida
con el brazo o golpe seco.
Descargar