de.uni_weimar.benike.misex3.MainActivity.java Source code

Java tutorial

Introduction

Here is the source code for de.uni_weimar.benike.misex3.MainActivity.java

Source

package de.uni_weimar.benike.misex3;

import android.app.Activity;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
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.os.Handler;
import android.os.Process;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.util.Log;
import android.view.View;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;

import com.androidplot.util.PlotStatistics;
import com.androidplot.xy.BoundaryMode;
import com.androidplot.xy.LineAndPointFormatter;
import com.androidplot.xy.SimpleXYSeries;
import com.androidplot.xy.XYPlot;
import com.androidplot.xy.XYStepMode;

import java.util.ArrayList;
import java.util.Arrays;

/* The following code snippets have been used as a reference:
http://androidplot.com/docs/a-dynamic-xy-plot/
https://developer.android.com/guide/topics/sensors/sensors_motion.html#sensors-motion-accel
icons from Google under CC-BY 4.0 (https://creativecommons.org/licenses/by/4.0/#):
https://design.google.com/icons/
 */

public class MainActivity extends Activity implements SeekBar.OnSeekBarChangeListener, SensorEventListener

{
    private static final String TAG = MainActivity.class.getName();

    private XYPlot mAccelerometerPlot = null;
    private XYPlot mFftPlot = null;

    private static int SAMPLE_MIN_VALUE = 1;
    private static int SAMPLE_MAX_VALUE = 250;
    private static int SAMPLE_STEP = 10;

    private static int WINDOW_MIN_VALUE = 5;
    private static int WINDOW_MAX_VALUE = 10;
    private static int WINDOW_STEP = 1;

    private int interval = SAMPLE_MIN_VALUE;

    private SimpleXYSeries mAccelerometerXSeries = null;
    private SimpleXYSeries mAccelerometerYSeries = null;
    private SimpleXYSeries mAccelerometerZSeries = null;
    private SimpleXYSeries mAccelerometerMSeries = null;

    private ArrayList<Double> mFftAverages = new ArrayList<>();
    private static final int LEN_AVERAGES = 128;

    private SimpleXYSeries mFftSeries = null;

    private int mWindowSize = 32; // number of points to plot in history
    private SensorManager mSensorManager = null;
    private Sensor mAccelerometerSensor = null;

    private SeekBar mSampleRateSeekBar;
    private SeekBar mFftWindowSeekBar;

    private FFT mFft;
    private Thread mFftThread;
    private Thread mDetectActivityThread;

    private static final int mNotificationId = 42;

    private NotificationManager mNotificationManager = null;
    private NotificationCompat.Builder mNotificationBuilder = null;

    private static final double ACTIVITY_THRESHOLD_WALKING = 14.5;
    private static final double ACTIVITY_THRESHOLD_RUNNING = 19.7;

    private TextView mWindowSizeTextView;

    public enum ActivityState {
        UNSURE, STANDING, WALKING, RUNNING
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // register for accelerometer events:
        mSensorManager = (SensorManager) getApplicationContext().getSystemService(Context.SENSOR_SERVICE);
        for (Sensor sensor : mSensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER)) {
            if (sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
                mAccelerometerSensor = sensor;
            }
        }

        // if we can't access the accelerometer sensor then exit:
        if (mAccelerometerSensor == null) {
            Log.e(TAG, "Failed to attach to Accelerator Sensor.");
            Toast.makeText(this, "Error! Failed to create accelerometer sensor!", Toast.LENGTH_LONG).show();
            cleanup();
        }

        mSensorManager.registerListener(this, mAccelerometerSensor, SensorManager.SENSOR_DELAY_UI);

        // setup the Accelerometer History plot:
        mAccelerometerPlot = (XYPlot) findViewById(R.id.accelerometerPlot);
        mFftPlot = (XYPlot) findViewById(R.id.fftPlot);

        mAccelerometerPlot.setRangeBoundaries(-25, 25, BoundaryMode.FIXED);
        mAccelerometerPlot.setDomainBoundaries(0, mWindowSize - 1, BoundaryMode.FIXED);

        mFftPlot.setRangeBoundaries(0, 250, BoundaryMode.FIXED);
        mFftPlot.setDomainBoundaries(0, mWindowSize / 2, BoundaryMode.AUTO);
        mFftPlot.setDomainStep(XYStepMode.SUBDIVIDE, 10);
        //mFftPlot.setRangeStep(XYStepMode.INCREMENT_BY_VAL, 50);

        mAccelerometerXSeries = new SimpleXYSeries("X");
        mAccelerometerXSeries.useImplicitXVals();
        mAccelerometerYSeries = new SimpleXYSeries("Y");
        mAccelerometerYSeries.useImplicitXVals();
        mAccelerometerZSeries = new SimpleXYSeries("Z");
        mAccelerometerZSeries.useImplicitXVals();
        mAccelerometerMSeries = new SimpleXYSeries("magnitude");
        mAccelerometerMSeries.useImplicitXVals();

        mFftSeries = new SimpleXYSeries("FFT");
        mFftSeries.useImplicitXVals();

        mFftPlot.addSeries(mFftSeries, new LineAndPointFormatter(Color.rgb(0, 0, 0), null, null, null));

        mAccelerometerPlot.addSeries(mAccelerometerXSeries,
                new LineAndPointFormatter(Color.rgb(100, 100, 200), null, null, null));
        mAccelerometerPlot.addSeries(mAccelerometerYSeries,
                new LineAndPointFormatter(Color.rgb(100, 200, 100), null, null, null));
        mAccelerometerPlot.addSeries(mAccelerometerZSeries,
                new LineAndPointFormatter(Color.rgb(200, 100, 100), null, null, null));
        mAccelerometerPlot.addSeries(mAccelerometerMSeries,
                new LineAndPointFormatter(Color.rgb(0, 0, 0), null, null, null));
        mAccelerometerPlot.setDomainStepValue(5);
        mAccelerometerPlot.setTicksPerRangeLabel(3);
        mAccelerometerPlot.setDomainLabel("Sample Index");
        mAccelerometerPlot.getDomainLabelWidget().pack();
        mAccelerometerPlot.setRangeLabel("m/s^2");
        mAccelerometerPlot.getRangeLabelWidget().pack();

        final PlotStatistics histStats = new PlotStatistics(1000, false);
        mAccelerometerPlot.addListener(histStats);

        // perform hardware accelerated rendering of the plots
        mAccelerometerPlot.setLayerType(View.LAYER_TYPE_NONE, null);
        mFftPlot.setLayerType(View.LAYER_TYPE_NONE, null);

        mFftPlot.setTicksPerRangeLabel(5);
        mFftPlot.setTicksPerDomainLabel(1);

        mSampleRateSeekBar = (SeekBar) findViewById(R.id.sampleRateSeekBar);
        mSampleRateSeekBar.setMax((SAMPLE_MAX_VALUE - SAMPLE_MIN_VALUE) / SAMPLE_STEP);
        mSampleRateSeekBar.setOnSeekBarChangeListener(this);

        mFftWindowSeekBar = (SeekBar) findViewById(R.id.fftWindowSeekBar);
        mFftWindowSeekBar.setMax((WINDOW_MAX_VALUE - WINDOW_MIN_VALUE) / WINDOW_STEP);
        mFftWindowSeekBar.setOnSeekBarChangeListener(this);

        mWindowSizeTextView = (TextView) findViewById(R.id.windowSizeTextView);

        // Perform FFT calculations in background thread
        mFft = new FFT(mWindowSize);
        Runnable r = new PerformFft();
        mFftThread = new Thread(r);
        mFftThread.start();

        mNotificationBuilder = new NotificationCompat.Builder(this)
                .setSmallIcon(R.drawable.ic_help_outline_black_24dp).setContentTitle("MIS Ex3 Activity Recognizer")
                .setContentText("Trying to guess your activity").setOngoing(true);

        Intent resultIntent = new Intent(this, MainActivity.class);

        TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
        stackBuilder.addParentStack(MainActivity.class);
        stackBuilder.addNextIntent(resultIntent);
        PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
        mNotificationBuilder.setContentIntent(resultPendingIntent);

        mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        mNotificationManager.notify(mNotificationId, mNotificationBuilder.build());

        Runnable detectActivity = new DetectActivity();
        mDetectActivityThread = new Thread(detectActivity);
        mDetectActivityThread.start();
    }

    private void resetSeries() {

        while (mAccelerometerMSeries.size() > mWindowSize) {
            mAccelerometerXSeries.removeFirst();
            mAccelerometerYSeries.removeFirst();
            mAccelerometerZSeries.removeFirst();
            mAccelerometerMSeries.removeFirst();
        }

        mAccelerometerPlot.redraw();
    }

    @Override
    public void onResume() {
        super.onResume();
        mSensorManager.registerListener(this, mAccelerometerSensor, SensorManager.SENSOR_DELAY_NORMAL);
    }

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

    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        if (seekBar == mSampleRateSeekBar) {
            Log.d(TAG, "Progress: " + progress);
            progress = seekBar.getMax() - progress;
            int value = SAMPLE_MIN_VALUE + progress * SAMPLE_STEP;
            Log.d(TAG, "Samplesize SeekBar value: " + value);
            ChangeSampleRate(value * 2000);
        } else if (seekBar == mFftWindowSeekBar) {
            int value = (int) Math.pow(2, WINDOW_MIN_VALUE + progress * WINDOW_STEP);
            Log.d(TAG, "Windowsize SeekBar value: " + value);

            mWindowSize = value;
            mAccelerometerPlot.setDomainBoundaries(0, mWindowSize - 1, BoundaryMode.FIXED);

            mFft = new FFT(mWindowSize);
            resetSeries();
        }
    }

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {
    }

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {
    }

    public void ChangeSampleRate(int us) {
        Log.d(TAG, "Samplerate value: " + us);
        mSensorManager.unregisterListener(this);
        mSensorManager.registerListener(this, mAccelerometerSensor, us);
    }

    private void cleanup() {
        // unregister with the orientation sensor before exiting:
        mSensorManager.unregisterListener(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        cleanup();
        mNotificationManager.cancel(mNotificationId);
    }

    // Called whenever a new accelSensor reading is taken.
    @Override
    public synchronized void onSensorChanged(SensorEvent sensorEvent) {

        // update instantaneous data:
        Number[] series1Numbers = { sensorEvent.values[0], sensorEvent.values[1], sensorEvent.values[2] };

        // get rid the oldest sample in history:
        if (mAccelerometerXSeries.size() > mWindowSize - 1) {
            mAccelerometerXSeries.removeFirst();
            mAccelerometerYSeries.removeFirst();
            mAccelerometerZSeries.removeFirst();
            mAccelerometerMSeries.removeFirst();
        }

        // add the latest history sample:
        final float accelXdata = sensorEvent.values[0];
        final float accelYdata = sensorEvent.values[1];
        final float accelZdata = sensorEvent.values[2];
        mAccelerometerXSeries.addLast(null, accelXdata);
        mAccelerometerYSeries.addLast(null, accelYdata);
        mAccelerometerZSeries.addLast(null, accelZdata);
        mAccelerometerMSeries.addLast(null,
                Math.sqrt(accelXdata * accelXdata + accelYdata * accelYdata + accelZdata * accelZdata) /* - 9.81 */
        );

        //Log.d(TAG, "Sample added. Size of m series: " + mAccelerometerMSeries.size());

        // redraw the Plots
        mAccelerometerPlot.redraw();
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int i) {
        // Not interested in this event
    }

    private void updateNotification(ActivityState activityState) {

        if (activityState == ActivityState.STANDING) {
            mNotificationBuilder.setSmallIcon(R.drawable.ic_accessibility_black_24dp)
                    .setContentText("You are standing/sitting");
        } else if (activityState == ActivityState.WALKING) {
            mNotificationBuilder.setSmallIcon(R.drawable.ic_directions_walk_black_24dp)
                    .setContentText("You are walking");
        } else if (activityState == ActivityState.RUNNING) {
            mNotificationBuilder.setSmallIcon(R.drawable.ic_accessibility_black_24dp)
                    .setContentText("You are running");
        } else { // unsure
            mNotificationBuilder.setSmallIcon(R.drawable.ic_help_outline_black_24dp)
                    .setContentText("Could not determine your activity...");
        }

        mNotificationManager.notify(mNotificationId, mNotificationBuilder.build());
    }

    private class PerformFft implements Runnable {

        private Handler mFftHandler = new Handler();

        @Override
        public void run() {
            android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
            if (mAccelerometerMSeries.size() == mWindowSize) {
                double re[] = new double[mWindowSize];
                double im[] = new double[mWindowSize];
                for (int i = 0; i < mAccelerometerMSeries.size(); ++i) {
                    re[i] = (double) mAccelerometerMSeries.getY(i);
                    im[i] = 0.0;
                }
                mFft.fft(re, im);

                final Number magnitude[] = new Number[mWindowSize / 2];
                for (int i = 0; i < mWindowSize / 2; ++i) {
                    magnitude[i] = Math.sqrt(re[i] * re[i] + im[i] * im[i]);
                    //magnitude[i] = //re[i] * re[i] + im[i] * im[i];
                }

                // Plot our magnitude on
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        mFftSeries.setModel(Arrays.asList(magnitude), SimpleXYSeries.ArrayFormat.Y_VALS_ONLY);
                        mFftPlot.redraw();
                    }
                });

                Double sum = 0.0;
                for (Number n : magnitude)
                    sum += (Double) n;
                Double average = sum / magnitude.length;

                if (mFftAverages.size() > LEN_AVERAGES - 1) {
                    mFftAverages.remove(0);
                }
                mFftAverages.add(average);
            }
            mFftHandler.post(this);
        }
    }

    private class DetectActivity implements Runnable {

        private Handler mActivityHandler = new Handler();

        @Override
        public void run() {
            android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            if (mFftAverages.size() == LEN_AVERAGES) {
                // calculate complete average
                Double sum = 0.0;
                for (Double d : mFftAverages)
                    sum += d;
                Double average = sum / LEN_AVERAGES;
                // Log.i(TAG, "Activity fft freq average: " + average);
                // we used adb wifi debugging to watch the logcat to determine thresholds for
                // different walking speeds (a bit problematic is the shift with different samplerates)
                if (average < ACTIVITY_THRESHOLD_WALKING) {
                    Log.d(TAG, "Activity detected: sitting/standing");
                    updateNotification(ActivityState.STANDING);
                } else if (average > ACTIVITY_THRESHOLD_WALKING && average < ACTIVITY_THRESHOLD_RUNNING) {
                    Log.d(TAG, "Activity detected: walking");
                    updateNotification(ActivityState.WALKING);
                } else if (average > ACTIVITY_THRESHOLD_RUNNING) {
                    Log.d(TAG, "Activity detected: running");
                    updateNotification(ActivityState.RUNNING);
                } else {
                    updateNotification(ActivityState.UNSURE);
                }
                mFftAverages.clear();
            }
            mActivityHandler.postDelayed(this, 5000);
        }
    }
}