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