blob: e9ce3ce3ccf73d2c6d186f03afcc209ef86091f7 [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.Country;
21import android.location.CountryListener;
22import android.location.Geocoder;
23import android.provider.Settings;
24import android.telephony.PhoneStateListener;
25import android.telephony.ServiceState;
26import android.telephony.TelephonyManager;
27import android.text.TextUtils;
28import android.util.Slog;
29
Marc Blank141e00c2010-09-20 20:35:09 -070030import java.util.Locale;
31import java.util.Timer;
32import java.util.TimerTask;
33
Bai Taoa58a8752010-07-13 15:32:16 +080034/**
35 * This class is used to detect the country where the user is. The sources of
36 * country are queried in order of reliability, like
37 * <ul>
38 * <li>Mobile network</li>
39 * <li>Location</li>
40 * <li>SIM's country</li>
41 * <li>Phone's locale</li>
42 * </ul>
43 * <p>
44 * Call the {@link #detectCountry()} to get the available country immediately.
45 * <p>
46 * To be notified of the future country change, using the
47 * {@link #setCountryListener(CountryListener)}
48 * <p>
49 * Using the {@link #stop()} to stop listening to the country change.
50 * <p>
51 * The country information will be refreshed every
52 * {@link #LOCATION_REFRESH_INTERVAL} once the location based country is used.
53 *
54 * @hide
55 */
56public class ComprehensiveCountryDetector extends CountryDetectorBase {
57
58 private final static String TAG = "ComprehensiveCountryDetector";
59
60 /**
61 * The refresh interval when the location based country was used
62 */
63 private final static long LOCATION_REFRESH_INTERVAL = 1000 * 60 * 60 * 24; // 1 day
64
65 protected CountryDetectorBase mLocationBasedCountryDetector;
66 protected Timer mLocationRefreshTimer;
67
68 private final int mPhoneType;
69 private Country mCountry;
70 private TelephonyManager mTelephonyManager;
71 private Country mCountryFromLocation;
72 private boolean mStopped = false;
73 private ServiceState mLastState;
74
75 private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
76 @Override
77 public void onServiceStateChanged(ServiceState serviceState) {
78 // TODO: Find out how often we will be notified, if this method is called too
79 // many times, let's consider querying the network.
80 Slog.d(TAG, "onServiceStateChanged");
81 // We only care the state change
82 if (mLastState == null || mLastState.getState() != serviceState.getState()) {
83 detectCountry(true, true);
84 mLastState = new ServiceState(serviceState);
85 }
86 }
87 };
88
89 /**
90 * The listener for receiving the notification from LocationBasedCountryDetector.
91 */
92 private CountryListener mLocationBasedCountryDetectionListener = new CountryListener() {
93 public void onCountryDetected(Country country) {
94 mCountryFromLocation = country;
95 // Don't start the LocationBasedCountryDetector.
96 detectCountry(true, false);
97 stopLocationBasedDetector();
98 }
99 };
100
101 public ComprehensiveCountryDetector(Context context) {
102 super(context);
103 mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
104 mPhoneType = mTelephonyManager.getPhoneType();
105 }
106
107 @Override
108 public Country detectCountry() {
109 // Don't start the LocationBasedCountryDetector if we have been stopped.
110 return detectCountry(false, !mStopped);
111 }
112
113 @Override
114 public void stop() {
115 Slog.i(TAG, "Stop the detector.");
116 cancelLocationRefresh();
117 removePhoneStateListener();
118 stopLocationBasedDetector();
119 mListener = null;
120 mStopped = true;
121 }
122
123 /**
124 * Get the country from different sources in order of the reliability.
125 */
126 private Country getCountry() {
127 Country result = null;
128 result = getNetworkBasedCountry();
129 if (result == null) {
130 result = getLastKnownLocationBasedCountry();
131 }
132 if (result == null) {
133 result = getSimBasedCountry();
134 }
135 if (result == null) {
136 result = getLocaleCountry();
137 }
138 return result;
139 }
140
141 /**
142 * @return the country from the mobile network.
143 */
144 protected Country getNetworkBasedCountry() {
145 String countryIso = null;
146 // TODO: The document says the result may be unreliable on CDMA networks. Shall we use
147 // it on CDMA phone? We may test the Android primarily used countries.
148 if (mPhoneType == TelephonyManager.PHONE_TYPE_GSM) {
149 countryIso = mTelephonyManager.getNetworkCountryIso();
150 if (!TextUtils.isEmpty(countryIso)) {
151 return new Country(countryIso, Country.COUNTRY_SOURCE_NETWORK);
152 }
153 }
154 return null;
155 }
156
157 /**
158 * @return the cached location based country.
159 */
160 protected Country getLastKnownLocationBasedCountry() {
161 return mCountryFromLocation;
162 }
163
164 /**
165 * @return the country from SIM card
166 */
167 protected Country getSimBasedCountry() {
168 String countryIso = null;
169 countryIso = mTelephonyManager.getSimCountryIso();
170 if (!TextUtils.isEmpty(countryIso)) {
171 return new Country(countryIso, Country.COUNTRY_SOURCE_SIM);
172 }
173 return null;
174 }
175
176 /**
177 * @return the country from the system's locale.
178 */
179 protected Country getLocaleCountry() {
180 Locale defaultLocale = Locale.getDefault();
181 if (defaultLocale != null) {
182 return new Country(defaultLocale.getCountry(), Country.COUNTRY_SOURCE_LOCALE);
183 } else {
184 return null;
185 }
186 }
187
188 /**
189 * @param notifyChange indicates whether the listener should be notified the change of the
190 * country
191 * @param startLocationBasedDetection indicates whether the LocationBasedCountryDetector could
192 * be started if the current country source is less reliable than the location.
193 * @return the current available UserCountry
194 */
195 private Country detectCountry(boolean notifyChange, boolean startLocationBasedDetection) {
196 Country country = getCountry();
197 runAfterDetectionAsync(mCountry != null ? new Country(mCountry) : mCountry, country,
198 notifyChange, startLocationBasedDetection);
199 mCountry = country;
200 return mCountry;
201 }
202
203 /**
204 * Run the tasks in the service's thread.
205 */
206 protected void runAfterDetectionAsync(final Country country, final Country detectedCountry,
207 final boolean notifyChange, final boolean startLocationBasedDetection) {
208 mHandler.post(new Runnable() {
209 public void run() {
210 runAfterDetection(
211 country, detectedCountry, notifyChange, startLocationBasedDetection);
212 }
213 });
214 }
215
216 @Override
217 public void setCountryListener(CountryListener listener) {
218 CountryListener prevListener = mListener;
219 mListener = listener;
220 if (mListener == null) {
221 // Stop listening all services
222 removePhoneStateListener();
223 stopLocationBasedDetector();
224 cancelLocationRefresh();
225 } else if (prevListener == null) {
226 addPhoneStateListener();
227 detectCountry(false, true);
228 }
229 }
230
231 void runAfterDetection(final Country country, final Country detectedCountry,
232 final boolean notifyChange, final boolean startLocationBasedDetection) {
233 if (notifyChange) {
234 notifyIfCountryChanged(country, detectedCountry);
235 }
236 if (startLocationBasedDetection && (detectedCountry == null
237 || detectedCountry.getSource() > Country.COUNTRY_SOURCE_LOCATION)
238 && isAirplaneModeOff() && mListener != null && isGeoCoderImplemented()) {
239 // Start finding location when the source is less reliable than the
240 // location and the airplane mode is off (as geocoder will not
241 // work).
242 // TODO : Shall we give up starting the detector within a
243 // period of time?
244 startLocationBasedDetector(mLocationBasedCountryDetectionListener);
245 }
246 if (detectedCountry == null
247 || detectedCountry.getSource() >= Country.COUNTRY_SOURCE_LOCATION) {
248 // Schedule the location refresh if the country source is
249 // not more reliable than the location or no country is
250 // found.
251 // TODO: Listen to the preference change of GPS, Wifi etc,
252 // and start detecting the country.
253 scheduleLocationRefresh();
254 } else {
255 // Cancel the location refresh once the current source is
256 // more reliable than the location.
257 cancelLocationRefresh();
258 stopLocationBasedDetector();
259 }
260 }
261
262 /**
263 * Find the country from LocationProvider.
264 */
265 private synchronized void startLocationBasedDetector(CountryListener listener) {
266 if (mLocationBasedCountryDetector != null) {
267 return;
268 }
269 mLocationBasedCountryDetector = createLocationBasedCountryDetector();
270 mLocationBasedCountryDetector.setCountryListener(listener);
271 mLocationBasedCountryDetector.detectCountry();
272 }
273
274 private synchronized void stopLocationBasedDetector() {
275 if (mLocationBasedCountryDetector != null) {
276 mLocationBasedCountryDetector.stop();
277 mLocationBasedCountryDetector = null;
278 }
279 }
280
281 protected CountryDetectorBase createLocationBasedCountryDetector() {
282 return new LocationBasedCountryDetector(mContext);
283 }
284
285 protected boolean isAirplaneModeOff() {
286 return Settings.System.getInt(
287 mContext.getContentResolver(), Settings.System.AIRPLANE_MODE_ON, 0) == 0;
288 }
289
290 /**
291 * Notify the country change.
292 */
293 private void notifyIfCountryChanged(final Country country, final Country detectedCountry) {
294 if (detectedCountry != null && mListener != null
295 && (country == null || !country.equals(detectedCountry))) {
296 Slog.d(TAG,
297 "The country was changed from " + country != null ? country.getCountryIso() :
298 country + " to " + detectedCountry.getCountryIso());
299 notifyListener(detectedCountry);
300 }
301 }
302
303 /**
304 * Schedule the next location refresh. We will do nothing if the scheduled task exists.
305 */
306 private synchronized void scheduleLocationRefresh() {
307 if (mLocationRefreshTimer != null) return;
308 mLocationRefreshTimer = new Timer();
309 mLocationRefreshTimer.schedule(new TimerTask() {
310 @Override
311 public void run() {
312 mLocationRefreshTimer = null;
313 detectCountry(false, true);
314 }
315 }, LOCATION_REFRESH_INTERVAL);
316 }
317
318 /**
319 * Cancel the scheduled refresh task if it exists
320 */
321 private synchronized void cancelLocationRefresh() {
322 if (mLocationRefreshTimer != null) {
323 mLocationRefreshTimer.cancel();
324 mLocationRefreshTimer = null;
325 }
326 }
327
328 protected synchronized void addPhoneStateListener() {
329 if (mPhoneStateListener == null && mPhoneType == TelephonyManager.PHONE_TYPE_GSM) {
330 mLastState = null;
331 mPhoneStateListener = new PhoneStateListener() {
332 @Override
333 public void onServiceStateChanged(ServiceState serviceState) {
334 // TODO: Find out how often we will be notified, if this
335 // method is called too
336 // many times, let's consider querying the network.
337 Slog.d(TAG, "onServiceStateChanged");
338 // We only care the state change
339 if (mLastState == null || mLastState.getState() != serviceState.getState()) {
340 detectCountry(true, true);
341 mLastState = new ServiceState(serviceState);
342 }
343 }
344 };
345 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
346 }
347 }
348
349 protected synchronized void removePhoneStateListener() {
350 if (mPhoneStateListener != null) {
351 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
352 mPhoneStateListener = null;
353 }
354 }
355
356 protected boolean isGeoCoderImplemented() {
Marc Blank141e00c2010-09-20 20:35:09 -0700357 return Geocoder.isPresent();
Bai Taoa58a8752010-07-13 15:32:16 +0800358 }
359}