/*------------------------------------------------------------------------------ ** Ident: Innovation en Inspiration > Google Android ** Author: rene ** Copyright: (c) Jan 22, 2009 Sogeti Nederland B.V. All Rights Reserved. **------------------------------------------------------------------------------ ** Sogeti Nederland B.V. | No part of this file may be reproduced ** Distributed Software Engineering | or transmitted in any form or by any ** Lange Dreef 17 | means, electronic or mechanical, for the ** 4131 NJ Vianen | purpose, without the express written ** The Netherlands | permission of the copyright holder. *------------------------------------------------------------------------------ * * This file is part of OpenGPSTracker. * * OpenGPSTracker is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OpenGPSTracker is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenGPSTracker. If not, see <http://www.gnu.org/licenses/>. * */ package nl.sogeti.android.gpstracker.viewer; import java.util.List; import nl.sogeti.android.gpstracker.R; import nl.sogeti.android.gpstracker.db.GPStracking.Segments; import nl.sogeti.android.gpstracker.db.GPStracking.Tracks; import nl.sogeti.android.gpstracker.db.GPStracking.Waypoints; import nl.sogeti.android.gpstracker.logger.GPSLoggerService; import nl.sogeti.android.gpstracker.logger.GPSLoggerServiceManager; import nl.sogeti.android.gpstracker.logger.SettingsDialog; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.app.AlertDialog.Builder; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.database.ContentObserver; import android.database.Cursor; import android.graphics.Point; import android.location.Location; import android.location.LocationManager; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.preference.PreferenceManager; import android.util.TypedValue; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import com.google.android.maps.GeoPoint; import com.google.android.maps.MapActivity; import com.google.android.maps.MapController; import com.google.android.maps.MapView; import com.google.android.maps.Overlay; /** * * @version $Id: LoggerMap.java 173 2009-11-15 12:55:31Z rcgroot@gmail.com $ * @author rene (c) Jan 18, 2009, Sogeti B.V. */ public class LoggerMap extends MapActivity { private static final int ZOOM_LEVEL = 10; // MENU'S private static final int MENU_SETTINGS = 0; private static final int MENU_TOGGLE = 1; private static final int MENU_TRACKLIST = 5; private static final int MENU_VIEW = 7; private static final String TAG = LoggerMap.class.getName(); protected static final String DISABLEBLANKING = "disableblanking"; private long mTrackId = -1; private MapView mMapView = null; private MapController mMapController = null; private GPSLoggerServiceManager mLoggerServiceManager; private EditText mFileNameView; private EditText mTrackNameView; private WakeLock mWakeLock = null; private double mAverageSpeed = 33.33d/2d; private TextView[] mSpeedtexts = null; private OnSharedPreferenceChangeListener mSharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() { public void onSharedPreferenceChanged( SharedPreferences sharedPreferences, String key ) { if( key.equals( TrackingOverlay.TRACKCOLORING ) ) { int trackColoringMethod = new Integer( sharedPreferences.getString( TrackingOverlay.TRACKCOLORING, "3" ) ).intValue(); updateSpeedbarVisibility( trackColoringMethod ); List<Overlay> overlays = LoggerMap.this.mMapView.getOverlays(); for( Overlay overlay : overlays ) { if( overlay instanceof TrackingOverlay ) { ( (TrackingOverlay) overlay ).setTrackColoringMethod( trackColoringMethod ); } } } else if( key.equals( LoggerMap.DISABLEBLANKING ) ) { updateBlankingBehavior(); } } }; private final ContentObserver mTrackObserver = new ContentObserver(new Handler()) { @Override public void onChange(boolean selfUpdate) { LoggerMap.this.createTrackingDataOverlays(); } }; private final DialogInterface.OnClickListener mNoTrackDialogListener = new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { Intent tracklistIntent = new Intent(LoggerMap.this, TrackList.class); tracklistIntent.putExtra( Tracks._ID, LoggerMap.this.mTrackId ); startActivityForResult(tracklistIntent, MENU_TRACKLIST); } } ; DialogInterface.OnClickListener mTrackNameDialogListener = new DialogInterface.OnClickListener() { public void onClick( DialogInterface dialog, int which ) { String trackName = mTrackNameView.getText().toString(); ContentValues values = new ContentValues(); values.put( Tracks.NAME, trackName); getContentResolver().update( ContentUris.withAppendedId( Tracks.CONTENT_URI, LoggerMap.this.mTrackId ), values, null, null ); mTrackNameView = null; } } ; @Override public boolean onKeyDown( int keyCode, KeyEvent event ) { Toast toast; boolean propagate = true; switch (keyCode) { case KeyEvent.KEYCODE_T: propagate = this.mMapView.getController().zoomIn(); break; case KeyEvent.KEYCODE_G: propagate = this.mMapView.getController().zoomOut(); break; case KeyEvent.KEYCODE_S: this.mMapView.setSatellite( !this.mMapView.isSatellite() ); toast = Toast.makeText( this.getApplicationContext(), "Satellite: "+this.mMapView.isSatellite(), Toast.LENGTH_SHORT ); toast.show(); propagate = false; break; case KeyEvent.KEYCODE_A: this.mMapView.setTraffic( !this.mMapView.isTraffic() ); toast = Toast.makeText( this.getApplicationContext(), "Traffic: "+this.mMapView.isTraffic(), Toast.LENGTH_SHORT ); toast.show(); propagate = false; break; case KeyEvent.KEYCODE_F: attempToMoveToTrack(this.mTrackId-1); propagate = false; break; case KeyEvent.KEYCODE_H: attempToMoveToTrack(this.mTrackId+1); propagate = false; break; default : propagate = super.onKeyDown( keyCode, event ); break; } return propagate; } @Override public boolean onCreateOptionsMenu( Menu menu ) { boolean result = super.onCreateOptionsMenu(menu); menu.add(0, MENU_TOGGLE, 0, R.string.menu_toggle_on).setIcon(android.R.drawable.ic_menu_mapmode).setAlphabeticShortcut( 't' ); menu.add(0, MENU_TRACKLIST, 0, R.string.menu_tracklist).setIcon(android.R.drawable.ic_menu_gallery).setAlphabeticShortcut( 'l' ); menu.add(0, MENU_VIEW, 0, R.string.menu_showTrack).setIcon(android.R.drawable.ic_menu_view).setAlphabeticShortcut( 'e' ); menu.add(0, MENU_SETTINGS, 0, R.string.menu_settings).setIcon(android.R.drawable.ic_menu_preferences).setAlphabeticShortcut( 's' ); return result; } @Override public boolean onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); if( this.mLoggerServiceManager.isLogging() ) { menu.findItem( MENU_TOGGLE ).setTitle( R.string.menu_toggle_off ); } else { menu.findItem( MENU_TOGGLE ).setTitle( R.string.menu_toggle_on ); } return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { boolean handled = false ; switch ( item.getItemId() ) { case MENU_TOGGLE: if( this.mLoggerServiceManager.isLogging() ) { this.mLoggerServiceManager.stopGPSLoggerService(); updateBlankingBehavior(); item.setTitle( R.string.menu_toggle_on ); } else { this.mTrackId = this.mLoggerServiceManager.startGPSLoggerService(null); attempToMoveToTrack( this.mTrackId ); updateBlankingBehavior(); item.setTitle( R.string.menu_toggle_off ); LayoutInflater factory = LayoutInflater.from( this ); View view = factory.inflate( R.layout.namedialog, null ); mTrackNameView = (EditText) view.findViewById( R.id.nameField ); createTrackTitleDialog( this, view, mTrackNameDialogListener ).show(); } updateBlankingBehavior(); handled = true; break; case MENU_SETTINGS: Intent i = new Intent( this, SettingsDialog.class ); startActivity( i ); handled = true; break; case MENU_TRACKLIST: Intent tracklistIntent = new Intent(this, TrackList.class); tracklistIntent.putExtra( Tracks._ID, this.mTrackId ); startActivityForResult(tracklistIntent, MENU_TRACKLIST); break; case MENU_VIEW: if( this.mTrackId >= 0 ) { Uri uri = ContentUris.withAppendedId( Tracks.CONTENT_URI, this.mTrackId ); Intent actionIntent = new Intent(Intent.ACTION_VIEW, uri ); startActivity( actionIntent ); handled = true; break; } else { createAlertNoTrack( this, mNoTrackDialogListener, null ).show(); } handled = true; break; default: handled = super.onOptionsItemSelected(item); } return handled; } /** * Called when the activity is first created. * */ @Override protected void onCreate( Bundle load ) { super.onCreate( load ); this.startService( new Intent( GPSLoggerService.SERVICENAME ) ); this.mLoggerServiceManager = new GPSLoggerServiceManager( (Context)this ); this.mLoggerServiceManager.connectToGPSLoggerService(); PreferenceManager.getDefaultSharedPreferences( this ).registerOnSharedPreferenceChangeListener( mSharedPreferenceChangeListener ); updateBlankingBehavior(); setContentView(R.layout.map); this.mMapView = (MapView) findViewById( R.id.myMapView ); this.mMapView.setClickable( true ); this.mMapView.setStreetView( false ); this.mMapView.setSatellite( false ); TextView[] speeds = { (TextView) findViewById( R.id.speedview05) , (TextView) findViewById( R.id.speedview04) , (TextView) findViewById( R.id.speedview03) , (TextView) findViewById( R.id.speedview02) , (TextView) findViewById( R.id.speedview01) , (TextView) findViewById( R.id.speedview00) } ; mSpeedtexts = speeds; /* Collect the zoomcontrols and place them */ this.mMapView.setBuiltInZoomControls( true ); this.mMapController = this.mMapView.getController(); /* Initial display: Last logged track drawn and zoomed to current location */ if( load==null || !load.containsKey("track") ) { moveToLastTrack(); } if( load==null || !load.containsKey("zoom") ) { this.mMapController.setZoom( LoggerMap.ZOOM_LEVEL ); } if( load==null || !load.containsKey("e6lat") || !load.containsKey("e6long") ) { GeoPoint point = getLastKnowGeopointLocation(); if( point.getLatitudeE6() != 0 && point.getLongitudeE6() != 0 ) { this.mMapView.getController().animateTo( point ); } else { this.mMapController.setZoom( LoggerMap.ZOOM_LEVEL ); } } } protected void onPause() { super.onPause(); resumeBlanking(); } protected void onResume() { this.mLoggerServiceManager.connectToGPSLoggerService(); super.onResume(); updateBlankingBehavior(); } /* * (non-Javadoc) * @see com.google.android.maps.MapActivity#onPause() */ @Override protected void onDestroy() { super.onDestroy(); PreferenceManager.getDefaultSharedPreferences( this ).unregisterOnSharedPreferenceChangeListener( this.mSharedPreferenceChangeListener ); } @Override public void onRestoreInstanceState(Bundle load) { if( load!=null && load.containsKey("track") ) { this.mTrackId = load.getLong( "track" ); attempToMoveToTrack( this.mTrackId ); } if( load!=null && load.containsKey("zoom") ) { this.mMapController.setZoom( load.getInt("zoom") ); } if( load!=null && load.containsKey("e6lat") && load.containsKey("e6long") ) { GeoPoint lastPoint = new GeoPoint( load.getInt("e6lat"), load.getInt("e6long") ); this.mMapView.getController().animateTo( lastPoint ); } } @Override public void onSaveInstanceState(Bundle save) { GeoPoint point = this.mMapView.getMapCenter(); save.putInt("e6lat", point.getLatitudeE6() ); save.putInt("e6long", point.getLongitudeE6() ); save.putInt("zoom", this.mMapView.getZoomLevel() ); save.putLong("track", this.mTrackId ); super.onSaveInstanceState(save); } /* * (non-Javadoc) * @see android.app.Activity#onActivityResult(int, int, android.content.Intent) */ @Override protected void onActivityResult( int requestCode, int resultCode, Intent data ) { super.onActivityResult(requestCode, resultCode, data); if( resultCode != RESULT_CANCELED ) { Bundle extras = data.getExtras(); switch(requestCode) { case MENU_TRACKLIST: long trackId = extras.getLong(Tracks._ID); attempToMoveToTrack( trackId ); break; } } } /** * * (non-Javadoc) * @see com.google.android.maps.MapActivity#isRouteDisplayed() */ @Override protected boolean isRouteDisplayed() { return true; } private void resumeBlanking() { if( mWakeLock != null && mWakeLock.isHeld() ) { mWakeLock.release(); } } private void updateBlankingBehavior() { boolean disableblanking = PreferenceManager.getDefaultSharedPreferences( this ).getBoolean( LoggerMap.DISABLEBLANKING, false ); PowerManager pm = (PowerManager) this.getSystemService( Context.POWER_SERVICE ); if( this.mWakeLock == null ) { this.mWakeLock = pm.newWakeLock( PowerManager.SCREEN_DIM_WAKE_LOCK, TAG ); } if( disableblanking && this.mLoggerServiceManager.isLogging() && !this.mWakeLock.isHeld() ) { this.mWakeLock.acquire(); } else { resumeBlanking(); } } private void updateSpeedbarVisibility( int trackColoringMethod ) { ContentResolver resolver = this.getApplicationContext().getContentResolver(); Cursor waypointsCursor = null ; try { waypointsCursor = resolver.query ( Uri.withAppendedPath( Tracks.CONTENT_URI, this.mTrackId+"/waypoints" ) , new String[] { "avg("+Waypoints.SPEED+")" } , null , null , null ); if( waypointsCursor != null && waypointsCursor.moveToLast() ) { mAverageSpeed = waypointsCursor.getDouble( 0 ); if( mAverageSpeed == 0 ) { mAverageSpeed = 33.33d/2d; } } } finally { if( waypointsCursor != null ) { waypointsCursor.close(); } } View speedbar = findViewById( R.id.speedbar ); if( trackColoringMethod == TrackingOverlay.DRAW_MEASURED || trackColoringMethod == TrackingOverlay.DRAW_CALCULATED ) { drawSpeedTexts( mAverageSpeed ); speedbar.setVisibility( View.VISIBLE ); for( int i=0 ; i<mSpeedtexts.length ; i++ ) { mSpeedtexts[i].setVisibility( View.VISIBLE ); } } else { speedbar.setVisibility( View.INVISIBLE ); for( int i=0 ; i<mSpeedtexts.length ; i++ ) { mSpeedtexts[i].setVisibility( View.INVISIBLE ); } } } /** * For the current track identifier the route of that track is drawn * by adding a OverLay for each segments in the track * * @param trackId * @see TrackingOverlay */ private void createTrackingDataOverlays() { List<Overlay> overlays = this.mMapView.getOverlays(); overlays.clear(); ContentResolver resolver = this.getApplicationContext().getContentResolver(); Cursor segments = null ; int trackColoringMethod = new Integer( PreferenceManager.getDefaultSharedPreferences( this ).getString( TrackingOverlay.TRACKCOLORING, "3" ) ).intValue(); updateSpeedbarVisibility( trackColoringMethod ); Cursor trackCursor = null ; try { trackCursor = resolver.query ( ContentUris.withAppendedId( Tracks.CONTENT_URI, this.mTrackId ) , new String[] { Tracks.NAME } , null , null , null ); if( trackCursor.moveToLast() ) { String name = trackCursor.getString( 0 ); drawToTrackName( name ); } } finally { if( trackCursor != null ) { trackCursor.close(); } } GeoPoint lastPoint = null; try { Uri segmentsUri = Uri.withAppendedPath( Tracks.CONTENT_URI, this.mTrackId+"/segments" ); segments = resolver.query( segmentsUri, new String[] { Segments._ID }, null, null, null ); if(segments.moveToFirst()) { do { long segmentsId = segments.getLong( 0 ); Uri segmentUri = Uri.withAppendedPath( segmentsUri, segmentsId+"/waypoints" ); TrackingOverlay segmentOverlay = new TrackingOverlay( (Context)this , segmentUri , trackColoringMethod , mAverageSpeed , this.mMapView ); overlays.add( segmentOverlay ); if( segments.isFirst() ) { segmentOverlay.addPlacement( TrackingOverlay.FIRST_SEGMENT ); } if( segments.isLast() ) { segmentOverlay.addPlacement( TrackingOverlay.LAST_SEGMENT ); lastPoint = getLastTrackPoint( this.mTrackId, segmentsId ); } } while( segments.moveToNext()); } } finally { if( segments != null ) { segments.close(); } } if( lastPoint != null ) { Point out = new Point(); this.mMapView.getProjection().toPixels( lastPoint, out ); if( out.x < this.mMapView.getWidth()/4 || out.y < this.mMapView.getHeight()/4 || out.x > (this.mMapView.getWidth()/4)*3 || out.y > (this.mMapView.getHeight()/4)*3 ) { this.mMapView.getController().animateTo( lastPoint ); } } this.mMapView.postInvalidate(); } /** * * @param avgSpeed avgSpeed in m/s */ private void drawSpeedTexts( double avgSpeed ) { TypedValue outValue = new TypedValue(); this.getResources().getValue( R.raw.conversion_from_mps, outValue, false ) ; float conversion_from_mps = outValue.getFloat(); String unit = this.getResources().getString( R.string.speed_unitname ); avgSpeed = avgSpeed * conversion_from_mps; for( int i=0 ; i<mSpeedtexts.length ; i++ ) { mSpeedtexts[i].setVisibility( View.VISIBLE ); int speed = (int) ((avgSpeed*2d)/5d)*i; mSpeedtexts[i].setText( speed+unit ); } } /** * Retrieve the last point of the current track * * @param context */ private GeoPoint getLastTrackPoint( long trackId, long segmentId ) { Cursor waypoint = null; GeoPoint lastPoint = null; try { ContentResolver resolver = this.getContentResolver(); waypoint = resolver.query( Uri.withAppendedPath( Tracks.CONTENT_URI, trackId+"/segments/"+segmentId+"/waypoints" ), new String[] { Waypoints.LATITUDE, Waypoints.LONGITUDE, "max("+Waypoints._ID+")" }, null, null, null ); boolean exists = waypoint.moveToLast(); if( exists ) { int microLatitude = (int) ( waypoint.getDouble( 0 ) * 1E6d ); int microLongitude = (int) ( waypoint.getDouble( 1 ) * 1E6d ); lastPoint = new GeoPoint(microLatitude, microLongitude); } } finally { if( waypoint != null ) { waypoint.close(); } } return lastPoint; } /** * * Alter this to set a new track as current. * * @param trackId * @return if the switch to the new track succeeded */ private boolean attempToMoveToTrack( long trackId ) { boolean exists; if( trackId >= 0 ) { Cursor track = null; try{ ContentResolver resolver = this.getApplicationContext().getContentResolver(); Uri trackUri = ContentUris.withAppendedId( Tracks.CONTENT_URI, trackId ) ; track = resolver.query( trackUri, new String[] { Tracks.NAME }, null, null, null ); exists = track.moveToFirst(); if( exists ) { this.mTrackId = trackId ; drawToTrackName(track.getString( 0 )); resolver.unregisterContentObserver( this.mTrackObserver ); resolver.registerContentObserver( trackUri, false, this.mTrackObserver ); createTrackingDataOverlays(); } } finally { if( track != null ) { track.close(); } } } else { exists = false; } return exists; } /** * * Alter the view to display the title with track information */ private void drawToTrackName(String trackName) { this.setTitle( this.getString( R.string.app_name ) + ": " + trackName); } /** * Get the last know position from the GPS provider and * return that information wrapped in a GeoPoint * to which the Map can navigate. * * @see GeoPoint * * @return */ private GeoPoint getLastKnowGeopointLocation() { LocationManager locationManager = (LocationManager) this.getApplication().getSystemService(Context.LOCATION_SERVICE) ; Location location = locationManager.getLastKnownLocation( LocationManager.GPS_PROVIDER ); int microLatitude = 0 ; int microLongitude = 0 ; if( location != null ) { microLatitude = (int) (location.getLatitude() * 1E6d); microLongitude = (int) (location.getLongitude() * 1E6d); } GeoPoint geoPoint = new GeoPoint(microLatitude, microLongitude); return geoPoint; } private int moveToLastTrack() { int trackId = 0; Cursor track = null; try { ContentResolver resolver = this.getApplicationContext().getContentResolver(); track = resolver.query( Tracks.CONTENT_URI, new String[] { "max("+Tracks._ID+")", Tracks.NAME, }, null, null, null ); if(track.moveToLast()) { attempToMoveToTrack( this.mTrackId ); } } finally { if( track != null ) { track.close(); } } return trackId; } public static Dialog createTrackTitleDialog( Activity ctx, View view, DialogInterface.OnClickListener positiveListener) { Builder builder = new AlertDialog.Builder( ctx ) .setTitle( R.string.dialog_routename_title ) .setMessage( R.string.dialog_routename_message ) .setIcon( android.R.drawable.ic_dialog_alert ) .setView( view ) .setPositiveButton(R.string.btn_okay, positiveListener); Dialog dialog = builder.create(); dialog.setOwnerActivity( ctx ); return dialog; } private static Dialog createAlertNoTrack( Activity ctx, DialogInterface.OnClickListener positiveListener, DialogInterface.OnClickListener negativeListener ) { Builder builder = new AlertDialog.Builder( ctx ) .setTitle( R.string.dialog_notrack_title ) .setMessage(R.string.dialog_notrack_message ) .setIcon( android.R.drawable.ic_dialog_alert ) .setPositiveButton( R.string.btn_selecttrack, positiveListener ) .setNegativeButton( R.string.btn_cancel, negativeListener ); Dialog dialog = builder.create(); dialog.setOwnerActivity( ctx ); return dialog; } }