Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: fix Brazil phone numbers before placing the call #289

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,7 @@ dependencies {
implementation(libs.autofit.text.view)
implementation(libs.kotlinx.serialization.json)
implementation(libs.eventbus)
}

testImplementation("junit:junit:4.13.2")
testImplementation("org.robolectric:robolectric:4.14")
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import org.fossify.commons.extensions.*
import org.fossify.commons.helpers.REQUEST_CODE_SET_DEFAULT_DIALER
import org.fossify.phone.R
import org.fossify.phone.extensions.getHandleToUse
import org.fossify.phone.helpers.fixInvalidNumbers

class DialerActivity : SimpleActivity() {
private var callNumber: Uri? = null
Expand Down Expand Up @@ -42,13 +43,15 @@ class DialerActivity : SimpleActivity() {
return
}


val actualCallNumber = fixInvalidNumbers(callNumber, this.getApplicationContext())
getHandleToUse(intent, callNumber.toString()) { handle ->
if (handle != null) {
Bundle().apply {
putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, handle)
putBoolean(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, false)
putBoolean(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, false)
telecomManager.placeCall(callNumber, this)
telecomManager.placeCall(actualCallNumber, this)
}
}
finish()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ class FavoritesFragment(context: Context, attributeSet: AttributeSet) : MyViewPa
}

fun columnCountChanged() {
(binding.fragmentList.layoutManager as? MyGridLayoutManager)?.spanCount = context!!.config.contactsGridColumnCount
(binding.fragmentList.layoutManager as? MyGridLayoutManager)?.spanCount =
context!!.config.contactsGridColumnCount
binding.fragmentList.adapter?.apply {
notifyItemRangeChanged(0, allContacts.size)
}
Expand Down
63 changes: 63 additions & 0 deletions app/src/main/kotlin/org/fossify/phone/helpers/CallNumberHelper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package org.fossify.phone.helpers

import android.net.Uri
import android.util.Log
import android.content.Context
import android.telephony.TelephonyManager
import org.fossify.phone.helpers.countries.fixInvalidNumbersBrazil

// Need to adapt to support 3 digit countries, or just use libphonenumber at that point
private val COUNTRY_CODE_PATTERN = Regex("""^\+(\d{1,2})""")
private val SUPPORTED_COUNTRY_CODES = listOf("55")
private val SUPPORTED_COUNTRY_ISOS = listOf("BR")
private val SUPPORTED_COUNTRY_FUNS = listOf(::fixInvalidNumbersBrazil)

/**
* Fix invalid numbers based on their country code
*/
@Suppress("TooGenericExceptionCaught")
fun fixInvalidNumbers(tel: Uri?, context: Context): Uri? {
if (tel != null) {
try {
return getCountryFun(tel, context)(tel)
} catch (e: Exception) {
Log.e("CallNumberHelper", "Error fixing invalid number: $e")
}
}
return tel
}

private fun identity(uri: Uri): Uri {
return uri
}

/**
* Get the transformation function from the country code of the dialed phone
* number, or infer from SIM if not an international call. If the country
* cannot be determined or is not supported, return the identity function.
*/
private fun getCountryFun(tel: Uri, context: Context): (Uri) -> Uri {
var idx: Int

// Try to match by country code
val decodedTel = tel.getSchemeSpecificPart()
val countryCodeMatch = COUNTRY_CODE_PATTERN.find(decodedTel)
if (countryCodeMatch != null) {
val countryCode = countryCodeMatch.groupValues.get(1)
Log.d("CallNumberHelper", "Code: $countryCode")
idx = SUPPORTED_COUNTRY_CODES.indexOf(countryCode)
} else {
// No country code in the number, so it must be a local call,
// so we can use the SIM country code
val tm = context.getSystemService(TelephonyManager::class.java)
val countryIso = tm.getSimCountryIso().uppercase()
Log.d("CallNumberHelper", "Iso: $countryIso")
idx = SUPPORTED_COUNTRY_ISOS.indexOf(countryIso)
}

if (idx >= 0) {
return SUPPORTED_COUNTRY_FUNS[idx]
}

return ::identity
}
49 changes: 49 additions & 0 deletions app/src/main/kotlin/org/fossify/phone/helpers/countries/Brazil.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.fossify.phone.helpers.countries

import android.net.Uri
import android.util.Log

// brazil number constants
private const val BR_LOCAL_AREA_LEN = 8
private const val BR_LAND_PREFIX_MAX = 5
private const val BR_CEL_PREFIX_CHAR = '9'

/**
* Returns the start index of the local area number
*/
private fun getLocalAreaIndex(input: String): Int {
var digitCount = 0
for (i in input.length - 1 downTo 0) {
if (input[i].isDigit()) {
digitCount++
if (digitCount == BR_LOCAL_AREA_LEN) {
return i
}
}
}
return -1
}

private fun prevDigit(input: String, index: Int): Char {
for (i in index - 1 downTo 0) {
if (input[i].isDigit()) {
return input[i]
}
}
return ' '
}

fun fixInvalidNumbersBrazil(tel: Uri): Uri {
val decodedTel = tel.getSchemeSpecificPart()
Log.d("CallNumberHelper", "Brazil phone: $decodedTel")
val idx = getLocalAreaIndex(decodedTel)
if (idx >= 0 && decodedTel.get(idx).digitToInt() > BR_LAND_PREFIX_MAX) {
val prev = prevDigit(decodedTel, idx)
if (prev != BR_CEL_PREFIX_CHAR) {
val fixedNum = decodedTel.substring(0, idx) + BR_CEL_PREFIX_CHAR + decodedTel.substring(idx)
Log.d("CallNumberHelper", "Brazil fixed phone: $fixedNum")
return Uri.parse("tel:$fixedNum")
}
}
return tel
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import android.content.Context
import android.net.Uri
import android.telephony.TelephonyManager
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.fossify.phone.helpers.fixInvalidNumbers
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
import org.robolectric.annotation.Config
import org.robolectric.Shadows
import org.robolectric.shadows.ShadowTelephonyManager

@RunWith(RobolectricTestRunner::class)
@Config(sdk = [34])
class CallNumberHelperTestBrazil {

private lateinit var context: Context
private lateinit var telephonyManager: TelephonyManager
private lateinit var shadowTelephonyManager: ShadowTelephonyManager

@Before
fun setUp() {
context = RuntimeEnvironment.getApplication()
telephonyManager = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
shadowTelephonyManager = Shadows.shadowOf(telephonyManager)
shadowTelephonyManager.setSimCountryIso("BR")
}

// Country and Area code

@Test
fun testInvalidWithCountryAndAreaCode() {
val inputNumber = Uri.parse("tel:+551190000000")
val expectedNumber = Uri.parse("tel:+5511990000000")
val outputNumber = fixInvalidNumbers(inputNumber, context )
assertEquals(expectedNumber, outputNumber)
}

@Test
fun testInvalidWithCountryAndAreaCodeAndNonDigits() {
val inputNumber = Uri.parse("tel:+55 (11) 9000-0000")
val expectedNumber = Uri.parse("tel:+55 (11) 99000-0000")
val outputNumber = fixInvalidNumbers(inputNumber, context )
assertEquals(expectedNumber, outputNumber)
}

@Test
fun testValidWithCountryAndAreaCode() {
val inputNumber = Uri.parse("tel:+5511990000000")
val expectedNumber = Uri.parse("tel:+5511990000000")
val outputNumber = fixInvalidNumbers(inputNumber, context)
assertEquals(expectedNumber, outputNumber)
}

@Test
fun testLandWithCountryAndAreaCode() {
val inputNumber = Uri.parse("tel:+551130000000")
val expectedNumber = Uri.parse("tel:+551130000000")
val outputNumber = fixInvalidNumbers(inputNumber, context)
assertEquals(expectedNumber, outputNumber)
}

@Test
fun testUnsupportedWithCountryAndAreaCode() {
val inputNumber = Uri.parse("tel:+190000000")
val expectedNumber = Uri.parse("tel:+190000000")
val outputNumber = fixInvalidNumbers(inputNumber, context)
assertEquals(expectedNumber, outputNumber)
}

// Carrier and Area code

@Test
fun testInvalidWithCarrierAndAreaCode() {
val inputNumber = Uri.parse("tel:0211190000000")
val expectedNumber = Uri.parse("tel:02111990000000")
val outputNumber = fixInvalidNumbers(inputNumber, context)
assertEquals(expectedNumber, outputNumber)
}

@Test
fun testInvalidWithCarrierAndAreaCodeAndNonDigits() {
val inputNumber = Uri.parse("tel:021 (11) 9000-0000")
val expectedNumber = Uri.parse("tel:021 (11) 99000-0000")
val outputNumber = fixInvalidNumbers(inputNumber, context)
assertEquals(expectedNumber, outputNumber)
}

@Test
fun testValidWithCarrierAndAreaCode() {
val inputNumber = Uri.parse("tel:02111990000000")
val expectedNumber = Uri.parse("tel:02111990000000")
val outputNumber = fixInvalidNumbers(inputNumber, context)
assertEquals(expectedNumber, outputNumber)
}

@Test
fun testLandWithCarrierAndAreaCode() {
val inputNumber = Uri.parse("tel:0211130000000")
val expectedNumber = Uri.parse("tel:0211130000000")
val outputNumber = fixInvalidNumbers(inputNumber, context)
assertEquals(expectedNumber, outputNumber)
}

// Local Area number

@Test
fun testInvalidLocalAreaNumber() {
val inputNumber = Uri.parse("tel:90000000")
val expectedNumber = Uri.parse("tel:990000000")
val outputNumber = fixInvalidNumbers(inputNumber, context)
assertEquals(expectedNumber, outputNumber)
}

@Test
fun testInvalidLocalAreaNumberAndNonDigits() {
val inputNumber = Uri.parse("tel:9000-0000")
val expectedNumber = Uri.parse("tel:99000-0000")
val outputNumber = fixInvalidNumbers(inputNumber, context)
assertEquals(expectedNumber, outputNumber)
}

@Test
fun testValidLocalAreaNumber() {
val inputNumber = Uri.parse("tel:990000000")
val expectedNumber = Uri.parse("tel:990000000")
val outputNumber = fixInvalidNumbers(inputNumber, context)
assertEquals(expectedNumber, outputNumber)
}

@Test
fun testlLandLocalAreaNumber() {
val inputNumber = Uri.parse("tel:30000000")
val expectedNumber = Uri.parse("tel:30000000")
val outputNumber = fixInvalidNumbers(inputNumber, context)
assertEquals(expectedNumber, outputNumber)
}

@Test
fun testUnsupportedLocalAreaNumber() {
shadowTelephonyManager.setSimCountryIso("US")
val inputNumber = Uri.parse("tel:90000000")
val expectedNumber = Uri.parse("tel:90000000")
val outputNumber = fixInvalidNumbers(inputNumber, context)
assertEquals(expectedNumber, outputNumber)
}

}