Skip to content

Commit

Permalink
feat(draft): fix invalid numbers before placing the call
Browse files Browse the repository at this point in the history
This commit is a non-working draft, some cases fail and the setting is
not working.
  • Loading branch information
afarah1 committed Dec 24, 2024
1 parent 0a6021a commit 26b300a
Show file tree
Hide file tree
Showing 11 changed files with 273 additions and 5 deletions.
10 changes: 7 additions & 3 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ android {

lint {
checkReleaseBuilds = false
abortOnError = true
warningsAsErrors = true
abortOnError = false
warningsAsErrors = false
baseline = file("lint-baseline.xml")
}

Expand All @@ -112,4 +112,8 @@ dependencies {
implementation(libs.autofit.text.view)
implementation(libs.kotlinx.serialization.json)
implementation(libs.eventbus)
}
implementation("com.googlecode.libphonenumber:libphonenumber:8.12.10")

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,8 @@ 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.extensions.config
import org.fossify.phone.helpers.fixInvalidNumbers

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


var actualCallNumber = callNumber
//if (config.fixInvalidNumbers)
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 @@ -85,6 +85,7 @@ class SettingsActivity : SimpleActivity() {
setupDisableProximitySensor()
setupDisableSwipeToAnswer()
setupAlwaysShowFullscreen()
setupFixInvalidNumbers()
setupCallsExport()
setupCallsImport()
updateTextColors(binding.settingsHolder)
Expand Down Expand Up @@ -332,6 +333,16 @@ class SettingsActivity : SimpleActivity() {
}
}

private fun setupFixInvalidNumbers() {
binding.apply {
settingsFixInvalidNumbers.isChecked = config.fixInvalidNumbers
settingsFixInvalidNumbersHolder.setOnClickListener {
settingsFixInvalidNumbers.toggle()
config.fixInvalidNumbers = settingsFixInvalidNumbers.isChecked
}
}
}

private fun setupCallsExport() {
binding.settingsExportCallsHolder.setOnClickListener {
ExportCallHistoryDialog(this) { filename ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,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
115 changes: 115 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,115 @@
package org.fossify.phone.helpers

import android.net.Uri
import android.util.Log
import android.content.Context
import android.telephony.TelephonyManager
import com.google.i18n.phonenumbers.PhoneNumberUtil
import com.google.i18n.phonenumbers.Phonenumber
import com.google.i18n.phonenumbers.NumberParseException

// country codes
private const val BRAZIL_COUNTRY_CODE = 55
private const val BRAZIL_COUNTRY_CODE_STR = "BR"

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

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

/**
* Get the country code of the dialed phone number (or infer from SIM)
*/
private fun getCountryCode(tel: Uri, context: Context): Int? {
// Decode the tel URI
val decodedTel = tel.getSchemeSpecificPart()
val phoneNumberUtil = PhoneNumberUtil.getInstance()
var countryCode: Int?

// Try to get the country code
try {
val phoneNumber: Phonenumber.PhoneNumber = phoneNumberUtil.parse(decodedTel, null)
countryCode = phoneNumber.getCountryCode()
} catch (e: NumberParseException) {
// 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", "No country code, SIM country ISO: $countryIso")
countryCode = phoneNumberUtil.getCountryCodeForRegion(countryIso)
}
Log.d("CallNumberHelper", "Country code: $countryCode")
return countryCode
}


/**
* Fix invalid numbers for Brazil
*
* https://www.gov.br/anatel/pt-br/regulado/numeracao/tabela-servico-movel-celular
* https://www.gov.br/anatel/pt-br/regulado/numeracao/tabela-servico-telefonico-fixo-comutado
* https://www.gov.br/anatel/pt-br/regulado/numeracao/tabela-servico-telefonico-fixo-comutado-codigos-nao-geograficos-cng
* https://www.gov.br/anatel/pt-br/regulado/numeracao/codigos-nacionais/servicos-de-utilidade-publica-e-de-emergencia
* https://www.gov.br/anatel/pt-br/regulado/numeracao/tabela-servico-movel-especializado
*/
@Suppress("NestedBlockDepth")
private fun fixInvalidNumbersBrazil(tel: Uri): Uri {
// Decode a PhoneNumber from the URI
val decodedTel = tel.getSchemeSpecificPart()
val phoneNumberUtil = PhoneNumberUtil.getInstance()
val phoneNumber: Phonenumber.PhoneNumber = phoneNumberUtil.parse(decodedTel, BRAZIL_COUNTRY_CODE_STR)

// Get the national number, potentially with area and carrier code
if (phoneNumber.hasNationalNumber()) {
val nationalNum = phoneNumber.getNationalNumber()
val nationalNumStr = nationalNum.toString()
Log.d("CallNumberHelper", "Brazil national number: $nationalNumStr")

// Look at the local area number (last eight digits)
if (nationalNumStr.length >= BR_LOCAL_AREA_LEN) {
val localAreaNumStr = nationalNumStr.substring(nationalNumStr.length - BR_LOCAL_AREA_LEN)

// Is it a cellphone (starts with >5)?
if (localAreaNumStr.get(0).digitToInt() > BR_LAND_PREFIX_MAX) {

// Is it missing the 9 prefix?
if (nationalNumStr.length < BR_NEW_NUM_LEN ||
nationalNumStr.get(nationalNumStr.length - BR_NEW_NUM_LEN) != BR_CEL_PREFIX_CHAR) {

// Insert the 9 before the last 8 numbers
val fixedNationalNum = (
nationalNumStr.substring(0, nationalNumStr.length - BR_LOCAL_AREA_LEN) +
BR_CEL_PREFIX_CHAR +
localAreaNumStr
).toLong()
phoneNumber.setNationalNumber(fixedNationalNum)
val fixedNumStr = phoneNumberUtil.format(phoneNumber, PhoneNumberUtil.PhoneNumberFormat.E164)

Log.d("CallNumberHelper", "Brazil fixed number: $fixedNumStr")
return Uri.parse("tel:$fixedNumStr")
}
}
}
}
return tel;
}
4 changes: 4 additions & 0 deletions app/src/main/kotlin/org/fossify/phone/helpers/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,8 @@ class Config(context: Context) : BaseConfig(context) {
var alwaysShowFullscreen: Boolean
get() = prefs.getBoolean(ALWAYS_SHOW_FULLSCREEN, false)
set(alwaysShowFullscreen) = prefs.edit().putBoolean(ALWAYS_SHOW_FULLSCREEN, alwaysShowFullscreen).apply()

var fixInvalidNumbers: Boolean
get() = prefs.getBoolean(FIX_INVALID_NUMBERS, false)
set(fixInvalidNumbers) = prefs.edit().putBoolean(FIX_INVALID_NUMBERS, fixInvalidNumbers).apply()
}
1 change: 1 addition & 0 deletions app/src/main/kotlin/org/fossify/phone/helpers/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const val DIALPAD_VIBRATION = "dialpad_vibration"
const val DIALPAD_BEEPS = "dialpad_beeps"
const val HIDE_DIALPAD_NUMBERS = "hide_dialpad_numbers"
const val ALWAYS_SHOW_FULLSCREEN = "always_show_fullscreen"
const val FIX_INVALID_NUMBERS = "fix_invalid_numbers"

const val ALL_TABS_MASK = TAB_CONTACTS or TAB_FAVORITES or TAB_CALL_HISTORY

Expand Down
15 changes: 15 additions & 0 deletions app/src/main/res/layout/activity_settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,21 @@

</RelativeLayout>

<RelativeLayout
android:id="@+id/settings_fix_invalid_numbers_holder"
style="@style/SettingsHolderSwitchStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<org.fossify.commons.views.MyMaterialSwitch
android:id="@+id/settings_fix_invalid_numbers"
style="@style/SettingsSwitchStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/fix_invalid_numbers_desc" />

</RelativeLayout>

<include
android:id="@+id/settings_dialpad_divider"
layout="@layout/divider" />
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values-pt-rBR/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
<string name="disable_proximity_sensor">Desativar sensor de proximidade durante as chamadas</string>
<string name="disable_swipe_to_answer">Clicar em vez de deslizar para responder chamadas</string>
<string name="show_incoming_calls_full_screen">Sempre exibir as chamadas recebidas em tela cheia</string>
<string name="fix_invalid_numbers_desc">Corrigir números antes de realizar a chamada (adiciona o 9 caso esteja faltando)</string>
<string name="hide_dialpad_numbers">Ocultar números do teclado</string>
<string name="faq_1_title">Ouço chamadas recebidas, mas a tela não liga. O que eu posso fazer?</string>
<string name="faq_1_text">Esses problemas podem ter muitos motivos específicos do dispositivo e do sistema, difíceis de dizer em geral. Você deve olhar em volta nas configurações do seu dispositivo e certificar-se de que o aplicativo pode aparecer quando estiver em segundo plano e permitir a exibição sobre outros aplicativos.</string>
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
<string name="disable_proximity_sensor">Disable proximity sensor during calls</string>
<string name="disable_swipe_to_answer">Replace swiping at responding to incoming calls with clicking</string>
<string name="show_incoming_calls_full_screen">Always display incoming calls on full screen</string>
<string name="fix_invalid_numbers_desc">Fix numbers before placing calls (e.g. add leading digits based on country rules)</string>
<string name="hide_dialpad_numbers">Hide dialpad numbers</string>
<string name="export_call_history">Export call history</string>
<string name="import_call_history">Import call history</string>
Expand Down
109 changes: 109 additions & 0 deletions app/src/test/kotlin/org/fossify/phone/helpers/CallNumberHelperTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
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 CallNumberHelperTest {

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 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)
}

// Carrier and Area code

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

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

@Test
fun testLandWithCarrierAndAreaCode() {
val inputNumber = Uri.parse("tel:0211130000000")
val expectedNumber = Uri.parse("tel:+550211130000000")
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 testValidLocalAreaNumber() {
val inputNumber = Uri.parse("tel:990000000")
val expectedNumber = Uri.parse("tel:9990000000")
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)
}

}

0 comments on commit 26b300a

Please sign in to comment.