Java tutorial
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); } }