radu.pidroid.Controller.java Source code

Java tutorial

Introduction

Here is the source code for radu.pidroid.Controller.java

Source

/*
  Controller.java
    
  Copyright (C) 2015 Radu Traian Jipa
  License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2
*/

package radu.pidroid;

import android.app.ActionBar;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
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.os.AsyncTask;
import android.os.Bundle;
import android.speech.RecognizerIntent;
import android.support.v4.widget.DrawerLayout;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ExpandableListView;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.Toast;

import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;

import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import radu.pidroid.Tutorials.ControllerTutorial;
import radu.pidroid.Connector.Messenger;
import radu.pidroid.Joystick.JoystickView;
import radu.pidroid.MjpegViewer.MjpegInputStream;
import radu.pidroid.MjpegViewer.MjpegView;
import radu.pidroid.Recogniser.CommandRecogniser;
import radu.pidroid.SettingsDrawer.DrawerAdapter;
import radu.pidroid.SettingsDrawer.DrawerHeader;
import radu.pidroid.SettingsDrawer.DrawerItem;
import radu.pidroid.SettingsDrawer.DrawerRow;
import radu.pidroid.SettingsDrawer.DrawerSettingsRow;

public class Controller extends Activity implements SensorEventListener, View.OnTouchListener,
        SeekBar.OnSeekBarChangeListener, ExpandableListView.OnGroupClickListener, JoystickView.MoveListener {

    //
    public final static String EXTRA_CONTROLS_ID = "radu.pidroid.CONTROLSID";
    public final static String EXTRA_MAX_TILT_ANGLE = "radu.pidroid.MAXTILTANGLE";
    public final static String EXTRA_ACCELERATION_TIME = "radu.pidroid.ACCELERATIONTIME";
    public final static String EXTRA_DECELERATION_TIME = "radu.pidroid.DECELERATIONTIME";
    public final static String EXTRA_TOUCH_RESPONSIVENESS = "radu.pidroid.TOUCHRESPONSIVNESS";
    public final static String EXTRA_TURN_SENSITIVITY = "radu.pidroid.TURNSENSITIVITY";
    public final static String EXTRA_HUD = "radu.pidroid.HUD";
    public final static String EXTRA_LEVEL_INDICATOR = "radu.pidroid.LEVELINDICATOR";
    public final static String EXTRA_CAMERA_STABILISATION_ON = "radu.pidroid.CAMERASTABILISATIONON";
    public final static String EXTRA_TUTORIALS_ON = "radu.pidroid.TUTORIALSON";

    //
    public final static int SPEECH_RECOGNITION_REQUEST_CODE = 513365;

    // acceleration states & direction states
    public final static int ROVER_ACCELERATING = 10;
    public final static int ROVER_DECELERATING = 11;
    public final static int ROVER_STOPPED = 12;
    public final static int ROVER_FORWARDS = 20;
    public final static int ROVER_BACKWARDS = 21;

    //
    public final static int TOUCH_CONTROLS = 30;
    public final static int SLIDER_CONTROLS = 31;
    public final static int JOYSTICK_CONTROLS = 32;

    //
    public static int controlsID;
    public static int maxTiltAngle;
    public static int accelerationTime;
    public static int decelerationTime;
    public static int touchResponsiveness;

    public static int turnAngle = 0;
    public static int turnSensitivity;

    //
    private String serverIP;
    private int serverPort;

    //
    public ImageView hudImageView, levelIndicatorImageView;

    int currentHudResource;
    final int[] hudResources = new int[] { R.drawable.hud_clean_1366x768, R.drawable.hud_lines_1366x768 };

    public ImageView speechButton;

    public MjpegView videoFeedMjpegView;
    boolean videoFeedOn, cameraStabilisationOn;
    VideoFeedTask videoFeedTask = null;

    RelativeLayout touchControlsLayout;
    public ImageView forwardsPowerImageView, backwardsPowerImageView;
    public ProgressBar forwardsPowerProgressBar, backwardsPowerProgressBar;

    RelativeLayout sliderControlsLayout;
    public SeekBar forwardsPowerSeekBar, backwardsPowerSeekBar;

    RelativeLayout joystickControlsLayout;
    public JoystickView largeCameraJoystickView, directionJoystickView;

    JoystickView smallCameraJoystickView;
    public ImageView toggleSpinImageView;

    private SensorManager mSensorManager;
    private Sensor mAccelerometer, mGeomagnetic;
    private float[] mAccelerometerData = new float[3];
    private float[] mGeomagneticData = new float[3];

    private int lastTurnAngle = 0;
    private boolean tiltControlsOn = true;
    private boolean spinControlOn = false;

    Timer timer = new Timer();
    private final int timerPeriod = 50;

    private int roverSpeed, roverLastSpeed = 0;
    private int roverDirection = ROVER_FORWARDS;
    private int roverAccelerationState = ROVER_STOPPED;

    private Messenger mMessenger;
    private CommandRecogniser mRecogniser;

    private boolean tutorialsOn;

    //**********************************************************************************************
    //                                  ACTIVITY METHODS
    //**********************************************************************************************

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.controller);

        ActionBar actionBar = getActionBar();
        actionBar.hide();

        speechButton = (ImageView) findViewById(R.id.speechButton);
        speechButton.setOnClickListener(new ImageView.OnClickListener() {
            public void onClick(View view) {
                startVoiceRecognition();
            } // onClick
        });

        touchControlsLayout = (RelativeLayout) findViewById(R.id.TouchControlsLayout);
        forwardsPowerImageView = (ImageView) findViewById(R.id.forwardsPowerImageView);
        forwardsPowerImageView.setOnTouchListener(this);
        backwardsPowerImageView = (ImageView) findViewById(R.id.backwardsPowerImageView);
        backwardsPowerImageView.setOnTouchListener(this);

        forwardsPowerProgressBar = (ProgressBar) findViewById(R.id.forwardsPowerProgressBar);
        backwardsPowerProgressBar = (ProgressBar) findViewById(R.id.backwardsPowerProgressBar);

        sliderControlsLayout = (RelativeLayout) findViewById(R.id.SliderControlsLayout);
        forwardsPowerSeekBar = (SeekBar) findViewById(R.id.forwardsPowerSeekBar);
        forwardsPowerSeekBar.setOnSeekBarChangeListener(this);
        backwardsPowerSeekBar = (SeekBar) findViewById(R.id.backwardsPowerSeekBar);
        backwardsPowerSeekBar.setOnSeekBarChangeListener(this);

        joystickControlsLayout = (RelativeLayout) findViewById(R.id.JoystickControlsLayout);
        largeCameraJoystickView = (JoystickView) findViewById(R.id.largeCameraJoystickView);
        largeCameraJoystickView.setMoveListener(this);
        directionJoystickView = (JoystickView) findViewById(R.id.directionJoystickView);
        directionJoystickView.setMoveListener(this);

        videoFeedMjpegView = (MjpegView) findViewById(R.id.videoFeedSurfaceView);
        hudImageView = (ImageView) findViewById(R.id.hudImageView);
        levelIndicatorImageView = (ImageView) findViewById(R.id.levelIndicatorImageView);

        mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
        mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        mGeomagnetic = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);

        smallCameraJoystickView = (JoystickView) findViewById(R.id.cameraJoystickView);
        smallCameraJoystickView.setMoveListener(this);
        smallCameraJoystickView.setTag("smallCamera");

        spinControlOn = false;
        toggleSpinImageView = (ImageView) findViewById(R.id.toggleSpinImageView);
        toggleSpinImageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                // if spinControl is ON then tiltControls are OFF and vice versa
                spinControlOn = !spinControlOn;
                tiltControlsOn = !tiltControlsOn;

                if (spinControlOn)
                    toggleSpinImageView.setImageResource(R.drawable.spin_button_down);
                else
                    toggleSpinImageView.setImageResource(R.drawable.spin_button_up);

                // Let PiDroid know whether to spin
                mMessenger.toggleSpin(spinControlOn);
            } // onClick
        });

        // Get the message from the intent
        Intent intent = getIntent();
        serverIP = intent.getStringExtra(Main.EXTRA_SERVERIP);
        serverPort = intent.getIntExtra(Main.EXTRA_SERVERPORT, 8090);

        mMessenger = new Messenger(this, serverIP, serverPort);
        mRecogniser = new CommandRecogniser(this, mMessenger, videoFeedMjpegView);

        if (Main.detailsCheckBox.isChecked())
            loadPreferences();
        else
            clearPreferences();

        setupNavigationDrawer();
        setupVideoFeed();

        if (tutorialsOn)
            new ControllerTutorial(this).start();
    } // onCreate

    @Override
    protected void onResume() {
        super.onResume();
        mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_FASTEST);
        mSensorManager.registerListener(this, mGeomagnetic, SensorManager.SENSOR_DELAY_FASTEST);

        //
        if (Main.detailsCheckBox.isChecked())
            loadPreferences();
        else
            clearPreferences();

        //setupVideoFeed();
    } // onResume

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

        //
        if (Main.detailsCheckBox.isChecked())
            savePreferences();

        videoFeedMjpegView.stopPlayback();
    } // onPause

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.controller, menu);
        return true;
    } // onCreateOptionsMenu

    @Override
    public void onBackPressed() {
        new AlertDialog.Builder(this, AlertDialog.THEME_HOLO_DARK).setTitle("Disconnect")
                .setMessage("Are you sure you want to disconnect?").setIcon(android.R.drawable.ic_dialog_alert)
                .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialogInterface, int i) {
                        mMessenger.updateRoverSpeed(roverSpeed = 0);
                        mMessenger.stopMessenger();
                        finish();
                    } // onClick
                }) // .setPositiveButton
                .setNegativeButton(android.R.string.cancel, null).create().show();

        videoFeedMjpegView.stopPlayback();
    } // onBackPressed

    private void loadPreferences() {
        //
        SharedPreferences preferences = this.getPreferences(Context.MODE_PRIVATE);

        controlsID = preferences.getInt(EXTRA_CONTROLS_ID, TOUCH_CONTROLS);
        maxTiltAngle = preferences.getInt(EXTRA_MAX_TILT_ANGLE, 45);
        accelerationTime = preferences.getInt(EXTRA_ACCELERATION_TIME, 1500);
        decelerationTime = preferences.getInt(EXTRA_DECELERATION_TIME, 1000);
        touchResponsiveness = preferences.getInt(EXTRA_TOUCH_RESPONSIVENESS, 200);
        turnSensitivity = preferences.getInt(EXTRA_TURN_SENSITIVITY, 1);
        tutorialsOn = preferences.getBoolean(EXTRA_TUTORIALS_ON, true);

        cameraStabilisationOn = preferences.getBoolean(EXTRA_CAMERA_STABILISATION_ON, true);
        videoFeedMjpegView.setCameraStabilisation(cameraStabilisationOn);

        currentHudResource = preferences.getInt(EXTRA_HUD, 0);
        levelIndicatorImageView.setVisibility(preferences.getInt(EXTRA_LEVEL_INDICATOR, View.VISIBLE));

        hudImageView.setImageResource(hudResources[currentHudResource]);
        setControls(controlsID);
    } // loadPreferences

    private void savePreferences() {
        //
        SharedPreferences preferences = this.getPreferences(Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = preferences.edit();

        editor.putInt(EXTRA_CONTROLS_ID, controlsID);
        editor.putInt(EXTRA_MAX_TILT_ANGLE, maxTiltAngle);
        editor.putInt(EXTRA_ACCELERATION_TIME, accelerationTime);
        editor.putInt(EXTRA_DECELERATION_TIME, decelerationTime);
        editor.putInt(EXTRA_TOUCH_RESPONSIVENESS, touchResponsiveness);
        editor.putInt(EXTRA_TURN_SENSITIVITY, turnSensitivity);
        editor.putInt(EXTRA_HUD, currentHudResource);
        editor.putInt(EXTRA_LEVEL_INDICATOR, levelIndicatorImageView.getVisibility());
        editor.putBoolean(EXTRA_CAMERA_STABILISATION_ON, cameraStabilisationOn);
        editor.putBoolean(EXTRA_TUTORIALS_ON, tutorialsOn);

        editor.apply();
    } // savePreferences

    private void clearPreferences() {
        //
        SharedPreferences preferences = this.getPreferences(Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = preferences.edit();

        editor.clear();
        editor.apply();
    } // clearPreferences

    //**********************************************************************************************
    //                                    NAVIGATION DRAWER
    //**********************************************************************************************

    public DrawerLayout mDrawerLayout;
    public DrawerAdapter mDrawerAdapter;
    public ExpandableListView mDrawerListView;

    private String[] mDrawerItemsList;
    private List<DrawerItem> parentData;
    private HashMap<DrawerItem, List<DrawerItem>> childData;

    private void setupNavigationDrawer() {
        mDrawerLayout = (DrawerLayout) findViewById(R.id.DrawerLayout);
        mDrawerListView = (ExpandableListView) findViewById(R.id.LeftDrawer);
        mDrawerListView.setOnGroupClickListener(this);

        mDrawerItemsList = getResources().getStringArray(R.array.drawer_items_list);

        createDrawerRows();

        mDrawerAdapter = new DrawerAdapter(mDrawerListView, parentData, childData);
        mDrawerListView.setAdapter(mDrawerAdapter);
    } // setupNavigationDrawer

    private void createDrawerRows() {
        /*
         NOTE:  1. These constants must be in accordance with StringArray
                "drawer_items_list" in res/values/strings.xml
            2. Used only to help automate the creation process of drawer items
        */
        final int DRAWER_HEADER = 0, DRAWER_ROW = 1, DRAWER_SETTINGS_ROW = 2;

        //
        parentData = new ArrayList<DrawerItem>();
        childData = new HashMap<DrawerItem, List<DrawerItem>>();

        LayoutInflater inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        //
        DrawerItem parent = null, child = null;
        List<DrawerItem> children;

        //
        for (int index = 0; index < mDrawerItemsList.length; index++) {

            //
            String[] parts = mDrawerItemsList[index].split(",");
            children = new ArrayList<DrawerItem>();

            //
            switch (Integer.parseInt(parts[0])) {

            case DRAWER_HEADER:
                parent = new DrawerHeader(this, inflater.inflate(R.layout.drawer_header, null));
                ((DrawerHeader) parent).setHeaderText(parts[1]);
                parentData.add(parent);
                break;

            case DRAWER_ROW:
                parent = new DrawerRow(this, inflater.inflate(R.layout.drawer_row, null));
                ((DrawerRow) parent).setRowText(parts[1]);
                ((DrawerRow) parent).setIconResource(android.R.drawable.ic_menu_always_landscape_portrait);
                parentData.add(parent);
                break;

            case DRAWER_SETTINGS_ROW:
                parent = new DrawerRow(this, inflater.inflate(R.layout.drawer_row, null));
                ((DrawerRow) parent).setRowText(parts[1]);
                ((DrawerRow) parent).setIconResource(android.R.drawable.ic_menu_always_landscape_portrait);
                parentData.add(parent);

                child = new DrawerSettingsRow(this, inflater.inflate(R.layout.drawer_settings_row, null));
                children.add(child);
                break;

            default:
                // invalid drawer item type
                Log.e("createDrawerRows(): ", "Check strings.xml");
            } // switch

            //
            childData.put(parent, children);
        } // for

        /*  The next part defines the behaviour of the drawer items.
            
        Because of the different types of rows, hard-coding these is the easiest
         approach as opposed to further complicating things to simply create them.
         */
        // TOUCH CONTROLS
        ((DrawerRow) parentData.get(1)).setRowFunction(new DrawerRow.ToggleSettings() {
            @Override
            public void toggle() {
                setControls(TOUCH_CONTROLS);
                mDrawerLayout.closeDrawer(mDrawerListView);
                Toast.makeText(parentData.get(1).getUIContext(), "Touch controls have been set", Toast.LENGTH_SHORT)
                        .show();
            } // toggle
        });

        // SLIDER CONTROLS
        ((DrawerRow) parentData.get(2)).setRowFunction(new DrawerRow.ToggleSettings() {
            @Override
            public void toggle() {
                setControls(SLIDER_CONTROLS);
                mDrawerLayout.closeDrawer(mDrawerListView);
                Toast.makeText(parentData.get(2).getUIContext(), "Slider controls have been set",
                        Toast.LENGTH_SHORT).show();
            } // toggle
        });

        // JOYSTICK CONTROLS
        ((DrawerRow) parentData.get(3)).setRowFunction(new DrawerRow.ToggleSettings() {
            @Override
            public void toggle() {
                setControls(JOYSTICK_CONTROLS);
                mDrawerLayout.closeDrawer(mDrawerListView);
                Toast.makeText(parentData.get(3).getUIContext(), "Joystick controls have been set",
                        Toast.LENGTH_SHORT).show();
            } // toggle
        });

        // MAXIMUM TILT ANGLE
        ((DrawerSettingsRow) childData.get(parentData.get(5)).get(0))
                .setSettingsFunction(new DrawerSettingsRow.SliderSettings() {
                    @Override
                    public String calculate(int progress) {
                        maxTiltAngle = (int) (progress * 0.8 + 10) / 10 * 10;
                        return Controller.maxTiltAngle + "";
                    } // calculate
                });
        ((DrawerSettingsRow) childData.get(parentData.get(5)).get(0))
                .initialise((int) ((maxTiltAngle / 10 * 10 - 10) / 0.8));

        // ACCELERATION TIME
        ((DrawerSettingsRow) childData.get(parentData.get(6)).get(0))
                .setSettingsFunction(new DrawerSettingsRow.SliderSettings() {
                    @Override
                    public String calculate(int progress) {
                        accelerationTime = progress / 10 * 250;
                        return accelerationTime + "ms";
                    } // calculate
                });
        ((DrawerSettingsRow) childData.get(parentData.get(6)).get(0)).initialise(accelerationTime / 250 * 10);

        // DECELERATION TIME
        ((DrawerSettingsRow) childData.get(parentData.get(7)).get(0))
                .setSettingsFunction(new DrawerSettingsRow.SliderSettings() {
                    @Override
                    public String calculate(int progress) {
                        decelerationTime = progress / 10 * 250;
                        return decelerationTime + "ms";
                    } // calculate
                });
        ((DrawerSettingsRow) childData.get(parentData.get(7)).get(0)).initialise(decelerationTime / 250 * 10);

        // TOUCH RESPONSIVENESS
        ((DrawerSettingsRow) childData.get(parentData.get(8)).get(0))
                .setSettingsFunction(new DrawerSettingsRow.SliderSettings() {
                    @Override
                    public String calculate(int progress) {
                        touchResponsiveness = progress / 10 * 50;
                        return touchResponsiveness + "ms";
                    } // calculate
                });
        ((DrawerSettingsRow) childData.get(parentData.get(8)).get(0)).initialise(touchResponsiveness / 50 * 10);

        // TURN SENSITIVITY
        ((DrawerSettingsRow) childData.get(parentData.get(10)).get(0))
                .setSettingsFunction(new DrawerSettingsRow.SliderSettings() {
                    @Override
                    public String calculate(int progress) {
                        turnSensitivity = progress / 25 + 1;
                        mMessenger.updateTurnSensitivity(turnSensitivity);
                        return turnSensitivity + "";
                    } // calculate
                });
        ((DrawerSettingsRow) childData.get(parentData.get(10)).get(0)).initialise((turnSensitivity - 1) * 25);

        // LEARN NEW OBJECT
        ((DrawerRow) parentData.get(11)).setRowFunction(new DrawerRow.ToggleSettings() {
            @Override
            public void toggle() {
                mDrawerLayout.closeDrawer(mDrawerListView);

                // Before we fire of the learning process, ask the user what this new object is
                new AlertDialog.Builder(parentData.get(11).getUIContext(), AlertDialog.THEME_HOLO_DARK)
                        .setTitle("What is this new object?").setIcon(android.R.drawable.ic_dialog_info)
                        .setItems(R.array.object_names, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialogInterface, int index) {
                                new LearningTask(parentData.get(11).getUIContext(), LearningTask.PIDROID_LEARN,
                                        index).execute();
                            } // onClick
                        }).setCancelable(false).create().show();
            } // toggle
        });

        // RECOGNISE OBJECT
        ((DrawerRow) parentData.get(12)).setRowFunction(new DrawerRow.ToggleSettings() {
            @Override
            public void toggle() {
                mDrawerLayout.closeDrawer(mDrawerListView);
                new LearningTask(parentData.get(12).getUIContext(), LearningTask.PIDROID_RECOGNISE).execute();
            } // toggle
        });

        // CLEAR LEARNING DATA
        ((DrawerRow) parentData.get(13)).setRowFunction(new DrawerRow.ToggleSettings() {
            @Override
            public void toggle() {
                new AlertDialog.Builder(parentData.get(13).getUIContext(), AlertDialog.THEME_HOLO_DARK)
                        .setTitle("Clear Data").setMessage("Are you sure you want to delete all learned data?")
                        .setIcon(android.R.drawable.ic_dialog_alert)
                        .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialogInterface, int i) {
                                mMessenger.clearLearningData();
                            } // onClick
                        }) // .setPositiveButton
                        .setNegativeButton(android.R.string.cancel, null).create().show();
            } // toggle
        });

        // CAMERA STABILISATION
        ((DrawerRow) parentData.get(15)).setRowFunction(new DrawerRow.ToggleSettings() {
            @Override
            public void toggle() {
                mDrawerLayout.closeDrawer(mDrawerListView);
                cameraStabilisationOn = !cameraStabilisationOn;
                videoFeedMjpegView.setCameraStabilisation(cameraStabilisationOn);

                if (cameraStabilisationOn)
                    Toast.makeText(parentData.get(15).getUIContext(), "Camera stabilisation is now ON",
                            Toast.LENGTH_LONG).show();
                else
                    Toast.makeText(parentData.get(15).getUIContext(), "Camera stabilisation is now OFF",
                            Toast.LENGTH_LONG).show();
            } // toggle
        });

        // CHANGE HUD
        ((DrawerRow) parentData.get(16)).setRowFunction(new DrawerRow.ToggleSettings() {
            @Override
            public void toggle() {
                if (++currentHudResource == hudResources.length)
                    currentHudResource = 0;
                hudImageView.setImageResource(hudResources[currentHudResource]);
            } // toggle
        });

        // LEVEL INDICATOR
        ((DrawerRow) parentData.get(17)).setRowFunction(new DrawerRow.ToggleSettings() {
            @Override
            public void toggle() {
                if (levelIndicatorImageView.getVisibility() == View.VISIBLE)
                    levelIndicatorImageView.setVisibility(View.INVISIBLE);
                else
                    levelIndicatorImageView.setVisibility(View.VISIBLE);
            } // toggle
        });

        // TUTORIALS
        ((DrawerRow) parentData.get(18)).setRowFunction(new DrawerRow.ToggleSettings() {
            @Override
            public void toggle() {
                tutorialsOn = !tutorialsOn;
                mDrawerLayout.closeDrawer(mDrawerListView);

                if (tutorialsOn)
                    Toast.makeText(parentData.get(18).getUIContext(), "Tutorials are now ON", Toast.LENGTH_LONG)
                            .show();
                else
                    Toast.makeText(parentData.get(18).getUIContext(), "Tutorials are now OFF", Toast.LENGTH_LONG)
                            .show();
            } // toggle
        });
    } // createDrawerRows

    public void setControls(int id) {
        switch (id) {

        case TOUCH_CONTROLS:
            touchControlsLayout.setVisibility(View.VISIBLE);
            smallCameraJoystickView.setVisibility(View.VISIBLE);
            sliderControlsLayout.setVisibility(View.INVISIBLE);
            joystickControlsLayout.setVisibility(View.INVISIBLE);
            tiltControlsOn = true;
            videoFeedMjpegView.setCameraStabilisation(cameraStabilisationOn = true);
            break;

        case SLIDER_CONTROLS:
            sliderControlsLayout.setVisibility(View.VISIBLE);
            smallCameraJoystickView.setVisibility(View.VISIBLE);
            touchControlsLayout.setVisibility(View.INVISIBLE);
            joystickControlsLayout.setVisibility(View.INVISIBLE);
            tiltControlsOn = true;
            videoFeedMjpegView.setCameraStabilisation(cameraStabilisationOn = true);
            break;

        case JOYSTICK_CONTROLS:
            joystickControlsLayout.setVisibility(View.VISIBLE);
            touchControlsLayout.setVisibility(View.INVISIBLE);
            sliderControlsLayout.setVisibility(View.INVISIBLE);
            smallCameraJoystickView.setVisibility(View.INVISIBLE);
            tiltControlsOn = false;
            videoFeedMjpegView.setCameraStabilisation(cameraStabilisationOn = false);
            break;

        default:
            Log.e("setControls(): ", "default triggered with controlsID = " + controlsID);
            return;
        } // switch

        controlsID = id;
    } // setControls

    public boolean onGroupClick(ExpandableListView parent, View view, int groupPosition, long id) {
        DrawerItem item = ((DrawerItem) mDrawerAdapter.getGroup(groupPosition));

        if (item instanceof DrawerRow)
            ((DrawerRow) item).executeRowFunction();

        return false;
    } // onGroupClick

    //**********************************************************************************************
    //                                    TILT CONTROLS
    //**********************************************************************************************

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

    @Override
    public void onSensorChanged(SensorEvent event) {
        // we received a sensor event. it is a good practice to check
        // that we received the proper event
        switch (event.sensor.getType()) {

        case Sensor.TYPE_ACCELEROMETER:
            mAccelerometerData = event.values;
            break;

        case Sensor.TYPE_MAGNETIC_FIELD:
            mGeomagneticData = event.values;

            if (tiltControlsOn) {
                computeTiltAngle();
                tiltControls();
            } // if
            break;

        default:
            Log.e("onSensorChanged", "wut?!");
            break;
        } // switch
    } // onSensorChanged

    private void computeTiltAngle() {
        float[] mRotationMatrix = new float[9];
        float[] mOrientationMatrix = new float[3];

        //
        if (!SensorManager.getRotationMatrix(mRotationMatrix, null, mAccelerometerData, mGeomagneticData))
            return;
        else
            SensorManager.getOrientation(mRotationMatrix, mOrientationMatrix);

        //
        turnAngle = (int) Math.round(Math.toDegrees((double) mOrientationMatrix[1]) + 90);
    } // computeTiltAngle

    private void tiltControls() {
        //
        if (levelIndicatorImageView.getVisibility() == View.VISIBLE && turnAngle < 90 + maxTiltAngle
                && turnAngle > 90 - maxTiltAngle) {
            levelIndicatorImageView.setRotation(turnAngle - 90);
            videoFeedMjpegView.setTiltAngle(turnAngle - 90);
        } // if

        //
        if (turnAngle > 90 + maxTiltAngle)
            turnAngle = 90 + maxTiltAngle;
        if (turnAngle < 90 - maxTiltAngle)
            turnAngle = 90 - maxTiltAngle;

        //
        turnAngle = (int) Math.round((turnAngle - (90 - maxTiltAngle)) * 90.0 / maxTiltAngle);

        //
        turnAngle = turnAngle > 90 ? turnAngle / 10 * 10 : (turnAngle + 10) / 10 * 10;
        turnAngle = turnAngle % 20 == 0 ? turnAngle - 10 : turnAngle;

        //
        if (turnAngle != lastTurnAngle) {
            lastTurnAngle = turnAngle;
            mMessenger.updateTurnAngle(turnAngle);
            Log.d("tiltControls():", "tilt angle = " + turnAngle);
        } // if
    } // tiltControls

    //**********************************************************************************************
    //                                TOUCH SCREEN CONTROLS
    //**********************************************************************************************

    private boolean isTouchOnView(View view, MotionEvent motionEvent) {
        //
        int viewPosition[] = new int[4];
        view.getLocationOnScreen(viewPosition);

        viewPosition[2] = viewPosition[0] + view.getWidth();
        viewPosition[3] = viewPosition[1] + view.getHeight();

        //
        return motionEvent.getRawX() >= viewPosition[0] && motionEvent.getRawX() <= viewPosition[2]
                && motionEvent.getRawY() >= viewPosition[1] && motionEvent.getRawY() <= viewPosition[3];
    } // isTouchOnView

    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {
        int action = motionEvent.getActionMasked();

        switch (view.getId()) {

        case R.id.forwardsPowerImageView:
            //
            if (action == MotionEvent.ACTION_DOWN) {
                if (roverAccelerationState != ROVER_STOPPED && roverDirection != ROVER_FORWARDS) {
                    roverAccelerationState = ROVER_STOPPED;
                    mMessenger.updateRoverSpeed(roverSpeed = 0);
                } // if
                else {
                    roverDirection = ROVER_FORWARDS;
                    roverAccelerationState = ROVER_ACCELERATING;
                    resetTimer(new roverSpeedTask());
                } // else
            } // if
            else
            //
            if ((action == MotionEvent.ACTION_UP || !isTouchOnView(view, motionEvent))
                    && roverAccelerationState != ROVER_DECELERATING) {
                roverAccelerationState = ROVER_DECELERATING;
                resetTimer(new roverSpeedTask());
            } // if
            break;

        case R.id.backwardsPowerImageView:
            //
            if (action == MotionEvent.ACTION_DOWN) {
                if (roverAccelerationState != ROVER_STOPPED && roverDirection != ROVER_BACKWARDS) {
                    roverAccelerationState = ROVER_STOPPED;
                    mMessenger.updateRoverSpeed(roverSpeed = 0);
                } // if
                else {
                    roverDirection = ROVER_BACKWARDS;
                    roverAccelerationState = ROVER_ACCELERATING;
                    resetTimer(new roverSpeedTask());
                } // else
            } // if
            else if ((action == MotionEvent.ACTION_UP || !isTouchOnView(view, motionEvent))
                    && roverAccelerationState != ROVER_DECELERATING) {
                roverAccelerationState = ROVER_DECELERATING;
                resetTimer(new roverSpeedTask());
            } // if
            break;

        default:
            Log.e("OnTouch:", "wut?");
        } // switch
        return true;
    } // onClick

    private class roverSpeedTask extends TimerTask {

        @Override
        public void run() {
            //
            if (roverAccelerationState == ROVER_ACCELERATING && roverSpeed < 100
                    || roverAccelerationState == ROVER_DECELERATING && roverSpeed > 0)
                roverSpeed = calculateSpeed();

            //
            if (roverSpeed == 0) {
                timer.cancel();
                timer.purge();
                roverAccelerationState = ROVER_STOPPED;
            } // if

            //
            if (roverSpeed / 25 * 25 != roverLastSpeed) {
                roverLastSpeed = (int) (roverSpeed / 25.0 * 25);
                if (roverDirection == ROVER_BACKWARDS)
                    roverLastSpeed *= -1;
                mMessenger.updateRoverSpeed(roverLastSpeed);
            } // if

            //
            if (roverDirection == ROVER_FORWARDS) {
                forwardsPowerProgressBar.setProgress(roverSpeed);
                backwardsPowerProgressBar.setProgress(0);
            } // if
            else {
                forwardsPowerProgressBar.setProgress(0);
                backwardsPowerProgressBar.setProgress(roverSpeed);
            } // else
        } // run
    } // increasePower

    private void resetTimer(TimerTask task) {
        timer.cancel();
        timer.purge();
        timer = new Timer();

        timer.schedule(task, touchResponsiveness, timerPeriod);
    } // resetTimer

    private int calculateSpeed() {
        int speed = 0;

        switch (roverAccelerationState) {

        case ROVER_ACCELERATING:
            speed = (int) (roverSpeed + (double) timerPeriod / accelerationTime * 100);
            break;

        case ROVER_DECELERATING:
            speed = (int) (roverSpeed - (double) timerPeriod / decelerationTime * 100);
            break;

        case ROVER_STOPPED:
            speed = 0;
            break;

        default:
            Log.e("calculateSpeed(): ", "default triggered");
        } // switch

        return speed < 0 ? 0 : speed;
    } // calculateSpeed

    //**********************************************************************************************
    //                                  SEEKBAR CONTROLS
    //**********************************************************************************************

    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        //
        if (progress <= 20) {
            progress = 0;
            seekBar.setProgress(0);
        } // if

        //
        switch (seekBar.getId()) {

        case R.id.forwardsPowerSeekBar:
            if (backwardsPowerSeekBar.getProgress() > 0)
                forwardsPowerSeekBar.setProgress(0);
            else {
                roverSpeed = progress;
                forwardsPowerProgressBar.setProgress(progress);
            }
            break;

        case R.id.backwardsPowerSeekBar:
            if (forwardsPowerSeekBar.getProgress() > 0)
                backwardsPowerSeekBar.setProgress(0);
            else {
                roverSpeed = -progress;
                backwardsPowerProgressBar.setProgress(progress);
            }
            break;

        default:
            Toast.makeText(this, "onProgressChanged: SeekBar: wut?!", Toast.LENGTH_SHORT).show();
        } // switch
    } // onProgressChanged

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {

    } // onStartTrackingTouch

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {
        mMessenger.updateRoverSpeed(roverSpeed);
    } // onStopTrackingTouch

    //**********************************************************************************************
    //                                  JOYSTICK CONTROLS
    //**********************************************************************************************

    private int cameraX = 0, cameraY = 0;

    @Override
    public void onMove(JoystickView view, double x, double y) {
        int newX = ((int) (x * 101)) / 20 * 20;
        int newY = ((int) (y * 101)) / 20 * -20;

        switch (view.getId()) {

        case R.id.cameraJoystickView:
        case R.id.largeCameraJoystickView:
            // If any of the pan/tilt positions have changed, tell PiDroid about it.
            if (cameraX != newX || cameraY != newY) {
                cameraX = newX;
                cameraY = newY;
                mMessenger.updateCameraPosition(cameraX, cameraY);
                Log.d("Controller: onMove():", "cameraX = " + cameraX + ", cameraY = " + cameraY);
            } // if
            break;

        case R.id.directionJoystickView:
            // If there is a new roverSpeed, tell PiDroid about it.
            if (roverSpeed != newY) {
                roverSpeed = newY;
                mMessenger.updateRoverSpeed(roverSpeed);
                //Log.d("Controller: onMove():", "roverSpeed = " + roverSpeed);
            } // if

            // Compute the angle made by the joystick measured with respect to the trigonometric circle.
            if (newX > 0)
                newX = (int) (Math.toDegrees(Math.atan(Math.abs((double) newY / newX))));
            else
                newX = (int) (Math.toDegrees(Math.atan(Math.abs((double) newX / newY))) + 90);

            // If there is a new turnAngle (or turning angle), tell PiDroid about it.
            if (turnAngle != newX) {
                turnAngle = newX;
                //Log.d("Controller: onMove():", "turn angle = " + turnAngle);
                mMessenger.updateRoverSpeed(roverSpeed);
            } // if
            break;
        } // switch
    } // onMove

    @Override
    public void onRelease(JoystickView view) {
        switch (view.getId()) {

        case R.id.cameraJoystickView:
        case R.id.largeCameraJoystickView:

            // Tell PiDroid to reset the camera position.
            //mMessenger.updateCameraPosition(cameraX = 0, cameraY = 0);
            break;

        case R.id.directionJoystickView:

            roverSpeed = 0;
            mMessenger.updateRoverSpeed(roverSpeed);

            turnAngle = 90;
            mMessenger.updateTurnAngle(turnAngle);
            break;
        } // switch
    } // onRelease

    //**********************************************************************************************
    //                                  VOICE RECOGNITION
    //**********************************************************************************************

    public void startVoiceRecognition() {
        Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
        startActivityForResult(intent, SPEECH_RECOGNITION_REQUEST_CODE);
    } // startVoiceRecognition

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        if (requestCode == SPEECH_RECOGNITION_REQUEST_CODE && intent != null) {
            ArrayList<String> predictions = intent.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
            mRecogniser.recogniseExecuteCommand(predictions);
        } // if

        super.onActivityResult(requestCode, resultCode, intent);
    } // onActivityResult

    //**********************************************************************************************
    //                                    VIDEO STREAM
    //**********************************************************************************************

    public boolean setupVideoFeed() {
        String URL = "http://" + serverIP + ":" + (serverPort + 1) + "/?action=stream";

        try {
            videoFeedTask = new VideoFeedTask();
            videoFeedTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, URL);
        } catch (Exception exception) {
            Log.e("setupVideoFeed():", "Could not connect to host!");
            return false;
        } // try-catch

        return true;
    } // setupVideoFeed

    public class VideoFeedTask extends AsyncTask<String, Void, MjpegInputStream> {

        @Override
        protected MjpegInputStream doInBackground(String... url) {
            HttpResponse res;
            DefaultHttpClient httpClient = new DefaultHttpClient();
            HttpParams httpParams = httpClient.getParams();
            HttpConnectionParams.setConnectionTimeout(httpParams, 5 * 1000);

            Log.d("VideoFeedTask:", "1. Sending http request");
            try {
                res = httpClient.execute(new HttpGet(URI.create(url[0])));
                Log.e("VideoFeedTask:", "2. Request finished, status = " + res.getStatusLine().getStatusCode());
                if (res.getStatusLine().getStatusCode() == 401) {
                    // You must turn off camera User Access Control before this will work
                    return null;
                }
                return new MjpegInputStream(res.getEntity().getContent());
            } catch (Exception e) {
                //e.printStackTrace();
                Log.e("VideoFeedTask:", "Caught exception: ", e.getCause());
            } // try-catch
            return null;
        }

        @Override
        protected void onPostExecute(MjpegInputStream result) {
            videoFeedMjpegView.setSource(result);

            if (result != null) {
                result.setSkip(1);
                //videoFeedOn = true;
            } // if
              //else
              //videoFeedOn = false;

            videoFeedMjpegView.showFps(false);
        } // onPostExecute
    } // VideoFeedTask

    //**********************************************************************************************
    //                                   MACHINE LEARNING
    //**********************************************************************************************

    public class LearningTask extends AsyncTask<Void, Void, Boolean> {

        public final static int PIDROID_LEARN = 0;
        public final static int PIDROID_RECOGNISE = 1;

        private final Context UIContext;
        private final ProgressDialog dialog;
        private final int taskType;

        private String objects[];
        private int objectType;

        public LearningTask(Context context, int taskType, int objectType) {
            this(context, taskType);

            this.objectType = objectType;
        } // constructor

        public LearningTask(Context context, int taskType) {
            this.UIContext = context;
            this.taskType = taskType;

            objects = getResources().getStringArray(R.array.object_names);

            // Define the behaviour of a reply from PiDroid sent by RecogniserController
            mMessenger.setRecogniserController(new Messenger.RecogniserController() {
                @Override
                public void onLearnNewObjectReply() {
                    videoFeedOn = true;
                } // onLearnNewObjectReply

                @Override
                public void onRecogniseObjectReply(int objType) {
                    videoFeedOn = true;

                    if (objType != -1)
                        new AlertDialog.Builder(UIContext, AlertDialog.THEME_HOLO_DARK)
                                .setPositiveButton("The object is a " + objects[objType].toUpperCase(), null)
                                .create().show();
                    else
                        new AlertDialog.Builder(UIContext, AlertDialog.THEME_HOLO_DARK)
                                .setPositiveButton("The object could not be recognised.", null).create().show();
                } // onRecogniseObjectReply
            });

            // Let the user know PiDroid is busy
            dialog = new ProgressDialog(UIContext, AlertDialog.THEME_HOLO_DARK);
            dialog.setMessage("PiDroid is computing...");
            dialog.setCancelable(false);
            dialog.show();

            Log.d("LearningTask:", "Progress dialog is showing...");
        } // constructor

        @Override
        protected Boolean doInBackground(Void... params) {
            // Because PiDroid uses the camera to learn new objects or recognise them, it stops the stream
            videoFeedMjpegView.stopPlayback();
            videoFeedTask.cancel(true);
            videoFeedOn = false;

            if (taskType == PIDROID_LEARN)
                // Tell PiDroid to kick off the learning process
                mMessenger.learnNewObject(objectType);
            else if (taskType == PIDROID_RECOGNISE)
                // Tell PiDroid to kick off the recognition process
                mMessenger.recogniseObject();

            // Here we wait for a reply from PiDroid meaning that the learning process has finished
            // The variable is set by the onLearnNewObjectReply() defined in the Constructor
            while (!videoFeedOn) {
                Log.d("LearningTask:", "Failed to restart video feed!");

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException interruptedException) {
                    Log.d("LearningTask:", "Thread got interrupted");
                } // try - catch
            } // while

            setupVideoFeed();

            Log.d("LearningTask:", "Progress dialog is being dismissed...");

            return true;
        } // doInBackground

        @Override
        protected void onPostExecute(Boolean result) {
            dialog.dismiss();
        } // onPostExecute
    } // LearningTask

} // Controller