blob: bb9e60f6e0729c9a00d6c2efa0c4794770bc67a9 [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";
Daisuke Miyakawaa550bdc2011-04-27 08:26:33 -070059 /* package */ static final boolean DEBUG = false;
Bai Taoa58a8752010-07-13 15:32:16 +080060
61 /**
62 * The refresh interval when the location based country was used
63 */
64 private final static long LOCATION_REFRESH_INTERVAL = 1000 * 60 * 60 * 24; // 1 day
65
66 protected CountryDetectorBase mLocationBasedCountryDetector;
67 protected Timer mLocationRefreshTimer;
68
69 private final int mPhoneType;
70 private Country mCountry;
71 private TelephonyManager mTelephonyManager;
72 private Country mCountryFromLocation;
73 private boolean mStopped = false;
74 private ServiceState mLastState;
75
76 private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
77 @Override
78 public void onServiceStateChanged(ServiceState serviceState) {
79 // TODO: Find out how often we will be notified, if this method is called too
80 // many times, let's consider querying the network.
81 Slog.d(TAG, "onServiceStateChanged");
82 // We only care the state change
83 if (mLastState == null || mLastState.getState() != serviceState.getState()) {
84 detectCountry(true, true);
85 mLastState = new ServiceState(serviceState);
86 }
87 }
88 };
89
90 /**
91 * The listener for receiving the notification from LocationBasedCountryDetector.
92 */
93 private CountryListener mLocationBasedCountryDetectionListener = new CountryListener() {
Daisuke Miyakawaa550bdc2011-04-27 08:26:33 -070094 @Override
Bai Taoa58a8752010-07-13 15:32:16 +080095 public void onCountryDetected(Country country) {
Daisuke Miyakawaa550bdc2011-04-27 08:26:33 -070096 if (DEBUG) Slog.d(TAG, "Country detected via LocationBasedCountryDetector");
Bai Taoa58a8752010-07-13 15:32:16 +080097 mCountryFromLocation = country;
98 // Don't start the LocationBasedCountryDetector.
99 detectCountry(true, false);
100 stopLocationBasedDetector();
101 }
102 };
103
104 public ComprehensiveCountryDetector(Context context) {
105 super(context);
106 mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
107 mPhoneType = mTelephonyManager.getPhoneType();
108 }
109
110 @Override
111 public Country detectCountry() {
112 // Don't start the LocationBasedCountryDetector if we have been stopped.
113 return detectCountry(false, !mStopped);
114 }
115
116 @Override
117 public void stop() {
118 Slog.i(TAG, "Stop the detector.");
119 cancelLocationRefresh();
120 removePhoneStateListener();
121 stopLocationBasedDetector();
122 mListener = null;
123 mStopped = true;
124 }
125
126 /**
127 * Get the country from different sources in order of the reliability.
128 */
129 private Country getCountry() {
130 Country result = null;
131 result = getNetworkBasedCountry();
132 if (result == null) {
133 result = getLastKnownLocationBasedCountry();
134 }
135 if (result == null) {
136 result = getSimBasedCountry();
137 }
138 if (result == null) {
139 result = getLocaleCountry();
140 }
141 return result;
142 }
143
144 /**
145 * @return the country from the mobile network.
146 */
147 protected Country getNetworkBasedCountry() {
148 String countryIso = null;
149 // TODO: The document says the result may be unreliable on CDMA networks. Shall we use
150 // it on CDMA phone? We may test the Android primarily used countries.
151 if (mPhoneType == TelephonyManager.PHONE_TYPE_GSM) {
152 countryIso = mTelephonyManager.getNetworkCountryIso();
153 if (!TextUtils.isEmpty(countryIso)) {
154 return new Country(countryIso, Country.COUNTRY_SOURCE_NETWORK);
155 }
156 }
157 return null;
158 }
159
160 /**
161 * @return the cached location based country.
162 */
163 protected Country getLastKnownLocationBasedCountry() {
164 return mCountryFromLocation;
165 }
166
167 /**
168 * @return the country from SIM card
169 */
170 protected Country getSimBasedCountry() {
171 String countryIso = null;
172 countryIso = mTelephonyManager.getSimCountryIso();
173 if (!TextUtils.isEmpty(countryIso)) {
174 return new Country(countryIso, Country.COUNTRY_SOURCE_SIM);
175 }
176 return null;
177 }
178
179 /**
180 * @return the country from the system's locale.
181 */
182 protected Country getLocaleCountry() {
183 Locale defaultLocale = Locale.getDefault();
184 if (defaultLocale != null) {
185 return new Country(defaultLocale.getCountry(), Country.COUNTRY_SOURCE_LOCALE);
186 } else {
187 return null;
188 }
189 }
190
191 /**
192 * @param notifyChange indicates whether the listener should be notified the change of the
193 * country
194 * @param startLocationBasedDetection indicates whether the LocationBasedCountryDetector could
195 * be started if the current country source is less reliable than the location.
196 * @return the current available UserCountry
197 */
198 private Country detectCountry(boolean notifyChange, boolean startLocationBasedDetection) {
199 Country country = getCountry();
200 runAfterDetectionAsync(mCountry != null ? new Country(mCountry) : mCountry, country,
201 notifyChange, startLocationBasedDetection);
202 mCountry = country;
203 return mCountry;
204 }
205
206 /**
207 * Run the tasks in the service's thread.
208 */
209 protected void runAfterDetectionAsync(final Country country, final Country detectedCountry,
210 final boolean notifyChange, final boolean startLocationBasedDetection) {
211 mHandler.post(new Runnable() {
Daisuke Miyakawaa550bdc2011-04-27 08:26:33 -0700212 @Override
Bai Taoa58a8752010-07-13 15:32:16 +0800213 public void run() {
214 runAfterDetection(
215 country, detectedCountry, notifyChange, startLocationBasedDetection);
216 }
217 });
218 }
219
220 @Override
221 public void setCountryListener(CountryListener listener) {
222 CountryListener prevListener = mListener;
223 mListener = listener;
224 if (mListener == null) {
225 // Stop listening all services
226 removePhoneStateListener();
227 stopLocationBasedDetector();
228 cancelLocationRefresh();
229 } else if (prevListener == null) {
230 addPhoneStateListener();
231 detectCountry(false, true);
232 }
233 }
234
235 void runAfterDetection(final Country country, final Country detectedCountry,
236 final boolean notifyChange, final boolean startLocationBasedDetection) {
237 if (notifyChange) {
238 notifyIfCountryChanged(country, detectedCountry);
239 }
Daisuke Miyakawaa550bdc2011-04-27 08:26:33 -0700240 if (DEBUG) {
241 Slog.d(TAG, "startLocationBasedDetection=" + startLocationBasedDetection
242 + " detectCountry=" + (detectedCountry == null ? null :
243 "(source: " + detectedCountry.getSource()
244 + ", countryISO: " + detectedCountry.getCountryIso() + ")")
245 + " isAirplaneModeOff()=" + isAirplaneModeOff()
246 + " mListener=" + mListener
247 + " isGeoCoderImplemnted()=" + isGeoCoderImplemented());
248 }
249
Bai Taoa58a8752010-07-13 15:32:16 +0800250 if (startLocationBasedDetection && (detectedCountry == null
251 || detectedCountry.getSource() > Country.COUNTRY_SOURCE_LOCATION)
252 && isAirplaneModeOff() && mListener != null && isGeoCoderImplemented()) {
Daisuke Miyakawaa550bdc2011-04-27 08:26:33 -0700253 if (DEBUG) Slog.d(TAG, "run startLocationBasedDetector()");
Bai Taoa58a8752010-07-13 15:32:16 +0800254 // Start finding location when the source is less reliable than the
255 // location and the airplane mode is off (as geocoder will not
256 // work).
257 // TODO : Shall we give up starting the detector within a
258 // period of time?
259 startLocationBasedDetector(mLocationBasedCountryDetectionListener);
260 }
261 if (detectedCountry == null
262 || detectedCountry.getSource() >= Country.COUNTRY_SOURCE_LOCATION) {
263 // Schedule the location refresh if the country source is
264 // not more reliable than the location or no country is
265 // found.
266 // TODO: Listen to the preference change of GPS, Wifi etc,
267 // and start detecting the country.
268 scheduleLocationRefresh();
269 } else {
270 // Cancel the location refresh once the current source is
271 // more reliable than the location.
272 cancelLocationRefresh();
273 stopLocationBasedDetector();
274 }
275 }
276
277 /**
278 * Find the country from LocationProvider.
279 */
280 private synchronized void startLocationBasedDetector(CountryListener listener) {
281 if (mLocationBasedCountryDetector != null) {
282 return;
283 }
Daisuke Miyakawaa550bdc2011-04-27 08:26:33 -0700284 if (DEBUG) {
285 Slog.d(TAG, "starts LocationBasedDetector to detect Country code via Location info "
286 + "(e.g. GPS)");
287 }
Bai Taoa58a8752010-07-13 15:32:16 +0800288 mLocationBasedCountryDetector = createLocationBasedCountryDetector();
289 mLocationBasedCountryDetector.setCountryListener(listener);
290 mLocationBasedCountryDetector.detectCountry();
291 }
292
293 private synchronized void stopLocationBasedDetector() {
Daisuke Miyakawaa550bdc2011-04-27 08:26:33 -0700294 if (DEBUG) {
295 Slog.d(TAG, "tries to stop LocationBasedDetector "
296 + "(current detector: " + mLocationBasedCountryDetector + ")");
297 }
Bai Taoa58a8752010-07-13 15:32:16 +0800298 if (mLocationBasedCountryDetector != null) {
299 mLocationBasedCountryDetector.stop();
300 mLocationBasedCountryDetector = null;
301 }
302 }
303
304 protected CountryDetectorBase createLocationBasedCountryDetector() {
305 return new LocationBasedCountryDetector(mContext);
306 }
307
308 protected boolean isAirplaneModeOff() {
309 return Settings.System.getInt(
310 mContext.getContentResolver(), Settings.System.AIRPLANE_MODE_ON, 0) == 0;
311 }
312
313 /**
314 * Notify the country change.
315 */
316 private void notifyIfCountryChanged(final Country country, final Country detectedCountry) {
317 if (detectedCountry != null && mListener != null
318 && (country == null || !country.equals(detectedCountry))) {
319 Slog.d(TAG,
320 "The country was changed from " + country != null ? country.getCountryIso() :
321 country + " to " + detectedCountry.getCountryIso());
322 notifyListener(detectedCountry);
323 }
324 }
325
326 /**
327 * Schedule the next location refresh. We will do nothing if the scheduled task exists.
328 */
329 private synchronized void scheduleLocationRefresh() {
330 if (mLocationRefreshTimer != null) return;
Daisuke Miyakawaa550bdc2011-04-27 08:26:33 -0700331 if (DEBUG) {
332 Slog.d(TAG, "start periodic location refresh timer. Interval: "
333 + LOCATION_REFRESH_INTERVAL);
334 }
Bai Taoa58a8752010-07-13 15:32:16 +0800335 mLocationRefreshTimer = new Timer();
336 mLocationRefreshTimer.schedule(new TimerTask() {
337 @Override
338 public void run() {
Daisuke Miyakawaa550bdc2011-04-27 08:26:33 -0700339 if (DEBUG) {
340 Slog.d(TAG, "periodic location refresh event. Starts detecting Country code");
341 }
Bai Taoa58a8752010-07-13 15:32:16 +0800342 mLocationRefreshTimer = null;
343 detectCountry(false, true);
344 }
345 }, LOCATION_REFRESH_INTERVAL);
346 }
347
348 /**
349 * Cancel the scheduled refresh task if it exists
350 */
351 private synchronized void cancelLocationRefresh() {
352 if (mLocationRefreshTimer != null) {
353 mLocationRefreshTimer.cancel();
354 mLocationRefreshTimer = null;
355 }
356 }
357
358 protected synchronized void addPhoneStateListener() {
359 if (mPhoneStateListener == null && mPhoneType == TelephonyManager.PHONE_TYPE_GSM) {
360 mLastState = null;
361 mPhoneStateListener = new PhoneStateListener() {
362 @Override
363 public void onServiceStateChanged(ServiceState serviceState) {
364 // TODO: Find out how often we will be notified, if this
365 // method is called too
366 // many times, let's consider querying the network.
367 Slog.d(TAG, "onServiceStateChanged");
368 // We only care the state change
369 if (mLastState == null || mLastState.getState() != serviceState.getState()) {
370 detectCountry(true, true);
371 mLastState = new ServiceState(serviceState);
372 }
373 }
374 };
375 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
376 }
377 }
378
379 protected synchronized void removePhoneStateListener() {
380 if (mPhoneStateListener != null) {
381 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
382 mPhoneStateListener = null;
383 }
384 }
385
386 protected boolean isGeoCoderImplemented() {
Marc Blank141e00c2010-09-20 20:35:09 -0700387 return Geocoder.isPresent();
Bai Taoa58a8752010-07-13 15:32:16 +0800388 }
389}