diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/GeneralMapLibreFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/GeneralMapLibreFragment.kt --- a/app/src/main/java/it/reyboz/bustorino/fragments/GeneralMapLibreFragment.kt +++ b/app/src/main/java/it/reyboz/bustorino/fragments/GeneralMapLibreFragment.kt @@ -120,8 +120,9 @@ protected lateinit var locationProvider: FusedNativeLocationProvider protected var shownToastNoPosition = false + private var locationEnabledOnDevice = true - + //TODO ACTIVATE THIS private val preferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener(){ pref, key -> /*when(key){ SettingsFragment.LIBREMAP_STYLE_PREF_KEY -> reloadMap() @@ -217,6 +218,7 @@ //remove this locationEngine?.removeLocationUpdates(this) } + } override fun onFailure(exception: Exception) { @@ -224,6 +226,15 @@ } } + protected val deviceLocationStatusListener = FusedNativeLocationProvider.LocationStatusListener { isEnabled -> + mapStateViewModel.locationDeviceEnabled.value = isEnabled + if(locationEnabledOnDevice && !isEnabled && locationInitialized) { + warnLocationNotEnabledOnDevice() + //setMapLocationEnabled(false) + } + locationEnabledOnDevice = isEnabled + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -288,11 +299,22 @@ super.onDestroy() } + override fun onPause() { + super.onPause() + } + override fun onDestroyView() { bottomLayout = null + locationProvider.removeListener(deviceLocationStatusListener) super.onDestroyView() } + protected fun warnLocationNotEnabledOnDevice(){ + context?.let{ + Toast.makeText(it,R.string.enable_location_message,Toast.LENGTH_SHORT).show() + } + } + protected fun reloadMap(){ /*map?.let { Log.d("GeneralMapFragment", "RELOADING MAP") @@ -320,63 +342,6 @@ } else throw RuntimeException("$context must implement CommonFragmentListener") } - /* - protected fun restoreMapStateFromBundle(bundle: Bundle): Boolean{ - val nullDouble = -10_000.0 - var boundsRestored =false - val latCenter = bundle.getDouble("center_map_lat", nullDouble) - val lonCenter = bundle.getDouble("center_map_lon",nullDouble) - val zoom = bundle.getDouble("map_zoom", nullDouble) - val bearing = bundle.getDouble("map_bearing", nullDouble) - val tilt = bundle.getDouble("map_tilt", nullDouble) - if(lonCenter!=nullDouble &&latCenter!=nullDouble) map?.let { - val center = LatLng(latCenter, lonCenter) - val newPos = CameraPosition.Builder().target(center) - if(zoom>0) newPos.zoom(zoom) - if(bearing!=nullDouble) newPos.bearing(bearing) - if(tilt != nullDouble) newPos.tilt(tilt) - it.cameraPosition=newPos.build() - - Log.d(DEBUG_TAG, "Restored map state from Bundle, center: $center, zoom: $zoom, bearing $bearing, tilt $tilt") - boundsRestored =true - } else{ - Log.d(DEBUG_TAG, "Not restoring map state, center: $latCenter,$lonCenter; zoom: $zoom, bearing: $bearing, tilt $tilt") - } - val mStop = bundle.getBundle("shown_stop")?.let { - Stop.fromBundle(it) - } - mStop?.let { openStopInBottomSheet(it) } - return boundsRestored - } - - protected fun saveMapStateBeforePause(bundle: Bundle){ - map?.let { - val newBbox = it.projection.visibleRegion.latLngBounds - - - val cp = it.cameraPosition - bundle.putDouble("center_map_lat", newBbox.center.latitude) - bundle.putDouble("center_map_lon", newBbox.center.longitude) - it.cameraPosition.zoom.let { z-> bundle.putDouble("map_zoom",z) } - bundle.putDouble("map_bearing",cp.bearing) - bundle.putDouble("map_tilt", cp.tilt) - - val locationComponent = it.locationComponent - bundle.putBoolean(KEY_LOCATION_ENABLED,locationComponent.isLocationComponentEnabled) - bundle.putParcelable("last_location", locationComponent.lastKnownLocation) - } - shownStopInBottomSheet?.let { - bundle.putBundle("shown_stop", it.toBundle()) - } - } - - protected fun saveMapStateInBundle(): Bundle { - val b = Bundle() - saveMapStateBeforePause(b) - return b - } - - */ protected fun stopToGeoJsonFeature(s: Stop): Feature{ return Feature.fromGeometry( @@ -477,6 +442,9 @@ if(locationComponent.isLocationComponentEnabled !=enabled) locationComponent.isLocationComponentEnabled= enabled changed = true} + Log.d(DEBUG_TAG, "Asked to set location component enabled: $enabled, changed: $changed") + mapStateViewModel.locationUserActive.value = enabled + return changed } @@ -492,21 +460,24 @@ locationComponent = map.locationComponent locationProvider = FusedNativeLocationProvider(context) + locationProvider.addListener(deviceLocationStatusListener) locationEngine = MapLibreLocationEngine(locationProvider) val options = LocationComponentActivationOptions.builder(context, style) .useDefaultLocationEngine(false) .locationEngine(locationEngine) .build() locationComponent.activateLocationComponent(options) - //locationComponent.cameraMode = CameraMode.TRACKING - //locationComponent.renderMode = RenderMode.COMPASS - locationInitialized = true + if(BuildConfig.DEBUG) Log.d(DEBUG_TAG, "Requesting location updates") locationEngine!!.requestLocationUpdates(LocationEngineRequest.Builder(500).setDisplacement(20.0f).build(), mapLibreLocationCallback, null) - // signal to show user location icon as active - mapStateViewModel.locationActive.value = true - setLocationComponentEnabled(true) + + if(!locationEnabledOnDevice){ + warnLocationNotEnabledOnDevice() + }else { + setLocationComponentEnabled(true) + } + locationInitialized = true onMapLocationComponentInitialized() } } @@ -994,12 +965,14 @@ val context = context ?: return if(enabled) { setMapLocationEnabled(false) - onMapLocationEnabled(false) } - else if(deviceHasGpsProvider()) { + else if(deviceHasLocationProvider()) { if(Permissions.bothLocationPermissionsGranted(context)){ - setMapLocationEnabled(true) - onMapLocationEnabled(true) + if(!locationEnabledOnDevice){ + warnLocationNotEnabledOnDevice() + } else{ + setMapLocationEnabled(true) + } } else{ Log.d(DEBUG_TAG, "Requesting permissions to show location") Permissions.getInstance(context).checkRequestLocationPermissions(requireActivity(), positionRequestResponder) @@ -1012,15 +985,20 @@ } + /** + * Set the map location component enabled + */ @SuppressLint("MissingPermission") protected fun setMapLocationEnabled(enabled: Boolean){ + Log.d(DEBUG_TAG, "Setting map location enabled: $enabled") map?.locationComponent?.isLocationComponentEnabled = enabled //map?.cameraPosition = - mapStateViewModel.locationActive.value = enabled + mapStateViewModel.locationUserActive.value = enabled + onMapLocationEnabled(enabled) } protected fun checkInitMapLocation(mapReady: MapLibreMap,style: Style, context: Context) { //enable location - val hasGps = deviceHasGpsProvider() + val hasGps = deviceHasLocationProvider() val permissions = Permissions.getInstance(context) if(hasGps) { if (Permissions.bothLocationPermissionsGranted(context)) { @@ -1031,11 +1009,9 @@ activity?.let{ req = permissions.checkRequestLocationPermissions(it, positionRequestResponder) } - //setLocationIconEnabled(false) - //setFollowingUser(false) + if(!req) { setMapLocationEnabled(false) - onMapLocationEnabled(false) } } @@ -1061,9 +1037,9 @@ return bottomSheetBehavior.state == BottomSheetBehavior.STATE_EXPANDED } - protected fun deviceHasGpsProvider(): Boolean{ + protected fun deviceHasLocationProvider(): Boolean{ val locManager = requireContext().getSystemService(LOCATION_SERVICE) as LocationManager - return locManager.allProviders.contains(LocationManager.GPS_PROVIDER) + return locManager.allProviders.size > 0 } /** diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt --- a/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt +++ b/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt @@ -351,7 +351,7 @@ descripTextView.text = route.longName descripTextView.visibility = View.VISIBLE } - mapStateViewModel.locationActive.observe(viewLifecycleOwner) { + mapStateViewModel.locationUserActive.observe(viewLifecycleOwner) { setLocationIconEnabled(it) } // enable info button if there are alerts on the line @@ -507,7 +507,7 @@ true } if(!newStatus) setLocationComponentEnabled(newStatus) - mapStateViewModel.locationActive.value = newStatus + mapStateViewModel.locationUserActive.value = newStatus } } diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java --- a/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java +++ b/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java @@ -173,34 +173,34 @@ boolean locationPermissionGranted, locationPermissionAsked = false; AppLocationManager locationManager; private final ActivityResultLauncher requestPermissionLauncher = - registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback>() { + registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback<>() { @Override public void onActivityResult(Map result) { - if(result==null) return; + if (result == null) return; - if(result.get(Manifest.permission.ACCESS_COARSE_LOCATION) == null || + if (result.get(Manifest.permission.ACCESS_COARSE_LOCATION) == null || result.get(Manifest.permission.ACCESS_FINE_LOCATION) == null) return; - Log.d(DEBUG_TAG, "Permissions for location are: "+result); - if(Boolean.TRUE.equals(result.get(Manifest.permission.ACCESS_COARSE_LOCATION)) - || Boolean.TRUE.equals(result.get(Manifest.permission.ACCESS_FINE_LOCATION))){ + Log.d(DEBUG_TAG, "Permissions for location are: " + result); + if (Boolean.TRUE.equals(result.get(Manifest.permission.ACCESS_COARSE_LOCATION)) + || Boolean.TRUE.equals(result.get(Manifest.permission.ACCESS_FINE_LOCATION))) { locationPermissionGranted = true; Log.w(DEBUG_TAG, "Starting position"); - if (mListener!= null && getContext()!=null){ - if (locationManager==null) + if (mListener != null && getContext() != null) { + if (locationManager == null) locationManager = AppLocationManager.getInstance(getContext()); locationManager.addLocationRequestFor(requester); } // show nearby fragment //showNearbyStopsFragment(); Log.d(DEBUG_TAG, "We have location permission"); - if(pendingNearbyStopsFragmentRequest){ + if (pendingNearbyStopsFragmentRequest) { showNearbyFragmentIfPossible(); pendingNearbyStopsFragmentRequest = false; } } - if(pendingNearbyStopsFragmentRequest) pendingNearbyStopsFragmentRequest =false; + if (pendingNearbyStopsFragmentRequest) pendingNearbyStopsFragmentRequest = false; } }); diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt --- a/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt +++ b/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt @@ -36,13 +36,13 @@ import androidx.preference.PreferenceManager import androidx.room.concurrent.AtomicBoolean import com.google.android.material.bottomsheet.BottomSheetBehavior -import it.reyboz.bustorino.BuildConfig import it.reyboz.bustorino.R import it.reyboz.bustorino.backend.Stop import it.reyboz.bustorino.backend.gtfs.LivePositionUpdate import it.reyboz.bustorino.backend.mato.MQTTMatoClient import it.reyboz.bustorino.data.PreferencesHolder import it.reyboz.bustorino.data.gtfs.TripAndPatternWithStops +import it.reyboz.bustorino.map.MapLibreLocationEngine import it.reyboz.bustorino.map.MapLibreStyles import it.reyboz.bustorino.viewmodels.StopsMapViewModel import org.maplibre.android.camera.CameraPosition @@ -50,7 +50,6 @@ import org.maplibre.android.geometry.LatLng import org.maplibre.android.geometry.LatLngBounds import org.maplibre.android.location.engine.LocationEngineCallback -import org.maplibre.android.location.engine.LocationEngineRequest import org.maplibre.android.location.engine.LocationEngineResult import org.maplibre.android.location.modes.CameraMode import org.maplibre.android.location.modes.RenderMode @@ -234,7 +233,8 @@ usingMQTTPositions = useMQTT } - mapStateViewModel.locationActive.observe(viewLifecycleOwner){ setLocationIconEnabled(it)} + mapStateViewModel.locationUserActive.observe(viewLifecycleOwner){ + setLocationIconEnabled(it)} mapStateViewModel.followingUserPosition.observe(viewLifecycleOwner){ updateFollowingIcon(it)} Log.d(DEBUG_TAG, "Fragment View Created!") @@ -622,7 +622,10 @@ } override fun onFailure(p0: java.lang.Exception) { - Log.e(DEBUG_TAG, "Failed to get the last location", p0) + if( p0 is MapLibreLocationEngine.NoLocationException) + Log.d(DEBUG_TAG, "Cannot find location: ${p0.message}") + else + Log.w(DEBUG_TAG, "Failed to get the last location, error: ${p0.message}",) } }) @@ -653,7 +656,7 @@ } setLocationComponentEnabled(false) //Update UI Status - mapStateViewModel.locationActive.value = false + mapStateViewModel.locationUserActive.value = false mapStateViewModel.followingUserPosition.value = false } else { map?.apply { @@ -665,7 +668,7 @@ ) setLocationComponentEnabled(true) locationComponent.cameraMode = CameraMode.TRACKING - mapStateViewModel.locationActive.value = true + mapStateViewModel.locationUserActive.value = true } setFollowUserLocation(true) } diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.java --- a/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.java +++ b/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.java @@ -22,13 +22,10 @@ import android.content.SharedPreferences; import android.location.Location; -import android.location.LocationManager; import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.core.location.LocationListenerCompat; -import androidx.fragment.app.Fragment; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; import androidx.core.util.Pair; @@ -51,6 +48,7 @@ import it.reyboz.bustorino.data.DatabaseUpdate; import it.reyboz.bustorino.adapters.SquareStopAdapter; import it.reyboz.bustorino.middleware.AutoFitGridLayoutManager; +import it.reyboz.bustorino.middleware.FusedNativeLocationProvider; import it.reyboz.bustorino.util.Permissions; import it.reyboz.bustorino.util.StopSorterByDistance; import it.reyboz.bustorino.viewmodels.NearbyStopsViewModel; @@ -58,7 +56,13 @@ import java.util.*; -public class NearbyStopsFragment extends Fragment { +public class NearbyStopsFragment extends ScreenBaseFragment { + + @Nullable + @Override + public View getBaseViewForSnackBar() { + return null; + } public enum FragType{ STOPS(1), ARRIVALS(2); @@ -78,7 +82,6 @@ private enum LocationShowingStatus {SEARCHING, FIRST_FIX, DISABLED, NO_PERMISSION} private FragmentListenerMain mListener; - private FragmentLocationListener fragmentLocationListener; private final static String DEBUG_TAG = "NearbyStopsFragment"; private final static String FRAGMENT_TYPE_KEY = "FragmentType"; @@ -104,18 +107,47 @@ private Integer MAX_DISTANCE = -3; private int MIN_NUM_STOPS = -1; - private int TIME_INTERVAL_REQUESTS = -1; - private LocationManager locManager; //These are useful for the case of nearby arrivals private NearbyArrivalsDownloader arrivalsManager = null; private ArrivalsStopAdapter arrivalsStopAdapter = null; private ArrayList currentNearbyStops = new ArrayList<>(); - private NearbyArrivalsDownloader nearbyArrivalsDownloader; private LocationShowingStatus showingStatus = LocationShowingStatus.NO_PERMISSION; + private final FusedNativeLocationProvider.LocationUpdateListener locationUpdateListener = new FusedNativeLocationProvider.LocationUpdateListener() { + @Override + public void onLocationUpdate(@NotNull Location location) { + updateLocationViewModel(location); + } + + @Override + public void onFusedStatusChanged(boolean isEnabled) { + Log.d(DEBUG_TAG, "Location provider is enabled: " + isEnabled); + if(isEnabled){ + setShowingStatus(LocationShowingStatus.SEARCHING); + } else{ + setShowingStatus(LocationShowingStatus.DISABLED); + } + } + }; + private final FusedNativeLocationProvider.Options locationOptionsArrivals = new FusedNativeLocationProvider.Options(5*1000L, 50f), + locationOptionsStops = new FusedNativeLocationProvider.Options(1000L, 5f);; + + + + /* + TODO: we do not request the permission in this fragment, only showing it when we have the location. Request position if this changes. + private final ActivityResultLauncher permissionsResultLauncher = getPositionRequestLauncher( + granted ->{ + + } + ); + */ + private FusedNativeLocationProvider locationProvider = null; + + private final NearbyArrivalsDownloader.ArrivalsListener arrivalsListener = new NearbyArrivalsDownloader.ArrivalsListener() { @Override public void setProgress(int completedRequests, int pendingRequests) { @@ -171,16 +203,15 @@ if (getArguments() != null) { setFragmentType(FragType.fromNum(getArguments().getInt(FRAGMENT_TYPE_KEY))); } - locManager = (LocationManager) requireContext().getSystemService(Context.LOCATION_SERVICE); - fragmentLocationListener = new FragmentLocationListener(); + //locManager = (LocationManager) requireContext().getSystemService(Context.LOCATION_SERVICE); + //fragmentLocationListener = new FragmentLocationListener(); if (getContext()!=null) { //globalSharedPref = getContext().getSharedPreferences(getString(R.string.mainSharedPreferences), Context.MODE_PRIVATE); //globalSharedPref.registerOnSharedPreferenceChangeListener(preferenceChangeListener); } - nearbyArrivalsDownloader = new NearbyArrivalsDownloader(getContext().getApplicationContext(), arrivalsListener); - - + //NearbyArrivalsDownloader nearbyArrivalsDownloader = new NearbyArrivalsDownloader(getContext().getApplicationContext(), arrivalsListener); + locationProvider = new FusedNativeLocationProvider(requireContext()); } @Override @@ -216,15 +247,19 @@ } WorkInfo wi = workInfos.get(0); - if (wi.getState() == WorkInfo.State.RUNNING && fragmentLocationListener.isRegistered) { - locManager.removeUpdates(fragmentLocationListener); - fragmentLocationListener.isRegistered = true; + if (wi.getState() == WorkInfo.State.RUNNING && locationProvider.isRunning()) { + locationProvider.stopUpdates(); viewModel.setDBUpdateRunning(true); } else{ //start the request - if(!fragmentLocationListener.isRegistered){ - requestLocationUpdates(); + if(Permissions.bothLocationPermissionsGranted(requireContext())) { + if(!locationProvider.isRunning()){ + startLocationUpdatesByType(); + } + } else{ + setShowingStatus(LocationShowingStatus.NO_PERMISSION); } + viewModel.setDBUpdateRunning(false); //actually restart request } @@ -255,6 +290,9 @@ setShowingStatus(LocationShowingStatus.NO_PERMISSION); } + //add location listener + locationProvider.addListener(locationUpdateListener); + return root; } @@ -262,17 +300,21 @@ @SuppressLint("MissingPermission") private boolean requestLocationUpdates(){ if(Permissions.anyLocationPermissionsGranted(requireContext())) { - if (locManager.getAllProviders().contains(LocationManager.GPS_PROVIDER)) { - locManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, - 3000, 10.0f, fragmentLocationListener - ); - fragmentLocationListener.isRegistered = true; - } - fragmentLocationListener.isRegistered = false; + startLocationUpdatesByType(); return true; } else return false; } + /** + * Internal bit used to start location updates + */ + private void startLocationUpdatesByType(){ + switch (fragment_type) { + case STOPS: locationProvider.startUpdates(locationOptionsStops); break; + case ARRIVALS: locationProvider.startUpdates(locationOptionsArrivals); break; + } + } + /** @@ -280,8 +322,9 @@ * @param type the type, TYPE_ARRIVALS or TYPE_STOPS */ private void setFragmentType(FragType type){ + boolean isChanged = fragment_type != type; this.fragment_type = type; - switch(type){ + /*switch(type){ case ARRIVALS: TIME_INTERVAL_REQUESTS = 5*1000; break; @@ -289,11 +332,35 @@ TIME_INTERVAL_REQUESTS = 1000; } + + */ + if(isChanged){ + startLocationUpdatesByType(); + setShowingStatus(LocationShowingStatus.SEARCHING); + } + } + /** + * Set the location in the view model if it is good + * @param location new location + */ + private void updateLocationViewModel(@NonNull Location location, float accuracy){ + if(viewModel==null) { + return; + } + if(location.getAccuracy()() + private val statusListeners = CopyOnWriteArraySet() // Active Android listeners, one per provider private val activeAndroidListeners = mutableListOf() @@ -61,14 +75,15 @@ @Volatile private var bestLocation: Location? = null - @Volatile - private var running = false + private var running = AtomicBoolean(false) private var runningOptions = Options(500L, 5f, null, true, true, true) - private val activeProviders = ArrayList() + private val availableProviders = ArrayList() + + private var lastStatusUpdateEnabled = false - private var havePermissions = false + private val providersAreEnabled = ConcurrentHashMap() //private val removedListener = mutableSetOf() @@ -91,6 +106,12 @@ } } + fun addListener(listener: LocationStatusListener) { + synchronized(listeners) { + statusListeners.add(listener) + } + } + /** * Removes a previously registered listener. */ @@ -106,6 +127,12 @@ } } + fun removeListener(listener: LocationStatusListener) { + synchronized(listeners) { + statusListeners.remove(listener) + } + } + /** * Starts receiving location updates from the enabled providers. * If already running, stops the existing providers first and restarts @@ -114,11 +141,26 @@ * Requires ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION. */ @SuppressLint("MissingPermission") - fun startUpdates(options: Options?): Boolean { - if (running) stopUpdates() + fun startUpdates(options: Options?) { + val wasNotRunning = running.compareAndSet(false, true) + + if (!wasNotRunning) { + //it's already running, no need to stop + Log.d(DEBUG_TAG, "Requested to start updates, but provider is running") + if(options!=null){ + if(runningOptions !== options){ + Log.d(DEBUG_TAG, "Stopping and restarting") + //need to restart + stopUpdatesInternal() + startUpdates(options) + } + } + return + } if (options!=null){ runningOptions = options } + lastStatusUpdateEnabled = false val selectedProviders = buildList { if (runningOptions.useGps) add(LocationManager.GPS_PROVIDER) if (runningOptions.useNetwork) add(LocationManager.NETWORK_PROVIDER) @@ -128,32 +170,42 @@ val effectiveLooper = runningOptions.looper ?: Looper.getMainLooper() selectedProviders.forEach { provider -> - if (!locationManager.isProviderEnabled(provider)) return@forEach + val isEnabled = locationManager.isProviderEnabled(provider) + if(isEnabled) { + lastStatusUpdateEnabled = true + } + providersAreEnabled[provider] = isEnabled + //listen for location, even if the provider is not started yet + val locListener = object : LocationListener { + override fun onLocationChanged(location: Location) { + onReceiveLocation(location) + } + + override fun onProviderDisabled(provider: String) { + super.onProviderDisabled(provider) + onProviderStatusChanged(provider, false) + } - val locListener = LocationListener { location -> - if (isBetterLocation(location, bestLocation)) { - bestLocation = location - //Log.d(DEBUG_TAG, "New best location: $bestLocation") - notifyListeners(location) + override fun onProviderEnabled(provider: String) { + super.onProviderEnabled(provider) + onProviderStatusChanged(provider, true) } } - //runCatching { - locationManager.requestLocationUpdates( - provider, - runningOptions.minIntervalMs, - runningOptions.minDisplacementM, - locListener, - effectiveLooper, - ) - activeAndroidListeners.add(locListener) - activeProviders.add(provider) - //} + runCatching { + locationManager.requestLocationUpdates( + provider, + runningOptions.minIntervalMs, + runningOptions.minDisplacementM, + locListener, + effectiveLooper, + ) + activeAndroidListeners.add(locListener) + availableProviders.add(provider) + } } - - running = activeAndroidListeners.isNotEmpty() - Log.d(DEBUG_TAG, "Started location updates, running: $running, with providers: $activeProviders") - return running + notifyListenerStatus(lastStatusUpdateEnabled) + Log.d(DEBUG_TAG, "Started location updates, running: ${running.get()}, with providers: $availableProviders") } /** @@ -162,17 +214,20 @@ * calling [startUpdates] again will resume delivering updates to them. */ private fun stopUpdatesInternal() { - if(!running) //we have already done this - return - Log.d(DEBUG_TAG, "Actually stopping location updates, active providers: $activeProviders") - activeAndroidListeners.forEach { listener -> - runCatching { locationManager.removeUpdates(listener) } + if(running.compareAndSet(true, false)) { + //we have to stop updates + Log.d(DEBUG_TAG, "Actually stopping location updates, active providers: $availableProviders") + activeAndroidListeners.forEach { listener -> + runCatching { locationManager.removeUpdates(listener) } + } + activeAndroidListeners.clear() + //running = false is set by compareAndSet + availableProviders.clear() } - activeAndroidListeners.clear() - running = false - activeProviders.clear() } + fun isRunning(): Boolean = running.get() + /** * Returns the best known location cached by the enabled providers, * without starting continuous updates. @@ -208,6 +263,36 @@ //synchronized(listeners) { listeners.forEach { it.onLocationUpdate(location) } } + private fun notifyListenerStatus(enabled: Boolean){ + Log.d(DEBUG_TAG, "Notifying listeners, the position is enabled: $enabled") + listeners.forEach { it.onFusedStatusChanged(enabled) } + statusListeners.forEach { it.onLocationStatusChanged(enabled) } + } + + private fun onReceiveLocation(location: Location) { + if (isBetterLocation(location, bestLocation)) { + bestLocation = location + //Log.d(DEBUG_TAG, "New best location: $bestLocation") + notifyListeners(location) + } + } + + private fun onProviderStatusChanged(provider: String,enabled: Boolean) { + providersAreEnabled.put(provider, enabled) + val actu = providersAreEnabled.reduceValues(1, Boolean::or) + if (actu!=null && actu!=lastStatusUpdateEnabled){ + lastStatusUpdateEnabled = actu + notifyListenerStatus(actu) + } + + } + + fun isLocationEnabled(): Boolean { + val probValue = providersAreEnabled.reduceValues(1, Boolean::or) + return probValue ?: true + } + + /** * Public call for stopping the updates diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/MapStateViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/MapStateViewModel.kt --- a/app/src/main/java/it/reyboz/bustorino/viewmodels/MapStateViewModel.kt +++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/MapStateViewModel.kt @@ -6,7 +6,6 @@ import it.reyboz.bustorino.map.MapCameraState import org.maplibre.android.camera.CameraPosition import org.maplibre.android.geometry.LatLng -import org.maplibre.android.geometry.LatLngBounds import org.maplibre.android.maps.MapLibreMap class MapStateViewModel : ViewModel() { @@ -36,8 +35,9 @@ var locationToShow: Location? = null - val locationActive = MutableLiveData(false) + val locationUserActive = MutableLiveData(false) val followingUserPosition = MutableLiveData(false) + val locationDeviceEnabled = MutableLiveData(false) companion object{ fun restoreMapState(map: MapLibreMap, savedCameraState: MapCameraState?): Boolean { diff --git a/app/src/main/res/layout/fragment_nearby_stops.xml b/app/src/main/res/layout/fragment_nearby_stops.xml --- a/app/src/main/res/layout/fragment_nearby_stops.xml +++ b/app/src/main/res/layout/fragment_nearby_stops.xml @@ -73,7 +73,7 @@ android:layout_centerHorizontal="true" /> Autorisez l\'accès à la localisation pour l\'afficher sur la carte Mise à jour de la base de données en cours… Lancer la mise à jour manuelle de la base de données - Veuillez activer la localisation sur l\'appareil + Veuillez activer la localisation sur l\'appareil Mise à jour de la base de données Forcer la mise à jour de la base de données à l\'arrêt diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -138,7 +138,7 @@ Comincia aggiornamento manuale del database Consenti l\'accesso alla posizione per mostrarla sulla mappa Consenti l\'accesso alla posizione per mostrare le fermate vicine - Abilitare il GPS + Abilitare la posizione sul dispositivo arriva alle alla fermata Mostra arrivi diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -83,7 +83,7 @@ Algemene instellingen Toegang tot locatie toestaan om te laten zien op de kaart Toegang tot locatie toestaan om haltes in de buurt te weergeven - Schakel aub de locatie in op het apparaat + Schakel aub de locatie in op het apparaat Database update gaande… Database aan het updaten Forceer database update diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -174,7 +174,7 @@ Allow access to location to show it on the map Allow access to location to show stops nearby - Please enable location on the device + Please enable location on the device No GPS receiver found on the device! Database update in progress… Updating the database