/*
* Copyright (C) 2011 Google Inc.
*
* 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 com.example.helloandroid;
import java.text.DecimalFormat;
import android.app.ProgressDialog;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.Toast;
/**
* @author tgnourse@google.com (Thomas Nourse)
*/
public class AccelerometerHandler implements SensorEventListener {
Context context;
DashboardCamera camera;
int threshold = 2000; // milli g forces.
int delay = 15; // seconds.
/**
* Sets the force threshold in any direction for accelerator triggered
* video capture.
* @param threshold The threshold for capture in units of milli g forces.
*/
public void setThreshold(int threshold) {
this.threshold = threshold;
Log.i("HelloAndroid", "Accident threshold is not " + threshold + " milli g forces");
}
/**
* Gets the current force threshold for capture.
* @return threshold The threshold for capture in units of milli g forces.
*/
public int getThreshold() {
return threshold;
}
/**
* Sets how long we should record after an accident is detected.
* @param delay The number of seconds to continue to record.
*/
public void setDelay(int delay) {
this.delay = delay;
Log.i("HelloAndroid", "Accident delay is now " + delay + " seconds.");
}
/**
* Gets how long we should record after an accident is detected.
* @return The number of seconds that we will continue to record.
*/
public int getDelay() {
return delay;
}
AccelerometerHandler(Context context, DashboardCamera camera) {
this.context = context;
this.camera = camera;
}
private SensorManager getSensorManager() {
return (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
}
// Can return null.
private Sensor getAccelerometer(SensorManager manager) {
return manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
}
public void disable() {
getSensorManager().unregisterListener(this);
Log.i("HelloAndroid", "Accelerometer listener disabled.");
}
public void enable() {
Log.i("HelloAndroid", "Enabling the accelerometer listener.");
// Find the accelerometer and register this object as a listener if it exists.
SensorManager manager = getSensorManager();
Sensor accelerometer = getAccelerometer(manager);
if (accelerometer != null) {
// SENSOR_DELAY_UI was chosen over SENSOR_DELAY_NORMAL because it was possible to
// miss events simulated in hand with the slower rate.
if (!manager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_UI)) {
Log.e("HelloAndroid", "Could not register a listener for the accelerometer.");
issueLongToast("Could not find and accelerometer.");
}
} else {
Log.e("HelloAndroid", "Could not get a default accelerometer sensor!");
issueLongToast("Could not find and accelerometer.");
}
Log.i("HelloAndroid", "Accelerometer listener enabled!");
}
private void issueShortToast(String message) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
}
private void issueLongToast(String message) {
Toast.makeText(context, message, Toast.LENGTH_LONG).show();
}
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// Do nothing.
}
private double getMagnitude(float x, float y, float z) {
return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2) + Math.pow(z, 2));
}
private double toGForce(double newtons) {
return newtons / SensorManager.GRAVITY_EARTH;
}
private double toGForce(int milliGForce) {
return (milliGForce * 1.0) / 1000;
}
private int toMilliGForce(double newtons) {
return (int) (toGForce(newtons) * 1000);
}
private double toNewtons(int milliGForce) {
return toNewtons(toGForce(milliGForce));
}
private double toNewtons(double gForce) {
return gForce * SensorManager.GRAVITY_EARTH;
}
public void onSensorChanged(SensorEvent event) {
if (event.values.length == 3) {
double force = getMagnitude(event.values[0], event.values[1], event.values[2]);
// Log.i("HelloAndroid", "Accelerometer Reading: " + force + "N ");
if (toGForce(force) >= toGForce(threshold)){
// Disable the sensor so it doesn't fire any more events.
disable();
Log.i("HelloAndroid", "Accident " + toGForce(force) + " >= " + toGForce(threshold));
// TODO(tgnourse): Before this dialog appears we should get a lock on the camera object.
// This disable() / enable() crap is just an approximation of that. Right now we can get
// into wonky states if say a touch and an acceleration trigger happen in very close
// proximity.
// Begin the count down until the recording should be captured.
SaveVideoInitiator initiator = new SaveVideoInitiator(force);
initiator.execute();
}
} else {
Log.e("HelloAndroid", "Got different (" + event.values.length + ") " +
"than 3 values from the acceleromter!! " +
"The phone may have been transported to another universe");
}
}
private class SaveVideoInitiator extends AsyncTask<Void, Integer, Void> {
private ProgressDialog dialog = new ProgressDialog(context);
private double force;
SaveVideoInitiator(double force) {
this.force = force;
}
protected void onPreExecute() {
dialog.setCancelable(false);
}
protected Void doInBackground(Void... unused) {
try {
// Sleep for recordingDelay seconds to get the aftermath.
for (int second = delay; second > 0; second--) {
publishProgress(second);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
// Do nothing. This shouldn't happen.
}
return null;
}
protected void onProgressUpdate(Integer... progress) {
if (progress.length == 1) {
dialog.setMessage("Accident detected! Measured "
+ (new DecimalFormat("#.#")).format(toGForce(force))
+ "G which is over the threshold of "
+ (new DecimalFormat("#.#")).format(toGForce(threshold))
+ "G. Recording for " + progress[0] + " more seconds.");
dialog.show();
} else {
Log.e("HelloAndroid", "Progress params should have a single Integer!");
}
}
protected void onPostExecute(Void unused) {
dialog.dismiss();
// Now stop and save the recording.
camera.stopAndSaveRecording();
// Re-enable the sensor.
// TODO(tgnourse): It would be better to re-enable the sensor after the video has been
// saved so we don't accidentally trigger another recording. The other option is to
// clean up the DashboardCamera class to just ignore certain additional events when it
// is already doing something (like saving a file).
enable();
}
}
}