From 3917981b0397d7db8681d11d68b264f94b8353d9 Mon Sep 17 00:00:00 2001 From: MHShetty Date: Wed, 30 Apr 2025 23:17:29 +0530 Subject: [PATCH 1/9] Add UI for mock location setting --- app/src/main/res/layout/more_settings.xml | 112 ++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/app/src/main/res/layout/more_settings.xml b/app/src/main/res/layout/more_settings.xml index c3b8117f2..847540b90 100644 --- a/app/src/main/res/layout/more_settings.xml +++ b/app/src/main/res/layout/more_settings.xml @@ -165,6 +165,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Date: Fri, 9 May 2025 03:13:04 +0530 Subject: [PATCH 2/9] Rename location.xml to more appropriate location_toggle.xml --- app/src/main/res/drawable/location_toggle.xml | 35 +++++++++++++++++++ app/src/main/res/layout/settings.xml | 2 +- 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 app/src/main/res/drawable/location_toggle.xml diff --git a/app/src/main/res/drawable/location_toggle.xml b/app/src/main/res/drawable/location_toggle.xml new file mode 100644 index 000000000..e3446a9a0 --- /dev/null +++ b/app/src/main/res/drawable/location_toggle.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/settings.xml b/app/src/main/res/layout/settings.xml index 2a0671f6e..80be1063b 100644 --- a/app/src/main/res/layout/settings.xml +++ b/app/src/main/res/layout/settings.xml @@ -48,7 +48,7 @@ android:textOff="" android:checked="false" android:layout_gravity="center" - android:background="@drawable/location" + android:background="@drawable/location_toggle" android:text="@string/toggle_button"/> From 5f9c8d5a87c38face938a6cf46542a0931c42d7c Mon Sep 17 00:00:00 2001 From: MHShetty Date: Fri, 9 May 2025 03:13:23 +0530 Subject: [PATCH 3/9] Add simple location icon (as image resource) --- app/src/main/res/drawable/location.xml | 47 +++++++------------------- 1 file changed, 12 insertions(+), 35 deletions(-) diff --git a/app/src/main/res/drawable/location.xml b/app/src/main/res/drawable/location.xml index e3446a9a0..9ddb83997 100644 --- a/app/src/main/res/drawable/location.xml +++ b/app/src/main/res/drawable/location.xml @@ -1,35 +1,12 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + From 505828d53304d173ed63761dcd854d48e8280d5b Mon Sep 17 00:00:00 2001 From: MHShetty Date: Fri, 9 May 2025 03:26:56 +0530 Subject: [PATCH 4/9] Modify NumInputFilter to NumLimitFilter (to correctly support float and signed assuming input type is correctly set for the filtered field) --- .../{NumInputFilter.kt => NumLimitFilter.kt} | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) rename app/src/main/java/app/grapheneos/camera/{NumInputFilter.kt => NumLimitFilter.kt} (56%) diff --git a/app/src/main/java/app/grapheneos/camera/NumInputFilter.kt b/app/src/main/java/app/grapheneos/camera/NumLimitFilter.kt similarity index 56% rename from app/src/main/java/app/grapheneos/camera/NumInputFilter.kt rename to app/src/main/java/app/grapheneos/camera/NumLimitFilter.kt index 793f9bb6e..2e49e1d69 100644 --- a/app/src/main/java/app/grapheneos/camera/NumInputFilter.kt +++ b/app/src/main/java/app/grapheneos/camera/NumLimitFilter.kt @@ -2,9 +2,15 @@ package app.grapheneos.camera import android.text.InputFilter import android.text.Spanned -import app.grapheneos.camera.ui.activities.MoreSettings -class NumInputFilter(private val settings: MoreSettings) : InputFilter { +// Ensures that the field is within the min/max limits for a number type input field +class NumLimitFilter( + val min: Float, + val max: Float, + val onOutOfRange: () -> Unit, +) : InputFilter { + + constructor(min: Int, max: Int, onOutOfRange: () -> Unit) : this(min.toFloat(), max.toFloat(), onOutOfRange) override fun filter( source: CharSequence, @@ -18,25 +24,20 @@ class NumInputFilter(private val settings: MoreSettings) : InputFilter { val input = (dest.subSequence(0, dstart).toString() + source + dest.subSequence( dend, dest.length - )).toInt() + )).toFloat() if (isInRange(input)) { return null } else { - settings.showMessage(settings.getString( - R.string.photo_quality_number_limit, min, max)) + this.onOutOfRange() } } catch (e: NumberFormatException) { e.printStackTrace() + return null } return "" } - private fun isInRange(value: Int): Boolean { - return value in min..max - } - - companion object { - const val min = 1 - const val max = 100 + private fun isInRange(value: Float): Boolean { + return value in this.min..this.max } } From e8c3eec7918778e7eb104d9b374ce39d7918ee26 Mon Sep 17 00:00:00 2001 From: MHShetty Date: Fri, 9 May 2025 03:27:54 +0530 Subject: [PATCH 5/9] Add code for mock location functionality --- .../java/app/grapheneos/camera/CamConfig.kt | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/app/src/main/java/app/grapheneos/camera/CamConfig.kt b/app/src/main/java/app/grapheneos/camera/CamConfig.kt index 128205f54..b409ebc22 100644 --- a/app/src/main/java/app/grapheneos/camera/CamConfig.kt +++ b/app/src/main/java/app/grapheneos/camera/CamConfig.kt @@ -111,6 +111,11 @@ class CamConfig(private val mActivity: MainActivity) { const val ENABLE_ZSL = "enable_zsl" + const val MOCK_LOCATION_STATE = "mock_location_state" + + const val MOCK_LOCATION_LATITUDE = "mock_location_latitude" + const val MOCK_LOCATION_LONGITUDE = "mock_location_longitude" + // const val IMAGE_FILE_FORMAT = "image_quality" // const val VIDEO_FILE_FORMAT = "video_quality" } @@ -156,6 +161,11 @@ class CamConfig(private val mActivity: MainActivity) { const val ENABLE_ZSL = false + const val MOCK_LOCATION_STATE = false + + const val MOCK_LOCATION_LATITUDE = 0.0f + const val MOCK_LOCATION_LONGITUDE = 0.0f + // const val IMAGE_FILE_FORMAT = "" // const val VIDEO_FILE_FORMAT = "" } @@ -555,6 +565,48 @@ class CamConfig(private val mActivity: MainActivity) { editor.apply() } + var mockLocationEnabled: Boolean + get() { + return commonPref.getBoolean( + SettingValues.Key.MOCK_LOCATION_STATE, + SettingValues.Default.MOCK_LOCATION_STATE + ) + } + set(value) { + val editor = commonPref.edit() + editor.putBoolean( + SettingValues.Key.MOCK_LOCATION_STATE, + value + ) + editor.apply() + } + + var mockLocationLatitude: Float + get() { + return commonPref.getFloat( + SettingValues.Key.MOCK_LOCATION_LATITUDE, + SettingValues.Default.MOCK_LOCATION_LATITUDE + ) + } + set(value) { + val editor = commonPref.edit() + editor.putFloat(SettingValues.Key.MOCK_LOCATION_LATITUDE, value) + editor.apply() + } + + var mockLocationLongitude: Float + get() { + return commonPref.getFloat( + SettingValues.Key.MOCK_LOCATION_LONGITUDE, + SettingValues.Default.MOCK_LOCATION_LONGITUDE + ) + } + set(value) { + val editor = commonPref.edit() + editor.putFloat(SettingValues.Key.MOCK_LOCATION_LONGITUDE, value) + editor.apply() + } + val isZslSupported : Boolean by lazy { camera!!.cameraInfo.isZslSupported } From 9813146e37b0e77a1d5c5744f39ae2637dad8751 Mon Sep 17 00:00:00 2001 From: MHShetty Date: Fri, 9 May 2025 03:28:36 +0530 Subject: [PATCH 6/9] Add string resources (for mock location functionality) --- app/src/main/res/values/strings.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9aa6ae1eb..a1fc69863 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -193,4 +193,14 @@ The video\'s audio recording has been muted The video\'s audio recording has been unmuted + + Use Mock Location + When enabled uses below location over the location provided by device for geo-tagging + + ° + Latitude + Longitude + + Latitude can only be between %1$.1f and %2$.1f + Longitude can only be between %1$.1f and %2$.1f From 6a8db613e699c5936332756c36ab239f6d95c9c6 Mon Sep 17 00:00:00 2001 From: MHShetty Date: Fri, 9 May 2025 03:29:20 +0530 Subject: [PATCH 7/9] Fix snackbar not being visible when keyboard is present (in more settings screen/page) --- app/src/main/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 36985190b..14c2c1842 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -185,6 +185,7 @@ android:theme="@style/Theme.App" android:taskAffinity=".ui.activities.InAppGallery" android:excludeFromRecents="true" + android:windowSoftInputMode="adjustResize" android:exported="false"/> Date: Fri, 9 May 2025 03:29:44 +0530 Subject: [PATCH 8/9] Make mock location setting functional --- .../camera/ui/activities/MoreSettings.kt | 123 +++++++++++++++--- 1 file changed, 103 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/app/grapheneos/camera/ui/activities/MoreSettings.kt b/app/src/main/java/app/grapheneos/camera/ui/activities/MoreSettings.kt index 18b228fbf..e11adabb0 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/activities/MoreSettings.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/activities/MoreSettings.kt @@ -2,7 +2,6 @@ package app.grapheneos.camera.ui.activities import android.content.Context import android.content.Intent -import android.graphics.Color import android.graphics.Rect import android.os.Bundle import android.view.KeyEvent @@ -16,12 +15,11 @@ import android.widget.TextView import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity -import androidx.core.view.OnApplyWindowInsetsListener import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import app.grapheneos.camera.CamConfig import app.grapheneos.camera.CapturedItems -import app.grapheneos.camera.NumInputFilter +import app.grapheneos.camera.NumLimitFilter import app.grapheneos.camera.R import app.grapheneos.camera.databinding.MoreSettingsBinding import app.grapheneos.camera.util.storageLocationToUiString @@ -150,9 +148,99 @@ open class MoreSettings : AppCompatActivity(), TextView.OnEditorActionListener { pQField.setText(camConfig.photoQuality.toString()) } - pQField.filters = arrayOf(NumInputFilter(this)) + pQField.filters = arrayOf(NumLimitFilter( + min = PHOTO_QUALITY_MIN, + max = PHOTO_QUALITY_MAX, + onOutOfRange = { + showMessage(getString(R.string.photo_quality_number_limit, PHOTO_QUALITY_MIN, PHOTO_QUALITY_MAX)) + } + )) + + pQField.onFocusChangeListener = object: View.OnFocusChangeListener { + override fun onFocusChange(v: View, hasFocus: Boolean) { + if (!hasFocus) { + if (pQField.text.isEmpty()) { + camConfig.photoQuality = 0 + + showMessage( + getString(R.string.photo_quality_was_set_to_auto) + ) + } else { + try { + camConfig.photoQuality = + Integer.parseInt(pQField.text.toString()) + } catch (exception: Exception) { + camConfig.photoQuality = 0 + + } + } + } + } + } pQField.setOnEditorActionListener(this) + val latitudeField = binding.latitudeTextfield + latitudeField.setText(camConfig.mockLocationLatitude.toString()) + latitudeField.filters = arrayOf(NumLimitFilter( + min = LATITUDE_MIN, + max = LATITUDE_MAX, + onOutOfRange = { + showMessage(getString(R.string.latitude_number_limit, LATITUDE_MIN, LATITUDE_MAX)) + } + )) + latitudeField.onFocusChangeListener = object: View.OnFocusChangeListener { + override fun onFocusChange(v: View, hasFocus: Boolean) { + if (!hasFocus) { + if (latitudeField.text?.isEmpty() != false) { + latitudeField.setText(camConfig.mockLocationLatitude.toString()) + showMessage(getString(R.string.latitude_number_limit, LATITUDE_MIN, LATITUDE_MAX)) + } else { + try { + camConfig.mockLocationLatitude = latitudeField.text.toString().toFloat() + } catch (exception: Exception) { + camConfig.mockLocationLatitude = 0f + showMessage(getString(R.string.latitude_number_limit, LATITUDE_MIN, LATITUDE_MAX)) + + } + } + } + } + } + latitudeField.setOnEditorActionListener(this) + + val longitudeField = binding.longitudeTextfield + longitudeField.setText(camConfig.mockLocationLongitude.toString()) + longitudeField.filters = arrayOf(NumLimitFilter( + min = LONGITUDE_MIN.toFloat(), + max = LONGITUDE_MAX.toFloat(), + onOutOfRange = { + showMessage(getString(R.string.longitude_number_limit, LONGITUDE_MIN, LONGITUDE_MAX)) + } + )) + longitudeField.onFocusChangeListener = object: View.OnFocusChangeListener { + override fun onFocusChange(v: View, hasFocus: Boolean) { + if (!hasFocus) { + if (longitudeField.text?.isEmpty() != false) { + longitudeField.setText(camConfig.mockLocationLongitude.toString()) + showMessage(getString(R.string.longitude_number_limit, LONGITUDE_MIN, LONGITUDE_MAX)) + } else { + try { + camConfig.mockLocationLongitude = longitudeField.text.toString().toFloat() + } catch (exception: Exception) { + camConfig.mockLocationLongitude = 0f + showMessage(getString(R.string.longitude_number_limit, LONGITUDE_MIN, LONGITUDE_MAX)) + } + } + } + } + } + longitudeField.setOnEditorActionListener(this) + + binding.mockLocationSettingSwitch.isChecked = camConfig.mockLocationEnabled + binding.mockLocationSettingSwitch.setOnClickListener { + camConfig.mockLocationEnabled = !camConfig.mockLocationEnabled + } + iFField = binding.imageFormatSettingField iFField.setOnEditorActionListener(this) @@ -267,7 +355,7 @@ open class MoreSettings : AppCompatActivity(), TextView.OnEditorActionListener { v.getGlobalVisibleRect(outRect) if (!outRect.contains(event.rawX.toInt(), event.rawY.toInt())) { clearFocus() - dumpData() +// dumpData() } } } @@ -287,24 +375,10 @@ open class MoreSettings : AppCompatActivity(), TextView.OnEditorActionListener { private fun dumpData() { // Dump state of photo quality - if (pQField.text.isEmpty()) { - camConfig.photoQuality = 0 - showMessage( - getString(R.string.photo_quality_was_set_to_auto) - ) - } else { - try { - - camConfig.photoQuality = - Integer.parseInt(pQField.text.toString()) - } catch (exception: Exception) { + // Dump state of image format - camConfig.photoQuality = 0 - - } - } // // Dump state of image format // camConfig.imageFormat = iFField.text.toString() @@ -332,6 +406,15 @@ open class MoreSettings : AppCompatActivity(), TextView.OnEditorActionListener { } companion object { + const val PHOTO_QUALITY_MIN = 1 + const val PHOTO_QUALITY_MAX = 100 + + const val LATITUDE_MIN = -90f + const val LATITUDE_MAX = 90f + + const val LONGITUDE_MIN = -180f + const val LONGITUDE_MAX = 180f + private var camConfigId = 0L private var staticCamConfig: CamConfig? = null From 8c684f67fb609e56ebef8c4515e75d306a92431a Mon Sep 17 00:00:00 2001 From: MHShetty Date: Fri, 9 May 2025 03:30:02 +0530 Subject: [PATCH 9/9] Implement mock location functionality --- .../app/grapheneos/camera/capturer/ImageCapturer.kt | 10 +++++++++- .../app/grapheneos/camera/capturer/VideoCapturer.kt | 10 +++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/grapheneos/camera/capturer/ImageCapturer.kt b/app/src/main/java/app/grapheneos/camera/capturer/ImageCapturer.kt index 17d947264..2d0b12b3c 100644 --- a/app/src/main/java/app/grapheneos/camera/capturer/ImageCapturer.kt +++ b/app/src/main/java/app/grapheneos/camera/capturer/ImageCapturer.kt @@ -7,6 +7,7 @@ import android.app.NotificationManager import android.content.ClipData import android.content.ClipboardManager import android.graphics.Bitmap +import android.location.Location import android.os.Build import android.util.Log import android.view.View @@ -77,7 +78,14 @@ class ImageCapturer(val mActivity: MainActivity) { && camConfig.saveImageAsPreviewed if (camConfig.requireLocation) { - val location = (mActivity.applicationContext as App).getLocation() + val location = if (camConfig.mockLocationEnabled) { + val loc = Location("") + loc.latitude = camConfig.mockLocationLatitude.toDouble() + loc.longitude = camConfig.mockLocationLongitude.toDouble() + loc + } else { + (mActivity.applicationContext as App).getLocation() + } if (location == null) { mActivity.showMessage(R.string.location_unavailable) } else { diff --git a/app/src/main/java/app/grapheneos/camera/capturer/VideoCapturer.kt b/app/src/main/java/app/grapheneos/camera/capturer/VideoCapturer.kt index 6a505d0ca..8dc5aeb3c 100644 --- a/app/src/main/java/app/grapheneos/camera/capturer/VideoCapturer.kt +++ b/app/src/main/java/app/grapheneos/camera/capturer/VideoCapturer.kt @@ -134,7 +134,15 @@ class VideoCapturer(private val mActivity: MainActivity) { var location: Location? = null if (camConfig.requireLocation) { - location = (mActivity.applicationContext as App).getLocation() + val location = if (camConfig.mockLocationEnabled) { + val loc = Location("") + loc.latitude = camConfig.mockLocationLatitude.toDouble() + loc.longitude = camConfig.mockLocationLongitude.toDouble() + loc + } else { + (mActivity.applicationContext as App).getLocation() + } + if (location == null) { mActivity.showMessage(R.string.location_unavailable) }