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 || 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