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..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) @@ -94,37 +96,30 @@ 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() return } - val phoneNumber = handle.schemeSpecificPart - Log.d("Redirection", "Resolved phone number: $phoneNumber") - 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 } + + Log.d("Redirection", "Number $numberAlias is not in filters, processing redirection...") 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() @@ -135,18 +130,20 @@ 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 } 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) @@ -203,6 +200,33 @@ class CallRedirectionService : CallRedirectionService() { return results.toTypedArray() } + private fun getAnonymizedAlias(number: String): String { + 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 { val telephony = getSystemService(TelephonyManager::class.java) ?: return true val simCountryIso = telephony.simCountryIso?.lowercase() ?: return true 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