From a95a548e1fd8250b1fdc4520ad53c7a5e6c96168 Mon Sep 17 00:00:00 2001 From: partisan Date: Sat, 31 May 2025 17:22:50 +0000 Subject: [PATCH 1/6] Update fastlane/metadata/android/en-US/full_description.txt --- fastlane/metadata/android/en-US/full_description.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt index 491ea81..0d1ab15 100644 --- a/fastlane/metadata/android/en-US/full_description.txt +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -48,5 +48,8 @@ Redirect calls to Signal, Telegram, Threema, or WhatsApp. Currently all of the permissions are required. +--- + **License:** GPL-3.0 + Free and open source From b981f41955679f1961d81951359683aa2d461317 Mon Sep 17 00:00:00 2001 From: partisan Date: Sat, 31 May 2025 20:01:50 +0000 Subject: [PATCH 2/6] Update fastlane/metadata/android/en-US/full_description.txt --- .../metadata/android/en-US/full_description.txt | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt index 0d1ab15..63a13d2 100644 --- a/fastlane/metadata/android/en-US/full_description.txt +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -5,43 +5,28 @@ Redirect calls to Signal, Telegram, Threema, or WhatsApp. **Features:** - Material You design - - Popup with cancel option - - Extensive settings panel: - - Toggle per-service support - - Redirection only on Wi-Fi/Data - - Allowlist specific contacts - - Change per-service priority - - Customize popup position, animation, and duration - - etc **Supports:** - Signal - - Telegram - - Threema - - WhatsApp **Permissions required:** - `CALL_PHONE` - initiate calls via messenger - - `READ_CONTACTS` - check contact compatibility - - `READ_PHONE_NUMBERS` - detect outgoing call - - `SYSTEM_ALERT_WINDOW` - show popup overlay - - `ACCESS_NETWORK_STATE` - check connectivity - `INTERNET` - check connectivity and verify donates From 9750daaaa2d0ffc1f862206bc0beb724f3ec6768 Mon Sep 17 00:00:00 2001 From: partisan Date: Sun, 1 Jun 2025 08:57:23 +0000 Subject: [PATCH 3/6] Update fastlane/metadata/android/en-US/full_description.txt --- fastlane/metadata/android/en-US/full_description.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt index 63a13d2..e63bda7 100644 --- a/fastlane/metadata/android/en-US/full_description.txt +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -28,7 +28,6 @@ Redirect calls to Signal, Telegram, Threema, or WhatsApp. - `READ_PHONE_NUMBERS` - detect outgoing call - `SYSTEM_ALERT_WINDOW` - show popup overlay - `ACCESS_NETWORK_STATE` - check connectivity - - `INTERNET` - check connectivity and verify donates Currently all of the permissions are required. From 09de3785b9d40b706cc4ec01c4d0d0bd6d09ac36 Mon Sep 17 00:00:00 2001 From: partisan Date: Wed, 4 Jun 2025 22:52:37 +0200 Subject: [PATCH 4/6] Pulse no longer shows popup if disallowed by system --- README.md | 16 ++++++++-------- app/build.gradle | 4 ++-- .../weforge/xyz/pulse/CallRedirectionService.kt | 10 +++------- .../partisan/weforge/xyz/pulse/MainFragment.kt | 4 ++-- .../partisan/weforge/xyz/pulse/Preferences.kt | 4 ++-- 5 files changed, 17 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 5bcb4a7..4923f71 100644 --- a/README.md +++ b/README.md @@ -83,24 +83,24 @@ In the “Add App” screen: 1. Add the following URL: https://weforge.xyz/partisan/Pulse 2. In **Override Source**, select **Forgejo (Codeberg)** -3. Tap the “Add” button at the very top, and you’re done! +3. Tap the “Add” button at the very top, and you're done! ## Install directly Go to the [Releases page](https://weforge.xyz/partisan/Pulse/releases) and download the latest file with the following format: `app-release.apk`. -Install it, and you’re done! +Install it, and you're done! _Please note that when installing directly, the app will not receive automatic updates._ # Permissions -- `ACCESS_NETWORK_STATE` – check connectivity -- `CALL_PHONE` – make a call via messenger -- `READ_CONTACTS` – check if contact has a messenger -- `READ_PHONE_NUMBERS` – detect outgoing call -- `SYSTEM_ALERT_WINDOW` – show redirecting popup and launch from background -- `INTERNET` – check connectivity and verify donates +- `ACCESS_NETWORK_STATE` - check connectivity +- `CALL_PHONE` - make a call via messenger +- `READ_CONTACTS` - check if contact has a messenger +- `READ_PHONE_NUMBERS` - detect outgoing call +- `SYSTEM_ALERT_WINDOW` - show redirecting popup and launch from background +- `INTERNET` - check connectivity and verify donates Currently all of the permissions are required. diff --git a/app/build.gradle b/app/build.gradle index 7b72933..d0bc59d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -11,8 +11,8 @@ android { applicationId = "partisan.weforge.xyz.pulse" minSdk = 29 targetSdk = 34 - versionCode = 16 - versionName = "2.0.2" + versionCode = 17 + versionName = "2.0.3" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app/src/main/java/partisan/weforge/xyz/pulse/CallRedirectionService.kt b/app/src/main/java/partisan/weforge/xyz/pulse/CallRedirectionService.kt index 5eae072..179014c 100644 --- a/app/src/main/java/partisan/weforge/xyz/pulse/CallRedirectionService.kt +++ b/app/src/main/java/partisan/weforge/xyz/pulse/CallRedirectionService.kt @@ -94,12 +94,6 @@ class CallRedirectionService : CallRedirectionService() { return } - if (!allowInteractiveResponse) { - Log.d("Redirection", "Aborting: interactive response not allowed by system") - placeCallUnmodified() - return - } - if (prefs.redirectIfRoaming && !isOutsideHomeCountry()) { Log.d("Redirection", "Aborting: redirect only while roaming, but we're inside home country") placeCallUnmodified() @@ -146,7 +140,9 @@ class CallRedirectionService : CallRedirectionService() { Log.d("Redirection", "Redirecting call to: ${record.mimetype} → ${record.uri}") - if (prefs.popupEnabled) { + Log.d("Redirection", "Popup ${if (allowInteractiveResponse) "allowed" else "not allowed"} by system; ${if (prefs.popupEnabled) "enabled" else "disabled"} in prefs") + + if (allowInteractiveResponse && prefs.popupEnabled) { window.show(record.uri, MIMETYPE_TO_DST_NAME[record.mimetype] ?: return) } else { window.call(record.uri) diff --git a/app/src/main/java/partisan/weforge/xyz/pulse/MainFragment.kt b/app/src/main/java/partisan/weforge/xyz/pulse/MainFragment.kt index 72cc529..ae75e24 100644 --- a/app/src/main/java/partisan/weforge/xyz/pulse/MainFragment.kt +++ b/app/src/main/java/partisan/weforge/xyz/pulse/MainFragment.kt @@ -51,8 +51,8 @@ class MainFragment : Fragment() { emitter = Emitter(duration = 100, TimeUnit.MILLISECONDS) .perSecond(100), - speed = 30f, - maxSpeed = 40f, + speed = 25f, + maxSpeed = 30f, damping = 0.85f, spread = 360, position = Position.Relative(0.5, 0.5) diff --git a/app/src/main/java/partisan/weforge/xyz/pulse/Preferences.kt b/app/src/main/java/partisan/weforge/xyz/pulse/Preferences.kt index 753fdb5..2552cf6 100644 --- a/app/src/main/java/partisan/weforge/xyz/pulse/Preferences.kt +++ b/app/src/main/java/partisan/weforge/xyz/pulse/Preferences.kt @@ -38,8 +38,8 @@ class Preferences(private val context: Context) { val isEnabled: Boolean get() = isServiceEnabledByUser && hasGeneralPermissions(context) && - hasDrawOverlays(context) && - hasCallRedirectionRole(context) + hasCallRedirectionRole(context) && + (popupEnabled.not() || hasDrawOverlays(context)) enum class PopupEffect { NONE, FADE, SCALE, BOUNCE, FLOP, MATRIX, SLIDE_SNAP, GAMER_MODE, RANDOM From fc3f6c58ce217283cc2b9dea605f188286a90a28 Mon Sep 17 00:00:00 2001 From: partisan Date: Thu, 5 Jun 2025 09:11:05 +0200 Subject: [PATCH 5/6] Anonymized phone number logging --- .../xyz/pulse/CallRedirectionService.kt | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/partisan/weforge/xyz/pulse/CallRedirectionService.kt b/app/src/main/java/partisan/weforge/xyz/pulse/CallRedirectionService.kt index 179014c..a8643dc 100644 --- a/app/src/main/java/partisan/weforge/xyz/pulse/CallRedirectionService.kt +++ b/app/src/main/java/partisan/weforge/xyz/pulse/CallRedirectionService.kt @@ -101,16 +101,17 @@ class CallRedirectionService : CallRedirectionService() { } val phoneNumber = handle.schemeSpecificPart - Log.d("Redirection", "Resolved phone number: $phoneNumber") + val numberAlias = getAnonymizedNumberAlias(phoneNumber) + Log.d("Redirection", "Processing call to: $numberAlias") if (prefs.redirectInternationalOnly && !isInternationalNumber(phoneNumber)) { - Log.d("Redirection", "Aborting: number is not international and pref requires it") + Log.d("Redirection", "Aborting: number $numberAlias is not international and pref requires it") placeCallUnmodified() return } if (prefs.isBlacklistEnabled && !prefs.isContactWhitelisted(phoneNumber)) { - Log.d("Redirection", "Aborting: number is not in whitelist while blacklist is enabled") + Log.d("Redirection", "Aborting: number $numberAlias is not in whitelist while blacklist is enabled") placeCallUnmodified() return } @@ -118,7 +119,7 @@ class CallRedirectionService : CallRedirectionService() { val records: Array try { records = getRecordsFromPhoneNumber(phoneNumber) - Log.d("Redirection", "Found ${records.size} raw records for contact") + Log.d("Redirection", "Found ${records.size} raw redirect apps for number $numberAlias") } catch (exc: SecurityException) { Log.w("Redirection", "SecurityException during record fetch", exc) placeCallUnmodified() @@ -129,11 +130,11 @@ class CallRedirectionService : CallRedirectionService() { .filter { prefs.isServiceEnabled(it.mimetype) } .sortedBy { prefs.getServicePriority(it.mimetype) } - Log.d("Redirection", "Filtered to ${enabledRecords.size} enabled records") + Log.d("Redirection", "Filtered to ${enabledRecords.size} enabled redirect apps") val record = enabledRecords.firstOrNull() if (record == null) { - Log.d("Redirection", "Aborting: no suitable record found for redirection") + Log.d("Redirection", "Aborting: no suitable redirect apps found for number $numberAlias") placeCallUnmodified() return } @@ -199,6 +200,11 @@ class CallRedirectionService : CallRedirectionService() { return results.toTypedArray() } + private fun getAnonymizedAlias(number: String): String { + val contactId = getContactIdByPhoneNumber(number) + return if (contactId != null) "#$contactId" else "#???" + } + private fun isInternationalNumber(phoneNumber: String): Boolean { val telephony = getSystemService(TelephonyManager::class.java) ?: return true val simCountryIso = telephony.simCountryIso?.lowercase() ?: return true From 079bbb20f51e1e9247b22bc9a0af22e6fe6cd1cb Mon Sep 17 00:00:00 2001 From: partisan Date: Thu, 5 Jun 2025 09:36:17 +0200 Subject: [PATCH 6/6] Store anonymized call numbers in shared preferences --- .../xyz/pulse/CallRedirectionService.kt | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/partisan/weforge/xyz/pulse/CallRedirectionService.kt b/app/src/main/java/partisan/weforge/xyz/pulse/CallRedirectionService.kt index a8643dc..66666f6 100644 --- a/app/src/main/java/partisan/weforge/xyz/pulse/CallRedirectionService.kt +++ b/app/src/main/java/partisan/weforge/xyz/pulse/CallRedirectionService.kt @@ -61,7 +61,9 @@ class CallRedirectionService : CallRedirectionService() { initialPhoneAccount: PhoneAccountHandle, allowInteractiveResponse: Boolean, ) { - Log.d("Redirection", "onPlaceCall triggered: uri=$handle, interactive=$allowInteractiveResponse") + val phoneNumber = handle.schemeSpecificPart + val numberAlias = getAnonymizedAlias(phoneNumber) + Log.d("Redirection", "onPlaceCall triggered: alias=$numberAlias, interactive=$allowInteractiveResponse") val capabilities = connectivityManager ?.getNetworkCapabilities(connectivityManager?.activeNetwork) @@ -100,10 +102,6 @@ class CallRedirectionService : CallRedirectionService() { return } - val phoneNumber = handle.schemeSpecificPart - val numberAlias = getAnonymizedNumberAlias(phoneNumber) - Log.d("Redirection", "Processing call to: $numberAlias") - if (prefs.redirectInternationalOnly && !isInternationalNumber(phoneNumber)) { Log.d("Redirection", "Aborting: number $numberAlias is not international and pref requires it") placeCallUnmodified() @@ -115,6 +113,8 @@ class CallRedirectionService : CallRedirectionService() { placeCallUnmodified() return } + + Log.d("Redirection", "Number $numberAlias is not in filters, processing redirection...") val records: Array try { @@ -201,8 +201,30 @@ class CallRedirectionService : CallRedirectionService() { } private fun getAnonymizedAlias(number: String): String { - val contactId = getContactIdByPhoneNumber(number) - return if (contactId != null) "#$contactId" else "#???" + val prefs = getSharedPreferences("anonymized_numbers", MODE_PRIVATE) + + // Return existing alias if already mapped + val existing = prefs.getString(number, null) + if (existing != null) return existing + + // Start from current counter + var counter = prefs.getInt("counter", 1) + var alias: String + + // Find the first unused alias (safety check) + while (true) { + alias = "#$counter" + if (!prefs.all.containsValue(alias)) break + counter++ + } + + // Store new alias and increment counter + prefs.edit() + .putString(number, alias) + .putInt("counter", counter + 1) + .apply() + + return alias } private fun isInternationalNumber(phoneNumber: String): Boolean {