package com.google.android.gms.location.sample.locationsettings; import android.app.Activity; import android.content.Intent; import android.content.IntentSender; import android.location.Location; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks; import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener; import com.google.android.gms.common.api.PendingResult; import com.google.android.gms.common.api.ResultCallback; import com.google.android.gms.common.api.Status; import com.google.android.gms.location.LocationListener; import com.google.android.gms.location.LocationRequest; import com.google.android.gms.location.LocationServices; import com.google.android.gms.location.LocationSettingsRequest; import com.google.android.gms.location.LocationSettingsResult; import com.google.android.gms.location.LocationSettingsStatusCodes; import java.text.DateFormat; import java.util.Date; /** * Using location settings. * <p/> * Uses the {@link com.google.android.gms.location.SettingsApi} to ensure that the device's system * settings are properly configured for the app's location needs. When making a request to * Location services, the device's system settings may be in a state that prevents the app from * obtaining the location data that it needs. For example, GPS or Wi-Fi scanning may be switched * off. The {@code SettingsApi} makes it possible to determine if a device's system settings are * adequate for the location request, and to optionally invoke a dialog that allows the user to * enable the necessary settings. * <p/> * This sample allows the user to request location updates using the ACCESS_FINE_LOCATION setting * (as specified in AndroidManifest.xml). The sample requires that the device has location enabled * and set to the "High accuracy" mode. If location is not enabled, or if the location mode does * not permit high accuracy determination of location, the activity uses the {@code SettingsApi} * to invoke a dialog without requiring the developer to understand which settings are needed for * different Location requirements. */ public class MainActivity extends AppCompatActivity implements ConnectionCallbacks, OnConnectionFailedListener, LocationListener, ResultCallback<LocationSettingsResult> { protected static final String TAG = "MainActivity"; /** * Constant used in the location settings dialog. */ protected static final int REQUEST_CHECK_SETTINGS = 0x1; /** * The desired interval for location updates. Inexact. Updates may be more or less frequent. */ public static final long UPDATE_INTERVAL_IN_MILLISECONDS = 10000; /** * The fastest rate for active location updates. Exact. Updates will never be more frequent * than this value. */ public static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS = UPDATE_INTERVAL_IN_MILLISECONDS / 2; // Keys for storing activity state in the Bundle. protected final static String KEY_REQUESTING_LOCATION_UPDATES = "requesting-location-updates"; protected final static String KEY_LOCATION = "location"; protected final static String KEY_LAST_UPDATED_TIME_STRING = "last-updated-time-string"; /** * Provides the entry point to Google Play services. */ protected GoogleApiClient mGoogleApiClient; /** * Stores parameters for requests to the FusedLocationProviderApi. */ protected LocationRequest mLocationRequest; /** * Stores the types of location services the client is interested in using. Used for checking * settings to determine if the device has optimal location settings. */ protected LocationSettingsRequest mLocationSettingsRequest; /** * Represents a geographical location. */ protected Location mCurrentLocation; // UI Widgets. protected Button mStartUpdatesButton; protected Button mStopUpdatesButton; protected TextView mLastUpdateTimeTextView; protected TextView mLatitudeTextView; protected TextView mLongitudeTextView; // Labels. protected String mLatitudeLabel; protected String mLongitudeLabel; protected String mLastUpdateTimeLabel; /** * Tracks the status of the location updates request. Value changes when the user presses the * Start Updates and Stop Updates buttons. */ protected Boolean mRequestingLocationUpdates; /** * Time when the location was updated represented as a String. */ protected String mLastUpdateTime; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_activity); // Locate the UI widgets. mStartUpdatesButton = (Button) findViewById(R.id.start_updates_button); mStopUpdatesButton = (Button) findViewById(R.id.stop_updates_button); mLatitudeTextView = (TextView) findViewById(R.id.latitude_text); mLongitudeTextView = (TextView) findViewById(R.id.longitude_text); mLastUpdateTimeTextView = (TextView) findViewById(R.id.last_update_time_text); // Set labels. mLatitudeLabel = getResources().getString(R.string.latitude_label); mLongitudeLabel = getResources().getString(R.string.longitude_label); mLastUpdateTimeLabel = getResources().getString(R.string.last_update_time_label); mRequestingLocationUpdates = false; mLastUpdateTime = ""; // Update values using data stored in the Bundle. updateValuesFromBundle(savedInstanceState); // Kick off the process of building the GoogleApiClient, LocationRequest, and // LocationSettingsRequest objects. buildGoogleApiClient(); createLocationRequest(); buildLocationSettingsRequest(); } /** * Updates fields based on data stored in the bundle. * * @param savedInstanceState The activity state saved in the Bundle. */ private void updateValuesFromBundle(Bundle savedInstanceState) { if (savedInstanceState != null) { // Update the value of mRequestingLocationUpdates from the Bundle, and make sure that // the Start Updates and Stop Updates buttons are correctly enabled or disabled. if (savedInstanceState.keySet().contains(KEY_REQUESTING_LOCATION_UPDATES)) { mRequestingLocationUpdates = savedInstanceState.getBoolean( KEY_REQUESTING_LOCATION_UPDATES); } // Update the value of mCurrentLocation from the Bundle and update the UI to show the // correct latitude and longitude. if (savedInstanceState.keySet().contains(KEY_LOCATION)) { // Since KEY_LOCATION was found in the Bundle, we can be sure that mCurrentLocation // is not null. mCurrentLocation = savedInstanceState.getParcelable(KEY_LOCATION); } // Update the value of mLastUpdateTime from the Bundle and update the UI. if (savedInstanceState.keySet().contains(KEY_LAST_UPDATED_TIME_STRING)) { mLastUpdateTime = savedInstanceState.getString(KEY_LAST_UPDATED_TIME_STRING); } updateUI(); } } /** * Builds a GoogleApiClient. Uses the {@code #addApi} method to request the * LocationServices API. */ protected synchronized void buildGoogleApiClient() { Log.i(TAG, "Building GoogleApiClient"); mGoogleApiClient = new GoogleApiClient.Builder(this) .addConnectionCallbacks(this) .addOnConnectionFailedListener(this) .addApi(LocationServices.API) .build(); } /** * Sets up the location request. Android has two location request settings: * {@code ACCESS_COARSE_LOCATION} and {@code ACCESS_FINE_LOCATION}. These settings control * the accuracy of the current location. This sample uses ACCESS_FINE_LOCATION, as defined in * the AndroidManifest.xml. * <p/> * When the ACCESS_FINE_LOCATION setting is specified, combined with a fast update * interval (5 seconds), the Fused Location Provider API returns location updates that are * accurate to within a few feet. * <p/> * These settings are appropriate for mapping applications that show real-time location * updates. */ protected void createLocationRequest() { mLocationRequest = new LocationRequest(); // Sets the desired interval for active location updates. This interval is // inexact. You may not receive updates at all if no location sources are available, or // you may receive them slower than requested. You may also receive updates faster than // requested if other applications are requesting location at a faster interval. mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS); // Sets the fastest rate for active location updates. This interval is exact, and your // application will never receive updates faster than this value. mLocationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS); mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); } /** * Uses a {@link com.google.android.gms.location.LocationSettingsRequest.Builder} to build * a {@link com.google.android.gms.location.LocationSettingsRequest} that is used for checking * if a device has the needed location settings. */ protected void buildLocationSettingsRequest() { LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder(); builder.addLocationRequest(mLocationRequest); mLocationSettingsRequest = builder.build(); } /** * Check if the device's location settings are adequate for the app's needs using the * {@link com.google.android.gms.location.SettingsApi#checkLocationSettings(GoogleApiClient, * LocationSettingsRequest)} method, with the results provided through a {@code PendingResult}. */ protected void checkLocationSettings() { PendingResult<LocationSettingsResult> result = LocationServices.SettingsApi.checkLocationSettings( mGoogleApiClient, mLocationSettingsRequest ); result.setResultCallback(this); } /** * The callback invoked when * {@link com.google.android.gms.location.SettingsApi#checkLocationSettings(GoogleApiClient, * LocationSettingsRequest)} is called. Examines the * {@link com.google.android.gms.location.LocationSettingsResult} object and determines if * location settings are adequate. If they are not, begins the process of presenting a location * settings dialog to the user. */ @Override public void onResult(LocationSettingsResult locationSettingsResult) { final Status status = locationSettingsResult.getStatus(); switch (status.getStatusCode()) { case LocationSettingsStatusCodes.SUCCESS: Log.i(TAG, "All location settings are satisfied."); startLocationUpdates(); break; case LocationSettingsStatusCodes.RESOLUTION_REQUIRED: Log.i(TAG, "Location settings are not satisfied. Show the user a dialog to" + "upgrade location settings "); try { // Show the dialog by calling startResolutionForResult(), and check the result // in onActivityResult(). status.startResolutionForResult(MainActivity.this, REQUEST_CHECK_SETTINGS); } catch (IntentSender.SendIntentException e) { Log.i(TAG, "PendingIntent unable to execute request."); } break; case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE: Log.i(TAG, "Location settings are inadequate, and cannot be fixed here. Dialog " + "not created."); break; } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { // Check for the integer request code originally supplied to startResolutionForResult(). case REQUEST_CHECK_SETTINGS: switch (resultCode) { case Activity.RESULT_OK: Log.i(TAG, "User agreed to make required location settings changes."); startLocationUpdates(); break; case Activity.RESULT_CANCELED: Log.i(TAG, "User chose not to make required location settings changes."); break; } break; } } /** * Handles the Start Updates button and requests start of location updates. Does nothing if * updates have already been requested. */ public void startUpdatesButtonHandler(View view) { checkLocationSettings(); } /** * Handles the Stop Updates button, and requests removal of location updates. */ public void stopUpdatesButtonHandler(View view) { // It is a good practice to remove location requests when the activity is in a paused or // stopped state. Doing so helps battery performance and is especially // recommended in applications that request frequent location updates. stopLocationUpdates(); } /** * Requests location updates from the FusedLocationApi. */ protected void startLocationUpdates() { LocationServices.FusedLocationApi.requestLocationUpdates( mGoogleApiClient, mLocationRequest, this ).setResultCallback(new ResultCallback<Status>() { @Override public void onResult(Status status) { mRequestingLocationUpdates = true; setButtonsEnabledState(); } }); } /** * Updates all UI fields. */ private void updateUI() { setButtonsEnabledState(); updateLocationUI(); } /** * Disables both buttons when functionality is disabled due to insuffucient location settings. * Otherwise ensures that only one button is enabled at any time. The Start Updates button is * enabled if the user is not requesting location updates. The Stop Updates button is enabled * if the user is requesting location updates. */ private void setButtonsEnabledState() { if (mRequestingLocationUpdates) { mStartUpdatesButton.setEnabled(false); mStopUpdatesButton.setEnabled(true); } else { mStartUpdatesButton.setEnabled(true); mStopUpdatesButton.setEnabled(false); } } /** * Sets the value of the UI fields for the location latitude, longitude and last update time. */ private void updateLocationUI() { if (mCurrentLocation != null) { mLatitudeTextView.setText(String.format("%s: %f", mLatitudeLabel, mCurrentLocation.getLatitude())); mLongitudeTextView.setText(String.format("%s: %f", mLongitudeLabel, mCurrentLocation.getLongitude())); mLastUpdateTimeTextView.setText(String.format("%s: %s", mLastUpdateTimeLabel, mLastUpdateTime)); } } /** * Removes location updates from the FusedLocationApi. */ protected void stopLocationUpdates() { // It is a good practice to remove location requests when the activity is in a paused or // stopped state. Doing so helps battery performance and is especially // recommended in applications that request frequent location updates. LocationServices.FusedLocationApi.removeLocationUpdates( mGoogleApiClient, this ).setResultCallback(new ResultCallback<Status>() { @Override public void onResult(Status status) { mRequestingLocationUpdates = false; setButtonsEnabledState(); } }); } @Override protected void onStart() { super.onStart(); mGoogleApiClient.connect(); } @Override public void onResume() { super.onResume(); // Within {@code onPause()}, we pause location updates, but leave the // connection to GoogleApiClient intact. Here, we resume receiving // location updates if the user has requested them. if (mGoogleApiClient.isConnected() && mRequestingLocationUpdates) { startLocationUpdates(); } } @Override protected void onPause() { super.onPause(); // Stop location updates to save battery, but don't disconnect the GoogleApiClient object. if (mGoogleApiClient.isConnected()) { stopLocationUpdates(); } } @Override protected void onStop() { super.onStop(); mGoogleApiClient.disconnect(); } /** * Runs when a GoogleApiClient object successfully connects. */ @Override public void onConnected(Bundle connectionHint) { Log.i(TAG, "Connected to GoogleApiClient"); // If the initial location was never previously requested, we use // FusedLocationApi.getLastLocation() to get it. If it was previously requested, we store // its value in the Bundle and check for it in onCreate(). We // do not request it again unless the user specifically requests location updates by pressing // the Start Updates button. // // Because we cache the value of the initial location in the Bundle, it means that if the // user launches the activity, // moves to a new location, and then changes the device orientation, the original location // is displayed as the activity is re-created. if (mCurrentLocation == null) { mCurrentLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient); mLastUpdateTime = DateFormat.getTimeInstance().format(new Date()); updateLocationUI(); } } /** * Callback that fires when the location changes. */ @Override public void onLocationChanged(Location location) { mCurrentLocation = location; mLastUpdateTime = DateFormat.getTimeInstance().format(new Date()); updateLocationUI(); Toast.makeText(this, getResources().getString(R.string.location_updated_message), Toast.LENGTH_SHORT).show(); } @Override public void onConnectionSuspended(int cause) { Log.i(TAG, "Connection suspended"); } @Override public void onConnectionFailed(ConnectionResult result) { // Refer to the javadoc for ConnectionResult to see what error codes might be returned in // onConnectionFailed. Log.i(TAG, "Connection failed: ConnectionResult.getErrorCode() = " + result.getErrorCode()); } /** * Stores activity data in the Bundle. */ public void onSaveInstanceState(Bundle savedInstanceState) { savedInstanceState.putBoolean(KEY_REQUESTING_LOCATION_UPDATES, mRequestingLocationUpdates); savedInstanceState.putParcelable(KEY_LOCATION, mCurrentLocation); savedInstanceState.putString(KEY_LAST_UPDATED_TIME_STRING, mLastUpdateTime); super.onSaveInstanceState(savedInstanceState); } }