From d2b12159af237ccad51eacfcaff67716577b77d4 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 11 Dec 2025 10:25:46 +0100 Subject: [PATCH] Allow linphone-config URIs in QR codes scanned inside Linphone --- .../ui/assistant/viewmodel/QrCodeViewModel.kt | 39 ++++++++++--------- .../java/org/linphone/ui/main/MainActivity.kt | 10 ++--- .../java/org/linphone/utils/LinphoneUtils.kt | 21 ++++++++++ 3 files changed, 46 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/org/linphone/ui/assistant/viewmodel/QrCodeViewModel.kt b/app/src/main/java/org/linphone/ui/assistant/viewmodel/QrCodeViewModel.kt index e0de49fbc..643b48848 100644 --- a/app/src/main/java/org/linphone/ui/assistant/viewmodel/QrCodeViewModel.kt +++ b/app/src/main/java/org/linphone/ui/assistant/viewmodel/QrCodeViewModel.kt @@ -19,7 +19,6 @@ */ package org.linphone.ui.assistant.viewmodel -import android.util.Patterns import androidx.annotation.UiThread import androidx.annotation.WorkerThread import androidx.lifecycle.MutableLiveData @@ -32,6 +31,7 @@ import org.linphone.ui.GenericViewModel import org.linphone.utils.Event import org.linphone.R import org.linphone.core.GlobalState +import org.linphone.utils.LinphoneUtils class QrCodeViewModel @UiThread @@ -76,26 +76,27 @@ class QrCodeViewModel if (result == null) { showRedToast(R.string.assistant_qr_code_invalid_toast, R.drawable.warning_circle) } else { - val isValidUrl = Patterns.WEB_URL.matcher(result).matches() - if (!isValidUrl) { - Log.e("$TAG The content of the QR Code doesn't seem to be a valid web URL") + val url = LinphoneUtils.getRemoteProvisioningUrlFromUri(result) + if (url == null) { + Log.e("$TAG The content of the QR Code [$result] doesn't seem to be a valid web URL") showRedToast(R.string.assistant_qr_code_invalid_toast, R.drawable.warning_circle) - } else { - Log.i( - "$TAG QR code URL set, restarting the Core outside of iterate() loop to apply configuration changes" - ) - core.nativePreviewWindowId = null - core.isVideoPreviewEnabled = false - core.isQrcodeVideoPreviewEnabled = false - core.provisioningUri = result + return + } - coreContext.postOnCoreThread { core -> - Log.i("$TAG Stopping Core") - coreContext.core.stop() - Log.i("$TAG Core has been stopped, restarting it") - coreContext.core.start() - Log.i("$TAG Core has been restarted") - } + Log.i( + "$TAG Setting QR code URL [$url], restarting the Core outside of iterate() loop to apply configuration changes" + ) + core.nativePreviewWindowId = null + core.isVideoPreviewEnabled = false + core.isQrcodeVideoPreviewEnabled = false + core.provisioningUri = url + + coreContext.postOnCoreThread { core -> + Log.i("$TAG Stopping Core") + core.stop() + Log.i("$TAG Core has been stopped, restarting it") + core.start() + Log.i("$TAG Core has been restarted") } } } diff --git a/app/src/main/java/org/linphone/ui/main/MainActivity.kt b/app/src/main/java/org/linphone/ui/main/MainActivity.kt index 56c69c961..f50b4cd9d 100644 --- a/app/src/main/java/org/linphone/ui/main/MainActivity.kt +++ b/app/src/main/java/org/linphone/ui/main/MainActivity.kt @@ -790,11 +790,11 @@ class MainActivity : GenericActivity() { } private fun handleConfigIntent(uri: String) { - val remoteConfigUri = uri.substring("linphone-config:".length) - val url = when { - remoteConfigUri.startsWith("http://") || remoteConfigUri.startsWith("https://") -> remoteConfigUri - remoteConfigUri.startsWith("file://") -> remoteConfigUri - else -> "https://$remoteConfigUri" + Log.i("$TAG Trying to parse config intent [$uri] as remote provisioning URL") + val url = LinphoneUtils.getRemoteProvisioningUrlFromUri(uri) + if (url == null) { + Log.e("$TAG Couldn't parse URI [$uri] into a valid remote provisioning URL, aborting") + return } coreContext.postOnCoreThread { core -> diff --git a/app/src/main/java/org/linphone/utils/LinphoneUtils.kt b/app/src/main/java/org/linphone/utils/LinphoneUtils.kt index 511df23d7..5ddcede1e 100644 --- a/app/src/main/java/org/linphone/utils/LinphoneUtils.kt +++ b/app/src/main/java/org/linphone/utils/LinphoneUtils.kt @@ -23,6 +23,7 @@ import android.graphics.Typeface import android.text.Spannable import android.text.SpannableStringBuilder import android.text.style.StyleSpan +import android.util.Patterns import androidx.annotation.AnyThread import androidx.annotation.DrawableRes import androidx.annotation.IntegerRes @@ -65,6 +66,26 @@ class LinphoneUtils { const val RECORDING_MKV_FILE_EXTENSION = ".mkv" const val RECORDING_SMFF_FILE_EXTENSION = ".smff" + @AnyThread + fun getRemoteProvisioningUrlFromUri(uri: String): String? { + val linphoneScheme = "linphone-config:" + return if (uri.startsWith(linphoneScheme)) { + val remoteConfigUri = uri.substring(linphoneScheme.length) + val url = when { + remoteConfigUri.startsWith("http://") || remoteConfigUri.startsWith("https://") -> remoteConfigUri + remoteConfigUri.startsWith("file://") -> remoteConfigUri + else -> "https://$remoteConfigUri" + } + url + } else { + val isValidUrl = Patterns.WEB_URL.matcher(uri).matches() + if (!isValidUrl) { + return null + } + uri + } + } + @WorkerThread fun getDefaultAccount(): Account? { return coreContext.core.defaultAccount ?: coreContext.core.accountList.firstOrNull()