LocationBasedCountryDetector.java revision a58a8751b4c2ce457f0082a0baaee61312d56195
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 java.io.IOException; 20import java.util.ArrayList; 21import java.util.List; 22import java.util.Timer; 23import java.util.TimerTask; 24 25import android.content.Context; 26import android.location.Address; 27import android.location.Country; 28import android.location.Geocoder; 29import android.location.Location; 30import android.location.LocationListener; 31import android.location.LocationManager; 32import android.os.Bundle; 33import android.util.Slog; 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 */ 48public 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 /** 90 * Register the listeners with the location providers 91 */ 92 protected void registerEnabledProviders(List<LocationListener> listeners) { 93 int total = listeners.size(); 94 for (int i = 0; i< total; i++) { 95 mLocationManager.requestLocationUpdates( 96 mEnabledProviders.get(i), 0, 0, listeners.get(i)); 97 } 98 } 99 100 /** 101 * Unregister the listeners with the location providers 102 */ 103 protected void unregisterProviders(List<LocationListener> listeners) { 104 for (LocationListener listener : listeners) { 105 mLocationManager.removeUpdates(listener); 106 } 107 } 108 109 /** 110 * @return the last known location from all providers 111 */ 112 protected Location getLastKnownLocation() { 113 List<String> providers = mLocationManager.getAllProviders(); 114 Location bestLocation = null; 115 for (String provider : providers) { 116 Location lastKnownLocation = mLocationManager.getLastKnownLocation(provider); 117 if (lastKnownLocation != null) { 118 if (bestLocation == null || bestLocation.getTime() < lastKnownLocation.getTime()) { 119 bestLocation = lastKnownLocation; 120 } 121 } 122 } 123 return bestLocation; 124 } 125 126 /** 127 * @return the timeout for querying the location. 128 */ 129 protected long getQueryLocationTimeout() { 130 return QUERY_LOCATION_TIMEOUT; 131 } 132 133 /** 134 * @return the total number of enabled location providers 135 */ 136 protected int getTotalEnabledProviders() { 137 if (mEnabledProviders == null) { 138 mEnabledProviders = mLocationManager.getProviders(true); 139 } 140 return mEnabledProviders.size(); 141 } 142 143 /** 144 * Start detecting the country. 145 * <p> 146 * Queries the location from all location providers, then starts a thread to query the 147 * country from GeoCoder. 148 */ 149 @Override 150 public synchronized Country detectCountry() { 151 if (mLocationListeners != null) { 152 throw new IllegalStateException(); 153 } 154 // Request the location from all enabled providers. 155 int totalProviders = getTotalEnabledProviders(); 156 if (totalProviders > 0) { 157 mLocationListeners = new ArrayList<LocationListener>(totalProviders); 158 for (int i = 0; i < totalProviders; i++) { 159 LocationListener listener = new LocationListener () { 160 public void onLocationChanged(Location location) { 161 if (location != null) { 162 LocationBasedCountryDetector.this.stop(); 163 queryCountryCode(location); 164 } 165 } 166 public void onProviderDisabled(String provider) { 167 } 168 public void onProviderEnabled(String provider) { 169 } 170 public void onStatusChanged(String provider, int status, Bundle extras) { 171 } 172 }; 173 mLocationListeners.add(listener); 174 } 175 registerEnabledProviders(mLocationListeners); 176 mTimer = new Timer(); 177 mTimer.schedule(new TimerTask() { 178 @Override 179 public void run() { 180 mTimer = null; 181 LocationBasedCountryDetector.this.stop(); 182 // Looks like no provider could provide the location, let's try the last 183 // known location. 184 queryCountryCode(getLastKnownLocation()); 185 } 186 }, getQueryLocationTimeout()); 187 } else { 188 // There is no provider enabled. 189 queryCountryCode(getLastKnownLocation()); 190 } 191 return mDetectedCountry; 192 } 193 194 /** 195 * Stop the current query without notifying the listener. 196 */ 197 @Override 198 public synchronized void stop() { 199 if (mLocationListeners != null) { 200 unregisterProviders(mLocationListeners); 201 mLocationListeners = null; 202 } 203 if (mTimer != null) { 204 mTimer.cancel(); 205 mTimer = null; 206 } 207 } 208 209 /** 210 * Start a new thread to query the country from Geocoder. 211 */ 212 private synchronized void queryCountryCode(final Location location) { 213 if (location == null) { 214 notifyListener(null); 215 return; 216 } 217 if (mQueryThread != null) return; 218 mQueryThread = new Thread(new Runnable() { 219 public void run() { 220 String countryIso = null; 221 if (location != null) { 222 countryIso = getCountryFromLocation(location); 223 } 224 if (countryIso != null) { 225 mDetectedCountry = new Country(countryIso, Country.COUNTRY_SOURCE_LOCATION); 226 } else { 227 mDetectedCountry = null; 228 } 229 notifyListener(mDetectedCountry); 230 mQueryThread = null; 231 } 232 }); 233 mQueryThread.start(); 234 } 235} 236