samedi 6 mars 2010

Utiliser le senseur d'accélération Android

Android supporte une grande variété de senseurs qui permettent d'obtenir des informations sur l'environnement, la position ou les déplacements du téléphone. Dans ce tutorial, nous allons voir comment récupérer l'accélération du téléphone pour déterminer si ce dernier est secoué par son utilisateur :

public interface ShakeListener {

    public void onShake(float speed);
    
}

Une instance de SensorManager doit être obtenue afin de connaître la liste des senseurs supportés. Aucune permission n'est requise pour utiliser les senseurs du téléphone. Pour récupérer la liste des Sensor d'accélération du téléphone, il faut demander au SensorManager la liste des senseurs caractérisés par la constante Sensor.TYPE_ACCELEROMETER. Si au moins un senseur est récupéré, il est possible de déclarer un SensorEventListener pour l'un des Sensor. Le senseur d'accélération sélectionné va alors nous informer des changements d'accélération selon l'une des fréquence suivante :
  1. SensorManager.SENSOR_DELAY_FASTEST : aussi rapidement que possible
  2. SensorManager.SENSOR_DELAY_GAME : fréquence adaptée aux jeux
  3. SensorManager.SENSOR_DELAY_NORMAL : fréquence normale
  4. SensorManager.SENSOR_DELAY_UI : fréquence adaptée pour traitement dans l'UI Thread

Pour déterminer si le téléphone est secoué par son utilisateur, il est préférable d'utiliser au moins la fréquence SensorManager.SENSOR_DELAY_GAME.

Le code du détecteur de secousse est le suivant :

package net.androgames.yams.shake;

import java.util.List;

import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;

/**
 * ShakeManager pour Android
 * @author antoine vianey
 * under GPL v3 : http://www.gnu.org/licenses/gpl-3.0.html
 */
public class ShakeManager {
    
    // vitesse min pour considerer le mouvement
    private static final int SHAKE_THRESHOLD = 6000;    
    // intervalle min entre deux secousses
    private static final int MIN_INTERVAL = 1000; 
    // delais max entre deux valeurs pour considerer un meme mouvement
    private static final int MAX_DELAY = 100;   
    
    private static boolean running = false;
    private static Sensor sensor;
    private static long lastUpdate = 0;
    private static long lastShake = 0;
    private static SensorManager sensorManager;
    private static ShakeListener listener;
    
    private static Boolean supported;
    
    private static float x = 0;
    private static float y = 0;
    private static float z = 0;
    private static float lastX = 0;
    private static float lastY = 0;
    private static float lastZ = 0;
    
    public static boolean isListening() {
        return running;
    }
    
    /**
     * Arrete l'ecoute des secousses
     */
    public static void stopListening() {
        running = false;
        try {
            if (sensorManager != null && sensorEventListener != null) {
                sensorManager.unregisterListener(sensorEventListener);
            }
        } catch (Exception e) {}
    }
    
    /**
     * Retourne true si la fonctionnalite est supportee
     * @param context
     * @return
     */
    public static boolean isSupported(Context context) {
        if (supported == null) {
            if (context != null) {
                sensorManager = (SensorManager) context.getSystemService(
                        Context.SENSOR_SERVICE);
                List<Sensor> sensors = sensorManager.getSensorList(
                        Sensor.TYPE_ACCELEROMETER);
                supported = new Boolean(sensors.size() > 0);
            } else {
                supported = Boolean.FALSE;
            }
        }
        return supported;
    }
    
    /**
     * Demarre l'ecoute des secousses du telephone
     * @param shakeListener
     * @param context
     */
    public static void startListening(ShakeListener shakeListener, 
            Context context) {
        sensorManager = (SensorManager) context.getSystemService(
                Context.SENSOR_SERVICE);
        List<Sensor> sensors = sensorManager.getSensorList(
                Sensor.TYPE_ACCELEROMETER);
        if (sensors.size() > 0) {
            sensor = sensors.get(0);
        }
        sensorManager.registerListener(sensorEventListener, sensor, 
                SensorManager.SENSOR_DELAY_GAME);
        listener = shakeListener;
    }

    private static SensorEventListener sensorEventListener = 
        new SensorEventListener() {

        private long now, timeDiff;
        private float speed;
        
        public void onAccuracyChanged(Sensor sensor, int accuracy) {}
        
        public void onSensorChanged(SensorEvent event) {
            now = System.currentTimeMillis();
            
            x = event.values[0];
            y = event.values[1];
            z = event.values[2];
            
            if (lastUpdate == 0) {
                lastUpdate = now;
                lastShake = now;
                lastX = x;
                lastY = y;
                lastZ = z;
            } else {
                timeDiff = now - lastUpdate;
                if (timeDiff > 0 && timeDiff < MAX_DELAY) {
                    speed = Math.abs(x + y + z - lastX - lastY - lastZ) 
                                / timeDiff * 10000;
                    if (speed > SHAKE_THRESHOLD) {
                        if (now - lastShake >= MIN_INTERVAL) {
                            listener.onShake(speed);
                            lastShake = now;
                            now = 0;
                        }
                    }
                }
                lastX = x;
                lastY = y;
                lastZ = z;
                lastUpdate = now;
            }
                
        }
        
    };

}

Dans le cas d'un SensorEvent retourné par un Sensor de type Sensor.TYPE_ACCELEROMETER, les informations retournées représentent l'accélération du téléphone dans un système de coordonnées cartésiens. Pour un téléphone au repos en position horizontale, les valeurs retournées doivent-être :
  1. 0 m/s2 selon l'axe x
  2. 0 m/s2 selon l'axe y
  3. 9,80665 m/s2 selon l'axe z (attraction terrestre)

D'un événement à l'autre, les coordonnées de l'accélération sont stockées afin de détecter les changements soudains d'accélération et déclencher l'événement onShake lorsque le threshold est atteint.

Vous pouvez également implémenter vos propres méthodes de détection de mouvements et nous les présenter ici même.

Amusez vous bien !

Aucun commentaire:

Enregistrer un commentaire

Fork me on GitHub