package de.tum.in.tumcampusapp.managers;
import android.Manifest;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.location.Location;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.common.base.Optional;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import de.tum.in.tumcampusapp.auxiliary.Const;
import de.tum.in.tumcampusapp.auxiliary.Utils;
import de.tum.in.tumcampusapp.models.cafeteria.Cafeteria;
import de.tum.in.tumcampusapp.models.tumo.Geo;
import de.tum.in.tumcampusapp.tumonline.TUMRoomFinderRequest;
/**
* Location manager, manages intelligent location services, provides methods to easily access
* the users current location, campus, next public transfer station and best cafeteria
*/
public class LocationManager {
private static final double[][] CAMPUS_LOCATIONS = {
{48.2648424, 11.6709511}, // Garching Forschungszentrum
{48.249432, 11.633905}, // Garching Hochbrück
{48.397990, 11.722727}, // Weihenstephan
{48.149436, 11.567635}, // Stammgelände
{48.110847, 11.4703001}, // Klinikum Großhadern
{48.137539, 11.601119}, // Klinikum rechts der Isar
{48.155916, 11.583095}, // Leopoldstraße
{48.150244, 11.580665} // Geschwister Schollplatz/Adalbertstraße
};
private static final String[] CAMPUS_SHORT = {
"G", // Garching Forschungszentrum
"H", // Garching Hochbrück
"W", // Weihenstephan
"C", // Stammgelände
"K", // Klinikum Großhadern
"I", // Klinikum rechts der Isar
"L", // Leopoldstraße
"S" // Geschwister Schollplatz/Adalbertstraße
};
private static final String[] DEFAULT_CAMPUS_STATION = {
"Garching-Forschungszentrum",
"Garching-Hochbrück",
"Weihenstephan",
"Theresienstraße",//TODO need to use id instead of name, otherwise it does not work = 1000120
"Klinikum Großhadern",
"Max-Weber-Platz",
"Giselastraße",
"Universität"
};
private static final String[] DEFAULT_CAMPUS_CAFETERIA = {"422", null, "423", "421", "414", null, "411", null};
private final Context mContext;
public LocationManager(Context c) {
mContext = c;
}
/**
* Tests if Google Play services is available and than gets last known position
*
* @return Returns the more or less current position or null on failure
*/
Location getCurrentLocation() {
if (servicesConnected()) {
Location loc = getLastLocation();
if (loc != null) {
return loc;
}
}
// If location services are not available use default location if set
final String defaultCampus = Utils.getSetting(mContext, Const.DEFAULT_CAMPUS, "G");
if (!"X".equals(defaultCampus)) {
for (int i = 0; i < CAMPUS_SHORT.length; i++) {
if (CAMPUS_SHORT[i].equals(defaultCampus)) {
Location location = new Location("defaultLocation");
location.setLatitude(CAMPUS_LOCATIONS[i][0]);
location.setLongitude(CAMPUS_LOCATIONS[i][1]);
return location;
}
}
}
return null;
}
/**
* Returns the "id" of the current campus
*
* @return Campus id
*/
int getCurrentCampus() {
Location loc = getCurrentLocation();
if (loc == null) {
return -1;
}
return getCampusFromLocation(loc);
}
/**
* Returns the "id" of the campus near the given location
* The used radius around the middle of the campus is 1km.
*
* @param location The location to search for a campus
* @return Campus id
*/
private static int getCampusFromLocation(Location location) {
final double lat = location.getLatitude();
final double lng = location.getLongitude();
float[] results = new float[1];
float bestDistance = Float.MAX_VALUE;
int bestCampus = -1;
for (int i = 0; i < CAMPUS_LOCATIONS.length; i++) {
Location.distanceBetween(CAMPUS_LOCATIONS[i][0], CAMPUS_LOCATIONS[i][1], lat, lng, results);
float distance = results[0];
if (distance < bestDistance) {
bestDistance = distance;
bestCampus = i;
}
}
if (bestDistance < 1000) {
return bestCampus;
} else {
return -1;
}
}
/**
* Returns the cafeteria's identifier which is near the given location
* The used radius around the cafeteria is 1km.
*
* @return Campus id
*/
public List<Cafeteria> getCafeterias() {
// Get current location
Location location = getCurrentOrNextLocation();
final double lat = location.getLatitude();
final double lng = location.getLongitude();
float[] results = new float[1];
CafeteriaManager manager = new CafeteriaManager(mContext);
Cursor cur = manager.getAllFromDb();
List<Cafeteria> list = new ArrayList<>(cur.getCount());
if (cur.moveToFirst()) {
do {
Cafeteria cafe = new Cafeteria(cur.getInt(0), cur.getString(1),
cur.getString(2), cur.getDouble(3), cur.getDouble(4));
Location.distanceBetween(cur.getDouble(3), cur.getDouble(4), lat, lng, results);
cafe.distance = results[0];
list.add(cafe);
} while (cur.moveToNext());
}
cur.close();
Collections.sort(list);
return list;
}
/**
* Gets the current location and if it is not available guess
* by querying for the next lecture.
*
* @return Any of the above described locations.
*/
private
@NonNull
Location getCurrentOrNextLocation() {
Location l = getCurrentLocation();
if (l != null) {
return l;
}
return getNextLocation();
}
/**
* Returns the last known location of the device
*
* @return The last location
*/
Location getLastLocation() {
//Check Location permission for Android 6.0
if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return null;
}
Location bestResult = null;
float bestAccuracy = Float.MAX_VALUE;
long bestTime = Long.MIN_VALUE;
long minTime = 0;
android.location.LocationManager locationManager = (android.location.LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
List<String> matchingProviders = locationManager.getAllProviders();
for (String provider : matchingProviders) {
Location location = locationManager.getLastKnownLocation(provider);
if (location != null) {
float accuracy = location.getAccuracy();
long time = location.getTime();
if (time > minTime && accuracy < bestAccuracy) {
bestResult = location;
bestAccuracy = accuracy;
bestTime = time;
} else if (time < minTime &&
bestAccuracy == Float.MAX_VALUE && time > bestTime) {
bestResult = location;
bestTime = time;
}
}
}
return bestResult;
}
/**
* Returns the name of the station that is nearby and/or set by the user
*
* @return Name of the station or null if the user is not near any campus
*/
public String getStation() {
int campus = getCurrentCampus();
if (campus == -1) {
return null;
}
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
final String defaultVal = DEFAULT_CAMPUS_STATION[campus];
return prefs.getString("card_stations_default_" + CAMPUS_SHORT[campus], defaultVal);
}
/**
* Checks that Google Play services are available
*/
private boolean servicesConnected() {
int resultCode = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(mContext);
if (ConnectionResult.SUCCESS == resultCode) {
return true;
} else {
Utils.log("Google Play services is NOT available.");
return false;
}
}
/**
* Gets the campus you are currently on or if you are at home or wherever
* query for your next lecture and find out at which campus it takes place
*/
int getCurrentOrNextCampus() {
int campus = getCurrentCampus();
if (campus != -1) {
return campus;
}
return getNextCampus();
}
/**
* Provides some intelligence to pick one cafeteria to show
*/
public int getCafeteria() {
int campus = getCurrentOrNextCampus();
if (campus != -1) { // If the user is in university or a lecture has been recognized
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
final String defaultVal = DEFAULT_CAMPUS_CAFETERIA[campus];
String cafeteria = prefs.getString("card_cafeteria_default_" + CAMPUS_SHORT[campus], defaultVal);
if (cafeteria != null) {
return Integer.parseInt(cafeteria);
}
}
// Get nearest cafeteria
List<Cafeteria> list = getCafeterias();
if (list == null || list.isEmpty()) {
return -1;
}
return list.get(0).id;
}
/**
* Queries your calender and gets the campus at which your next lecture takes place
*/
int getNextCampus() {
return getCampusFromLocation(getNextLocation());
}
/**
* Gets the location of the next room where the user has a lecture.
* If no lectures are available Garching will be returned
*
* @return Location of the next lecture room
*/
private Location getNextLocation() {
CalendarManager manager = new CalendarManager(mContext);
Geo geo = manager.getNextCalendarItemGeo();
Location location = new Location("roomfinder");
if (geo == null) {
location.setLatitude(48.2648424);
location.setLongitude(11.6709511);
} else {
location.setLatitude(Double.parseDouble(geo.getLatitude()));
location.setLongitude(Double.parseDouble(geo.getLongitude()));
}
return location;
}
/**
* Translates room title to Geo
* HINT: Don't call from UI thread
*
* @param roomTitle Room title
* @return Location or null on failure
*/
public Optional<Geo> roomLocationStringToGeo(String roomTitle) {
String loc = roomTitle;
TUMRoomFinderRequest requestHandler = new TUMRoomFinderRequest(mContext);
if (loc.contains("(")) {
loc = loc.substring(0, loc.indexOf('(')).trim();
}
List<Map<String, String>> request = requestHandler.fetchRooms(loc);
if (request != null && !request.isEmpty()) {
String room = request.get(0).get(TUMRoomFinderRequest.KEY_ARCH_ID);
return requestHandler.fetchCoordinates(room);
}
return Optional.absent();
}
}