Sensores de posição

A plataforma Android oferece dois sensores que permitem determinar a posição de um dispositivo: o sensor de campo geomagnético e o acelerômetro. Além disso, esse sistema fornece um sensor que permite identificar a proximidade da face de um dispositivo em relação a um objeto (conhecido como sensor de proximidade). O sensor de campo geomagnético e o sensor de proximidade são baseados em hardware. A maioria dos fabricantes de celulares e tablets inclui um sensor de campo geomagnético nos próprios produtos. Da mesma forma, os fabricantes de celulares geralmente incluem um sensor de proximidade para determinar quando o dispositivo é mantido próximo ao rosto do usuário (por exemplo, durante uma chamada telefônica). Para determinar a orientação de um dispositivo, você pode usar as leituras do acelerômetro do dispositivo e do sensor de campo geomagnético.

Observação: o uso do sensor de orientação foi suspenso no Android 2.2 (API de nível 8), e o tipo dele também está obsoleto no Android 4.4W (API de nível 20).

Os sensores de posição são úteis para determinar a posição física de um dispositivo no frame de referência mundial. Por exemplo, você pode usar o sensor de campo geomagnético junto com o acelerômetro para identificar a posição de um dispositivo em relação ao polo norte magnético. Você também pode usar esses sensores para determinar a orientação de um dispositivo no frame de referência do seu aplicativo. Em geral, os sensores de posição não são usados para monitorar o movimento do dispositivo, como trepidação, inclinação ou impulso. Para saber mais, consulte Sensores de movimento.

O sensor de campo geomagnético e o acelerômetro retornam matrizes multidimensionais dos valores do sensor para cada SensorEvent. Por exemplo, o sensor de campo geomagnético fornece valores de intensidade para cada um dos três eixos de coordenadas durante um único evento. Da mesma forma, o sensor do acelerômetro mede a aceleração aplicada ao dispositivo durante um evento. Para saber mais sobre os sistemas de coordenadas usados pelos sensores, consulte Sistemas de coordenadas do sensor. O sensor de proximidade oferece um valor único para cada evento. A Tabela 1 resume os sensores de posição compatíveis com a plataforma Android.

Tabela 1. Sensores de posição compatíveis com a plataforma Android.

Sensor Dados de eventos do sensor Descrição Unidades de medida
TYPE_GAME_ROTATION_VECTOR SensorEvent.values[0] Componente do vetor de rotação ao longo do eixo X (X * sen (θ / 2)). Sem unidade
SensorEvent.values[1] Componente do vetor de rotação ao longo do eixo Y (Y * sen (θ / 2)).
SensorEvent.values[2] Componente do vetor de rotação ao longo do eixo Z (Z * sen (θ / 2)).
TYPE_GEOMAGNETIC_ROTATION_VECTOR SensorEvent.values[0] Componente do vetor de rotação ao longo do eixo X (X * sen (θ / 2)). Sem unidade
SensorEvent.values[1] Componente do vetor de rotação ao longo do eixo Y (Y * sen (θ / 2)).
SensorEvent.values[2] Componente do vetor de rotação ao longo do eixo Z (Z * sen (θ / 2)).
TYPE_MAGNETIC_FIELD SensorEvent.values[0] Intensidade do campo geomagnético ao longo do eixo X. μT
SensorEvent.values[1] Intensidade do campo geomagnético ao longo do eixo Y.
SensorEvent.values[2] Intensidade do campo geomagnético ao longo do eixo Z.
TYPE_MAGNETIC_FIELD_UNCALIBRATED SensorEvent.values[0] Intensidade do campo geomagnético (sem calibração do ferro duro) ao longo do eixo X. μT
SensorEvent.values[1] Intensidade do campo geomagnético (sem calibração do ferro duro) ao longo do eixo Y.
SensorEvent.values[2] Intensidade do campo geomagnético (sem calibração do ferro duro) ao longo do eixo Z.
SensorEvent.values[3] Estimativa da polarização do ferro ao longo do eixo X.
SensorEvent.values[4] Estimativa da polarização do ferro ao longo do eixo Y.
SensorEvent.values[5] Estimativa da polarização do ferro ao longo do eixo Z.
TYPE_ORIENTATION1 SensorEvent.values[0] Azimute (ângulo ao redor do eixo Z). Graus
SensorEvent.values[1] Inclinação (ângulo ao redor do eixo X).
SensorEvent.values[2] Rolagem (ângulo ao redor do eixo Y).
TYPE_PROXIMITY SensorEvent.values[0] Distância do objeto.2 cm

1O uso deste sensor foi suspenso no Android 2.2 (API de nível 8), e o tipo dele está obsoleto no Android 4.4W (API de nível 20). A biblioteca do sensor fornece métodos alternativos para adquirir a orientação do dispositivo. Veja essas informações em Calcular a orientação do dispositivo.

2 Alguns sensores de proximidade oferecem somente valores binários que representam posições de "perto" e "longe".

Usar o sensor vetorial de rotação para jogos

O sensor vetorial de rotação para jogos é idêntico ao sensor vetorial de rotação. Contudo, ele não usa o campo geomagnético. Por isso, o eixo Y não aponta para o norte, e sim para outra referência. É permitido que essa referência se desloque na mesma ordem de magnitude que o giroscópio em torno do eixo Z.

Como o sensor vetorial de rotação para jogos não usa o campo magnético, as rotações relativas são mais precisas e não são afetadas pelas alterações nesse campo. Use este sensor em um jogo se a referência da direção norte não for importante para você e caso o vetor de rotação normal não se alinhe às suas necessidades devido à dependência do campo magnético.

O código a seguir mostra como ter uma instância do sensor vetorial de rotação padrão para jogos:

Kotlin

private lateinit var sensorManager: SensorManager
private var sensor: Sensor? = null
...
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GAME_ROTATION_VECTOR)

Java

private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GAME_ROTATION_VECTOR);

Usar o sensor vetorial de rotação geomagnética

O sensor vetorial de rotação geomagnética é semelhante ao sensor vetorial de rotação. Contudo, ele usa um magnetômetro em vez de um giroscópio. A precisão desse sensor é menor que a do sensor vetorial de rotação normal, mas há menos consumo de energia. Só use este sensor se você quiser coletar algumas informações de rotação em segundo plano sem consumir muita bateria. Este sensor é mais útil quando usado com lotes.

O código a seguir mostra como ter uma instância do sensor vetorial de rotação geomagnética padrão:

Kotlin

private lateinit var sensorManager: SensorManager
private var sensor: Sensor? = null
...
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR)

Java

private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR);

Calcular a orientação do dispositivo

Ao calcular a orientação, você pode monitorar a posição do dispositivo em relação ao frame de referência da Terra (especificamente, o polo norte magnético). O código a seguir mostra como você pode calcular a orientação do dispositivo:

Kotlin

private lateinit var sensorManager: SensorManager
...
// Rotation matrix based on current readings from accelerometer and magnetometer.
val rotationMatrix = FloatArray(9)
SensorManager.getRotationMatrix(rotationMatrix, null, accelerometerReading, magnetometerReading)

// Express the updated rotation matrix as three orientation angles.
val orientationAngles = FloatArray(3)
SensorManager.getOrientation(rotationMatrix, orientationAngles)

Java

private SensorManager sensorManager;
...
// Rotation matrix based on current readings from accelerometer and magnetometer.
final float[] rotationMatrix = new float[9];
SensorManager.getRotationMatrix(rotationMatrix, null,
    accelerometerReading, magnetometerReading);

// Express the updated rotation matrix as three orientation angles.
final float[] orientationAngles = new float[3];
SensorManager.getOrientation(rotationMatrix, orientationAngles);

O sistema calcula os ângulos de orientação usando o sensor de campo geomagnético e o acelerômetro do dispositivo. Ao usar esses dois sensores de hardware, o sistema fornecerá dados para os três ângulos de orientação a seguir:

  • Azimute (graus de rotação em torno do eixo -Z). Este é o ângulo entre a direção atual da bússola do dispositivo e o norte magnético. Se o canto superior do dispositivo estiver voltado para o norte magnético, o azimute será 0 grau. Já se ele estiver voltado para o sul, o azimute será de 180 graus. Da mesma forma, se o canto superior do dispositivo estiver voltado para o leste, o azimute será de 90 graus. Já se ele estiver voltado para o oeste, o azimute será de 270 graus.
  • Inclinação (graus de rotação em torno do eixo X). Este é o ângulo entre um plano paralelo à tela do dispositivo e um plano paralelo ao solo. Se você segurar o dispositivo paralelamente ao chão com o canto inferior mais próximo de você e inclinar o canto superior em direção ao chão, o ângulo de inclinação será positivo. Inclinar o dispositivo na direção oposta, afastando o canto superior do solo, fará com que o ângulo de inclinação seja negativo. O intervalo de valores é de -180 a 180 graus.
  • Rolagem (graus de rotação em torno do eixo Y). Este é o ângulo entre um plano perpendicular à tela do dispositivo e um plano perpendicular ao solo. Se você segurar o dispositivo paralelamente ao chão com o canto inferior mais próximo de você e inclinar o canto esquerdo em direção ao chão, o ângulo de rolagem será positivo. Inclinar o dispositivo na direção oposta, movendo o canto direito em direção ao solo, fará com que o ângulo de rolagem seja negativo. O intervalo de valores é de -90 a 90 graus.

Observação: a definição de rolagem do sensor mudou para refletir a grande maioria das implementações no ecossistema do geossensor.

Esses ângulos funcionam com um sistema de coordenadas diferente daquele usado na aviação (para guinada, inclinação e rolagem). No sistema de aviação, o eixo X está ao longo do lado mais longo do avião, da cauda ao nariz.

O sensor de orientação gera informações processando os dados brutos do sensor do acelerômetro e do sensor de campo geomagnético. Devido ao processamento pesado envolvido nesse processo, a exatidão e a precisão do sensor de orientação são reduzidas. Especificamente, esse sensor só é confiável quando o ângulo de rolagem é 0. Como resultado, o uso do sensor de orientação foi suspenso no Android 2.2 (API de nível 8), e o tipo dele está obsoleto no Android 4.4W (API de nível 20). Em vez de usar os dados brutos do sensor de orientação, recomendamos que você use o método getRotationMatrix() com getOrientation() para calcular os valores de orientação, conforme mostrado no código de exemplo a seguir. Como parte desse processo, você pode usar o método remapCoordinateSystem() para converter os valores de orientação no frame de referência do seu aplicativo.

Kotlin

class SensorActivity : Activity(), SensorEventListener {

    private lateinit var sensorManager: SensorManager
    private val accelerometerReading = FloatArray(3)
    private val magnetometerReading = FloatArray(3)

    private val rotationMatrix = FloatArray(9)
    private val orientationAngles = FloatArray(3)

    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main)
        sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
    }

    override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
        // Do something here if sensor accuracy changes.
        // You must implement this callback in your code.
    }

    override fun onResume() {
        super.onResume()

        // Get updates from the accelerometer and magnetometer at a constant rate.
        // To make batch operations more efficient and reduce power consumption,
        // provide support for delaying updates to the application.
        //
        // In this example, the sensor reporting delay is small enough such that
        // the application receives an update before the system checks the sensor
        // readings again.
        sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)?.also { accelerometer ->
            sensorManager.registerListener(
                    this,
                    accelerometer,
                    SensorManager.SENSOR_DELAY_NORMAL,
                    SensorManager.SENSOR_DELAY_UI
            )
        }
        sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)?.also { magneticField ->
            sensorManager.registerListener(
                    this,
                    magneticField,
                    SensorManager.SENSOR_DELAY_NORMAL,
                    SensorManager.SENSOR_DELAY_UI
            )
        }
    }

    override fun onPause() {
        super.onPause()

        // Don't receive any more updates from either sensor.
        sensorManager.unregisterListener(this)
    }

    // Get readings from accelerometer and magnetometer. To simplify calculations,
    // consider storing these readings as unit vectors.
    override fun onSensorChanged(event: SensorEvent) {
        if (event.sensor.type == Sensor.TYPE_ACCELEROMETER) {
            System.arraycopy(event.values, 0, accelerometerReading, 0, accelerometerReading.size)
        } else if (event.sensor.type == Sensor.TYPE_MAGNETIC_FIELD) {
            System.arraycopy(event.values, 0, magnetometerReading, 0, magnetometerReading.size)
        }
    }

    // Compute the three orientation angles based on the most recent readings from
    // the device's accelerometer and magnetometer.
    fun updateOrientationAngles() {
        // Update rotation matrix, which is needed to update orientation angles.
        SensorManager.getRotationMatrix(
                rotationMatrix,
                null,
                accelerometerReading,
                magnetometerReading
        )

        // "mRotationMatrix" now has up-to-date information.

        SensorManager.getOrientation(rotationMatrix, mOrientationAngles)

        // "mOrientationAngles" now has up-to-date information.
    }
}

Java

public class SensorActivity extends Activity implements SensorEventListener {

    private SensorManager sensorManager;
    private final float[] accelerometerReading = new float[3];
    private final float[] magnetometerReading = new float[3];

    private final float[] rotationMatrix = new float[9];
    private final float[] orientationAngles = new float[3];

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        // Do something here if sensor accuracy changes.
        // You must implement this callback in your code.
    }

    @Override
    protected void onResume() {
        super.onResume();

        // Get updates from the accelerometer and magnetometer at a constant rate.
        // To make batch operations more efficient and reduce power consumption,
        // provide support for delaying updates to the application.
        //
        // In this example, the sensor reporting delay is small enough such that
        // the application receives an update before the system checks the sensor
        // readings again.
        Sensor accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        if (accelerometer != null) {
            sensorManager.registerListener(this, accelerometer,
                SensorManager.SENSOR_DELAY_NORMAL, SensorManager.SENSOR_DELAY_UI);
        }
        Sensor magneticField = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
        if (magneticField != null) {
            sensorManager.registerListener(this, magneticField,
                SensorManager.SENSOR_DELAY_NORMAL, SensorManager.SENSOR_DELAY_UI);
        }
    }

    @Override
    protected void onPause() {
        super.onPause();

        // Don't receive any more updates from either sensor.
        sensorManager.unregisterListener(this);
    }

    // Get readings from accelerometer and magnetometer. To simplify calculations,
    // consider storing these readings as unit vectors.
    @Override
    public void onSensorChanged(SensorEvent event) {
        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
          System.arraycopy(event.values, 0, accelerometerReading,
              0, accelerometerReading.length);
        } else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
            System.arraycopy(event.values, 0, magnetometerReading,
                0, magnetometerReading.length);
        }
    }

    // Compute the three orientation angles based on the most recent readings from
    // the device's accelerometer and magnetometer.
    public void updateOrientationAngles() {
        // Update rotation matrix, which is needed to update orientation angles.
        SensorManager.getRotationMatrix(rotationMatrix, null,
            accelerometerReading, mMagnetometerReading);

        // "mRotationMatrix" now has up-to-date information.

        SensorManager.getOrientation(rotationMatrix, mOrientationAngles);

        // "mOrientationAngles" now has up-to-date information.
    }
}

Normalmente, você não precisa executar nenhum processamento, nem filtrar dados dos ângulos de orientação brutos do dispositivo, além de converter o sistema de coordenadas do sensor no frame de referência do seu aplicativo.

Usar o sensor de campo geomagnético

O sensor do campo geomagnético permite monitorar as alterações no campo magnético da Terra. O código a seguir mostra como ter uma instância do sensor de campo geomagnético padrão:

Kotlin

private lateinit var sensorManager: SensorManager
private var sensor: Sensor? = null
...
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)

Java

private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);

Este sensor fornece dados brutos de intensidade do campo (em μT) para cada um dos três eixos de coordenadas. Normalmente, não é preciso usar esse sensor diretamente. Em vez disso, você pode usar o sensor vetorial de rotação para determinar o movimento rotacional bruto ou o acelerômetro e o sensor de campo geomagnético com o método getRotationMatrix() para acessar a matriz de rotação e inclinação. Você pode usar essas matrizes com os métodos getOrientation() e getInclination() para conseguir dados de azimute e inclinação geomagnética.

Observação: ao testar seu aplicativo, você pode melhorar a precisão do sensor acenando com o dispositivo no padrão da imagem 8.

Usar o magnetômetro sem calibração

O magnetômetro sem calibração é semelhante ao sensor de campo geomagnético, mas nenhuma calibração de ferro duro é aplicada ao campo magnético. A calibração de fábrica e a compensação de temperatura ainda são aplicadas ao campo magnético. O magnetômetro sem calibração é útil para gerenciar estimativas ruins de ferro duro. Em geral, o geomagneticsensor_event.values[0] ficará próximo a uncalibrated_magnetometer_event.values[0] - uncalibrated_magnetometer_event.values[3]. Ou seja,

calibrated_x ~= uncalibrated_x - bias_estimate_x

Observação: os sensores sem calibração fornecem resultados mais brutos e podem incluir algumas tendências, mas as medidas deles contêm menos saltos de correções aplicadas por meio da calibração. Alguns aplicativos podem preferir esses resultados não calibrados e considerá-los mais suaves e confiáveis. Por exemplo, para um aplicativo que esteja tentando realizar uma fusão de sensor própria, a apresentação de calibrações pode distorcer os resultados.

Além do campo magnético, o magnetômetro sem calibração também fornece a polarização estimada de ferro duro em cada eixo. O código a seguir mostra como ter uma instância do magnetômetro sem calibração padrão:

Kotlin

private lateinit var sensorManager: SensorManager
private var sensor: Sensor? = null
...
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED)

Java

private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED);

Usar o sensor de proximidade

O sensor de proximidade permite determinar a que distância um objeto está de um dispositivo. O código a seguir mostra como ter uma instância do sensor de proximidade padrão:

Kotlin

private lateinit var sensorManager: SensorManager
private var sensor: Sensor? = null
...
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)

Java

private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);

Em geral, o sensor de proximidade é usado para determinar a que distância está a cabeça de uma pessoa em relação ao dispositivo (por exemplo, quando um usuário faz ou recebe uma chamada telefônica). A maioria dos sensores de proximidade retorna a distância absoluta, em cm, mas alguns informam somente "perto" ou "longe". O código a seguir mostra como usar o sensor de proximidade:

Kotlin

class SensorActivity : Activity(), SensorEventListener {

    private lateinit var sensorManager: SensorManager
    private var mProximity: Sensor? = null

    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main)

        // Get an instance of the sensor service, and use that to get an instance of
        // a particular sensor.
        sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
        mProximity = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)
    }

    override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
        // Do something here if sensor accuracy changes.
    }

    override fun onSensorChanged(event: SensorEvent) {
        val distance = event.values[0]
        // Do something with this sensor data.
    }

    override fun onResume() {
        // Register a listener for the sensor.
        super.onResume()

        mProximity?.also { proximity ->
            sensorManager.registerListener(this, proximity, SensorManager.SENSOR_DELAY_NORMAL)
        }
    }

    override fun onPause() {
        // Be sure to unregister the sensor when the activity pauses.
        super.onPause()
        sensorManager.unregisterListener(this)
    }
}

Java

public class SensorActivity extends Activity implements SensorEventListener {
    private SensorManager sensorManager;
    private Sensor proximity;

    @Override
    public final void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // Get an instance of the sensor service, and use that to get an instance of
        // a particular sensor.
        sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
        proximity = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
    }

    @Override
    public final void onAccuracyChanged(Sensor sensor, int accuracy) {
        // Do something here if sensor accuracy changes.
    }

    @Override
    public final void onSensorChanged(SensorEvent event) {
        float distance = event.values[0];
        // Do something with this sensor data.
    }

    @Override
    protected void onResume() {
        // Register a listener for the sensor.
        super.onResume();
        sensorManager.registerListener(this, proximity, SensorManager.SENSOR_DELAY_NORMAL);
      }

    @Override
    protected void onPause() {
        // Be sure to unregister the sensor when the activity pauses.
        super.onPause();
        sensorManager.unregisterListener(this);
    }
}

Observação: alguns sensores de proximidade retornam valores binários que representam "perto" ou "longe". Nesse caso, o sensor geralmente informa o valor máximo de intervalo no estado "longe" e um valor menor no estado "perto". Normalmente, o valor de "longe" é > 5 cm, mas isso pode variar de sensor para sensor. Você pode determinar o alcance máximo de um sensor usando o método getMaximumRange().

Leia também