package io.nlopez.smartlocation.location.providers;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Bundle;
import android.os.Looper;
import android.support.v4.app.ActivityCompat;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
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 io.nlopez.smartlocation.OnLocationUpdatedListener;
import io.nlopez.smartlocation.location.LocationStore;
import io.nlopez.smartlocation.location.ServiceLocationProvider;
import io.nlopez.smartlocation.location.config.LocationParams;
import io.nlopez.smartlocation.utils.GooglePlayServicesListener;
import io.nlopez.smartlocation.utils.Logger;
import io.nlopez.smartlocation.utils.ServiceConnectionListener;
/**
* Created by mrm on 20/12/14.
*/
public class LocationGooglePlayServicesProvider implements ServiceLocationProvider, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, LocationListener, ResultCallback<Status> {
public static final int REQUEST_START_LOCATION_FIX = 10001;
public static final int REQUEST_CHECK_SETTINGS = 20001;
private static final String GMS_ID = "GMS";
private GoogleApiClient client;
private Logger logger;
private OnLocationUpdatedListener listener;
private boolean shouldStart = false;
private boolean stopped = false;
private LocationStore locationStore;
private LocationRequest locationRequest;
private Context context;
private GooglePlayServicesListener googlePlayServicesListener;
private ServiceConnectionListener serviceListener;
private boolean checkLocationSettings;
private boolean fulfilledCheckLocationSettings;
private boolean alwaysShow = true;
public LocationGooglePlayServicesProvider() {
checkLocationSettings = false;
fulfilledCheckLocationSettings = false;
}
public LocationGooglePlayServicesProvider(GooglePlayServicesListener playServicesListener) {
this();
googlePlayServicesListener = playServicesListener;
}
public LocationGooglePlayServicesProvider(ServiceConnectionListener serviceListener) {
this();
this.serviceListener = serviceListener;
}
@Override
public void init(Context context, Logger logger) {
this.logger = logger;
this.context = context;
locationStore = new LocationStore(context);
if (!shouldStart) {
this.client = new GoogleApiClient.Builder(context)
.addApi(LocationServices.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
client.connect();
} else {
logger.d("already started");
}
}
private LocationRequest createRequest(LocationParams params, boolean singleUpdate) {
LocationRequest request = LocationRequest.create()
.setFastestInterval(params.getInterval())
.setInterval(params.getInterval())
.setSmallestDisplacement(params.getDistance());
switch (params.getAccuracy()) {
case HIGH:
request.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
break;
case MEDIUM:
request.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
break;
case LOW:
request.setPriority(LocationRequest.PRIORITY_LOW_POWER);
break;
case LOWEST:
request.setPriority(LocationRequest.PRIORITY_NO_POWER);
break;
}
if (singleUpdate) {
request.setNumUpdates(1);
}
return request;
}
@Override
public void start(OnLocationUpdatedListener listener, LocationParams params, boolean singleUpdate) {
this.listener = listener;
if (listener == null) {
logger.d("Listener is null, you sure about this?");
}
locationRequest = createRequest(params, singleUpdate);
if (client.isConnected()) {
startUpdating(locationRequest);
} else if (stopped) {
shouldStart = true;
client.connect();
stopped = false;
} else {
shouldStart = true;
logger.d("still not connected - scheduled start when connection is ok");
}
}
private void startUpdating(LocationRequest request) {
// TODO wait until the connection is done and retry
if (checkLocationSettings && !fulfilledCheckLocationSettings) {
logger.d("startUpdating wont be executed for now, as we have to test the location settings before");
checkLocationSettings();
return;
}
if (client.isConnected()) {
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) !=
PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(context,
Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
logger.i("Permission check failed. Please handle it in your app before setting up location");
// TODO: Consider calling ActivityCompat#requestPermissions here to request the
// missing permissions, and then overriding onRequestPermissionsResult
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
}
LocationServices.FusedLocationApi.requestLocationUpdates(client, request, this, Looper.getMainLooper()).setResultCallback(this);
} else {
logger.w("startUpdating executed without the GoogleApiClient being connected!!");
}
}
private void checkLocationSettings() {
LocationSettingsRequest request = new LocationSettingsRequest.Builder().setAlwaysShow(alwaysShow).addLocationRequest(locationRequest).build();
LocationServices.SettingsApi.checkLocationSettings(client, request).setResultCallback(settingsResultCallback);
}
@Override
public void stop() {
logger.d("stop");
if (client.isConnected()) {
LocationServices.FusedLocationApi.removeLocationUpdates(client, this);
client.disconnect();
}
fulfilledCheckLocationSettings = false;
shouldStart = false;
stopped = true;
}
@Override
public Location getLastLocation() {
if (client != null && client.isConnected()) {
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) !=
PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(context,
Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return null;
}
Location location = LocationServices.FusedLocationApi.getLastLocation(client);
if (location != null) {
return location;
}
}
if (locationStore != null) {
return locationStore.get(GMS_ID);
}
return null;
}
@Override
public ServiceConnectionListener getServiceListener() {
return serviceListener;
}
@Override
public void setServiceListener(ServiceConnectionListener listener) {
serviceListener = listener;
}
@Override
public void onConnected(Bundle bundle) {
logger.d("onConnected");
if (shouldStart) {
startUpdating(locationRequest);
}
if (googlePlayServicesListener != null) {
googlePlayServicesListener.onConnected(bundle);
}
if (serviceListener != null) {
serviceListener.onConnected();
}
}
@Override
public void onConnectionSuspended(int i) {
logger.d("onConnectionSuspended " + i);
if (googlePlayServicesListener != null) {
googlePlayServicesListener.onConnectionSuspended(i);
}
if (serviceListener != null) {
serviceListener.onConnectionSuspended();
}
}
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
logger.d("onConnectionFailed " + connectionResult.toString());
if (googlePlayServicesListener != null) {
googlePlayServicesListener.onConnectionFailed(connectionResult);
}
if (serviceListener != null) {
serviceListener.onConnectionFailed();
}
}
@Override
public void onLocationChanged(Location location) {
logger.d("onLocationChanged", location);
if (listener != null) {
listener.onLocationUpdated(location);
}
if (locationStore != null) {
logger.d("Stored in SharedPreferences");
locationStore.put(GMS_ID, location);
}
}
@Override
public void onResult(Status status) {
if (status.isSuccess()) {
logger.d("Locations update request successful");
} else if (status.hasResolution() && context instanceof Activity) {
logger.w(
"Unable to register, but we can solve this - will startActivityForResult. You should hook into the Activity onActivityResult and call this provider's onActivityResult method for continuing this call flow.");
try {
status.startResolutionForResult((Activity) context, REQUEST_START_LOCATION_FIX);
} catch (IntentSender.SendIntentException e) {
logger.e(e, "problem with startResolutionForResult");
}
} else {
// No recovery. Weep softly or inform the user.
logger.e("Registering failed: " + status.getStatusMessage());
}
}
/**
* @return TRUE if active, FALSE if the settings wont be checked before launching the location updates request
*/
public boolean isCheckingLocationSettings() {
return checkLocationSettings;
}
/**
* Sets whether or not we should request (before starting updates) the availability of the
* location settings and act upon it.
*
* @param allowingLocationSettings TRUE to show the dialog if needed, FALSE otherwise (default)
*/
public void setCheckLocationSettings(boolean allowingLocationSettings) {
this.checkLocationSettings = allowingLocationSettings;
}
/**
* Sets whether or not we should show location settings dialog with NEVER button
*
* @param alwaysShow TRUE to show dialog without NEVER button, FALSE - with NEVER button (default)
*/
public void setLocationSettingsAlwaysShow(boolean alwaysShow) {
this.alwaysShow = alwaysShow;
}
/**
* This method should be called in the onActivityResult of the calling activity whenever we are
* trying to implement the Check Location Settings fix dialog.
*
* @param requestCode
* @param resultCode
* @param data
*/
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CHECK_SETTINGS) {
switch (resultCode) {
case Activity.RESULT_OK:
logger.i("User agreed to make required location settings changes.");
fulfilledCheckLocationSettings = true;
startUpdating(locationRequest);
break;
case Activity.RESULT_CANCELED:
logger.i("User chose not to make required location settings changes.");
stop();
break;
}
} else if (requestCode == REQUEST_START_LOCATION_FIX) {
switch (resultCode) {
case Activity.RESULT_OK:
logger.i("User fixed the problem.");
startUpdating(locationRequest);
break;
case Activity.RESULT_CANCELED:
logger.i("User chose not to fix the problem.");
stop();
break;
}
}
}
private ResultCallback<LocationSettingsResult> settingsResultCallback = new ResultCallback<LocationSettingsResult>() {
@Override
public void onResult(LocationSettingsResult locationSettingsResult) {
final Status status = locationSettingsResult.getStatus();
switch (status.getStatusCode()) {
case LocationSettingsStatusCodes.SUCCESS:
logger.d("All location settings are satisfied.");
fulfilledCheckLocationSettings = true;
startUpdating(locationRequest);
break;
case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
logger.w("Location settings are not satisfied. Show the user a dialog to " +
"upgrade location settings. You should hook into the Activity onActivityResult and call this provider's onActivityResult method for continuing this call flow. ");
if (context instanceof Activity) {
try {
// Show the dialog by calling startResolutionForResult(), and check the result
// in onActivityResult().
status.startResolutionForResult((Activity) context, REQUEST_CHECK_SETTINGS);
} catch (IntentSender.SendIntentException e) {
logger.i("PendingIntent unable to execute request.");
}
} else {
logger.w("Provided context is not the context of an activity, therefore we can't launch the resolution activity.");
}
break;
case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
logger.i("Location settings are inadequate, and cannot be fixed here. Dialog " +
"not created.");
stop();
break;
}
}
};
}