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