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.Binder;
     27 import android.os.Bundle;
     28 import android.util.Slog;
     29 
     30 import java.io.IOException;
     31 import java.util.ArrayList;
     32 import java.util.List;
     33 import java.util.Timer;
     34 import java.util.TimerTask;
     35 
     36 /**
     37  * This class detects which country the user currently is in through the enabled
     38  * location providers and the GeoCoder
     39  * <p>
     40  * Use {@link #detectCountry} to start querying. If the location can not be
     41  * resolved within the given time, the last known location will be used to get
     42  * the user country through the GeoCoder. The IllegalStateException will be
     43  * thrown if there is a ongoing query.
     44  * <p>
     45  * The current query can be stopped by {@link #stop()}
     46  *
     47  * @hide
     48  */
     49 public class LocationBasedCountryDetector extends CountryDetectorBase {
     50     private final static String TAG = "LocationBasedCountryDetector";
     51     private final static long QUERY_LOCATION_TIMEOUT = 1000 * 60 * 5; // 5 mins
     52 
     53     /**
     54      * Used for canceling location query
     55      */
     56     protected Timer mTimer;
     57 
     58     /**
     59      * The thread to query the country from the GeoCoder.
     60      */
     61     protected Thread mQueryThread;
     62     protected List<LocationListener> mLocationListeners;
     63 
     64     private LocationManager mLocationManager;
     65     private List<String> mEnabledProviders;
     66 
     67     public LocationBasedCountryDetector(Context ctx) {
     68         super(ctx);
     69         mLocationManager = (LocationManager) ctx.getSystemService(Context.LOCATION_SERVICE);
     70     }
     71 
     72     /**
     73      * @return the ISO 3166-1 two letters country code from the location
     74      */
     75     protected String getCountryFromLocation(Location location) {
     76         String country = null;
     77         Geocoder geoCoder = new Geocoder(mContext);
     78         try {
     79             List<Address> addresses = geoCoder.getFromLocation(
     80                     location.getLatitude(), location.getLongitude(), 1);
     81             if (addresses != null && addresses.size() > 0) {
     82                 country = addresses.get(0).getCountryCode();
     83             }
     84         } catch (IOException e) {
     85             Slog.w(TAG, "Exception occurs when getting country from location");
     86         }
     87         return country;
     88     }
     89 
     90     protected boolean isAcceptableProvider(String provider) {
     91         // We don't want to actively initiate a location fix here (with gps or network providers).
     92         return LocationManager.PASSIVE_PROVIDER.equals(provider);
     93     }
     94 
     95     /**
     96      * Register a listener with a provider name
     97      */
     98     protected void registerListener(String provider, LocationListener listener) {
     99         final long bid = Binder.clearCallingIdentity();
    100         try {
    101             mLocationManager.requestLocationUpdates(provider, 0, 0, listener);
    102         } finally {
    103             Binder.restoreCallingIdentity(bid);
    104         }
    105     }
    106 
    107     /**
    108      * Unregister an already registered listener
    109      */
    110     protected void unregisterListener(LocationListener listener) {
    111         final long bid = Binder.clearCallingIdentity();
    112         try {
    113             mLocationManager.removeUpdates(listener);
    114         } finally {
    115             Binder.restoreCallingIdentity(bid);
    116         }
    117     }
    118 
    119     /**
    120      * @return the last known location from all providers
    121      */
    122     protected Location getLastKnownLocation() {
    123         final long bid = Binder.clearCallingIdentity();
    124         try {
    125             List<String> providers = mLocationManager.getAllProviders();
    126             Location bestLocation = null;
    127             for (String provider : providers) {
    128                 Location lastKnownLocation = mLocationManager.getLastKnownLocation(provider);
    129                 if (lastKnownLocation != null) {
    130                     if (bestLocation == null ||
    131                             bestLocation.getElapsedRealtimeNanos() <
    132                             lastKnownLocation.getElapsedRealtimeNanos()) {
    133                         bestLocation = lastKnownLocation;
    134                     }
    135                 }
    136             }
    137             return bestLocation;
    138         } finally {
    139             Binder.restoreCallingIdentity(bid);
    140         }
    141     }
    142 
    143     /**
    144      * @return the timeout for querying the location.
    145      */
    146     protected long getQueryLocationTimeout() {
    147         return QUERY_LOCATION_TIMEOUT;
    148     }
    149 
    150     protected List<String> getEnabledProviders() {
    151         if (mEnabledProviders == null) {
    152             mEnabledProviders = mLocationManager.getProviders(true);
    153         }
    154         return mEnabledProviders;
    155     }
    156 
    157     /**
    158      * Start detecting the country.
    159      * <p>
    160      * Queries the location from all location providers, then starts a thread to query the
    161      * country from GeoCoder.
    162      */
    163     @Override
    164     public synchronized Country detectCountry() {
    165         if (mLocationListeners  != null) {
    166             throw new IllegalStateException();
    167         }
    168         // Request the location from all enabled providers.
    169         List<String> enabledProviders = getEnabledProviders();
    170         int totalProviders = enabledProviders.size();
    171         if (totalProviders > 0) {
    172             mLocationListeners = new ArrayList<LocationListener>(totalProviders);
    173             for (int i = 0; i < totalProviders; i++) {
    174                 String provider = enabledProviders.get(i);
    175                 if (isAcceptableProvider(provider)) {
    176                     LocationListener listener = new LocationListener () {
    177                         @Override
    178                         public void onLocationChanged(Location location) {
    179                             if (location != null) {
    180                                 LocationBasedCountryDetector.this.stop();
    181                                 queryCountryCode(location);
    182                             }
    183                         }
    184                         @Override
    185                         public void onProviderDisabled(String provider) {
    186                         }
    187                         @Override
    188                         public void onProviderEnabled(String provider) {
    189                         }
    190                         @Override
    191                         public void onStatusChanged(String provider, int status, Bundle extras) {
    192                         }
    193                     };
    194                     mLocationListeners.add(listener);
    195                     registerListener(provider, listener);
    196                 }
    197             }
    198 
    199             mTimer = new Timer();
    200             mTimer.schedule(new TimerTask() {
    201                 @Override
    202                 public void run() {
    203                     mTimer = null;
    204                     LocationBasedCountryDetector.this.stop();
    205                     // Looks like no provider could provide the location, let's try the last
    206                     // known location.
    207                     queryCountryCode(getLastKnownLocation());
    208                 }
    209             }, getQueryLocationTimeout());
    210         } else {
    211             // There is no provider enabled.
    212             queryCountryCode(getLastKnownLocation());
    213         }
    214         return mDetectedCountry;
    215     }
    216 
    217     /**
    218      * Stop the current query without notifying the listener.
    219      */
    220     @Override
    221     public synchronized void stop() {
    222         if (mLocationListeners != null) {
    223             for (LocationListener listener : mLocationListeners) {
    224                 unregisterListener(listener);
    225             }
    226             mLocationListeners = null;
    227         }
    228         if (mTimer != null) {
    229             mTimer.cancel();
    230             mTimer = null;
    231         }
    232     }
    233 
    234     /**
    235      * Start a new thread to query the country from Geocoder.
    236      */
    237     private synchronized void queryCountryCode(final Location location) {
    238         if (location == null) {
    239             notifyListener(null);
    240             return;
    241         }
    242         if (mQueryThread != null) return;
    243         mQueryThread = new Thread(new Runnable() {
    244             @Override
    245             public void run() {
    246                 String countryIso = null;
    247                 if (location != null) {
    248                     countryIso = getCountryFromLocation(location);
    249                 }
    250                 if (countryIso != null) {
    251                     mDetectedCountry = new Country(countryIso, Country.COUNTRY_SOURCE_LOCATION);
    252                 } else {
    253                     mDetectedCountry = null;
    254                 }
    255                 notifyListener(mDetectedCountry);
    256                 mQueryThread = null;
    257             }
    258         });
    259         mQueryThread.start();
    260     }
    261 }
    262