LocationBasedCountryDetector.java revision a58a8751b4c2ce457f0082a0baaee61312d56195
1/*
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}
236