package com.cpiekarski.fourteeners.utils;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.HandlerThread;
import android.os.Looper;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.TreeSet;
/**
* Determines device location, when to update the location, and which mountains are nearby.
*
* Can be unit tested using the {@link #MOCK_PROVIDER} location provider.
*
*/
public class DeviceLocation {
final public String TAG = "DeviceLocation";
/**
* Mock location provider to be used for Unit testing module / system
*/
static public final String MOCK_PROVIDER = LocationManager.GPS_PROVIDER+"Test";
/**
* Only take a GPS update every 1000ms
*/
static public final long MIN_UPDATE_TIME = 0;
static public final int GPS = 0;
static public final int NETWORK = 1;
static public final int MOCK = 2;
public enum LocationType {
GPS, NETWORK, MOCK;
}
private Context mCtx;
private LocationManager mLocationManager;
private LocationListener mLocationListener;
private Location mLastMockLocation;
private Location mLastGPSLocation;
private Location mLastNetworkLocation;
private Location mLastLocation;
private LocationType mType;
private Looper mLooper;
public DeviceLocation(Context ctx) {
mCtx = ctx;
// Acquire a reference to the system Location Manager
mLocationManager = (LocationManager) mCtx.getSystemService(Context.LOCATION_SERVICE);
mLastLocation = mLocationManager.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER);
// Define a listener that responds to location updates
mLocationListener = new LocationListener() {
public void onLocationChanged(Location location) {
// Called when a new location is found by the gps location provider.
SRLOG.v(TAG, "new location is "+location.toString());
SRLOG.v(TAG, "New location came from: "+location.getProvider());
synchronized(this) {
if(location.getProvider().equals(LocationManager.GPS_PROVIDER)) {
mLastGPSLocation = location;
mType = LocationType.GPS;
} else if (location.getProvider().equals(LocationManager.NETWORK_PROVIDER)) {
mLastNetworkLocation = location;
mType = LocationType.NETWORK;
} else if (location.isFromMockProvider()) {
mLastMockLocation = location;
mType = LocationType.MOCK;
}
mLastLocation = location;
}
}
public void onStatusChanged(String provider, int status, Bundle extras) {
SRLOG.v(TAG, "Status Changed " + provider);
}
public void onProviderEnabled(String provider) {
SRLOG.v(TAG, "Provider enabled changed");
}
public void onProviderDisabled(String provider) {
SRLOG.v(TAG, "Provider disabled changed");
}
};
Looper mine = Looper.getMainLooper();
SRLOG.v(TAG, "looper is "+mine);
HandlerThread thread = new HandlerThread("DeviceLocationHandlerThread");
thread.start(); // starts the thread.
mLooper = thread.getLooper();
SRLOG.v(TAG, "new looper is "+mLooper);
// Register the listener with the Location Manager to receive location updates
//mLocationManager.requestLocationUpdates(MOCK_PROVIDER, 0, 0, mLocationListener);
}
public void dumpProviders() {
for(String l : mLocationManager.getAllProviders()) {
SRLOG.v(TAG, l);
}
}
/**
* Start updates from the Network provider only
*/
public void getNetworkUpdates() {
mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, mLocationListener, mLooper);
}
/**
* Start updates from only the GPS provider only
*/
public void getGPSUpdates() {
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, MIN_UPDATE_TIME, 0, mLocationListener, mLooper);
}
/**
* Start updates from any provider on the system
*/
public void getPassiveUpdates() {
mLocationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, 0, 0, mLocationListener, mLooper);
}
/**
* Get a location from only the {@link #MOCK_PROVIDER} provider
*/
public void getMockUpdates() {
SRLOG.v(TAG, "Requesting location updates for mock provider");
mLocationManager.requestLocationUpdates(MOCK_PROVIDER, 0, 0, mLocationListener, mLooper);
}
/**
* Remove the location listener from the {@link #mLocationManager}
*/
public void stopUpdates() {
mLocationManager.removeUpdates(mLocationListener);
}
/**
* Polls LocationManager for last known location.
*
* @return last know location directly from LocationManager
*/
public Location getLastGPSLocation() {
return mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
}
/**
* Polls LocationManager for last mocked location
* @see {@link #MOCK_PROVIDER}
*
* @return Null if never set, Location otherwise
*/
public Location getLastMockLocation() {
return mLocationManager.getLastKnownLocation(MOCK_PROVIDER);
}
/**
* Returns the last known location from the receiver
*
* @return Null if never set, Location otherwise
*/
public synchronized Location getLastUpdateLocation() {
return mLastLocation;
}
public synchronized LocationType getLastLocationType() {
return mType;
}
public Location getLastPassiveLocation() {
return mLocationManager.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER);
}
/**
* Indicates if the device has a GPS provider
*
* @return True if the GPS providers exists
*/
public boolean deviceHasGPS() {
List<String> providers = mLocationManager.getAllProviders();
if(providers.contains(LocationManager.GPS_PROVIDER)) {
return true;
}
return false;
}
/**
* Is the GPS provider enabled on the device?
*
* @return True if enabled
*/
public boolean isGPSEnabled() {
List<String> enabled = mLocationManager.getProviders(true);
if(enabled.contains(LocationManager.GPS_PROVIDER)) {
return true;
}
return false;
}
/**
* Given our last received location, provide a ArrayList of Mountains with max
* size of howMany.
*
* O(n log n) where N is the number of mountains
*
* @param howMany Maximum number of nearest Mountains to return.
* @return sorted list of nearest Mountains
*/
public final ArrayList<Mountain> getNearestMountains(int howMany) {
ArrayList<Mountain> l = new ArrayList<Mountain>();
final Location loc = mLastLocation;
TreeSet<Mountain> mntTree = new TreeSet<Mountain>(new Comparator<Mountain>() {
/**
* an integer < 0 if lhs is less than rhs, 0 if they are equal, and > 0 if lhs is greater than rhs.
*/
@Override
public int compare(Mountain lhs, Mountain rhs) {
float[] r1 = new float[1];
float[] r2 = new float[1];
SRLOG.v(TAG, loc.toString());
SRLOG.v(TAG, lhs.toString());
SRLOG.v(TAG, rhs.toString());
Location.distanceBetween(loc.getLatitude(), loc.getLongitude(), lhs.getLatitude(), lhs.getLongitude(), r1);
Location.distanceBetween(loc.getLatitude(), loc.getLongitude(), rhs.getLatitude(), rhs.getLongitude(), r2);
return r1[0] < r2[0] ? -1 : r1[0] == r2[0] ? 0 : 1;
}
});
SRLOG.d(TAG, "Starting add "+loc.toString());
//add all mountains to the sorted tree array
Mountains mnts = Mountains.getInstance(mCtx);
for(String name: mnts.getAllPeakNames()) {
SRLOG.i(TAG, "Adding "+name);
mntTree.add(mnts.getMountain(name));
}
int i = 0;
for(Mountain m : mntTree) {
if(i < howMany) {
SRLOG.i(TAG, "Adding "+m.getName());
l.add(m);
++i;
} else {
break;
}
}
return l;
}
}