com.mhennessy.mapfly.MainActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.mhennessy.mapfly.MainActivity.java

Source

package com.mhennessy.mapfly;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.Address;
import android.location.Geocoder;
import android.os.Bundle;
import android.speech.RecognitionListener;
import android.speech.RecognizerIntent;
import android.speech.SpeechRecognizer;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;

import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.CancelableCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;
import com.mhennessy.mapfly.MapTypeMenu.MapTypeMenuItem;

@SuppressWarnings("deprecation")
public class MainActivity extends FragmentActivity implements SensorEventListener {
    public static final String TAG = "Mapfly";

    public static final int CAMERA_UPDATE_DURATION = 3000;

    public static final int UPDATE_FRAMES_PER_SECOND = 60;
    public static final int UPDATE_INTERVAL = 1000 / UPDATE_FRAMES_PER_SECOND;

    public static final int FLY_DIRECTION = -1;
    public static final int FLY_SENSITIVITY = 3;
    public static final float FLY_SPEED_PER_SECOND = 15f / UPDATE_FRAMES_PER_SECOND;

    public static final float DEFAULT_ZOOM = 17;
    public static final float DEFAULT_TILT = 30;

    private static final MapTypeMenu MAP_TYPE_MENU = new MapTypeMenu();

    private static final int SHAKE_THRESHOLD = 1600;

    private SensorManager mSensorManager;
    private Sensor mOrientationSensor;
    private Sensor mAccelerationSensor;
    private float[] mOrientationValues = new float[3];
    private long mLastAccelerometerUpdate;
    private long mLastShake;
    private float[] mLastAccelerometerValues = new float[3];

    private GoogleMap mMap;
    private Geocoder mGeocoder;
    private Timer mUpdateTimer;
    private boolean mFlyingEnabled;

    private MenuItem mFlyToggleBtn;
    private MenuItem mStreetsToggleBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initSensors();
        initMap();
    }

    private void initSensors() {
        mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
        mOrientationSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
        mAccelerationSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        mFlyingEnabled = false;
    }

    private void initMap() {
        mGeocoder = new Geocoder(this);
        SupportMapFragment mapFragment = ((SupportMapFragment) getSupportFragmentManager()
                .findFragmentById(R.id.map));
        mMap = mapFragment.getMap();

        mMap.addMarker(new MarkerOptions().position(new LatLng(33.748832, -84.38751300000001)).title("Marker"));
        mMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);

        CameraPosition position = new CameraPosition.Builder().zoom(DEFAULT_ZOOM)
                .target(new LatLng(33.748832, -84.38751300000001)).bearing(90).tilt(DEFAULT_TILT).build();

        animateCameraToPosition(position, new CancelableCallback() {
            @Override
            public void onFinish() {
                initUpdateTimer();
            }

            @Override
            public void onCancel() {
            }
        });
    }

    private void animateCameraToPosition(CameraPosition position, CancelableCallback callback) {
        // Flying is resource intensive. Disable it for a smoother animation.
        setFlyingEnabled(false);
        mMap.animateCamera(CameraUpdateFactory.newCameraPosition(position), CAMERA_UPDATE_DURATION, callback);
    }

    private void initUpdateTimer() {
        mUpdateTimer = new Timer();
        mUpdateTimer.schedule(new TimerTask() {
            @Override
            public void run() {
                MainActivity.this.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        if (mFlyingEnabled) {
                            updateCameraPosition(mOrientationValues[0], mOrientationValues[1],
                                    mOrientationValues[2]);
                        }
                    }
                });
            }

        }, 0, UPDATE_INTERVAL);
    }

    private void setFlyingEnabled(boolean enabled) {
        if (mFlyToggleBtn != null) {
            mFlyingEnabled = enabled;
            mFlyToggleBtn.setTitle(mFlyingEnabled ? R.string.action_fly_stop : R.string.action_fly_start);
            setDefualtAppTitle();
        }
    }

    private void setStreetsEnabled(boolean enabled) {
        mStreetsToggleBtn.setTitle(enabled ? R.string.action_streets_hide : R.string.action_streets_show);
        mMap.setTrafficEnabled(enabled);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        mFlyToggleBtn = (MenuItem) menu.findItem(R.id.action_fly_toggle);
        mStreetsToggleBtn = (MenuItem) menu.findItem(R.id.action_streets_toggle);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.action_speak_location:
            setMapLocationBasedOnSpeech();
            return true;
        case R.id.action_fly_toggle:
            toggleFlying();
            return true;
        case R.id.action_select_map_type:
            promptForMapType();
            return true;
        case R.id.action_streets_toggle:
            toggleStreets();
            return true;
        case R.id.action_legal:
            /**
             * If you use the Google Maps Android API in your application, you
             * must include the Google Play Services attribution text as part of
             * a "Legal Notices" section in your application. Including legal
             * notices as an independent menu item, or as part of an "About"
             * menu item, is recommended.
             */
            new AlertDialog.Builder(this).setTitle("Legal")
                    .setMessage(GooglePlayServicesUtil.getOpenSourceSoftwareLicenseInfo(this)).show();
            return true;
        default:
            return super.onOptionsItemSelected(item);
        }
    }

    public void toggleFlying() {
        setFlyingEnabled(mFlyToggleBtn.getTitle().equals(getString(R.string.action_fly_start)));
    }

    public void toggleStreets() {
        setStreetsEnabled(mStreetsToggleBtn.getTitle().equals(getString(R.string.action_streets_show)));
    }

    private void promptForMapType() {
        new AlertDialog.Builder(this).setSingleChoiceItems(MAP_TYPE_MENU.getMenuLabels(), 0, null)
                .setPositiveButton(R.string.select_btn_label, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int whichButton) {
                        dialog.dismiss();
                        int selectedPosition = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
                        MapTypeMenuItem selectedMenuItem = MAP_TYPE_MENU.getMenuItems().get(selectedPosition);
                        int mapType = selectedMenuItem.getValue();
                        mMap.setMapType(mapType);
                        CameraPosition cameraPosition = selectedMenuItem.getCameraPosition();
                        if (cameraPosition != null) {
                            animateCameraToPosition(cameraPosition, null);
                        }
                    }
                }).show();
    }

    private void setDefualtAppTitle() {
        setTitle(R.string.app_name);
    }

    @Override
    protected void onResume() {
        super.onResume();
        mSensorManager.registerListener(this, mOrientationSensor, SensorManager.SENSOR_DELAY_NORMAL);
        mSensorManager.registerListener(this, mAccelerationSensor, SensorManager.SENSOR_DELAY_GAME);
    }

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

    @Override
    public void onSensorChanged(SensorEvent event) {
        switch (event.sensor.getType()) {
        case Sensor.TYPE_ORIENTATION:
            onOrientationChange(event);
            break;
        case Sensor.TYPE_ACCELEROMETER:
            onAccelerometerChange(event);
            break;
        }
    }

    private void onOrientationChange(SensorEvent event) {
        float[] values = event.values;
        System.arraycopy(values, 0, mOrientationValues, 0, values.length);
    }

    /**
     * Source:
     * http://stackoverflow.com/questions/5271448/how-to-detect-shake-event
     * -with-android
     */
    private void onAccelerometerChange(SensorEvent event) {
        float[] values = event.values;
        long currentTime = System.currentTimeMillis();
        // Only allow one update every 100 milliseconds.
        if ((currentTime - mLastAccelerometerUpdate) > 100 && (currentTime - mLastShake) > 2000) {
            long timeDifference = (currentTime - mLastAccelerometerUpdate);
            mLastAccelerometerUpdate = currentTime;

            float x = values[0];
            float y = values[1];
            float z = values[2];
            float last_x = mLastAccelerometerValues[0];
            float last_y = mLastAccelerometerValues[1];
            float last_z = mLastAccelerometerValues[2];

            float speed = Math.abs(x + y + z - last_x - last_y - last_z) / timeDifference * 10000;

            if (speed > SHAKE_THRESHOLD) {
                String message = "Shake detected w/ speed: " + speed;
                Log.d(TAG, message);
                Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
                toggleFlying();
                mLastShake = currentTime;
            }
            System.arraycopy(event.values, 0, mLastAccelerometerValues, 0, event.values.length);
        }
    }

    private void updateCameraPosition(float azimuthAngle, float pitchAngle, float rollAngle) {
        float moveX = getFlyDelta(rollAngle);
        float moveY = getFlyDelta(pitchAngle);
        setTitle(String.format("%s - roll: %.2f pitch: %.2f", getString(R.string.app_name), moveY, moveX));
        mMap.moveCamera(CameraUpdateFactory.scrollBy(moveX, moveY));
    }

    private float getFlyDelta(float cameraAngle) {
        if (Math.abs(cameraAngle) < FLY_SENSITIVITY) {
            cameraAngle = 0;
        }
        return FLY_SPEED_PER_SECOND * FLY_DIRECTION * cameraAngle;
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        // no-op
    }

    public void setMapLocation(String locationName) {
        try {
            List<Address> possibleLocations = mGeocoder.getFromLocationName(locationName, 1);
            if (possibleLocations != null && !possibleLocations.isEmpty()) {
                Address address = possibleLocations.get(0);
                setTitle(address.getFeatureName());
                CameraPosition position = new CameraPosition.Builder()
                        .target(new LatLng(address.getLatitude(), address.getLongitude())).zoom(DEFAULT_ZOOM)
                        .tilt(DEFAULT_TILT).build();
                animateCameraToPosition(position, null);
            }
        } catch (IOException e) {
            Log.e(TAG, "Error getting address from location name.");
        }
    }

    public void setMapLocationBasedOnSpeech() {
        Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
        intent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, "com.mhennessy.mapfly");

        SpeechRecognizer recognizer = SpeechRecognizer.createSpeechRecognizer(this.getApplicationContext());

        // Stop flying so that messages can be displayed to the user without
        // being overwritten by pitch/roll info.
        setFlyingEnabled(false);
        RecognitionListener listener = new RecognitionListener() {
            @Override
            public void onResults(Bundle results) {
                ArrayList<String> voiceResults = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
                if (voiceResults == null) {
                    Log.e(TAG, "No voice results");
                } else {
                    Log.d(TAG, "Printing matches: ");
                    for (String match : voiceResults) {
                        Log.d(TAG, match);
                    }
                    String bestMatch = voiceResults.get(0);
                    setMapLocation(bestMatch);
                }
            }

            @Override
            public void onReadyForSpeech(Bundle params) {
                setTitle("Say something!");
                Log.d(TAG, "Ready for speech");
            }

            @Override
            public void onError(int error) {
                setTitle("Speach Error");
                Log.d(TAG, "Error listening for speech: " + error);
            }

            @Override
            public void onBeginningOfSpeech() {
                Log.d(TAG, "Speech starting");
            }

            @Override
            public void onBufferReceived(byte[] buffer) {
                // no-op
            }

            @Override
            public void onEndOfSpeech() {
                // no-op
            }

            @Override
            public void onEvent(int eventType, Bundle params) {
                // no-op
            }

            @Override
            public void onPartialResults(Bundle partialResults) {
                // no-op
            }

            @Override
            public void onRmsChanged(float rmsdB) {
                // no-op
            }
        };
        recognizer.setRecognitionListener(listener);
        recognizer.startListening(intent);
    }
}