Home | History | Annotate | Download | only in location
      1 /*
      2  * Copyright (C) 2012 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.example.android.location;
     18 
     19 import android.annotation.SuppressLint;
     20 import android.app.AlertDialog;
     21 import android.app.Dialog;
     22 import android.content.Context;
     23 import android.content.DialogInterface;
     24 import android.content.Intent;
     25 import android.location.Address;
     26 import android.location.Geocoder;
     27 import android.location.Location;
     28 import android.location.LocationListener;
     29 import android.location.LocationManager;
     30 import android.os.AsyncTask;
     31 import android.os.Build;
     32 import android.os.Bundle;
     33 import android.os.Handler;
     34 import android.os.Message;
     35 import android.provider.Settings;
     36 import android.support.v4.app.DialogFragment;
     37 import android.support.v4.app.FragmentActivity;
     38 import android.view.View;
     39 import android.widget.Button;
     40 import android.widget.TextView;
     41 import android.widget.Toast;
     42 
     43 import java.io.IOException;
     44 import java.util.List;
     45 import java.util.Locale;
     46 
     47 public class LocationActivity extends FragmentActivity {
     48     private TextView mLatLng;
     49     private TextView mAddress;
     50     private Button mFineProviderButton;
     51     private Button mBothProviderButton;
     52     private LocationManager mLocationManager;
     53     private Handler mHandler;
     54     private boolean mGeocoderAvailable;
     55     private boolean mUseFine;
     56     private boolean mUseBoth;
     57 
     58     // Keys for maintaining UI states after rotation.
     59     private static final String KEY_FINE = "use_fine";
     60     private static final String KEY_BOTH = "use_both";
     61     // UI handler codes.
     62     private static final int UPDATE_ADDRESS = 1;
     63     private static final int UPDATE_LATLNG = 2;
     64 
     65     private static final int TEN_SECONDS = 10000;
     66     private static final int TEN_METERS = 10;
     67     private static final int TWO_MINUTES = 1000 * 60 * 2;
     68 
     69     /**
     70      * This sample demonstrates how to incorporate location based services in your app and
     71      * process location updates.  The app also shows how to convert lat/long coordinates to
     72      * human-readable addresses.
     73      */
     74     @SuppressLint("NewApi")
     75     @Override
     76     public void onCreate(Bundle savedInstanceState) {
     77         super.onCreate(savedInstanceState);
     78         setContentView(R.layout.main);
     79 
     80         // Restore apps state (if exists) after rotation.
     81         if (savedInstanceState != null) {
     82             mUseFine = savedInstanceState.getBoolean(KEY_FINE);
     83             mUseBoth = savedInstanceState.getBoolean(KEY_BOTH);
     84         } else {
     85             mUseFine = false;
     86             mUseBoth = false;
     87         }
     88         mLatLng = (TextView) findViewById(R.id.latlng);
     89         mAddress = (TextView) findViewById(R.id.address);
     90         // Receive location updates from the fine location provider (gps) only.
     91         mFineProviderButton = (Button) findViewById(R.id.provider_fine);
     92         // Receive location updates from both the fine (gps) and coarse (network) location
     93         // providers.
     94         mBothProviderButton = (Button) findViewById(R.id.provider_both);
     95 
     96         // The isPresent() helper method is only available on Gingerbread or above.
     97         mGeocoderAvailable =
     98                 Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD && Geocoder.isPresent();
     99 
    100         // Handler for updating text fields on the UI like the lat/long and address.
    101         mHandler = new Handler() {
    102             public void handleMessage(Message msg) {
    103                 switch (msg.what) {
    104                     case UPDATE_ADDRESS:
    105                         mAddress.setText((String) msg.obj);
    106                         break;
    107                     case UPDATE_LATLNG:
    108                         mLatLng.setText((String) msg.obj);
    109                         break;
    110                 }
    111             }
    112         };
    113         // Get a reference to the LocationManager object.
    114         mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
    115     }
    116 
    117     // Restores UI states after rotation.
    118     @Override
    119     protected void onSaveInstanceState(Bundle outState) {
    120         super.onSaveInstanceState(outState);
    121         outState.putBoolean(KEY_FINE, mUseFine);
    122         outState.putBoolean(KEY_BOTH, mUseBoth);
    123     }
    124 
    125     @Override
    126     protected void onResume() {
    127         super.onResume();
    128         setup();
    129     }
    130 
    131     @Override
    132     protected void onStart() {
    133         super.onStart();
    134 
    135         // Check if the GPS setting is currently enabled on the device.
    136         // This verification should be done during onStart() because the system calls this method
    137         // when the user returns to the activity, which ensures the desired location provider is
    138         // enabled each time the activity resumes from the stopped state.
    139         LocationManager locationManager =
    140                 (LocationManager) getSystemService(Context.LOCATION_SERVICE);
    141         final boolean gpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
    142 
    143         if (!gpsEnabled) {
    144             // Build an alert dialog here that requests that the user enable
    145             // the location services, then when the user clicks the "OK" button,
    146             // call enableLocationSettings()
    147             new EnableGpsDialogFragment().show(getSupportFragmentManager(), "enableGpsDialog");
    148         }
    149     }
    150 
    151     // Method to launch Settings
    152     private void enableLocationSettings() {
    153         Intent settingsIntent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
    154         startActivity(settingsIntent);
    155     }
    156 
    157     // Stop receiving location updates whenever the Activity becomes invisible.
    158     @Override
    159     protected void onStop() {
    160         super.onStop();
    161         mLocationManager.removeUpdates(listener);
    162     }
    163 
    164     // Set up fine and/or coarse location providers depending on whether the fine provider or
    165     // both providers button is pressed.
    166     private void setup() {
    167         Location gpsLocation = null;
    168         Location networkLocation = null;
    169         mLocationManager.removeUpdates(listener);
    170         mLatLng.setText(R.string.unknown);
    171         mAddress.setText(R.string.unknown);
    172         // Get fine location updates only.
    173         if (mUseFine) {
    174             mFineProviderButton.setBackgroundResource(R.drawable.button_active);
    175             mBothProviderButton.setBackgroundResource(R.drawable.button_inactive);
    176             // Request updates from just the fine (gps) provider.
    177             gpsLocation = requestUpdatesFromProvider(
    178                     LocationManager.GPS_PROVIDER, R.string.not_support_gps);
    179             // Update the UI immediately if a location is obtained.
    180             if (gpsLocation != null) updateUILocation(gpsLocation);
    181         } else if (mUseBoth) {
    182             // Get coarse and fine location updates.
    183             mFineProviderButton.setBackgroundResource(R.drawable.button_inactive);
    184             mBothProviderButton.setBackgroundResource(R.drawable.button_active);
    185             // Request updates from both fine (gps) and coarse (network) providers.
    186             gpsLocation = requestUpdatesFromProvider(
    187                     LocationManager.GPS_PROVIDER, R.string.not_support_gps);
    188             networkLocation = requestUpdatesFromProvider(
    189                     LocationManager.NETWORK_PROVIDER, R.string.not_support_network);
    190 
    191             // If both providers return last known locations, compare the two and use the better
    192             // one to update the UI.  If only one provider returns a location, use it.
    193             if (gpsLocation != null && networkLocation != null) {
    194                 updateUILocation(getBetterLocation(gpsLocation, networkLocation));
    195             } else if (gpsLocation != null) {
    196                 updateUILocation(gpsLocation);
    197             } else if (networkLocation != null) {
    198                 updateUILocation(networkLocation);
    199             }
    200         }
    201     }
    202 
    203     /**
    204      * Method to register location updates with a desired location provider.  If the requested
    205      * provider is not available on the device, the app displays a Toast with a message referenced
    206      * by a resource id.
    207      *
    208      * @param provider Name of the requested provider.
    209      * @param errorResId Resource id for the string message to be displayed if the provider does
    210      *                   not exist on the device.
    211      * @return A previously returned {@link android.location.Location} from the requested provider,
    212      *         if exists.
    213      */
    214     private Location requestUpdatesFromProvider(final String provider, final int errorResId) {
    215         Location location = null;
    216         if (mLocationManager.isProviderEnabled(provider)) {
    217             mLocationManager.requestLocationUpdates(provider, TEN_SECONDS, TEN_METERS, listener);
    218             location = mLocationManager.getLastKnownLocation(provider);
    219         } else {
    220             Toast.makeText(this, errorResId, Toast.LENGTH_LONG).show();
    221         }
    222         return location;
    223     }
    224 
    225     // Callback method for the "fine provider" button.
    226     public void useFineProvider(View v) {
    227         mUseFine = true;
    228         mUseBoth = false;
    229         setup();
    230     }
    231 
    232     // Callback method for the "both providers" button.
    233     public void useCoarseFineProviders(View v) {
    234         mUseFine = false;
    235         mUseBoth = true;
    236         setup();
    237     }
    238 
    239     private void doReverseGeocoding(Location location) {
    240         // Since the geocoding API is synchronous and may take a while.  You don't want to lock
    241         // up the UI thread.  Invoking reverse geocoding in an AsyncTask.
    242         (new ReverseGeocodingTask(this)).execute(new Location[] {location});
    243     }
    244 
    245     private void updateUILocation(Location location) {
    246         // We're sending the update to a handler which then updates the UI with the new
    247         // location.
    248         Message.obtain(mHandler,
    249                 UPDATE_LATLNG,
    250                 location.getLatitude() + ", " + location.getLongitude()).sendToTarget();
    251 
    252         // Bypass reverse-geocoding only if the Geocoder service is available on the device.
    253         if (mGeocoderAvailable) doReverseGeocoding(location);
    254     }
    255 
    256     private final LocationListener listener = new LocationListener() {
    257 
    258         @Override
    259         public void onLocationChanged(Location location) {
    260             // A new location update is received.  Do something useful with it.  Update the UI with
    261             // the location update.
    262             updateUILocation(location);
    263         }
    264 
    265         @Override
    266         public void onProviderDisabled(String provider) {
    267         }
    268 
    269         @Override
    270         public void onProviderEnabled(String provider) {
    271         }
    272 
    273         @Override
    274         public void onStatusChanged(String provider, int status, Bundle extras) {
    275         }
    276     };
    277 
    278     /** Determines whether one Location reading is better than the current Location fix.
    279       * Code taken from
    280       * http://developer.android.com/guide/topics/location/obtaining-user-location.html
    281       *
    282       * @param newLocation  The new Location that you want to evaluate
    283       * @param currentBestLocation  The current Location fix, to which you want to compare the new
    284       *        one
    285       * @return The better Location object based on recency and accuracy.
    286       */
    287     protected Location getBetterLocation(Location newLocation, Location currentBestLocation) {
    288         if (currentBestLocation == null) {
    289             // A new location is always better than no location
    290             return newLocation;
    291         }
    292 
    293         // Check whether the new location fix is newer or older
    294         long timeDelta = newLocation.getTime() - currentBestLocation.getTime();
    295         boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
    296         boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
    297         boolean isNewer = timeDelta > 0;
    298 
    299         // If it's been more than two minutes since the current location, use the new location
    300         // because the user has likely moved.
    301         if (isSignificantlyNewer) {
    302             return newLocation;
    303         // If the new location is more than two minutes older, it must be worse
    304         } else if (isSignificantlyOlder) {
    305             return currentBestLocation;
    306         }
    307 
    308         // Check whether the new location fix is more or less accurate
    309         int accuracyDelta = (int) (newLocation.getAccuracy() - currentBestLocation.getAccuracy());
    310         boolean isLessAccurate = accuracyDelta > 0;
    311         boolean isMoreAccurate = accuracyDelta < 0;
    312         boolean isSignificantlyLessAccurate = accuracyDelta > 200;
    313 
    314         // Check if the old and new location are from the same provider
    315         boolean isFromSameProvider = isSameProvider(newLocation.getProvider(),
    316                 currentBestLocation.getProvider());
    317 
    318         // Determine location quality using a combination of timeliness and accuracy
    319         if (isMoreAccurate) {
    320             return newLocation;
    321         } else if (isNewer && !isLessAccurate) {
    322             return newLocation;
    323         } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
    324             return newLocation;
    325         }
    326         return currentBestLocation;
    327     }
    328 
    329     /** Checks whether two providers are the same */
    330     private boolean isSameProvider(String provider1, String provider2) {
    331         if (provider1 == null) {
    332           return provider2 == null;
    333         }
    334         return provider1.equals(provider2);
    335     }
    336 
    337     // AsyncTask encapsulating the reverse-geocoding API.  Since the geocoder API is blocked,
    338     // we do not want to invoke it from the UI thread.
    339     private class ReverseGeocodingTask extends AsyncTask<Location, Void, Void> {
    340         Context mContext;
    341 
    342         public ReverseGeocodingTask(Context context) {
    343             super();
    344             mContext = context;
    345         }
    346 
    347         @Override
    348         protected Void doInBackground(Location... params) {
    349             Geocoder geocoder = new Geocoder(mContext, Locale.getDefault());
    350 
    351             Location loc = params[0];
    352             List<Address> addresses = null;
    353             try {
    354                 addresses = geocoder.getFromLocation(loc.getLatitude(), loc.getLongitude(), 1);
    355             } catch (IOException e) {
    356                 e.printStackTrace();
    357                 // Update address field with the exception.
    358                 Message.obtain(mHandler, UPDATE_ADDRESS, e.toString()).sendToTarget();
    359             }
    360             if (addresses != null && addresses.size() > 0) {
    361                 Address address = addresses.get(0);
    362                 // Format the first line of address (if available), city, and country name.
    363                 String addressText = String.format("%s, %s, %s",
    364                         address.getMaxAddressLineIndex() > 0 ? address.getAddressLine(0) : "",
    365                         address.getLocality(),
    366                         address.getCountryName());
    367                 // Update address field on UI.
    368                 Message.obtain(mHandler, UPDATE_ADDRESS, addressText).sendToTarget();
    369             }
    370             return null;
    371         }
    372     }
    373 
    374     /**
    375      * Dialog to prompt users to enable GPS on the device.
    376      */
    377     private class EnableGpsDialogFragment extends DialogFragment {
    378 
    379         @Override
    380         public Dialog onCreateDialog(Bundle savedInstanceState) {
    381             return new AlertDialog.Builder(getActivity())
    382                     .setTitle(R.string.enable_gps)
    383                     .setMessage(R.string.enable_gps_dialog)
    384                     .setPositiveButton(R.string.enable_gps, new DialogInterface.OnClickListener() {
    385                         @Override
    386                         public void onClick(DialogInterface dialog, int which) {
    387                             enableLocationSettings();
    388                         }
    389                     })
    390                     .create();
    391         }
    392     }
    393 }
    394