+
+}
diff --git a/locgetter/src/main/java/com/titanium/locgetter/main/LocationGetterBuilder.java b/locgetter/src/main/java/com/titanium/locgetter/main/LocationGetterBuilder.java
deleted file mode 100644
index 9017dd5..0000000
--- a/locgetter/src/main/java/com/titanium/locgetter/main/LocationGetterBuilder.java
+++ /dev/null
@@ -1,105 +0,0 @@
-package com.titanium.locgetter.main;
-
-import android.content.Context;
-import android.support.annotation.NonNull;
-import android.util.Log;
-
-import com.google.android.gms.common.api.GoogleApiClient;
-import com.google.android.gms.location.LocationRequest;
-import com.google.android.gms.location.LocationServices;
-
-
-/**
- * Used to create new instances of {@link LocationGetter}
- */
-public class LocationGetterBuilder {
-
- /**
- * Defines interval,priority of location updates
- */
- private LocationRequest locationRequest;
- private GoogleApiClient googleApiClient;
- /**
- * Application context
- */
- private Context context;
- /**
- * Custom logger
- */
- private Logger logger;
-
- /**
- * Creates new instance of builder
- */
- public LocationGetterBuilder(@NonNull Context context) {
- this.context = context;
- }
-
- /**
- * Sets custom location request to this location getter
- *
- * @param locationRequest defines interval , priority of location updates
- */
- public LocationGetterBuilder setLocationRequest(LocationRequest locationRequest) {
- this.locationRequest = locationRequest;
- return this;
- }
-
- /**
- * Sets custom logger to {@link LocationGetter}
- * By default all logs goes to {@link Log#i}
- */
- public LocationGetterBuilder setLogger(Logger logger) {
- this.logger = logger;
- return this;
- }
-
- /**
- * Sets google api client to this location builder.
- * Important : {@link GoogleApiClient} should contain {@link LocationServices#API}
- * If it is not initialized, it will be initialized in lazy way
- *
- * @param googleApiClient with enabled {@link LocationServices#API}
- */
- public LocationGetterBuilder setGoogleApiClient(GoogleApiClient googleApiClient) {
- this.googleApiClient = googleApiClient;
- return this;
- }
-
- /**
- * Builds new instance of {@link LocationGetter}
- * If not defined {@link GoogleApiClient} and {@link LocationRequest} will use default
- */
- public LocationGetter build() {
- if (googleApiClient == null)
- googleApiClient = getMinimalGoogleApiClient(context);
- if (locationRequest == null)
- locationRequest = getDefaultLocationRequest();
- if (logger == null)
- logger = getDefaultLogger();
- return new LocationGetterImpl(logger, context, locationRequest, googleApiClient);
- }
-
- private GoogleApiClient getMinimalGoogleApiClient(Context ctx) {
- return new GoogleApiClient.Builder(ctx)
- .addApi(LocationServices.API)
- .build();
- }
-
- private LocationRequest getDefaultLocationRequest() {
- return getLocationRequestByParams(Constants.DEFAULT_FASTEST_INTERVAL, Constants.DEFAULT_INTERVAL, LocationRequest.PRIORITY_HIGH_ACCURACY);
- }
-
- private Logger getDefaultLogger() {
- return Log::i;
- }
-
- private LocationRequest getLocationRequestByParams(long fastestInterval, long interval, int priority) {
- LocationRequest locationrequest = new LocationRequest();
- locationrequest.setFastestInterval(fastestInterval);
- locationrequest.setInterval(interval);
- locationrequest.setPriority(priority);
- return locationrequest;
- }
-
-}
diff --git a/locgetter/src/main/java/com/titanium/locgetter/main/LocationGetterBuilder.kt b/locgetter/src/main/java/com/titanium/locgetter/main/LocationGetterBuilder.kt
new file mode 100644
index 0000000..bbe45fe
--- /dev/null
+++ b/locgetter/src/main/java/com/titanium/locgetter/main/LocationGetterBuilder.kt
@@ -0,0 +1,50 @@
+package com.titanium.locgetter.main
+
+import android.content.Context
+import android.util.Log
+import com.titanium.locgetter.exception.MockLocationException
+
+/**
+ * Used to create new instances of [LocationGetter]
+ */
+class LocationGetterBuilder(private val context: Context) {
+
+ /**
+ * Custom logger
+ * By default all logs goes to [Log.i]
+ */
+ private var logger: (String, String) -> Unit = { tag, message -> Log.d(tag, message) }
+
+ /**
+ * Defines if locations getter will accept mocked locations
+ * If set to false, mocked locations will cause a [MockLocationException] to be thrown
+ * By default true
+ */
+ private var acceptMockLocations = true
+
+ /**
+ * Custom logger
+ * By default all logs goes to [Log.i]
+ */
+ fun logger(logger: (String, String) -> Unit): LocationGetterBuilder {
+ this.logger = logger
+ return this
+ }
+
+ /**
+ * Defines if locations getter will accept mocked locations
+ * If set to false, mocked locations will cause a [MockLocationException] to be thrown
+ * By default true
+ */
+ fun acceptMockLocations(acceptMockLocations: Boolean): LocationGetterBuilder {
+ this.acceptMockLocations = acceptMockLocations
+ return this
+ }
+
+ /**
+ * Builds new instance of [LocationGetter]
+ */
+ fun build(): LocationGetter = MainLocationGetter(context.applicationContext, logger, acceptMockLocations)
+
+
+}
diff --git a/locgetter/src/main/java/com/titanium/locgetter/main/LocationGetterImpl.java b/locgetter/src/main/java/com/titanium/locgetter/main/LocationGetterImpl.java
deleted file mode 100644
index f21177f..0000000
--- a/locgetter/src/main/java/com/titanium/locgetter/main/LocationGetterImpl.java
+++ /dev/null
@@ -1,155 +0,0 @@
-package com.titanium.locgetter.main;
-
-
-import android.Manifest;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.location.Location;
-import android.support.annotation.Nullable;
-import android.support.v4.content.ContextCompat;
-
-import com.google.android.gms.common.ConnectionResult;
-import com.google.android.gms.common.GoogleApiAvailability;
-import com.google.android.gms.common.api.GoogleApiClient;
-import com.google.android.gms.common.api.PendingResult;
-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 com.titanium.locgetter.exception.LocationSettingsException;
-import com.titanium.locgetter.exception.NoGoogleApiException;
-import com.titanium.locgetter.exception.PermissionException;
-
-import io.reactivex.Observable;
-import io.reactivex.Single;
-import io.reactivex.android.schedulers.AndroidSchedulers;
-import io.reactivex.schedulers.Schedulers;
-
-import static com.titanium.locgetter.main.Constants.ACCESS_LOCATION_PERMISSION_RESULT;
-import static com.titanium.locgetter.main.Constants.THROWABLE_KEY_LOCATION;
-
-class LocationGetterImpl implements LocationGetter {
-
- private static final String TAG = "LocationGetter";
- private Logger logger;
- private GoogleApiClient googleApiClient;
- private LocationRequest locationRequest;
- private Context appContext;
- private Location latestLoc;
-
- /**
- * @param logger for custom logging
- * @param ctx application context to prevent leaks
- * @param request location request to define
- * @param googleApiClient needed for turning on locations api
- */
- LocationGetterImpl(Logger logger, Context ctx, LocationRequest request, GoogleApiClient googleApiClient) {
- this.logger = logger;
- this.googleApiClient = googleApiClient;
- appContext = ctx;
- locationRequest = request;
- }
-
- @Override
- public Observable getLatestLocations() {
- if (ContextCompat.checkSelfPermission(appContext, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
- && ContextCompat.checkSelfPermission(appContext, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
- return Observable.just(googleApiClient)
- .subscribeOn(Schedulers.newThread())
- .doOnNext(this::initGoogleApiClient)
- .doOnNext(client -> checkGoogleApiAvailability())
- .map(this::getLocationSettingsStatus)
- .doOnNext(this::checkSettingsStatus)
- .flatMap(integer -> new LocationSniffer().startEmittingLocations());
- }
- return Observable.error(new PermissionException(Manifest.permission.ACCESS_FINE_LOCATION, ACCESS_LOCATION_PERMISSION_RESULT));
- }
-
- private void checkGoogleApiAvailability() {
- GoogleApiAvailability googleApiAvailability = GoogleApiAvailability.getInstance();
- int errorCode = googleApiAvailability.isGooglePlayServicesAvailable(appContext);
- if (errorCode != ConnectionResult.SUCCESS) {
- throw new NoGoogleApiException(errorCode);
- }
- }
-
- private void initGoogleApiClient(GoogleApiClient googleApiClient) {
- if (!googleApiClient.isConnected()) {
- logger.log(TAG, "Google api client is not connected");
- logger.log(TAG, "Google api client connecting");
- ConnectionResult res = googleApiClient.blockingConnect();
- if (!res.isSuccess()) {
- throw new RuntimeException(THROWABLE_KEY_LOCATION);
- }
- logger.log(TAG, "Google api client connected");
- }
- }
-
- private Status getLocationSettingsStatus(GoogleApiClient googleApiClient) {
- LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder();
- builder.setAlwaysShow(true);
- builder.addLocationRequest(locationRequest);
- PendingResult result = LocationServices.SettingsApi.checkLocationSettings(googleApiClient, builder.build());
- return result.await().getStatus();
- }
-
- private void checkSettingsStatus(Status status) {
- logger.log(TAG, "checkSettingsStatus -> settings status = " + status);
- switch (status.getStatusCode()) {
- case LocationSettingsStatusCodes.SUCCESS:
- logger.log(TAG, "checkSettingsStatus:Location is enabled");
- break;
- case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
- logger.log(TAG, "checkSettingsStatus:Location settings are not satisfied. Show the user a dialog to upgrade location settings ");
- case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
- throw new LocationSettingsException(status);
- }
- }
-
- @Override
- public Single getLatestLocation() {
- return getLatestLocations()
- .firstOrError();
- }
-
- @Nullable
- @Override
- public Location getLatestSavedLocation() {
- return latestLoc;
- }
-
- private class LocationSniffer {
- private LocationListener listener;
-
- @SuppressWarnings({"MissingPermission"})
- private Observable startEmittingLocations() {
- //wrap callback with observable.create
- return Observable.create(e -> {
- listener = location -> {
- //as soon as it is disposed - unsubscribe from updates
- if (e.isDisposed())
- unSubscribeFromUpdates();
- else {
- logger.log(TAG, "Got new location :" + location);
- latestLoc = location;
- e.onNext(location);
- }
- };
- subscribeToUpdates();
- });
- }
-
- @SuppressWarnings({"MissingPermission"})
- private void subscribeToUpdates() {
- //subscribe should happen on main thread
- AndroidSchedulers.mainThread().scheduleDirect(() -> LocationServices.FusedLocationApi.requestLocationUpdates(googleApiClient, locationRequest, listener));
- }
-
- private void unSubscribeFromUpdates() {
- LocationServices.FusedLocationApi.removeLocationUpdates(googleApiClient, listener);
- }
- }
-}
diff --git a/locgetter/src/main/java/com/titanium/locgetter/main/Logger.java b/locgetter/src/main/java/com/titanium/locgetter/main/Logger.java
deleted file mode 100644
index 28de1b4..0000000
--- a/locgetter/src/main/java/com/titanium/locgetter/main/Logger.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.titanium.locgetter.main;
-
-public interface Logger {
- /**
- * Logs message with specific tag
- */
- void log(String tag, String message);
-}
diff --git a/locgetter/src/main/java/com/titanium/locgetter/main/MainLocationGetter.kt b/locgetter/src/main/java/com/titanium/locgetter/main/MainLocationGetter.kt
new file mode 100644
index 0000000..e217310
--- /dev/null
+++ b/locgetter/src/main/java/com/titanium/locgetter/main/MainLocationGetter.kt
@@ -0,0 +1,141 @@
+package com.titanium.locgetter.main
+
+import android.Manifest
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.location.Location
+import android.location.LocationManager
+import android.os.Build
+import android.provider.Settings
+import android.support.v4.content.ContextCompat
+import com.google.android.gms.location.LocationCallback
+import com.google.android.gms.location.LocationRequest
+import com.google.android.gms.location.LocationResult
+import com.google.android.gms.location.LocationServices
+import com.titanium.locgetter.exception.LocationSettingsException
+import com.titanium.locgetter.exception.MockLocationException
+import com.titanium.locgetter.exception.NoLocationPermission
+import io.reactivex.Observable
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.subjects.PublishSubject
+import io.reactivex.subjects.Subject
+
+
+class MainLocationGetter constructor(private val appContext: Context,
+ private val logger: ((String, String) -> Unit),
+ private val acceptMockLocations: Boolean) : LocationGetter {
+
+ override val hotLocations: Subject = PublishSubject.create()
+ private var latestLocation: Location? = null
+ private val locationRequest: LocationRequest
+ private var fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(appContext)
+ val locationShare: Observable = Observable.defer { LocationSniffer().startEmittingLocations() }
+ .share()
+
+ init {
+ locationRequest = LocationRequest.create()
+ locationRequest.fastestInterval = 5000
+ locationRequest.interval = 5000
+ locationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
+ }
+
+ override fun getLatestSavedLocation() = latestLocation
+
+ override fun getLatestLocations() = (if (isLocationPermitted()) Observable.just("") else askPermissions())
+ .flatMap { checkSettings() }
+ .flatMap { locationShare }
+
+ private fun checkSettings() = if (isLocationEnabled())
+ Observable.just(1)
+ else
+ askSettings()
+
+ private fun askSettings() = Observable.create { emitter ->
+ val onError = { if (!emitter.isDisposed) emitter.onError(LocationSettingsException()) }
+ val askRequest: (Activity) -> Unit = { f -> f.startActivityForResult(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS), 1) }
+ val askResult: (Int, Intent?) -> Unit = { _, _ ->
+ if (isLocationEnabled()) {
+ emitter.onNext("")
+ emitter.onComplete()
+ } else {
+ onError()
+ }
+ }
+ appContext.launchConnectableActivity(askRequest, onActivityResult = askResult, onDeAttach = onError)
+ }
+
+
+ @SuppressLint("NewApi")
+ private fun askPermissions(): Observable {
+ return Observable.create { emitter ->
+ val permRequest: (Activity) -> Unit = { f -> f.requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 1) }
+ val onDeAttach: () -> Unit = { if (!emitter.isDisposed) emitter.onError(RuntimeException("timeout")) }
+ val permResult: (Boolean) -> Unit = { isGranted ->
+ if (isGranted) {
+ emitter.onNext("")
+ emitter.onComplete()
+ } else {
+ emitter.onError(NoLocationPermission())
+ }
+ }
+ appContext.launchConnectableActivity(permRequest, onRequestPermissionsResult = permResult, onDeAttach = onDeAttach)
+ }
+ }
+
+ private fun isLocationPermitted() = ContextCompat.checkSelfPermission(appContext, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
+
+ private fun isLocationEnabled() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ val lm = appContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
+ lm.isLocationEnabled
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ val mode = Settings.Secure.getInt(appContext.contentResolver, Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF)
+ mode != Settings.Secure.LOCATION_MODE_OFF
+ } else {
+ val locationManager = appContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
+ locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) && locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
+ }
+
+ private inner class LocationSniffer {
+ private var onLocation: ((Location) -> Unit)? = null
+ private var listener: LocationCallback = object : LocationCallback() {
+ override fun onLocationResult(p0: LocationResult?) {
+ p0?.lastLocation?.let { onLocation?.invoke(it) }
+ }
+ }
+
+ @SuppressLint("NewApi")
+ fun startEmittingLocations(): Observable {
+ //wrap callback with observable.create
+ return Observable.create { e ->
+ onLocation = { location ->
+ //as soon as it is disposed - unsubscribe from updates
+ if (e.isDisposed)
+ unSubscribeFromUpdates()
+ else {
+ if (location.isFromMockProvider && !acceptMockLocations) {
+ e.onError(MockLocationException(location))
+ } else {
+ logger(TAG, "Got new location :$location ,speed :${location.speed}")
+ latestLocation = location
+ hotLocations.onNext(location)
+ e.onNext(location)
+ }
+ }
+ }
+ subscribeToUpdates()
+ }
+ }
+
+ @SuppressLint("MissingPermission")
+ private fun subscribeToUpdates() = AndroidSchedulers.mainThread().scheduleDirect { fusedLocationProviderClient.requestLocationUpdates(locationRequest, listener, null) }
+
+ private fun unSubscribeFromUpdates() = fusedLocationProviderClient.removeLocationUpdates(listener)
+ }
+
+ companion object {
+ val TAG = "MainLocationGetter"
+ }
+}
\ No newline at end of file