Reconocimiento de actividades Android

¿Qué vamos a ver en este tutorial?

Voy a explicaros como usar la api de reconocimiento de actividades que nos proporciona Android, con esta api seremos capaces de reconocer que actividad está realizando el usuario como por ej. correr, andar, en vehículo, en bicicleta y parado, para poder hacer una aplicación basada en el contexto en el que se encuentra el usuario, podéis ver el ejemplo del reconocimiento de actividad en este código de ejemplo by Google.

Tenéis el código completo en GitHub para que podáis verlo con todo lujo de detalles: código en GitHub

Este tutorial está pensado para programadores, ya que no me voy a parar mucho en explicar como se crea un proyecto o como hacer ciertas cosas con java, pero tampoco va a ser muy difícil de seguir, si te gusta compártelo para ayudarnos 🙂 nunca viene mal cualquier ayuda, sin más, vamos a empezar.

1. Configuración del proyecto

Primero vamos a crear un proyecto Android con un nivel 14 mínimo de Api (yo trabajo con Android Studio que creo que es lo más conveniente para trabajar con Android…. ¿lógico verdad?), yo me he creado el proyecto con una actividad en blanco, una vez tengamos el proyecto creado y Gradle terminé de construir, vamos a modificar precisamente sus ficheros, abrimos el fichero de Gradle del módulo app, no el general, y añadimos dentro de nuestras dependencias la dependencia de localización para poder usar la api de reconocimiento de actividades:

compile 'com.google.android.gms:play-services-location:9.6.1'

Ahora tenemos que añadir los permisos necesarios en el manifiesto, en este caso tenemos que pedir el propio del reconocimiento de actividad:

<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" />

Una vez hecho esto ahora vamos sincronizar Gradle ya que hemos cambiado la configuración, después de esto vamos a crear un servicio que será el encargado de reconocer las actividades del usuario, yo lo he creado desde el navegador con botón derecho, nuevo, IntentService, ya que tiene que ser un IntentService y haciéndolo así Android Studio ya modifica el manifiesto para añadirlo, si no, recordar añadirlo en el manifiesto. El servicio quedaría así:

public class ReconocimientoService extends IntentService {

    public ReconocimientoService() {
        super("ReconocimientoService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {

    }

}

Como veis al ser un IntentService tenemos un método que es el onHandleIntent que será llamado cuando la api de reconocimiento nos devuelva intentos con la información de las actividades realizadas por el usuario.

2. Creamos la actividad principal

Primero vamos a crear un par de botones en la actividad principal para iniciar el servicio de reconocimiento y pararlo, así que nos vamos al layout del MainActivitiy y los añadimos:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="es.samsoft.android.recoactsamsoft.MainActivity">
    <Button
        android:id="@+id/bt_encender"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="Encender"
        android:enabled="false"
        android:onClick="registrarServicio"/>
    <Button
        android:id="@+id/bt_apagar"
        android:layout_below="@+id/bt_encender"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:enabled="false"
        android:text="Apagar"
        android:onClick="desregistrarServicio"/>
</RelativeLayout>

Con esto nos vamos a nuestro MainActivity para crear los métodos que hemos añadido a los botones, bueno, yo lo he hecho con el atajo Alt+Intro y seleccionando la opción del menú crear método xxxx en MainActivity, a mi me encantan los atajos de esta plataforma así que me veréis usarlos mucho y espero que vosotros también ya que son muy productivos. No voy a hacer bonito el layout ya que mi principal misión es que aprendáis a usar esta api y no hacer una app bonita, así que funcionalismo a tope. Por cierto, los botones están deshabilitados por defecto, ahora veremos para que.

Vamos a ver como queda el esqueleto mínimo del MainActivity:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void registrarServicio(View view) {
    }


    public void desregistrarServicio(View view) {
    }
}

3. Haciendo las peticiones a la api

Ahora vamos a añadir la api para registrar el servicio de reconocimiento de actividad que hemos creado antes, para esto tenemos que hacer que nuestra Activity implemente los métodos de la api (los callbacks de onConnected y los demás, os sonarán si habéis trabajado antes con las api de Google) y además añadiremos la variable de la api, para que quede claro os muestro como queda ahora nuestra Activity:

public class MainActivity extends AppCompatActivity 
        implements GoogleApiClient.ConnectionCallbacks, 
        GoogleApiClient.OnConnectionFailedListener{
    
    private GoogleApiClient api;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void registrarServicio(View view) {
    }


    public void desregistrarServicio(View view) {
    }

    @Override
    public void onConnected(@Nullable Bundle bundle) {
        
    }

    @Override
    public void onConnectionSuspended(int i) {

    }

    @Override
    public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {

    }
}

Para abreviar un poco y que esto no sea un tostón más allá de lo normal, vamos a suponer que nuestra api siempre se va a conectar sin ningún error, ni que se va a suspender la conexión, así que vamos a implementar solo el onConnected, solo tenéis que echar algo de imaginación al cazo para saber que hay que hacer en los otros dos métodos. Igualmente ahora lo que voy a hacer es cargar la api para dejarla preparada para cuando el usuario pulse el botón de registrar, añadiremos un progress dialog para que el usuario no pueda tocar ningún botón mientras la api se conecta, la cosa quedaría así:

public class MainActivity extends AppCompatActivity
        implements GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener{

    private GoogleApiClient api;
    private ProgressDialog progressDialog;
    
    private Button botonReg;
    private Button botonDesReg;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        //obtenemos los botones
        botonReg=(Button)findViewById(R.id.bt_encender);
        botonDesReg=(Button)findViewById(R.id.bt_apagar);
        //creamos el progress dialog
        progressDialog = ProgressDialog.show(this, "Conectando con la api",
                "Espere por favor");
        //creamos la api
        api=new GoogleApiClient.Builder(this)
                .addApi(ActivityRecognition.API)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .build();
        
        //mostramos el dialogo y conectamos con la api
        progressDialog.show();
        api.connect();

    }

    public void registrarServicio(View view) {
    }


    public void desregistrarServicio(View view) {
    }

    @Override
    public void onConnected(@Nullable Bundle bundle) {
        //si el dialogo se muestra lo cerramos
        if(progressDialog.isShowing())progressDialog.dismiss();
        //habilitamos los botones
        botonReg.setEnabled(true);
        botonDesReg.setEnabled(true);
    }

    @Override
    public void onConnectionSuspended(int i) {
        //si el dialogo se muestra lo cerramos
        if(progressDialog.isShowing())progressDialog.dismiss();
    }

    @Override
    public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
        //si el dialogo se muestra lo cerramos
        if(progressDialog.isShowing())progressDialog.dismiss();
    }
}

Con esto ya tendríamos la llamada a la api creada y conectada para registrar el servicio de reconocimiento cuando el usuario pulse el botón.

Ahora vamos a implementar el botón registrar, en este botón vamos a hacer el request propiamente dicho, el método queda así:

public void registrarServicio(View view) {
        //necesitamos tener controlado si el servicio ya estaba registrado anteriormente
        //asi que usare un SharedPreferences para guardar su estado
        SharedPreferences preferences=getSharedPreferences("preferencias",MODE_PRIVATE);
        
        if(!preferences.getBoolean("servicioOn",false)) {
            //si el servicio NO estaba registrado lo registramos
            Intent intent = new Intent(this, ReconocimientoService.class);
            PendingIntent pendingIntent = 
                    PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
            ActivityRecognition.ActivityRecognitionApi
                    .requestActivityUpdates(api, 5000, pendingIntent);
            //subimos al shared prefs la variable de control
            SharedPreferences.Editor editor=preferences.edit();
            editor.putBoolean("servicioOn",true);
            editor.apply();
        }
        else{
            //si ya estaba registrado anteriormente mostramos un mensaje al usuario
            Toast.makeText(this,"El servicio ya estaba registrado",Toast.LENGTH_LONG).show();
        }
    }

Lo primero y ante todo he creado una variable de control persistente con un SharedPreferences, aquí su documentación, con la que comprobaremos si ya habíamos registrado el servicio.

Si no estaba registrado el servicio procedemos a registrarlo y para ello creamos un intento con el servicio de reconocimiento y con ese intento, creamos un intento pendiente, que tiene los siguientes parámetros: contexto, número de petición (cualquier entero vale), el intento y las banderas que necesitemos, en este caso la de actualizar el servicio en el caso de que ya estuviera arrancado (por eso no es necesario controlar si ya estaba registrado).

Una vez creado el intento pendiente hacemos el request o petición a la api de reconocimiento con los siguientes parámetros: el apiCliente, la tasa de refresco en milisegundos (en mi caso 5 segundos) y el intento pendiente que hemos creado anteriormente. Por último he subido la variable de control al SharedPreferences para indicar que tenemos registrado el servicio.

Por no irnos aún del MainActivity vamos a implementar el método para parar el servicio de reconocimiento de actividades que queda como sigue:

public void desregistrarServicio(View view) {
    //obtenemos el shared preferences para preguntar si ya teniamos registrado el servicio
    SharedPreferences preferences=getSharedPreferences("preferencias",MODE_PRIVATE);

    if(preferences.getBoolean("servicioOn",false)) {
        //si estaba registrado lo detenemos
        Intent intent = new Intent(this, ReconocimientoService.class);
        PendingIntent pendingIntent =
                PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        ActivityRecognition.ActivityRecognitionApi.removeActivityUpdates(
                api,
                pendingIntent);
        //subimos al shared prefs la variable de control
        SharedPreferences.Editor editor=preferences.edit();
        editor.putBoolean("servicioOn",false);
        editor.apply();
    }
    else{
        //si no avisamos al usuario
        Toast.makeText(this,"El servicio ya estaba detenido",Toast.LENGTH_LONG).show();
    }
}

El código es bastante similar a cuando lo registramos solo que ahora llamamos al método de la api removeActivityUpdates.

4. Reconociendo las actividades del usuario

Como ya tenemos lista nuestra MainActivity vamos a por el servicio que es bastante sencillo de ver, este servicio es un IntentService por lo que hay que tener en cuenta que su ciclo de vida es lo que dure la tarea que realiza, no es persistente por lo que ni se os ocurra registrar algún broadcast ni nada que dependa de el o intentar mostrar un Toast, si lo hacéis por que sois muy osados os encontraréis con un problema y es que el servicio muere llevándose con el todo lo que estuviera registrado en él, quien avisa no es traidor ;).

Vamos a ver como queda y os explico paso a paso, lo primero vamos a implementar el método que es llamado cada vez que se detectan actividades, que es el onHandleIntent:

@Override
    protected void onHandleIntent(Intent intent) {
        if(ActivityRecognitionResult.hasResult(intent)) {
            ActivityRecognitionResult result = ActivityRecognitionResult.extractResult(intent);
            comprobarActividades( result.getProbableActivities() );
        }
    }

Es bastante sencillo ¿verdad?, simplemente cogemos el resultado del intento que nos llega desde la api cuando llama a nuestro servicio y se lo pasamos a un método que me he creado para reconocer que actividades se han reconocido, así que sin más, vamos a por la chicha:

private void comprobarActividades(List<DetectedActivity> actividadesProvables){
    //recorremos la lista de actividades
    for( DetectedActivity activity : actividadesProvables) {
        //preguntamos por el tipo
        switch( activity.getType() ) {
            case DetectedActivity.IN_VEHICLE: {
                //preguntamos por la provabilidad de que sea esa actividad
                //si es mayor de 75 (de cien) mostramos un mensaje
                if(activity.getConfidence()>75){
                    Log.i("samsoftRecAct","En vehiculo");
                }
                break;
            }
            case DetectedActivity.ON_BICYCLE: {
                if(activity.getConfidence()>75){
                    Log.i("samsoftRecAct","En bici");
                }
                break;
            }
            case DetectedActivity.ON_FOOT: {
                //este lo dejamos vacio por que va implicito en correr y andar
                break;
            }
            case DetectedActivity.RUNNING: {
                if(activity.getConfidence()>75){
                    Log.i("samsoftRecAct","Corriendo");
                }
                break;
            }
            case DetectedActivity.WALKING: {
                if(activity.getConfidence()>75){
                    Log.i("samsoftRecAct","Andando");
                }
                break;
            }
            case DetectedActivity.STILL: {
                if(activity.getConfidence()>75){
                    Log.i("samsoftRecAct","Quieto");
                }
                break;
            }
            case DetectedActivity.TILTING: {
                if(activity.getConfidence()>75){
                    Log.i("samsoftRecAct","Tumbado");
                }
                break;
            }

            case DetectedActivity.UNKNOWN: {
                //si es desconocida no decimos nada
                break;
            }
        }
    }
}

Es tan sencillo como recorrer la lista de actividades que nos devuelve la api con un for, dentro del for ponemos un switch preguntando por las actividades que queremos reconocer, en este caso he puesto todas las que reconoce la api, y dentro de cada clase preguntamos por su nivel de probabilidad tiene de que sea esa actividad, si es mayor del 75% hacemos lo que queramos y creamos oportunos, yo en el ejemplo muestro un mensaje por consola diciendo que actividad es.

5. Probando, probando…

Y con esto y un bizcocho probamos nuestra maravillosa app para ver el resultado en nuestro móvil, lanzamos la app y pulsamos en el botón encender para ver por consola:

captura salida consola del reconocimiento de actividades

captura salida consola del reconocimiento de actividades

Para comprobar que detectaba correr, no me he ido a correr como entenderéis jejeje, lo que he hecho es simularlo batiendo el móvil como si fuera una baticao ;).

El aspecto final de nuestra app de prueba de esta api es bien sencillo, dos botones centrados y out, no nos pongamos exquisitos a estas alturas del tutorial, ya os avisé al principio, aún así os dejo la captura:

 

captura pantalla app de reconocimiento de actividades

captura pantalla app de reconocimiento de actividades

Bueno señor@s con esto hemos terminado el tutorial de reconocimiento de actividades del usuario en Android, como veis tampoco es muy difícil hacer un reconocimiento de las actividades del usuario y con esta herramienta que nos proporciona Android podremos desarrollar apps que se basen en lo que está haciendo el usuario en ese momento. Yo personalmente la he usado en OneLog, nuestra app de monitorización de fitness para Android y Android Wear, que por cierto la podéis encontrar aquí (ES TOTALMENTE GRATIS)OneLog en PlayStore.

Espero que os resulte útil el artículo y espero no haberos aburrido con tanto reconocimiento de actividades :D, si os gusto compartirlo!!! Spread the word my friend! y si veis algo que este mal o cualquier sugerencia, no os cortéis, decidlo.

 

Hasta el próximo tuto amig@s, un saludo, Samir Guerrero para serviros.