/* * #%L * Wheelmap - App * %% * Copyright (C) 2011 - 2012 Michal Harakal - Michael Kroez - Sozialhelden e.V. * %% * Wheelmap App based on the Wheelmap Service by Sozialhelden e.V. * * 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. * #L% */ package org.wheelmap.android.manager; import android.content.Context; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; import android.os.Handler; import java.util.List; import de.akquinet.android.androlog.Log; import de.greenrobot.event.EventBus; public class MyLocationManager { private static final String TAG = MyLocationManager.class.getSimpleName(); private static MyLocationManager sInstance; // center of germany public static final double DEFAULT_LATITUDE = 51.28940590271679; public static final double DEFAULT_LONGITUDE = 10.26123046875; private static final long TIME_DISTANCE_LIMIT = 5*60*1000;//TimeUnit.MINUTES.toMillis(5); // 5 Minutes private static final long TIME_GPS_UPDATE_INTERVAL = 1000 * 10; private static final float TIME_GPS_UPDATE_DISTANCE = 20f; private static final long RELEASE_DELAY = 2000; private EventBus mBus; private LocationManager mLocationManager; private MyGPSLocationListener mGPSLocationListener; private MyNetworkLocationListener mNetworkLocationListener; private Location mCurrentBestLocation; private List<String> mProviders; private boolean doesRequestUpdates; private boolean gpsExists; private boolean networkExists; private boolean wasBestLastKnownLocation; private int mSubscriber; private Handler mHandler = new Handler(); public static class LocationEvent { public final Location location; public final boolean isValid; public LocationEvent(Location location, boolean isValid) { this.location = location; this.isValid = isValid; } @Override public String toString() { return "LocationEvent: {isValid: "+isValid+", location:"+ location + "}"; } } public static class RegisterEvent { public static final RegisterEvent INSTANCE = new RegisterEvent(); } public static class UnregisterEvent { public static final UnregisterEvent INSTANCE = new UnregisterEvent(); } private Runnable mReleaseRunnable = new Runnable() { @Override public void run() { releaseLocationUpdates(); } }; private MyLocationManager(Context context) { mBus = EventBus.getDefault(); mBus.register(this); mLocationManager = (LocationManager) context .getSystemService(Context.LOCATION_SERVICE); mProviders = mLocationManager.getAllProviders(); gpsExists = findProvider(LocationManager.GPS_PROVIDER); networkExists = findProvider(LocationManager.NETWORK_PROVIDER); mGPSLocationListener = new MyGPSLocationListener(); mNetworkLocationListener = new MyNetworkLocationListener(); mCurrentBestLocation = calcBestLastKnownLocation(); boolean isValid = true; if (mCurrentBestLocation == null) { isValid = false; mCurrentBestLocation = new Location( LocationManager.NETWORK_PROVIDER); mCurrentBestLocation.setLongitude(DEFAULT_LONGITUDE); mCurrentBestLocation.setLatitude(DEFAULT_LATITUDE); mCurrentBestLocation.setAccuracy(1000 * 1000); } mBus.postSticky(new LocationEvent(mCurrentBestLocation, isValid)); wasBestLastKnownLocation = true; } public static void init(Context context) { if (sInstance == null) { sInstance = new MyLocationManager(context); } } public static void destroy() { sInstance.releaseLocationUpdates(); sInstance = null; } public void onEventMainThread(RegisterEvent e) { Log.d(TAG, "onRegisterEvent: entity registered: " + mSubscriber); mSubscriber = Math.max(mSubscriber, 0); mSubscriber++; if (mSubscriber > 0) { requestLocationUpdates(); mHandler.removeCallbacks(mReleaseRunnable); } } public void onEventMainThread(UnregisterEvent e) { Log.d(TAG, "onUnregisterEvent: entity unregistered: " + mSubscriber); mSubscriber--; mSubscriber = Math.max(mSubscriber, 0); if (mSubscriber > 0) { return; } mHandler.postDelayed(mReleaseRunnable, RELEASE_DELAY); } public static Location getLastLocation() { return sInstance.mCurrentBestLocation; } private boolean findProvider(String find) { for (String provider : mProviders) { if (provider.equals(find)) { return true; } } return false; } protected void requestLocationUpdates() { gpsExists = findProvider(LocationManager.GPS_PROVIDER); networkExists = findProvider(LocationManager.NETWORK_PROVIDER); if (!doesRequestUpdates) { Log.d(TAG, "requestLocationUpdates"); if (gpsExists) { mLocationManager.requestLocationUpdates( LocationManager.GPS_PROVIDER, TIME_GPS_UPDATE_INTERVAL, TIME_GPS_UPDATE_DISTANCE, mGPSLocationListener); } if (networkExists) { mLocationManager.requestLocationUpdates( LocationManager.NETWORK_PROVIDER, 0, 0, mNetworkLocationListener); } doesRequestUpdates = true; } } private void releaseLocationUpdates() { Log.d(TAG, "releaseLocationUpdates"); mLocationManager.removeUpdates(mGPSLocationListener); mLocationManager.removeUpdates(mNetworkLocationListener); doesRequestUpdates = false; } private Location calcBestLastKnownLocation() { Location networkLocation = mLocationManager .getLastKnownLocation(LocationManager.NETWORK_PROVIDER); Location gpsLocation = mLocationManager .getLastKnownLocation(LocationManager.GPS_PROVIDER); long now = System.currentTimeMillis(); if (gpsLocation == null && networkLocation == null) { return null; } else if (gpsLocation == null) { return networkLocation; } else if (networkLocation == null) { return gpsLocation; } else if (now - gpsLocation.getTime() < TIME_DISTANCE_LIMIT) { return gpsLocation; } else if (gpsLocation.getTime() < networkLocation.getTime()) { return gpsLocation; } else { return networkLocation; } } private class MyGPSLocationListener implements LocationListener { @Override public void onLocationChanged(Location location) { Log.d(TAG, "MyGPSLocationListener: location received. Accuracy = " + location.getAccuracy()); if (wasBestLastKnownLocation || isBetterLocation(location, mCurrentBestLocation)) { Log.d(TAG, "gps location superseeds mCurrentBestLocation location"); updateLocation(location); wasBestLastKnownLocation = false; } } @Override public void onProviderDisabled(String provider) { } @Override public void onProviderEnabled(String provider) { } @Override public void onStatusChanged(String provider, int status, Bundle extras) { } } private class MyNetworkLocationListener implements LocationListener { @Override public void onLocationChanged(Location location) { Log.d(TAG, "MyNetworkLocationListener: location received. Accuracy = " + location.getAccuracy()); if (wasBestLastKnownLocation || isBetterLocation(location, mCurrentBestLocation)) { Log.d(TAG, "network location superseeds mCurrentBestLocation location"); updateLocation(location); wasBestLastKnownLocation = false; } } @Override public void onProviderDisabled(String provider) { } @Override public void onProviderEnabled(String provider) { } @Override public void onStatusChanged(String provider, int status, Bundle extras) { } } private void updateLocation(Location location) { Log.d( TAG, "updateLocation: " + location); mCurrentBestLocation = location; mBus.postSticky(new LocationEvent(mCurrentBestLocation, true)); } private static final int TWO_MINUTES = 1000 * 60 * 2; /** * Determines whether one Location reading is better than the current Location fix * * @param location The new Location that you want to evaluate * @param currentBestLocation The current Location fix, to which you want to compare the new * one */ protected boolean isBetterLocation(Location location, Location currentBestLocation) { 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 > TWO_MINUTES; boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES; 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 } // 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; } /** * Checks whether two providers are the same */ private boolean isSameProvider(String provider1, String provider2) { if (provider1 == null) { return provider2 == null; } return provider1.equals(provider2); } }