Home | History | Annotate | Download | only in location
      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 
     17 package com.android.server.location;
     18 
     19 import android.content.Context;
     20 import android.location.Address;
     21 import android.location.Country;
     22 import android.location.Geocoder;
     23 import android.location.Location;
     24 import android.location.LocationListener;
     25 import android.location.LocationManager;
     26 import android.os.Bundle;
     27 import android.util.Slog;
     28 
     29 import java.io.IOException;
     30 import java.util.ArrayList;
     31 import java.util.List;
     32 import java.util.Timer;
     33 import 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  */
     48 public 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.getElapsedRealtimeNanos() <
    119                         lastKnownLocation.getElapsedRealtimeNanos()) {
    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