From 29fce40e560a11d34b73169567284ed95a9ac179 Mon Sep 17 00:00:00 2001 From: Alex Zolotarev Date: Thu, 15 Dec 2011 12:46:10 +0300 Subject: [PATCH] [android] Added WiFi location support for Kindle Fire, fixed location bugs --- android/AndroidManifest.xml | 3 + .../src/com/mapswithme/maps/MWMActivity.java | 12 +- .../maps/location/LocationService.java | 46 ++++-- .../maps/location/WifiLocation.java | 142 ++++++++++++++++++ .../com/mapswithme/util/ConnectionState.java | 54 +++++++ 5 files changed, 242 insertions(+), 15 deletions(-) create mode 100644 android/src/com/mapswithme/maps/location/WifiLocation.java create mode 100644 android/src/com/mapswithme/util/ConnectionState.java diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 1d38f3aef5..4dfabe4fd9 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -14,6 +14,9 @@ + + + m_observers = new HashSet(2); + // Used to filter locations from different providers private Location m_lastLocation = null; + private WifiLocation m_wifiScanner = null; + private LocationManager m_locationManager; private SensorManager m_sensorManager; private Sensor m_compassSensor; @@ -77,23 +81,34 @@ public class LocationService implements LocationListener, SensorEventListener while (it.hasNext()) it.next().onCompassUpdated(time, magneticNorth, trueNorth, accuracy); } - - public boolean isSubscribed(Listener observer) - { - return m_observers.contains(observer); - } - public void startUpdate(Listener observer) + public void startUpdate(Listener observer, Context c) { m_observers.add(observer); if (!m_isActive) { - // @TODO Add WiFi provider - final List enabledProviders = m_locationManager.getProviders(true); + List enabledProviders = m_locationManager.getProviders(true); + // Remove passive provider, we don't use it in the current implementation + for (int i = 0; i < enabledProviders.size(); ++i) + if (enabledProviders.get(i).equals(LocationManager.PASSIVE_PROVIDER)) + { + enabledProviders.remove(i); + break; + } if (enabledProviders.size() == 0) { - observer.onLocationStatusChanged(DISABLED_BY_USER); + // Use WiFi BSSIDS and Google Internet location service if no other options are available + // But only if connection is available + if (com.mapswithme.util.ConnectionState.isConnected(c)) + { + observer.onLocationStatusChanged(STARTED); + if (m_wifiScanner == null) + m_wifiScanner = new WifiLocation(); + m_wifiScanner.StartScan(c, this); + } + else + observer.onLocationStatusChanged(DISABLED_BY_USER); } else { @@ -108,7 +123,9 @@ public class LocationService implements LocationListener, SensorEventListener m_locationManager.requestLocationUpdates(provider, 0, 0, this); // Send last known location for faster startup. // It should pass filter in the handler below. - onLocationChanged(m_locationManager.getLastKnownLocation(provider)); + final Location lastKnown = m_locationManager.getLastKnownLocation(provider); + if (lastKnown != null) + onLocationChanged(lastKnown); } } if (m_sensorManager != null) @@ -253,4 +270,11 @@ public class LocationService implements LocationListener, SensorEventListener { Log.d(TAG, String.format("Status changed for location provider: %s to %d", provider, status)); } + + @Override + public void onWifiLocationUpdated(Location l) + { + if (l != null) + onLocationChanged(l); + } } diff --git a/android/src/com/mapswithme/maps/location/WifiLocation.java b/android/src/com/mapswithme/maps/location/WifiLocation.java new file mode 100644 index 0000000000..c559366de7 --- /dev/null +++ b/android/src/com/mapswithme/maps/location/WifiLocation.java @@ -0,0 +1,142 @@ +package com.mapswithme.maps.location; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.util.List; + +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONTokener; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.location.Location; +import android.net.wifi.ScanResult; +import android.net.wifi.WifiManager; + +public class WifiLocation extends BroadcastReceiver +{ + private final String MWM_GEOLOCATION_SERVER = "http://geolocation.server/"; + + public interface Listener + { + public void onWifiLocationUpdated(Location l); + } + // @TODO support multiple listeners + private Listener m_observer = null; + + private WifiManager m_wifi = null; + + public WifiLocation() + { + } + + // @TODO support multiple listeners + // Returns true if was started successfully + public boolean StartScan(Context c, Listener l) + { + m_observer = l; + if (m_wifi == null) + { + m_wifi = (WifiManager) c.getSystemService(Context.WIFI_SERVICE); + c.registerReceiver(this, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)); + return m_wifi.startScan(); + } + return true; + } + + @Override + public void onReceive(Context c, Intent intent) + { + c.unregisterReceiver(this); + + // Prepare JSON request with BSSIDs + StringBuilder json = new StringBuilder("{\"version\":\"1.1.0\""); + + boolean wifiHeaderAdded = false; + List results = m_wifi.getScanResults(); + for (ScanResult r : results) + { + if (r.BSSID != null) + { + if (!wifiHeaderAdded) + { + json.append(",\"wifi_towers\":["); + wifiHeaderAdded = true; + } + json.append("{\"mac_address\":\""); + json.append(r.BSSID); + json.append("\",\"ssid\":\""); + json.append(r.SSID == null ? " " : r.SSID); + json.append("\",\"signal_strength\":"); + json.append(String.valueOf(r.level)); + json.append("},"); + } + } + if (wifiHeaderAdded) + { + json.deleteCharAt(json.length() - 1); + json.append("]"); + } + json.append("}"); + + // Result for Listener + Location location = null; + + // Send http POST to google location service + URL url; + OutputStreamWriter wr = null; + BufferedReader rd = null; + try + { + url = new URL(MWM_GEOLOCATION_SERVER); + URLConnection conn = url.openConnection(); + conn.setDoOutput(true); + wr = new OutputStreamWriter(conn.getOutputStream()); + wr.write(json.toString()); + wr.flush(); + // Get the response + rd = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8")); + String line = null; + String response = ""; + while ((line = rd.readLine()) != null) { + response += line; + } + + final JSONObject jRoot = new JSONObject(response); + final JSONObject jLocation = jRoot.getJSONObject("location"); + final double lat = jLocation.getDouble("latitude"); + final double lon = jLocation.getDouble("longitude"); + final double acc = jLocation.getDouble("accuracy"); + + location = new Location("wifiscanner"); + location.setAccuracy((float)acc); + location.setLatitude(lat); + location.setLongitude(lon); + location.setTime(java.lang.System.currentTimeMillis()); + + wr.close(); + rd.close(); + } catch (MalformedURLException e) + { + e.printStackTrace(); + } catch (IOException e) + { + e.printStackTrace(); + } catch (JSONException e) + { + e.printStackTrace(); + } + + if (m_observer != null) + m_observer.onWifiLocationUpdated(location); + m_wifi = null; + } +} diff --git a/android/src/com/mapswithme/util/ConnectionState.java b/android/src/com/mapswithme/util/ConnectionState.java new file mode 100644 index 0000000000..227838d07d --- /dev/null +++ b/android/src/com/mapswithme/util/ConnectionState.java @@ -0,0 +1,54 @@ +package com.mapswithme.util; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; + +public class ConnectionState +{ + public static final int NOT_CONNECTED = 0; + public static final int CONNECTED_BY_3G = 1; + public static final int CONNECTED_BY_WIFI = 2; + public static final int CONNECTED_BY_WIFI_AND_3G = CONNECTED_BY_3G & CONNECTED_BY_WIFI; + + public static int getState(Context c) + { + boolean haveConnectedWifi = false; + boolean haveConnectedMobile = false; + + ConnectivityManager cm = (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo[] netInfo = cm.getAllNetworkInfo(); + for (NetworkInfo ni : netInfo) + { + if (ni.getTypeName().equalsIgnoreCase("WIFI")) + if (ni.isConnected()) + haveConnectedWifi = true; + if (ni.getTypeName().equalsIgnoreCase("MOBILE")) + if (ni.isConnected()) + haveConnectedMobile = true; + } + if (haveConnectedWifi && haveConnectedMobile) + return CONNECTED_BY_WIFI_AND_3G; + else if (haveConnectedMobile) + return CONNECTED_BY_3G; + else if (haveConnectedWifi) + return CONNECTED_BY_WIFI; + + return NOT_CONNECTED; + } + + public static boolean is3GConnected(Context c) + { + return (getState(c) & CONNECTED_BY_3G) == CONNECTED_BY_3G; + } + + public static boolean isWifiConnected(Context c) + { + return (getState(c) & CONNECTED_BY_WIFI) == CONNECTED_BY_WIFI; + } + + public static boolean isConnected(Context c) + { + return !(getState(c) == NOT_CONNECTED); + } +}