Skip to content

Commit

Permalink
Add support for some phone number formatting options
Browse files Browse the repository at this point in the history
The two new added options are `digits_only` and `formatted`
(country-specific style). These are the only two options that can be
feasibly supported without pulling in something like libphonenumber.

Fixes: #290

Signed-off-by: Andrew Gunnerson <[email protected]>
  • Loading branch information
chenxiaolong committed Apr 18, 2023
1 parent 0527380 commit 0384d8a
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 13 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,10 @@ BCR supports customizing the template used for determining the output filenames
* If the date format is changed, the old recordings should be manually renamed or moved to another directory to ensure that they won't inadvertently be deleted. For example, if `yyMMdd_HHmmss` was changed to `HHmmss_yyMMdd`, the timestamps from the old recording's filenames would be parsed incorrectly and may get deleted.
* `{direction}`: For 1-on-1 calls, either `in` or `out` depending on if the call is an incoming or outgoing call. If the call is a conference call, then `conference` is used instead.
* `{sim_slot}`: **[Android 11+ only]** The SIM slot number for the call (counting from 1). This is only defined for multi-SIM devices that have multiple SIMs active.
* `{phone_number}`: The phone number for the call. This is undefined for private calls.
* `{phone_number}`: The phone number for the call. This is undefined for private calls. Available formatting options:
* `{phone_number:E.164}`: Default (same as just `{phone_number}`). Phone number formatted in the international E.164 format (`+<country code><subscriber>`).
* `{phone_number:digits_only}`: Phone number with digits only (no `+` or separators).
* `{phone_number:formatted}`: Phone number formatted using the country-specific style.
* `{caller_name}`: The caller ID as provided by CNAP from the carrier.
* `{contact_name}` The name of the (first) contact associated with the phone number. This is only defined if BCR is granted the Contacts permission.
* `{call_log_name}`: The name shown in the call log. This may include more information, like the name of the business, if the system dialer performs reverse lookups. This is only defined if BCR is granted the Read Call Logs permission.
Expand Down
78 changes: 66 additions & 12 deletions app/src/main/java/com/chiller3/bcr/OutputFilenameGenerator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import android.provider.CallLog
import android.provider.ContactsContract
import android.telecom.Call
import android.telecom.PhoneAccount
import android.telephony.PhoneNumberUtils
import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager
import android.util.Log
Expand All @@ -25,6 +26,7 @@ import java.time.format.DateTimeParseException
import java.time.format.SignStyle
import java.time.temporal.ChronoField
import java.time.temporal.Temporal
import java.util.Locale

data class OutputFilename(
val value: String,
Expand Down Expand Up @@ -126,6 +128,68 @@ class OutputFilenameGenerator(
}
}

/**
* Get the current ISO country code for phone number formatting.
*/
private fun getIsoCountryCode(): String? {
val telephonyManager = context.getSystemService(TelephonyManager::class.java)
var result: String? = null

if (telephonyManager.phoneType == TelephonyManager.PHONE_TYPE_GSM) {
result = telephonyManager.networkCountryIso
}
if (result.isNullOrEmpty()) {
result = telephonyManager.simCountryIso
}
if (result.isNullOrEmpty()) {
result = Locale.getDefault().country
}
if (result.isNullOrEmpty()) {
return null
}
return result.uppercase()
}

private fun getPhoneNumber(details: Call.Details, arg: String?): String? {
val uri = details.handle

val number = if (uri?.scheme == PhoneAccount.SCHEME_TEL) {
uri.schemeSpecificPart
} else {
null
} ?: return null

when (arg) {
null, "E.164" -> {
// Default is already E.164
return number
}
"digits_only" -> {
return number.filter { Character.digit(it, 10) != -1 }
}
"formatted" -> {
val country = getIsoCountryCode()
if (country == null) {
Log.w(TAG, "Failed to detect country")
return number
}

val formatted = PhoneNumberUtils.formatNumber(number, country)
if (formatted == null) {
Log.w(TAG, "Phone number cannot be formatted for country $country")
// Don't fail since this isn't the user's fault
return number
}

return formatted
}
else -> {
Log.w(TAG, "Unknown phone_number format arg: $arg")
return null
}
}
}

private fun getContactDisplayName(details: Call.Details, allowManualLookup: Boolean): String? {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val name = details.contactDisplayName
Expand Down Expand Up @@ -154,7 +218,7 @@ class OutputFilenameGenerator(

Log.d(TAG, "Performing manual contact lookup")

val number = getPhoneNumber(details)
val number = getPhoneNumber(details, null)
if (number == null) {
Log.w(TAG, "Cannot determine phone number from call")
return null
Expand Down Expand Up @@ -323,7 +387,7 @@ class OutputFilenameGenerator(
}
"phone_number" -> {
val joined = displayDetails.asSequence()
.map { d -> getPhoneNumber(d) }
.map { d -> getPhoneNumber(d, arg) }
.filterNotNull()
.joinToString(",")
if (joined.isNotEmpty()) {
Expand Down Expand Up @@ -504,16 +568,6 @@ class OutputFilenameGenerator(
private const val CALL_LOG_QUERY_TIMEOUT_NANOS = 2_000_000_000L
private const val CALL_LOG_QUERY_RETRY_DELAY_MILLIS = 100L

private fun getPhoneNumber(details: Call.Details): String? {
val uri = details.handle

return if (uri?.scheme == PhoneAccount.SCHEME_TEL) {
uri.schemeSpecificPart
} else {
null
}
}

fun redactTruncate(msg: String): String = buildString {
val n = 2

Expand Down

0 comments on commit 0384d8a

Please sign in to comment.