LocationBasedCountryDetector.java revision a550bdc84af70babb48091197bfb1b93d3671664
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 || bestLocation.getTime() < lastKnownLocation.getTime()) {
118                    bestLocation = lastKnownLocation;
119                }
120            }
121        }
122        return bestLocation;
123    }
124
125    /**
126     * @return the timeout for querying the location.
127     */
128    protected long getQueryLocationTimeout() {
129        return QUERY_LOCATION_TIMEOUT;
130    }
131
132    protected List<String> getEnabledProviders() {
133        if (mEnabledProviders == null) {
134            mEnabledProviders = mLocationManager.getProviders(true);
135        }
136        return mEnabledProviders;
137    }
138
139    /**
140     * Start detecting the country.
141     * <p>
142     * Queries the location from all location providers, then starts a thread to query the
143     * country from GeoCoder.
144     */
145    @Override
146    public synchronized Country detectCountry() {
147        if (mLocationListeners  != null) {
148            throw new IllegalStateException();
149        }
150        // Request the location from all enabled providers.
151        List<String> enabledProviders = getEnabledProviders();
152        int totalProviders = enabledProviders.size();
153        if (totalProviders > 0) {
154            mLocationListeners = new ArrayList<LocationListener>(totalProviders);
155            for (int i = 0; i < totalProviders; i++) {
156                String provider = enabledProviders.get(i);
157                if (isAcceptableProvider(provider)) {
158                    LocationListener listener = new LocationListener () {
159                        @Override
160                        public void onLocationChanged(Location location) {
161                            if (location != null) {
162                                LocationBasedCountryDetector.this.stop();
163                                queryCountryCode(location);
164                            }
165                        }
166                        @Override
167                        public void onProviderDisabled(String provider) {
168                        }
169                        @Override
170                        public void onProviderEnabled(String provider) {
171                        }
172                        @Override
173                        public void onStatusChanged(String provider, int status, Bundle extras) {
174                        }
175                    };
176                    mLocationListeners.add(listener);
177                    registerListener(provider, listener);
178                }
179            }
180
181            mTimer = new Timer();
182            mTimer.schedule(new TimerTask() {
183                @Override
184                public void run() {
185                    mTimer = null;
186                    LocationBasedCountryDetector.this.stop();
187                    // Looks like no provider could provide the location, let's try the last
188                    // known location.
189                    queryCountryCode(getLastKnownLocation());
190                }
191            }, getQueryLocationTimeout());
192        } else {
193            // There is no provider enabled.
194            queryCountryCode(getLastKnownLocation());
195        }
196        return mDetectedCountry;
197    }
198
199    /**
200     * Stop the current query without notifying the listener.
201     */
202    @Override
203    public synchronized void stop() {
204        if (mLocationListeners != null) {
205            for (LocationListener listener : mLocationListeners) {
206                unregisterListener(listener);
207            }
208            mLocationListeners = null;
209        }
210        if (mTimer != null) {
211            mTimer.cancel();
212            mTimer = null;
213        }
214    }
215
216    /**
217     * Start a new thread to query the country from Geocoder.
218     */
219    private synchronized void queryCountryCode(final Location location) {
220        if (location == null) {
221            notifyListener(null);
222            return;
223        }
224        if (mQueryThread != null) return;
225        mQueryThread = new Thread(new Runnable() {
226            @Override
227            public void run() {
228                String countryIso = null;
229                if (location != null) {
230                    countryIso = getCountryFromLocation(location);
231                }
232                if (countryIso != null) {
233                    mDetectedCountry = new Country(countryIso, Country.COUNTRY_SOURCE_LOCATION);
234                } else {
235                    mDetectedCountry = null;
236                }
237                notifyListener(mDetectedCountry);
238                mQueryThread = null;
239            }
240        });
241        mQueryThread.start();
242    }
243}
244