From 8265cc6306c771ba3dd36abee37684c710700821 Mon Sep 17 00:00:00 2001 From: Saifuddin Adenwala Date: Tue, 3 Dec 2024 11:57:11 +0530 Subject: [PATCH] Migrate location and language module from Java to Kotlin (#5988) * Rename .java to .kt * Migrated location and language module from Java to Kotlin * Changed lastLocation visibility --- .../LocationPicker/LocationPickerActivity.kt | 6 +- .../language/AppLanguageLookUpTable.java | 141 --------- .../language/AppLanguageLookUpTable.kt | 135 +++++++++ .../fr/free/nrw/commons/location/LatLng.java | 198 ------------- .../fr/free/nrw/commons/location/LatLng.kt | 150 ++++++++++ .../location/LocationPermissionsHelper.java | 186 ------------ .../location/LocationPermissionsHelper.kt | 200 +++++++++++++ .../location/LocationServiceManager.java | 274 ------------------ .../location/LocationServiceManager.kt | 255 ++++++++++++++++ .../location/LocationUpdateListener.java | 7 - .../location/LocationUpdateListener.kt | 12 + .../nrw/commons/upload/LanguagesAdapter.kt | 16 +- .../kotlin/fr/free/nrw/commons/LatLngTests.kt | 2 +- .../media/MediaDetailFragmentUnitTests.kt | 8 +- .../commons/upload/LanguagesAdapterTest.kt | 10 +- 15 files changed, 773 insertions(+), 827 deletions(-) delete mode 100644 app/src/main/java/fr/free/nrw/commons/language/AppLanguageLookUpTable.java create mode 100644 app/src/main/java/fr/free/nrw/commons/language/AppLanguageLookUpTable.kt delete mode 100644 app/src/main/java/fr/free/nrw/commons/location/LatLng.java create mode 100644 app/src/main/java/fr/free/nrw/commons/location/LatLng.kt delete mode 100644 app/src/main/java/fr/free/nrw/commons/location/LocationPermissionsHelper.java create mode 100644 app/src/main/java/fr/free/nrw/commons/location/LocationPermissionsHelper.kt delete mode 100644 app/src/main/java/fr/free/nrw/commons/location/LocationServiceManager.java create mode 100644 app/src/main/java/fr/free/nrw/commons/location/LocationServiceManager.kt delete mode 100644 app/src/main/java/fr/free/nrw/commons/location/LocationUpdateListener.java create mode 100644 app/src/main/java/fr/free/nrw/commons/location/LocationUpdateListener.kt diff --git a/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerActivity.kt b/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerActivity.kt index 6508c4f25..1a5ec0a34 100644 --- a/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerActivity.kt @@ -423,7 +423,7 @@ class LocationPickerActivity : BaseActivity(), LocationPermissionCallback { * Moves map to GPS location */ private fun moveMapToGPSLocation() { - locationManager.lastLocation?.let { + locationManager.getLastLocation()?.let { moveMapTo(GeoPoint(it.latitude, it.longitude)) } } @@ -591,7 +591,7 @@ class LocationPickerActivity : BaseActivity(), LocationPermissionCallback { override fun onLocationPermissionGranted() { if (moveToCurrentLocation || activity != "MediaActivity") { - if (locationPermissionsHelper.isLocationAccessToAppsTurnedOn) { + if (locationPermissionsHelper.isLocationAccessToAppsTurnedOn()) { locationManager.requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER) locationManager.requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER) addMarkerAtGPSLocation() @@ -606,7 +606,7 @@ class LocationPickerActivity : BaseActivity(), LocationPermissionCallback { * Adds a marker at the user's GPS location */ private fun addMarkerAtGPSLocation() { - locationManager.lastLocation?.let { + locationManager.getLastLocation()?.let { addLocationMarker(GeoPoint(it.latitude, it.longitude)) markerImage.translationY = 0f } diff --git a/app/src/main/java/fr/free/nrw/commons/language/AppLanguageLookUpTable.java b/app/src/main/java/fr/free/nrw/commons/language/AppLanguageLookUpTable.java deleted file mode 100644 index a0286a7ef..000000000 --- a/app/src/main/java/fr/free/nrw/commons/language/AppLanguageLookUpTable.java +++ /dev/null @@ -1,141 +0,0 @@ -package fr.free.nrw.commons.language; - -import android.content.Context; -import android.content.res.Resources; -import android.text.TextUtils; - -import androidx.annotation.ArrayRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import fr.free.nrw.commons.R; -import java.lang.ref.SoftReference; -import java.util.Arrays; -import java.util.List; -import java.util.Locale; - -/** Immutable look up table for all app supported languages. All article languages may not be - * present in this table as it is statically bundled with the app. */ -public class AppLanguageLookUpTable { - public static final String SIMPLIFIED_CHINESE_LANGUAGE_CODE = "zh-hans"; - public static final String TRADITIONAL_CHINESE_LANGUAGE_CODE = "zh-hant"; - public static final String CHINESE_CN_LANGUAGE_CODE = "zh-cn"; - public static final String CHINESE_HK_LANGUAGE_CODE = "zh-hk"; - public static final String CHINESE_MO_LANGUAGE_CODE = "zh-mo"; - public static final String CHINESE_SG_LANGUAGE_CODE = "zh-sg"; - public static final String CHINESE_TW_LANGUAGE_CODE = "zh-tw"; - public static final String CHINESE_YUE_LANGUAGE_CODE = "zh-yue"; - public static final String CHINESE_LANGUAGE_CODE = "zh"; - public static final String NORWEGIAN_LEGACY_LANGUAGE_CODE = "no"; - public static final String NORWEGIAN_BOKMAL_LANGUAGE_CODE = "nb"; - public static final String TEST_LANGUAGE_CODE = "test"; - public static final String FALLBACK_LANGUAGE_CODE = "en"; // Must exist in preference_language_keys. - - @NonNull private final Resources resources; - - // Language codes for all app supported languages in fixed order. The special code representing - // the dynamic system language is null. - @NonNull private SoftReference> codesRef = new SoftReference<>(null); - - // English names for all app supported languages in fixed order. - @NonNull private SoftReference> canonicalNamesRef = new SoftReference<>(null); - - // Native names for all app supported languages in fixed order. - @NonNull private SoftReference> localizedNamesRef = new SoftReference<>(null); - - public AppLanguageLookUpTable(@NonNull Context context) { - resources = context.getResources(); - } - - /** - * @return Nonnull immutable list. The special code representing the dynamic system language is - * null. - */ - @NonNull - public List getCodes() { - List codes = codesRef.get(); - if (codes == null) { - codes = getStringList(R.array.preference_language_keys); - codesRef = new SoftReference<>(codes); - } - return codes; - } - - @Nullable - public String getCanonicalName(@Nullable String code) { - String name = defaultIndex(getCanonicalNames(), indexOfCode(code), null); - if (TextUtils.isEmpty(name) && !TextUtils.isEmpty(code)) { - if (code.equals(Locale.CHINESE.getLanguage())) { - name = Locale.CHINESE.getDisplayName(Locale.ENGLISH); - } else if (code.equals(NORWEGIAN_LEGACY_LANGUAGE_CODE)) { - name = defaultIndex(getCanonicalNames(), indexOfCode(NORWEGIAN_BOKMAL_LANGUAGE_CODE), null); - } - } - return name; - } - - @Nullable - public String getLocalizedName(@Nullable String code) { - String name = defaultIndex(getLocalizedNames(), indexOfCode(code), null); - if (TextUtils.isEmpty(name) && !TextUtils.isEmpty(code)) { - if (code.equals(Locale.CHINESE.getLanguage())) { - name = Locale.CHINESE.getDisplayName(Locale.CHINESE); - } else if (code.equals(NORWEGIAN_LEGACY_LANGUAGE_CODE)) { - name = defaultIndex(getLocalizedNames(), indexOfCode(NORWEGIAN_BOKMAL_LANGUAGE_CODE), null); - } - } - return name; - } - - public List getCanonicalNames() { - List names = canonicalNamesRef.get(); - if (names == null) { - names = getStringList(R.array.preference_language_canonical_names); - canonicalNamesRef = new SoftReference<>(names); - } - return names; - } - - public List getLocalizedNames() { - List names = localizedNamesRef.get(); - if (names == null) { - names = getStringList(R.array.preference_language_local_names); - localizedNamesRef = new SoftReference<>(names); - } - return names; - } - - public boolean isSupportedCode(@Nullable String code) { - return getCodes().contains(code); - } - - private T defaultIndex(List list, int index, T defaultValue) { - return inBounds(list, index) ? list.get(index) : defaultValue; - } - - /** - * Searches #codes for the specified language code and returns the index for use in - * #canonicalNames and #localizedNames. - * - * @param code The language code to search for. The special code representing the dynamic system - * language is null. - * @return The index of the language code or -1 if the code is not supported. - */ - private int indexOfCode(@Nullable String code) { - return getCodes().indexOf(code); - } - - /** @return Nonnull immutable list. */ - @NonNull - private List getStringList(int id) { - return Arrays.asList(getStringArray(id)); - } - - private boolean inBounds(List list, int index) { - return index >= 0 && index < list.size(); - } - - public String[] getStringArray(@ArrayRes int id) { - return resources.getStringArray(id); - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/language/AppLanguageLookUpTable.kt b/app/src/main/java/fr/free/nrw/commons/language/AppLanguageLookUpTable.kt new file mode 100644 index 000000000..6809fd79c --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/language/AppLanguageLookUpTable.kt @@ -0,0 +1,135 @@ +package fr.free.nrw.commons.language + +import android.content.Context +import android.content.res.Resources +import android.text.TextUtils + +import androidx.annotation.ArrayRes +import fr.free.nrw.commons.R +import java.lang.ref.SoftReference +import java.util.Arrays +import java.util.Locale + + +/** Immutable look up table for all app supported languages. All article languages may not be + * present in this table as it is statically bundled with the app. */ +class AppLanguageLookUpTable(context: Context) { + + companion object { + const val SIMPLIFIED_CHINESE_LANGUAGE_CODE = "zh-hans" + const val TRADITIONAL_CHINESE_LANGUAGE_CODE = "zh-hant" + const val CHINESE_CN_LANGUAGE_CODE = "zh-cn" + const val CHINESE_HK_LANGUAGE_CODE = "zh-hk" + const val CHINESE_MO_LANGUAGE_CODE = "zh-mo" + const val CHINESE_SG_LANGUAGE_CODE = "zh-sg" + const val CHINESE_TW_LANGUAGE_CODE = "zh-tw" + const val CHINESE_YUE_LANGUAGE_CODE = "zh-yue" + const val CHINESE_LANGUAGE_CODE = "zh" + const val NORWEGIAN_LEGACY_LANGUAGE_CODE = "no" + const val NORWEGIAN_BOKMAL_LANGUAGE_CODE = "nb" + const val TEST_LANGUAGE_CODE = "test" + const val FALLBACK_LANGUAGE_CODE = "en" // Must exist in preference_language_keys. + } + + private val resources: Resources = context.resources + + // Language codes for all app supported languages in fixed order. The special code representing + // the dynamic system language is null. + private var codesRef = SoftReference>(null) + + // English names for all app supported languages in fixed order. + private var canonicalNamesRef = SoftReference>(null) + + // Native names for all app supported languages in fixed order. + private var localizedNamesRef = SoftReference>(null) + + /** + * @return Nonnull immutable list. The special code representing the dynamic system language is + * null. + */ + fun getCodes(): List { + var codes = codesRef.get() + if (codes == null) { + codes = getStringList(R.array.preference_language_keys) + codesRef = SoftReference(codes) + } + return codes + } + + fun getCanonicalName(code: String?): String? { + var name = defaultIndex(getCanonicalNames(), indexOfCode(code), null) + if (name.isNullOrEmpty() && !code.isNullOrEmpty()) { + name = when (code) { + Locale.CHINESE.language -> Locale.CHINESE.getDisplayName(Locale.ENGLISH) + NORWEGIAN_LEGACY_LANGUAGE_CODE -> + defaultIndex(getCanonicalNames(), indexOfCode(NORWEGIAN_BOKMAL_LANGUAGE_CODE), null) + else -> null + } + } + return name + } + + fun getLocalizedName(code: String?): String? { + var name = defaultIndex(getLocalizedNames(), indexOfCode(code), null) + if (name.isNullOrEmpty() && !code.isNullOrEmpty()) { + name = when (code) { + Locale.CHINESE.language -> Locale.CHINESE.getDisplayName(Locale.CHINESE) + NORWEGIAN_LEGACY_LANGUAGE_CODE -> + defaultIndex(getLocalizedNames(), indexOfCode(NORWEGIAN_BOKMAL_LANGUAGE_CODE), null) + else -> null + } + } + return name + } + + fun getCanonicalNames(): List { + var names = canonicalNamesRef.get() + if (names == null) { + names = getStringList(R.array.preference_language_canonical_names) + canonicalNamesRef = SoftReference(names) + } + return names + } + + fun getLocalizedNames(): List { + var names = localizedNamesRef.get() + if (names == null) { + names = getStringList(R.array.preference_language_local_names) + localizedNamesRef = SoftReference(names) + } + return names + } + + fun isSupportedCode(code: String?): Boolean { + return getCodes().contains(code) + } + + private fun defaultIndex(list: List, index: Int, defaultValue: T?): T? { + return if (inBounds(list, index)) list[index] else defaultValue + } + + /** + * Searches #codes for the specified language code and returns the index for use in + * #canonicalNames and #localizedNames. + * + * @param code The language code to search for. The special code representing the dynamic system + * language is null. + * @return The index of the language code or -1 if the code is not supported. + */ + private fun indexOfCode(code: String?): Int { + return getCodes().indexOf(code) + } + + /** @return Nonnull immutable list. */ + private fun getStringList(id: Int): List { + return getStringArray(id).toList() + } + + private fun inBounds(list: List<*>, index: Int): Boolean { + return index in list.indices + } + + fun getStringArray(@ArrayRes id: Int): Array { + return resources.getStringArray(id) + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/location/LatLng.java b/app/src/main/java/fr/free/nrw/commons/location/LatLng.java deleted file mode 100644 index 4970fc54f..000000000 --- a/app/src/main/java/fr/free/nrw/commons/location/LatLng.java +++ /dev/null @@ -1,198 +0,0 @@ -package fr.free.nrw.commons.location; - -import android.location.Location; -import android.net.Uri; -import android.os.Parcel; -import android.os.Parcelable; - -import androidx.annotation.NonNull; - -/** - * a latitude and longitude point with accuracy information, often of a picture - */ -public class LatLng implements Parcelable { - - private final double latitude; - private final double longitude; - private final float accuracy; - - /** - * Accepts latitude and longitude. - * North and South values are cut off at 90° - * - * @param latitude the latitude - * @param longitude the longitude - * @param accuracy the accuracy - * - * Examples: - * the Statue of Liberty is located at 40.69° N, 74.04° W - * The Statue of Liberty could be constructed as LatLng(40.69, -74.04, 1.0) - * where positive signifies north, east and negative signifies south, west. - */ - public LatLng(double latitude, double longitude, float accuracy) { - if (-180.0D <= longitude && longitude < 180.0D) { - this.longitude = longitude; - } else { - this.longitude = ((longitude - 180.0D) % 360.0D + 360.0D) % 360.0D - 180.0D; - } - this.latitude = Math.max(-90.0D, Math.min(90.0D, latitude)); - this.accuracy = accuracy; - } - /** - * An alternate constructor for this class. - * @param in A parcelable which contains the latitude, longitude, and accuracy - */ - public LatLng(Parcel in) { - latitude = in.readDouble(); - longitude = in.readDouble(); - accuracy = in.readFloat(); - } - - /** - * gets the latitude and longitude of a given non-null location - * @param location the non-null location of the user - * @return LatLng the Latitude and Longitude of a given location - */ - public static LatLng from(@NonNull Location location) { - return new LatLng(location.getLatitude(), location.getLongitude(), location.getAccuracy()); - } - - /** - * creates a hash code for the longitude and longitude - */ - public int hashCode() { - byte var1 = 1; - long var2 = Double.doubleToLongBits(this.latitude); - int var3 = 31 * var1 + (int)(var2 ^ var2 >>> 32); - var2 = Double.doubleToLongBits(this.longitude); - var3 = 31 * var3 + (int)(var2 ^ var2 >>> 32); - return var3; - } - - /** - * checks for equality of two LatLng objects - * @param o the second LatLng object - */ - public boolean equals(Object o) { - if (this == o) { - return true; - } else if (!(o instanceof LatLng)) { - return false; - } else { - LatLng var2 = (LatLng)o; - return Double.doubleToLongBits(this.latitude) == Double.doubleToLongBits(var2.latitude) && Double.doubleToLongBits(this.longitude) == Double.doubleToLongBits(var2.longitude); - } - } - - /** - * returns a string representation of the latitude and longitude - */ - public String toString() { - return "lat/lng: (" + this.latitude + "," + this.longitude + ")"; - } - - /** - * Rounds the float to 4 digits and returns absolute value. - * - * @param coordinate A coordinate value as string. - * @return String of the rounded number. - */ - private String formatCoordinate(double coordinate) { - double roundedNumber = Math.round(coordinate * 10000d) / 10000d; - double absoluteNumber = Math.abs(roundedNumber); - return String.valueOf(absoluteNumber); - } - - /** - * Returns "N" or "S" depending on the latitude. - * - * @return "N" or "S". - */ - private String getNorthSouth() { - if (this.latitude < 0) { - return "S"; - } - - return "N"; - } - - /** - * Returns "E" or "W" depending on the longitude. - * - * @return "E" or "W". - */ - private String getEastWest() { - if (this.longitude >= 0 && this.longitude < 180) { - return "E"; - } - - return "W"; - } - - /** - * Returns a nicely formatted coordinate string. Used e.g. in - * the detail view. - * - * @return The formatted string. - */ - public String getPrettyCoordinateString() { - return formatCoordinate(this.latitude) + " " + this.getNorthSouth() + ", " - + formatCoordinate(this.longitude) + " " + this.getEastWest(); - } - - /** - * Return the location accuracy in meter. - * - * @return float - */ - public float getAccuracy() { - return accuracy; - } - - /** - * Return the longitude in degrees. - * - * @return double - */ - public double getLongitude() { - return longitude; - } - - /** - * Return the latitude in degrees. - * - * @return double - */ - public double getLatitude() { - return latitude; - } - - public Uri getGmmIntentUri() { - return Uri.parse("geo:" + latitude + "," + longitude + "?z=16"); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeDouble(latitude); - dest.writeDouble(longitude); - dest.writeFloat(accuracy); - } - - public static final Creator CREATOR = new Creator() { - @Override - public LatLng createFromParcel(Parcel in) { - return new LatLng(in); - } - - @Override - public LatLng[] newArray(int size) { - return new LatLng[size]; - } - }; -} - diff --git a/app/src/main/java/fr/free/nrw/commons/location/LatLng.kt b/app/src/main/java/fr/free/nrw/commons/location/LatLng.kt new file mode 100644 index 000000000..4e21b93c2 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/location/LatLng.kt @@ -0,0 +1,150 @@ +package fr.free.nrw.commons.location + +import android.location.Location +import android.net.Uri +import android.os.Parcel +import android.os.Parcelable +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min +import kotlin.math.round + + +/** + * A latitude and longitude point with accuracy information, often of a picture. + */ +data class LatLng( + var latitude: Double, + var longitude: Double, + val accuracy: Float +) : Parcelable { + + /** + * Accepts latitude and longitude. + * North and South values are cut off at 90° + * + * Examples: + * the Statue of Liberty is located at 40.69° N, 74.04° W + * The Statue of Liberty could be constructed as LatLng(40.69, -74.04, 1.0) + * where positive signifies north, east and negative signifies south, west. + */ + init { + val adjustedLongitude = when { + longitude in -180.0..180.0 -> longitude + else -> ((longitude - 180.0) % 360.0 + 360.0) % 360.0 - 180.0 + } + latitude = max(-90.0, min(90.0, latitude)) + longitude = adjustedLongitude + } + + /** + * Accepts a non-null [Location] and converts it to a [LatLng]. + */ + companion object { + /** + * gets the latitude and longitude of a given non-null location + * @param location the non-null location of the user + * @return LatLng the Latitude and Longitude of a given location + */ + @JvmStatic + fun from(location: Location): LatLng { + return LatLng(location.latitude, location.longitude, location.accuracy) + } + + @JvmField + val CREATOR: Parcelable.Creator = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): LatLng { + return LatLng(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + } + + /** + * An alternate constructor for this class. + * @param parcel A parcelable which contains the latitude, longitude, and accuracy + */ + private constructor(parcel: Parcel) : this( + latitude = parcel.readDouble(), + longitude = parcel.readDouble(), + accuracy = parcel.readFloat() + ) + + /** + * Creates a hash code for the latitude and longitude. + */ + override fun hashCode(): Int { + var result = 1 + val latitudeBits = latitude.toBits() + result = 31 * result + (latitudeBits xor (latitudeBits ushr 32)).toInt() + val longitudeBits = longitude.toBits() + result = 31 * result + (longitudeBits xor (longitudeBits ushr 32)).toInt() + return result + } + + /** + * Checks for equality of two LatLng objects. + * @param other the second LatLng object + */ + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is LatLng) return false + return latitude.toBits() == other.latitude.toBits() && + longitude.toBits() == other.longitude.toBits() + } + + /** + * Returns a string representation of the latitude and longitude. + */ + override fun toString(): String { + return "lat/lng: ($latitude,$longitude)" + } + + /** + * Returns a nicely formatted coordinate string. Used e.g. in + * the detail view. + * + * @return The formatted string. + */ + fun getPrettyCoordinateString(): String { + return "${formatCoordinate(latitude)} ${getNorthSouth()}, " + + "${formatCoordinate(longitude)} ${getEastWest()}" + } + + /** + * Gets a URI for a Google Maps intent at the location. + */ + fun getGmmIntentUri(): Uri { + return Uri.parse("geo:$latitude,$longitude?z=16") + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeDouble(latitude) + parcel.writeDouble(longitude) + parcel.writeFloat(accuracy) + } + + override fun describeContents(): Int = 0 + + private fun formatCoordinate(coordinate: Double): String { + val roundedNumber = round(coordinate * 10000) / 10000 + return abs(roundedNumber).toString() + } + + /** + * Returns "N" or "S" depending on the latitude. + * + * @return "N" or "S". + */ + private fun getNorthSouth(): String = if (latitude < 0) "S" else "N" + + /** + * Returns "E" or "W" depending on the longitude. + * + * @return "E" or "W". + */ + private fun getEastWest(): String = if (longitude in 0.0..179.999) "E" else "W" +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/location/LocationPermissionsHelper.java b/app/src/main/java/fr/free/nrw/commons/location/LocationPermissionsHelper.java deleted file mode 100644 index 77e089c9c..000000000 --- a/app/src/main/java/fr/free/nrw/commons/location/LocationPermissionsHelper.java +++ /dev/null @@ -1,186 +0,0 @@ -package fr.free.nrw.commons.location; - -import android.Manifest; -import android.Manifest.permission; -import android.app.Activity; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.provider.Settings; -import android.widget.Toast; -import androidx.core.app.ActivityCompat; -import fr.free.nrw.commons.R; -import fr.free.nrw.commons.filepicker.Constants.RequestCodes; -import fr.free.nrw.commons.utils.DialogUtil; -import fr.free.nrw.commons.utils.PermissionUtils; - -/** - * Helper class to handle location permissions. - * - * Location flow for fragments containing a map is as follows: - * Case 1: When location permission has never been asked for or denied before - * Check if permission is already granted or not. - * If not already granted, ask for it (if it isn't denied twice before). - * If now user grants permission, go to Case 3/4, else go to Case 2. - * - * Case 2: When location permission is just asked but has been denied - * Shows a toast to tell the user why location permission is needed. - * Also shows a rationale to the user, on agreeing to which, we go back to Case 1. - * Show current location / nearby pins / nearby images according to the default location. - * - * Case 3: When location permission are already granted, but location services are off - * Asks the user to turn on the location service, using a dialog. - * If the user rejects, checks for the last known location and shows stuff using that location. - * Also displays a toast telling the user why location should be turned on. - * - * Case 4: When location permission has been granted and location services are also on - * Do whatever is required by that particular activity / fragment using current location. - * - */ -public class LocationPermissionsHelper { - - Activity activity; - LocationServiceManager locationManager; - LocationPermissionCallback callback; - - public LocationPermissionsHelper(Activity activity, LocationServiceManager locationManager, - LocationPermissionCallback callback) { - this.activity = activity; - this.locationManager = locationManager; - this.callback = callback; - } - - /** - * Ask for location permission if the user agrees on attaching location with pictures and the - * app does not have the access to location - * - * @param dialogTitleResource Resource id of the title of the dialog - * @param dialogTextResource Resource id of the text of the dialog - */ - public void requestForLocationAccess( - int dialogTitleResource, - int dialogTextResource - ) { - if (checkLocationPermission(activity)) { - callback.onLocationPermissionGranted(); - } else { - if (ActivityCompat.shouldShowRequestPermissionRationale(activity, - permission.ACCESS_FINE_LOCATION)) { - DialogUtil.showAlertDialog(activity, activity.getString(dialogTitleResource), - activity.getString(dialogTextResource), - activity.getString(android.R.string.ok), - activity.getString(android.R.string.cancel), - () -> { - ActivityCompat.requestPermissions(activity, - new String[]{permission.ACCESS_FINE_LOCATION}, 1); - }, - () -> callback.onLocationPermissionDenied( - activity.getString(R.string.upload_map_location_access)), - null, - false); - } else { - ActivityCompat.requestPermissions(activity, - new String[]{permission.ACCESS_FINE_LOCATION}, - RequestCodes.LOCATION); - } - } - } - - /** - * Shows a dialog for user to open the settings page and turn on location services - * - * @param activity Activity object - * @param dialogTextResource int id of the required string resource - */ - public void showLocationOffDialog(Activity activity, int dialogTextResource) { - DialogUtil - .showAlertDialog(activity, - activity.getString(R.string.ask_to_turn_location_on), - activity.getString(dialogTextResource), - activity.getString(R.string.title_app_shortcut_setting), - activity.getString(R.string.cancel), - () -> openLocationSettings(activity), - () -> Toast.makeText(activity, activity.getString(dialogTextResource), - Toast.LENGTH_LONG).show() - ); - } - - /** - * Opens the location access page in settings, for user to turn on location services - * - * @param activity Activtiy object - */ - public void openLocationSettings(Activity activity) { - final Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); - final PackageManager packageManager = activity.getPackageManager(); - - if (intent.resolveActivity(packageManager) != null) { - activity.startActivity(intent); - } else { - Toast.makeText(activity, R.string.cannot_open_location_settings, Toast.LENGTH_LONG) - .show(); - } - } - - /** - * Shows a dialog for user to open the app's settings page and give location permission - * - * @param activity Activity object - * @param dialogTextResource int id of the required string resource - */ - public void showAppSettingsDialog(Activity activity, int dialogTextResource) { - DialogUtil - .showAlertDialog(activity, activity.getString(R.string.location_permission_title), - activity.getString(dialogTextResource), - activity.getString(R.string.title_app_shortcut_setting), - activity.getString(R.string.cancel), - () -> openAppSettings(activity), - () -> Toast.makeText(activity, activity.getString(dialogTextResource), - Toast.LENGTH_LONG).show() - ); - } - - /** - * Opens detailed settings page of the app for the user to turn on location services - * - * @param activity Activity object - */ - public void openAppSettings(Activity activity) { - Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); - Uri uri = Uri.fromParts("package", activity.getPackageName(), null); - intent.setData(uri); - activity.startActivity(intent); - } - - - /** - * Check if apps have access to location even after having individual access - * - * @return Returns true if location services are on and false otherwise - */ - public boolean isLocationAccessToAppsTurnedOn() { - return (locationManager.isNetworkProviderEnabled() - || locationManager.isGPSProviderEnabled()); - } - - /** - * Checks if location permission is already granted or not - * - * @param activity Activity object - * @return Returns true if location permission is granted and false otherwise - */ - public boolean checkLocationPermission(Activity activity) { - return PermissionUtils.hasPermission(activity, - new String[]{Manifest.permission.ACCESS_FINE_LOCATION}); - } - - /** - * Handle onPermissionDenied within individual classes based on the requirements - */ - public interface LocationPermissionCallback { - - void onLocationPermissionDenied(String toastMessage); - - void onLocationPermissionGranted(); - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/location/LocationPermissionsHelper.kt b/app/src/main/java/fr/free/nrw/commons/location/LocationPermissionsHelper.kt new file mode 100644 index 000000000..771d9efdc --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/location/LocationPermissionsHelper.kt @@ -0,0 +1,200 @@ +package fr.free.nrw.commons.location + +import android.Manifest.permission +import android.app.Activity +import android.content.Intent +import android.net.Uri +import android.provider.Settings +import android.widget.Toast +import androidx.core.app.ActivityCompat +import fr.free.nrw.commons.R +import fr.free.nrw.commons.filepicker.Constants.RequestCodes +import fr.free.nrw.commons.utils.DialogUtil +import fr.free.nrw.commons.utils.PermissionUtils + +/** + * Helper class to handle location permissions. + * + * Location flow for fragments containing a map is as follows: + * Case 1: When location permission has never been asked for or denied before + * Check if permission is already granted or not. + * If not already granted, ask for it (if it isn't denied twice before). + * If now user grants permission, go to Case 3/4, else go to Case 2. + * + * Case 2: When location permission is just asked but has been denied + * Shows a toast to tell the user why location permission is needed. + * Also shows a rationale to the user, on agreeing to which, we go back to Case 1. + * Show current location / nearby pins / nearby images according to the default location. + * + * Case 3: When location permission are already granted, but location services are off + * Asks the user to turn on the location service, using a dialog. + * If the user rejects, checks for the last known location and shows stuff using that location. + * Also displays a toast telling the user why location should be turned on. + * + * Case 4: When location permission has been granted and location services are also on + * Do whatever is required by that particular activity / fragment using current location. + * + */ +class LocationPermissionsHelper( + private val activity: Activity, + private val locationManager: LocationServiceManager, + private val callback: LocationPermissionCallback? +) { + + /** + * Ask for location permission if the user agrees on attaching location with pictures and the + * app does not have the access to location + * + * @param dialogTitleResource Resource id of the title of the dialog + * @param dialogTextResource Resource id of the text of the dialog + */ + fun requestForLocationAccess( + dialogTitleResource: Int, + dialogTextResource: Int + ) { + if (checkLocationPermission(activity)) { + callback?.onLocationPermissionGranted() + } else { + if (ActivityCompat.shouldShowRequestPermissionRationale( + activity, + permission.ACCESS_FINE_LOCATION + ) + ) { + DialogUtil.showAlertDialog( + activity, + activity.getString(dialogTitleResource), + activity.getString(dialogTextResource), + activity.getString(android.R.string.ok), + activity.getString(android.R.string.cancel), + { + ActivityCompat.requestPermissions( + activity, + arrayOf(permission.ACCESS_FINE_LOCATION), + 1 + ) + }, + { + callback?.onLocationPermissionDenied( + activity.getString(R.string.upload_map_location_access) + ) + }, + null, + false + ) + } else { + ActivityCompat.requestPermissions( + activity, + arrayOf(permission.ACCESS_FINE_LOCATION), + RequestCodes.LOCATION + ) + } + } + } + + /** + * Shows a dialog for user to open the settings page and turn on location services + * + * @param activity Activity object + * @param dialogTextResource int id of the required string resource + */ + fun showLocationOffDialog(activity: Activity, dialogTextResource: Int) { + DialogUtil.showAlertDialog( + activity, + activity.getString(R.string.ask_to_turn_location_on), + activity.getString(dialogTextResource), + activity.getString(R.string.title_app_shortcut_setting), + activity.getString(R.string.cancel), + { openLocationSettings(activity) }, + { + Toast.makeText( + activity, + activity.getString(dialogTextResource), + Toast.LENGTH_LONG + ).show() + } + ) + } + + /** + * Opens the location access page in settings, for user to turn on location services + * + * @param activity Activity object + */ + fun openLocationSettings(activity: Activity) { + val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS) + val packageManager = activity.packageManager + + if (intent.resolveActivity(packageManager) != null) { + activity.startActivity(intent) + } else { + Toast.makeText(activity, R.string.cannot_open_location_settings, Toast.LENGTH_LONG) + .show() + } + } + + /** + * Shows a dialog for user to open the app's settings page and give location permission + * + * @param activity Activity object + * @param dialogTextResource int id of the required string resource + */ + fun showAppSettingsDialog(activity: Activity, dialogTextResource: Int) { + DialogUtil.showAlertDialog( + activity, + activity.getString(R.string.location_permission_title), + activity.getString(dialogTextResource), + activity.getString(R.string.title_app_shortcut_setting), + activity.getString(R.string.cancel), + { openAppSettings(activity) }, + { + Toast.makeText( + activity, + activity.getString(dialogTextResource), + Toast.LENGTH_LONG + ).show() + } + ) + } + + /** + * Opens detailed settings page of the app for the user to turn on location services + * + * @param activity Activity object + */ + private fun openAppSettings(activity: Activity) { + val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) + val uri = Uri.fromParts("package", activity.packageName, null) + intent.data = uri + activity.startActivity(intent) + } + + /** + * Check if apps have access to location even after having individual access + * + * @return Returns true if location services are on and false otherwise + */ + fun isLocationAccessToAppsTurnedOn(): Boolean { + return locationManager.isNetworkProviderEnabled() || locationManager.isGPSProviderEnabled() + } + + /** + * Checks if location permission is already granted or not + * + * @param activity Activity object + * @return Returns true if location permission is granted and false otherwise + */ + fun checkLocationPermission(activity: Activity): Boolean { + return PermissionUtils.hasPermission( + activity, + arrayOf(permission.ACCESS_FINE_LOCATION) + ) + } + + /** + * Handle onPermissionDenied within individual classes based on the requirements + */ + interface LocationPermissionCallback { + fun onLocationPermissionDenied(toastMessage: String) + fun onLocationPermissionGranted() + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/location/LocationServiceManager.java b/app/src/main/java/fr/free/nrw/commons/location/LocationServiceManager.java deleted file mode 100644 index 4c7289ea5..000000000 --- a/app/src/main/java/fr/free/nrw/commons/location/LocationServiceManager.java +++ /dev/null @@ -1,274 +0,0 @@ -package fr.free.nrw.commons.location; - -import android.Manifest.permission; -import android.app.Activity; -import android.content.Context; -import android.content.pm.PackageManager; -import android.location.Location; -import android.location.LocationListener; -import android.location.LocationManager; -import android.os.Bundle; -import androidx.core.app.ActivityCompat; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.CopyOnWriteArrayList; - -import timber.log.Timber; - -public class LocationServiceManager implements LocationListener { - - // Maybe these values can be improved for efficiency - private static final long MIN_LOCATION_UPDATE_REQUEST_TIME_IN_MILLIS = 10 * 100; - private static final long MIN_LOCATION_UPDATE_REQUEST_DISTANCE_IN_METERS = 1; - - private LocationManager locationManager; - private Location lastLocation; - //private Location lastLocationDuplicate; // Will be used for nearby card view on contributions activity - private final List locationListeners = new CopyOnWriteArrayList<>(); - private boolean isLocationManagerRegistered = false; - private Set locationExplanationDisplayed = new HashSet<>(); - private Context context; - - /** - * Constructs a new instance of LocationServiceManager. - * - * @param context the context - */ - public LocationServiceManager(Context context) { - this.context = context; - this.locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); - } - - public LatLng getLastLocation() { - if (lastLocation == null) { - lastLocation = getLastKnownLocation(); - if(lastLocation != null) { - return LatLng.from(lastLocation); - } - else { - return null; - } - } - return LatLng.from(lastLocation); - } - - private Location getLastKnownLocation() { - List providers = locationManager.getProviders(true); - Location bestLocation = null; - for (String provider : providers) { - Location l=null; - if (ActivityCompat.checkSelfPermission(context, permission.ACCESS_FINE_LOCATION) - == PackageManager.PERMISSION_GRANTED - && ActivityCompat.checkSelfPermission(context, permission.ACCESS_COARSE_LOCATION) - == PackageManager.PERMISSION_GRANTED) { - l = locationManager.getLastKnownLocation(provider); - } - if (l == null) { - continue; - } - if (bestLocation == null - || l.getAccuracy() < bestLocation.getAccuracy()) { - bestLocation = l; - } - } - if (bestLocation == null) { - return null; - } - return bestLocation; - } - - /** - * Registers a LocationManager to listen for current location. - */ - public void registerLocationManager() { - if (!isLocationManagerRegistered) { - isLocationManagerRegistered = requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER) - && requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER); - } - } - - /** - * Requests location updates from the specified provider. - * - * @param locationProvider the location provider - * @return true if successful - */ - public boolean requestLocationUpdatesFromProvider(String locationProvider) { - try { - // If both providers are not available - if (locationManager == null || !(locationManager.getAllProviders().contains(locationProvider))) { - return false; - } - 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; - } - } - - /** - * Returns whether a given location is better than the current best location. - * - * @param location the location to be tested - * @param currentBestLocation the current best location - * @return LOCATION_SIGNIFICANTLY_CHANGED if location changed significantly - * LOCATION_SLIGHTLY_CHANGED if location changed slightly - */ - private LocationChangeType isBetterLocation(Location location, Location currentBestLocation) { - - if (currentBestLocation == null) { - // A new location is always better than no location - return LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED; - } - - // 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 isNewer = timeDelta > 0; - - // 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()); - - float[] results = new float[5]; - Location.distanceBetween( - currentBestLocation.getLatitude(), - currentBestLocation.getLongitude(), - location.getLatitude(), - location.getLongitude(), - results); - - // If it's been more than two minutes since the current location, use the new location - // because the user has likely moved - if (isSignificantlyNewer - || isMoreAccurate - || (isNewer && !isLessAccurate) - || (isNewer && !isSignificantlyLessAccurate && isFromSameProvider)) { - if (results[0] < 1000) { // Means change is smaller than 1000 meter - return LocationChangeType.LOCATION_SLIGHTLY_CHANGED; - } else { - return LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED; - } - } else{ - return LocationChangeType.LOCATION_NOT_CHANGED; - } - } - - /** - * 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; - locationExplanationDisplayed.clear(); - try { - locationManager.removeUpdates(this); - } catch (SecurityException e) { - Timber.e(e, "Security exception"); - } - } - - /** - * Adds a new listener to the list of location listeners. - * - * @param listener the new listener - */ - public void addLocationListener(LocationUpdateListener listener) { - if (!locationListeners.contains(listener)) { - locationListeners.add(listener); - } - } - - /** - * Removes a listener from the list of location listeners. - * - * @param listener the listener to be removed - */ - public void removeLocationListener(LocationUpdateListener listener) { - locationListeners.remove(listener); - } - - @Override - public void onLocationChanged(Location location) { - Timber.d("on location changed"); - if (isBetterLocation(location, lastLocation) - .equals(LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED)) { - lastLocation = location; - //lastLocationDuplicate = location; - for (LocationUpdateListener listener : locationListeners) { - listener.onLocationChangedSignificantly(LatLng.from(lastLocation)); - } - } else if (location.distanceTo(lastLocation) >= 500) { - // Update nearby notification card at every 500 meters. - for (LocationUpdateListener listener : locationListeners) { - listener.onLocationChangedMedium(LatLng.from(lastLocation)); - } - } - - else if (isBetterLocation(location, lastLocation) - .equals(LocationChangeType.LOCATION_SLIGHTLY_CHANGED)) { - lastLocation = location; - //lastLocationDuplicate = location; - for (LocationUpdateListener listener : locationListeners) { - listener.onLocationChangedSlightly(LatLng.from(lastLocation)); - } - } - } - - @Override - public void onStatusChanged(String provider, int status, Bundle extras) { - Timber.d("%s's status changed to %d", provider, status); - } - - @Override - public void onProviderEnabled(String provider) { - Timber.d("Provider %s enabled", provider); - } - - @Override - public void onProviderDisabled(String provider) { - Timber.d("Provider %s disabled", provider); - } - - public boolean isNetworkProviderEnabled() { - return locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER); - } - - public boolean isGPSProviderEnabled() { - return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); - } - - public enum LocationChangeType{ - LOCATION_SIGNIFICANTLY_CHANGED, //Went out of borders of nearby markers - LOCATION_SLIGHTLY_CHANGED, //User might be walking or driving - LOCATION_MEDIUM_CHANGED, //Between slight and significant changes, will be used for nearby card view updates. - LOCATION_NOT_CHANGED, - PERMISSION_JUST_GRANTED, - MAP_UPDATED, - SEARCH_CUSTOM_AREA, - CUSTOM_QUERY - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/location/LocationServiceManager.kt b/app/src/main/java/fr/free/nrw/commons/location/LocationServiceManager.kt new file mode 100644 index 000000000..3a4c4b72e --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/location/LocationServiceManager.kt @@ -0,0 +1,255 @@ +package fr.free.nrw.commons.location + +import android.Manifest +import android.app.Activity +import android.content.Context +import android.content.pm.PackageManager +import android.location.Location +import android.location.LocationListener +import android.location.LocationManager +import android.os.Bundle +import androidx.core.app.ActivityCompat +import timber.log.Timber +import java.util.concurrent.CopyOnWriteArrayList + + +class LocationServiceManager(private val context: Context) : LocationListener { + + companion object { + // Maybe these values can be improved for efficiency + private const val MIN_LOCATION_UPDATE_REQUEST_TIME_IN_MILLIS = 10 * 100L + private const val MIN_LOCATION_UPDATE_REQUEST_DISTANCE_IN_METERS = 1f + } + + private val locationManager: LocationManager = + context.getSystemService(Context.LOCATION_SERVICE) as LocationManager + private var lastLocationVar: Location? = null + private val locationListeners = CopyOnWriteArrayList() + private var isLocationManagerRegistered = false + private val locationExplanationDisplayed = mutableSetOf() + + /** + * Constructs a new instance of LocationServiceManager. + * + */ + fun getLastLocation(): LatLng? { + if (lastLocationVar == null) { + lastLocationVar = getLastKnownLocation() + return lastLocationVar?.let { LatLng.from(it) } + } + return LatLng.from(lastLocationVar!!) + } + + private fun getLastKnownLocation(): Location? { + val providers = locationManager.getProviders(true) + var bestLocation: Location? = null + for (provider in providers) { + val location: Location? = if ( + ActivityCompat.checkSelfPermission( + context, + Manifest.permission.ACCESS_FINE_LOCATION) + == + PackageManager.PERMISSION_GRANTED && + ActivityCompat.checkSelfPermission( + context, + Manifest.permission.ACCESS_COARSE_LOCATION) + == + PackageManager.PERMISSION_GRANTED + ) { + locationManager.getLastKnownLocation(provider) + } else { + null + } + + if ( + location != null + && + (bestLocation == null || location.accuracy < bestLocation.accuracy) + ) { + bestLocation = location + } + } + return bestLocation + } + + /** + * Registers a LocationManager to listen for current location. + */ + fun registerLocationManager() { + if (!isLocationManagerRegistered) { + isLocationManagerRegistered = + requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER) && + requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER) + } + } + + /** + * Requests location updates from the specified provider. + * + * @param locationProvider the location provider + * @return true if successful + */ + fun requestLocationUpdatesFromProvider(locationProvider: String): Boolean { + return try { + if (locationManager.allProviders.contains(locationProvider)) { + locationManager.requestLocationUpdates( + locationProvider, + MIN_LOCATION_UPDATE_REQUEST_TIME_IN_MILLIS, + MIN_LOCATION_UPDATE_REQUEST_DISTANCE_IN_METERS, + this + ) + true + } else { + false + } + } catch (e: IllegalArgumentException) { + Timber.e(e, "Illegal argument exception") + false + } catch (e: SecurityException) { + Timber.e(e, "Security exception") + false + } + } + + /** + * Returns whether a given location is better than the current best location. + * + * @param location the location to be tested + * @param currentBestLocation the current best location + * @return LOCATION_SIGNIFICANTLY_CHANGED if location changed significantly + * LOCATION_SLIGHTLY_CHANGED if location changed slightly + */ + private fun isBetterLocation(location: Location, currentBestLocation: Location?): LocationChangeType { + if (currentBestLocation == null) { + return LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED + } + + val timeDelta = location.time - currentBestLocation.time + val isSignificantlyNewer = timeDelta > MIN_LOCATION_UPDATE_REQUEST_TIME_IN_MILLIS + val isNewer = timeDelta > 0 + val accuracyDelta = (location.accuracy - currentBestLocation.accuracy).toInt() + val isMoreAccurate = accuracyDelta < 0 + val isSignificantlyLessAccurate = accuracyDelta > 200 + val isFromSameProvider = isSameProvider(location.provider, currentBestLocation.provider) + + val results = FloatArray(5) + Location.distanceBetween( + currentBestLocation.latitude, currentBestLocation.longitude, + location.latitude, location.longitude, + results + ) + + return when { + isSignificantlyNewer + || + isMoreAccurate + || + (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) -> { + if (results[0] < 1000) LocationChangeType.LOCATION_SLIGHTLY_CHANGED + else LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED + } + else -> LocationChangeType.LOCATION_NOT_CHANGED + } + } + + /** + * Checks whether two providers are the same + */ + private fun isSameProvider(provider1: String?, provider2: String?): Boolean { + return provider1 == provider2 + } + + /** + * Unregisters location manager. + */ + fun unregisterLocationManager() { + isLocationManagerRegistered = false + locationExplanationDisplayed.clear() + try { + locationManager.removeUpdates(this) + } catch (e: SecurityException) { + Timber.e(e, "Security exception") + } + } + + /** + * Adds a new listener to the list of location listeners. + * + * @param listener the new listener + */ + fun addLocationListener(listener: LocationUpdateListener) { + if (!locationListeners.contains(listener)) { + locationListeners.add(listener) + } + } + + /** + * Removes a listener from the list of location listeners. + * + * @param listener the listener to be removed + */ + fun removeLocationListener(listener: LocationUpdateListener) { + locationListeners.remove(listener) + } + + override fun onLocationChanged(location: Location) { + Timber.d("on location changed") + val changeType = isBetterLocation(location, lastLocationVar) + if (changeType == LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED) { + lastLocationVar = location + locationListeners.forEach { it.onLocationChangedSignificantly(LatLng.from(location)) } + } else if (lastLocationVar?.let { location.distanceTo(it) }!! >= 500) { + locationListeners.forEach { it.onLocationChangedMedium(LatLng.from(location)) } + } else if (changeType == LocationChangeType.LOCATION_SLIGHTLY_CHANGED) { + lastLocationVar = location + locationListeners.forEach { it.onLocationChangedSlightly(LatLng.from(location)) } + } + } + + @Deprecated("Deprecated in Java", ReplaceWith( + "Timber.d(\"%s's status changed to %d\", provider, status)", + "timber.log.Timber" + ) + ) + override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) { + Timber.d("%s's status changed to %d", provider, status) + } + + + + override fun onProviderEnabled(provider: String) { + Timber.d("Provider %s enabled", provider) + } + + override fun onProviderDisabled(provider: String) { + Timber.d("Provider %s disabled", provider) + } + + fun isNetworkProviderEnabled(): Boolean { + return locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) + } + + fun isGPSProviderEnabled(): Boolean { + return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) + } + + enum class LocationChangeType { + LOCATION_SIGNIFICANTLY_CHANGED, + LOCATION_SLIGHTLY_CHANGED, + LOCATION_MEDIUM_CHANGED, + LOCATION_NOT_CHANGED, + PERMISSION_JUST_GRANTED, + MAP_UPDATED, + SEARCH_CUSTOM_AREA, + CUSTOM_QUERY + } +} + + + + + + + + + diff --git a/app/src/main/java/fr/free/nrw/commons/location/LocationUpdateListener.java b/app/src/main/java/fr/free/nrw/commons/location/LocationUpdateListener.java deleted file mode 100644 index 61ff26b11..000000000 --- a/app/src/main/java/fr/free/nrw/commons/location/LocationUpdateListener.java +++ /dev/null @@ -1,7 +0,0 @@ -package fr.free.nrw.commons.location; - -public interface LocationUpdateListener { - void onLocationChangedSignificantly(LatLng latLng); // Will be used to update all nearby markers on the map - void onLocationChangedSlightly(LatLng latLng); // Will be used to track users motion - void onLocationChangedMedium(LatLng latLng); // Will be used updating nearby card view notification -} diff --git a/app/src/main/java/fr/free/nrw/commons/location/LocationUpdateListener.kt b/app/src/main/java/fr/free/nrw/commons/location/LocationUpdateListener.kt new file mode 100644 index 000000000..e90cc1224 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/location/LocationUpdateListener.kt @@ -0,0 +1,12 @@ +package fr.free.nrw.commons.location + +interface LocationUpdateListener { + // Will be used to update all nearby markers on the map + fun onLocationChangedSignificantly(latLng: LatLng) + + // Will be used to track users motion + fun onLocationChangedSlightly(latLng: LatLng) + + // Will be used updating nearby card view notification + fun onLocationChangedMedium(latLng: LatLng) +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/upload/LanguagesAdapter.kt b/app/src/main/java/fr/free/nrw/commons/upload/LanguagesAdapter.kt index 2847fa0c0..fa825d0a6 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/LanguagesAdapter.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/LanguagesAdapter.kt @@ -42,8 +42,8 @@ class LanguagesAdapter constructor( AppLanguageLookUpTable(context) init { - languageNamesList = language.localizedNames - languageCodesList = language.codes + languageNamesList = language.getLocalizedNames() + languageCodesList = language.getCodes() } private val filter = LanguageFilter() @@ -117,7 +117,7 @@ class LanguagesAdapter constructor( */ fun getIndexOfUserDefaultLocale(context: Context): Int { val userLanguageCode = context.locale?.language ?: return DEFAULT_INDEX - return language.codes.indexOf(userLanguageCode).takeIf { it >= 0 } ?: DEFAULT_INDEX + return language.getCodes().indexOf(userLanguageCode).takeIf { it >= 0 } ?: DEFAULT_INDEX } fun getIndexOfLanguageCode(languageCode: String): Int = languageCodesList.indexOf(languageCode) @@ -128,17 +128,17 @@ class LanguagesAdapter constructor( override fun performFiltering(constraint: CharSequence?): FilterResults { val filterResults = FilterResults() val temp: LinkedHashMap = LinkedHashMap() - if (constraint != null && language.localizedNames != null) { - val length: Int = language.localizedNames.size + if (constraint != null) { + val length: Int = language.getLocalizedNames().size var i = 0 while (i < length) { - val key: String = language.codes[i] - val value: String = language.localizedNames[i] + val key: String = language.getCodes()[i] + val value: String = language.getLocalizedNames()[i] val defaultlanguagecode = getIndexOfUserDefaultLocale(context) if (value.contains(constraint, true) || Locale(key) .getDisplayName( - Locale(language.codes[defaultlanguagecode]), + Locale(language.getCodes()[defaultlanguagecode]), ).contains(constraint, true) ) { temp[key] = value diff --git a/app/src/test/kotlin/fr/free/nrw/commons/LatLngTests.kt b/app/src/test/kotlin/fr/free/nrw/commons/LatLngTests.kt index d9ef4d6e8..3b208b5c1 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/LatLngTests.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/LatLngTests.kt @@ -62,5 +62,5 @@ class LatLngTests { private fun assertPrettyCoordinateString( expected: String, place: LatLng, - ) = assertEquals(expected, place.prettyCoordinateString) + ) = assertEquals(expected, place.getPrettyCoordinateString()) } diff --git a/app/src/test/kotlin/fr/free/nrw/commons/media/MediaDetailFragmentUnitTests.kt b/app/src/test/kotlin/fr/free/nrw/commons/media/MediaDetailFragmentUnitTests.kt index ea1d3402d..9f73d2b81 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/media/MediaDetailFragmentUnitTests.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/media/MediaDetailFragmentUnitTests.kt @@ -248,11 +248,11 @@ class MediaDetailFragmentUnitTests { @Throws(Exception::class) fun testOnUpdateCoordinatesClickedCurrentLocationNull() { `when`(media.coordinates).thenReturn(null) - `when`(locationManager.lastLocation).thenReturn(null) + `when`(locationManager.getLastLocation()).thenReturn(null) `when`(applicationKvStore.getString(lastLocation)).thenReturn("37.773972,-122.431297") fragment.onUpdateCoordinatesClicked() Mockito.verify(media, Mockito.times(1)).coordinates - Mockito.verify(locationManager, Mockito.times(1)).lastLocation + Mockito.verify(locationManager, Mockito.times(1)).getLastLocation() val shadowActivity: ShadowActivity = shadowOf(activity) val startedIntent = shadowActivity.nextStartedActivity val shadowIntent: ShadowIntent = shadowOf(startedIntent) @@ -276,11 +276,11 @@ class MediaDetailFragmentUnitTests { @Throws(Exception::class) fun testOnUpdateCoordinatesClickedCurrentLocationNotNull() { `when`(media.coordinates).thenReturn(null) - `when`(locationManager.lastLocation).thenReturn(LatLng(-0.000001, -0.999999, 0f)) + `when`(locationManager.getLastLocation()).thenReturn(LatLng(-0.000001, -0.999999, 0f)) `when`(applicationKvStore.getString(lastLocation)).thenReturn("37.773972,-122.431297") fragment.onUpdateCoordinatesClicked() - Mockito.verify(locationManager, Mockito.times(3)).lastLocation + Mockito.verify(locationManager, Mockito.times(3)).getLastLocation() val shadowActivity: ShadowActivity = shadowOf(activity) val startedIntent = shadowActivity.nextStartedActivity val shadowIntent: ShadowIntent = shadowOf(startedIntent) diff --git a/app/src/test/kotlin/fr/free/nrw/commons/upload/LanguagesAdapterTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/upload/LanguagesAdapterTest.kt index 801d4e900..f272a8288 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/upload/LanguagesAdapterTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/upload/LanguagesAdapterTest.kt @@ -54,8 +54,8 @@ class LanguagesAdapterTest { .from(context) .inflate(R.layout.row_item_languages_spinner, null) as View - languageNamesList = language.localizedNames - languageCodesList = language.codes + languageNamesList = language.getLocalizedNames() + languageCodesList = language.getCodes() languagesAdapter = LanguagesAdapter(context, selectedLanguages) } @@ -124,12 +124,12 @@ class LanguagesAdapterTest { var i = 0 var s = 0 while (i < length) { - val key: String = language.codes[i] - val value: String = language.localizedNames[i] + val key: String = language.getCodes()[i] + val value: String = language.getLocalizedNames()[i] if (value.contains(constraint, true) || Locale(key) .getDisplayName( - Locale(language.codes[defaultlanguagecode!!]), + Locale(language.getCodes()[defaultlanguagecode!!]), ).contains(constraint, true) ) { s++