Utilizando os sensores do Android em sua aplicação para monitoramento do ambiente externo

A plataforma Android é idela, especialmente por causa dos desenvolvvedores Java, para criar aplicações inovadoras que fazem uso de sensores de hardware. Vamos ver nesse artigo algumas das opções de interface disponiveis para aplicações Android, incluindo o uso do sub-sistema de sensores e gravação de trechos de audio.

Que tipo de aplicações você pode construir que possam tirar vantagem dos recursos do hardware de um dispositivo Android? Qualquer coisa que precise de olhos ou ouvidos eletrônicos é um bom candidato. Uma babá eletrônica, sistema de segurança, ou mesmo um sismógrafo vem a mente. Ao longo desse artigo, tenha em mente que um dispositivo Android não é meramente um “telefone celular” mas também um dispositivo localizado em uma localização fixa com conectividade de rede sem fio, como EDGE ou WiFi.

Recursos de sensor do Android

Um aaspecto novo de trabalhar com a plataforma Android é que você pode acessar alguns dos recursos de dentro do próprio dispositivo. Historicamente, a falta de acesso ao hardware de um dispositivo tem sido frustante para os desenvolvedores de aplicações móveis. Ainda que o ambiente Java do Android ainda fique entre você e o metal, o time de desenvolvimento do Android trouxe muito dos recursos do hardware para a superficie.

Se ainda não o tiver feito, você deve agora baixar e instalar o SDK do Android. Você pode também visualizar o conteúdo do pacote android.hardware e seguir pelos exemplos desse artigo. O pacote android.media contém classes que fornecem funções úteis.

Algumas das características baseadas no hardware expostas no SDK do Android são descritas abaixo.

Característica Descrição
android.hardware.Camera A class that enables your application to interact with the camera to snap a photo, acquire images for a preview screen, and modify parameters used to govern how the camera operates.
android.hardware.SensorManager A class that permits access to the sensors available within the Android platform. Not every Android-equipped device will support all of the sensors in the SensorManager, though it’s exciting to think about the possibilities. (See below for a brief discussion of available sensors.)
android.hardware.SensorListener An interface implemented by a class that wants to receive updates to sensor values as they change in real time. An application implements this interface to monitor one or more sensors available in the hardware. For example, the code in this article contains a class that implements this interface to monitor the orientation of the device and the built-in accelerometer.
android.media.MediaRecorder A class, used to record media samples, that can be useful for recording audio activity within a specific location (such as a baby nursery). Audio clippings can also be analyzed for identification purposes in an access-control or security application. For example, it could be helpful to open the door to your time-share with your voice, rather than having to meet with the realtor to get a key.
android.FaceDetector A class that permits basic recognition of a person’s face as contained in a bitmap. You cannot get much more personal than your face. Using this as a device lock means no more passwords to remember — biometrics capability on a cell phone.
android.os.* A package containing several useful classes for interacting with the operating environment, including power management, file watcher, handler, and message classes. Like many portable devices, Android-powered phones can consume a tremendous amount of power. Keeping a device “awake” at the right time to be in position to monitor an event of interest is a design aspect that deserves attention up front.
java.util.Date
java.util.Timer
java.util.TimerTask
When measuring events in the real world, date and time are often significant. For example, the java.util.Date class lets you get a time stamp when a particular event or condition is encountered. You can use java.util.Timer and java.util.TimerTask to perform periodic tasks, or point-in-time tasks, respectively.

O pacote android.hardware.SensorManager contém muitas constantes, que representam diferentes aspectos do sistema de sensores do Android, que incluem:

Tipo do sensor
Orientação, acelerômetro, iluminação, campo magnético, proximidade, temperatura, etc.
Amostragem
Rápido, jogo, normal, interface do usuário. Quando uma aplicação solicita uma taxa de amostragem especifica, é apenas uma dica ou sugestão para o sub-sistema de sensores. Não existe nenhuma garantia que uma taxa em particular esteja disponivel.
Precisão
Alta, baixa, média, incerto.

A interface SensorListener é a base para as aplicações que usam sensores. Ela inclue dois metódos necessários:

  • onSensorChanged(int sensor, float values[]): é invocado toda vez que o valor do sensor é mudado. O metódo é invocado apenas para sensores que estão sendo monitorados por essa aplicação (mais sobre isso abaixo). Os argumentos do metódo incluem um inteiro que identifica o sensor que sofreu alteração, assim como um array de valores que representam os dados do sensor. Alguns sensores fornecem apenas um único valor, enquanto outros fornecem três valores. Os sensores de orientação e acelerômetro fornecem cada um três valores.
  • onAcurracyChanged(int sensor, int accuracy): é invocado quando a precisão de um sensor muda. Os argumentos são dois inteiro: Um representa o sensor que mudou e o outro representa a nova precisão do sensor.

Para interagir com um sensor, uma aplicação precisa registrar que quer “escutar” um ou mais sensores. O registro é feito com o metódo registerListerner da classe SensorManager. O código exemplo desse artigo demonstra como uma aplicação registra um SensorListener.

Lembre que nem todos os dispositivos Android suportam algum dos ou todos os sensores definidos no SDK. Sua aplicação precisa saber quais sensores estão disponíveis para seu uso no dispositivo que está sendo executada.


Exemplo de uso de um sensor

A aplicação exemplo simplesmente monitora mudanças nos sensores de orientação e acelerômetro. Quando mudanças são detectadas, os valores dos sensores são mostrados na tela em um TextView. A Figura abaixo mostra a aplicação em ação.

A aplicação foi criada usando o ambiente Eclipse como o plugin ADT. (Para mais informação sobre o desenvolvimento de aplicações Android com o Eclipse, clique aqui). A Listagem abaixo mostra o código dessa aplicação.

package com.msi.ibm.eyes;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import android.hardware.SensorManager;
import android.hardware.SensorListener;
public class IBMEyes extends Activity implements SensorListener {
    final String tag = "IBMEyes";
    SensorManager sm = null;
    TextView xViewA = null;
    TextView yViewA = null;
    TextView zViewA = null;
    TextView xViewO = null;
    TextView yViewO = null;
    TextView zViewO = null;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
       // get reference to SensorManager
        sm = (SensorManager) getSystemService(SENSOR_SERVICE);
        setContentView(R.layout.main);
        xViewA = (TextView) findViewById(R.id.xbox);
        yViewA = (TextView) findViewById(R.id.ybox);
        zViewA = (TextView) findViewById(R.id.zbox);
        xViewO = (TextView) findViewById(R.id.xboxo);
        yViewO = (TextView) findViewById(R.id.yboxo);
        zViewO = (TextView) findViewById(R.id.zboxo);
    }
    public void onSensorChanged(int sensor, float[] values) {
        synchronized (this) {
            Log.d(tag, "onSensorChanged: " + sensor + ", x: " +
values[0] + ", y: " + values[1] + ", z: " + values[2]);
            if (sensor == SensorManager.SENSOR_ORIENTATION) {
                xViewO.setText("Orientation X: " + values[0]);
                yViewO.setText("Orientation Y: " + values[1]);
                zViewO.setText("Orientation Z: " + values[2]);
            }
            if (sensor == SensorManager.SENSOR_ACCELEROMETER) {
                xViewA.setText("Accel X: " + values[0]);
                yViewA.setText("Accel Y: " + values[1]);
                zViewA.setText("Accel Z: " + values[2]);
            }
        }
    }

    public void onAccuracyChanged(int sensor, int accuracy) {
    	Log.d(tag,"onAccuracyChanged: " + sensor + ", accuracy: " + accuracy);
    }
    @Override
    protected void onResume() {
        super.onResume();
      // register this class as a listener for the orientation and accelerometer sensors
        sm.registerListener(this,
                SensorManager.SENSOR_ORIENTATION |SensorManager.SENSOR_ACCELEROMETER,
                SensorManager.SENSOR_DELAY_NORMAL);
    }

    @Override
    protected void onStop() {
        // unregister listener
        sm.unregisterListener(this);
        super.onStop();
    }
}

A aplicação foi escrita como uma aplicação normal baseada em activity porque faz simplesmente atualizar a tela com dados obtidos dos sensores. Em uma aplicação onde o dispositivo pode estar executando outras atividades em segundo plano, construir a aplicação como um Service pode ser mais apropriado.

O metódo onCreate da Activity pega uma referência para o SensorManager, onde todas as funções relacionadas a sensores são baseadas. O metódo também cria referências para seis TextViews necessários para a exibição dos dados dos sensores.

O metódo onResume() usa a referência ao SensorManager para registrar por mudanças nos sensores através do metódo registerListerner:

  • O primeiro parâmetro é uma instância da classe que implementa a interface SensorListener.
  • O segundo parâmetro é uma máscara de bits para o sensor desejado. Nesse caso, a aplicação está requisitando dados de SENSOR_ORIENTATION e SENSOR_ACCELEROMETER.
  • O terceiro parâmetro é uma dica para o sistema que indica rapidamente como a aplicação solicita atualizações nos valores do sensor.

Quando a aplicaçao é pausada, você precisa cancelar o registro ao listener assim você não recebe mais atualizações dos sensores. Isso é feito pelo metódo unregisterListener do SensorManager. O único parâmetro é a instância do SensorListener.

Em ambas as chamadas dos metódos registerListener e unregisterListener, a aplicação usa a palavra-chave this. Note que a palavra-chave implements na definicação da classe onde é declarado que essa classe implementa a interface SensorListener. Esse é o motivo porquê você pode passar this para os metódos citados.

Um SensorListener precisa implementar os metódos onSensorChanger e onAccuracyChanged. A aplicação do exemplo não se preocupa realmente como a precisão dos sensores, mais como os valores atuais de X, Y e Z. O metódo onAccuracyChanged está fazendo essencialmente nada; só cria uma nova entrada no arquivo de log a cada vez que é invocado.

Parece que o metódo onSensorChanged é invocado constantemente, já que os sensores do acelerômetro e da orientação ficam rapidamente enviando dados. Dê uma olhada no primeiro parâmetro para determinar qual sensor está enviando dados. Assim que o sensor  é identificado, o elemento da interface correspondente é atualizado com os dados contidos no array de valores passado como segundo argumento do metódo. Enquanto no exemplo os dados são apenas exibidos, em aplicações mais complexas os valores são analizados, comparados com valores anteriores, ou passados por algum algoritmo de reconhecimento de padrões que determinam o que o usuário está fazendo.


Usando o Gravador de mídia

O pacote android.media contém classes que interagem com o sub-ssitema de mídia. A classe android.media.MediaRecorder é usada para capturar pedaços de mídia, incluindo audio e video. A MediaRecorder funciona como uma máquina de estado. Você precisa configurar vários parâmetros, como dispositivo de origem e formato. Assim que as configurações forem concluidas, a gravação pode começar por um periodo arbitrário de tempo até que seja terminado.

A listagem abaixo traz um código para realizar gravações de audio em um dispositivo Android. O código mostrado não inclui os elementos da interface da aplicação.

MediaRecorder mrec ;
File audiofile = null;
private static final String TAG="SoundRecordingDemo";
protected void startRecording() throws IOException
{
   mrec.setAudioSource(MediaRecorder.AudioSource.MIC);
   mrec.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
   mrec.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
   if (mSampleFile == null)
   {
       File sampleDir = Environment.getExternalStorageDirectory();
       try
       {
          audiofile = File.createTempFile("ibm", ".3gp", sampleDir);
       }
       catch (IOException e)
       {
           Log.e(TAG,"sdcard access error");
           return;
       }
   }
   mrec.setOutputFile(audiofile.getAbsolutePath());
   mrec.prepare();
   mrec.start();
}
protected void stopRecording()
{
   mrec.stop();
   mrec.release();
   processaudiofile(audiofile.getAbsolutePath());
}
protected void processaudiofile()
{
   ContentValues values = new ContentValues(3);
   long current = System.currentTimeMillis();
   values.put(MediaStore.Audio.Media.TITLE, "audio" + audiofile.getName());
   values.put(MediaStore.Audio.Media.DATE_ADDED, (int) (current / 1000));
   values.put(MediaStore.Audio.Media.MIME_TYPE, "audio/3gpp");
   values.put(MediaStore.Audio.Media.DATA, audiofile.getAbsolutePath());
   ContentResolver contentResolver = getContentResolver();

   Uri base = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
   Uri newUri = contentResolver.insert(base, values);

   sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, newUri));
}

No metódo startRecording, uma instância de MediaRecorder é instânciada e inicializada:

  • A fonte de entrada é configurada como sendo o microfone (MIC).
  • O formato de saida é configurada como 3GPP que é um formato de mídia especifica para dispositivos móveis.
  • O encoder é configurado para AMR_NB, que é um formato de audio com amostragem de 8 KHZ. O NB é a largura de banda. A documentação do SDK explica os diferentes formatos e encoders disponiveis.

O arquivo de audio é armazenado no cartão SD, ao invés da memória interna. O metódo External.getExternalStorageDirectory() retorna o nome do diretório do cartão,  e um arquivo temporário é criado nesse diretório. Esse arquivo é então associado com a instância do MediaRecorder pela chamada do metódo setOutputFile. Os dados do audio serão armazenados nesse arquivo.

O metódo prepare é invocado para finalizar a incialização do MediaRecorder. Quando você estiver pronto para o inicio do processo de gravação, o metódo start é chamado. A gravação grava os dados capturados no arquivo localizado no cartão até que o metódo stop é invocado. O metódo release libera os recursos alocados pela instância de MediaRecorder.

Assim que o audio tiver sido capturado, existem algumas que ações que podem ser executadas:

  • Adicionar o arquivo de audio a biblioteca de mídia do dispositivo.
  • Executar algum algoritmo de reconhecimento de padrão para identificar o som:
    • É um bebê chorando?
    • É a voz do dono, e devemos desbloquear o telefone?
    • É a frase “abre-te sesame” que destrava a porta da entrada secreta?
  • Automaticamente fazer o upload do arquivo para um local da rede para ser processado.

No código exemplo, o metódo processaudiofile adiciona o arquivo á biblioteca de mídia. Um Intend é usado para notificar a aplicação de mídia que um novo conteúdo está disponivel.

Uma observação final sobre o pedaço de código: Se você o executar, o audio não será gravado de primeira. Você verá que um arquivo foi criado, mas sem nenhum audio. Você precisa adicionar a seguinte permissão no arquivo AndroidManisfest.xml:

<uses-permission android:name="android.permission.RECORD_AUDIO"></uses-permission>

Fonte: http://www.ibm.com/developerworks/opensource/library/os-android-sensor/index.html

http://www.klebermota.eti.br/2010/09/primeiros-passos-com-android-criando-um-hello-world/
  • Boa tradução. Porém nem aqui nem na fonte fala sobre o arquivo build.xml que é essencial para construir a classe R. A classe R, por sua vez, contem atributos necessários para o funcionamento do método onCreate pertencente a classe IBMEyes.