/* * Copyright (C) 2011 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package tgnourse.diveguide; import java.util.ArrayList; import java.util.List; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.pm.ActivityInfo; import android.graphics.Color; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.location.GpsStatus; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.location.LocationProvider; import android.os.Bundle; import android.os.Handler; import android.os.PowerManager; import android.os.SystemClock; import android.os.Vibrator; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.widget.TextView; import android.widget.Toast; /** * @author tgnourse@google.com (Thomas Nourse) */ public class DiveGuideActivity extends Activity { /** * We need to keep track of all of these listeners so we can de-register them later. */ private GpsStatus.Listener gpsStatusListener; private GpsStatus.NmeaListener nmeaListener; private LocationListener locationListener; private LocationManager locationManager; /** * Information about the current location. */ private CurrentLocation currentLocation; private List<TargetLocation> targetLocations; private int index = 0; /** * We need to keep track of all the sensor listeners too. */ private SensorManager sensorManager; private SensorEventListener sensorListener; /** * Output file. */ ExternalStorageFile file; /** * Screen lock. */ PowerManager.WakeLock screenLock; /** * Set up the UI and grab the LocationManager. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); // Create the current location object. currentLocation = new CurrentLocation(); targetLocations = new ArrayList<TargetLocation>(); // Set up the UI. setContentView(R.layout.main); // Set up the various location listeners so we get GPS updates. locationManager = (LocationManager) getSystemService(LOCATION_SERVICE); gpsStatusListener = new MyGpsStatusListener(); nmeaListener = new MyNmeaListener(); locationListener = new MyLocationListener(); // Set up the sensor listeners. sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); sensorListener = new MySensorListener(); // Create the file object (note: we don't open it until onResume()). file = new ExternalStorageFile("divetracker", "csv"); } private void setTargetLocations(CharSequence site) { targetLocations.clear(); if (site.equals("Pt Lobos")) { // Point Lobos targetLocations.add(new TargetLocation("Boat Dock", Color.rgb(255, 0, 255), 36.520278, -121.940558)); targetLocations.add(new TargetLocation("Cannery Pt", Color.rgb(255, 0, 0), 36.521044,-121.939933)); targetLocations.add(new TargetLocation("N Middle Reef", Color.rgb(255, 255, 0), 36.522417, -121.939550)); targetLocations.add(new TargetLocation("Whale Bones", Color.rgb(0, 255, 255), 36.523200, -121.939333)); targetLocations.add(new TargetLocation("Granite Pt Wall?", Color.rgb(0, 255, 0), 36.522417, -121.939550)); } else if (site.equals("Monastery North")) { // Monastery North targetLocations.add(new TargetLocation("North Beach", Color.rgb(255, 0, 255), 36.525696,-121.925223)); targetLocations.add(new TargetLocation("Kelp Tip", Color.rgb(255, 0, 0), 36.527372,-121.9279)); } else if (site.equals("Monastery South")) { // Monastery South targetLocations.add(new TargetLocation("South Beach", Color.rgb(255, 0, 255), 36.522587,-121.928909)); targetLocations.add(new TargetLocation("Kelp Finger Tip", Color.rgb(255, 0, 0), 36.525829,-121.930261)); } else if (site.equals("Breakwater")) { // Breakwater targetLocations.add(new TargetLocation("South Beach", Color.rgb(255, 0, 255), 36.609723, -121.894727)); targetLocations.add(new TargetLocation("The Barge", Color.rgb(255, 0, 0), 36.610633, -121.890150)); targetLocations.add(new TargetLocation("End of the Wall", Color.rgb(255, 255, 0), 36.608561, -121.889647)); targetLocations.add(new TargetLocation("Wall Elbow", Color.rgb(0, 255, 255), 36.609508, -121.892790)); // Old location for the Metridium Fields from the interwebs // targetLocations.add(new TargetLocation("Metridium Fields", Color.rgb(0, 255, 0), 36.612400, -121.892817)); targetLocations.add(new TargetLocation("Metridium Fields", Color.rgb(0, 255, 0), 36.612703, -121.892886)); } if (currentLocation.hasLocation()) { showNextTargetLocation(); } } private void updateUI(TargetLocation target) { if (currentLocation.hasLocation()) { TextView destination = (TextView) findViewById(R.id.destination); destination.setText(target.getName()); destination.setTextColor(target.getColor()); TextView distanceValue = (TextView) findViewById(R.id.distance_value); TextView distanceUnits = (TextView) findViewById(R.id.distance_units); TextView headingValue = (TextView) findViewById(R.id.heading_value); TextView headingDirection = (TextView) findViewById(R.id.heading_direction); TextView ageValue = (TextView) findViewById(R.id.age_value); TextView ageUnits = (TextView) findViewById(R.id.age_units); CurrentLocation.Difference difference = currentLocation.getDifference(target); distanceValue.setText(Util.getHumanReadableDistance(difference.getDistance())); distanceUnits.setText(Util.getUnitForDistance(difference.getDistance())); headingValue.setText(String.valueOf(difference.getHeading())); headingDirection.setText(Util.getDirectionFromHeading(difference.getHeading())); ageValue.setText(Util.getHumanReadableDuration(difference.getAge())); ageUnits.setText(Util.getUnitForDuration(difference.getAge())); } else { updateMessage("Waiting for GPS ..."); } } private void updateMessage(String message) { TextView status = (TextView) findViewById(R.id.destination); status.setText(message); status.setTextColor(Color.WHITE); } private void updateUI() { if (targetLocations.size() > 0) { updateUI(targetLocations.get(index)); } else { updateMessage("Pick a dive site ..."); } } private void showNextTargetLocation() { // Reset the index if the dive site changed. if (!(index < targetLocations.size())) { index = 0; } if (targetLocations.size() > 0) { ((Vibrator) getSystemService(Context.VIBRATOR_SERVICE)).vibrate(500); updateUI(targetLocations.get(index)); index = (index + 1) % targetLocations.size(); } else { updateMessage("Pick a dive site ..."); Toast.makeText(getApplicationContext(), "Please pick a dive site first.", Toast.LENGTH_SHORT).show(); } } /** * Start the location tracking. */ @Override protected void onResume() { super.onResume(); Util.log("onResume()"); file.open(); // Re-register all of the listeners. if (!locationManager.addGpsStatusListener(gpsStatusListener)) { Util.error("Couldn't add Gps Status Listener!"); } if (!locationManager.addNmeaListener(nmeaListener)) { Util.error("Couldn't add Nmea Listener!"); } locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListener); // Register to listen to all the sensors. List<Sensor> sensors = sensorManager.getSensorList(Sensor.TYPE_ALL); for (Sensor sensor : sensors) { Util.log("Registering listener for " + sensor.getName()); sensorManager.registerListener(sensorListener, sensor, SensorManager.SENSOR_DELAY_FASTEST); } PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); screenLock = powerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "DiveTracker"); screenLock.acquire(); startTimer(); // If there's no site currently selected, prompt the user to select one. if (targetLocations.size() == 0) { showDialog(DIALOG_SITE_SELECTION); } } private Handler handler = new Handler(); long startTime = 0L; private Runnable updateTimeTask = new Runnable() { public void run() { long seconds = (SystemClock.uptimeMillis() - startTime) / 1000; long minutes = seconds / 60; seconds = seconds % 60; updateUI(); handler.postAtTime(this, startTime + (((minutes * 60) + seconds + 1) * 1000)); } }; private void startTimer() { Util.log("startTimer()"); startTime = System.currentTimeMillis(); handler.removeCallbacks(updateTimeTask); handler.postDelayed(updateTimeTask, 100); } public void stopTimer() { Util.log("stopTimer()"); handler.removeCallbacks(updateTimeTask); } /** * Stop the location tracking. */ @Override protected void onPause() { super.onPause(); Util.log("onPause()"); stopTimer(); screenLock.release(); // Remover the listeners. locationManager.removeGpsStatusListener(gpsStatusListener); locationManager.removeNmeaListener(nmeaListener); locationManager.removeUpdates(locationListener); sensorManager.unregisterListener(sensorListener); file.close(); } private class MyLocationListener implements LocationListener { public void onLocationChanged(Location location) { // Util.log("New location: " + location); currentLocation.locationChanged(location); updateUI(); String line = "GPS," + System.currentTimeMillis() + "," + location.getAccuracy() + "," + location.getAltitude() + "," + location.getBearing() + "," + location.getLatitude() + "," + location.getLongitude() + "," + location.getSpeed() + "," + location.getTime(); // Util.log(line); file.write(line); } public void onProviderDisabled(String provider) { Util.log("Provider disabled: " + provider); } public void onProviderEnabled(String provider) { Util.log("Provider enabled: " + provider); } public void onStatusChanged(String provider, int status, Bundle extras) { Util.log("Provider status changed: " + provider); switch (status) { case LocationProvider.AVAILABLE: Util.log(provider + " is available."); case LocationProvider.TEMPORARILY_UNAVAILABLE: Util.log(provider + " is temporarily unavailable."); case LocationProvider.OUT_OF_SERVICE: Util.log(provider + " is out of service."); } } } private class MyGpsStatusListener implements GpsStatus.Listener { public void onGpsStatusChanged(int event) { //LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE); switch (event) { case GpsStatus.GPS_EVENT_FIRST_FIX: //Util.log("First fix"); break; case GpsStatus.GPS_EVENT_SATELLITE_STATUS: //Util.log("Satellite status"); break; case GpsStatus.GPS_EVENT_STARTED: //Util.log("Started"); break; case GpsStatus.GPS_EVENT_STOPPED: //Util.log("Stopped"); break; } // Request a new GpsStatus object instead of having one filled in. //GpsStatus status = locationManager.getGpsStatus(null); //Util.log("Max Satellites: " + status.getMaxSatellites()); //Util.log("Time to First Fix: " + status.getTimeToFirstFix()); } } private class MyNmeaListener implements GpsStatus.NmeaListener { public void onNmeaReceived(long timestamp, String nmea) { //Calendar calendar = Calendar.getInstance(); //calendar.setTimeInMillis(timestamp); //Util.log(calendar.getTime().toString() + "] " + nmea); } } private class MySensorListener implements SensorEventListener { long lastSwitchTime = 0; // ms private double getMagnitude(float x, float y, float z) { return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2) + Math.pow(z, 2)); } public void onAccuracyChanged(Sensor sensor, int accuracy) { // Util.log("Sensor accuracy changed " + sensor.getName() + ", " + accuracy); } public void onSensorChanged(SensorEvent event) { StringBuffer values = new StringBuffer(); for (float value : event.values) { values.append(value); values.append(':'); } String line = "SNS," + System.currentTimeMillis() + "," + event.sensor.getType() + "," + "\"" + event.sensor.getName() + "\"," + event.timestamp + "," + event.accuracy + "," + values; // Util.log(line); file.write(line); if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER && event.values.length == 3) { double force = getMagnitude(event.values[0], event.values[1], event.values[2]); double forceThreshold = 25.0; // N long timeThreshold = 800; // ms long currentTime = System.currentTimeMillis(); if (force > forceThreshold && currentTime - lastSwitchTime > timeThreshold) { Util.log("Detected a shake! " + force + " > " + forceThreshold); showNextTargetLocation(); lastSwitchTime = currentTime; } } } } /** * Android calls this when the user taps the menu button. */ public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.options_menu, menu); return true; } /** * Android calls this when a user taps an option in the options menu. */ public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.edit_dive_sites: // showDialog(DIALOG_EDIT_DIVE_SITES); break; case R.id.warnings: // showDialog(DIALOG_WARNINGS); break; case R.id.set_dive_site: showDialog(DIALOG_SITE_SELECTION); break; case R.id.next_target: showNextTargetLocation(); break; } return true; } static final int DIALOG_SITE_SELECTION = 0; protected Dialog onCreateDialog(int id) { Dialog dialog; switch(id) { case DIALOG_SITE_SELECTION: // do the work to define the pause Dialog final CharSequence[] items = {"Breakwater", "Monastery North", "Monastery South", "Pt Lobos"}; AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("Pick a dive site"); builder.setItems(items, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int item) { Toast.makeText(getApplicationContext(), "Dive site set to " + items[item], Toast.LENGTH_SHORT).show(); setTargetLocations(items[item]); showNextTargetLocation(); } }); dialog = builder.create(); break; default: dialog = null; } return dialog; } }