blob: f068b44c04afe30b16e6975c45f87c73c016815a [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
Bai Taoa58a8752010-07-13 15:32:16 +080069 private Country mCountry;
Makoto Onukif9165b72011-12-09 17:43:35 -080070 private final TelephonyManager mTelephonyManager;
Bai Taoa58a8752010-07-13 15:32:16 +080071 private Country mCountryFromLocation;
72 private boolean mStopped = false;
Bai Taoa58a8752010-07-13 15:32:16 +080073
Makoto Onukif9165b72011-12-09 17:43:35 -080074 private PhoneStateListener mPhoneStateListener;
Bai Taoa58a8752010-07-13 15:32:16 +080075
76 /**
77 * The listener for receiving the notification from LocationBasedCountryDetector.
78 */
79 private CountryListener mLocationBasedCountryDetectionListener = new CountryListener() {
Daisuke Miyakawaa550bdc2011-04-27 08:26:33 -070080 @Override
Bai Taoa58a8752010-07-13 15:32:16 +080081 public void onCountryDetected(Country country) {
Daisuke Miyakawaa550bdc2011-04-27 08:26:33 -070082 if (DEBUG) Slog.d(TAG, "Country detected via LocationBasedCountryDetector");
Bai Taoa58a8752010-07-13 15:32:16 +080083 mCountryFromLocation = country;
84 // Don't start the LocationBasedCountryDetector.
85 detectCountry(true, false);
86 stopLocationBasedDetector();
87 }
88 };
89
90 public ComprehensiveCountryDetector(Context context) {
91 super(context);
92 mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
Bai Taoa58a8752010-07-13 15:32:16 +080093 }
94
95 @Override
96 public Country detectCountry() {
97 // Don't start the LocationBasedCountryDetector if we have been stopped.
98 return detectCountry(false, !mStopped);
99 }
100
101 @Override
102 public void stop() {
Makoto Onukif9165b72011-12-09 17:43:35 -0800103 // Note: this method in this subclass called only by tests.
Bai Taoa58a8752010-07-13 15:32:16 +0800104 Slog.i(TAG, "Stop the detector.");
105 cancelLocationRefresh();
106 removePhoneStateListener();
107 stopLocationBasedDetector();
108 mListener = null;
109 mStopped = true;
110 }
111
112 /**
113 * Get the country from different sources in order of the reliability.
114 */
115 private Country getCountry() {
116 Country result = null;
117 result = getNetworkBasedCountry();
118 if (result == null) {
119 result = getLastKnownLocationBasedCountry();
120 }
121 if (result == null) {
122 result = getSimBasedCountry();
123 }
124 if (result == null) {
125 result = getLocaleCountry();
126 }
127 return result;
128 }
129
Makoto Onukif9165b72011-12-09 17:43:35 -0800130 private boolean isNetworkCountryCodeAvailable() {
131 // On CDMA TelephonyManager.getNetworkCountryIso() just returns SIM country. We don't want
132 // to prioritize it over location based country, so ignore it.
133 final int phoneType = mTelephonyManager.getPhoneType();
134 if (DEBUG) Slog.v(TAG, " phonetype=" + phoneType);
135 return phoneType == TelephonyManager.PHONE_TYPE_GSM;
136 }
137
Bai Taoa58a8752010-07-13 15:32:16 +0800138 /**
139 * @return the country from the mobile network.
140 */
141 protected Country getNetworkBasedCountry() {
142 String countryIso = null;
Makoto Onukif9165b72011-12-09 17:43:35 -0800143 if (isNetworkCountryCodeAvailable()) {
Bai Taoa58a8752010-07-13 15:32:16 +0800144 countryIso = mTelephonyManager.getNetworkCountryIso();
145 if (!TextUtils.isEmpty(countryIso)) {
146 return new Country(countryIso, Country.COUNTRY_SOURCE_NETWORK);
147 }
148 }
149 return null;
150 }
151
152 /**
153 * @return the cached location based country.
154 */
155 protected Country getLastKnownLocationBasedCountry() {
156 return mCountryFromLocation;
157 }
158
159 /**
160 * @return the country from SIM card
161 */
162 protected Country getSimBasedCountry() {
163 String countryIso = null;
164 countryIso = mTelephonyManager.getSimCountryIso();
165 if (!TextUtils.isEmpty(countryIso)) {
166 return new Country(countryIso, Country.COUNTRY_SOURCE_SIM);
167 }
168 return null;
169 }
170
171 /**
172 * @return the country from the system's locale.
173 */
174 protected Country getLocaleCountry() {
175 Locale defaultLocale = Locale.getDefault();
176 if (defaultLocale != null) {
177 return new Country(defaultLocale.getCountry(), Country.COUNTRY_SOURCE_LOCALE);
178 } else {
179 return null;
180 }
181 }
182
183 /**
184 * @param notifyChange indicates whether the listener should be notified the change of the
185 * country
186 * @param startLocationBasedDetection indicates whether the LocationBasedCountryDetector could
187 * be started if the current country source is less reliable than the location.
188 * @return the current available UserCountry
189 */
190 private Country detectCountry(boolean notifyChange, boolean startLocationBasedDetection) {
191 Country country = getCountry();
192 runAfterDetectionAsync(mCountry != null ? new Country(mCountry) : mCountry, country,
193 notifyChange, startLocationBasedDetection);
194 mCountry = country;
195 return mCountry;
196 }
197
198 /**
199 * Run the tasks in the service's thread.
200 */
201 protected void runAfterDetectionAsync(final Country country, final Country detectedCountry,
202 final boolean notifyChange, final boolean startLocationBasedDetection) {
203 mHandler.post(new Runnable() {
Daisuke Miyakawaa550bdc2011-04-27 08:26:33 -0700204 @Override
Bai Taoa58a8752010-07-13 15:32:16 +0800205 public void run() {
206 runAfterDetection(
207 country, detectedCountry, notifyChange, startLocationBasedDetection);
208 }
209 });
210 }
211
212 @Override
213 public void setCountryListener(CountryListener listener) {
214 CountryListener prevListener = mListener;
215 mListener = listener;
216 if (mListener == null) {
217 // Stop listening all services
218 removePhoneStateListener();
219 stopLocationBasedDetector();
220 cancelLocationRefresh();
221 } else if (prevListener == null) {
222 addPhoneStateListener();
223 detectCountry(false, true);
224 }
225 }
226
227 void runAfterDetection(final Country country, final Country detectedCountry,
228 final boolean notifyChange, final boolean startLocationBasedDetection) {
229 if (notifyChange) {
230 notifyIfCountryChanged(country, detectedCountry);
231 }
Daisuke Miyakawaa550bdc2011-04-27 08:26:33 -0700232 if (DEBUG) {
233 Slog.d(TAG, "startLocationBasedDetection=" + startLocationBasedDetection
234 + " detectCountry=" + (detectedCountry == null ? null :
235 "(source: " + detectedCountry.getSource()
236 + ", countryISO: " + detectedCountry.getCountryIso() + ")")
237 + " isAirplaneModeOff()=" + isAirplaneModeOff()
238 + " mListener=" + mListener
239 + " isGeoCoderImplemnted()=" + isGeoCoderImplemented());
240 }
241
Bai Taoa58a8752010-07-13 15:32:16 +0800242 if (startLocationBasedDetection && (detectedCountry == null
243 || detectedCountry.getSource() > Country.COUNTRY_SOURCE_LOCATION)
244 && isAirplaneModeOff() && mListener != null && isGeoCoderImplemented()) {
Daisuke Miyakawaa550bdc2011-04-27 08:26:33 -0700245 if (DEBUG) Slog.d(TAG, "run startLocationBasedDetector()");
Bai Taoa58a8752010-07-13 15:32:16 +0800246 // Start finding location when the source is less reliable than the
247 // location and the airplane mode is off (as geocoder will not
248 // work).
249 // TODO : Shall we give up starting the detector within a
250 // period of time?
251 startLocationBasedDetector(mLocationBasedCountryDetectionListener);
252 }
253 if (detectedCountry == null
254 || detectedCountry.getSource() >= Country.COUNTRY_SOURCE_LOCATION) {
255 // Schedule the location refresh if the country source is
256 // not more reliable than the location or no country is
257 // found.
258 // TODO: Listen to the preference change of GPS, Wifi etc,
259 // and start detecting the country.
260 scheduleLocationRefresh();
261 } else {
262 // Cancel the location refresh once the current source is
263 // more reliable than the location.
264 cancelLocationRefresh();
265 stopLocationBasedDetector();
266 }
267 }
268
269 /**
270 * Find the country from LocationProvider.
271 */
272 private synchronized void startLocationBasedDetector(CountryListener listener) {
273 if (mLocationBasedCountryDetector != null) {
274 return;
275 }
Daisuke Miyakawaa550bdc2011-04-27 08:26:33 -0700276 if (DEBUG) {
277 Slog.d(TAG, "starts LocationBasedDetector to detect Country code via Location info "
278 + "(e.g. GPS)");
279 }
Bai Taoa58a8752010-07-13 15:32:16 +0800280 mLocationBasedCountryDetector = createLocationBasedCountryDetector();
281 mLocationBasedCountryDetector.setCountryListener(listener);
282 mLocationBasedCountryDetector.detectCountry();
283 }
284
285 private synchronized void stopLocationBasedDetector() {
Daisuke Miyakawaa550bdc2011-04-27 08:26:33 -0700286 if (DEBUG) {
287 Slog.d(TAG, "tries to stop LocationBasedDetector "
288 + "(current detector: " + mLocationBasedCountryDetector + ")");
289 }
Bai Taoa58a8752010-07-13 15:32:16 +0800290 if (mLocationBasedCountryDetector != null) {
291 mLocationBasedCountryDetector.stop();
292 mLocationBasedCountryDetector = null;
293 }
294 }
295
296 protected CountryDetectorBase createLocationBasedCountryDetector() {
297 return new LocationBasedCountryDetector(mContext);
298 }
299
300 protected boolean isAirplaneModeOff() {
301 return Settings.System.getInt(
302 mContext.getContentResolver(), Settings.System.AIRPLANE_MODE_ON, 0) == 0;
303 }
304
305 /**
306 * Notify the country change.
307 */
308 private void notifyIfCountryChanged(final Country country, final Country detectedCountry) {
309 if (detectedCountry != null && mListener != null
310 && (country == null || !country.equals(detectedCountry))) {
311 Slog.d(TAG,
312 "The country was changed from " + country != null ? country.getCountryIso() :
313 country + " to " + detectedCountry.getCountryIso());
314 notifyListener(detectedCountry);
315 }
316 }
317
318 /**
319 * Schedule the next location refresh. We will do nothing if the scheduled task exists.
320 */
321 private synchronized void scheduleLocationRefresh() {
322 if (mLocationRefreshTimer != null) return;
Daisuke Miyakawaa550bdc2011-04-27 08:26:33 -0700323 if (DEBUG) {
324 Slog.d(TAG, "start periodic location refresh timer. Interval: "
325 + LOCATION_REFRESH_INTERVAL);
326 }
Bai Taoa58a8752010-07-13 15:32:16 +0800327 mLocationRefreshTimer = new Timer();
328 mLocationRefreshTimer.schedule(new TimerTask() {
329 @Override
330 public void run() {
Daisuke Miyakawaa550bdc2011-04-27 08:26:33 -0700331 if (DEBUG) {
332 Slog.d(TAG, "periodic location refresh event. Starts detecting Country code");
333 }
Bai Taoa58a8752010-07-13 15:32:16 +0800334 mLocationRefreshTimer = null;
335 detectCountry(false, true);
336 }
337 }, LOCATION_REFRESH_INTERVAL);
338 }
339
340 /**
341 * Cancel the scheduled refresh task if it exists
342 */
343 private synchronized void cancelLocationRefresh() {
344 if (mLocationRefreshTimer != null) {
345 mLocationRefreshTimer.cancel();
346 mLocationRefreshTimer = null;
347 }
348 }
349
350 protected synchronized void addPhoneStateListener() {
Makoto Onukif9165b72011-12-09 17:43:35 -0800351 if (mPhoneStateListener == null) {
Bai Taoa58a8752010-07-13 15:32:16 +0800352 mPhoneStateListener = new PhoneStateListener() {
353 @Override
354 public void onServiceStateChanged(ServiceState serviceState) {
Makoto Onukif9165b72011-12-09 17:43:35 -0800355 if (!isNetworkCountryCodeAvailable()) {
356 return;
Bai Taoa58a8752010-07-13 15:32:16 +0800357 }
Makoto Onukif9165b72011-12-09 17:43:35 -0800358 if (DEBUG) Slog.d(TAG, "onServiceStateChanged: " + serviceState.getState());
359
360 detectCountry(true, true);
Bai Taoa58a8752010-07-13 15:32:16 +0800361 }
362 };
363 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
364 }
365 }
366
367 protected synchronized void removePhoneStateListener() {
368 if (mPhoneStateListener != null) {
369 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
370 mPhoneStateListener = null;
371 }
372 }
373
374 protected boolean isGeoCoderImplemented() {
Marc Blank141e00c2010-09-20 20:35:09 -0700375 return Geocoder.isPresent();
Bai Taoa58a8752010-07-13 15:32:16 +0800376 }
377}