/* * Copyright 2011 Greg Milette and Adam Stroud * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package root.gast.playground.sensor.orientation; import java.util.HashMap; import java.util.List; import root.gast.playground.R; import root.gast.speech.SpeechRecognizingAndSpeakingActivity; import android.content.SharedPreferences; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.media.AudioManager; import android.os.Bundle; import android.speech.tts.TextToSpeech; import android.speech.tts.TextToSpeech.Engine; import android.util.Log; import android.view.View; import android.view.WindowManager; import android.widget.RadioButton; import android.widget.RadioGroup; import android.widget.TextView; import android.widget.ToggleButton; /** * Determines whether the device is face up or face down and gives a audio * notification (via TTS) when the face-up/face-down orientation changes. * * @author Adam Stroud <<a href="mailto:adam.stroud@gmail.com">adam.stroud@gmail.com</a>> */ public class DetermineOrientationActivity extends SpeechRecognizingAndSpeakingActivity implements SensorEventListener { private static final String TAG = "DetermineOrientationActivity"; private static final int RATE = SensorManager.SENSOR_DELAY_NORMAL; private static final int TTS_STREAM = AudioManager.STREAM_NOTIFICATION; private static final String TTS_NOTIFICATION_PREFERENCES_KEY = "TTS_NOTIFICATION_PREFERENCES_KEY"; private static final double GRAVITY_THRESHOLD = SensorManager.STANDARD_GRAVITY / 2; private SensorManager sensorManager; private float[] accelerationValues; private float[] magneticValues; private TextToSpeech tts; private boolean isFaceUp; private RadioGroup sensorSelector; private TextView selectedSensorValue; private TextView orientationValue; private TextView sensorXLabel; private TextView sensorXValue; private TextView sensorYLabel; private TextView sensorYValue; private TextView sensorZLabel; private TextView sensorZValue; private HashMap<String, String> ttsParams; private ToggleButton ttsNotificationsToggleButton; private SharedPreferences preferences; private boolean ttsNotifications; private int selectedSensorId; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); super.setContentView(R.layout.determine_orientation); // Keep the screen on so that changes in orientation can be easily // observed getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); // Set up stream to use for Text-To-Speech ttsParams = new HashMap<String, String>(); ttsParams.put(Engine.KEY_PARAM_STREAM, String.valueOf(TTS_STREAM)); // Set the volume control to use the same stream as TTS which allows // the user to easily adjust the TTS volume this.setVolumeControlStream(TTS_STREAM); // Get a reference to the sensor service sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); // Initialize references to the UI views that will be updated in the // code sensorSelector = (RadioGroup) findViewById(R.id.sensorSelector); selectedSensorValue = (TextView) findViewById(R.id.selectedSensorValue); orientationValue = (TextView) findViewById(R.id.orientationValue); sensorXLabel = (TextView) findViewById(R.id.sensorXLabel); sensorXValue = (TextView) findViewById(R.id.sensorXValue); sensorYLabel = (TextView) findViewById(R.id.sensorYLabel); sensorYValue = (TextView) findViewById(R.id.sensorYValue); sensorZLabel = (TextView) findViewById(R.id.sensorZLabel); sensorZValue = (TextView) findViewById(R.id.sensorZValue); ttsNotificationsToggleButton = (ToggleButton) findViewById(R.id.ttsNotificationsToggleButton); // Retrieve stored preferences preferences = getPreferences(MODE_PRIVATE); ttsNotifications = preferences.getBoolean(TTS_NOTIFICATION_PREFERENCES_KEY, true); } @Override protected void onResume() { super.onResume(); ttsNotificationsToggleButton.setChecked(ttsNotifications); updateSelectedSensor(); } @Override protected void onPause() { super.onPause(); // Unregister updates from sensors sensorManager.unregisterListener(this); // Shutdown TTS facility if (tts != null) { tts.shutdown(); } } @Override public void onSensorChanged(SensorEvent event) { float[] rotationMatrix; switch (event.sensor.getType()) { case Sensor.TYPE_GRAVITY: sensorXLabel.setText(R.string.xAxisLabel); sensorXValue.setText(String.valueOf(event.values[0])); sensorYLabel.setText(R.string.yAxisLabel); sensorYValue.setText(String.valueOf(event.values[1])); sensorZLabel.setText(R.string.zAxisLabel); sensorZValue.setText(String.valueOf(event.values[2])); sensorYLabel.setVisibility(View.VISIBLE); sensorYValue.setVisibility(View.VISIBLE); sensorZLabel.setVisibility(View.VISIBLE); sensorZValue.setVisibility(View.VISIBLE); if (selectedSensorId == R.id.gravitySensor) { if (event.values[2] >= GRAVITY_THRESHOLD) { onFaceUp(); } else if (event.values[2] <= (GRAVITY_THRESHOLD * -1)) { onFaceDown(); } } else { accelerationValues = event.values.clone(); rotationMatrix = generateRotationMatrix(); if (rotationMatrix != null) { determineOrientation(rotationMatrix); } } break; case Sensor.TYPE_ACCELEROMETER: accelerationValues = event.values.clone(); rotationMatrix = generateRotationMatrix(); if (rotationMatrix != null) { determineOrientation(rotationMatrix); } break; case Sensor.TYPE_MAGNETIC_FIELD: magneticValues = event.values.clone(); rotationMatrix = generateRotationMatrix(); if (rotationMatrix != null) { determineOrientation(rotationMatrix); } break; case Sensor.TYPE_ROTATION_VECTOR: rotationMatrix = new float[16]; SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values); determineOrientation(rotationMatrix); break; } } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { Log.d(TAG, String.format("Accuracy for sensor %s = %d", sensor.getName(), accuracy)); } /** * Generates a rotation matrix using the member data stored in * accelerationValues and magneticValues. * * @return The rotation matrix returned from * {@link android.hardware.SensorManager#getRotationMatrix(float[], float[], float[], float[])} * or <code>null</code> if either <code>accelerationValues</code> or * <code>magneticValues</code> is null. */ private float[] generateRotationMatrix() { float[] rotationMatrix = null; if (accelerationValues != null && magneticValues != null) { rotationMatrix = new float[16]; boolean rotationMatrixGenerated; rotationMatrixGenerated = SensorManager.getRotationMatrix(rotationMatrix, null, accelerationValues, magneticValues); if (!rotationMatrixGenerated) { Log.w(TAG, getString(R.string.rotationMatrixGenFailureMessage)); rotationMatrix = null; } } return rotationMatrix; } /** * Uses the last read accelerometer and gravity values to determine if the * device is face up or face down. * * @param rotationMatrix The rotation matrix to use if the orientation * calculation */ private void determineOrientation(float[] rotationMatrix) { float[] orientationValues = new float[3]; SensorManager.getOrientation(rotationMatrix, orientationValues); double azimuth = Math.toDegrees(orientationValues[0]); double pitch = Math.toDegrees(orientationValues[1]); double roll = Math.toDegrees(orientationValues[2]); sensorXLabel.setText(R.string.azimuthLabel); sensorXValue.setText(String.valueOf(azimuth)); sensorYLabel.setText(R.string.pitchLabel); sensorYValue.setText(String.valueOf(pitch)); sensorZLabel.setText(R.string.rollLabel); sensorZValue.setText(String.valueOf(roll)); sensorYLabel.setVisibility(View.VISIBLE); sensorYValue.setVisibility(View.VISIBLE); sensorZLabel.setVisibility(View.VISIBLE); sensorZValue.setVisibility(View.VISIBLE); if (pitch <= 10) { if (Math.abs(roll) >= 170) { onFaceDown(); } else if (Math.abs(roll) <= 10) { onFaceUp(); } } } /** * Handler for device being face up. */ private void onFaceUp() { if (!isFaceUp) { if (tts != null && ttsNotificationsToggleButton.isChecked()) { tts.speak(getString(R.string.faceUpText), TextToSpeech.QUEUE_FLUSH, ttsParams); } orientationValue.setText(R.string.faceUpText); isFaceUp = true; } } /** * Handler for device being face down. */ private void onFaceDown() { if (isFaceUp) { if (tts != null && ttsNotificationsToggleButton.isChecked()) { tts.speak(getString(R.string.faceDownText), TextToSpeech.QUEUE_FLUSH, ttsParams); } orientationValue.setText(R.string.faceDownText); isFaceUp = false; } } /** * Updates the views for when the selected sensor is changed */ private void updateSelectedSensor() { // Clear any current registrations sensorManager.unregisterListener(this); // Determine which radio button is currently selected and enable the // appropriate sensors selectedSensorId = sensorSelector.getCheckedRadioButtonId(); if (selectedSensorId == R.id.accelerometerMagnetometer) { sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), RATE); sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD), RATE); } else if (selectedSensorId == R.id.gravityMagnetometer) { sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY), RATE); sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD), RATE); } else if ((selectedSensorId == R.id.gravitySensor)) { sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY), RATE); } else { sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR), RATE); } // Update the label with the currently selected sensor RadioButton selectedSensorRadioButton = (RadioButton) findViewById(selectedSensorId); selectedSensorValue.setText(selectedSensorRadioButton.getText()); } /** * Handles click event for the sensor selector. * * @param view The view that was clicked */ public void onSensorSelectorClick(View view) { updateSelectedSensor(); } /** * Handles click event for the TTS toggle button. * * @param view The view for the toggle button */ public void onTtsNotificationsToggleButtonClicked(View view) { ttsNotifications = ((ToggleButton) view).isChecked(); preferences.edit() .putBoolean(TTS_NOTIFICATION_PREFERENCES_KEY, ttsNotifications) .commit(); } @Override public void onSuccessfulInit(TextToSpeech tts) { super.onSuccessfulInit(tts); this.tts = tts; } @Override protected void receiveWhatWasHeard(List<String> heard, float[] confidenceScores) { // no-op } }