Por que eu devo ler este artigo:O uso de sensores em aplicativos móveis é cada vez mais comum. Eles tornam possível o monitoramento de uma série de variáveis, como movimentação, velocidade, posicionamento, entre outras. Este artigo apresenta como podemos trabalhar com sensores na plataforma Android, abordando de forma prática o uso de uma tecnologia com tendência crescente de uso.

Um sensor é um dispositivo que responde a um estímulo físico. Alguns exemplos são sensores de: luz, som, temperatura, biometria, pressão e proximidade. Sua presença é muito comum nos celulares atuais.

Todas as classes necessárias para interação com os sensores presentes em um dispositivo Android podem ser encontradas no pacote android.hardware.*. Esse pacote também contém classes para gerenciamento do uso da câmera, como as classes Camera, Camera.Size, Camera.Parameters e Camera.CameraInfo, além das interfaces Camera.AutoFocusCallback, Camera.ErrorCallback, Camera.OnZoomChangeListener, Camera.PictureCallback, Camera.PreviewCallback e Camera.ShutterCallback.

Existem basicamente três classes de vital importância para nós programadores:

  • SensorManager (android.hardware.SensorManager): permite que acessemos os sensores dos dispositivos;
  • Sensor (android.hardware.Sensor): representa um dos sensores propriamente dito;
  • SensorEvent (android.hardware.SensorEvent): encapsula as informações de um evento ocasionado por um sensor. Por exemplo, no sensor de luz, quando a leitura indica uma alteração na luminosidade do ambiente, o sensor acusará um evento. Cabe então ao programa trabalhar com os novos valores.

Veremos a partir de agora como proceder para desenvolvermos nosso projeto.

Primeiro passo: Sensor Manager

O primeiro passo em seu projeto é instanciar a primeira classe apresentada, a SensorManager. A grande maioria dos aplicativos Android será formada por algumas Activities, que podem ser imaginadas como telas do software. Por exemplo, uma tela de login, uma tela de cadastro ou uma tela onde o usuário visualiza os dados recuperados de um sensor de presença, de proximidade, de temperatura e assim por diante.

A classe Activity possui um método chamado getSystemService, que permite que o desenvolvedor trabalhe com um serviço no nível de sistema operacional. O serviço que será acessado depende do parâmetro que vamos passar para esse método. Para nossa comodidade, a mesma classe também fornece diversas constantes para acessar diferentes serviços. Uma delas é a SENSOR_SERVICE, que faz com que o método citado retorne um SensorManager, conforme apresentada a seguir:

SensorManager mSensorManager = (SensorManager) 
getSystemService(SENSOR_SERVICE);

Aprenda mais sobre Android

Segundo Passo: Conhecendo os sensores

Apesar da API fornecer as classes necessárias para trabalhar com uma quantidade muito grande de sensores, é necessário que o smartphone possua estes hardwares. Não são todos aparelhos que terão um sensor de temperatura ou de luz, porém outros são mais comuns, como sensor de proximidade e acelerômetro. Entretanto, podemos criar uma rotina que verifique quais são os sensores disponíveis e quais as possibilidades que nos serão oferecidas.

O código da Listagem 1 tem a função de listar todos os sensores disponíveis no device.

List<Sensor> lista = mSensorManager.getSensorList(Sensor.TYPE_ALL);
Iterator<Sensor> iterator = lista.iterator();
String sensores = "... ";
while (iterator.hasNext()) {
   Sensor sensor = iterator.next();
   sensores += " - " + sensor.getName() + "\n";
}
Toast.makeText( getApplicationContext(), sensores, 
Toast.LENGTH_LONG).show();
Listagem 1. Código para listagem dos sensores presentes no device Android

Na primeira linha chamamos o método getSensorList. Isso fará com que recebamos de volta uma instância de List com os sensores disponíveis de um determinado tipo, ou, de todos os tipos se usarmos a constante TYPE_ALL.

Na segunda linha criamos uma instância de Iterator para navegar por todas as instâncias retornadas pela pesquisa feita na primeira linha. Logo em seguida, iniciamos um laço, tendo como argumento de parada o método hasNext() de Iterator. Cada instância de Sensor presente na lista tem seu nome recuperado (linha 6, método getName()) e anexado à variável sensores. Depois, podemos mostrar essa variável no LogCat do Eclipse ou na tela do smartphone.

Terceiro Passo: Trabalhando com sensores

No desenvolvimento de um aplicativo, os sensores desejados já fazem parte do escopo do projeto, dessa forma, o programador já tem definido o sensor que precisa utilizar. Assim, basta recuperar uma instância do Sensor desejado.

O código a seguir recupera do device os sensores de luminosidade e proximidade:

Sensor mLuz = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
Sensor mProx = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY); 

A SensorManager possui um método chamado getDefaultSensor, que retorna uma instância de Sensor e requer um parâmetro do tipo int. Porém, a classe já nos fornece um conjunto de constantes, o qual define os vários tipos de sensores que são possíveis de tratamento com a Android API.

Com as duas instâncias de Sensor em mãos (mLuz, mProximidade), podemos registrar um listener para acompanhar possíveis mudanças nos valores de proximidade e da intensidade de luz ambiente. Um Listener é um conceito muito utilizado na programação Java, sendo seu objetivo aguardar por uma determinada ação. Pode ser usado para verificar possíveis cliques em um botão, mudanças na posição geográfica do dispositivo, mudanças nos valores de um determinado sensor etc.

Para isso, insira o trecho de código apresentado na Listagem 2 em seu aplicativo.

 
public class UsandoSensores extends Activity {

    private SensorManager mSensorManager;
    private Sensor mLuz;
    private Sensor mProximidade;

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

        mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE)

        mLuz = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
        mProx = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);

        mSensorManager.registerListener(new LuzSensor(), mLuz, SensorManager.SENSOR_DELAY_FASTEST);
        mSensorManager.registerListener(new ProxSensor(), mProximidade, SensorManager.SENSOR_DELAY_FASTEST);
}

class ProxSensor implements SensorEventListener {
  public void onAccuracyChanged(Sensor sensor, int accuracy) {}
  public void onSensorChanged(SensorEvent event) {
        …
  }
}

class LuzSensor implements SensorEventListener {
  public void onAccuracyChanged(Sensor sensor, int accuracy) {}
  public void onSensorChanged(SensorEvent event) {
        …
  }
}
} 
Listagem 2. Definindo listener

O registerListener requer três parâmetros:

  • Uma instância de uma classe que implemente SensorEventListener;
  • Uma instância da classe Sensor;
  • Um inteiro que define a taxa de leitura de dados do sensor.

A classe SensorManager já nos apresenta algumas constantes para definir a taxa de atualização da leitura dos sensores. Estas constantes são:

  • SENSOR_DELAY_FASTEST: retorna os dados do sensor o mais rápido possível;
  • SENSOR_DELAY_GAME: utiliza uma taxa adequada para jogos;
  • SENSOR_DELAY_NORMAL: taxa adequada para mudanças na orientação da tela;
  • SENSOR_DELAY_UI: taxa adequada para a interface de usuário.

Perceba que na listagem foram criadas duas classes distintas que implementam SensorEventListener. Ambas precisam implementar os métodos onAccuracyChanged() e onSensorChanged(). O primeiro é chamado quando há exatidão dos dados lidos pelo sensor sobre alterações e o segundo, quando o hardware intercepta uma leitura do sensor para o qual foi registrada esse listener.

Estudo de caso: Acelerômetro e lista de sensores

Para exemplificar o uso de sensores, vamos desenvolver um primeiro aplicativo.

Este aplicativo terá uma tela principal, na qual são apresentados os valores lidos nos três eixos (x, y e z) do sensor de aceleração do device. Também será apresentado um botão, o qual iniciará a segunda tela do aplicativo com a lista de todos os sensores disponíveis no mesmo.

O projeto será nomeado UsandoAcelerometro e utilizará os nomes activity_principal.xml e PrincipalActivity.java para a tela e a classe Java, respectivamente. Já para listar os sensores serão utilizados os nomes activity_listar.xml e ListarActivity.java.

O código completo da classe tela activity_principal.xml é apresentado na Listagem 3.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".PrincipalActivity">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Valor x:"/>

    <TextView
        android:id="@+id/tvValorX"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text=""/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Valor y:"/>

    <TextView
        android:id="@+id/tvValorY"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text=""/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Valor Z:"/>

    <TextView
        android:id="@+id/tvValorZ"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text=""/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:text="Meus sensores"
        android:onClick="btMeusSensoresOnClick"/>

</LinearLayout>
Listagem 3. activity_principal.xml – Tela principal do aplicativo do Acelerômetro

O código representa uma tela formada por um gerenciador de Layout LinearLayout (linha 01), contendo pares de campos para identificar o valor do eixo x (linhas 08 e 13), eixo y (linhas 19 e 24) e eixo z (linhas 30 e 35). Desses, os campos que possuem propriedade ID são os que apresentarão os dados e os demais serão utilizados como rótulos.

Ainda na interface gráfica temos um botão para apresentar os sensores do device (linha 41). Esse botão, ao ser pressionado, executará o método btMeusSensoresOnClick, que será codificado na PrincipalActivity.

O código dessa Activity, responsável pelo tratamento da interface gráfica, é apresentado na Listagem 4.

package br.edu.utfpr.usandoacelerometro;

import android.app.Activity;
import android.content.Intent;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

import java.util.List;

public class PrincipalActivity extends Activity implements 
SensorEventListener {

    private TextView tvValorX;
    private TextView tvValorY;
    private TextView tvValorZ;

    private SensorManager mSensorManager;
    private Sensor mAcelerometro;

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

        tvValorX = (TextView) findViewById( R.id.tvValorX );
        tvValorY = (TextView) findViewById( R.id.tvValorY );
        tvValorZ = (TextView) findViewById( R.id.tvValorZ );

        mSensorManager = (SensorManager) 
        getSystemService(SENSOR_SERVICE);
        mAcelerometro = mSensorManager
        .getDefaultSensor(Sensor.TYPE_ACCELEROMETER);

        
    }

    @Override
    protected void onResume(){
        super.onResume();
        mSensorManager.registerListener(this, mAcelerometro, 
        SensorManager.SENSOR_DELAY_UI);
    }

    @Override
    protected void onPause(){
        super.onPause();
        mSensorManager.unregisterListener(this);
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        float x = event.values[0];
        float y = event.values[1];
        float z = event.values[2];

        tvValorX.setText( String.valueOf( x ) );
        tvValorY.setText( String.valueOf( y ) );
        tvValorZ.setText( String.valueOf( z ) );

    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {

    }

    public void btMeusSensoresOnClick(View v) {
        List<Sensor> listaSensores = mSensorManager
        .getSensorList(Sensor.TYPE_ALL);
        String[] lista = new String[listaSensores.size()];

        for (int i = 0; i < listaSensores.size(); i++){
            lista[i] = listaSensores.get(i).getName();
        }

        Intent i = new Intent(this, ListarActivity.class);
        i.putExtra("sensores", lista);
        startActivity(i);
    }
}
Listagem 4. PrincipalActivity.java – Classe Java para tratamento da tela principal

As primeiras linhas da classe (linhas 01 a 13) são responsáveis pela declaração do pacote e importação das classes utilizadas no programa. Na linha 15 acontece a declaração da classe principal, a qual implementa a interface SensorEventListener, ou seja, nessa classe serão codificados os métodos onSensorChanged() e onAccuracyChanged(), que serão utilizados para tratamento dos dados vindos dos sensores.

Nas linhas 17 a 19 acontece a declaração dos componentes visuais da tela, os quais são recuperados nas linhas 29 a 31.

Para o tratamento de eventos, dois objetos são necessários: um da classe SensorManager (linha 21) e outro para a classe Sensor (linha 22).

Para o objeto SensorManager, é recuperado o serviço de sensor padrão do device (linha 33). Para o exemplo foi utilizado apenas o sensor de acelerômetro (linha 34).

Considerando que o programa pode ser interrompido a qualquer momento, seja para a apresentação de outra tela, seja para receber uma ligação, o registro do listener acontece no método onResume() – linha 40, o qual é chamado após o onCreate() e toda vez que o programa retorna de um estado de pausa. Nesse registro é passado por parâmetro a Activity atual, o sensor que será registrado e o intervalo entre uma captura e outra.

No momento em que a aplicação entra em estado de pausa (linha 46), o registro do listener é desfeito (linha 48).

Para a recuperação dos dados vindos do sensor, foi utilizado o método onSensorChanged() – linha 52, o qual recupera os três valores na ordem: x, y e z (isso acontece porque o sensor de aceleração informa três dados distintos). Esses dados são apresentados na tela nos campos de texto (linhas 57 a 59).

Já o tratamento do clique do botão para apresentar os sensores acontece no método btMeusSensoresOnClick(linha 68), o qual recupera uma lista com todos os sensores disponíveis no device (linha 69), sendo essa lista passada para um array de String (lógica que se estende da linha 70 a 74).

Por fim, uma Intent é instanciada enviando os dados coletados para a tela de visualização a partir do comando startActivity (linhas 76 a 78).

O código correspondente à tela de activity_listar.xml é apresentado na Listagem 5.

<ListView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/listaSensores"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ListarActivity" />
Listagem 5. activity_listar.xml – Tela para listar os sensores disponíveis no device

A tela Listar é composta por um único componente visual – ListView (linha 01), o qual ocupa toda a tela do device (linhas 04 e 05) e é processado pelo código Java usando como referência o nome listaSensores (linha 03).

O código Java da tela Listar é apresentado na Listagem 6.

 
package br.edu.utfpr.usandoacelerometro;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class ListarActivity extends Activity {

    private ListView listaSensores;

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

        listaSensores = (ListView) findViewById( R.id.listaSensores );

        Intent intent = getIntent();
        String[] sensores = intent.getStringArrayExtra("sensores");

        ArrayAdapter<String> adapter = new ArrayAdapter<
        String>(this, android.R.layout.simple_list_item_1, sensores);

        listaSensores.setAdapter(adapter);
    }

}
Listagem 6. ListarActivity.java – Classe Java para tratamento da tela Listar

Essa Activity apresenta o Array de String contendo o nome dos sensores que foram capturados na tela Principal no componente ListView. Inicialmente o ListView é declarado e recuperado (linhas 11 e 18). Em seguida, os dados coletados dos sensores são recuperados (linhas 20 e 21). Essa lista é utilizada para a instanciação de um ArrayAdapter (linha 23 e 25), o qual apresentará os dados no ListView.

As telas do aplicativo são apresentadas na Figura 1.

Utilização
Figura 1. Telas do aplicativo UsandoAcelerometro

À esquerda os dados lidos do sensor de acelerômetro. À direita tem-se a lista com todos os sensores disponíveis em um Galaxy SIII Mini.

Estudo de caso: Sensores de proximidade e luminosidade

O segundo aplicativo desenvolvido simula um aplicativo de gravação. Ao nos aproximarmos do aparelho, o microfone fica habilitado (esse recurso é simulado mudando a figura na tela: à distância, a figura do microfone fica com uma marca vermelha, quando se aproxima, a marca desaparece). Para melhorar a usabilidade do aplicativo, em ambientes claros, o fundo do aplicativo fica preto e em ambientes escuros, fica branco. As telas do aplicativo são apresentadas na Figura 2.

Utilização Utilização de sensores na plataforma Android
Figura 2. Telas do aplicativo, com microfones habilitados e não, assim como fundo preto e branco

Esse aplicativo chama-se Gravador e é formado por uma única tela: activity_gravador_principal.xml (tela) e GravadorPrincipalActivity.java (código Java).

O código da tela principal do aplicativo é apresentado na Listagem 7.

 
<LinearLayout xmlns:android="http://schemas.android
.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:id="@+id/llTela"
    tools:context=".GravadorPrincipalActivity">

    <ImageView
        android:id="@+id/ivMicrofone"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:src="@drawable/microfoneoff" />

</LinearLayout>
Listagem 7. activity_gravador_principal.xml – Interface gráfica da tela principal

A tela da aplicação se resume a um LinearLayout (linha 01) com um nome definido (linha 06), o qual permitirá a troca da cor de fundo em tempo de execução. Como componente visual, temos um ImageView (linha 09), que inicia com a imagem de microfoneoff.

Para o aplicativo, foram utilizadas duas imagens: microfoneon e microfoneoff. Preferencialmente, essas imagens devem ter extensão png com fundo transparente para uma melhor visualização.

O tratamento dos sensores é feito por meio do código Java que segue na Listagem 8.

package br.edu.utfpr.gravador;

import android.app.Activity;
import android.graphics.Color;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.widget.ImageView;
import android.widget.LinearLayout;

public class GravadorPrincipalActivity extends Activity {

    private LinearLayout llTela;
    private ImageView ivMicrofone;

    private SensorManager mSensorManager;
    private Sensor mLuz;
    private Sensor mProx;


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

        llTela = (LinearLayout) findViewById(R.id.llTela);
        ivMicrofone = (ImageView) findViewById(R.id.ivMicrofone);

        mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
        mLuz = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
        mProx = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);

        mSensorManager.registerListener(new LuzSensor(), mLuz, 
        SensorManager.SENSOR_DELAY_UI);
        mSensorManager.registerListener(new ProxSensor(), mProx, 
        SensorManager.SENSOR_DELAY_UI);

    }

    class ProxSensor implements SensorEventListener {
        public void onAccuracyChanged(Sensor sensor, int accuracy) {
        }

        public void onSensorChanged(SensorEvent event) {
            float vl = event.values[0];

            if (vl >= 10 ) {
                ivMicrofone.setImageResource(R.drawable.microfoneoff);
            } else {
                ivMicrofone.setImageResource(R.drawable.microfoneon);
            }

        }
    }

    class LuzSensor implements SensorEventListener {
        public void onAccuracyChanged(Sensor sensor, int accuracy) {
        }

        public void onSensorChanged(SensorEvent event) {
            float vl = event.values[0];

            if (vl < 10) {
                llTela.setBackgroundColor(Color.WHITE);
            } else {
                llTela.setBackgroundColor(Color.BLACK);
            }
        }
    }
}
Listagem 8. GravadorPrincipalActivity.java – Classe Java para tratamento da tela principal

Essa classe tem a estrutura muito parecida com o aplicativo de acelerômetro. As principais mudanças estão nos componentes visuais declarados (linhas 15 e 16) e recuperados (linhas 28 e 29).

Outra mudança está no número de sensores, que agora são dois, declarados nas linhas 19 e 20, e recuperados nas linhas 32 e 33. Estes sensores são registrados aos seus respectivos listeners nas linhas 35 e 36.

Assim, duas classes são codificadas, a primeira para tratar o Listener do sensor de proximidade (linha 40) e a segunda para tratar o listener do sensor de luminosidade (linha 56).

Trabalhando com o sensor de luz

Existem dezenas de sensores disponíveis, entre eles o de luminosidade. Esse mensura a quantidade de luz em um determinado ambiente.

A leitura do sensor de luminosidade pode alterar de device para device, por exemplo, em teste no Motorola Defy, o valor máximo de luminosidade chega perto de 100. Porém, o Samsung Galaxy S mostra uma escala de valores de 0 até 5.000. Ou seja, o desenvolvedor deve criar uma lógica inteligente levando em conta essa disparidade de valores. Mas, por hora, imaginemos que o foco seria o Defy, então, o teste if presente na linha 63 está correto. Logo, se o valor for menor que 10, significa que a luz ambiente está abaixo de um nível aceitável definido pelo código. Sendo assim, alteramos a cor de fundo da tela para branca. Caso contrário, configuramos a cor preta como fundo da tela.

Trabalhando com o sensor de proximidade

Um sensor de proximidade tem como objetivo indicar a presença de um objeto a uma distância pré-definida. O funcionamento desse sensor depende das características do hardware.

Assim como o sensor de luz, o sensor de proximidade também retorna apenas um valor, sendo assim, basta recuperar o valor no índice 0 do vetor values da SensorEvent. É importante estarmos atentos ao fato de que para esse sensor também teremos uma diferença de escala de valores entre o Samsung e o Motorola. No Galaxy S, os valores variavam entre 0.0 e 5.0. Já no Defy o valor máximo ultrapassa facilmente 10.0. Como nosso foco nesse exemplo foi o Defy, o teste if presente na linha 47 verifica se a distância é maior ou igual a 10. Caso afirmativo, o usuário está com o smartphone muito longe, então, configuramos a imagem microfoneoff no objeto ivMicrofone. Caso contrário, configuramos a imagem microfoneOn no mesmo objeto.

Este artigo apresentou os conceitos de utilização de sensores aplicados aos devices da plataforma Android. Para exemplificar, foram desenvolvidos estudos de caso que fizeram uso dos sensores de aceleração, proximidade e luminosidade. Para testar o aplicativo foram utilizados devices Android de duas marcas distintas para verificar as particularidades de cada uma. Infelizmente, utilizando o AVD (Android Virtual Device) não é possível testar de maneira satisfatória o programa.

A utilização de sensores abre um leque muito grande de oportunidades para o desenvolvimento de aplicativos para a plataforma Android. É possível fazer uso desses não só para o desenvolvimento de jogos, mas também para utilitários, aplicativos de monitoramento e de realidade aumentada.