1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.server.location; 18 19 import android.content.Context; 20 import android.location.Address; 21 import android.location.Country; 22 import android.location.Geocoder; 23 import android.location.Location; 24 import android.location.LocationListener; 25 import android.location.LocationManager; 26 import android.os.Bundle; 27 import android.util.Slog; 28 29 import java.io.IOException; 30 import java.util.ArrayList; 31 import java.util.List; 32 import java.util.Timer; 33 import java.util.TimerTask; 34 35 /** 36 * This class detects which country the user currently is in through the enabled 37 * location providers and the GeoCoder 38 * <p> 39 * Use {@link #detectCountry} to start querying. If the location can not be 40 * resolved within the given time, the last known location will be used to get 41 * the user country through the GeoCoder. The IllegalStateException will be 42 * thrown if there is a ongoing query. 43 * <p> 44 * The current query can be stopped by {@link #stop()} 45 * 46 * @hide 47 */ 48 public class LocationBasedCountryDetector extends CountryDetectorBase { 49 private final static String TAG = "LocationBasedCountryDetector"; 50 private final static long QUERY_LOCATION_TIMEOUT = 1000 * 60 * 5; // 5 mins 51 52 /** 53 * Used for canceling location query 54 */ 55 protected Timer mTimer; 56 57 /** 58 * The thread to query the country from the GeoCoder. 59 */ 60 protected Thread mQueryThread; 61 protected List<LocationListener> mLocationListeners; 62 63 private LocationManager mLocationManager; 64 private List<String> mEnabledProviders; 65 66 public LocationBasedCountryDetector(Context ctx) { 67 super(ctx); 68 mLocationManager = (LocationManager) ctx.getSystemService(Context.LOCATION_SERVICE); 69 } 70 71 /** 72 * @return the ISO 3166-1 two letters country code from the location 73 */ 74 protected String getCountryFromLocation(Location location) { 75 String country = null; 76 Geocoder geoCoder = new Geocoder(mContext); 77 try { 78 List<Address> addresses = geoCoder.getFromLocation( 79 location.getLatitude(), location.getLongitude(), 1); 80 if (addresses != null && addresses.size() > 0) { 81 country = addresses.get(0).getCountryCode(); 82 } 83 } catch (IOException e) { 84 Slog.w(TAG, "Exception occurs when getting country from location"); 85 } 86 return country; 87 } 88 89 protected boolean isAcceptableProvider(String provider) { 90 // We don't want to actively initiate a location fix here (with gps or network providers). 91 return LocationManager.PASSIVE_PROVIDER.equals(provider); 92 } 93 94 /** 95 * Register a listener with a provider name 96 */ 97 protected void registerListener(String provider, LocationListener listener) { 98 mLocationManager.requestLocationUpdates(provider, 0, 0, listener); 99 } 100 101 /** 102 * Unregister an already registered listener 103 */ 104 protected void unregisterListener(LocationListener listener) { 105 mLocationManager.removeUpdates(listener); 106 } 107 108 /** 109 * @return the last known location from all providers 110 */ 111 protected Location getLastKnownLocation() { 112 List<String> providers = mLocationManager.getAllProviders(); 113 Location bestLocation = null; 114 for (String provider : providers) { 115 Location lastKnownLocation = mLocationManager.getLastKnownLocation(provider); 116 if (lastKnownLocation != null) { 117 if (bestLocation == null || 118 bestLocation.getElapsedRealtimeNanos() < 119 lastKnownLocation.getElapsedRealtimeNanos()) { 120 bestLocation = lastKnownLocation; 121 } 122 } 123 } 124 return bestLocation; 125 } 126 127 /** 128 * @return the timeout for querying the location. 129 */ 130 protected long getQueryLocationTimeout() { 131 return QUERY_LOCATION_TIMEOUT; 132 } 133 134 protected List<String> getEnabledProviders() { 135 if (mEnabledProviders == null) { 136 mEnabledProviders = mLocationManager.getProviders(true); 137 } 138 return mEnabledProviders; 139 } 140 141 /** 142 * Start detecting the country. 143 * <p> 144 * Queries the location from all location providers, then starts a thread to query the 145 * country from GeoCoder. 146 */ 147 @Override 148 public synchronized Country detectCountry() { 149 if (mLocationListeners != null) { 150 throw new IllegalStateException(); 151 } 152 // Request the location from all enabled providers. 153 List<String> enabledProviders = getEnabledProviders(); 154 int totalProviders = enabledProviders.size(); 155 if (totalProviders > 0) { 156 mLocationListeners = new ArrayList<LocationListener>(totalProviders); 157 for (int i = 0; i < totalProviders; i++) { 158 String provider = enabledProviders.get(i); 159 if (isAcceptableProvider(provider)) { 160 LocationListener listener = new LocationListener () { 161 @Override 162 public void onLocationChanged(Location location) { 163 if (location != null) { 164 LocationBasedCountryDetector.this.stop(); 165 queryCountryCode(location); 166 } 167 } 168 @Override 169 public void onProviderDisabled(String provider) { 170 } 171 @Override 172 public void onProviderEnabled(String provider) { 173 } 174 @Override 175 public void onStatusChanged(String provider, int status, Bundle extras) { 176 } 177 }; 178 mLocationListeners.add(listener); 179 registerListener(provider, listener); 180 } 181 } 182 183 mTimer = new Timer(); 184 mTimer.schedule(new TimerTask() { 185 @Override 186 public void run() { 187 mTimer = null; 188 LocationBasedCountryDetector.this.stop(); 189 // Looks like no provider could provide the location, let's try the last 190 // known location. 191 queryCountryCode(getLastKnownLocation()); 192 } 193 }, getQueryLocationTimeout()); 194 } else { 195 // There is no provider enabled. 196 queryCountryCode(getLastKnownLocation()); 197 } 198 return mDetectedCountry; 199 } 200 201 /** 202 * Stop the current query without notifying the listener. 203 */ 204 @Override 205 public synchronized void stop() { 206 if (mLocationListeners != null) { 207 for (LocationListener listener : mLocationListeners) { 208 unregisterListener(listener); 209 } 210 mLocationListeners = null; 211 } 212 if (mTimer != null) { 213 mTimer.cancel(); 214 mTimer = null; 215 } 216 } 217 218 /** 219 * Start a new thread to query the country from Geocoder. 220 */ 221 private synchronized void queryCountryCode(final Location location) { 222 if (location == null) { 223 notifyListener(null); 224 return; 225 } 226 if (mQueryThread != null) return; 227 mQueryThread = new Thread(new Runnable() { 228 @Override 229 public void run() { 230 String countryIso = null; 231 if (location != null) { 232 countryIso = getCountryFromLocation(location); 233 } 234 if (countryIso != null) { 235 mDetectedCountry = new Country(countryIso, Country.COUNTRY_SOURCE_LOCATION); 236 } else { 237 mDetectedCountry = null; 238 } 239 notifyListener(mDetectedCountry); 240 mQueryThread = null; 241 } 242 }); 243 mQueryThread.start(); 244 } 245 } 246