blob: 139f05d211fdb34aaebf94393cc8447dae037d65 [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
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}