com.kircherelectronics.accelerationfilter.activity.AccelerationPlotActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.kircherelectronics.accelerationfilter.activity.AccelerationPlotActivity.java

Source

package com.kircherelectronics.accelerationfilter.activity;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.Calendar;

import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;

import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.Window;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.androidplot.xy.XYPlot;
import com.kircherelectronics.accelerationfilter.R;
import com.kircherelectronics.accelerationfilter.dialog.FilterSettingsDialog;
import com.kircherelectronics.accelerationfilter.dialog.SensorSettingsDialog;
import com.kircherelectronics.accelerationfilter.filter.LowPassFilter;
import com.kircherelectronics.accelerationfilter.filter.MeanFilter;
import com.kircherelectronics.accelerationfilter.plot.DynamicBarPlot;
import com.kircherelectronics.accelerationfilter.plot.DynamicLinePlot;
import com.kircherelectronics.accelerationfilter.plot.PlotColor;
import com.kircherelectronics.accelerationfilter.plot.PlotPrefCallback;
import com.kircherelectronics.accelerationfilter.prefs.PrefUtils;

/*
 * Acceleration Filter
 * Copyright (C) 2013, Kaleb Kircher - Boki Software, Kircher Engineering, LLC
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * Implements an Activity that is intended to run filters on accelerometer
 * inputs and then graph the outputs. The user can select which filters should
 * be used and set key parameters for each filter.
 * 
 * Currently supports an IIR digital low-pass filter. The low-pass filters are
 * classified as recursive, or infinite response filters (IIR). The current, nth
 * sample output depends on both current and previous inputs as well as previous
 * outputs. It is essentially a weighted moving average, which comes in many
 * different flavors depending on the values for the coefficients, a and b. The
 * low-pass filter, the Wikipedia LPF, is an IIR single-pole implementation. The
 * coefficient, a (alpha), can be adjusted based on the sample period of the
 * sensor to produce the desired time constant that the filter will act on. It
 * takes a simple form of y[i] = y[i] + alpha * (x[i] - y[i]). Alpha is defined
 * as alpha = dt / (timeConstant + dt);) where the time constant is the length
 * of signals the filter should act on and dt is the sample period (1/frequency)
 * of the sensor.
 * 
 * A finite impulse response (FIR) moving average filter is also implemented.
 * This filter tends to be extremely effective at removing noise from the
 * signal, much more so than the low-pass filter.
 * 
 * @author Kaleb
 * @version %I%, %G%
 */
public class AccelerationPlotActivity extends Activity
        implements SensorEventListener, Runnable, OnTouchListener, PlotPrefCallback {

    private static final String tag = AccelerationPlotActivity.class.getSimpleName();

    // Only noise below this threshold will be plotted
    private final static float MAX_NOISE_THRESHOLD = 0.1f;

    // The size of the sample window that determines RMS Amplitude Noise
    // (standard deviation)
    public static int STD_DEV_SAMPLE_WINDOW = 20;

    // Plot keys for the acceleration plot
    private final static int PLOT_ACCEL_X_AXIS_KEY = 0;
    private final static int PLOT_ACCEL_Y_AXIS_KEY = 1;
    private final static int PLOT_ACCEL_Z_AXIS_KEY = 2;

    // Plot keys for the LPF Android Developer plot
    private final static int PLOT_LPF_X_AXIS_KEY = 3;
    private final static int PLOT_LPF_Y_AXIS_KEY = 4;
    private final static int PLOT_LPF_Z_AXIS_KEY = 5;

    // Plot keys for the mean filter plot
    private final static int PLOT_MEAN_X_AXIS_KEY = 6;
    private final static int PLOT_MEAN_Y_AXIS_KEY = 7;
    private final static int PLOT_MEAN_Z_AXIS_KEY = 8;

    // Plot keys for the noise bar plot
    private final static int BAR_PLOT_ACCEL_KEY = 0;
    private final static int BAR_PLOT_LPF_KEY = 1;
    private final static int BAR_PLOT_MEAN_KEY = 2;

    private boolean dataReady = false;

    // Indicate if the output should be logged to a .csv file
    private boolean logData = false;

    // Indicate if the AndDev LPF should be plotted
    private boolean lpfActive = false;

    // Indicate if the Mean Filter should be plotted
    private boolean meanFilterActive = false;

    private boolean invertAxisActive = false;

    private boolean plotLPFReady = false;
    private boolean plotMeanReady = false;

    private boolean run = false;

    private double dStdDevMeanZAxis = 0;

    // Touch to zoom constants for the dynamicPlot
    private float distance = 0;
    private float zoom = 1.2f;

    private float lpfTimeConstant = 1;
    private float meanFilterTimeConstant = 1;

    // Outputs for the acceleration and LPFs
    private float[] acceleration = new float[3];
    private float[] lpfOutput = new float[3];
    private float[] meanFilterOutput = new float[3];

    // The generation of the log output
    private int generation = 0;

    // Color keys for the acceleration plot
    private int plotAccelXAxisColor;
    private int plotAccelYAxisColor;
    private int plotAccelZAxisColor;

    // Color keys for the LPF Android Developer plot
    private int plotLPFXAxisColor;
    private int plotLPFYAxisColor;
    private int plotLPFZAxisColor;

    // Color keys for the mean filter plot
    private int plotMeanXAxisColor;
    private int plotMeanYAxisColor;
    private int plotMeanZAxisColor;

    // Log output time stamp
    private long logTime = 0;

    // Decimal formats for the UI outputs
    private DecimalFormat df;

    private DynamicBarPlot barPlot;
    // Graph plot for the UI outputs
    private DynamicLinePlot dynamicPlot;

    // Handler for the UI plots so everything plots smoothly
    private Handler handler;

    // Icon to indicate logging is active
    private ImageView iconLogger;

    // Low-Pass Filter
    private LowPassFilter lpf;

    // Mean filter
    private MeanFilter meanFilter;

    // Plot colors
    private PlotColor color;

    private Runnable runnable;

    // Sensor manager to access the accelerometer sensor
    private SensorManager sensorManager;

    private FilterSettingsDialog settingsDialog;

    // RMS Noise levels
    private DescriptiveStatistics stdDevMaginitudeAccel;
    private DescriptiveStatistics stdDevMaginitude;
    private DescriptiveStatistics stdDevMaginitudeMean;

    private DescriptiveStatistics stdDevMaginitudeMeanZAxis;

    private SensorSettingsDialog sensorSettingsDialog;

    // Acceleration plot titles
    private String plotAccelXAxisTitle = "A-X";
    private String plotAccelYAxisTitle = "A-Y";
    private String plotAccelZAxisTitle = "A-Z";

    // LPF Android Developer plot tiltes
    private String plotLPFXAxisTitle = "LPF-X";
    private String plotLPFYAxisTitle = "LPF-Y";
    private String plotLPFZAxisTitle = "LPF-Z";

    // Mean filter plot tiltes
    private String plotMeanXAxisTitle = "M-X";
    private String plotMeanYAxisTitle = "M-Y";
    private String plotMeanZAxisTitle = "M-Z";
    private String plotStdDevMeanZAxisTitle = "StdDevMZ";

    private String frequencySelection;

    // Output log
    private String log;

    // Acceleration UI outputs
    private TextView xAxis;
    private TextView yAxis;
    private TextView zAxis;

    private Thread thread;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.plot_sensor_activity);

        // Read in the saved prefs
        readPrefs();

        initTextOutputs();

        initIcons();

        initStatistics();

        initFilters();

        initColor();

        initPlots();

        sensorManager = (SensorManager) this.getSystemService(Context.SENSOR_SERVICE);

        handler = new Handler();

        runnable = new Runnable() {
            @Override
            public void run() {
                handler.postDelayed(this, 100);

                plotData();
                updateAccelerationText();
            }
        };
    }

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

        sensorManager.unregisterListener(this);

        if (logData) {
            writeLogToFile();
        }

        if (run && thread != null) {
            run = false;

            thread.interrupt();

            thread = null;
        }

        handler.removeCallbacks(runnable);
    }

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

        readPrefs();
        readSensorPrefs();

        // Reset the filters
        lpf.reset();
        meanFilter.reset();

        thread = new Thread(this);

        if (!run) {
            run = true;

            thread.start();
        }

        handler.post(runnable);

        updateSensorDelay();
    }

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

    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        // Get a local copy of the sensor values
        System.arraycopy(event.values, 0, acceleration, 0, event.values.length);

        if (invertAxisActive) {
            acceleration[0] = -acceleration[0];
            acceleration[1] = -acceleration[1];
            acceleration[2] = -acceleration[2];
        }

        if (lpfActive) {
            lpfOutput = lpf.addSamples(acceleration);
        }
        if (meanFilterActive) {
            meanFilterOutput = meanFilter.filterFloat(acceleration);

            stdDevMaginitudeMeanZAxis.addValue(meanFilterOutput[2]);

            this.dStdDevMeanZAxis = stdDevMaginitudeMeanZAxis.getStandardDeviation();
        }

        dataReady = true;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.settings_plot_menu, menu);
        return true;
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private void updateMenu() {
        invalidateOptionsMenu();
    }

    /**
     * Event Handling for Individual menu item selected Identify single menu
     * item by it's id
     * */
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {

        // Log the data
        case R.id.action_log_data:
            startDataLog();
            updateMenu();
            return true;

        // Log the data
        case R.id.action_vector:
            Intent myIntent = new Intent(this, AccelerationVectorActivity.class);
            startActivity(myIntent);
            return true;

        // Log the data
        case R.id.menu_settings_filter:
            showFilterSettingsDialog();
            return true;

        case R.id.action_settings_sensor:
            showSensorSettingsDialog();
            return true;

        // Log the data
        case R.id.menu_settings_help:
            showHelpDialog();
            return true;

        default:
            return super.onOptionsItemSelected(item);
        }
    }

    /**
     * Manage the content of the options menu dynamically.
     */
    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        super.onPrepareOptionsMenu(menu);

        if (logData) {
            menu.getItem(2).getSubMenu().getItem(0).setEnabled(false);
        } else {
            menu.getItem(2).getSubMenu().getItem(0).setEnabled(true);
        }

        return true;
    }

    /**
     * Pinch to zoom.
     */
    @Override
    public boolean onTouch(View v, MotionEvent e) {
        // MotionEvent reports input details from the touch screen
        // and other input controls.
        float newDist = 0;

        switch (e.getAction()) {

        case MotionEvent.ACTION_MOVE:

            // pinch to zoom
            if (e.getPointerCount() == 2) {
                if (distance == 0) {
                    distance = fingerDist(e);
                }

                newDist = fingerDist(e);

                zoom *= distance / newDist;

                dynamicPlot.setMaxRange(zoom * Math.log(zoom));
                dynamicPlot.setMinRange(-zoom * Math.log(zoom));

                distance = newDist;
            }
        }

        return false;
    }

    /**
     * Output and logs are run on their own thread to keep the UI from hanging
     * and the output smooth.
     */
    @Override
    public void run() {
        while (run && !Thread.currentThread().isInterrupted()) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            logData();
        }

        Thread.currentThread().interrupt();
    }

    @Override
    public void checkPlotPrefs() {
        readPrefs();
        readSensorPrefs();
        checkLPFActive();
        checkMeanActive();

        updateSensorDelay();

        lpf.setTimeConstant(this.lpfTimeConstant);
        meanFilter.setTimeConstant(this.meanFilterTimeConstant);
    }

    /**
     * Read in the current user preferences.
     */
    private void readSensorPrefs() {
        SharedPreferences prefs = this.getSharedPreferences(PrefUtils.SENSOR_PREFS, Activity.MODE_PRIVATE);

        this.frequencySelection = prefs.getString(PrefUtils.SENSOR_FREQUENCY_PREF, PrefUtils.SENSOR_FREQUENCY_FAST);
    }

    /**
     * Indicate if the Android Developer LPF should be plotted.
     * 
     * @param lpfActive
     *            Plot the filter if true.
     */
    private void checkLPFActive() {
        if (this.lpfActive) {
            addLPFPlot();
        } else {
            removeLPFAndDevPlot();
        }
    }

    /**
     * Indicate if the Mean Filter should be plotted.
     * 
     * @param meanFilterActive
     *            Plot the filter if true.
     */
    private void checkMeanActive() {
        if (this.meanFilterActive) {
            addMeanFilterPlot();
        } else {
            removeMeanFilterPlot();
        }
    }

    /**
     * Create the output graph line chart.
     */
    private void addAccelerationPlot() {
        addGraphPlot(plotAccelXAxisTitle, PLOT_ACCEL_X_AXIS_KEY, plotAccelXAxisColor);
        addGraphPlot(plotAccelYAxisTitle, PLOT_ACCEL_Y_AXIS_KEY, plotAccelYAxisColor);
        addGraphPlot(plotAccelZAxisTitle, PLOT_ACCEL_Z_AXIS_KEY, plotAccelZAxisColor);
    }

    /**
     * Add a plot to the graph.
     * 
     * @param title
     *            The name of the plot.
     * @param key
     *            The unique plot key
     * @param color
     *            The color of the plot
     */
    private void addGraphPlot(String title, int key, int color) {
        dynamicPlot.addSeriesPlot(title, key, color);
    }

    /**
     * Add the Android Developer LPF plot.
     */
    private void addLPFPlot() {
        if (lpfActive && !plotLPFReady) {
            addGraphPlot(plotLPFXAxisTitle, PLOT_LPF_X_AXIS_KEY, plotLPFXAxisColor);
            addGraphPlot(plotLPFYAxisTitle, PLOT_LPF_Y_AXIS_KEY, plotLPFYAxisColor);
            addGraphPlot(plotLPFZAxisTitle, PLOT_LPF_Z_AXIS_KEY, plotLPFZAxisColor);

            plotLPFReady = true;
        }
    }

    /**
     * Add the Mean Filter plot.
     */
    private void addMeanFilterPlot() {
        if (meanFilterActive && !plotMeanReady) {
            addGraphPlot(plotMeanXAxisTitle, PLOT_MEAN_X_AXIS_KEY, plotMeanXAxisColor);
            addGraphPlot(plotMeanYAxisTitle, PLOT_MEAN_Y_AXIS_KEY, plotMeanYAxisColor);
            addGraphPlot(plotMeanZAxisTitle, PLOT_MEAN_Z_AXIS_KEY, plotMeanZAxisColor);

            plotMeanReady = true;
        }
    }

    /**
     * Get the distance between fingers for the touch to zoom.
     * 
     * @param event
     * @return
     */
    private final float fingerDist(MotionEvent event) {
        float x = event.getX(0) - event.getX(1);
        float y = event.getY(0) - event.getY(1);
        return (float) Math.sqrt(x * x + y * y);
    }

    /**
     * Create the plot colors.
     */
    private void initColor() {
        color = new PlotColor(this);

        plotAccelXAxisColor = color.getDarkBlue();
        plotAccelYAxisColor = color.getDarkGreen();
        plotAccelZAxisColor = color.getDarkRed();

        plotLPFXAxisColor = color.getLightBlue();
        plotLPFYAxisColor = color.getLightGreen();
        plotLPFZAxisColor = color.getLightRed();

        plotMeanXAxisColor = color.getLightBlue();
        plotMeanYAxisColor = color.getLightGreen();
        plotMeanZAxisColor = color.getLightRed();
    }

    /**
     * Initialize the activity icons.
     */
    private void initIcons() {
        // Create the logger icon
        iconLogger = (ImageView) findViewById(R.id.icon_logger);
        iconLogger.setVisibility(View.INVISIBLE);
    }

    /**
     * Initialize the available filters.
     */
    private void initFilters() {
        lpf = new LowPassFilter();
        lpf.setTimeConstant(this.lpfTimeConstant);

        meanFilter = new MeanFilter();
        meanFilter.setTimeConstant(this.meanFilterTimeConstant);
    }

    /**
     * Initialize the plots.
     */
    private void initPlots() {
        View view = findViewById(R.id.ScrollView01);
        view.setOnTouchListener(this);

        // Create the graph plot
        XYPlot plot = (XYPlot) findViewById(R.id.plot_sensor);
        plot.setTitle("Acceleration");
        dynamicPlot = new DynamicLinePlot(plot);
        dynamicPlot.setMaxRange(21);
        dynamicPlot.setMinRange(-21);

        // setup the APR Levels plot:
        XYPlot noiseLevelsPlot = (XYPlot) findViewById(R.id.plot_noise);
        noiseLevelsPlot.setTitle("Noise");

        barPlot = new DynamicBarPlot(noiseLevelsPlot, "Sensor Noise");

        addAccelerationPlot();
        addLPFPlot();
        addMeanFilterPlot();
    }

    /**
     * Initialize the statistics.
     */
    private void initStatistics() {
        // Create the RMS Noise calculations
        stdDevMaginitudeAccel = new DescriptiveStatistics();
        stdDevMaginitudeAccel.setWindowSize(STD_DEV_SAMPLE_WINDOW);

        stdDevMaginitude = new DescriptiveStatistics();
        stdDevMaginitude.setWindowSize(STD_DEV_SAMPLE_WINDOW);

        stdDevMaginitudeMean = new DescriptiveStatistics();
        stdDevMaginitudeMean.setWindowSize(STD_DEV_SAMPLE_WINDOW);

        stdDevMaginitudeMeanZAxis = new DescriptiveStatistics();
        stdDevMaginitudeMeanZAxis.setWindowSize(180);
    }

    /**
     * Initialize the Text View sensor outputs.
     */
    private void initTextOutputs() {
        // Format the UI outputs so they look nice
        df = new DecimalFormat("#.##");

        // Create the acceleration UI outputs
        xAxis = (TextView) findViewById(R.id.value_x_axis);
        yAxis = (TextView) findViewById(R.id.value_y_axis);
        zAxis = (TextView) findViewById(R.id.value_z_axis);
    }

    /**
     * Plot the output data in the UI.
     */
    private void plotData() {
        updateGraphPlot();

        updateBarPlot();
    }

    /**
     * Remove the Mean Filter plot.
     */
    private void removeMeanFilterPlot() {
        if (!meanFilterActive && plotMeanReady) {
            plotMeanReady = false;

            removeGraphPlot(PLOT_MEAN_X_AXIS_KEY);
            removeGraphPlot(PLOT_MEAN_Y_AXIS_KEY);
            removeGraphPlot(PLOT_MEAN_Z_AXIS_KEY);
        }
    }

    /**
     * Remove the Android Developer LPF plot.
     */
    private void removeLPFAndDevPlot() {
        if (!lpfActive && plotLPFReady) {
            plotLPFReady = false;

            removeGraphPlot(PLOT_LPF_X_AXIS_KEY);
            removeGraphPlot(PLOT_LPF_Y_AXIS_KEY);
            removeGraphPlot(PLOT_LPF_Z_AXIS_KEY);
        }
    }

    /**
     * Remove a plot from the graph.
     * 
     * @param key
     */
    private void removeGraphPlot(int key) {
        dynamicPlot.removeSeriesPlot(key);
    }

    private void showHelpDialog() {
        Dialog helpDialog = new Dialog(this);
        helpDialog.setCancelable(true);
        helpDialog.setCanceledOnTouchOutside(true);

        helpDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);

        helpDialog.setContentView(getLayoutInflater().inflate(R.layout.help_dialog_view, null));

        helpDialog.show();
    }

    /**
     * Show a settings dialog.
     */
    private void showSensorSettingsDialog() {
        if (sensorSettingsDialog == null) {
            sensorSettingsDialog = new SensorSettingsDialog(this, this);
            sensorSettingsDialog.setCancelable(true);
            sensorSettingsDialog.setCanceledOnTouchOutside(true);
        }

        sensorSettingsDialog.show();
    }

    /**
     * Show a settings dialog.
     */
    private void showFilterSettingsDialog() {
        if (settingsDialog == null) {
            settingsDialog = new FilterSettingsDialog(this, this);
            settingsDialog.setCancelable(true);
            settingsDialog.setCanceledOnTouchOutside(true);
        }

        settingsDialog.show();
    }

    /**
     * Begin logging data to an external .csv file.
     */
    private void startDataLog() {
        if (logData == false) {
            generation = 0;

            stdDevMaginitudeMeanZAxis.clear();

            CharSequence text = "Logging Data";
            int duration = Toast.LENGTH_SHORT;

            Toast toast = Toast.makeText(this, text, duration);
            toast.show();

            String headers = "Generation" + ",";

            headers += "Timestamp" + ",";

            headers += this.plotAccelXAxisTitle + ",";

            headers += this.plotAccelYAxisTitle + ",";

            headers += this.plotAccelZAxisTitle + ",";

            if (lpfActive) {
                headers += this.plotLPFXAxisTitle + ",";

                headers += this.plotLPFYAxisTitle + ",";

                headers += this.plotLPFZAxisTitle + ",";
            }

            if (meanFilterActive) {
                headers += this.plotMeanXAxisTitle + ",";

                headers += this.plotMeanYAxisTitle + ",";

                headers += this.plotMeanZAxisTitle + ",";

                headers += this.plotStdDevMeanZAxisTitle + ",";
            }

            log = headers;

            log += System.getProperty("line.separator");

            iconLogger.setVisibility(View.VISIBLE);

            logData = true;
        } else {
            iconLogger.setVisibility(View.INVISIBLE);

            logData = false;
            writeLogToFile();
        }
    }

    /**
     * Log output data to an external .csv file.
     */
    private void logData() {
        if (logData && dataReady) {
            if (generation == 0) {
                logTime = System.currentTimeMillis();
            }

            log += generation++ + ",";
            log += df.format((System.currentTimeMillis() - logTime) / 1000.0f) + ",";

            log += acceleration[0] + ",";
            log += acceleration[1] + ",";
            log += acceleration[2] + ",";

            if (lpfActive) {
                log += lpfOutput[0] + ",";
                log += lpfOutput[1] + ",";
                log += lpfOutput[2] + ",";
            }

            if (meanFilterActive) {
                log += meanFilterOutput[0] + ",";
                log += meanFilterOutput[1] + ",";
                log += meanFilterOutput[2] + ",";
                log += this.dStdDevMeanZAxis + ",";
            }

            log += System.getProperty("line.separator");

            dataReady = false;
        }
    }

    /**
     * Write the logged data out to a persisted file.
     */
    private void writeLogToFile() {
        Calendar c = Calendar.getInstance();
        String filename = "AccelerationFilter-" + c.get(Calendar.YEAR) + "-" + (c.get(Calendar.MONTH) + 1) + "-"
                + c.get(Calendar.DAY_OF_MONTH) + "-" + c.get(Calendar.HOUR) + "-" + c.get(Calendar.MINUTE) + "-"
                + c.get(Calendar.SECOND) + ".csv";

        File dir = new File(Environment.getExternalStorageDirectory() + File.separator + "AccelerationFilter"
                + File.separator + "Logs");
        if (!dir.exists()) {
            dir.mkdirs();
        }

        File file = new File(dir, filename);

        FileOutputStream fos;
        byte[] data = log.getBytes();
        try {
            fos = new FileOutputStream(file);
            fos.write(data);
            fos.flush();
            fos.close();

            CharSequence text = "Log Saved";
            int duration = Toast.LENGTH_SHORT;

            Toast toast = Toast.makeText(this, text, duration);
            toast.show();
        } catch (FileNotFoundException e) {
            CharSequence text = e.toString();
            int duration = Toast.LENGTH_SHORT;

            Toast toast = Toast.makeText(this, text, duration);
            toast.show();
        } catch (IOException e) {
            // handle exception
        } finally {
            // Update the MediaStore so we can view the file without rebooting.
            // Note that it appears that the ACTION_MEDIA_MOUNTED approach is
            // now blocked for non-system apps on Android 4.4.
            MediaScannerConnection.scanFile(this, new String[] { file.getPath() }, null,
                    new MediaScannerConnection.OnScanCompletedListener() {
                        @Override
                        public void onScanCompleted(final String path, final Uri uri) {

                        }
                    });
        }
    }

    /**
     * Read in the current user preferences.
     */
    private void readPrefs() {
        SharedPreferences prefs = this.getSharedPreferences(PrefUtils.FILTER_PREFS, Activity.MODE_PRIVATE);

        this.lpfActive = prefs.getBoolean(PrefUtils.LPF_ACTIVE_PREF, false);
        this.meanFilterActive = prefs.getBoolean(PrefUtils.MEAN_FILTER_ACTIVE_PREF, false);
        this.invertAxisActive = prefs.getBoolean(PrefUtils.INVERT_AXIS_ACTIVE, false);

        this.lpfTimeConstant = prefs.getFloat(PrefUtils.LPF_TIME_CONSTANT, 1);
        this.meanFilterTimeConstant = prefs.getFloat(PrefUtils.MEAN_FILTER_TIME_CONSTANT, 1);
    }

    /**
     * Update the acceleration sensor output Text Views.
     */
    private void updateAccelerationText() {
        // Update the view with the new acceleration data
        xAxis.setText(df.format(acceleration[0]));
        yAxis.setText(df.format(acceleration[1]));
        zAxis.setText(df.format(acceleration[2]));
    }

    /**
     * Update the graph plot.
     */
    private void updateGraphPlot() {
        dynamicPlot.setData(acceleration[0], PLOT_ACCEL_X_AXIS_KEY);
        dynamicPlot.setData(acceleration[1], PLOT_ACCEL_Y_AXIS_KEY);
        dynamicPlot.setData(acceleration[2], PLOT_ACCEL_Z_AXIS_KEY);

        if (lpfActive) {
            dynamicPlot.setData(lpfOutput[0], PLOT_LPF_X_AXIS_KEY);
            dynamicPlot.setData(lpfOutput[1], PLOT_LPF_Y_AXIS_KEY);
            dynamicPlot.setData(lpfOutput[2], PLOT_LPF_Z_AXIS_KEY);
        }

        if (meanFilterActive) {
            dynamicPlot.setData(meanFilterOutput[0], PLOT_MEAN_X_AXIS_KEY);
            dynamicPlot.setData(meanFilterOutput[1], PLOT_MEAN_Y_AXIS_KEY);
            dynamicPlot.setData(meanFilterOutput[2], PLOT_MEAN_Z_AXIS_KEY);
        }

        dynamicPlot.draw();
    }

    /**
     * Update the bar plot.
     */
    private void updateBarPlot() {
        Number[] seriesNumbers = new Number[3];

        stdDevMaginitudeAccel.addValue(Math
                .sqrt(Math.pow(acceleration[0], 2) + Math.pow(acceleration[1], 2) + Math.pow(acceleration[2], 2)));

        double var = stdDevMaginitudeAccel.getStandardDeviation();

        if (var > MAX_NOISE_THRESHOLD) {
            var = MAX_NOISE_THRESHOLD;
        }

        seriesNumbers[BAR_PLOT_ACCEL_KEY] = var;

        if (plotLPFReady) {
            stdDevMaginitude.addValue(
                    Math.sqrt(Math.pow(lpfOutput[0], 2) + Math.pow(lpfOutput[1], 2) + Math.pow(lpfOutput[2], 2)));

            var = stdDevMaginitude.getStandardDeviation();

            if (var > MAX_NOISE_THRESHOLD) {
                var = MAX_NOISE_THRESHOLD;
            }

            seriesNumbers[BAR_PLOT_LPF_KEY] = var;
        }
        if (!plotLPFReady) {
            seriesNumbers[BAR_PLOT_LPF_KEY] = 0;
        }

        if (plotMeanReady) {
            stdDevMaginitudeMean.addValue(
                    Math.abs(meanFilterOutput[0]) + Math.abs(meanFilterOutput[1]) + Math.abs(meanFilterOutput[2]));

            var = stdDevMaginitudeMean.getStandardDeviation();

            if (var > MAX_NOISE_THRESHOLD) {
                var = MAX_NOISE_THRESHOLD;
            }

            seriesNumbers[BAR_PLOT_MEAN_KEY] = var;
        }

        if (!plotMeanReady) {
            seriesNumbers[BAR_PLOT_MEAN_KEY] = 0;
        }

        barPlot.onDataAvailable(seriesNumbers);
    }

    /**
     * Set the sensor delay based on user preferences. 0 = slow, 1 = medium, 2 =
     * fast.
     * 
     * @param position
     *            The desired sensor delay.
     */
    private void setSensorDelay(int position) {
        switch (position) {
        case 0:

            sensorManager.unregisterListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER));

            // Register for sensor updates.
            sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
                    SensorManager.SENSOR_DELAY_NORMAL);
            break;
        case 1:

            sensorManager.unregisterListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER));

            // Register for sensor updates.
            sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
                    SensorManager.SENSOR_DELAY_GAME);
            break;
        case 2:

            sensorManager.unregisterListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER));

            // Register for sensor updates.
            sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
                    SensorManager.SENSOR_DELAY_FASTEST);
            break;
        }

        lpf.reset();
        meanFilter.reset();
    }

    /**
     * Updates the sensor delay based on the user preference. 0 = slow, 1 =
     * medium, 2 = fast.
     */
    private void updateSensorDelay() {
        if (frequencySelection.equals(PrefUtils.SENSOR_FREQUENCY_SLOW)) {
            setSensorDelay(0);
        }

        if (frequencySelection.equals(PrefUtils.SENSOR_FREQUENCY_MEDIUM)) {
            setSensorDelay(1);
        }

        if (frequencySelection.equals(PrefUtils.SENSOR_FREQUENCY_FAST)) {
            setSensorDelay(2);
        }
    }
}