diff --git a/app/build.gradle b/app/build.gradle index 1076f3e46..afc711052 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,4 +1,4 @@ -apply from: '../gitutils.gradle' +//apply from: '../gitutils.gradle' apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' @@ -78,10 +78,9 @@ android { defaultConfig { applicationId 'fr.free.nrw.commons' - versionCode 76 versionName '2.6.1' - setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName()) + //setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName()) minSdkVersion project.minSdkVersion targetSdkVersion project.targetSdkVersion @@ -100,7 +99,7 @@ android { } debug { testCoverageEnabled true - versionNameSuffix "-debug-" + getBranchName() + "~" + getBuildVersion() + //versionNameSuffix "-debug-" + getBranchName() + "~" + getBuildVersion() } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b0cbdd34f..872d02b9e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -16,6 +16,9 @@ + + + locationListeners = new CopyOnWriteArrayList<>(); + private boolean isLocationManagerRegistered = false; @Inject public LocationServiceManager(Context context) { + this.context = context; this.locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); - provider = locationManager.getBestProvider(new Criteria(), true); } public boolean isProviderEnabled() { return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); } - public LatLng getLastLocation() { - return lastLocation; + public boolean isLocationPermissionGranted() { + return ContextCompat.checkSelfPermission(context, + Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED; } - /** - * Returns the accuracy of the location. The measurement is - * given as a radius in meter of 68 % confidence. - * - * @return Float - */ - public Float getLatestLocationAccuracy() { - return latestLocationAccuracy; + public void requestPermissions(Activity activity) { + if (activity.isFinishing()) { + return; + } + ActivityCompat.requestPermissions(activity, + new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, + LOCATION_REQUEST); + } + + public boolean isPermissionExplanationRequired(Activity activity) { + if (activity.isFinishing()) { + return false; + } + return ActivityCompat.shouldShowRequestPermissionRationale(activity, + Manifest.permission.ACCESS_FINE_LOCATION); + } + + public LatLng getLastLocation() { + if (lastLocation == null) { + return null; + } + return LatLng.from(lastLocation); } /** Registers a LocationManager to listen for current location. */ public void registerLocationManager() { + if (!isLocationManagerRegistered) + isLocationManagerRegistered = requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER) + && requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER); + } + + private boolean requestLocationUpdatesFromProvider(String locationProvider) { try { - locationManager.requestLocationUpdates(provider, 400, 1, this); - Location location = locationManager.getLastKnownLocation(provider); - //Location works, just need to 'send' GPS coords - // via emulator extended controls if testing on emulator - Timber.d("Checking for location..."); - if (location != null) { - this.onLocationChanged(location); - } + locationManager.requestLocationUpdates(locationProvider, + MIN_LOCATION_UPDATE_REQUEST_TIME_IN_MILLIS, + MIN_LOCATION_UPDATE_REQUEST_DISTANCE_IN_METERS, + this); + return true; } catch (IllegalArgumentException e) { Timber.e(e, "Illegal argument exception"); + return false; } catch (SecurityException e) { Timber.e(e, "Security exception"); + return false; } } + protected boolean isBetterLocation(Location location, Location currentBestLocation) { + if (currentBestLocation == null) { + // A new location is always better than no location + return true; + } + + // Check whether the new location fix is newer or older + long timeDelta = location.getTime() - currentBestLocation.getTime(); + boolean isSignificantlyNewer = timeDelta > MIN_LOCATION_UPDATE_REQUEST_TIME_IN_MILLIS; + boolean isSignificantlyOlder = timeDelta < -MIN_LOCATION_UPDATE_REQUEST_TIME_IN_MILLIS; + boolean isNewer = timeDelta > 0; + + // If it's been more than two minutes since the current location, use the new location + // because the user has likely moved + if (isSignificantlyNewer) { + return true; + // If the new location is more than two minutes older, it must be worse + } else if (isSignificantlyOlder) { + return false; + } + + // Check whether the new location fix is more or less accurate + int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy()); + boolean isLessAccurate = accuracyDelta > 0; + boolean isMoreAccurate = accuracyDelta < 0; + boolean isSignificantlyLessAccurate = accuracyDelta > 200; + + // Check if the old and new location are from the same provider + boolean isFromSameProvider = isSameProvider(location.getProvider(), + currentBestLocation.getProvider()); + + // Determine location quality using a combination of timeliness and accuracy + if (isMoreAccurate) { + return true; + } else if (isNewer && !isLessAccurate) { + return true; + } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) { + return true; + } + return false; + } + + /** + * Checks whether two providers are the same + */ + private boolean isSameProvider(String provider1, String provider2) { + if (provider1 == null) { + return provider2 == null; + } + return provider1.equals(provider2); + } + /** Unregisters location manager. */ public void unregisterLocationManager() { + isLocationManagerRegistered = false; try { locationManager.removeUpdates(this); } catch (SecurityException e) { @@ -89,15 +170,11 @@ public class LocationServiceManager implements LocationListener { @Override public void onLocationChanged(Location location) { - double currentLatitude = location.getLatitude(); - double currentLongitude = location.getLongitude(); - latestLocationAccuracy = location.getAccuracy(); - Timber.d("Latitude: %f Longitude: %f Accuracy %f", - currentLatitude, currentLongitude, latestLocationAccuracy); - lastLocation = new LatLng(currentLatitude, currentLongitude, latestLocationAccuracy); - - for (LocationUpdateListener listener : locationListeners) { - listener.onLocationChanged(lastLocation); + if (isBetterLocation(location, lastLocation)) { + lastLocation = location; + for (LocationUpdateListener listener : locationListeners) { + listener.onLocationChanged(LatLng.from(lastLocation)); + } } } diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java index 578b5e91e..186746196 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java @@ -1,6 +1,5 @@ package fr.free.nrw.commons.nearby; -import android.Manifest; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -10,10 +9,8 @@ import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.annotation.NonNull; -import android.support.v4.app.ActivityCompat; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; -import android.support.v4.content.ContextCompat; import android.support.v7.app.AlertDialog; import android.view.Menu; import android.view.MenuInflater; @@ -46,12 +43,13 @@ import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; import timber.log.Timber; +import static fr.free.nrw.commons.location.LocationServiceManager.LOCATION_REQUEST; + public class NearbyActivity extends NavigationBaseActivity implements LocationUpdateListener { @BindView(R.id.progressBar) ProgressBar progressBar; - private static final int LOCATION_REQUEST = 1; private static final String MAP_LAST_USED_PREFERENCE = "mapLastUsed"; @Inject @@ -70,7 +68,6 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); setContentView(R.layout.activity_nearby); ButterKnife.bind(this); - checkLocationPermission(); bundle = new Bundle(); initDrawer(); initViewState(); @@ -102,7 +99,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp // Handle item selection switch (item.getItemId()) { case R.id.action_refresh: - lockNearbyView = false; + lockNearbyView(false); refreshView(true); return true; case R.id.action_toggle_view: @@ -115,52 +112,9 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp } } - private void checkLocationPermission() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (ContextCompat.checkSelfPermission(this, - Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { - refreshView(false); - } else { - if (ContextCompat.checkSelfPermission(this, - Manifest.permission.ACCESS_FINE_LOCATION) - != PackageManager.PERMISSION_GRANTED) { - - // Should we show an explanation? - if (ActivityCompat.shouldShowRequestPermissionRationale(this, - Manifest.permission.ACCESS_FINE_LOCATION)) { - - // Show an explanation to the user *asynchronously* -- don't block - // this thread waiting for the user's response! After the user - // sees the explanation, try again to request the permission. - - new AlertDialog.Builder(this) - .setMessage(getString(R.string.location_permission_rationale)) - .setPositiveButton("OK", (dialog, which) -> { - ActivityCompat.requestPermissions(NearbyActivity.this, - new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, - LOCATION_REQUEST); - dialog.dismiss(); - }) - .setNegativeButton("Cancel", null) - .create() - .show(); - - } else { - - // No explanation needed, we can request the permission. - - ActivityCompat.requestPermissions(this, - new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, - LOCATION_REQUEST); - - // MY_PERMISSIONS_REQUEST_READ_CONTACTS is an - // app-defined int constant. The callback method gets the - // result of the request. - } - } - } - } else { - refreshView(false); + private void requestLocationPermissions() { + if (!isFinishing()) { + locationManager.requestPermissions(this); } } @@ -185,7 +139,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp .setCancelable(false) .setPositiveButton(R.string.give_permission, (dialog, which) -> { //will ask for the location permission again - checkLocationPermission(); + checkGps(); }) .setNegativeButton(R.string.cancel, (dialog, which) -> { //dismiss dialog and finish activity @@ -214,6 +168,37 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp .show(); } else { Timber.d("GPS is enabled"); + checkLocationPermission(); + } + } + + private void checkLocationPermission() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (locationManager.isLocationPermissionGranted()) { + refreshView(false); + } else { + // Should we show an explanation? + if (locationManager.isPermissionExplanationRequired(this)) { + // Show an explanation to the user *asynchronously* -- don't block + // this thread waiting for the user's response! After the user + // sees the explanation, try again to request the permission. + new AlertDialog.Builder(this) + .setMessage(getString(R.string.location_permission_rationale_nearby)) + .setPositiveButton("OK", (dialog, which) -> { + requestLocationPermissions(); + dialog.dismiss(); + }) + .setNegativeButton("Cancel", null) + .create() + .show(); + + } else { + // No explanation needed, we can request the permission. + requestLocationPermissions(); + } + } + } else { + refreshView(false); } } @@ -238,7 +223,6 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp @Override protected void onStart() { super.onStart(); - locationManager.registerLocationManager(); locationManager.addLocationListener(this); } @@ -262,13 +246,18 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp super.onResume(); lockNearbyView = false; checkGps(); - refreshView(false); } + /** + * This method should be the single point to load/refresh nearby places + * + * @param isHardRefresh + */ private void refreshView(boolean isHardRefresh) { if (lockNearbyView) { return; } + locationManager.registerLocationManager(); LatLng lastLocation = locationManager.getLastLocation(); if (curLatLang != null && curLatLang.equals(lastLocation)) { //refresh view only if location has changed if (isHardRefresh) { @@ -314,7 +303,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp bundle.putString("PlaceList", gsonPlaceList); bundle.putString("CurLatLng", gsonCurLatLng); - lockNearbyView = true; + lockNearbyView(true); // Begin the transaction if (viewMode.isMap()) { setMapFragment(); @@ -325,6 +314,18 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp hideProgressBar(); } + private void lockNearbyView(boolean lock) { + if (lock) { + lockNearbyView = true; + locationManager.unregisterLocationManager(); + locationManager.removeLocationListener(this); + } else { + lockNearbyView = false; + locationManager.registerLocationManager(); + locationManager.addLocationListener(this); + } + } + private void hideProgressBar() { if (progressBar != null) { progressBar.setVisibility(View.GONE); @@ -353,11 +354,6 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp fragmentTransaction.commitAllowingStateLoss(); } - public static void startYourself(Context context) { - Intent settingsIntent = new Intent(context, NearbyActivity.class); - context.startActivity(settingsIntent); - } - @Override public void onLocationChanged(LatLng latLng) { refreshView(false); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 81da9000f..04d49faee 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -213,4 +213,5 @@ Tap this message (or hit back) to skip this step. Location has not changed. Location not available. + Permission required to display a list of nearby places