Added timeout for getting account template from network
This commit is contained in:
@@ -80,6 +80,7 @@ import java.io.File
|
|||||||
import java.io.StringReader
|
import java.io.StringReader
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
import javax.net.ssl.HttpsURLConnection
|
||||||
|
|
||||||
fun NavGraphBuilder.accountScreenRoute(navController: NavController) {
|
fun NavGraphBuilder.accountScreenRoute(navController: NavController) {
|
||||||
composable(
|
composable(
|
||||||
@@ -131,7 +132,7 @@ private fun AccountScreen(
|
|||||||
|
|
||||||
LaunchedEffect(kind, acc) {
|
LaunchedEffect(kind, acc) {
|
||||||
if (kind == "new")
|
if (kind == "new")
|
||||||
initAccountFromConfig(acc) { isAccountAvailable = true }
|
initAccountFromNetwork(acc) { isAccountAvailable = true }
|
||||||
else
|
else
|
||||||
isAccountAvailable = true
|
isAccountAvailable = true
|
||||||
}
|
}
|
||||||
@@ -1629,25 +1630,28 @@ private fun checkOnClick(ctx: Context, viewModel: AccountViewModel, ua: UserAgen
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initAccountFromConfig(acc: Account, onConfigLoaded: () -> Unit) {
|
private fun initAccountFromNetwork(acc: Account, onConfigLoaded: () -> Unit) {
|
||||||
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
val url = "https://${Utils.uriHostPart(acc.aor)}/baresip/account_config.xml"
|
val url = "https://${Utils.uriHostPart(acc.aor)}/baresip/account_config.xml"
|
||||||
|
val urlConnection = URL(url).openConnection() as HttpsURLConnection
|
||||||
|
urlConnection.connectTimeout = 5000
|
||||||
|
urlConnection.readTimeout = 3000
|
||||||
val caFile = File(BaresipService.filesPath + "/ca_certs.crt")
|
val caFile = File(BaresipService.filesPath + "/ca_certs.crt")
|
||||||
val config = try {
|
val template = try {
|
||||||
if (caFile.exists())
|
if (caFile.exists())
|
||||||
Utils.readUrlWithCustomCAs(URL(url), caFile)
|
Utils.readUrlWithCustomCAs(urlConnection, caFile)
|
||||||
else
|
else
|
||||||
URL(url).readText()
|
urlConnection.inputStream.bufferedReader().use { it.readText() }
|
||||||
} catch (e: java.lang.Exception) {
|
} catch (e: java.lang.Exception) {
|
||||||
Log.d(TAG, "Failed to get account configuration from network: ${e.message}")
|
Log.d(TAG, "Failed to get account template from network: ${e.message}")
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
if (config != null) {
|
if (template != null) {
|
||||||
Log.d(TAG, "Got account config '$config'")
|
Log.d(TAG, "Got account template '$template'")
|
||||||
val parserFactory: XmlPullParserFactory = XmlPullParserFactory.newInstance()
|
val parserFactory: XmlPullParserFactory = XmlPullParserFactory.newInstance()
|
||||||
val parser: XmlPullParser = parserFactory.newPullParser()
|
val parser: XmlPullParser = parserFactory.newPullParser()
|
||||||
parser.setInput(StringReader(config))
|
parser.setInput(StringReader(template))
|
||||||
var tag: String?
|
var tag: String?
|
||||||
var text = ""
|
var text = ""
|
||||||
var event = parser.eventType
|
var event = parser.eventType
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ import java.lang.reflect.Method
|
|||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
import java.net.NetworkInterface
|
import java.net.NetworkInterface
|
||||||
import java.net.SocketException
|
import java.net.SocketException
|
||||||
import java.net.URL
|
|
||||||
import java.security.KeyStore
|
import java.security.KeyStore
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
import java.security.cert.CertificateException
|
import java.security.cert.CertificateException
|
||||||
@@ -1038,18 +1037,16 @@ object Utils {
|
|||||||
Api.AAudio_close_stream()
|
Api.AAudio_close_stream()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun readUrlWithCustomCAs(url: URL, caFile: File): String? {
|
fun readUrlWithCustomCAs(urlConnection: HttpsURLConnection, caFile: File): String? {
|
||||||
if (!caFile.exists()) {
|
if (!caFile.exists()) {
|
||||||
Log.d("Utils", "Custom CA file not found at ${caFile.path}")
|
Log.d("Utils", "Custom CA file not found at ${caFile.path}")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. Create a TrustManager that trusts the CAs in the user-provided file
|
// Create a TrustManager that trusts the CAs in the user-provided file
|
||||||
val customTrustManager = fun(): X509TrustManager {
|
val customTrustManager = fun(): X509TrustManager {
|
||||||
val certificateFactory = CertificateFactory.getInstance("X.509")
|
val certificateFactory = CertificateFactory.getInstance("X.509")
|
||||||
val certificateInputStream = caFile.inputStream()
|
val certificateInputStream = caFile.inputStream()
|
||||||
// generateCertificates (plural) is crucial for loading all certs from the file
|
|
||||||
val certificates = certificateFactory.generateCertificates(certificateInputStream)
|
val certificates = certificateFactory.generateCertificates(certificateInputStream)
|
||||||
certificateInputStream.close()
|
certificateInputStream.close()
|
||||||
|
|
||||||
@@ -1064,29 +1061,28 @@ object Utils {
|
|||||||
return tmf.trustManagers.find { it is X509TrustManager } as X509TrustManager
|
return tmf.trustManagers.find { it is X509TrustManager } as X509TrustManager
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// 2. Create a TrustManager that trusts the default system CAs
|
// Create a TrustManager that trusts the default system CAs
|
||||||
val systemTrustManager = fun(): X509TrustManager {
|
val systemTrustManager = fun(): X509TrustManager {
|
||||||
val factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
|
val factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
|
||||||
factory.init(null as KeyStore?) // A null keystore loads the system's default CAs
|
factory.init(null as KeyStore?) // A null keystore loads the system's default CAs
|
||||||
return factory.trustManagers.find { it is X509TrustManager } as X509TrustManager
|
return factory.trustManagers.find { it is X509TrustManager } as X509TrustManager
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// 3. Create a composite TrustManager that delegates to both system and custom CAs
|
// Create a composite TrustManager that delegates to both system and custom CAs
|
||||||
@SuppressLint("CustomX509TrustManager")
|
@SuppressLint("CustomX509TrustManager")
|
||||||
val compositeTrustManager = object : X509TrustManager {
|
val compositeTrustManager = object : X509TrustManager {
|
||||||
override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {
|
override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {
|
||||||
// This is for client certificate authentication, which you are not using.
|
// Delegate to the system manager by default.
|
||||||
// It's safe to just delegate to the system manager by default.
|
|
||||||
systemTrustManager.checkClientTrusted(chain, authType)
|
systemTrustManager.checkClientTrusted(chain, authType)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {
|
override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {
|
||||||
try {
|
try {
|
||||||
// First, try to validate the chain with the system's default TrustManager.
|
// Try to validate the chain with the system's default TrustManager.
|
||||||
systemTrustManager.checkServerTrusted(chain, authType)
|
systemTrustManager.checkServerTrusted(chain, authType)
|
||||||
} catch (_: CertificateException) {
|
} catch (_: CertificateException) {
|
||||||
// If that fails, and only if that fails, try to validate with our custom TrustManager.
|
// If that fails, and only if that fails, try to validate with our custom TrustManager.
|
||||||
// This will throw the final CertificateException if it also fails, which is the correct behavior.
|
// This will throw the final CertificateException if it also fails.
|
||||||
customTrustManager.checkServerTrusted(chain, authType)
|
customTrustManager.checkServerTrusted(chain, authType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1097,19 +1093,18 @@ object Utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Create an SSLContext that uses our new composite TrustManager
|
// Create an SSLContext that uses our new composite TrsustManager
|
||||||
val sslContext = SSLContext.getInstance("TLS")
|
val sslContext = SSLContext.getInstance("TLS")
|
||||||
sslContext.init(null, arrayOf(compositeTrustManager), null)
|
sslContext.init(null, arrayOf(compositeTrustManager), null)
|
||||||
|
|
||||||
// 5. Tell HttpsURLConnection to use our custom SSLContext for this connection
|
// Tell HttpsURLConnection to use our custom SSLContext for this connection
|
||||||
val urlConnection = url.openConnection() as HttpsURLConnection
|
|
||||||
urlConnection.sslSocketFactory = sslContext.socketFactory
|
urlConnection.sslSocketFactory = sslContext.socketFactory
|
||||||
|
|
||||||
// 6. Proceed with the connection and return the result
|
// Proceed with the connection and return the result
|
||||||
return urlConnection.inputStream.bufferedReader().use { it.readText() }
|
return urlConnection.inputStream.bufferedReader().use { it.readText() }
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// Catch any exception from certificate loading or from the network connection and log it
|
// Catch any exception from certificate loading or from the network connection
|
||||||
Log.e("Utils", "readUrlWithCustomCa failed: ${e.message}")
|
Log.e("Utils", "readUrlWithCustomCa failed: ${e.message}")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -1133,8 +1128,7 @@ object Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun createTextAvatar(letter: String, colorHex: String): Bitmap {
|
fun createTextAvatar(letter: String, colorHex: String): Bitmap {
|
||||||
// Standard notification large icon size is usually 64dp or 48dp.
|
// Use decent resolution 128x128 and let Android scale it down.
|
||||||
// We use a decent resolution (e.g. 128x128) and let Android scale it down.
|
|
||||||
val size = 128
|
val size = 128
|
||||||
|
|
||||||
// Use KTX createBitmap to match toCircle style
|
// Use KTX createBitmap to match toCircle style
|
||||||
@@ -1143,7 +1137,7 @@ object Utils {
|
|||||||
// Use Fully Qualified Names to avoid Compose conflicts
|
// Use Fully Qualified Names to avoid Compose conflicts
|
||||||
val canvas = android.graphics.Canvas(bitmap)
|
val canvas = android.graphics.Canvas(bitmap)
|
||||||
|
|
||||||
// 1. Draw the colored circle background
|
// Draw the colored circle background
|
||||||
val bgPaint = android.graphics.Paint()
|
val bgPaint = android.graphics.Paint()
|
||||||
bgPaint.isAntiAlias = true
|
bgPaint.isAntiAlias = true
|
||||||
try {
|
try {
|
||||||
@@ -1153,14 +1147,14 @@ object Utils {
|
|||||||
}
|
}
|
||||||
canvas.drawCircle(size / 2f, size / 2f, size / 2f, bgPaint)
|
canvas.drawCircle(size / 2f, size / 2f, size / 2f, bgPaint)
|
||||||
|
|
||||||
// 2. Draw the text (Initial)
|
// Draw the text (Initial)
|
||||||
val textPaint = android.graphics.Paint()
|
val textPaint = android.graphics.Paint()
|
||||||
textPaint.isAntiAlias = true
|
textPaint.isAntiAlias = true
|
||||||
textPaint.color = android.graphics.Color.WHITE
|
textPaint.color = android.graphics.Color.WHITE
|
||||||
textPaint.textSize = size / 2f // Text size is half the circle size
|
textPaint.textSize = size / 2f // Text size is half the circle size
|
||||||
textPaint.textAlign = android.graphics.Paint.Align.CENTER
|
textPaint.textAlign = android.graphics.Paint.Align.CENTER
|
||||||
|
|
||||||
// Use a bold font if possible to match your UI
|
// Use a bold font if possible
|
||||||
textPaint.typeface = android.graphics.Typeface.create(android.graphics.Typeface.DEFAULT, android.graphics.Typeface.BOLD)
|
textPaint.typeface = android.graphics.Typeface.create(android.graphics.Typeface.DEFAULT, android.graphics.Typeface.BOLD)
|
||||||
|
|
||||||
// Calculate vertical center to center the text properly
|
// Calculate vertical center to center the text properly
|
||||||
|
|||||||
Reference in New Issue
Block a user