LocationBasedCountryDetector.java revision 2eeeec248a38ff33999c83f4b8d5bab7d50e79d2
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 android.content.Context;
20import android.location.Address;
21import android.location.Country;
22import android.location.Geocoder;
23import android.location.Location;
24import android.location.LocationListener;
25import android.location.LocationManager;
26import android.os.Bundle;
27import android.util.Slog;
28
29import java.io.IOException;
30import java.util.ArrayList;
31import java.util.List;
32import java.util.Timer;
33import java.util.TimerTask;
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    protected boolean isAcceptableProvider(String provider) {
90        // We don't want to actively initiate a location fix here (with gps or network providers).
91        return LocationManager.PASSIVE_PROVIDER.equals(provider);
92    }
93
94    /**
95     * Register a listener with a provider name
96     */
97    protected void registerListener(String provider, LocationListener listener) {
98        mLocationManager.requestLocationUpdates(provider, 0, 0, listener);
99    }
100
101    /**
102     * Unregister an already registered listener
103     */
104    protected void unregisterListener(LocationListener listener) {
105        mLocationManager.removeUpdates(listener);
106    }
107
108    /**
109     * @return the last known location from all providers
110     */
111    protected Location getLastKnownLocation() {
112        List<String> providers = mLocationManager.getAllProviders();
113        Location bestLocation = null;
114        for (String provider : providers) {
115            Location lastKnownLocation = mLocationManager.getLastKnownLocation(provider);
116            if (lastKnownLocation != null) {
117                if (bestLocation == null ||
118                        bestLocation.getElapsedRealtimeNano() <
119                        lastKnownLocation.getElapsedRealtimeNano()) {
120                    bestLocation = lastKnownLocation;
121                }
122            }
123        }
124        return bestLocation;
125    }
126
127    /**
128     * @return the timeout for querying the location.
129     */
130    protected long getQueryLocationTimeout() {
131        return QUERY_LOCATION_TIMEOUT;
132    }
133
134    protected List<String> getEnabledProviders() {
135        if (mEnabledProviders == null) {
136            mEnabledProviders = mLocationManager.getProviders(true);
137        }
138        return mEnabledProviders;
139    }
140
141    /**
142     * Start detecting the country.
143     * <p>
144     * Queries the location from all location providers, then starts a thread to query the
145     * country from GeoCoder.
146     */
147    @Override
148    public synchronized Country detectCountry() {
149        if (mLocationListeners  != null) {
150            throw new IllegalStateException();
151        }
152        // Request the location from all enabled providers.
153        List<String> enabledProviders = getEnabledProviders();
154        int totalProviders = enabledProviders.size();
155        if (totalProviders > 0) {
156            mLocationListeners = new ArrayList<LocationListener>(totalProviders);
157            for (int i = 0; i < totalProviders; i++) {
158                String provider = enabledProviders.get(i);
159                if (isAcceptableProvider(provider)) {
160                    LocationListener listener = new LocationListener () {
161                        @Override
162                        public void onLocationChanged(Location location) {
163                            if (location != null) {
164                                LocationBasedCountryDetector.this.stop();
165                                queryCountryCode(location);
166                            }
167                        }
168                        @Override
169                        public void onProviderDisabled(String provider) {
170                        }
171                        @Override
172                        public void onProviderEnabled(String provider) {
173                        }
174                        @Override
175                        public void onStatusChanged(String provider, int status, Bundle extras) {
176                        }
177                    };
178                    mLocationListeners.add(listener);
179                    registerListener(provider, listener);
180                }
181            }
182
183            mTimer = new Timer();
184            mTimer.schedule(new TimerTask() {
185                @Override
186                public void run() {
187                    mTimer = null;
188                    LocationBasedCountryDetector.this.stop();
189                    // Looks like no provider could provide the location, let's try the last
190                    // known location.
191                    queryCountryCode(getLastKnownLocation());
192                }
193            }, getQueryLocationTimeout());
194        } else {
195            // There is no provider enabled.
196            queryCountryCode(getLastKnownLocation());
197        }
198        return mDetectedCountry;
199    }
200
201    /**
202     * Stop the current query without notifying the listener.
203     */
204    @Override
205    public synchronized void stop() {
206        if (mLocationListeners != null) {
207            for (LocationListener listener : mLocationListeners) {
208                unregisterListener(listener);
209            }
210            mLocationListeners = null;
211        }
212        if (mTimer != null) {
213            mTimer.cancel();
214            mTimer = null;
215        }
216    }
217
218    /**
219     * Start a new thread to query the country from Geocoder.
220     */
221    private synchronized void queryCountryCode(final Location location) {
222        if (location == null) {
223            notifyListener(null);
224            return;
225        }
226        if (mQueryThread != null) return;
227        mQueryThread = new Thread(new Runnable() {
228            @Override
229            public void run() {
230                String countryIso = null;
231                if (location != null) {
232                    countryIso = getCountryFromLocation(location);
233                }
234                if (countryIso != null) {
235                    mDetectedCountry = new Country(countryIso, Country.COUNTRY_SOURCE_LOCATION);
236                } else {
237                    mDetectedCountry = null;
238                }
239                notifyListener(mDetectedCountry);
240                mQueryThread = null;
241            }
242        });
243        mQueryThread.start();
244    }
245}
246