blob: d4fb8ee5abb031ed4eebae26878280da6acf0d54 [file] [log] [blame]
Bai Taoa58a8752010-07-13 15:32:16 +08001/*
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
Bai Taoa58a8752010-07-13 15:32:16 +080019import 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.Bundle;
27import android.util.Slog;
28
Daisuke Miyakawaa550bdc2011-04-27 08:26:33 -070029import java.io.IOException;
30import java.util.ArrayList;
31import java.util.List;
32import java.util.Timer;
33import java.util.TimerTask;
34
Bai Taoa58a8752010-07-13 15:32:16 +080035/**
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
Daisuke Miyakawaa550bdc2011-04-27 08:26:33 -070089 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);
Bai Taoa58a8752010-07-13 15:32:16 +080092 }
93
94 /**
Daisuke Miyakawaa550bdc2011-04-27 08:26:33 -070095 * Register a listener with a provider name
Bai Taoa58a8752010-07-13 15:32:16 +080096 */
Daisuke Miyakawaa550bdc2011-04-27 08:26:33 -070097 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);
Bai Taoa58a8752010-07-13 15:32:16 +0800106 }
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 || bestLocation.getTime() < lastKnownLocation.getTime()) {
118 bestLocation = lastKnownLocation;
119 }
120 }
121 }
122 return bestLocation;
123 }
124
125 /**
126 * @return the timeout for querying the location.
127 */
128 protected long getQueryLocationTimeout() {
129 return QUERY_LOCATION_TIMEOUT;
130 }
131
Daisuke Miyakawaa550bdc2011-04-27 08:26:33 -0700132 protected List<String> getEnabledProviders() {
Bai Taoa58a8752010-07-13 15:32:16 +0800133 if (mEnabledProviders == null) {
134 mEnabledProviders = mLocationManager.getProviders(true);
135 }
Daisuke Miyakawaa550bdc2011-04-27 08:26:33 -0700136 return mEnabledProviders;
Bai Taoa58a8752010-07-13 15:32:16 +0800137 }
138
139 /**
140 * Start detecting the country.
141 * <p>
142 * Queries the location from all location providers, then starts a thread to query the
143 * country from GeoCoder.
144 */
145 @Override
146 public synchronized Country detectCountry() {
147 if (mLocationListeners != null) {
148 throw new IllegalStateException();
149 }
150 // Request the location from all enabled providers.
Daisuke Miyakawaa550bdc2011-04-27 08:26:33 -0700151 List<String> enabledProviders = getEnabledProviders();
152 int totalProviders = enabledProviders.size();
Bai Taoa58a8752010-07-13 15:32:16 +0800153 if (totalProviders > 0) {
154 mLocationListeners = new ArrayList<LocationListener>(totalProviders);
155 for (int i = 0; i < totalProviders; i++) {
Daisuke Miyakawaa550bdc2011-04-27 08:26:33 -0700156 String provider = enabledProviders.get(i);
157 if (isAcceptableProvider(provider)) {
158 LocationListener listener = new LocationListener () {
159 @Override
160 public void onLocationChanged(Location location) {
161 if (location != null) {
162 LocationBasedCountryDetector.this.stop();
163 queryCountryCode(location);
164 }
Bai Taoa58a8752010-07-13 15:32:16 +0800165 }
Daisuke Miyakawaa550bdc2011-04-27 08:26:33 -0700166 @Override
167 public void onProviderDisabled(String provider) {
168 }
169 @Override
170 public void onProviderEnabled(String provider) {
171 }
172 @Override
173 public void onStatusChanged(String provider, int status, Bundle extras) {
174 }
175 };
176 mLocationListeners.add(listener);
177 registerListener(provider, listener);
178 }
Bai Taoa58a8752010-07-13 15:32:16 +0800179 }
Daisuke Miyakawaa550bdc2011-04-27 08:26:33 -0700180
Bai Taoa58a8752010-07-13 15:32:16 +0800181 mTimer = new Timer();
182 mTimer.schedule(new TimerTask() {
183 @Override
184 public void run() {
185 mTimer = null;
186 LocationBasedCountryDetector.this.stop();
187 // Looks like no provider could provide the location, let's try the last
188 // known location.
189 queryCountryCode(getLastKnownLocation());
190 }
191 }, getQueryLocationTimeout());
192 } else {
193 // There is no provider enabled.
194 queryCountryCode(getLastKnownLocation());
195 }
196 return mDetectedCountry;
197 }
198
199 /**
200 * Stop the current query without notifying the listener.
201 */
202 @Override
203 public synchronized void stop() {
204 if (mLocationListeners != null) {
Daisuke Miyakawaa550bdc2011-04-27 08:26:33 -0700205 for (LocationListener listener : mLocationListeners) {
206 unregisterListener(listener);
207 }
Bai Taoa58a8752010-07-13 15:32:16 +0800208 mLocationListeners = null;
209 }
210 if (mTimer != null) {
211 mTimer.cancel();
212 mTimer = null;
213 }
214 }
215
216 /**
217 * Start a new thread to query the country from Geocoder.
218 */
219 private synchronized void queryCountryCode(final Location location) {
220 if (location == null) {
221 notifyListener(null);
222 return;
223 }
224 if (mQueryThread != null) return;
225 mQueryThread = new Thread(new Runnable() {
Daisuke Miyakawaa550bdc2011-04-27 08:26:33 -0700226 @Override
Bai Taoa58a8752010-07-13 15:32:16 +0800227 public void run() {
228 String countryIso = null;
229 if (location != null) {
230 countryIso = getCountryFromLocation(location);
231 }
232 if (countryIso != null) {
233 mDetectedCountry = new Country(countryIso, Country.COUNTRY_SOURCE_LOCATION);
234 } else {
235 mDetectedCountry = null;
236 }
237 notifyListener(mDetectedCountry);
238 mQueryThread = null;
239 }
240 });
241 mQueryThread.start();
242 }
243}