/* * Copyright (C) 2012, Katy Hilgenberg. * Special acknowledgments to: Knowledge & Data Engineering Group, University of Kassel (http://www.kde.cs.uni-kassel.de). * Contact: sdcf@cs.uni-kassel.de * * This file is part of the SDCFramework (Sensor Data Collection Framework) project. * * The SDCFramework is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The SDCFramework 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the SDCFramework. If not, see <http://www.gnu.org/licenses/>. */ package de.unikassel.android.sdcframework.devices; import android.content.Context; import android.location.Location; import android.location.LocationManager; import de.unikassel.android.sdcframework.data.Sample; import de.unikassel.android.sdcframework.data.independent.LocationSampleData; import de.unikassel.android.sdcframework.data.independent.SampleData; import de.unikassel.android.sdcframework.util.LifeCycleObjectImpl; import de.unikassel.android.sdcframework.util.Logger; import de.unikassel.android.sdcframework.util.facade.EventObserver; import de.unikassel.android.sdcframework.util.facade.ObservableEventSource; /** * Class to track location information using available sensors. * * @author Katy Hilgenberg * */ public final class LocationTracker extends LifeCycleObjectImpl implements EventObserver< Sample > { /** * The time difference for significance */ private static final int SIGNIFICANT_TIME_DIFF = 0; /** * The current Location */ private Location currentLocation; /** * Enabled flag */ private boolean enabled; /** * the application context */ private Context context; /** * Constructor */ public LocationTracker() {} /* (non-Javadoc) * @see de.unikassel.android.sdcframework.util.LifeCycleObjectImpl#onCreate(android.content.Context) */ @Override public final void onCreate( Context applicationContext ) { this.context = applicationContext; super.onCreate( applicationContext ); } /** * Method to get the last known location */ private void getLastKnownLocation() { if( context == null ) return; // try to get last know location LocationManager locationManager = (LocationManager) context.getSystemService( Context.LOCATION_SERVICE ); for ( String provider : locationManager.getProviders( true ) ) { Location lastKnownLocation = locationManager.getLastKnownLocation( provider ); if ( isBetterLocation( lastKnownLocation, getCurrentLocation() ) ) { setCurrentLocation( lastKnownLocation ); } } Logger.getInstance().debug( this, "requested last known location!" ); } /** * Determines whether one Location reading is better than the current Location * fix ( source: * http://developer.android.com/guide/topics/location/obtaining-user * -location.html ) * * @param location * the new location * @param currentBestLocation * the current location */ private final static boolean isBetterLocation( Location location, Location currentBestLocation ) { // if the new location is invalid return false; if ( location == null ) { return false; } if ( currentBestLocation == null ) { // A new location is always better than no location return true; } // Check whether the new location fix is newer or older long timeDelta = location.getTime() - currentBestLocation.getTime(); boolean isSignificantlyNewer = timeDelta > SIGNIFICANT_TIME_DIFF; boolean isSignificantlyOlder = timeDelta < -SIGNIFICANT_TIME_DIFF; boolean isNewer = timeDelta > 0; // If it's been more than two minutes since the current location, use the // new location // because the user has likely moved if ( isSignificantlyNewer ) { return true; // If the new location is more than two minutes older, it must be worse } else if ( isSignificantlyOlder ) { return false; } // Check whether the new location fix is more or less accurate int accuracyDelta = (int) ( location.getAccuracy() - currentBestLocation.getAccuracy() ); boolean isLessAccurate = accuracyDelta > 0; boolean isMoreAccurate = accuracyDelta < 0; boolean isSignificantlyLessAccurate = accuracyDelta > 200; // Check if the old and new location are from the same provider boolean isFromSameProvider = isSameProvider( location.getProvider(), currentBestLocation.getProvider() ); // Determine location quality using a combination of timeliness and accuracy if ( isMoreAccurate ) { return true; } else if ( isNewer && !isLessAccurate ) { return true; } else if ( isNewer && !isSignificantlyLessAccurate && isFromSameProvider ) { return true; } return false; } /** * Method to check for the same provider * * @param provider1 * first provider * @param provider2 * second provider * @return true if it is the same provider */ private final static boolean isSameProvider( String provider1, String provider2 ) { if ( provider1 == null ) { return provider2 == null; } return provider1.equals( provider2 ); } /* * (non-Javadoc) * * @see * de.unikassel.android.sdcframework.util.facade.EventObserver#onEvent(de. * unikassel.android.sdcframework.util.facade.ObservableEventSource, * de.unikassel.android.sdcframework.util.facade.ObservableEvent) */ @Override public final void onEvent( ObservableEventSource< ? extends Sample > eventSource, Sample observedEvent ) { Location newLocation = locationFromSample( observedEvent ); if ( isBetterLocation( newLocation, getCurrentLocation() ) ) { setCurrentLocation( newLocation ); } } /** * Method to create a location object from a location sample * * @param sample * the sample * @return the converted location sample or null */ private final Location locationFromSample( Sample sample ) { Location location = null; SampleData data = sample.getData(); // test for correct sample data type if ( data instanceof LocationSampleData ) { LocationSampleData locData = (LocationSampleData) data; location = new Location( sample.getDeviceIdentifier() ); location.setTime( sample.getTimeStamp() ); location.setLatitude( locData.getLatitude() ); location.setLongitude( locData.getLongitude() ); if ( locData.getAltitude() != null ) { location.setAltitude( locData.getAltitude() ); } if ( locData.getAccuracy() != null ) { location.setAccuracy( locData.getAccuracy() ); } if ( locData.getSpeed() != null ) { location.setSpeed( locData.getSpeed() ); } } return location; } /** * Setter for the currentLocation * * @param currentLocation * the currentLocation to set */ private final synchronized void setCurrentLocation( Location currentLocation ) { this.currentLocation = currentLocation; } /** * Getter for the currentLocation * * @return the currentLocation */ public final synchronized Location getCurrentLocation() { if( isEnabled() ) return currentLocation; return null; } /** * Setter for the enabled * @param enabled the enabled to set */ public synchronized void setEnabled( boolean enabled ) { if( enabled && this.enabled == false ) { getLastKnownLocation(); } this.enabled = enabled; } /** * Getter for the enabled * @return the enabled */ public synchronized boolean isEnabled() { return enabled; } }