diff --git a/app/build.gradle b/app/build.gradle index 7cc5fc676..2bb4341f5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -83,7 +83,6 @@ android { defaultConfig { applicationId 'fr.free.nrw.commons' - versionCode 76 versionName '2.6.1' setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName()) @@ -104,6 +103,7 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' } debug { + applicationIdSuffix ".debug" testCoverageEnabled true versionNameSuffix "-debug-" + getBranchName() + "~" + getBuildVersion() } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9e502689f..9a232d1b9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -16,6 +16,9 @@ + + + locationListeners = new CopyOnWriteArrayList<>(); + private boolean isLocationManagerRegistered = false; 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) { @@ -87,15 +168,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 3997cc7b2..57d21dd95 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; @@ -44,6 +41,8 @@ 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 { @@ -71,7 +70,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(); @@ -103,7 +101,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: @@ -116,52 +114,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); } } @@ -186,7 +141,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 @@ -210,11 +165,48 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp Timber.d("Loaded settings page"); startActivityForResult(callGPSSettingIntent, 1); }) - .setNegativeButton(R.string.menu_cancel_upload, (dialog, id) -> dialog.cancel()) + .setNegativeButton(R.string.menu_cancel_upload, (dialog, id) -> { + showLocationPermissionDeniedErrorDialog(); + dialog.cancel(); + }) .create() .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", (dialog, id) -> { + showLocationPermissionDeniedErrorDialog(); + dialog.cancel(); + }) + .create() + .show(); + + } else { + // No explanation needed, we can request the permission. + requestLocationPermissions(); + } + } + } else { + refreshView(false); } } @@ -239,7 +231,6 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp @Override protected void onStart() { super.onStart(); - locationManager.registerLocationManager(); locationManager.addLocationListener(this); } @@ -263,13 +254,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) { @@ -309,7 +305,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(); @@ -320,6 +316,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); 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 diff --git a/app/src/test/java/fr/free/nrw/commons/nearby/NearbyActivityTest.java b/app/src/test/java/fr/free/nrw/commons/nearby/NearbyActivityTest.java index 1255df1c3..eb1cff52b 100644 --- a/app/src/test/java/fr/free/nrw/commons/nearby/NearbyActivityTest.java +++ b/app/src/test/java/fr/free/nrw/commons/nearby/NearbyActivityTest.java @@ -1,5 +1,6 @@ package fr.free.nrw.commons.nearby; +import android.app.Activity; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/design/screenshots/Chinese (Simplified) zh-CN/car-categories.png b/design/screenshots/Chinese (Simplified) zh-CN/car-categories.png new file mode 100644 index 000000000..7119b2ce9 Binary files /dev/null and b/design/screenshots/Chinese (Simplified) zh-CN/car-categories.png differ diff --git a/design/screenshots/Chinese (Simplified) zh-CN/car-description.png b/design/screenshots/Chinese (Simplified) zh-CN/car-description.png new file mode 100644 index 000000000..bd9bc20bb Binary files /dev/null and b/design/screenshots/Chinese (Simplified) zh-CN/car-description.png differ diff --git a/design/screenshots/Chinese (Simplified) zh-CN/car-details.png b/design/screenshots/Chinese (Simplified) zh-CN/car-details.png new file mode 100644 index 000000000..0f83717f3 Binary files /dev/null and b/design/screenshots/Chinese (Simplified) zh-CN/car-details.png differ diff --git a/design/screenshots/Chinese (Simplified) zh-CN/drawer.png b/design/screenshots/Chinese (Simplified) zh-CN/drawer.png new file mode 100644 index 000000000..a9de28ab0 Binary files /dev/null and b/design/screenshots/Chinese (Simplified) zh-CN/drawer.png differ diff --git a/design/screenshots/Chinese (Simplified) zh-CN/gallery.png b/design/screenshots/Chinese (Simplified) zh-CN/gallery.png new file mode 100644 index 000000000..76ab0f6d6 Binary files /dev/null and b/design/screenshots/Chinese (Simplified) zh-CN/gallery.png differ diff --git a/design/screenshots/Chinese (Simplified) zh-CN/nearby-list.png b/design/screenshots/Chinese (Simplified) zh-CN/nearby-list.png new file mode 100644 index 000000000..352a45239 Binary files /dev/null and b/design/screenshots/Chinese (Simplified) zh-CN/nearby-list.png differ diff --git a/design/screenshots/Chinese (Simplified) zh-CN/nearby-map.png b/design/screenshots/Chinese (Simplified) zh-CN/nearby-map.png new file mode 100644 index 000000000..77c5c562b Binary files /dev/null and b/design/screenshots/Chinese (Simplified) zh-CN/nearby-map.png differ diff --git a/design/screenshots/Chinese (Simplified) zh-CN/restaurant-categories.png b/design/screenshots/Chinese (Simplified) zh-CN/restaurant-categories.png new file mode 100644 index 000000000..18e4f5891 Binary files /dev/null and b/design/screenshots/Chinese (Simplified) zh-CN/restaurant-categories.png differ diff --git a/design/screenshots/Chinese (Simplified) zh-CN/restaurant-description.png b/design/screenshots/Chinese (Simplified) zh-CN/restaurant-description.png new file mode 100644 index 000000000..288af316a Binary files /dev/null and b/design/screenshots/Chinese (Simplified) zh-CN/restaurant-description.png differ diff --git a/design/screenshots/Chinese (Simplified) zh-CN/restaurant-details.png b/design/screenshots/Chinese (Simplified) zh-CN/restaurant-details.png new file mode 100644 index 000000000..7451dd9bf Binary files /dev/null and b/design/screenshots/Chinese (Simplified) zh-CN/restaurant-details.png differ diff --git a/design/screenshots/Chinese (Simplified) zh-CN/school-categories.png b/design/screenshots/Chinese (Simplified) zh-CN/school-categories.png new file mode 100644 index 000000000..747d23ea7 Binary files /dev/null and b/design/screenshots/Chinese (Simplified) zh-CN/school-categories.png differ diff --git a/design/screenshots/Chinese (Simplified) zh-CN/school-description.png b/design/screenshots/Chinese (Simplified) zh-CN/school-description.png new file mode 100644 index 000000000..3df6470eb Binary files /dev/null and b/design/screenshots/Chinese (Simplified) zh-CN/school-description.png differ diff --git a/design/screenshots/Chinese (Simplified) zh-CN/school-details.png b/design/screenshots/Chinese (Simplified) zh-CN/school-details.png new file mode 100644 index 000000000..97e031f2d Binary files /dev/null and b/design/screenshots/Chinese (Simplified) zh-CN/school-details.png differ diff --git a/design/screenshots/Chinese (Simplified) zh-CN/taking-picture.png b/design/screenshots/Chinese (Simplified) zh-CN/taking-picture.png new file mode 100644 index 000000000..0c4445f5f Binary files /dev/null and b/design/screenshots/Chinese (Simplified) zh-CN/taking-picture.png differ