[
  {
    "path": ".clang-format",
    "content": "---\nLanguage:        Cpp\n# BasedOnStyle:  LLVM\nAccessModifierOffset: -4\nAlignAfterOpenBracket: DontAlign\nAlignConsecutiveAssignments: false\nAlignConsecutiveDeclarations: false\nAlignEscapedNewlinesLeft: true\nAlignOperands:   true\nAlignTrailingComments: true\nAllowAllParametersOfDeclarationOnNextLine: true\nAllowShortBlocksOnASingleLine: false\nAllowShortCaseLabelsOnASingleLine: false\nAllowShortFunctionsOnASingleLine: None\nAllowShortIfStatementsOnASingleLine: false\nAllowShortLoopsOnASingleLine: false\nAlwaysBreakAfterDefinitionReturnType: None\nAlwaysBreakAfterReturnType: None\nAlwaysBreakBeforeMultilineStrings: false\nAlwaysBreakTemplateDeclarations: false\nBinPackArguments: true\nBinPackParameters: true\nBraceWrapping:\n  AfterClass:      false\n  AfterControlStatement: false\n  AfterEnum:       false\n  AfterFunction:   true\n  AfterNamespace:  false\n  AfterObjCDeclaration: false\n  AfterStruct:     false\n  AfterUnion:      false\n  BeforeCatch:     false\n  BeforeElse:      false\n  IndentBraces:    false\nBreakBeforeBinaryOperators: NonAssignment\nBreakBeforeBraces: Mozilla\nBreakBeforeTernaryOperators: true\nBreakConstructorInitializersBeforeComma: false\nColumnLimit:     100\nCommentPragmas:  '^ IWYU pragma:'\nConstructorInitializerAllOnOneLineOrOnePerLine: false\nConstructorInitializerIndentWidth: 4\nContinuationIndentWidth: 8\nCpp11BracedListStyle: true\nDerivePointerAlignment: false\nDisableFormat:   false\nExperimentalAutoDetectBinPacking: false\nForEachMacros:   [ foreach, Q_FOREACH, BOOST_FOREACH ]\nIncludeCategories:\n  - Regex:           '^\"(llvm|llvm-c|clang|clang-c)/'\n    Priority:        2\n  - Regex:           '^(<|\"(gtest|isl|json)/)'\n    Priority:        3\n  - Regex:           '.*'\n    Priority:        1\nIndentCaseLabels: true\nIndentWidth:     4\nIndentWrappedFunctionNames: false\nIndentPPDirectives: BeforeHash\nKeepEmptyLinesAtTheStartOfBlocks: true\nMacroBlockBegin: ''\nMacroBlockEnd:   ''\nMaxEmptyLinesToKeep: 2\nNamespaceIndentation: None\nObjCBlockIndentWidth: 4\nObjCSpaceAfterProperty: false\nObjCSpaceBeforeProtocolList: true\nPenaltyBreakBeforeFirstCallParameter: 19\nPenaltyBreakComment: 300\nPenaltyBreakFirstLessLess: 120\nPenaltyBreakString: 1000\nPenaltyExcessCharacter: 1000000\nPenaltyReturnTypeOnItsOwnLine: 60\nPointerAlignment: Right\nReflowComments:  false\nSortIncludes:    false\nSpaceAfterCStyleCast: false\nSpaceBeforeAssignmentOperators: true\nSpaceBeforeParens: ControlStatements\nSpaceInEmptyParentheses: false\nSpacesBeforeTrailingComments: 1\nSpacesInAngles:  false\nSpacesInContainerLiterals: true\nSpacesInCStyleCastParentheses: false\nSpacesInParentheses: false\nSpacesInSquareBrackets: false\nStandard:        Cpp11\nUseTab:\t\t Never\nTabWidth:        4\n...\n\n"
  },
  {
    "path": ".gitignore",
    "content": "/.idea\nlocal.properties\n.DS_Store\n*~\n*.iml\n*.orig\n*.rej\njava_*\n.gradle\n/local.properties\nbuild\n/captures\n.externalNativeBuild\n/app/release\n/app/.cxx\ngradle/wrapper/gradle-wrapper.jar\n/distribution/*\n/distribution.video/*\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"libbaresip-android\"]\n\tpath = libbaresip-android\n\turl = https://github.com/juha-h/libbaresip-android.git\n\tbranch = master\n"
  },
  {
    "path": "LICENSE",
    "content": "BSD 3-Clause License\n\nCopyright (c) 2018, TutPro Inc.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n  list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n  this list of conditions and the following disclaimer in the documentation\n  and/or other materials provided with the distribution.\n\n* Neither the name of the copyright holder nor the names of its\n  contributors may be used to endorse or promote products derived from\n  this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "PrivacyPolicy.txt",
    "content": "baresip Privacy Policy\n\nLast updated: Sept 25, 2022\n\nbaresip does not collect any personal, app usage, or any other information nor share any information with anyone. Information you submit in baresip app is stored privately and securely on your Android device and is deleted when you uninstall the app.\n\nYou can be optionally choose in Settings that baresip uses Android contacts as references to SIP and tel URIs.  If chosen, baresip does not store or upload Android contacts anywhere nor share them with anyone.\n\nUse https://github.com/juha-h/baresip-studio/discussions page for questions regarding baresip app's privacy policy.\n"
  },
  {
    "path": "README.md",
    "content": "This is a bare-bones Android Studio project implementing <a href=\"https://github.com/alfredh/baresip\">baresip</a> based SIP User Agent for Android. Its development is motivated by need for a secure, privacy focused SIP user agent for Android that does not depend on third party push notification services.\n\nCurrently the application supports voice calling and messaging, voice conference calls, UDP, TCP, TLS, and WSS signaling transports, voicemail Message Waiting Indication, call transfers (REFER), AMR, Codec2, G.722, G.722.1, G.729, Opus, and PCMU/PCMA voice codecs, as well as ZRTP and (DTLS) SRTP media encapsulation. Minimum supported Android version is 9 (API level 28).\n\nIf you need video calling and have a device that supports Camera2 API, you can instead of this application install its sister application baresip+ from video branch.\n\nAfter cloning the project, generate static libraries and include files to distribution directory using master branch of <a href=\"https://github.com/juha-h/libbaresip-android\">libbaresip-android</a>.\n\nThen in Android Studio (tested with Android Studio Panda 2 | 2025.3.2):\n\n- Open an existing Android Studio project\n\n- File -> Invalidate Caches ... -> Invalidate & Restart\n\n- Build -> Generate Signed Bundle / APK ...\n\nReady to be installed baresip app is available from <a href=\"https://f-droid.org/app/com.tutpro.baresip\">F-Droid</a>, <a href=\"https://play.google.com/store/apps/details?id=com.tutpro.baresip\">Play Store</a>, and from <a href=\"https://github.com/juha-h/baresip-studio/releases\">GitHub</a>.  Signing certificate SHA-256 fingerprint of the GitHub APKs is FE:CC:79:C2:0A:B7:25:B9:B7:8B:B1:6A:75:BA:9A:04:09:28:22:CF:52:BA:32:E4:A4:37:17:0A:68:02:06:02.  Use `keytool -printcert -jarfile app-release.apk` to verify.\n\nLanguage translations are managed via baresip <a href=\"https://hosted.weblate.org/projects/baresip/\">Weblate</a> project.\n\nCopyright (c) 2018 TutPro Inc. Distributed under BSD-3-Clause license.\n"
  },
  {
    "path": "app/build.gradle.kts",
    "content": "import com.android.build.api.dsl.ApplicationExtension\n\nplugins {\n    alias(libs.plugins.compose.compiler)\n    alias(libs.plugins.kotlin.serialization)\n    id(\"com.android.application\")\n}\n\nconfigure<ApplicationExtension> {\n    compileSdk = 36\n    ndkVersion = \"29.0.14206865\"\n    defaultConfig {\n        applicationId = \"com.tutpro.baresip\"\n        minSdk = 28\n        versionCode = 492\n        versionName = \"78.1.1\"\n        @Suppress(\"UnstableApiUsage\")\n        externalNativeBuild {\n            cmake {\n                cFlags += \"-DHAVE_INTTYPES_H -lstdc++\"\n                arguments.addAll(listOf(\"-DANDROID_STL=c++_shared\"))\n            }\n        }\n        ndk {\n            // noinspection ChromeOsAbiSupport\n            abiFilters.addAll(listOf(\"armeabi-v7a\", \"arm64-v8a\"))\n        }\n        vectorDrawables.useSupportLibrary = true\n    }\n    buildTypes {\n        debug {\n            ndk {\n                abiFilters.add(\"x86_64\")\n            }\n        }\n        release {\n            isMinifyEnabled = true\n            isShrinkResources = true\n            proguardFiles(\n                getDefaultProguardFile(\"proguard-android-optimize.txt\"),\n                \"proguard-rules.pro\"\n            )\n        }\n    }\n    buildFeatures {\n        viewBinding = true\n        buildConfig = true\n        compose = true\n    }\n    externalNativeBuild {\n        cmake {\n            path = file(\"src/main/cpp/CMakeLists.txt\")\n            version = \"3.31.6\"\n        }\n    }\n    namespace = \"com.tutpro.baresip\"\n}\n\ncomposeCompiler {\n    reportsDestination = layout.buildDirectory.dir(\"compose_compiler\")\n}\n\ndependencies {\n    implementation(libs.androidx.foundation.android)\n    implementation(libs.androidx.runtime.livedata)\n    implementation(libs.androidx.compose.material3)\n    implementation(platform(libs.androidx.compose.bom))\n    implementation(libs.androidx.activity.compose)\n    implementation(libs.androidx.ui)\n    implementation(libs.kotlin.stdlib.jdk8)\n    implementation(libs.material)\n    implementation(libs.androidx.preference.ktx)\n    implementation(libs.androidx.exifinterface)\n    implementation(libs.androidx.core.ktx)\n    implementation(libs.kotlinx.coroutines.android)\n    implementation(libs.kotlinx.serialization.json)\n    implementation(libs.androidx.activity.ktx)\n    implementation(libs.androidx.fragment.ktx)\n    implementation(libs.androidx.media)\n    implementation(libs.coil.compose)\n    implementation(libs.androidx.navigation.compose)\n    implementation(libs.androidx.navigation.runtime.android)\n    implementation(libs.androidx.lifecycle.process)\n    implementation(libs.androidx.lifecycle.viewmodel.ktx)\n    implementation(libs.androidx.compose.material.icons.core)\n    implementation(libs.androidx.compose.material.icons.extended)\n    implementation(libs.androidx.compose.runtime)\n}\n"
  },
  {
    "path": "app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /Users/gfan/dev/sdk_current/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n-keepattributes LineNumberTable,SourceFile\n-dontobfuscate\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <uses-permission android:name=\"android.permission.BLUETOOTH\"\n        android:maxSdkVersion=\"30\" />\n    <uses-permission android:name=\"android.permission.BLUETOOTH_CONNECT\" />\n    <uses-permission android:name=\"android.permission.FOREGROUND_SERVICE\"\n        tools:ignore=\"ForegroundServicesPolicy\" />\n    <uses-permission android:name=\"android.permission.FOREGROUND_SERVICE_PHONE_CALL\" />\n    <uses-permission android:name=\"android.permission.FOREGROUND_SERVICE_MICROPHONE\" />\n    <uses-permission android:name=\"android.permission.FOREGROUND_SERVICE_SPECIAL_USE\" />\n    <uses-permission android:name=\"android.permission.MANAGE_OWN_CALLS\" />\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.RECORD_AUDIO\" />\n    <uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\" />\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n    <uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\" />\n    <uses-permission android:name=\"android.permission.WAKE_LOCK\" />\n    <uses-permission android:name=\"android.permission.DISABLE_KEYGUARD\" />\n    <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\"\n        android:maxSdkVersion=\"32\" />\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"\n        android:maxSdkVersion=\"28\"\n        tools:ignore=\"ScopedStorage\" />\n    <uses-permission android:name=\"android.permission.READ_CONTACTS\" />\n    <uses-permission android:name=\"android.permission.WRITE_CONTACTS\" />\n    <uses-permission android:name=\"android.permission.CALL_PHONE\" />\n    <uses-permission android:name=\"android.permission.READ_PHONE_STATE\" />\n    <uses-permission android:name=\"android.permission.READ_PHONE_NUMBERS\" />\n    <uses-permission android:name=\"android.permission.USE_FULL_SCREEN_INTENT\" tools:ignore=\"FullScreenIntentPolicy\" />\n    <uses-permission android:name=\"android.permission.POST_NOTIFICATIONS\" />\n    <uses-permission android:name=\"android.permission.RECEIVE_BOOT_COMPLETED\" />\n    <uses-permission android:name=\"android.permission.VIBRATE\" />\n    <uses-permission android:name=\"android.permission.BROADCAST_STICKY\" />\n    <uses-permission android:name=\"android.permission.SYSTEM_ALERT_WINDOW\" />\n\n    <uses-feature\n        android:name=\"android.hardware.telephony\"\n        android:required=\"false\" />\n    <uses-feature\n        android:name=\"android.hardware.camera\"\n        android:required=\"false\" />\n\n    <application\n        android:name=\".BaresipApp\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:roundIcon=\"@mipmap/ic_launcher_round\"\n        android:installLocation=\"internalOnly\"\n        android:label=\"@string/app_name\"\n        android:supportsRtl=\"true\"\n        android:largeHeap=\"true\"\n        android:enableOnBackInvokedCallback=\"true\"\n        android:theme=\"@style/Theme.Material3.DayNight.NoActionBar\"\n        tools:remove=\"android:appComponentFactory\"\n        tools:targetApi=\"33\">\n\n        <activity\n            android:name=\".MainActivity\"\n            android:configChanges=\"orientation|keyboardHidden|screenSize\"\n            android:exported=\"true\"\n            android:launchMode=\"singleTask\"\n            android:windowSoftInputMode=\"adjustResize\"\n            android:showWhenLocked=\"true\"\n            android:turnScreenOn=\"true\" >\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n            <intent-filter>\n                <action android:name=\"android.intent.action.DIAL\" />\n                <action android:name=\"android.intent.action.CALL\" />\n                <category android:name=\"android.intent.category.DEFAULT\" />\n            </intent-filter>\n            <intent-filter>\n                <action android:name=\"android.intent.action.VIEW\" />\n                <action android:name=\"android.intent.action.DIAL\" />\n                <action android:name=\"android.intent.action.CALL\" />\n                <category android:name=\"android.intent.category.DEFAULT\" />\n                <category android:name=\"android.intent.category.BROWSABLE\" />\n                <data android:scheme=\"tel\" />\n                <data android:scheme=\"sip\" />\n            </intent-filter>\n        </activity>\n\n        <service\n            android:name=\".BaresipService\"\n            android:enabled=\"true\"\n            android:foregroundServiceType=\"microphone|phoneCall|specialUse\"\n            android:stopWithTask=\"false\" >\n            <property android:name=\"android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE\"\n                android:value=\"Maintaining SIP registration for incoming VoIP calls\" />\n        </service>\n\n        <service\n            android:name=\".ConnectionService\"\n            android:enabled=\"true\"\n            android:exported=\"true\"\n            android:label=\"@string/app_name\"\n            android:permission=\"android.permission.BIND_TELECOM_CONNECTION_SERVICE\" >\n            <intent-filter>\n                <action android:name=\"android.telecom.ConnectionService\" />\n            </intent-filter>\n        </service>\n\n        <service\n            android:name=\".InCallService\"\n            android:enabled=\"true\"\n            android:exported=\"true\"\n            android:permission=\"android.permission.BIND_INCALL_SERVICE\" >\n            <meta-data\n                android:name=\"android.telecom.IN_CALL_SERVICE_UI\"\n                android:value=\"true\" />\n            <meta-data\n                android:name=\"android.telecom.IN_CALL_SERVICE_CAR_MODE_UI\"\n                android:value=\"false\" />\n            <intent-filter>\n                <action android:name=\"android.telecom.InCallService\" />\n            </intent-filter>\n        </service>\n\n        <receiver\n            android:name=\".BootCompletedReceiver\"\n            android:enabled=\"true\"\n            android:exported=\"false\"\n            android:permission=\"android.permission.RECEIVE_BOOT_COMPLETED\" >\n            <intent-filter>\n                <action android:name=\"android.intent.action.BOOT_COMPLETED\" />\n                <action android:name=\"android.intent.action.QUICKBOOT_POWERON\" />\n                <action android:name=\"android.intent.action.MY_PACKAGE_REPLACED\" />\n            </intent-filter>\n        </receiver>\n\n        <receiver\n            android:name=\".TaskReceiver\"\n            android:enabled=\"true\"\n            android:exported=\"true\"\n            tools:ignore=\"ExportedReceiver\" >\n            <intent-filter>\n                <action android:name=\"com.tutpro.baresip.REGISTER\" />\n                <action android:name=\"com.tutpro.baresip.UNREGISTER\" />\n                <action android:name=\"com.tutpro.baresip.QUIT\" />\n            </intent-filter>\n        </receiver>\n\n    </application>\n\n</manifest>"
  },
  {
    "path": "app/src/main/assets/accounts",
    "content": ""
  },
  {
    "path": "app/src/main/assets/config.static",
    "content": "poll_method epoll\ncall_local_timeout 120\ncall_max_calls 4\ncall_hold_other_calls yes\nfilter_registrar udp,tcp,tls,ws,wss\naudio_player aaudio,nil\naudio_source aaudio,nil\naudio_alert aaudio,nil\naudio_level no\nausrc_format s16\nauplay_format s16\nauenc_format s16\naudec_format s16\naudio_buffer 20-160\naudio_buffer_mode adaptive\naudio_silence -35.0\naudio_telev_pt 101\naudio_jitter_buffer_type adaptive\naudio_jitter_buffer_ms 100-200\naudio_jitter_buffer_size 50\nrtp_stats yes\nrtp_timeout 60\nrtp_rxmode thread\nmodule aaudio.so\nmodule stun.so\nmodule turn.so\nmodule ice.so\nmodule srtp.so\nmodule dtls_srtp.so\nmodule gzrtp.so\nmodule uuid.so\nmodule_app account.so\nmodule_app debug_cmd.so\nmodule_app mwi.so\nopus_samplerate 16000\nopus_stereo no\nopus_sprop_stereo no\nopus_cbr no\nopus_inbandfec yes\nopus_application voip\ndtls_srtp_use_ec prime256v1\n"
  },
  {
    "path": "app/src/main/assets/contacts",
    "content": "\"The Test Call\" <sip:thetestcall@sip2sip.info>\n"
  },
  {
    "path": "app/src/main/cpp/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.18...4.0)\n\nproject(baresip)\n\nadd_link_options(\"LINKER:--build-id=none\")\n\nset(distribution_DIR ${CMAKE_SOURCE_DIR}/../../../../distribution)\n\nadd_library(lib_crypto STATIC IMPORTED)\nset_target_properties(lib_crypto PROPERTIES IMPORTED_LOCATION\n        ${distribution_DIR}/openssl/lib/${ANDROID_ABI}/libcrypto.a)\n\nadd_library(lib_ssl STATIC IMPORTED)\nset_target_properties(lib_ssl PROPERTIES IMPORTED_LOCATION\n        ${distribution_DIR}/openssl/lib/${ANDROID_ABI}/libssl.a)\n\nadd_library(lib_re STATIC IMPORTED)\nset_target_properties(lib_re PROPERTIES IMPORTED_LOCATION\n        ${distribution_DIR}/re/lib/${ANDROID_ABI}/libre.a)\n\nadd_library(lib_opus STATIC IMPORTED)\nset_target_properties(lib_opus PROPERTIES IMPORTED_LOCATION\n        ${distribution_DIR}/opus/lib/${ANDROID_ABI}/libopus.a)\n\nadd_library(lib_g722 STATIC IMPORTED)\nset_target_properties(lib_g722 PROPERTIES IMPORTED_LOCATION\n        ${distribution_DIR}/g722/lib/${ANDROID_ABI}/libg722.a)\n\nadd_library(lib_g722_1 STATIC IMPORTED)\nset_target_properties(lib_g722_1 PROPERTIES IMPORTED_LOCATION\n        ${distribution_DIR}/g7221/lib/${ANDROID_ABI}/libg722_1.a)\n\nadd_library(lib_g729 STATIC IMPORTED)\nset_target_properties(lib_g729 PROPERTIES IMPORTED_LOCATION\n        ${distribution_DIR}/g729/lib/${ANDROID_ABI}/libbcg729.a)\n\nadd_library(lib_codec2 STATIC IMPORTED)\nset_target_properties(lib_codec2 PROPERTIES IMPORTED_LOCATION\n        ${distribution_DIR}/codec2/lib/${ANDROID_ABI}/libcodec2.a)\n\nadd_library(lib_amrnb STATIC IMPORTED)\nset_target_properties(lib_amrnb PROPERTIES IMPORTED_LOCATION\n        ${distribution_DIR}/amr/lib/${ANDROID_ABI}/libamrnb.a)\n\nadd_library(lib_amrwb STATIC IMPORTED)\nset_target_properties(lib_amrwb PROPERTIES IMPORTED_LOCATION\n        ${distribution_DIR}/amr/lib/${ANDROID_ABI}/libamrwb.a)\n\nadd_library(lib_amrwbenc STATIC IMPORTED)\nset_target_properties(lib_amrwbenc PROPERTIES IMPORTED_LOCATION\n        ${distribution_DIR}/amr/lib/${ANDROID_ABI}/libamrwbenc.a)\n\nadd_library(lib_zrtpcppcore STATIC IMPORTED)\nset_target_properties(lib_zrtpcppcore PROPERTIES IMPORTED_LOCATION\n        ${distribution_DIR}/gzrtp/lib/${ANDROID_ABI}/libzrtpcppcore.a)\n\nadd_library(lib_sndfile STATIC IMPORTED)\nset_target_properties(lib_sndfile PROPERTIES IMPORTED_LOCATION\n        ${distribution_DIR}/sndfile/lib/${ANDROID_ABI}/libsndfile.a)\n\nadd_library(lib_baresip STATIC IMPORTED)\nset_target_properties(lib_baresip PROPERTIES IMPORTED_LOCATION\n        ${distribution_DIR}/baresip/lib/${ANDROID_ABI}/libbaresip.a)\n\nadd_library(baresip SHARED ${CMAKE_SOURCE_DIR}/baresip.c)\n\ntarget_include_directories(baresip PRIVATE\n        ${distribution_DIR}/openssl/include\n        ${distribution_DIR}/re/include\n        ${distribution_DIR}/baresip/include)\n\nadd_definitions(-DHAVE_PTHREAD)\n\ntarget_link_libraries(\n        baresip\n        android\n        aaudio\n        lib_baresip\n        lib_re\n        lib_ssl\n        lib_crypto\n        lib_opus\n        lib_g722\n        lib_g722_1\n        lib_g729\n        lib_codec2\n        lib_amrnb\n        lib_amrwb\n        lib_amrwbenc\n        lib_zrtpcppcore\n        lib_sndfile\n        z\n        log)\n"
  },
  {
    "path": "app/src/main/cpp/baresip.c",
    "content": "#include <string.h>\n#include <pthread.h>\n#include <jni.h>\n#include <aaudio/AAudio.h>\n#include <stdlib.h>\n#include <re.h>\n#include <baresip.h>\n#include \"logger.h\"\n\nenum\n{\n    ASYNC_WORKERS = 4\n};\n\ntypedef struct baresip_context\n{\n    JavaVM *javaVM;\n    jclass mainActivityClz;\n    jobject mainActivityObj;\n\n} BaresipContext;\n\nBaresipContext g_ctx;\n\nstatic int vprintf_null(const char *p, size_t size, void *arg)\n{\n    (void)p;\n    (void)size;\n    (void)arg;\n    return 0;\n}\n\nstatic void net_debug_log()\n{\n    char debug_buf[2048];\n    int l;\n    l = re_snprintf(&(debug_buf[0]), 2047, \"%H\", net_debug, baresip_network());\n    if (l != -1) {\n        debug_buf[l] = '\\0';\n        LOGD(\"%s\\n\", debug_buf);\n    }\n}\n\nstatic void net_dns_debug_log()\n{\n    char debug_buf[2048];\n    int l;\n    LOGD(\"net_dns_debug_log\\n\");\n    l = re_snprintf(&(debug_buf[0]), 2047, \"%H\", net_dns_debug, baresip_network());\n    if (l != -1) {\n        debug_buf[l] = '\\0';\n        LOGD(\"%s\\n\", debug_buf);\n    }\n}\n\nstatic void ua_debug_log(struct ua *ua)\n{\n    char debug_buf[2048];\n    int l;\n    l = re_snprintf(&(debug_buf[0]), 2047, \"%H\", ua_debug, ua);\n    if (l != -1) {\n        debug_buf[l] = '\\0';\n        LOGD(\"%s\\n\", debug_buf);\n    }\n}\n\nstatic void account_debug_log(struct account *acc)\n{\n    char debug_buf[2048];\n    int l;\n    l = re_snprintf(&(debug_buf[0]), 2047, \"%H\", account_debug, acc);\n    if (l != -1) {\n        debug_buf[l] = '\\0';\n        LOGD(\"%s\\n\", debug_buf);\n    }\n}\n\n#if 0\nstatic void ua_print_status_log(struct ua *ua)\n{\n    char debug_buf[2048];\n    int l;\n    l = re_snprintf(&(debug_buf[0]), 2047, \"%H\", ua_print_status, ua);\n    if (l != -1) {\n        debug_buf[l] = '\\0';\n        LOGD(\"%s\\n\", debug_buf);\n    }\n}\n#endif\n\nstatic struct re_printf pf_null = {vprintf_null, 0};\n\nstatic void signal_handler(int sig)\n{\n    static bool term = false;\n\n    if (term) {\n        module_app_unload();\n        mod_close();\n        exit(0);\n    }\n\n    term = true;\n    LOGI(\"terminated by signal (%d)\\n\", sig);\n    ua_stop_all(false);\n}\n\nstatic void ua_exit_handler(void *arg)\n{\n    (void)arg;\n    LOGD(\"ua exited -- stopping main runloop\\n\");\n    re_cancel();\n}\n\nstatic const char *bevent_reg_str(enum bevent_ev ev)\n{\n    switch (ev) {\n        case BEVENT_REGISTERING:\n            return \"registering\";\n        case BEVENT_REGISTER_OK:\n        case BEVENT_FALLBACK_OK:\n            return \"registered\";\n        case BEVENT_REGISTER_FAIL:\n        case BEVENT_FALLBACK_FAIL:\n            return \"registering failed\";\n        case BEVENT_UNREGISTERING:\n            return \"unregistering\";\n        default:\n            return \"?\";\n    }\n}\n\nstatic const char *translate_errorcode(uint16_t scode)\n{\n    switch (scode) {\n        case 404:\n            return \"\"; /* ignore */\n        case 486:\n        case 603:\n            return \"busy\";\n        case 487:\n            return \"\"; /* ignore */\n        default:\n            return \"error\";\n    }\n}\n\nstatic void event_handler(enum bevent_ev ev, struct bevent *event, void *arg)\n{\n    (void)arg;\n    const char *prm  = bevent_get_text(event);\n    struct call *call = bevent_get_call(event);\n    struct ua *ua = bevent_get_ua(event);\n    const struct sip_msg *msg = bevent_get_msg(event);\n    struct account *acc = ua_account(bevent_get_ua(event));\n    const char *tone;\n    char event_buf[256];\n    enum sdp_dir ardir;\n    int len, err;\n    struct pl module, module_event, data;\n\n    LOGD(\"ua event (%s) %s\\n\", bevent_str(ev), prm);\n\n    switch (ev) {\n        case BEVENT_CREATE:\n            len = re_snprintf(event_buf, sizeof event_buf, \"create\", \"\");\n            break;\n        case BEVENT_REGISTERING:\n        case BEVENT_UNREGISTERING:\n        case BEVENT_REGISTER_OK:\n        case BEVENT_FALLBACK_OK:\n            len = re_snprintf(event_buf, sizeof event_buf, \"%s\", bevent_reg_str(ev));\n            break;\n        case BEVENT_REGISTER_FAIL:\n        case BEVENT_FALLBACK_FAIL:\n            len = re_snprintf(event_buf, sizeof event_buf, \"registering failed,%s\", prm);\n            break;\n        case BEVENT_SIPSESS_CONN:\n            ua = uag_find_msg(msg);\n            // There is no call yet and call is thus used to hold SIP message\n            call = (struct call *)msg;\n            len = re_snprintf(event_buf, sizeof event_buf, \"%s,%r,%ld\", prm, &msg->from.auri, (long)event);\n            break;\n        case BEVENT_CALL_INCOMING:\n            len = re_snprintf(event_buf, sizeof event_buf, \"call incoming,%s\", prm);\n            break;\n        case BEVENT_CALL_OUTGOING:\n            len = re_snprintf(event_buf, sizeof event_buf, \"call outgoing\", \"\");\n            break;\n        case BEVENT_CALL_ANSWERED:\n            len = re_snprintf(event_buf, sizeof event_buf, \"call answered\", \"\");\n            break;\n        case BEVENT_CALL_REDIRECT:\n            len = re_snprintf(event_buf, sizeof event_buf, \"call redirect,%s\", prm + 4);\n            break;\n        case BEVENT_CALL_LOCAL_SDP:\n            if (strcmp(prm, \"offer\") == 0)\n                return;\n            len = re_snprintf(event_buf, sizeof event_buf, \"call %sed\", prm);\n            break;\n        case BEVENT_CALL_RINGING:\n            len = re_snprintf(event_buf, sizeof event_buf, \"call ringing\", \"\");\n            break;\n        case BEVENT_CALL_PROGRESS:\n            ardir = sdp_media_rdir(stream_sdpmedia(audio_strm(call_audio(call))));\n            len = re_snprintf(event_buf, sizeof event_buf, \"call progress,%d\", ardir);\n            break;\n        case BEVENT_CALL_ESTABLISHED:\n            len = re_snprintf(event_buf, sizeof event_buf, \"call established\", \"\");\n            break;\n        case BEVENT_CALL_REMOTE_SDP:\n            ardir = sdp_media_rdir(stream_sdpmedia(audio_strm(call_audio(call))));\n            len = re_snprintf(event_buf, sizeof event_buf, \"call update,%d,%ld\", ardir, (long)event);\n            break;\n        case BEVENT_CALL_MENC:\n            if (prm[0] == '0')\n                len = re_snprintf(event_buf, sizeof event_buf, \"call secure\", \"\");\n            else if (prm[0] == '1')\n                len = re_snprintf(event_buf, sizeof event_buf, \"call verify,%s\", prm + 2);\n            else if (prm[0] == '2')\n                len = re_snprintf(event_buf, sizeof event_buf, \"call verified,%s\", prm + 2);\n            else\n                len = re_snprintf(event_buf, sizeof event_buf, \"unknown menc event\", \"\");\n            break;\n        case BEVENT_CALL_TRANSFER:\n            len = re_snprintf(event_buf, sizeof event_buf, \"call transfer,%s\", prm);\n            break;\n        case BEVENT_CALL_TRANSFER_FAILED:\n            call_hold(call, false);\n            len = re_snprintf(event_buf, sizeof event_buf, \"transfer failed,%s\", prm);\n            break;\n        case BEVENT_CALL_CLOSED:\n            tone = call_scode(call) ? translate_errorcode(call_scode(call)) : \"\";\n            len = re_snprintf(event_buf, sizeof event_buf, \"call closed,%s,%s\", prm, tone);\n            break;\n        case BEVENT_MWI_NOTIFY:\n            len = re_snprintf(event_buf, sizeof event_buf, \"mwi notify,%s\", prm);\n            break;\n        case BEVENT_MODULE:\n            err = re_regex(prm, strlen(prm), \"[^,]*,[^,]*,[~]*\", &module, &module_event, &data);\n            if (err)\n                return;\n            if (!pl_strcmp(&module_event, \"dump\")) {\n                len = re_snprintf(event_buf, sizeof event_buf, \"sndfile dump,%r\", &data);\n                break;\n            }\n            if (!pl_strcmp(&module_event, \"recorder sessionid\")) {\n                len = re_snprintf(event_buf, sizeof event_buf, \"recorder sessionid,%r\", &data);\n                break;\n            }\n        default:\n            return;\n    }\n\n    if (len == -1) {\n        LOGE(\"failed to print event to buffer\\n\");\n        return;\n    }\n\n    JavaVM *javaVM = g_ctx.javaVM;\n    JNIEnv *env;\n    jint res = (*javaVM)->GetEnv(javaVM, (void**)&env, JNI_VERSION_1_6);\n    if (res != JNI_OK) {\n        LOGD(\"failed to get javaVM environment, ErrorCode = %d\\n\", res);\n        res = (*javaVM)->AttachCurrentThread(javaVM, &env, NULL);\n        if (JNI_OK != res) {\n            LOGE(\"failed to AttachCurrentThread, ErrorCode = %d\\n\", res);\n            return;\n        }\n    }\n\n    jmethodID methodId =\n            (*env)->GetMethodID(env, g_ctx.mainActivityClz, \"uaEvent\", \"(Ljava/lang/String;JJ)V\");\n    jstring jEvent = (*env)->NewStringUTF(env, event_buf);\n    LOGD(\"sending ua/call %ld/%ld event %s\\n\", (long)ua, (long)call, event_buf);\n    (*env)->CallVoidMethod(env, g_ctx.mainActivityObj, methodId, jEvent, (jlong)ua, (jlong)call);\n    (*env)->DeleteLocalRef(env, jEvent);\n\n}\n\nstatic void message_handler(\n        struct ua *ua, const struct pl *peer, const struct pl *ctype, struct mbuf *body, void *arg)\n{\n    (void)arg;\n    char ctype_buf[128];\n    char peer_buf[256];\n    size_t size;\n\n    if (snprintf(peer_buf, 256, \"%.*s\", (int)peer->l, peer->p) >= 256) {\n        LOGE(\"message peer is too long (max 255 characters)\\n\");\n        return;\n    }\n\n    JavaVM *javaVM = g_ctx.javaVM;\n    JNIEnv *env;\n    jint res = (*javaVM)->GetEnv(javaVM, (void **)&env, JNI_VERSION_1_6);\n    if (res != JNI_OK) {\n        res = (*javaVM)->AttachCurrentThread(javaVM, &env, NULL);\n        if (JNI_OK != res) {\n            LOGE(\"failed to AttachCurrentThread, ErrorCode = %d\\n\", res);\n            return;\n        }\n    }\n\n    jmethodID methodId = (*env)->GetMethodID(env, g_ctx.mainActivityClz, \"messageEvent\",\n            \"(JLjava/lang/String;Ljava/lang/String;[B)V\");\n    jstring jPeer = (*env)->NewStringUTF(env, peer_buf);\n    pl_strcpy(ctype, ctype_buf, 256);\n    jstring jCtype = (*env)->NewStringUTF(env, ctype_buf);\n    jbyteArray jMsg;\n    size = mbuf_get_left(body);\n    jMsg = (*env)->NewByteArray(env, (jsize)size);\n    if ((*env)->GetArrayLength(env, jMsg) != size) {\n        (*env)->DeleteLocalRef(env, jMsg);\n        jMsg = (*env)->NewByteArray(env, (jsize)size);\n    }\n    void *temp = (*env)->GetPrimitiveArrayCritical(env, (jarray)jMsg, 0);\n    memcpy(temp, mbuf_buf(body), size);\n    (*env)->ReleasePrimitiveArrayCritical(env, jMsg, temp, 0);\n    LOGD(\"sending message %ld/%s/%s/%.*s\\n\", (long)ua, peer_buf, ctype_buf, (int)size,\n            mbuf_buf(body));\n    (*env)->CallVoidMethod(env, g_ctx.mainActivityObj, methodId, (jlong)ua, jPeer, jCtype, jMsg);\n    (*env)->DeleteLocalRef(env, jCtype);\n    (*env)->DeleteLocalRef(env, jPeer);\n    (*env)->DeleteLocalRef(env, jMsg);\n\n}\n\nstatic void send_resp_handler(int err, const struct sip_msg *msg, void *arg)\n{\n    (void)arg;\n    char reason_buf[64];\n\n    if (err) {\n        LOGD(\"send_response_handler received error %d\\n\", err);\n        return;\n    }\n\n    pl_strcpy(&(msg->reason), reason_buf, 64);\n    LOGD(\"send_response_handler received response '%u %s' at %s\\n\", msg->scode, reason_buf,\n            (char *)arg);\n\n    JavaVM *javaVM = g_ctx.javaVM;\n    JNIEnv *env;\n    jint res = (*javaVM)->GetEnv(javaVM, (void **)&env, JNI_VERSION_1_6);\n    if (res != JNI_OK) {\n        res = (*javaVM)->AttachCurrentThread(javaVM, &env, NULL);\n        if (JNI_OK != res) {\n            LOGE(\"failed to AttachCurrentThread, ErrorCode = %d\\n\", res);\n            return;\n        }\n    }\n\n    jmethodID methodId = (*env)->GetMethodID(env, g_ctx.mainActivityClz, \"messageResponse\",\n            \"(ILjava/lang/String;Ljava/lang/String;)V\");\n    jstring javaReason = (*env)->NewStringUTF(env, reason_buf);\n    jstring javaTime = (*env)->NewStringUTF(env, (char *)arg);\n    (*env)->CallVoidMethod(env, g_ctx.mainActivityObj, methodId, msg->scode, javaReason, javaTime);\n    (*env)->DeleteLocalRef(env, javaReason);\n    (*env)->DeleteLocalRef(env, javaTime);\n\n}\n\nenum\n{\n    ID_UA_STOP_ALL\n};\n\nstatic struct mqueue *mq;\n\nstatic void mqueue_handler(int id, void *data, void *arg)\n{\n    (void)arg;\n    if (id == ID_UA_STOP_ALL) {\n        LOGD(\"calling ua_stop_all with force %u\\n\", (unsigned)(uintptr_t)data);\n        ua_stop_all((bool)(uintptr_t)data);\n    }\n}\n\n#include <unistd.h>\n\nstatic int pfd[2];\nstatic pthread_t loggingThread;\n\nstatic void *loggingFunction(void *arg)\n{\n    (void)arg;\n    ssize_t readSize;\n    char buf[128];\n\n    while ((readSize = read(pfd[0], buf, sizeof buf - 1)) > 0) {\n        if (buf[readSize - 1] == '\\n') {\n            --readSize;\n        }\n        buf[readSize] = 0;\n        LOGD(\"%s\", buf);\n    }\n\n    return 0;\n}\n\nstatic int runLoggingThread()\n{\n    setvbuf(stdout, 0, _IOLBF, 0);\n    setvbuf(stderr, 0, _IONBF, 0);\n\n    pipe(pfd);\n    dup2(pfd[1], 1);\n    dup2(pfd[1], 2);\n\n    int ret = pthread_create(&loggingThread, NULL, loggingFunction, NULL);\n    if (ret != 0) {\n        LOGE(\"failed to create logging thread: %d\", ret);\n        return ret;\n    }\n\n    pthread_detach(loggingThread);\n\n    return 0;\n}\n\nJNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)\n{\n    (void)reserved;\n\n    LOGD(\"at JNI_OnLoad\\n\");\n\n    memset(&g_ctx, 0, sizeof(g_ctx));\n\n    g_ctx.javaVM = vm;\n    g_ctx.mainActivityClz = NULL;\n    g_ctx.mainActivityObj = NULL;\n\n    return JNI_VERSION_1_6;\n}\n\nJNIEXPORT void JNICALL Java_com_tutpro_baresip_BaresipService_baresipStart(\n        JNIEnv *env, jobject instance, jstring jPath, jstring jAddrs, jint jLogLevel,\n        jstring jSoftware)\n{\n    LOGI(\"starting baresip\\n\");\n\n    int err;\n    char start_error[64] = \"\";\n\n    JavaVM *javaVM = g_ctx.javaVM;\n\n    jclass clz = (*env)->GetObjectClass(env, instance);\n    g_ctx.mainActivityClz = (*env)->NewGlobalRef(env, clz);\n    g_ctx.mainActivityObj = (*env)->NewGlobalRef(env, instance);\n\n    const char *path = (*env)->GetStringUTFChars(env, jPath, 0);\n    const char *addrs = (*env)->GetStringUTFChars(env, jAddrs, 0);\n    const char *software = (*env)->GetStringUTFChars(env, jSoftware, 0);\n\n    runLoggingThread();\n\n    err = libre_init();\n    if (err) {\n        LOGE(\"failed to init libre\");\n        goto out;\n    }\n\n    if (re_thread_check(true) == 0) {\n        LOGI(\"attaching to re thread\\n\");\n        jint res = (*javaVM)->AttachCurrentThread(javaVM, &env, NULL);\n        if (JNI_OK != res) {\n            LOGE(\"failed to AttachCurrentThread: %d\\n\", res);\n            goto out;\n        }\n    } else {\n        LOGE(\"not on re thread\\n\");\n        goto out;\n    }\n\n    conf_path_set(path);\n\n    log_level_set((enum log_level)jLogLevel);\n\n    err = conf_configure();\n    if (err) {\n        LOGW(\"conf_configure() failed: (%d)\\n\", err);\n        strcpy(start_error, \"conf_configure\");\n        goto out;\n    }\n\n    re_thread_async_init(ASYNC_WORKERS);\n\n    err = baresip_init(conf_config());\n    if (err) {\n        LOGW(\"baresip_init() failed (%d)\\n\", err);\n        strcpy(start_error, \"baresip_init\");\n        goto out;\n    }\n\n    // Turn off DNS client cache (should be OK with async workers, but it not)\n    dnsc_cache_max(net_dnsc(baresip_network()), 0);\n\n    if (strlen(addrs) > 0) {\n        char *addr_list = (char *)malloc(strlen(addrs) + 1);\n        struct sa temp_sa;\n        char buf[256];\n        net_flush_addresses(baresip_network());\n        strcpy(addr_list, addrs);\n        char *ptr = strtok(addr_list, \";\");\n        while (ptr != NULL) {\n            if (0 == sa_set_str(&temp_sa, ptr, 0)) {\n                sa_ntop(&temp_sa, buf, 256);\n                ptr = strtok(NULL, \";\");\n                net_add_address_ifname(baresip_network(), &temp_sa, ptr);\n            } else {\n                LOGE(\"invalid ip address (%s)\\n\", ptr);\n                ptr = strtok(NULL, \";\");\n            }\n            *(ptr - 1) = ';';\n            ptr = strtok(NULL, \";\");\n        }\n        free(addr_list);\n    }\n\n    // net_debug_log();\n\n    err = ua_init(software, true, true, true);\n    if (err) {\n        LOGE(\"ua_init() failed (%d)\\n\", err);\n        strcpy(start_error, \"ua_init\");\n        goto out;\n    }\n\n    uag_set_exit_handler(ua_exit_handler, NULL);\n\n    err = bevent_register(event_handler, NULL);\n    if (err) {\n        LOGE(\"bevent_register() failed (%d)\\n\", err);\n        strcpy(start_error, \"bevent_register\");\n        goto out;\n    }\n\n    err = message_listen(baresip_message(), message_handler, NULL);\n    if (err) {\n        LOGE(\"message_listen() failed (%d)\\n\", err);\n        strcpy(start_error, \"message_listen\");\n        goto out;\n    }\n\n    err = conf_modules();\n    if (err) {\n        LOGW(\"conf_modules() failed (%d)\\n\", err);\n        strcpy(start_error, \"conf_modules\");\n        goto out;\n    }\n\n    err = mqueue_alloc(&mq, mqueue_handler, NULL);\n    if (err) {\n        LOGW(\"mqueue_alloc failed (%d)\\n\", err);\n        strcpy(start_error, \"mqueue_alloc\");\n        goto out;\n    }\n\n    // no need to call re_leave/enter since main is not running yet\n    jmethodID startedId = (*env)->GetMethodID(env, g_ctx.mainActivityClz, \"started\", \"()V\");\n    (*env)->CallVoidMethod(env, g_ctx.mainActivityObj, startedId);\n\n    LOGI(\"running main loop ...\\n\");\n    err = re_main(signal_handler);\n\nout:\n    if (err) {\n        LOGE(\"stopping UAs due to error: (%d)\\n\", err);\n        ua_stop_all(true);\n    } else {\n        LOGI(\"main loop exit\\n\");\n    }\n\n    mq = mem_deref(mq);\n\n    LOGD(\"closing ...\");\n    ua_close();\n    module_app_unload();\n    conf_close();\n    baresip_close();\n\n    bevent_unregister(event_handler);\n\n    LOGD(\"unloading modules ...\");\n    mod_close();\n\n    LOGD(\"closing re thread\\n\");\n    re_thread_async_close();\n\n    LOGD(\"closing libre\\n\");\n    libre_close();\n\n    // tmr_debug();\n    // mem_debug();\n\n    LOGD(\"sending stopped event\");\n    jstring javaError = (*env)->NewStringUTF(env, start_error);\n    jmethodID stoppedId =\n            (*env)->GetMethodID(env, g_ctx.mainActivityClz, \"stopped\", \"(Ljava/lang/String;)V\");\n    (*env)->CallVoidMethod(env, g_ctx.mainActivityObj, stoppedId, javaError);\n    (*env)->DeleteLocalRef(env, javaError);\n\n    (*env)->ReleaseStringUTFChars(env, jPath, path);\n    (*env)->ReleaseStringUTFChars(env, jAddrs, addrs);\n    (*env)->ReleaseStringUTFChars(env, jSoftware, software);\n}\n\nJNIEXPORT void JNICALL Java_com_tutpro_baresip_BaresipService_baresipStop(\n        JNIEnv *env, jobject obj, jboolean force)\n{\n    (void)env;\n    (void)obj;\n    LOGD(\"ua_stop_all upon baresipStop\");\n    mqueue_push(mq, ID_UA_STOP_ALL, (void *)((long)force));\n}\n\nJNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_account_1display_1name(\n        JNIEnv *env, jobject obj, jlong acc)\n{\n    (void)obj;\n    if (acc) {\n        const char *dn = account_display_name((struct account *)acc);\n        if (dn)\n            return (*env)->NewStringUTF(env, dn);\n        else\n            return (*env)->NewStringUTF(env, \"\");\n    } else\n        return (*env)->NewStringUTF(env, \"\");\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1display_1name(\n        JNIEnv *env, jobject obj, jlong acc, jstring jDn)\n{\n    (void)obj;\n    const char *dn = (*env)->GetStringUTFChars(env, jDn, 0);\n    int res;\n    if (strlen(dn) > 0)\n        res = account_set_display_name((struct account *)acc, dn);\n    else\n        res = account_set_display_name((struct account *)acc, NULL);\n    (*env)->ReleaseStringUTFChars(env, jDn, dn);\n    return res;\n}\n\nJNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_account_1aor(\n        JNIEnv *env, jobject obj, jlong acc)\n{\n    (void)obj;\n    if (acc)\n        return (*env)->NewStringUTF(env, account_aor((struct account *)acc));\n    else\n        return (*env)->NewStringUTF(env, \"\");\n}\n\nJNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_account_1luri(\n        JNIEnv *env, jobject obj, jlong acc)\n{\n    (void)obj;\n    const struct uri *uri = account_luri((struct account *)acc);\n    char uri_buf[512];\n    int l;\n    l = re_snprintf(&(uri_buf[0]), 511, \"%H\", uri_encode, uri);\n    if (l != -1)\n        uri_buf[l] = '\\0';\n    else\n        uri_buf[0] = '\\0';\n    return (*env)->NewStringUTF(env, uri_buf);\n}\n\nJNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_account_1auth_1user(\n        JNIEnv *env, jobject obj, jlong acc)\n{\n    (void)obj;\n    if (acc) {\n        const char *au = account_auth_user((struct account *)acc);\n        if (au)\n            return (*env)->NewStringUTF(env, au);\n        else\n            return (*env)->NewStringUTF(env, \"\");\n    } else\n        return (*env)->NewStringUTF(env, \"\");\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1auth_1user(\n        JNIEnv *env, jobject obj, jlong acc, jstring jUser)\n{\n    (void)obj;\n    const char *user = (*env)->GetStringUTFChars(env, jUser, 0);\n    int res;\n    if (strlen(user) > 0)\n        res = account_set_auth_user((struct account *)acc, user);\n    else\n        res = account_set_auth_user((struct account *)acc, NULL);\n    (*env)->ReleaseStringUTFChars(env, jUser, user);\n    return res;\n}\n\nJNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_account_1auth_1pass(\n        JNIEnv *env, jobject obj, jlong acc)\n{\n    (void)obj;\n    if (acc) {\n        const char *ap = account_auth_pass((struct account *)acc);\n        if (ap)\n            return (*env)->NewStringUTF(env, ap);\n        else\n            return (*env)->NewStringUTF(env, \"\");\n    } else\n        return (*env)->NewStringUTF(env, \"\");\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1auth_1pass(\n        JNIEnv *env, jobject obj, jlong acc, jstring jPass)\n{\n    (void)obj;\n    const char *pass = (*env)->GetStringUTFChars(env, jPass, 0);\n    int res;\n    if (strlen(pass) > 0)\n        res = account_set_auth_pass((struct account *)acc, pass);\n    else\n        res = account_set_auth_pass((struct account *)acc, NULL);\n    (*env)->ReleaseStringUTFChars(env, jPass, pass);\n    return res;\n}\n\nJNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_account_1outbound(\n        JNIEnv *env, jobject obj, jlong acc, jint jIx)\n{\n    (void)obj;\n    const uint16_t native_ix = jIx;\n    const char *outbound;\n    if (acc) {\n        outbound = account_outbound((struct account *)acc, native_ix);\n        if (outbound)\n            return (*env)->NewStringUTF(env, outbound);\n        else\n            return (*env)->NewStringUTF(env, \"\");\n    } else {\n        return (*env)->NewStringUTF(env, \"\");\n    }\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1outbound(\n        JNIEnv *env, jobject obj, jlong acc, jstring jOb, jint jIx)\n{\n    (void)obj;\n    const char *ob = (*env)->GetStringUTFChars(env, jOb, 0);\n    const uint16_t ix = jIx;\n    int res;\n    if (strlen(ob) > 0)\n        res = account_set_outbound((struct account *)acc, ob, ix);\n    else\n        res = account_set_outbound((struct account *)acc, NULL, ix);\n    (*env)->ReleaseStringUTFChars(env, jOb, ob);\n    return res;\n}\n\nJNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_account_1audio_1codec(\n        JNIEnv *env, jobject obj, jlong acc, jint ix)\n{\n    (void)obj;\n    const struct list *codecl;\n    char codec_buf[32];\n    int len;\n    struct le *le;\n    codec_buf[0] = '\\0';\n    if (acc) {\n        codecl = account_aucodecl((struct account *)acc);\n        if (!list_isempty(codecl)) {\n            int i = -1;\n            for (le = list_head(codecl); le != NULL; le = le->next) {\n                i++;\n                if (i == ix) {\n                    const struct aucodec *ac = le->data;\n                    len = re_snprintf(\n                            codec_buf, sizeof codec_buf, \"%s/%u/%u\", ac->name, ac->srate, ac->ch);\n                    if (len == -1) {\n                        LOGE(\"failed to print audio codec to buffer\\n\");\n                        codec_buf[0] = '\\0';\n                    }\n                    break;\n                }\n            }\n        }\n    }\n    return (*env)->NewStringUTF(env, codec_buf);\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1audio_1codecs(\n        JNIEnv *env, jobject obj, jlong acc, jstring jCodecs)\n{\n    (void)obj;\n    const char *codecs = (*env)->GetStringUTFChars(env, jCodecs, 0);\n    int res = account_set_audio_codecs((struct account *)acc, codecs);\n    (*env)->ReleaseStringUTFChars(env, jCodecs, codecs);\n    return res;\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1video_1codecs(\n        JNIEnv *env, jobject obj, jlong acc, jstring jCodecs)\n{\n    (void)obj;\n    const char *codecs = (*env)->GetStringUTFChars(env, jCodecs, 0);\n    int res = account_set_video_codecs((struct account *)acc, codecs);\n    (*env)->ReleaseStringUTFChars(env, jCodecs, codecs);\n    return res;\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1regint(\n        JNIEnv *env, jobject obj, jlong acc)\n{\n    (void)env;\n    (void)obj;\n    if (acc)\n        return (jint)account_regint((struct account *)acc);\n    else\n        return 0;\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1regint(\n        JNIEnv *env, jobject obj, jlong acc, jint jRegint)\n{\n    (void)env;\n    (void)obj;\n    const uint32_t regint = (uint32_t)jRegint;\n    return account_set_regint((struct account *)acc, regint);\n}\n\nJNIEXPORT jboolean JNICALL Java_com_tutpro_baresip_Api_account_1check_1origin(\n        JNIEnv *env, jobject obj, jlong acc)\n{\n    (void)env;\n    (void)obj;\n    return account_check_origin((struct account *)acc);\n}\n\nJNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_account_1set_1check_1origin(\n        JNIEnv *env, jobject obj, jlong acc, jboolean value)\n{\n    (void)env;\n    (void)obj;\n    account_set_check_origin((struct account *)acc, value);\n}\n\nJNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_account_1mediaenc(\n        JNIEnv *env, jobject obj, jlong acc)\n{\n    (void)obj;\n    if (acc) {\n        const char *mediaenc = account_mediaenc((struct account *)acc);\n        if (mediaenc)\n            return (*env)->NewStringUTF(env, mediaenc);\n    }\n    return (*env)->NewStringUTF(env, \"\");\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1mediaenc(\n        JNIEnv *env, jobject obj, jlong acc, jstring jMencid)\n{\n    (void)obj;\n    const char *mencid = (*env)->GetStringUTFChars(env, jMencid, 0);\n    int res;\n    if (strlen(mencid) > 0)\n        res = account_set_mediaenc((struct account *)acc, mencid);\n    else\n        res = account_set_mediaenc((struct account *)acc, NULL);\n    (*env)->ReleaseStringUTFChars(env, jMencid, mencid);\n    return res;\n}\n\nJNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_account_1medianat(\n        JNIEnv *env, jobject obj, jlong acc)\n{\n    (void)obj;\n    if (acc) {\n        const char *medianat = account_medianat((struct account *)acc);\n        if (medianat)\n            return (*env)->NewStringUTF(env, medianat);\n    }\n    return (*env)->NewStringUTF(env, \"\");\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1medianat(\n        JNIEnv *env, jobject obj, jlong acc, jstring jMedNat)\n{\n    (void)obj;\n    const char *mednat = (*env)->GetStringUTFChars(env, jMedNat, 0);\n    int res;\n    if (strlen(mednat) > 0)\n        res = account_set_medianat((struct account *)acc, mednat);\n    else\n        res = account_set_medianat((struct account *)acc, NULL);\n    (*env)->ReleaseStringUTFChars(env, jMedNat, mednat);\n    return res;\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1sipnat(\n        JNIEnv *env, jobject obj, jlong acc, jstring jSipNat)\n{\n    (void)obj;\n    const char *sipnat = (*env)->GetStringUTFChars(env, jSipNat, 0);\n    int res;\n    if (strlen(sipnat) > 0)\n        res = account_set_sipnat((struct account *)acc, sipnat);\n    else\n        res = account_set_sipnat((struct account *)acc, NULL);\n    (*env)->ReleaseStringUTFChars(env, jSipNat, sipnat);\n    return res;\n}\n\nJNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_account_1stun_1uri(\n        JNIEnv *env, jobject obj, jlong acc)\n{\n    (void)obj;\n    if (acc) {\n        const struct stun_uri *stun_uri = account_stun_uri((struct account *)acc);\n        if (stun_uri) {\n            char uri_str[256];\n            if (stun_uri->port != 0) {\n                if (stun_uri->proto == IPPROTO_TCP)\n                    sprintf(uri_str, \"%s:%s:%d?transport=tcp\",\n                            stunuri_scheme_name(stun_uri->scheme), stun_uri->host, stun_uri->port);\n                else\n                    sprintf(uri_str, \"%s:%s:%d\", stunuri_scheme_name(stun_uri->scheme),\n                            stun_uri->host, stun_uri->port);\n            } else {\n                if (stun_uri->proto == IPPROTO_TCP)\n                    sprintf(uri_str, \"%s:%s?transport=tcp\", stunuri_scheme_name(stun_uri->scheme),\n                            stun_uri->host);\n                else\n                    sprintf(uri_str, \"%s:%s\", stunuri_scheme_name(stun_uri->scheme),\n                            stun_uri->host);\n            }\n            return (*env)->NewStringUTF(env, uri_str);\n        }\n    }\n    return (*env)->NewStringUTF(env, \"\");\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1stun_1uri(\n        JNIEnv *env, jobject obj, jlong acc, jstring jUri)\n{\n    (void)obj;\n    const char *uri = (*env)->GetStringUTFChars(env, jUri, 0);\n    int res;\n    if (strlen(uri) > 0)\n        res = account_set_stun_uri((struct account *)acc, uri);\n    else\n        res = account_set_stun_uri((struct account *)acc, NULL);\n    (*env)->ReleaseStringUTFChars(env, jUri, uri);\n    return res;\n}\n\nJNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_account_1stun_1user(\n        JNIEnv *env, jobject obj, jlong acc)\n{\n    (void)obj;\n    if (acc) {\n        const char *stun_user = account_stun_user((struct account *)acc);\n        if (stun_user)\n            return (*env)->NewStringUTF(env, stun_user);\n    }\n    return (*env)->NewStringUTF(env, \"\");\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1stun_1user(\n        JNIEnv *env, jobject obj, jlong acc, jstring jUser)\n{\n    (void)obj;\n    const char *user = (*env)->GetStringUTFChars(env, jUser, 0);\n    int res;\n    if (strlen(user) > 0)\n        res = account_set_stun_user((struct account *)acc, user);\n    else\n        res = account_set_stun_user((struct account *)acc, NULL);\n    (*env)->ReleaseStringUTFChars(env, jUser, user);\n    return res;\n}\n\nJNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_account_1stun_1pass(\n        JNIEnv *env, jobject obj, jlong acc)\n{\n    (void)obj;\n    if (acc) {\n        const char *stun_pass = account_stun_pass((struct account *)acc);\n        if (stun_pass)\n            return (*env)->NewStringUTF(env, stun_pass);\n    }\n    return (*env)->NewStringUTF(env, \"\");\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1stun_1pass(\n        JNIEnv *env, jobject obj, jlong acc, jstring jPass)\n{\n    (void)obj;\n    const char *pass = (*env)->GetStringUTFChars(env, jPass, 0);\n    int res;\n    if (strlen(pass) > 0)\n        res = account_set_stun_pass((struct account *)acc, pass);\n    else\n        res = account_set_stun_pass((struct account *)acc, NULL);\n    (*env)->ReleaseStringUTFChars(env, jPass, pass);\n    return res;\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1mwi(\n        JNIEnv *env, jobject obj, jlong acc, jboolean value)\n{\n    (void)env;\n    (void)obj;\n    return account_set_mwi((struct account *)acc, value);\n}\n\nJNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_account_1vm_1uri(\n        JNIEnv *env, jobject obj, jlong acc)\n{\n    (void)obj;\n    char uri_buf[256];\n    if (acc) {\n        struct pl pl;\n        const struct sip_addr *addr = account_laddr((struct account *)acc);\n        int err = msg_param_decode(&(addr->params), \"vm_uri\", &pl);\n        if (err) {\n            return (*env)->NewStringUTF(env, \"\");\n        } else {\n            pl_strcpy(&pl, uri_buf, 256);\n            return (*env)->NewStringUTF(env, uri_buf);\n        }\n    } else\n        return (*env)->NewStringUTF(env, \"\");\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1answermode(\n        JNIEnv *env, jobject obj, jlong acc)\n{\n    (void)env;\n    (void)obj;\n    return account_answermode((struct account *)acc);\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1answermode(\n        JNIEnv *env, jobject obj, jlong acc, jint jMode)\n{\n    (void)env;\n    (void)obj;\n    const uint32_t mode = (uint32_t)jMode;\n    return account_set_answermode((struct account *)acc, mode);\n}\n\nJNIEXPORT jboolean JNICALL Java_com_tutpro_baresip_Api_account_1sip_1autoredirect(\n        JNIEnv *env, jobject obj, jlong acc)\n{\n    (void)env;\n    (void)obj;\n    return account_sip_autoredirect((struct account *)acc);\n}\n\nJNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_account_1set_1sip_1autoredirect(\n        JNIEnv *env, jobject obj, jlong acc, jboolean allow)\n{\n    (void)env;\n    (void)obj;\n    return account_set_sip_autoredirect((struct account *)acc, allow);\n}\n\nJNIEXPORT jboolean JNICALL Java_com_tutpro_baresip_Api_account_1rtcp_1mux(\n        JNIEnv *env, jobject obj, jlong acc)\n{\n    (void)env;\n    (void)obj;\n    return account_rtcp_mux((struct account *)acc);\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1rtcp_1mux(\n        JNIEnv *env, jobject obj, jlong acc, jboolean value)\n{\n    (void)env;\n    (void)obj;\n    return account_set_rtcp_mux((struct account *)acc, value);\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1rel100_1mode(\n        JNIEnv *env, jobject obj, jlong acc)\n{\n    (void)env;\n    (void)obj;\n    return account_rel100_mode((struct account *)acc);\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1rel100_1mode(\n        JNIEnv *env, jobject obj, jlong acc, jint jMode)\n{\n    (void)env;\n    (void)obj;\n    const uint32_t mode = (uint32_t)jMode;\n    return account_set_rel100_mode((struct account *)acc, mode);\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1dtmfmode(\n        JNIEnv *env, jobject obj, jlong acc)\n{\n    (void)env;\n    (void)obj;\n    return account_dtmfmode((struct account *)acc);\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1dtmfmode(\n        JNIEnv *env, jobject obj, jlong acc, jint jMode)\n{\n    (void)env;\n    (void)obj;\n    const uint32_t mode = (uint32_t)jMode;\n    return account_set_dtmfmode((struct account *)acc, mode);\n}\n\nJNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_account_1extra(\n        JNIEnv *env, jobject obj, jlong acc)\n{\n    (void)obj;\n    if (acc) {\n        const char *extra = account_extra((struct account *)acc);\n        if (extra)\n            return (*env)->NewStringUTF(env, extra);\n    }\n    return (*env)->NewStringUTF(env, \"\");\n}\n\nJNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_account_1debug(\n        JNIEnv *env, jobject obj, jlong acc)\n{\n    (void)env;\n    (void)obj;\n    account_debug_log((struct account *)acc);\n}\n\nJNIEXPORT jlong JNICALL Java_com_tutpro_baresip_Api_ua_1alloc(\n        JNIEnv *env, jobject obj, jstring jUri)\n{\n    (void)obj;\n    const char *uri = (*env)->GetStringUTFChars(env, jUri, 0);\n    struct ua *ua;\n    LOGD(\"allocating UA '%s'\\n\", uri);\n    re_thread_enter();\n    int res = ua_alloc(&ua, uri);\n    re_thread_leave();\n    if (res == 0) {\n        LOGD(\"allocated ua '%ld'\\n\", (long)ua);\n    } else {\n        LOGE(\"failed to allocate ua '%s'\\n\", uri);\n    }\n    (*env)->ReleaseStringUTFChars(env, jUri, uri);\n    return (jlong)ua;\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_ua_1register(JNIEnv *env, jobject obj, jlong ua)\n{\n    (void)env;\n    (void)obj;\n    LOGD(\"registering UA '%ld'\\n\", (long)ua);\n    re_thread_enter();\n    int res = ua_register((struct ua *)ua);\n    re_thread_leave();\n    return res;\n}\n\nJNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_ua_1unregister(\n        JNIEnv *env, jobject obj, jlong ua)\n{\n    (void)env;\n    (void)obj;\n    re_thread_enter();\n    ua_unregister((struct ua *)ua);\n    re_thread_leave();\n}\n\nJNIEXPORT jboolean JNICALL Java_com_tutpro_baresip_Api_ua_1isregistered(\n        JNIEnv *env, jobject obj, jlong ua)\n{\n    (void)env;\n    (void)obj;\n    return ua_isregistered((struct ua *)ua) ? true : false;\n}\n\nJNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_ua_1destroy(JNIEnv *env, jobject obj, jlong ua)\n{\n    (void)env;\n    (void)obj;\n    LOGD(\"destroying ua %ld\\n\", (long)ua);\n    re_thread_enter();\n    (void)ua_destroy((struct ua *)ua);\n    re_thread_leave();\n}\n\nJNIEXPORT jlong JNICALL Java_com_tutpro_baresip_Api_ua_1account(JNIEnv *env, jobject obj, jlong ua)\n{\n    (void)env;\n    (void)obj;\n    struct account *acc = 0;\n    if (ua)\n        acc = ua_account((struct ua *)ua);\n    return (jlong)acc;\n}\n\nJNIEXPORT jlong JNICALL Java_com_tutpro_baresip_Api_ua_1update_1account(\n        JNIEnv *env, jobject obj, jlong ua)\n{\n    (void)env;\n    (void)obj;\n    LOGD(\"updating account of ua %ld\\n\", (long)ua);\n    return ua_update_account((struct ua *)ua);\n}\n\nJNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_ua_1hangup(\n        JNIEnv *env, jobject obj, jlong ua, jlong call, jint code, jstring reason)\n{\n    (void)obj;\n    const uint16_t native_code = code;\n    const char *native_reason = (*env)->GetStringUTFChars(env, reason, 0);\n    LOGD(\"hanging up call %ld/%ld\\n\", (long)ua, (long)call);\n    re_thread_enter();\n    if (strlen(native_reason) == 0)\n        ua_hangup((struct ua *)ua, (struct call *)call, native_code, NULL);\n    else\n        ua_hangup((struct ua *)ua, (struct call *)call, native_code, native_reason);\n    re_thread_leave();\n    (*env)->ReleaseStringUTFChars(env, reason, native_reason);\n}\n\nJNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_ua_1accept(\n        JNIEnv *env, jobject obj, jlong ua, jlong msg)\n{\n    (void)env;\n    (void)obj;\n    int err;\n    char *from_uri;\n    pl_strdup(&from_uri, &((struct sip_msg *)msg)->from.auri);\n    LOGD(\"accepting incoming call for ua %ld from %s\\n\", (long)ua, from_uri);\n    mem_deref(from_uri);\n    re_thread_enter();\n    err = ua_accept((struct ua *)ua, (struct sip_msg *)msg);\n    re_thread_leave();\n    if (err)\n        LOGW(\"accepting incoming call for ua %ld failed with error %d\\n\", (long)ua, err);\n}\n\nJNIEXPORT jlong JNICALL Java_com_tutpro_baresip_Api_ua_1call_1alloc(\n        JNIEnv *env, jobject obj, jlong ua, jlong xCall, jint vidMode)\n{\n    (void)env;\n    (void)obj;\n    struct call *call = NULL;\n    int err;\n    LOGD(\"allocating new call for ua %ld xcall %ld\\n\", (long)ua, (long)xCall);\n    re_thread_enter();\n    err = ua_call_alloc(&call, (struct ua *)ua, (enum vidmode)vidMode, NULL, (struct call *)xCall,\n            call_localuri((struct call *)xCall), true);\n    re_thread_leave();\n    if (err)\n        LOGW(\"call allocation for ua %ld failed with error %d\\n\", (long)ua, err);\n    return (jlong)call;\n}\n\nJNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_ua_1answer(\n        JNIEnv *env, jobject obj, jlong ua, jlong call, jint vidMode)\n{\n    (void)env;\n    (void)obj;\n    LOGD(\"answering ua/call %ld/%ld with video mode %d\\n\", (long)ua, (long)call, vidMode);\n    re_thread_enter();\n    ua_answer((struct ua *)ua, (struct call *)call, (enum vidmode)vidMode);\n    re_thread_leave();\n}\n\nJNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_ua_1add_1custom_1header(\n        JNIEnv *env, jobject obj, jlong ua, jstring jname, jstring jbody)\n{\n    (void)obj;\n    const char *name = (*env)->GetStringUTFChars(env, jname, 0);\n    const char *body = (*env)->GetStringUTFChars(env, jbody, 0);\n    LOGD(\"adding header to %ld with name/body %s/%s\\n\", (long)ua, name, body);\n    re_thread_enter();\n    struct pl pl_name, pl_body;\n    pl_set_str(&pl_name, name);\n    pl_set_str(&pl_body, body);\n    int err = ua_add_custom_hdr((struct ua *)ua, &pl_name, &pl_body);\n    re_thread_leave();\n    if (err)\n        LOGW(\"adding custom header to ua %ld failed with  error %d\\n\", (long)ua, err);\n    (*env)->ReleaseStringUTFChars(env, jname, name);\n    (*env)->ReleaseStringUTFChars(env, jbody, body);\n}\n\nJNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_ua_1debug(JNIEnv *env, jobject obj, jlong ua)\n{\n    (void)env;\n    (void)obj;\n    ua_debug_log((struct ua *)ua);\n}\n\nJNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_sip_1treply(\n        JNIEnv *env, jobject obj, jlong msg, jint code, jstring reason)\n{\n    (void)obj;\n    const uint16_t native_code = code;\n    const char *native_reason = (*env)->GetStringUTFChars(env, reason, 0);\n    LOGD(\"replying with %d/%s\\n\", native_code, native_reason);\n    re_thread_enter();\n    (void)sip_treply(NULL, uag_sip(), (const struct sip_msg *)(struct msg *)msg,\n            native_code, native_reason);\n    re_thread_leave();\n    (*env)->ReleaseStringUTFChars(env, reason, native_reason);\n}\n\nJNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_bevent_1stop(\n        JNIEnv *env, jobject obj, jlong event)\n{\n    (void)env;\n    (void)obj;\n    re_thread_enter();\n    bevent_stop((struct bevent *)event);\n    re_thread_leave();\n}\n\nJNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_calls_1mute(\n        JNIEnv *env, jobject obj, jboolean mute)\n{\n    (void)env;\n    (void)obj;\n    struct le *ua_le;\n    struct le *call_le;\n    LOGD(\"muting calls %d\\n\", mute);\n    re_thread_enter();\n    for (ua_le = list_head(uag_list()); ua_le != NULL; ua_le = ua_le->next) {\n        const struct ua *ua = ua_le->data;\n        for (call_le = list_head(ua_calls(ua)); call_le != NULL; call_le = call_le->next) {\n            const struct call *call = call_le->data;\n            audio_mute(call_audio(call), mute);\n        }\n    }\n    re_thread_leave();\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_call_1connect(\n        JNIEnv *env, jobject obj, jlong call, jstring jPeer)\n{\n    (void)obj;\n    const char *native_peer = (*env)->GetStringUTFChars(env, jPeer, 0);\n    LOGD(\"connecting call %ld to %s\\n\", (long)call, native_peer);\n    re_thread_enter();\n    struct pl pl;\n    pl_set_str(&pl, native_peer);\n    int err = call_connect((struct call *)call, &pl);\n    re_thread_leave();\n    if (err)\n        LOGW(\"call_connect error: %d\\n\", err);\n    (*env)->ReleaseStringUTFChars(env, jPeer, native_peer);\n    return err;\n}\n\nJNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_call_1notify_1sipfrag(\n        JNIEnv *env, jobject obj, jlong call, jint code, jstring reason)\n{\n    (void)obj;\n    const uint16_t native_code = code;\n    const char *native_reason = (*env)->GetStringUTFChars(env, reason, 0);\n    LOGD(\"notifying call %ld/%s\\n\", (long)call, native_reason);\n    re_thread_enter();\n    (void)call_notify_sipfrag((struct call *)call, native_code, native_reason);\n    re_thread_leave();\n    (*env)->ReleaseStringUTFChars(env, reason, native_reason);\n}\n\nJNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_call_1start_1audio(\n        JNIEnv *env, jobject obj, jlong call)\n{\n    (void)env;\n    (void)obj;\n    LOGD(\"starting audio of call %ld\\n\", (long)call);\n    re_thread_enter();\n    struct audio *a = call_audio((struct call *)call);\n    if (!audio_started(a))\n        audio_update(a);\n    re_thread_leave();\n}\n\nJNIEXPORT jboolean JNICALL Java_com_tutpro_baresip_Api_call_1hold(\n        JNIEnv *env, jobject obj, jlong call, jboolean hold)\n{\n    (void)env;\n    (void)obj;\n    int err;\n    if (hold) {\n        LOGD(\"holding call %ld\\n\", (long)call);\n        re_thread_enter();\n        err = call_hold((struct call *)call, true);\n        re_thread_leave();\n    } else {\n        LOGD(\"resuming call %ld\\n\", (long)call);\n        re_thread_enter();\n        err = call_hold((struct call *)call, false);\n        re_thread_leave();\n    }\n    if (err)\n        LOGW(\"call_hold error: %d\\n\", err);\n    return err == 0;\n}\n\nJNIEXPORT jboolean JNICALL Java_com_tutpro_baresip_Api_call_1ismuted(\n        JNIEnv *env, jobject obj, jlong call)\n{\n    (void)env;\n    (void)obj;\n    return audio_ismuted(call_audio((struct call *)call));\n}\n\nJNIEXPORT jboolean JNICALL Java_com_tutpro_baresip_Api_call_1supported(\n        JNIEnv *env, jobject obj, jlong call, jint tags)\n{\n    (void)env;\n    (void)obj;\n    return call_supported((struct call *)call, tags);\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_call_1transfer(\n        JNIEnv *env, jobject obj, jlong call, jstring jPeer)\n{\n    (void)obj;\n    const char *native_peer = (*env)->GetStringUTFChars(env, jPeer, 0);\n    LOGD(\"transfering call %ld to %s\\n\", (long)call, native_peer);\n    re_thread_enter();\n    int err = call_transfer((struct call *)call, native_peer);\n    re_thread_leave();\n    if (err)\n        LOGW(\"call_transfer error: %d\\n\", err);\n    (*env)->ReleaseStringUTFChars(env, jPeer, native_peer);\n    return err;\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_call_1send_1digit(\n        JNIEnv *env, jobject obj, jlong call, jchar digit)\n{\n    (void)env;\n    (void)obj;\n    const uint16_t native_digit = digit;\n    LOGD(\"sending DTMF digit '%c' to call %ld\\n\", (char)native_digit, (long)call);\n    re_thread_enter();\n    int res = call_send_digit((struct call *)call, (char)native_digit);\n    if (!res)\n        res = call_send_digit((struct call *)call, KEYCODE_REL);\n    re_thread_leave();\n    return res;\n}\n\nJNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_call_1audio_1codecs(\n        JNIEnv *env, jobject obj, jlong call)\n{\n    (void)obj;\n    const struct aucodec *tx = audio_codec(call_audio((struct call *)call), true);\n    const struct aucodec *rx = audio_codec(call_audio((struct call *)call), false);\n    char codec_buf[256];\n    char *start = &(codec_buf[0]);\n    unsigned int left = sizeof codec_buf;\n    int len = -1;\n    if (tx && rx)\n        len = re_snprintf(start, left, \"%s/%u/%u,%s/%u/%u\", tx->name, tx->srate, tx->ch, rx->name,\n                rx->srate, rx->ch);\n    else {\n        LOGE(\"failed to get audio codecs of call %ld\\n\", (long)call);\n        len = re_snprintf(start, left, \"%s/%u/%u,%s/%u/%u\", '?', 0, 0, '?', 0, 0);\n    }\n    return (*env)->NewStringUTF(env, codec_buf);\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_call_1duration(\n        JNIEnv *env, jobject obj, jlong call)\n{\n    (void)env;\n    (void)obj;\n    return (jint)call_duration((struct call *)call);\n}\n\nJNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_call_1stats(\n        JNIEnv *env, jobject obj, jlong call, jstring jStream)\n{\n    (void)obj;\n    const char *native_stream = (*env)->GetStringUTFChars(env, jStream, 0);\n    const struct stream *s;\n    if (strcmp(native_stream, \"audio\") == 0)\n        s = audio_strm(call_audio((struct call *)call));\n    else\n        s = video_strm(call_video((struct call *)call));\n    const struct rtcp_stats *stats = stream_rtcp_stats(s);\n    char stats_buf[256];\n    int len;\n    if (stats) {\n        const double tx_rate = 1.0 * stream_metric_get_tx_bitrate(s) / 1000.0;\n        const double rx_rate = 1.0 * stream_metric_get_rx_bitrate(s) / 1000.0;\n        const double tx_avg_rate = 1.0 * stream_metric_get_tx_avg_bitrate(s) / 1000.0;\n        const double rx_avg_rate = 1.0 * stream_metric_get_rx_avg_bitrate(s) / 1000.0;\n        len = re_snprintf(&(stats_buf[0]), 256, \"%.1f/%.1f,%.1f/%.1f,%u/%u,%d/%d,%.1f/%.1f\",\n                tx_rate, rx_rate, tx_avg_rate, rx_avg_rate, stats->tx.sent, stats->rx.sent,\n                stats->tx.lost, stats->rx.lost, 1.0 * stats->tx.jit / 1000,\n                1.0 * stats->rx.jit / 1000);\n        if (len == -1) {\n            LOGE(\"failed to get stats of call %ld %s stream\\n\", (long)call, native_stream);\n            stats_buf[0] = '\\0';\n        }\n    }\n    (*env)->ReleaseStringUTFChars(env, jStream, native_stream);\n    return (*env)->NewStringUTF(env, stats_buf);\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_call_1state(\n        JNIEnv *env, jobject obj, jlong call)\n{\n    (void)env;\n    (void)obj;\n    return call_state((struct call *)call);\n}\n\nJNIEXPORT jboolean JNICALL Java_com_tutpro_baresip_Api_call_1replaces(\n        JNIEnv *env, jobject obj, jlong call)\n{\n    (void)env;\n    (void)obj;\n    return call_supported((struct call *)call, REPLACES) ? true : false;\n}\n\nJNIEXPORT jboolean JNICALL Java_com_tutpro_baresip_Api_call_1replace_1transfer(\n        JNIEnv *env, jobject obj, jlong xferCall, jlong call)\n{\n    (void)env;\n    (void)obj;\n    re_thread_enter();\n    int res = call_replace_transfer((struct call *)xferCall, (struct call *)call);\n    re_thread_leave();\n    return res == 0 ? true : false;\n}\n\nJNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_call_1peer_1uri(\n        JNIEnv *env, jobject obj, jlong call)\n{\n    (void)obj;\n    const char *uri = call_peeruri((struct call *)call);\n    if (uri)\n        return (*env)->NewStringUTF(env, uri);\n    return (*env)->NewStringUTF(env, \"\");\n}\n\nJNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_call_1diverter_1uri(\n        JNIEnv *env, jobject obj, jlong call)\n{\n    (void)obj;\n    const char *uri = call_diverteruri((struct call *)call);\n    if (uri)\n        return (*env)->NewStringUTF(env, uri);\n    return (*env)->NewStringUTF(env, \"\");\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_message_1send(\n        JNIEnv *env, jobject obj, jlong ua, jstring jPeer, jstring jMsg, jstring jTime)\n{\n    (void)obj;\n    const char *native_peer = (*env)->GetStringUTFChars(env, jPeer, 0);\n    const char *native_msg = (*env)->GetStringUTFChars(env, jMsg, 0);\n    const char *native_time = (*env)->GetStringUTFChars(env, jTime, 0);\n    LOGD(\"sending message from ua %ld to %s at %s\\n\", (long)ua, native_peer, native_time);\n    re_thread_enter();\n    int err = message_send(\n            (struct ua *)ua, native_peer, native_msg, send_resp_handler, (void *)native_time);\n    re_thread_leave();\n    if (err) {\n        LOGW(\"message_send failed with error %d\\n\", err);\n    }\n    (*env)->ReleaseStringUTFChars(env, jPeer, native_peer);\n    (*env)->ReleaseStringUTFChars(env, jMsg, native_msg);\n    return err;\n}\n\nJNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_call_1destroy(\n        JNIEnv *env, jobject obj, jlong call)\n{\n    (void)env;\n    (void)obj;\n    re_thread_enter();\n    mem_deref((struct call *)call);\n    re_thread_leave();\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_cmd_1exec(\n        JNIEnv *env, jobject obj, jstring javaCmd)\n{\n    (void)obj;\n    const char *native_cmd = (*env)->GetStringUTFChars(env, javaCmd, 0);\n    LOGD(\"processing command '%s'\\n\", native_cmd);\n    re_thread_enter();\n    int res = cmd_process_long(baresip_commands(), native_cmd, strlen(native_cmd), &pf_null, NULL);\n    re_thread_leave();\n    (*env)->ReleaseStringUTFChars(env, javaCmd, native_cmd);\n    return res;\n}\n\nJNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_audio_1codecs(JNIEnv *env, jobject obj)\n{\n    (void)obj;\n    struct list *aucodecl = baresip_aucodecl();\n    struct le *le;\n    char codec_buf[256];\n    char *start = &(codec_buf[0]);\n    unsigned int left = sizeof codec_buf;\n    int len;\n    for (le = list_head(aucodecl); le != NULL; le = le->next) {\n        const struct aucodec *ac = le->data;\n        if (start == &(codec_buf[0]))\n            len = re_snprintf(start, left, \"%s/%u/%u\", ac->name, ac->srate, ac->ch);\n        else\n            len = re_snprintf(start, left, \",%s/%u/%u\", ac->name, ac->srate, ac->ch);\n        if (len == -1) {\n            LOGE(\"failed to print codec to buffer\\n\");\n            codec_buf[0] = '\\0';\n            return (*env)->NewStringUTF(env, codec_buf);\n        }\n        start = start + len;\n        left = left - len;\n    }\n    *start = '\\0';\n    return (*env)->NewStringUTF(env, codec_buf);\n}\n\nJNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_video_1codecs(JNIEnv *env, jobject obj)\n{\n    (void)obj;\n    struct list *vidcodecl = baresip_vidcodecl();\n    struct le *le;\n    char codec_buf[256];\n    char *start = &(codec_buf[0]);\n    unsigned int left = sizeof codec_buf;\n    int len;\n    for (le = list_head(vidcodecl); le != NULL; le = le->next) {\n        const struct vidcodec *vc = le->data;\n        if (start == &(codec_buf[0]))\n            len = re_snprintf(start, left, \"%s\", vc->name);\n        else\n            len = re_snprintf(start, left, \",%s\", vc->name);\n        if (len == -1) {\n            LOGE(\"failed to print codec to buffer\\n\");\n            codec_buf[0] = '\\0';\n            return (*env)->NewStringUTF(env, codec_buf);\n        }\n        start = start + len;\n        left = left - len;\n    }\n    *start = '\\0';\n    return (*env)->NewStringUTF(env, codec_buf);\n}\n\nJNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_log_1level_1set(\n        JNIEnv *env, jobject obj, jint level)\n{\n    (void)env;\n    (void)obj;\n    const enum log_level native_level = (enum log_level)level;\n    LOGD(\"setting log level to '%u'\\n\", native_level);\n    log_level_set(native_level);\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_net_1use_1nameserver(\n        JNIEnv *env, jobject obj, jstring javaServers)\n{\n    (void)obj;\n    const char *native_servers = (*env)->GetStringUTFChars(env, javaServers, 0);\n    char servers[256];\n    char *server;\n    struct sa nsv[NET_MAX_NS];\n    uint32_t count = 0;\n    char *comma;\n    int res;\n    int err;\n    LOGD(\"setting dns servers '%s'\\n\", native_servers);\n    if (str_len(native_servers) > 255) {\n        LOGW(\"net_use_nameserver: too long servers list (%s)\\n\", native_servers);\n        return 1;\n    }\n    str_ncpy(servers, native_servers, 256);\n    (*env)->ReleaseStringUTFChars(env, javaServers, native_servers);\n    server = &(servers[0]);\n    while ((count < NET_MAX_NS) && ((comma = strchr(server, ',')) != NULL)) {\n        *comma = '\\0';\n        err = sa_decode(&(nsv[count]), server, str_len(server));\n        if (err) {\n            LOGW(\"net_use_nameserver: could not decode '%s' (%u)\\n\", server, err);\n            return err;\n        }\n        server = ++comma;\n        count++;\n    }\n    if ((count < NET_MAX_NS) && (str_len(server) > 0)) {\n        err = sa_decode(&(nsv[count]), server, str_len(server));\n        if (err) {\n            LOGW(\"net_use_nameserver: could not decode `%s' (%u)\\n\", server, err);\n            return err;\n        }\n        count++;\n    }\n    res = net_use_nameserver(baresip_network(), nsv, count);\n    return res;\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_net_1add_1address_1ifname(\n        JNIEnv *env, jobject obj, jstring jAddr, jstring jIfName)\n{\n    (void)obj;\n    const char *addr = (*env)->GetStringUTFChars(env, jAddr, 0);\n    const char *name = (*env)->GetStringUTFChars(env, jIfName, 0);\n    int res;\n    struct sa temp_sa;\n    char buf[256];\n    LOGD(\"adding address/ifname '%s/%s'\\n\", addr, name);\n    if (0 == sa_set_str(&temp_sa, addr, 0)) {\n        sa_ntop(&temp_sa, buf, 256);\n        re_thread_enter();\n        res = net_add_address_ifname(baresip_network(), &temp_sa, name);\n        re_thread_leave();\n    } else {\n        LOGE(\"invalid ip address %s\\n\", addr);\n        res = EAFNOSUPPORT;\n    }\n    (*env)->ReleaseStringUTFChars(env, jAddr, addr);\n    (*env)->ReleaseStringUTFChars(env, jIfName, name);\n    return res;\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_net_1rm_1address(\n        JNIEnv *env, jobject obj, jstring jIp)\n{\n    (void)obj;\n    const char *native_ip = (*env)->GetStringUTFChars(env, jIp, 0);\n    int res;\n    struct sa temp_sa;\n    char buf[256];\n    LOGD(\"removing address '%s'\\n\", native_ip);\n    if (str_len(native_ip) == 0) {\n        (*env)->ReleaseStringUTFChars(env, jIp, native_ip);\n        return 0;\n    }\n    if (0 == sa_set_str(&temp_sa, native_ip, 0)) {\n        sa_ntop(&temp_sa, buf, 256);\n        re_thread_enter();\n        res = net_rm_address(baresip_network(), &temp_sa);\n        re_thread_leave();\n    } else {\n        LOGE(\"invalid ip address %s\\n\", native_ip);\n        res = EAFNOSUPPORT;\n    }\n    (*env)->ReleaseStringUTFChars(env, jIp, native_ip);\n    return res;\n}\n\nJNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_uag_1reset_1transp(\n        JNIEnv *env, jobject obj, jboolean reg, jboolean reinvite)\n{\n    (void)env;\n    (void)obj;\n    LOGD(\"resetting transports (%d, %d)\\n\", reg, reinvite);\n    re_thread_enter();\n    (void)uag_reset_transp(reg, reinvite);\n    re_thread_leave();\n}\n\nJNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_uag_1enable_1sip_1trace(\n        JNIEnv *env, jobject obj, jboolean enable)\n{\n    (void)env;\n    (void)obj;\n    LOGD(\"enabling sip trace (%d)\\n\", enable);\n    uag_enable_sip_trace(enable);\n}\n\nJNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_config_1verify_1server_1set(\n        JNIEnv *env, jobject obj, jboolean verify)\n{\n    (void)env;\n    (void)obj;\n    struct config *conf = conf_config();\n    LOGD(\"setting verify_server (%d)\\n\", verify);\n    conf->sip.verify_server = verify;\n}\n\nJNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_net_1debug(JNIEnv *env, jobject obj)\n{\n    (void)env;\n    (void)obj;\n    re_thread_enter();\n    net_debug_log();\n    re_thread_leave();\n}\n\nJNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_net_1dns_1debug(JNIEnv *env, jobject obj)\n{\n    (void)env;\n    (void)obj;\n    re_thread_enter();\n    net_dns_debug_log();\n    re_thread_leave();\n}\n\nJNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_module_1load(\n        JNIEnv *env, jobject obj, jstring javaModule)\n{\n    (void)obj;\n    const char *native_module = (*env)->GetStringUTFChars(env, javaModule, 0);\n    int result = module_load(\".\", native_module);\n    (*env)->ReleaseStringUTFChars(env, javaModule, native_module);\n    return result;\n}\n\nJNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_module_1unload(\n        JNIEnv *env, jobject obj, jstring javaModule)\n{\n    (void)obj;\n    const char *native_module = (*env)->GetStringUTFChars(env, javaModule, 0);\n    module_unload(native_module);\n    LOGD(\"unloaded module %s\\n\", native_module);\n    (*env)->ReleaseStringUTFChars(env, javaModule, native_module);\n}\n\nAAudioStream *AAudio_stream = NULL;\n\nJNIEXPORT jint JNICALL\nJava_com_tutpro_baresip_Api_AAudio_1open_1stream(JNIEnv *env, jobject obj) {\n    AAudioStreamBuilder *builder = NULL;\n    jint sessionId = -1;\n\n    if (AAudio_createStreamBuilder(&builder) != AAUDIO_OK) {\n        return -1;\n    }\n\n    AAudioStreamBuilder_setDirection(builder, AAUDIO_DIRECTION_INPUT);\n    AAudioStreamBuilder_setSharingMode(builder, AAUDIO_SHARING_MODE_SHARED);\n    AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);\n    AAudioStreamBuilder_setUsage(builder,AAUDIO_USAGE_VOICE_COMMUNICATION);\n    AAudioStreamBuilder_setInputPreset(builder, AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION);\n    AAudioStreamBuilder_setFormat(builder, AAUDIO_FORMAT_PCM_I16);\n    AAudioStreamBuilder_setSampleRate(builder, 16000);\n    AAudioStreamBuilder_setChannelCount(builder, 1);\n    AAudioStreamBuilder_setSessionId(builder, AAUDIO_SESSION_ID_ALLOCATE);\n\n    if (AAudioStreamBuilder_openStream(builder, &AAudio_stream) == AAUDIO_OK)\n        sessionId = AAudioStream_getSessionId(AAudio_stream);\n    else {\n        LOGE(\"Failed to open AAudio stream\\n\");\n        AAudio_stream = NULL;\n    }\n    AAudioStreamBuilder_delete(builder);\n\n    return sessionId;\n}\n\nJNIEXPORT void JNICALL\nJava_com_tutpro_baresip_Api_AAudio_1close_1stream(JNIEnv *env, jobject obj)\n{\n    if (AAudio_stream != NULL) {\n        AAudioStream_close(AAudio_stream);\n        AAudio_stream = NULL;\n    }\n}\n"
  },
  {
    "path": "app/src/main/cpp/logger.h",
    "content": "#include <baresip.h>\n#include <android/log.h>\n\n#ifndef BARESIP_LOGGER_H\n\n    #define BARESIP_LOGGER_H\n\n    #define LOG_TAG \"Baresip Lib\"\n\n    #define LOGD(...)                     \\\n        if (log_level_get() < LEVEL_INFO) \\\n        ((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))\n\n    #define LOGI(...)                     \\\n        if (log_level_get() < LEVEL_WARN) \\\n        ((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))\n\n    #define LOGW(...)                      \\\n        if (log_level_get() < LEVEL_ERROR) \\\n        ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__))\n\n    #define LOGE(...)                       \\\n        if (log_level_get() <= LEVEL_ERROR) \\\n        ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))\n\n#endif //BARESIP_LOGGER_H\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/AboutScreen.kt",
    "content": "package com.tutpro.baresip\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.asPaddingValues\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.imePadding\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.statusBars\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.TopAppBarDefaults\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.text.AnnotatedString\nimport androidx.compose.ui.text.SpanStyle\nimport androidx.compose.ui.text.TextLinkStyles\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.fromHtml\nimport androidx.navigation.NavController\nimport androidx.navigation.NavGraphBuilder\nimport androidx.navigation.compose.composable\n\nfun NavGraphBuilder.aboutScreenRoute(navController: NavController) {\n    composable(\"about\") {\n        AboutScreen(onBack = { navController.navigateUp() })\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun AboutScreen(onBack: () -> Unit) {\n    Scaffold(\n        modifier = Modifier.fillMaxSize().imePadding(),\n        containerColor = MaterialTheme.colorScheme.background,\n        topBar = {\n            Column(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .background(MaterialTheme.colorScheme.background)\n                    .padding(\n                        top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()\n                    )\n            ) {\n                TopAppBar(\n                    title = {\n                        Text(\n                            text = stringResource(R.string.about_title),\n                            fontWeight = FontWeight.Bold\n                        )\n                    },\n                    navigationIcon = {\n                        IconButton(onClick = onBack) {\n                            Icon(\n                                imageVector = Icons.AutoMirrored.Filled.ArrowBack,\n                                contentDescription = null,\n                            )\n                        }\n                    },\n                    colors = TopAppBarDefaults.topAppBarColors(\n                        containerColor = MaterialTheme.colorScheme.primary,\n                        titleContentColor = MaterialTheme.colorScheme.onPrimary,\n                        navigationIconContentColor = MaterialTheme.colorScheme.onPrimary,\n                    ),\n                    windowInsets = WindowInsets(0, 0, 0, 0)\n                )\n            }\n        }\n    ) { contentPadding ->\n        Text(\n            text = AnnotatedString.fromHtml(\n                htmlString = stringResource(R.string.about_text, BuildConfig.VERSION_NAME),\n                linkStyles = TextLinkStyles(SpanStyle(color = MaterialTheme.colorScheme.error))\n            ),\n            color = MaterialTheme.colorScheme.onBackground,\n            modifier = Modifier\n                .padding(horizontal = 16.dp, vertical = 16.dp)\n                .padding(contentPadding)\n                .verticalScroll(rememberScrollState())\n                .fillMaxSize()\n        )\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/Account.kt",
    "content": "package com.tutpro.baresip\n\nimport android.content.Context\nimport java.net.URLDecoder\nimport java.net.URLEncoder\nimport java.util.Locale\n\nclass Account(val accp: Long) {\n\n    var nickName = \"\"\n    var displayName = Api.account_display_name(accp)\n    val aor = Api.account_aor(accp)\n    val luri = Api.account_luri(accp)\n    var authUser = Api.account_auth_user(accp)\n    var authPass = Api.account_auth_pass(accp)\n    var outbound = ArrayList<String>()\n    var mediaNat = Api.account_medianat(accp)\n    var stunServer = Api.account_stun_uri(accp)\n    var stunUser = Api.account_stun_user(accp)\n    var stunPass = Api.account_stun_pass(accp)\n    var audioCodec = ArrayList<String>()\n    var videoCodec = ArrayList<String>()\n    var regint = Api.account_regint(accp)\n    var checkOrigin = Api.account_check_origin(accp)\n    var configuredRegInt = REGISTRATION_INTERVAL\n    var mediaEnc = Api.account_mediaenc(accp)\n    var rtcpMux = Api.account_rtcp_mux(accp)\n    var rel100Mode = Api.account_rel100_mode(accp)\n    var dtmfMode = Api.account_dtmfmode(accp)\n    var answerMode = Api.account_answermode(accp)\n    var autoRedirect = Api.account_sip_autoredirect(accp)\n    var blockUnknown = false\n    var vmUri = Api.account_vm_uri(accp)\n    var vmNew = 0\n    var vmOld = 0\n    var missedCalls = false\n    var unreadMessages = false\n    var callHistory = true\n    var countryCode = \"\"\n    var telProvider = Utils.aorDomain(aor)\n    var resumeUri = \"\"\n    var numericKeypad = false\n    var customParams = \"\"\n\n    init {\n\n        if (authPass == \"\")\n            authPass = NO_AUTH_PASS\n\n        var i = 0\n        while (true) {\n            val ob = Api.account_outbound(accp, i)\n            if (ob != \"\") {\n                outbound.add(ob)\n                i++\n            } else {\n                break\n            }\n        }\n\n        i = 0\n        while (true) {\n            val ac = Api.account_audio_codec(accp, i)\n            if (ac != \"\") {\n                audioCodec.add(ac)\n                i++\n            } else {\n                break\n            }\n        }\n\n        val extra = Api.account_extra(accp)\n        if (Utils.paramExists(extra, \"nickname\"))\n            nickName = Utils.paramValue(extra, \"nickname\")\n        if (Utils.paramExists(extra, \"regint\"))\n            configuredRegInt = Utils.paramValue(extra, \"regint\").toInt()\n        callHistory = Utils.paramValue(extra, \"call_history\") == \"\"\n        blockUnknown= Utils.paramExists(extra, \"block_unknown\")\n        if (Utils.paramExists(extra, \"country_code\"))\n            countryCode = Utils.paramValue(extra, \"country_code\")\n        if (Utils.paramExists(extra, \"tel_provider\"))\n            telProvider = URLDecoder.decode(Utils.paramValue(extra, \"tel_provider\"), \"UTF-8\")\n        numericKeypad = Utils.paramExists(extra, \"numeric_keypad\")\n        customParams = extra.substringAfter(\"last=empty\").substringAfter(\";\")\n    }\n\n    fun print() : String {\n\n        var res = if (displayName != \"\")\n            \"\\\"${displayName}\\\" \"\n        else\n            \"\"\n\n        res = \"$res<$luri>\"\n\n        if (authUser != \"\") res += \";auth_user=\\\"${authUser}\\\"\"\n\n        if ((authPass != \"\") && !BaresipService.aorPasswords.containsKey(aor))\n            res += \";auth_pass=\\\"${authPass}\\\"\"\n\n        if (outbound.isNotEmpty()) {\n            res += \";outbound=\\\"${outbound[0]}\\\"\"\n            if (outbound.size > 1) res += \";outbound2=\\\"${outbound[1]}\\\"\"\n            res = \"$res;sipnat=outbound\"\n        }\n\n        if (!checkOrigin) res += \";check_origin=no\"\n\n        if (mediaNat != \"\") res += \";medianat=${mediaNat}\"\n\n        if (stunServer != \"\")\n            res += \";stunserver=\\\"${stunServer}\\\"\"\n\n        if (stunUser != \"\")\n            res += \";stunuser=\\\"${stunUser}\\\"\"\n\n        if (stunPass != \"\")\n            res += \";stunpass=\\\"${stunPass}\\\"\"\n\n        if (audioCodec.isNotEmpty()) {\n            var first = true\n            res = \"$res;audio_codecs=\"\n            for (c in audioCodec)\n                if (first) {\n                    res += c\n                    first = false\n                } else {\n                    res = \"$res,$c\"\n                }\n        }\n\n        if (mediaEnc != \"\") res += \";mediaenc=${mediaEnc}\"\n\n        if (rtcpMux)\n            res += \";rtcp_mux=yes\"\n\n        res += if (rel100Mode == Api.REL100_ENABLED)\n            \";100rel=yes\"\n        else\n            \";100rel=no\"\n\n        if (dtmfMode == Api.DTMFMODE_SIP_INFO)\n            res += \";dtmfmode=info\"\n        else if (dtmfMode == Api.DTMFMODE_AUTO)\n            res += \";dtmfmode=auto\"\n\n        res = if (vmUri == \"\")\n            \"$res;mwi=no\"\n        else\n            \"$res;mwi=yes;vm_uri=\\\"$vmUri\\\"\"\n\n        if (answerMode == Api.ANSWERMODE_AUTO)\n            res += \";answermode=auto\"\n\n        if (autoRedirect)\n            res += \";sip_autoredirect=yes\"\n\n        res += \";ptime=20;regint=${regint};regq=0.5;pubint=0;inreq_allowed=yes;call_transfer=yes\"\n\n        var extra = \"\"\n\n        if (nickName != \"\")\n            extra += \";nickname=${nickName}\"\n\n        if (!callHistory)\n            extra += \";call_history=no\"\n\n        if (blockUnknown)\n            extra += \";block_unknown=yes\"\n\n        if (telProvider != \"\")\n            extra += \";tel_provider=${URLEncoder.encode(telProvider, \"UTF-8\")}\"\n\n        if (countryCode != \"\")\n            extra += \";country_code=$countryCode\"\n\n        if (configuredRegInt != REGISTRATION_INTERVAL)\n            extra += \";regint=$configuredRegInt\"\n\n        if (numericKeypad)\n            extra += \";numeric_keypad=yes\"\n\n        extra += \";last=empty\"\n\n        if (customParams != \"\")\n            extra += \";$customParams\"\n\n        res += \";extra=\\\"\" + extra.substringAfter(\";\") + \"\\\"\"\n\n        if (customParams != \"\")\n            res += \";$customParams\"\n\n        return res\n    }\n\n    fun vmMessages(cxt: Context) : String {\n\n        val new = if (vmNew > 0) {\n            if (vmNew == 1)\n                cxt.getString(R.string.one_new_message)\n            else\n                \"$vmNew ${cxt.getString(R.string.new_messages)}\"\n        } else\n            \"\"\n\n        val old = if (vmOld > 0) {\n            if (vmOld == 1)\n                cxt.getString(R.string.one_old_message)\n            else\n                    \"$vmOld ${cxt.getString(R.string.old_messages)}\"\n        } else\n            \"\"\n\n        var msg = cxt.getString(R.string.you_have)\n        if (new != \"\") {\n            msg = \"$msg $new\"\n            if (old != \"\") msg = \"$msg ${cxt.getString(R.string.and)} $old\"\n        } else {\n            msg = if (old != \"\")\n                \"$msg $old\"\n            else\n                cxt.getString(R.string.no_messages)\n        }\n\n         return \"$msg.\"\n    }\n\n    fun host() : String {\n        return aor.split(\"@\")[1]\n    }\n\n    fun text(): String {\n        return if (nickName != \"\")\n            nickName\n        else\n            aor.split(\":\")[1].substringBefore(\";\")\n    }\n\n    private fun removeAudioCodecsStartingWith(prefix: String) {\n        val newCodecs = ArrayList<String>()\n        for (acSpec in audioCodec)\n            if (!acSpec.lowercase(Locale.ROOT).startsWith(prefix)) newCodecs.add(acSpec)\n        audioCodec = newCodecs\n    }\n\n    fun removeAudioCodecs(codecModule: String) {\n        when (codecModule) {\n            \"g711\" ->\n                removeAudioCodecsStartingWith(\"pcm\")\n            \"g722\" ->\n                removeAudioCodecsStartingWith(\"g722/\")\n            else ->\n                removeAudioCodecsStartingWith(codecModule)\n        }\n    }\n\n    companion object {\n\n        fun accounts(): ArrayList<Account> {\n            val res = ArrayList<Account>()\n            for (ua in BaresipService.uas.value) {\n                res.add(ua.account)\n            }\n            return res\n        }\n\n        fun saveAccounts() {\n            var accounts = \"\"\n            for (a in accounts()) accounts = accounts + a.print() + \"\\n\"\n            Utils.putFileContents(\n                BaresipService.filesPath + \"/accounts\",\n                accounts.toByteArray(Charsets.UTF_8)\n            )\n            Log.d(TAG, \"Saved accounts '${accounts}' to '${BaresipService.filesPath}/accounts'\")\n        }\n\n        fun ofAor(aor: String): Account? {\n            for (ua in BaresipService.uas.value)\n                if (ua.account.aor == aor) return ua.account\n            return null\n        }\n\n        fun checkDisplayName(dn: String): Boolean {\n            if (dn == \"\") return true\n            val dnRegex = Regex(\"^([* .!%_`'~]|[+]|[-a-zA-Z0-9]){1,64}$\")\n            return dnRegex.matches(dn)\n        }\n\n        fun checkAuthUser(au: String): Boolean {\n            if (au == \"\") return true\n            val ud = au.split(\"@\")\n            return when (ud.size) {\n                1 -> Utils.checkUriUser(au)\n                2 -> Utils.checkUriUser(ud[0]) && Utils.checkDomain(ud[1])\n                else -> false\n            }\n        }\n\n        fun checkAuthPass(ap: String): Boolean {\n            return (ap.isNotEmpty()) && (ap.length <= 64) &&\n                    Regex(\"^[ -~]*$\").matches(ap) && !ap.contains('\"')\n        }\n\n        fun uniqueNickName(nickName: String): Boolean {\n            for (ua in BaresipService.uas.value)\n                if (ua.account.nickName == nickName)\n                    return false\n            return true\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/AccountScreen.kt",
    "content": "package com.tutpro.baresip\n\nimport android.content.Context\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.asPaddingValues\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.imePadding\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.statusBars\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.text.KeyboardOptions\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.filled.ArrowDropDown\nimport androidx.compose.material.icons.filled.Check\nimport androidx.compose.material.icons.filled.Visibility\nimport androidx.compose.material.icons.filled.VisibilityOff\nimport androidx.compose.material3.CircularProgressIndicator\nimport androidx.compose.material3.DropdownMenu\nimport androidx.compose.material3.DropdownMenuItem\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.HorizontalDivider\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.OutlinedTextField\nimport androidx.compose.material3.OutlinedTextFieldDefaults\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Switch\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.TopAppBarDefaults\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.derivedStateOf\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalSoftwareKeyboardController\nimport androidx.compose.ui.platform.SoftwareKeyboardController\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.TextStyle\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.input.KeyboardCapitalization\nimport androidx.compose.ui.text.input.KeyboardType\nimport androidx.compose.ui.text.input.PasswordVisualTransformation\nimport androidx.compose.ui.text.input.VisualTransformation\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.lifecycle.viewmodel.compose.viewModel\nimport androidx.navigation.NavController\nimport androidx.navigation.NavGraphBuilder\nimport androidx.navigation.NavType\nimport androidx.navigation.compose.composable\nimport androidx.navigation.navArgument\nimport com.tutpro.baresip.CustomElements.AlertDialog\nimport com.tutpro.baresip.CustomElements.verticalScrollbar\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.launch\nimport org.xmlpull.v1.XmlPullParser\nimport org.xmlpull.v1.XmlPullParserFactory\nimport java.io.File\nimport java.io.StringReader\nimport java.net.URL\nimport java.util.Locale\nimport javax.net.ssl.HttpsURLConnection\n\nfun NavGraphBuilder.accountScreenRoute(navController: NavController) {\n    composable(\n        route = \"account/{aor}/{kind}\",\n        arguments = listOf(\n            navArgument(\"aor\") { type = NavType.StringType },\n            navArgument(\"kind\") { type = NavType.StringType }\n        )\n    ) { backStackEntry ->\n        val ctx = LocalContext.current\n        val viewModel = viewModel<AccountViewModel>()\n        val aor = backStackEntry.arguments?.getString(\"aor\")!!\n        val kind = backStackEntry.arguments?.getString(\"kind\")!!\n        val ua = UserAgent.ofAor(aor)!!\n        AccountScreen(\n            viewModel = viewModel,\n            navController = navController,\n            onBack = { navController.navigateUp() },\n            checkOnClick = {\n                val ok = checkOnClick(ctx, viewModel, ua)\n                if (ok) {\n                    if (reRegister) ua.reRegister()\n                    navController.navigateUp()\n                }\n            },\n            aor = aor,\n            kind = kind\n        )\n    }\n}\n\nprivate var keyboardController: SoftwareKeyboardController? = null\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun AccountScreen(\n    viewModel: AccountViewModel,\n    navController: NavController,\n    onBack: () -> Unit,\n    checkOnClick: () -> Unit,\n    aor: String,\n    kind: String\n) {\n    val ua = UserAgent.ofAor(aor)!!\n    val acc = ua.account\n\n    var isAccountAvailable by remember { mutableStateOf(false) }\n    var isAccountLoaded by remember { mutableStateOf(false) }\n\n    LaunchedEffect(kind, acc) {\n        if (kind == \"new\")\n            initAccountFromNetwork(acc) { isAccountAvailable = true }\n        else\n            isAccountAvailable = true\n    }\n\n    if (isAccountAvailable)\n        LaunchedEffect(acc) {\n            viewModel.loadAccount(acc)\n            isAccountLoaded = true\n        }\n\n    Scaffold(\n        modifier = Modifier.fillMaxSize().imePadding(),\n        containerColor = MaterialTheme.colorScheme.background,\n        topBar = {\n            Column(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .background(MaterialTheme.colorScheme.background)\n                    .padding(\n                        top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()\n                    )\n            ) {\n                TopAppBar(\n                    title = {\n                        Text(\n                            text = acc.text(),\n                            fontWeight = FontWeight.Bold\n                        )\n                    },\n                    colors = TopAppBarDefaults.topAppBarColors(\n                        containerColor = MaterialTheme.colorScheme.primary,\n                        navigationIconContentColor = MaterialTheme.colorScheme.onPrimary,\n                        titleContentColor = MaterialTheme.colorScheme.onPrimary,\n                        actionIconContentColor = MaterialTheme.colorScheme.onPrimary\n                    ),\n                    navigationIcon = {\n                        IconButton(onClick = onBack) {\n                            Icon(\n                                imageVector = Icons.AutoMirrored.Filled.ArrowBack,\n                                contentDescription = null,\n                            )\n                        }\n                    },\n                    windowInsets = WindowInsets(0, 0, 0, 0),\n                    actions = {\n                        IconButton(onClick = checkOnClick) {\n                            Icon(\n                                imageVector = Icons.Filled.Check,\n                                contentDescription = \"Check\"\n                            )\n                        }\n                    },\n                )\n            }\n        }\n    ) { contentPadding ->\n        if (isAccountLoaded)\n            AccountContent(viewModel, navController, contentPadding, ua)\n        else\n            Box(\n                modifier = Modifier.fillMaxSize(),\n                contentAlignment = Alignment.Center\n            ) {\n                CircularProgressIndicator()\n            }\n    }\n}\n\nprivate val password = mutableStateOf(\"\")\nprivate val showPasswordDialog = mutableStateOf(false)\n\nprivate val alertTitle = mutableStateOf(\"\")\nprivate val alertMessage = mutableStateOf(\"\")\nprivate val showAlert = mutableStateOf(false)\n\nprivate var reRegister = false\n\n@Composable\nprivate fun AccountContent(\n    viewModel: AccountViewModel,\n    navController: NavController,\n    contentPadding: PaddingValues,\n    ua: UserAgent\n) {\n    val ctx = LocalContext.current\n    val aor = ua.account.aor\n\n    val mediaNat by viewModel.mediaNat.collectAsState()\n    val showStun by remember { derivedStateOf { mediaNat.isNotEmpty() } }\n\n    @Composable\n    fun AoR() {\n        Row(\n            Modifier\n                .fillMaxWidth()\n                .padding(top = 8.dp, end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            OutlinedTextField(\n                value = ua.account.luri,\n                enabled = false,\n                onValueChange = {},\n                modifier = Modifier.fillMaxWidth(),\n                textStyle = TextStyle(fontSize = 18.sp),\n                label = {\n                    Text(text = stringResource(R.string.sip_uri), fontWeight = FontWeight.Bold)\n                },\n                colors = OutlinedTextFieldDefaults.colors(\n                    disabledTextColor = MaterialTheme.colorScheme.onSurface,\n                    disabledBorderColor = MaterialTheme.colorScheme.outline,\n                    disabledLeadingIconColor = MaterialTheme.colorScheme.onSurfaceVariant,\n                    disabledTrailingIconColor = MaterialTheme.colorScheme.onSurfaceVariant,\n                    disabledLabelColor = MaterialTheme.colorScheme.onSurfaceVariant,\n                )\n            )\n        }\n    }\n\n    @Composable\n    fun Nickname() {\n        val nickNameTitle = stringResource(R.string.nickname)\n        val nickNameHelp = stringResource(R.string.account_nickname_help)\n        Row(\n            Modifier.fillMaxWidth().padding(top = 8.dp, end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            val nickName by viewModel.nickName.collectAsState()\n            OutlinedTextField(\n                value = nickName,\n                placeholder = { Text(nickNameTitle) },\n                onValueChange = { viewModel.nickName.value = it },\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .clickable {\n                        alertTitle.value = nickNameTitle\n                        alertMessage.value = nickNameHelp\n                        showAlert.value = true\n                    },\n                singleLine = true,\n                textStyle = TextStyle(fontSize = 18.sp),\n                label = { Text(nickNameTitle) },\n                keyboardOptions = KeyboardOptions(\n                    capitalization = KeyboardCapitalization.Words,\n                    keyboardType = KeyboardType.Text),\n            )\n        }\n    }\n\n    @Composable\n    fun DisplayName() {\n        val displayNameTitle = stringResource(R.string.display_name)\n        val displayNameHelp = stringResource(R.string.display_name_help)\n        Row(\n            Modifier.fillMaxWidth().padding(top = 8.dp, end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            val displayName by viewModel.displayName.collectAsState()\n            OutlinedTextField(\n                value = displayName,\n                placeholder = { Text(displayNameTitle) },\n                onValueChange = { viewModel.displayName.value = it },\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .clickable {\n                        alertTitle.value = displayNameTitle\n                        alertMessage.value = displayNameHelp\n                        showAlert.value = true\n                    },\n                singleLine = true,\n                textStyle = TextStyle(fontSize = 18.sp),\n                label = { Text(displayNameTitle) },\n                keyboardOptions = KeyboardOptions(\n                    capitalization = KeyboardCapitalization.Sentences,\n                    keyboardType = KeyboardType.Text\n                ),\n            )\n        }\n    }\n\n    @Composable\n    fun AuthUser() {\n        val authenticationUserNameTitle = stringResource(R.string.authentication_username)\n        val authenticationUserNameHelp = stringResource(R.string.authentication_username_help)\n        Row(\n            Modifier.fillMaxWidth().padding(top = 8.dp, end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            val authUser by viewModel.authUser.collectAsState()\n            OutlinedTextField(\n                value = authUser,\n                placeholder = { Text(authenticationUserNameTitle) },\n                onValueChange = { viewModel.authUser.value = it },\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .clickable {\n                        alertTitle.value = authenticationUserNameTitle\n                        alertMessage.value = authenticationUserNameHelp\n                        showAlert.value = true\n                    },\n                textStyle = TextStyle(fontSize = 18.sp),\n                label = { Text(authenticationUserNameTitle) },\n                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)\n            )\n        }\n    }\n\n    @Composable\n    fun AuthPass() {\n        val authenticationPasswordTitle = stringResource(R.string.authentication_password)\n        val authenticationPasswordHelp = stringResource(R.string.authentication_password_help)\n        val showPassword = remember { mutableStateOf(false) }\n        Row(\n            Modifier.fillMaxWidth().padding(top = 8.dp, end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            val authPass by viewModel.authPass.collectAsState()\n            OutlinedTextField(\n                value = authPass,\n                placeholder = { Text(authenticationPasswordTitle) },\n                onValueChange = { viewModel.authPass.value = it },\n                singleLine = true,\n                visualTransformation = if (showPassword.value)\n                    VisualTransformation.None\n                else\n                    PasswordVisualTransformation(),\n                trailingIcon = {\n                    IconButton(onClick = {\n                        showPassword.value = !showPassword.value\n                    }) {\n                        Icon(\n                            if (showPassword.value)\n                                Icons.Filled.Visibility\n                            else\n                                Icons.Filled.VisibilityOff,\n                            contentDescription = \"Visibility\",\n                            tint = MaterialTheme.colorScheme.onSurfaceVariant\n                        )\n                    }\n                },\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .clickable {\n                        alertTitle.value = authenticationPasswordTitle\n                        alertMessage.value = authenticationPasswordHelp\n                        showAlert.value = true\n                    },\n                textStyle = TextStyle(fontSize = 18.sp),\n                label = { Text(authenticationPasswordTitle) },\n                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)\n            )\n        }\n    }\n\n    @Composable\n    fun AskPassword(ctx: Context, navController: NavController, ua: UserAgent) {\n        CustomElements.PasswordDialog(\n            ctx = ctx,\n            showPasswordDialog = showPasswordDialog,\n            password = password,\n            keyboardController = keyboardController,\n            title = stringResource(R.string.authentication_password),\n            okAction = {\n                if (password.value != \"\") {\n                    BaresipService.aorPasswords[ua.account.aor] = password.value\n                    Api.account_set_auth_pass(ua.account.accp, password.value)\n                    password.value = \"\"\n                    ua.reRegister()\n                    navController.navigateUp()\n                }\n            },\n            cancelAction = {\n                ua.reRegister()\n                navController.navigateUp()\n            }\n        )\n    }\n\n    @Composable\n    fun Outbound() {\n        val outboundProxiesTitle = stringResource(R.string.outbound_proxies)\n        val outboundProxiesHelp = stringResource(R.string.outbound_proxies_help)\n        Text(text = stringResource(R.string.outbound_proxies),\n            fontSize = 18.sp,\n            modifier = Modifier\n                .padding(top = 8.dp)\n                .clickable {\n                    alertTitle.value = outboundProxiesTitle\n                    alertMessage.value = outboundProxiesHelp\n                    showAlert.value = true\n                }\n        )\n        Row(\n            Modifier.fillMaxWidth().padding(top = 8.dp, end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            val outbound1 by viewModel.outbound1.collectAsState()\n            val sipUriOfProxyServerTitle = stringResource(R.string.sip_uri_of_proxy_server)\n            OutlinedTextField(\n                value = outbound1,\n                placeholder = { Text(sipUriOfProxyServerTitle) },\n                onValueChange = { viewModel.outbound1.value = it },\n                modifier = Modifier.fillMaxWidth(),\n                singleLine = true,\n                textStyle = TextStyle(fontSize = 18.sp),\n                label = { Text(sipUriOfProxyServerTitle) },\n                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)\n            )\n        }\n        Row(\n            Modifier.fillMaxWidth().padding(top = 8.dp, end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            val outbound2 by viewModel.outbound2.collectAsState()\n            val anotherProxyServerTitle = stringResource(R.string.sip_uri_of_another_proxy_server)\n            OutlinedTextField(\n                value = outbound2,\n                placeholder = { Text(anotherProxyServerTitle) },\n                onValueChange = { viewModel.outbound2.value = it },\n                modifier = Modifier.fillMaxWidth(),\n                singleLine = true,\n                textStyle = TextStyle(fontSize = 18.sp),\n                label = { Text(anotherProxyServerTitle) },\n                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)\n            )\n        }\n    }\n\n    @Composable\n    fun Register() {\n        val registerTitle = stringResource(R.string.register)\n        val registerHelp = stringResource(R.string.register_help)\n        Row(\n            Modifier.fillMaxWidth().padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            Text(text = registerTitle,\n                modifier = Modifier\n                    .weight(1f)\n                    .clickable {\n                        alertTitle.value = registerTitle\n                        alertMessage.value = registerHelp\n                        showAlert.value = true\n                    },\n                fontSize = 18.sp,\n            )\n            val register by viewModel.register.collectAsState()\n            Switch(\n                checked = register,\n                onCheckedChange = { viewModel.register.value = it }\n            )\n        }\n    }\n\n    @Composable\n    fun RegInt() {\n        val regIntTitle = stringResource(R.string.reg_int)\n        val regIntHelp = stringResource(R.string.reg_int_help)\n        Row(\n            Modifier.fillMaxWidth().padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            val regInt by viewModel.regInt.collectAsState()\n            OutlinedTextField(\n                value = regInt,\n                placeholder = { Text(regIntTitle) },\n                onValueChange = { viewModel.regInt.value = it },\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .clickable {\n                        alertTitle.value = regIntTitle\n                        alertMessage.value = regIntHelp\n                        showAlert.value = true\n                    },\n                singleLine = true,\n                textStyle = TextStyle(fontSize = 18.sp),\n                label = { Text(regIntTitle) },\n                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)\n            )\n        }\n    }\n\n    @Composable\n    fun CheckOrigin() {\n        val checkOriginTitle = stringResource(R.string.check_origin)\n        val checkOriginHelp = stringResource(R.string.check_origin_help)\n        Row(\n            Modifier.fillMaxWidth().padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            Text(text = checkOriginTitle,\n                modifier = Modifier\n                    .weight(1f)\n                    .clickable {\n                        alertTitle.value = checkOriginTitle\n                        alertMessage.value = checkOriginHelp\n                        showAlert.value = true\n                    },\n                fontSize = 18.sp,\n            )\n            val checkOrigin by viewModel.checkOrigin.collectAsState()\n            Switch(\n                checked = checkOrigin,\n                onCheckedChange = { viewModel.checkOrigin.value = it }\n            )\n        }\n    }\n\n    @Composable\n    fun BlockUnknown() {\n        val blockUnknownTitle = stringResource(R.string.block_unknown)\n        val blockUnknownHelp = stringResource(R.string.block_unknown_help)\n        val block by viewModel.blockUnknown.collectAsState()\n        Row(\n            Modifier.fillMaxWidth().padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            Text(text = blockUnknownTitle,\n                modifier = Modifier\n                    .weight(1f)\n                    .clickable {\n                        alertTitle.value = blockUnknownTitle\n                        alertMessage.value = blockUnknownHelp\n                        showAlert.value = true\n                    },\n                fontSize = 18.sp\n            )\n            Switch(\n                checked = block,\n                onCheckedChange = { viewModel.blockUnknown.value = it }\n            )\n        }\n    }\n\n    @Composable\n    fun AudioCodecs(navController: NavController, aor: String) {\n        Row(\n            Modifier.fillMaxWidth(),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            Text(\n                text = stringResource(R.string.audio_codecs),\n                modifier = Modifier\n                    .weight(1f)\n                    .clickable {\n                        val route = \"codecs/$aor/audio\"\n                        navController.navigate(route)\n                    },\n                fontSize = 18.sp,\n                fontWeight = FontWeight.Bold\n            )\n        }\n    }\n\n    @Composable\n    fun MediaEnc() {\n        val mediaEncryptionTitle = stringResource(R.string.media_encryption)\n        val mediaEncryptionHelp = stringResource(R.string.media_encryption_help)\n        Row(\n            Modifier.fillMaxWidth().padding(top = 2.dp, end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            Text(text = mediaEncryptionTitle,\n                modifier = Modifier\n                    .weight(1f)\n                    .clickable {\n                        alertTitle.value = mediaEncryptionTitle\n                        alertMessage.value = mediaEncryptionHelp\n                        showAlert.value = true\n                    },\n                fontSize = 18.sp\n            )\n            val isDropDownExpanded = remember { mutableStateOf(false) }\n            val mediaEnc by viewModel.mediaEnc.collectAsState()\n            Box {\n                Row(\n                    horizontalArrangement = Arrangement.Center,\n                    verticalAlignment = Alignment.CenterVertically,\n                    modifier = Modifier.clickable {\n                        isDropDownExpanded.value = true\n                    }\n                ) {\n                    Text(text = mediaEncMap[mediaEnc]!!)\n                    Icon(\n                        imageVector = Icons.Filled.ArrowDropDown,\n                        contentDescription = null,\n                        modifier = Modifier.size(36.dp)\n                    )\n                }\n                DropdownMenu(\n                    expanded = isDropDownExpanded.value,\n                    onDismissRequest = {\n                        isDropDownExpanded.value = false\n                    }) {\n                    var index = 0\n                    mediaEncMap.forEach {\n                        DropdownMenuItem(text = { Text(text = it.value) },\n                            onClick = {\n                                isDropDownExpanded.value = false\n                                viewModel.mediaEnc.value = it.key\n                            }\n                        )\n                        if (index < 4)\n                            HorizontalDivider(thickness = 1.dp)\n                        index++\n                    }\n                }\n            }\n        }\n    }\n\n    @Composable\n    fun MediaNat() {\n        val mediaNatTitle = stringResource(R.string.media_nat)\n        val mediaNatHelp = stringResource(R.string.media_nat_help)\n        Row(\n            Modifier.fillMaxWidth().padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            Text(mediaNatTitle,\n                modifier = Modifier\n                    .weight(1f)\n                    .clickable {\n                        alertTitle.value = mediaNatTitle\n                        alertMessage.value = mediaNatHelp\n                        showAlert.value = true\n                    },\n                fontSize = 18.sp\n            )\n            val isDropDownExpanded = remember { mutableStateOf(false) }\n            val mediaNat by viewModel.mediaNat.collectAsState()\n            Box {\n                Row(\n                    horizontalArrangement = Arrangement.Center,\n                    verticalAlignment = Alignment.CenterVertically,\n                    modifier = Modifier.clickable {\n                        isDropDownExpanded.value = true\n                    }\n                ) {\n                    Text(text = mediaNatMap[mediaNat]!!)\n                    Icon(\n                        imageVector = Icons.Filled.ArrowDropDown,\n                        contentDescription = null,\n                        modifier = Modifier.size(36.dp)\n                    )\n                }\n                DropdownMenu(\n                    expanded = isDropDownExpanded.value,\n                    onDismissRequest = {\n                        isDropDownExpanded.value = false\n                    }\n                ) {\n                    var index = 0\n                    mediaNatMap.forEach {\n                        DropdownMenuItem(text = { Text(text = it.value) },\n                            onClick = {\n                                isDropDownExpanded.value = false\n                                viewModel.mediaNat.value = it.key\n                            }\n                        )\n                        if (index < 3)\n                            HorizontalDivider(thickness = 1.dp)\n                        index++\n                    }\n                }\n            }\n        }\n    }\n\n    @Composable\n    fun StunServer() {\n        val stunServerTitle = stringResource(R.string.stun_server)\n        val stunServerHelp = stringResource(R.string.stun_server_help)\n        Row(\n            Modifier.fillMaxWidth().padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            val stunServer by viewModel.stunServer.collectAsState()\n            OutlinedTextField(\n                value = stunServer,\n                placeholder = { Text(stunServerTitle) },\n                onValueChange = { viewModel.stunServer.value = it },\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .clickable {\n                        alertTitle.value = stunServerTitle\n                        alertMessage.value = stunServerHelp\n                        showAlert.value = true\n                    },\n                textStyle = TextStyle(fontSize = 18.sp),\n                label = { Text(stunServerTitle) },\n                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)\n            )\n        }\n    }\n\n    @Composable\n    fun StunUser() {\n        val stunUsernameTitle = stringResource(R.string.stun_username)\n        val stunUsernameHelp = stringResource(R.string.stun_username_help)\n        Row(\n            Modifier.fillMaxWidth().padding(top = 8.dp, end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            val stunUser by viewModel.stunUser.collectAsState()\n            OutlinedTextField(\n                value = stunUser,\n                placeholder = { Text(stunUsernameTitle) },\n                onValueChange = { viewModel.stunUser.value = it },\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .clickable {\n                        alertTitle.value = stunUsernameTitle\n                        alertMessage.value = stunUsernameHelp\n                        showAlert.value = true\n                    },\n                textStyle = TextStyle(fontSize = 18.sp),\n                label = { Text(stunUsernameTitle) },\n                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)\n            )\n        }\n    }\n\n    @Composable\n    fun StunPass() {\n        val stunPasswordTitle = stringResource(R.string.stun_password)\n        val stunPasswordHelp = stringResource(R.string.stun_password_help)\n        val showPassword = remember { mutableStateOf(false) }\n        Row(\n            Modifier.fillMaxWidth().padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            val stunPass by viewModel.stunPass.collectAsState()\n            OutlinedTextField(\n                value = stunPass,\n                placeholder = { Text(stunPasswordTitle) },\n                onValueChange = { viewModel.stunPass.value = it },\n                singleLine = true,\n                visualTransformation = if (showPassword.value)\n                    VisualTransformation.None\n                else\n                    PasswordVisualTransformation(),\n                trailingIcon = {\n                    IconButton(onClick = { showPassword.value = !showPassword.value }) {\n                        Icon(\n                            if (showPassword.value)\n                                Icons.Filled.Visibility\n                            else\n                                Icons.Filled.VisibilityOff,\n                            contentDescription = \"Visibility\",\n                            tint = MaterialTheme.colorScheme.onSurfaceVariant\n                        )\n                    }\n                },\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .clickable {\n                        alertTitle.value = stunPasswordTitle\n                        alertMessage.value = stunPasswordHelp\n                        showAlert.value = true\n                    },\n                textStyle = TextStyle(fontSize = 18.sp),\n                label = { Text(stunPasswordTitle) },\n                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)\n            )\n        }\n    }\n\n    @Composable\n    fun RtcpMux() {\n        val rtcpMuxTitle = stringResource(R.string.rtcp_mux)\n        val rtcpMuxHelp = stringResource(R.string.rtcp_mux_help)\n        Row(\n            Modifier.fillMaxWidth().padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            val rtcpMux by viewModel.rtcpMux.collectAsState()\n            Text(text = rtcpMuxTitle,\n                modifier = Modifier\n                    .weight(1f)\n                    .clickable {\n                        alertTitle.value = rtcpMuxTitle\n                        alertMessage.value = rtcpMuxHelp\n                        showAlert.value = true\n                    },\n                fontSize = 18.sp\n            )\n            Switch(\n                checked = rtcpMux,\n                onCheckedChange = { viewModel.rtcpMux.value = it }\n            )\n        }\n    }\n\n    @Composable\n    fun Rel100() {\n        val rel100Title = stringResource(R.string.rel_100)\n        val rel100Help = stringResource(R.string.rel_100_help)\n        Row(\n            Modifier.fillMaxWidth().padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            val rel100 by viewModel.rel100.collectAsState()\n            Text(\n                text = rel100Title,\n                modifier = Modifier\n                    .weight(1f)\n                    .clickable {\n                        alertTitle.value = rel100Title\n                        alertMessage.value = rel100Help\n                        showAlert.value = true\n                    },\n                fontSize = 18.sp\n            )\n            Switch(\n                checked = rel100,\n                onCheckedChange = { viewModel.rel100.value = it }\n            )\n        }\n    }\n\n    @Composable\n    fun Dtmf() {\n        val dtmfModeTitle = stringResource(R.string.dtmf_mode)\n        val dtmfModeHelp = stringResource(R.string.dtmf_mode_help)\n        val dtmfInbandText = stringResource(R.string.dtmf_inband)\n        val dtmfInfoText = stringResource(R.string.dtmf_info)\n        val dtmfAutoText = stringResource(R.string.dtmf_auto)\n        val dtmfMode by viewModel.dtmfMode.collectAsState()\n        Row(\n            Modifier.fillMaxWidth().padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            val dtmfModeMap = mapOf(Api.DTMFMODE_RTP_EVENT to dtmfInbandText,\n                Api.DTMFMODE_SIP_INFO to dtmfInfoText,\n                Api.DTMFMODE_AUTO to dtmfAutoText)\n            Text(text = dtmfModeTitle,\n                modifier = Modifier\n                    .weight(1f)\n                    .clickable {\n                        alertTitle.value = dtmfModeTitle\n                        alertMessage.value = dtmfModeHelp\n                        showAlert.value = true\n                    },\n                fontSize = 18.sp\n            )\n            val isDropDownExpanded = remember { mutableStateOf(false) }\n            Box {\n                Row(\n                    horizontalArrangement = Arrangement.Center,\n                    verticalAlignment = Alignment.CenterVertically,\n                    modifier = Modifier.clickable {\n                        isDropDownExpanded.value = true\n                    }\n                ) {\n                    Text(text = dtmfModeMap[dtmfMode]!!)\n                    Icon(\n                        imageVector = Icons.Filled.ArrowDropDown,\n                        contentDescription = null,\n                        modifier = Modifier.size(36.dp)\n                    )\n                }\n                DropdownMenu(\n                    expanded = isDropDownExpanded.value,\n                    onDismissRequest = {\n                        isDropDownExpanded.value = false\n                    }) {\n                    var index = 0\n                    dtmfModeMap.forEach {\n                        DropdownMenuItem(text = { Text(text = it.value) },\n                            onClick = {\n                                isDropDownExpanded.value = false\n                                viewModel.dtmfMode.value = it.key\n                            }\n                        )\n                        if (index < 2)\n                            HorizontalDivider(thickness = 1.dp)\n                        index++\n                    }\n                }\n            }\n        }\n    }\n\n    val manualText = stringResource(R.string.manual)\n    val autoText = stringResource(R.string.auto)\n\n    @Composable\n    fun Answer() {\n        val answerModeTitle = stringResource(R.string.answer_mode)\n        val answerModeHelp = stringResource(R.string.answer_mode_help)\n        val answerMode by viewModel.answerMode.collectAsState()\n        Row(\n            Modifier.fillMaxWidth().padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            val answerModeMap = mapOf(Api.ANSWERMODE_MANUAL to manualText,\n                Api.ANSWERMODE_AUTO to autoText)\n            Text(text = answerModeTitle,\n                modifier = Modifier\n                    .weight(1f)\n                    .clickable {\n                        alertTitle.value = answerModeTitle\n                        alertMessage.value = answerModeHelp\n                        showAlert.value = true\n                    },\n                fontSize = 18.sp\n            )\n            val isDropDownExpanded = remember { mutableStateOf(false) }\n            Box {\n                Row(\n                    horizontalArrangement = Arrangement.Center,\n                    verticalAlignment = Alignment.CenterVertically,\n                    modifier = Modifier.clickable { isDropDownExpanded.value = true }\n                ) {\n                    Text(text = answerModeMap[answerMode]!!)\n                    Icon(\n                        imageVector = Icons.Filled.ArrowDropDown,\n                        contentDescription = null,\n                        modifier = Modifier.size(36.dp)\n                    )\n                }\n                DropdownMenu(\n                    expanded = isDropDownExpanded.value,\n                    onDismissRequest = { isDropDownExpanded.value = false }\n                ) {\n                    var index = 0\n                    answerModeMap.forEach {\n                        DropdownMenuItem(text = { Text(text = it.value) },\n                            onClick = {\n                                isDropDownExpanded.value = false\n                                viewModel.answerMode.value = it.key\n                            })\n                        if (index < 1)\n                            HorizontalDivider(thickness = 1.dp)\n                        index++\n                    }\n                }\n            }\n        }\n    }\n\n    @Composable\n    fun Redirect() {\n        val redirectModeTitle = stringResource(R.string.redirect_mode)\n        val redirectModeHelp = stringResource(R.string.redirect_mode_help)\n        val autoRedirect by viewModel.autoRedirect.collectAsState()\n        Row(\n            Modifier.fillMaxWidth().padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            val redirectModeMap = mapOf(false to manualText,\n                true to autoText)\n            Text(text = redirectModeTitle,\n                modifier = Modifier\n                    .weight(1f)\n                    .clickable {\n                        alertTitle.value = redirectModeTitle\n                        alertMessage.value = redirectModeHelp\n                        showAlert.value = true\n                    },\n                fontSize = 18.sp\n            )\n            val isDropDownExpanded = remember { mutableStateOf(false) }\n            Box {\n                Row(\n                    horizontalArrangement = Arrangement.Center,\n                    verticalAlignment = Alignment.CenterVertically,\n                    modifier = Modifier.clickable { isDropDownExpanded.value = true }\n                ) {\n                    Text(text = redirectModeMap[autoRedirect]!!)\n                    Icon(\n                        imageVector = Icons.Filled.ArrowDropDown,\n                        contentDescription = null,\n                        modifier = Modifier.size(36.dp)\n                    )\n                }\n                DropdownMenu(\n                    expanded = isDropDownExpanded.value,\n                    onDismissRequest = { isDropDownExpanded.value = false }\n                ) {\n                    var index = 0\n                    redirectModeMap.forEach {\n                        DropdownMenuItem(\n                            text = { Text(text = it.value) },\n                            onClick = {\n                                isDropDownExpanded.value = false\n                                viewModel.autoRedirect.value = it.key\n                            }\n                        )\n                        if (index < 1)\n                            HorizontalDivider(thickness = 1.dp)\n                        index++\n                    }\n                }\n            }\n        }\n    }\n\n    @Composable\n    fun Voicemail() {\n        val voicemailUriTitle = stringResource(R.string.voicemail_uri)\n        val voicemailUriHelp = stringResource(R.string.voicemain_uri_help)\n        val vmUri by viewModel.vmUri.collectAsState()\n        Row(\n            Modifier.fillMaxWidth().padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            OutlinedTextField(\n                value = vmUri,\n                placeholder = { Text(voicemailUriTitle) },\n                onValueChange = { viewModel.vmUri.value = it },\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .clickable {\n                        alertTitle.value = voicemailUriTitle\n                        alertMessage.value = voicemailUriHelp\n                        showAlert.value = true\n                    },\n                singleLine = true,\n                textStyle = TextStyle(fontSize = 18.sp),\n                label = { Text(voicemailUriTitle) },\n                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)\n            )\n        }\n    }\n\n    @Composable\n    fun CountryCode() {\n        val countryCodeTitle = stringResource(R.string.country_code)\n        val countryCodeHelp = stringResource(R.string.country_code_help)\n        val countryCode by viewModel.countryCode.collectAsState()\n        Row(\n            Modifier.fillMaxWidth().padding(top = 8.dp, end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            OutlinedTextField(\n                value = countryCode,\n                placeholder = { Text(countryCodeTitle) },\n                onValueChange = { viewModel.countryCode.value = it },\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .clickable {\n                        alertTitle.value = countryCodeTitle\n                        alertMessage.value = countryCodeHelp\n                        showAlert.value = true\n                    },\n                singleLine = true,\n                textStyle = TextStyle(fontSize = 18.sp),\n                label = { Text(countryCodeTitle) },\n                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)\n            )\n        }\n    }\n\n    @Composable\n    fun TelProvider() {\n        val telephonyProviderTitle = stringResource(R.string.telephony_provider)\n        val telephonyProviderHelp = stringResource(R.string.telephony_provider_help)\n        val telProvider by viewModel.telProvider.collectAsState()\n        Row(\n            Modifier.fillMaxWidth().padding(top = 8.dp, end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            OutlinedTextField(\n                value = telProvider,\n                placeholder = { Text(telephonyProviderTitle) },\n                onValueChange = { viewModel.telProvider.value = it },\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .clickable {\n                        alertTitle.value = telephonyProviderTitle\n                        alertMessage.value = telephonyProviderHelp\n                        showAlert.value = true\n                    },\n                singleLine = true,\n                textStyle = TextStyle(fontSize = 18.sp),\n                label = { Text(telephonyProviderTitle) },\n                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)\n            )\n        }\n    }\n\n    @Composable\n    fun NumericKeypad() {\n        val numericKeypadTitle = stringResource(R.string.numeric_keypad)\n        val numericKeypadHelp = stringResource(R.string.numeric_keypad_help)\n        val numericKeypad by viewModel.numericKeypad.collectAsState()\n        Row(\n            Modifier.fillMaxWidth().padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            Text(text = numericKeypadTitle,\n                modifier = Modifier\n                    .weight(1f)\n                    .clickable {\n                        alertTitle.value = numericKeypadTitle\n                        alertMessage.value = numericKeypadHelp\n                        showAlert.value = true\n                    },\n                fontSize = 18.sp\n            )\n            Switch(\n                checked = numericKeypad,\n                onCheckedChange = { viewModel.numericKeypad.value = it }\n            )\n        }\n    }\n\n    @Composable\n    fun DefaultAccount() {\n        val defaultAccountTitle = stringResource(R.string.default_account)\n        val defaultAccountHelp = stringResource(R.string.default_account_help)\n        val defaultAccount by viewModel.defaultAccount.collectAsState()\n        Row(\n            Modifier.fillMaxWidth().padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            Text(text = defaultAccountTitle,\n                modifier = Modifier\n                    .weight(1f)\n                    .clickable {\n                        alertTitle.value = defaultAccountTitle\n                        alertMessage.value = defaultAccountHelp\n                        showAlert.value = true\n                    },\n                fontSize = 18.sp\n            )\n            Switch(\n                checked = defaultAccount,\n                onCheckedChange = { viewModel.defaultAccount.value = it }\n            )\n        }\n    }\n\n    @Composable\n    fun CustomParams() {\n        val customParamsTitle = stringResource(R.string.custom_parameters)\n        val customParamsHelp = stringResource(R.string.custom_parameters_help)\n        val customParams by viewModel.customParams.collectAsState()\n        Row(\n            Modifier.fillMaxWidth().padding(top = 8.dp, end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            OutlinedTextField(\n                value = customParams,\n                placeholder = { Text(customParamsTitle) },\n                onValueChange = { viewModel.customParams.value = it },\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .clickable {\n                        alertTitle.value = customParamsTitle\n                        alertMessage.value = customParamsHelp\n                        showAlert.value = true\n                    },\n                singleLine = true,\n                textStyle = TextStyle(fontSize = 18.sp),\n                label = { Text(customParamsTitle) },\n                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)\n            )\n        }\n    }\n\n    if (showAlert.value) {\n        AlertDialog(\n            showDialog = showAlert,\n            title = alertTitle.value,\n            message = alertMessage.value,\n            lastButtonText = stringResource(R.string.ok),\n        )\n    }\n\n    keyboardController = LocalSoftwareKeyboardController.current\n\n    val scrollState = rememberScrollState()\n\n    Column(\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(contentPadding)\n            .padding(start = 16.dp, end = 4.dp, top = 8.dp, bottom = 16.dp)\n            .verticalScrollbar(scrollState)\n            .verticalScroll(state = scrollState),\n        verticalArrangement = Arrangement.spacedBy(8.dp),\n    ) {\n        AoR()\n        Nickname()\n        DisplayName()\n        AuthUser()\n        AuthPass()\n        if (showPasswordDialog.value)\n            AskPassword(ctx, navController, ua)\n        Outbound()\n        Register()\n        if (viewModel.register.collectAsState().value) {\n            RegInt()\n            CheckOrigin()\n        }\n        BlockUnknown()\n        AudioCodecs(navController, aor)\n        MediaEnc()\n        MediaNat()\n        if (showStun) {\n            StunServer()\n            StunUser()\n            StunPass()\n        }\n        RtcpMux()\n        Rel100()\n        Dtmf()\n        Answer()\n        Redirect()\n        Voicemail()\n        CountryCode()\n        TelProvider()\n        NumericKeypad()\n        DefaultAccount()\n        CustomParams()\n    }\n}\n\nprivate fun checkOnClick(ctx: Context, viewModel: AccountViewModel, ua: UserAgent): Boolean {\n\n    val acc = ua.account\n    val noticeTitle = ctx.getString(R.string.notice)\n\n    val nn = viewModel.nickName.value.trim()\n    if (nn != acc.nickName) {\n        if (Account.checkDisplayName(nn)) {\n            if (nn == \"\" || Account.uniqueNickName(nn)) {\n                acc.nickName = nn\n                Log.d(TAG, \"New nickname is ${acc.nickName}\")\n            }\n            else {\n                alertTitle.value = noticeTitle\n                alertMessage.value = String.format(ctx.getString(R.string.non_unique_account_nickname), nn)\n                showAlert.value = true\n                return false\n            }\n        }\n        else {\n            alertTitle.value = noticeTitle\n            alertMessage.value = String.format(ctx.getString(R.string.invalid_account_nickname), nn)\n            showAlert.value = true\n            return false\n        }\n    }\n\n    val dn = viewModel.displayName.value.trim()\n    if (dn != acc.displayName) {\n        if (Account.checkDisplayName(dn)) {\n            if (Api.account_set_display_name(acc.accp, dn) == 0) {\n                acc.displayName = Api.account_display_name(acc.accp)\n                Log.d(TAG, \"New display name is ${acc.displayName}\")\n            } else {\n                Log.e(TAG, \"Setting of display name failed\")\n            }\n        }\n        else {\n            alertTitle.value = noticeTitle\n            alertMessage.value = String.format(ctx.getString(R.string.invalid_display_name), dn)\n            showAlert.value = true\n            return false\n        }\n    }\n\n    val au = viewModel.authUser.value.trim()\n    if (au != acc.authUser) {\n        if (Account.checkAuthUser(au)) {\n            if (Api.account_set_auth_user(acc.accp, au) == 0) {\n                acc.authUser = Api.account_auth_user(acc.accp)\n                Log.d(TAG, \"New auth user is ${acc.authUser}\")\n                if (acc.regint > 0)\n                    reRegister = true\n            }\n            else {\n                Log.e(TAG, \"Setting of auth user failed\")\n            }\n        }\n        else {\n            alertTitle.value = noticeTitle\n            alertMessage.value = String.format(ctx.getString(R.string.invalid_authentication_username), au)\n            showAlert.value = true\n            return false\n        }\n    }\n\n    val ap = viewModel.authPass.value.trim()\n    if (ap != \"\") {\n        if (ap != acc.authPass) {\n            if (Account.checkAuthPass(ap)) {\n                if (Api.account_set_auth_pass(acc.accp, ap) == 0) {\n                    acc.authPass = Api.account_auth_pass(acc.accp)\n                    if (acc.regint > 0)\n                        reRegister = true\n                }\n                else\n                    Log.e(TAG, \"Setting of auth pass failed\")\n                BaresipService.aorPasswords.remove(acc.aor)\n            }\n            else {\n                alertTitle.value = noticeTitle\n                alertMessage.value = String.format(ctx.getString(R.string.invalid_authentication_password), ap)\n                showAlert.value = true\n                return false\n            }\n        }\n        else\n            BaresipService.aorPasswords.remove(acc.aor)\n    }\n    else { // ap == \"\"\n        if (acc.authPass != NO_AUTH_PASS && acc.authPass != BaresipService.aorPasswords[acc.aor])\n            if (Api.account_set_auth_pass(acc.accp, \"\") == 0) {\n                acc.authPass = NO_AUTH_PASS\n                BaresipService.aorPasswords[acc.aor] = NO_AUTH_PASS\n            }\n    }\n\n    val ob = ArrayList<String>()\n    var ob1 = viewModel.outbound1.value.trim().replace(\" \", \"\")\n    if (ob1 != \"\") {\n        if (!ob1.startsWith(\"sip:\"))\n            ob1 = \"sip:$ob1\"\n        if (checkOutboundUri(ob1)) {\n            ob.add(ob1)\n        }\n        else {\n            alertTitle.value = noticeTitle\n            alertMessage.value = String.format(ctx.getString(R.string.invalid_proxy_server_uri), ob1)\n            showAlert.value = true\n            return false\n        }\n    }\n    var ob2 = viewModel.outbound2.value.trim().replace(\" \", \"\")\n    if (ob2 != \"\") {\n        if (!ob2.startsWith(\"sip:\"))\n            ob2 = \"sip:$ob2\"\n        if (checkOutboundUri(ob2))\n            ob.add(ob2)\n        else {\n            alertTitle.value = noticeTitle\n            alertMessage.value = String.format(ctx.getString(R.string.invalid_proxy_server_uri), ob2)\n            showAlert.value = true\n            return false\n        }\n    }\n    if (ob != acc.outbound) {\n        for (i in 0..1) {\n            val uri = if (ob.size > i)\n                ob[i]\n            else\n                \"\"\n            if (Api.account_set_outbound(acc.accp, uri, i) != 0)\n                Log.e(TAG, \"Setting of outbound proxy $i uri '$uri' failed\")\n        }\n        Log.d(TAG, \"New outbound proxies are $ob\")\n        acc.outbound = ob\n        if (ob.isEmpty())\n            Api.account_set_sipnat(acc.accp, \"\")\n        else\n            Api.account_set_sipnat(acc.accp, \"outbound\")\n        if (acc.regint > 0)\n            reRegister = true\n    }\n\n    val regInt = try {\n        viewModel.regInt.value.trim().toInt()\n    } catch (_: NumberFormatException) {\n        0\n    }\n    if (regInt !in 60..3600) {\n        alertTitle.value = noticeTitle\n        alertMessage.value = String.format(ctx.getString(R.string.invalid_reg_int), \"$regInt\")\n        showAlert.value = true\n        return false\n    }\n    val reReg = (viewModel.register.value != acc.regint > 0) ||\n            (viewModel.register.value && regInt != acc.configuredRegInt)\n    if (reReg) {\n        if (Api.account_set_regint(acc.accp,\n                if (viewModel.register.value) regInt else 0) != 0) {\n            Log.e(TAG, \"Setting of regint failed\")\n        } else {\n            acc.regint = Api.account_regint(acc.accp)\n            acc.configuredRegInt = regInt\n            Log.d(TAG, \"New regint is ${acc.regint}\")\n            reRegister = true\n        }\n    }\n    else {\n        if (regInt != acc.configuredRegInt)\n            acc.configuredRegInt = regInt\n    }\n\n    val newCheckOrigin = viewModel.checkOrigin.value\n    if (newCheckOrigin != acc.checkOrigin) {\n        Api.account_set_check_origin(ua.account.accp, newCheckOrigin)\n        acc.checkOrigin = Api.account_check_origin(ua.account.accp)\n        Log.d(TAG, \"New checkOrigin is ${acc.checkOrigin}\")\n    }\n\n    val newMediaNat = viewModel.mediaNat.value\n    if (newMediaNat != acc.mediaNat) {\n        if (Api.account_set_medianat(acc.accp, newMediaNat) == 0) {\n            acc.mediaNat = Api.account_medianat(acc.accp)\n            Log.d(TAG, \"New medianat is ${acc.mediaNat}\")\n        }\n        else\n            Log.e(TAG, \"Setting of medianat $newMediaNat failed\")\n    }\n\n    var newStunServer = viewModel.stunServer.value.trim()\n    if (newMediaNat != \"\") {\n        if ((newMediaNat == \"stun\" || newMediaNat == \"ice\") && newStunServer == \"\")\n            newStunServer = ctx.getString(R.string.stun_server_default)\n        if (!Utils.checkStunUri(newStunServer) ||\n            (newMediaNat == \"turn\" &&\n                    newStunServer.substringBefore(\":\") !in setOf(\"turn\", \"turns\"))) {\n            alertTitle.value = ctx.getString(R.string.notice)\n            alertMessage.value = String.format(ctx.getString(R.string.invalid_stun_server),\n                newStunServer)\n            showAlert.value = true\n            return false\n        }\n    }\n\n    if (acc.stunServer != newStunServer) {\n        if (Api.account_set_stun_uri(acc.accp, newStunServer) == 0) {\n            acc.stunServer = Api.account_stun_uri(acc.accp)\n            Log.d(TAG, \"New STUN/TURN server URI is '${acc.stunServer}'\")\n        } else {\n            Log.e(TAG, \"Setting of STUN/TURN URI server $newStunServer failed\")\n        }\n    }\n\n    val newStunUser = viewModel.stunUser.value.trim()\n    if (acc.stunUser != newStunUser) {\n        if (Account.checkAuthUser(newStunUser)) {\n            if (Api.account_set_stun_user(acc.accp, newStunUser) == 0) {\n                acc.stunUser = Api.account_stun_user(acc.accp)\n                Log.d(TAG, \"New STUN/TURN user is ${acc.stunUser}\")\n            }\n            else\n                Log.e(TAG, \"Setting of STUN/TURN user $newStunUser failed\")\n        }\n        else {\n            alertTitle.value = noticeTitle\n            alertMessage.value = String.format(ctx.getString(R.string.invalid_stun_username),\n                newStunUser)\n            showAlert.value = true\n            return false\n        }\n    }\n\n    val newStunPass = viewModel.stunPass.value.trim()\n    if (acc.stunPass != newStunPass) {\n        if (newStunPass.isEmpty() || Account.checkAuthPass(newStunPass)) {\n            if (Api.account_set_stun_pass(acc.accp, newStunPass) == 0)\n                acc.stunPass = Api.account_stun_pass(acc.accp)\n            else\n                Log.e(TAG, \"Setting of stun pass failed\")\n        }\n        else {\n            alertTitle.value = noticeTitle\n            alertMessage.value = String.format(ctx.getString(R.string.invalid_stun_password),\n                newStunPass)\n            showAlert.value = true\n            return false\n        }\n    }\n\n    val newRtcpMux = viewModel.rtcpMux.value\n    if (newRtcpMux != acc.rtcpMux)\n        if (Api.account_set_rtcp_mux(acc.accp, newRtcpMux) == 0) {\n            acc.rtcpMux = Api.account_rtcp_mux(acc.accp)\n            Log.d(TAG, \"New rtcpMux is ${acc.rtcpMux}\")\n        } else {\n            Log.e(TAG, \"Setting of account_rtc_mux $newRtcpMux failed\")\n        }\n\n    val new100Rel = viewModel.rel100.value\n    if (new100Rel != (acc.rel100Mode == Api.REL100_ENABLED)) {\n        val mode = if (new100Rel) Api.REL100_ENABLED else Api.REL100_DISABLED\n        if (Api.account_set_rel100_mode(acc.accp, mode) == 0) {\n            acc.rel100Mode = Api.account_rel100_mode(acc.accp)\n            Api.ua_update_account(ua.uap)\n            Log.d(TAG, \"New rel100Mode is ${acc.rel100Mode}\")\n        } else {\n            Log.e(TAG, \"Setting of account_rel100Mode $mode failed\")\n        }\n    }\n\n    val newDtmfMode = viewModel.dtmfMode.value\n    if (newDtmfMode != acc.dtmfMode) {\n        if (Api.account_set_dtmfmode(acc.accp, newDtmfMode) == 0) {\n            acc.dtmfMode = Api.account_dtmfmode(acc.accp)\n            Log.d(TAG, \"New dtmfmode is ${acc.dtmfMode}\")\n        } else {\n            Log.e(TAG, \"Setting of dtmfmode $newDtmfMode failed\")\n        }\n    }\n\n    val newAnswerMode = viewModel.answerMode.value\n    if (newAnswerMode != acc.answerMode) {\n        if (Api.account_set_answermode(acc.accp, newAnswerMode) == 0) {\n            acc.answerMode = Api.account_answermode(acc.accp)\n            Log.d(TAG, \"New answermode is ${acc.answerMode}\")\n        } else {\n            Log.e(TAG, \"Setting of answermode $newAnswerMode failed\")\n        }\n    }\n\n    val newAutoRedirect = viewModel.autoRedirect.value\n    if (newAutoRedirect != acc.autoRedirect) {\n        Api.account_set_sip_autoredirect(acc.accp, newAutoRedirect)\n        acc.autoRedirect = newAutoRedirect\n        Log.d(TAG, \"New autoRedirect is ${acc.autoRedirect}\")\n    }\n\n    val newBlockUnknown = viewModel.blockUnknown.value\n    if (newBlockUnknown != acc.blockUnknown) {\n        acc.blockUnknown = newBlockUnknown\n        Log.d(TAG, \"New blockUnknown is ${acc.blockUnknown}\")\n    }\n\n    var newVmUri = viewModel.vmUri.value.trim()\n    if (newVmUri != acc.vmUri) {\n        if (newVmUri != \"\") {\n            if (!newVmUri.startsWith(\"sip:\")) newVmUri = \"sip:$newVmUri\"\n            if (!newVmUri.contains(\"@\")) newVmUri = \"$newVmUri@${acc.host()}\"\n            if (!Utils.checkUri(newVmUri)) {\n                alertTitle.value = noticeTitle\n                alertMessage.value = String.format(ctx.getString(R.string.invalid_sip_or_tel_uri),\n                    newVmUri)\n                showAlert.value = true\n                return false\n            }\n            Api.account_set_mwi(acc.accp, true)\n        }\n        else\n            Api.account_set_mwi(acc.accp, false)\n        acc.vmUri = newVmUri\n        Log.d(TAG, \"New voicemail URI is ${acc.vmUri}\")\n    }\n\n    val newCountryCode = viewModel.countryCode.value.trim()\n    if (newCountryCode != acc.countryCode) {\n        if (newCountryCode != \"\" && !Utils.checkCountryCode(newCountryCode)) {\n            alertTitle.value = ctx.getString(R.string.notice)\n            alertMessage.value = String.format(ctx.getString(R.string.invalid_country_code),\n                newCountryCode)\n            showAlert.value = true\n            return false\n        }\n        acc.countryCode = newCountryCode\n        Log.d(TAG, \"New country code is ${acc.countryCode}\")\n    }\n\n    val hostPart = viewModel.telProvider.value.trim()\n    if (hostPart != acc.telProvider) {\n        if (hostPart != \"\" && !Utils.checkHostPortParams(hostPart)) {\n            alertTitle.value = noticeTitle\n            alertMessage.value = String.format(ctx.getString(R.string.invalid_sip_uri_hostpart), hostPart)\n            showAlert.value = true\n            return false\n        }\n        acc.telProvider = hostPart\n        Log.d(TAG, \"New tel provider is ${acc.telProvider}\")\n    }\n\n    val newNumericKeypad = viewModel.numericKeypad.value\n    if (newNumericKeypad != acc.numericKeypad) {\n        acc.numericKeypad = newNumericKeypad\n        Log.d(TAG, \"New numericKeyboard is ${acc.numericKeypad}\")\n    }\n\n    val newCustomParams = viewModel.customParams.value\n    if (newCustomParams != acc.customParams) {\n        acc.customParams = newCustomParams\n        Log.d(TAG, \"New customParams is ${acc.customParams}\")\n    }\n\n    if (viewModel.defaultAccount.value) ua.makeDefault()\n\n    Api.account_debug(acc.accp)\n\n    Account.saveAccounts()\n\n    if (acc.authUser != \"\" && BaresipService.aorPasswords[acc.aor] == NO_AUTH_PASS) {\n        showPasswordDialog.value = true\n        return false\n    }\n    else\n        return true\n}\n\nprivate fun initAccountFromNetwork(acc: Account, onConfigLoaded: () -> Unit) {\n    val scope = CoroutineScope(Job() + Dispatchers.Main)\n    scope.launch(Dispatchers.IO) {\n        val url = \"https://${Utils.uriHostPart(acc.aor)}/baresip/account_config.xml\"\n        val urlConnection = URL(url).openConnection() as HttpsURLConnection\n        urlConnection.connectTimeout = 5000\n        urlConnection.readTimeout = 3000\n        val caFile = File(BaresipService.filesPath + \"/ca_certs.crt\")\n        val template = try {\n            if (caFile.exists())\n                Utils.readUrlWithCustomCAs(urlConnection, caFile)\n            else\n                urlConnection.inputStream.bufferedReader().use { it.readText() }\n        } catch (e: java.lang.Exception) {\n            Log.d(TAG, \"Failed to get account template from network: ${e.message}\")\n            null\n        }\n        if (template != null) {\n            Log.d(TAG, \"Got account template '$template'\")\n            val parserFactory: XmlPullParserFactory = XmlPullParserFactory.newInstance()\n            val parser: XmlPullParser = parserFactory.newPullParser()\n            parser.setInput(StringReader(template))\n            var tag: String?\n            var text = \"\"\n            var event = parser.eventType\n            val audioCodecs = ArrayList(Api.audio_codecs().split(\",\"))\n            val videoCodecs = ArrayList(Api.video_codecs().split(\",\"))\n\n            while (event != XmlPullParser.END_DOCUMENT) {\n                tag = parser.name\n                when (event) {\n                    XmlPullParser.TEXT ->\n                        text = parser.text\n\n                    XmlPullParser.START_TAG -> {\n                        if (tag == \"audio-codecs\")\n                            acc.audioCodec.clear()\n                        if (tag == \"video-codecs\")\n                            acc.videoCodec.clear()\n                    }\n\n                    XmlPullParser.END_TAG ->\n                        when (tag) {\n                            \"outbound-proxy-1\" ->\n                                if (text.isNotEmpty())\n                                    acc.outbound.add(text)\n\n                            \"outbound-proxy-2\" ->\n                                if (text.isNotEmpty())\n                                    acc.outbound.add(text)\n\n                            \"registration-interval\" ->\n                                acc.configuredRegInt = try {\n                                    text.toInt()\n                                } catch (_: NumberFormatException) {\n                                    900\n                                }\n\n                            \"register\" -> {\n                                acc.regint = if (text == \"yes\") acc.configuredRegInt else 0\n                                if (acc.regint > 0)\n                                    acc.checkOrigin = true\n                            }\n\n                            \"audio-codec\" ->\n                                if (text in audioCodecs)\n                                    acc.audioCodec.add(text)\n\n                            \"video-codec\" ->\n                                if (text in videoCodecs)\n                                    acc.videoCodec.add(text)\n\n                            \"media-encoding\" -> {\n                                val enc = text.lowercase(Locale.ROOT)\n                                if (enc in mediaEncMap.keys && enc.isNotEmpty())\n                                    acc.mediaEnc = enc\n                            }\n\n                            \"media-nat\" -> {\n                                val nat = text.lowercase(Locale.ROOT)\n                                if (nat in mediaNatMap.keys && nat.isNotEmpty())\n                                    acc.mediaNat = nat\n                            }\n\n                            \"stun-turn-server\" ->\n                                if (text.isNotEmpty())\n                                    acc.stunServer = text\n\n                            \"rtcp-mux\" ->\n                                acc.rtcpMux = text == \"yes\"\n\n                            \"100rel-mode\" ->\n                                acc.rel100Mode = if (text == \"yes\")\n                                    Api.REL100_ENABLED\n                                else\n                                    Api.REL100_DISABLED\n\n                            \"dtmf-mode\" ->\n                                if (text in arrayOf(\"rtp-event\", \"sip-info\", \"auto\"))\n                                    acc.dtmfMode = when (text) {\n                                        \"rtp-event\" -> Api.DTMFMODE_RTP_EVENT\n                                        \"sip-info\" -> Api.DTMFMODE_SIP_INFO\n                                        else -> Api.DTMFMODE_AUTO\n                                    }\n\n                            \"answer-mode\" ->\n                                if (text in arrayOf(\"manual\", \"auto\"))\n                                    acc.answerMode = if (text == \"manual\")\n                                        Api.ANSWERMODE_MANUAL\n                                    else\n                                        Api.ANSWERMODE_AUTO\n\n                            \"redirect-mode\" ->\n                                acc.autoRedirect = text == \"yes\"\n\n                            \"voicemail-uri\" ->\n                                if (text.isNotEmpty())\n                                    acc.vmUri = text\n\n                            \"country-code\" ->\n                                acc.countryCode = text\n\n                            \"tel-provider\" ->\n                                acc.telProvider = text\n                        }\n                }\n                event = parser.next()\n            }\n        }\n        onConfigLoaded()\n    }\n}\n\nprivate fun checkOutboundUri(uri: String): Boolean {\n    if (!uri.startsWith(\"sip:\")) return false\n    return Utils.checkHostPortParams(uri.substring(4))\n}"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/AccountViewModel.kt",
    "content": "package com.tutpro.baresip\n\nimport androidx.lifecycle.ViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\n\nclass AccountViewModel: ViewModel() {\n\n    val nickName = MutableStateFlow(\"\")\n    val displayName = MutableStateFlow(\"\")\n    val authUser = MutableStateFlow(\"\")\n    val authPass = MutableStateFlow(\"\")\n    val outbound1 = MutableStateFlow(\"\")\n    val outbound2 = MutableStateFlow(\"\")\n    val register = MutableStateFlow(false)\n    val regInt = MutableStateFlow(\"\")\n    val checkOrigin = MutableStateFlow(true)\n    val mediaEnc = MutableStateFlow(\"\")\n    val mediaNat = MutableStateFlow(\"\")\n    val stunServer = MutableStateFlow(\"\")\n    val stunUser = MutableStateFlow(\"\")\n    val stunPass = MutableStateFlow(\"\")\n    val rtcpMux = MutableStateFlow(false)\n    val rel100 = MutableStateFlow(false)\n    val dtmfMode = MutableStateFlow(0)\n    val answerMode = MutableStateFlow(0)\n    val autoRedirect = MutableStateFlow(false)\n    val blockUnknown = MutableStateFlow(false)\n    val vmUri = MutableStateFlow(\"\")\n    val countryCode = MutableStateFlow(\"\")\n    val telProvider = MutableStateFlow(\"\")\n    val numericKeypad = MutableStateFlow(false)\n\n    val defaultAccount = MutableStateFlow(false)\n    val customParams = MutableStateFlow(\"\")\n\n    private var isLoaded = false\n\n    fun loadAccount(acc: Account) {\n        if (isLoaded) return else isLoaded = true\n        nickName.value = acc.nickName\n        displayName.value = acc.displayName\n        authUser.value = acc.authUser\n        authPass.value = if (BaresipService.aorPasswords[acc.aor] == null && acc.authPass != NO_AUTH_PASS)\n            acc.authPass\n        else\n            \"\"\n        outbound1.value = if (acc.outbound.isNotEmpty()) acc.outbound[0] else \"\"\n        outbound2.value = if (acc.outbound.size > 1) acc.outbound[1] else \"\"\n        register.value = acc.regint > 0\n        regInt.value = acc.configuredRegInt.toString()\n        checkOrigin.value = acc.checkOrigin\n        mediaEnc.value = acc.mediaEnc\n        mediaNat.value = acc.mediaNat\n        stunServer.value = acc.stunServer\n        stunUser.value = acc.stunUser\n        stunPass.value = acc.stunPass\n        rtcpMux.value = acc.rtcpMux\n        rel100.value = acc.rel100Mode == Api.REL100_ENABLED\n        dtmfMode.value = acc.dtmfMode\n        answerMode.value = acc.answerMode\n        autoRedirect.value = acc.autoRedirect\n        blockUnknown.value = acc.blockUnknown\n        vmUri.value = acc.vmUri\n        countryCode.value = acc.countryCode\n        telProvider.value = acc.telProvider\n        numericKeypad.value = acc.numericKeypad\n        defaultAccount.value = UserAgent.findAorIndex(acc.aor)!! == 0\n        customParams.value = acc.customParams\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/AccountsScreen.kt",
    "content": "package com.tutpro.baresip\n\nimport android.content.Context\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.asPaddingValues\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.imePadding\nimport androidx.compose.foundation.layout.navigationBarsPadding\nimport androidx.compose.foundation.layout.offset\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.statusBars\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.lazy.rememberLazyListState\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.text.KeyboardOptions\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.filled.Add\nimport androidx.compose.material.icons.filled.Delete\nimport androidx.compose.material.icons.outlined.Clear\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.OutlinedTextField\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.SmallFloatingActionButton\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.TopAppBarDefaults\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalFocusManager\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.TextStyle\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.input.KeyboardType\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.navigation.NavController\nimport androidx.navigation.NavGraphBuilder\nimport androidx.navigation.compose.composable\nimport com.tutpro.baresip.CustomElements.AlertDialog\nimport com.tutpro.baresip.CustomElements.verticalScrollbar\n\nfun NavGraphBuilder.accountsScreenRoute(navController: NavController) {\n    composable(\"accounts\") {\n        AccountsScreen(navController)\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun AccountsScreen(navController: NavController) {\n    Scaffold(\n        modifier = Modifier.fillMaxSize().imePadding(),\n        containerColor = MaterialTheme.colorScheme.background,\n        topBar = {\n            Column(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .background(MaterialTheme.colorScheme.background)\n                    .padding(\n                        top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()\n                    )\n            ) {\n                TopAppBar(\n                    title = {\n                        Text(\n                            text = stringResource(R.string.accounts),\n                            fontWeight = FontWeight.Bold\n                        )\n                    },\n                    colors = TopAppBarDefaults.topAppBarColors(\n                        containerColor = MaterialTheme.colorScheme.primary,\n                        navigationIconContentColor = MaterialTheme.colorScheme.onPrimary,\n                        titleContentColor = MaterialTheme.colorScheme.onPrimary,\n                    ),\n                    navigationIcon = {\n                        IconButton(onClick = { navController.navigateUp() }) {\n                            Icon(\n                                imageVector = Icons.AutoMirrored.Filled.ArrowBack,\n                                contentDescription = null,\n                            )\n                        }\n                    },\n                    windowInsets = WindowInsets(0, 0, 0, 0)\n                )\n            }\n        },\n        bottomBar = { NewAccount(navController) },\n        content = { contentPadding ->\n            AccountsContent(contentPadding, navController)\n        },\n    )\n}\n\n@Composable\nfun AccountsContent(contentPadding: PaddingValues, navController: NavController) {\n\n    val showDialog = remember { mutableStateOf(false) }\n    val message = remember { mutableStateOf(\"\") }\n    val lastAction = remember { mutableStateOf({}) }\n\n    AlertDialog(\n        showDialog = showDialog,\n        title = stringResource(R.string.confirmation),\n        message = message.value,\n        firstButtonText = stringResource(R.string.cancel),\n        lastButtonText = stringResource(R.string.delete),\n        onLastClicked = lastAction.value,\n    )\n\n    val showAccounts = remember { mutableStateOf(true) }\n\n    if (showAccounts.value && BaresipService.uas.value.isNotEmpty()) {\n\n        val lazyListState = rememberLazyListState()\n\n        LazyColumn(\n            state = lazyListState,\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(contentPadding)\n                .padding(start = 16.dp, end = 4.dp, top = 8.dp, bottom = 8.dp)\n                .verticalScrollbar(state = lazyListState),\n            verticalArrangement = Arrangement.spacedBy(4.dp),\n        ) {\n            items(BaresipService.uas.value) { ua ->\n                val account = ua.account\n                val aor = account.aor\n                val text = account.text()\n                Row(\n                    verticalAlignment = Alignment.CenterVertically,\n                ) {\n                    Text(\n                        text = text,\n                        fontSize = 20.sp,\n                        color = MaterialTheme.colorScheme.onBackground,\n                        modifier = Modifier\n                            .weight(1f)\n                            .padding(start = 10.dp)\n                            .clickable {\n                                navController.navigate(\"account/$aor/old\")\n                            }\n                    )\n                    val deleteAccountMessage = stringResource(R.string.delete_account)\n                    SmallFloatingActionButton(\n                        modifier = Modifier.padding(end = 8.dp),\n                        onClick = {\n                            message.value = String.format(deleteAccountMessage, text)\n                            lastAction.value = {\n                                CallHistoryNew.clear(aor)\n                                Message.clearMessagesOfAor(aor)\n                                ua.remove()\n                                Api.ua_destroy(ua.uap)\n                                Account.saveAccounts()\n                                showAccounts.value = false\n                                showAccounts.value = true\n                            }\n                            showDialog.value = true\n                        },\n                        containerColor = MaterialTheme.colorScheme.errorContainer,\n                        contentColor = MaterialTheme.colorScheme.onErrorContainer\n                    ) {\n                        Icon(\n                            Icons.Filled.Delete,\n                            contentDescription = stringResource(R.string.delete)\n                        )\n                    }\n                }\n            }\n        }\n    }\n}\n\n@Composable\nfun NewAccount(navController: NavController) {\n\n    val alertTitle = remember { mutableStateOf(\"\") }\n    val alertMessage = remember { mutableStateOf(\"\") }\n    val showAlert = remember { mutableStateOf(false) }\n\n    if (showAlert.value)\n        AlertDialog(\n            showDialog = showAlert,\n            title = alertTitle.value,\n            message = alertMessage.value,\n            lastButtonText = stringResource(R.string.ok),\n        )\n\n    fun createNew(ctx: Context, newAor: String): Account? {\n\n        val aor = if (newAor.startsWith(\"sip:\"))\n            newAor\n        else\n            \"sip:$newAor\"\n\n        if (!Utils.checkAor(aor)) {\n            alertTitle.value = ctx.getString(R.string.notice)\n            alertMessage.value =\n                String.format(ctx.getString(R.string.invalid_aor), aor.split(\":\")[1])\n            showAlert.value = true\n            return null\n        }\n\n        if (Account.ofAor(aor) != null) {\n            alertTitle.value = ctx.getString(R.string.notice)\n            alertMessage.value =\n                String.format(ctx.getString(R.string.account_exists), aor.split(\":\")[1])\n            showAlert.value = true\n            return null\n        }\n\n        val ua = UserAgent.uaAlloc(\n            \"<$aor>;stunserver=\\\"stun:stun.l.google.com:19302\\\";regq=0.5;pubint=0;regint=0;check_origin=no;mwi=no\"\n        )\n        if (ua == null) {\n            alertTitle.value = ctx.getString(R.string.notice)\n            alertMessage.value = ctx.getString(R.string.account_allocation_failure)\n            showAlert.value = true\n            return null\n        }\n\n        val acc = ua.account\n        acc.checkOrigin = true\n        Log.d(TAG, \"Allocated UA ${ua.uap} with SIP URI ${acc.luri}\")\n        Account.saveAccounts()\n        return acc\n    } // createNew\n\n    var newAor by remember { mutableStateOf(\"\") }\n    val focusManager = LocalFocusManager.current\n\n    Row(\n        modifier = Modifier\n            .fillMaxWidth()\n            .navigationBarsPadding()\n            .padding(start = 16.dp, end = 8.dp, top = 10.dp, bottom = 16.dp),\n        verticalAlignment = Alignment.CenterVertically\n    ) {\n        val ctx = LocalContext.current\n        val newAccountTitle = stringResource(R.string.new_account)\n        val accountsHelp = stringResource(R.string.accounts_help)\n        OutlinedTextField(\n            value = newAor,\n            placeholder = { Text(text = stringResource(R.string.new_account)) },\n            onValueChange = { newAor = it },\n            modifier = Modifier\n                .weight(1f)\n                .padding(end = 8.dp)\n                .verticalScroll(rememberScrollState())\n                .clickable {\n                    alertTitle.value = newAccountTitle\n                    alertMessage.value = accountsHelp\n                    showAlert.value = true\n                },\n            singleLine = false,\n            trailingIcon = {\n                if (newAor.isNotEmpty()) {\n                    Icon(\n                        Icons.Outlined.Clear,\n                        contentDescription = \"Clear\",\n                        modifier = Modifier.clickable { newAor = \"\" },\n                        tint = MaterialTheme.colorScheme.onSurfaceVariant\n                    )\n                }\n            },\n            label = { Text(stringResource(R.string.new_account)) },\n            textStyle = TextStyle(fontSize = 18.sp),\n            keyboardOptions = KeyboardOptions(\n                keyboardType = KeyboardType.Text,\n            )\n        )\n        SmallFloatingActionButton(\n            modifier = Modifier.offset(y = 2.dp),\n            onClick = {\n                val account = createNew(ctx, newAor.trim())\n                if (account != null) {\n                    navController.navigate(\"account/${account.aor}/new\")\n                    newAor = \"\"\n                    focusManager.clearFocus()\n                }\n            },\n            containerColor = MaterialTheme.colorScheme.secondary,\n            contentColor = MaterialTheme.colorScheme.onSecondary\n        ) {\n            Icon(\n                imageVector = Icons.Filled.Add,\n                modifier = Modifier.size(36.dp),\n                contentDescription = stringResource(R.string.add)\n            )\n        }\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/AndroidContactScreen.kt",
    "content": "package com.tutpro.baresip\n\nimport android.content.Context\nimport android.content.Intent\nimport android.net.Uri\nimport androidx.compose.foundation.Canvas\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.asPaddingValues\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.imePadding\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.statusBars\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.lazy.rememberLazyListState\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.automirrored.filled.Chat\nimport androidx.compose.material.icons.outlined.Call\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.TopAppBarDefaults\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.SolidColor\nimport androidx.compose.ui.layout.ContentScale\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.navigation.NavController\nimport androidx.navigation.NavGraphBuilder\nimport androidx.navigation.NavType\nimport androidx.navigation.compose.composable\nimport androidx.navigation.navArgument\nimport coil.compose.AsyncImage\n\nfun NavGraphBuilder.androidContactScreenRoute(navController: NavController, viewModel: ViewModel) {\n    composable(\n        route = \"android_contact/{name}\",\n        arguments = listOf(navArgument(\"name\") { type = NavType.StringType })\n    ) { backStackEntry ->\n        val ctx = LocalContext.current\n        val name = backStackEntry.arguments?.getString(\"name\")!!\n        ContactScreen(\n            ctx = ctx,\n            viewModel = viewModel,\n            navController = navController,\n            name = name\n        )\n    }\n}\n\n@Composable\nprivate fun ContactScreen(ctx: Context, viewModel: ViewModel, navController: NavController, name: String) {\n    val contact = Contact.androidContact(name)\n    if (contact == null) {\n        Log.e(TAG, \"No Android contact found with name $name\")\n        navController.navigateUp()\n    }\n    Scaffold(\n        modifier = Modifier.fillMaxSize().imePadding(),\n        containerColor = MaterialTheme.colorScheme.background,\n        topBar = {\n            Column(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .background(MaterialTheme.colorScheme.background)\n                    .padding(top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding())\n            ) {\n                TopAppBar(name, navController)\n            }\n        },\n        content = { contentPadding ->\n            ContactContent(ctx, viewModel, navController, contentPadding, contact!!)\n        }\n    )\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun TopAppBar(title: String, navController: NavController) {\n    TopAppBar(\n        title = { Text(text = title, fontWeight = FontWeight.Bold) },\n        colors = TopAppBarDefaults.topAppBarColors(\n            containerColor = MaterialTheme.colorScheme.primary,\n            navigationIconContentColor = MaterialTheme.colorScheme.onPrimary,\n            titleContentColor = MaterialTheme.colorScheme.onPrimary,\n        ),\n        windowInsets = WindowInsets(0, 0, 0, 0),\n        navigationIcon = {\n            IconButton(onClick = { navController.navigateUp() }) {\n                Icon(\n                    imageVector = Icons.AutoMirrored.Filled.ArrowBack,\n                    contentDescription = \"Back\",\n                )\n            }\n        }\n    )\n}\n\n@Composable\nprivate fun ContactContent(\n    ctx: Context,\n    viewModel: ViewModel,\n    navController: NavController,\n    contentPadding: PaddingValues,\n    contact: Contact.AndroidContact\n) {\n    Column(\n        modifier = Modifier\n            .fillMaxWidth()\n            .background(MaterialTheme.colorScheme.background)\n            .padding(contentPadding)\n            .padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 52.dp),\n        verticalArrangement = Arrangement.spacedBy(12.dp),\n    ) {\n        Avatar(contact)\n        ContactName(contact.name)\n        Uris(ctx, viewModel, navController, contact)\n    }\n}\n\n@Composable\nprivate fun TextAvatar(text: String, color: Int) {\n    Box(\n        modifier = Modifier.size(avatarSize.dp),\n        contentAlignment = Alignment.Center\n    ) {\n        Canvas(modifier = Modifier.fillMaxSize()) {\n            drawCircle(SolidColor(Color(color)))\n        }\n        Text(text, fontSize = 72.sp, color = Color.White)\n    }\n}\n\n@Composable\nprivate fun ImageAvatar(uri: Uri) {\n    AsyncImage(\n        model = uri,\n        contentDescription = stringResource(R.string.avatar_image),\n        contentScale = ContentScale.Crop,\n        modifier = Modifier.size(avatarSize.dp).clip(CircleShape)\n    )\n}\n\n@Composable\nprivate fun Avatar(contact: Contact.AndroidContact) {\n    Row(\n        Modifier.fillMaxWidth(),\n        verticalAlignment = Alignment.CenterVertically,\n        horizontalArrangement = Arrangement.Center\n    ) {\n        val color = contact.color\n        val name = contact.name\n        val thumbnailUri = contact.thumbnailUri\n        if (thumbnailUri != null)\n            ImageAvatar(thumbnailUri)\n        else\n            TextAvatar(if (name == \"\") \"\" else name[0].toString(), color)\n    }\n}\n\n@Composable\nprivate fun ContactName(name: String) {\n    Row(\n        Modifier.fillMaxWidth().padding(top = 16.dp, bottom = 8.dp),\n        verticalAlignment = Alignment.CenterVertically,\n        horizontalArrangement = Arrangement.Start\n    ) {\n        Text(name, fontSize = 24.sp, color = MaterialTheme.colorScheme.onBackground)\n    }\n}\n\n@Composable\nprivate fun Uris(\n    ctx: Context,\n    viewModel: ViewModel,\n    navController: NavController,\n    contact: Contact.AndroidContact\n) {\n    val lazyListState = rememberLazyListState()\n    LazyColumn(\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(start = 16.dp, end = 4.dp)\n            .background(MaterialTheme.colorScheme.background),\n        state = lazyListState,\n        verticalArrangement = Arrangement.spacedBy(12.dp),\n    ) {\n        items(contact.uris) { uri ->\n            Row(\n                verticalAlignment = Alignment.CenterVertically,\n            ) {\n                Text(\n                    text = uri.substringAfter(\":\"),\n                    modifier = Modifier.weight(1f),\n                    fontSize = 18.sp,\n                    color = MaterialTheme.colorScheme.onBackground,\n                )\n\n                // Chat Button\n                IconButton(\n                    onClick = {\n                        val aor = viewModel.selectedAor.value\n                        val ua = UserAgent.ofAor(aor)\n                        if (ua == null)\n                            Log.w(TAG, \"Message clickable did not find AoR $aor\")\n                        else {\n                            val intent = Intent(ctx, MainActivity::class.java)\n                            intent.putExtra(\"uap\", ua.uap)\n                            intent.putExtra(\"peer\", uri)\n                            handleIntent(ctx, viewModel, intent, \"message\")\n                            navController.navigate(\"main\") {\n                                popUpTo(\"main\") { inclusive = false }\n                                launchSingleTop = true\n                            }\n                        }\n                    }\n                ) {\n                    Icon(\n                        imageVector = Icons.AutoMirrored.Filled.Chat,\n                        contentDescription = \"Send Message\",\n                        tint = MaterialTheme.colorScheme.onBackground\n                    )\n                }\n\n                // Call Button\n                IconButton(\n                    onClick = {\n                        val aor = viewModel.selectedAor.value\n                        val ua = UserAgent.ofAor(aor)\n                        if (ua == null)\n                            Log.w(TAG, \"Call clickable did not find AoR $aor\")\n                        else {\n                            val intent = Intent(ctx, MainActivity::class.java)\n                            intent.putExtra(\"uap\", ua.uap)\n                            intent.putExtra(\"peer\", uri)\n                            handleIntent(ctx, viewModel, intent, \"call\")\n                            navController.navigate(\"main\") {\n                                popUpTo(\"main\") { inclusive = false }\n                                launchSingleTop = true\n                            }\n                        }\n                    }\n                ) {\n                    Icon(\n                        imageVector = Icons.Outlined.Call,\n                        contentDescription = \"Call\",\n                        tint = MaterialTheme.colorScheme.onBackground\n                    )\n                }\n            }\n        }\n    }\n}\n\n\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/Api.kt",
    "content": "package com.tutpro.baresip\n\nobject Api {\n\n    const val VIDMODE_OFF = 0\n    // const val VIDMODE_ON = 1\n    const val ANSWERMODE_MANUAL = 0\n    const val ANSWERMODE_AUTO = 2\n    const val DTMFMODE_RTP_EVENT = 0\n    const val DTMFMODE_SIP_INFO = 1\n    const val DTMFMODE_AUTO = 2\n    const val REL100_DISABLED = 0\n    const val REL100_ENABLED = 1\n    // const cal REL100_REQUIRED = 2\n\n    const val SDP_INACTIVE = 0\n    const val SDP_RECVONLY = 1\n    // const val SDP_SENDONLY = 2\n    // const val SDP_SENDRECV = 3\n\n    const val CALL_STATE_EARLY = 4\n\n    const val REPLACES = 1\n\n    external fun account_set_display_name(acc: Long, dn: String): Int\n    external fun account_display_name(acc: Long): String\n    external fun account_aor(acc: Long): String\n    external fun account_luri(acc: Long): String\n    external fun account_auth_user(acc: Long): String\n    external fun account_set_auth_user(acc: Long, user: String): Int\n    external fun account_auth_pass(acc: Long): String\n    external fun account_set_auth_pass(acc: Long, pass: String): Int\n    external fun account_outbound(acc: Long, ix: Int): String\n    external fun account_set_outbound(acc: Long, ob: String, ix: Int): Int\n    external fun account_set_sipnat(acc: Long, sipnat: String): Int\n    external fun account_audio_codec(acc: Long, ix: Int): String\n    external fun account_regint(acc: Long): Int\n    external fun account_set_regint(acc: Long, regint: Int): Int\n    external fun account_check_origin(acc: Long): Boolean\n    external fun account_set_check_origin(acc: Long, check: Boolean)\n    external fun account_stun_uri(acc: Long): String\n    external fun account_set_stun_uri(acc: Long, uri: String): Int\n    external fun account_stun_user(acc: Long): String\n    external fun account_set_stun_user(acc: Long, user: String): Int\n    external fun account_stun_pass(acc: Long): String\n    external fun account_set_stun_pass(acc: Long, pass: String): Int\n    external fun account_mediaenc(acc: Long): String\n    external fun account_set_mediaenc(acc: Long, mediaenc: String): Int\n    external fun account_medianat(acc: Long): String\n    external fun account_set_medianat(acc: Long, medianat: String): Int\n    external fun account_set_audio_codecs(acc: Long, codecs: String): Int\n    external fun account_set_video_codecs(acc: Long, codecs: String): Int\n    external fun account_set_mwi(acc: Long, value: Boolean): Int\n    external fun account_vm_uri(acc: Long): String\n    external fun account_answermode(acc: Long): Int\n    external fun account_set_answermode(acc: Long, mode: Int): Int\n    external fun account_sip_autoredirect(acc: Long): Boolean\n    external fun account_set_sip_autoredirect(acc: Long, allow: Boolean)\n    external fun account_rel100_mode(acc: Long): Int\n    external fun account_set_rel100_mode(acc: Long, mode: Int): Int\n    external fun account_rtcp_mux(acc: Long): Boolean\n    external fun account_set_rtcp_mux(acc: Long, value: Boolean): Int\n    external fun account_dtmfmode(acc: Long): Int\n    external fun account_set_dtmfmode(acc: Long, mode: Int): Int\n    external fun account_extra(acc: Long): String\n    external fun account_debug(acc: Long)\n\n    external fun uag_reset_transp(register: Boolean, reinvite: Boolean)\n    external fun uag_enable_sip_trace(enable: Boolean)\n\n    external fun ua_account(uap: Long): Long\n    external fun ua_update_account(uap: Long): Long\n    external fun ua_alloc(uri: String): Long\n    external fun ua_destroy(uap: Long)\n    external fun ua_register(uap: Long): Int\n    @Suppress(\"unused\")\n    external fun ua_isregistered(uap: Long): Boolean\n    external fun ua_unregister(uap: Long)\n    external fun ua_accept(uap: Long, msg: Long)\n    external fun ua_hangup(uap: Long, callp: Long, code: Int, reason: String)\n    external fun ua_call_alloc(uap: Long, xcallp: Long, video: Int): Long\n    external fun ua_answer(uap: Long, callp: Long, video: Int)\n    @Suppress(\"unused\")\n    external fun ua_add_custom_header(uap: Long, name: String, body: String)\n    @Suppress(\"unused\")\n    external fun ua_debug(uap: Long)\n\n    external fun sip_treply(msg: Long, code: Int, reason: String)\n\n    external fun bevent_stop(event: Long)\n\n    external fun call_connect(callp: Long, peer_uri: String): Int\n    external fun call_hold(callp: Long, hold: Boolean): Boolean\n    @Suppress(\"unused\")\n    external fun call_ismuted(callp: Long): Boolean\n    external fun call_transfer(callp: Long, peer_uri: String): Int\n    external fun call_send_digit(callp: Long, digit: Char): Int\n    external fun call_notify_sipfrag(callp: Long, code: Int, reason: String)\n    @Suppress(\"unused\")\n    external fun call_start_audio(callp: Long)\n    external fun call_audio_codecs(callp: Long): String\n    external fun call_duration(callp: Long): Int\n    external fun call_stats(callp: Long, stream: String): String\n    external fun call_state(callp: Long): Int\n    external fun call_replaces(callp: Long): Boolean\n    external fun call_replace_transfer(xfer_callp: Long, callp: Long): Boolean\n    external fun call_peer_uri(callp: Long): String\n    external fun call_diverter_uri(callp: Long): String\n\n    external fun call_supported(callp: Long, header: Int): Boolean\n    external fun call_destroy(callp: Long)\n\n    external fun calls_mute(mute: Boolean)\n\n    external fun message_send(uap: Long, peer_uri: String, message: String, time: String): Int\n\n    external fun audio_codecs(): String\n    external fun video_codecs(): String\n\n    external fun log_level_set(level: Int)\n\n    external fun net_use_nameserver(servers: String): Int\n    external fun net_add_address_ifname(ip_addr: String, if_name: String): Int\n    external fun net_rm_address(ip_addr: String): Int\n    external fun net_debug()\n    @Suppress(\"unused\")\n    external fun net_dns_debug()\n\n    external fun config_verify_server_set(verify: Boolean)\n\n    external fun cmd_exec(cmd: String): Int\n\n    external fun module_load(module: String): Int\n    external fun module_unload(module: String)\n\n    external fun AAudio_open_stream(): Int\n    external fun AAudio_close_stream()\n}\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/AudioScreen.kt",
    "content": "package com.tutpro.baresip\n\nimport android.content.Context\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.asPaddingValues\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.imePadding\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.statusBars\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.text.KeyboardOptions\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.filled.ArrowDropDown\nimport androidx.compose.material.icons.filled.Check\nimport androidx.compose.material3.DropdownMenu\nimport androidx.compose.material3.DropdownMenuItem\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.HorizontalDivider\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.OutlinedTextField\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Switch\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.TopAppBarDefaults\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.input.KeyboardType\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.navigation.NavController\nimport androidx.navigation.NavGraphBuilder\nimport androidx.navigation.compose.composable\nimport com.tutpro.baresip.CustomElements.AlertDialog\nimport com.tutpro.baresip.CustomElements.verticalScrollbar\n\nenum class Result {\n    OK, ERROR, RESTART\n}\n\nfun NavGraphBuilder.audioScreenRoute(\n    navController: NavController,\n) {\n    composable(\"audio\") {\n        val ctx = LocalContext.current\n        AudioScreen(\n            onBack = {\n                navController.navigateUp()\n            },\n            checkOnClick = {\n                when (checkOnClick(ctx)) {\n                    Result.OK -> navController.navigateUp()\n                    Result.RESTART -> {\n                        navController.previousBackStackEntry\n                            ?.savedStateHandle\n                            ?.set(\"audio_settings_result\", true)\n                        navController.navigateUp()\n                    }\n                    Result.ERROR -> {}\n                }\n            },\n        )\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun AudioScreen(\n    onBack: () -> Unit,\n    checkOnClick: () -> Unit,\n) {\n    Scaffold(\n        modifier = Modifier.fillMaxSize().imePadding(),\n        containerColor = MaterialTheme.colorScheme.background,\n        topBar = {\n            Column(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .background(MaterialTheme.colorScheme.background)\n                    .padding(\n                        top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()\n                    )\n            ) {\n                TopAppBar(\n                    title = {\n                        Text(\n                            text = stringResource(R.string.audio_settings),\n                            fontWeight = FontWeight.Bold\n                        )\n                    },\n                    colors = TopAppBarDefaults.topAppBarColors(\n                        containerColor = MaterialTheme.colorScheme.primary,\n                        navigationIconContentColor = MaterialTheme.colorScheme.onPrimary,\n                        titleContentColor = MaterialTheme.colorScheme.onPrimary,\n                        actionIconContentColor = MaterialTheme.colorScheme.onPrimary\n                    ),\n                    navigationIcon = {\n                        IconButton(onClick = onBack) {\n                            Icon(\n                                imageVector = Icons.AutoMirrored.Filled.ArrowBack,\n                                contentDescription = null,\n                            )\n                        }\n                    },\n                    windowInsets = WindowInsets(0, 0, 0, 0),\n                    actions = {\n                        IconButton(onClick = checkOnClick) {\n                            Icon(\n                                imageVector = Icons.Filled.Check,\n                                contentDescription = \"Check\"\n                            )\n                        }\n                    },\n                )\n            }\n        }\n    ) { contentPadding ->\n        AudioContent(contentPadding)\n    }\n}\n\nprivate var newCallVolume = BaresipService.callVolume\nprivate var oldMicGain = \"\"\nprivate var newMicGain = \"\"\nprivate var oldSpeakerPhone = BaresipService.speakerPhone\nprivate var newSpeakerPhone = oldSpeakerPhone\nprivate var oldAudioModules = ArrayList<String>()\nprivate var newAudioModules = mutableMapOf<String, Boolean>()\nprivate var oldOpusBitrate = \"\"\nprivate var newOpusBitrate = oldOpusBitrate\nprivate var oldOpusPacketLoss = \"\"\nprivate var newOpusPacketLoss = oldOpusPacketLoss\nprivate var newAudioDelay = BaresipService.audioDelay.toString()\nprivate var newToneCountry = BaresipService.toneCountry\n\nprivate var save = false\n\nprivate val alertTitle = mutableStateOf(\"\")\nprivate val alertMessage = mutableStateOf(\"\")\nprivate val showAlert = mutableStateOf(false)\n\n@Composable\nprivate fun AudioContent(contentPadding: PaddingValues) {\n\n    oldAudioModules = Config.variables(\"module\")\n    oldOpusBitrate = Config.variable(\"opus_bitrate\")\n    oldOpusPacketLoss = Config.variable(\"opus_packet_loss\")\n    if (!BaresipService.agcAvailable)\n        oldMicGain = Config.variable(\"augain\")\n\n    if (showAlert.value) {\n        AlertDialog(\n            showDialog = showAlert,\n            title = alertTitle.value,\n            message = alertMessage.value,\n            lastButtonText = stringResource(R.string.ok),\n        )\n    }\n\n    val scrollState = rememberScrollState()\n\n    Column(\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(contentPadding)\n            .padding(top = 8.dp, bottom = 8.dp, start = 16.dp, end = 4.dp)\n            .verticalScrollbar(scrollState)\n            .verticalScroll(state = scrollState),\n        verticalArrangement = Arrangement.spacedBy(12.dp),\n    ) {\n        CallVolume()\n        MicGain()\n        SpeakerPhone()\n        AudioModules()\n        OpusBitRate()\n        OpusPacketLoss()\n        AudioDelay()\n        ToneCountry()\n    }\n}\n\n@Composable\nprivate fun CallVolume() {\n    Row(\n        Modifier.fillMaxWidth().padding(end=10.dp),\n        verticalAlignment = Alignment.CenterVertically,\n        horizontalArrangement = Arrangement.Start\n    ) {\n        val defaultCallVolumeTitle = stringResource(R.string.default_call_volume)\n        val defaultCallVolumeHelp = stringResource(R.string.default_call_volume_help)\n        Text(text = defaultCallVolumeTitle,\n            modifier = Modifier.weight(1f)\n                .clickable {\n                    alertTitle.value = defaultCallVolumeTitle\n                    alertMessage.value = defaultCallVolumeHelp\n                    showAlert.value = true\n                },\n            fontSize = 18.sp)\n        val isDropDownExpanded = remember {\n            mutableStateOf(false)\n        }\n        val volNames = listOf(\"--\",  \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"10\")\n        val volValues = listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)\n        val itemPosition = remember {\n            mutableIntStateOf(volValues.indexOf(BaresipService.callVolume))\n        }\n        Box {\n            Row(\n                horizontalArrangement = Arrangement.Center,\n                verticalAlignment = Alignment.CenterVertically,\n                modifier = Modifier.clickable {\n                    isDropDownExpanded.value = true\n                }\n            ) {\n                Text(text = volNames[itemPosition.intValue])\n                Icon(\n                    imageVector = Icons.Filled.ArrowDropDown,\n                    contentDescription = null,\n                    modifier = Modifier.size(36.dp)\n                )\n            }\n            DropdownMenu(\n                expanded = isDropDownExpanded.value,\n                onDismissRequest = {\n                    isDropDownExpanded.value = false\n                }) {\n                volNames.forEachIndexed { index, vol ->\n                    DropdownMenuItem(text = {\n                        Text(text = vol)\n                    },\n                        onClick = {\n                            isDropDownExpanded.value = false\n                            itemPosition.intValue = index\n                            newCallVolume = volValues[index]\n                        })\n                    if (index < 10)\n                        HorizontalDivider(thickness = 1.dp)\n                }\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun MicGain() {\n    if (!BaresipService.agcAvailable)\n        Row(\n            Modifier.fillMaxWidth().padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            val microphoneGainTitle = stringResource(R.string.microphone_gain)\n            val microphoneGainHelp = stringResource(R.string.microphone_gain_help)\n            var micGain by remember { mutableStateOf(oldMicGain) }\n            newMicGain = micGain\n            OutlinedTextField(\n                value = micGain,\n                placeholder = { Text(microphoneGainTitle) },\n                onValueChange = {\n                    micGain = it\n                    newMicGain = micGain\n                },\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .clickable {\n                        alertTitle.value = microphoneGainTitle\n                        alertMessage.value = microphoneGainHelp\n                        showAlert.value = true\n                    },\n                textStyle = androidx.compose.ui.text.TextStyle(fontSize = 18.sp),\n                label = { Text(microphoneGainTitle) },\n                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)\n            )\n        }\n}\n\n@Composable\nprivate fun SpeakerPhone() {\n    Row(\n        Modifier.fillMaxWidth().padding(end=10.dp),\n        verticalAlignment = Alignment.CenterVertically,\n        horizontalArrangement = Arrangement.Start\n    ) {\n        val speakerPhoneTitle = stringResource(R.string.speaker_phone)\n        val speakerPhoneHelp = stringResource(R.string.speaker_phone_help)\n        Text(text = speakerPhoneTitle,\n            modifier = Modifier.weight(1f)\n                .clickable {\n                    alertTitle.value = speakerPhoneTitle\n                    alertMessage.value = speakerPhoneHelp\n                    showAlert.value = true\n                },\n            fontSize = 18.sp)\n        var speakerPhone by remember { mutableStateOf(BaresipService.speakerPhone) }\n        Switch(\n            checked = speakerPhone,\n            onCheckedChange = {\n                speakerPhone = it\n                newSpeakerPhone = speakerPhone\n            }\n        )\n    }\n}\n\n@Composable\nprivate fun AudioModules() {\n    Column(\n        modifier = Modifier.fillMaxWidth(),\n        horizontalAlignment = Alignment.Start,\n    ) {\n        val audioModulesTitle = stringResource(R.string.audio_modules_title)\n        val audioModulesHelp = stringResource(R.string.audio_modules_help)\n        Text(text = audioModulesTitle,\n            fontSize = 18.sp,\n            modifier = Modifier.clickable {\n                alertTitle.value = audioModulesTitle\n                alertMessage.value = audioModulesHelp\n                showAlert.value = true\n            })\n        for (module in Config.audioModules) {\n            Row(horizontalArrangement = Arrangement.Start,\n                verticalAlignment = Alignment.CenterVertically,\n                modifier = Modifier.padding(start = 18.dp, end = 10.dp)\n            ) {\n                Text(text = String.format(stringResource(R.string.bullet_item), module), fontSize = 18.sp)\n                Spacer(modifier = Modifier.weight(1f))\n                var checked by remember { mutableStateOf(oldAudioModules.contains(\"${module}.so\")) }\n                Switch(\n                    checked = checked,\n                    onCheckedChange = {\n                        checked = it\n                        newAudioModules[module] = checked\n                    }\n                )\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun OpusBitRate() {\n    Row(\n        Modifier.fillMaxWidth().padding(end = 10.dp),\n        verticalAlignment = Alignment.CenterVertically,\n        horizontalArrangement = Arrangement.Start\n    ) {\n        val opusBitRateTitle = stringResource(R.string.opus_bit_rate)\n        val opusBitRateHelp = stringResource(R.string.opus_bit_rate_help)\n        var opusBitrate by remember { mutableStateOf(oldOpusBitrate) }\n        newOpusBitrate = opusBitrate\n        OutlinedTextField(\n            value = opusBitrate,\n            placeholder = { Text(opusBitRateTitle) },\n            onValueChange = {\n                opusBitrate = it\n                newOpusBitrate = opusBitrate\n            },\n            modifier = Modifier\n                .fillMaxWidth()\n                .clickable {\n                    alertTitle.value = opusBitRateTitle\n                    alertMessage.value = opusBitRateHelp\n                    showAlert.value = true\n                },\n            textStyle = androidx.compose.ui.text.TextStyle(fontSize = 18.sp),\n            label = { Text(opusBitRateTitle) },\n            keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)\n        )\n    }\n}\n\n@Composable\nprivate fun OpusPacketLoss() {\n    Row(\n        Modifier.fillMaxWidth().padding(end = 10.dp, top = 8.dp),\n        verticalAlignment = Alignment.CenterVertically,\n        horizontalArrangement = Arrangement.Start\n    ) {\n        val opusPacketLossTitle = stringResource(R.string.opus_packet_loss)\n        val opusPacketLossHelp = stringResource(R.string.opus_packet_loss_help)\n        var opusPacketLoss by remember { mutableStateOf(oldOpusPacketLoss) }\n        newOpusPacketLoss = opusPacketLoss\n        OutlinedTextField(\n            value = opusPacketLoss,\n            placeholder = { Text(opusPacketLossTitle) },\n            onValueChange = {\n                opusPacketLoss = it\n                newOpusPacketLoss = opusPacketLoss\n            },\n            modifier = Modifier\n                .fillMaxWidth()\n                .clickable {\n                    alertTitle.value = opusPacketLossTitle\n                    alertMessage.value = opusPacketLossHelp\n                    showAlert.value = true\n                },\n            textStyle = androidx.compose.ui.text.TextStyle(fontSize = 18.sp),\n            label = { Text(opusPacketLossTitle) },\n            keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)\n        )\n    }\n}\n\n@Composable\nprivate fun AudioDelay() {\n    Row(\n        Modifier.fillMaxWidth().padding(end = 10.dp, top = 8.dp),\n        verticalAlignment = Alignment.CenterVertically,\n        horizontalArrangement = Arrangement.Start\n    ) {\n        val audioDelayTitle = stringResource(R.string.audio_delay)\n        val audioDelayHelp = stringResource(R.string.audio_delay_help)\n        var audioDelay by remember { mutableStateOf(BaresipService.audioDelay.toString()) }\n        newAudioDelay = audioDelay\n        OutlinedTextField(\n            value = audioDelay,\n            placeholder = { Text(audioDelayTitle) },\n            onValueChange = {\n                audioDelay = it\n                newAudioDelay = audioDelay\n            },\n            modifier = Modifier\n                .fillMaxWidth()\n                .clickable {\n                    alertTitle.value = audioDelayTitle\n                    alertMessage.value = audioDelayHelp\n                    showAlert.value = true\n                },\n            textStyle = androidx.compose.ui.text.TextStyle(fontSize = 18.sp),\n            label = { Text(audioDelayTitle) },\n            keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)\n        )\n    }\n}\n\n@Composable\nprivate fun ToneCountry() {\n    Row(\n        Modifier.fillMaxWidth().padding(end=10.dp),\n        verticalAlignment = Alignment.CenterVertically,\n        horizontalArrangement = Arrangement.Start\n    ) {\n        val toneCountryTitle = stringResource(R.string.tone_country)\n        val toneCountryHelp = stringResource(R.string.tone_country_help)\n        Text(text = toneCountryTitle,\n            modifier = Modifier.weight(1f)\n                .clickable {\n                    alertTitle.value = toneCountryTitle\n                    alertMessage.value = toneCountryHelp\n                    showAlert.value = true\n                },\n            fontSize = 18.sp)\n        val isDropDownExpanded = remember {\n            mutableStateOf(false)\n        }\n        val countryNames = arrayListOf(\"BG\", \"BR\", \"DE\", \"CZ\", \"ES\", \"FI\", \"FR\", \"GB\", \"JP\", \"NO\", \"NZ\", \"SE\", \"RU\", \"US\")\n        val countryValues = arrayListOf(\"bg\", \"br\", \"de\", \"cz\", \"es\", \"fi\", \"fr\", \"uk\", \"jp\", \"no\", \"nz\", \"se\", \"ru\", \"us\")\n        val itemPosition = remember {\n            mutableIntStateOf(countryValues.indexOf(BaresipService.toneCountry))\n        }\n        Box {\n            Row(\n                horizontalArrangement = Arrangement.Center,\n                verticalAlignment = Alignment.CenterVertically,\n                modifier = Modifier.clickable {\n                    isDropDownExpanded.value = true\n                }\n            ) {\n                Text(text = countryNames[itemPosition.intValue])\n                Icon(\n                    imageVector = Icons.Filled.ArrowDropDown,\n                    contentDescription = null,\n                    modifier = Modifier.size(36.dp)\n                )\n            }\n            DropdownMenu(\n                expanded = isDropDownExpanded.value,\n                onDismissRequest = {\n                    isDropDownExpanded.value = false\n                }) {\n                countryNames.forEachIndexed { index, name ->\n                    DropdownMenuItem(text = {\n                        Text(text = name)\n                    },\n                        onClick = {\n                            isDropDownExpanded.value = false\n                            itemPosition.intValue = index\n                            newToneCountry = countryValues[index]\n                        })\n                    if (index < 10)\n                        HorizontalDivider(thickness = 1.dp)\n                }\n            }\n        }\n    }\n}\n\nprivate fun checkOnClick(ctx: Context): Result {\n\n    var restart = false\n\n    if (BaresipService.callVolume != newCallVolume) {\n        BaresipService.callVolume = newCallVolume\n        Config.replaceVariable(\"call_volume\", newCallVolume.toString())\n        save = true\n    }\n\n    if (!BaresipService.agcAvailable) {\n        var gain = newMicGain.trim()\n        if (!gain.contains(\".\"))\n            gain = \"$gain.0\"\n        if (gain != oldMicGain) {\n            if (!checkMicGain(gain)) {\n                alertTitle.value = ctx.getString(R.string.notice)\n                alertMessage.value = \"${ctx.getString(R.string.invalid_microphone_gain)}: $gain.\"\n                showAlert.value = true\n                return Result.ERROR\n            }\n            if (gain == \"1.0\") {\n                Api.module_unload(\"augain\")\n                Config.removeVariableValue(\"module\", \"augain.so\")\n                Config.replaceVariable(\"augain\", \"1.0\")\n            } else {\n                if (oldMicGain == \"1.0\") {\n                    if (Api.module_load(\"augain\") != 0) {\n                        alertTitle.value = ctx.getString(R.string.error)\n                        alertMessage.value = ctx.getString(R.string.failed_to_load_module) + \": augain.so\"\n                        showAlert.value = true\n                        return Result.ERROR\n                    }\n                    Config.addVariable(\"module\", \"augain.so\")\n                }\n                Config.replaceVariable(\"augain\", gain)\n                Api.cmd_exec(\"augain $gain\")\n            }\n            save = true\n        }\n    }\n\n    if (newSpeakerPhone != BaresipService.speakerPhone) {\n        BaresipService.speakerPhone = newSpeakerPhone\n        Config.replaceVariable(\"speaker_phone\",\n            if (BaresipService.speakerPhone) \"yes\" else \"no\")\n        save = true\n    }\n\n    for (module in Config.audioModules) {\n        if (newAudioModules[module] != null) {\n            if (newAudioModules[module]!!) {\n                if (!oldAudioModules.contains(\"${module}.so\")) {\n                    if (Api.module_load(\"${module}.so\") != 0) {\n                        alertTitle.value = ctx.getString(R.string.error)\n                        alertMessage.value = \"${ctx.getString(R.string.failed_to_load_module)}: ${module}.so\"\n                        showAlert.value = true\n                        return Result.ERROR\n                    }\n                    Config.addVariable(\"module\", \"${module}.so\")\n                    save = true\n                }\n            } else if (oldAudioModules.contains(\"${module}.so\")) {\n                Api.module_unload(\"${module}.so\")\n                Config.removeVariableValue(\"module\", \"${module}.so\")\n                for (ua in BaresipService.uas.value)\n                    ua.account.removeAudioCodecs(module)\n                Account.saveAccounts()\n                save = true\n            }\n        }\n    }\n\n    if (newOpusBitrate != oldOpusBitrate) {\n        if (!checkOpusBitRate(newOpusBitrate)) {\n            alertTitle.value = ctx.getString(R.string.notice)\n            alertMessage.value = \"${ctx.getString(R.string.invalid_opus_bitrate)}: $newOpusBitrate.\"\n            showAlert.value = true\n            return Result.ERROR\n        }\n        Config.replaceVariable(\"opus_bitrate\", newOpusBitrate)\n        restart = true\n        save = true\n    }\n\n    if (newOpusPacketLoss != oldOpusPacketLoss) {\n        if (!checkOpusPacketLoss(newOpusPacketLoss)) {\n            alertTitle.value = ctx.getString(R.string.notice)\n            alertMessage.value = \"${ctx.getString(R.string.invalid_opus_packet_loss)}: $newOpusPacketLoss\"\n            showAlert.value = true\n            return Result.ERROR\n        }\n        Config.replaceVariable(\"opus_packet_loss\", newOpusPacketLoss)\n        restart = true\n        save = true\n    }\n\n    val audioDelay = newAudioDelay.trim()\n    if (audioDelay != BaresipService.audioDelay.toString()) {\n        if (!checkAudioDelay(audioDelay)) {\n            alertTitle.value = ctx.getString(R.string.notice)\n            alertMessage.value = String.format(ctx.getString(R.string.invalid_audio_delay), audioDelay)\n            showAlert.value = true\n            return Result.ERROR\n        }\n        Config.replaceVariable(\"audio_delay\", audioDelay)\n        BaresipService.audioDelay = audioDelay.toLong()\n        save = true\n    }\n\n    if (BaresipService.toneCountry != newToneCountry) {\n        BaresipService.toneCountry = newToneCountry\n        Config.replaceVariable(\"tone_country\", newToneCountry)\n        save = true\n    }\n\n    if (save) Config.save()\n\n    return if (restart) Result.RESTART else Result.OK\n}\n\nprivate fun checkMicGain(micGain: String): Boolean {\n    val number =\n        try {\n            micGain.toDouble()\n        } catch (_: NumberFormatException) {\n            return false\n        }\n    return number >= 1.0\n}\n\nprivate fun checkOpusBitRate(opusBitRate: String): Boolean {\n    val number = opusBitRate.toIntOrNull() ?: return false\n    return (number >= 6000) && (number <= 510000)\n}\n\nprivate fun checkOpusPacketLoss(opusPacketLoss: String): Boolean {\n    val number = opusPacketLoss.toIntOrNull() ?: return false\n    return (number >= 0) && (number <= 100)\n}\n\nprivate fun checkAudioDelay(audioDelay: String): Boolean {\n    val number = audioDelay.toIntOrNull() ?: return false\n    return (number >= 100) && (number <= 3000)\n}\n\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/BaresipApp.kt",
    "content": "package com.tutpro.baresip\n\nimport android.app.Application\n\nclass BaresipApp : Application() {\n\n    override fun onCreate() {\n        super.onCreate()\n        if (!BaresipService.libraryLoaded) {\n            Log.i(TAG, \"Loading baresip library\")\n            System.loadLibrary(\"baresip\")\n            BaresipService.libraryLoaded = true\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/BaresipContactScreen.kt",
    "content": "package com.tutpro.baresip\n\nimport android.content.ContentProviderOperation\nimport android.content.ContentValues\nimport android.content.Context\nimport android.database.Cursor\nimport android.graphics.Bitmap\nimport android.graphics.BitmapFactory\nimport android.graphics.Matrix\nimport android.net.Uri\nimport android.provider.ContactsContract\nimport android.provider.ContactsContract.CommonDataKinds\nimport android.provider.ContactsContract.Contacts.Data\nimport androidx.activity.compose.BackHandler\nimport androidx.activity.compose.rememberLauncherForActivityResult\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.compose.foundation.Canvas\nimport androidx.compose.foundation.ExperimentalFoundationApi\nimport androidx.compose.foundation.Image\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.combinedClickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.asPaddingValues\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.imePadding\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.statusBars\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.foundation.text.KeyboardOptions\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.filled.Check\nimport androidx.compose.material3.CircularProgressIndicator\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.OutlinedTextField\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Switch\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.TopAppBarDefaults\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.focus.FocusRequester\nimport androidx.compose.ui.focus.focusRequester\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.SolidColor\nimport androidx.compose.ui.layout.ContentScale\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.input.KeyboardCapitalization\nimport androidx.compose.ui.text.input.KeyboardType\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.core.graphics.scale\nimport androidx.exifinterface.media.ExifInterface\nimport androidx.navigation.NavController\nimport androidx.navigation.NavGraphBuilder\nimport androidx.navigation.NavType\nimport androidx.navigation.compose.composable\nimport androidx.navigation.navArgument\nimport coil.compose.rememberAsyncImagePainter\nimport com.tutpro.baresip.CustomElements.AlertDialog\nimport java.io.ByteArrayOutputStream\nimport java.io.File\nimport java.io.FileOutputStream\n\nfun NavGraphBuilder.baresipContactScreenRoute(navController: NavController) {\n    composable(\n        route = \"baresip_contact/{uri_or_name}/{kind}\",\n        arguments = listOf(\n            navArgument(\"uri_or_name\") { type = NavType.StringType },\n            navArgument(\"kind\") { type = NavType.StringType }\n        )\n    ) { backStackEntry ->\n        val uriOrNameArg = backStackEntry.arguments?.getString(\"uri_or_name\")!!\n        val kindArg = backStackEntry.arguments?.getString(\"kind\")!!\n        ContactScreen(\n            navController = navController,\n            uriOrNameArg = uriOrNameArg,\n            kindArg = kindArg\n        )\n    }\n}\n\nprivate data class ScreenState(\n    val new: Boolean = false,\n    val favorite: Boolean = false,\n    val android: Boolean = false,\n    val id: Long = 0,\n    val newId: Long = 0,\n    val name: String = \"\",\n    val uri: String = \"\",\n    val color: Int = 0,\n    val avatarImageUri: String? = null,\n    val tmpAvatarFile: File? = null,\n    val isLoading: Boolean = true\n)\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun ContactScreen(\n    navController: NavController,\n    uriOrNameArg: String,\n    kindArg: String\n) {\n\n    val ctx = LocalContext.current\n    var screenState by remember { mutableStateOf(ScreenState()) }\n\n    val title = if (screenState.new)\n        stringResource(R.string.new_contact)\n    else\n        uriOrNameArg\n\n    LaunchedEffect(key1 = uriOrNameArg, key2 = kindArg) {\n        val isNew = kindArg == \"new\"\n        if (isNew) {\n            val time = System.currentTimeMillis()\n            screenState = ScreenState(\n                new = true,\n                name = \"\",\n                uri = uriOrNameArg,\n                favorite = false,\n                android = BaresipService.contactsMode == \"android\",\n                color = Utils.randomColor(),\n                id = time,\n                newId = time,\n                isLoading = false\n            )\n        }\n        else {\n            val contact = Contact.baresipContact(uriOrNameArg)!!\n            val avatarFile = File(BaresipService.filesPath, \"${contact.id}.png\")\n            screenState = ScreenState(\n                new = false,\n                name = uriOrNameArg,\n                uri = contact.uri,\n                favorite = contact.favorite,\n                android = false,\n                color = contact.color,\n                id = contact.id,\n                newId = contact.id,\n                avatarImageUri = if (contact.avatarImage != null && avatarFile.exists())\n                    Uri.fromFile(avatarFile).toString()\n                else\n                    null,\n                isLoading = false\n            )\n        }\n    }\n\n    val onBack: () -> Unit = {\n        screenState.tmpAvatarFile?.let { tempFile ->\n            if (tempFile.exists()) {\n                Log.d(TAG, \"Back pressed, deleting temp avatar: ${tempFile.name}\")\n                Utils.deleteFile(tempFile)\n            }\n        }\n        navController.navigateUp()\n    }\n\n    val onCheck: () -> Unit = {\n        val result = checkOnClick(\n            ctx = ctx,\n            currentState = screenState,\n            uriOrNameArg = uriOrNameArg,\n        )\n        if (result)\n            navController.navigateUp()\n    }\n\n    BackHandler(enabled = true) {\n        onBack()\n    }\n\n    Scaffold(\n        modifier = Modifier\n            .fillMaxSize()\n            .imePadding(),\n        containerColor = MaterialTheme.colorScheme.background,\n        topBar = {\n            Column(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .background(MaterialTheme.colorScheme.background)\n                    .padding(top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding())\n            ) {\n                TopAppBar(title, onBack = onBack, onCheck = onCheck)\n            }\n        },\n        content = { contentPadding ->\n            if (!screenState.isLoading) {\n                ContactContent(\n                    contentPadding = contentPadding,\n                    screenState = screenState,\n                    onStateChange = { newState -> screenState = newState }\n                )\n            } else {\n                // Optional: Show a loading indicator\n                Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {\n                    CircularProgressIndicator()\n                }\n            }\n        }\n    )\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun TopAppBar(title: String, onBack: () -> Unit, onCheck: () -> Unit) {\n    TopAppBar(\n        title = { Text(text = title, fontWeight = FontWeight.Bold) },\n        colors = TopAppBarDefaults.topAppBarColors(\n            containerColor = MaterialTheme.colorScheme.primary,\n            navigationIconContentColor = MaterialTheme.colorScheme.onPrimary,\n            titleContentColor = MaterialTheme.colorScheme.onPrimary,\n            actionIconContentColor = MaterialTheme.colorScheme.onPrimary\n        ),\n        windowInsets = WindowInsets(0, 0, 0, 0),\n        navigationIcon = {\n            IconButton(onClick = onBack) {\n                Icon(\n                    imageVector = Icons.AutoMirrored.Filled.ArrowBack,\n                    contentDescription = \"Back\",\n                )\n            }\n        },\n        actions = {\n            IconButton(onClick = onCheck) {\n                Icon(\n                    imageVector = Icons.Filled.Check,\n                    contentDescription = \"Check\"\n                )\n            }\n        }\n    )\n}\n\nprivate val alertTitle = mutableStateOf(\"\")\nprivate val alertMessage = mutableStateOf(\"\")\nprivate val showAlert = mutableStateOf(false)\n\n@OptIn(ExperimentalFoundationApi::class)\n@Composable\nprivate fun ContactContent(\n    contentPadding: PaddingValues,\n    screenState: ScreenState,\n    onStateChange: (ScreenState) -> Unit\n) {\n    val ctx = LocalContext.current\n\n    if (showAlert.value) {\n        AlertDialog(\n            showDialog = showAlert,\n            title = alertTitle.value,\n            message = alertMessage.value,\n            lastButtonText = stringResource(R.string.ok),\n        )\n    }\n\n    Column(\n        modifier = Modifier\n            .fillMaxWidth()\n            .background(MaterialTheme.colorScheme.background)\n            .padding(contentPadding)\n            .padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 52.dp),\n        verticalArrangement = Arrangement.spacedBy(12.dp),\n    ) {\n        Avatar(\n            ctx = ctx,\n            name = screenState.name,\n            color = screenState.color,\n            currentAvatarUri = screenState.avatarImageUri,\n            onNewAvatarChosen = { processedTmpFile, generatedNewId ->\n                onStateChange(\n                    screenState.copy(\n                        avatarImageUri = Uri.fromFile(processedTmpFile).toString(),\n                        tmpAvatarFile = processedTmpFile,\n                        newId = generatedNewId\n                    )\n                )\n            },\n            onAvatarColorChange = { newRandomColor ->\n                // User long-clicked to change color, this means discarding any image.\n                // The old tempAvatarFile (if any) should be deleted.\n                screenState.tmpAvatarFile?.let {\n                    if (it.exists())\n                        Utils.deleteFile(it)\n                }\n                onStateChange(\n                    screenState.copy(\n                        color = newRandomColor,\n                        avatarImageUri = null,\n                        tmpAvatarFile = null\n                    )\n                )\n            }\n        )\n        ContactName(\n            name = screenState.name,\n            new = screenState.new,\n            onNameChange = { newName -> onStateChange(screenState.copy(name = newName)) }\n        )\n        ContactUri(\n            uri = screenState.uri,\n            onUriChange = { newUri -> onStateChange(screenState.copy(uri = newUri)) }\n        )\n        Favorite(\n            ctx = ctx,\n            favorite = screenState.favorite,\n            onFavoriteChange = {\n                newFavorite -> onStateChange(screenState.copy(favorite = newFavorite))\n            }\n        )\n        if (screenState.new && BaresipService.contactsMode == \"both\")\n            Android(\n                ctx = ctx,\n                android = screenState.android,\n                onAndroidChange = { newAndroid -> onStateChange(screenState.copy(android = newAndroid)) }\n            )\n    }\n}\n\n@OptIn(ExperimentalFoundationApi::class) \n@Composable\nprivate fun Avatar(\n    ctx: Context,\n    name: String,\n    color: Int,\n    currentAvatarUri: String?,\n    onNewAvatarChosen: (newImageFile: File, newImageId: Long) -> Unit,\n    onAvatarColorChange: (newColor: Int) -> Unit\n) {\n    val avatarImagePicker =\n        rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->\n            if (uri != null) {\n                try {\n                    val inputStream = ctx.contentResolver.openInputStream(uri)\n                    val avatarBitmap = BitmapFactory.decodeStream(inputStream)\n                    inputStream?.close()\n\n                    if (avatarBitmap == null) {\n                        Log.e(TAG, \"Failed to decode bitmap from URI: $uri\")\n                        return@rememberLauncherForActivityResult\n                    }\n\n                    val scaledBitmap = avatarBitmap.scale(192, 192) // Define desired scale\n\n                    val orientationInputStream = ctx.contentResolver.openInputStream(uri)\n                    val exif = if (orientationInputStream != null) ExifInterface(orientationInputStream) else null\n                    orientationInputStream?.close()\n                    val orientation = exif?.getAttributeInt(\n                        ExifInterface.TAG_ORIENTATION,\n                        ExifInterface.ORIENTATION_NORMAL\n                    ) ?: ExifInterface.ORIENTATION_NORMAL\n                    val rotatedBitmap = rotateBitmap(scaledBitmap, orientation)\n\n                    val newImageId = System.currentTimeMillis()\n                    val tempNewImageFile = File(BaresipService.filesPath, \"${newImageId}.png\")\n\n                    if (saveBitmap(rotatedBitmap, tempNewImageFile)) {\n                        onNewAvatarChosen(tempNewImageFile, newImageId)\n                    } else {\n                        Log.e(TAG, \"Failed to save processed avatar image to ${tempNewImageFile.absolutePath}\")\n                        if (tempNewImageFile.exists()) Utils.deleteFile(tempNewImageFile)\n                    }\n                } catch (e: Exception) {\n                    Log.e(TAG, \"Could not process avatar image: ${e.message}\")\n                }\n            }\n        }\n\n    Row(\n        Modifier.fillMaxWidth(),\n        verticalAlignment = Alignment.CenterVertically,\n        horizontalArrangement = Arrangement.Center\n    ) {\n        Box(\n            contentAlignment = Alignment.Center,\n            modifier = Modifier\n                .size(avatarSize.dp)\n                .clip(CircleShape)\n                .background(if (currentAvatarUri == null) Color(color) else Color.Transparent)\n                .combinedClickable(\n                    onClick = {\n                        avatarImagePicker.launch(\"image/*\")\n                    },\n                    onLongClick = {\n                        onAvatarColorChange(Utils.randomColor())\n                    }\n                )\n        ) {\n            if (currentAvatarUri == null) {\n                Box(\n                    modifier = Modifier.size(avatarSize.dp),\n                    contentAlignment = Alignment.Center\n                ) {\n                    Canvas(modifier = Modifier.fillMaxSize()) {\n                        drawCircle(SolidColor(Color(color)))\n                    }\n                    val text = if (name.isNotBlank()) name.take(1).uppercase() else \"?\"\n                    Text(text, fontSize = 72.sp, color = Color.White)\n                }\n            } else {\n                Image(\n                    painter = rememberAsyncImagePainter(model = currentAvatarUri),\n                    contentDescription = stringResource(R.string.avatar_image),\n                    contentScale = ContentScale.Crop,\n                    modifier = Modifier.size(avatarSize.dp).clip(CircleShape)\n                )\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun ContactName(name: String, new: Boolean, onNameChange: (String) -> Unit) {\n    val focusRequester = remember { FocusRequester() }\n    OutlinedTextField(\n        value = name,\n        placeholder = { Text(stringResource(R.string.contact_name)) },\n        onValueChange = onNameChange,\n        modifier = Modifier\n            .fillMaxWidth()\n            .focusRequester(focusRequester),\n        textStyle = androidx.compose.ui.text.TextStyle(fontSize = 18.sp),\n        label = { Text(stringResource(R.string.contact_name)) },\n        keyboardOptions = KeyboardOptions(\n            keyboardType = KeyboardType.Text,\n            capitalization = KeyboardCapitalization.Words\n        )\n    )\n    LaunchedEffect(new) {\n        if (new)\n            focusRequester.requestFocus()\n    }\n}\n\n@Composable\nprivate fun ContactUri(uri: String, onUriChange: (String) -> Unit) {\n    OutlinedTextField(\n        value = uri,\n        placeholder = { Text(stringResource(R.string.user_domain_or_number)) },\n        onValueChange = onUriChange,\n        modifier = Modifier.fillMaxWidth(),\n        textStyle = androidx.compose.ui.text.TextStyle(fontSize = 18.sp),\n        label = { Text(stringResource(R.string.sip_or_tel_uri)) },\n        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)\n    )\n}\n\n@Composable\nprivate fun Favorite(ctx: Context, favorite: Boolean, onFavoriteChange: (Boolean) -> Unit) {\n    Row(\n        Modifier.fillMaxWidth(),\n        verticalAlignment = Alignment.CenterVertically,\n        horizontalArrangement = Arrangement.Start\n    ) {\n        Text(\n            text = stringResource(R.string.favorite),\n            modifier = Modifier.weight(1f)\n                .clickable {\n                    alertTitle.value = ctx.getString(R.string.favorite)\n                    alertMessage.value = ctx.getString(R.string.favorite_help)\n                    showAlert.value = true\n                },\n        )\n        Switch(\n            checked = favorite,\n            onCheckedChange = onFavoriteChange\n        )\n    }\n}\n\n@Composable\nprivate fun Android(ctx: Context, android: Boolean, onAndroidChange: (Boolean) -> Unit) {\n    Row(\n        Modifier.fillMaxWidth(),\n        verticalAlignment = Alignment.CenterVertically,\n        horizontalArrangement = Arrangement.Start\n    ) {\n        Text(\n            text = stringResource(R.string.android),\n            modifier = Modifier.weight(1f)\n                .clickable {\n                    alertTitle.value = ctx.getString(R.string.android)\n                    alertMessage.value = ctx.getString(R.string.android_contact_help)\n                    showAlert.value = true\n                },\n        )\n        Switch(\n            checked = android,\n            onCheckedChange = onAndroidChange\n        )\n    }\n}\n\nprivate fun checkOnClick(\n    ctx: Context,\n    currentState: ScreenState,\n    uriOrNameArg: String\n): Boolean {\n\n    var newUri = currentState.uri.filterNot{setOf('-', ' ', '(', ')').contains(it)}\n    if (!newUri.startsWith(\"sip:\") && !newUri.startsWith(\"tel:\"))\n        newUri = if (Utils.isTelNumber(newUri))\n            \"tel:$newUri\"\n        else\n            \"sip:$newUri\"\n\n    if (!Utils.checkUri(newUri)) {\n        alertTitle.value = ctx.getString(R.string.notice)\n        alertMessage.value = String.format(ctx.getString(R.string.invalid_sip_or_tel_uri), newUri)\n        showAlert.value = true\n        return false\n    }\n\n    var newName = currentState.name.trim()\n    if (newName == \"\") newName = newUri.substringAfter(\":\")\n    if (!Utils.checkName(newName)) {\n        alertTitle.value = ctx.getString(R.string.notice)\n        alertMessage.value = String.format(ctx.getString(R.string.invalid_contact), newName)\n        showAlert.value = true\n        return false\n    }\n\n    val alert: Boolean = if (currentState.new)\n        Contact.nameExists(newName, BaresipService.contacts, true)\n    else {\n        (uriOrNameArg != newName) && Contact.nameExists(newName, BaresipService.contacts, false)\n    }\n    if (alert) {\n        alertTitle.value = ctx.getString(R.string.notice)\n        alertMessage.value = String.format(ctx.getString(R.string.contact_already_exists), newName)\n        showAlert.value = true\n        return false\n    }\n\n    var idToUse = currentState.id\n\n    if (currentState.tmpAvatarFile != null && currentState.tmpAvatarFile.exists()) {\n        if (currentState.id != currentState.newId) { // Avatar changed, implying new ID\n            val oldAvatar = File(BaresipService.filesPath, \"${currentState.id}.png\")\n            if (oldAvatar.exists()) Utils.deleteFile(oldAvatar)\n            idToUse = currentState.newId\n        }\n    } else if (currentState.avatarImageUri == null) { // Avatar was explicitly cleared\n        val avatarFile = File(BaresipService.filesPath, \"$idToUse.png\")\n        if (avatarFile.exists())\n            Utils.deleteFile(avatarFile)\n    }\n\n    val contact: Contact.BaresipContact =\n        Contact.BaresipContact(\n            newName,\n            newUri,\n            currentState.color,\n            idToUse,\n            currentState.favorite\n        )\n\n    if (currentState.avatarImageUri == null)\n        contact.avatarImage = null\n    else {\n        val imageFilePath = BaresipService.filesPath + \"/${idToUse}.png\"\n        contact.avatarImage = BitmapFactory.decodeFile(imageFilePath)\n    }\n\n    if (currentState.android) {\n        addOrUpdateAndroidContact(ctx, contact)\n        if (contact.favorite) {\n            val contentValues = ContentValues()\n            contentValues.put(ContactsContract.Contacts.STARRED, 1)\n            try {\n                ctx.contentResolver.update(\n                    ContactsContract.RawContacts.CONTENT_URI, contentValues,\n                    ContactsContract.Contacts.DISPLAY_NAME + \"='\" + newName + \"'\", null\n                )\n            } catch (e: Exception) {\n                Log.e(TAG, \"Update of Android favorite failed: ${e.message}\")\n            }\n        }\n    }\n    else {\n        if (currentState.new)\n            Contact.addBaresipContact(contact)\n        else\n            Contact.updateBaresipContact(currentState.id, contact)\n    }\n\n    return true\n}\n\nprivate fun rotateBitmap(bitmap: Bitmap, orientation: Int): Bitmap {\n    val matrix = Matrix()\n    when (orientation) {\n        ExifInterface.ORIENTATION_NORMAL -> return bitmap\n        ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> matrix.setScale(-1f, 1f)\n        ExifInterface.ORIENTATION_ROTATE_180 -> matrix.setRotate(180f)\n        ExifInterface.ORIENTATION_FLIP_VERTICAL -> {\n            matrix.setRotate(180f)\n            matrix.postScale(-1f, 1f)\n        }\n        ExifInterface.ORIENTATION_TRANSPOSE -> {\n            matrix.setRotate(90f)\n            matrix.postScale(-1f, 1f)\n        }\n        ExifInterface.ORIENTATION_ROTATE_90 -> matrix.setRotate(90f)\n        ExifInterface.ORIENTATION_TRANSVERSE -> {\n            matrix.setRotate(-90f)\n            matrix.postScale(-1f, 1f)\n        }\n        ExifInterface.ORIENTATION_ROTATE_270 -> matrix.setRotate(-90f)\n        else -> return bitmap\n    }\n    val rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0,\n        bitmap.width, bitmap.height, matrix, true)\n    bitmap.recycle()\n    return rotatedBitmap\n}\n\nprivate fun saveBitmap(bitmap: Bitmap, file: File): Boolean {\n    if (file.exists()) file.delete()\n    try {\n        val out = FileOutputStream(file)\n        val scaledBitmap = bitmap.scale(avatarSize, avatarSize)\n        scaledBitmap.compress(Bitmap.CompressFormat.PNG, 100, out)\n        out.flush()\n        out.close()\n        Log.d(TAG, \"Saved bitmap to ${file.absolutePath} of length ${file.length()}\")\n    } catch (e: Exception) {\n        Log.e(TAG, \"Failed to save bitmap to ${file.absolutePath}: $e\")\n        return false\n    }\n    return true\n}\n\nprivate fun addOrUpdateAndroidContact(ctx: Context, contact: Contact.BaresipContact) {\n    val projection = arrayOf(ContactsContract.Data.RAW_CONTACT_ID)\n    val selection = ContactsContract.Data.MIMETYPE + \"='\" +\n            CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE + \"' AND \" +\n            CommonDataKinds.StructuredName.DISPLAY_NAME + \"='\" + contact.name + \"'\"\n    val c: Cursor? = ctx.contentResolver.query(\n        ContactsContract.Data.CONTENT_URI, projection,\n        selection, null, null)\n    if (c != null && c.moveToFirst()) {\n        updateAndroidContact(ctx, c.getLong(0), contact)\n    } else {\n        addAndroidContact(ctx, contact)\n    }\n    c?.close()\n}\n\nprivate fun addAndroidContact(ctx: Context, contact: Contact.BaresipContact): Boolean {\n    val ops = ArrayList<ContentProviderOperation>()\n    ops.add(\n        ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)\n            .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)\n            .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null).build())\n    ops.add(\n        ContentProviderOperation\n            .newInsert(ContactsContract.Data.CONTENT_URI)\n            .withValueBackReference(Data.RAW_CONTACT_ID, 0)\n            .withValue(Data.MIMETYPE, CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)\n            .withValue(CommonDataKinds.StructuredName.DISPLAY_NAME, contact.name)\n            .build())\n    val mimeType = if (contact.uri.startsWith(\"sip:\"))\n        CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE\n    else\n        CommonDataKinds.Phone.CONTENT_ITEM_TYPE\n    ops.add(\n        ContentProviderOperation\n            .newInsert(ContactsContract.Data.CONTENT_URI)\n            .withValueBackReference(Data.RAW_CONTACT_ID, 0)\n            .withValue(Data.MIMETYPE, mimeType)\n            .withValue(Data.DATA1, contact.uri.substringAfter(\":\"))\n            .build())\n\n    if (contact.avatarImage != null) {\n        val photoData: ByteArray? = bitmapToPNGByteArray(contact.avatarImage!!)\n        if (photoData != null) {\n            ops.add(\n                ContentProviderOperation\n                    .newInsert(ContactsContract.Data.CONTENT_URI)\n                    .withValueBackReference(Data.RAW_CONTACT_ID, 0)\n                    .withValue(Data.MIMETYPE, CommonDataKinds.Photo.CONTENT_ITEM_TYPE)\n                    .withValue(CommonDataKinds.Photo.PHOTO, photoData)\n                    .build())\n        }\n    }\n    try {\n        ctx.contentResolver.applyBatch(ContactsContract.AUTHORITY, ops)\n    } catch (e: Exception) {\n        Log.e(TAG, \"Adding of contact ${contact.name} failed: ${e.message}\")\n        return false\n    }\n    return true\n}\n\nprivate fun updateAndroidContact(ctx: Context, rawContactId: Long, contact: Contact.BaresipContact) {\n    if (updateAndroidUri(ctx, rawContactId, contact.uri) == 0)\n        addAndroidUri(ctx, rawContactId, contact.uri)\n    if (updateAndroidPhoto(ctx, rawContactId, contact.avatarImage) == 0)\n        if (contact.avatarImage != null)\n            addAndroidPhoto(ctx, rawContactId, contact.avatarImage!!)\n}\n\nprivate fun addAndroidUri(ctx: Context, rawContactId: Long, uri: String) {\n    val mimeType = if (uri.startsWith(\"sip:\"))\n        CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE\n    else\n        CommonDataKinds.Phone.CONTENT_ITEM_TYPE\n    val ops = ArrayList<ContentProviderOperation>()\n    ops.add(\n        ContentProviderOperation\n            .newInsert(ContactsContract.Data.CONTENT_URI)\n            .withValue(Data.RAW_CONTACT_ID, rawContactId)\n            .withValue(Data.MIMETYPE, mimeType)\n            .withValue(Data.DATA1, uri.substringAfter(\":\"))\n            .build())\n    try {\n        ctx.contentResolver.applyBatch(ContactsContract.AUTHORITY, ops)\n    } catch (e: Exception) {\n        Log.e(TAG, \"Adding of SIP URI $uri failed: ${e.message}\")\n    }\n}\n\nprivate fun updateAndroidUri(ctx: Context, rawContactId: Long, uri: String): Int {\n    val mimeType = if (uri.startsWith(\"sip:\"))\n        CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE\n    else\n        CommonDataKinds.Phone.CONTENT_ITEM_TYPE\n    val contentValues = ContentValues()\n    contentValues.put(ContactsContract.Data.DATA1, uri)\n    val where = \"${ContactsContract.Data.RAW_CONTACT_ID}=$rawContactId and \" +\n            \"${ContactsContract.Data.MIMETYPE}='$mimeType'\"\n    return try {\n        ctx.contentResolver.update(ContactsContract.Data.CONTENT_URI, contentValues, where, null)\n    }  catch (e: Exception) {\n        Log.e(TAG, \"Update of Android URI $uri failed: ${e.message}\")\n        0\n    }\n}\n\nprivate fun addAndroidPhoto(ctx: Context, rawContactId: Long, photoBits: Bitmap) {\n    val photoBytes = bitmapToPNGByteArray(photoBits)\n    if (photoBytes != null) {\n        val ops = ArrayList<ContentProviderOperation>()\n        ops.add(\n            ContentProviderOperation\n                .newInsert(ContactsContract.Data.CONTENT_URI)\n                .withValue(Data.RAW_CONTACT_ID, rawContactId)\n                .withValue(Data.MIMETYPE, CommonDataKinds.Photo.CONTENT_ITEM_TYPE)\n                .withValue(CommonDataKinds.Photo.PHOTO, photoBytes)\n                .build())\n        try {\n            ctx.contentResolver.applyBatch(ContactsContract.AUTHORITY, ops)\n        } catch (e: Exception) {\n            Log.e(TAG, \"Adding of Android photo failed: ${e.message}\")\n        }\n    }\n}\n\nprivate fun updateAndroidPhoto(ctx: Context, rawContactId: Long, photoBits: Bitmap?): Int {\n    val photoBytes = if (photoBits == null)\n        null\n    else\n        bitmapToPNGByteArray(photoBits)\n    val contentValues = ContentValues()\n    contentValues.put(CommonDataKinds.Photo.PHOTO, photoBytes)\n    val where = \"${ContactsContract.Data.RAW_CONTACT_ID}=$rawContactId and \" +\n            \"${ContactsContract.Data.MIMETYPE}='${CommonDataKinds.Photo.CONTENT_ITEM_TYPE}'\"\n    return try {\n        ctx.contentResolver.update(ContactsContract.Data.CONTENT_URI, contentValues, where, null)\n    }  catch (e: Exception) {\n        Log.e(TAG, \"updateAndroidPhoto failed: ${e.message}\")\n        0\n    }\n}\n\nprivate fun bitmapToPNGByteArray(bitmap: Bitmap): ByteArray? {\n    val size = bitmap.width * bitmap.height * 4\n    val out = ByteArrayOutputStream(size)\n    return try {\n        bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)\n        out.flush()\n        out.close()\n        out.toByteArray()\n    } catch (e: Exception) {\n        Log.w(TAG, \"Unable to serialize photo: ${e.message}\")\n        null\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt",
    "content": "package com.tutpro.baresip\n\nimport android.Manifest.permission.RECORD_AUDIO\nimport android.annotation.SuppressLint\nimport android.app.Notification\nimport android.app.NotificationChannel\nimport android.app.NotificationManager\nimport android.app.PendingIntent\nimport android.app.Service\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.content.pm.PackageManager\nimport android.content.pm.ServiceInfo\nimport android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE\nimport android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL\nimport android.content.res.Configuration\nimport android.database.ContentObserver\nimport android.graphics.ImageDecoder\nimport android.media.AudioAttributes\nimport android.media.AudioManager\nimport android.media.AudioManager.MODE_IN_COMMUNICATION\nimport android.media.AudioManager.MODE_NORMAL\nimport android.media.MediaPlayer\nimport android.media.Ringtone\nimport android.media.RingtoneManager\nimport android.media.audiofx.AcousticEchoCanceler\nimport android.media.audiofx.AutomaticGainControl\nimport android.media.audiofx.NoiseSuppressor\nimport android.net.ConnectivityManager\nimport android.net.LinkProperties\nimport android.net.Network\nimport android.net.NetworkCapabilities\nimport android.net.NetworkRequest\nimport android.net.Uri\nimport android.net.wifi.WifiManager\nimport android.os.Build.VERSION\nimport android.os.CountDownTimer\nimport android.os.Handler\nimport android.os.IBinder\nimport android.os.Looper\nimport android.os.PowerManager\nimport android.os.VibrationEffect\nimport android.os.Vibrator\nimport android.os.VibratorManager\nimport android.provider.ContactsContract\nimport android.provider.Settings\nimport android.system.OsConstants\nimport android.telecom.DisconnectCause\nimport android.telecom.PhoneAccountHandle\nimport android.telecom.TelecomManager\nimport android.view.View\nimport android.widget.RemoteViews\nimport android.widget.Toast\nimport androidx.annotation.Keep\nimport androidx.appcompat.app.AppCompatDelegate\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.core.app.NotificationCompat\nimport androidx.core.app.NotificationCompat.MessagingStyle\nimport androidx.core.app.Person\nimport androidx.core.app.RemoteInput\nimport androidx.core.content.ContextCompat\nimport androidx.core.graphics.drawable.IconCompat\nimport androidx.core.net.toUri\nimport androidx.lifecycle.MutableLiveData\nimport com.tutpro.baresip.Utils.toCircle\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.launch\nimport java.io.File\nimport java.net.InetAddress\nimport java.nio.charset.Charset\nimport java.nio.charset.StandardCharsets\nimport java.util.GregorianCalendar\nimport java.util.Timer\nimport java.util.TimerTask\nimport kotlin.concurrent.schedule\nimport kotlin.math.roundToInt\n\nclass BaresipService: Service() {\n\n    internal lateinit var intent: Intent\n    private lateinit var am: AudioManager\n    private lateinit var nt: Ringtone\n    private lateinit var nm: NotificationManager\n    private lateinit var snb: NotificationCompat.Builder\n    private lateinit var cm: ConnectivityManager\n    private lateinit var pm: PowerManager\n    private lateinit var wm: WifiManager\n    private lateinit var tm: TelecomManager\n    private lateinit var vibrator: Vibrator\n    private lateinit var partialWakeLock: PowerManager.WakeLock\n    private lateinit var proximityWakeLock: PowerManager.WakeLock\n    private lateinit var wifiLock: WifiManager.WifiLock\n    private lateinit var hotSpotReceiver: BroadcastReceiver\n    private lateinit var androidContactsObserver: ContentObserver\n    private lateinit var networkCallback: ConnectivityManager.NetworkCallback\n    private lateinit var stopState: String\n    private lateinit var quitTimer: CountDownTimer\n\n    private var vbTimer: Timer? = null\n    private var origVolume = mutableMapOf<Int, Int>()\n    private var linkAddresses = mutableMapOf<String, String>()\n    private var activeNetwork: Network? = null\n    private var allNetworks = mutableSetOf<Network>()\n    private var hotSpotIsEnabled = false\n    private var hotSpotAddresses = mapOf<String, String>()\n    private var mediaPlayer: MediaPlayer? = null\n    private var androidContactsObserverRegistered = false\n    private var hotSpotReceiverRegistered = false\n    private var isNotificationInCall = false\n    private var isServiceClean = false\n\n    @SuppressLint(\"WakelockTimeout\")\n    override fun onCreate() {\n        super.onCreate()\n\n        Log.i(TAG, \"BaresipService onCreate\")\n        instance = this\n\n        intent = Intent(\"com.tutpro.baresip.EVENT\")\n        intent.setPackage(\"com.tutpro.baresip\")\n\n        filesPath = filesDir.absolutePath\n        pName = packageName\n\n        am = applicationContext.getSystemService(AUDIO_SERVICE) as AudioManager\n\n        val ntUri = RingtoneManager.getActualDefaultRingtoneUri(applicationContext,\n                RingtoneManager.TYPE_NOTIFICATION)\n        nt = RingtoneManager.getRingtone(applicationContext, ntUri)\n\n        val rtUri = if (Preferences(applicationContext).ringtoneUri == \"\")\n            Settings.System.DEFAULT_RINGTONE_URI\n        else\n            Preferences(applicationContext).ringtoneUri!!.toUri()\n        rt = RingtoneManager.getRingtone(applicationContext, rtUri)\n\n        nm = getSystemService(NOTIFICATION_SERVICE) as NotificationManager\n        createNotificationChannels()\n        snb = NotificationCompat.Builder(this, LOW_CHANNEL_ID)\n\n        pm = getSystemService(POWER_SERVICE) as PowerManager\n\n        vibrator = if (VERSION.SDK_INT >= 31) {\n            val vibratorManager = applicationContext.getSystemService(VIBRATOR_MANAGER_SERVICE) as VibratorManager\n            vibratorManager.defaultVibrator\n        } else {\n            @Suppress(\"DEPRECATION\")\n            applicationContext.getSystemService(VIBRATOR_SERVICE) as Vibrator\n        }\n\n        // This is needed to keep service running also in Doze Mode\n        partialWakeLock = pm.newWakeLock(\n            PowerManager.PARTIAL_WAKE_LOCK,\n            \"com.tutpro.baresip:wakelock\"\n        ).apply { setReferenceCounted(false) }\n\n        networkCallback = object : ConnectivityManager.NetworkCallback() {\n\n            override fun onAvailable(network: Network) {\n                super.onAvailable(network)\n                Log.d(TAG, \"Network $network is available\")\n                synchronized(allNetworks) {\n                    if (network !in allNetworks)\n                        allNetworks.add(network)\n                }\n            }\n\n            override fun onLosing(network: Network, maxMsToLive: Int) {\n                super.onLosing(network, maxMsToLive)\n                Log.d(TAG, \"Network $network is losing after $maxMsToLive ms\")\n            }\n\n            override fun onLost(network: Network) {\n                super.onLost(network)\n                Log.d(TAG, \"Network $network is lost\")\n                synchronized(allNetworks) {\n                    if (network in allNetworks)\n                        allNetworks.remove(network)\n                }\n                if (isServiceRunning)\n                    updateNetwork()\n            }\n\n            override fun onCapabilitiesChanged(network: Network, caps: NetworkCapabilities) {\n                super.onCapabilitiesChanged(network, caps)\n                synchronized(allNetworks) {\n                    if (network !in allNetworks)\n                        allNetworks.add(network)\n                }\n                if (isServiceRunning)\n                    updateNetwork()\n            }\n\n            override fun onLinkPropertiesChanged(network: Network, props: LinkProperties) {\n                super.onLinkPropertiesChanged(network, props)\n                Log.d(TAG, \"Network $network link properties changed: $props\")\n                synchronized(allNetworks) {\n                    if (network !in allNetworks)\n                        allNetworks.add(network)\n                }\n                if (isServiceRunning)\n                    updateNetwork()\n            }\n        }\n\n        cm = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager\n        cm.registerNetworkCallback(\n            NetworkRequest.Builder()\n                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)\n                .build(),\n            networkCallback\n        )\n\n        wm = applicationContext.getSystemService(WIFI_SERVICE) as WifiManager\n        hotSpotIsEnabled = Utils.isHotSpotOn(wm)\n\n        hotSpotReceiver = object : BroadcastReceiver() {\n            override fun onReceive(contxt: Context, intent: Intent) {\n                val action = intent.action\n                if (\"android.net.wifi.WIFI_AP_STATE_CHANGED\" == action) {\n                    val state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 0)\n                    if (WifiManager.WIFI_STATE_ENABLED == state % 10) {\n                        if (hotSpotIsEnabled) {\n                            Log.d(TAG, \"HotSpot is still enabled\")\n                        } else {\n                            Log.d(TAG, \"HotSpot is enabled\")\n                            hotSpotIsEnabled = true\n                            Timer().schedule(1000) {\n                                hotSpotAddresses = Utils.hotSpotAddresses()\n                                Log.d(TAG, \"HotSpot addresses $hotSpotAddresses\")\n                                if (hotSpotAddresses.isNotEmpty()) {\n                                    var reset = false\n                                    for ((k, v) in hotSpotAddresses)\n                                        if (afMatch(k))\n                                            if (Api.net_add_address_ifname(k, v) != 0)\n                                                Log.e(TAG, \"Failed to add $v address $k\")\n                                            else\n                                                reset = true\n                                    if (reset)\n                                        Timer().schedule(2000) {\n                                            updateNetwork()\n                                        }\n                                } else {\n                                    Log.w(TAG, \"Could not get hotspot addresses\")\n                                }\n                            }\n                        }\n                    } else {\n                        if (!hotSpotIsEnabled) {\n                            Log.d(TAG, \"HotSpot is still disabled\")\n                        } else {\n                            Log.d(TAG, \"HotSpot is disabled\")\n                            hotSpotIsEnabled = false\n                            if (hotSpotAddresses.isNotEmpty()) {\n                                for ((k, _) in hotSpotAddresses)\n                                    if (Api.net_rm_address(k) != 0)\n                                        Log.e(TAG, \"Failed to remove address $k\")\n                                hotSpotAddresses = mapOf()\n                                updateNetwork()\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        ContextCompat.registerReceiver(\n            applicationContext,\n            hotSpotReceiver,\n            IntentFilter(\"android.net.wifi.WIFI_AP_STATE_CHANGED\"),\n            ContextCompat.RECEIVER_NOT_EXPORTED\n        )\n        hotSpotReceiverRegistered = true\n\n        tm = getSystemService(TELECOM_SERVICE) as TelecomManager\n        registerPhoneAccount()\n\n        proximityWakeLock = pm.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK,\n                \"com.tutpro.baresip:proximity_wakelock\")\n\n        wifiLock = if (VERSION.SDK_INT < 29)\n            @Suppress(\"DEPRECATION\")\n            wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, \"Baresip\")\n        else\n            wm.createWifiLock(WifiManager.WIFI_MODE_FULL_LOW_LATENCY, \"Baresip\")\n        wifiLock.setReferenceCounted(false)\n\n        androidContactsObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {\n            override fun onChange(self: Boolean) {\n                Log.d(TAG, \"Android contacts change\")\n                if (contactsMode != \"baresip\") {\n                    Contact.loadAndroidContacts(this@BaresipService.applicationContext)\n                    Contact.contactsUpdate()\n                }\n            }\n        }\n\n        stopState = \"initial\"\n        quitTimer = object : CountDownTimer(5000, 1000) {\n            override fun onTick(millisUntilFinished: Long) {\n                Log.d(TAG, \"Seconds remaining: ${millisUntilFinished / 1000}\")\n            }\n            override fun onFinish() {\n                when (stopState) {\n                    \"initial\" -> {\n                        if (isServiceRunning)\n                            baresipStop(true)\n                        stopState = \"force\"\n                        quitTimer.start()\n                    }\n                    \"force\" -> {\n                        cleanService()\n                        isServiceRunning = false\n                        postServiceEvent(ServiceEvent(\"stopped\", arrayListOf(\"\"), System.nanoTime()))\n                        stopSelf()\n                        // exitProcess(0)\n                    }\n                }\n            }\n        }\n\n    }\n\n    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {\n\n        val action: String\n\n        if (intent == null) {\n            action = \"Start\"\n            Log.d(TAG, \"Received onStartCommand with null intent\")\n        } else {\n            // Utils.dumpIntent(intent)\n            action = intent.action!!\n            Log.d(TAG, \"Received onStartCommand action $action\")\n        }\n\n        when (action) {\n\n            \"Start\" -> {\n\n                isStartReceived = true\n\n                if (VERSION.SDK_INT < 31) {\n                    @Suppress(\"DEPRECATION\")\n                    allNetworks = cm.allNetworks.toMutableSet()\n                }\n\n                updateDnsServers()\n                updatePartialWakeLock()\n\n                val assets = arrayOf(\"accounts\", \"config\", \"contacts\")\n                var file = File(filesPath)\n                if (!file.exists()) {\n                    Log.i(TAG, \"Creating baresip directory\")\n                    try {\n                        File(filesPath).mkdirs()\n                    } catch (e: Error) {\n                        Log.e(TAG, \"Failed to create directory: ${e.message}\")\n                    }\n                }\n                for (a in assets) {\n                    file = File(\"${filesPath}/$a\")\n                    if (!file.exists() && a != \"config\") {\n                        Log.i(TAG, \"Copying asset '$a'\")\n                        Utils.copyAssetToFile(applicationContext, a, \"$filesPath/$a\")\n                    } else {\n                        Log.i(TAG, \"Asset '$a' already copied\")\n                    }\n                    if (a == \"config\")\n                        Config.initialize(applicationContext)\n                }\n\n                if (contactsMode != \"android\")\n                    Contact.restoreBaresipContacts()\n                if (contactsMode != \"baresip\") {\n                    Contact.loadAndroidContacts(applicationContext)\n                    registerAndroidContactsObserver()\n                }\n                Contact.contactsUpdate()\n\n                val history = CallHistory.get()\n                if (history.isEmpty()) {\n                    CallHistoryNew.restore()\n                } else {\n                    for (old in history) {\n                        val new = CallHistoryNew(old.aor, old.peerUri, old.direction)\n                        new.stopTime = old.stopTime\n                        new.startTime = old.startTime\n                        new.recording = old.recording\n                        new.add()\n                    }\n                }\n\n                Blocked.restore()\n\n                val recordings = File(filesDir, \"recordings\")\n\n                val restored = File(filesPath, \"restored\")\n                if (restored.exists()) {\n                    Log.d(TAG, \"Clearing recordings\")\n                    CallHistoryNew.clearRecordings()\n                    CallHistoryNew.save()\n                    if (recordings.exists())\n                        recordings.deleteRecursively()\n                    restored.delete()\n                }\n\n                File(filesDir, \"recordings\").mkdir()\n                File(filesDir, \"tmp\").mkdir()\n\n                Message.restore()\n\n                hotSpotAddresses = Utils.hotSpotAddresses()\n                linkAddresses = linkAddresses()\n                var addresses = \"\"\n                for (la in linkAddresses)\n                    addresses = \"$addresses;${la.key};${la.value}\"\n                Log.i(TAG, \"Link addresses: $addresses\")\n                activeNetwork = cm.activeNetwork\n                Log.i(TAG, \"Active network: $activeNetwork\")\n\n                Log.i(TAG, \"AEC/AGC/NS available = $aecAvailable/$agcAvailable/$nsAvailable\")\n\n                val userAgent = Config.variable(\"user_agent\")\n                Thread {\n                    baresipStart(\n                        filesPath,\n                        addresses.removePrefix(\";\"),\n                        logLevel,\n                        if (userAgent != \"\")\n                            userAgent\n                        else\n                            \"baresip v${BuildConfig.VERSION_NAME} \" +\n                                \"(Android ${VERSION.RELEASE}/${System.getProperty(\"os.arch\") ?: \"?\"})\"\n                    )\n                }.start()\n\n                isServiceRunning = true\n\n                showStatusNotification()\n\n                if (linkAddresses.isEmpty())\n                    toast(getString(R.string.no_network), Toast.LENGTH_LONG)\n\n                if (!aecAvailable)\n                    toast(getString(R.string.no_aec), Toast.LENGTH_LONG)\n            }\n\n            \"Notification Dismissed\" -> {\n                updateStatusNotification()\n            }\n\n            \"Start Content Observer\" -> {\n                registerAndroidContactsObserver()\n            }\n\n            \"Stop Content Observer\" -> {\n                unRegisterAndroidContactsObserver()\n            }\n\n            \"Call Answer\" -> {\n                val uap = intent!!.getLongExtra(\"uap\", 0L)\n                val callp = intent.getLongExtra(\"callp\", 0L)\n                stopRinging()\n                stopMediaPlayer()\n                am.mode = MODE_IN_COMMUNICATION\n                setCallVolume()\n                proximitySensing(proximitySensing)\n                Api.ua_answer(uap, callp, Api.VIDMODE_OFF)\n            }\n\n            \"Call Reject\" -> {\n                val callp = intent!!.getLongExtra(\"callp\", 0L)\n                val call = Call.ofCallp(callp)\n                if (call == null) {\n                    Log.w(TAG, \"onStartCommand did not find call $callp\")\n                } else {\n                    val peerUri = call.peerUri\n                    val aor = call.ua.account.aor\n                    Log.d(TAG, \"Aor $aor rejected incoming call $callp from $peerUri\")\n                    call.rejected = true\n                    Api.ua_hangup(call.ua.uap, callp, 486, \"Rejected\")\n                }\n            }\n\n            \"Call Hangup\" -> {\n                val callp = intent!!.getLongExtra(\"callp\", 0L)\n                Log.d(TAG, \"onStartCommand Hangup action for $callp\")\n                val connection = ConnectionService.connections[callp]\n                if (connection != null) {\n                    connection.onDisconnect() // This calls Api.ua_hangup(..., 0, \"\")\n                } else {\n                    val call = Call.ofCallp(callp)\n                    if (call != null) Api.ua_hangup(call.ua.uap, callp, 0, \"\")\n                }\n            }\n\n            \"Transfer Deny\" -> {\n                val callp = intent!!.getLongExtra(\"callp\", 0L)\n                val call = Call.ofCallp(callp)\n                if (call == null)\n                    Log.w(TAG, \"onStartCommand did not find call $callp\")\n                else\n                    call.notifySipfrag(603, \"Decline\")\n                nm.cancel(TRANSFER_NOTIFICATION_ID)\n            }\n\n            \"Message Save\" -> {\n                val uap = intent!!.getLongExtra(\"uap\", 0L)\n                val ua = UserAgent.ofUap(uap)\n                if (ua == null)\n                    Log.w(TAG, \"onStartCommand did not find UA $uap\")\n                else {\n                    Message.updateAorMessage(\n                        ua.account.aor,\n                        intent.getStringExtra(\"time\")!!.toLong()\n                    )\n                    ua.account.unreadMessages = Message.unreadMessages(ua.account.aor)\n                }\n                nm.cancel(MESSAGE_NOTIFICATION_ID)\n            }\n\n            \"Message Delete\" -> {\n                val uap = intent!!.getLongExtra(\"uap\", 0L)\n                val ua = UserAgent.ofUap(uap)\n                if (ua == null)\n                    Log.w(TAG, \"onStartCommand did not find UA $uap\")\n                else {\n                    Message.deleteAorMessage(\n                        ua.account.aor,\n                        intent.getStringExtra(\"time\")!!.toLong()\n                    )\n                    ua.account.unreadMessages = Message.unreadMessages(ua.account.aor)\n                }\n                nm.cancel(MESSAGE_NOTIFICATION_ID)\n            }\n\n            \"Message Inline Reply\" -> {\n                val remoteInputResults = RemoteInput.getResultsFromIntent(intent!!)\n                if (remoteInputResults != null) {\n                    val replyText = remoteInputResults.getCharSequence(KEY_TEXT_REPLY)?.toString()\n                    if (!replyText.isNullOrEmpty()) {\n                        val uap = intent.getLongExtra(\"uap\", -1L)\n                        val ua = UserAgent.ofUap(uap)!!\n                        val aor = ua.account.aor\n                        var peerUri = intent.getStringExtra(\"peer\")!!\n                        val timeStamp = intent.getLongExtra(\"time\", 0L)\n                        if (Utils.isTelUri(peerUri)) {\n                            if (ua.account.telProvider == \"\") {\n                                Log.w(TAG, \"No telephony provider for $aor\")\n                                peerUri = \"\"\n                            } else\n                                peerUri = Utils.telToSip(peerUri, ua.account)\n                        }\n                        if (peerUri != \"\") {\n                            Log.d(TAG, \"Direct Reply from $aor to $peerUri: $replyText\")\n                            Message.updateAorMessage(aor, timeStamp)\n                            val time = System.currentTimeMillis()\n                            val msg = Message(aor, peerUri, replyText, time, MESSAGE_UP_WAIT, 0, \"\", false)\n                            msg.add()\n                            if (Api.message_send(uap, peerUri, replyText, time.toString()) != 0) {\n                                Log.w(TAG, \"message_send failed\")\n                                msg.direction = MESSAGE_UP_FAIL\n                                msg.responseReason = getString(R.string.message_failed)\n                            }\n                            else {\n                                ua.account.unreadMessages = Message.unreadMessages(aor)\n                            }\n                        }\n                    }\n                }\n                nm.cancel(MESSAGE_NOTIFICATION_ID)\n            }\n\n            \"Update Notification\" -> {\n                updateStatusNotification()\n            }\n\n            \"Stop\" -> {\n                cleanService()\n                if (isServiceRunning) {\n                    baresipStop(false)\n                    quitTimer.start()\n                }\n            }\n\n            else -> {\n                Log.e(TAG, \"Unknown start action $action\")\n            }\n\n        }\n\n        return START_STICKY\n    }\n\n    override fun onConfigurationChanged(newConfig: Configuration) {\n        super.onConfigurationChanged(newConfig)\n        val currentNightMode = newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK\n        when (currentNightMode) {\n            Configuration.UI_MODE_NIGHT_NO -> {\n                darkTheme.value = Preferences(applicationContext).displayTheme ==\n                    AppCompatDelegate.MODE_NIGHT_YES\n            }\n            Configuration.UI_MODE_NIGHT_YES -> {\n                darkTheme.value = true\n            }\n        }\n    }\n\n    override fun onBind(intent: Intent): IBinder? {\n        return null\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        Log.d(TAG, \"onDestroy at Baresip Service\")\n        cleanService()\n        instance = null\n        if (isServiceRunning)\n            sendBroadcast(Intent(\"com.tutpro.baresip.Restart\"))\n    }\n\n    @Suppress(\"unused\")\n    @SuppressLint(\"UnspecifiedImmutableFlag\", \"DiscouragedApi\", \"FullScreenIntentPolicy\")\n    @Keep\n    fun uaEvent(event: String, uap: Long, callp: Long) {\n\n        if (!isServiceRunning) return\n\n        val ev = event.split(\",\")\n\n        if (ev[0] == \"create\") {\n\n            val ua = UserAgent(uap)\n            ua.status = if (ua.account.regint == 0)\n                R.drawable.circle_white\n            else\n                circleYellow.getValue(colorblind)\n            ua.add()\n\n            val acc = ua.account\n            if (acc.authUser != \"\" && acc.authPass == NO_AUTH_PASS) {\n                val password = aorPasswords[acc.aor]\n                if (password != null) {\n                    Api.account_set_auth_pass(acc.accp, password)\n                    acc.authPass = password\n                } else {\n                    Api.account_set_auth_pass(acc.accp, NO_AUTH_PASS)\n                }\n            }\n\n            Log.d(TAG, \"got uaEvent $event/${acc.aor}\")\n            return\n        }\n\n        if (ev[0] == \"sndfile dump\") {\n            Log.d(TAG, \"Got sndfile dump ${ev[1]}\")\n            if (Call.inCall()) {\n                if (ev[1].endsWith(\"enc.wav\"))\n                    Call.calls()[0].dumpfiles[0] = ev[1]\n                else\n                    Call.calls()[0].dumpfiles[1] = ev[1]\n            }\n            return\n        }\n\n        if (ev[0] == \"recorder sessionid\") {\n            recorderSessionId = ev[1].toInt()\n            Log.d(TAG, \"got recorder sessionid $recorderSessionId\")\n            if (recorderSessionId != 0) {\n                if (aecAvailable) {\n                    aec = AcousticEchoCanceler.create(recorderSessionId)\n                    if (aec != null) {\n                        if (!aec!!.enabled) {\n                            aec!!.enabled = true\n                            if (aec!!.enabled)\n                                Log.d(TAG, \"AEC is enabled\")\n                            else\n                                Log.w(TAG, \"Failed to enable AEC\")\n                        }\n                        else\n                            Log.d(TAG, \"AEC is already enabled\")\n                    } else\n                        Log.w(TAG, \"Failed to create AEC for session $recorderSessionId\")\n                }\n                if (agcAvailable) {\n                    agc = AutomaticGainControl.create(recorderSessionId)\n                    if (agc != null) {\n                        if (!agc!!.enabled) {\n                            agc!!.enabled = true\n                            if (agc!!.enabled)\n                                Log.d(TAG, \"AGC is enabled\")\n                        }\n                    } else\n                        Log.w(TAG, \"Failed to create AGC\")\n                }\n                if (nsAvailable) {\n                    ns = NoiseSuppressor.create(recorderSessionId)\n                    if (ns != null) {\n                        if (!ns!!.enabled) {\n                            ns!!.enabled = true\n                            if (ns!!.enabled)\n                                Log.d(TAG, \"NS is enabled\")\n                        }\n                    } else\n                        Log.w(TAG, \"Failed to create NS\")\n                }\n                recorderSessionId = 0\n            }\n            return\n        }\n\n        val ua = UserAgent.ofUap(uap)\n        if (ua == null) {\n            Log.w(TAG, \"uaEvent $event did not find ua $uap\")\n            return\n        }\n\n        val aor = ua.account.aor\n\n        Log.d(TAG, \"got uaEvent $event/$aor/$callp\")\n\n        val call = Call.ofCallp(callp)\n        if (call == null && callp != 0L &&\n            !setOf(\"incoming call\", \"call incoming\", \"call closed\").contains(ev[0])) {\n            Log.w(TAG, \"uaEvent $event did not find call $callp\")\n            return\n        }\n\n        for (accountIndex in uas.value.indices) {\n            if (uas.value[accountIndex].account.aor == aor) {\n                when (ev[0]) {\n                    \"registering\", \"unregistering\" -> {\n                        ua.updateStatus(circleYellow.getValue(colorblind))\n                        updateStatusNotification()\n                        if (isMainVisible)\n                            registrationUpdate.postValue(System.currentTimeMillis())\n                        return\n                    }\n                    \"registered\" -> {\n                        ua.updateStatus(\n                            if (Api.account_regint(ua.account.accp) == 0)\n                                R.drawable.circle_white\n                            else\n                                circleGreen.getValue(colorblind)\n                        )\n                        updateStatusNotification()\n                        if (isMainVisible)\n                            registrationUpdate.postValue(System.currentTimeMillis())\n                        return\n                    }\n                    \"registering failed\" -> {\n                        ua.updateStatus(if (Api.account_regint(ua.account.accp) == 0)\n                            R.drawable.circle_white\n                        else\n                            circleRed.getValue(colorblind)\n                        )\n                        updateStatusNotification()\n                        if (isMainVisible)\n                            registrationUpdate.postValue(System.currentTimeMillis())\n                        if (Utils.isVisible()) {\n                            val reason = if (ev.size > 1) {\n                                if (ev[1] == \"Invalid argument\") // Likely due to DNS lookup failure\n                                    \": DNS lookup failed\"\n                                else\n                                    \": ${ev[1]}\"\n                            } else\n                                \"\"\n                            toast(String.format(getString(R.string.registering_failed), aor) + reason)\n                        }\n                        return\n                    }\n                    \"call outgoing\" -> {\n                        if (call!!.status.value == \"transferring\")\n                            break\n                        stopMediaPlayer()\n                        setCallVolume()\n                        proximitySensing(proximitySensing)\n                    }\n                    \"call ringing\" -> {\n                        ConnectionService.connections[callp]?.setRinging()\n                        ensureCommunicationMode()\n                        playRingBack()\n                        return\n                    }\n                    \"call progress\" -> {\n                        ensureCommunicationMode()\n                        if ((ev[1].toInt() and Api.SDP_RECVONLY) != 0)\n                            stopMediaPlayer()\n                        else {\n                            ConnectionService.connections[callp]?.setRinging()\n                            playRingBack()\n                        }\n                        return\n                    }\n                    \"incoming call\" -> {\n                        val peerUri = ev[1]\n                        val toastMsg = if (Call.isAnyCallActive(applicationContext))\n                            String.format(getString(R.string.call_auto_rejected),\n                                Utils.friendlyUri(this, peerUri, ua.account))\n                        else if (ua.account.blockUnknown && Contact.contactName(peerUri) == peerUri)\n                            String.format(getString(R.string.call_blocked),\n                                Utils.friendlyUri(this, peerUri, ua.account))\n                        else if (!Utils.checkPermissions(this, arrayOf(RECORD_AUDIO)))\n                            getString(R.string.no_calls)\n                        else\n                            \"\"\n                        if (toastMsg != \"\") {\n                            Log.d(TAG, \"Auto-rejecting incoming call to $uap from $peerUri\")\n                            Api.sip_treply(callp, 486, \"Busy Here\")\n                            Api.bevent_stop(ev[2].toLong())\n                            toast(toastMsg)\n                            if (toastMsg.contains(getString(R.string.call_blocked))) {\n                                if (ua.account.callHistory)\n                                    Blocked(\n                                        ua.account.aor,\n                                        peerUri,\n                                        \"invite\",\n                                        GregorianCalendar().timeInMillis\n                                    ).add()\n                            }\n                            else {\n                                val name = \"callwaiting_$toneCountry\"\n                                val resourceId = resources.getIdentifier(\n                                    name,\n                                    \"raw\",\n                                    packageName\n                                )\n                                if (resourceId != 0) {\n                                    playUnInterrupted(resourceId, 1)\n                                } else {\n                                    Log.e(TAG, \"Callwaiting tone $name.wav not found\")\n                                }\n                                if (ua.account.callHistory) {\n                                    CallHistoryNew(aor, peerUri, \"in\").add()\n                                    ua.account.missedCalls = true\n                                }\n                            }\n                            return\n                        }\n                        // callp holds SIP message pointer\n                        Api.ua_accept(uap, callp)\n                        return\n                    }\n                    \"call incoming\" -> {\n                        val peerUri = ev[1]\n                        Log.d(TAG, \"Incoming call $uap/$callp/$peerUri\")\n                        if (Call.ofCallp(callp) == null)\n                            Call(callp, ua, peerUri, \"in\", \"incoming\").add()\n                        val extras = android.os.Bundle()\n                        extras.putLong(\"uap\", uap)\n                        extras.putLong(\"callp\", callp)\n                        extras.putString(\"peerUri\", peerUri)\n                        try {\n                            tm.addNewIncomingCall(getPhoneAccountHandle(this), extras)\n                        } catch (e: Exception) {\n                            Log.e(TAG, \"Telecom addNewIncomingCall failed: ${e.message}\")\n                        }\n                        return\n                    }\n                    \"call answered\" -> {\n                        stopMediaPlayer()\n                        ensureCommunicationMode()\n                        if (call!!.status.value == \"incoming\")\n                            call.status.value = \"answered\"\n                        else\n                            return\n                    }\n                    \"call redirect\" -> {\n                        stopMediaPlayer()\n                    }\n                    \"call established\" -> {\n                        ConnectionService.connections[callp]?.setActive()\n                        ensureCommunicationMode()\n                        nm.cancel(CALL_NOTIFICATION_ID)\n                        Log.d(TAG, \"AoR $aor call $callp established\")\n                        call!!.status.value = \"connected\"\n                        call.onhold = false\n                        call.startTime = GregorianCalendar()\n                        updateStatusNotification()\n                        if (!isMainVisible)\n                            return\n                    }\n                    \"call update\" -> {\n                        if (call!!.conferenceCall) {\n                            Log.d(TAG, \"Refusing to update conference call ${call.callp}\")\n                            Api.bevent_stop(ev[2].toLong())\n                            return\n                        }\n                        val newHeldState = when (ev[1].toInt()) {Api.SDP_INACTIVE, Api.SDP_RECVONLY -> true\n                            else -> false\n                        }\n                        val connection = ConnectionService.connections[callp]\n                        if (call.held && !newHeldState) {\n                            Log.d(TAG, \"Call ${call.callp} un-held by peer.\")\n                            call.onhold = false\n                            // Use a Coroutine with a small delay to let the SIP\n                            // transaction (the re-INVITE from the peer) finish\n                            // before trying to hold the other call and resume this one.\n                            CoroutineScope(Dispatchers.Main).launch {\n                                delay(100)\n                                call.resume()\n                            }\n                        }\n                        call.held = newHeldState\n                        if (newHeldState) {\n                            // Peer put us on hold\n                            call.showOnHoldNotice.value = true\n                            call.callOnHold.value = true\n                            connection?.setOnHold()\n                        } else {\n                            // Peer un-held us\n                            call.showOnHoldNotice.value = false\n                            // Only clear the UI if we aren't also manually holding it\n                            if (!call.onhold) {\n                                call.callOnHold.value = false\n                                connection?.setActive()\n                            }\n                        }\n                        if (call.state() == Api.CALL_STATE_EARLY) {\n                            if ((ev[1].toInt() and Api.SDP_RECVONLY) != 0)\n                                stopMediaPlayer()\n                            else {\n                                ConnectionService.connections[callp]?.setRinging()\n                                playRingBack()\n                            }\n                        }\n                        if (call.status.value == \"connected\" && !call.held && !call.onhold) {\n                            if (call.callOnHold.value || call.showOnHoldNotice.value) {\n                                Log.d(TAG, \"Safety guard: Clearing stuck hold flags for ${call.callp}\")\n                                call.callOnHold.value = false\n                                call.showOnHoldNotice.value = false\n                                connection?.setActive()\n                            }\n                        }\n                        if (!isMainVisible || call.status.value != \"connected\")\n                            return\n                    }\n                    \"call verified\", \"call secure\" -> {\n                        if (ev[0] == \"call secure\") {\n                            call!!.security = R.color.colorTrafficYellow\n                        } else {\n                            call!!.security = R.color.colorTrafficGreen\n                            call.zid = ev[1]\n                        }\n                        if (!isMainVisible)\n                            return\n                    }\n                    \"call transfer\" -> {\n                        if (!Utils.isVisible()) {\n                            val piFlags = PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT\n                            val intent = Intent(applicationContext, MainActivity::class.java)\n                            intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or\n                                Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK\n                            intent.putExtra(\"action\", \"transfer show\")\n                                .putExtra(\"callp\", callp).putExtra(\"uri\", ev[1])\n                            val pi = PendingIntent.getActivity(applicationContext, TRANSFER_REQ_CODE, intent, piFlags)\n                            val nb = NotificationCompat.Builder(this, HIGH_CHANNEL_ID)\n                            val target = Utils.friendlyUri(this, ev[1], ua.account)\n                            nb.setSmallIcon(R.drawable.ic_notification_call)\n                                .setColor(ContextCompat.getColor(this, R.color.colorPrimary))\n                                .setContentIntent(pi)\n                                .setDefaults(Notification.DEFAULT_SOUND)\n                                .setAutoCancel(true)\n                                .setContentTitle(getString(R.string.transfer_request_to))\n                                .setContentText(target)\n                            val acceptIntent = Intent(applicationContext, MainActivity::class.java)\n                            acceptIntent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or\n                                    Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK\n                            acceptIntent.putExtra(\"action\", \"transfer accept\")\n                                .putExtra(\"callp\", callp).putExtra(\"uri\", ev[1])\n                            val acceptPendingIntent = PendingIntent.getActivity(applicationContext,\n                                ACCEPT_REQ_CODE, acceptIntent, piFlags)\n                            val denyIntent = Intent(this, BaresipService::class.java)\n                            denyIntent.action = \"Transfer Deny\"\n                            denyIntent.putExtra(\"callp\", callp)\n                            val denyPendingIntent = PendingIntent.getService(this,\n                                    DENY_REQ_CODE, denyIntent, piFlags)\n                            nb.addAction(R.drawable.ic_notification_call,\n                                getString(R.string.accept), acceptPendingIntent)\n                            nb.addAction(R.drawable.ic_notification_call_end,\n                                getString(R.string.deny), denyPendingIntent)\n                            nm.notify(TRANSFER_NOTIFICATION_ID, nb.build())\n                            return\n                        }\n                    }\n                    \"transfer failed\" -> {\n                        Log.d(TAG, \"AoR $aor call $callp transfer failed: ${ev[1]}\")\n                        stopMediaPlayer()\n                        call!!.referTo = \"\"\n                        if (Utils.isVisible())\n                            toast(\"${getString(R.string.transfer_failed)}: ${ev[1].trim()}\")\n                        if (!isMainVisible)\n                            return\n                    }\n                    \"call closed\" -> {\n                        Log.d(TAG, \"AoR $aor call $callp is closed prm: ${ev[1]}\")\n                        ConnectionService.lastDisconnectTime = System.currentTimeMillis()\n                        val connection = ConnectionService.connections[callp]\n                        if (connection != null) {\n                            val cause = when {\n                                ev[1].contains(\"200\") -> DisconnectCause.LOCAL\n                                ev[1].contains(\"486\") -> DisconnectCause.BUSY\n                                ev[1].contains(\"404\") -> DisconnectCause.ERROR\n                                ev[1].contains(\"403\") || ev[1].contains(\"401\") -> DisconnectCause.RESTRICTED\n                                else -> DisconnectCause.REMOTE\n                            }\n                            connection.setDisconnected(DisconnectCause(cause))\n                            Handler(Looper.getMainLooper()).postDelayed({\n                                connection.destroy()\n                                ConnectionService.connections.remove(callp)\n                            }, 500)\n                        }\n                        nm.cancel(CALL_NOTIFICATION_ID)\n                        if (call != null) {\n                            stopRinging()\n                            stopMediaPlayer()\n                            aec?.release()\n                            aec = null\n                            agc?.release()\n                            agc = null\n                            ns?.release()\n                            ns = null\n                            val newCall = call.newCall\n                            if (newCall != null) {\n                                newCall.onHoldCall = null\n                                call.newCall = null\n                            }\n                            val onHoldCall = call.onHoldCall\n                            if (onHoldCall != null) {\n                                onHoldCall.newCall = null\n                                onHoldCall.referTo = \"\"\n                                call.onHoldCall = null\n                            }\n                            val isConference = call.conferenceCall\n                            call.remove()\n                            updateStatusNotification()\n                            if (isConference && ua.calls().isEmpty())\n                                Api.module_unload(\"mixminus\")\n                            val reason = ev[1]\n                            val tone = ev[2]\n                            if (tone == \"busy\")\n                                playBusy()\n                            else\n                                ensureCommunicationMode()\n                            if (call.dir == \"out\")\n                                call.rejected = call.startTime == null &&\n                                        !reason.startsWith(\"408\") &&\n                                        !reason.startsWith(\"480\") &&\n                                        !reason.startsWith(\"Connection reset by\")\n                            val missed = call.dir == \"in\" && call.startTime == null && !call.rejected\n                            val completedElsewhere = missed && ev[2].startsWith(\"SIP\") &&\n                                    ev[2].contains(\";cause=200\")\n                            if (ua.account.callHistory) {\n                                CoroutineScope(Dispatchers.IO).launch {\n                                    val history = CallHistoryNew(aor, call.peerUri, call.dir)\n                                    history.stopTime = GregorianCalendar()\n                                    history.startTime = if (completedElsewhere) history.stopTime else call.startTime\n                                    history.rejected = call.rejected\n                                    if (call.dumpfiles[0] != \"\") {\n                                        history.recording = call.dumpfiles\n                                    }\n                                    history.add()\n                                    if (call.startTime != null && call.dumpfiles[0] != \"\") {\n                                        delay(500)\n                                        val rxFile = File(call.dumpfiles[0])\n                                        val txFile = File(call.dumpfiles[1])\n                                        val mergedFileName = rxFile.name\n                                            .replace(\"dump\", \"rec\")\n                                            .replace(\"=>\", \"-\")\n                                            .replace(\"sip:\", \"\")\n                                            .replace(\"-enc\", \"\")\n                                            .replace(\"*\", \"#\")\n                                            .replace(\";user=phone\", \"\")\n                                        val mergedFile = File(filesPath, mergedFileName)\n                                        if (Utils.mergeWavFiles(rxFile, txFile, mergedFile)) {\n                                            Log.d(TAG, \"Automatic merge succeeded.\")\n                                            history.recording = arrayOf(mergedFile.absolutePath, \"\")\n                                            CallHistoryNew.save()\n                                            try {\n                                                rxFile.delete()\n                                                txFile.delete()\n                                            } catch (e: Exception) {\n                                                Log.w(TAG, \"Could not delete temporary raw files after merge: ${e.message}\")\n                                            }\n                                        } else {\n                                            Log.e(TAG, \"Automatic merge failed. Storing raw file paths as fallback.\")\n                                            history.recording = call.dumpfiles\n                                        }\n                                    }\n                                }\n                                ua.account.missedCalls = ua.account.missedCalls || missed\n                            }\n                            if (missed) {\n                                val piFlags = PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT\n                                val caller = Utils.friendlyUri(this, call.peerUri, ua.account)\n                                val intent = Intent(applicationContext, MainActivity::class.java)\n                                intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or\n                                        Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK\n                                intent.putExtra(\"action\", \"call missed\")\n                                        .putExtra(\"uap\", uap)\n                                val pi = PendingIntent.getActivity(applicationContext, CALL_REQ_CODE,\n                                    intent, piFlags)\n                                val nb = NotificationCompat.Builder(this, HIGH_CHANNEL_ID)\n                                nb.setSmallIcon(R.drawable.ic_notification_call_missed)\n                                        .setColor(ContextCompat.getColor(this, R.color.colorPrimary))\n                                        .setContentIntent(pi)\n                                        .setCategory(Notification.CATEGORY_CALL)\n                                        .setAutoCancel(true)\n                                var missedCalls = 0\n                                for (notification in nm.activeNotifications)\n                                    if (notification.id == CALL_MISSED_NOTIFICATION_ID)\n                                        missedCalls++\n                                if (missedCalls == 0) {\n                                    nb.setContentTitle(getString(R.string.missed_call_from))\n                                    nb.setContentText(caller)\n                                }\n                                else {\n                                    nb.setContentTitle(getString(R.string.missed_calls))\n                                    nb.setContentText(\n                                            String.format(getString(R.string.missed_calls_count),\n                                                    missedCalls + 1))\n                                }\n                                nm.notify(CALL_MISSED_NOTIFICATION_ID, nb.build())\n                            }\n                            if (!Utils.isVisible())\n                                return\n                        }\n                        val reason = ev[1].trim()\n                        if ((reason != \"\") && (ua.calls().isEmpty())) {\n                            if (reason[0].isDigit()) {\n                                if (reason[0] != '3')\n                                    toast(\"${getString(R.string.call_failed)}: $reason\")\n                            }\n                            else\n                                toast(\"${getString(R.string.call_closed)}: ${Api.call_peer_uri(callp)}: $reason\")\n                        }\n                    }\n                }\n            }\n        }\n\n        postServiceEvent(ServiceEvent(event, arrayListOf(uap, callp), System.nanoTime()))\n\n    }\n\n    @Suppress(\"unused\")\n    @SuppressLint(\"UnspecifiedImmutableFlag\")\n    @Keep\n    fun messageEvent(uap: Long, peerUri: String, cType: String, msg: ByteArray) {\n\n        val ua = UserAgent.ofUap(uap)\n        if (ua == null) {\n            Log.w(TAG, \"messageEvent did not find ua $uap\")\n            return\n        }\n\n        if (ua.account.blockUnknown && Contact.contactName(peerUri) == peerUri) {\n            Log.d(TAG, \"Auto-rejecting incoming message by $uap from $peerUri\")\n            Blocked(\n                ua.account.aor,\n                peerUri,\n                \"message\",\n                GregorianCalendar().timeInMillis\n            ).add()\n            toast(String.format(\n                    getString(R.string.message_blocked),\n                    Utils.friendlyUri(this, peerUri, ua.account)\n                )\n            )\n            // Api.sip_treply(callp, 486, \"Busy Here\")\n            // Api.bevent_stop(bevent)\n            return\n        }\n\n        val charsetString = Utils.paramValue(cType.replace(\" \", \"\"), \"charset\")\n        val charset = try {\n            Charset.forName(charsetString)\n        } catch (_: Exception) {\n            StandardCharsets.UTF_8\n        }\n        val text = try {\n            String(msg, charset)\n        } catch (e: Exception) {\n            val error = \"Decoding of message failed using charset $charset from $cType: ${e.message}!\"\n            Log.w(TAG, error)\n            error\n        }\n\n        val timeStamp = System.currentTimeMillis()\n        val timeStampString = timeStamp.toString()\n        Log.d(TAG, \"Message event for $uap from $peerUri at $timeStampString\")\n        Message(ua.account.aor, peerUri, text, timeStamp, MESSAGE_DOWN, 0, \"\", true).add()\n        ua.account.unreadMessages = true\n\n        if (!Utils.isVisible()) {\n\n            // common flags\n            val piFlags = PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT\n\n            // message show\n            val intent = Intent(applicationContext, MainActivity::class.java)\n            intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP or\n                        Intent.FLAG_ACTIVITY_NEW_TASK\n            intent.putExtra(\"action\", \"message show\").putExtra(\"uap\", uap).putExtra(\"peer\", peerUri)\n            val pi = PendingIntent.getActivity(applicationContext, MESSAGE_REQ_CODE, intent, piFlags)\n\n            // message notification builder\n            val sender = Utils.friendlyUri(this, peerUri, ua.account)\n            val senderContact = Contact.findContact(peerUri)\n            val personBuilder = Person.Builder().setName(sender)\n            val contactColor = senderContact?.color() ?: \"#B0B0B0\"\n            val initial = if (sender.isNotEmpty()) sender.take(1) else \"?\"\n            val textAvatarBitmap = Utils.createTextAvatar(initial,contactColor)\n            var icon = IconCompat.createWithBitmap(textAvatarBitmap)\n            if (senderContact is Contact.BaresipContact) {\n                if (senderContact.avatarImage != null)\n                    icon = IconCompat.createWithBitmap(senderContact.avatarImage!!.toCircle())\n            }\n            else if (senderContact is Contact.AndroidContact) {\n                if (senderContact.thumbnailUri != null) {\n                    try {\n                        val source = ImageDecoder.createSource(contentResolver,\n                            senderContact.thumbnailUri!!)\n                        val bitmap = ImageDecoder.decodeBitmap(source) { decoder, _, _ ->\n                            decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE\n                            // decoder.setTargetSize(256, 256)\n                        }\n                        icon = IconCompat.createWithBitmap(bitmap.toCircle())\n                    } catch (e: Exception) {\n                        Log.e(TAG, \"Failed to load Android contact avatar: $e\")\n                    }\n                }\n            }\n            val senderPerson = personBuilder.setIcon(icon).build()\n            val localUserPerson = Person.Builder()\n                .setName(getString(R.string.you))\n                .setKey(ua.account.aor)\n                .build()\n            val messagingStyle = MessagingStyle(localUserPerson)\n                .setConversationTitle(null)\n                .setGroupConversation(false)\n                .addMessage(text, timeStamp, senderPerson)\n            val nb = NotificationCompat.Builder(this, HIGH_CHANNEL_ID)\n                .setSmallIcon(R.drawable.ic_notification_message)\n                .setColor(ContextCompat.getColor(this, R.color.colorPrimary))\n                .setContentIntent(pi)\n                .setSound(Settings.System.DEFAULT_NOTIFICATION_URI)\n                .setAutoCancel(true)\n                .setStyle(messagingStyle)\n                .setCategory(NotificationCompat.CATEGORY_MESSAGE)\n                .setPriority(NotificationCompat.PRIORITY_HIGH)\n\n            // message inline reply\n            val remoteInput = RemoteInput.Builder(KEY_TEXT_REPLY)\n                .setLabel(getString(R.string.reply))\n                .build()\n            val directReplyIntent = Intent(this, BaresipService::class.java)\n            directReplyIntent.action = \"Message Inline Reply\"\n            directReplyIntent.putExtra(\"uap\", uap).putExtra(\"peer\", peerUri).putExtra(\"time\", timeStamp)\n            val directReplyPendingIntent = PendingIntent.getService(\n                this,\n                DIRECT_REPLY_REQ_CODE,\n                directReplyIntent,\n                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE\n            )\n            val inlineReplyAction = NotificationCompat.Action.Builder(\n                R.drawable.ic_notification_reply,\n                getString(R.string.reply),\n                directReplyPendingIntent\n            ).addRemoteInput(remoteInput)\n                .setAllowGeneratedReplies(true)\n                .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY).\n                build()\n\n            // message save\n            val saveIntent = Intent(this, BaresipService::class.java)\n            saveIntent.action = \"Message Save\"\n            saveIntent.putExtra(\"uap\", uap).putExtra(\"time\", timeStampString)\n            val savePendingIntent = PendingIntent.getService(this, SAVE_REQ_CODE, saveIntent, piFlags)\n            val saveAction = NotificationCompat.Action.Builder(\n                R.drawable.ic_notification_save,\n                getString(R.string.save),\n                savePendingIntent\n            ).build()\n\n            // message delete\n            val deleteIntent = Intent(this, BaresipService::class.java)\n            deleteIntent.action = \"Message Delete\"\n            deleteIntent.putExtra(\"uap\", uap).putExtra(\"time\", timeStampString)\n            val deletePendingIntent = PendingIntent.getService(this, DELETE_REQ_CODE, deleteIntent, piFlags)\n            val deleteAction = NotificationCompat.Action.Builder(\n                R.drawable.ic_notification_delete,\n                getString(R.string.delete),\n                deletePendingIntent\n            ).build()\n\n            nb.addAction(inlineReplyAction).addAction(saveAction).addAction(deleteAction)\n            nm.notify(MESSAGE_NOTIFICATION_ID, nb.build())\n\n            return\n        }\n\n        if (nm.currentInterruptionFilter <= NotificationManager.INTERRUPTION_FILTER_ALL)\n            nt.play()\n\n        postServiceEvent(ServiceEvent(\"message show\", arrayListOf(uap, peerUri), System.nanoTime()))\n    }\n\n    @Keep\n    @Suppress(\"UNUSED\")\n    fun messageResponse(responseCode: Int, responseReason: String, time: String) {\n        Log.d(TAG, \"Message response '$responseCode $responseReason' at $time\")\n        val timeStamp = time.toLong()\n        for (m in messages.reversed())\n            if (m.timeStamp == timeStamp) {\n                if (responseCode < 300) {\n                    m.direction = MESSAGE_UP\n                } else {\n                    m.direction = MESSAGE_UP_FAIL\n                    m.responseCode = responseCode\n                    m.responseReason = responseReason\n                }\n                messageUpdate.postValue(System.currentTimeMillis())\n                break\n            } else {\n                if (m.timeStamp < timeStamp - 60000)\n                    break\n            }\n    }\n\n    private var audioModeChangedListener: AudioManager.OnModeChangedListener? = null\n\n    fun runCall(uap: Long, uri: String, conferenceCall: Boolean, onHoldCallp: Long) {\n\n        val executeCall = {\n            val handler = Handler(Looper.getMainLooper())\n            handler.postDelayed({\n                val ua = UserAgent.ofUap(uap)\n                if (ua != null) {\n                    if (conferenceCall && ua.calls().isEmpty())\n                        Api.module_load(\"mixminus\")\n                    val callp = ua.callAlloc(0L, Api.VIDMODE_OFF)\n                    if (callp != 0L) {\n                        ConnectionService.promoteOutgoingConnection(callp)\n                        val onHoldCall = Call.ofCallp(onHoldCallp)\n                        val call = Call(callp, ua, uri, \"out\", \"outgoing\")\n                        call.onHoldCall = onHoldCall\n                        call.conferenceCall = conferenceCall\n                        call.add()\n                        updateStatusNotification()\n                        if (onHoldCall != null)\n                            onHoldCall.newCall = call\n                        if (!call.connect(uri)) {\n                            Log.w(TAG, \"call_connect $callp failed\")\n                            ConnectionService.onCallClosed(callp)\n                            call.remove()\n                            call.destroy()\n                            updateStatusNotification()\n                        }\n                    } else {\n                        ConnectionService.pendingOutgoingConnection?.let {\n                            it.setDisconnected(DisconnectCause(DisconnectCause.ERROR))\n                            it.destroy()\n                            ConnectionService.pendingOutgoingConnection = null\n                        }\n                    }\n                }\n            }, audioDelay)\n        }\n\n        if (VERSION.SDK_INT < 31) {\n            Log.d(TAG, \"Setting audio mode to MODE_IN_COMMUNICATION\")\n            am.mode = MODE_IN_COMMUNICATION\n            executeCall()\n        } else {\n            if (am.mode == MODE_IN_COMMUNICATION) {\n                Log.d(TAG, \"Audio mode already in MODE_IN_COMMUNICATION\")\n                executeCall()\n            } else {\n                audioModeChangedListener = AudioManager.OnModeChangedListener { mode ->\n                    if (mode == MODE_IN_COMMUNICATION) {\n                        Log.d(TAG, \"Audio mode changed to MODE_IN_COMMUNICATION\")\n                        audioModeChangedListener?.let {\n                            am.removeOnModeChangedListener(it)\n                            audioModeChangedListener = null\n                        }\n                        executeCall()\n                    }\n                }\n                am.addOnModeChangedListener(mainExecutor, audioModeChangedListener!!)\n                Log.d(TAG, \"Setting audio mode to MODE_IN_COMMUNICATION (waiting for callback)\")\n                am.mode = MODE_IN_COMMUNICATION\n            }\n        }\n    }\n\n    @Suppress(\"unused\")\n    @Keep\n    fun started() {\n        Log.d(TAG, \"Received 'started' from baresip\")\n        isNativeReady = true\n        Api.net_debug()\n        postServiceEvent(ServiceEvent(\"started\", arrayListOf(callActionUri), System.nanoTime()))\n        callActionUri = \"\"\n        Log.d(TAG, \"Battery optimizations are ignored: \" +\n                \"${pm.isIgnoringBatteryOptimizations(packageName)}\")\n        Log.d(TAG, \"Partial wake lock/wifi lock is held: \" +\n                \"${partialWakeLock.isHeld}/${wifiLock.isHeld}\")\n        updateStatusNotification()\n    }\n\n    @Suppress(\"unused\")\n    @Keep\n    fun stopped(error: String) {\n        Log.d(TAG, \"Received 'stopped' from baresip with start error '$error'\")\n        isNativeReady = false\n        quitTimer.cancel()\n        cleanService()\n        isServiceRunning = false\n        postServiceEvent(ServiceEvent(\"stopped\", arrayListOf(error), System.nanoTime()))\n        stopSelf()\n    }\n\n    private fun createNotificationChannels() {\n        val lowChannel = NotificationChannel(LOW_CHANNEL_ID, \"No sound, no vibrate\",\n            NotificationManager.IMPORTANCE_LOW)\n        lowChannel.description = \"Background status notifications\"\n        lowChannel.enableVibration(false)\n        lowChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC\n        nm.createNotificationChannel(lowChannel)\n        val ringAttributes = AudioAttributes.Builder()\n            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)\n            .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)\n            .build()\n        val highChannel = NotificationChannel(HIGH_CHANNEL_ID, \"Sound, vibrate, and peek\",\n            NotificationManager.IMPORTANCE_HIGH)\n        highChannel.description = \"Incoming calls and important alerts\"\n        highChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC\n        highChannel.enableVibration(true)\n        highChannel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI, ringAttributes)\n        nm.createNotificationChannel(highChannel)\n        val mediumChannel = NotificationChannel(MEDIUM_CHANNEL_ID, \"Sound only\",\n            NotificationManager.IMPORTANCE_DEFAULT)\n        mediumChannel.description = \"Incoming messages\"\n        mediumChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC\n        mediumChannel.enableVibration(false)\n        mediumChannel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI, ringAttributes)\n        nm.createNotificationChannel(mediumChannel)\n    }\n\n    private fun buildStatusNotification(): Notification {\n        if (VERSION.SDK_INT >= 31)\n            snb.foregroundServiceBehavior = Notification.FOREGROUND_SERVICE_IMMEDIATE\n        snb.setOngoing(true)\n        val notification = snb.build()\n        notification.flags = notification.flags or Notification.FLAG_NO_CLEAR or\n                Notification.FLAG_ONGOING_EVENT\n        return notification\n    }\n\n    @SuppressLint(\"UnspecifiedImmutableFlag\")\n    private fun showStatusNotification() {\n        val intent = Intent(applicationContext, MainActivity::class.java)\n            .setAction(Intent.ACTION_MAIN)\n            .addCategory(Intent.CATEGORY_LAUNCHER)\n        val pi = PendingIntent.getActivity(applicationContext, STATUS_REQ_CODE, intent,\n            PendingIntent.FLAG_IMMUTABLE)\n        val deleteIntent = Intent(this, BaresipService::class.java)\n            .setAction(\"Notification Dismissed\")\n        val dpi = PendingIntent.getService(\n            this,\n            STATUS_REQ_CODE,\n            deleteIntent,\n            PendingIntent.FLAG_IMMUTABLE\n        )\n        val notificationLayout = RemoteViews(packageName, R.layout.status_notification)\n        snb.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)\n            .setSmallIcon(R.drawable.ic_notification_b)\n            .setContentIntent(pi)\n            .setDeleteIntent(dpi)\n            .setOngoing(true)\n            .setCategory(Notification.CATEGORY_SERVICE)\n            .setStyle(NotificationCompat.DecoratedCustomViewStyle())\n            .setCustomContentView(notificationLayout)\n        val notification = buildStatusNotification()\n        try {\n            if (VERSION.SDK_INT >= 34)\n                startForeground(STATUS_NOTIFICATION_ID, notification,\n                    ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE)\n            else\n                startForeground(STATUS_NOTIFICATION_ID, notification)\n        } catch (e: Exception) {\n            Log.e(TAG, \"Failed to start foreground service: ${e.message}\")\n        }\n    }\n\n    fun updateStatusNotification() {\n\n        val activeCall = Call.calls().find {\n            it.status.value == \"connected\" || it.status.value == \"outgoing\" || it.status.value == \"answered\"\n        }\n\n        val builder = NotificationCompat.Builder(this, LOW_CHANNEL_ID)\n        val intent = Intent(applicationContext, MainActivity::class.java)\n            .setAction(Intent.ACTION_MAIN)\n            .addCategory(Intent.CATEGORY_LAUNCHER)\n\n        val pi = PendingIntent.getActivity(applicationContext, STATUS_REQ_CODE, intent, PendingIntent.FLAG_IMMUTABLE)\n        val deleteIntent = Intent(this, BaresipService::class.java).setAction(\"Notification Dismissed\")\n        val dpi = PendingIntent.getService(this, STATUS_REQ_CODE, deleteIntent, PendingIntent.FLAG_IMMUTABLE)\n\n        builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)\n            .setSmallIcon(R.drawable.ic_notification_b)\n            .setContentIntent(pi)\n            .setDeleteIntent(dpi)\n            .setOngoing(true)\n\n        if (activeCall != null) {\n            val peerUri = activeCall.peerUri\n            val caller = Utils.friendlyUri(this, peerUri, activeCall.ua.account)\n            val person = Person.Builder().setName(caller).build()\n\n            val hangupIntent = Intent(this, BaresipService::class.java)\n            hangupIntent.action = \"Call Hangup\"\n            hangupIntent.putExtra(\"callp\", activeCall.callp)\n            val hpi = PendingIntent.getService(this, REJECT_REQ_CODE, hangupIntent,\n                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)\n\n            builder.setStyle(NotificationCompat.CallStyle.forOngoingCall(person, hpi))\n                .setCategory(Notification.CATEGORY_CALL)\n                .setPriority(NotificationCompat.PRIORITY_LOW)\n                .setWhen(activeCall.startTime?.timeInMillis ?: System.currentTimeMillis())\n                .setUsesChronometer(true)\n                .setContentText(\n                    if (activeCall.onhold)\n                        getString(R.string.call_is_on_hold)\n                    else\n                        when (activeCall.status.value) {\n                            \"outgoing\", \"incoming\" -> getString(R.string.call_is_ringing)\n                            \"connected\", \"answered\" -> getString(R.string.call_is_connected)\n                            else -> getString(R.string.call)\n                        }\n                )\n        } else {\n            builder.setStyle(null)\n            builder.setStyle(NotificationCompat.DecoratedCustomViewStyle())\n                .setCategory(Notification.CATEGORY_SERVICE)\n                .setPriority(NotificationCompat.PRIORITY_MIN)\n                .setWhen(0)\n                .setShowWhen(false)\n                .setUsesChronometer(false)\n                .setContentTitle(\"\")\n                .setContentText(\"\")\n\n            val notificationLayout = RemoteViews(packageName, R.layout.status_notification)\n            for (i in 0..3) {\n                val resId = when (i) {\n                    0 -> R.id.status0\n                    1 -> R.id.status1\n                    2 -> R.id.status2\n                    else -> R.id.status3\n                }\n                if (i < uas.value.size) {\n                    notificationLayout.setImageViewResource(resId, uas.value[i].status)\n                    notificationLayout.setViewVisibility(resId, View.VISIBLE)\n                } else {\n                    notificationLayout.setViewVisibility(resId, View.INVISIBLE)\n                }\n            }\n            if (uas.value.size > 4)\n                notificationLayout.setViewVisibility(R.id.etc, View.VISIBLE)\n            else\n                notificationLayout.setViewVisibility(R.id.etc, View.INVISIBLE)\n\n            builder.setCustomContentView(notificationLayout)\n        }\n\n        if (VERSION.SDK_INT >= 31)\n            builder.foregroundServiceBehavior = Notification.FOREGROUND_SERVICE_IMMEDIATE\n\n        builder.setOngoing(true)\n        val notification = builder.build()\n        notification.flags = notification.flags or Notification.FLAG_NO_CLEAR or Notification.FLAG_ONGOING_EVENT\n\n        try {\n            if (activeCall != null) {\n                val type = if (VERSION.SDK_INT >= 30) {\n                    if (ContextCompat.checkSelfPermission(this, RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED)\n                        FOREGROUND_SERVICE_TYPE_PHONE_CALL or FOREGROUND_SERVICE_TYPE_MICROPHONE\n                    else\n                        FOREGROUND_SERVICE_TYPE_PHONE_CALL\n                } else 0\n                if (VERSION.SDK_INT >= 29)\n                    startForeground(STATUS_NOTIFICATION_ID, notification, type)\n                else\n                    startForeground(STATUS_NOTIFICATION_ID, notification)\n                isNotificationInCall = true\n            } else {\n                if (isNotificationInCall) {\n                    stopForeground(STOP_FOREGROUND_REMOVE)\n                    isNotificationInCall = false\n                    if (VERSION.SDK_INT >= 34)\n                        startForeground(STATUS_NOTIFICATION_ID, notification,\n                            ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE)\n                    else if (VERSION.SDK_INT >= 30)\n                        startForeground(STATUS_NOTIFICATION_ID, notification, 0)\n                    else\n                        startForeground(STATUS_NOTIFICATION_ID, notification)\n                } else {\n                    // Use standard notify for standby updates.\n                    // This is much more stable for background registration.\n                    nm.notify(STATUS_NOTIFICATION_ID, notification)\n                }\n            }\n        } catch (e: Exception) {\n            Log.e(TAG, \"Failed to update foreground notification: ${e.message}\")\n            nm.notify(STATUS_NOTIFICATION_ID, notification)\n        }\n    }\n\n    @SuppressLint(\"WakelockTimeout\")\n    private fun updatePartialWakeLock() {\n        // Hold the wake lock as long as the service is active to ensure\n        // native SIP timers (registration, keep-alives) continue to run.\n        try {\n            if (!partialWakeLock.isHeld) {\n                Log.i(TAG, \"Acquiring Partial Wake Lock\")\n                partialWakeLock.acquire()\n            }\n        } catch (e: Exception) {\n            Log.e(TAG, \"Error managing partialWakeLock: ${e.message}\")\n        }\n    }\n\n    private fun toast(message: String, length: Int = Toast.LENGTH_SHORT) {\n        Handler(Looper.getMainLooper()).post {\n            Toast.makeText(this@BaresipService.applicationContext, message, length).show()\n        }\n    }\n\n    @SuppressLint(\"FullScreenIntentPolicy\")\n    fun handleIncomingCall(call: Call) {\n        val ua = call.ua\n        val peerUri = call.peerUri\n        val callp = call.callp\n\n        val callerNumber = peerUri.split(\":\")[1].split(\"@\")[0]\n        if (shouldStartRinging(callerNumber))\n            startRinging()\n\n        if (!Utils.isVisible()) {\n            val piFlags = PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT\n            val intent = Intent(applicationContext, MainActivity::class.java)\n                .putExtra(\"action\", \"call show\")\n                .putExtra(\"callp\", callp)\n            intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK\n            val pi = PendingIntent.getActivity(applicationContext, CALL_REQ_CODE, intent, piFlags)\n\n            val channelId = HIGH_CHANNEL_ID\n            val nb = NotificationCompat.Builder(this, channelId)\n            val caller = Utils.friendlyUri(this, peerUri, ua.account)\n            val callerContact = Contact.findContact(peerUri)\n            val personBuilder = Person.Builder().setName(caller)\n            val contactColor = callerContact?.color() ?: \"#B0B0B0\"\n            val initial = if (caller.isNotEmpty()) caller.take(1) else \"?\"\n            val textAvatarBitmap = Utils.createTextAvatar(initial, contactColor)\n            var icon = IconCompat.createWithBitmap(textAvatarBitmap)\n\n            if (callerContact is Contact.BaresipContact) {\n                if (callerContact.avatarImage != null)\n                    icon = IconCompat.createWithBitmap(callerContact.avatarImage!!.toCircle())\n            } else if (callerContact is Contact.AndroidContact) {\n                if (callerContact.thumbnailUri != null) {\n                    try {\n                        val source = ImageDecoder.createSource(contentResolver, callerContact.thumbnailUri!!)\n                        val bitmap = ImageDecoder.decodeBitmap(source) { decoder, _, _ ->\n                            decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE\n                        }\n                        icon = IconCompat.createWithBitmap(bitmap.toCircle())\n                    } catch (e: Exception) {\n                        Log.e(TAG, \"Failed to load Android contact avatar: $e\")\n                    }\n                }\n            }\n\n            val person = personBuilder.setIcon(icon).build()\n            nb.setSmallIcon(R.drawable.ic_notification_call)\n                .setColor(ContextCompat.getColor(this, R.color.colorPrimary))\n                .setContentIntent(pi)\n                .setCategory(Notification.CATEGORY_CALL)\n                .setAutoCancel(false)\n                .setOngoing(true)\n                .setContentText(getString(R.string.is_calling))\n                .setWhen(System.currentTimeMillis())\n                .setShowWhen(true)\n                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)\n                .setPriority(NotificationCompat.PRIORITY_MAX)\n\n            if (VERSION.SDK_INT < 34 || nm.canUseFullScreenIntent())\n                nb.setFullScreenIntent(pi, true)\n\n            val answerIntent = Intent(applicationContext, MainActivity::class.java)\n                .putExtra(\"action\", \"call answer\")\n                .putExtra(\"callp\", callp)\n            val api = PendingIntent.getActivity(applicationContext, ANSWER_REQ_CODE, answerIntent, piFlags)\n\n            val rejectIntent = Intent(this, BaresipService::class.java)\n            rejectIntent.action = \"Call Reject\"\n            rejectIntent.putExtra(\"callp\", callp)\n            val rpi = PendingIntent.getService(this, REJECT_REQ_CODE, rejectIntent, piFlags)\n\n            nb.setStyle(NotificationCompat.CallStyle.forIncomingCall(person, rpi, api))\n            nm.notify(CALL_NOTIFICATION_ID, nb.build())\n        }\n\n        postServiceEvent(ServiceEvent(\"call incoming\", arrayListOf(ua.uap, callp), System.nanoTime()))\n    }\n\n    private fun startRinging() {\n        am.mode = AudioManager.MODE_RINGTONE\n        rt!!.isLooping = true\n        rt!!.play()\n        if (shouldVibrate()) {\n            val effect = VibrationEffect.createOneShot(500, VibrationEffect.DEFAULT_AMPLITUDE)\n            vbTimer = Timer()\n            vbTimer!!.schedule(object : TimerTask() {\n                override fun run() {\n                    if (VERSION.SDK_INT >= 33) {\n                        vibrator.vibrate(\n                            effect,\n                            android.os.VibrationAttributes.Builder()\n                                .setUsage(android.os.VibrationAttributes.USAGE_RINGTONE)\n                                .build()\n                        )\n                    } else {\n                        val attributes = AudioAttributes.Builder()\n                            .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)\n                            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)\n                            .build()\n                        @Suppress(\"DEPRECATION\")\n                        vibrator.vibrate(effect, attributes)\n                    }\n                }\n            }, 500L, 2000L)\n        }\n    }\n\n    private fun shouldStartRinging(callerNumber: String): Boolean {\n        val currentFilter = nm.currentInterruptionFilter\n        if (currentFilter <= NotificationManager.INTERRUPTION_FILTER_ALL)\n            return true\n        val channel = nm.getNotificationChannel(HIGH_CHANNEL_ID)\n        if (channel != null && channel.canBypassDnd())\n            return true\n        return isStarredContact(callerNumber)\n    }\n\n    private fun shouldVibrate(): Boolean {\n        // 1. If the phone is in Silent mode, never vibrate\n        if (am.ringerMode == AudioManager.RINGER_MODE_SILENT) return false\n        // 2. If the phone is in Vibrate mode, always vibrate\n        if (am.ringerMode == AudioManager.RINGER_MODE_VIBRATE) return true\n        // 3. If the phone is in Normal (Ringing) mode:\n        // First, check if the ringer volume is actually non-zero\n        if (am.getStreamVolume(AudioManager.STREAM_RING) == 0) return false\n        // Finally, check the system \"Vibrate for calls\" setting.\n        // Although deprecated, it is the standard way to check the \"Also vibrate for calls\" toggle.\n        return try {\n            @Suppress(\"DEPRECATION\")\n            Settings.System.getInt(contentResolver, Settings.System.VIBRATE_WHEN_RINGING, 0) != 0\n        } catch (_: Exception) {\n            // If the setting can't be read, default to FALSE.\n            false\n        }\n    }\n\n    private fun isStarredContact(callerNumber: String): Boolean {\n        if (contactsMode == \"baresip\" || callerNumber.isBlank())\n            return false\n        val phoneUri = Uri.withAppendedPath(\n            ContactsContract.PhoneLookup.CONTENT_FILTER_URI,\n            Uri.encode(callerNumber)\n        )\n        val projection = arrayOf(ContactsContract.PhoneLookup.STARRED)\n        try {\n            val cursor = contentResolver.query(phoneUri, projection, null, null, null)\n            cursor?.use {\n                if (it.moveToFirst()) {\n                    val isStarred = it.getInt(0) == 1\n                    if (isStarred) {\n                        Log.d(TAG, \"Caller '$callerNumber' is a starred contact.\")\n                    }\n                    return isStarred\n                }\n            }\n        } catch (e: Exception) {\n            Log.e(TAG, \"Could not query contacts for starred status: ${e.message}\")\n        }\n        return false\n    }\n\n    private fun stopRinging() {\n        rt!!.stop()\n        if (vbTimer != null) {\n            vbTimer!!.cancel()\n            vbTimer = null\n        }\n    }\n\n    @SuppressLint(\"DiscouragedApi\")\n    private fun playRingBack() {\n        if (mediaPlayer == null) {\n            val name = \"ringback_$toneCountry\"\n            val resourceId = resources.getIdentifier(name, \"raw\", packageName)\n            if (resourceId != 0) {\n                val audioAttributes = AudioAttributes.Builder()\n                    .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)\n                    .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)\n                    .build()\n\n                mediaPlayer = MediaPlayer().apply {\n                    setAudioAttributes(audioAttributes)\n                    val afd = resources.openRawResourceFd(resourceId)\n                    setDataSource(afd.fileDescriptor, afd.startOffset, afd.length)\n                    afd.close()\n                    isLooping = true\n                    prepare()\n                    start()\n                }\n            } else {\n                Log.e(TAG, \"Ringback tone $name.wav not found\")\n            }\n        }\n    }\n\n    @SuppressLint(\"DiscouragedApi\")\n    private fun playBusy() {\n        if (mediaPlayer == null) {\n            val name = \"busy_$toneCountry\"\n            val resourceId = resources.getIdentifier(name, \"raw\", packageName)\n            if (resourceId != 0) {\n                val audioAttributes = AudioAttributes.Builder()\n                    .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)\n                    .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)\n                    .build()\n\n                mediaPlayer = MediaPlayer().apply {\n                    setAudioAttributes(audioAttributes)\n                    val afd = resources.openRawResourceFd(resourceId)\n                    setDataSource(afd.fileDescriptor, afd.startOffset, afd.length)\n                    afd.close()\n                    setOnCompletionListener {\n                        stopMediaPlayer()\n                        ensureCommunicationMode()\n                    }\n                    prepare()\n                    start()\n                }\n            } else {\n                Log.e(TAG, \"Busy tone $name.wav not found\")\n            }\n        }\n    }\n\n    private fun playUnInterrupted(raw: Int, count: Int) {\n        val player = MediaPlayer.create(this, raw)\n        player.setOnCompletionListener {\n            it.stop()\n            it.release()\n            if (count > 1) playUnInterrupted(raw, count - 1)\n        }\n        player.start()\n    }\n\n    private fun stopMediaPlayer() {\n        mediaPlayer?.stop()\n        mediaPlayer?.release()\n        mediaPlayer = null\n    }\n\n    private fun setCallVolume() {\n        if (callVolume != 0)\n            for (streamType in listOf(AudioManager.STREAM_MUSIC, AudioManager.STREAM_VOICE_CALL)) {\n                origVolume[streamType] = am.getStreamVolume(streamType)\n                val maxVolume = am.getStreamMaxVolume(streamType)\n                am.setStreamVolume(streamType, (callVolume * 0.1 * maxVolume).roundToInt(), 0)\n                Log.d(TAG, \"Orig/new/max $streamType volume is \" +\n                        \"${origVolume[streamType]}/${am.getStreamVolume(streamType)}/$maxVolume\")\n            }\n    }\n\n    private fun resetCallVolume() {\n        if (callVolume != 0)\n            for ((streamType, streamVolume) in origVolume) {\n                am.setStreamVolume(streamType, streamVolume, 0)\n                Log.d(TAG, \"Reset $streamType volume to ${am.getStreamVolume(streamType)}\")\n            }\n    }\n\n    private fun ensureCommunicationMode() {\n        if (Call.inCall()) {\n            if (am.mode != MODE_IN_COMMUNICATION) {\n                am.mode = MODE_IN_COMMUNICATION\n                Log.d(TAG, \"Manual Mode Guard (SDK ${VERSION.SDK_INT}): Setting MODE_IN_COMMUNICATION\")\n            }\n        } else {\n            if (am.mode != MODE_NORMAL) {\n                am.mode = MODE_NORMAL\n                Log.d(TAG, \"Manual Mode Guard (SDK ${VERSION.SDK_INT}): Resetting to MODE_NORMAL\")\n            }\n            Utils.clearCommunicationDevice(am)\n            if (speakerPhone) {\n                speakerPhone = false\n                postServiceEvent(ServiceEvent(\"speaker update,false\", arrayListOf(0L, 0L), System.nanoTime()))\n            }\n            if (isMicMuted) {\n                isMicMuted = false\n                postServiceEvent(ServiceEvent(\"mic muted,false\", arrayListOf(0L, 0L), System.nanoTime()))\n            }\n            resetCallVolume()\n            proximitySensing(false)\n        }\n    }\n\n    @SuppressLint(\"WakelockTimeout\", \"Wakelock\")\n    private fun proximitySensing(enable: Boolean) {\n        if (enable) {\n            if (!proximityWakeLock.isHeld) {\n                Log.d(TAG, \"Acquiring proximity wake lock\")\n                proximityWakeLock.acquire()\n            } else {\n                Log.d(TAG, \"Proximity wake lock already acquired\")\n            }\n        } else {\n            if (proximityWakeLock.isHeld) {\n                proximityWakeLock.release()\n                Log.d(TAG, \"Released proximity wake lock\")\n            } else {\n                Log.d(TAG, \"Proximity wake lock is not held\")\n            }\n        }\n    }\n\n    private fun updateNetwork() {\n        if (!isNativeReady) return\n\n        val dnsChanged = updateDnsServers()\n\n        val addresses = linkAddresses()\n        if (linkAddresses != addresses)\n            Log.d(TAG, \"Old/new link addresses $linkAddresses/$addresses\")\n\n        var added = 0\n        for (a in addresses)\n            if (!linkAddresses.containsKey(a.key)) {\n                if (Api.net_add_address_ifname(a.key, a.value) != 0)\n                    Log.e(TAG, \"Failed to add address: $a\")\n                else\n                    added++\n            }\n\n        var removed = 0\n        for (a in linkAddresses)\n            if (!addresses.containsKey(a.key)) {\n                if (Api.net_rm_address(a.key) != 0)\n                    Log.e(TAG, \"Failed to remove address: $a\")\n                else\n                    removed++\n            }\n\n        val active = cm.activeNetwork\n        if (added != removed || activeNetwork != active || dnsChanged)\n            Log.d(TAG, \"Added/Removed = $added/$removed Old/New Active = $activeNetwork/$active DNS Changed = $dnsChanged\")\n\n        if (added > 0 || removed > 0 || active != activeNetwork || dnsChanged) {\n            Api.net_debug()\n            linkAddresses = addresses\n            activeNetwork = active\n            Api.uag_reset_transp(register = true, reinvite = true)\n        }\n\n        val hasWifi = allNetworks.any { network ->\n            val caps = cm.getNetworkCapabilities(network)\n            caps != null && caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)\n        }\n\n        if (hasWifi) {\n            if (!wifiLock.isHeld) {\n                Log.d(TAG, \"Acquiring WiFi Lock\")\n                wifiLock.acquire()\n            }\n        }\n        else {\n            if (wifiLock.isHeld) {\n                Log.d(TAG, \"Releasing WiFi Lock\")\n                wifiLock.release()\n            }\n        }\n    }\n\n    private fun linkAddresses(): MutableMap<String, String> {\n        val addresses = mutableMapOf<String, String>()\n        synchronized(allNetworks) {\n            for (n in allNetworks) {\n                val caps = cm.getNetworkCapabilities(n) ?: continue\n                if (caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOREGROUND) ||\n                    caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN)\n                ) {\n                    val props = cm.getLinkProperties(n) ?: continue\n                    for (la in props.linkAddresses)\n                        if (la.scope == OsConstants.RT_SCOPE_UNIVERSE &&\n                                props.interfaceName != null && la.address.hostAddress != null &&\n                                afMatch(la.address.hostAddress!!))\n                            addresses[la.address.hostAddress!!] = props.interfaceName!!\n                }\n            }\n        }\n        if (hotSpotIsEnabled) {\n            for ((k, v) in hotSpotAddresses)\n                if (afMatch(k))\n                    addresses[k] = v\n        }\n        return addresses\n    }\n\n    private fun afMatch(address: String): Boolean {\n        return when (addressFamily) {\n            \"\" -> true\n            \"ipv4\" -> address.contains(\".\")\n            else -> address.contains(\":\")\n        }\n    }\n\n    private fun updateDnsServers(): Boolean {\n        if (isServiceRunning && !dynDns)\n            return false\n        val servers = mutableListOf<InetAddress>()\n        // Use DNS servers first from active network (if available)\n        val activeNetwork = cm.activeNetwork\n        if (activeNetwork != null) {\n            val linkProps = cm.getLinkProperties(activeNetwork)\n            if (linkProps != null)\n                servers.addAll(linkProps.dnsServers)\n        }\n        // Then add DNS servers from the other networks\n        for (n in allNetworks) {\n            if (n == cm.activeNetwork) continue\n            val linkProps = cm.getLinkProperties(n)\n            if (linkProps != null)\n                for (server in linkProps.dnsServers)\n                    if (!servers.contains(server)) servers.add(server)\n        }\n        // Update if change\n        if (servers != dnsServers) {\n            if (isServiceRunning && Config.updateDnsServers(servers) != 0) {\n                Log.w(TAG, \"Failed to update DNS servers '${servers}'\")\n            } else {\n                // Log.d(TAG, \"Updated DNS servers: '${servers}'\")\n                dnsServers = servers\n                return true\n            }\n        }\n        return false\n    }\n\n    private fun registerAndroidContactsObserver() {\n        if (!androidContactsObserverRegistered)\n            try {\n                contentResolver.registerContentObserver(ContactsContract.Contacts.CONTENT_URI,\n                        true, androidContactsObserver)\n                androidContactsObserverRegistered = true\n            } catch (e: SecurityException) {\n                Log.i(TAG, \"No Contacts permission: ${e.message}\")\n            }\n    }\n\n    private fun unRegisterAndroidContactsObserver() {\n        if (androidContactsObserverRegistered) {\n            contentResolver.unregisterContentObserver(androidContactsObserver)\n            androidContactsObserverRegistered = false\n        }\n    }\n\n    private fun registerPhoneAccount() {\n        val phoneAccountHandle = getPhoneAccountHandle(this)\n        val phoneAccount = android.telecom.PhoneAccount.builder(phoneAccountHandle, getString(R.string.app_name))\n            .setCapabilities(android.telecom.PhoneAccount.CAPABILITY_SELF_MANAGED)\n            .addSupportedUriScheme(android.telecom.PhoneAccount.SCHEME_SIP)\n            .addSupportedUriScheme(android.telecom.PhoneAccount.SCHEME_TEL)\n            .build()\n        tm.registerPhoneAccount(phoneAccount)\n    }\n\n    private fun cleanService() {\n        if (!isServiceClean) {\n            try {\n                if (hotSpotReceiverRegistered) {\n                    applicationContext.unregisterReceiver(hotSpotReceiver)\n                    hotSpotReceiverRegistered = false\n                }\n            } catch (_: IllegalArgumentException) {\n                Log.e(TAG, \"hotSpotReceiver was not registered with applicationContext\")\n            }\n            val callps = ConnectionService.connections.keys.toList()\n            for (callp in callps)\n                ConnectionService.onCallClosed(callp)\n            ConnectionService.pendingOutgoingConnection?.let {\n                it.setDisconnected(DisconnectCause(DisconnectCause.CANCELED))\n                it.destroy()\n                ConnectionService.pendingOutgoingConnection = null\n            }\n            stopRinging()\n            stopMediaPlayer()\n            uas.value = emptyList()\n            uasStatus.value = emptyMap()\n            callHistory.clear()\n            messages = emptyList()\n            if (this::nm.isInitialized)\n                nm.cancelAll()\n            if (this::partialWakeLock.isInitialized && partialWakeLock.isHeld)\n                partialWakeLock.release()\n            if (this::proximityWakeLock.isInitialized && proximityWakeLock.isHeld)\n                proximityWakeLock.release()\n            if (this::wifiLock.isInitialized)\n                wifiLock.release()\n            if (this::networkCallback.isInitialized)\n                cm.unregisterNetworkCallback(networkCallback)\n            if (this::androidContactsObserver.isInitialized)\n                contentResolver.unregisterContentObserver(androidContactsObserver)\n            isServiceClean = true\n        }\n    }\n\n    private external fun baresipStart(\n        path: String,\n        addresses: String,\n        logLevel: Int,\n        software: String\n    )\n\n    external fun baresipStop(force: Boolean)\n\n    @SuppressLint(\"MutableCollectionMutableState\")\n    companion object {\n\n        private const val TAG = \"BaresipService\"\n\n        var instance: BaresipService? = null\n        var isServiceRunning = false\n        var isNativeReady = false\n        var isStartReceived = false\n        var isConfigInitialized = false\n        var libraryLoaded = false\n        var callVolume = 0\n        var speakerPhone = false\n        var audioDelay = if (VERSION.SDK_INT < 31) 1500L else 500L\n        var dynDns = false\n        var filesPath = \"\"\n        var pName = \"\"\n        var logLevel = 2\n        var sipTrace = false\n        var callActionUri = \"\"\n        var isMainVisible = false\n        var isMicMuted = false\n        var isRecOn = false\n        var toneCountry = \"us\"\n        var proximitySensing = true\n\n        val uas = mutableStateOf(emptyList<UserAgent>())\n        val uasStatus = mutableStateOf(emptyMap<String, Int>())\n        var contacts by mutableStateOf(mutableListOf<Contact>())\n        val baresipContacts = mutableStateOf(emptyList<Contact.BaresipContact>())\n        val androidContacts = mutableStateOf(emptyList<Contact.AndroidContact>())\n        val contactNames = mutableStateOf(emptyList<String>())\n\n        val darkTheme = mutableStateOf(false)\n        val dynamicColors = mutableStateOf(false)\n        var messages by mutableStateOf(emptyList<Message>())\n        val messageUpdate = MutableLiveData<Long>()\n        val registrationUpdate = MutableLiveData<Long>()\n\n        val serviceEvent = MutableLiveData<Event<Long>>()\n        val serviceEvents = mutableListOf<ServiceEvent>()\n\n        val calls = ArrayList<Call>()\n        var callHistory = ArrayList<CallHistoryNew>()\n        var blocked = ArrayList<Blocked>()\n\n        var contactsMode = \"baresip\"\n        var addressFamily = \"\"\n        var dnsServers = listOf<InetAddress>()\n        // <aor, password> of those accounts that have auth username without auth password\n        val aorPasswords = mutableMapOf<String, String>()\n        var aecAvailable = false\n        private var aec: AcousticEchoCanceler? = null\n        var agcAvailable = false\n        var rt: Ringtone? = null\n\n        var colorblind = false\n        val circleGreen = mapOf(true to R.drawable.circle_green_blind,\n            false to R.drawable.circle_green)\n        val circleYellow = mapOf(true to R.drawable.circle_yellow_blind,\n            false to R.drawable.circle_yellow)\n        val circleRed = mapOf(true to R.drawable.circle_red_blind,\n            false to R.drawable.circle_red)\n\n        private var agc: AutomaticGainControl? = null\n        private val nsAvailable = NoiseSuppressor.isAvailable()\n        private var ns: NoiseSuppressor? = null\n        private var recorderSessionId = 0\n\n        internal const val KEY_TEXT_REPLY = \"key_text_reply_baresip\"\n        private const val PHONE_ACCOUNT_ID = \"baresip_phone_account\"\n\n        fun getPhoneAccountHandle(ctx: Context): PhoneAccountHandle {\n            val componentName = android.content.ComponentName(ctx, ConnectionService::class.java)\n            return PhoneAccountHandle(componentName, PHONE_ACCOUNT_ID)\n        }\n\n        fun postServiceEvent(event: ServiceEvent) {\n            serviceEvents.add(event)\n            if (serviceEvents.size == 1) {\n                Log.d(TAG, \"Posted service event ${event.event} at ${event.timeStamp}\")\n                serviceEvent.postValue(Event(event.timeStamp))\n            } else {\n                Log.d(TAG, \"Added service event ${event.event}\")\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/Blocked.kt",
    "content": "package com.tutpro.baresip\n\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.json.Json\n\nimport java.io.File\n\n@Serializable\nclass Blocked (\n    val aor: String,\n    val peerUri: String,\n    val request: String,\n    val timeStamp: Long\n) {\n    private val blockedSize = 128\n\n    fun add() {\n        BaresipService.blocked.add(this)\n        val aorBlocked = BaresipService.blocked.filter {\n            it.aor == this.aor && it.request == this.request\n        }\n        if (aorBlocked.size > blockedSize) {\n            val oldestToRemove = aorBlocked.first()\n            BaresipService.blocked.remove(oldestToRemove)\n        }\n        save()\n    }\n\n    companion object {\n\n        fun clear(aor: String) {\n            val updatedBlockedList = BaresipService.blocked.filter { it.aor != aor }\n            BaresipService.blocked = ArrayList(updatedBlockedList)\n            save()\n        }\n\n        fun save() {\n            Log.d(TAG, \"Saving ${BaresipService.blocked.size} blocked calls and messages\")\n            val file = File(BaresipService.filesPath + \"/blocked.json\")\n            try {\n                val jsonString = Json.encodeToString(BaresipService.blocked)\n                file.writeText(jsonString)\n            } catch (e: Exception) {\n                Log.e(TAG, \"Serialization exception: $e\")\n                e.printStackTrace()\n            }\n        }\n\n        fun restore() {\n            val file = File(BaresipService.filesPath + \"/blocked.json\")\n            if (file.exists()) {\n                try {\n                    val jsonString = file.readText()\n                    val blockedList = Json.decodeFromString<List<Blocked>>(jsonString)\n                    BaresipService.blocked = ArrayList(blockedList)\n                    Log.d(TAG, \"Restored ${BaresipService.blocked.size} blocked calls and messages\")\n                } catch (e: Exception) {\n                    Log.e(TAG, \"Deserialization exception: $e\")\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/BlockedScreen.kt",
    "content": "package com.tutpro.baresip\n\nimport android.content.Context\nimport androidx.activity.compose.BackHandler\nimport androidx.compose.foundation.ExperimentalFoundationApi\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.asPaddingValues\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.imePadding\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.statusBars\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.lazy.rememberLazyListState\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.filled.Menu\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.TopAppBarDefaults\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.DisposableEffect\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.MutableState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.LifecycleEventObserver\nimport androidx.lifecycle.compose.LocalLifecycleOwner\nimport androidx.navigation.NavController\nimport androidx.navigation.NavGraphBuilder\nimport androidx.navigation.NavType\nimport androidx.navigation.compose.composable\nimport androidx.navigation.navArgument\nimport com.tutpro.baresip.CustomElements.AlertDialog\nimport com.tutpro.baresip.CustomElements.verticalScrollbar\nimport java.util.GregorianCalendar\n\nfun NavGraphBuilder.blockedScreenRoute(navController: NavController) {\n    composable(\n        route = \"blocked/{request}/{aor}\",\n        arguments = listOf(\n            navArgument(\"aor\") { type = NavType.StringType },\n            navArgument(\"request\") { type = NavType.StringType }\n        )\n    ) { backStackEntry ->\n        val aor = backStackEntry.arguments?.getString(\"aor\")!!\n        val request = backStackEntry.arguments?.getString(\"request\")!!\n        BlockedScreen(navController, request, aor)\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun BlockedScreen(navController: NavController, request: String, aor: String) {\n\n    val account = Account.ofAor(aor)!!\n\n    val blocked: MutableState<List<Blocked>> = remember { mutableStateOf(emptyList()) }\n    var isBlockedLoaded by remember { mutableStateOf(false) }\n\n    var refreshTrigger by remember { mutableIntStateOf(0) }\n    val lifecycleOwner = LocalLifecycleOwner.current\n\n    LaunchedEffect(aor, refreshTrigger) {\n        blocked.value = loadBlocked(request, aor)\n        isBlockedLoaded = true\n    }\n\n    DisposableEffect(lifecycleOwner) {\n        val observer = LifecycleEventObserver { _, event ->\n            if (event == Lifecycle.Event.ON_RESUME)\n                refreshTrigger++\n        }\n        lifecycleOwner.lifecycle.addObserver(observer)\n        onDispose {\n            lifecycleOwner.lifecycle.removeObserver(observer)\n        }\n    }\n\n    BackHandler(enabled = true) {\n        navController.navigateUp()\n    }\n\n    Scaffold(\n        modifier = Modifier\n            .fillMaxSize()\n            .imePadding(),\n        containerColor = MaterialTheme.colorScheme.background,\n        topBar = {\n            Column(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .background(MaterialTheme.colorScheme.background)\n                    .padding(top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding())\n            ) {\n                TopAppBar(navController, account, request, blocked)\n            }\n        },\n        content = { contentPadding ->\n            if (isBlockedLoaded)\n                BlockedContent(\n                    LocalContext.current,\n                    navController,\n                    contentPadding,\n                    account, blocked\n                )\n        },\n    )\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun TopAppBar(\n    navController: NavController,\n    account: Account,\n    request: String,\n    blocked: MutableState<List<Blocked>>\n) {\n    var expanded by remember { mutableStateOf(false) }\n    val delete = stringResource(R.string.delete)\n    val showDialog = remember { mutableStateOf(false) }\n    val lastAction = remember { mutableStateOf({}) }\n\n    AlertDialog(\n        showDialog = showDialog,\n        title = stringResource(R.string.confirmation),\n        message = String.format(stringResource(R.string.blocked_delete_alert), account.text()),\n        firstButtonText = stringResource(R.string.cancel),\n        lastButtonText = stringResource(R.string.delete),\n        onLastClicked = lastAction.value,\n    )\n\n    TopAppBar(\n        title = {\n            Text(\n                text = if (request == \"invite\")\n                    stringResource(R.string.blocked_calls)\n                else\n                    stringResource(R.string.blocked_messages),\n                fontWeight = FontWeight.Bold\n            )\n        },\n        colors = TopAppBarDefaults.topAppBarColors(\n            containerColor = MaterialTheme.colorScheme.primary,\n            navigationIconContentColor = MaterialTheme.colorScheme.onPrimary,\n            titleContentColor = MaterialTheme.colorScheme.onPrimary,\n            actionIconContentColor = MaterialTheme.colorScheme.onPrimary\n        ),\n        windowInsets = WindowInsets(0, 0, 0, 0),\n        navigationIcon = {\n            IconButton(\n                onClick = { navController.navigateUp() }\n            ) {\n                Icon(\n                    imageVector = Icons.AutoMirrored.Filled.ArrowBack,\n                    contentDescription = \"Back\",\n                )\n            }\n        },\n        actions = {\n            IconButton(\n                onClick = { expanded = !expanded }\n            ) {\n                Icon(\n                    imageVector = Icons.Filled.Menu,\n                    contentDescription = \"Menu\",\n                )\n            }\n            CustomElements.DropdownMenu(\n                expanded,\n                { expanded = false },\n                listOf(delete),\n                onItemClick = { selectedItem ->\n                    expanded = false\n                    when (selectedItem) {\n                        delete -> {\n                            lastAction.value = {\n                                Blocked.clear(account.aor)\n                                blocked.value = emptyList()\n                            }\n                            showDialog.value = true\n                        }\n                    }\n                }\n            )\n        }\n    )\n}\n\n@Composable\nprivate fun BlockedContent(\n    ctx: Context,\n    navController: NavController,\n    contentPadding: PaddingValues,\n    account: Account,\n    blocked: MutableState<List<Blocked>>\n) {\n    Column(\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(contentPadding)\n            .padding(bottom = 16.dp),\n        verticalArrangement = Arrangement.spacedBy(12.dp)\n    ) {\n        Account(account)\n        Blocked(ctx, navController, blocked)\n    }\n}\n\n@Composable\nprivate fun Account(account: Account) {\n    Text(\n        text = stringResource(R.string.account) + \" \" + account.text(),\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(top = 8.dp),\n        fontSize = 18.sp,\n        fontWeight = FontWeight.SemiBold,\n        textAlign = TextAlign.Center\n    )\n}\n\n@OptIn(ExperimentalFoundationApi::class)\n@Composable\nprivate fun Blocked(ctx: Context, navController: NavController, blocked: MutableState<List<Blocked>>) {\n    val showDialog = remember { mutableStateOf(false) }\n    val message = remember { mutableStateOf(\"\") }\n    val lastButtonText = remember { mutableStateOf(\"\") }\n    val lastAction = remember { mutableStateOf({}) }\n\n    AlertDialog(\n        showDialog = showDialog,\n        title = stringResource(R.string.confirmation),\n        message = message.value,\n        firstButtonText = stringResource(R.string.cancel),\n        lastButtonText = lastButtonText.value,\n        onLastClicked = lastAction.value,\n    )\n\n    val lazyListState = rememberLazyListState()\n    LazyColumn(\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(start = 16.dp, end = 4.dp)\n            .verticalScrollbar(state = lazyListState)\n            .background(MaterialTheme.colorScheme.background),\n        state = lazyListState,\n        verticalArrangement = Arrangement.spacedBy(12.dp),\n    ) {\n        items(items = blocked.value, key = { blocked -> blocked.timeStamp }) { blocked ->\n            val peerUri = blocked.peerUri\n            Row(\n                verticalAlignment = Alignment.CenterVertically,\n                modifier = Modifier.fillMaxWidth()\n                    .clickable(onClick = {\n                        message.value = String.format(ctx.getString(R.string.blocked_contact_question),\n                            peerUri)\n                        lastButtonText.value = ctx.getString(R.string.add_contact)\n                        lastAction.value = {\n                            navController.navigate(\"baresip_contact/$peerUri/new\")\n                        }\n                        showDialog.value = true\n                    })\n            ) {\n                Text(text = \"\\u2022\",\n                    modifier = Modifier.padding(start = 8.dp, end = 4.dp),\n                    fontSize = 18.sp)\n\n                Text(text = peerUri.replace(\"sip:\", \"\"),\n                    fontSize = 18.sp,\n                    maxLines = 1,\n                    overflow = TextOverflow.Ellipsis\n                )\n\n                Spacer(modifier = Modifier.weight(1f))\n\n                val calendar = GregorianCalendar()\n                calendar.timeInMillis = blocked.timeStamp\n                Text(\n                    text = Utils.relativeTime(ctx, calendar),\n                    fontSize = 12.sp,\n                    minLines = 2, maxLines = 2,\n                    lineHeight = 16.sp,\n                    textAlign = TextAlign.End,\n                    color = MaterialTheme.colorScheme.onBackground,\n                    modifier = Modifier.padding(end = 16.dp)\n                )\n            }\n        }\n    }\n}\n\nprivate fun loadBlocked(request: String, aor: String): MutableList<Blocked> {\n    val res = mutableListOf<Blocked>()\n    for (i in BaresipService.blocked.indices.reversed()) {\n        val b = BaresipService.blocked[i]\n        if (b.aor == aor && b.request == request) {\n            res.add(Blocked(\"\", b.peerUri, \"\", b.timeStamp))\n        }\n    }\n    Log.d(TAG, \"Loaded ${res.size} blocked $request requests\")\n    return res\n}\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/BootCompletedReceiver.kt",
    "content": "package com.tutpro.baresip\n\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport androidx.core.content.ContextCompat\n\nimport java.nio.charset.StandardCharsets\n\nclass BootCompletedReceiver : BroadcastReceiver() {\n\n    override fun onReceive(context: Context, intent: Intent) {\n\n        Log.i(TAG, \"BootCompletedReceiver received intent ${intent.action}\")\n\n        val configPath = context.filesDir.absolutePath + \"/config\"\n        val config = Utils.getFileContents(configPath) ?: return\n        val asCv = Utils.getNameValue(String(config, StandardCharsets.ISO_8859_1), \"auto_start\")\n        if ((asCv.isNotEmpty()) && (asCv[0] == \"yes\")) {\n            Log.i(TAG, \"Start baresip service upon boot completed\")\n            val baresipService = Intent(context, BaresipService::class.java).apply {\n                action = \"Start\"\n                putExtra(\"onStartup\", true)\n            }\n            ContextCompat.startForegroundService(context, baresipService)\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/Call.kt",
    "content": "package com.tutpro.baresip\n\nimport android.content.Context\nimport android.media.AudioManager\nimport androidx.compose.runtime.MutableState\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.core.net.toUri\nimport java.util.*\n\nclass Call(val callp: Long, val ua: UserAgent, val peerUri: String, val dir: String, initialStatus: String) {\n\n    var status: MutableState<String> = mutableStateOf(initialStatus)\n\n    var onhold = false\n    var held = false\n    val terminated = mutableStateOf(false)\n    var conferenceCall = false\n    var onHoldCall: Call? = null\n    var newCall: Call? = null\n    var rejected = false  // Incoming rejected by user or outgoing fails but not due to 408 or 480\n    var security = R.color.colorTrafficRed\n    var zid = \"\"\n    var startTime: GregorianCalendar? = null  // Set when call is established\n    var referTo = \"\"\n    var dumpfiles = arrayOf(\"\", \"\")\n\n    // UI state properties\n    val callUri: MutableState<String> = mutableStateOf(\"\")\n    val callUriEnabled: MutableState<Boolean> = mutableStateOf(true)\n    val callUriLabel: MutableState<String> = mutableStateOf(\"\")\n    val securityIconTint: MutableState<Int> = mutableIntStateOf(-1)\n    val showCallTimer: MutableState<Boolean> = mutableStateOf(false)\n    var callDuration: Int = 0\n    val showSuggestions: MutableState<Boolean> = mutableStateOf(false)\n    val showCallButton: MutableState<Boolean> = mutableStateOf(true)\n    val showCancelButton: MutableState<Boolean> = mutableStateOf(false)\n    val showAnswerRejectButtons: MutableState<Boolean> = mutableStateOf(false)\n    val showHangupButton: MutableState<Boolean> = mutableStateOf(false)\n    val showOnHoldNotice: MutableState<Boolean> = mutableStateOf(false)\n    val callOnHold: MutableState<Boolean> = mutableStateOf(false)\n    val transferButtonEnabled: MutableState<Boolean> = mutableStateOf(false)\n    val callTransfer: MutableState<Boolean> = mutableStateOf(false)\n    val dtmfText: MutableState<String> = mutableStateOf(\"\")\n    val dtmfEnabled: MutableState<Boolean> = mutableStateOf(false)\n    val focusDtmf: MutableState<Boolean> = mutableStateOf(false)\n\n    fun add() {\n        BaresipService.calls.add(this)\n    }\n\n    fun remove() {\n        BaresipService.calls.remove(this)\n    }\n\n    fun connect(uri: String): Boolean {\n        return Api.call_connect(callp, uri) == 0\n    }\n\n    fun hold(): Boolean {\n        if (onhold) return true\n        if (Api.call_hold(callp, true)) {\n            onhold = true\n            callOnHold.value = true\n            showOnHoldNotice.value = false\n            ConnectionService.connections[callp]?.setOnHold()\n            return true\n        }\n        return false\n    }\n\n    fun resume(): Boolean {\n        if (!onhold && !held) return true\n        // 1. Hold other calls first\n        for (c in BaresipService.calls) {\n            if (c.callp != this.callp && !c.onhold && !c.held) {\n                Log.d(\"Baresip\", \"Auto-holding active call ${c.callp}\")\n                c.hold()\n            }\n        }\n        val connection = ConnectionService.connections[callp]\n        // 2. SIP Signaling\n        if (Api.call_hold(callp, false)) {\n            onhold = false\n            callOnHold.value = false\n            showOnHoldNotice.value = false\n            // 3. Telecom Sync\n            connection?.setAddress(\"sip:$peerUri\".toUri(), android.telecom.TelecomManager.PRESENTATION_ALLOWED)\n            connection?.setActive()\n            return true\n        }\n        return false\n    }\n\n    fun transfer(uri: String): Boolean {\n        if (!onhold) hold()\n        Log.d(TAG, \"Transferring call $callp to $uri\")\n        return Api.call_transfer(callp, uri) == 0\n    }\n\n    fun executeTransfer(): Boolean {\n        return if (onHoldCall != null) {\n            if (Api.call_hold(callp, true))\n                Api.call_replace_transfer(onHoldCall!!.callp, callp)\n            else\n                false\n        } else\n            false\n    }\n\n    fun sendDigit(digit: Char): Int {\n        return Api.call_send_digit(callp, digit)\n    }\n\n    fun notifySipfrag(code: Int, reason: String) {\n        Api.call_notify_sipfrag(callp, code, reason)\n    }\n\n    fun duration(): Int {\n        return Api.call_duration(callp)\n    }\n\n    fun stats(stream: String): String {\n        return Api.call_stats(callp, stream)\n    }\n\n    fun state(): Int {\n        return Api.call_state(callp)\n    }\n\n    fun audioCodecs(): String {\n        return Api.call_audio_codecs(callp)\n    }\n\n    fun replaces(): Boolean {\n        return Api.call_replaces(callp)\n    }\n\n    fun diverterUri(): String {\n        return Api.call_diverter_uri(callp)\n    }\n\n    init {\n        if (ua.account.mediaEnc != \"\") security = R.color.colorTrafficRed\n    }\n\n    fun destroy() {\n        Api.call_destroy(callp)\n    }\n\n    companion object {\n\n        fun calls(): ArrayList<Call> {\n            return BaresipService.calls\n        }\n\n        fun ofCallp(callp: Long): Call? {\n            for (c in BaresipService.calls)\n                if (c.callp == callp) return c\n            return null\n        }\n\n        fun call(status: String): Call? {\n            for (c in BaresipService.calls)\n                if (c.status.value == status) return c\n            return null\n        }\n\n        fun inCall(): Boolean {\n            return BaresipService.calls.isNotEmpty()\n        }\n\n        fun isAnyCallActive(ctx: Context): Boolean {\n            // Check if there exist SIP calls that are not onhold or held\n            if (BaresipService.calls.any { !it.onhold && !it.held }) return true\n            // MODE_IN_CALL indicates a PSTN call is active\n            val am = ctx.getSystemService(Context.AUDIO_SERVICE) as AudioManager\n            return am.mode == AudioManager.MODE_IN_CALL\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/CallDetailsScreen.kt",
    "content": "package com.tutpro.baresip\n\nimport android.content.Context\nimport android.media.AudioAttributes\nimport android.media.MediaPlayer\nimport android.text.format.DateUtils\nimport android.widget.Toast\nimport androidx.activity.compose.rememberLauncherForActivityResult\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.compose.foundation.ExperimentalFoundationApi\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.combinedClickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.asPaddingValues\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.imePadding\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.statusBars\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.lazy.rememberLazyListState\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.automirrored.filled.CallMade\nimport androidx.compose.material.icons.automirrored.filled.CallReceived\nimport androidx.compose.material3.AlertDialog\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.LinearProgressIndicator\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.TopAppBarDefaults\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.MutableState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableFloatStateOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.setValue\nimport androidx.compose.runtime.snapshots.SnapshotStateList\nimport androidx.compose.runtime.toMutableStateList\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.colorResource\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.navigation.NavController\nimport androidx.navigation.NavGraphBuilder\nimport androidx.navigation.compose.composable\nimport com.tutpro.baresip.CallRow.Details\nimport com.tutpro.baresip.CustomElements.AlertDialog\nimport com.tutpro.baresip.CustomElements.verticalScrollbar\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport java.io.File\nimport java.io.FileInputStream\nimport java.text.DateFormat\nimport java.util.GregorianCalendar\n\nfun NavGraphBuilder.callDetailsScreenRoute(navController: NavController, viewModel: ViewModel) {\n    composable(\"call_details\") { _ ->\n        val callRow = remember { viewModel.consumeSelectedCallRow() }\n        CallDetailsScreen(navController, callRow!!)\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun CallDetailsScreen(navController: NavController, callRow: CallRow) {\n    val detailsState = remember { callRow.details.toMutableStateList() }\n    Scaffold(\n        modifier = Modifier.fillMaxSize().imePadding(),\n        containerColor = MaterialTheme.colorScheme.background,\n        topBar = {\n            Column(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .background(MaterialTheme.colorScheme.background)\n                    .padding(top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding())\n            ) {\n                TopAppBar(\n                    title = {\n                        Text(\n                            text = stringResource(R.string.call_details),\n                            fontWeight = FontWeight.Bold\n                        )\n                    },\n                    colors = TopAppBarDefaults.topAppBarColors(\n                        containerColor = MaterialTheme.colorScheme.primary,\n                        navigationIconContentColor = MaterialTheme.colorScheme.onPrimary,\n                        titleContentColor = MaterialTheme.colorScheme.onPrimary,\n                    ),\n                    windowInsets = WindowInsets(0, 0, 0, 0),\n                    navigationIcon = {\n                        IconButton(onClick = navController::navigateUp) {\n                            Icon(\n                                imageVector = Icons.AutoMirrored.Filled.ArrowBack,\n                                contentDescription = \"Back\",\n                            )\n                        }\n                    },\n                )\n            }\n        },\n        content = { contentPadding ->\n            CallDetailsContent(\n                LocalContext.current,\n                contentPadding,\n                callRow,\n                detailsState,\n                onDelete = { detail ->\n                    detailsState.remove(detail)\n                    if (detailsState.isEmpty())\n                        navController.navigateUp()\n                }\n            )\n        },\n    )\n}\n\n@Composable\nprivate fun CallDetailsContent(\n    ctx: Context,\n    contentPadding: PaddingValues,\n    callRow: CallRow,\n    details: SnapshotStateList<Details>,\n    onDelete: (Details) -> Unit\n) {\n    Column(\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(contentPadding)\n            .padding(top = 16.dp, start = 16.dp, end = 4.dp, bottom = 16.dp),\n        verticalArrangement = Arrangement.spacedBy(12.dp)\n    ) {\n        Peer(ctx, callRow)\n        Details(ctx, details, onDelete)\n    }\n}\n\n@Composable\nprivate fun Peer(ctx: Context, callRow: CallRow) {\n    val account = Account.ofAor(callRow.aor)!!\n    val headerText = stringResource(R.string.peer) + \" \" +\n            Utils.friendlyUri(ctx, callRow.peerUri, account)\n    Text(\n        text = headerText,\n        modifier = Modifier.fillMaxWidth(),\n        fontSize = 18.sp,\n        fontWeight = FontWeight.SemiBold,\n        textAlign = TextAlign.Center\n    )\n}\n\n@Composable\nprivate fun Details(ctx: Context, details: SnapshotStateList<Details>, onDelete: (Details) -> Unit) {\n    Row(verticalAlignment = Alignment.CenterVertically) {\n        Text(text = stringResource(R.string.direction),\n            fontSize = 16.sp,\n            fontWeight = FontWeight.SemiBold,\n            modifier = Modifier.width(96.dp)\n        )\n        Spacer(modifier = Modifier.width(6.dp))\n        Text(text = stringResource(R.string.time),\n            fontSize = 16.sp,\n            fontWeight = FontWeight.SemiBold\n        )\n        Spacer(modifier = Modifier.weight(1f))\n        Text(text = stringResource(R.string.calls_duration),\n            modifier = Modifier.padding(end = 12.dp),\n            fontSize = 16.sp,\n            fontWeight = FontWeight.SemiBold\n        )\n    }\n    val lazyListState = rememberLazyListState()\n    LazyColumn(\n        modifier = Modifier\n            .fillMaxWidth()\n            .verticalScrollbar(state = lazyListState)\n            .background(MaterialTheme.colorScheme.background),\n        state = lazyListState,\n        verticalArrangement = Arrangement.spacedBy(12.dp),\n    ) {\n        items(details) { detail ->\n            Row(verticalAlignment = Alignment.CenterVertically) {\n                Box(\n                    modifier = Modifier\n                        .clip(CircleShape)\n                        .clickable {\n                            Toast.makeText(\n                                ctx,\n                                when (detail.direction) {\n                                    CALL_DOWN_GREEN, CALL_UP_GREEN -> ctx.getString(R.string.call_answered)\n                                    CALL_DOWN_BLUE -> ctx.getString(R.string.call_answered_elsewhere)\n                                    CALL_MISSED_IN, CALL_MISSED_OUT -> ctx.getString(R.string.call_missed)\n                                    CALL_DOWN_RED, CALL_UP_RED -> ctx.getString(R.string.call_rejected)\n                                    else -> \"\"\n                                },\n                                Toast.LENGTH_SHORT\n                            ).show()\n                        },\n                    contentAlignment = Alignment.Center\n                ) {\n                    Icon(\n                        imageVector = if (callUp(detail.direction))\n                            Icons.AutoMirrored.Filled.CallMade\n                        else\n                            Icons.AutoMirrored.Filled.CallReceived,\n                        tint = colorResource(id = callTint(detail.direction)),\n                        contentDescription = \"Direction\"\n                    )\n                }\n                Spacer(modifier = Modifier.width(78.dp))\n                val durationText = startTime(detail, onDelete)\n                Spacer(modifier = Modifier.weight(1f))\n                Duration(ctx, detail, durationText)\n            }\n        }\n    }\n}\n\n@Composable\n\nprivate fun startTime(detail: Details, onDelete: (Details) -> Unit): String {\n    val startTime = detail.startTime\n    val stopTime = detail.stopTime\n    val startTimeText: String\n    val durationText: String\n    val stopText = if (DateUtils.isToday(stopTime.timeInMillis)) {\n        val fmt = DateFormat.getTimeInstance(DateFormat.MEDIUM)\n        stringResource(R.string.today) + \" \" + fmt.format(stopTime.time)\n    } else {\n        val fmt = DateFormat.getDateTimeInstance()\n        fmt.format(stopTime.time)\n    }\n    if (startTime == GregorianCalendar(0, 0, 0)) {\n        startTimeText = stopText\n        durationText = \"?\"\n    } else {\n        if (startTime == null  || detail.direction == CALL_DOWN_BLUE) {\n            startTimeText = stopText\n            durationText = \"\"\n        } else {\n            val startText = if (DateUtils.isToday(startTime.timeInMillis)) {\n                val fmt = DateFormat.getTimeInstance(DateFormat.MEDIUM)\n                stringResource(R.string.today) + \" \" + fmt.format(startTime.time)\n            } else {\n                val fmt = DateFormat.getDateTimeInstance()\n                fmt.format(startTime.time)\n            }\n            startTimeText = startText\n            val duration = (stopTime.time.time - startTime.time.time) / 1000\n            durationText = DateUtils.formatElapsedTime(duration)\n        }\n    }\n    val showDialog = remember { mutableStateOf(false) }\n\n    AlertDialog(\n        showDialog = showDialog,\n        title = stringResource(R.string.confirmation),\n        message = stringResource(R.string.delete_call_alert),\n        firstButtonText = stringResource(R.string.cancel),\n        lastButtonText = stringResource(R.string.delete),\n        onLastClicked = {\n            CallHistoryNew.remove(detail.startTime, detail.stopTime)\n            onDelete(detail)\n        },\n    )\n\n    Text(\n        text = startTimeText,\n        modifier = Modifier.combinedClickable(\n            onClick = {},\n            onLongClick = {\n                showDialog.value = true\n            }\n        )\n    )\n\n    return durationText\n}\n\n@OptIn(ExperimentalFoundationApi::class)\n@Composable\nprivate fun Duration(ctx: Context, detail: Details, durationText: String) {\n\n    val showPlaybackDialog = remember { mutableStateOf(false) }\n    val showDownloadDialog = remember { mutableStateOf(false) }\n\n    val mediaPlayer = remember { MediaPlayer() }\n    val scope = rememberCoroutineScope()\n\n    // 1. Setup the File Saver Launcher\n    val saveLauncher = rememberLauncherForActivityResult(\n        contract = ActivityResultContracts.CreateDocument(\"audio/x-wav\")\n    ) { uri ->\n        uri?.let { destinationUri ->\n            scope.launch(Dispatchers.IO) {\n                try {\n                    val sourcePath = detail.recording.firstOrNull { it.isNotEmpty() }\n                    if (sourcePath != null) {\n                        val sourceFile = File(sourcePath)\n                        if (sourceFile.exists()) {\n                            ctx.contentResolver.openOutputStream(destinationUri)?.use { output ->\n                                FileInputStream(sourceFile).use { input ->\n                                    input.copyTo(output)\n                                }\n                            }\n                            withContext(Dispatchers.Main) {\n                                Toast.makeText(\n                                    ctx, ctx.getString(R.string.recording_saved),\n                                    Toast.LENGTH_SHORT\n                                ).show()\n                            }\n                        } else {\n                            withContext(Dispatchers.Main) {\n                                Toast.makeText(ctx, \"Source file not found\", Toast.LENGTH_SHORT)\n                                    .show()\n                            }\n                        }\n                    }\n                } catch (e: Exception) {\n                    Log.e(TAG, \"Failed to save file: $e\")\n                    withContext(Dispatchers.Main) {\n                        Toast.makeText(ctx, \"Save failed\", Toast.LENGTH_SHORT).show()\n                    }\n                }\n            }\n        }\n    }\n\n    PlaybackDialog(\n        showDialog = showPlaybackDialog,\n        mediaPlayer = mediaPlayer,\n        onStop = {\n            if (mediaPlayer.isPlaying)\n                mediaPlayer.stop()\n            mediaPlayer.reset()\n            showPlaybackDialog.value = false\n        }\n    )\n\n    // 2. Download Confirmation Dialog\n    if (showDownloadDialog.value)\n        AlertDialog(\n            showDialog = showDownloadDialog,\n            title = stringResource(R.string.save_recording),\n            message = stringResource(R.string.save_recording_question),\n            firstButtonText = stringResource(R.string.cancel),\n            lastButtonText = stringResource(R.string.save),\n            onLastClicked = {\n                showDownloadDialog.value = false\n                detail.recording.firstOrNull { it.isNotEmpty() }?.let {\n                    val suggestedName = File(it).name\n                    saveLauncher.launch(suggestedName)\n                }\n            },\n        )\n\n    val hasRecording = detail.recording.isNotEmpty() && detail.recording[0].isNotEmpty()\n    if (hasRecording) {\n        Text(\n            text = durationText,\n            color = MaterialTheme.colorScheme.error,\n            modifier = Modifier\n                .padding(end = 12.dp)\n                .combinedClickable(\n                    onLongClick = {\n                        showDownloadDialog.value = true\n                    },\n                    onClick = {\n                        if (!mediaPlayer.isPlaying) {\n                            mediaPlayer.reset()\n                            scope.launch(Dispatchers.IO) {\n                                var finalFile: File? = null\n                                // Use a local copy of the recording state for this operation\n                                val currentRecording = detail.recording\n\n                                val currentIsRaw = currentRecording.size > 1 &&\n                                        currentRecording[0].isNotEmpty() &&\n                                        currentRecording[1].isNotEmpty()\n                                val currentIsMerged = currentRecording.isNotEmpty() &&\n                                        currentRecording[0].isNotEmpty() &&\n                                        (currentRecording.size == 1 || currentRecording[1].isEmpty())\n\n                                if (currentIsRaw) {\n                                    val fileIn = File(currentRecording[0])\n                                    val fileOut = File(currentRecording[1])\n                                    // SAFETY CHECK: If the raw file is gone, the background service\n                                    // likely finished merging just now.\n                                    if (!fileIn.exists()) {\n                                        val expectedMergedName =\n                                            \"merged_${fileIn.nameWithoutExtension}_${fileOut.nameWithoutExtension}.wav\"\n                                        val fallbackMerged = File(\n                                            BaresipService.filesPath + \"/recordings\",\n                                            expectedMergedName\n                                        )\n                                        if (fallbackMerged.exists()) {\n                                            Log.d(\n                                                TAG,\n                                                \"Raw file missing, found merged fallback: ${fallbackMerged.name}\"\n                                            )\n                                            finalFile = fallbackMerged\n                                            // Update state to match reality by creating a new List\n                                            detail.recording =\n                                                listOf(fallbackMerged.absolutePath, \"\")\n                                        } else {\n                                            Log.e(\n                                                TAG,\n                                                \"Raw file missing and fallback not found: ${currentRecording[0]}\"\n                                            )\n                                        }\n                                    } else {\n                                        // Normal Raw processing\n                                        val mergedFileName =\n                                            \"merged_${fileIn.nameWithoutExtension}_${fileOut.nameWithoutExtension}.wav\"\n                                        val mergedFile = File(\n                                            BaresipService.filesPath + \"/recordings\",\n                                            mergedFileName\n                                        )\n                                        if (mergedFile.exists()) {\n                                            Log.d(\n                                                TAG,\n                                                \"Using already merged file: ${mergedFile.name}\"\n                                            )\n                                            finalFile = mergedFile\n                                        } else {\n                                            if (Utils.mergeWavFiles(fileIn, fileOut, mergedFile))\n                                                finalFile = mergedFile\n                                        }\n\n                                        // If merge successful, update state and delete originals\n                                        if (finalFile != null && finalFile.exists()) {\n                                            detail.recording =\n                                                listOf(finalFile.absolutePath, \"\")\n                                            try {\n                                                if (fileIn.exists()) fileIn.delete()\n                                                if (fileOut.exists()) fileOut.delete()\n                                                CallHistoryNew.save()\n                                            } catch (e: Exception) {\n                                                Log.w(\n                                                    TAG,\n                                                    \"MergeWav: Failed to delete original files: ${e.message}\"\n                                                )\n                                            }\n                                        }\n                                    }\n                                } else if (currentIsMerged) {\n                                    val f = File(currentRecording[0])\n                                    if (f.exists()) {\n                                        Log.d(TAG, \"Using already merged file: ${currentRecording[0]}\")\n                                        finalFile = f\n                                    } else {\n                                        Log.e(\n                                            TAG,\n                                            \"Merged file record exists but file is missing: ${currentRecording[0]}\"\n                                        )\n                                    }\n                                }\n\n                                withContext(Dispatchers.Main) {\n                                    if (finalFile != null && finalFile.exists()) {\n                                        try {\n                                            mediaPlayer.apply {\n                                                setAudioAttributes(\n                                                    AudioAttributes.Builder()\n                                                        .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)\n                                                        .setUsage(AudioAttributes.USAGE_MEDIA)\n                                                        .build()\n                                                )\n                                                val fis = FileInputStream(finalFile)\n                                                setDataSource(fis.fd)\n                                                fis.close()\n                                                setOnPreparedListener {\n                                                    it.start()\n                                                    showPlaybackDialog.value = true\n                                                }\n                                                setOnCompletionListener {\n                                                    showPlaybackDialog.value = false\n                                                    it.reset()\n                                                }\n                                                prepareAsync()\n                                            }\n                                        } catch (e: Exception) {\n                                            Log.e(TAG, \"Playback failed: $e\")\n                                            Toast.makeText(\n                                                ctx, \"Playback error\",\n                                                Toast.LENGTH_SHORT\n                                            ).show()\n                                        }\n                                    } else {\n                                        Toast.makeText(\n                                            ctx, \"Failed to process audio file\",\n                                            Toast.LENGTH_SHORT\n                                        ).show()\n                                    }\n                                }\n                            }\n                        } else {\n                            mediaPlayer.stop()\n                            mediaPlayer.reset()\n                            showPlaybackDialog.value = false\n                        }\n                    }\n                )\n        )\n    } else {\n        Text(text = durationText, modifier = Modifier.padding(end = 12.dp))\n    }\n}\n\n@Composable\nprivate fun PlaybackDialog(\n    showDialog: MutableState<Boolean>,\n    mediaPlayer: MediaPlayer,\n    onStop: () -> Unit\n) {\n    if (showDialog.value) {\n        // State to hold progress (0.0f to 1.0f)\n        var currentProgress by remember { mutableFloatStateOf(0f) }\n        var currentPositionText by remember { mutableStateOf(\"00:00\") }\n        var totalDurationText by remember { mutableStateOf(\"00:00\") }\n\n        // Update progress every 100ms\n        LaunchedEffect(showDialog.value) {\n            if (mediaPlayer.isPlaying) {\n                val duration = mediaPlayer.duration\n                totalDurationText = DateUtils.formatElapsedTime(duration / 1000L)\n\n                while (mediaPlayer.isPlaying) {\n                    val current = mediaPlayer.currentPosition\n                    currentProgress = if (duration > 0) current.toFloat() / duration.toFloat() else 0f\n                    currentPositionText = DateUtils.formatElapsedTime(current / 1000L)\n                    delay(100) // Poll every 100ms\n                }\n            }\n        }\n\n        AlertDialog(\n            onDismissRequest = {\n                // If user clicks outside, stop playback\n                onStop()\n            },\n            title = {\n                Text(text = stringResource(R.string.playing_recording))\n            },\n            text = {\n                Column(\n                    modifier = Modifier.fillMaxWidth(),\n                    horizontalAlignment = Alignment.CenterHorizontally\n                ) {\n                    LinearProgressIndicator(\n                        progress = { currentProgress },\n                        modifier = Modifier.fillMaxWidth().height(8.dp),\n                    )\n                    Spacer(modifier = Modifier.height(8.dp))\n                    Row(\n                        modifier = Modifier.fillMaxWidth(),\n                        horizontalArrangement = Arrangement.SpaceBetween\n                    ) {\n                        Text(text = currentPositionText, style = MaterialTheme.typography.bodySmall)\n                        Text(text = totalDurationText, style = MaterialTheme.typography.bodySmall)\n                    }\n                }\n            },\n            confirmButton = {\n                TextButton(\n                    onClick = { onStop() }\n                ) {\n                    Text(stringResource(R.string.stop))\n                }\n            }\n        )\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/CallHistory.kt",
    "content": "package com.tutpro.baresip\n\nimport java.io.File\nimport java.io.FileInputStream\nimport java.io.FileOutputStream\nimport java.io.IOException\nimport java.io.ObjectInputStream\nimport java.io.ObjectOutputStream\nimport java.io.Serializable\nimport java.util.GregorianCalendar\n\nclass CallHistoryNew(val aor: String, val peerUri: String, val direction: String) : Serializable {\n\n    // Set to time when call is established (if ever) or stopTime if call was completed elsewhere\n    var startTime: GregorianCalendar? = null\n    var stopTime = GregorianCalendar()        // Set to time when call is closed\n    var rejected = false\n    var recording = arrayOf(\"\", \"\")           // Encoder and decoder recording files, merged file is in [0]\n\n    fun add() {\n        BaresipService.callHistory.add(this)\n        val aorSpecificHistory = BaresipService.callHistory.filter { it.aor == this.aor }\n        if (aorSpecificHistory.size > CALL_HISTORY_SIZE) {\n            val oldestToRemove = aorSpecificHistory.first()\n            deleteRecordingFiles(oldestToRemove.recording)\n            BaresipService.callHistory.remove(oldestToRemove)\n        }\n        save()\n    }\n\n    companion object {\n\n        private const val serialVersionUID: Long = 3\n        private const val CALL_HISTORY_SIZE = 256\n\n        fun aorLatestPeerUri(aor: String): String? {\n            for (h in BaresipService.callHistory.reversed())\n                if (h.aor == aor) return h.peerUri\n            return null\n        }\n\n        fun clear(aor: String) {\n            for (i in BaresipService.callHistory.indices.reversed()) {\n                val h = BaresipService.callHistory[i]\n                if (h.aor == aor) {\n                    deleteRecordingFiles(h.recording)\n                    BaresipService.callHistory.removeAt(i)\n                }\n            }\n            save()\n        }\n\n        fun remove(startTime: GregorianCalendar?, stopTime: GregorianCalendar) {\n            val iterator = BaresipService.callHistory.iterator()\n            while (iterator.hasNext()) {\n                val h = iterator.next()\n                if (h.startTime == startTime && h.stopTime == stopTime) {\n                    deleteRecordingFiles(h.recording)\n                    iterator.remove()\n                }\n            }\n            save()\n        }\n\n        fun save() {\n            Log.d(TAG, \"Saving history of ${BaresipService.callHistory.size} calls\")\n            val file = File(BaresipService.filesPath + \"/call_history\")\n            try {\n                val fos = FileOutputStream(file)\n                val oos = ObjectOutputStream(fos)\n                oos.writeObject(BaresipService.callHistory)\n                oos.close()\n                fos.close()\n            } catch (e: IOException) {\n                Log.e(TAG, \"OutputStream exception: $e\")\n                e.printStackTrace()\n            }\n        }\n\n        fun restore() {\n            val file = File(BaresipService.filesPath + \"/call_history\")\n            if (file.exists()) {\n                try {\n                    val fis = FileInputStream(file)\n                    val ois = ObjectInputStream(fis)\n                    @Suppress(\"UNCHECKED_CAST\")\n                    val restoredHistory = ois.readObject() as? List<CallHistoryNew>\n                    if (restoredHistory != null)\n                        BaresipService.callHistory = ArrayList(restoredHistory)\n                    ois.close()\n                    fis.close()\n                    Log.d(TAG, \"Restored history of ${BaresipService.callHistory.size} calls\")\n                } catch (e: Exception) {\n                    Log.e(TAG, \"InputStream exception: - $e\")\n                }\n            }\n        }\n\n        fun deleteRecordingFiles(recording: Array<String>) {\n            Utils.deleteFile(File(recording[0]))\n            Utils.deleteFile(File(recording[1]))\n        }\n\n        fun clearRecordings() {\n            for (h in BaresipService.callHistory)\n                h.recording = arrayOf(\"\", \"\")\n        }\n\n        @Suppress(\"UNUSED\")\n        fun print() {\n            for (h in BaresipService.callHistory)\n                Log.d(TAG, \"[${h.aor}, ${h.peerUri}, ${h.direction}, ${h.startTime},\" +\n                        \"${h.stopTime}, ${h.rejected}, ${h.recording}\")\n        }\n    }\n\n}\n\nclass CallHistory(val aor: String, val peerUri: String, val direction: String) : Serializable {\n\n    var startTime: GregorianCalendar? = null\n    var stopTime = GregorianCalendar()\n    var recording = arrayOf(\"\", \"\")\n\n    companion object {\n\n        private const val serialVersionUID: Long = 2\n\n        fun get(): ArrayList<CallHistory> {\n            val file = File(BaresipService.filesPath, \"history\")\n            var result = ArrayList<CallHistory>()\n            if (file.exists()) {\n                try {\n                    val fis = FileInputStream(file)\n                    val ois = ObjectInputStream(fis)\n                    @Suppress(\"UNCHECKED_CAST\")\n                    val restoredHistory = ois.readObject() as? List<CallHistory>\n                    if (restoredHistory != null)\n                        result = ArrayList(restoredHistory)\n                    ois.close()\n                    fis.close()\n                    Log.d(TAG, \"Got history of ${result.size} calls\")\n                    file.delete()\n                } catch (e: Exception) {\n                    Log.e(TAG, \"InputStream exception: - $e\")\n                }\n            }\n            return result\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/CallRow.kt",
    "content": "package com.tutpro.baresip\n\nimport java.util.GregorianCalendar\n\ndata class CallRow(\n    val aor: String,\n    val peerUri: String,\n    var direction: Int,\n    var startTime: GregorianCalendar?,\n    var stopTime: GregorianCalendar,\n    var recording: List<String>\n) {\n    data class Details(\n        var direction: Int,\n        var startTime: GregorianCalendar?,\n        var stopTime: GregorianCalendar,\n        var recording: List<String>\n    )\n    val details = mutableListOf(Details(direction, startTime, stopTime, recording))\n}\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/CallsScreen.kt",
    "content": "package com.tutpro.baresip\n\nimport android.content.Context\nimport android.content.Intent\nimport androidx.activity.compose.BackHandler\nimport androidx.compose.foundation.ExperimentalFoundationApi\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.combinedClickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.asPaddingValues\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.imePadding\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.statusBars\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.lazy.rememberLazyListState\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.automirrored.filled.CallMade\nimport androidx.compose.material.icons.automirrored.filled.CallReceived\nimport androidx.compose.material.icons.filled.AccountCircle\nimport androidx.compose.material.icons.filled.Menu\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.TopAppBarDefaults\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.DisposableEffect\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.MutableState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.draw.scale\nimport androidx.compose.ui.layout.ContentScale\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.colorResource\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.LifecycleEventObserver\nimport androidx.lifecycle.compose.LocalLifecycleOwner\nimport androidx.navigation.NavController\nimport androidx.navigation.NavGraphBuilder\nimport androidx.navigation.NavType\nimport androidx.navigation.compose.composable\nimport androidx.navigation.navArgument\nimport coil.compose.AsyncImage\nimport com.tutpro.baresip.CustomElements.AlertDialog\nimport com.tutpro.baresip.CustomElements.verticalScrollbar\n\nfun NavGraphBuilder.callsScreenRoute(navController: NavController, viewModel: ViewModel) {\n    composable(\n        route = \"calls/{aor}\",\n        arguments = listOf(navArgument(\"aor\") { type = NavType.StringType })\n    ) { backStackEntry ->\n        val aor = backStackEntry.arguments?.getString(\"aor\")!!\n        CallsScreen(navController, viewModel, aor)\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun CallsScreen(navController: NavController, viewModel: ViewModel, aor: String) {\n\n    val account = Account.ofAor(aor)!!\n\n    val callHistory: MutableState<List<CallRow>> = remember { mutableStateOf(emptyList()) }\n    var isHistoryLoaded by remember { mutableStateOf(false) }\n\n    var refreshTrigger by remember { mutableIntStateOf(0) }\n    val lifecycleOwner = LocalLifecycleOwner.current\n\n    LaunchedEffect(aor, refreshTrigger) {\n        callHistory.value = loadCallHistory(aor)\n        isHistoryLoaded = true\n    }\n\n    DisposableEffect(lifecycleOwner) {\n        val observer = LifecycleEventObserver { _, event ->\n            if (event == Lifecycle.Event.ON_RESUME)\n                refreshTrigger++\n        }\n        lifecycleOwner.lifecycle.addObserver(observer)\n        onDispose {\n            lifecycleOwner.lifecycle.removeObserver(observer)\n        }\n    }\n\n    BackHandler(enabled = true) {\n        account.missedCalls = false\n        navController.navigateUp()\n    }\n\n    Scaffold(\n        modifier = Modifier.fillMaxSize().imePadding(),\n        containerColor = MaterialTheme.colorScheme.background,\n        topBar = {\n            Column(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .background(MaterialTheme.colorScheme.background)\n                    .padding(top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding())\n            ) {\n                TopAppBar(navController, account, callHistory)\n            }\n        },\n        content = { contentPadding ->\n            if (isHistoryLoaded)\n                CallsContent(\n                    LocalContext.current,\n                    navController,\n                    viewModel,\n                    contentPadding,\n                    account,\n                    callHistory\n                )\n        },\n    )\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun TopAppBar(navController: NavController, account: Account, callHistory: MutableState<List<CallRow>>) {\n\n    var expanded by remember { mutableStateOf(false) }\n\n    val delete = stringResource(R.string.delete)\n    val disable = stringResource(R.string.disable_history)\n    val enable = stringResource(R.string.enable_history)\n    val blocked = stringResource(R.string.blocked)\n\n    val showDialog = remember { mutableStateOf(false) }\n    val lastAction = remember { mutableStateOf({}) }\n\n    AlertDialog(\n        showDialog = showDialog,\n        title = stringResource(R.string.confirmation),\n        message = String.format(stringResource(R.string.delete_history_alert), account.text()),\n        firstButtonText = stringResource(R.string.cancel),\n        lastButtonText = stringResource(R.string.delete),\n        onLastClicked = lastAction.value,\n    )\n\n    TopAppBar(\n        title = {\n            Text(\n                text = stringResource(R.string.call_history),\n                fontWeight = FontWeight.Bold\n            )\n        },\n        colors = TopAppBarDefaults.topAppBarColors(\n            containerColor = MaterialTheme.colorScheme.primary,\n            navigationIconContentColor = MaterialTheme.colorScheme.onPrimary,\n            titleContentColor = MaterialTheme.colorScheme.onPrimary,\n            actionIconContentColor = MaterialTheme.colorScheme.onPrimary\n        ),\n        windowInsets = WindowInsets(0, 0, 0, 0),\n        navigationIcon = {\n            IconButton(\n                onClick = {\n                    account.missedCalls = false\n                    navController.navigateUp()\n                }\n            ) {\n                Icon(\n                    imageVector = Icons.AutoMirrored.Filled.ArrowBack,\n                    contentDescription = \"Back\",\n                )\n            }\n        },\n        actions = {\n            IconButton(\n                onClick = { expanded = !expanded }\n            ) {\n                Icon(\n                    imageVector = Icons.Filled.Menu,\n                    contentDescription = \"Menu\",\n                )\n            }\n            CustomElements.DropdownMenu(\n                expanded,\n                { expanded = false },\n                if (account.callHistory) listOf(disable, delete, blocked) else listOf(enable),\n                onItemClick = { selectedItem ->\n                    expanded = false\n                    when (selectedItem) {\n                        delete -> {\n                            lastAction.value = {\n                                CallHistoryNew.clear(account.aor)\n                                callHistory.value = emptyList()\n                                Blocked.clear(account.aor)\n                            }\n                            showDialog.value = true\n                        }\n                        disable, enable -> {\n                            account.callHistory = !account.callHistory\n                            Account.saveAccounts()\n                        }\n                        blocked -> {\n                            navController.navigate(\"blocked/invite/${account.aor}\")\n                        }\n                    }\n                }\n            )\n        }\n    )\n}\n\n@Composable\nprivate fun CallsContent(\n    ctx: Context,\n    navController: NavController,\n    viewModel: ViewModel,\n    contentPadding: PaddingValues,\n    account: Account,\n    callHistory: MutableState<List<CallRow>>\n) {\n    Column(\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(contentPadding)\n            .padding(bottom = 16.dp),\n        verticalArrangement = Arrangement.spacedBy(12.dp)\n    ) {\n        Account(account)\n        Calls(ctx, navController, viewModel, account, callHistory)\n    }\n}\n\n@Composable\nprivate fun Account(account: Account) {\n    Text(\n        text = stringResource(R.string.account) + \" \" + account.text(),\n        modifier = Modifier.fillMaxWidth().padding(top = 8.dp),\n        fontSize = 18.sp,\n        fontWeight = FontWeight.SemiBold,\n        textAlign = TextAlign.Center\n    )\n}\n\n@OptIn(ExperimentalFoundationApi::class)\n@Composable\nprivate fun Calls(\n    ctx: Context,\n    navController: NavController,\n    viewModel: ViewModel,\n    account: Account,\n    callHistory: MutableState<List<CallRow>>\n) {\n\n    val showDialog = remember { mutableStateOf(false) }\n    val message = remember { mutableStateOf(\"\") }\n    val secondButtonText = remember { mutableStateOf(\"\") }\n    val secondAction = remember { mutableStateOf({}) }\n    val lastButtonText = remember { mutableStateOf(\"\") }\n    val lastAction = remember { mutableStateOf({}) }\n\n    AlertDialog(\n        showDialog = showDialog,\n        title = stringResource(R.string.confirmation),\n        message = message.value,\n        firstButtonText = stringResource(R.string.cancel),\n        secondButtonText = secondButtonText.value,\n        onSecondClicked = secondAction.value,\n        lastButtonText = lastButtonText.value,\n        onLastClicked = lastAction.value,\n    )\n\n    val lazyListState = rememberLazyListState()\n    LazyColumn(\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(start = 16.dp, end = 4.dp)\n            .verticalScrollbar(state = lazyListState)\n            .background(MaterialTheme.colorScheme.background),\n        state = lazyListState,\n        verticalArrangement = Arrangement.spacedBy(12.dp),\n    ) {\n        items(items = callHistory.value, key = { callRow -> callRow.stopTime }) { callRow ->\n            val peerUri = callRow.peerUri\n            var recordings = false\n            Row(\n                verticalAlignment = Alignment.CenterVertically,\n                modifier = Modifier.fillMaxWidth()\n            ) {\n                Box(modifier = Modifier.weight(1f)) {\n                    Row(\n                        verticalAlignment = Alignment.CenterVertically,\n                        modifier = Modifier.combinedClickable(\n                            onClick = {\n                                val aor = account.aor\n                                val ua = UserAgent.ofAor(aor)\n                                val intent = Intent(ctx, MainActivity::class.java)\n                                if (ua != null) {\n                                    intent.putExtra(\"uap\", ua.uap)\n                                    intent.putExtra(\"peer\", peerUri)\n                                }\n                                else\n                                    Log.w(TAG, \"onClickListener did not find UA for $aor\")\n                                val peerName = Utils.friendlyUri(ctx, peerUri, account)\n                                message.value = String.format(ctx.getString(R.string.contact_action_question), peerName)\n                                secondButtonText.value = ctx.getString(R.string.call)\n                                secondAction.value = {\n                                    if (ua != null) {\n                                        handleIntent(ctx, viewModel, intent, \"call\")\n                                        navController.navigate(\"main\") {\n                                            popUpTo(\"main\")\n                                            launchSingleTop = true\n                                        }\n                                    }\n                                }\n                                lastButtonText.value = ctx.getString(R.string.send_message)\n                                lastAction.value = {\n                                    if (ua != null) {\n                                        handleIntent(ctx, viewModel, intent, \"message\")\n                                        navController.navigateUp()\n                                    }\n                                }\n                                showDialog.value = true\n                            },\n                            onLongClick = {\n                                val peerName = Utils.friendlyUri(ctx, peerUri, account)\n                                val callText: String = if (callRow.details.size > 1)\n                                    ctx.getString(R.string.calls_calls)\n                                else\n                                    ctx.getString(R.string.calls_call)\n                                val contactExists = Contact.nameExists(peerName, BaresipService.contacts, false)\n                                if (contactExists) {\n                                    message.value = String.format(\n                                        ctx.getString(R.string.calls_delete_question),\n                                        peerName, callText\n                                    )\n                                    secondButtonText.value = \"\"\n                                    lastButtonText.value = ctx.getString(R.string.delete)\n                                    lastAction.value = {\n                                        removeFromHistory(callHistory, callRow)\n                                    }\n                                }\n                                else {\n                                    message.value = String.format(\n                                        ctx.getString(R.string.calls_add_delete_question),\n                                        peerName, callText\n                                    )\n                                    secondButtonText.value = ctx.getString(R.string.add_contact)\n                                    secondAction.value = {\n                                        navController.navigate(\"baresip_contact/$peerUri/new\")\n                                    }\n                                    lastButtonText.value = ctx.getString(R.string.delete)\n                                    lastAction.value = {\n                                        removeFromHistory(callHistory, callRow)\n                                    }\n                                }\n                                showDialog.value = true\n                            }\n                        )\n                    ) {\n                        when (val contact = Contact.findContact(peerUri)) {\n                            is Contact.BaresipContact -> {\n                                val avatarImage = contact.avatarImage\n                                if (avatarImage != null)\n                                    CustomElements.ImageAvatar(avatarImage)\n                                else\n                                    CustomElements.TextAvatar(contact.name, contact.color)\n                            }\n                            is Contact.AndroidContact -> {\n                                val thumbNailUri = contact.thumbnailUri\n                                if (thumbNailUri != null)\n                                    AsyncImage(\n                                        model = thumbNailUri,\n                                        contentDescription = \"Avatar\",\n                                        contentScale = ContentScale.Crop,\n                                        modifier = Modifier\n                                            .size(36.dp)\n                                            .clip(CircleShape),\n                                    )\n                                else\n                                    CustomElements.TextAvatar(contact.name, contact.color)\n                            }\n                            null -> {\n                                Icon(\n                                    imageVector = Icons.Filled.AccountCircle,\n                                    contentDescription = \"Avatar\",\n                                    modifier = Modifier.size(36.dp).scale(1.2f),\n                                    tint = MaterialTheme.colorScheme.secondary\n                                )\n                            }\n                        }\n                        Spacer(modifier = Modifier.width(4.dp))\n                        var count = 1\n                        for (d in callRow.details) {\n                            if (d.recording.isNotEmpty() && d.recording[0] != \"\")\n                                recordings = true\n                            if (count > 3)\n                                continue\n                            Icon(\n                                imageVector = if (callUp(d.direction))\n                                    Icons.AutoMirrored.Filled.CallMade\n                                else\n                                    Icons.AutoMirrored.Filled.CallReceived,\n                                modifier = Modifier.size(20.dp),\n                                tint = colorResource(id = callTint(d.direction)),\n                                contentDescription = \"Direction\"\n                            )\n                            count++\n                        }\n                        if (count > 3)\n                            Text(\"...\", color = MaterialTheme.colorScheme.onBackground)\n                        Text(text = Utils.friendlyUri(ctx, peerUri, account),\n                            modifier = Modifier.padding(start = 8.dp),\n                            fontSize = 18.sp,\n                            maxLines = 1,\n                            overflow = TextOverflow.Ellipsis\n                        )\n                    }\n                }\n                Text(\n                    text = Utils.relativeTime(ctx, callRow.stopTime),\n                    fontSize = 12.sp,\n                    minLines = 2, maxLines = 2,\n                    lineHeight = 16.sp,\n                    textAlign = TextAlign.End,\n                    color = if (recordings)\n                        MaterialTheme.colorScheme.error\n                    else\n                        MaterialTheme.colorScheme.onBackground,\n                    modifier = Modifier\n                        .padding(end = 16.dp)\n                        .clickable(onClick = {\n                            viewModel.selectCallRow(callRow)\n                            navController.navigate(\"call_details\")\n                        })\n                )\n            }\n        }\n    }\n}\n\nprivate fun loadCallHistory(aor: String): MutableList<CallRow> {\n    val res = mutableListOf<CallRow>()\n    for (i in BaresipService.callHistory.indices.reversed()) {\n        val h = BaresipService.callHistory[i]\n        if (h.aor == aor) {\n            val direction: Int = if (h.direction == \"in\") {\n                if (h.startTime != null) {\n                    if (h.startTime != h.stopTime) CALL_DOWN_GREEN else CALL_DOWN_BLUE\n                }\n                else\n                    if (h.rejected) CALL_DOWN_RED else CALL_MISSED_IN\n            }\n            else {\n                if (h.startTime != null)\n                    CALL_UP_GREEN\n                else\n                    if (h.rejected) CALL_UP_RED else CALL_MISSED_OUT\n            }\n            if (res.isNotEmpty() && res.last().peerUri == h.peerUri)\n                res.last().details.add(CallRow.Details(\n                    direction, h.startTime,\n                    h.stopTime, h.recording.toList()\n                ))\n            else\n                res.add(CallRow(h.aor, h.peerUri, direction, h.startTime, h.stopTime, h.recording.toList()))\n        }\n    }\n    return res\n}\n\nprivate fun removeFromHistory(callHistory: MutableState<List<CallRow>>, callRow: CallRow) {\n    for (details in callRow.details) {\n        CallHistoryNew.deleteRecordingFiles(details.recording.toTypedArray())\n        BaresipService.callHistory.removeAll {\n            it.startTime == details.startTime && it.stopTime == details.stopTime\n        }\n    }\n    CallHistoryNew.deleteRecordingFiles(callRow.recording.toTypedArray())\n    val updatedList = callHistory.value.filterNot { it == callRow }\n    callHistory.value = updatedList\n    CallHistoryNew.save()\n}\n\nfun callUp(direction: Int): Boolean {\n    return when (direction) {\n        CALL_UP_GREEN, CALL_UP_RED, CALL_MISSED_OUT -> true\n        else -> false\n    }\n}\n\nfun callTint(direction: Int): Int {\n    return when (direction) {\n        CALL_UP_GREEN, CALL_DOWN_GREEN -> R.color.colorTrafficGreen\n        CALL_UP_RED, CALL_DOWN_RED -> R.color.colorTrafficRed\n        CALL_DOWN_BLUE -> R.color.colorPrimary\n        else -> R.color.colorTrafficYellow\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/ChatScreen.kt",
    "content": "package com.tutpro.baresip\n\nimport android.content.Context\nimport android.content.Intent\nimport android.text.format.DateUtils.isToday\nimport android.widget.Toast\nimport androidx.activity.compose.BackHandler\nimport androidx.compose.foundation.ExperimentalFoundationApi\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.asPaddingValues\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.imePadding\nimport androidx.compose.foundation.layout.navigationBarsPadding\nimport androidx.compose.foundation.layout.offset\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.statusBars\nimport androidx.compose.foundation.layout.wrapContentHeight\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.lazy.rememberLazyListState\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.foundation.text.KeyboardOptions\nimport androidx.compose.foundation.text.selection.SelectionContainer\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.automirrored.filled.Send\nimport androidx.compose.material.icons.outlined.Call\nimport androidx.compose.material.icons.outlined.Clear\nimport androidx.compose.material3.Button\nimport androidx.compose.material3.ButtonDefaults\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.OutlinedTextField\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.SmallFloatingActionButton\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.TopAppBarDefaults\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.DisposableEffect\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.focus.FocusRequester\nimport androidx.compose.ui.focus.focusRequester\nimport androidx.compose.ui.layout.onGloballyPositioned\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalSoftwareKeyboardController\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.TextStyle\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.input.KeyboardCapitalization\nimport androidx.compose.ui.text.input.KeyboardType\nimport androidx.compose.ui.text.input.TextFieldValue\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.LifecycleEventObserver\nimport androidx.lifecycle.Observer\nimport androidx.lifecycle.compose.LocalLifecycleOwner\nimport androidx.navigation.NavController\nimport androidx.navigation.NavGraphBuilder\nimport androidx.navigation.NavType\nimport androidx.navigation.compose.composable\nimport androidx.navigation.navArgument\nimport com.tutpro.baresip.CustomElements.AlertDialog\nimport com.tutpro.baresip.CustomElements.verticalScrollbar\nimport kotlinx.coroutines.launch\nimport java.lang.String.format\nimport java.text.DateFormat\nimport java.util.GregorianCalendar\n\nfun NavGraphBuilder.chatScreenRoute(navController: NavController, viewModel: ViewModel) {\n    composable(\n        route = \"chat/{aor}/{peer}\",\n        arguments = listOf(\n            navArgument(\"aor\") { type = NavType.StringType },\n            navArgument(\"peer\") { type = NavType.StringType }\n        )\n    ) { backStackEntry ->\n        val aor = backStackEntry.arguments?.getString(\"aor\")!!\n        val peerUri = backStackEntry.arguments?.getString(\"peer\")!!\n        ChatScreen(\n            ctx = LocalContext.current,\n            navController = navController,\n            viewModel = viewModel,\n            account = Account.ofAor(aor)!!,\n            peerUri = peerUri\n        )\n    }\n}\n\n@Composable\nprivate fun ChatScreen(\n    ctx: Context,\n    navController: NavController,\n    viewModel: ViewModel,\n    account: Account,\n    peerUri: String\n) {\n    val lifecycleOwner = LocalLifecycleOwner.current\n\n    val aor = account.aor\n\n    var chatMessages by remember(aor, peerUri) { mutableStateOf<List<Message>>(emptyList()) }\n\n    DisposableEffect(lifecycleOwner) {\n        val observer = LifecycleEventObserver { _, event ->\n            if (event == Lifecycle.Event.ON_RESUME) {\n                Log.d(TAG, \"Resumed to ChatScreen for AOR: $aor peer $peerUri\")\n                chatMessages = loadPeerMessages(aor, peerUri)\n            }\n        }\n        lifecycleOwner.lifecycle.addObserver(observer)\n        onDispose {\n            lifecycleOwner.lifecycle.removeObserver(observer)\n        }\n    }\n\n    var areMessagesLoaded by remember(aor, peerUri) {\n        mutableStateOf(false)\n    }\n\n    val reloadMessages = {\n        Log.d(TAG, \"Reloading messages for $aor peer $peerUri\")\n        chatMessages = loadPeerMessages(aor, peerUri)\n        if (!areMessagesLoaded)\n            areMessagesLoaded = true\n    }\n\n    val addMessage = { newMessage: Message ->\n        chatMessages = chatMessages + newMessage\n    }\n\n    DisposableEffect(key1 = lifecycleOwner, key2 = account.aor, key3 = peerUri) {\n        val messagesObserver = Observer<Long> { timestamp ->\n            Log.d(TAG, \"Message update received via LiveData for $peerUri, timestamp: $timestamp\")\n            reloadMessages()\n        }\n        reloadMessages() // Initial load\n        Log.d(TAG, \"Observing message updates for $peerUri\")\n        BaresipService.messageUpdate.observe(lifecycleOwner, messagesObserver)\n        onDispose {\n            Log.d(TAG, \"Removing message observer for $peerUri\")\n            BaresipService.messageUpdate.removeObserver(messagesObserver)\n        }\n    }\n\n    BackHandler(enabled = true) {\n        backAction(navController, account, peerUri)\n    }\n\n    Scaffold(\n        modifier = Modifier\n            .fillMaxSize()\n            .imePadding(),\n        containerColor = MaterialTheme.colorScheme.background,\n        topBar = {\n            Column(modifier = Modifier\n                .fillMaxWidth()\n                .background(MaterialTheme.colorScheme.background)\n                .padding(top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding())\n            ) {\n                TopAppBar(ctx, navController, viewModel, account, peerUri)\n            }\n        },\n        bottomBar = {\n            NewMessage(\n                ctx = ctx,\n                viewModel,\n                account = account,\n                peerUri = peerUri,\n                addMessage = addMessage\n            )\n        },\n        content = { contentPadding ->\n            if (areMessagesLoaded)\n                ChatContent(ctx,\n                    navController,\n                    contentPadding,\n                    account, peerUri,\n                    chatMessages,\n                    reloadMessages\n                )\n        }\n    )\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun TopAppBar(\n    ctx: Context,\n    navController: NavController,\n    viewModel: ViewModel,\n    account: Account,\n    peerUri: String\n) {\n    val aor = account.aor\n\n    TopAppBar(\n        title = {\n            Text(\n                text = format(ctx.getString(R.string.chat_with), Utils.friendlyUri(ctx, peerUri, account)),\n                fontSize = 22.sp,\n                fontWeight = FontWeight.Bold\n            )\n        },\n        colors = TopAppBarDefaults.topAppBarColors(\n            containerColor = MaterialTheme.colorScheme.primary,\n            navigationIconContentColor = MaterialTheme.colorScheme.onPrimary,\n            titleContentColor = MaterialTheme.colorScheme.onPrimary,\n            actionIconContentColor = MaterialTheme.colorScheme.onPrimary\n        ),\n        navigationIcon = {\n            IconButton(onClick = { backAction(navController, account, peerUri) }) {\n                Icon(\n                    imageVector = Icons.AutoMirrored.Filled.ArrowBack,\n                    contentDescription = \"Back\",\n                )\n            }\n        },\n        windowInsets = WindowInsets(0, 0, 0, 0),\n        actions = {\n            IconButton(\n                onClick = {\n                    val ua = UserAgent.ofAor(account.aor)\n                    if (ua != null) {\n                        val intent = Intent(ctx, MainActivity::class.java)\n                        intent.putExtra(\"uap\", ua.uap)\n                        intent.putExtra(\"peer\", peerUri)\n                        handleIntent(ctx, viewModel, intent, \"call\")\n                        navController.navigate(\"main\") {\n                            popUpTo(\"main\")\n                            launchSingleTop = true\n                        }\n                    }\n                    else\n                        Log.w(TAG, \"Call button onClick listener did not find UA for $aor\")\n                }\n            ) {\n                Icon(\n                    imageVector = Icons.Outlined.Call,\n                    contentDescription = \"Call\",\n                )\n            }\n        }\n    )\n}\n\n@Composable\nprivate fun ChatContent(\n    ctx: Context,\n    navController: NavController,\n    contentPadding: PaddingValues,\n    account: Account,\n    peerUri: String,\n    messages: List<Message>,\n    onMessageDeleted: () -> Unit\n) {\n    Column(\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(contentPadding),\n        verticalArrangement = Arrangement.Bottom\n    ) {\n        Account(ctx, account)\n        Spacer(modifier = Modifier.weight(1f))\n        Messages(ctx, navController, account, peerUri, messages, onMessageDeleted)\n    }\n}\n\n@Composable\nprivate fun Account(ctx: Context, account: Account) {\n    Text(\n        text = ctx.getString(R.string.account) + \" \" + account.text(),\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(top = 8.dp, bottom = 8.dp),\n        fontSize = 18.sp,\n        fontWeight = FontWeight.SemiBold,\n        textAlign = TextAlign.Center\n    )\n}\n\n@Composable\nprivate fun Messages(\n    ctx: Context,\n    navController: NavController,\n    account: Account,\n    peerUri: String,\n    messages: List<Message>,\n    onMessageDeleted: () -> Unit\n) {\n    val peerName = Utils.friendlyUri(ctx, peerUri, account)\n\n    val showDialog = remember { mutableStateOf(false) }\n    val dialogMessage = remember { mutableStateOf(\"\") }\n    val secondButtonText = remember { mutableStateOf(\"\") }\n    val secondAction = remember { mutableStateOf({}) }\n    val lastButtonText = remember { mutableStateOf(\"\") }\n    val lastAction = remember { mutableStateOf({}) }\n\n    AlertDialog(\n        showDialog = showDialog,\n        title = stringResource(R.string.confirmation),\n        message = dialogMessage.value,\n        firstButtonText = stringResource(R.string.cancel),\n        secondButtonText = secondButtonText.value,\n        onSecondClicked = secondAction.value,\n        lastButtonText = lastButtonText.value,\n        onLastClicked = lastAction.value,\n    )\n\n    val lazyListState = rememberLazyListState()\n    val coroutineScope = rememberCoroutineScope()\n\n    LaunchedEffect(messages) {\n        // Scroll to the bottom when new messages are added\n        if (messages.isNotEmpty()) {\n            coroutineScope.launch {\n                lazyListState.scrollToItem(0)\n            }\n        }\n    }\n\n    LazyColumn(\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(start = 16.dp, end = 2.dp)\n            .verticalScrollbar(state = lazyListState)\n            .background(MaterialTheme.colorScheme.background),\n        reverseLayout = true,\n        state = lazyListState,\n        verticalArrangement = Arrangement.spacedBy(16.dp),\n    ) {\n        items(items = messages, key = { message -> message.timeStamp }) { message ->\n            val down = message.direction == MESSAGE_DOWN\n            val sender: String = if (down)\n                peerName\n            else if (BaresipService.uas.value.size == 1)\n                stringResource(R.string.you)\n            else\n                account.text()\n            var info: String\n            val cal = GregorianCalendar()\n            cal.timeInMillis = message.timeStamp\n            val fmt: DateFormat = if (isToday(message.timeStamp))\n                DateFormat.getTimeInstance(DateFormat.SHORT)\n            else\n                DateFormat.getDateInstance(DateFormat.SHORT)\n            info = fmt.format(cal.time)\n            if (info.length < 6) info = \"${stringResource(R.string.today)} $info\"\n            if (message.direction == MESSAGE_UP_FAIL) {\n                info = if (message.responseCode != 0)\n                    \"$info - ${stringResource(R.string.message_failed)}: \" + \"${message.responseCode} ${message.responseReason}\"\n                else\n                    \"$info - ${stringResource(R.string.sending_failed)}\"\n            }\n            Row(\n                verticalAlignment = Alignment.CenterVertically,\n                modifier = Modifier.padding(end = 12.dp)\n            ) {\n                Button(\n                    onClick = {\n                        if (Contact.findContact(peerUri) == null) {\n                            dialogMessage.value = String.format(\n                                ctx.getString(R.string.long_message_question),\n                                peerUri\n                            )\n                            secondButtonText.value = ctx.getString(R.string.add_contact)\n                            secondAction.value = {\n                                navController.navigate(\"baresip_contact/$peerUri/new\")\n                            }\n                            lastButtonText.value = ctx.getString(R.string.delete)\n                            lastAction.value = {\n                                message.delete()\n                                onMessageDeleted()\n                            }\n                        } else {\n                            dialogMessage.value = ctx.getString(R.string.short_message_question)\n                            secondButtonText.value = \"\"\n                            lastButtonText.value = ctx.getString(R.string.delete)\n                            lastAction.value = {\n                                message.delete()\n                                onMessageDeleted()\n                            }\n                        }\n                        showDialog.value = true\n                    },\n                    shape = if (message.direction == MESSAGE_DOWN)\n                        RoundedCornerShape(50.dp, 20.dp, 20.dp, 10.dp)\n                    else\n                        RoundedCornerShape(20.dp, 10.dp, 50.dp, 20.dp),\n                    colors = ButtonDefaults.buttonColors(\n                        containerColor =\n                            if (message.direction == MESSAGE_DOWN)\n                                MaterialTheme.colorScheme.secondaryContainer\n                            else\n                                MaterialTheme.colorScheme.primaryContainer\n                    ),\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .wrapContentHeight()\n                        .padding(\n                            start = if (message.direction == MESSAGE_DOWN) 0.dp else 24.dp,\n                            end = if (message.direction == MESSAGE_DOWN) 24.dp else 0.dp\n                        )\n                ) {\n                    Column {\n                        val textColor = if (message.direction == MESSAGE_DOWN)\n                            MaterialTheme.colorScheme.onSecondaryContainer\n                        else\n                            MaterialTheme.colorScheme.onPrimaryContainer\n                        Row {\n                            Text(text = sender, fontSize = 12.sp, color = textColor)\n                            Spacer(modifier = Modifier.weight(1f))\n                            Text(text = info, fontSize = 12.sp, color = textColor)\n                        }\n                        Row {\n                            SelectionContainer {\n                                Text(\n                                    text = message.message,\n                                    color = textColor,\n                                    fontWeight = if (message.direction == MESSAGE_DOWN && message.new)\n                                        FontWeight.Bold else FontWeight.Normal\n                                )\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\n@OptIn(ExperimentalFoundationApi::class)\n@Composable\nprivate fun NewMessage(\n    ctx: Context,\n    viewModel: ViewModel,\n    account: Account,\n    peerUri: String,\n    addMessage: (message: Message) -> Unit\n) {\n\n    val aor = account.aor\n    val ua = UserAgent.ofAor(aor)!!\n\n    val newMessage = rememberSaveable(stateSaver = TextFieldValue.Saver) {\n        mutableStateOf(TextFieldValue(viewModel.getAorPeerMessage(aor, peerUri)))\n    }\n\n    var textFieldLoaded by remember { mutableStateOf(false) }\n    val focusRequester = remember { FocusRequester() }\n\n    val showDialog = remember { mutableStateOf(false) }\n    val dialogMessage = remember { mutableStateOf(\"\") }\n\n    AlertDialog(\n        showDialog = showDialog,\n        title = stringResource(R.string.notice),\n        message = dialogMessage.value,\n        lastButtonText = stringResource(R.string.ok),\n    )\n\n    Row(modifier = Modifier\n        .fillMaxWidth()\n        .navigationBarsPadding()\n        .padding(start = 16.dp, end = 8.dp, top = 10.dp, bottom = 16.dp),\n        verticalAlignment = Alignment.CenterVertically\n    ) {\n        val keyboardController = LocalSoftwareKeyboardController.current\n        OutlinedTextField(\n            value = newMessage.value,\n            placeholder = { Text(stringResource(R.string.new_message)) },\n            onValueChange = {\n                newMessage.value = it\n                viewModel.updateAorPeerMessage(aor, peerUri, it.text)\n            },\n            modifier = Modifier\n                .weight(1f)\n                .padding(end = 8.dp)\n                .verticalScroll(rememberScrollState())\n                .focusRequester(focusRequester)\n                .onGloballyPositioned {\n                    if (!textFieldLoaded)\n                        textFieldLoaded = true\n                },\n            singleLine = false,\n            trailingIcon = {\n                if (newMessage.value.text.isNotEmpty()) {\n                    Icon(\n                        Icons.Outlined.Clear,\n                        contentDescription = \"Clear\",\n                        modifier = Modifier.clickable {\n                            newMessage.value = TextFieldValue(\"\")\n                            viewModel.updateAorPeerMessage(aor, peerUri, \"\")\n                        },\n                        tint = MaterialTheme.colorScheme.onSurfaceVariant\n                    )\n                }\n            },\n            label = { Text(stringResource(R.string.new_message)) },\n            textStyle = TextStyle(fontSize = 18.sp),\n            keyboardOptions = KeyboardOptions(\n                capitalization = KeyboardCapitalization.Sentences,\n                keyboardType = KeyboardType.Text,\n                autoCorrectEnabled = true\n            )\n        )\n        LaunchedEffect(Unit) {\n            if (newMessage.value.text.isNotEmpty())\n                focusRequester.requestFocus()\n        }\n        SmallFloatingActionButton(\n            modifier = Modifier.offset(y = 2.dp),\n            onClick = {\n                val msgText = newMessage.value.text\n                if (msgText.isNotEmpty()) {\n                    keyboardController?.hide()\n                    val time = System.currentTimeMillis()\n                    val msg = Message(\n                        aor,\n                        peerUri,\n                        msgText,\n                        time,\n                        MESSAGE_UP_WAIT,\n                        0,\n                        \"\",\n                        true\n                    )\n                    msg.add()\n                    var msgUri = \"\"\n                    addMessage(msg)\n                    if (Utils.isTelUri(peerUri))\n                        if (ua.account.telProvider == \"\") {\n                            dialogMessage.value = String.format(\n                                ctx.getString(R.string.no_telephony_provider),\n                                Utils.plainAor(aor)\n                            )\n                            showDialog.value = true\n                        }\n                        else {\n                            msgUri = Utils.telToSip(peerUri, ua.account)\n                        }\n                    else\n                        msgUri = peerUri\n                    if (msgUri != \"\")\n                        if (Api.message_send(ua.uap,\n                                msgUri,\n                                msgText,\n                                time.toString()\n                        ) != 0) {\n                            Toast.makeText(\n                                ctx, \"${ctx.getString(R.string.message_failed)}!\",\n                                Toast.LENGTH_SHORT\n                            ).show()\n                            msg.direction = MESSAGE_UP_FAIL\n                            msg.responseReason = ctx.getString(R.string.message_failed)\n                        }\n                        else {\n                            newMessage.value = TextFieldValue(\"\")\n                            viewModel.updateAorPeerMessage(aor, peerUri, \"\")\n                            keyboardController?.hide()\n                        }\n                }\n            },\n            containerColor = MaterialTheme.colorScheme.secondary,\n            contentColor = MaterialTheme.colorScheme.onSecondary\n        ) {\n            Icon(\n                imageVector = Icons.AutoMirrored.Filled.Send,\n                modifier = Modifier.size(28.dp),\n                contentDescription = stringResource(R.string.add)\n            )\n        }\n    }\n}\n\nprivate fun backAction(navController: NavController, account: Account, peerUri: String) {\n    val aor = account.aor\n    Message.updateMessagesFromPearRead(aor, peerUri)\n    account.unreadMessages = Message.unreadMessages(aor)\n    navController.navigateUp()\n}\n\nprivate fun loadPeerMessages(aor: String, peerUri: String): List<Message> {\n    val res = mutableListOf<Message>()\n    for (m in BaresipService.messages.reversed())\n        if ((m.aor == aor) && (m.peerUri == peerUri)) res.add(m)\n    return res\n}\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/ChatsScreen.kt",
    "content": "package com.tutpro.baresip\n\nimport android.content.Context\nimport android.text.format.DateUtils.isToday\nimport androidx.compose.animation.animateContentSize\nimport androidx.compose.foundation.BorderStroke\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.asPaddingValues\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.heightIn\nimport androidx.compose.foundation.layout.imePadding\nimport androidx.compose.foundation.layout.navigationBarsPadding\nimport androidx.compose.foundation.layout.offset\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.statusBars\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.layout.wrapContentHeight\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.lazy.rememberLazyListState\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.foundation.text.BasicText\nimport androidx.compose.foundation.text.KeyboardOptions\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.filled.AccountCircle\nimport androidx.compose.material.icons.filled.Add\nimport androidx.compose.material.icons.filled.Menu\nimport androidx.compose.material.icons.outlined.Clear\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.OutlinedTextField\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.SmallFloatingActionButton\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.TopAppBarDefaults\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.DisposableEffect\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.MutableState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.draw.scale\nimport androidx.compose.ui.draw.shadow\nimport androidx.compose.ui.layout.ContentScale\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalFocusManager\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.AnnotatedString\nimport androidx.compose.ui.text.TextStyle\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.input.ImeAction\nimport androidx.compose.ui.text.input.KeyboardType\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.LifecycleEventObserver\nimport androidx.lifecycle.compose.LocalLifecycleOwner\nimport androidx.navigation.NavController\nimport androidx.navigation.NavGraphBuilder\nimport androidx.navigation.NavType\nimport androidx.navigation.compose.composable\nimport androidx.navigation.navArgument\nimport coil.compose.AsyncImage\nimport com.tutpro.baresip.BaresipService.Companion.contactNames\nimport com.tutpro.baresip.CustomElements.AlertDialog\nimport com.tutpro.baresip.CustomElements.DropdownMenu\nimport com.tutpro.baresip.CustomElements.SelectableAlertDialog\nimport com.tutpro.baresip.CustomElements.verticalScrollbar\nimport java.text.DateFormat\nimport java.util.GregorianCalendar\n\nfun NavGraphBuilder.chatsScreenRoute(navController: NavController) {\n    composable(\n        route = \"chats/{aor}\",\n        arguments = listOf(navArgument(\"aor\") { type = NavType.StringType })\n    ) { backStackEntry ->\n        val aor = backStackEntry.arguments?.getString(\"aor\")!!\n        ChatsScreen(navController, aor)\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun ChatsScreen(navController: NavController, aor: String) {\n\n    val ctx = LocalContext.current\n\n    val account = Account.ofAor(aor)!!\n    val uaMessages: MutableState<List<Message>> = remember { mutableStateOf(emptyList()) }\n    var areMessagesLoaded by remember { mutableStateOf(false) }\n\n    var refreshTrigger by remember { mutableIntStateOf(0) }\n    val lifecycleOwner = LocalLifecycleOwner.current\n\n    LaunchedEffect(aor, refreshTrigger) {\n        uaMessages.value = loadMessages(account)\n        areMessagesLoaded = true\n    }\n\n    DisposableEffect(lifecycleOwner) {\n        val observer = LifecycleEventObserver { _, event ->\n            if (event == Lifecycle.Event.ON_RESUME)\n                refreshTrigger++\n        }\n        lifecycleOwner.lifecycle.addObserver(observer)\n        onDispose {\n            lifecycleOwner.lifecycle.removeObserver(observer)\n        }\n    }\n\n    Scaffold(\n        modifier = Modifier\n            .fillMaxSize()\n            .imePadding(),\n        containerColor = MaterialTheme.colorScheme.background,\n        topBar = {\n            Column(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .background(MaterialTheme.colorScheme.background)\n                    .padding(top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding())\n            ) {\n                TopAppBar(navController, account, uaMessages)\n            }\n        },\n        bottomBar = { NewChatPeer(ctx, navController, account) },\n        content = { contentPadding ->\n            if (areMessagesLoaded)\n                ChatsContent(LocalContext.current, navController, contentPadding, account, uaMessages)\n        },\n    )\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun TopAppBar(\n    navController: NavController,\n    account: Account,\n    uaMessages: MutableState<List<Message>>\n) {\n\n    var menuExpanded by remember { mutableStateOf(false) }\n    val delete = stringResource(R.string.delete)\n    val blocked = stringResource(R.string.blocked)\n    val showDialog = remember { mutableStateOf(false) }\n    val lastAction = remember { mutableStateOf({}) }\n\n    AlertDialog(\n        showDialog = showDialog,\n        title = stringResource(R.string.confirmation),\n        message = String.format(stringResource(R.string.delete_chats_alert), account.text()),\n        firstButtonText = stringResource(R.string.cancel),\n        lastButtonText = stringResource(R.string.delete),\n        onLastClicked = lastAction.value,\n    )\n\n    TopAppBar(\n        title = { Text(text = stringResource(R.string.chats), fontWeight = FontWeight.Bold) },\n        colors = TopAppBarDefaults.topAppBarColors(\n            containerColor = MaterialTheme.colorScheme.primary,\n            navigationIconContentColor = MaterialTheme.colorScheme.onPrimary,\n            titleContentColor = MaterialTheme.colorScheme.onPrimary,\n            actionIconContentColor = MaterialTheme.colorScheme.onPrimary\n        ),\n        navigationIcon = {\n            IconButton(onClick = { navController.navigateUp() }) {\n                Icon(\n                    imageVector = Icons.AutoMirrored.Filled.ArrowBack,\n                    contentDescription = null,\n                )\n            }\n        },\n        windowInsets = WindowInsets(0, 0, 0, 0),\n        actions = {\n            IconButton(\n                onClick = { menuExpanded = !menuExpanded }\n            ) {\n                Icon(\n                    imageVector = Icons.Filled.Menu,\n                    contentDescription = \"Menu\",\n                )\n            }\n            DropdownMenu (\n                expanded = menuExpanded,\n                onDismissRequest = { menuExpanded = false },\n                items = listOf(delete, blocked),\n                onItemClick = { selectedItem ->\n                    menuExpanded = false\n                    when (selectedItem) {\n                        delete -> {\n                            lastAction.value = {\n                                deleteMessages(uaMessages, account, \"\")\n                                account.unreadMessages = false\n                            }\n                            showDialog.value = true\n                        }\n                        blocked -> {\n                            navController.navigate(\"blocked/message/${account.aor}\")\n                        }\n                    }\n                }\n            )\n        },\n    )\n}\n\n@Composable\nprivate fun ChatsContent(\n    ctx: Context,\n    navController: NavController,\n    contentPadding: PaddingValues,\n    account: Account,\n    uaMessages: MutableState<List<Message>>\n) {\n    Column(\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(contentPadding),\n        verticalArrangement = Arrangement.Top\n    ) {\n        Account(account)\n        Chats(ctx, navController, account, uaMessages)\n    }\n}\n\n@Composable\nprivate fun Account(account: Account) {\n    Text(\n        text = stringResource(R.string.account) + \" \" + account.text(),\n        modifier = Modifier.fillMaxWidth().padding(top = 8.dp, bottom = 8.dp),\n        fontSize = 18.sp,\n        fontWeight = FontWeight.SemiBold,\n        textAlign = TextAlign.Center\n    )\n}\n\n@Composable\nprivate fun Chats(\n    ctx: Context,\n    navController: NavController,\n    account: Account,\n    uaMessages: MutableState<List<Message>>\n) {\n    val aor = account.aor\n\n    val showDialog = remember { mutableStateOf(false) }\n    val dialogMessage = remember { mutableStateOf(\"\") }\n    val secondButtonText = remember { mutableStateOf(\"\") }\n    val secondAction = remember { mutableStateOf({}) }\n    val lastButtonText = remember { mutableStateOf(\"\") }\n    val lastAction = remember { mutableStateOf({}) }\n\n    if (showDialog.value)\n        AlertDialog(\n            showDialog = showDialog,\n            title = stringResource(R.string.confirmation),\n            message = dialogMessage.value,\n            firstButtonText = stringResource(R.string.cancel),\n            secondButtonText = secondButtonText.value,\n            onSecondClicked = secondAction.value,\n            lastButtonText = lastButtonText.value,\n            onLastClicked = lastAction.value,\n        )\n\n    val lazyListState = rememberLazyListState()\n\n    LazyColumn(\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(start = 8.dp, end = 4.dp)\n            .verticalScrollbar(state = lazyListState)\n            .background(MaterialTheme.colorScheme.background),\n        reverseLayout = true,\n        state = lazyListState,\n        verticalArrangement = Arrangement.spacedBy(16.dp),\n    ) {\n        items(items = uaMessages.value, key = { message -> message.timeStamp }) { message ->\n            Row(verticalAlignment = Alignment.CenterVertically) {\n                when (val contact = Contact.findContact(message.peerUri)) {\n                    is Contact.BaresipContact -> {\n                        val avatarImage = contact.avatarImage\n                        if (avatarImage != null)\n                            CustomElements.ImageAvatar(avatarImage)\n                        else\n                            CustomElements.TextAvatar(contact.name, contact.color)\n                    }\n\n                    is Contact.AndroidContact -> {\n                        val thumbNailUri = contact.thumbnailUri\n                        if (thumbNailUri != null)\n                            AsyncImage(\n                                model = thumbNailUri,\n                                contentDescription = \"Avatar\",\n                                contentScale = ContentScale.Crop,\n                                modifier = Modifier.size(36.dp).clip(CircleShape),\n                            )\n                        else\n                            CustomElements.TextAvatar(contact.name, contact.color)\n                    }\n\n                    null -> {\n                        Icon(\n                            imageVector = Icons.Filled.AccountCircle,\n                            contentDescription = \"Avatar\",\n                            modifier = Modifier.size(36.dp).scale(1.2f),\n                            tint = MaterialTheme.colorScheme.secondary\n                        )\n                    }\n                }\n                Spacer(modifier = Modifier.width(6.dp))\n                val buttonShape = if (message.direction == MESSAGE_DOWN) {\n                    RoundedCornerShape(50.dp, 20.dp, 20.dp, 10.dp)\n                } else {\n                    RoundedCornerShape(20.dp, 10.dp, 50.dp, 20.dp)\n                }\n                val borderStroke = if (account.unreadMessages && Message.unreadMessagesFromPeer(aor, message.peerUri)) {\n                    BorderStroke(width = 2.dp, color = MaterialTheme.colorScheme.error)\n                } else {\n                    null\n                }\n                CustomElements.Button(\n                    onClick = {\n                        navController.navigate(\"chat/${aor}/${message.peerUri}\")\n                    },\n                    onLongClick = {\n                        val peer = Utils.friendlyUri(ctx, message.peerUri, account)\n                        val contactExists =\n                            Contact.nameExists(peer, BaresipService.contacts, false)\n                        if (contactExists) {\n                            dialogMessage.value = String.format(\n                                ctx.getString(R.string.short_chat_question),\n                                peer\n                            )\n                            secondButtonText.value = \"\"\n                            lastButtonText.value = ctx.getString(R.string.delete)\n                            lastAction.value = {\n                                deleteMessages(uaMessages, account, message.peerUri)\n                            }\n                        } else {\n                            dialogMessage.value =\n                                String.format(ctx.getString(R.string.long_chat_question), peer)\n                            secondButtonText.value = ctx.getString(R.string.delete)\n                            secondAction.value = {\n                                deleteMessages(uaMessages, account, message.peerUri)\n                            }\n                            lastButtonText.value = ctx.getString(R.string.add_contact)\n                            lastAction.value = {\n                                navController.navigate(\"baresip_contact/${message.peerUri}/new\")\n                            }\n                        }\n                        showDialog.value = true\n                    },\n                    modifier = Modifier.fillMaxWidth().wrapContentHeight().padding(end = 6.dp),\n                    shape = buttonShape,\n                    border = borderStroke,\n                    color = if (message.direction == MESSAGE_DOWN)\n                        MaterialTheme.colorScheme.secondaryContainer\n                    else\n                        MaterialTheme.colorScheme.primaryContainer\n                ) {\n                    val peer = Utils.friendlyUri(ctx, message.peerUri, account)\n                    val cal = GregorianCalendar()\n                    cal.timeInMillis = message.timeStamp\n                    val fmt: DateFormat = if (isToday(message.timeStamp))\n                        DateFormat.getTimeInstance(DateFormat.SHORT)\n                    else\n                        DateFormat.getDateInstance(DateFormat.SHORT)\n                    val info = fmt.format(cal.time)\n                    Column {\n                        val textColor = if (message.direction == MESSAGE_DOWN)\n                            MaterialTheme.colorScheme.onSecondaryContainer\n                        else\n                            MaterialTheme.colorScheme.onPrimaryContainer\n                        Row {\n                            Text(text = peer, color = textColor, fontSize = 12.sp)\n                            Spacer(modifier = Modifier.weight(1f))\n                            Text(text = info, color = textColor, fontSize = 12.sp)\n                        }\n                        Row {\n                            BasicText(\n                                text = message.message,\n                                maxLines = 1,\n                                overflow = TextOverflow.Ellipsis,\n                                style = TextStyle(\n                                    color = textColor,\n                                    fontWeight = if (message.direction == MESSAGE_DOWN && message.new)\n                                        FontWeight.Bold else FontWeight.Normal,\n                                    fontSize = 16.sp\n                                )\n                            )\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun NewChatPeer(ctx: Context, navController: NavController, account: Account) {\n\n    val alertTitle = remember { mutableStateOf(\"\") }\n    val alertMessage = remember { mutableStateOf(\"\") }\n    val showAlert = remember { mutableStateOf(false) }\n\n    fun makeChat(ctx: Context, navController: NavController, account: Account, chatPeer: String) {\n        val peerUri = if (Utils.isTelNumber(chatPeer))\n            \"tel:$chatPeer\"\n        else\n            chatPeer\n        val uri = if (Utils.isTelUri(peerUri)) {\n            if (account.telProvider == \"\") {\n                alertTitle.value = ctx.getString(R.string.notice)\n                alertMessage.value =\n                    String.format(ctx.getString(R.string.no_telephony_provider), account.aor)\n                showAlert.value = true\n                \"\"\n            } else\n                Utils.telToSip(peerUri, account)\n        } else\n            Utils.uriComplete(peerUri, account.aor)\n        if (alertMessage.value.isEmpty()) {\n            if (!Utils.checkUri(uri)) {\n                alertTitle.value = ctx.getString(R.string.notice)\n                alertMessage.value = String.format(ctx.getString(R.string.invalid_sip_or_tel_uri), uri)\n                showAlert.value = true\n            }\n            else\n                navController.navigate(\"chat/${account.aor}/${uri}\")\n        }\n    }\n\n    if (showAlert.value) {\n        AlertDialog(\n            showDialog = showAlert,\n            title = alertTitle.value,\n            message = alertMessage.value,\n            lastButtonText = stringResource(R.string.ok),\n        )\n    }\n\n    val showDialog = remember { mutableStateOf(false) }\n    val items = remember { mutableStateOf(listOf<String>()) }\n    val itemAction = remember { mutableStateOf<(Int) -> Unit>({ _ -> run {} }) }\n\n    SelectableAlertDialog(\n        openDialog = showDialog,\n        title = stringResource(R.string.choose_destination_uri),\n        items = items.value,\n        onItemClicked = itemAction.value,\n        neutralButtonText = stringResource(R.string.cancel),\n        onNeutralClicked = {}\n    )\n\n    val suggestions by remember { contactNames }\n    var filteredSuggestions by remember { mutableStateOf<List<AnnotatedString>>(emptyList()) }\n    var showSuggestions by remember { mutableStateOf(false) }\n    val lazyListState = rememberLazyListState()\n    val focusManager = LocalFocusManager.current\n    Row(\n        modifier = Modifier\n            .fillMaxWidth()\n            .navigationBarsPadding()\n            .padding(start = 16.dp, end = 8.dp, top = 10.dp, bottom = 16.dp),\n        verticalAlignment = Alignment.CenterVertically,\n    ) {\n        var newPeer by remember { mutableStateOf(\"\") }\n        Column(\n            horizontalAlignment = Alignment.Start,\n            modifier = Modifier.weight(1f)\n        ) {\n            if (showSuggestions && filteredSuggestions.isNotEmpty()) {\n                Column(\n                    modifier = Modifier\n                        .shadow(8.dp, RoundedCornerShape(8.dp))\n                        .background(\n                            color = MaterialTheme.colorScheme.surfaceVariant,\n                            shape = RoundedCornerShape(8.dp)\n                        )\n                        .animateContentSize()\n                ) {\n                    Box(modifier = Modifier.fillMaxWidth().heightIn(max = 150.dp)\n                    ) {\n                        LazyColumn(\n                            modifier = Modifier\n                                .fillMaxWidth()\n                                .heightIn(max = 180.dp)\n                                .verticalScrollbar(state = lazyListState, width = 6.dp),\n                            horizontalAlignment = Alignment.Start,\n                            state = lazyListState\n                        ) {\n                            items(\n                                items = filteredSuggestions,\n                                key = { suggestion -> suggestion.toString() }\n                            ) { suggestion ->\n                                Box(\n                                    modifier = Modifier\n                                        .fillMaxWidth()\n                                        .clickable {\n                                            newPeer = suggestion.toString()\n                                            showSuggestions = false\n                                        }\n                                        .padding(12.dp)\n                                ) {\n                                    Text(\n                                        text = suggestion,\n                                        modifier = Modifier.fillMaxWidth(),\n                                        color = MaterialTheme.colorScheme.onSurfaceVariant,\n                                        fontSize = 18.sp\n                                    )\n                                }\n                            }\n                        }\n                    }\n                }\n                Spacer(modifier = Modifier.height(8.dp))\n            }\n            OutlinedTextField(\n                value = newPeer,\n                placeholder = { Text(stringResource(R.string.new_chat_peer)) },\n                onValueChange = {\n                    newPeer = it\n                    showSuggestions = newPeer.length > 1\n                    filteredSuggestions = if (it.isEmpty()) {\n                        emptyList()\n                    } else {\n                        val normalizedInput = Utils.unaccent(it)\n                        suggestions\n                            .filter { suggestion ->\n                                it.length > 1 &&\n                                    Utils.unaccent(suggestion).contains(normalizedInput, ignoreCase = true)\n                            }\n                            .map { suggestion ->\n                                Utils.buildAnnotatedStringWithHighlight(suggestion, it)\n                            }\n                    }\n                },\n                modifier = Modifier.padding(end = 6.dp).fillMaxWidth(),\n                singleLine = true,\n                trailingIcon = {\n                    if (newPeer.isNotEmpty())\n                        Icon(\n                            Icons.Outlined.Clear,\n                            contentDescription = null,\n                            modifier = Modifier.clickable {\n                                if (showSuggestions)\n                                    showSuggestions = false\n                                newPeer = \"\"\n                            },\n                            tint = MaterialTheme.colorScheme.onSurfaceVariant\n                        )\n                },\n                label = { Text(stringResource(R.string.new_chat_peer)) },\n                textStyle = TextStyle(fontSize = 18.sp),\n                keyboardOptions = KeyboardOptions(\n                    keyboardType = KeyboardType.Text,\n                    imeAction = ImeAction.Done\n                )\n            )\n        }\n        Spacer(Modifier.width(4.dp))\n        SmallFloatingActionButton(\n            modifier = Modifier.padding(end = 4.dp).offset(y = 2.dp),\n            onClick = {\n                showSuggestions = false\n                val peerText = newPeer.trim()\n                if (peerText.isNotEmpty()) {\n                    val uris = Contact.contactUris(peerText)\n                    if (uris.isEmpty())\n                        makeChat(ctx, navController, account, peerText)\n                    else if (uris.size == 1)\n                        makeChat(ctx, navController, account, uris[0])\n                    else {\n                        items.value = uris\n                        itemAction.value = { index ->\n                            makeChat(ctx, navController, account, uris[index])\n                        }\n                        showDialog.value = true\n                    }\n                }\n                newPeer = \"\"\n                focusManager.clearFocus()\n            },\n            containerColor = MaterialTheme.colorScheme.secondary,\n            contentColor = MaterialTheme.colorScheme.onSecondary\n        ) {\n            Icon(\n                imageVector = Icons.Filled.Add,\n                modifier = Modifier.size(36.dp),\n                contentDescription = stringResource(R.string.add)\n            )\n        }\n    }\n}\n\nprivate fun loadMessages(account: Account) : List<Message> {\n    val res = mutableListOf<Message>()\n    account.unreadMessages = false\n    for (m in BaresipService.messages.reversed()) {\n        if (m.aor != account.aor) continue\n        var found = false\n        for (r in res)\n            if (r.peerUri == m.peerUri) {\n                found = true\n                break\n            }\n        if (!found) {\n            res.add(0, m)\n            if (m.new)\n                account.unreadMessages = true\n        }\n    }\n    return res.toList()\n}\n\nprivate fun deleteMessages(uaMessages: MutableState<List<Message>>, account: Account, peerUri: String) {\n    val updatedMessages = BaresipService.messages.toMutableList()\n    updatedMessages.removeAll {\n        it.aor == account.aor && (peerUri == \"\" || it.peerUri == peerUri)\n    }\n    BaresipService.messages = updatedMessages.toList()\n    Message.save()\n    uaMessages.value = loadMessages(account)\n}"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/Codec.kt",
    "content": "package com.tutpro.baresip\n\nimport androidx.compose.runtime.MutableState\n\ndata class Codec(val name: String, var enabled: MutableState<Boolean>)"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/CodecsScreen.kt",
    "content": "package com.tutpro.baresip\n\nimport androidx.compose.foundation.ExperimentalFoundationApi\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.combinedClickable\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.asPaddingValues\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.imePadding\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.statusBars\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.filled.Check\nimport androidx.compose.material.icons.filled.Reorder\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.HorizontalDivider\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.ListItem\nimport androidx.compose.material3.ListItemDefaults\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.TopAppBarDefaults\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.mutableStateListOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.snapshots.SnapshotStateList\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.alpha\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport androidx.navigation.NavController\nimport androidx.navigation.NavGraphBuilder\nimport androidx.navigation.NavType\nimport androidx.navigation.compose.composable\nimport androidx.navigation.navArgument\nimport com.tutpro.baresip.CustomElements.verticalScrollbar\n\nfun NavGraphBuilder.codecsScreenRoute(navController: NavController) {\n    composable(\n        route = \"codecs/{aor}/{media}\",\n        arguments = listOf(\n            navArgument(\"aor\") { type = NavType.StringType },\n            navArgument(\"media\") { type = NavType.StringType })\n    ) { backStackEntry ->\n        val aor = backStackEntry.arguments?.getString(\"aor\")!!\n        val media = backStackEntry.arguments?.getString(\"media\")!!\n        val account = UserAgent.ofAor(aor)?.account!!\n        CodecsScreen(\n            onBack = { navController.navigateUp() },\n            checkOnClick = { updatedCodecs ->\n                val enabledCodecNames = updatedCodecs.filter { it.enabled.value }.map { it.name }\n                val codecList = Utils.implode(enabledCodecNames, \",\")\n                Log.d(TAG, \"Saving codecs for ${account.aor} (${media}): $codecList\")\n                val success = if (media == \"audio\")\n                    Api.account_set_audio_codecs(account.accp, codecList)\n                else\n                    Api.account_set_video_codecs(account.accp, codecList)\n                if (success == 0) {\n                    if (media == \"audio\")\n                        account.audioCodec = ArrayList(enabledCodecNames)\n                    else\n                        account.videoCodec = ArrayList(enabledCodecNames)\n                    Account.saveAccounts()\n                    Log.d(\"CodecsSave\", \"Codecs saved successfully.\")\n                }\n                else\n                    Log.e(TAG, \"Failed to set $aor codecs.\")\n                navController.navigateUp()\n            },\n            aor = aor,\n            media = media\n        )\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun CodecsScreen(\n    onBack: () -> Unit,\n    checkOnClick: (List<Codec>) -> Unit,\n    aor: String,\n    media: String\n) {\n    val ua = UserAgent.ofAor(aor)!!\n    val acc = ua.account\n    val codecs = remember { mutableStateListOf<Codec>() }\n\n    LaunchedEffect(acc, media) {\n        val allCodecs: List<String> = if (media == \"audio\") {\n            Api.audio_codecs().split(\",\")\n        } else {\n            Api.video_codecs().split(\",\").distinct()\n        }\n        val accCodecs: List<String> = if (media == \"audio\") {\n            acc.audioCodec\n        } else {\n            acc.videoCodec\n        }\n        val currentCodecs = mutableListOf<Codec>()\n        for (codec in accCodecs)\n            currentCodecs.add(Codec(codec, mutableStateOf(true)))\n        for (codec in allCodecs)\n            if (codec !in accCodecs)\n                currentCodecs.add(Codec(codec, mutableStateOf(false)))\n        codecs.clear()\n        codecs.addAll(currentCodecs)\n    }\n\n    Scaffold(\n        modifier = Modifier.fillMaxSize().imePadding(),\n        containerColor = MaterialTheme.colorScheme.background,\n        topBar = {\n            Column(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .background(MaterialTheme.colorScheme.background)\n                    .padding(\n                        top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()\n                    )\n            ) {\n                TopAppBar(\n                    title = {\n                        Text(\n                            text = if (media == \"audio\")\n                                stringResource(R.string.audio_codecs)\n                            else\n                                stringResource(R.string.video_codecs),\n                            fontWeight = FontWeight.Bold\n                        )\n                    },\n                    colors = TopAppBarDefaults.topAppBarColors(\n                        containerColor = MaterialTheme.colorScheme.primary,\n                        navigationIconContentColor = MaterialTheme.colorScheme.onPrimary,\n                        titleContentColor = MaterialTheme.colorScheme.onPrimary,\n                        actionIconContentColor = MaterialTheme.colorScheme.onPrimary\n                    ),\n                    windowInsets = WindowInsets(0, 0, 0, 0),\n                    navigationIcon = {\n                        IconButton(onClick = onBack) {\n                            Icon(\n                                imageVector = Icons.AutoMirrored.Filled.ArrowBack,\n                                contentDescription = \"Back\",\n                            )\n                        }\n                    },\n                    actions = {\n                        IconButton(onClick = {\n                            checkOnClick(codecs)\n                        }) {\n                            Icon(\n                                imageVector = Icons.Filled.Check,\n                                contentDescription = \"Check\"\n                            )\n                        }\n                    }\n                )\n            }\n        },\n        content = { contentPadding ->\n            CodecsContent(\n                contentPadding,\n                codecs\n            )\n        },\n    )\n}\n\n@Composable\nprivate fun CodecsContent(\n    contentPadding: PaddingValues,\n    codecs: SnapshotStateList<Codec>\n) {\n    Column(\n        modifier = Modifier\n            .fillMaxWidth()\n            .background(MaterialTheme.colorScheme.background)\n            .padding(contentPadding)\n            .padding(bottom = 16.dp),\n    ) {\n        Codecs(codecs = codecs)\n    }\n}\n\n@OptIn(ExperimentalFoundationApi::class)\n@Composable\nprivate fun Codecs(codecs: SnapshotStateList<Codec>) {\n\n    val draggableState = rememberDraggableListState(\n        onMove = { fromIndex, toIndex ->\n            codecs.add(toIndex, codecs.removeAt(fromIndex))\n        }\n    )\n\n    LazyColumn(\n        modifier = Modifier\n            .padding(end = 4.dp)\n            .verticalScrollbar(state = draggableState.listState),\n        state = draggableState.listState,\n        contentPadding = PaddingValues(start = 12.dp, end = 12.dp),\n    ) {\n        draggableItems(\n            state = draggableState,\n            items = codecs,\n            key = { item -> item.name }\n        ) { item, isDragging ->\n            ListItem(\n                colors = ListItemDefaults.colors(\n                    containerColor = if (isDragging)\n                        MaterialTheme.colorScheme.surfaceContainer\n                    else\n                        Color.Transparent\n                ),\n                headlineContent = {\n                    Text(text = item.name,\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .alpha(if (item.enabled.value) 1.0f else 0.5f)\n                            .padding(start = 6.dp)\n                            .combinedClickable(\n                                onClick = {},\n                                onLongClick = {\n                                    item.enabled.value = !item.enabled.value\n                                    if (item.enabled.value) {\n                                        val index = codecs.indexOf(item)\n                                        codecs.removeAt(index)\n                                        codecs.add(0, item)\n                                    } else {\n                                        val index = codecs.indexOf(item)\n                                        codecs.removeAt(index)\n                                        codecs.add(item)\n                                    }\n                                }\n                            )\n                    )\n                },\n                trailingContent = {\n                    Icon(\n                        modifier = Modifier.dragHandle(\n                            state = draggableState,\n                            key = item.name\n                        ),\n                        imageVector =Icons.Filled.Reorder,\n                        contentDescription = null\n                    )\n                },\n            )\n            if (codecs.indexOf(item) > 0)\n                HorizontalDivider(\n                    color = MaterialTheme.colorScheme.outlineVariant,\n                    modifier = Modifier.padding(horizontal = 12.dp),\n                    thickness = 1.dp\n                )\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/Colors.kt",
    "content": "package com.tutpro.baresip\n\nimport androidx.compose.ui.graphics.Color\n\nval Primary = Color(0xFF0CA1FD)\nval OnPrimary = Color(0xFFFFFFFF)\nval PrimaryContainer = Color(0xFFA1CBE6)\nval OnPrimaryContainer = Color(0xFF032133)\nval Secondary = Color(0xFF00B9A1)\nval OnSecondary = Color(0xFFFFFFFF)\nval SecondaryContainer = Color(0xFF9DE6DC)\nval OnSecondaryContainer = Color(0xFF00332C)\nval Tertiary = Color(0xFF8A8378)\nval OnTertiary = Color(0xFFFFFFFF)\nval TertiaryContainer = Color(0xFFE6E2DC)\nval OnTertiaryContainer = Color(0xFF33302C)\nval Error = Color(0xFFBA1A1A)\nval OnError = Color(0xFFFFFFFF)\nval ErrorContainer = Color(0xFFFFDAD6)\nval OnErrorContainer = Color(0xFF93000A)\nval Background = Color(0xFFfbfcfc)\nval OnBackground = Color(0xFF313233)\nval Surface = Color(0xFFfbfcfc)\nval OnSurface = Color(0xFF313233)\nval SurfaceVariant = Color(0xFFd8e0e6)\nval OnSurfaceVariant = Color(0xFF535f66)\nval SurfaceContainerLowest = Color(0xFFFFFFFF)\nval SurfaceContainerLow = Color(0xFFF2F3F9)\nval SurfaceContainer = Color(0xFFECEEF4)\nval SurfaceContainerHigh = Color(0xFFE6E8EE)\nval SurfaceContainerHighest = Color(0xFFE0E2E8)\nval Outline = Color(0xFF7c8e99)\nval OutlineVariant = Color(0xFFC2C7CF)\n\nval PrimaryDark = Color(0xFF84C1E6)\nval OnPrimaryDark = Color(0xFF04314C)\nval PrimaryContainerDark = Color(0xFF054166)\nval OnPrimaryContainerDark = Color(0xFFA1CBE6)\nval SecondaryDark = Color(0xFF7FE6D8)\nval OnSecondaryDark = Color(0xFF004C43)\nval SecondaryContainerDark = Color(0xFF006659)\nval OnSecondaryContainerDark = Color(0xFF9DE6DC)\nval TertiaryDark = Color(0xFFE6E0D8)\nval OnTertiaryDark = Color(0xFF4C4943)\nval TertiaryContainerDark = Color(0xFF666159)\nval OnTertiaryContainerDark = Color(0xFFE6E2DC)\nval ErrorDark = Color(0xFFFFB4AB)\nval OnErrorDark = Color(0xFF690005)\nval ErrorContainerDark = Color(0xFF93000A)\nval OnErrorContainerDark = Color(0xFFFFDAD6)\nval BackgroundDark = Color(0xFF313233)\nval OnBackgroundDark = Color(0xFFe2e4e6)\nval SurfaceDark = Color(0xFF313233)\nval OnSurfaceDark = Color(0xFFe2e4e6)\nval SurfaceVariantDark = Color(0xFF535f66)\nval OnSurfaceVariantDark = Color(0xFFd2dee6)\nval SurfaceContainerLowestDark = Color(0xFF0B0E12)\nval SurfaceContainerLowDark = Color(0xFF181C20)\nval SurfaceContainerDark = Color(0xFF1D2024)\nval SurfaceContainerHighDark = Color(0xFF272A2F)\nval SurfaceContainerHighestDark = Color(0xFF32353A)\nval OutlineDark = Color(0xFF9daab3)\nval OutlineVariantDark = Color(0xFF42474E)\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/Config.kt",
    "content": "package com.tutpro.baresip\n\nimport android.Manifest\nimport android.content.Context\nimport androidx.appcompat.app.AppCompatDelegate\nimport java.io.File\nimport java.net.InetAddress\nimport java.nio.charset.StandardCharsets\n\nobject Config {\n\n    private val configPath = BaresipService.filesPath + \"/config\"\n    val audioModules = listOf(\"opus\", \"amr\", \"libg722\", \"g7221\", \"g729\", \"codec2\", \"g711\")\n    private lateinit var config: String\n    private lateinit var previousConfig: String\n    private lateinit var previousLines: List<String>\n\n    fun initialize(ctx: Context) {\n\n        config = ctx.assets.open(\"config.static\").bufferedReader().use { it.readText() }\n        if (!File(configPath).exists()) {\n            for (module in audioModules)\n                config = \"${config}module ${module}.so\\n\"\n            previousConfig = config\n        } else {\n            previousConfig = String(Utils.getFileContents(configPath)!!, StandardCharsets.ISO_8859_1)\n        }\n        previousLines = previousConfig.split(\"\\n\")\n\n        val logLevel = previousVariable(\"log_level\")\n        if (logLevel == \"\") {\n            config = \"${config}log_level 2\\n\"\n            Log.logLevel = Log.LogLevel.WARN\n        } else {\n            config = \"${config}log_level $logLevel\\n\"\n            BaresipService.logLevel = logLevel.toInt()\n            Log.logLevelSet(BaresipService.logLevel)\n        }\n\n        val autoStart = previousVariable(\"auto_start\")\n        config = if (autoStart != \"\")\n            \"${config}auto_start $autoStart\\n\"\n        else\n            \"${config}auto_start no\\n\"\n\n        val sipListen = previousVariable(\"sip_listen\")\n        if (sipListen != \"\")\n            config = \"${config}sip_listen $sipListen\\n\"\n\n        val addressFamily = previousVariable(\"net_af\")\n        if (addressFamily != \"\") {\n            config = \"${config}net_af $addressFamily\\n\"\n            BaresipService.addressFamily = addressFamily\n        }\n\n        val transportProtocols = previousVariable(\"sip_transports\")\n        if (transportProtocols != \"\")\n            config = \"${config}sip_transports $transportProtocols\\n\"\n\n        val sipCertificate = previousVariable(\"sip_certificate\")\n        if (sipCertificate != \"\")\n            config = \"${config}sip_certificate $sipCertificate\\n\"\n\n        val sipVerifyServer = previousVariable(\"sip_verify_server\")\n        if (sipVerifyServer != \"\")\n            config = \"${config}sip_verify_server $sipVerifyServer\\n\"\n\n        val caBundlePath = \"${BaresipService.filesPath}/ca_bundle.crt\"\n        val caBundleFile = File(caBundlePath)\n        val caFilePath = \"${BaresipService.filesPath}/ca_certs.crt\"\n        val caFile = File(caFilePath)\n        if (caFile.exists())\n            caFile.copyTo(caBundleFile, true)\n        else\n            caBundleFile.writeBytes(byteArrayOf())\n        Log.d(TAG, \"Size of caFile = ${caBundleFile.length()}\")\n        val cacertsPath = \"/system/etc/security/cacerts\"\n        val cacertsDir = File(cacertsPath)\n        var caCount = 0\n        if (cacertsDir.exists()) {\n            cacertsDir.walk().forEach {\n                if (it.isFile) {\n                    caBundleFile.appendBytes(\n                        it.readBytes()\n                            .toString(Charsets.UTF_8)\n                            .substringBefore(\"Certificate:\")\n                            .toByteArray(Charsets.UTF_8)\n                    )\n                    caCount++\n                }\n            }\n            Log.d(TAG, \"Added $caCount ca certificates from $cacertsPath\")\n        } else {\n            Log.w(TAG, \"Directory $cacertsDir does not exist!\")\n        }\n        Log.d(TAG, \"Size of caBundleFile = ${caBundleFile.length()}\")\n        config = \"${config}sip_cafile $caBundlePath\\n\"\n\n        val dynamicDns = previousVariable(\"dyn_dns\")\n        if (dynamicDns == \"no\") {\n            config = \"${config}dyn_dns no\\n\"\n            for (server in previousVariables(\"dns_server\"))\n                config = \"${config}dns_server $server\\n\"\n        } else {\n            config = \"${config}dyn_dns yes\\n\"\n            for (dnsServer in BaresipService.dnsServers)\n                config = if (Utils.checkIpV4(dnsServer.hostAddress!!))\n                    \"${config}dns_server ${dnsServer.hostAddress}:53\\n\"\n                else\n                    \"${config}dns_server [${dnsServer.hostAddress}]:53\\n\"\n            BaresipService.dynDns = true\n        }\n\n        val userAgent = previousVariable(\"user_agent\")\n        if (userAgent != \"\")\n            config = \"${config}user_agent $userAgent\\n\"\n\n        val sipCuserRandom = previousVariable(\"sip_cuser_random\")\n        config = if (sipCuserRandom != \"\")\n            \"${config}sip_cuser_random $sipCuserRandom\\n\"\n        else\n            \"${config}sip_cuser_random yes\\n\"\n\n        val darkTheme = previousVariable(\"dark_theme\")\n        Preferences(ctx).displayTheme = if (darkTheme == \"yes\") {\n            config = \"${config}dark_theme yes\\n\"\n            AppCompatDelegate.MODE_NIGHT_YES\n        } else {\n            AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM\n        }\n\n        val dynamicColors = previousVariable(\"dynamic_colors\")\n        BaresipService.dynamicColors.value = if (dynamicColors == \"yes\") {\n            config = \"${config}dynamic_colors yes\\n\"\n            true\n        } else {\n            AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM\n            false\n        }\n\n        val colorblind = previousVariable(\"colorblind\")\n        config = if (colorblind != \"\")\n            \"${config}colorblind $colorblind\\n\"\n        else\n            \"${config}colorblind no\\n\"\n        BaresipService.colorblind = colorblind == \"yes\"\n\n        val proximitySensing = previousVariable(\"proximity_sensing\")\n        config = if (proximitySensing != \"\")\n            \"${config}proximity_sensing $proximitySensing\\n\"\n        else\n            \"${config}proximity_sensing yes\\n\"\n        BaresipService.proximitySensing = proximitySensing != \"no\"\n\n        var contactsMode = previousVariable(\"contacts_mode\").lowercase()\n        if (contactsMode != \"\") {\n            if (contactsMode != \"baresip\" &&\n                    !Utils.checkPermissions(ctx, arrayOf(Manifest.permission.READ_CONTACTS,\n                            Manifest.permission.WRITE_CONTACTS)))\n                contactsMode = \"baresip\"\n        } else {\n            contactsMode = \"baresip\"\n        }\n        config = \"${config}contacts_mode $contactsMode\\n\"\n        BaresipService.contactsMode = contactsMode\n\n        config = \"${config}snd_path ${BaresipService.filesPath}/recordings\\n\"\n\n        val callVolume = previousVariable(\"call_volume\")\n        if (callVolume != \"\") {\n            config = \"${config}call_volume $callVolume\\n\"\n            BaresipService.callVolume = callVolume.toInt()\n        } else {\n            config = \"${config}call_volume ${BaresipService.callVolume}\\n\"\n        }\n\n        val speakerPhone = previousVariable(\"speaker_phone\")\n        if (speakerPhone != \"\") {\n            config = \"${config}speaker_phone $speakerPhone\\n\"\n            BaresipService.speakerPhone = speakerPhone == \"yes\"\n        } else {\n            config = \"${config}speaker_phone no\\n\"\n        }\n\n        val previousModules = previousVariables(\"module\")\n        for (module in audioModules)\n            if (\"${module}.so\" in previousModules ||\n                (module == \"libg722\" && \"g722.so\" in previousModules))\n                    config = \"${config}module ${module}.so\\n\"\n\n        Utils.aecAgcCheck()\n\n        val micGain = previousVariable(\"augain\")\n        config = if (BaresipService.agcAvailable || micGain == \"\"  || micGain == \"1.0\")\n            \"${config}augain 1.0\\n\"\n        else\n            \"${config}module augain.so\\naugain $micGain\\n\"\n\n        val opusBitRate = previousVariable(\"opus_bitrate\")\n        config = if (opusBitRate == \"\")\n            \"${config}opus_bitrate 28000\\n\"\n        else\n            \"${config}opus_bitrate $opusBitRate\\n\"\n\n        val opusPacketLoss = previousVariable(\"opus_packet_loss\")\n        config = if (opusPacketLoss == \"\")\n            \"${config}opus_packet_loss 1\\n\"\n        else\n            \"${config}opus_packet_loss $opusPacketLoss\\n\"\n\n        val audioDelay = previousVariable(\"audio_delay\")\n        if (audioDelay != \"\") {\n            config = \"${config}audio_delay $audioDelay\\n\"\n            BaresipService.audioDelay = audioDelay.toLong()\n        } else {\n            config = \"${config}audio_delay ${BaresipService.audioDelay}\\n\"\n        }\n\n        val toneCountry = previousVariable(\"tone_country\")\n        if (toneCountry != \"\")\n            BaresipService.toneCountry = toneCountry\n        config = \"${config}tone_country ${BaresipService.toneCountry}\\n\"\n\n        save()\n        BaresipService.isConfigInitialized = true\n\n    }\n\n    private fun previousVariable(name: String): String {\n        for (line in previousLines) {\n            val nameValue = line.split(\" \", limit = 2)\n            if (nameValue.size == 2 && nameValue[0] == name)\n                return nameValue[1].trim()\n        }\n        return \"\"\n    }\n\n    private fun previousVariables(name: String): ArrayList<String> {\n        val result = ArrayList<String>()\n        for (line in previousLines) {\n            val nameValue = line.split(\" \", limit = 2)\n            if (nameValue.size == 2 && nameValue[0] == name)\n                result.add(nameValue[1].trim())\n        }\n        return result\n    }\n\n    fun variable(name: String): String {\n        for (line in config.split(\"\\n\")) {\n            val nameValue = line.split(\" \", limit = 2)\n            if (nameValue.size == 2 && nameValue[0] == name)\n                return nameValue[1].trim()\n        }\n        return \"\"\n    }\n\n    fun variables(name: String): ArrayList<String> {\n        val result = ArrayList<String>()\n        for (line in config.split(\"\\n\")) {\n            val nameValue = line.split(\" \", limit = 2)\n            if (nameValue.size == 2 && nameValue[0] == name)\n                result.add(nameValue[1].trim())\n        }\n        return result\n    }\n\n    fun addVariable(name: String, value: String) {\n        config += \"$name $value\\n\"\n    }\n\n    fun removeVariable(variable: String) {\n        config = Utils.removeLinesStartingWithString(config, \"$variable \")\n    }\n\n    fun removeVariableValue(variable: String, value: String) {\n        config = Utils.removeLinesStartingWithString(config, \"$variable $value\")\n    }\n\n    fun replaceVariable(variable: String, value: String) {\n        removeVariable(variable)\n        if (value != \"\")\n            addVariable(variable, value)\n    }\n\n    fun reset() {\n        Utils.deleteFile(File(configPath))\n    }\n\n    fun save() {\n        Utils.putFileContents(configPath, config.toByteArray())\n        Log.d(TAG, \"Saved new config '$config'\")\n        // Api.reload_config()\n    }\n\n    fun dnsServers(): String {\n        return if (variable(\"dyn_dns\") == \"yes\")\n            \"\"\n        else {\n            val servers = variables(\"dns_server\")\n            var serverList = \"\"\n            for (server in servers)\n                serverList += \", $server\"\n            serverList.trimStart(',').trimStart(' ')\n        }\n    }\n\n    fun updateDnsServers(dnsServers: List<InetAddress>): Int {\n        var servers = \"\"\n        for (dnsServer in dnsServers) {\n            if (dnsServer.hostAddress == null) continue\n            var address = dnsServer.hostAddress!!.removePrefix(\"/\")\n            address = if (Utils.checkIpV4(address))\n                \"${address}:53\"\n            else\n                \"[${address}]:53\"\n            servers = if (servers == \"\")\n                address\n            else\n                \"${servers},${address}\"\n        }\n        return Api.net_use_nameserver(servers)\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/ConnectionService.kt",
    "content": "package com.tutpro.baresip\n\nimport android.content.Intent\nimport android.telecom.CallAudioState\nimport android.telecom.Connection\nimport android.telecom.ConnectionRequest\nimport android.telecom.ConnectionService\nimport android.telecom.DisconnectCause\nimport android.telecom.PhoneAccountHandle\nimport android.telecom.TelecomManager\nimport android.net.Uri\nimport java.util.concurrent.ConcurrentHashMap\n\nclass ConnectionService : ConnectionService() {\n\n    private val TAG = \"BaresipConnection\"\n\n    companion object {\n        val connections = ConcurrentHashMap<Long, BaresipConnection>()\n        var pendingOutgoingConnection: BaresipConnection? = null\n        var lastDisconnectTime = 0L\n\n        fun promoteOutgoingConnection(callp: Long) {\n            pendingOutgoingConnection?.let {\n                it.callp = callp\n                connections[callp] = it\n                pendingOutgoingConnection = null\n            }\n        }\n\n        fun onCallClosed(callp: Long) {\n            connections[callp]?.let {\n                it.setDisconnected(DisconnectCause(DisconnectCause.REMOTE))\n                it.destroy()\n                connections.remove(callp)\n            }\n        }\n    }\n\n    override fun onCreateIncomingConnection(\n        connectionManagerPhoneAccount: PhoneAccountHandle?,\n        request: ConnectionRequest?\n    ): Connection {\n        val extras = request?.extras\n        val uap = extras?.getLong(\"uap\") ?: 0L\n        val callp = extras?.getLong(\"callp\") ?: 0L\n        val peerUri = extras?.getString(\"peerUri\") ?: \"\"\n\n        Log.d(TAG, \"onCreateIncomingConnection for $peerUri\")\n\n        val connection = BaresipConnection(uap, callp)\n        connections[callp] = connection\n        connection.setAddress(Uri.fromParts(\"sip\", peerUri, null), TelecomManager.PRESENTATION_ALLOWED)\n        connection.connectionCapabilities = Connection.CAPABILITY_SUPPORT_HOLD or Connection.CAPABILITY_HOLD\n\n        val call = Call.ofCallp(callp)\n        if (call != null)\n            BaresipService.instance?.handleIncomingCall(call)\n\n        val ua = UserAgent.ofUap(uap)\n        if (ua != null) {\n            if (BaresipService.speakerPhone) {\n                @Suppress(\"DEPRECATION\")\n                connection.setAudioRoute(CallAudioState.ROUTE_SPEAKER)\n            }\n            if (ua.account.answerMode == Api.ANSWERMODE_AUTO) {\n                Log.d(TAG, \"Auto-answering call $callp\")\n                connection.onAnswer()\n            } else {\n                connection.setRinging()\n            }\n        }\n\n        return connection\n    }\n\n    override fun onCreateIncomingConnectionFailed(\n        connectionManagerPhoneAccount: PhoneAccountHandle?,\n        request: ConnectionRequest?\n    ) {\n        Log.e(TAG, \"onCreateIncomingConnectionFailed\")\n    }\n\n    override fun onCreateOutgoingConnection(\n        connectionManagerPhoneAccount: PhoneAccountHandle?,\n        request: ConnectionRequest?\n    ): Connection {\n        val rootExtras = request?.extras\n        val nestedExtras = rootExtras?.getBundle(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS)\n\n        val uap = rootExtras?.getLong(\"uap\", 0L).takeIf { it != 0L }\n            ?: nestedExtras?.getLong(\"uap\") ?: 0L\n\n        val conferenceCall = rootExtras?.getBoolean(\"conferenceCall\", false) ?:\n        nestedExtras?.getBoolean(\"conferenceCall\") ?: false\n\n        val onHoldCallp = rootExtras?.getLong(\"onHoldCallp\", 0L).takeIf { it != 0L }\n            ?: nestedExtras?.getLong(\"onHoldCallp\") ?: 0L\n\n        val destination = request?.address?.schemeSpecificPart ?: \"\"\n\n        Log.d(TAG, \"onCreateOutgoingConnection to $destination (uap=$uap)\")\n\n        val connection = BaresipConnection(uap, 0L)\n        pendingOutgoingConnection = connection\n\n        if (BaresipService.speakerPhone) {\n            @Suppress(\"DEPRECATION\")\n            connection.setAudioRoute(CallAudioState.ROUTE_SPEAKER)\n        }\n\n        connection.setAddress(request?.address, TelecomManager.PRESENTATION_ALLOWED)\n        connection.connectionCapabilities = Connection.CAPABILITY_SUPPORT_HOLD or Connection.CAPABILITY_HOLD\n\n        // Start the SIP connection logic\n        if (uap != 0L) {\n            val sipUri = if (destination.startsWith(\"sip:\")) destination else \"sip:$destination\"\n            BaresipService.instance?.runCall(uap, sipUri, conferenceCall, onHoldCallp)\n        } else {\n            Log.e(TAG, \"Cannot start outgoing call: uap is 0\")\n            connection.setDisconnected(DisconnectCause(DisconnectCause.ERROR, \"No Account\"))\n            connection.destroy()\n            pendingOutgoingConnection = null\n        }\n\n        connection.setDialing()\n        return connection\n    }\n\n    override fun onCreateOutgoingConnectionFailed(\n        connectionManagerPhoneAccount: PhoneAccountHandle?,\n        request: ConnectionRequest?\n    ) {\n        Log.e(TAG, \"onCreateOutgoingConnectionFailed\")\n        pendingOutgoingConnection = null\n    }\n\n    inner class BaresipConnection(val uap: Long, var callp: Long) : Connection() {\n\n        private var isDisconnecting = false\n\n        override fun onAnswer() {\n            Log.d(TAG, \"Telecom Connection onAnswer $callp\")\n            val intent = Intent(this@ConnectionService, MainActivity::class.java)\n            intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK\n            intent.putExtra(\"action\", \"call answer\")\n            intent.putExtra(\"callp\", callp)\n            startActivity(intent)\n            BaresipService.instance?.updateStatusNotification()\n            setActive()\n        }\n\n        override fun onReject() {\n            Log.d(TAG, \"Telecom Connection onReject $callp\")\n            Api.ua_hangup(uap, callp, 486, \"Rejected\")\n            setDisconnected(DisconnectCause(DisconnectCause.REJECTED))\n            connections.remove(callp)\n            destroy()\n        }\n\n        override fun onDisconnect() {\n            if (isDisconnecting) return\n\n            if (System.currentTimeMillis() - lastDisconnectTime < 500) {\n                Log.d(TAG, \"Ignoring cascaded onDisconnect for $callp\")\n                return\n            }\n\n            Log.d(TAG, \"Telecom Connection onDisconnect $callp\")\n            isDisconnecting = true\n            lastDisconnectTime = System.currentTimeMillis()\n\n            if (callp == 0L) {\n                pendingOutgoingConnection = null\n                setDisconnected(DisconnectCause(DisconnectCause.CANCELED))\n                destroy()\n                return\n            }\n            val call = Call.ofCallp(callp)\n            if (call != null)\n                Api.ua_hangup(uap, callp, 0, \"\")\n            setDisconnected(DisconnectCause(DisconnectCause.LOCAL))\n            destroy()\n            // Allow other disconnects after a short period to prevent the \"Telecom Cascade\" effect\n            android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({\n                isDisconnecting = false\n            }, 1000)\n        }\n\n        override fun onAbort() {\n            Log.d(TAG, \"Telecom Connection onAbort $callp\")\n            if (callp != 0L) {\n                Api.ua_hangup(uap, callp, 0, \"\")\n                connections.remove(callp)\n            } else {\n                pendingOutgoingConnection = null\n            }\n            setDisconnected(DisconnectCause(DisconnectCause.CANCELED))\n            destroy()\n        }\n\n        @Deprecated(\"Deprecated in Java\")\n        @Suppress(\"DEPRECATION\")\n        override fun onCallAudioStateChanged(state: CallAudioState?) {\n            super.onCallAudioStateChanged(state)\n            Log.d(TAG, \"onCallAudioStateChanged: $state\")\n            state?.let {\n                if (BaresipService.isMicMuted != it.isMuted) {\n                    BaresipService.isMicMuted = it.isMuted\n                    Api.calls_mute(it.isMuted)\n                    BaresipService.postServiceEvent(\n                        ServiceEvent(\"mic muted,${it.isMuted}\", arrayListOf(uap, callp),\n                            System.nanoTime())\n                    )\n                }\n                val isSpeaker = it.route == CallAudioState.ROUTE_SPEAKER\n                if (BaresipService.speakerPhone != isSpeaker) {\n                    BaresipService.speakerPhone = isSpeaker\n                    BaresipService.postServiceEvent(\n                        ServiceEvent(\"speaker update,${isSpeaker}\",\n                            arrayListOf(uap, callp),\n                            System.nanoTime()\n                        )\n                    )\n                }\n            }\n        }\n\n        override fun onHold() {\n            Log.d(TAG, \"Telecom Connection onHold $callp\")\n            val call = Call.ofCallp(callp)\n            if (call != null && !call.conferenceCall) {\n                // 1. Force SIP Signaling\n                if (Api.call_hold(call.callp, true)) {\n                    // 2. Sync Call object state\n                    call.onhold = true\n                    call.callOnHold.value = true\n                    call.showOnHoldNotice.value = true\n                    // 3. Tell Telecom the move is complete\n                    setOnHold()\n                } else {\n                    Log.e(TAG, \"SIP Hold failed for $callp\")\n                }\n            }\n        }\n\n        override fun onUnhold() {\n            Log.d(TAG, \"Telecom Connection onUnhold $callp\")\n            val call = Call.ofCallp(callp)\n            if (call != null && !call.conferenceCall) {\n                // 1. Force SIP Signaling\n                if (Api.call_hold(call.callp, false)) {\n                    // 2. Sync Call object state\n                    call.onhold = false\n                    call.callOnHold.value = false\n                    call.showOnHoldNotice.value = false\n                    // 3. Tell Telecom we are active\n                    setActive()\n                } else {\n                    Log.e(TAG, \"SIP Resume failed for $callp\")\n                }\n            }\n        }\n\n        override fun onPlayDtmfTone(c: Char) {\n            Log.d(TAG, \"Telecom Connection onPlayDtmfTone $c\")\n            if (callp != 0L) {\n                val call = Call.ofCallp(callp)\n                call?.sendDigit(c)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/Constants.kt",
    "content": "package com.tutpro.baresip\n\nconst val TAG = \"Baresip\"\n\nconst val LOW_CHANNEL_ID = \"com.tutpro.baresip.low\"\nconst val MEDIUM_CHANNEL_ID = \"com.tutpro.baresip.medium\"\nconst val HIGH_CHANNEL_ID = \"com.tutpro.baresip.high\"\n\nconst val STATUS_NOTIFICATION_ID = 101\nconst val CALL_NOTIFICATION_ID = 102\nconst val CALL_MISSED_NOTIFICATION_ID = 103\nconst val TRANSFER_NOTIFICATION_ID = 104\nconst val MESSAGE_NOTIFICATION_ID = 105\n\nconst val STATUS_REQ_CODE = 1\nconst val CALL_REQ_CODE = 2\nconst val ANSWER_REQ_CODE = 3\nconst val REJECT_REQ_CODE = 4\nconst val TRANSFER_REQ_CODE = 5\nconst val ACCEPT_REQ_CODE = 6\nconst val DENY_REQ_CODE = 7\nconst val MESSAGE_REQ_CODE = 8\n// const val REPLY_REQ_CODE = 9\nconst val SAVE_REQ_CODE = 10\nconst val DELETE_REQ_CODE = 11\nconst val DIRECT_REPLY_REQ_CODE = 12\n\nconst val REGISTRATION_INTERVAL = 900\nconst val NO_AUTH_PASS = \"t%Qa?~?J8,~6\"\n\nconst val MESSAGE_DOWN = 2131165304\nconst val MESSAGE_UP = 2131165306\nconst val MESSAGE_UP_FAIL = 2131165307\nconst val MESSAGE_UP_WAIT = 2131165308\n\nconst val CALL_UP_GREEN = 2131165323\nconst val CALL_DOWN_GREEN = 2131165316\nconst val CALL_UP_RED = 2131165324\nconst val CALL_DOWN_BLUE = 2131165315\nconst val CALL_DOWN_RED = 2131165317\nconst val CALL_MISSED_OUT = 2131165320\nconst val CALL_MISSED_IN = 2131165319\n\nval mediaEncMap = mapOf(\"zrtp\" to \"ZRTP\", \"dtls_srtp\" to \"DTLS-SRTPF\", \"srtp-mand\" to \"SRTP-MAND\",\n    \"srtp\" to \"SRTP\", \"\" to \"--\")\n\nval mediaNatMap = mapOf(\"stun\" to \"STUN\", \"turn\" to \"TURN\", \"ice\" to \"ICE\", \"\" to \"--\")\n\n\n\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/Contact.kt",
    "content": "package com.tutpro.baresip\n\nimport android.content.Context\nimport android.database.Cursor\nimport android.graphics.Bitmap\nimport android.graphics.BitmapFactory\nimport android.net.Uri\nimport android.provider.ContactsContract\nimport androidx.core.net.toUri\nimport com.tutpro.baresip.BaresipService.Companion.contactNames\nimport java.io.File\nimport java.util.ArrayList\n\nsealed class Contact {\n\n    class BaresipContact(var name: String, var uri: String, var color: Int, var id: Long,\n                         var favorite: Boolean): Contact() {\n        var avatarImage: Bitmap? = null\n    }\n\n    class AndroidContact(var name: String, var color: Int, var thumbnailUri: Uri?,\n                         var id: Long, var favorite: Boolean): Contact() {\n        val uris = ArrayList<String>()\n    }\n\n    fun name(): String {\n        return when (this) {\n            is AndroidContact -> name\n            is BaresipContact -> name\n        }\n    }\n\n    fun id(): Long {\n        return when (this) {\n            is AndroidContact -> id\n            is BaresipContact -> id\n        }\n    }\n\n    fun favorite(): Boolean {\n        return when (this) {\n            is AndroidContact -> favorite\n            is BaresipContact -> favorite\n        }\n    }\n\n    fun color(): String {\n        val intColor = when (this) {\n            is AndroidContact -> color\n            is BaresipContact -> color\n        }\n        // Mask with 0xFFFFFF to ensure we get the standard 6-character hex code (RRGGBB)\n        // ignoring the alpha channel for compatibility with simple string parsers.\n        return String.format(\"#%06X\", 0xFFFFFF and intColor)\n    }\n\n    fun copy(): Contact {\n        val copy = when (this) {\n            is BaresipContact ->\n                BaresipContact(name, uri, color, id, favorite)\n            is AndroidContact ->\n                AndroidContact(name, color, thumbnailUri, id, favorite)\n        }\n        when (this) {\n            is BaresipContact ->\n                (copy as BaresipContact).avatarImage = this.avatarImage\n            is AndroidContact ->\n                (copy as AndroidContact).uris.addAll(this.uris)\n        }\n        return copy\n    }\n\n    companion object {\n\n        // Return contact name of uri or uri itself if contact with uri is not found\n        fun contactName(uri: String): String {\n            var contact = findContact(uri)\n            if (contact == null) {\n                val userPart = Utils.uriUserPart(uri)\n                if (Utils.isTelNumber(userPart))\n                    contact = findContact(\"tel:$userPart\")\n            }\n            if (contact != null)\n                return contact.name()\n            return uri\n        }\n\n        fun baresipContact(name: String): BaresipContact? {\n            for (c in BaresipService.baresipContacts.value)\n                if (c.name == name)\n                    return c\n            return null\n        }\n\n        fun androidContact(name: String): AndroidContact? {\n            for (c in BaresipService.androidContacts.value)\n                if (c.name == name)\n                    return c\n            return null\n        }\n\n        // Return URIs of contact name\n        fun contactUris(name: String): ArrayList<String> {\n            val uris = ArrayList<String>()\n            for (c in BaresipService.contacts)\n                when (c) {\n                    is BaresipContact -> {\n                        if (c.name.equals(name, ignoreCase = true)) {\n                            uris.add(c.uri.removePrefix(\"<\")\n                                .replaceAfter(\">\", \"\")\n                                .replace(\">\", \"\"))\n                            return uris\n                        }\n                    }\n                    is AndroidContact -> {\n                        if (c.name == name) {\n                            for (u in c.uris)\n                                uris.add(u)\n                            return uris\n                        }\n                    }\n                }\n            return uris\n        }\n\n        fun findContact(uri: String): Contact? {\n            for (c in BaresipService.contacts)\n                when (c) {\n                    is BaresipContact -> {\n                        if (Utils.uriMatch(c.uri, uri))\n                            return c\n                    }\n                    is AndroidContact -> {\n                        val cleanUri = uri.filterNot{setOf('-', ' ', '(', ')').contains(it)}\n                        for (u in c.uris)\n                            if (Utils.uriMatch(u.filterNot{setOf('-', ' ', '(', ')').contains(it)},\n                                    cleanUri))\n                                return c\n                    }\n                }\n            return null\n        }\n\n        fun nameExists(name: String, list: List<Contact>, ignoreCase: Boolean): Boolean {\n            for (c in list)\n                if (c.name().equals(name, ignoreCase = ignoreCase))\n                    return true\n            return false\n        }\n\n        fun saveBaresipContacts() {\n            val avatarFiles = avatarFileNames()\n            var contents = \"\"\n            for (c in BaresipService.baresipContacts.value) {\n                contents += \"\\\"${c.name}\\\" <${c.uri}>;id=${c.id};color=${c.color}\" +\n                        \";favorite=${if (c.favorite) \"yes\" else \"no\"}\\n\"\n                avatarFiles.remove(c.id.toString() + \".png\")\n            }\n            Utils.putFileContents(BaresipService.filesPath + \"/contacts\",\n                    contents.toByteArray())\n            for (f in avatarFiles)\n                File(BaresipService.filesPath + \"/\" + f).delete()\n        }\n\n        fun loadAndroidContacts(ctx: Context) {\n            // If phone type is needed, add DATA2 to projection. Then phone type can be get from\n            // cursor using getColumnIndex(ContactsContract.CommonDataKinds.Phone.TYPE\n            val projection = arrayOf(ContactsContract.Data.CONTACT_ID, ContactsContract.Data.DISPLAY_NAME,\n                ContactsContract.Data.MIMETYPE, ContactsContract.Data.DATA1,\n                /* ContactsContract.Data.DATA2 ,*/ ContactsContract.Data.PHOTO_THUMBNAIL_URI,\n                ContactsContract.Contacts.STARRED)\n            val selection =\n                ContactsContract.Data.MIMETYPE + \"='\" + ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE + \"' OR \" +\n                        ContactsContract.Data.MIMETYPE + \"='\" + ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE + \"'\"\n            val cur: Cursor? = ctx.contentResolver.query(ContactsContract.Data.CONTENT_URI, projection,\n                    selection, null, null)\n            BaresipService.androidContacts.value = listOf()\n            val contacts = HashMap<Long, AndroidContact>()\n            while (cur != null && cur.moveToNext()) {\n                val id = cur.getLong(0)\n                val name = cur.getString(1) ?: \"\"\n                val mime = cur.getString(2)\n                val data = cur.getString(3)\n                val thumb = cur.getString(4)?.toUri()\n                val starred = cur.getInt(5)\n                val contact = if (contacts.containsKey(id))\n                    contacts[id]!!\n                else\n                    AndroidContact(name, Utils.randomColor(), thumb, id, starred == 1)\n                if (contact.name == \"\" && name != \"\")\n                    contact.name = name\n                if (contact.thumbnailUri == null &&  thumb != null)\n                    contact.thumbnailUri = thumb\n                if (mime == ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) {\n                    val uri = \"tel:${data.filterNot { setOf('-', ' ', '(', ')').contains(it) }}\"\n                    if (uri !in contact.uris)\n                        contact.uris.add(uri)\n                    // contact.types.add(typeToString(cur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.TYPE)))\n                }\n                else if (mime == ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE)\n                    contact.uris.add(\"sip:$data\")\n                else\n                    continue\n                if (!contacts.containsKey(id))\n                    contacts[id] = contact\n            }\n            cur?.close()\n            val newList = mutableListOf<AndroidContact>()\n            for ((_, value) in contacts)\n                if (value.name != \"\" && value.uris.isNotEmpty())\n                    newList.add(value)\n            BaresipService.androidContacts.value = newList.toList()\n        }\n\n        @Suppress(\"unused\")\n        private fun typeToString(type: Int): String {\n            return when(type) {\n                ContactsContract.CommonDataKinds.Phone.TYPE_HOME -> \"Home\"\n                ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE -> \"Mobile\"\n                ContactsContract.CommonDataKinds.Phone.TYPE_WORK -> \"Work\"\n                else -> \"Unknown\"\n            }\n        }\n\n        fun restoreBaresipContacts(): Boolean {\n            val content = Utils.getFileContents(BaresipService.filesPath + \"/contacts\")\n                    ?: return false\n            val contacts = String(content)\n            var contactNo = 0\n            val baseId = System.currentTimeMillis()\n            BaresipService.baresipContacts.value = mutableListOf()\n            contacts.lines().forEach {\n                val parts = it.split(\"\\\"\")\n                if (parts.size == 3) {\n                    contactNo++\n                    val name = parts[1]\n                    val uriParams = parts[2].trim()\n                    val uri = uriParams.substringAfter(\"<\").substringBefore(\">\")\n                    val params = uriParams.substringAfter(\">;\")\n                    val colorValue = Utils.paramValue(params, \"color\" )\n                    val color: Int = if (colorValue != \"\")\n                        colorValue.toInt()\n                    else\n                        Utils.randomColor()\n                    val idValue = Utils.paramValue(params, \"id\" )\n                    val id: Long = if (idValue != \"\")\n                        idValue.toLong()\n                    else\n                        baseId + contactNo\n                    val favorite = Utils.paramValue(params, \"favorite\" ) == \"yes\"\n                    Log.d(TAG, \"Restoring contact $name, $uri, $color, $id\")\n                    val contact = BaresipContact(name, uri, color, id, favorite)\n                    val avatarFilePath = BaresipService.filesPath + \"/$id.png\"\n                    if (File(avatarFilePath).exists()) {\n                        try {\n                            contact.avatarImage = BitmapFactory.decodeFile(avatarFilePath)\n                            Log.d(TAG, \"Set avatarImage\")\n                            if (contact.avatarImage == null)\n                                Log.d(TAG, \"Contact $id avatarImage is null\")\n                        } catch (e: Exception) {\n                            Log.e(TAG, \"Could not read avatar image from file $id.png: ${e.message}\")\n                        }\n                    }\n                    BaresipService.baresipContacts.value += contact\n                }\n            }\n            return true\n        }\n\n        fun contactsUpdate() {\n            BaresipService.contacts = mutableListOf()\n            if (BaresipService.contactsMode != \"android\")\n                for (c in BaresipService.baresipContacts.value)\n                    BaresipService.contacts.add(c.copy())\n            if (BaresipService.contactsMode != \"baresip\")\n                for (c in BaresipService.androidContacts.value)\n                    if (!nameExists(c.name, BaresipService.contacts, true))\n                        BaresipService.contacts.add(c.copy())\n            BaresipService.contacts.sortBy{ when (it) {\n                is BaresipContact -> if (it.favorite) \"0\" + it.name else \"1\" + it.name\n                is AndroidContact -> if (it.favorite) \"0\" + it.name else \"1\" + it.name\n            }}\n            generateContactNames()\n        }\n\n        fun addBaresipContact(contact: BaresipContact) {\n            BaresipService.baresipContacts.value += contact\n            saveBaresipContacts()\n            contactsUpdate()\n        }\n\n        fun updateBaresipContact(id: Long, contact: BaresipContact) {\n            val updatedContacts = BaresipService.baresipContacts.value.toMutableList()\n            updatedContacts.removeIf { it.id == id }\n            updatedContacts.add(contact)\n            BaresipService.baresipContacts.value = updatedContacts.toList()\n            saveBaresipContacts()\n            contactsUpdate()\n        }\n\n        fun removeBaresipContact(contact: BaresipContact) {\n            val removed = BaresipService.baresipContacts.value.toMutableList()\n            removed.removeIf { it.id == contact.id }\n            BaresipService.baresipContacts.value = removed.toList()\n            saveBaresipContacts()\n            contactsUpdate()\n        }\n\n        private fun generateContactNames () {\n            val newList = mutableListOf<String>()\n            for (c in BaresipService.contacts)\n                when (c) {\n                    is BaresipContact ->\n                        newList.add(c.name)\n                    is AndroidContact ->\n                        newList.add(c.name)\n                }\n            contactNames.value = newList.toList()\n        }\n\n        private fun avatarFileNames(): MutableList<String> {\n            return File(BaresipService.filesPath).list()!!.filter{ it.endsWith(\".png\")}.toMutableList()\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/ContactsScreen.kt",
    "content": "package com.tutpro.baresip\n\nimport android.content.Context\nimport android.content.Intent\nimport android.provider.ContactsContract\nimport androidx.compose.foundation.ExperimentalFoundationApi\nimport androidx.compose.foundation.Image\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.combinedClickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.asPaddingValues\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.imePadding\nimport androidx.compose.foundation.layout.navigationBarsPadding\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.statusBars\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.lazy.rememberLazyListState\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.foundation.text.KeyboardOptions\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.filled.Add\nimport androidx.compose.material.icons.filled.Edit\nimport androidx.compose.material.icons.outlined.Clear\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.OutlinedTextField\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.SmallFloatingActionButton\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.TopAppBarDefaults\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.graphics.asImageBitmap\nimport androidx.compose.ui.layout.ContentScale\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalSoftwareKeyboardController\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.TextStyle\nimport androidx.compose.ui.text.buildAnnotatedString\nimport androidx.compose.ui.text.font.FontStyle\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.input.ImeAction\nimport androidx.compose.ui.text.input.KeyboardType\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.navigation.NavController\nimport androidx.navigation.NavGraphBuilder\nimport androidx.navigation.compose.composable\nimport coil.compose.AsyncImage\nimport com.tutpro.baresip.CustomElements.AlertDialog\nimport com.tutpro.baresip.CustomElements.TextAvatar\nimport com.tutpro.baresip.CustomElements.verticalScrollbar\nimport java.io.File\nimport java.io.IOException\n\nconst val avatarSize: Int = 96\n\nfun NavGraphBuilder.contactsScreenRoute(\n    navController: NavController,\n    viewModel: ViewModel\n) {\n    composable(\"contacts\") { _ ->\n        ContactsScreen(navController = navController, viewModel = viewModel)\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun ContactsScreen(\n    navController: NavController,\n    viewModel: ViewModel\n) {\n\n    val ctx = LocalContext.current\n    var searchContactName by remember { mutableStateOf(\"\") }\n\n    Scaffold(\n        modifier = Modifier.fillMaxSize().imePadding(),\n        containerColor = MaterialTheme.colorScheme.background,\n        topBar = {\n            Column(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .background(MaterialTheme.colorScheme.background)\n                    .padding(\n                        top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()\n                    )\n            ) {\n                TopAppBar(\n                    title = {\n                        Text(\n                            text = stringResource(R.string.contacts),\n                            fontWeight = FontWeight.Bold\n                        )\n                    },\n                    colors = TopAppBarDefaults.topAppBarColors(\n                        containerColor = MaterialTheme.colorScheme.primary,\n                        navigationIconContentColor = MaterialTheme.colorScheme.onPrimary,\n                        titleContentColor = MaterialTheme.colorScheme.onPrimary,\n                    ),\n                    navigationIcon = {\n                        IconButton(\n                            onClick = { navController.navigateUp() }\n                        ) {\n                            Icon(\n                                imageVector = Icons.AutoMirrored.Filled.ArrowBack,\n                                contentDescription = \"Back\",\n                            )\n                        }\n                    },\n                    windowInsets = WindowInsets(0, 0, 0, 0),\n                )\n            }\n        },\n        bottomBar = {\n            BottomBar(\n                navController = navController,\n                searchContactName = searchContactName,\n                onSearchContactNameChange = { searchContactName = it }\n            )\n        },\n        content = { contentPadding ->\n            ContactsContent(ctx, viewModel, navController, contentPadding, searchContactName)\n        }\n    )\n}\n\n@Composable\nprivate fun BottomBar(\n    navController: NavController,\n    searchContactName: String,\n    onSearchContactNameChange: (String) -> Unit\n) {\n    val keyboardController = LocalSoftwareKeyboardController.current\n\n    Row(\n        modifier = Modifier\n            .fillMaxWidth()\n            .navigationBarsPadding()\n            .padding(start = 16.dp, end = 16.dp, bottom = 16.dp),\n        verticalAlignment = Alignment.CenterVertically,\n        horizontalArrangement = Arrangement.spacedBy(8.dp)\n    ) {\n        OutlinedTextField(\n            value = searchContactName,\n            onValueChange = {\n                onSearchContactNameChange(it)\n                if (it.isBlank()) {\n                    keyboardController?.hide()\n                }\n            },\n            modifier = Modifier.weight(1f),\n            singleLine = true,\n            trailingIcon = {\n                if (searchContactName.isNotEmpty())\n                    Icon(\n                        Icons.Outlined.Clear,\n                        contentDescription = null,\n                        modifier = Modifier.clickable {\n                            onSearchContactNameChange(\"\")\n                            keyboardController?.hide()\n                        },\n                        tint = MaterialTheme.colorScheme.onSurfaceVariant\n                    )\n            },\n            label = { Text(stringResource(R.string.search)) },\n            textStyle = TextStyle(fontSize = 18.sp),\n            keyboardOptions = KeyboardOptions(\n                keyboardType = KeyboardType.Text,\n                imeAction = ImeAction.Done\n            )\n        )\n        SmallFloatingActionButton(\n            onClick = { navController.navigate(\"baresip_contact//new\") },\n            containerColor = MaterialTheme.colorScheme.secondary,\n            contentColor = MaterialTheme.colorScheme.onSecondary\n        ) {\n            Icon(\n                imageVector = Icons.Filled.Add,\n                modifier = Modifier.size(36.dp),\n                contentDescription = stringResource(R.string.add)\n            )\n        }\n    }\n}\n\n@OptIn(ExperimentalFoundationApi::class)\n@Composable\nprivate fun ContactsContent(\n    ctx: Context,\n    viewModel: ViewModel,\n    navController: NavController,\n    contentPadding: PaddingValues,\n    searchQuery: String\n) {\n    val showDialog = remember { mutableStateOf(false) }\n    val dialogMessage = remember { mutableStateOf(\"\") }\n    val secondText = remember { mutableStateOf(\"\") }\n    val secondAction = remember { mutableStateOf({}) }\n    val lastText = remember { mutableStateOf(\"\") }\n    val lastAction = remember { mutableStateOf({}) }\n\n    if (showDialog.value)\n        AlertDialog(\n            showDialog = showDialog,\n            title = stringResource(R.string.confirmation),\n            message = dialogMessage.value,\n            firstButtonText = stringResource(R.string.cancel),\n            secondButtonText = secondText.value,\n            onSecondClicked = secondAction.value,\n            lastButtonText = lastText.value,\n            onLastClicked = lastAction.value,\n        )\n\n    val lazyListState = rememberLazyListState()\n\n    LaunchedEffect(searchQuery) {\n        if (searchQuery.isBlank()) {\n            lazyListState.scrollToItem(0)\n        }\n    }\n\n    val filteredContacts = remember(BaresipService.contacts, searchQuery) {\n        if (searchQuery.isBlank()) {\n            BaresipService.contacts.map { contact ->\n                Pair(contact, buildAnnotatedString { append(contact.name()) })\n            }\n        } else {\n            val normalizedQuery = Utils.unaccent(searchQuery)\n            BaresipService.contacts\n                .filter { contact ->\n                    Utils.unaccent(contact.name()).contains(normalizedQuery, ignoreCase = true)\n                }\n                .map { contact ->\n                    Pair(contact, Utils.buildAnnotatedStringWithHighlight(contact.name(), searchQuery))\n                }\n        }\n    }\n\n    LazyColumn(\n        modifier = Modifier\n            .fillMaxWidth()\n            .background(MaterialTheme.colorScheme.background)\n            .padding(contentPadding)\n            .padding(start = 16.dp, end = 4.dp, top = 16.dp, bottom = 10.dp)\n            .verticalScrollbar(state = lazyListState),\n        state = lazyListState,\n        verticalArrangement = Arrangement.spacedBy(10.dp),\n    ) {\n        items(filteredContacts, key = { it.first.id() }) { (contact, annotatedName) ->\n\n            Row(\n                verticalAlignment = Alignment.CenterVertically,\n                horizontalArrangement = Arrangement.Start\n            ) {\n                when (contact) {\n\n                    is Contact.BaresipContact -> {\n                        val avatarImage = contact.avatarImage\n                        if (avatarImage != null)\n                            Image(\n                                bitmap = avatarImage.asImageBitmap(),\n                                contentDescription = \"Avatar\",\n                                contentScale = ContentScale.Crop,\n                                modifier = Modifier\n                                    .size(36.dp)\n                                    .clip(CircleShape)\n                            )\n                        else\n                            TextAvatar(contact.name(), contact.color)\n                    }\n\n                    is Contact.AndroidContact -> {\n                        val thumbNailUri = contact.thumbnailUri\n                        if (thumbNailUri != null)\n                            AsyncImage(\n                                model = thumbNailUri,\n                                contentDescription = \"Avatar\",\n                                contentScale = ContentScale.Crop,\n                                modifier = Modifier\n                                    .size(36.dp)\n                                    .clip(CircleShape),\n                            )\n                        else\n                            TextAvatar(contact.name(), contact.color)\n                    }\n                }\n\n                when (contact) {\n\n                    is Contact.BaresipContact -> {\n                        Text(text = annotatedName,\n                            fontSize = 20.sp,\n                            fontStyle = if (contact.favorite()) FontStyle.Italic else FontStyle.Normal,\n                            modifier = Modifier\n                                .weight(1f)\n                                .padding(start = 10.dp)\n                                .combinedClickable(\n                                    onClick = {\n                                        val aor = viewModel.selectedAor.value\n                                        val ua = UserAgent.ofAor(aor)\n                                        val intent = Intent(ctx, MainActivity::class.java)\n                                        if (ua != null) {\n                                            intent.putExtra(\"uap\", ua.uap)\n                                            intent.putExtra(\"peer\", contact.uri)\n                                        }\n                                        else\n                                            Log.w(TAG, \"onClickListener did not find UA for $aor\")\n                                        dialogMessage.value = String.format(\n                                            ctx.getString(R.string.contact_action_question),\n                                            contact.name()\n                                        )\n                                        secondText.value = ctx.getString(R.string.call)\n                                        secondAction.value = {\n                                            if (ua != null) {\n                                                handleIntent(ctx, viewModel, intent, \"call\")\n                                                navController.navigate(\"main\") {\n                                                    popUpTo(\"main\")\n                                                    launchSingleTop = true\n                                                }\n                                            }\n                                        }\n                                        lastText.value = ctx.getString(R.string.send_message)\n                                        lastAction.value = {\n                                            if (ua != null) {\n                                                handleIntent(ctx, viewModel, intent, \"message\")\n                                                navController.navigateUp()\n                                            }\n                                        }\n                                        showDialog.value = true\n                                    },\n                                    onLongClick = {\n                                        dialogMessage.value = String.format(\n                                            ctx.getString(R.string.contact_delete_question),\n                                            contact.name()\n                                        )\n                                        secondText.value = \"\"\n                                        lastText.value = ctx.getString(R.string.delete)\n                                        lastAction.value = {\n                                            val id = contact.id\n                                            val avatarFile = File(\n                                                BaresipService.filesPath,\n                                                \"$id.png\"\n                                            )\n                                            if (avatarFile.exists()) {\n                                                try {\n                                                    avatarFile.delete()\n                                                } catch (e: IOException) {\n                                                    Log.e(\n                                                        TAG,\n                                                        \"Could not delete file $id.png: ${e.message}\"\n                                                    )\n                                                }\n                                            }\n                                            Contact.removeBaresipContact(contact)\n                                        }\n                                        showDialog.value = true\n                                    }\n                                )\n                        )\n                        SmallFloatingActionButton(\n                            modifier = Modifier.padding(end = 10.dp),\n                            onClick = { navController.navigate(\"baresip_contact/${contact.name()}/old\") },\n                            containerColor = MaterialTheme.colorScheme.tertiaryContainer,\n                            contentColor = MaterialTheme.colorScheme.onTertiaryContainer\n                        ) {\n                            Icon(\n                                imageVector = Icons.Filled.Edit,\n                                modifier = Modifier.size(28.dp),\n                                contentDescription = stringResource(R.string.edit)\n                            )\n                        }\n                    }\n\n                    is Contact.AndroidContact -> {\n                        Text(text = annotatedName,\n                            fontSize = 20.sp,\n                            fontStyle = if (contact.favorite()) FontStyle.Italic else FontStyle.Normal,\n                            modifier = Modifier\n                                .weight(1f)\n                                .padding(start = 10.dp, top = 4.dp, bottom = 4.dp)\n                                .combinedClickable(\n                                    onClick = { navController.navigate(\"android_contact/${contact.name()}\") },\n                                    onLongClick = {\n                                        dialogMessage.value = String.format(\n                                            ctx.getString(R.string.contact_delete_question),\n                                            contact.name()\n                                        )\n                                        secondText.value = \"\"\n                                        lastText.value = ctx.getString(R.string.delete)\n                                        lastAction.value = {\n                                            ctx.contentResolver.delete(\n                                                ContactsContract.RawContacts.CONTENT_URI,\n                                                ContactsContract.Contacts.DISPLAY_NAME + \"='\" + contact.name() + \"'\",\n                                                null\n                                            )\n                                        }\n                                        showDialog.value = true\n                                    }\n                                )\n                        )\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/CustomElements.kt",
    "content": "package com.tutpro.baresip\n\nimport android.content.Context\nimport android.graphics.Bitmap\nimport android.widget.Toast\nimport androidx.compose.animation.core.animateFloatAsState\nimport androidx.compose.animation.core.tween\nimport androidx.compose.foundation.BorderStroke\nimport androidx.compose.foundation.Canvas\nimport androidx.compose.foundation.Image\nimport androidx.compose.foundation.ScrollState\nimport androidx.compose.foundation.gestures.detectTapGestures\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.RowScope\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.LazyListState\nimport androidx.compose.foundation.lazy.itemsIndexed\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.foundation.text.KeyboardOptions\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.Visibility\nimport androidx.compose.material.icons.filled.VisibilityOff\nimport androidx.compose.material3.BasicAlertDialog\nimport androidx.compose.material3.ButtonDefaults\nimport androidx.compose.material3.Card\nimport androidx.compose.material3.CardDefaults\nimport androidx.compose.material3.DropdownMenu\nimport androidx.compose.material3.DropdownMenuItem\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.HorizontalDivider\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.OutlinedTextField\nimport androidx.compose.material3.OutlinedTextFieldDefaults\nimport androidx.compose.material3.Surface\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.MutableState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.draw.drawWithContent\nimport androidx.compose.ui.focus.FocusRequester\nimport androidx.compose.ui.focus.focusRequester\nimport androidx.compose.ui.geometry.CornerRadius\nimport androidx.compose.ui.geometry.Offset\nimport androidx.compose.ui.geometry.Size\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.Shape\nimport androidx.compose.ui.graphics.SolidColor\nimport androidx.compose.ui.graphics.asImageBitmap\nimport androidx.compose.ui.input.pointer.pointerInput\nimport androidx.compose.ui.layout.ContentScale\nimport androidx.compose.ui.platform.SoftwareKeyboardController\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.TextStyle\nimport androidx.compose.ui.text.input.KeyboardType\nimport androidx.compose.ui.text.input.PasswordVisualTransformation\nimport androidx.compose.ui.text.input.VisualTransformation\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.unit.Dp\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.compose.ui.window.DialogProperties\n\nobject CustomElements {\n\n    @Composable\n    fun Button(\n        onClick: () -> Unit,\n        onLongClick: () -> Unit,\n        modifier: Modifier = Modifier,\n        shape: Shape,\n        border: BorderStroke? = null,\n        color: Color,\n        content: @Composable RowScope.() -> Unit\n    ) {\n        Surface(\n            shape = shape,\n            color = color,\n            border = border,\n            modifier = modifier\n                .pointerInput(Unit) {\n                    detectTapGestures(\n                        onTap = { onClick() },\n                        onLongPress = { onLongClick() },\n                    )\n                }\n                .then(modifier),\n        ) {\n            Row(\n                modifier = Modifier.padding(ButtonDefaults.ContentPadding),\n                verticalAlignment = Alignment.CenterVertically,\n                content = content\n            )\n        }\n    }\n\n    @Composable\n    fun DropdownMenu(\n        expanded: Boolean,\n        onDismissRequest: () -> Unit,\n        items: List<String>,\n        onItemClick: (String) -> Unit\n    ) {\n        DropdownMenu(\n            expanded = expanded,\n            onDismissRequest = onDismissRequest,\n            containerColor = MaterialTheme.colorScheme.surfaceVariant\n        ) {\n            val itemsIterator = items.iterator()\n            while (itemsIterator.hasNext()) {\n                val item = itemsIterator.next()\n                DropdownMenuItem(\n                    text = {\n                        Text(\n                            text = item,\n                            color = MaterialTheme.colorScheme.onSurfaceVariant,\n                            fontSize = 16.sp\n                        ) },\n                    onClick = { onItemClick(item) }\n                )\n                if (itemsIterator.hasNext())\n                    HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant)\n            }\n        }\n    }\n\n    @Composable\n    fun TextAvatar(name: String, color: Int) {\n        Box(\n            modifier = Modifier.size(36.dp),\n            contentAlignment = Alignment.Center\n        ) {\n            Canvas(modifier = Modifier.fillMaxSize()) {\n                drawCircle(SolidColor(Color(color)))\n            }\n            val text = if (name == \"\") \"\" else name[0].toString()\n            Text(text, color = Color.White, fontSize = 20.sp)\n        }\n    }\n\n    @Composable\n    fun ImageAvatar(bitmap: Bitmap) {\n        Image(\n            bitmap = bitmap.asImageBitmap(),\n            contentDescription = \"Avatar\",\n            contentScale = ContentScale.Crop,\n            modifier = Modifier\n                .size(36.dp)\n                .clip(CircleShape)\n        )\n    }\n\n    @Composable\n    fun Modifier.verticalScrollbar(\n        state: ScrollState,\n        scrollbarWidth: Dp = 4.dp,\n        alwaysShow: Boolean = true,\n        color: Color = MaterialTheme.colorScheme.outlineVariant\n    ): Modifier {\n        val alpha by animateFloatAsState(\n            targetValue = if(state.isScrollInProgress || alwaysShow) 1f else 0f,\n            animationSpec = tween(400, delayMillis = if(state.isScrollInProgress) 0 else 700),\n            label = \"scrollbarAlpha\"\n        )\n        return this then Modifier.drawWithContent {\n            drawContent()\n\n            val viewHeight = state.viewportSize.toFloat()\n            if (viewHeight <= 0f) return@drawWithContent // Safety check for zero height\n\n            val contentHeight = state.maxValue + viewHeight\n            val minHeight = 10.dp.toPx()\n\n            // Ensure the 'max' of coerceIn is at least as large as 'min'\n            val scrollbarHeight = (viewHeight * (viewHeight / contentHeight))\n                .coerceIn(minHeight.coerceAtMost(viewHeight) .. viewHeight)\n\n            val variableZone = viewHeight - scrollbarHeight\n\n            // Prevent division by zero if maxValue is 0 (no scrolling needed)\n            val scrollbarOffsetY = if (state.maxValue > 0)\n                (state.value.toFloat() / state.maxValue) * variableZone\n            else\n                0f\n\n            drawRoundRect(\n                cornerRadius = CornerRadius(scrollbarWidth.toPx() / 2, scrollbarWidth.toPx() / 2),\n                color = color,\n                topLeft = Offset(this.size.width - scrollbarWidth.toPx(), scrollbarOffsetY),\n                size = Size(scrollbarWidth.toPx(), scrollbarHeight),\n                alpha = alpha\n            )\n        }\n    }\n\n    @Composable\n    fun Modifier.verticalScrollbar(\n        state: LazyListState,\n        width: Dp = 4.dp,\n        alwaysShow: Boolean = true,\n        color: Color = MaterialTheme.colorScheme.outlineVariant\n    ): Modifier {\n        val alpha by animateFloatAsState(\n            targetValue = if (state.isScrollInProgress || alwaysShow) 1f else 0f,\n            animationSpec = tween(durationMillis = if (state.isScrollInProgress) 150 else 500),\n            label = \"lazyScrollbarAlpha\"\n        )\n        return this.drawWithContent {\n            drawContent()\n\n            val totalItems = state.layoutInfo.totalItemsCount\n            val visibleItemsInfo = state.layoutInfo.visibleItemsInfo\n\n            // Check if there are items and if they actually exceed the viewport\n            if (totalItems > 0 && visibleItemsInfo.isNotEmpty()) {\n                val firstVisibleElementIndex = visibleItemsInfo.first().index\n                val needDrawScrollbar = state.isScrollInProgress || alpha > 0.0f\n\n                if (needDrawScrollbar) {\n                    val elementHeight = this.size.height / totalItems\n                    val scrollbarOffsetY = firstVisibleElementIndex * elementHeight\n                    val scrollbarHeight = visibleItemsInfo.size * elementHeight\n\n                    // Only draw if the scrollbar is actually smaller than the track\n                    if (scrollbarHeight < this.size.height)\n                        drawRoundRect(\n                            cornerRadius = CornerRadius(width.toPx() / 2, width.toPx() / 2),\n                            color = color,\n                            topLeft = Offset(this.size.width - width.toPx(), scrollbarOffsetY),\n                            size = Size(width.toPx(), scrollbarHeight),\n                            alpha = alpha\n                        )\n                }\n            }\n        }\n    }\n\n    @OptIn(ExperimentalMaterial3Api::class)\n    @Composable\n    fun AlertDialog(\n        showDialog: MutableState<Boolean>,\n        title: String,\n        message: String,\n        firstButtonText: String = \"\",\n        onFirstClicked: () -> Unit = {},\n        secondButtonText: String = \"\",\n        onSecondClicked: () -> Unit = {},\n        thirdButtonText: String = \"\",\n        onThirdClicked: () -> Unit = {},\n        lastButtonText: String = \"\",\n        onLastClicked: () -> Unit = {}\n    ) {\n        if (showDialog.value) {\n            BasicAlertDialog(\n                onDismissRequest = { showDialog.value = false },\n                content = {\n                    Card(\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .padding(top = 16.dp, start = 16.dp, end = 16.dp, bottom = 0.dp),\n                        shape = RoundedCornerShape(16.dp),\n                        colors = CardDefaults.cardColors(\n                            containerColor = MaterialTheme.colorScheme.surfaceVariant\n                        )\n                    ) {\n                        Column(modifier = Modifier.padding(16.dp)) {\n                            Text(\n                                text = title,\n                                modifier = Modifier.fillMaxWidth(),\n                                textAlign = TextAlign.Start,\n                                fontSize = 20.sp,\n                                color = MaterialTheme.colorScheme.onSurface\n                            )\n                            Spacer(modifier = Modifier.height(16.dp))\n                            Text(\n                                text = message,\n                                modifier = Modifier.fillMaxWidth(),\n                                textAlign = TextAlign.Start,\n                                fontSize = 16.sp,\n                                color = MaterialTheme.colorScheme.onSurfaceVariant\n                            )\n                            Spacer(modifier = Modifier.height(16.dp))\n\n                            if (lastButtonText.isNotEmpty()) {\n\n                                val buttonCount = listOf(\n                                    firstButtonText, secondButtonText, thirdButtonText, lastButtonText\n                                ).count { it.isNotEmpty() }\n\n                                if (buttonCount >= 3) {\n                                    // Use a Column for 3-4 buttons, aligned to the end (right)\n                                    Column(\n                                        modifier = Modifier.fillMaxWidth(),\n                                        horizontalAlignment = Alignment.End\n                                    ) {\n                                        TextButton(onClick = {\n                                            onFirstClicked()\n                                            showDialog.value = false\n                                        }) {\n                                            Text(\n                                                text = firstButtonText.uppercase(),\n                                                fontSize = 14.sp,\n                                                color = MaterialTheme.colorScheme.onSurfaceVariant\n                                            )\n                                        }\n                                        TextButton(onClick = {\n                                            onSecondClicked()\n                                            showDialog.value = false\n                                        }) {\n                                            Text(\n                                                text = secondButtonText.uppercase(),\n                                                fontSize = 14.sp,\n                                                color = MaterialTheme.colorScheme.primary,\n                                            )\n                                        }\n                                        if (thirdButtonText.isNotEmpty())\n                                            TextButton(onClick = {\n                                                onThirdClicked()\n                                                showDialog.value = false\n                                            }) {\n                                                Text(\n                                                    text = thirdButtonText.uppercase(),\n                                                    fontSize = 14.sp,\n                                                    color = MaterialTheme.colorScheme.primary,\n                                                )\n                                            }\n                                        TextButton(onClick = {\n                                            onLastClicked()\n                                            showDialog.value = false\n                                        }) {\n                                            Text(\n                                                text = lastButtonText.uppercase(),\n                                                fontSize = 14.sp,\n                                                color = MaterialTheme.colorScheme.primary\n                                            )\n                                        }\n                                    }\n                                } else {\n                                    // Use the existing Row for 1 or 2 buttons\n                                    Row(\n                                        modifier = Modifier.fillMaxWidth(),\n                                        horizontalArrangement = Arrangement.End\n                                    ) {\n                                        if (firstButtonText.isNotEmpty())\n                                            TextButton(onClick = {\n                                                onFirstClicked()\n                                                showDialog.value = false\n                                            }) {\n                                                Text(\n                                                    text = firstButtonText.uppercase(),\n                                                    fontSize = 14.sp,\n                                                    color = MaterialTheme.colorScheme.onSurfaceVariant\n                                                )\n                                            }\n                                        if (secondButtonText.isNotEmpty())\n                                            TextButton(onClick = {\n                                                onSecondClicked()\n                                                showDialog.value = false\n                                            }) {\n                                                Text(\n                                                    text = secondButtonText.uppercase(),\n                                                    fontSize = 14.sp,\n                                                    color = MaterialTheme.colorScheme.primary\n                                                )\n                                            }\n                                        TextButton(onClick = {\n                                            onLastClicked()\n                                            showDialog.value = false\n                                        }) {\n                                            Text(\n                                                text = lastButtonText.uppercase(),\n                                                fontSize = 14.sp,\n                                                color = MaterialTheme.colorScheme.primary\n                                            )\n                                        }\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            )\n        }\n    }\n\n    @OptIn(ExperimentalMaterial3Api::class)\n    @Composable\n    fun SelectableAlertDialog(\n        openDialog: MutableState<Boolean>,\n        title: String,\n        items: List<String>,\n        onItemClicked: (Int) -> Unit,\n        neutralButtonText: String = \"\",\n        onNeutralClicked: () -> Unit = {}\n    ) {\n        if (openDialog.value) {\n            BasicAlertDialog(\n                onDismissRequest = {\n                    openDialog.value = false\n                },\n                content = {\n                    Card(\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .padding(top = 16.dp, start = 16.dp, end = 16.dp, bottom = 0.dp),\n                        shape = RoundedCornerShape(16.dp),\n                        colors = CardDefaults.cardColors(\n                            containerColor = MaterialTheme.colorScheme.surfaceContainer\n                        )\n                    ) {\n                        Column(modifier = Modifier.padding(16.dp)) {\n                            Text(\n                                text = title,\n                                fontSize = 20.sp,\n                                color = MaterialTheme.colorScheme.onSurface,\n                                textAlign = TextAlign.Start,\n                                modifier = Modifier.fillMaxWidth()\n                            )\n                            Spacer(modifier = Modifier.height(16.dp))\n                            LazyColumn(\n                                modifier = Modifier.fillMaxWidth(),\n                                horizontalAlignment = Alignment.End\n                            ) {\n                                itemsIndexed(items) { index, item ->\n                                    TextButton(\n                                        onClick = {\n                                            onItemClicked(index)\n                                            openDialog.value = false\n                                        },\n                                        modifier = Modifier.fillMaxWidth()\n                                    ) {\n                                        Text(\n                                            text = item,\n                                            color = MaterialTheme.colorScheme.onSurface,\n                                            modifier = Modifier.fillMaxWidth(),\n                                            textAlign = TextAlign.Start\n                                        )\n                                    }\n                                }\n                            }\n                            if (neutralButtonText.isNotEmpty()) {\n                                Row(\n                                    modifier = Modifier.fillMaxWidth(),\n                                    horizontalArrangement = Arrangement.End\n                                ) {\n                                    TextButton(\n                                        onClick = {\n                                            onNeutralClicked()\n                                            openDialog.value = false\n                                        }\n                                    ) {\n                                        Text(\n                                            text = neutralButtonText.uppercase(),\n                                            color = MaterialTheme.colorScheme.onSurfaceVariant,\n                                        )\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            )\n        }\n    }\n\n    @OptIn(ExperimentalMaterial3Api::class)\n    @Composable\n    fun PasswordDialog(\n        ctx: Context,\n        showPasswordDialog: MutableState<Boolean>,\n        password: MutableState<String>,\n        keyboardController: SoftwareKeyboardController?,\n        title: String,\n        message: String = \"\",\n        okAction: () -> Unit,\n        cancelAction: () -> Unit\n    ) {\n        val showPassword = remember { mutableStateOf(false) }\n        val focusRequester = remember { FocusRequester() }\n\n        if (showPasswordDialog.value) {\n            BasicAlertDialog(\n                properties = DialogProperties(\n                    dismissOnBackPress = false,\n                    dismissOnClickOutside = false,\n                ),\n                onDismissRequest = {\n                    keyboardController?.hide()\n                    showPasswordDialog.value = false\n                }\n            ) {\n                Card(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(top = 16.dp, start = 16.dp, end = 16.dp, bottom = 0.dp),\n                    shape = RoundedCornerShape(16.dp),\n                    colors = CardDefaults.cardColors(\n                        containerColor = MaterialTheme.colorScheme.surfaceVariant\n                    )\n                ) {\n                    Column(modifier = Modifier.padding(16.dp)) {\n                        Text(\n                            text = title,\n                            fontSize = 20.sp,\n                            modifier = Modifier.padding(top = 16.dp, bottom = 16.dp),\n                            color = MaterialTheme.colorScheme.onSurface,\n                        )\n                        if (message.isNotEmpty())\n                            Text(\n                                text = message,\n                                fontSize = 16.sp,\n                                modifier = Modifier.padding(16.dp),\n                                color = MaterialTheme.colorScheme.onSurfaceVariant,\n                            )\n                        OutlinedTextField(\n                            value = password.value,\n                            singleLine = true,\n                            colors = OutlinedTextFieldDefaults.colors(\n                                cursorColor = MaterialTheme.colorScheme.primary,\n                            ),\n                            onValueChange = {\n                                password.value = it\n                            },\n                            visualTransformation = if (showPassword.value)\n                                VisualTransformation.None\n                            else\n                                PasswordVisualTransformation(),\n                            trailingIcon = {\n                                IconButton(onClick = {\n                                    showPassword.value = !showPassword.value\n                                }) {\n                                    Icon(\n                                        imageVector = if (showPassword.value)\n                                            Icons.Filled.Visibility\n                                        else\n                                            Icons.Filled.VisibilityOff,\n                                        contentDescription = \"Visibility\",\n                                        tint = MaterialTheme.colorScheme.onSurfaceVariant\n                                    )\n                                }\n                            },\n                            modifier = Modifier\n                                .fillMaxWidth()\n                                .padding(start = 4.dp, end = 4.dp, top = 12.dp, bottom = 2.dp)\n                                .focusRequester(focusRequester),\n                            textStyle = TextStyle(\n                                fontSize = 18.sp,\n                                color = MaterialTheme.colorScheme.onSurface\n                            ),\n                            keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)\n                        )\n                        LaunchedEffect(key1 = Unit) {\n                            focusRequester.requestFocus()\n                            keyboardController?.show()\n                        }\n                        Row(\n                            modifier = Modifier.fillMaxWidth(),\n                            horizontalArrangement = Arrangement.End\n                        ) {\n                            TextButton(\n                                onClick = {\n                                    keyboardController?.hide()\n                                    showPasswordDialog.value = false\n                                    cancelAction()\n                                },\n                            ) {\n                                Text(\n                                    text = stringResource(R.string.cancel),\n                                    color = MaterialTheme.colorScheme.onSurfaceVariant\n                                )\n                            }\n                            Spacer(modifier = Modifier.width(8.dp))\n                            TextButton(\n                                onClick = {\n                                    keyboardController?.hide()\n                                    showPasswordDialog.value = false\n                                    password.value = password.value.trim()\n                                    if (!Account.checkAuthPass(password.value)) {\n                                        Toast.makeText(\n                                            ctx,\n                                            String.format(\n                                                ctx.getString(R.string.invalid_authentication_password),\n                                                password.value\n                                            ),\n                                            Toast.LENGTH_SHORT\n                                        ).show()\n                                        password.value = \"\"\n                                    }\n                                    okAction()\n                                },\n                            ) {\n                                Text(\n                                    text = stringResource(R.string.ok),\n                                    color = MaterialTheme.colorScheme.primary\n                                )\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/DraggableLazyList.kt",
    "content": "/*\n * Copyright 2024 Allan Veloso Lopes\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Temporarily implementation until this issue is made available on the official library\n// https://issuetracker.google.com/issues/356619939\n// Based on https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/LazyColumnDragAndDropDemo.kt\n// https://issuetracker.google.com/issues/181282427\npackage com.tutpro.baresip\n\nimport androidx.compose.animation.core.Animatable\nimport androidx.compose.animation.core.Spring\nimport androidx.compose.animation.core.spring\nimport androidx.compose.foundation.gestures.detectDragGestures\nimport androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress\nimport androidx.compose.foundation.gestures.scrollBy\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.lazy.LazyItemScope\nimport androidx.compose.foundation.lazy.LazyListItemInfo\nimport androidx.compose.foundation.lazy.LazyListScope\nimport androidx.compose.foundation.lazy.LazyListState\nimport androidx.compose.foundation.lazy.itemsIndexed\nimport androidx.compose.foundation.lazy.rememberLazyListState\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableFloatStateOf\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.geometry.Offset\nimport androidx.compose.ui.graphics.graphicsLayer\nimport androidx.compose.ui.input.pointer.pointerInput\nimport androidx.compose.ui.zIndex\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.channels.Channel\nimport kotlinx.coroutines.launch\n\n/**\n * Creates a [DraggableListState] that is remembered across compositions.\n *\n * @param lazyListState the [LazyListState] whose items are being dragged.\n * @param onMove the callback that is triggered when an item is moved.\n */\n@Composable\nfun rememberDraggableListState(\n    lazyListState: LazyListState = rememberLazyListState(),\n    onMove: (Int, Int) -> Unit\n): DraggableListState {\n    val scope = rememberCoroutineScope()\n    val state = remember(lazyListState) {\n        DraggableListState(\n            listState = lazyListState,\n            onMove = onMove,\n            scope = scope\n        )\n    }\n    LaunchedEffect(state) {\n        while (true) {\n            val diff = state.scrollChannel.receive()\n            lazyListState.scrollBy(diff)\n        }\n    }\n    return state\n}\n\n/**\n * A state object that can be hoisted to control and observe dragging within a list.\n *\n * In most cases, this will be created via [rememberLazyListState].\n *\n * @param listState the state of the list where the item is being dragged.\n * @param scope a coroutine scope for performing animations.\n * @param onMove the callback that is triggered when an item is moved. The callback is\n * invoked whenever the dragged item middle point passes another item top or bottom offset,\n * it does not depend on the drag to be completed.\n */\nclass DraggableListState internal constructor(\n    val listState: LazyListState,\n    private val scope: CoroutineScope,\n    private val onMove: (Int, Int) -> Unit\n) {\n    var draggingItemIndex by mutableStateOf<Int?>(null)\n        private set\n\n    internal val scrollChannel = Channel<Float>()\n\n    private var draggingItemDraggedDelta by mutableFloatStateOf(0f)\n\n    private var draggingItemInitialOffset by mutableIntStateOf(0)\n\n    val draggingItemOffset: Float\n        get() = draggingItemLayoutInfo?.let { item ->\n            draggingItemInitialOffset + draggingItemDraggedDelta - item.offset\n        } ?: 0f\n\n    private val draggingItemLayoutInfo: LazyListItemInfo?\n        get() = listState.layoutInfo.visibleItemsInfo.firstOrNull { it.index == draggingItemIndex }\n\n    var previousIndexOfDraggedItem by mutableStateOf<Int?>(null)\n        private set\n\n    var previousItemOffset = Animatable(0f)\n        private set\n\n    internal fun onDragStart(index: Int) {\n        listState.layoutInfo.visibleItemsInfo\n            .find { it.index == index }\n            ?.also {\n                draggingItemIndex = it.index\n                draggingItemInitialOffset = it.offset\n            }\n    }\n\n    internal fun onDragStart(key: Any) {\n        listState.layoutInfo.visibleItemsInfo\n            .find { it.key == key }\n            ?.also {\n                draggingItemIndex = it.index\n                draggingItemInitialOffset = it.offset\n            }\n    }\n\n    internal fun onDragInterrupted() {\n        if (draggingItemIndex != null) {\n            previousIndexOfDraggedItem = draggingItemIndex\n            val startOffset = draggingItemOffset\n            scope.launch {\n                previousItemOffset.snapTo(startOffset)\n                previousItemOffset.animateTo(\n                    0f,\n                    spring(stiffness = Spring.StiffnessMediumLow, visibilityThreshold = 1f)\n                )\n                previousIndexOfDraggedItem = null\n            }\n        }\n        draggingItemDraggedDelta = 0f\n        draggingItemIndex = null\n        draggingItemInitialOffset = 0\n    }\n\n    internal fun onDrag(offset: Offset) {\n        draggingItemDraggedDelta += offset.y\n\n        val draggingItem = draggingItemLayoutInfo ?: return\n        val startOffset = draggingItem.offset + draggingItemOffset\n        val endOffset = startOffset + draggingItem.size\n        val middleOffset = startOffset + (endOffset - startOffset) / 2f\n\n        val targetItem = listState.layoutInfo.visibleItemsInfo.find { item ->\n            middleOffset.toInt() in item.offset..item.offsetEnd &&\n                    draggingItem.index != item.index\n        }\n\n        if (targetItem != null) {\n            if (\n                draggingItem.index == listState.firstVisibleItemIndex ||\n                targetItem.index == listState.firstVisibleItemIndex\n            ) {\n                listState.requestScrollToItem(\n                    listState.firstVisibleItemIndex,\n                    listState.firstVisibleItemScrollOffset\n                )\n            }\n            onMove(draggingItem.index, targetItem.index)\n            draggingItemIndex = targetItem.index\n        } else {\n            val overscroll = when {\n                draggingItemDraggedDelta > 0 ->\n                    (endOffset - listState.layoutInfo.viewportEndOffset).coerceAtLeast(0f)\n\n                draggingItemDraggedDelta < 0 ->\n                    (startOffset - listState.layoutInfo.viewportStartOffset).coerceAtMost(0f)\n\n                else -> 0f\n            }\n            if (overscroll != 0f) {\n                scrollChannel.trySend(overscroll)\n            }\n        }\n    }\n\n    private val LazyListItemInfo.offsetEnd: Int\n        get() = this.offset + this.size\n}\n\n/**\n * Defines the area that will act as the handle for dragging operations.\n *\n * @param state The draggable state of the list where the handler is being used.\n * @param index The index of the item for which the handler is being used.\n * @param onlyAfterLongPress Indicates whether dragging should begin only after a long press.\n */\nfun Modifier.dragHandle(\n    state: DraggableListState,\n    index: Int,\n    onlyAfterLongPress: Boolean = false\n): Modifier {\n    return pointerInput(state) {\n        if (onlyAfterLongPress) {\n            detectDragGesturesAfterLongPress(\n                onDrag = { change, offset ->\n                    change.consume()\n                    state.onDrag(offset = offset)\n                },\n                onDragStart = { state.onDragStart(index = index) },\n                onDragEnd = { state.onDragInterrupted() },\n                onDragCancel = { state.onDragInterrupted() }\n            )\n        } else {\n            detectDragGestures(\n                onDrag = { change, offset ->\n                    change.consume()\n                    state.onDrag(offset = offset)\n                },\n                onDragStart = { state.onDragStart(index = index) },\n                onDragEnd = { state.onDragInterrupted() },\n                onDragCancel = { state.onDragInterrupted() }\n            )\n        }\n    }\n}\n\n/**\n * Defines the area that will act as the handle for dragging operations.\n *\n * If you use this version of the [dragHandle], you must implement the key parameter on the\n * [draggableItems] or [draggableItemsIndexed].\n *\n * @param state The draggable state of the list where the handler is being used.\n * @param key The unique identifier for the item within the list for which the handler is being used.\n * @param onlyAfterLongPress Indicates whether dragging should begin only after a long press.\n */\nfun Modifier.dragHandle(\n    state: DraggableListState,\n    key: Any,\n    onlyAfterLongPress: Boolean = false\n): Modifier {\n    return pointerInput(state) {\n        if (onlyAfterLongPress) {\n            detectDragGesturesAfterLongPress(\n                onDrag = { change, offset ->\n                    change.consume()\n                    state.onDrag(offset = offset)\n                },\n                onDragStart = { state.onDragStart(key = key) },\n                onDragEnd = { state.onDragInterrupted() },\n                onDragCancel = { state.onDragInterrupted() }\n            )\n        } else {\n            detectDragGestures(\n                onDrag = { change, offset ->\n                    change.consume()\n                    state.onDrag(offset = offset)\n                },\n                onDragStart = { state.onDragStart(key = key) },\n                onDragEnd = { state.onDragInterrupted() },\n                onDragCancel = { state.onDragInterrupted() }\n            )\n        }\n    }\n}\n\n/**\n * Adds a list of draggable items.\n *\n * To the items to be draggable, it's also necessary to add the modifier [dragHandle] to\n * the preferable UI element of the item which will be the target of the drag operation.\n *\n * @param items the data list\n * @param key a factory of stable and unique keys representing the item. Using the same key\n * for multiple items in the list is not allowed. Type of the key should be saveable\n * via Bundle on Android. If null is passed the position in the list will represent the key.\n * When you specify the key the scroll position will be maintained based on the key, which\n * means if you add/remove items before the current visible item the item with the given key\n * will be kept as the first visible one. This can be overridden by calling 'requestScrollToItem'\n * on the 'LazyListState'.\n * @param contentType a factory of the content types for the item. The item compositions of\n * the same type could be reused more efficiently. Note that null is a valid type and items of such\n * type will be considered compatible.\n * @param itemContent the content displayed by a single item\n */\ninline fun <T> LazyListScope.draggableItemsIndexed(\n    state: DraggableListState,\n    items: List<T>,\n    noinline key: ((index: Int, item: T) -> Any)? = null,\n    crossinline contentType: (index: Int, item: T) -> Any? = { _, _ -> null },\n    crossinline itemContent: @Composable LazyItemScope.(index: Int, item: T, isDragging: Boolean) -> Unit\n) = itemsIndexed(\n    items = items,\n    key = key,\n    contentType = contentType\n) { index, item ->\n\n    val isDragging = index == state.draggingItemIndex\n    val draggingModifier = if (isDragging) {\n        Modifier\n            .zIndex(1f)\n            .graphicsLayer { translationY = state.draggingItemOffset }\n    } else if (index == state.previousIndexOfDraggedItem) {\n        Modifier\n            .zIndex(1f)\n            .graphicsLayer { translationY = state.previousItemOffset.value }\n    } else {\n        Modifier.animateItem(fadeInSpec = null, fadeOutSpec = null)\n    }\n    Box(modifier = draggingModifier) {\n        itemContent(index, item, isDragging)\n    }\n}\n\n/**\n * Adds a list of draggable items.\n *\n * To the items to be draggable, it's also necessary to add the modifier [dragHandle] to\n * the preferable UI element of the item which will be the target of the drag operation.\n *\n * @param items the data list\n * @param key a factory of stable and unique keys representing the item. Using the same key\n * for multiple items in the list is not allowed. Type of the key should be saveable\n * via Bundle on Android. If null is passed the position in the list will represent the key.\n * When you specify the key the scroll position will be maintained based on the key, which\n * means if you add/remove items before the current visible item the item with the given key\n * will be kept as the first visible one. This can be overridden by calling 'requestScrollToItem'\n * on the 'LazyListState'.\n * @param contentType a factory of the content types for the item. The item compositions of\n * the same type could be reused more efficiently. Note that null is a valid type and items of such\n * type will be considered compatible.\n * @param itemContent the content displayed by a single item\n */\ninline fun <T> LazyListScope.draggableItems(\n    state: DraggableListState,\n    items: List<T>,\n    noinline key: ((item: T) -> Any)? = null,\n    crossinline contentType: (item: T) -> Any? = { _ -> null },\n    crossinline itemContent: @Composable LazyItemScope.(item: T, isDragging: Boolean) -> Unit\n) = draggableItemsIndexed(\n    state = state,\n    items = items,\n    key = key?.let { block -> { _, item -> block(item) } },\n    contentType = { _, item -> contentType(item) },\n    itemContent = { _, item, isDragging -> itemContent(item, isDragging) }\n)\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/Event.kt",
    "content": "package com.tutpro.baresip\n\nimport java.util.concurrent.atomic.AtomicBoolean\n\nopen class Event<out T>(private val content: T) {\n    private val hasBeenHandled = AtomicBoolean(false)\n    fun getContentIfNotHandled(): T? {\n        return if (hasBeenHandled.get()) {\n            null\n        } else {\n            hasBeenHandled.set(true)\n            content\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/InCallService.kt",
    "content": "package com.tutpro.baresip\n\nimport android.app.Service\nimport android.content.Intent\nimport android.os.IBinder\n\n// This is needed in order to allow choosing baresip as default Phone app\nclass InCallService : Service() {\n    override fun onBind(intent: Intent): IBinder? {\n        Log.d(TAG, \"InCallService onBind with intent: ${intent.action}\")\n        return null\n    }\n}\n\n/*import android.telecom.InCallService\nimport android.telecom.Call\n\nclass InCallService : InCallService() {\n    override fun onCallAdded(call: Call) {\n        super.onCallAdded(call)\n        // This is triggered when the system wants YOU to show the call UI\n        Log.d(\"Baresip\", \"InCallService: Call added\")\n    }\n}*/\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/Log.kt",
    "content": "package com.tutpro.baresip\n\nobject Log {\n\n    enum class LogLevel {\n        DEBUG, INFO, WARN, ERROR, OFF\n    }\n\n    var logLevel: LogLevel = LogLevel.INFO\n\n    fun logLevelSet(value: Int) {\n        when (value) {\n            0 -> logLevel = LogLevel.DEBUG\n            1 -> logLevel = LogLevel.INFO\n            2 -> logLevel = LogLevel.WARN\n            3 -> logLevel = LogLevel.ERROR\n            4 -> logLevel = LogLevel.OFF\n        }\n    }\n\n    fun d(tag: String, msg: String) {\n        if (logLevel < LogLevel.INFO) android.util.Log.d(tag, msg)\n    }\n\n    fun i(tag: String, msg: String) {\n        if (logLevel < LogLevel.WARN) android.util.Log.i(tag, msg)\n    }\n\n    fun w(tag: String, msg: String) {\n        if (logLevel < LogLevel.ERROR) android.util.Log.w(tag, msg)\n    }\n\n    fun e(tag: String, msg: String) {\n        if (logLevel < LogLevel.OFF) android.util.Log.w(tag, msg)\n    }\n}"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt",
    "content": "@file:OptIn(ExperimentalMaterial3Api::class)\n\npackage com.tutpro.baresip\n\nimport android.Manifest.permission.BLUETOOTH_CONNECT\nimport android.Manifest.permission.POST_NOTIFICATIONS\nimport android.Manifest.permission.READ_EXTERNAL_STORAGE\nimport android.Manifest.permission.RECORD_AUDIO\nimport android.Manifest.permission.WRITE_EXTERNAL_STORAGE\nimport android.annotation.SuppressLint\nimport android.app.KeyguardManager\nimport android.app.NotificationManager\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.content.Intent.ACTION_CALL\nimport android.content.Intent.ACTION_DIAL\nimport android.content.Intent.ACTION_VIEW\nimport android.content.IntentFilter\nimport android.media.AudioManager\nimport android.os.Build\nimport android.os.Bundle\nimport android.view.KeyEvent\nimport android.view.WindowManager\nimport android.view.inputmethod.InputMethodManager\nimport androidx.activity.ComponentActivity\nimport androidx.activity.compose.setContent\nimport androidx.activity.result.ActivityResultLauncher\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.activity.viewModels\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.lifecycle.Observer\nimport androidx.navigation.NavHostController\nimport androidx.navigation.compose.NavHost\nimport androidx.navigation.compose.rememberNavController\nimport androidx.core.content.ContextCompat\nimport kotlin.system.exitProcess\n\nclass MainActivity : ComponentActivity() {\n\n    private lateinit var imm: InputMethodManager\n    private lateinit var nm: NotificationManager\n    private lateinit var am: AudioManager\n    private lateinit var kgm: KeyguardManager\n    private lateinit var screenEventReceiver: BroadcastReceiver\n    private lateinit var serviceEventObserver: Observer<Event<Long>>\n    private lateinit var requestPermissionLauncher: ActivityResultLauncher<String>\n    private lateinit var requestPermissionsLauncher: ActivityResultLauncher<Array<String>>\n    private lateinit var comDevChangedListener: AudioManager.OnCommunicationDeviceChangedListener\n\n    private lateinit var baresipService: Intent\n\n    private var restart = false\n    private var atStartup = false\n    private var initialized = false\n\n    private val viewModel: ViewModel by viewModels()\n    private lateinit var navController:  NavHostController\n\n    @SuppressLint(\"ClickableViewAccessibility\")\n    override fun onCreate(savedInstanceState: Bundle?) {\n\n        super.onCreate(savedInstanceState)\n\n        val extraAction = intent.getStringExtra(\"action\")\n        Log.i(TAG, \"Main onCreate ${intent.action}/${intent.data}/$extraAction\")\n\n        window.addFlags(WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES)\n\n        BaresipService.darkTheme.value = Utils.isThemeDark(this)\n\n        // Must be done after view has been created\n        this.setShowWhenLocked(true)\n        this.setTurnScreenOn( true)\n        Utils.requestDismissKeyguard(this)\n\n        imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager\n        nm = getSystemService(NOTIFICATION_SERVICE) as NotificationManager\n        am = getSystemService(AUDIO_SERVICE) as AudioManager\n        kgm = getSystemService(KEYGUARD_SERVICE) as KeyguardManager\n\n        serviceEventObserver = Observer {\n            val event = it.getContentIfNotHandled()\n            Log.d(TAG, \"Observed event $event\")\n            if (event != null && BaresipService.serviceEvents.isNotEmpty()) {\n                val first = BaresipService.serviceEvents.removeAt(0)\n                if (taskId != -1) {\n                    if (first.event == \"started\" && !initialized)\n                        // Android has restarted baresip when permission has been denied in app settings\n                        recreate()\n                    else {\n                        if (first.event == \"stopped\") {\n                            Log.d(\n                                TAG,\n                                \"Handling service event 'stopped' with start error '${first.params[0]}'\"\n                            )\n                            if (first.params[0] != \"\")\n                                handleDialog(\n                                    ctx = applicationContext,\n                                    title = getString(R.string.notice),\n                                    message =getString(R.string.start_failed)\n                                )\n                            else {\n                                finishAndRemoveTask()\n                                if (restart)\n                                    reStart()\n                                else\n                                    exitProcess(0)\n                            }\n                        } else\n                            handleServiceEvent(this, viewModel, first.event, first.params)\n                    }\n                }\n                else\n                    Log.d(TAG, \"Omit service event '$event' for task -1\")\n            }\n        }\n\n        BaresipService.serviceEvent.observeForever(serviceEventObserver)\n\n        screenEventReceiver = object : BroadcastReceiver() {\n            override fun onReceive(contxt: Context, intent: Intent) {\n                if (kgm.isKeyguardLocked) {\n                    Log.d(TAG, \"Screen on when locked\")\n                    this@MainActivity.setShowWhenLocked(Call.inCall())\n                }\n            }\n        }\n\n        this.registerReceiver(screenEventReceiver, IntentFilter().apply {\n            addAction(Intent.ACTION_SCREEN_ON)\n        })\n\n        if (Build.VERSION.SDK_INT >= 31) {\n            comDevChangedListener = AudioManager.OnCommunicationDeviceChangedListener { device ->\n                if (device != null) {\n                    Log.d(TAG, \"Com device changed to type ${device.type} in mode ${am.mode}\")\n                }\n            }\n            am.addOnCommunicationDeviceChangedListener(mainExecutor, comDevChangedListener)\n        }\n\n        initialized = true\n\n        val restartApp = {\n            Log.i(TAG, \"Restarting baresip\")\n            window.setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,\n                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)\n            if (BaresipService.isServiceRunning) {\n                restart = true\n                baresipService.action = \"Stop\"\n                ContextCompat.startForegroundService(this, baresipService)\n            } else {\n                finishAndRemoveTask()\n                val pm = applicationContext.packageManager\n                val intent = pm.getLaunchIntentForPackage(applicationContext.packageName)\n                if (intent != null) {\n                    applicationContext.startActivity(intent)\n                    exitProcess(0)\n                } else {\n                    Log.e(TAG, \"Failed to restart: Launch intent is null\")\n                }\n            }\n        }\n\n        val quitApp = {\n            Log.i(TAG, \"Quiting baresip\")\n            window.setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,\n                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)\n            if (BaresipService.isServiceRunning) {\n                restart = false\n                baresipService.action = \"Stop\"\n                ContextCompat.startForegroundService(this, baresipService)\n            } else {\n                finishAndRemoveTask()\n                exitProcess(0)\n            }\n        }\n\n        baresipService = Intent(this@MainActivity, BaresipService::class.java)\n\n        atStartup = intent.hasExtra(\"onStartup\")\n\n        when (intent?.action) {\n            ACTION_DIAL, ACTION_CALL, ACTION_VIEW ->\n                if (BaresipService.isServiceRunning)\n                    callAction(applicationContext, viewModel, intent.data, if (intent?.action == ACTION_CALL) \"call\" else \"dial\")\n                else\n                    BaresipService.callActionUri = intent.data.toString().replace(\"%2B\", \"+\")\n                        .replace(\"%20\", \"\").filterNot{setOf('-', ' ', '(', ')').contains(it)}\n        }\n\n        val permissions = if (Build.VERSION.SDK_INT >= 33)\n            arrayOf(POST_NOTIFICATIONS, RECORD_AUDIO, BLUETOOTH_CONNECT)\n        else if (Build.VERSION.SDK_INT >= 31)\n            arrayOf(RECORD_AUDIO, BLUETOOTH_CONNECT)\n        else\n            if (Build.VERSION.SDK_INT < 29)\n                arrayOf(RECORD_AUDIO, READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE)\n            else\n                arrayOf(RECORD_AUDIO)\n\n        requestPermissionLauncher =\n            registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->\n                Log.i(TAG, \"Permission granted: $isGranted\")\n            }\n\n        requestPermissionsLauncher =\n            registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {\n                val denied = mutableListOf<String>()\n                val shouldShow = mutableListOf<String>()\n                it.forEach { permission ->\n                    if (!permission.value) {\n                        denied.add(permission.key)\n                        if (shouldShowRequestPermissionRationale(permission.key))\n                            shouldShow.add(permission.key)\n                    }\n                }\n                if (denied.contains(POST_NOTIFICATIONS) && !shouldShow.contains(POST_NOTIFICATIONS)) {\n                    handleDialog(\n                        ctx = applicationContext,\n                        title = getString(R.string.notice),\n                        message = getString(R.string.no_notifications),\n                        action = { quitRestart(false) }\n                    )\n                } else {\n                    if (shouldShow.isNotEmpty()) {\n                        handleDialog(\n                            ctx = applicationContext,\n                            title = getString(R.string.permissions_rationale),\n                            message = getString(R.string.audio_permissions),\n                            action = { requestPermissionsLauncher.launch(permissions) }\n                        )\n                    }\n                    else {\n                        if (!BaresipService.isStartReceived) {\n                            baresipService.action = \"Start\"\n                            ContextCompat.startForegroundService(this, baresipService)\n                            if (atStartup)\n                                moveTaskToBack(true)\n                        }\n                    }\n                }\n            }\n\n        setContent {\n\n            AppTheme {\n\n                navController = rememberNavController()\n\n                LaunchedEffect(key1 = viewModel) {\n                    viewModel.navigationCommand.collect { command ->\n                        Log.d(TAG, \"MainActivity: Received NavigationCommand: $command\")\n                        when (command) {\n                            is NavigationCommand.NavigateToChat -> {\n                                val route = \"chat/${command.aor}/${command.peerUri}\"\n                                navController.navigate(route)\n                            }\n                            is NavigationCommand.NavigateToCalls -> {\n                                val route = \"calls/${command.aor}\"\n                                navController.navigate(route)\n                            }\n                            is NavigationCommand.NavigateToHome -> {\n                                navController.navigate(\"main\")\n                            }\n                        }\n                    }\n                }\n\n                NavHost(navController, startDestination = \"main\") {\n                    mainScreenRoute(\n                        navController = navController,\n                        viewModel = viewModel,\n                        onRequestPermissions = { requestPermissionsLauncher.launch(permissions) },\n                        onRestartApp = { restartApp() },\n                        onQuitApp = { quitApp() }\n                    )\n                    aboutScreenRoute(navController)\n                    settingsScreenRoute(\n                        navController = navController,\n                        onRestartApp = { restartApp() }\n                    )\n                    accountsScreenRoute(navController)\n                    audioScreenRoute(navController)\n                    accountScreenRoute(navController)\n                    codecsScreenRoute(navController)\n                    contactsScreenRoute(navController, viewModel)\n                    baresipContactScreenRoute(navController)\n                    androidContactScreenRoute(navController, viewModel)\n                    callsScreenRoute(navController, viewModel)\n                    callDetailsScreenRoute(navController, viewModel)\n                    blockedScreenRoute(navController)\n                    chatsScreenRoute(navController)\n                    chatScreenRoute(navController, viewModel)\n                }\n            }\n        }\n\n    } // OnCreate\n\n    override fun onStart() {\n        super.onStart()\n        Log.i(TAG, \"Main onStart\")\n        val action = intent.getStringExtra(\"action\")\n        if (action != null) {\n            // MainActivity was not visible when call, message, or transfer request came in\n            intent.removeExtra(\"action\")\n            handleIntent(applicationContext, viewModel, intent, action)\n        }\n    }\n\n    override fun onResume() {\n        super.onResume()\n        Log.d(TAG, \"Main onResume\")\n        nm.cancelAll()\n    }\n\n    override fun onPause() {\n        super.onPause()\n        Log.d(TAG, \"Main onPause\")\n    }\n\n    override fun onDestroy() {\n        Log.d(TAG, \"Main onDestroy\")\n\n        this.unregisterReceiver(screenEventReceiver)\n\n        if (Build.VERSION.SDK_INT >= 31)\n            am.removeOnCommunicationDeviceChangedListener(comDevChangedListener)\n\n        BaresipService.serviceEvent.removeObserver(serviceEventObserver)\n        BaresipService.serviceEvents.clear()\n\n        super.onDestroy()\n    }\n\n    override fun onNewIntent(intent: Intent) {\n        // Called when MainActivity already exists at the top of current task\n        super.onNewIntent(intent)\n\n        this.setShowWhenLocked(true)\n        this.setTurnScreenOn(true)\n\n        Log.d(TAG, \"onNewIntent with action/data '${intent.action}/${intent.data}'\")\n\n        when (intent.action) {\n            ACTION_DIAL, ACTION_CALL, ACTION_VIEW ->\n                callAction(\n                    applicationContext,\n                    viewModel,\n                    intent.data,\n                    if (intent.action == ACTION_CALL) \"call\" else \"dial\"\n                )\n            else -> {\n                val action = intent.getStringExtra(\"action\")\n                if (action != null) {\n                    intent.removeExtra(\"action\")\n                    handleIntent(applicationContext, viewModel, intent, action)\n                }\n            }\n        }\n    }\n\n    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {\n        val stream = if (am.mode == AudioManager.MODE_RINGTONE)\n            AudioManager.STREAM_RING\n        else\n            AudioManager.STREAM_VOICE_CALL\n        when (keyCode) {\n            KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP -> {\n                am.adjustStreamVolume(stream,\n                        if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)\n                            AudioManager.ADJUST_LOWER else\n                            AudioManager.ADJUST_RAISE,\n                        AudioManager.FLAG_SHOW_UI)\n                Log.d(TAG, \"Adjusted volume $keyCode of stream $stream to ${am.getStreamVolume(stream)}\")\n                return true\n            }\n        }\n        return super.onKeyDown(keyCode, event)\n    }\n\n    private fun quitRestart(reStart: Boolean) {\n        Log.i(TAG, \"quitRestart Restart = $reStart\")\n        window.setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,\n                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)\n        if (BaresipService.isServiceRunning) {\n            restart = reStart\n            baresipService.action = \"Stop\"\n            ContextCompat.startForegroundService(this, baresipService)\n        } else {\n            finishAndRemoveTask()\n            if (reStart)\n                quitRestart(true)\n            else\n                exitProcess(0)\n        }\n    }\n\n    private fun reStart() {\n        Log.d(TAG, \"Trigger restart\")\n        val pm = applicationContext.packageManager\n        val intent = pm.getLaunchIntentForPackage(this.packageName)\n        this.startActivity(intent)\n        exitProcess(0)\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/MainScreen.kt",
    "content": "package com.tutpro.baresip\n\nimport android.Manifest.permission.READ_EXTERNAL_STORAGE\nimport android.Manifest.permission.RECORD_AUDIO\nimport android.Manifest.permission.WRITE_EXTERNAL_STORAGE\nimport android.app.Activity\nimport android.app.Activity.RESULT_OK\nimport android.app.KeyguardManager\nimport android.content.Context\nimport android.content.Intent\nimport android.content.res.Configuration\nimport android.media.AudioManager\nimport android.net.Uri\nimport android.os.Build\nimport android.os.Handler\nimport android.os.Looper\nimport android.os.Process\nimport android.os.SystemClock\nimport android.provider.DocumentsContract\nimport android.provider.MediaStore\nimport android.widget.Toast\nimport androidx.activity.compose.rememberLauncherForActivityResult\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.appcompat.app.AppCompatDelegate\nimport androidx.compose.animation.animateContentSize\nimport androidx.compose.foundation.BorderStroke\nimport androidx.compose.foundation.ExperimentalFoundationApi\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.combinedClickable\nimport androidx.compose.foundation.gestures.detectHorizontalDragGestures\nimport androidx.compose.foundation.gestures.detectTapGestures\nimport androidx.compose.foundation.interaction.MutableInteractionSource\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.asPaddingValues\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.heightIn\nimport androidx.compose.foundation.layout.imePadding\nimport androidx.compose.foundation.layout.navigationBarsPadding\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.statusBars\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.lazy.rememberLazyListState\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.foundation.text.BasicTextField\nimport androidx.compose.foundation.text.KeyboardOptions\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.Chat\nimport androidx.compose.material.icons.filled.AddIcCall\nimport androidx.compose.material.icons.filled.Call\nimport androidx.compose.material.icons.filled.CallEnd\nimport androidx.compose.material.icons.filled.Dialpad\nimport androidx.compose.material.icons.filled.History\nimport androidx.compose.material.icons.filled.KeyboardArrowDown\nimport androidx.compose.material.icons.filled.KeyboardArrowUp\nimport androidx.compose.material.icons.filled.Lock\nimport androidx.compose.material.icons.filled.LockOpen\nimport androidx.compose.material.icons.filled.Menu\nimport androidx.compose.material.icons.filled.Mic\nimport androidx.compose.material.icons.filled.MicOff\nimport androidx.compose.material.icons.filled.Person\nimport androidx.compose.material.icons.filled.RecordVoiceOver\nimport androidx.compose.material.icons.filled.SpeakerPhone\nimport androidx.compose.material.icons.filled.VoiceOverOff\nimport androidx.compose.material.icons.filled.Voicemail\nimport androidx.compose.material.icons.outlined.ArrowCircleRight\nimport androidx.compose.material.icons.outlined.Clear\nimport androidx.compose.material.icons.outlined.Info\nimport androidx.compose.material.icons.outlined.PauseCircle\nimport androidx.compose.material3.BasicAlertDialog\nimport androidx.compose.material3.ButtonColors\nimport androidx.compose.material3.Card\nimport androidx.compose.material3.CardDefaults\nimport androidx.compose.material3.DropdownMenuItem\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.HorizontalDivider\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.OutlinedButton\nimport androidx.compose.material3.OutlinedTextField\nimport androidx.compose.material3.OutlinedTextFieldDefaults\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Switch\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.TopAppBarDefaults\nimport androidx.compose.material3.pulltorefresh.PullToRefreshDefaults.Indicator\nimport androidx.compose.material3.pulltorefresh.pullToRefresh\nimport androidx.compose.material3.pulltorefresh.rememberPullToRefreshState\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.DisposableEffect\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.key\nimport androidx.compose.runtime.mutableFloatStateOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.draw.shadow\nimport androidx.compose.ui.focus.FocusRequester\nimport androidx.compose.ui.focus.focusRequester\nimport androidx.compose.ui.focus.onFocusChanged\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.SolidColor\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport androidx.compose.ui.input.pointer.pointerInput\nimport androidx.compose.ui.platform.LocalConfiguration\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalSoftwareKeyboardController\nimport androidx.compose.ui.res.colorResource\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.res.vectorResource\nimport androidx.compose.ui.text.AnnotatedString\nimport androidx.compose.ui.text.TextStyle\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.input.KeyboardType\nimport androidx.compose.ui.text.input.VisualTransformation\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.core.content.ContextCompat\nimport androidx.core.net.toUri\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.LifecycleEventObserver\nimport androidx.lifecycle.compose.LocalLifecycleOwner\nimport androidx.navigation.NavController\nimport androidx.navigation.NavGraphBuilder\nimport androidx.navigation.compose.composable\nimport androidx.navigation.compose.currentBackStackEntryAsState\nimport com.tutpro.baresip.BaresipService.Companion.contactNames\nimport com.tutpro.baresip.BaresipService.Companion.uas\nimport com.tutpro.baresip.BaresipService.Companion.uasStatus\nimport com.tutpro.baresip.CustomElements.AlertDialog\nimport com.tutpro.baresip.CustomElements.DropdownMenu\nimport com.tutpro.baresip.CustomElements.PasswordDialog\nimport com.tutpro.baresip.CustomElements.SelectableAlertDialog\nimport com.tutpro.baresip.CustomElements.verticalScrollbar\nimport kotlinx.coroutines.delay\nimport java.io.File\nimport java.text.SimpleDateFormat\nimport java.util.Date\nimport java.util.Locale\n\nprivate val dialpadButtonEnabled = mutableStateOf(true)\nprivate var pullToRefreshEnabled = mutableStateOf(true)\n\nprivate var downloadsInputUri: Uri? = null\nprivate var downloadsOutputUri: Uri? = null\n\nprivate val passwordTitle = mutableStateOf(\"\")\nprivate val showPasswordDialog = mutableStateOf(false)\nprivate val showPasswordsDialog = mutableStateOf(false)\n\nprivate var passwordAccounts = mutableListOf<String>()\nprivate var password = mutableStateOf(\"\")\n\nprivate val selectItems = mutableStateOf(listOf<String>())\nprivate val selectItemAction = mutableStateOf<(Int) -> Unit>({ _ -> run {} })\nprivate val showSelectItemDialog = mutableStateOf(false)\n\nfun NavGraphBuilder.mainScreenRoute(\n    navController: NavController,\n    viewModel: ViewModel,\n    onRequestPermissions: () -> Unit,\n    onRestartApp: () -> Unit,\n    onQuitApp: () -> Unit\n) {\n    composable(\"main\") {\n        MainScreen(\n            navController = navController,\n            viewModel = viewModel,\n            onRequestPermissions = onRequestPermissions,\n            onRestartClick = onRestartApp,\n            onQuitClick = onQuitApp\n        )\n    }\n}\n\n@Composable\nprivate fun MainScreen(\n    navController: NavController,\n    viewModel: ViewModel,\n    onRequestPermissions: () -> Unit,\n    onRestartClick: () -> Unit,\n    onQuitClick: () -> Unit\n) {\n    val ctx = LocalContext.current\n    val lifecycleOwner = LocalLifecycleOwner.current\n    val configuration = LocalConfiguration.current\n    val keyboardController = LocalSoftwareKeyboardController.current\n\n    val ua = uas.value.find { it.account.aor == viewModel.selectedAor.value }\n    val call = ua?.currentCall()\n\n    val showKeyboard by viewModel.showKeyboard.collectAsState()\n    val hideKeyboard by viewModel.hideKeyboard.collectAsState()\n\n    LaunchedEffect(showKeyboard) {\n        if (viewModel.showKeyboard.value > 0)\n            keyboardController?.show()\n    }\n\n    LaunchedEffect(hideKeyboard) {\n        if (viewModel.hideKeyboard.value > 0)\n            keyboardController?.hide()\n    }\n\n    DisposableEffect(lifecycleOwner) {\n        val observer = LifecycleEventObserver { _, event ->\n            when (event) {\n                Lifecycle.Event.ON_RESUME -> {\n                    Log.d(TAG, \"Resumed to MainScreen\")\n                    BaresipService.isMainVisible = true\n                    viewModel.updateCalls(Call.calls().toList())\n                    (Call.call(\"incoming\") ?: Call.calls().lastOrNull())?.let {\n                        spinToAor(viewModel, it.ua.account.aor)\n                    } ?: run {\n                        if (uas.value.isNotEmpty() && viewModel.selectedAor.value == \"\")\n                            spinToAor(viewModel, uas.value.first().account.aor)\n                    }\n                    val ua = UserAgent.ofAor(viewModel.selectedAor.value)\n                    if (ua != null) {\n                        showCall(ctx, viewModel, ua)\n                        viewModel.triggerAccountUpdate()\n                    }\n                }\n                Lifecycle.Event.ON_PAUSE -> {\n                    Log.d(TAG, \"Paused from MainScreen\")\n                    BaresipService.isMainVisible = false\n                }\n                else -> {}\n            }\n        }\n        lifecycleOwner.lifecycle.addObserver(observer)\n        onDispose {\n            Log.d(TAG, \"onDispose for MainScreen\")\n            lifecycleOwner.lifecycle.removeObserver(observer)\n            BaresipService.isMainVisible = false\n        }\n    }\n\n    val encryptPasswordTitle = stringResource(R.string.encrypt_password)\n    val backupRequestLauncher = rememberLauncherForActivityResult(\n        contract = ActivityResultContracts.StartActivityForResult()\n    ) { result ->\n        if (result.resultCode == RESULT_OK) {\n            result.data?.data?.also { uri ->\n                downloadsOutputUri = uri\n                passwordTitle.value = encryptPasswordTitle\n                showPasswordDialog.value = true\n            }\n        }\n    }\n\n    val noticeTitle = stringResource(R.string.notice)\n    val noBackupMessage = stringResource(R.string.no_backup)\n    fun launchBackupRequest() {\n        if (Build.VERSION.SDK_INT < 29) {\n            if (!Utils.checkPermissions(ctx, arrayOf(WRITE_EXTERNAL_STORAGE))) {\n                alertTitle.value = noticeTitle\n                alertMessage.value = noBackupMessage\n                showAlert.value = true\n            }\n            else {\n                val path = Utils.downloadsPath(\"baresip.bs\")\n                downloadsOutputUri = File(path).toUri()\n                passwordTitle.value = encryptPasswordTitle\n                showPasswordDialog.value = true\n            }\n        }\n        else {\n            val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {\n                addCategory(Intent.CATEGORY_OPENABLE)\n                type = \"application/octet-stream\"\n                putExtra(\n                    Intent.EXTRA_TITLE,\n                    \"baresip_\" + SimpleDateFormat(\n                        \"yyyy_MM_dd_HH_mm_ss\",\n                        Locale.getDefault()\n                    ).format(Date())\n                )\n                putExtra(DocumentsContract.EXTRA_INITIAL_URI, MediaStore.Downloads.EXTERNAL_CONTENT_URI)\n            }\n            backupRequestLauncher.launch(intent)\n        }\n    }\n\n    val decryptPasswordTitle = stringResource(R.string.decrypt_password)\n    val restoreRequestLauncher = rememberLauncherForActivityResult(\n        contract = ActivityResultContracts.StartActivityForResult()\n    ) { result ->\n        if (result.resultCode == RESULT_OK) {\n            result.data?.data?.also { uri ->\n                downloadsInputUri = uri\n                passwordTitle.value = decryptPasswordTitle\n                showPasswordDialog.value = true\n            }\n        }\n    }\n\n    val noRestoreMessage = stringResource(R.string.no_restore)\n    fun launchRestoreRequest() {\n        if (Build.VERSION.SDK_INT < 29) {\n            if (!Utils.checkPermissions(ctx, arrayOf(READ_EXTERNAL_STORAGE))) {\n                alertTitle.value = noticeTitle\n                alertMessage.value = noRestoreMessage\n                showAlert.value = true\n            }\n            else {\n                val path = Utils.downloadsPath(\"baresip.bs\")\n                downloadsInputUri = File(path).toUri()\n                passwordTitle.value = decryptPasswordTitle\n                showPasswordDialog.value = true\n            }\n        }\n        else {\n            val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {\n                addCategory(Intent.CATEGORY_OPENABLE)\n                type = \"application/octet-stream\"\n                putExtra(DocumentsContract.EXTRA_INITIAL_URI, MediaStore.Downloads.EXTERNAL_CONTENT_URI)\n            }\n            restoreRequestLauncher.launch(intent)\n        }\n    }\n\n    val logcatRequestLauncher = rememberLauncherForActivityResult(\n        contract = ActivityResultContracts.StartActivityForResult()\n    ) { result ->\n        if (result.resultCode == RESULT_OK)\n            result.data?.data?.also { uri ->\n                try {\n                    val out = ctx.contentResolver.openOutputStream(uri)\n                    val process = Runtime.getRuntime().exec(\"logcat -d --pid=${Process.myPid()}\")\n                    val bufferedReader = process.inputStream.bufferedReader()\n                    bufferedReader.forEachLine { line ->\n                        out!!.write(line.toByteArray())\n                        out.write('\\n'.code.toByte().toInt())\n                    }\n                    out!!.close()\n                } catch (e: Exception) {\n                    Log.e(TAG, \"Failed to write logcat to file: $e\")\n                }\n            }\n    }\n\n    fun launchLogcatRequest() {\n        if (Build.VERSION.SDK_INT >= 29) {\n            val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {\n                addCategory(Intent.CATEGORY_OPENABLE)\n                type = \"text/plain\"\n                putExtra(\n                    Intent.EXTRA_TITLE,\n                    \"baresip_logcat_\" + SimpleDateFormat(\n                        \"yyyy_MM_dd_HH_mm_ss\",\n                        Locale.getDefault()\n                    ).format(Date())\n                )\n                putExtra(DocumentsContract.EXTRA_INITIAL_URI, MediaStore.Downloads.EXTERNAL_CONTENT_URI)\n            }\n            logcatRequestLauncher.launch(intent)\n        }\n    }\n\n    LaunchedEffect(key1 = call?.status, key2 = configuration.orientation) {\n        val isConnected = call != null && call.status.value == \"connected\" && !call.held\n        if (isConnected) {\n            if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {\n                call.focusDtmf.value = true\n                delay(300)\n                keyboardController?.show()\n            }\n            else\n                keyboardController?.hide()\n        }\n    }\n\n    if (showPasswordDialog.value)\n        PasswordDialog(\n            ctx = ctx,\n            showPasswordDialog = showPasswordDialog,\n            password = password,\n            keyboardController = keyboardController,\n            title = passwordTitle.value,\n            okAction = {\n                if (password.value != \"\") {\n                    if (passwordTitle.value == encryptPasswordTitle)\n                        backup(ctx, password.value)\n                    else\n                        restore(ctx, password.value, onRestartClick)\n                    password.value = \"\"\n                }\n            },\n            cancelAction = {\n                if (downloadsOutputUri != null) {\n                    Utils.deleteFile(ctx, downloadsOutputUri!!)\n                }\n            }\n        )\n\n    if (showPasswordsDialog.value) {\n        if (passwordAccounts.isNotEmpty()) {\n            val account = passwordAccounts.removeAt(0)\n            val params = account.substringAfter(\">\")\n            if (Utils.paramValue(params, \"auth_user\") != \"\" &&\n                    Utils.paramValue(params, \"auth_pass\") == \"\") {\n                val aor = account.substringAfter(\"<\").substringBefore(\">\")\n                PasswordDialog(\n                    ctx = ctx,\n                    showPasswordDialog = showPasswordsDialog,\n                    password = password,\n                    keyboardController = keyboardController,\n                    title = stringResource(R.string.authentication_password),\n                    message = stringResource(R.string.account) + \" \" + Utils.plainAor(aor),\n                    okAction = {\n                        if (password.value != \"\")\n                            BaresipService.aorPasswords[aor] = password.value\n                        showPasswordsDialog.value = true\n                    },\n                    cancelAction = {\n                        showPasswordsDialog.value = true\n                    }\n                )\n            } else {\n                showPasswordsDialog.value = false\n                showPasswordsDialog.value = true\n            }\n        }\n        else\n            onRequestPermissions()\n    }\n\n    LaunchedEffect(Unit) {\n        if (!BaresipService.isServiceRunning) {\n            val path = ctx.filesDir.absolutePath + \"/accounts\"\n            if (File(path).exists()) {\n                passwordAccounts = String(\n                    Utils.getFileContents(path)!!,\n                    Charsets.UTF_8\n                ).lines().toMutableList()\n                showPasswordsDialog.value = true\n            } else {\n                // Baresip is started for the first time\n                onRequestPermissions()\n            }\n        }\n    }\n\n    val navBackStackEntry by navController.currentBackStackEntryAsState()\n    val currentRoute = navBackStackEntry?.destination?.route\n\n    LaunchedEffect(currentRoute, viewModel.selectedAor.collectAsState()) {\n        if (currentRoute == \"main\") {\n            Log.d(TAG, \"Updating icons for AOR: ${viewModel.selectedAor.value}\")\n            viewModel.triggerAccountUpdate()\n        }\n    }\n\n    Scaffold(\n        modifier = Modifier\n            .fillMaxSize()\n            .imePadding(),\n        containerColor = MaterialTheme.colorScheme.background,\n        topBar = {\n            Column(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .background(MaterialTheme.colorScheme.background)\n                    .padding(\n                        top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()\n                    )\n            ) {\n                TopAppBar(\n                    viewModel = viewModel,\n                    navController = navController,\n                    onBackupClick = { launchBackupRequest() },\n                    onRestoreClick = { launchRestoreRequest() },\n                    onLogcatClick = { launchLogcatRequest() },\n                    onRestartClick = onRestartClick,\n                    onQuitClick = onQuitClick\n                )\n            }\n        },\n        bottomBar = { BottomBar(ctx, viewModel, navController) },\n        content = { contentPadding ->\n            MainContent(navController, viewModel, contentPadding)\n        }\n    )\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun TopAppBar(\n    viewModel: ViewModel,\n    navController: NavController,\n    onBackupClick: () -> Unit,\n    onRestoreClick: () -> Unit,\n    onLogcatClick: () -> Unit,\n    onRestartClick: () -> Unit,\n    onQuitClick: () -> Unit\n) {\n    val ctx = LocalContext.current\n    val currentMicIcon by viewModel.micIcon.collectAsState()\n\n    val am = ctx.getSystemService(Context.AUDIO_SERVICE) as AudioManager\n\n    val recOffImage = Icons.Filled.VoiceOverOff\n    val recOnImage = Icons.Filled.RecordVoiceOver\n    var isRecOn by remember { mutableStateOf(BaresipService.isRecOn) }\n    val isSpeakerOn by viewModel.isSpeakerOn.collectAsState()\n    var menuExpanded by remember { mutableStateOf(false) }\n\n    val about = stringResource(R.string.about)\n    val settings = stringResource(R.string.configuration)\n    val accounts = stringResource(R.string.accounts)\n    val backup = stringResource(R.string.backup)\n    val restore = stringResource(R.string.restore)\n    val logcat = stringResource(R.string.logcat)\n    val restart = stringResource(R.string.restart)\n    val quit = stringResource(R.string.quit)\n\n    TopAppBar(\n        title = {\n            Text(\n                text = stringResource(R.string.baresip),\n                fontSize = 22.sp,\n                fontWeight = FontWeight.Bold\n            )\n        },\n        colors = TopAppBarDefaults.topAppBarColors(\n            containerColor = MaterialTheme.colorScheme.primary,\n            titleContentColor = MaterialTheme.colorScheme.onPrimary,\n            actionIconContentColor = MaterialTheme.colorScheme.onPrimary\n        ),\n        windowInsets = WindowInsets(0, 0, 0, 0),\n        actions = {\n\n            Spacer(modifier = Modifier.width(8.dp))\n\n            val callRecordingTitle = stringResource(R.string.call_recording_title)\n            val callRecordingMessage = stringResource(R.string.call_recording_tip)\n            Box(contentAlignment = Alignment.Center,\n                modifier = Modifier\n                    .size(48.dp)\n                    .clip(CircleShape)\n                    .combinedClickable(\n                        onClick = {\n                            if (Call.call(\"connected\") == null) {\n                                BaresipService.isRecOn = !BaresipService.isRecOn\n                                if (BaresipService.isRecOn) {\n                                    Api.module_load(\"sndfile\")\n                                } else {\n                                    Api.module_unload(\"sndfile\")\n                                }\n                                isRecOn = BaresipService.isRecOn\n                            } else {\n                                Toast.makeText(ctx, R.string.rec_in_call, Toast.LENGTH_SHORT).show()\n                            }\n                        },\n                        onLongClick = {\n                            alertTitle.value = callRecordingTitle\n                            alertMessage.value = callRecordingMessage\n                            showAlert.value = true\n                        }\n                    )\n            ) {\n                Icon(\n                    imageVector = if (isRecOn) recOnImage else recOffImage,\n                    contentDescription = null,\n                    tint = if (isRecOn)\n                        MaterialTheme.colorScheme.error\n                    else\n                        MaterialTheme.colorScheme.onPrimary,\n                    modifier = Modifier.size(40.dp)\n                )\n            }\n\n            Spacer(modifier = Modifier.width(22.dp))\n\n            val microPhoneTitle = stringResource(R.string.microphone_title)\n            val microPhoneMessage = stringResource(R.string.microphone_tip)\n            Box(\n                contentAlignment = Alignment.Center,\n                modifier = Modifier\n                    .size(48.dp)\n                    .clip(CircleShape)\n                    .combinedClickable(\n                        onClick = {\n                            if (Call.call(\"connected\") != null) {\n                                BaresipService.isMicMuted = !BaresipService.isMicMuted\n                                if (BaresipService.isMicMuted) {\n                                    viewModel.updateMicIcon(Icons.Filled.MicOff)\n                                    Api.calls_mute(true)\n                                } else {\n                                    viewModel.updateMicIcon(Icons.Filled.Mic)\n                                    Api.calls_mute(false)\n                                }\n                            }\n                        },\n                        onLongClick = {\n                            alertTitle.value = microPhoneTitle\n                            alertMessage.value = microPhoneMessage\n                            showAlert.value = true\n                        }\n                    )\n            ) {\n                Icon(\n                    imageVector = currentMicIcon,\n                    contentDescription = null,\n                    tint = if (BaresipService.isMicMuted)\n                        MaterialTheme.colorScheme.error\n                    else\n                        MaterialTheme.colorScheme.onPrimary,\n                    modifier = Modifier.size(40.dp)\n                )\n            }\n\n            Spacer(modifier = Modifier.width(16.dp))\n\n            val speakerPhoneTitle = stringResource(R.string.speakerphone_title)\n            val speakerPhoneMessage = stringResource(R.string.speakerphone_tip)\n            Box(\n                contentAlignment = Alignment.Center,\n                modifier = Modifier\n                    .size(48.dp)\n                    .clip(CircleShape)\n                    .combinedClickable(\n                        onClick = {\n                            val isCurrentlyOn = isSpeakerOn\n                            val aor = viewModel.selectedAor.value\n                            val ua = uas.value.find { it.account.aor == aor }\n                            val call = ua?.currentCall()\n                            val connection =\n                                if (call != null) ConnectionService.connections[call.callp] else null\n                            if (connection != null) {\n                                @Suppress(\"DEPRECATION\")\n                                connection.setAudioRoute(\n                                    if (isCurrentlyOn)\n                                        android.telecom.CallAudioState.ROUTE_EARPIECE\n                                    else\n                                        android.telecom.CallAudioState.ROUTE_SPEAKER\n                                )\n                            } else {\n                                Utils.toggleSpeakerPhone(ContextCompat.getMainExecutor(ctx), am)\n                            }\n                        },\n                        onLongClick = {\n                            alertTitle.value = speakerPhoneTitle\n                            alertMessage.value = speakerPhoneMessage\n                            showAlert.value = true\n                        }\n                    )\n            ) {\n                Icon(\n                    imageVector = Icons.Filled.SpeakerPhone,\n                    contentDescription = null,\n                    tint = if (isSpeakerOn)\n                        MaterialTheme.colorScheme.error\n                    else\n                        MaterialTheme.colorScheme.onPrimary,\n                    modifier = Modifier.size(40.dp)\n                )\n            }\n\n            IconButton(\n                onClick = { menuExpanded = !menuExpanded }\n            ) {\n                Icon(\n                    imageVector = Icons.Filled.Menu,\n                    contentDescription = \"Menu\",\n                    tint = MaterialTheme.colorScheme.onPrimary\n                )\n            }\n\n            DropdownMenu(\n                expanded = menuExpanded,\n                onDismissRequest = { menuExpanded = false },\n                items = if (Build.VERSION.SDK_INT >= 29)\n                    listOf(about, settings, accounts, backup, restore, logcat, restart, quit)\n                else\n                    listOf(about, settings, accounts, backup, restore, restart, quit),\n                onItemClick = { selectedItem ->\n                    menuExpanded = false\n                    when (selectedItem) {\n                        about -> { navController.navigate(\"about\") }\n                        settings -> { navController.navigate(\"settings\") }\n                        accounts -> { navController.navigate(\"accounts\") }\n                        backup -> onBackupClick()\n                        restore -> onRestoreClick()\n                        logcat -> onLogcatClick()\n                        restart -> onRestartClick()\n                        quit -> onQuitClick()\n                    }\n                }\n            )\n        }\n    )\n}\n\n@Composable\nprivate fun BottomBar(ctx: Context, viewModel: ViewModel, navController: NavController) {\n\n    val aor by viewModel.selectedAor.collectAsState()\n    val accountUpdate by viewModel.accountUpdate.collectAsState()\n\n    val showVmIcon = remember(aor, accountUpdate) {\n        if (aor.isNotEmpty()) Account.ofAor(aor)?.vmUri?.isNotEmpty() ?: false else false\n    }\n    val hasNewVoicemail = remember(aor, accountUpdate) {\n        if (aor.isNotEmpty()) (Account.ofAor(aor)?.vmNew ?: 0) > 0 else false\n    }\n    val hasUnreadMessages = remember(aor, accountUpdate) {\n        if (aor.isNotEmpty()) Account.ofAor(aor)?.unreadMessages ?: false else false\n    }\n    val hasMissedCalls = remember(aor, accountUpdate) {\n        if (aor.isNotEmpty()) Account.ofAor(aor)?.missedCalls ?: false else false\n    }\n\n    val isDialpadVisible by viewModel.isDialpadVisible.collectAsState()\n\n    val buttonSize = 48.dp\n\n    Row(\n        modifier = Modifier\n            .fillMaxWidth()\n            .navigationBarsPadding()\n            .padding(bottom = 16.dp),\n        horizontalArrangement = Arrangement.SpaceEvenly,\n        verticalAlignment = Alignment.CenterVertically\n    ) {\n\n        if (showVmIcon)\n            IconButton(\n                // Disable the button if no account is selected\n                enabled = aor.isNotEmpty(),\n                onClick = {\n                    val ua = UserAgent.ofAor(aor)!!\n                    val acc = ua.account\n                    if (acc.vmUri.isNotEmpty()) {\n                        dialogTitle.value = ctx.getString(R.string.voicemail_messages)\n                        dialogMessage.value = acc.vmMessages(ctx)\n                        firstText.value = ctx.getString(R.string.cancel)\n                        onFirstClicked.value = {}\n                        secondText.value = \"\"\n                        lastText.value = ctx.getString(R.string.listen)\n                        onLastClicked.value = {\n                            val intent = Intent(ctx, MainActivity::class.java)\n                            intent.putExtra(\"uap\", ua.uap)\n                            intent.putExtra(\"peer\", acc.vmUri)\n                            handleIntent(ctx, viewModel, intent, \"call\")\n                        }\n                        showDialog.value = true\n                    }\n                },\n                modifier = Modifier\n                    .weight(1f)\n                    .size(buttonSize)\n            ) {\n                Icon(\n                    imageVector = Icons.Filled.Voicemail,\n                    contentDescription = null,\n                    Modifier.size(buttonSize),\n                    tint = if (hasNewVoicemail) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.secondary\n                )\n            }\n\n        IconButton(\n            onClick = { navController.navigate(\"contacts\") },\n            modifier = Modifier\n                .weight(1f)\n                .size(buttonSize)\n        ) {\n            Icon(\n                imageVector = Icons.Filled.Person,\n                contentDescription = null,\n                Modifier.size(buttonSize),\n                tint = MaterialTheme.colorScheme.secondary\n            )\n        }\n\n        IconButton(\n            enabled = aor.isNotEmpty(),\n            onClick = {\n                navController.navigate(\"chats/$aor\")\n            },\n            modifier = Modifier\n                .weight(1f)\n                .size(buttonSize)\n        ) {\n            Icon(\n                imageVector = Icons.AutoMirrored.Filled.Chat,\n                contentDescription = null,\n                Modifier.size(buttonSize),\n                tint = if (hasUnreadMessages) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.secondary\n            )\n        }\n\n        IconButton(\n            enabled = aor.isNotEmpty(),\n            onClick = {\n                navController.navigate(\"calls/$aor\")\n            },\n            modifier = Modifier\n                .weight(1f)\n                .size(buttonSize)\n        ) {\n            Icon(\n                imageVector = Icons.Filled.History,\n                contentDescription = null,\n                Modifier.size(buttonSize),\n                tint = if (hasMissedCalls) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.secondary\n            )\n        }\n\n        IconButton(\n            onClick = { viewModel.toggleDialpadVisibility() },\n            modifier = Modifier\n                .weight(1f)\n                .size(buttonSize),\n            enabled = dialpadButtonEnabled.value\n        ) {\n            Icon(\n                imageVector = Icons.Filled.Dialpad,\n                contentDescription = null,\n                modifier = Modifier.size(buttonSize),\n                tint = if (isDialpadVisible)\n                    MaterialTheme.colorScheme.error\n                else\n                    MaterialTheme.colorScheme.secondary\n            )\n        }\n    }\n}\n\nprivate val alertTitle = mutableStateOf(\"\")\nprivate val alertMessage = mutableStateOf(\"\")\nprivate val showAlert = mutableStateOf(false)\n\nprivate val dialogTitle = mutableStateOf(\"\")\nprivate val dialogMessage = mutableStateOf(\"\")\nprivate val firstText = mutableStateOf(\"\")\nprivate val onFirstClicked = mutableStateOf({})\n\nprivate val secondText = mutableStateOf(\"\")\nprivate val onSecondClicked = mutableStateOf({})\nprivate val lastText = mutableStateOf(\"\")\nprivate val onLastClicked = mutableStateOf({})\nprivate val showDialog = mutableStateOf(false)\n\n@Composable\nprivate fun CallCard(\n    ctx: Context,\n    viewModel: ViewModel,\n    call: Call?,\n    dialerState: ViewModel.DialerState?\n) {\n    Column {\n        CallUriRow(ctx, viewModel, call, dialerState)\n        CallRow(ctx, viewModel, call, dialerState)\n        if (call != null && call.showOnHoldNotice.value)\n            OnHoldNotice()\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun MainContent(navController: NavController, viewModel: ViewModel, contentPadding: PaddingValues) {\n\n    var isRefreshing by remember { mutableStateOf(false) }\n    val refreshState = rememberPullToRefreshState()\n    var offset by remember { mutableFloatStateOf(0f) }\n    val swipeThreshold = 200\n    val ctx = LocalContext.current\n\n    val calls by viewModel.calls.collectAsState()\n    val selectedAor by viewModel.selectedAor.collectAsState()\n    val aorCalls = calls.filter { it.ua.account.aor == selectedAor }\n    val hasActiveCalls = aorCalls.any { !it.callOnHold.value }\n    val conferenceCall = aorCalls.any { it.conferenceCall }\n\n    LaunchedEffect(isRefreshing) {\n        if (isRefreshing) {\n            delay(1000)\n            isRefreshing = false\n        }\n    }\n\n    if (showAlert.value)\n        AlertDialog(\n            showDialog = showAlert,\n            title = alertTitle.value,\n            message = alertMessage.value,\n            lastButtonText = stringResource(R.string.ok),\n        )\n\n    if (showDialog.value)\n        AlertDialog(\n            showDialog = showDialog,\n            title = stringResource(R.string.confirmation),\n            message = dialogMessage.value,\n            firstButtonText = firstText.value,\n            onFirstClicked = onFirstClicked.value,\n            secondButtonText = secondText.value,\n            onSecondClicked = onSecondClicked.value,\n            lastButtonText = lastText.value,\n            onLastClicked = onLastClicked.value,\n        )\n\n    SelectableAlertDialog(\n        openDialog = showSelectItemDialog,\n        title = stringResource(R.string.choose_destination_uri),\n        items = selectItems.value,\n        onItemClicked = selectItemAction.value,\n        neutralButtonText = stringResource(R.string.cancel),\n        onNeutralClicked = {}\n    )\n\n    Column(\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(contentPadding)\n            .padding(top = 18.dp, bottom = 6.dp, start = 16.dp, end = 16.dp)\n            .fillMaxSize()\n            .pullToRefresh(\n                state = refreshState,\n                isRefreshing = isRefreshing,\n                onRefresh = {\n                    isRefreshing = true\n                    if (uas.value.isNotEmpty()) {\n                        if (viewModel.selectedAor.value == \"\")\n                            spinToAor(viewModel, uas.value.first().account.aor)\n                        val ua = UserAgent.ofAor(viewModel.selectedAor.value)!!\n                        if (ua.account.regint > 0)\n                            Api.ua_register(ua.uap)\n                    }\n                },\n                enabled = pullToRefreshEnabled.value,\n            )\n            .pointerInput(Unit) {\n                detectHorizontalDragGestures(\n                    onDragStart = { offset = 0f },\n                    onDragEnd = {\n                        if (offset < -swipeThreshold) {\n                            if (uas.value.isNotEmpty()) {\n                                val curPos = UserAgent.findAorIndex(viewModel.selectedAor.value)\n                                val newPos = if (curPos == null)\n                                    0\n                                else\n                                    (curPos + 1) % uas.value.size\n                                if (curPos != newPos) {\n                                    val ua = uas.value[newPos]\n                                    spinToAor(viewModel, ua.account.aor)\n                                    showCall(ctx, viewModel, ua)\n                                }\n                            }\n                        } else if (offset > swipeThreshold) {\n                            if (uas.value.isNotEmpty()) {\n                                val curPos = UserAgent.findAorIndex(viewModel.selectedAor.value)\n                                val newPos = when (curPos) {\n                                    null -> 0\n                                    0 -> uas.value.size - 1\n                                    else -> curPos - 1\n                                }\n                                if (curPos != newPos) {\n                                    val ua = uas.value[newPos]\n                                    spinToAor(viewModel, ua.account.aor)\n                                    showCall(ctx, viewModel, ua)\n                                }\n                            }\n                        }\n                    }\n                ) { _, dragAmount ->\n                    offset += dragAmount\n                }\n            }\n            .verticalScroll(rememberScrollState()),\n        verticalArrangement = Arrangement.Top,\n        horizontalAlignment = Alignment.CenterHorizontally,\n    ) {\n        AccountSpinner(ctx, viewModel, navController)\n\n        aorCalls.forEach { call ->\n            key(call.callp) {\n                CallCard(ctx = ctx, viewModel = viewModel, call = call, dialerState = null)\n            }\n        }\n\n        if (!hasActiveCalls || conferenceCall)\n            CallCard(ctx = ctx, viewModel = viewModel, call = null, dialerState = viewModel.dialerState)\n\n        Indicator(\n            modifier = Modifier.align(Alignment.CenterHorizontally),\n            isRefreshing = isRefreshing,\n            state = refreshState,\n        )\n    }\n}\n\n@OptIn(ExperimentalFoundationApi::class)\n@Composable\nprivate fun AccountSpinner(ctx: Context, viewModel: ViewModel, navController: NavController) {\n\n    var expanded by rememberSaveable { mutableStateOf(false) }\n    val selected: String by viewModel.selectedAor.collectAsState()\n\n    if (uas.value.isEmpty())\n        viewModel.updateSelectedAor(\"\")\n    else\n        if (selected == \"\" || UserAgent.ofAor(selected) == null) {\n            viewModel.updateSelectedAor(uas.value.first().account.aor)\n        }\n\n    showCall(ctx, viewModel, UserAgent.ofAor(selected))\n    viewModel.triggerAccountUpdate()\n\n    if (selected == \"\") {\n        OutlinedButton(\n            onClick = {\n                navController.navigate(\"accounts\")\n            },\n            modifier = Modifier\n                .padding(horizontal = 4.dp)\n                .height(50.dp)\n                .fillMaxWidth(),\n            colors = ButtonColors(\n                containerColor = MaterialTheme.colorScheme.surfaceVariant,\n                contentColor = MaterialTheme.colorScheme.onSurfaceVariant,\n                disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant,\n                disabledContentColor = MaterialTheme.colorScheme.onSurfaceVariant\n            ),\n            shape = RoundedCornerShape(12.dp),\n            contentPadding = PaddingValues(horizontal = 10.dp)\n        ) {\n            Text(text = \"\")\n        }\n    }\n    else\n        OutlinedButton(\n            onClick = {\n                expanded = !expanded\n            },\n            enabled = true,\n            modifier = Modifier\n                .padding(horizontal = 4.dp)\n                .height(50.dp)\n                .pointerInput(Unit) {\n                    detectTapGestures(\n                        onPress = {\n                            expanded = true\n                        },\n                        onLongPress = {\n                            val ua = UserAgent.ofAor(selected)\n                            if (ua != null) {\n                                val acc = ua.account\n                                if (Api.account_regint(acc.accp) > 0) {\n                                    Api.account_set_regint(acc.accp, 0)\n                                    Api.ua_unregister(ua.uap)\n                                } else {\n                                    Api.account_set_regint(\n                                        acc.accp,\n                                        acc.configuredRegInt\n                                    )\n                                    Api.ua_register(ua.uap)\n                                }\n                                acc.regint = Api.account_regint(acc.accp)\n                                Account.saveAccounts()\n                            }\n                        }\n                    )\n                },\n            colors = ButtonColors(\n                containerColor = MaterialTheme.colorScheme.surfaceVariant,\n                contentColor = MaterialTheme.colorScheme.onSurfaceVariant,\n                disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant,\n                disabledContentColor = MaterialTheme.colorScheme.onSurfaceVariant\n            ),\n            shape = RoundedCornerShape(12.dp),\n            contentPadding = PaddingValues(horizontal = 10.dp)\n        ) {\n            Icon(\n                imageVector = ImageVector.vectorResource(\n                    uasStatus.value[selected] ?: R.drawable.circle_yellow\n                ),\n                contentDescription = null,\n                tint = Color.Unspecified,\n                modifier = Modifier\n                    .padding(end = 10.dp)\n                    .clickable(onClick = {\n                        navController.navigate(\"account/$selected/old\")\n                    })\n            )\n            Text(\n                text = Account.ofAor(selected)?.text() ?: \"\",\n                fontSize = 17.sp,\n                fontWeight = FontWeight.Bold,\n                overflow = TextOverflow.Ellipsis,\n                maxLines = 1,\n                modifier = Modifier\n                    .weight(1f)\n                    .combinedClickable(\n                        onClick = { expanded = true },\n                        onLongClick = {\n                            val ua = UserAgent.ofAor(selected)\n                            if (ua != null) {\n                                val acc = ua.account\n                                if (Api.account_regint(acc.accp) > 0) {\n                                    Api.account_set_regint(acc.accp, 0)\n                                    Api.ua_unregister(ua.uap)\n                                } else {\n                                    Api.account_set_regint(\n                                        acc.accp,\n                                        acc.configuredRegInt\n                                    )\n                                    Api.ua_register(ua.uap)\n                                }\n                                acc.regint = Api.account_regint(acc.accp)\n                                Account.saveAccounts()\n                            }\n                        }\n                    )\n            )\n            Icon(\n                imageVector = if (expanded)\n                    Icons.Filled.KeyboardArrowUp\n                else\n                    Icons.Filled.KeyboardArrowDown,\n                contentDescription = null\n            )\n            androidx.compose.material3.DropdownMenu(\n                expanded = expanded,\n                onDismissRequest = { expanded = false },\n                containerColor = MaterialTheme.colorScheme.surfaceContainer,\n            ) {\n                uas.value.forEachIndexed { index, ua ->\n                    val acc = ua.account\n                    DropdownMenuItem(\n                        onClick = {\n                            expanded = false\n                            viewModel.updateSelectedAor(acc.aor)\n                            showCall(ctx, viewModel, ua)\n                            viewModel.triggerAccountUpdate()\n                        },\n                        text = { Text(\n                            text = acc.text(),\n                            fontSize = 17.sp,\n                            fontWeight = FontWeight.Bold\n                        ) },\n                        leadingIcon = {\n                            Icon(\n                                imageVector = ImageVector.vectorResource(uasStatus.value[acc.aor]!!),\n                                contentDescription = null,\n                                tint = Color.Unspecified,\n                            )\n                        }\n                    )\n                    if (index < uas.value.size - 1) {\n                        HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant)\n                    }\n                }\n            }\n        }\n}\n\n@Composable\nprivate fun CallUriRow(\n    ctx: Context,\n    viewModel: ViewModel,\n    call: Call?,\n    dialerState: ViewModel.DialerState?\n) {\n\n    val isDialer = dialerState != null\n\n    val suggestions by remember { contactNames }\n    var filteredSuggestions by remember { mutableStateOf<List<AnnotatedString>>(emptyList()) }\n    val focusRequester = remember { FocusRequester() }\n    val lazyListState = rememberLazyListState()\n    val isDialpadVisible by viewModel.isDialpadVisible.collectAsState()\n\n    Row(\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(top = 4.dp, bottom = 8.dp),\n        verticalAlignment = Alignment.CenterVertically\n    ) {\n        Column(\n            modifier = Modifier.weight(1f),\n            horizontalAlignment = Alignment.CenterHorizontally\n        ) {\n            OutlinedTextField(\n                value = if (isDialer) dialerState.callUri.value else call!!.callUri.value,\n                readOnly = if (isDialer) !dialerState.callUriEnabled.value else !call!!.callUriEnabled.value,\n                singleLine = true,\n                onValueChange = {\n                    if (isDialer) {\n                        if (it != dialerState.callUri.value) {\n                            dialerState.callUri.value = it\n                            if (it == \"\") {\n                                dialerState.showCallButton.value = true\n                                dialerState.showCallConferenceButton.value = true\n                            }\n                            val normalizedInput = Utils.unaccent(it)\n                            filteredSuggestions = suggestions\n                                .filter { suggestion ->\n                                    it.length > 1 &&\n                                            Utils.unaccent(suggestion)\n                                                .contains(normalizedInput, ignoreCase = true)\n                                }\n                                .map { suggestion ->\n                                    Utils.buildAnnotatedStringWithHighlight(suggestion, it)\n                                }\n                            dialerState.showSuggestions.value = it.length > 1\n                        }\n                    }\n                },\n                trailingIcon = {\n                    if (isDialer && dialerState.callUriEnabled.value && dialerState.callUri.value.isNotEmpty())\n                        Icon(Icons.Outlined.Clear,\n                            contentDescription = null,\n                            modifier = Modifier.clickable {\n                                if (dialerState.showSuggestions.value)\n                                    dialerState.showSuggestions.value = false\n                                dialerState.callUri.value = \"\"\n                                dialerState.showCallButton.value = true\n                                dialerState.showCallConferenceButton.value = true\n                            },\n                            tint = MaterialTheme.colorScheme.onSurfaceVariant\n                        )\n                },\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .padding(start = 4.dp, end = 4.dp, top = 12.dp, bottom = 2.dp)\n                    .focusRequester(focusRequester)\n                    .onFocusChanged {\n                        if (isDialer) {\n                            val account = Account.ofAor(viewModel.selectedAor.value)\n                            if (account != null && account.numericKeypad)\n                                if (!isDialpadVisible)\n                                    viewModel.toggleDialpadVisibility()\n                        }\n                    },\n                label = {\n                    Text(\n                        text = if (isDialer)\n                            dialerState.callUriLabel.value\n                        else\n                            call!!.callUriLabel.value,\n                        fontSize = 18.sp\n                    )\n                },\n                textStyle = TextStyle(fontSize = 18.sp),\n                keyboardOptions = if (isDialpadVisible)\n                    KeyboardOptions(keyboardType = KeyboardType.Phone)\n                else\n                    KeyboardOptions(keyboardType = KeyboardType.Text)\n            )\n            Spacer(modifier = Modifier.height(8.dp))\n            Column(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .shadow(8.dp, RoundedCornerShape(8.dp))\n                    .background(\n                        color = MaterialTheme.colorScheme.surfaceVariant,\n                        shape = RoundedCornerShape(8.dp)\n                    )\n                    .animateContentSize()\n            ) {\n                if (isDialer && dialerState.showSuggestions.value && filteredSuggestions.isNotEmpty()) {\n                    Box(\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .heightIn(max = 150.dp)\n                    ) {\n                        LazyColumn(\n                            modifier = Modifier\n                                .fillMaxWidth()\n                                .verticalScrollbar(state = lazyListState, width = 6.dp),\n                            horizontalAlignment = Alignment.Start,\n                            state = lazyListState\n                        ) {\n                            items(\n                                items = filteredSuggestions,\n                                key = { suggestion -> suggestion.toString() }\n                            ) { suggestion ->\n                                Box(\n                                    modifier = Modifier\n                                        .fillMaxWidth()\n                                        .clickable {\n                                            dialerState.callUri.value = suggestion.toString()\n                                            dialerState.showSuggestions.value = false\n                                        }\n                                        .padding(12.dp)\n                                ) {\n                                    Text(\n                                        text = suggestion,\n                                        modifier = Modifier.fillMaxWidth(),\n                                        color = MaterialTheme.colorScheme.onSurfaceVariant,\n                                        fontSize = 18.sp\n                                    )\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        if (call != null && call.showCallTimer.value) {\n            CallTimer(\n                initialDurationSeconds = call.callDuration.toLong(),\n                modifier = Modifier.padding(\n                    start = 6.dp,\n                    top = 6.dp,\n                    end = if (call.securityIconTint.value != -1) 6.dp else 0.dp\n                )\n            )\n        }\n        if (call != null && call.securityIconTint.value != -1)\n            Box(\n                modifier = Modifier\n                    .padding(top = 4.dp)\n                    .size(32.dp)\n                    .clip(CircleShape)\n                    .clickable {\n                        when (call.securityIconTint.value) {\n                            R.color.colorTrafficRed -> {\n                                alertTitle.value = ctx.getString(R.string.alert)\n                                alertMessage.value = ctx.getString(R.string.call_not_secure)\n                                showAlert.value = true\n                            }\n\n                            R.color.colorTrafficYellow -> {\n                                alertTitle.value = ctx.getString(R.string.alert)\n                                alertMessage.value = ctx.getString(R.string.peer_not_verified)\n                                showAlert.value = true\n                            }\n\n                            R.color.colorTrafficGreen -> {\n                                dialogTitle.value = ctx.getString(R.string.info)\n                                dialogMessage.value = ctx.getString(R.string.call_is_secure)\n                                firstText.value = ctx.getString(R.string.cancel)\n                                onFirstClicked.value = {}\n                                secondText.value = \"\"\n                                lastText.value = ctx.getString(R.string.unverify)\n                                onLastClicked.value = {\n                                    if (Api.cmd_exec(\"zrtp_unverify \" + call.zid) != 0)\n                                        Log.e(\n                                            TAG,\n                                            \"Command 'zrtp_unverify ${call.zid}' failed\"\n                                        )\n                                    else\n                                        call.securityIconTint.value = R.color.colorTrafficYellow\n                                }\n                                showDialog.value = true\n                            }\n                        }\n                    },\n                contentAlignment = Alignment.Center\n            ) {\n                Icon(\n                    imageVector = if (call.securityIconTint.value == R.color.colorTrafficRed)\n                        Icons.Filled.LockOpen\n                    else\n                        Icons.Filled.Lock,\n                    contentDescription = null,\n                    modifier = Modifier.size(28.dp),\n                    tint = colorResource(call.securityIconTint.value)\n                )\n            }\n    }\n}\n\n@Composable\nprivate fun CallTimer(\n    initialDurationSeconds: Long,\n    modifier: Modifier = Modifier\n) {\n    val startTime = remember(initialDurationSeconds) {\n        SystemClock.elapsedRealtime() - (initialDurationSeconds * 1000L)\n    }\n\n    var timeText by remember { mutableStateOf(\"\") }\n\n    LaunchedEffect(startTime) {\n        while (true) {\n            val now = SystemClock.elapsedRealtime()\n            val elapsedMillis = now - startTime\n            val seconds = if (elapsedMillis > 0) elapsedMillis / 1000 else 0\n            timeText = android.text.format.DateUtils.formatElapsedTime(seconds)\n            delay(1000L)\n        }\n    }\n\n    Text(\n        text = timeText,\n        fontSize = 16.sp,\n        color = MaterialTheme.colorScheme.onBackground,\n        modifier = modifier\n    )\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun CallRow(\n    ctx: Context,\n    viewModel: ViewModel,\n    call: Call?,\n    dialerState: ViewModel.DialerState?\n) {\n\n    val isDialer = dialerState != null\n    val isDialpadVisible by viewModel.isDialpadVisible.collectAsState()\n\n    Row( modifier = Modifier\n        .fillMaxWidth(),\n        verticalAlignment = Alignment.CenterVertically,\n        horizontalArrangement = Arrangement.Absolute.SpaceBetween\n    ) {\n        if (isDialer) {\n            if (dialerState.showCallButton.value)\n                IconButton(\n                    modifier = Modifier.size(48.dp),\n                    enabled = dialerState.callButtonsEnabled.value,\n                    onClick = {\n                        if (!dialerState.callButtonsEnabled.value) return@IconButton\n                        dialerState.showCallConferenceButton.value = false\n                        dialerState.showSuggestions.value = false\n                        callClick(ctx, viewModel, dialerState)\n                    },\n                ) {\n                    Icon(\n                        imageVector = Icons.Filled.Call,\n                        modifier = Modifier.size(42.dp),\n                        tint = colorResource(if (dialerState.callButtonsEnabled.value)\n                            R.color.colorTrafficGreen\n                        else\n                            R.color.colorTrafficYellow),\n                        contentDescription = null,\n                    )\n                }\n            if (dialerState.showCallConferenceButton.value) {\n                Spacer(modifier = Modifier.weight(1f, true))\n                IconButton(\n                    modifier = Modifier.size(48.dp),\n                    enabled = dialerState.callButtonsEnabled.value,\n                    onClick = {\n                        if (!dialerState.callButtonsEnabled.value) return@IconButton\n                        dialerState.showCallButton.value = false\n                        dialerState.showSuggestions.value = false\n                        callClick(ctx, viewModel, dialerState)\n                    }\n                ) {\n                    Icon(\n                        imageVector = Icons.Filled.AddIcCall,\n                        modifier = Modifier.size(42.dp),\n                        tint = colorResource(\n                            if (dialerState.callButtonsEnabled.value)\n                                R.color.colorTrafficGreen\n                            else\n                                R.color.colorTrafficYellow\n                        ),\n                        contentDescription = null,\n                    )\n                }\n            }\n        }\n        else {\n            if (call!!.showCancelButton.value) {\n                if (!call.conferenceCall)\n                    Spacer(modifier = Modifier.weight(1f))\n                IconButton(\n                    modifier = Modifier.size(48.dp),\n                    enabled = !call.terminated.value,\n                    onClick = {\n                        if (call.terminated.value) return@IconButton\n                        call.terminated.value = true\n                        val connection = ConnectionService.connections[call.callp]\n                        if (connection != null)\n                            connection.onDisconnect()\n                        else {\n                            Log.d(TAG, \"AoR ${call.ua.account.aor} canceling call ${call.callp}\")\n                            Api.ua_hangup(call.ua.uap, call.callp, 487, \"Request Terminated\")\n                        }\n                    },\n                ) {\n                    Icon(\n                        imageVector = Icons.Filled.CallEnd,\n                        modifier = Modifier.size(42.dp),\n                        tint = colorResource(R.color.colorTrafficRed),\n                        contentDescription = null,\n                    )\n                }\n                Spacer(modifier = Modifier.width(12.dp))\n            }\n\n            if (call.showHangupButton.value) {\n\n                IconButton(\n                    modifier = Modifier.size(48.dp),\n                    enabled = !call.terminated.value,\n                    onClick = {\n                        if (call.terminated.value) return@IconButton\n                        call.terminated.value = true\n                        val connection = ConnectionService.connections[call.callp]\n                        if (connection != null)\n                            connection.onDisconnect()\n                        else {\n                            Log.d(TAG, \"AoR ${call.ua.account.aor} hanging up call ${call.callp}\")\n                            Api.ua_hangup(call.ua.uap, call.callp, 487, \"Request Terminated\")\n                        }\n                    }\n                ) {\n                    Icon(\n                        imageVector = Icons.Filled.CallEnd,\n                        modifier = Modifier.size(42.dp),\n                        tint = colorResource(R.color.colorTrafficRed),\n                        contentDescription = null,\n                    )\n                }\n\n                if (!call.conferenceCall)\n                    IconButton(    modifier = Modifier.size(48.dp),\n                        onClick = {\n                            if (call.callOnHold.value) {\n                                Log.d(TAG, \"User requested resume for ${call.callp}\")\n                                call.resume() // This now automatically holds other calls\n                            } else {\n                                Log.d(TAG, \"User requested hold for ${call.callp}\")\n                                call.hold()\n                            }\n                        },\n                    ) {\n                        Icon(\n                            imageVector = Icons.Outlined.PauseCircle,\n                            modifier = Modifier.size(42.dp),\n                            tint = if (call.callOnHold.value)\n                                MaterialTheme.colorScheme.error\n                            else\n                                MaterialTheme.colorScheme.secondary,\n                            contentDescription = null,\n                        )\n                    }\n\n                var showTransferDialog by remember { mutableStateOf(false) }\n\n                if (!call.conferenceCall)\n                    IconButton(\n                        modifier = Modifier.size(48.dp),\n                        enabled = call.transferButtonEnabled.value,\n                        onClick = {\n                            if (call.onHoldCall != null) {\n                                if (!Api.call_supported(call.callp, Api.REPLACES)) {\n                                   alertTitle.value = ctx.getString(R.string.notice)\n                                    alertMessage.value = ctx.getString(R.string.replaces_not_supported)\n                                    showAlert.value = true\n                                }\n                                else {\n                                    val connection = ConnectionService.connections[call.callp]\n                                    connection?.onHold()\n                                    if (!call.executeTransfer()) {\n                                        alertTitle.value = ctx.getString(R.string.notice)\n                                        alertMessage.value = ctx.getString(R.string.transfer_failed)\n                                        showAlert.value = true\n                                    }\n                                }\n                            }\n                            else\n                                showTransferDialog = true\n                        },\n                    ) {\n                        Icon(\n                            imageVector = Icons.Outlined.ArrowCircleRight,\n                            modifier = Modifier.size(42.dp),\n                            tint = if (call.callTransfer.value)\n                                MaterialTheme.colorScheme.error\n                            else\n                                MaterialTheme.colorScheme.secondary,\n                            contentDescription = null,\n                        )\n                    }\n\n                if (showTransferDialog) {\n\n                    val showDialog = remember { mutableStateOf(true) }\n                    val blindChecked = remember { mutableStateOf(true) }\n\n                    if (showDialog.value)\n                        BasicAlertDialog(\n                            onDismissRequest = {\n                                viewModel.requestHideKeyboard()\n                                showDialog.value = false\n                                showTransferDialog = false\n                            }\n                        ) {\n                            Card(\n                                modifier = Modifier\n                                    .fillMaxWidth()\n                                    .padding(top = 16.dp, start = 16.dp, end = 16.dp, bottom = 0.dp),\n                                shape = RoundedCornerShape(16.dp),\n                                colors = CardDefaults.cardColors(\n                                    containerColor = MaterialTheme.colorScheme.surfaceContainerHigh\n                                )\n                            ) {\n                                Column(modifier = Modifier.padding(16.dp)) {\n                                    Text(\n                                        text = stringResource(R.string.call_transfer),\n                                        fontSize = 20.sp,\n                                        color = MaterialTheme.colorScheme.onSurface,\n                                    )\n                                    var transferUri by remember { mutableStateOf(\"\") }\n                                    val suggestions by remember { contactNames }\n                                    var filteredSuggestions by remember { mutableStateOf<List<AnnotatedString>>(emptyList()) }\n                                    val focusRequester = remember { FocusRequester() }\n                                    val lazyListState = rememberLazyListState()\n                                    OutlinedTextField(\n                                        value = transferUri,\n                                        singleLine = true,\n                                        onValueChange = {\n                                            if (it != transferUri) {\n                                                transferUri = it\n                                                if (it.length > 1) {\n                                                    val normalizedInput = Utils.unaccent(it)\n                                                    filteredSuggestions =\n                                                        suggestions.filter { suggestion ->\n                                                            Utils.unaccent(suggestion)\n                                                                .contains(normalizedInput, ignoreCase = true)\n                                                        }\n                                                            .map { suggestion ->\n                                                                Utils.buildAnnotatedStringWithHighlight(suggestion, it)\n                                                            }\n                                                }\n                                                call.showSuggestions.value = transferUri.length > 1\n                                            }\n                                        },\n                                        trailingIcon = {\n                                            if (transferUri.isNotEmpty())\n                                                Icon(\n                                                    Icons.Outlined.Clear,\n                                                    contentDescription = null,\n                                                    modifier = Modifier.clickable {\n                                                        if (call.showSuggestions.value)\n                                                            call.showSuggestions.value = false\n                                                        else\n                                                            transferUri = \"\"\n                                                    },\n                                                    tint = MaterialTheme.colorScheme.onSurfaceVariant\n                                                )\n                                        },\n                                        modifier = Modifier\n                                            .fillMaxWidth()\n                                            .padding(\n                                                start = 4.dp,\n                                                end = 4.dp,\n                                                top = 12.dp,\n                                                bottom = 2.dp\n                                            )\n                                            .focusRequester(focusRequester),\n                                        label = { Text(stringResource(R.string.transfer_destination)) },\n                                        textStyle = TextStyle(fontSize = 18.sp),\n                                        keyboardOptions = if (isDialpadVisible)\n                                            KeyboardOptions(keyboardType = KeyboardType.Phone)\n                                        else\n                                            KeyboardOptions(keyboardType = KeyboardType.Text)\n                                    )\n                                    Spacer(modifier = Modifier.height(8.dp))\n                                    Column(\n                                        modifier = Modifier\n                                            .fillMaxWidth()\n                                            .shadow(8.dp, RoundedCornerShape(8.dp))\n                                            .background(\n                                                color = MaterialTheme.colorScheme.surfaceVariant,\n                                                shape = RoundedCornerShape(8.dp)\n                                            )\n                                            .animateContentSize()\n                                    ) {\n                                        if (call.showSuggestions.value && filteredSuggestions.isNotEmpty()) {\n                                            Box(modifier = Modifier\n                                                .fillMaxWidth()\n                                                .heightIn(max = 150.dp)) {\n                                                LazyColumn(\n                                                    modifier = Modifier\n                                                        .fillMaxWidth()\n                                                        .verticalScrollbar(\n                                                            state = lazyListState,\n                                                            width = 6.dp,\n                                                        ),\n                                                    horizontalAlignment = Alignment.Start,\n                                                    state = lazyListState,\n                                                ) {\n                                                    items(\n                                                        items = filteredSuggestions,\n                                                        key = { suggestion -> suggestion.toString() }\n                                                    ) { suggestion ->\n                                                        Box(\n                                                            modifier = Modifier\n                                                                .fillMaxWidth()\n                                                                .clickable {\n                                                                    transferUri =\n                                                                        suggestion.toString()\n                                                                    call.showSuggestions.value =\n                                                                        false\n                                                                }\n                                                                .padding(12.dp)\n                                                        ) {\n                                                            Text(\n                                                                text = suggestion,\n                                                                modifier = Modifier.fillMaxWidth(),\n                                                                color = MaterialTheme.colorScheme.onSurfaceVariant,\n                                                                fontSize = 18.sp\n                                                            )\n                                                        }\n                                                    }\n                                                }\n                                            }\n                                        }\n                                    }\n                                    if (call.replaces())\n                                        Row(\n                                            modifier = Modifier.fillMaxWidth(),\n                                            horizontalArrangement = Arrangement.Start,\n                                        ) {\n                                            Row(verticalAlignment = Alignment.CenterVertically) {\n                                                Text(\n                                                    text = stringResource(R.string.blind),\n                                                    color = MaterialTheme.colorScheme.onSurfaceVariant,\n                                                    modifier = Modifier.padding(8.dp),\n                                                )\n                                                Switch(\n                                                    checked = blindChecked.value,\n                                                    onCheckedChange = {\n                                                        blindChecked.value = true\n                                                    }\n                                                )\n                                            }\n                                            Spacer(modifier = Modifier.width(8.dp))\n                                            Row(verticalAlignment = Alignment.CenterVertically) {\n                                                Text(\n                                                    text = stringResource(R.string.attended),\n                                                    color = MaterialTheme.colorScheme.onSurfaceVariant,\n                                                    modifier = Modifier.padding(8.dp),\n                                                )\n                                                Switch(\n                                                    checked = !blindChecked.value,\n                                                    onCheckedChange = {\n                                                        blindChecked.value = false\n                                                    }\n                                                )\n                                            }\n                                        }\n                                    Row(\n                                        modifier = Modifier.fillMaxWidth(),\n                                        horizontalArrangement = Arrangement.End,\n                                        verticalAlignment = Alignment.CenterVertically\n                                    ) {\n                                        TextButton(\n                                            onClick = {\n                                                viewModel.requestHideKeyboard()\n                                                showDialog.value = false\n                                                showTransferDialog = false\n                                            },\n                                            modifier = Modifier.padding(end = 32.dp),\n                                        ) {\n                                            Text(\n                                                text = stringResource(R.string.cancel),\n                                                color = MaterialTheme.colorScheme.onSurfaceVariant\n                                            )\n                                        }\n                                        TextButton(\n                                            onClick = {\n                                                call.showSuggestions.value = false\n                                                var uriText = transferUri.trim()\n                                                if (uriText.isNotEmpty()) {\n                                                    val uris = Contact.contactUris(uriText)\n                                                    if (uris.size > 1) {\n                                                        selectItems.value = uris\n                                                        selectItemAction.value = { index ->\n                                                            val uri = uris[index]\n                                                            transfer(\n                                                                ctx,\n                                                                viewModel,\n                                                                call.ua,\n                                                                if (Utils.isTelNumber(uri)) \"tel:$uri\" else uri,\n                                                                !blindChecked.value\n                                                            )\n                                                            showSelectItemDialog.value = false\n                                                        }\n                                                        showSelectItemDialog.value = true\n                                                    }\n                                                    else {\n                                                        if (uris.size == 1) uriText = uris[0]\n                                                        transfer(\n                                                            ctx,\n                                                            viewModel,\n                                                            call.ua,\n                                                            if (Utils.isTelNumber(uriText)) \"tel:$uriText\" else uriText,\n                                                            !blindChecked.value\n                                                        )\n                                                    }\n                                                    viewModel.requestHideKeyboard()\n                                                    showDialog.value = false\n                                                    showTransferDialog = false\n                                                }\n                                            },\n                                            modifier = Modifier.padding(end = 16.dp),\n                                        ) {\n                                            Text(\n                                                text = stringResource(\n                                                    if (blindChecked.value)\n                                                        R.string.transfer\n                                                    else\n                                                        R.string.call\n                                                ).uppercase(),\n                                                color = MaterialTheme.colorScheme.primary\n                                            )\n                                        }\n                                    }\n                                }\n                            }\n                        }\n\n                }\n\n                val focusRequester = remember { FocusRequester() }\n                val shouldRequestFocus by call.focusDtmf\n                val interactionSource = remember { MutableInteractionSource() }\n                BasicTextField(\n                    value = call.dtmfText.value,\n                    onValueChange = { newText ->\n                        if (newText.length > call.dtmfText.value.length) {\n                            val char = newText.last()\n                            if (char.isDigit() || char == '*' || char == '#') {\n                                Log.d(TAG, \"Got DTMF digit '$char'\")\n                                call.sendDigit(char)\n                            }\n                        }\n                        call.dtmfText.value = newText\n                    },\n                    modifier = Modifier\n                        .width(80.dp)\n                        .focusRequester(focusRequester),\n                    enabled = call.dtmfEnabled.value,\n                    textStyle = TextStyle(\n                        fontSize = 16.sp,\n                        color = MaterialTheme.colorScheme.onSurface\n                    ),\n                    keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone),\n                    cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),\n                    singleLine = true,\n                    interactionSource = interactionSource,\n                    decorationBox = { innerTextField ->\n                        OutlinedTextFieldDefaults.DecorationBox(\n                            value = call.dtmfText.value,\n                            visualTransformation = VisualTransformation.None,\n                            innerTextField = innerTextField,\n                            singleLine = true,\n                            enabled = call.dtmfEnabled.value,\n                            interactionSource = interactionSource,\n                            label = {\n                                Text(\n                                    stringResource(R.string.dtmf),\n                                    style = TextStyle(fontSize = 12.sp)\n                                )\n                            },\n                            contentPadding = PaddingValues(\n                                start = 4.dp,\n                                end = 4.dp,\n                                top = 8.dp,\n                                bottom = 8.dp\n                            ),\n                            colors = OutlinedTextFieldDefaults.colors(\n                                focusedContainerColor = Color.Transparent,\n                                unfocusedContainerColor = Color.Transparent,\n                                disabledContainerColor = Color.Transparent,\n                                focusedBorderColor = MaterialTheme.colorScheme.primary,\n                                unfocusedBorderColor = MaterialTheme.colorScheme.onSurfaceVariant,\n                            )\n                        )\n                    }\n                )\n                LaunchedEffect(shouldRequestFocus) {\n                    if (shouldRequestFocus) {\n                        focusRequester.requestFocus()\n                        call.focusDtmf.value = false\n                    }\n                }\n\n                IconButton(\n                    modifier = Modifier.size(48.dp),\n                    onClick = {\n                        val stats = call.stats(\"audio\")\n                        if (stats.isNotEmpty() && call.startTime != null) {\n                            val parts = ArrayList(stats.split(\",\"))\n                            if (parts[2] == \"0/0\") {\n                                parts[2] = \"?/?\"\n                                parts[3] = \"?/?\"\n                                parts[4] = \"?/?\"\n                            }\n                            val codecs = call.audioCodecs().split(',')\n                            val duration = call.duration()\n                            val txCodec = codecs[0].split(\"/\")\n                            val rxCodec = codecs[1].split(\"/\")\n                            alertTitle.value = ctx.getString(R.string.call_info)\n                            alertMessage.value =\n                                \"${String.format(ctx.getString(R.string.duration), duration)}\\n\" +\n                                        \"${ctx.getString(R.string.codecs)}: ${txCodec[0]} ch ${txCodec[2]}/${rxCodec[0]} ch ${rxCodec[2]}\\n\" +\n                                        \"${String.format(ctx.getString(R.string.rate), parts[0])}\\n\" +\n                                        \"${String.format(ctx.getString(R.string.average_rate), parts[1])}\\n\" +\n                                        \"${ctx.getString(R.string.packets)}: ${parts[2]}\\n\" +\n                                        \"${ctx.getString(R.string.lost)}: ${parts[3]}\\n\" +\n                                        String.format(ctx.getString(R.string.jitter), parts[4])\n                            showAlert.value = true\n                        } else {\n                            alertTitle.value = ctx.getString(R.string.call_info)\n                            alertMessage.value = ctx.getString(R.string.call_info_not_available)\n                            showAlert.value = true\n                        }\n                    },\n                ) {\n                    Icon(\n                        imageVector = Icons.Outlined.Info,\n                        modifier = Modifier.size(36.dp),\n                        tint = MaterialTheme.colorScheme.secondary,\n                        contentDescription = null,\n                    )\n                }\n            }\n\n            if (call.showAnswerRejectButtons.value) {\n\n                IconButton(\n                    modifier = Modifier.size(48.dp),\n                    onClick = {\n                        answer(ctx, call)\n                    },\n                ) {\n                    Icon(\n                        imageVector = Icons.Filled.Call,\n                        modifier = Modifier.size(42.dp),\n                        tint = colorResource(R.color.colorTrafficGreen),\n                        contentDescription = null,\n                    )\n                }\n\n                Spacer(Modifier.weight(1f))\n\n                IconButton(\n                    modifier = Modifier.size(48.dp),\n                    onClick = {\n                        reject(call)\n                    },\n                ) {\n                    Icon(\n                        imageVector = Icons.Filled.CallEnd,\n                        modifier = Modifier.size(42.dp),\n                        tint = colorResource(R.color.colorTrafficRed),\n                        contentDescription = null,\n                    )\n                }\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun OnHoldNotice() {\n    Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {\n        OutlinedButton(\n            onClick = {},\n            border = BorderStroke(1.dp, MaterialTheme.colorScheme.error),\n            modifier = Modifier.padding(16.dp),\n            shape = RoundedCornerShape(20)\n        ) {\n            Text(\n                text = stringResource(R.string.call_is_on_hold),\n                fontSize = 18.sp\n            )\n        }\n    }\n}\n\nprivate fun spinToAor(viewModel: ViewModel, aor: String) {\n    if (aor != viewModel.selectedAor.value)\n        viewModel.updateSelectedAor(aor)\n    viewModel.triggerAccountUpdate()\n}\n\nprivate fun callClick(ctx: Context, viewModel: ViewModel, dialerState: ViewModel.DialerState?) {\n    if (viewModel.selectedAor.value != \"\") {\n        if (Utils.checkPermissions(ctx, arrayOf(RECORD_AUDIO))) {\n            if (dialerState != null) {\n                val uriText = dialerState.callUri.value.trim()\n                if (uriText.isNotEmpty()) {\n                    val uris = Contact.contactUris(uriText)\n                    if (uris.isEmpty())\n                        makeCall(ctx, viewModel, uriText, dialerState.showCallConferenceButton.value)\n                    else if (uris.size == 1)\n                        makeCall(ctx, viewModel, uris[0], dialerState.showCallConferenceButton.value)\n                    else {\n                        selectItems.value = uris\n                        selectItemAction.value = { index ->\n                            makeCall(ctx, viewModel, uris[index], dialerState.showCallConferenceButton.value)\n                        }\n                        showSelectItemDialog.value = true\n                    }\n                } else {\n                    val ua = UserAgent.ofAor(viewModel.selectedAor.value)!!\n                    val latestPeerUri = CallHistoryNew.aorLatestPeerUri(ua.account.aor)\n                    if (latestPeerUri != null)\n                        dialerState.callUri.value = Utils.friendlyUri(ctx, latestPeerUri, ua.account)\n                }\n            }\n        }\n        else\n            Toast.makeText(ctx, R.string.no_calls, Toast.LENGTH_SHORT).show()\n    }\n}\n\nprivate fun makeCall(ctx: Context, viewModel: ViewModel, uriText: String, conferenceCall: Boolean,\n                     onHoldCallp: Long = 0L) {\n    val aor = viewModel.selectedAor.value\n    val ua = UserAgent.ofAor(aor)!!\n    val peerUri = if (Utils.isTelNumber(uriText))\n        \"tel:$uriText\"\n    else\n        uriText\n    val uri = if (Utils.isTelUri(peerUri)) {\n        if (ua.account.telProvider == \"\") {\n            alertTitle.value = ctx.getString(R.string.notice)\n            alertMessage.value = String.format(ctx.getString(R.string.no_telephony_provider), aor)\n            showAlert.value = true\n            return\n        }\n        Utils.telToSip(peerUri, ua.account)\n    }\n    else\n        Utils.uriComplete(peerUri, aor)\n    if (!Utils.checkUri(uri)) {\n        alertTitle.value = ctx.getString(R.string.notice)\n        alertMessage.value = String.format(ctx.getString(R.string.invalid_sip_or_tel_uri), uri)\n        showAlert.value = true\n    }\n    else if (Utils.isPSTNCallActive(ctx) || Call.calls().any { it.ua.account.aor != ua.account.aor } )\n        Toast.makeText(ctx, R.string.call_already_active, Toast.LENGTH_SHORT).show()\n    else {\n        val tm = ctx.getSystemService(Context.TELECOM_SERVICE) as android.telecom.TelecomManager\n        val extras = android.os.Bundle()\n        extras.putParcelable(android.telecom.TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,\n            BaresipService.getPhoneAccountHandle(ctx))\n        val callExtras = android.os.Bundle()\n        callExtras.putBoolean(\"conferenceCall\", conferenceCall)\n        callExtras.putLong(\"uap\", ua.uap)\n        if (onHoldCallp != 0L)\n            callExtras.putLong(\"onHoldCallp\", onHoldCallp)\n        extras.putBundle(android.telecom.TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, callExtras)\n        try {\n            Log.d(TAG, \"Placing Telecom call to $uri with uap=${ua.uap}\")\n            tm.placeCall(uri.toUri(), extras)\n        } catch (e: SecurityException) {\n            Log.e(TAG, \"placeCall failed: ${e.message}\")\n        }\n    }\n}\n\nprivate fun answer(ctx: Context, call: Call) {\n    Log.d(TAG, \"AoR ${call.ua.account.aor} answering call from ${call.callUri.value}\")\n    ConnectionService.connections[call.callp]?.setActive()\n    val intent = Intent(ctx, BaresipService::class.java)\n    intent.action = \"Call Answer\"\n    intent.putExtra(\"uap\", call.ua.uap)\n    intent.putExtra(\"callp\", call.callp)\n    ContextCompat.startForegroundService(ctx, intent)\n}\n\nprivate fun reject(call: Call) {\n    Log.d(TAG, \"AoR ${call.ua.account.aor} rejecting call ${call.callp} from ${call.callUri.value}\")\n    val connection = ConnectionService.connections[call.callp]\n    if (connection != null)\n        connection.onReject()\n    else {\n        call.rejected = true\n        Api.ua_hangup(call.ua.uap, call.callp, 486, \"Busy Here\")\n    }\n}\n\nprivate fun transfer(ctx: Context, viewModel: ViewModel, ua: UserAgent, uriText: String, attended: Boolean) {\n    val uri = if (Utils.isTelUri(uriText))\n        Utils.telToSip(uriText, ua.account)\n    else\n        Utils.uriComplete(uriText, ua.account.aor)\n    if (!Utils.checkUri(uri)) {\n        alertTitle.value = ctx.getString(R.string.notice)\n        alertMessage.value = String.format(ctx.getString(R.string.invalid_sip_or_tel_uri), uri)\n        showAlert.value = true\n    }\n    else {\n        val call = ua.currentCall()\n        if (call != null) {\n            if (attended) {\n                val connection = ConnectionService.connections[call.callp]\n                val success = if (connection != null) {\n                    connection.onHold()\n                    true\n                } else {\n                    call.hold()\n                }\n                if (success) {\n                    call.onhold = true\n                    call.referTo = uri\n                    makeCall(ctx, viewModel, uri, false, call.callp)\n                    showCall(ctx, viewModel, ua, call)\n                }\n            }\n            else {\n                val connection = ConnectionService.connections[call.callp]\n                connection?.onHold()\n                if (!call.transfer(uri)) {\n                    alertTitle.value = ctx.getString(R.string.notice)\n                    alertMessage.value = ctx.getString(R.string.transfer_failed)\n                    showAlert.value = true\n                }\n            }\n            showCall(ctx, viewModel, ua)\n        }\n    }\n}\n\nprivate fun showCall(ctx: Context, viewModel: ViewModel, ua: UserAgent?, showCall: Call? = null) {\n    if (ua == null)\n        return\n    val call = showCall ?: ua.currentCall()\n    if (call == null) {\n        pullToRefreshEnabled.value = true\n        viewModel.dialerState.callUri.value = ua.account.resumeUri\n        viewModel.dialerState.callUriLabel.value = ctx.getString(R.string.outgoing_call_to_dots)\n        viewModel.dialerState.callUriEnabled.value = true\n        viewModel.dialerState.showCallButton.value = true\n        viewModel.dialerState.showCallConferenceButton.value = true\n        viewModel.dialerState.callButtonsEnabled.value = true\n        viewModel.dialerState.showSuggestions.value = false\n        dialpadButtonEnabled.value = true\n        if (BaresipService.isMicMuted) {\n            BaresipService.isMicMuted = false\n            viewModel.updateMicIcon(Icons.Filled.Mic)\n        }\n    } else {\n        viewModel.dialerState.callUri.value = \"\"\n        pullToRefreshEnabled.value = false\n        call.callUriEnabled.value = false\n        val isLandscape = ctx.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE\n        if (isLandscape || call.held || call.status.value != \"connected\") {\n            call.focusDtmf.value = false\n            call.dtmfEnabled.value = !call.held\n            Handler(Looper.getMainLooper()).postDelayed({\n                viewModel.requestHideKeyboard()\n            }, 25)\n        }\n        else {\n            call.dtmfEnabled.value = true\n            call.focusDtmf.value = true\n            viewModel.requestShowKeyboard()\n        }\n        Log.d(TAG, \"Showing call ${call.callp} from ${call.ua.account.aor} with status ${call.status.value}\")\n        when (call.status.value) {\n            \"outgoing\", \"transferring\", \"answered\" -> {\n                call.callUriLabel.value = if (call.status.value == \"answered\")\n                    ctx.getString(R.string.incoming_call_from_dots)\n                else\n                    ctx.getString(R.string.outgoing_call_to_dots)\n                call.callUri.value = Utils.friendlyUri(ctx, call.peerUri, ua.account)\n                call.showCallTimer.value = false\n                call.securityIconTint.value = -1\n                call.showCallButton.value = false\n                call.showCancelButton.value = call.status.value == \"outgoing\"\n                call.showHangupButton.value = !call.showCancelButton.value\n                call.showAnswerRejectButtons.value = false\n                call.showOnHoldNotice.value = false\n                dialpadButtonEnabled.value = false\n            }\n            \"incoming\" -> {\n                call.showCallTimer.value = false\n                call.securityIconTint.value = -1\n                val uri = call.diverterUri()\n                if (uri != \"\") {\n                    call.callUriLabel.value = ctx.getString(R.string.diverted_by_dots)\n                    call.callUri.value = Utils.friendlyUri(ctx, uri, ua.account)\n                }\n                else {\n                    call.callUriLabel.value = ctx.getString(R.string.incoming_call_from_dots)\n                    call.callUri.value = Utils.friendlyUri(ctx, call.peerUri, ua.account)\n                }\n                call.showCallButton.value = false\n                call.showCancelButton.value = false\n                call.showHangupButton.value = false\n                call.showAnswerRejectButtons.value = true\n                call.showOnHoldNotice.value = false\n                dialpadButtonEnabled.value = false\n            }\n            \"connected\" -> {\n                if (call.referTo != \"\") {\n                    call.callUriLabel.value = ctx.getString(R.string.outgoing_call_to_dots)\n                    call.callUri.value = Utils.friendlyUri(ctx, call.referTo, ua.account)\n                    call.transferButtonEnabled.value = false\n                } else {\n                    if (call.dir == \"out\") {\n                        call.callUriLabel.value = ctx.getString(R.string.outgoing_call_to_dots)\n                        call.callUri.value = Utils.friendlyUri(ctx, call.peerUri, ua.account)\n                    } else {\n                        call.callUriLabel.value = ctx.getString(R.string.incoming_call_from_dots)\n                        call.callUri.value = Utils.friendlyUri(ctx, call.peerUri, ua.account)\n                    }\n                    call.transferButtonEnabled.value = true\n                }\n                call.callTransfer.value = call.onHoldCall != null\n                call.callDuration = call.duration()\n                call.showCallTimer.value = true\n                if (ua.account.mediaEnc == \"\")\n                    call.securityIconTint.value = -1\n                else\n                    call.securityIconTint.value = call.security\n                call.showCallButton.value = false\n                call.showCancelButton.value = false\n                call.showHangupButton.value = true\n                call.showAnswerRejectButtons.value = false\n                call.callOnHold.value = call.onhold\n                Handler(Looper.getMainLooper()).postDelayed({\n                    call.showOnHoldNotice.value = call.held\n                }, 100)\n            }\n        }\n    }\n}\n\nfun handleServiceEvent(ctx: Context, viewModel: ViewModel, event: String, params: ArrayList<Any>) {\n\n    fun handleNextEvent(logMessage: String? = null) {\n        if (logMessage != null)\n            Log.w(TAG, logMessage)\n        if (BaresipService.serviceEvents.isNotEmpty()) {\n            val first = BaresipService.serviceEvents.removeAt(0)\n            handleServiceEvent(ctx, viewModel, first.event, first.params)\n        }\n    }\n\n    if (event == \"started\") {\n        val uriString = params[0] as String\n        Log.d(TAG, \"Handling service event 'started' with URI '$uriString'\")\n        if (uriString != \"\")\n            callAction(ctx, viewModel, uriString.toUri(), \"dial\")\n        else {\n            if (viewModel.selectedAor.value == \"\" && uas.value.isNotEmpty())\n                viewModel.updateSelectedAor(uas.value.first().account.aor)\n        }\n        if (Preferences(ctx).displayTheme != AppCompatDelegate.getDefaultNightMode()) {\n            AppCompatDelegate.setDefaultNightMode(Preferences(ctx).displayTheme)\n        }\n        handleNextEvent()\n        return\n    }\n\n    val uap = params[0] as Long\n    val ua = UserAgent.ofUap(uap)\n    if (ua == null) {\n        handleNextEvent(\"handleServiceEvent '$event' did not find ua $uap\")\n        return\n    }\n\n    val ev = event.split(\",\")\n    Log.d(TAG, \"Handling service event '${ev[0]}' for $uap\")\n    val acc = ua.account\n    val aor = ua.account.aor\n\n    when (ev[0]) {\n        \"call rejected\" -> {\n            if (aor == viewModel.selectedAor.value)\n                viewModel.triggerAccountUpdate()\n        }\n        \"call incoming\", \"call outgoing\" -> {\n            val callp = params[1] as Long\n            if (!BaresipService.isMainVisible)\n                viewModel.navigateToHome()\n            spinToAor(viewModel, aor)\n            showCall(ctx, viewModel, ua, Call.ofCallp(callp))\n        }\n        \"call answered\" -> {\n            if (!BaresipService.isMainVisible)\n                viewModel.navigateToHome()\n            spinToAor(viewModel, aor)\n            val callp = params[1] as Long\n            showCall(ctx, viewModel, ua, Call.ofCallp(callp))\n        }\n        \"call redirect\" -> {\n            val redirectUri = ev[1]\n            val target = Utils.friendlyUri(ctx, redirectUri, acc)\n            if (acc.autoRedirect) {\n                redirect(ctx, viewModel, ua, redirectUri)\n                Toast.makeText(ctx,\n                    String.format(ctx.getString(R.string.redirect_notice), target),\n                    Toast.LENGTH_SHORT\n                ).show()\n            }\n            else {\n                dialogTitle.value = ctx.getString(R.string.redirect_request)\n                dialogMessage.value = String.format(ctx.getString(R.string.redirect_request_query), target)\n                firstText.value = \"\"\n                secondText.value = ctx.getString(R.string.no)\n                onSecondClicked.value = { }\n                lastText.value = ctx.getString(R.string.yes)\n                onLastClicked.value = {\n                    redirect(ctx, viewModel, ua, redirectUri)\n                }\n                showDialog.value = true\n            }\n            showCall(ctx, viewModel, ua)\n        }\n        \"call established\" -> {\n            if (aor == viewModel.selectedAor.value) {\n                viewModel.dialerState.callButtonsEnabled.value = true // Re-enable dialer\n                val callp = params[1] as Long\n                val call = Call.ofCallp(callp)\n                if (call != null) {\n                    call.dtmfText.value = \"\"\n                    if (call.conferenceCall)\n                        Api.cmd_exec(\"conference\")\n                }\n                showCall(ctx, viewModel, ua, call)\n            }\n        }\n        \"call update\" -> {\n            showCall(ctx, viewModel, ua)\n        }\n        \"call verify\" -> {\n            val callp = params[1] as Long\n            val call = Call.ofCallp(callp)\n            if (call == null) {\n                handleNextEvent(\"Call $callp to be verified is not found\")\n                return\n            }\n            dialogTitle.value = ctx.getString(R.string.verify)\n            dialogMessage.value = String.format(ctx.getString(R.string.verify_sas), ev[1])\n            firstText.value = \"\"\n            secondText.value = ctx.getString(R.string.no)\n            onSecondClicked.value = {\n                call.security = R.color.colorTrafficYellow\n                call.zid = ev[2]\n                if (aor == viewModel.selectedAor.value)\n                    call.securityIconTint.value = R.color.colorTrafficYellow\n                secondText.value = \"\"\n            }\n            lastText.value = ctx.getString(R.string.yes)\n            onLastClicked.value = {\n                call.security = if (Api.cmd_exec(\"zrtp_verify ${ev[2]}\") != 0) {\n                    Log.e(TAG, \"Command 'zrtp_verify ${ev[2]}' failed\")\n                    R.color.colorTrafficYellow\n                } else {\n                    R.color.colorTrafficGreen\n                }\n                call.zid = ev[2]\n                if (aor == viewModel.selectedAor.value)\n                    call.securityIconTint.value = call.security\n            }\n            showDialog.value = true\n        }\n        \"call verified\", \"call secure\" -> {\n            val callp = params[1] as Long\n            val call = Call.ofCallp(callp)\n            if (call == null) {\n                handleNextEvent(\"Call $callp that is verified is not found\")\n                return\n            }\n            if (aor == viewModel.selectedAor.value)\n                call.securityIconTint.value = call.security\n        }\n        \"call transfer\", \"transfer show\" -> {\n            if (!BaresipService.isMainVisible)\n                viewModel.navigateToHome()\n            val callp = params[1] as Long\n            val call = Call.ofCallp(callp)\n            val target = Utils.friendlyUri(ctx, ev[1], acc)\n            dialogTitle.value = if (call != null)\n                ctx.getString(R.string.transfer_request)\n            else\n                ctx.getString(R.string.call_request)\n            dialogMessage.value = if (call != null)\n                String.format(ctx.getString(R.string.transfer_request_query), target)\n            else\n                String.format(ctx.getString(R.string.call_request_query), target)\n            firstText.value = \"\"\n            secondText.value = ctx.getString(R.string.no)\n            onSecondClicked.value = {\n                if (call in Call.calls())\n                    call!!.notifySipfrag(603, \"Decline\")\n            }\n            lastText.value = ctx.getString(R.string.yes)\n            onLastClicked.value = {\n                if (call in Call.calls())\n                    acceptTransfer(ctx, viewModel, ua, call!!, ev[1])\n                else\n                    makeCall(ctx, viewModel, ev[1], false)\n            }\n            showDialog.value = true\n        }\n        \"transfer accept\" -> {\n            val callp = params[1] as Long\n            val call = Call.ofCallp(callp)\n            if (call in Call.calls())\n                Api.ua_hangup(uap, callp, 487, \"Request Terminated\")\n            makeCall(ctx, viewModel, ev[1], false)\n            showCall(ctx, viewModel, ua)\n        }\n        \"transfer failed\" -> {\n            showCall(ctx, viewModel, ua)\n        }\n        \"call closed\" -> {\n            viewModel.updateCalls(Call.calls().toList())\n            val activity = ctx as? Activity\n            if (activity != null) {\n                val kgm = activity.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager\n                if (kgm.isKeyguardLocked) {\n                    activity.moveTaskToBack(true)\n                    return\n                }\n            }\n            if (aor == viewModel.selectedAor.value) {\n                viewModel.dialerState.callButtonsEnabled.value = true\n                ua.account.resumeUri = \"\"\n                showCall(ctx, viewModel, ua)\n                if (acc.missedCalls)\n                    viewModel.triggerAccountUpdate()\n            }\n        }\n        \"message\", \"message show\", \"message reply\" -> {\n            Handler(Looper.getMainLooper()).postDelayed({\n                viewModel.onNewMessageReceived(aor, params[1] as String)\n            }, 200)\n        }\n        \"mwi notify\" -> {\n            val lines = ev[1].split(\"\\n\")\n            for (line in lines) {\n                if (line.startsWith(\"Voice-Message:\")) {\n                    val counts = (line.split(\" \")[1]).split(\"/\")\n                    acc.vmNew = counts[0].toInt()\n                    acc.vmOld = counts[1].toInt()\n                    break\n                }\n            }\n            if (aor == viewModel.selectedAor.value)\n                viewModel.triggerAccountUpdate()\n        }\n        \"mic muted\" -> {\n            val muted = ev[1].toBoolean()\n            if (muted)\n                viewModel.updateMicIcon(Icons.Filled.MicOff)\n            else\n                viewModel.updateMicIcon(Icons.Filled.Mic)\n        }\n        \"speaker update\" -> {\n            viewModel.updateSpeakerPhoneStatus(ev[1].toBoolean())\n        }\n        else -> Log.e(TAG, \"Unknown event '${ev[0]}'\")\n    }\n\n    viewModel.updateCalls(Call.calls().toList())\n    handleNextEvent()\n}\n\nfun handleIntent(ctx: Context, viewModel: ViewModel, intent: Intent, action: String) {\n    Log.d(TAG, \"Handling intent '$action'\")\n    val ev = action.split(\",\")\n    when (ev[0]) {\n        \"call\", \"dial\" -> {\n            if (Call.inCall()) {\n                Toast.makeText(ctx, ctx.getString(R.string.call_already_active),\n                    Toast.LENGTH_SHORT).show()\n                return\n            }\n            val uap = intent.getLongExtra(\"uap\", 0L)\n            val ua = UserAgent.ofUap(uap)\n            if (ua == null) {\n                Log.w(TAG, \"handleIntent 'call' did not find ua $uap\")\n                return\n            }\n            viewModel.dialerState.callUri.value = intent.getStringExtra(\"peer\")!!\n            spinToAor(viewModel, ua.account.aor)\n            if (ev[0] == \"call\") {\n                viewModel.dialerState.showCallConferenceButton.value = false\n                callClick(ctx, viewModel, viewModel.dialerState)\n            }\n        }\n        \"call show\", \"call answer\" -> {\n            val callp = intent.getLongExtra(\"callp\", 0L)\n            val call = Call.ofCallp(callp)\n            if (call == null) {\n                Log.w(TAG, \"handleIntent '$action' did not find call $callp\")\n                return\n            }\n            val ua = call.ua\n            spinToAor(viewModel, ua.account.aor)\n            if (ev[0] == \"call answer\")\n                answer(ctx, call)\n            else\n                BaresipService.postServiceEvent(ServiceEvent(\n                    \"call incoming\",\n                    arrayListOf(call.ua.uap, callp),\n                    System.nanoTime())\n                )\n        }\n        \"call missed\" -> {\n            val uap = intent.getLongExtra(\"uap\", 0L)\n            val ua = UserAgent.ofUap(uap)\n            if (ua == null) {\n                Log.w(TAG, \"handleIntent did not find ua $uap\")\n                return\n            }\n            spinToAor(viewModel, ua.account.aor)\n            viewModel.navigateToCalls(ua.account.aor)\n        }\n        \"call transfer\", \"transfer show\", \"transfer accept\" -> {\n            val callp = intent.getLongExtra(\"callp\", 0L)\n            val call = Call.ofCallp(callp)\n            if (call == null) {\n                Log.w(TAG, \"handleIntent '$action' did not find call $callp\")\n                // moveTaskToBack(true)\n                return\n            }\n            val uri = if (ev[0] == \"call transfer\")\n                ev[1]\n            else\n                intent.getStringExtra(\"uri\")!!\n            BaresipService.postServiceEvent(ServiceEvent(\n                ev[0] + \",\" + uri,\n                arrayListOf(call.ua.uap, callp),\n                System.nanoTime())\n            )\n        }\n        \"message\", \"message show\", \"message reply\" -> {\n            val uap = intent.getLongExtra(\"uap\", 0L)\n            val ua = UserAgent.ofUap(uap)\n            if (ua == null) {\n                Log.w(TAG, \"handleIntent did not find ua $uap\")\n                return\n            }\n            spinToAor(viewModel, ua.account.aor)\n            BaresipService.postServiceEvent(ServiceEvent(\n                ev[0],\n                arrayListOf(uap, intent.getStringExtra(\"peer\")!!),\n                System.nanoTime())\n            )\n        }\n    }\n}\n\nfun handleDialog(ctx: Context, title: String, message: String, action: () -> Unit = {}) {\n    dialogTitle.value = title\n    dialogMessage.value = message\n    firstText.value = \"\"\n    secondText.value = \"\"\n    lastText.value = ctx.getString(R.string.ok)\n    onLastClicked.value = { action() }\n    showDialog.value = true\n}\n\nfun callAction(ctx: Context, viewModel: ViewModel, uri: Uri?, action: String) {\n    if (Call.inCall() || uas.value.isEmpty())\n        return\n    Log.d(TAG, \"Action $action to $uri\")\n    if (uri != null) {\n        var uriStr: String\n        var uap: Long\n        when (uri.scheme) {\n            \"sip\" -> {\n                uriStr = Utils.uriUnescape(uri.toString())\n                var ua = UserAgent.ofDomain(Utils.uriHostPart(uriStr))\n                if (ua == null && uas.value.isNotEmpty())\n                    ua = uas.value[0]\n                if (ua == null) {\n                    Log.w(TAG, \"No accounts for '$uriStr'\")\n                    return\n                }\n                uap = ua.uap\n            }\n            \"tel\" -> {\n                uriStr = uri.toString().replace(\"%2B\", \"+\").replace(\"%20\", \"\")\n                    .filterNot { setOf('-', ' ', '(', ')').contains(it) }\n                var account: Account? = null\n                for (a in Account.accounts())\n                    if (a.telProvider != \"\") {\n                        account = a\n                        break\n                    }\n                if (account == null) {\n                    Log.w(TAG, \"No telephony providers for '$uriStr'\")\n                    return\n                }\n                uap = UserAgent.ofAor(account.aor)!!.uap\n            }\n            else -> {\n                Log.w(TAG, \"Unsupported URI scheme ${uri.scheme}\")\n                return\n            }\n        }\n        val intent = Intent(ctx, MainActivity::class.java)\n        intent.putExtra(\"uap\", uap)\n        intent.putExtra(\"peer\", uriStr)\n        handleIntent(ctx, viewModel, intent, action)\n    }\n}\n\nprivate fun redirect(ctx: Context, viewModel: ViewModel, ua: UserAgent, redirectUri: String) {\n    if (ua.account.aor != viewModel.selectedAor.value)\n        spinToAor(viewModel, ua.account.aor)\n    viewModel.dialerState.callUri.value = redirectUri\n    callClick(ctx, viewModel, viewModel.dialerState)\n}\n\nprivate fun acceptTransfer(ctx: Context, viewModel: ViewModel, ua: UserAgent, call: Call, uri: String) {\n    val newCallp = ua.callAlloc(call.callp, Api.VIDMODE_OFF)\n    if (newCallp != 0L) {\n        Log.d(TAG, \"Adding outgoing call ${ua.uap}/$newCallp/$uri\")\n        val newCall = Call(newCallp, ua, uri, \"out\", \"transferring\")\n        newCall.add()\n        if (newCall.connect(uri)) {\n            if (ua.account.aor != viewModel.selectedAor.value)\n                spinToAor(viewModel, ua.account.aor)\n            showCall(ctx, viewModel, ua)\n        } else {\n            Log.w(TAG, \"call_connect $newCallp failed\")\n            call.notifySipfrag(500, \"Call Error\")\n        }\n    } else {\n        Log.w(TAG, \"callAlloc for ua ${ua.uap} call ${call.callp} transfer failed\")\n        call.notifySipfrag(500, \"Call Error\")\n    }\n}\n\nprivate fun backup(ctx: Context, password: String) {\n    val files = arrayListOf(\"accounts\", \"config\", \"contacts\", \"call_history\",\n        \"messages\", \"uuid\", \"gzrtp.zid\", \"cert.pem\", \"ca_cert\", \"ca_certs.crt\")\n    File(BaresipService.filesPath).walk().forEach {\n        if (it.name.endsWith(\".png\"))\n            files.add(it.name)\n    }\n    val zipFile = ctx.getString(R.string.app_name) + \".zip\"\n    val zipFilePath = BaresipService.filesPath + \"/$zipFile\"\n    if (!Utils.zip(files, zipFile)) {\n        Log.w(TAG, \"Failed to write zip file '$zipFile'\")\n        alertTitle.value = ctx.getString(R.string.error)\n        alertMessage.value = String.format(ctx.getString(R.string.backup_failed),\n            Utils.fileNameOfUri(ctx, downloadsOutputUri!!))\n        showAlert.value = true\n        downloadsOutputUri = null\n        return\n    }\n    val content = Utils.getFileContents(zipFilePath)\n    if (content == null) {\n        Log.w(TAG, \"Failed to read zip file '$zipFile'\")\n        alertTitle.value = ctx.getString(R.string.error)\n        alertMessage.value = String.format(ctx.getString(R.string.backup_failed),\n            Utils.fileNameOfUri(ctx, downloadsOutputUri!!))\n        showAlert.value = true\n        downloadsOutputUri = null\n        return\n    }\n    if (!Utils.encryptToUri(ctx, downloadsOutputUri!!, content, password)) {\n        alertTitle.value = ctx.getString(R.string.error)\n        alertMessage.value = String.format(ctx.getString(R.string.backup_failed),\n            Utils.fileNameOfUri(ctx, downloadsOutputUri!!))\n        showAlert.value = true\n        downloadsOutputUri = null\n        return\n    }\n    alertTitle.value = ctx.getString(R.string.info)\n    alertMessage.value = String.format(ctx.getString(R.string.backed_up),\n        Utils.fileNameOfUri(ctx, downloadsOutputUri!!))\n    showAlert.value = true\n    Utils.deleteFile(File(zipFilePath))\n    downloadsOutputUri = null\n}\n\nprivate fun restore(ctx: Context, password: String, onRestartApp: () -> Unit) {\n    val zipFile = ctx.getString(R.string.app_name) + \".zip\"\n    val zipFilePath = BaresipService.filesPath + \"/$zipFile\"\n    val zipData = Utils.decryptFromUri(ctx, downloadsInputUri!!, password)\n    if (zipData == null) {\n        alertTitle.value = ctx.getString(R.string.error)\n        alertMessage.value = String.format(ctx.getString(R.string.restore_failed),\n            Utils.fileNameOfUri(ctx, downloadsOutputUri!!))\n        showAlert.value = true\n        downloadsOutputUri = null\n        return\n    }\n    if (!Utils.putFileContents(zipFilePath, zipData)) {\n        Log.w(TAG, \"Failed to write zip file '$zipFile'\")\n        alertTitle.value = ctx.getString(R.string.error)\n        alertMessage.value = String.format(ctx.getString(R.string.restore_failed),\n            Utils.fileNameOfUri(ctx, downloadsOutputUri!!))\n        showAlert.value = true\n        downloadsOutputUri = null\n        return\n    }\n    if (!Utils.unZip(zipFilePath)) {\n        Log.w(TAG, \"Failed to unzip file '$zipFile'\")\n        alertTitle.value = ctx.getString(R.string.error)\n        alertMessage.value = String.format(\n            ctx.getString(R.string.restore_unzip_failed),\n            \"baresip\",\n            BuildConfig.VERSION_NAME\n        )\n        showAlert.value = true\n        downloadsOutputUri = null\n        return\n    }\n    Utils.deleteFile(File(zipFilePath))\n\n    File(\"${BaresipService.filesPath}/recordings\").walk().forEach {\n        if (it.name.startsWith(\"dump\"))\n            Utils.deleteFile(it)\n    }\n\n    Utils.createEmptyFile(BaresipService.filesPath + \"/restored\")\n\n    dialogTitle.value = ctx.getString(R.string.info)\n    dialogMessage.value = ctx.getString(R.string.restored)\n    firstText.value = ctx.getString(R.string.cancel)\n    onFirstClicked.value = {\n        showDialog.value = false\n    }\n    secondText.value = \"\"\n    lastText.value = ctx.getString(R.string.restart)\n    onLastClicked.value = {\n        onRestartApp()\n        showDialog.value = false\n    }\n    showDialog.value = true\n\n    downloadsOutputUri = null\n}\n\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/Message.kt",
    "content": "package com.tutpro.baresip\n\nimport java.io.*\nimport java.util.ArrayList\n\nclass Message(val aor: String, val peerUri: String, val message: String, val timeStamp: Long,\n              var direction: Int, var responseCode: Int, var responseReason: String,\n              var new: Boolean): Serializable {\n\n    fun add() {\n        val updatedMessages = BaresipService.messages.toMutableList()\n        updatedMessages.add(this)\n        BaresipService.messages = updatedMessages.toList()\n        var count = 0\n        var remove: Message? = null\n        for (message in BaresipService.messages)\n            if (message.aor == this.aor) {\n                count++\n                if (count > MESSAGE_HISTORY_SIZE) {\n                    remove = message\n                    break\n                }\n            }\n        if (remove != null)\n            updatedMessages.remove(remove)\n        BaresipService.messages = updatedMessages.toList()\n        save()\n    }\n\n    fun delete() {\n        val updatedMessages = BaresipService.messages.toMutableList()\n        updatedMessages.remove(this)\n        BaresipService.messages = updatedMessages.toList()\n        save()\n    }\n\n    companion object {\n\n        const val MESSAGE_HISTORY_SIZE = 100\n\n        fun messages(): List<Message> {\n            return BaresipService.messages\n        }\n\n        fun clearMessagesOfAor(aor: String) {\n            val updatedMessages = BaresipService.messages.toMutableList()\n            val it = updatedMessages.iterator()\n            while (it.hasNext()) if (it.next().aor == aor) it.remove()\n            BaresipService.messages = updatedMessages.toList()\n            save()\n        }\n\n        fun deleteAorMessage(aor: String, time: Long) {\n            val updatedMessages = BaresipService.messages.toMutableList()\n            for (message in updatedMessages.reversed())\n                if (message.aor == aor && message.timeStamp == time) {\n                    updatedMessages.remove(message)\n                    BaresipService.messages = updatedMessages.toList()\n                    save()\n                    return\n                }\n        }\n\n        fun updateAorMessage(aor: String, time: Long) {\n            val updatedMessages = BaresipService.messages.toMutableList()\n            for (message in updatedMessages.reversed())\n                if (message.aor == aor && message.timeStamp == time) {\n                    message.new = false\n                    BaresipService.messages = updatedMessages.toList()\n                    save()\n                    return\n                }\n        }\n\n        fun unreadMessages(aor: String): Boolean {\n            for (message in BaresipService.messages.reversed())\n                if (message.aor == aor && message.new)\n                    return true\n            return false\n        }\n\n        fun unreadMessagesFromPeer(aor: String, peerUri: String): Boolean {\n            for (message in BaresipService.messages.reversed())\n                if (message.aor == aor && message.peerUri == peerUri && message.new)\n                    return true\n            return false\n        }\n\n        fun updateMessagesFromPearRead(aor: String, peerUri: String): Boolean {\n            val updatedMessages = BaresipService.messages.toMutableList()\n            var updated = false\n            for (message in updatedMessages)\n                if (message.aor == aor && message.peerUri == peerUri && message.new) {\n                    message.new = false\n                    updated = true\n                }\n            if (updated) {\n                BaresipService.messages = updatedMessages.toList()\n                save()\n            }\n            return updated\n        }\n\n        fun save() {\n            val file = File(BaresipService.filesPath, \"messages\")\n            try {\n                val fos = FileOutputStream(file)\n                val oos = ObjectOutputStream(fos)\n                oos.writeObject(BaresipService.messages)\n                oos.close()\n                fos.close()\n                Log.d(TAG, \"Saved ${BaresipService.messages.size} messages\")\n            } catch (e: IOException) {\n                Log.e(TAG, \"OutputStream exception: $e\")\n                e.printStackTrace()\n            }\n        }\n\n        fun restore() {\n            val file = File(BaresipService.filesPath, \"messages\")\n            if (file.exists()) {\n                try {\n                    val fis = FileInputStream(file)\n                    val ois = ObjectInputStream(fis)\n                    @Suppress(\"UNCHECKED_CAST\")\n                    val restoredMessages = ois.readObject() as? List<Message>\n                    if (restoredMessages != null)\n                        BaresipService.messages = restoredMessages\n                    ois.close()\n                    fis.close()\n                    Log.d(TAG, \"Restored ${BaresipService.messages.size} messages\")\n                } catch (e: Exception) {\n                    Log.e(TAG, \"InputStream exception: - $e\")\n                }\n            }\n        }\n\n    }\n}\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/Preferences.kt",
    "content": "package com.tutpro.baresip\n\nimport android.content.Context\nimport androidx.preference.PreferenceManager\nimport androidx.core.content.edit\n\nclass Preferences(context: Context) {\n\n    companion object {\n        private const val DISPLAY_THEME = \"com.tutpro.baresip.DISPLAY_THEME\"\n    }\n\n    private val preferences = PreferenceManager.getDefaultSharedPreferences(context)\n\n    var displayTheme = preferences.getInt(DISPLAY_THEME, -1)\n        set(value) = preferences.edit { putInt(DISPLAY_THEME, value) }\n\n    var ringtoneUri = preferences.getString(\"ringtone_uri\", \"\")\n        set(value) = preferences.edit { putString(\"ringtone_uri\", value) }\n\n}"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/ServiceEvent.kt",
    "content": "package com.tutpro.baresip\n\nclass ServiceEvent (val event: String, val params: ArrayList<Any>, val timeStamp: Long)"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/SettingsScreen.kt",
    "content": "package com.tutpro.baresip\n\nimport android.Manifest\nimport android.app.Activity\nimport android.app.Activity.RESULT_OK\nimport android.app.role.RoleManager\nimport android.content.ActivityNotFoundException\nimport android.content.Context\nimport android.content.Context.POWER_SERVICE\nimport android.content.Context.ROLE_SERVICE\nimport android.content.Intent\nimport android.content.pm.PackageManager\nimport android.media.RingtoneManager\nimport android.net.Uri\nimport android.os.Build.VERSION\nimport android.os.PowerManager\nimport android.provider.Settings\nimport androidx.activity.compose.LocalActivity\nimport androidx.activity.compose.rememberLauncherForActivityResult\nimport androidx.activity.result.ActivityResult\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.annotation.RequiresApi\nimport androidx.appcompat.app.AppCompatDelegate\nimport androidx.core.content.ContextCompat\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.asPaddingValues\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.imePadding\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.statusBars\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.text.KeyboardOptions\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowBack\nimport androidx.compose.material.icons.filled.ArrowDropDown\nimport androidx.compose.material.icons.filled.Check\nimport androidx.compose.material3.DropdownMenu\nimport androidx.compose.material3.DropdownMenuItem\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.HorizontalDivider\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.OutlinedTextField\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Switch\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.TopAppBarDefaults\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.livedata.observeAsState\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.input.KeyboardType\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale\nimport androidx.core.net.toUri\nimport androidx.lifecycle.viewmodel.compose.viewModel\nimport androidx.navigation.NavController\nimport androidx.navigation.NavGraphBuilder\nimport androidx.navigation.compose.composable\nimport com.tutpro.baresip.CustomElements.AlertDialog\nimport com.tutpro.baresip.CustomElements.verticalScrollbar\nimport com.tutpro.baresip.Utils.copyInputStreamToFile\nimport java.io.File\nimport java.io.FileInputStream\nimport java.util.Locale\n\nprivate var restart = false\nprivate val showRestartDialog = mutableStateOf(false)\nprivate var save = false\n\nfun NavGraphBuilder.settingsScreenRoute(\n    navController: NavController,\n    onRestartApp: () -> Unit\n) {\n    composable(\"settings\") {\n        val ctx = LocalContext.current\n        val viewModel = viewModel<SettingsViewModel>()\n        SettingsScreen(\n            ctx = ctx,\n            navController = navController,\n            settingsViewModel = viewModel,\n            onBack = { navController.navigateUp() },\n            checkOnClick = {\n                if (checkOnClick(ctx, viewModel)) {\n                    if (restart)\n                        showRestartDialog.value = true\n                    else\n                        navController.navigateUp()\n                }\n            },\n            onRestartApp = onRestartApp\n        )\n    }\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun SettingsScreen(\n    ctx: Context,\n    navController: NavController,\n    settingsViewModel: SettingsViewModel,\n    onBack: () -> Unit,\n    checkOnClick: () -> Unit,\n    onRestartApp: () -> Unit\n) {\n    val activity = LocalActivity.current\n    var areSettingsLoaded by remember { mutableStateOf(false) }\n\n    val audioResult = navController.currentBackStackEntry\n        ?.savedStateHandle\n        ?.getLiveData<Boolean>(\"audio_settings_result\")\n        ?.observeAsState()\n\n    LaunchedEffect(audioResult?.value) {\n        if (audioResult?.value == true) {\n            Log.d(TAG, \"Got result from AudioSettings: true\")\n            restart = true\n            navController.currentBackStackEntry\n                ?.savedStateHandle\n                ?.remove<Boolean>(\"audio_settings_result\")\n        }\n    }\n\n    LaunchedEffect(null) {\n        settingsViewModel.loadSettings(ctx)\n        areSettingsLoaded = true\n    }\n\n    Scaffold(\n        modifier = Modifier\n            .fillMaxSize()\n            .imePadding(),\n        containerColor = MaterialTheme.colorScheme.background,\n        topBar = {\n            Column(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .background(MaterialTheme.colorScheme.background)\n                    .padding(\n                        top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()\n                    )\n            ) {\n                TopAppBar(\n                    title = {\n                        Text(\n                            text = stringResource(R.string.configuration),\n                            fontWeight = FontWeight.Bold\n                        )\n                    },\n                    colors = TopAppBarDefaults.topAppBarColors(\n                        containerColor = MaterialTheme.colorScheme.primary,\n                        navigationIconContentColor = MaterialTheme.colorScheme.onPrimary,\n                        titleContentColor = MaterialTheme.colorScheme.onPrimary,\n                        actionIconContentColor = MaterialTheme.colorScheme.onPrimary\n                    ),\n                    navigationIcon = {\n                        IconButton(onClick = onBack) {\n                            Icon(\n                                imageVector = Icons.AutoMirrored.Filled.ArrowBack,\n                                contentDescription = null,\n                            )\n                        }\n                    },\n                    windowInsets = WindowInsets(0, 0, 0, 0),\n                    actions = {\n                        IconButton(onClick = checkOnClick) {\n                            Icon(\n                                imageVector = Icons.Filled.Check,\n                                contentDescription = \"Check\"\n                            )\n                        }\n                    },\n                )\n            }\n        }\n    ) { contentPadding ->\n\n        if (showRestartDialog.value) {\n            AlertDialog(\n                showDialog = showRestartDialog,\n                title = stringResource(R.string.restart_request),\n                message = stringResource(R.string.config_restart),\n                firstButtonText = stringResource(R.string.cancel),\n                onFirstClicked = {\n                    navController.navigateUp()\n                },\n                lastButtonText = stringResource(R.string.restart),\n                onLastClicked = {\n                    onRestartApp()\n                },\n            )\n        }\n\n        if (areSettingsLoaded && activity != null)\n            SettingsContent(settingsViewModel, contentPadding, navController, activity, onRestartApp)\n    }\n}\n\nprivate val dialogTitle = mutableStateOf(\"\")\nprivate val dialogMessage = mutableStateOf(\"\")\nprivate val firstButtonText = mutableStateOf(\"\")\nprivate val onFirstClicked = mutableStateOf({})\nprivate val lastButtonText = mutableStateOf(\"\")\nprivate val onLastClicked = mutableStateOf({})\nprivate val showDialog = mutableStateOf(false)\n\nprivate val alertTitle = mutableStateOf(\"\")\nprivate val alertMessage = mutableStateOf(\"\")\nprivate val showAlert = mutableStateOf(false)\n\n@Composable\nprivate fun SettingsContent(\n    viewModel: SettingsViewModel,\n    contentPadding: PaddingValues,\n    navController: NavController,\n    activity: Activity,\n    onRestartApp: () -> Unit\n) {\n    val alertTitleText = stringResource(R.string.alert)\n    val errorTitleText = stringResource(R.string.error)\n    val noticeTitleText = stringResource(R.string.notice)\n    val confirmationText = stringResource(R.string.confirmation)\n    val okButtonText = stringResource(R.string.ok)\n    val cancelButtonText = stringResource(R.string.cancel)\n\n    if (showAlert.value) {\n        AlertDialog(\n            showDialog = showAlert,\n            title = alertTitle.value,\n            message = alertMessage.value,\n            firstButtonText = \"\",\n            lastButtonText = okButtonText,\n        )\n    }\n\n    if (showDialog.value)\n        AlertDialog(\n            showDialog = showDialog,\n            title = dialogTitle.value,\n            message = dialogMessage.value,\n            firstButtonText = firstButtonText.value,\n            onFirstClicked = onFirstClicked.value,\n            lastButtonText = lastButtonText.value,\n            onLastClicked = onLastClicked.value,\n        )\n\n    @Composable\n    fun StartAutomatically() {\n        val startAutomaticallyTitle = stringResource(R.string.start_automatically)\n        val startAutomaticallyHelp = stringResource(R.string.start_automatically_help)\n        val appearOnTopPermissionMessage = stringResource(R.string.appear_on_top_permission)\n        Row(\n            Modifier\n                .fillMaxWidth()\n                .padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            val ctx = LocalContext.current\n            Text(text = startAutomaticallyTitle,\n                modifier = Modifier\n                    .weight(1f)\n                    .clickable {\n                        alertTitle.value = startAutomaticallyTitle\n                        alertMessage.value = startAutomaticallyHelp\n                        showAlert.value = true\n                    },\n                fontSize = 18.sp\n            )\n            val startAutomatically by viewModel.autoStart.collectAsState()\n            Switch(\n                checked = startAutomatically,\n                onCheckedChange = {\n                    if (it) {\n                        if (!isAppearOnTopPermissionGranted(ctx)) {\n                            dialogTitle.value = noticeTitleText\n                            dialogMessage.value = appearOnTopPermissionMessage\n                            firstButtonText.value = cancelButtonText\n                            onFirstClicked.value = {}\n                            lastButtonText.value = okButtonText\n                            onLastClicked.value = {\n                                val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)\n                                ctx.startActivity(intent)\n                            }\n                            showDialog.value = true\n                            viewModel.autoStart.value = false\n                        }\n                        else\n                            viewModel.autoStart.value = true\n                    }\n                    else\n                        viewModel.autoStart.value = false\n                }\n            )\n        }\n    }\n\n    @Composable\n    fun AddressFamily() {\n        val addressFamilyTitle = stringResource(R.string.address_family)\n        val addressFamilyHelp = stringResource(R.string.address_family_help)\n        Row(\n            Modifier\n                .fillMaxWidth()\n                .padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            val addressFamily by viewModel.addressFamily.collectAsState()\n            Text(text = addressFamilyTitle,\n                modifier = Modifier\n                    .weight(1f)\n                    .clickable {\n                        alertTitle.value = addressFamilyTitle\n                        alertMessage.value = addressFamilyHelp\n                        showAlert.value = true\n                    },\n                fontSize = 18.sp\n            )\n            val isDropDownExpanded = remember {\n                mutableStateOf(false)\n            }\n            val familyNames = listOf(\"--\",  \"IPv4\", \"IPv6\")\n            val familyValues = listOf(\"\",  \"ipv4\", \"ipv6\")\n            val itemPosition = remember { mutableIntStateOf(familyValues.indexOf(addressFamily)) }\n            Box {\n                Row(\n                    horizontalArrangement = Arrangement.End,\n                    verticalAlignment = Alignment.CenterVertically,\n                    modifier = Modifier.clickable {\n                        isDropDownExpanded.value = true\n                    }\n                ) {\n                    Text(text = familyNames[itemPosition.intValue])\n                    Icon(\n                        imageVector = Icons.Filled.ArrowDropDown,\n                        contentDescription = null,\n                        modifier = Modifier.size(36.dp)\n                    )\n                    //CustomElements.DrawDrawable(R.drawable.arrow_drop_down)\n                }\n                DropdownMenu(\n                    expanded = isDropDownExpanded.value,\n                    onDismissRequest = {\n                        isDropDownExpanded.value = false\n                    }) {\n                    familyNames.forEachIndexed { index, family ->\n                        DropdownMenuItem(\n                            text = { Text(text = family) },\n                            onClick = {\n                                isDropDownExpanded.value = false\n                                itemPosition.intValue = index\n                                viewModel.addressFamily.value = familyValues[index]\n                            })\n                        if (index < 2)\n                            HorizontalDivider(thickness = 1.dp)\n                    }\n                }\n            }\n        }\n    }\n\n    @Composable\n    fun ListenAddress() {\n        val listenAddressTitle = stringResource(R.string.listen_address)\n        val listenAddressHelp = stringResource(R.string.listen_address_help)\n        Row(\n            Modifier\n                .fillMaxWidth()\n                .padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start,\n        ) {\n            val listenAddress by viewModel.listenAddress.collectAsState()\n            OutlinedTextField(\n                value = listenAddress,\n                placeholder = { Text(stringResource(R.string._0_0_0_0_5060)) },\n                onValueChange = {\n                    viewModel.listenAddress.value = it\n                },\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .clickable {\n                        alertTitle.value = listenAddressTitle\n                        alertMessage.value = listenAddressHelp\n                        showAlert.value = true\n                    },\n                singleLine = true,\n                textStyle = androidx.compose.ui.text.TextStyle(fontSize = 18.sp),\n                label = { Text(listenAddressTitle) },\n                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)\n            )\n        }\n    }\n\n    @Composable\n    fun TransportProtocols() {\n        val transportProtocolsTitle = stringResource(R.string.transport_protocols)\n        val transportProtocolsHelp = stringResource(R.string.transport_protocols_help)\n        Row(\n            Modifier\n                .fillMaxWidth()\n                .padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            val transportProtocols by viewModel.transportProtocols.collectAsState()\n            OutlinedTextField(\n                value = transportProtocols,\n                onValueChange = {\n                    viewModel.transportProtocols.value = it\n                },\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .clickable {\n                        alertTitle.value = transportProtocolsTitle\n                        alertMessage.value = transportProtocolsHelp\n                        showAlert.value = true\n                    },\n                textStyle = androidx.compose.ui.text.TextStyle(fontSize = 18.sp),\n                label = { Text(transportProtocolsTitle) },\n                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)\n            )\n        }\n    }\n\n    @Composable\n    fun DnsServers() {\n        val dnsServersTitle = stringResource(R.string.dns_servers)\n        val dnsServersHelp = stringResource(R.string.dns_servers_help)\n        Row(\n            Modifier\n                .fillMaxWidth()\n                .padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            val dnsServers by viewModel.dnsServers.collectAsState()\n            OutlinedTextField(\n                value = dnsServers,\n                onValueChange = {\n                    viewModel.dnsServers.value = it\n                },\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .clickable {\n                        alertTitle.value = dnsServersTitle\n                        alertMessage.value = dnsServersHelp\n                        showAlert.value = true\n                    },\n                textStyle = androidx.compose.ui.text.TextStyle(fontSize = 18.sp),\n                label = { Text(dnsServersTitle) },\n                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)\n            )\n        }\n    }\n\n    @Composable\n    fun TlsCertificateFile(activity: Activity) {\n\n        val ctx = LocalContext.current\n\n        val readCertError = stringResource(R.string.read_cert_error)\n        val tlsCertificateFileTitle = stringResource(R.string.tls_certificate_file)\n        val tlsCertificateFileHelp = stringResource(R.string.tls_certificate_file_help)\n\n        val requestPermissionLauncher = rememberLauncherForActivityResult(\n            contract = ActivityResultContracts.RequestPermission()\n        ) {}\n\n        val certificateRequest = rememberLauncherForActivityResult(\n            contract = ActivityResultContracts.StartActivityForResult()\n        ) {\n            val certPath = BaresipService.filesPath + \"/cert.pem\"\n            val certFile = File(certPath)\n            if (it.resultCode == RESULT_OK) {\n                it.data?.data?.also { uri ->\n                    try {\n                        val inputStream = ctx.contentResolver.openInputStream(uri) as FileInputStream\n                        certFile.copyInputStreamToFile(inputStream)\n                        inputStream.close()\n                        Config.replaceVariable(\"sip_certificate\", certPath)\n                        viewModel.tlsCertificateFile.value = true\n                        save = true\n                        restart = true\n                    } catch (e: Error) {\n                        alertTitle.value = errorTitleText\n                        alertMessage.value = readCertError + \": \" + e.message\n                        showAlert.value = true\n                        viewModel.tlsCertificateFile.value = false\n                    }\n                }\n            }\n            else\n                viewModel.tlsCertificateFile.value = false\n            if (!viewModel.tlsCertificateFile.value)\n                Utils.deleteFile(certFile)\n        }\n\n        val showAlertDialog = remember { mutableStateOf(false) }\n\n        Row(\n            Modifier\n                .fillMaxWidth()\n                .padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            val ctx = LocalContext.current\n            Text(text = tlsCertificateFileTitle,\n                modifier = Modifier\n                    .weight(1f)\n                    .clickable {\n                        alertTitle.value = tlsCertificateFileTitle\n                        alertMessage.value = tlsCertificateFileHelp\n                        showAlert.value = true\n                    },\n                fontSize = 18.sp\n            )\n            val tlsCertificateFile by viewModel.tlsCertificateFile.collectAsState()\n            Switch(\n                checked = tlsCertificateFile,\n                onCheckedChange = {\n                    viewModel.tlsCertificateFile.value = it\n                    if (it)\n                        if (VERSION.SDK_INT < 29) {\n                            viewModel.tlsCertificateFile.value = false\n                            val permission = Manifest.permission.READ_EXTERNAL_STORAGE\n                            when {\n                                ContextCompat.checkSelfPermission(ctx, permission) ==\n                                        PackageManager.PERMISSION_GRANTED -> {\n                                    Log.d(TAG, \"Read External Storage permission granted\")\n                                    val downloadsPath = Utils.downloadsPath(\"cert.pem\")\n                                    val content = Utils.getFileContents(downloadsPath)\n                                    if (content == null) {\n                                        alertTitle.value = errorTitleText\n                                        alertMessage.value = readCertError\n                                        showAlert.value = true\n                                        return@Switch\n                                    }\n                                    val certPath = BaresipService.filesPath + \"/cert.pem\"\n                                    Utils.putFileContents(certPath, content)\n                                    Config.replaceVariable(\"sip_certificate\", certPath)\n                                    viewModel.tlsCertificateFile.value = true\n                                    save = true\n                                    restart = true\n                                }\n                                shouldShowRequestPermissionRationale(activity, permission) ->\n                                    showAlertDialog.value = true\n                                else ->\n                                    requestPermissionLauncher.launch(permission)\n                            }\n                        }\n                        else\n                            Utils.selectInputFile(certificateRequest)\n                    else {\n                        Config.removeVariable(\"sip_certificate\")\n                        Utils.deleteFile(File(BaresipService.filesPath + \"/cert.pem\"))\n                        save = true\n                        restart = true\n                    }\n                }\n            )\n        }\n\n        if (showAlertDialog.value)\n            AlertDialog(\n                showDialog = showAlertDialog,\n                title = stringResource(R.string.notice),\n                message = stringResource(R.string.no_read_permission),\n                firstButtonText = \"\",\n                onFirstClicked = {},\n                lastButtonText = stringResource(R.string.ok),\n                onLastClicked = { requestPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE) },\n            )\n    }\n\n    @Composable\n    fun VerifyServer() {\n        val verifyServerTitle = stringResource(R.string.verify_server)\n        val verifyServerHelp = stringResource(R.string.verify_server_help)\n        Row(\n            Modifier\n                .fillMaxWidth()\n                .padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            Text(text = verifyServerTitle,\n                modifier = Modifier\n                    .weight(1f)\n                    .clickable {\n                        alertTitle.value = verifyServerTitle\n                        alertMessage.value = verifyServerHelp\n                        showAlert.value = true\n                    },\n                fontSize = 18.sp\n            )\n            val verifyServer by viewModel.verifyServer.collectAsState()\n            Switch(\n                checked = verifyServer,\n                onCheckedChange = { viewModel.verifyServer.value = it }\n            )\n        }\n    }\n\n    @Composable\n    fun CaFile(activity: Activity) {\n\n        val ctx = LocalContext.current\n\n        val tlsCaFileTitle = stringResource(R.string.tls_ca_file)\n        val tlsCaFileHelp = stringResource(R.string.tls_ca_file_help)\n        val readCaCertsError = stringResource(R.string.read_ca_certs_error)\n        val noReadPermissionMessage = stringResource(R.string.no_read_permission)\n\n        val requestPermissionLauncher = rememberLauncherForActivityResult(\n            contract = ActivityResultContracts.RequestPermission()\n        ) {}\n\n        val caCertsRequest = rememberLauncherForActivityResult(\n            contract = ActivityResultContracts.StartActivityForResult()\n        ) {\n            val caCertsFile = File(BaresipService.filesPath + \"/ca_certs.crt\")\n            if (it.resultCode == RESULT_OK)\n                it.data?.data?.also { uri ->\n                    try {\n                        val inputStream = ctx.contentResolver.openInputStream(uri) as FileInputStream\n                        caCertsFile.copyInputStreamToFile(inputStream)\n                        inputStream.close()\n                        restart = true\n                    } catch (e: Error) {\n                        alertTitle.value = errorTitleText\n                        alertMessage.value = readCaCertsError + \": \" + e.message\n                        showAlert.value = true\n                        viewModel.caFile.value = false\n                    }\n                }\n            else\n                viewModel.caFile.value = false\n            if (!viewModel.caFile.value) caCertsFile.delete()\n        }\n\n        Row(\n            Modifier\n                .fillMaxWidth()\n                .padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            val ctx = LocalContext.current\n            Text(text = tlsCaFileTitle,\n                modifier = Modifier\n                    .weight(1f)\n                    .clickable {\n                        alertTitle.value = tlsCaFileTitle\n                        alertMessage.value = tlsCaFileHelp\n                        showAlert.value = true\n                    },\n                fontSize = 18.sp\n            )\n            val caFile by viewModel.caFile.collectAsState()\n            Switch(\n                checked = caFile,\n                onCheckedChange = {\n                    viewModel.caFile.value = it\n                    if (it) {\n                        if (VERSION.SDK_INT < 29) {\n                            viewModel.caFile.value = false\n                            val permission = Manifest.permission.READ_EXTERNAL_STORAGE\n                            when {\n                                ContextCompat.checkSelfPermission(ctx, permission) == PackageManager.PERMISSION_GRANTED -> {\n                                    Log.d(TAG, \"Read External Storage permission granted\")\n                                    val downloadsPath = Utils.downloadsPath(\"ca_certs.crt\")\n                                    val content = Utils.getFileContents(downloadsPath)\n                                    if (content == null) {\n                                        alertTitle.value = errorTitleText\n                                        alertMessage.value = readCaCertsError\n                                        showAlert.value = true\n                                        return@Switch\n                                    }\n                                    File(BaresipService.filesPath + \"/ca_certs.crt\").writeBytes(content)\n                                    viewModel.caFile.value = true\n                                    restart = true\n                                }\n                                shouldShowRequestPermissionRationale(activity, permission) -> {\n                                    dialogTitle.value = noticeTitleText\n                                    dialogMessage.value = noReadPermissionMessage\n                                    firstButtonText.value = \"\"\n                                    lastButtonText.value = okButtonText\n                                    onLastClicked.value = {\n                                        requestPermissionLauncher.launch(permission)\n                                    }\n                                    showDialog.value = true\n                                }\n                                else ->\n                                    requestPermissionLauncher.launch(permission)\n                            }\n                        }\n                        else\n                            Utils.selectInputFile(caCertsRequest)\n                    }\n                    else {\n                        Utils.deleteFile(File(BaresipService.filesPath + \"/ca_certs.crt\"))\n                        restart = true\n                    }\n                }\n            )\n        }\n    }\n\n    @Composable\n    fun UserAgent() {\n        val userAgentTitle = stringResource(R.string.user_agent)\n        val userAgentHelp = stringResource(R.string.user_agent_help)\n        Row(\n            Modifier\n                .fillMaxWidth()\n                .padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            val userAgent by viewModel.userAgent.collectAsState()\n            OutlinedTextField(\n                value = userAgent,\n                placeholder = { Text(userAgentTitle) },\n                onValueChange = { viewModel.userAgent.value = it },\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .clickable {\n                        alertTitle.value = userAgentTitle\n                        alertMessage.value = userAgentHelp\n                        showAlert.value = true\n                    },\n                textStyle = androidx.compose.ui.text.TextStyle(fontSize = 18.sp),\n                label = { Text(userAgentTitle) },\n                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)\n            )\n        }\n    }\n\n    @Composable\n    fun UniqueContactUri() {\n        val uniqueContactUriTitle = stringResource(R.string.unique_contact_uri)\n        val uniqueContactUriHelp = stringResource(R.string.unique_contact_uri_help)\n        Row(\n            Modifier\n                .fillMaxWidth()\n                .padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            Text(text = uniqueContactUriTitle,\n                modifier = Modifier\n                    .weight(1f)\n                    .clickable {\n                        alertTitle.value = uniqueContactUriTitle\n                        alertMessage.value = uniqueContactUriHelp\n                        showAlert.value = true\n                    },\n                fontSize = 18.sp\n            )\n            val uniqueContactUri by viewModel.uniqueContactUri.collectAsState()\n            Switch(\n                checked = uniqueContactUri,\n                onCheckedChange = { viewModel.uniqueContactUri.value = it }\n            )\n        }\n    }\n\n    @Composable\n    fun AudioSettings(navController: NavController) {\n        Row(\n            Modifier\n                .fillMaxWidth()\n                .padding(top = 12.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            Text(\n                text = stringResource(R.string.audio_settings),\n                modifier = Modifier\n                    .weight(1f)\n                    .clickable { navController.navigate(\"audio\") },\n                fontSize = 18.sp,\n                fontWeight = FontWeight. Bold\n            )\n        }\n    }\n\n    @Composable\n    fun Contacts(activity: Activity) {\n        val contactsTitle = stringResource(R.string.contacts)\n        val contactsHelp = stringResource(R.string.contacts_help)\n        val consentRequestTitle = stringResource(R.string.consent_request)\n        val contactsConsentMessage = stringResource(R.string.contacts_consent)\n        val denyText = stringResource(R.string.deny)\n        val acceptText = stringResource(R.string.accept)\n        val both = stringResource(R.string.both)\n        val noAndroidContactsMessage = stringResource(R.string.no_android_contacts)\n        val showAlertDialog = remember { mutableStateOf(false) }\n        Row(\n            Modifier\n                .fillMaxWidth()\n                .padding(top = 12.dp)\n                .padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            val ctx = LocalContext.current\n            Text(text = contactsTitle,\n                modifier = Modifier\n                    .weight(1f)\n                    .clickable {\n                        alertTitle.value = contactsTitle\n                        alertMessage.value = contactsHelp\n                        showAlert.value = true\n                    },\n                fontSize = 18.sp\n            )\n            val isDropDownExpanded = remember {\n                mutableStateOf(false)\n            }\n            val contactNames = listOf(\n                \"baresip\",\n                \"Android\",\n                both\n            )\n            val contactsMode by viewModel.contactsMode.collectAsState()\n            val contactValues = listOf(\"baresip\",  \"android\", \"both\")\n            val itemPosition = remember {\n                mutableIntStateOf(contactValues.indexOf(contactsMode))\n            }\n            val requestPermissionsLauncher = rememberLauncherForActivityResult(\n                contract = ActivityResultContracts.RequestMultiplePermissions()\n            ) {}\n            val contactsPermissions = arrayOf(Manifest.permission.READ_CONTACTS,\n                Manifest.permission.WRITE_CONTACTS)\n            Box {\n                Row(\n                    horizontalArrangement = Arrangement.Center,\n                    verticalAlignment = Alignment.CenterVertically,\n                    modifier = Modifier.clickable {\n                        isDropDownExpanded.value = true\n                    }\n                ) {\n                    Text(text = contactNames[itemPosition.intValue])\n                    Icon(\n                        imageVector = Icons.Filled.ArrowDropDown,\n                        contentDescription = null,\n                        modifier = Modifier.size(36.dp)\n                    )\n                }\n                DropdownMenu(\n                    expanded = isDropDownExpanded.value,\n                    onDismissRequest = {\n                        isDropDownExpanded.value = false\n                    }) {\n                    contactNames.forEachIndexed { index, name ->\n                        DropdownMenuItem(\n                            text = { Text(text = name) },\n                            onClick = {\n                                isDropDownExpanded.value = false\n                                val mode = contactValues[index]\n                                if (mode != \"baresip\" && !Utils.checkPermissions(ctx, contactsPermissions)) {\n                                    dialogTitle.value = consentRequestTitle\n                                    dialogMessage.value = contactsConsentMessage\n                                    firstButtonText.value = denyText\n                                    onFirstClicked.value = {\n                                        itemPosition.intValue = contactValues.indexOf(contactsMode)\n                                    }\n                                    lastButtonText.value = acceptText\n                                    onLastClicked.value = {\n                                        showDialog.value = false\n                                        viewModel.contactsMode.value = mode\n                                        if (ContextCompat.checkSelfPermission(\n                                                ctx,\n                                                Manifest.permission.READ_CONTACTS\n                                            ) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(\n                                                ctx,\n                                                Manifest.permission.WRITE_CONTACTS\n                                            ) == PackageManager.PERMISSION_GRANTED\n                                        ) {\n                                            Log.d(TAG, \"Contacts permissions already granted\")\n                                        }\n                                        else {\n                                            if (shouldShowRequestPermissionRationale(\n                                                    activity, Manifest.permission.READ_CONTACTS\n                                                ) ||\n                                                shouldShowRequestPermissionRationale(\n                                                    activity, Manifest.permission.WRITE_CONTACTS\n                                                )\n                                            )\n                                                showAlertDialog.value = true\n                                            else\n                                                requestPermissionsLauncher.launch(\n                                                    arrayOf(\n                                                        Manifest.permission.READ_CONTACTS,\n                                                        Manifest.permission.WRITE_CONTACTS\n                                                    )\n                                                )\n                                        }\n                                    }\n                                    showDialog.value = true\n                                }\n                                else {\n                                    itemPosition.intValue = index\n                                    viewModel.contactsMode.value = contactValues[index]\n                                }\n                            })\n                        if (index < 2)\n                            HorizontalDivider(thickness = 1.dp)\n                    }\n                }\n            }\n            if (showAlertDialog.value)\n                AlertDialog(\n                    showDialog = showAlertDialog,\n                    title = noticeTitleText,\n                    message = noAndroidContactsMessage,\n                    firstButtonText = \"\",\n                    onFirstClicked = {},\n                    lastButtonText = okButtonText,\n                    onLastClicked = { requestPermissionsLauncher.launch(\n                        arrayOf(\n                            Manifest.permission.READ_CONTACTS,\n                            Manifest.permission.WRITE_CONTACTS\n                        )\n                    )},\n                )\n        }\n    }\n\n    @Composable\n    fun Ringtone() {\n        val ringToneTitle = stringResource(R.string.ringtone)\n        val selectRingToneMessage = stringResource(R.string.select_ringtone)\n        val launcher = rememberLauncherForActivityResult(\n            ActivityResultContracts.StartActivityForResult()\n        ) { result: ActivityResult ->\n            if (result.resultCode == RESULT_OK) {\n                val uri: Uri? = if (VERSION.SDK_INT >= 33)\n                    result.data?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, Uri::class.java)\n                else\n                    @Suppress(\"DEPRECATION\")\n                    result.data?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)\n                if (uri != null)\n                    viewModel.ringtoneUri.value = uri.toString()\n            }\n        }\n        Row(\n            Modifier\n                .fillMaxWidth()\n                .padding(top = 12.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            Text(\n                text = ringToneTitle,\n                modifier = Modifier\n                    .weight(1f)\n                    .clickable {\n                        val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER)\n                        intent.putExtra(\n                            RingtoneManager.EXTRA_RINGTONE_TYPE,\n                            RingtoneManager.TYPE_RINGTONE\n                        )\n                        intent.putExtra(\n                            RingtoneManager.EXTRA_RINGTONE_TITLE,\n                            selectRingToneMessage\n                        )\n                        intent.putExtra(\n                            RingtoneManager.EXTRA_RINGTONE_EXISTING_URI,\n                            viewModel.ringtoneUri.value.toUri()\n                        )\n                        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false)\n                        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true)\n                        launcher.launch(intent)\n                    },\n                fontSize = 18.sp,\n                fontWeight = FontWeight.Bold\n            )\n        }\n    }\n\n    @Composable\n    fun BatteryOptimizations() {\n        val batteryOptimizationsTitle = stringResource(R.string.battery_optimizations)\n        val batteryOptimizationsHelp = stringResource(R.string.battery_optimizations_help)\n        Row(\n            Modifier\n                .fillMaxWidth()\n                .padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            val ctx = LocalContext.current\n            val batterySettingsLauncher = rememberLauncherForActivityResult(\n                contract = ActivityResultContracts.StartActivityForResult()\n            ) { _ ->\n                val powerManager = ctx.getSystemService(POWER_SERVICE) as PowerManager\n                viewModel.batteryOptimizations.value =\n                    !powerManager.isIgnoringBatteryOptimizations(ctx.packageName)\n            }\n            Text(text = batteryOptimizationsTitle,\n                modifier = Modifier\n                    .weight(1f)\n                    .clickable {\n                        alertTitle.value = batteryOptimizationsTitle\n                        alertMessage.value = batteryOptimizationsHelp\n                        showAlert.value = true\n                    },\n                fontSize = 18.sp\n            )\n            val battery by viewModel.batteryOptimizations.collectAsState()\n            Switch(\n                checked = battery,\n                onCheckedChange = {\n                    viewModel.batteryOptimizations.value = it\n                    batterySettingsLauncher.launch(\n                        Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS))\n                }\n            )\n        }\n    }\n\n    @Composable\n    fun DarkTheme() {\n        val darkThemeTitle = stringResource(R.string.dark_theme)\n        val darkThemeHelp = stringResource(R.string.dark_theme_help)\n        Row(\n            Modifier\n                .fillMaxWidth()\n                .padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            Text(text = darkThemeTitle,\n                modifier = Modifier\n                    .weight(1f)\n                    .clickable {\n                        alertTitle.value = darkThemeTitle\n                        alertMessage.value = darkThemeHelp\n                        showAlert.value = true\n                    },\n                fontSize = 18.sp\n            )\n            val darkTheme by viewModel.darkTheme.collectAsState()\n            Switch(\n                checked = darkTheme,\n                onCheckedChange = { viewModel.darkTheme.value = it }\n            )\n        }\n    }\n\n    @Composable\n    fun DynamicColors() {\n        val dynamicColorsTitle = stringResource(R.string.dynamic_colors)\n        val dynamicColorsHelp = stringResource(R.string.dynamic_colors_help)\n        Row(\n            Modifier\n                .fillMaxWidth()\n                .padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            Text(text = dynamicColorsTitle,\n                modifier = Modifier\n                    .weight(1f)\n                    .clickable {\n                        alertTitle.value = dynamicColorsTitle\n                        alertMessage.value = dynamicColorsHelp\n                        showAlert.value = true\n                    },\n                fontSize = 18.sp\n            )\n            val dynamicColors by viewModel.dynamicColors.collectAsState()\n            Switch(\n                checked = dynamicColors,\n                onCheckedChange = { viewModel.dynamicColors.value = it }\n            )\n        }\n    }\n\n    @Composable\n    fun ColorBlind() {\n        val colorBlindTitle = stringResource(R.string.colorblind)\n        val colorBlindHelp = stringResource(R.string.colorblind_help)\n        Row(\n            Modifier\n                .fillMaxWidth()\n                .padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            Text(text = colorBlindTitle,\n                modifier = Modifier\n                    .weight(1f)\n                    .clickable {\n                        alertTitle.value = colorBlindTitle\n                        alertMessage.value = colorBlindHelp\n                        showAlert.value = true\n                    },\n                fontSize = 18.sp\n            )\n            val colorblind by viewModel.colorblind.collectAsState()\n            Switch(\n                checked = colorblind,\n                onCheckedChange = { viewModel.colorblind.value = it }\n            )\n        }\n    }\n    @Composable\n    fun ProximitySensing() {\n        val proximitySensingTitle = stringResource(R.string.proximity_sensing)\n        val proximitySensingHelp = stringResource(R.string.proximity_sensing_help)\n        Row(\n            Modifier\n                .fillMaxWidth()\n                .padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            Text(text = proximitySensingTitle,\n                modifier = Modifier\n                    .weight(1f)\n                    .clickable {\n                        alertTitle.value = proximitySensingTitle\n                        alertMessage.value = proximitySensingHelp\n                        showAlert.value = true\n                    },\n                fontSize = 18.sp\n            )\n            val proximitySensing by viewModel.proximitySensing.collectAsState()\n            Switch(\n                checked = proximitySensing,\n                onCheckedChange = { viewModel.proximitySensing.value = it }\n            )\n        }\n    }\n\n    @RequiresApi(29)\n    @Composable\n    fun DefaultDialer() {\n        val defaultPhoneAppTitle = stringResource(R.string.default_phone_app)\n        val defaultPhoneAppHelp = stringResource(R.string.default_phone_app_help)\n        val dialerRoleNotAvailableMessage = stringResource(R.string.dialer_role_not_available)\n        Row(\n            Modifier\n                .fillMaxWidth()\n                .padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            val ctx = LocalContext.current\n            Text(text = defaultPhoneAppTitle,\n                modifier = Modifier\n                    .weight(1f)\n                    .clickable {\n                        alertTitle.value = defaultPhoneAppTitle\n                        alertMessage.value = defaultPhoneAppHelp\n                        showAlert.value = true\n                    },\n                fontSize = 18.sp\n            )\n            val defaultDialer by viewModel.defaultDialer.collectAsState()\n            val roleManager = ctx.getSystemService(ROLE_SERVICE) as RoleManager\n            val dialerRoleRequest = rememberLauncherForActivityResult(\n                contract = ActivityResultContracts.StartActivityForResult()\n            ) { result ->\n                Log.d(TAG, \"dialerRoleRequest result: $result\")\n                viewModel.defaultDialer.value = roleManager.isRoleHeld(RoleManager.ROLE_DIALER)\n            }\n            Switch(\n                checked = defaultDialer,\n                onCheckedChange = {\n                    viewModel.defaultDialer.value = it\n                    if (it) {\n                        if (!roleManager.isRoleAvailable(RoleManager.ROLE_DIALER)) {\n                            alertTitle.value = alertTitleText\n                            alertMessage.value = dialerRoleNotAvailableMessage\n                            showAlert.value = true\n                        }\n                        else\n                            if (!roleManager.isRoleHeld(RoleManager.ROLE_DIALER))\n                                dialerRoleRequest.launch(roleManager.createRequestRoleIntent(RoleManager.ROLE_DIALER))\n                    } else {\n                        try {\n                            dialerRoleRequest.launch(Intent(\"android.settings.MANAGE_DEFAULT_APPS_SETTINGS\"))\n                        } catch (e: ActivityNotFoundException) {\n                            Log.e(TAG, \"ActivityNotFound exception: ${e.message}\")\n                        }\n                    }\n                }\n            )\n        }\n    }\n\n    @Composable\n    fun Debug() {\n        val debugTitle = stringResource(R.string.debug)\n        val debugHelp = stringResource(R.string.debug_help)\n        Row(\n            Modifier\n                .fillMaxWidth()\n                .padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            Text(text = debugTitle,\n                modifier = Modifier\n                    .weight(1f)\n                    .clickable {\n                        alertTitle.value = debugTitle\n                        alertMessage.value = debugHelp\n                        showAlert.value = true\n                    },\n                fontSize = 18.sp\n            )\n            val debug by viewModel.debug.collectAsState()\n            Switch(\n                checked = debug,\n                onCheckedChange = { viewModel.debug.value = it }\n            )\n        }\n    }\n\n    @Composable\n    fun SipTrace() {\n        val sipTraceTitle = stringResource(R.string.sip_trace)\n        val sipTraceHelp = stringResource(R.string.sip_trace_help)\n        Row(\n            Modifier\n                .fillMaxWidth()\n                .padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            Text(text = sipTraceTitle,\n                modifier = Modifier\n                    .weight(1f)\n                    .clickable {\n                        alertTitle.value = sipTraceTitle\n                        alertMessage.value = sipTraceHelp\n                        showAlert.value = true\n                    },\n                fontSize = 18.sp\n            )\n            val sipTrace by viewModel.sipTrace.collectAsState()\n            Switch(\n                checked = sipTrace,\n                onCheckedChange = { viewModel.sipTrace.value = it }\n            )\n        }\n    }\n\n    @Composable\n    fun Reset(onRestartApp: () -> Unit) {\n        val resetConfigTitle = stringResource(R.string.reset_config)\n        val resetConfigHelp = stringResource(R.string.reset_config_help)\n        val resetConfigAlert = stringResource(R.string.reset_config_alert)\n        val resetButtonText = stringResource(R.string.reset)\n        Row(\n            Modifier\n                .fillMaxWidth()\n                .padding(end = 10.dp),\n            verticalAlignment = Alignment.CenterVertically,\n            horizontalArrangement = Arrangement.Start\n        ) {\n            Text(text = resetConfigTitle,\n                modifier = Modifier\n                    .weight(1f)\n                    .clickable {\n                        alertTitle.value = resetConfigTitle\n                        alertMessage.value = resetConfigHelp\n                        showAlert.value = true\n                    },\n                fontSize = 18.sp\n            )\n            var reset by remember { mutableStateOf(false) }\n            Switch(\n                checked = reset,\n                onCheckedChange = {\n                    dialogTitle.value = confirmationText\n                    dialogMessage.value = resetConfigAlert\n                    firstButtonText.value = cancelButtonText\n                    onFirstClicked.value = {\n                        reset = false\n                    }\n                    lastButtonText.value = resetButtonText\n                    onLastClicked.value = {\n                        Config.reset()\n                        onRestartApp()\n                    }\n                    showDialog.value = true\n                }\n            )\n        }\n    }\n\n    if (Config.variable(\"auto_start\") == \"yes\" &&\n            !isAppearOnTopPermissionGranted(LocalContext.current)) {\n        Config.replaceVariable(\"auto_start\", \"no\")\n        save = true\n    }\n\n    val scrollState = rememberScrollState()\n\n    Column(\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(contentPadding)\n            .padding(start = 16.dp, end = 4.dp, top = 8.dp, bottom = 8.dp)\n            .verticalScrollbar(scrollState)\n            .verticalScroll(scrollState),\n        verticalArrangement = Arrangement.spacedBy(8.dp),\n    ) {\n        StartAutomatically()\n        AddressFamily()\n        ListenAddress()\n        TransportProtocols()\n        DnsServers()\n        TlsCertificateFile(activity)\n        VerifyServer()\n        CaFile(activity)\n        UserAgent()\n        UniqueContactUri()\n        AudioSettings(navController)\n        Contacts(activity)\n        Ringtone()\n        BatteryOptimizations()\n        DarkTheme()\n        if (VERSION.SDK_INT >= 31)\n            DynamicColors()\n        ColorBlind()\n        ProximitySensing()\n        if (VERSION.SDK_INT >= 29)\n            DefaultDialer()\n        Debug()\n        SipTrace()\n        Reset(onRestartApp)\n    }\n}\n\nprivate fun checkOnClick(ctx: Context, viewModel: SettingsViewModel): Boolean {\n\n    val noticeTitle = ctx.getString(R.string.notice)\n\n    if ((Config.variable(\"auto_start\") == \"yes\") != viewModel.autoStart.value) {\n        Config.replaceVariable(\n            \"auto_start\",\n            if (viewModel.autoStart.value) \"yes\" else \"no\"\n        )\n        save = true\n    }\n\n    val listenAddr = viewModel.listenAddress.value.trim()\n    if (listenAddr != Config.variable(\"sip_listen\")) {\n        if ((listenAddr != \"\") && !Utils.checkIpPort(listenAddr)) {\n            alertTitle.value = noticeTitle\n            alertMessage.value = \"${ctx.getString(R.string.invalid_listen_address)}: $listenAddr\"\n            showAlert.value = true\n            return false\n        }\n        Config.replaceVariable(\"sip_listen\", listenAddr)\n        save = true\n        restart = true\n    }\n\n    if (Config.variable(\"net_af\").lowercase() != viewModel.addressFamily.value) {\n        Config.replaceVariable(\"net_af\", viewModel.addressFamily.value)\n        save = true\n        restart = true\n    }\n\n    val transportProtocols = viewModel.transportProtocols.value\n        .lowercase(Locale.ROOT).replace(\" \", \"\")\n    if (transportProtocols != Config.variable(\"sip_transports\")) {\n        if (!checkTransportProtocols(transportProtocols)) {\n            alertTitle.value = noticeTitle\n            alertMessage.value = \"${ctx.getString(R.string.invalid_transport_protocols)}: \" +\n                    transportProtocols\n            showAlert.value = true\n            return false\n        }\n        Config.removeVariable(\"sip_transports\")\n        if (transportProtocols.isNotEmpty())\n            Config.replaceVariable(\"sip_transports\", transportProtocols)\n        save = true\n        restart = true\n    }\n\n    val dnsServers = addMissingPorts(viewModel.dnsServers.value\n        .lowercase(Locale.ROOT).replace(\" \", \"\"))\n    if (dnsServers != Config.dnsServers()) {\n        if (!checkDnsServers(dnsServers)) {\n            alertTitle.value = noticeTitle\n            alertMessage.value = \"${ctx.getString(R.string.invalid_dns_servers)}: $dnsServers\"\n            showAlert.value = true\n            return false\n        }\n        Config.removeVariable(\"dns_server\")\n        if (dnsServers.isNotEmpty()) {\n            for (server in dnsServers.split(\",\"))\n                Config.addVariable(\"dns_server\", server)\n            Config.replaceVariable(\"dyn_dns\", \"no\")\n            if (Api.net_use_nameserver(dnsServers) != 0) {\n                alertTitle.value = noticeTitle\n                alertMessage.value = \"${ctx.getString(R.string.failed_to_set_dns_servers)}: $dnsServers\"\n                showAlert.value = true\n                return false\n            }\n        } else {\n            Config.replaceVariable(\"dyn_dns\", \"yes\")\n            Config.updateDnsServers(BaresipService.dnsServers)\n        }\n        // Api.net_dns_debug()\n        save = true\n    }\n\n    if ((Config.variable(\"sip_verify_server\") == \"yes\") != viewModel.verifyServer.value) {\n        Config.replaceVariable(\"sip_verify_server\",\n            if (viewModel.verifyServer.value) \"yes\" else \"no\")\n        Api.config_verify_server_set(viewModel.verifyServer.value)\n        save = true\n    }\n\n    val userAgent = viewModel.userAgent.value.trim()\n    if (userAgent != Config.variable(\"user_agent\")) {\n        if (userAgent != \"\" && !Utils.checkServerVal(userAgent)) {\n            alertTitle.value = noticeTitle\n            alertMessage.value = \"${ctx.getString(R.string.invalid_user_agent)}: \" +\n                    userAgent\n            showAlert.value = true\n            return false\n        }\n        if (userAgent != \"\")\n            Config.replaceVariable(\"user_agent\", userAgent)\n        else\n            Config.removeVariable(\"user_agent\")\n        save = true\n        restart = true\n    }\n\n    if ((Config.variable(\"sip_cuser_random\") == \"yes\") != viewModel.uniqueContactUri.value) {\n        Config.replaceVariable(\"sip_cuser_random\",\n            if (viewModel.uniqueContactUri.value) \"yes\" else \"no\")\n        save = true\n        restart = true\n    }\n\n    val ringtoneUri = viewModel.ringtoneUri.value\n    Preferences(ctx).ringtoneUri = ringtoneUri\n    BaresipService.rt = RingtoneManager.getRingtone(ctx, ringtoneUri.toUri())\n\n    val contactsMode = viewModel.contactsMode.value\n    if (Config.variable(\"contacts_mode\").lowercase() != contactsMode) {\n        Config.replaceVariable(\"contacts_mode\", contactsMode)\n        BaresipService.contactsMode = contactsMode\n        val baresipService = Intent(ctx, BaresipService::class.java)\n        when (contactsMode) {\n            \"baresip\" -> {\n                BaresipService.androidContacts.value = listOf()\n                Contact.restoreBaresipContacts()\n                baresipService.action = \"Stop Content Observer\"\n            }\n            \"android\" -> {\n                BaresipService.baresipContacts.value = mutableListOf()\n                Contact.loadAndroidContacts(ctx)\n                baresipService.action = \"Start Content Observer\"\n            }\n            \"both\" -> {\n                Contact.restoreBaresipContacts()\n                Contact.loadAndroidContacts(ctx)\n                baresipService.action = \"Start Content Observer\"\n            }\n        }\n        Contact.contactsUpdate()\n        ContextCompat.startForegroundService(ctx, baresipService)\n        save = true\n    }\n\n    val darkTheme = viewModel.darkTheme.value\n    val newDisplayTheme = if (darkTheme)\n        AppCompatDelegate.MODE_NIGHT_YES\n    else\n        AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM\n    if (Preferences(ctx).displayTheme == AppCompatDelegate.MODE_NIGHT_YES != darkTheme) {\n        Preferences(ctx).displayTheme = newDisplayTheme\n        BaresipService.darkTheme.value = darkTheme\n        AppCompatDelegate.setDefaultNightMode(newDisplayTheme)\n        Config.replaceVariable(\"dark_theme\",\n            if (darkTheme) \"yes\" else \"no\")\n        save = true\n    }\n\n    val dynamicColors = viewModel.dynamicColors.value\n    if (BaresipService.dynamicColors.value != dynamicColors) {\n        BaresipService.dynamicColors.value = dynamicColors\n        Config.replaceVariable(\"dynamic_colors\",\n            if (dynamicColors) \"yes\" else \"no\")\n        save = true\n    }\n\n    val colorblind = viewModel.colorblind.value\n    if ((Config.variable(\"colorblind\") == \"yes\") != colorblind) {\n        Config.replaceVariable(\n            \"colorblind\",\n            if (colorblind) \"yes\" else \"no\"\n        )\n        BaresipService.colorblind = colorblind\n        UserAgent.updateColorblindStatus()\n        val baresipService = Intent(ctx, BaresipService::class.java)\n        baresipService.action = \"Update Notification\"\n        ContextCompat.startForegroundService(ctx, baresipService)\n        save = true\n    }\n\n    val proximitySensing = viewModel.proximitySensing.value\n    if ((Config.variable(\"proximity_sensing\") == \"yes\") != proximitySensing) {\n        Config.replaceVariable(\n            \"proximity_sensing\",\n            if (proximitySensing) \"yes\" else \"no\"\n        )\n        BaresipService.proximitySensing = proximitySensing\n        save = true\n    }\n\n    val debug = viewModel.debug.value\n    if ((Config.variable(\"log_level\") == \"0\") != debug) {\n        val logLevelString = if (debug) \"0\" else \"2\"\n        Config.replaceVariable(\"log_level\", logLevelString)\n        Api.log_level_set(logLevelString.toInt())\n        Log.logLevelSet(logLevelString.toInt())\n        save = true\n    }\n\n    val sipTrace = viewModel.sipTrace.value\n    if (BaresipService.sipTrace != sipTrace) {\n        BaresipService.sipTrace = sipTrace\n        Api.uag_enable_sip_trace(sipTrace)\n    }\n\n    if (save) Config.save()\n\n    return true\n}\n\nprivate fun isAppearOnTopPermissionGranted(ctx: Context): Boolean {\n    return Settings.canDrawOverlays(ctx)\n}\n\nprivate fun addMissingPorts(addressList: String): String {\n    if (addressList == \"\") return \"\"\n    var result = \"\"\n    for (addr in addressList.split(\",\"))\n        result = if (Utils.checkIpPort(addr)) {\n            \"$result,$addr\"\n        } else {\n            if (Utils.checkIpV4(addr))\n                \"$result,$addr:53\"\n            else\n                \"$result,[$addr]:53\"\n        }\n    return result.substring(1)\n}\n\nprivate fun checkTransportProtocols(transportProtocols: String): Boolean {\n    if (transportProtocols.isEmpty())\n        return true\n    for (protocol in transportProtocols.split(\",\"))\n        if (protocol !in listOf(\"udp\", \"tcp\", \"tls\", \"ws\", \"wss\"))\n            return false\n    return true\n}\n\nprivate fun checkDnsServers(dnsServers: String): Boolean {\n    if (dnsServers.isEmpty()) return true\n    for (server in dnsServers.split(\",\"))\n        if (!Utils.checkIpPort(server.trim())) return false\n    return true\n}\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/SettingsViewModel.kt",
    "content": "package com.tutpro.baresip\n\nimport android.app.role.RoleManager\nimport android.content.Context\nimport android.content.Context.POWER_SERVICE\nimport android.content.Context.ROLE_SERVICE\nimport android.media.RingtoneManager\nimport android.os.Build\nimport android.os.PowerManager\nimport androidx.appcompat.app.AppCompatDelegate\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.lifecycle.ViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport java.io.File\n\nclass SettingsViewModel: ViewModel() {\n    \n    val autoStart = MutableStateFlow(false)\n    val listenAddress = MutableStateFlow(\"\")\n    val addressFamily = MutableStateFlow(\"\")\n    val transportProtocols = MutableStateFlow(\"\")\n    val dnsServers = MutableStateFlow(\"\")\n    val tlsCertificateFile = MutableStateFlow(false)\n    val verifyServer = MutableStateFlow(false)\n    val caFile = MutableStateFlow(false)\n    val userAgent = MutableStateFlow(\"\")\n    val uniqueContactUri =  MutableStateFlow(true)\n    val contactsMode = MutableStateFlow(\"\")\n    val ringtoneUri = MutableStateFlow(\"\")\n    val batteryOptimizations = MutableStateFlow(false)\n    val darkTheme = MutableStateFlow(false)\n    val dynamicColors = MutableStateFlow(false)\n    val colorblind = MutableStateFlow(false)\n    val proximitySensing = MutableStateFlow(false)\n    val defaultDialer = MutableStateFlow(false)\n    val debug = MutableStateFlow(false)\n    val sipTrace = MutableStateFlow(false)\n\n    private var isLoaded = false\n\n    fun loadSettings(ctx: Context) {\n\n        if (isLoaded) return else isLoaded = true\n\n        autoStart.value = Config.variable(\"auto_start\") == \"yes\"\n\n        listenAddress.value = Config.variable(\"sip_listen\")\n\n        val familyValues = listOf(\"\",  \"ipv4\", \"ipv6\")\n        val itemPosition =\n            mutableIntStateOf(familyValues.indexOf(Config.variable(\"net_af\").lowercase()))\n        addressFamily.value = familyValues[itemPosition.intValue]\n\n        transportProtocols.value = Config.variable(\"sip_transports\")\n\n        dnsServers.value = Config.dnsServers()\n\n        tlsCertificateFile.value = File(BaresipService.filesPath + \"/cert.pem\").exists()\n\n        verifyServer.value = Config.variable(\"sip_verify_server\") == \"yes\"\n\n        caFile.value = File(BaresipService.filesPath + \"/ca_certs.crt\").exists()\n\n        userAgent.value = Config.variable(\"user_agent\")\n\n        uniqueContactUri.value =  Config.variable(\"sip_cuser_random\") == \"yes\"\n\n        contactsMode.value = Config.variable(\"contacts_mode\").lowercase()\n\n        ringtoneUri.value = if (Preferences(ctx).ringtoneUri == \"\")\n            RingtoneManager.getActualDefaultRingtoneUri(ctx, RingtoneManager.TYPE_RINGTONE).toString()\n        else\n            Preferences(ctx).ringtoneUri!!\n\n        val powerManager = ctx.getSystemService(POWER_SERVICE) as PowerManager\n        batteryOptimizations.value = !powerManager.isIgnoringBatteryOptimizations(ctx.packageName)\n\n        darkTheme.value = Preferences(ctx).displayTheme == AppCompatDelegate.MODE_NIGHT_YES\n\n        dynamicColors.value = BaresipService.dynamicColors.value\n\n        colorblind.value = Config.variable(\"colorblind\") == \"yes\"\n\n        proximitySensing.value = Config.variable(\"proximity_sensing\") == \"yes\"\n\n        if (Build.VERSION.SDK_INT >= 29) {\n            val roleManager = ctx.getSystemService(ROLE_SERVICE) as RoleManager\n            defaultDialer.value = roleManager.isRoleHeld(RoleManager.ROLE_DIALER)\n        }\n\n        debug.value = Config.variable(\"log_level\") == \"0\"\n\n        sipTrace.value = BaresipService.sipTrace\n    }\n\n}"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/TaskReceiver.kt",
    "content": "package com.tutpro.baresip\n\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport androidx.core.content.ContextCompat\n\nclass TaskReceiver : BroadcastReceiver() {\n\n    override fun onReceive(context: Context, intent: Intent) {\n\n        Log.d(TAG, \"TaskReceiver: received intent ${intent.action}\")\n\n        when (intent.action) {\n\n            \"com.tutpro.baresip.REGISTER\", \"com.tutpro.baresip.UNREGISTER\" -> {\n                var aor = intent.getStringExtra(\"aor\")\n                if (aor == null) {\n                    Log.i(TAG, \"TaskReceiver: 'aor' extra is missing\")\n                    return\n                }\n                if (!aor.startsWith(\"sip:\"))\n                    aor = \"sip:$aor\"\n                val ua = UserAgent.ofAor(aor)\n                if (ua == null) {\n                    Log.i(TAG, \"TaskReceiver: user agent of AoR $aor is not found\")\n                    return\n                }\n                val acc = ua.account\n                if (intent.action == \"com.tutpro.baresip.REGISTER\") {\n                    Log.d(TAG, \"TaskReceiver: registering $aor\")\n                    Api.account_set_regint(acc.accp, REGISTRATION_INTERVAL)\n                    Api.ua_register(ua.uap)\n                    acc.regint = Api.account_regint(acc.accp)\n                    Account.saveAccounts()\n                } else {\n                    Log.d(TAG, \"TaskReceiver: un-registering $aor\")\n                    Api.account_set_regint(acc.accp, 0)\n                    Api.ua_unregister(ua.uap)\n                    acc.regint = Api.account_regint(acc.accp)\n                    Account.saveAccounts()\n                }\n            }\n\n            \"com.tutpro.baresip.QUIT\" -> {\n                Log.d(TAG, \"TaskReceiver: quiting\")\n                val baresipService = Intent(context, BaresipService::class.java)\n                if (BaresipService.isServiceRunning) {\n                    baresipService.action = \"Stop\"\n                    ContextCompat.startForegroundService(context, baresipService)\n                }\n            }\n\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/Theme.kt",
    "content": "package com.tutpro.baresip\n\nimport android.app.Activity\nimport android.os.Build.VERSION\nimport androidx.activity.ComponentActivity\nimport androidx.activity.SystemBarStyle\nimport androidx.activity.enableEdgeToEdge\nimport androidx.compose.foundation.isSystemInDarkTheme\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.dynamicDarkColorScheme\nimport androidx.compose.material3.dynamicLightColorScheme\nimport androidx.compose.material3.lightColorScheme\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.SideEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.remember\nimport androidx.compose.ui.graphics.toArgb\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalView\nimport androidx.core.graphics.ColorUtils\nimport androidx.core.view.WindowCompat\n\n@Composable\nfun AppTheme(\n    content: @Composable () -> Unit\n) {\n    val context = LocalContext.current\n    val useDarkTheme by remember { BaresipService.darkTheme }\n    val useDynamicColors by remember { BaresipService.dynamicColors }\n\n    val useActualDynamicColors = useDynamicColors && VERSION.SDK_INT >= 31\n    val isDark = useDarkTheme || isSystemInDarkTheme()\n\n    val colorScheme = when {\n        useActualDynamicColors -> {\n            if (isDark)\n                dynamicDarkColorScheme(context)\n            else\n                dynamicLightColorScheme(context)\n        }\n        isDark -> darkColorScheme(\n            primary = PrimaryDark,\n            onPrimary = OnPrimaryDark,\n            primaryContainer = PrimaryContainerDark,\n            onPrimaryContainer = OnPrimaryContainerDark,\n            secondary = SecondaryDark,\n            onSecondary = OnSecondaryDark,\n            secondaryContainer = SecondaryContainerDark,\n            onSecondaryContainer = OnSecondaryContainerDark,\n            tertiary = TertiaryDark,\n            onTertiary = OnTertiaryDark,\n            tertiaryContainer = TertiaryContainerDark,\n            onTertiaryContainer = OnTertiaryContainerDark,\n            error = ErrorDark,\n            onError = OnErrorDark,\n            errorContainer = ErrorContainerDark,\n            onErrorContainer = OnErrorContainerDark,\n            background = BackgroundDark,\n            onBackground = OnBackgroundDark,\n            surface = SurfaceDark,\n            onSurface = OnSurfaceDark,\n            surfaceVariant = SurfaceVariantDark,\n            onSurfaceVariant = OnSurfaceVariantDark,\n            surfaceContainer = SurfaceContainerDark,\n            surfaceContainerLow = SurfaceContainerLowDark,\n            surfaceContainerHigh = SurfaceContainerHighDark,\n            surfaceContainerLowest = SurfaceContainerLowestDark,\n            surfaceContainerHighest = SurfaceContainerHighestDark,\n            outline = OutlineDark,\n            outlineVariant = OutlineVariantDark\n        )\n        else -> lightColorScheme(\n            primary = Primary,\n            onPrimary = OnPrimary,\n            primaryContainer = PrimaryContainer,\n            onPrimaryContainer = OnPrimaryContainer,\n            secondary = Secondary,\n            onSecondary = OnSecondary,\n            secondaryContainer = SecondaryContainer,\n            onSecondaryContainer = OnSecondaryContainer,\n            tertiary = Tertiary,\n            onTertiary = OnTertiary,\n            tertiaryContainer = TertiaryContainer,\n            onTertiaryContainer = OnTertiaryContainer,\n            error = Error,\n            onError = OnError,\n            errorContainer = ErrorContainer,\n            onErrorContainer = OnErrorContainer,\n            background = Background,\n            onBackground = OnBackground,\n            surface = Surface,\n            onSurface = OnSurface,\n            surfaceVariant = SurfaceVariant,\n            onSurfaceVariant = OnSurfaceVariant,\n            surfaceContainer = SurfaceContainer,\n            surfaceContainerLow = SurfaceContainerLow,\n            surfaceContainerHigh = SurfaceContainerHigh,\n            surfaceContainerLowest = SurfaceContainerLowest,\n            surfaceContainerHighest = SurfaceContainerHighest,\n            outline = Outline,\n            outlineVariant = OutlineVariant\n        )\n    }\n\n    val view = LocalView.current\n    if (!view.isInEditMode) {\n        SideEffect {\n            val activity = view.context as? ComponentActivity\n            if (activity != null) {\n                activity.enableEdgeToEdge(\n                    statusBarStyle = SystemBarStyle.auto(\n                        android.graphics.Color.TRANSPARENT,\n                        android.graphics.Color.TRANSPARENT,\n                    ) { isDark },\n                    navigationBarStyle = SystemBarStyle.auto(\n                        android.graphics.Color.TRANSPARENT,\n                        android.graphics.Color.TRANSPARENT,\n                    ) { isDark }\n                )\n            } else {\n                val window = (view.context as Activity).window\n                val insetsController = WindowCompat.getInsetsController(window, view)\n                val isBackgroundEffectivelyLight =\n                    ColorUtils.calculateLuminance(colorScheme.background.toArgb()) > 0.5\n                insetsController.isAppearanceLightStatusBars = isBackgroundEffectivelyLight\n                insetsController.isAppearanceLightNavigationBars = isBackgroundEffectivelyLight\n            }\n        }\n    }\n\n    MaterialTheme(\n        colorScheme = colorScheme,\n        content = content\n    )\n}\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/UserAgent.kt",
    "content": "package com.tutpro.baresip\n\nimport com.tutpro.baresip.BaresipService.Companion.circleYellow\nimport com.tutpro.baresip.BaresipService.Companion.colorblind\nimport com.tutpro.baresip.BaresipService.Companion.uas\nimport com.tutpro.baresip.BaresipService.Companion.uasStatus\n\nclass UserAgent(val uap: Long) {\n\n    val account = Account(Api.ua_account(uap))\n    var status = R.drawable.circle_white\n\n    fun callAlloc(xCall: Long, videoMode: Int): Long {\n        return Api.ua_call_alloc(uap, xCall, videoMode)\n    }\n\n    fun add() {\n        val updatedUas = uas.value.toMutableList()\n        updatedUas.add(this)\n        uas.value = updatedUas.toList()\n        uasStatus.value = statusMap()\n    }\n\n    fun remove() {\n        val updatedUas = uas.value.toMutableList()\n        updatedUas.remove(this)\n        uas.value = updatedUas.toList()\n        uasStatus.value = statusMap()\n    }\n\n    fun updateStatus(status: Int) {\n        uas.value.find { it.uap == this.uap }?.status = status\n        uasStatus.value = statusMap()\n    }\n\n    fun calls(dir: String = \"\"): ArrayList<Call> {\n        val result = ArrayList<Call>()\n        for (c in BaresipService.calls)\n            if ((c.ua == this) && ((dir == \"\") || c.dir == dir)) result.add(c)\n        return result\n    }\n\n    // Returns call of UA that was added last or NULL\n    fun currentCall(): Call? {\n        for (c in BaresipService.calls.reversed())\n            if (c.ua == this)\n                return c\n        return null\n    }\n\n    fun reRegister() {\n        this.status = circleYellow.getValue(colorblind)\n        if (this.account.regint == 0)\n            Api.ua_unregister(this.uap)\n        else\n            Api.ua_register(this.uap)\n    }\n\n    fun makeDefault() {\n        val index = uas.value.indexOf(this)\n        val updatedUas = uas.value.toMutableList()\n        updatedUas.removeAt(index)\n        updatedUas.add(0, this)\n        uas.value = updatedUas.toList()\n        uasStatus.value = statusMap()\n    }\n\n    companion object {\n\n        fun ofAor(aor: String): UserAgent? {\n            for (ua in uas.value)\n                if (ua.account.aor == aor) return ua\n            return null\n        }\n\n        fun ofDomain(domain: String): UserAgent? {\n            for (ua in uas.value)\n                if (Utils.aorDomain(ua.account.aor) == domain) return ua\n            return null\n        }\n\n        fun ofUap(uap: Long): UserAgent? {\n            for (ua in uas.value)\n                if (ua.uap == uap) return ua\n            return null\n        }\n\n        fun uaAlloc(uri: String): UserAgent? {\n            val uap = Api.ua_alloc(uri)\n            if (uap != 0L) return UserAgent(uap)\n            Log.e(TAG, \"Failed to allocate UserAgent for $uri\")\n            return null\n        }\n\n        fun findAorIndex(aor: String): Int? {\n            for (i in uas.value.indices) {\n                if (uas.value[i].account.aor == aor) return i\n            }\n            return null\n        }\n\n        fun register() {\n            for (ua in uas.value) {\n                if (ua.account.regint > 0) {\n                    if (Api.ua_register(ua.uap) != 0)\n                        Log.d(TAG, \"Failed to register ${ua.account.aor}\")\n                }\n            }\n        }\n\n        fun updateColorblindStatus() {\n            val updatedUas = uas.value.toMutableList()\n            for (ua in updatedUas)\n                ua.status =\n                    if (colorblind)\n                        when (ua.status) {\n                            R.drawable.circle_green -> R.drawable.circle_green_blind\n                            R.drawable.circle_yellow -> R.drawable.circle_yellow_blind\n                            R.drawable.circle_red -> R.drawable.circle_red_blind\n                            else -> R.drawable.circle_white\n                        }\n                    else\n                        when (ua.status) {\n                            R.drawable.circle_green_blind -> R.drawable.circle_green\n                            R.drawable.circle_yellow_blind -> R.drawable.circle_yellow\n                            R.drawable.circle_red_blind -> R.drawable.circle_red\n                            else -> R.drawable.circle_white\n                        }\n            uas.value = updatedUas.toList()\n            uasStatus.value = statusMap()\n        }\n\n        fun statusMap(): Map<String, Int> {\n            val result = emptyMap<String, Int>().toMutableMap()\n            for (ua in uas.value)\n                result[ua.account.aor] = ua.status\n            return result\n        }\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/Utils.kt",
    "content": "package com.tutpro.baresip\n\nimport android.annotation.SuppressLint\nimport android.app.Activity\nimport android.app.KeyguardManager\nimport android.content.ContentResolver\nimport android.content.Context\nimport android.content.Intent\nimport android.content.pm.PackageManager\nimport android.content.res.Configuration\nimport android.graphics.Bitmap\nimport android.graphics.PorterDuff\nimport android.graphics.PorterDuffXfermode\nimport android.media.AudioAttributes\nimport android.media.AudioDeviceInfo\nimport android.media.AudioManager\nimport android.media.MediaPlayer\nimport android.media.audiofx.AcousticEchoCanceler\nimport android.media.audiofx.AutomaticGainControl\nimport android.net.Uri\nimport android.net.wifi.WifiManager\nimport android.os.Build\nimport android.os.Bundle\nimport android.os.Environment\nimport android.provider.DocumentsContract\nimport android.provider.MediaStore\nimport android.provider.OpenableColumns\nimport android.text.format.DateUtils\nimport androidx.activity.result.ActivityResultLauncher\nimport androidx.annotation.RequiresApi\nimport androidx.appcompat.app.AppCompatDelegate\nimport androidx.compose.ui.text.AnnotatedString\nimport androidx.compose.ui.text.SpanStyle\nimport androidx.compose.ui.text.buildAnnotatedString\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.withStyle\nimport androidx.core.content.ContextCompat\nimport androidx.core.graphics.toColorInt\nimport androidx.core.net.toUri\nimport androidx.core.text.isDigitsOnly\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.ProcessLifecycleOwner\nimport androidx.navigation.NavController\nimport com.tutpro.baresip.Call.Companion.inCall\nimport java.io.BufferedInputStream\nimport java.io.BufferedOutputStream\nimport java.io.File\nimport java.io.FileInputStream\nimport java.io.FileNotFoundException\nimport java.io.FileOutputStream\nimport java.io.IOException\nimport java.io.InputStream\nimport java.io.ObjectInputStream\nimport java.io.ObjectOutputStream\nimport java.io.RandomAccessFile\nimport java.io.Serializable\nimport java.lang.reflect.Method\nimport java.net.InetAddress\nimport java.net.NetworkInterface\nimport java.net.SocketException\nimport java.security.KeyStore\nimport java.security.SecureRandom\nimport java.security.cert.CertificateException\nimport java.security.cert.CertificateFactory\nimport java.security.cert.X509Certificate\nimport java.text.DateFormat\nimport java.util.Calendar\nimport java.util.Enumeration\nimport java.util.GregorianCalendar\nimport java.util.Locale\nimport java.util.Random\nimport java.util.concurrent.Executor\nimport java.util.zip.ZipEntry\nimport java.util.zip.ZipFile\nimport java.util.zip.ZipOutputStream\nimport javax.crypto.Cipher\nimport javax.crypto.SecretKeyFactory\nimport javax.crypto.spec.IvParameterSpec\nimport javax.crypto.spec.PBEKeySpec\nimport javax.crypto.spec.SecretKeySpec\nimport javax.net.ssl.HttpsURLConnection\nimport javax.net.ssl.SSLContext\nimport javax.net.ssl.TrustManagerFactory\nimport javax.net.ssl.X509TrustManager\n\nobject Utils {\n\n    fun getNameValue(string: String, name: String): ArrayList<String> {\n        val lines = string.split(\"\\n\")\n        val result = ArrayList<String>()\n        for (line in lines) {\n            if (line.startsWith(name))\n                result.add((line.substring(name.length).trim()).split(\" \\t\")[0])\n        }\n        return result\n    }\n\n    fun removeLinesStartingWithString(lines: String, string: String): String {\n        var result = \"\"\n        for (line in lines.split(\"\\n\"))\n            if (!line.startsWith(string) && (line.isNotEmpty())) result += line + \"\\n\"\n        return result\n    }\n\n    fun uriHostPart(uri: String): String {\n        return if (uri.contains(\"@\")) {\n            uri.substringAfter(\"@\")\n                    .substringBefore(\":\")\n                    .substringBefore(\";\")\n                    .substringBefore(\"?\")\n                    .substringBefore(\">\")\n        } else {\n            val parts = uri.split(\":\")\n            when (parts.size) {\n                2 -> parts[1].substringBefore(\";\")\n                        .substringBefore(\"?\")\n                        .substringBefore(\">\")\n                3 -> parts[1]\n                else -> \"\"\n            }\n        }\n    }\n\n    fun uriUserPart(uri: String): String {\n        return if (uri.contains(\"@\"))\n            uri.substringAfter(\":\").substringBefore(\"@\")\n        else\n            \"\"\n    }\n\n    fun uriMatch(firstUri: String, secondUri: String): Boolean {\n        if (firstUri.startsWith(\"tel:\"))\n            return firstUri == secondUri || firstUri.substringAfter(\":\") == uriUserPart(secondUri)\n        if (firstUri.startsWith(\"sip:\"))\n            return uriUserPart(firstUri) == uriUserPart(secondUri) &&\n                    uriHostPart(firstUri) == uriHostPart(secondUri)\n        return false\n    }\n\n    private fun uriParams(uri: String): List<String> {\n        val params = uri.split(\";\")\n        return if (params.size == 1) listOf() else params.subList(1, params.size)\n    }\n\n    fun friendlyUri(ctx: Context, uri: String, account: Account, e164Check: Boolean = true): String {\n        var u = Contact.contactName(uri)\n        if (u != uri)\n            return u\n        if (e164Check) {\n            val e164Uri = e164Uri(uri, account.countryCode)\n            u = Contact.contactName(e164Uri)\n            if (u != e164Uri)\n                return u\n        }\n        u = u.replace(\"%23\", \"#\")\n        if (u.contains(\"@\")) {\n            val user = uriUserPart(u)\n            val host = uriHostPart(u)\n            val params = uriParams(u).filter{it != \"transport=udp\"}\n            return if (host == aorDomain(account.aor) || params.contains(\"user=phone\"))\n                user\n            else if (host == \"anonymous.invalid\")\n                ctx.getString(R.string.anonymous)\n            else if (host == \"unknown.invalid\")\n                ctx.getString(R.string.unknown)\n            else\n                if (params.isEmpty())\n                    \"$user@$host\"\n                else\n                    \"$user@$host;\" + params.joinToString(\";\")\n        }\n        if (uri.startsWith(\"<\") && (uri.endsWith(\">\")))\n            u = uri.substring(1).substringBeforeLast(\">\")\n        u = u.substringBefore(\"?\")\n        u = u.replace(\":5060\", \"\")\n        u = u.replace(\";transport=udp\", \"\", true)\n        return u\n    }\n\n    private fun e164Uri(uri: String, countryCode: String): String {\n        if (countryCode == \"\") return uri\n        val scheme = uri.take(4)\n        val userPart = uriUserPart(uri)\n        return if (userPart.isDigitsOnly()) {\n            when {\n                userPart.startsWith(\"00\") -> uri.replace(\"$scheme$userPart\",\n                        scheme + userPart.substring(2))\n                userPart.startsWith(\"0\") -> uri.replace(\"${scheme}0\",\n                    \"$scheme$countryCode\")\n                else -> uri.replace(scheme, \"$scheme$countryCode\")\n            }\n        } else\n            uri\n    }\n\n    fun uriComplete(uri: String, aor: String): String {\n        val res = if (!uri.startsWith(\"sip:\")) \"sip:$uri\" else uri\n        return if (checkUriUser(uri)) \"$res@${aorDomain(aor)}\" else res\n    }\n\n    private fun String.replace(vararg pairs: Pair<String, String>): String =\n            pairs.fold(this) { acc, (old, new) -> acc.replace(old, new, ignoreCase = true) }\n\n    fun uriUnescape(uri: String): String {\n        return uri.replace(\"%2B\" to \"+\", \"%3A\" to \":\", \"%3B\" to \";\", \"%40\" to \"@\", \"%3D\" to \"=\")\n    }\n\n    fun aorDomain(aor: String): String {\n        return uriHostPart(aor)\n    }\n\n    fun plainAor(aor: String): String {\n        return uriUserPart(aor) + \"@\" + uriHostPart(aor)\n    }\n\n    fun checkAor(aor: String): Boolean {\n        if (!checkSipUri(aor)) return false\n        val params = uriParams(aor)\n        return params.isEmpty() ||\n                ((params.size == 1) &&\n                        params[0] in arrayOf(\"transport=udp\", \"transport=tcp\", \"transport=tls\"))\n    }\n\n    private fun checkTransport(transport: String, transports: Set<String>): Boolean {\n        return transport.split(\"=\")[0] == \"transport\" &&\n                transport.split(\"=\")[1].lowercase() in transports\n    }\n\n    fun checkStunUri(uri: String): Boolean {\n        if (uri.substringBefore(\":\").lowercase() !in setOf(\"stun\", \"stuns\", \"turn\", \"turns\"))\n            return false\n        return checkHostPort(uri.substringAfter(\":\").substringBefore(\"?\")) &&\n                (uri.indexOf(\"?\") == -1 ||\n                checkTransport(uri.substringAfter(\"?\"), setOf(\"udp\", \"tcp\")))\n    }\n\n    fun checkIpV4(ip: String): Boolean {\n        return Regex(\"^(([0-1]?[0-9]{1,2}\\\\.)|(2[0-4][0-9]\\\\.)|(25[0-5]\\\\.)){3}(([0-1]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))$\").matches(ip)\n    }\n\n    private fun checkIpV6(ip: String): Boolean {\n        return Regex(\"^(([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4})$\").matches(ip)\n    }\n\n    private fun checkIpv6InBrackets(bracketedIp: String): Boolean {\n        return bracketedIp.startsWith(\"[\") && bracketedIp.endsWith(\"]\") &&\n                checkIpV6(bracketedIp.substring(1, bracketedIp.length - 2))\n    }\n\n    fun checkUriUser(user: String): Boolean {\n        val escaped = \"\"\"%(\\d|A|B|C|D|E|F|a|b|c|d|e|f){2}\"\"\".toRegex()\n        escaped.replace(user, \"\").forEach {\n            if (!(it.isLetterOrDigit() || \"-_.!~*\\'()&=+$,;?/\".contains(it))) return false }\n        return user.isNotEmpty() && !checkIpV4(user) && !checkIpV6(user)\n    }\n\n    fun checkDomain(domain: String): Boolean {\n        val parts = domain.split(\".\")\n        for (p in parts) {\n            if (p.endsWith(\"-\") || p.startsWith(\"-\") ||\n                    !Regex(\"^[-a-zA-Z0-9]+$\").matches(p))\n                return false\n        }\n        return true\n    }\n\n    private fun checkPort(port: String): Boolean {\n        val number = port.toIntOrNull() ?: return false\n        return (number > 0) && (number < 65536)\n    }\n\n    fun checkIpPort(ipPort: String): Boolean {\n        return if (ipPort.startsWith(\"[\"))\n            checkIpv6InBrackets(ipPort.substringBeforeLast(\":\")) &&\n                    checkPort(ipPort.substringAfterLast(\":\"))\n        else\n            checkIpV4(ipPort.substringBeforeLast(\":\")) &&\n                    checkPort(ipPort.substringAfterLast(\":\"))\n    }\n\n    private fun checkDomainPort(domainPort: String): Boolean {\n        return checkDomain(domainPort.substringBeforeLast(\":\")) &&\n                checkPort(domainPort.substringAfterLast(\":\"))\n    }\n\n    private fun checkHostPort(hostPort: String): Boolean {\n        return checkIpV4(hostPort) || checkIpv6InBrackets(hostPort) || checkDomain(hostPort) ||\n                checkIpPort(hostPort) || checkDomainPort(hostPort)\n    }\n\n    private fun checkParams(params: String): Boolean {\n        for (param in params.split(\";\"))\n            if (!checkParam(param)) return false\n        return true\n    }\n\n    private fun checkParam(param: String): Boolean {\n        val nameValue = param.split(\"=\")\n        if (nameValue.size == 1)\n            return checkParamChars(nameValue[0])\n        if (nameValue.size == 2) {\n            if (nameValue[0] == \"transport\")\n                return setOf(\"udp\", \"tcp\", \"tls\", \"wss\").contains(nameValue[1].lowercase())\n            return checkParamChars(nameValue[1])\n        }\n        return false\n    }\n\n    private fun checkParamChars(s: String): Boolean {\n        // Does not currently allow escaped characters\n        val allowed = \"[]/:&+$-_.!~*'()\"\n        for (c in s)\n            if (!allowed.contains(c) && !c.isLetterOrDigit())\n                return false\n        return true\n    }\n\n    fun paramValue(params: String, name: String): String {\n        if (params == \"\") return \"\"\n        for (param in params.split(\";\"))\n            if (param.substringBefore(\"=\") == name) return param.substringAfter(\"=\")\n        return \"\"\n    }\n\n    fun paramExists(params: String, name: String): Boolean {\n        for (param in params.split(\";\"))\n            if (param.substringBefore(\"=\") == name) return true\n        return false\n    }\n\n    fun checkHostPortParams(hpp: String) : Boolean {\n        val restParams = hpp.split(\";\", limit = 2)\n        return if (restParams.size == 1)\n            checkHostPort(restParams[0])\n        else\n            checkHostPort(restParams[0]) && checkParams(restParams[1])\n    }\n\n    private fun checkSipUri(uri: String): Boolean {\n        return if (uri.startsWith(\"sip:\")) {\n            val userRest = uri.substring(4).split(\"@\")\n            when (userRest.size) {\n                1 ->\n                    checkHostPortParams(userRest[0])\n                2 ->\n                    checkUriUser(userRest[0]) && checkHostPortParams(userRest[1])\n                else -> false\n            }\n        } else {\n            false\n        }\n    }\n\n    fun isTelNumber(no: String): Boolean {\n        return no.isNotEmpty() && Regex(\"^([+][1-9])?[0-9- (),*#]{0,24}$\").matches(no)\n    }\n\n    fun isTelUri(uri: String): Boolean {\n        return uri.startsWith(\"tel:\") && isTelNumber(uri.substring(4))\n    }\n\n    fun checkUri(uri: String): Boolean {\n        return checkSipUri(uri) || isTelUri(uri)\n    }\n\n    fun telToSip(telUri: String, account: Account): String {\n        val hostPart = if (account.telProvider != \"\")\n            account.telProvider\n        else\n            aorDomain(account.aor)\n        return \"sip:\" + telUri.substring(4)\n            .filterNot{setOf('-', ' ', '(', ')').contains(it)}\n            .replace(\"#\", \"%23\") +\n                \"@\" + hostPart + \";user=phone\"\n    }\n\n    fun checkName(name: String): Boolean {\n        return name.isNotEmpty() && name == String(name.toByteArray(), Charsets.UTF_8) &&\n                name.lines().size == 1 && !name.contains('\"')\n    }\n\n    fun checkCountryCode(cc: String): Boolean {\n        return cc.startsWith(\"+\") && cc.length > 1 && cc.length < 5 &&\n                cc.substring(1).isDigitsOnly() && cc[1] != '0'\n    }\n\n    fun checkServerVal(server: String): Boolean {\n        val parts = server.replace(Regex(\"[(][^()\\\\\\\\]+[)]\"), \"\")\n            .trim().split(\"\\\\s+\".toRegex())\n        for (part in parts)\n            if (!checkProduct(part))\n                return false\n        return true\n    }\n\n    private fun checkProduct(product: String): Boolean {\n        val parts = product.split(\"/\", limit = 2)\n        return if (parts.count() == 2)\n            checkToken(parts[0]) && checkToken(parts[1])\n        else\n            checkToken(parts[0])\n    }\n\n    private fun checkToken(token: String): Boolean {\n        return Regex(\"^[-a-zA-Z0-9.!%*_+`'~]+$\").matches(token)\n    }\n\n    @Suppress(\"unused\")\n    fun checkIfName(name: String): Boolean {\n        if ((name.length < 2) || !name.first().isLetter()) return false\n        for (c in name)\n            if (!c.isLetterOrDigit()) return false\n        return true\n    }\n\n    fun implode(list: List<String>, sep: String): String {\n        var res = \"\"\n        for (s in list) {\n            res = if (res == \"\")\n                s\n            else\n                res + sep + s\n        }\n        return res\n    }\n\n    fun unaccent(input: String): String {\n        val normalized = java.text.Normalizer.normalize(input, java.text.Normalizer.Form.NFD)\n        return \"\\\\p{InCombiningDiacriticalMarks}+\".toRegex().replace(normalized, \"\")\n    }\n\n    fun buildAnnotatedStringWithHighlight(name: String, query: String): AnnotatedString {\n        val normalizedName = unaccent(name)\n        val normalizedQuery = unaccent(query)\n        val startIndex = normalizedName.indexOf(normalizedQuery, ignoreCase = true)\n        return if (startIndex == -1) {\n            buildAnnotatedString { append(name) }\n        } else {\n            buildAnnotatedString {\n                append(name.take(startIndex))\n                withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {\n                    append(name.drop(startIndex).take(normalizedQuery.length))\n                }\n                append(name.drop(startIndex + normalizedQuery.length))\n            }\n        }\n    }\n\n    fun isVisible(): Boolean {\n        return ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)\n    }\n\n    fun isHotSpotOn(wm: WifiManager): Boolean {\n        try {\n            val method: Method = wm.javaClass.getDeclaredMethod(\"isWifiApEnabled\")\n            method.isAccessible = true\n            return method.invoke(wm) as Boolean\n        } catch (_: Throwable) {\n        }\n        return false\n    }\n\n    fun hotSpotAddresses(): Map<String, String> {\n        val result = mutableMapOf<String, String>()\n        try {\n            val interfaces: Enumeration<NetworkInterface> = NetworkInterface.getNetworkInterfaces()\n            while (interfaces.hasMoreElements()) {\n                val iface: NetworkInterface = interfaces.nextElement()\n                val ifName = iface.name\n                Log.d(TAG, \"Found interface with name $ifName\")\n                if (ifName.startsWith(\"ap\") || ifName.contains(\"wlan\")) {\n                    val addresses: Enumeration<InetAddress> = iface.inetAddresses\n                    while (addresses.hasMoreElements()) {\n                        val inetAddress: InetAddress = addresses.nextElement()\n                        if (inetAddress.isSiteLocalAddress)\n                            result[inetAddress.hostAddress!!] = ifName\n                    }\n                    if (result.isNotEmpty()) return result\n                }\n            }\n        } catch (ex: SocketException) {\n            Log.e(TAG, \"hotSpotAddresses SocketException: $ex\")\n        } catch (ex: NullPointerException) {\n            Log.e(TAG, \"hotSpotAddresses NullPointerException: $ex\")\n        }\n        return result\n    }\n\n    fun checkPermissions(ctx: Context, permissions: Array<String>) : Boolean {\n        for (p in permissions) {\n            if (ContextCompat.checkSelfPermission(ctx, p) != PackageManager.PERMISSION_GRANTED) {\n                Log.d(TAG, \"Permission $p is denied\")\n                return false\n            } else {\n                Log.d(TAG, \"Permission $p is granted\")\n            }\n        }\n        return true\n    }\n\n    fun copyAssetToFile(context: Context, asset: String, path: String) {\n        try {\n            val `is` = context.assets.open(asset)\n            val os = FileOutputStream(path)\n            val buffer = ByteArray(512)\n            var byteRead: Int = `is`.read(buffer)\n            while (byteRead  != -1) {\n                os.write(buffer, 0, byteRead)\n                byteRead = `is`.read(buffer)\n            }\n            os.close()\n            `is`.close()\n        } catch (e: IOException) {\n            Log.e(TAG, \"Failed to copy asset '$asset' to file: $e\")\n        }\n    }\n\n    fun deleteFile(file: File) {\n        if (file.exists()) {\n            try {\n                file.delete()\n            } catch (e: IOException) {\n                Log.e(TAG, \"Could not delete file ${file.absolutePath}: $e\")\n            }\n        }\n    }\n\n    fun deleteFile(ctx: Context, uri: Uri): Boolean {\n        val contentResolver: ContentResolver = ctx.contentResolver\n        try {\n            if (DocumentsContract.isDocumentUri(ctx, uri)) {\n                if (DocumentsContract.deleteDocument(contentResolver, uri)) {\n                    Log.d(TAG, \"File deleted successfully: $uri\")\n                    return true\n                } else {\n                    Log.d(TAG, \"File not found or could not be deleted: $uri\")\n                    return false\n                }\n            } else {\n                Log.d(TAG, \"Uri is not a document uri: $uri\")\n                return false\n            }\n        } catch (e: UnsupportedOperationException) {\n            Log.w(TAG, \"Error deleting file $uri: $e\")\n            return false\n        } catch (e: Exception) {\n            Log.e(TAG, \"Error deleting file $uri: $e\")\n            return false\n        }\n    }\n\n    fun getFileContents(filePath: String): ByteArray? {\n        return try {\n            File(filePath).readBytes()\n        } catch (e: FileNotFoundException) {\n            Log.e(TAG, \"File '$filePath' not found: ${e.printStackTrace()}\")\n            null\n        } catch (e: Exception) {\n            Log.e(TAG, \"Failed to read file '$filePath': ${e.printStackTrace()}\")\n            null\n        }\n    }\n\n    fun putFileContents(filePath: String, contents: ByteArray): Boolean {\n        try {\n            File(filePath).writeBytes(contents)\n        }\n        catch (e: IOException) {\n            Log.e(TAG, \"Failed to write file '$filePath': $e\")\n            return false\n        }\n        return true\n    }\n\n    fun File.copyInputStreamToFile(inputStream: InputStream): Boolean {\n        try {\n            this.outputStream().use { fileOut ->\n                inputStream.copyTo(fileOut)\n            }\n            return true\n        }\n        catch (e: IOException) {\n            Log.e(TAG, \"Failed to write file '${this.absolutePath}': $e\")\n        }\n        return false\n    }\n\n    @RequiresApi(29)\n    fun selectInputFile(request: ActivityResultLauncher<Intent>) {\n        val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {\n            addCategory(Intent.CATEGORY_OPENABLE)\n            type = \"*/*\"\n            putExtra(DocumentsContract.EXTRA_INITIAL_URI, MediaStore.Downloads.EXTERNAL_CONTENT_URI)\n        }\n        request.launch(intent)\n    }\n\n    @RequiresApi(29)\n    @Suppress(\"unused\")\n    fun selectOutputFile(title: String) {\n        Intent(Intent.ACTION_CREATE_DOCUMENT).apply {\n            addCategory(Intent.CATEGORY_OPENABLE)\n            type = \"application/octet-stream\"\n            putExtra(Intent.EXTRA_TITLE, title)\n            putExtra(DocumentsContract.EXTRA_INITIAL_URI, MediaStore.Downloads.EXTERNAL_CONTENT_URI)\n        }\n    }\n\n    fun downloadsPath(fileName: String): String {\n        return Environment.getExternalStoragePublicDirectory(\n            Environment.DIRECTORY_DOWNLOADS).path + \"/$fileName\"\n    }\n\n    fun fileNameOfUri(ctx: Context, uri: Uri): String {\n        val cursor = ctx.contentResolver.query(uri, null, null, null, null)\n        var name = \"\"\n        if (cursor != null) {\n            val index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)\n            cursor.moveToFirst()\n            name = cursor.getString(index)\n            cursor.close()\n        }\n        return if (name == \"\")\n            \"$uri\".substringAfterLast(\"/\")\n        else\n            name\n    }\n\n    class Crypto(val salt: ByteArray, val iter: Int, val iv: ByteArray, val data: ByteArray): Serializable {\n        companion object {\n            @Suppress(\"unused\")\n            private const val serialVersionUID: Long = -29238082928391L\n        }\n    }\n\n    private fun encrypt(content: ByteArray, password: CharArray): ByteArray? {\n        fun intToByteArray(int: Int): ByteArray {\n            val bytes = ByteArray(2)\n            bytes[0] = (int shr 0).toByte()\n            bytes[1] = (int shr 8).toByte()\n            return bytes\n        }\n        try {\n            val sr = SecureRandom()\n            val salt = ByteArray(128)\n            sr.nextBytes(salt)\n            val iterationCount = Random().nextInt(1024) + 512\n            val pbKeySpec = PBEKeySpec(password, salt, iterationCount, 128)\n            val secretKeyFactory = SecretKeyFactory.getInstance(\"PBKDF2WithHmacSHA1\")\n            val keyBytes = secretKeyFactory.generateSecret(pbKeySpec).encoded\n            val keySpec = SecretKeySpec(keyBytes, \"AES\")\n            val ivRandom = SecureRandom()\n            val iv = ByteArray(16)\n            ivRandom.nextBytes(iv)\n            val ivSpec = IvParameterSpec(iv)\n            val cipher = Cipher.getInstance(\"AES/CBC/PKCS7Padding\")\n            cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec)\n            val cipherData = cipher.doFinal(content)\n            val res = ByteArray(128 + 2 + 16 + cipherData.size)\n            salt.copyInto(res, 0)\n            intToByteArray(iterationCount).copyInto(res, salt.size)\n            iv.copyInto(res, salt.size + 2)\n            cipherData.copyInto(res, salt.size + 2 + iv.size)\n            return res\n        } catch (e: Exception) {\n            Log.e(TAG, \"Encrypt failed: ${e.printStackTrace()}\")\n        }\n        return null\n    }\n\n    private fun decrypt(content: ByteArray, password: CharArray): ByteArray? {\n        fun byteArrayToInt(bytes: ByteArray) : Int {\n            return (bytes[1].toInt() and 0xff shl 8) or (bytes[0].toInt() and 0xff)\n        }\n        try {\n            val salt = content.copyOfRange(0, 128)\n            val iterationCount = byteArrayToInt(content.copyOfRange(128, 130))\n            val iv = content.copyOfRange(130, 146)\n            val data = content.copyOfRange(146, content.size)\n            val pbKeySpec = PBEKeySpec(password, salt, iterationCount, 128)\n            val secretKeyFactory = SecretKeyFactory.getInstance(\"PBKDF2WithHmacSHA1\")\n            val keyBytes = secretKeyFactory.generateSecret(pbKeySpec).encoded\n            val keySpec = SecretKeySpec(keyBytes, \"AES\")\n            val cipher = Cipher.getInstance(\"AES/CBC/PKCS7Padding\")\n            val ivSpec = IvParameterSpec(iv)\n            cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec)\n            return cipher.doFinal(data)\n        } catch (e: Exception) {\n            Log.e(TAG, \"Decrypt failed: ${e.printStackTrace()}\")\n        }\n        return null\n    }\n\n    private fun decryptOld(obj: Crypto, password: CharArray): ByteArray? {\n        var plainData: ByteArray? = null\n        try {\n            val pbKeySpec = PBEKeySpec(password, obj.salt, obj.iter, 128)\n            val secretKeyFactory = SecretKeyFactory.getInstance(\"PBKDF2WithHmacSHA1\")\n            val keyBytes = secretKeyFactory.generateSecret(pbKeySpec).encoded\n            val keySpec = SecretKeySpec(keyBytes, \"AES\")\n            val cipher = Cipher.getInstance(\"AES/CBC/PKCS7Padding\")\n            val ivSpec = IvParameterSpec(obj.iv)\n            cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec)\n            plainData = cipher.doFinal(obj.data)\n        } catch (e: Exception) {\n            Log.e(TAG, \"Decrypt failed: ${e.printStackTrace()}\")\n        }\n        return plainData\n    }\n\n    fun encryptToUri(ctx: Context, uri: Uri, content: ByteArray, password: String): Boolean {\n        val obj = encrypt(content, password.toCharArray())\n        val stream = ctx.contentResolver.openOutputStream(uri) as FileOutputStream\n        try {\n            ObjectOutputStream(stream).use {\n                it.writeObject(obj)\n            }\n        } catch (e: Exception) {\n            Log.w(TAG, \"encryptToUri failed: $e\")\n            return false\n        }\n        return true\n    }\n\n    fun decryptFromUri(ctx: Context, uri: Uri, password: String): ByteArray? {\n        var plainData: ByteArray? = null\n        var stream: FileInputStream\n        try {\n            stream = ctx.contentResolver.openInputStream(uri) as FileInputStream\n        } catch(e: Exception) {\n            Log.w(TAG, \"decryptFromUri could not open stream: $e\")\n            return null\n        }\n        try {\n            ObjectInputStream(stream).use {\n                val content = it.readObject() as ByteArray\n                plainData = decrypt(content, password.toCharArray())\n            }\n            stream.close()\n        } catch (e: Exception) {\n            Log.w(TAG, \"decryptFromUri as ByteArray failed: $e\")\n            stream.close()\n            try {\n                stream = ctx.contentResolver.openInputStream(uri) as FileInputStream\n                ObjectInputStream(stream).use {\n                    val obj = it.readObject() as Crypto\n                    plainData = decryptOld(obj, password.toCharArray())\n                }\n                stream.close()\n            } catch (e: Exception) {\n                Log.w(TAG, \"decryptFromUri as Crypto failed: $e\")\n            }\n        }\n        return plainData\n    }\n\n    fun zip(fileNames: ArrayList<String>, zipFileName: String): Boolean {\n        val zipFilePath = BaresipService.filesPath + \"/\" + zipFileName\n        try {\n            ZipOutputStream(BufferedOutputStream(FileOutputStream(zipFilePath))).use { out ->\n                val data = ByteArray(1024)\n                for (fileName in fileNames) {\n                    val filePath = BaresipService.filesPath + \"/\" + fileName\n                    if (File(filePath).exists()) {\n                        FileInputStream(filePath).use { fi ->\n                            BufferedInputStream(fi).use { origin ->\n                                val entry = ZipEntry(fileName)\n                                out.putNextEntry(entry)\n                                while (true) {\n                                    val readBytes = origin.read(data)\n                                    if (readBytes == -1) break\n                                    out.write(data, 0, readBytes)\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        } catch (e: IOException) {\n            Log.e(TAG, \"Failed to zip file '$zipFilePath': $e\")\n            return false\n        }\n        return true\n    }\n\n    fun unZip(zipFilePath: String): Boolean {\n        val allFiles = listOf(\"accounts\", \"call_history\", \"config\", \"contacts\", \"messages\", \"uuid\",\n                \"gzrtp.zid\", \"cert.pem\", \"ca_cert\", \"ca_certs.crt\")\n        val zipFiles = mutableListOf<String>()\n        try {\n            ZipFile(File(zipFilePath)).use { zip ->\n                zip.entries().asSequence().forEach { entry ->\n                    val entryName = if (entry.name.startsWith(\"/\"))\n                        entry.name.substringAfterLast(\"/\")\n                    else\n                        entry.name\n                    zipFiles.add(entryName)\n                    zip.getInputStream(entry).use { input ->\n                        File(BaresipService.filesPath + \"/\" + entryName).outputStream().use { output ->\n                            input.copyTo(output)\n                        }\n                    }\n                }\n            }\n        } catch (e: IOException) {\n            Log.e(TAG, \"Failed to unzip file '$zipFilePath': $e\")\n            return false\n        }\n        (allFiles - zipFiles.toSet()).iterator().forEach {\n            deleteFile(File(BaresipService.filesPath, it))\n        }\n        return true\n    }\n\n    @Suppress(\"unused\")\n    fun dumpIntent(intent: Intent) {\n        val bundle: Bundle = intent.extras ?: return\n        val keys = bundle.keySet()\n        val it = keys.iterator()\n        Log.d(TAG, \"Dumping intent start\")\n        while (it.hasNext()) {\n            val key = it.next()\n            Log.d(TAG, \"[\" + key + \"=\" + bundle.getBundle(key) + \"]\")\n        }\n        Log.d(TAG, \"Dumping intent finish\")\n    }\n\n    fun randomColor(): Int {\n        val rnd = Random()\n        return android.graphics.Color.argb(255, rnd.nextInt(256), rnd.nextInt(256),\n                rnd.nextInt(256))\n    }\n\n    fun requestDismissKeyguard(activity: Activity) {\n        val kgm = activity.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager\n        kgm.requestDismissKeyguard(activity, null)\n\n    }\n\n    fun isThemeDark(ctx: Context) : Boolean {\n        return Preferences(ctx).displayTheme == AppCompatDelegate.MODE_NIGHT_YES ||\n                ctx.resources?.configuration?.uiMode?.and(Configuration.UI_MODE_NIGHT_MASK) ==\n                        Configuration.UI_MODE_NIGHT_YES\n    }\n\n    fun isPSTNCallActive(ctx: Context): Boolean {\n        // MODE_IN_CALL indicates a PSTN call is active\n        val am = ctx.getSystemService(Context.AUDIO_SERVICE) as AudioManager\n        return am.mode == AudioManager.MODE_IN_CALL\n    }\n\n    fun relativeTime(ctx: Context, time: GregorianCalendar): String {\n        return if (DateUtils.isToday(time.timeInMillis)) {\n            val fmt = DateFormat.getTimeInstance(DateFormat.SHORT)\n            ctx.getString(R.string.today) + \"\\n\" + fmt.format(time.time)\n        } else {\n            val month = time.getDisplayName(Calendar.MONTH, Calendar.SHORT, Locale.getDefault())!!\n                .replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }\n            val day = time.get(Calendar.DAY_OF_MONTH)\n            val currentYear = Calendar.getInstance().get(Calendar.YEAR)\n            if (time.get(Calendar.YEAR) == currentYear) {\n                val fmt = DateFormat.getTimeInstance(DateFormat.SHORT)\n                \"$month $day\" + \"\\n\" + fmt.format(time.time)\n            } else {\n                \"$month $day\" + \"\\n\" + time.get(Calendar.YEAR)\n            }\n        }\n    }\n\n    private fun setSpeakerPhone(executor: Executor, am: AudioManager, enable: Boolean) {\n        if (Build.VERSION.SDK_INT >= 31) {\n            if (!enable) {\n                Log.d(TAG, \"Disabling speakerphone\")\n                clearCommunicationDevice(am)\n                if (inCall() && am.mode == AudioManager.MODE_NORMAL) {\n                    Log.d(TAG, \"Restoring MODE_IN_COMMUNICATION\")\n                    am.mode = AudioManager.MODE_IN_COMMUNICATION\n                }\n                return\n            }\n            val current = am.communicationDevice!!.type\n            Log.d(TAG, \"Current com dev/mode is $current/${am.mode}\")\n            var speakerDevice: AudioDeviceInfo? = null\n            for (device in am.availableCommunicationDevices)\n                if (device.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {\n                    speakerDevice = device\n                    break\n                }\n            if (speakerDevice == null) {\n                Log.w(TAG,\"Could not find requested communication device\")\n                return\n            }\n            if (current != speakerDevice.type) {\n                // Currently at API levels 31+, speakerphone needs normal mode\n                if (am.mode == AudioManager.MODE_NORMAL) {\n                    Log.d(TAG, \"Setting com device to ${speakerDevice.type} in MODE_NORMAL\")\n                    if (!am.setCommunicationDevice(speakerDevice))\n                        Log.e(TAG, \"Could not set com device\")\n                } else {\n                    val normalListener = object : AudioManager.OnModeChangedListener {\n                        override fun onModeChanged(mode: Int) {\n                            if (mode == AudioManager.MODE_NORMAL) {\n                                am.removeOnModeChangedListener(this)\n                                Log.d(\n                                    TAG, \"Setting com device to ${speakerDevice.type}\" +\n                                            \" in mode ${am.mode}\"\n                                )\n                                if (!am.setCommunicationDevice(speakerDevice))\n                                    Log.e(TAG, \"Could not set com device\")\n                            }\n                        }\n                    }\n                    am.addOnModeChangedListener(executor, normalListener)\n                    Log.d(TAG, \"Setting mode to NORMAL\")\n                    am.mode = AudioManager.MODE_NORMAL\n                }\n                Log.d(TAG, \"New com device/mode is ${am.communicationDevice!!.type}/${am.mode}\")\n            }\n        } else {\n            @Suppress(\"DEPRECATION\")\n            am.isSpeakerphoneOn = enable\n            Log.d(TAG, \"Speakerphone is $enable\")\n        }\n    }\n\n    fun toggleSpeakerPhone(executor: Executor, am: AudioManager) {\n        if (Build.VERSION.SDK_INT >= 31) {\n            if (am.communicationDevice!!.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER)\n                setSpeakerPhone(executor, am, false)\n            else\n                setSpeakerPhone(executor, am, true)\n        } else {\n            @Suppress(\"DEPRECATION\")\n            setSpeakerPhone(executor, am, !am.isSpeakerphoneOn)\n        }\n    }\n\n    fun clearCommunicationDevice(am: AudioManager) {\n        if (Build.VERSION.SDK_INT >= 31) {\n            am.clearCommunicationDevice()\n        } else {\n            @Suppress(\"DEPRECATION\")\n            if (am.isSpeakerphoneOn)\n                am.isSpeakerphoneOn = false\n        }\n    }\n\n    @Suppress(\"unused\")\n    fun playFile(ctx: Context, path: String) {\n        Log.d(TAG, \"Playing file $path\")\n        MediaPlayer().apply {\n            setAudioAttributes(\n                AudioAttributes.Builder()\n                    .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)\n                    .setUsage(AudioAttributes.USAGE_MEDIA)\n                    .build()\n            )\n            setOnPreparedListener {\n                Log.d(TAG, \"Starting MediaPlayer\")\n                it.start()\n                Log.d(TAG, \"MediaPlayer started\")\n            }\n            setOnCompletionListener {\n                Log.d(TAG, \"Stopping MediaPlayer\")\n                it.stop()\n                it.release()\n            }\n            try {\n                Log.d(TAG, \"Preparing $path\")\n                setDataSource(ctx, path.toUri())\n                prepareAsync()\n            } catch (e: IllegalArgumentException) {\n                Log.e(TAG, \"MediaPlayer IllegalArgumentException: ${e.printStackTrace()}\")\n            } catch (e: IOException) {\n                Log.e(TAG, \"MediaPlayer IOException: ${e.printStackTrace()}\")\n            } catch (e: Exception) {\n                Log.e(TAG, \"MediaPlayer Exception: ${e.printStackTrace()}\")\n            }\n        }\n    }\n\n    fun aecAgcCheck() {\n        val sessionId = Api.AAudio_open_stream()\n        if (sessionId == -1) {\n            Log.e(TAG, \"Failed to open AAudio stream\")\n            return\n        }\n\n        if (AcousticEchoCanceler.isAvailable()) {\n            val aec = AcousticEchoCanceler.create(sessionId)\n            if (aec != null) {\n                BaresipService.aecAvailable = true\n                aec.release()\n                Log.i(TAG, \"Creation of hardware AEC for $sessionId succeeded\")\n            } else {\n                Log.w(TAG, \"Creation of hardware AEC for $sessionId failed\")\n            }\n        }\n        else\n            Log.i(TAG, \"Hardware AEC is NOT available\")\n\n        if (AutomaticGainControl.isAvailable()) {\n            val agc = AutomaticGainControl.create(sessionId)\n            if (agc != null) {\n                BaresipService.agcAvailable = true\n                agc.release()\n                Log.d(TAG, \"Creation of hardware AGC for $sessionId succeeded\")\n            } else {\n                Log.w(TAG, \"Creation of hardware AGC for $sessionId failed\")\n            }\n        }\n        else\n            Log.i(TAG, \"Hardware AGC is NOT available\")\n\n        Api.AAudio_close_stream()\n    }\n\n    fun readUrlWithCustomCAs(urlConnection: HttpsURLConnection, caFile: File): String? {\n        if (!caFile.exists()) {\n            Log.d(\"Utils\", \"Custom CA file not found at ${caFile.path}\")\n            return null\n        }\n        try {\n            // Create a TrustManager that trusts the CAs in the user-provided file\n            val customTrustManager = fun(): X509TrustManager {\n                val certificateFactory = CertificateFactory.getInstance(\"X.509\")\n                val certificateInputStream = caFile.inputStream()\n                val certificates = certificateFactory.generateCertificates(certificateInputStream)\n                certificateInputStream.close()\n\n                val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())\n                keyStore.load(null, null)\n                certificates.forEachIndexed { index, certificate ->\n                    keyStore.setCertificateEntry(\"user_ca_$index\", certificate)\n                }\n\n                val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())\n                tmf.init(keyStore)\n                return tmf.trustManagers.find { it is X509TrustManager } as X509TrustManager\n            }()\n\n            // Create a TrustManager that trusts the default system CAs\n            val systemTrustManager = fun(): X509TrustManager {\n                val factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())\n                factory.init(null as KeyStore?) // A null keystore loads the system's default CAs\n                return factory.trustManagers.find { it is X509TrustManager } as X509TrustManager\n            }()\n\n            // Create a composite TrustManager that delegates to both system and custom CAs\n            @SuppressLint(\"CustomX509TrustManager\")\n            val compositeTrustManager = object : X509TrustManager {\n                override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {\n                    // Delegate to the system manager by default.\n                    systemTrustManager.checkClientTrusted(chain, authType)\n                }\n\n                override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {\n                    try {\n                        // Try to validate the chain with the system's default TrustManager.\n                        systemTrustManager.checkServerTrusted(chain, authType)\n                    } catch (_: CertificateException) {\n                        // If that fails, and only if that fails, try to validate with our custom TrustManager.\n                        // This will throw the final CertificateException if it also fails.\n                        customTrustManager.checkServerTrusted(chain, authType)\n                    }\n                }\n\n                override fun getAcceptedIssuers(): Array<X509Certificate> {\n                    // Return a combined list of issuers from both trust managers.\n                    return systemTrustManager.acceptedIssuers + customTrustManager.acceptedIssuers\n                }\n            }\n\n            // Create an SSLContext that uses our new composite TrsustManager\n            val sslContext = SSLContext.getInstance(\"TLS\")\n            sslContext.init(null, arrayOf(compositeTrustManager), null)\n\n            // Tell HttpsURLConnection to use our custom SSLContext for this connection\n            urlConnection.sslSocketFactory = sslContext.socketFactory\n\n            // Proceed with the connection and return the result\n            return urlConnection.inputStream.bufferedReader().use { it.readText() }\n\n        } catch (e: Exception) {\n            // Catch any exception from certificate loading or from the network connection\n            Log.e(\"Utils\", \"readUrlWithCustomCa failed: ${e.message}\")\n            return null\n        }\n    }\n\n    fun Bitmap.toCircle(): Bitmap {\n        // Use full package names to avoid conflict with Compose classes\n        val output = androidx.core.graphics.createBitmap(this.width, this.height, Bitmap.Config.ARGB_8888)\n        val canvas = android.graphics.Canvas(output)\n        val paint = android.graphics.Paint()\n        val rect = android.graphics.Rect(0, 0, this.width, this.height)\n\n        paint.isAntiAlias = true\n        canvas.drawARGB(0, 0, 0, 0)\n\n        canvas.drawCircle(this.width / 2f, this.height / 2f, this.width / 2f, paint)\n\n        paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)\n        canvas.drawBitmap(this, rect, rect, paint)\n        return output\n    }\n\n    fun createTextAvatar(letter: String, colorHex: String): Bitmap {\n        // Use decent resolution 128x128 and let Android scale it down.\n        val size = 128\n\n        // Use KTX createBitmap to match toCircle style\n        val bitmap = androidx.core.graphics.createBitmap(size, size, Bitmap.Config.ARGB_8888)\n\n        // Use Fully Qualified Names to avoid Compose conflicts\n        val canvas = android.graphics.Canvas(bitmap)\n\n        // Draw the colored circle background\n        val bgPaint = android.graphics.Paint()\n        bgPaint.isAntiAlias = true\n        try {\n            bgPaint.color = colorHex.toColorInt()\n        } catch (_: Exception) {\n            bgPaint.color = android.graphics.Color.GRAY // Fallback color\n        }\n        canvas.drawCircle(size / 2f, size / 2f, size / 2f, bgPaint)\n\n        // Draw the text (Initial)\n        val textPaint = android.graphics.Paint()\n        textPaint.isAntiAlias = true\n        textPaint.color = android.graphics.Color.WHITE\n        textPaint.textSize = size / 2f // Text size is half the circle size\n        textPaint.textAlign = android.graphics.Paint.Align.CENTER\n\n        // Use a bold font if possible\n        textPaint.typeface = android.graphics.Typeface.create(android.graphics.Typeface.DEFAULT, android.graphics.Typeface.BOLD)\n\n        // Calculate vertical center to center the text properly\n        val bounds = android.graphics.Rect()\n        textPaint.getTextBounds(letter, 0, letter.length, bounds)\n        val yOffset = (bounds.bottom - bounds.top) / 2f\n\n        // Draw text at Center X, Center Y + half text height (to visually center)\n        canvas.drawText(letter.uppercase(), size / 2f, (size / 2f) + (yOffset / 2) + (bounds.height()/4), textPaint)\n\n        return bitmap\n    }\n\n    fun mergeWavFiles(file1: File, file2: File, mergedFile: File): Boolean {\n        try {\n            val fis1 = FileInputStream(file1)\n            val fis2 = FileInputStream(file2)\n            val fos = FileOutputStream(mergedFile)\n\n            // Skip headers (assumed 44 bytes for standard WAV)\n            // NOTE: A robust implementation parses the header to find the 'data' chunk.\n            // For this quick fix, assuming 44 bytes is standard for Baresip output.\n            val headerSize = 44\n            val header1 = ByteArray(headerSize)\n            val header2 = ByteArray(headerSize)\n\n            if (fis1.read(header1) != headerSize || fis2.read(header2) != headerSize) {\n                Log.e(TAG, \"MergeWav: Files too small\")\n                return false\n            }\n\n            // Construct new header for stereo\n            // Copy header from file1 but update channels to 2 and block align\n            val newHeader = header1.clone()\n\n            // 1. Update File Size (Indices 4-7) - placeholder, fixed at end\n            // 2. Update Channels (Index 22) to 2 (Stereo)\n            newHeader[22] = 2\n            newHeader[23] = 0\n\n            // 3. Update Block Align (Index 32) - usually 2*Channels (16bit) -> 4\n            newHeader[32] = 4\n            newHeader[33] = 0\n\n            // 4. Update Byte Rate (Index 28) - usually SampleRate * BlockAlign\n            // Assuming 8000Hz sample rate: 8000 * 4 = 32000\n            // You should calculate this dynamically based on the input header if possible.\n            // For now, copying the rest is usually \"okay\" if players are lenient,\n            // but setting channels to 2 is the critical part.\n\n            fos.write(newHeader)\n\n            // MERGE LOOP with Buffering\n            val bufferSize = 4096 // 4KB buffer\n            val buffer1 = ByteArray(bufferSize)\n            val buffer2 = ByteArray(bufferSize)\n            val stereoBuffer = ByteArray(bufferSize * 2) // Output is twice as large\n\n            var bytesRead1: Int\n            var bytesRead2: Int\n            var totalBytesData = 0\n\n            while (true) {\n                bytesRead1 = fis1.read(buffer1)\n                bytesRead2 = fis2.read(buffer2)\n\n                if (bytesRead1 == -1 && bytesRead2 == -1) break\n\n                // Use the smaller read count to avoid out of bounds if files differ slightly\n                val limit = maxOf(bytesRead1, bytesRead2)\n                var outIndex = 0\n\n                // Interleave samples (Simple Left/Right merge)\n                // Assuming 16-bit audio (2 bytes per sample)\n                for (i in 0 until limit step 2) {\n                    // Left Channel (File 1)\n                    if (i + 1 < bytesRead1) {\n                        stereoBuffer[outIndex++] = buffer1[i]\n                        stereoBuffer[outIndex++] = buffer1[i+1]\n                    } else {\n                        // Padding if file1 ended\n                        stereoBuffer[outIndex++] = 0\n                        stereoBuffer[outIndex++] = 0\n                    }\n\n                    // Right Channel (File 2)\n                    if (i + 1 < bytesRead2) {\n                        stereoBuffer[outIndex++] = buffer2[i]\n                        stereoBuffer[outIndex++] = buffer2[i+1]\n                    } else {\n                        // Padding if file2 ended\n                        stereoBuffer[outIndex++] = 0\n                        stereoBuffer[outIndex++] = 0\n                    }\n                }\n\n                fos.write(stereoBuffer, 0, outIndex)\n                totalBytesData += outIndex\n            }\n\n            fis1.close()\n            fis2.close()\n\n            // Fix Header Sizes\n            // ChunkSize (4-7) = TotalFileSize - 8\n            val totalFileSize = totalBytesData + 44 - 8\n            val rFile = RandomAccessFile(mergedFile, \"rw\")\n            rFile.seek(4)\n            rFile.write(intToLittleEndian(totalFileSize), 0, 4)\n\n            // Subchunk2Size (40-43) = DataSize\n            rFile.seek(40)\n            rFile.write(intToLittleEndian(totalBytesData), 0, 4)\n            rFile.close()\n            fos.close()\n\n            return true\n        } catch (e: Exception) {\n            Log.e(TAG, \"MergeWav error: $e\")\n            return false\n        }\n    }\n\n    // Helper for header writing\n    private fun intToLittleEndian(value: Int): ByteArray {\n        return byteArrayOf(\n            (value and 0xff).toByte(),\n            (value shr 8 and 0xff).toByte(),\n            (value shr 16 and 0xff).toByte(),\n            (value shr 24 and 0xff).toByte()\n        )\n    }\n\n    fun createEmptyFile(path: String): File {\n        val file = File(path)\n        if (file.exists())\n            file.delete()\n        file.createNewFile()\n        return file\n    }\n\n    @Suppress(\"unused\")\n    fun listFilesInDirectory(directoryPath: String): List<File> {\n        val directory = File(directoryPath)\n        if (!directory.exists()) {\n            Log.w(TAG, \"Directory does not exist: $directoryPath\")\n            return emptyList()\n        }\n        if (!directory.isDirectory) {\n            Log.w(TAG, \"Path is not a directory: $directoryPath\")\n            return emptyList()\n        }\n        val files = directory.listFiles()\n        if (files == null) {\n            Log.e(\n                TAG,\n                \"Failed to list files in directory (listFiles returned null): $directoryPath\"\n            )\n            return emptyList()\n        }\n        return files.filter { it.isFile }\n    }\n\n    @SuppressLint(\"RestrictedApi\")\n    @Suppress(\"unused\", \"DEPRECATION\")\n    fun printBackStack(navController: NavController) {\n        Log.e(TAG, \"---- Current Navigation Back Stack ----\")\n        navController.currentBackStack.value.forEachIndexed { index, navBackStackEntry ->\n            val route = navBackStackEntry.destination.route\n            val arguments = navBackStackEntry.arguments?.let { bundle ->\n                bundle.keySet().joinToString(\", \") { key -> \"$key=${bundle.get(key)}\" }\n            } ?: \"null\"\n            Log.e(TAG, \"$index: Route='${route}', Args=[$arguments], ID=${navBackStackEntry.id}\")\n        }\n        Log.e(TAG, \"--------------------------------------\")\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/kotlin/com/tutpro/baresip/ViewModel.kt",
    "content": "package com.tutpro.baresip\n\nimport androidx.compose.runtime.MutableState\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport kotlinx.coroutines.flow.MutableSharedFlow\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.asSharedFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.Mic\nimport kotlinx.coroutines.launch\n\n// Sealed class for type-safe navigation events\nsealed class NavigationCommand {\n    object NavigateToHome : NavigationCommand()\n    data class NavigateToCalls(val aor: String) : NavigationCommand()\n    data class NavigateToChat(val aor: String, val peerUri: String) : NavigationCommand()\n}\n\nclass ViewModel: ViewModel() {\n\n    // A map to store message drafts. Key is \"aor:peerUri\"\n    private val messageDrafts = mutableMapOf<String, String>()\n\n    fun getAorPeerMessage(aor: String, peerUri: String): String {\n        return messageDrafts[\"$aor:$peerUri\"] ?: \"\"\n    }\n\n    fun updateAorPeerMessage(aor: String, peerUri: String, message: String) {\n        val key = \"$aor:$peerUri\"\n        if (message.isEmpty()) {\n            messageDrafts.remove(key)\n        } else {\n            messageDrafts[key] = message\n        }\n    }\n\n    data class DialerState(\n        val callUri: MutableState<String> = mutableStateOf(\"\"),\n        val callUriEnabled: MutableState<Boolean> = mutableStateOf(true),\n        val callUriLabel: MutableState<String> = mutableStateOf(\"\"),\n        val showSuggestions: MutableState<Boolean> = mutableStateOf(false),\n        val showCallButton: MutableState<Boolean> = mutableStateOf(true),\n        val showCallConferenceButton: MutableState<Boolean> = mutableStateOf(true),\n        val callButtonsEnabled: MutableState<Boolean> = mutableStateOf(true),\n        val conferenceCall: MutableState<Boolean> = mutableStateOf(false),\n    )\n\n    val dialerState = DialerState()\n\n    private val _calls = MutableStateFlow<List<Call>>(emptyList())\n    val calls = _calls.asStateFlow()\n\n    private val _selectedAor = MutableStateFlow(\"\")\n    val selectedAor = _selectedAor.asStateFlow()\n\n    private val _accountUpdate = MutableStateFlow(0)\n    val accountUpdate = _accountUpdate.asStateFlow()\n\n    private val _micIcon = MutableStateFlow(Icons.Filled.Mic)\n    val micIcon = _micIcon.asStateFlow()\n\n    private val _isSpeakerOn = MutableStateFlow(false)\n    val isSpeakerOn = _isSpeakerOn.asStateFlow()\n\n    private val _isDialpadVisible = MutableStateFlow(false)\n    val isDialpadVisible = _isDialpadVisible.asStateFlow()\n\n    private val _showKeyboard = MutableStateFlow(0)\n    val showKeyboard = _showKeyboard.asStateFlow()\n\n    private val _hideKeyboard = MutableStateFlow(0)\n    val hideKeyboard = _hideKeyboard.asStateFlow()\n\n    private val _navigationCommand = MutableSharedFlow<NavigationCommand>()\n    val navigationCommand = _navigationCommand.asSharedFlow()\n\n    private var _selectedCallRow: CallRow? = null\n\n    fun selectCallRow(callRow: CallRow) {\n        _selectedCallRow = callRow\n    }\n\n    fun consumeSelectedCallRow(): CallRow? {\n        val callRow = _selectedCallRow\n        _selectedCallRow = null\n        return callRow\n    }\n\n    fun onNewMessageReceived(aor: String, peerUri: String) {\n        viewModelScope.launch {\n            _navigationCommand.emit(NavigationCommand.NavigateToChat(aor, peerUri))\n        }\n    }\n\n    fun updateCalls(calls: List<Call>) {\n        _calls.value = calls\n    }\n\n    fun updateSelectedAor(aor: String) {\n        _selectedAor.value = aor\n    }\n\n    fun triggerAccountUpdate() {\n        _accountUpdate.value += 1\n    }\n\n    fun updateMicIcon(icon: ImageVector) {\n        _micIcon.value = icon\n    }\n\n    fun updateSpeakerPhoneStatus(on: Boolean) {\n        _isSpeakerOn.value = on\n    }\n\n    fun toggleDialpadVisibility() {\n        _isDialpadVisible.value = !_isDialpadVisible.value\n    }\n\n    fun requestShowKeyboard() {\n        _showKeyboard.value += 1\n    }\n\n    fun requestHideKeyboard() {\n        _hideKeyboard.value += 1\n    }\n\n    fun navigateToHome() {\n        viewModelScope.launch {\n            _navigationCommand.emit(NavigationCommand.NavigateToHome)\n        }\n    }\n\n    fun navigateToCalls(aor: String) {\n        viewModelScope.launch {\n            _navigationCommand.emit(NavigationCommand.NavigateToCalls(aor))\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/res/drawable/circle_green.xml",
    "content": "<vector android:autoMirrored=\"true\" android:height=\"28dp\"\nandroid:viewportHeight=\"100\" android:viewportWidth=\"100\"\nandroid:width=\"28dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n<path android:fillColor=\"@color/colorTrafficGreen\"\n    android:pathData=\"M50,50m-40,0a40,40 0,1 1,80 0a40,40 0,1 1,-80 0\"\n    android:strokeColor=\"#000000\" android:strokeWidth=\"3\"/>\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/circle_green_blind.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"28dp\"\n    android:height=\"28dp\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n\n    <group\n        >\n\n        <!-- 1. Black filled circle -->\n        <path\n            android:fillColor=\"#000000\"\n            android:pathData=\"M480,880\n                Q397,880 324,848.5\n                Q251,817 197,763\n                Q143,709 111.5,636\n                Q80,563 80,480\n                Q80,397 111.5,324\n                Q143,251 197,197\n                Q251,143 324,111.5\n                Q397,80 480,80\n                Q563,80 636,111.5\n                Q709,143 763,197\n                Q817,251 848.5,324\n                Q880,397 880,480\n                Q880,563 848.5,636\n                Q817,709 763,763\n                Q709,817 636,848.5\n                Q563,880 480,880Z\" />\n\n        <!-- 2. Red ring using stroke only -->\n        <path\n            android:fillColor=\"@android:color/transparent\"\n            android:strokeColor=\"#00f0c0\"\n            android:strokeWidth=\"65\"\n            android:pathData=\"M480,880\n                Q397,880 324,848.5\n                Q251,817 197,763\n                Q143,709 111.5,636\n                Q80,563 80,480\n                Q80,397 111.5,324\n                Q143,251 197,197\n                Q251,143 324,111.5\n                Q397,80 480,80\n                Q563,80 636,111.5\n                Q709,143 763,197\n                Q817,251 848.5,324\n                Q880,397 880,480\n                Q880,563 848.5,636\n                Q817,709 763,763\n                Q709,817 636,848.5\n                Q563,880 480,880Z\" />\n\n        <!-- 3. Checkmark -->\n        <path\n            android:fillColor=\"#00f0c0\"\n            android:pathData=\"M424,664L706,382L650,326L424,552L310,438L254,494L424,664Z\" />\n    </group>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/circle_red.xml",
    "content": "<vector android:autoMirrored=\"true\" android:height=\"28dp\"\n    android:viewportHeight=\"100\" android:viewportWidth=\"100\"\n    android:width=\"28dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@color/colorTrafficRed\"\n        android:pathData=\"M50,50m-40,0a40,40 0,1 1,80 0a40,40 0,1 1,-80 0\"\n        android:strokeColor=\"#000000\" android:strokeWidth=\"3\"/>\n</vector>\n\n\n"
  },
  {
    "path": "app/src/main/res/drawable/circle_red_blind.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"28dp\"\n    android:height=\"28dp\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n\n    <group\n        >\n\n        <!-- 1. Black filled circle -->\n        <path\n            android:fillColor=\"#000000\"\n            android:pathData=\"M480,880\n                Q397,880 324,848.5\n                Q251,817 197,763\n                Q143,709 111.5,636\n                Q80,563 80,480\n                Q80,397 111.5,324\n                Q143,251 197,197\n                Q251,143 324,111.5\n                Q397,80 480,80\n                Q563,80 636,111.5\n                Q709,143 763,197\n                Q817,251 848.5,324\n                Q880,397 880,480\n                Q880,563 848.5,636\n                Q817,709 763,763\n                Q709,817 636,848.5\n                Q563,880 480,880Z\" />\n\n        <!-- 2. Orange ring -->\n        <path\n            android:fillColor=\"@android:color/transparent\"\n            android:strokeColor=\"#FF5500\"\n            android:strokeWidth=\"65\"\n            android:pathData=\"M480,880\n                Q397,880 324,848.5\n                Q251,817 197,763\n                Q143,709 111.5,636\n                Q80,563 80,480\n                Q80,397 111.5,324\n                Q143,251 197,197\n                Q251,143 324,111.5\n                Q397,80 480,80\n                Q563,80 636,111.5\n                Q709,143 763,197\n                Q817,251 848.5,324\n                Q880,397 880,480\n                Q880,563 848.5,636\n                Q817,709 763,763\n                Q709,817 636,848.5\n                Q563,880 480,880Z\" />\n\n        <!-- 3. Orange exclamation mark -->\n        <!-- Dot -->\n        <path\n            android:fillColor=\"#FF5500\"\n            android:pathData=\"M480,680\n                Q497,680 508.5,668.5\n                Q520,657 520,640\n                Q520,623 508.5,611.5\n                Q497,600 480,600\n                Q463,600 451.5,611.5\n                Q440,623 440,640\n                Q440,657 451.5,668.5\n                Q463,680 480,680Z\" />\n\n        <!-- Vertical bar -->\n        <path\n            android:fillColor=\"#FF5500\"\n            android:pathData=\"M440,520L520,520L520,280L440,280L440,520Z\" />\n    </group>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/circle_white.xml",
    "content": "<vector android:autoMirrored=\"true\" android:height=\"28dp\"\n    android:viewportHeight=\"100\" android:viewportWidth=\"100\"\n    android:width=\"28dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"#ffffff\"\n        android:pathData=\"M50,50m-40,0a40,40 0,1 1,80 0a40,40 0,1 1,-80 0\"\n        android:strokeColor=\"#000000\" android:strokeWidth=\"3\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/circle_yellow.xml",
    "content": "<vector android:autoMirrored=\"true\" android:height=\"28dp\"\n    android:viewportHeight=\"100\" android:viewportWidth=\"100\"\n    android:width=\"28dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@color/colorTrafficYellow\"\n        android:pathData=\"M50,50m-40,0a40,40 0,1 1,80 0a40,40 0,1 1,-80 0\"\n        android:strokeColor=\"#000000\" android:strokeWidth=\"3\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/circle_yellow_blind.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"28dp\"\n    android:height=\"28dp\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\">\n\n    <group\n        >\n\n        <!-- 1. Black filled circle -->\n        <path\n            android:fillColor=\"#000000\"\n            android:pathData=\"M480,880\n                Q397,880 324,848.5\n                Q251,817 197,763\n                Q143,709 111.5,636\n                Q80,563 80,480\n                Q80,397 111.5,324\n                Q143,251 197,197\n                Q251,143 324,111.5\n                Q397,80 480,80\n                Q563,80 636,111.5\n                Q709,143 763,197\n                Q817,251 848.5,324\n                Q880,397 880,480\n                Q880,563 848.5,636\n                Q817,709 763,763\n                Q709,817 636,848.5\n                Q563,880 480,880Z\" />\n\n        <!-- 2. Yellow ring -->\n        <path\n            android:fillColor=\"@android:color/transparent\"\n            android:strokeColor=\"#FFEE00\"\n            android:strokeWidth=\"65\"\n            android:pathData=\"M480,880\n                Q397,880 324,848.5\n                Q251,817 197,763\n                Q143,709 111.5,636\n                Q80,563 80,480\n                Q80,397 111.5,324\n                Q143,251 197,197\n                Q251,143 324,111.5\n                Q397,80 480,80\n                Q563,80 636,111.5\n                Q709,143 763,197\n                Q817,251 848.5,324\n                Q880,397 880,480\n                Q880,563 848.5,636\n                Q817,709 763,763\n                Q709,817 636,848.5\n                Q563,880 480,880Z\" />\n\n        <!-- 3. Yellow dots (extracted from original path) -->\n        <!-- Left dot -->\n        <path\n            android:fillColor=\"#FFEE00\"\n            android:pathData=\"M280,540\n                Q305,540 322.5,522.5\n                Q340,505 340,480\n                Q340,455 322.5,437.5\n                Q305,420 280,420\n                Q255,420 237.5,437.5\n                Q220,455 220,480\n                Q220,505 237.5,522.5\n                Q255,540 280,540Z\" />\n\n        <!-- Center dot -->\n        <path\n            android:fillColor=\"#FFEE00\"\n            android:pathData=\"M480,540\n                Q505,540 522.5,522.5\n                Q540,505 540,480\n                Q540,455 522.5,437.5\n                Q505,420 480,420\n                Q455,420 437.5,437.5\n                Q420,455 420,480\n                Q420,505 437.5,522.5\n                Q455,540 480,540Z\" />\n\n        <!-- Right dot -->\n        <path\n            android:fillColor=\"#FFEE00\"\n            android:pathData=\"M680,540\n                Q705,540 722.5,522.5\n                Q740,505 740,480\n                Q740,455 722.5,437.5\n                Q705,420 680,420\n                Q655,420 637.5,437.5\n                Q620,455 620,480\n                Q620,505 637.5,522.5\n                Q655,540 680,540Z\" />\n    </group>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher_foreground.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"432\"\n    android:viewportHeight=\"432\">\n\n    <path\n        android:fillColor=\"#0298FD\"\n        android:pathData=\"M234,311C224,313,215,312,207,306C203,304,200,301,197,299C195,301,192,303,190,306C187,312,181,311,176,311C175,311,174,309,173,308C173,307,173,305,173,304C173,251,173,197,173,144C173,135,172,134,163,134C161,134,159,134,157,134C157,129,157,125,157,120C169,119,181,118,193,118C201,118,201,118,201,126C201,149,201,171,201,195C202,194,203,194,204,194C220,181,244,183,260,196C270,204,276,215,278,228C279,236,280,244,280,252C280,269,275,285,263,298C255,305,246,311,234,311M187,130C187,129,188,128,189,126C188,126,187,126,185,126C179,126,174,125,168,124C167,124,167,124,166,124C166,124,165,124,164,124C164,125,163,125,163,125C163,125,164,126,165,126C170,128,174,129,179,131C182,132,184,135,186,130C186,130,186,130,187,130M209,195C210,195,211,195,212,195C221,191,231,191,241,195C243,196,245,197,248,199C248,198,248,198,249,197C246,195,242,194,239,192C232,186,223,188,215,190C212,191,210,193,208,195C208,195,208,195,208,195C208,195,208,195,209,195M258,280C258,279,258,279,258,277C258,276,259,274,260,272C260,269,262,267,262,265C262,247,264,229,258,212C257,207,255,203,251,201C250,203,251,204,251,205C253,209,255,212,255,215C256,227,256,239,256,250C256,252,257,255,257,258C257,261,256,264,256,267C255,276,253,284,248,291C244,296,240,300,234,301C228,302,223,303,217,304C232,311,254,297,258,280M247,225C247,222,247,219,246,216C244,210,242,203,235,199C224,193,212,200,205,208C202,211,202,215,202,218C202,238,202,258,202,278C202,282,202,286,205,289C209,293,214,299,219,300C232,302,238,299,244,284C249,273,246,261,248,249C249,241,247,233,247,225M187,203C187,199,187,194,187,190C187,175,187,160,187,146C187,144,186,142,185,141C184,142,183,144,182,145C182,146,182,147,182,147C182,194,182,241,182,287C182,289,182,291,183,293C184,292,185,292,185,291C186,286,188,281,188,276C188,263,188,249,188,236C188,225,187,215,187,203z\"/>\n\n    <path android:fillColor=\"#0FA5FF\" android:pathData=\"M186.88,203.72C187.35,214.7 187.83,225.32 187.96,235.93C188.12,249.42 188.26,262.93 187.86,276.41C187.71,281.38 186.03,286.31 184.94,291.24C184.84,291.7 183.98,292 182.61,293.02C182.35,290.63 182.02,288.98 182.02,287.32C181.99,240.68 182,194.03 182,147.39C182,146.73 181.8,145.95 182.06,145.42C182.88,143.79 183.87,142.25 184.8,140.68C185.53,142.3 186.88,143.92 186.89,145.55C187.06,160.37 187.01,175.2 186.97,190.02C186.96,194.46 186.81,198.91 186.88,203.72z\" android:strokeColor=\"#00000000\"/>\n    <path android:fillColor=\"#0DA3FF\" android:pathData=\"M256.93,257C256.62,254.73 256.09,252.46 256.03,250.18C255.73,238.55 255.81,226.9 255.15,215.29C254.95,211.85 252.74,208.52 251.45,205.14C250.96,203.86 250.46,202.58 250.62,200.95C255.17,203.1 256.52,207.38 257.96,211.58C263.89,228.95 261.65,246.95 261.88,264.75C261.91,267.02 260.42,269.31 259.33,271.37C259.11,268.69 259.53,266.2 259.18,263.81C258.84,261.48 257.71,259.27 256.93,257z\" android:strokeColor=\"#00000000\"/>\n    <path android:fillColor=\"#1BB1FF\" android:pathData=\"M256.94,257.43C257.71,259.27 258.84,261.48 259.18,263.81C259.53,266.2 259.11,268.69 259.04,271.59C258.73,273.77 258.41,275.51 257.87,277.84C257.48,278.98 257.3,279.52 257.13,280.06C254.34,296.54 231.79,311.14 217.46,303.92C223,302.9 228.29,301.76 233.65,301C240.09,300.1 243.97,295.58 247.5,291.01C252.83,284.12 254.64,275.72 255.92,267.27C256.39,264.15 256.61,260.99 256.94,257.43z\" android:strokeColor=\"#00000000\"/>\n    <path android:fillColor=\"#0DA3FF\" android:pathData=\"M207.96,194.97C210.27,193.28 212.35,190.8 214.93,190.03C223.2,187.59 231.53,186.46 239.37,191.93C242.25,193.95 245.57,195.35 248.69,197.03C248.45,197.53 248.2,198.02 247.95,198.52C245.48,197.33 243,196.13 240.52,194.96C231.04,190.51 221.5,191.28 211.9,194.54C210.84,194.9 209.67,194.92 208.28,195.05C208,195 207.96,194.97 207.96,194.97z\" android:strokeColor=\"#00000000\"/>\n    <path android:fillColor=\"#72EFFF\" android:pathData=\"M167.95,124.47C173.66,125.01 179.37,125.54 185.54,126.46C185.98,127.86 185.94,128.89 185.9,129.92C185.9,129.92 185.99,129.98 185.52,129.91C183.04,129.9 181.01,129.96 178.98,130.03C174.25,128.78 169.51,127.54 164.51,125.84C164.33,125.04 164.42,124.68 164.52,124.32C165.07,124.3 165.63,124.28 166.64,124.33C167.1,124.39 167.52,124.5 167.52,124.5C167.52,124.5 167.95,124.47 167.95,124.47z\" android:strokeColor=\"#00000000\"/>\n    <path android:fillColor=\"#1AB0FF\" android:pathData=\"M179.11,130.34C181.01,129.96 183.04,129.9 185.49,129.87C184.24,134.89 181.7,132.46 179.11,130.34z\" android:strokeColor=\"#00000000\"/>\n    <path android:fillColor=\"#1AB0FF\" android:pathData=\"M186.27,129.9C185.94,128.89 185.98,127.86 185.99,126.45C186.87,126.18 187.78,126.31 188.69,126.43C188,127.59 187.32,128.74 186.27,129.9z\" android:strokeColor=\"#00000000\"/>\n    <path android:fillColor=\"#1AB0FF\" android:pathData=\"M164.24,124.3C164.42,124.68 164.33,125.04 164.18,125.67C163.73,125.6 163.34,125.27 162.94,124.94C163.28,124.72 163.62,124.5 164.24,124.3z\" android:strokeColor=\"#00000000\"/>\n    <path android:fillColor=\"#0DA3FF\" android:pathData=\"M257.35,279.97C257.3,279.52 257.48,278.98 257.85,278.22C257.88,278.63 257.73,279.26 257.35,279.97z\" android:strokeColor=\"#00000000\"/>\n    <path android:fillColor=\"#1AB0FF\" android:pathData=\"M167.76,124.37C167.95,124.47 167.52,124.5 167.52,124.5C167.52,124.5 167.1,124.39 166.91,124.27C167.02,124.12 167.3,124.16 167.76,124.37z\" android:strokeColor=\"#00000000\"/>\n    <path android:fillColor=\"#0DA3FF\" android:pathData=\"M208.01,195.02C208.03,195.04 207.85,195.23 207.85,195.23C207.85,195.23 207.85,195.05 207.9,195.01C207.96,194.97 208,195 208.01,195.02z\" android:strokeColor=\"#00000000\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_notification_b.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"130\"\n    android:viewportHeight=\"195\">\n\n    <!--\n       Changed translateY from -65 to -63 to push the icon down slightly.\n       If it needs to go lower, try -61 or -60.\n    -->\n    <group android:translateX=\"-95\" android:translateY=\"-63\">\n        <path\n            android:fillColor=\"#FF000000\"\n            android:pathData=\"M150,88C150.33,106.48 150.66,124.96 151,144C154.3,142.35 157.6,140.7 161,139C171.02,136.34 181.1,138.03 190.13,142.89C199.05,148.18 204.44,156.93 207.49,166.73C210.81,181.52 210.96,199.98 203.61,213.52C197.2,223.43 189.37,229.81 178,233C166.37,234.16 158.42,232.75 148.88,225.69C148.26,225.13 147.64,224.57 147,224C146.62,224.58 146.24,225.15 145.86,225.75C145.31,226.49 144.75,227.24 144.19,228C143.67,228.74 143.14,229.49 142.61,230.25C140,232 140,232 129,232C128.67,188.77 128.34,145.54 128,101C124.37,100.67 120.74,100.34 117,100C117,96.37 117,92.74 117,89C121.22,88.69 125.43,88.39 129.65,88.09C131.09,87.99 132.52,87.89 133.96,87.78C136.02,87.63 138.08,87.48 140.14,87.34C141.39,87.25 142.63,87.16 143.91,87.07C147,87 147,87 150,88ZM156.79,150.5C152.47,154.35 150.49,157.17 149.98,163.01C149.93,165.33 149.93,167.65 149.97,169.97C149.97,171.2 149.96,172.44 149.96,173.71C149.96,176.31 149.97,178.92 150.01,181.52C150.06,185.49 150.03,189.45 149.99,193.43C150,195.96 150.02,198.5 150.04,201.03C150.02,202.21 150.01,203.39 150,204.61C150.16,211.62 151.18,215.51 156.21,220.51C159.86,223.55 162.73,224.84 167.44,225.44C172.85,224.77 176.36,222.83 180,218.77C186.19,210.2 185.48,197.96 185.39,187.78C185.38,185.5 185.39,183.23 185.41,180.95C185.5,165.04 185.5,165.04 179.25,150.81C172.45,144.13 164.2,145.47 156.79,150.5Z\"/>\n    </group>\n</vector>\n\n\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_notification_call.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:height=\"24dp\"\n    android:viewportHeight=\"24\"\n    android:viewportWidth=\"24\"\n    android:width=\"24dp\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M20.01,15.38c-1.23,0 -2.42,-0.2 -3.53,-0.56 -0.35,-0.12 -0.74,-0.03 -1.01,0.24l-1.57,1.97c-2.83,-1.35 -5.48,-3.9 -6.89,-6.83l1.95,-1.66c0.27,-0.28 0.35,-0.67 0.24,-1.02 -0.37,-1.11 -0.56,-2.3 -0.56,-3.53 0,-0.54 -0.45,-0.99 -0.99,-0.99H4.19C3.65,3 3,3.24 3,3.99 3,13.28 10.73,21 20.01,21c0.71,0 0.99,-0.63 0.99,-1.18v-3.45c0,-0.54 -0.45,-0.99 -0.99,-0.99z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_notification_call_end.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:height=\"24dp\"\n    android:viewportHeight=\"24\"\n    android:viewportWidth=\"24\"\n    android:width=\"24dp\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M12,9c-1.6,0 -3.15,0.25 -4.6,0.72v3.1c0,0.39 -0.23,0.74 -0.56,0.9 -0.98,0.49 -1.87,1.12 -2.66,1.85 -0.18,0.18 -0.43,0.28 -0.7,0.28 -0.28,0 -0.53,-0.11 -0.71,-0.29L0.29,13.08c-0.18,-0.17 -0.29,-0.42 -0.29,-0.7 0,-0.28 0.11,-0.53 0.29,-0.71C3.34,8.78 7.46,7 12,7s8.66,1.78 11.71,4.67c0.18,0.18 0.29,0.43 0.29,0.71 0,0.28 -0.11,0.53 -0.29,0.71l-2.48,2.48c-0.18,0.18 -0.43,0.29 -0.71,0.29 -0.27,0 -0.52,-0.11 -0.7,-0.28 -0.79,-0.74 -1.69,-1.36 -2.67,-1.85 -0.33,-0.16 -0.56,-0.5 -0.56,-0.9v-3.1C15.15,9.25 13.6,9 12,9z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_notification_call_missed.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:autoMirrored=\"true\"\n    android:height=\"24dp\"\n    android:viewportHeight=\"24\"\n    android:viewportWidth=\"24\"\n    android:width=\"24dp\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M19.59,7L12,14.59 6.41,9H11V7H3v8h2v-4.59l7,7 9,-9z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_notification_delete.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:height=\"24dp\"\n    android:viewportHeight=\"24\"\n    android:viewportWidth=\"24\"\n    android:width=\"24dp\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_notification_message.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:autoMirrored=\"true\"\n    android:height=\"24dp\"\n    android:viewportHeight=\"24\"\n    android:viewportWidth=\"24\"\n    android:width=\"24dp\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM18,14L6,14v-2h12v2zM18,11L6,11L6,9h12v2zM18,8L6,8L6,6h12v2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_notification_reply.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:autoMirrored=\"true\"\n    android:height=\"24dp\"\n    android:viewportHeight=\"24\"\n    android:viewportWidth=\"24\"\n    android:width=\"24dp\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M10,9V5l-7,7 7,7v-4.1c5,0 8.5,1.6 11,5.1 -1,-5 -4,-10 -11,-11z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_notification_save.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:height=\"24dp\"\n    android:viewportHeight=\"24\"\n    android:viewportWidth=\"24\"\n    android:width=\"24dp\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/layout/status_notification.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/mainRow\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"horizontal\" >\n\n    <ImageView android:id=\"@+id/status0\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:contentDescription=\"@string/status\" />\n\n    <ImageView android:id=\"@+id/status1\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:paddingStart=\"10dp\"\n        android:paddingEnd=\"0dp\"\n        android:contentDescription=\"@string/status\" />\n\n    <ImageView android:id=\"@+id/status2\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:paddingStart=\"10dp\"\n        android:paddingEnd=\"0dp\"\n        android:contentDescription=\"@string/status\" />\n\n    <ImageView android:id=\"@+id/status3\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:paddingStart=\"10dp\"\n        android:paddingEnd=\"0dp\"\n        android:contentDescription=\"@string/status\" />\n\n    <TextView android:id=\"@+id/etc\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:layout_marginBottom=\"14dp\"\n        android:paddingStart=\"5dp\"\n        android:paddingEnd=\"0dp\"\n        android:text=\"@string/dots\"\n        android:textColor=\"#000000\"\n        style = \"@android:style/TextAppearance.Medium\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@color/ic_launcher_background\" />\n    <foreground android:drawable=\"@drawable/ic_launcher_foreground\" />\n</adaptive-icon>\n\n"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi/ic_launcher_round.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@color/ic_launcher_background\" />\n    <foreground android:drawable=\"@drawable/ic_launcher_foreground\" />\n</adaptive-icon>\n"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#0ca1fd</color>\n    <color name=\"colorSecondary\">#00B9A1</color>\n    <color name=\"colorError\">#BA1A1A</color>\n    <color name=\"colorWhite\">#ffffff</color>\n    <color name=\"colorTrafficGreen\">#4CAF50</color>\n    <color name=\"colorTrafficYellow\">#FFEB3B</color>\n    <color name=\"colorTrafficRed\">#d83025</color>\n    <color name=\"ic_launcher_background\">#FFFFFF</color>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"app_name\" translatable=\"false\">baresip</string>\n    <string name=\"app_name_plus\" translatable=\"false\">baresip+</string>\n    <!-- About Activity -->\n    <string name=\"about_title\">About baresip</string>\n    <string name=\"about_title_plus\">About baresip+</string>\n    <string name=\"about_text\">\n        <![CDATA[\n        <h1>SIP User Agent based on <a href=\"https://github.com/baresip/baresip\">baresip</a> library</h1>\n        <br>\n        <p>Juha Heinanen &lt;jh@tutpro.com&gt;</p>\n        <p>Version %1$s</p>\n        <br>\n        <h2>Usage Hints</h2>\n        <ul>\n            <li>Check that default values in baresip\\'s Settings meet your needs\n                (touch item titles for help).</li>\n            <li>Then in Accounts, create one or more accounts (again touch item titles for help).</li>\n            <li>Registration status of an account is shown with a colored dot: green (registration\n                succeeded), yellow (registration is in progress), red (registration failed), white (registration\n                has not been activated).</li>\n            <li>Touch on the status dot leads directly to account configuration.</li>\n            <li>Long touch on baresip bar icons shows information about the icons.</li>\n            <li>Swipe down gesture causes re-registration of the currently shown account.</li>\n            <li>Long touch on currently shown account enables or disables account\\'s registration.</li>\n            <li>Swipe left/right gesture toggles between the accounts.</li>\n            <li>Previous call party can be reselect by touching the call icon when Callee is empty.</li>\n            <li>Peers of calls and messages can be added to contacts by long touches.</li>\n            <li>Long touches can also be used to remove calls, chats, messages, and contacts.</li>\n            <li>Touch/long touch on contact icon can be used to install/remove image avatar.</li>\n            <li>Long touch on an audio codec can be used to enable/disable the codec.</li>\n            <li>See <a href=\"https://github.com/juha-h/baresip-studio/wiki\">Wiki</a> for more information.</li>\n        </ul>\n        <h2>Privacy Policy</h2>\n            <p>Privacy policy is available <a href=\"https://raw.githubusercontent.com/juha-h/baresip-studio/master/PrivacyPolicy.txt\">here</a>.</p>\n        <br>\n        <h2>Source Code</h2>\n            <p>Source code is available at <a href=\"https://github.com/juha-h/baresip-studio\">GitHub</a>,\n            where also issues can be reported.</p>\n        <br>\n        <h2>Language Translations</h2>\n            <p>Language translations are managed via baresip\n                <a href=\"https://hosted.weblate.org/projects/baresip/\">Weblate</a> project.</p>\n        <br>\n        <h2>Licenses</h2>\n        <ul>\n            <li><b>BSD-3-Clause</b> except the following:</li>\n            <li><b>Apache 2.0</b> AMR codecs and TLS security</li>\n            <li><b>AGPLv4</b> ZRTP media encryption</li>\n            <li><b>GNU LGPL 2.1</b> Codec2 codec</li>\n            <li><b>Free</b> G.722 codec</li>\n            <li><b>GNU GPLv3</b> G.729 codec</li>\n        </ul>\n        ]]>\n    </string>\n    <string name=\"about_text_plus\">\n        <![CDATA[\n        <h1>SIP User Agent with video calls based on <a href=\"https://github.com/baresip/baresip\">baresip</a> library</h1>\n        <br>\n        <p>Juha Heinanen &lt;jh@tutpro.com&gt;</p>\n        <p>Version %1$s</p>\n        <br>\n        <h2>Usage Hints</h2>\n        <ul>\n            <li>Check that default values in baresip+\\'s Settings meet your needs\n                (touch item titles for help).</li>\n            <li>Then in Accounts, create one or more accounts (again touch item titles for help).</li>\n            <li>Registration status of an account is shown with a colored dot: green (registration\n                succeeded), yellow (registration is in progress), red (registration failed), white (registration\n                has not been activated).</li>\n            <li>Touch on the status dot leads directly to account configuration.</li>\n            <li>Long touch on baresip bar icons shows information about the icons.</li>\n            <li>Swipe down gesture causes re-registration of the currently shown account.</li>\n            <li>Long touch on currently shown account enables or disables account\\'s registration.</li>\n            <li>Swipe left/right gesture toggles between the accounts.</li>\n            <li>Previous call party can be reselect by touching the call icon when Callee is empty.</li>\n            <li>Peers of calls and messages can be added to contacts by long touches.</li>\n            <li>Long touches can also be used to remove calls, chats, messages, and contacts.</li>\n            <li>Touch/long touch of contact icon can be used to install/remove image avatar.</li>\n            <li>Long touch on an audio or video codec can be used to enable/disable the codec.</li>\n            <li>See <a href=\"https://github.com/juha-h/baresip-studio/wiki\">Wiki</a> for more\n                information.</li>\n        </ul>\n        <h2>Known Issues</h2>\n        <ul>\n            <li>Selfview is not properly shown when video stream is sendonly.</li>\n        </ul>\n        <h2>Privacy Policy</h2>\n        <p>Privacy policy is available\n            <a href=\"https://raw.githubusercontent.com/juha-h/baresip-studio/video/PrivacyPolicy.txt\">here</a>.</p>\n        <br>\n        <h2>Source code</h2>\n        <p>Source code is available at <a href=\"https://github.com/juha-h/baresip-studio\">GitHub</a>,\n            where also issues can be reported.</p>\n        <br>\n        <h2>Language translations</h2>\n        <p>Language translations are managed via baresip\n            <a href=\"https://hosted.weblate.org/projects/baresip/\">Weblate</a> project.</p>\n        <br>\n        <h2>Licenses</h2>\n        <ul>\n            <li><b>BSD-3-Clause</b> except the following:</li>\n            <li><b>Apache 2.0</b> AMR codecs and TLS security</li>\n            <li><b>AGPLv4</b> ZRTP media encryption</li>\n            <li><b>GNU LGPL 2.1</b> Codec2 codec</li>\n            <li><b>Free</b> G.722 codec</li>\n            <li><b>GNU GPLv3</b> G.729 codec</li>\n            <li><b>GNU GPLv2</b> H.264 and H.265 codecs</li>\n            <li><b>AOMedia</b> AV1 codec</li>\n        </ul>\n        ]]>\n    </string>\n    <!-- Account Activity -->\n    <string name=\"account\">Account</string>\n    <string name=\"account_nickname_help\">Nickname (if any) used to identify this account within\n        baresip app.</string>\n    <string name=\"nickname\">Nickname</string>\n    <string name=\"invalid_account_nickname\">Invalid Account Nickname \\'%1$s\\'</string>\n    <string name=\"non_unique_account_nickname\">Nickname \\'%1$s\\' already exists</string>\n    <string name=\"display_name\">Display Name</string>\n    <string name=\"display_name_help\">Name (if any) used in From URI of outbound requests.</string>\n    <string name=\"invalid_display_name\">Invalid Display Name \\'%1$s\\'</string>\n    <string name=\"authentication_username\">Authentication Username</string>\n    <string name=\"authentication_username_help\">Authentication username\n    if authentication of SIP requests is required.  Default value is account\\'s username.\n    </string>\n    <string name=\"invalid_authentication_username\">Invalid Authentication Username \\'%1$s\\'</string>\n    <string name=\"authentication_password\">Authentication Password</string>\n    <string name=\"authentication_password_help\">Authentication\n    Password up to 64 characters. If Authentication Username is given, but Password is not\n    given, it will be asked when baresip is started.\n    </string>\n    <string name=\"invalid_authentication_password\">Invalid Authentication Password \\'%1$s\\'</string>\n    <string name=\"outbound_proxies\">Outbound Proxies</string>\n    <string name=\"outbound_proxies_help\">SIP URI of one or two proxies that must be used when sending requests.\n        If two is given, REGISTER requests are sent to both and other requests are sent to\n        one that responds.  If no outbound proxy is given, requests are sent based on\n        DNS NAPTR/SRV/A record lookup of callee URI hostpart. If hostpart of SIP URI is an IPv6\n        address, the address must be written inside brackets [].\n\\nExamples:\n\\n • sip:example.com:5061;transport=tls\n\\n • sip:[2001:67c:223:777::10];transport=tcp\n\\n • sip:192.168.43.50:443;transport=wss\n    </string>\n    <string name=\"sip_uri_of_proxy_server\">SIP URI of Proxy Server</string>\n    <string name=\"sip_uri_of_another_proxy_server\">SIP URI of another Proxy Server</string>\n    <string name=\"invalid_proxy_server_uri\">Invalid Proxy Server URI \\'%1$s\\'</string>\n    <string name=\"register\">Register</string>\n    <string name=\"register_help\">If checked, registration is enabled and REGISTER requests are sent\n        at the interval specified by Registration Interval.</string>\n    <string name=\"reg_int\">Registration Interval</string>\n    <string name=\"reg_int_help\">Tells how often (in seconds) baresip sends REGISTER requests.\n        Valid values are from 60 to 3600.</string>\n    <string name=\"invalid_reg_int\">Invalid Registration Interval \\'%1$s\\'</string>\n    <string name=\"check_origin\">Check Origin</string>\n    <string name=\"check_origin_help\">If checked, inbound requests are allowed only from IP addresses\n        where register requests were sent.\n    </string>\n    <string name=\"media_nat\">Media NAT Traversal</string>\n    <string name=\"media_nat_help\">Selects media NAT traversal protocol (if any).  Possible choices are STUN\n        (Session Traversal Utilities for NAT, RFC 5389) and ICE (Interactive Connectivity\n        Establishment, RFC 5245).\n    </string>\n    <string name=\"stun_server\">STUN/TURN Server</string>\n    <string name=\"stun_server_help\">A STUN/TURN Server URI of form scheme:host[:port][\\?transport=udp|tcp],\n        where scheme is \\'stun\\', \\'stuns\\', \\'turn\\', or \\'turns\\'. Factory default STUN Server\n        for STUN and ICE protocols is \\'stun:stun.l.google.com:19302\\' pointing to public Google\n        STUN server. There is no factory default TURN server.\n    </string>\n    <string name=\"invalid_stun_server\">Invalid STUN/TURN Server URI \\'%1$s\\'</string>\n    <string name=\"stun_server_default\" translatable=\"false\">stun:stun.l.google.com:19302</string>\n    <string name=\"stun_username\">STUN/TURN Username</string>\n    <string name=\"stun_username_help\">Username if required by STUN/TURN server</string>\n    <string name=\"invalid_stun_username\">Invalid Username \\'%1$s\\'</string>\n    <string name=\"stun_password\">STUN/TURN Password</string>\n    <string name=\"stun_password_help\">Password if required by STUN/TURN server</string>\n    <string name=\"invalid_stun_password\">Invalid Password \\'%1$s\\'</string>\n    <string name=\"media_encryption\">Media Encryption</string>\n    <string name=\"media_encryption_help\">Selects media transport encryption protocol (if any).\n\\n • ZRTP (recommended) means that ZRTP end-to-end media encryption negotiation is tried after\n            the call has been established.\n\\n • DTLS-SRTPF means that UDP/TLS/RTP/SAVPF is offered in outgoing call and that RTP/SAVP,\n            RTP/SAVPF, UDP/TLS/RTP/SAVP, or UDP/TLS/RTP/SAVPF is used if offered in incoming call.\n\\n • SRTP-MANDF means that RTP/SAVPF is offered in outgoing call and required in incoming call.\n\\n • SRTP-MAND means that RTP/SAVP is offered in outgoing call and required in incoming call.\n\\n • SRTP means that RTP/AVP is offered in outgoing call and that RTP/SAVP or RTP/SAVPF is used\n            if offered in incoming call.\n    </string>\n    <string name=\"rtcp_mux\">RTCP Multiplexing</string>\n    <string name=\"rtcp_mux_help\">If checked, RTP and RTCP packets are multiplexed on a single port (RFC 5761).</string>\n    <string name=\"rel_100\">Reliable Provisional Responses</string>\n    <string name=\"rel_100_help\">If checked, indicate support for reliable provisional responses (RFC 3262).</string>\n    <string name=\"dtmf_mode\">DTMF Mode</string>\n    <string name=\"dtmf_mode_help\">Selects how DTMF tones 0–9, #, *, and A-D are sent.</string>\n    <string name=\"dtmf_inband\">In-band RTP Events</string>\n    <string name=\"dtmf_info\">SIP INFO Requests</string>\n    <string name=\"dtmf_auto\">In-band RTP or SIP INFO</string>\n    <string name=\"answer_mode\">Answer Mode</string>\n    <string name=\"answer_mode_help\">Selects how incoming calls are answered.</string>\n    <string name=\"redirect_mode\">Redirect Mode</string>\n    <string name=\"redirect_mode_help\">Selects if call redirect request is followed automatically or\n        if confirmation is requested.</string>\n    <string name=\"manual\">Manual</string>\n    <string name=\"auto\">Automatic</string>\n    <string name=\"block_unknown\">Block Unknown</string>\n    <string name=\"block_unknown_help\">Block calls and messages from peers that are not found in contacts.</string>\n    <string name=\"voicemail_uri\">Voicemail URI</string>\n    <string name=\"voicemain_uri_help\">SIP URI for checking of voicemail messages. If left empty, voicemail\n        messages (Message Waiting Indications) are not subscribed to.\n    </string>\n    <string name=\"country_code\">Country Code</string>\n    <string name=\"country_code_help\">E.164 country code of this account.  If From URI userpart of\n        incoming call or message contains a telephone number that does not start with \\'+\\' sign and if contact\n        lookup fails, the number is prefixed with this country code and contact lookup is\n        tried again. If the telephone number starts with a single digit \\'0\\', digit \\'0\\' is removed\n        before the number is prefixed.\n    </string>\n    <string name=\"invalid_country_code\">Invalid Country Code \\'%1$s\\'</string>\n    <string name=\"telephony_provider\">Telephony Provider</string>\n    <string name=\"telephony_provider_help\">SIP URI host part used in calls to telephone numbers.\n        Factory default is account\\'s domain. If not given, this account cannot be used to call\n        telephone numbers.\n    </string>\n    <string name=\"numeric_keypad\">Numeric Keypad</string>\"\n    <string name=\"numeric_keypad_help\">If checked, numeric keypad is shown when \\\"Call to …\\\"\n        field is focused.</string>\"\n    <string name=\"invalid_sip_uri_hostpart\">Invalid SIP URI host part \\'%1$s\\'</string>\n    <string name=\"default_account\">Default Account</string>\n    <string name=\"default_account_help\">If checked, this account is selected when baresip is started.\n    </string>\n    <string name=\"custom_parameters\">Custom Parameters</string>\n    <string name=\"custom_parameters_help\">Semicolor separeted list of custom account parameters</string>\n    <!-- Accounts Activity -->\n    <string name=\"accounts\">Accounts</string>\n    <string name=\"new_account\">SIP URI of New Account</string>\n    <string name=\"accounts_help\">SIP URI of new account of form\n        &lt;user&gt;@&lt;domain&gt;[:&lt;port&gt;][;transport=udp|tcp|tls].\n        If &lt;port&gt; is given and transport protocol is not given, transport protocol defaults to UDP.\n        If &lt;port&gt; is not given and transport protocol is given, &lt;port&gt; defaults to 5060 or 5061 (TLS).\n        If neither is given and no outbound proxy is specified, account\\'s registrar (if any) is\n        determined solely based on domain\\'s DNS information.\n    </string>\n    <string name=\"invalid_aor\">Invalid user@domain[:port][;transport=udp|tcp|tls] \\'%1$s\\'</string>\n    <string name=\"account_exists\">Account \\'%1$s\\' already exists.</string>\n    <string name=\"account_allocation_failure\">\"Failed to allocate new account.</string>\n    <string name=\"encrypt_password\">Encrypt Password</string>\n    <string name=\"decrypt_password\">Decrypt Password</string>\n    <string name=\"delete_account\">Do you want to delete account \\'%1$s\\'\\?</string>\n    <!-- Baresip Service -->\n    <string name=\"is_calling\">is calling</string>\n    <string name=\"missed_call_from\">Missed call from</string>\n    <string name=\"missed_calls\">Missed calls</string>\n    <string name=\"missed_calls_count\" tools:ignore=\"PluralsCandidate\">%1$d missed calls</string>\n    <string name=\"transfer_request_to\">Call transfer request to</string>\n    <string name=\"call_auto_rejected\">Auto-rejected call from \\`%1$s\\`</string>\n    <string name=\"call_blocked\">Auto-rejected blocked call from \\`%1$s\\`</string>\n    <string name=\"message_blocked\">Auto-rejected blocked message from \\`%1$s\\`</string>\n    <!-- Blocked Activity -->\n    <string name=\"blocked\">Blocked</string>\n    <string name=\"blocked_calls\">Blocked Calls</string>\n    <string name=\"blocked_messages\">Blocked Messages</string>\n    <string name=\"blocked_delete_alert\">Do you want to delete blocked requests of \\'%1$s\\'\\?</string>\n    <string name=\"blocked_contact_question\">Do you want to add peer \\'%1$s\\' to contacts\\?</string>\n    <!-- Calls Activity -->\n    <string name=\"call_history\">Call History</string>\n    <string name=\"call_details\">Call Details</string>\n    <string name=\"call\">Call</string>\n    <string name=\"calls_calls\">calls</string>\n    <string name=\"calls_call\">call</string>\n    <string name=\"peer\">Peer</string>\n    <string name=\"direction\">Direction</string>\n    <string name=\"time\">Time</string>\n    <string name=\"calls_duration\">Duration</string>\n    <string name=\"calls_add_delete_question\">Do you want to add \\'%1$s\\' to contacts or delete\n        %2$s from call history\\?</string>\n    <string name=\"calls_delete_question\">Do you want to delete \\'%1$s\\' %2$s from call history\\?</string>\n    <string name=\"disable_history\">Disable</string>\n    <string name=\"enable_history\">Enable</string>\n    <string name=\"delete_history_alert\">Do you want to delete call history of account \\'%1$s\\'\\?</string>\n    <string name=\"call_answered\">Call answered</string>\n    <string name=\"call_answered_elsewhere\">Call answered elsewhere</string>\n    <string name=\"call_missed\">Call missed</string>\n    <string name=\"call_rejected\">Call rejected</string>\n    <!-- Call Details Activity -->\n    <string name=\"playing_recording\">Playing recording …</string>\n    <string name=\"save_recording\">Save Recording</string>\n    <string name=\"save_recording_question\">Do you want to save this recording?</string>\n    <string name=\"recording_saved\">Recording saved</string>\n    <string name=\"delete_call_alert\">Do you want to delete this call from history?</string>\n    <!-- Chat Activity -->\n    <string name=\"chat_with\">Chat with %1$s</string>\n    <string name=\"new_message\">New message</string>\n    <string name=\"long_message_question\">Do you want to delete message or add peer \\'%1$s\\' to contacts\\?</string>\n    <string name=\"short_message_question\">Do you want to delete message\\?</string>\n    <string name=\"add_contact\">Add Contact</string>\n    <string name=\"sending_failed\">Sending of message failed</string>\n    <string name=\"message_failed\">Failed</string>\n    <!-- Chats Activity -->\n    <string name=\"chats\">Chat History</string>\n    <string name=\"today\">Today</string>\n    <string name=\"you\">You</string>\n    <string name=\"new_chat_peer\">New Chat Peer</string>\n    <string name=\"long_chat_question\">Do you want to delete chat with peer \\'%1$s\\' or\n        add peer to contacts\\?</string>\n    <string name=\"short_chat_question\">Do you want to delete chat with \\'%1$s\\'\\?</string>\n    <string name=\"delete_chats_alert\">Do you want to delete chat history of account \\'%1$s\\'\\?</string>\n    <!-- Codecs Activity -->\n    <string name=\"audio_codecs\">Audio Codecs</string>\n    <string name=\"video_codecs\">Video Codecs</string>\n    <!-- Settings Activity -->\n    <string name=\"configuration\">Settings</string>\n    <string name=\"start_automatically\">Start Automatically</string>\n    <string name=\"start_automatically_help\">If checked, baresip starts automatically after device boots\n        or after a new version of baresip is installed. Start is delayed until device is unlocked.\n    </string>\n    <string name=\"appear_on_top_permission\">Automatic start needs Appear on Top permission.</string>\n    <string name=\"battery_optimizations\">Battery Optimizations</string>\n    <string name=\"battery_optimizations_help\">Disable battery optimizations (recommended) if you want\n        to reduce likelihood that Android restricts baresip\\'s access to network or enters baresip\n        to standby state.</string>\n    <string name=\"default_phone_app\">Default Phone App</string>\n    <string name=\"dialer_role_not_available\">Dialer role is not available</string>\n    <string name=\"default_phone_app_help\">If checked, baresip is the default phone app.  Do not check\n        if your device may need to handle also other than SIP calls or messages.</string>\n    <string name=\"listen_address\">Listen Address</string>\n    <string name=\"listen_address_help\">IP address and port of form \\'address:port\\' at which baresip listens\n        for incoming SIP requests.  If IP address is an IPv6 address, it must be written inside\n        brackets [].  IPv4 address 0.0.0.0 or IPv6 address [::] makes baresip listen at all\n        available addresses. If left empty (factory default), baresip listens at an arbitrary port at\n        all available addresses.\n    </string>\n    <string name=\"invalid_listen_address\">Invalid Listen Address</string>\n    <string name=\"_0_0_0_0_5060\" translatable=\"false\">0.0.0.0:5060</string>\n    <string name=\"address_family\">Address Family</string>\n    <string name=\"address_family_help\">Chooses which IP addresses baresip is\n        using.  If IPv4 or IPv6 is chosen, baresip uses only IPv4 or IPv6 addresses.  If neither is\n        chosen, baresip uses both IPv4 and IPv6 addresses.\n    </string>\n    <string name=\"transport_protocols\">Transport Protocols</string>\n    <string name=\"transport_protocols_help\">Comma separated list of supported SIP request/response\n        transport protocols. If left empty, default value is \\'udp,tcp,tls,ws,wss\\' that includes\n        all supported transport protocols.  List only the ones you need.  Use of UDP is not\n        recommended for security and other reasons.\n    </string>\n    <string name=\"invalid_transport_protocols\">Invalid Transport Protocols list</string>\n    <string name=\"dns_servers\">DNS Servers</string>\n    <string name=\"dns_servers_help\">Comma separated list of addresses of DNS servers.  If not given,\n        DNS server addresses are obtained dynamically from the system. Each DNS address is of form\n        \\'ip:port\\' or \\'ip\\'. If port is omitted, it defaults to 53.  If ip is an IPv6 address and\n        also port is given, ip must\n        be written inside brackets [].  As an example, list \\'8.8.8.8:53,[2001:4860:4860::8888]:53\\'\n        points to IPv4 and IPv6 addresses of public Google DNS servers.</string>\n    <string name=\"invalid_dns_servers\">Invalid DNS Servers</string>\n    <string name=\"failed_to_set_dns_servers\">Failed to set DNS servers</string>\n    <string name=\"tls_certificate_file\">TLS Certificate File</string>\n    <string name=\"tls_certificate_file_help\">If checked, a file containing TLS certificate and\n        private key of this baresip instance has been or will be loaded. In Android version 9,\n        a file called \\'cert.pem\\' is loaded from Download folder. For security reasons,\n        delete the file after loading.</string>\n    <string name=\"verify_server\">Verify Server Certificates</string>\n    <string name=\"verify_server_help\">If checked, baresip verifies TLS certificates of SIP User\n        Agent and SIP Proxy Servers when TLS transport is used.</string>\n    <string name=\"tls_ca_file\">TLS CA File</string>\n    <string name=\"tls_ca_file_help\">If checked, a file has been or will be loaded that contains\n        TLS certificates of such Certificate Authorities that are not included in Android OS.\n        In Android version 9, a file called \\'ca_certs.crt\\' is loaded from Download folder.</string>\n    <string name=\"no_read_permission\">No External Storage read permission</string>\n    <string name=\"unique_contact_uri\">Unique Contact URI</string>\n    <string name=\"unique_contact_uri_help\">If checked, Contact URI is guaranteed to be unique.\n        Needs to be checked if there is more than one account with the same SIP URI userpart,\n        but also improves protection of the accounts from attacks.</string>\n    <string name=\"audio_settings\">Audio Settings</string>\n    <string name=\"speaker_phone\">Speaker Phone</string>\n    <string name=\"speaker_phone_help\">If checked, speaker phone is turned automatically on\n        when call starts.</string>\n    <string name=\"audio_modules_title\">Audio Modules</string>\n    <string name=\"audio_modules_help\">Audio codecs provided by the checked modules are\n        available for use by the accounts.</string>\n    <string name=\"failed_to_load_module\">Failed to load module.</string>\n    <string name=\"microphone_gain\">Microphone Gain</string>\n    <string name=\"microphone_gain_help\">Multiply microphone volume by this decimal number.  Minimum\n        value is 1.0 (factory default) that disables microphone gain. Larger values may negatively\n        affect audio quality.</string>\n    <string name=\"invalid_microphone_gain\">Invalid Microphone Gain value</string>\n    <string name=\"opus_bit_rate\">Opus Bit Rate</string>\n    <string name=\"opus_bit_rate_help\">Average maximum bit rate used by Opus audio stream.\n        Valid values are 6000-510000. Factory default is 28000.</string>\n    <string name=\"opus_packet_loss\">Expected Opus packet-loss</string>\n    <string name=\"opus_packet_loss_help\">Expected Opus audio stream packet loss percentage,\n        from 0–100. Factory default value is 1. Value 0 also turns off Opus Forward Error\n        Correction (FEC).</string>\n    <string name=\"invalid_opus_bitrate\">Invalid Opus bitrate</string>\n    <string name=\"invalid_opus_packet_loss\">Invalid Opus Packet Loss Percentage</string>\n    <string name=\"audio_delay\">Audio Delay</string>\n    <string name=\"audio_delay_help\">Time (in milliseconds) to wait audio from callee when call is established.\n        Set to a higher value if you miss audio from callee at the beginning of the call.</string>\n    <string name=\"invalid_audio_delay\">Invalid Audio Delay \\'%1$s\\'. Valid values are from 100 to 3000.</string>\n    <string name=\"default_call_volume\">Default Call Volume</string>\n    <string name=\"default_call_volume_help\">If set, default call audio volume at scale 1–10.</string>\n    <string name=\"tone_country\">Tone Country</string>\n    <string name=\"tone_country_help\">Country of call ringing, waiting, and callee busy tones</string>\n    <string name=\"dark_theme\">Dark Theme</string>\n    <string name=\"dark_theme_help\">Force dark display theme</string>\n    <string name=\"dynamic_colors\">Dynamic Colors</string>\n    <string name=\"dynamic_colors_help\">Use dynamic colors if enabled in Android Settings</string>\n    <string name=\"colorblind\">Colorblind</string>\n    <string name=\"colorblind_help\">Use colorblind friendly registration status icons</string>\n    <string name=\"proximity_sensing\">Proximity Sensing</string>\">\n    <string name=\"proximity_sensing_help\">If checked, proximity sensing is active during calls.</string>\n    <string name=\"video_size\">Video Frame Size</string>\n    <string name=\"video_size_help\">Size of transmitted video frames (width x height)</string>\n    <string name=\"video_fps\">Video Frames Per Second</string>\n    <string name=\"video_fps_help\">Video frame rate that will be offered during the SDP handshake.\n        Valid values are from 10 to 30.</string>\n    <string name=\"invalid_fps\">Invalid Frames Per Second \\'%1$d\\'</string>\n    <string name=\"ringtone\">Ringtone</string>\n    <string name=\"select_ringtone\">Select Ringtone</string>\n    <string name=\"user_agent\">User Agent</string>\n    <string name=\"user_agent_help\">Custom SIP request/response User-Agent header field value</string>\n    <string name=\"invalid_user_agent\">Invalid User-Agent header field value</string>\n    <string name=\"contacts_help\">Chooses if baresip contacts, Android contacts, or both are used.\n        If both are used and a contact with the same name exists in both contacts,\n        the baresip contact will be chosen.</string>\n    <string name=\"both\">Both</string>\n    <string name=\"debug\">Debug</string>\n    <string name=\"debug_help\">If checked, provides debug and info level log messages to Logcat.</string>\n    <string name=\"sip_trace\">SIP Trace</string>\n    <string name=\"sip_trace_help\">If checked and if Debug is checked, Logcat messages include also SIP\n        request and response trace. Unchecked automatically at baresip start.</string>\n    <string name=\"reset_config\">Reset to Factory Defaults</string>\n    <string name=\"reset_config_help\">If checked, settings are reset to factory default values.</string>\n    <string name=\"reset_config_alert\">Are you sure you want to reset settings to factory\n        default values\\?</string>\n    <string name=\"reset\">Reset</string>\n    <string name=\"read_cert_error\">Failed to read file \\'cert.pem\\'.</string>\n    <string name=\"read_ca_certs_error\">Failed to read file \\'ca_certs.crt\\'.</string>\n    <string name=\"config_restart\">You need to restart baresip in order to activate the new\n        settings. Restart now\\?\n    </string>\n    <string name=\"consent_request\">Consent Request</string>\n    <string name=\"contacts_consent\">If Android contacts is chosen, they can be used\n        in calling and messaging as references to SIP and tel URIs. baresip app does not store Android\n        contacts nor share them with anyone.  In order to make Android contacts available in baresip,\n        Google requires that you accept their use as described here and in app\\'s\n        <a href=\"https://raw.githubusercontent.com/juha-h/baresip-studio/master/PrivacyPolicy.txt\">Privacy Policy</a>.\n    </string>\n    <!-- Contact Activity -->\n    <string name=\"new_contact\">New Contact</string>\n    <string name=\"contact_name\">Name</string>\n    <string name=\"sip_or_tel_uri\">SIP or tel URI</string>\n    <string name=\"user_domain_or_number\">user@domain or telephone number</string>\n    <string name=\"favorite\">Favorite</string>\n    <string name=\"favorite_help\">If checked, contact is shown among other favorites\n        on top of contacts list.</string>\n    <string name=\"invalid_contact\">Invalid contact name \\'%1$s\\'</string>\n    <string name=\"contact_already_exists\">Contact \\'%1$s\\' already exists.</string>\n    <string name=\"android_contact_help\">If checked, this contact is added to Android contacts.</string>\n    <string name=\"avatar_image\">Profile image</string>\n    <!-- Contacts Activity -->\n    <string name=\"contacts\">Contacts</string>\n    <string name=\"contact_action_question\">Do you want to call or send message to \\'%1$s\\'\\?</string>\n    <string name=\"send_message\">Send Message</string>\n    <string name=\"contact_delete_question\">Do you want to delete contact \\'%1$s\\'\\?</string>\n    <string name=\"search\">Search</string>\n    <!-- Generic -->\n    <string name=\"alert\">Alert</string>\n    <string name=\"info\">Info</string>\n    <string name=\"notice\">Notice</string>\n    <string name=\"cancel\">Cancel</string>\n    <string name=\"stop\">Stop</string>\n    <string name=\"reply\">Reply</string>\n    <string name=\"save\">Save</string>\n    <string name=\"ok\">OK</string>\n    <string name=\"yes\">Yes</string>\n    <string name=\"no\">No</string>\n    <string name=\"accept\">Accept</string>\n    <string name=\"deny\">Deny</string>\n    <string name=\"sip_uri\" translatable=\"false\">SIP URI</string>\n    <string name=\"add\">Add</string>\n    <string name=\"delete\">Delete</string>\n    <string name=\"edit\">Edit</string>\n    <string name=\"status\">Status</string>\n    <string name=\"error\">Error</string>\n    <string name=\"confirmation\">Confirmation</string>\n    <string name=\"anonymous\">Anonymous</string>\n    <string name=\"unknown\">Unknown</string>\n    <string name=\"dots\" translatable=\"false\">…</string>\n    <string name=\"bullet_item\" translatable=\"false\">\\u2022 %1$s</string>\n    <string name=\"invalid_sip_or_tel_uri\">Invalid SIP or tel URI \\'%1$s\\'</string>\n    <string name=\"baresip\" translatable=\"false\">baresip</string>\n    <string name=\"android\" translatable=\"false\">Android</string>\n    <!-- Main Activity -->\n    <string name=\"backup\">Backup</string>\n    <string name=\"restore\">Restore</string>\n    <string name=\"logcat\" translatable=\"false\">Logcat</string>\n    <string name=\"about\">About</string>\n    <string name=\"restart\">Restart</string>\n    <string name=\"quit\">Quit</string>\n    <string name=\"outgoing_call_to_dots\">Call to …</string>\n    <string name=\"incoming_call_from_dots\">Call from …</string>\n    <string name=\"diverted_by_dots\">Diverted by …</string>\n    <string name=\"no_telephony_provider\">Account \\'%1$s\\' has no Telephony Provider</string>\n    <string name=\"video_call\">Video call</string>\n    <string name=\"video_request\">Video Request</string>\n    <string name=\"allow_video\">Accept sending and receiving video with \\'%1$s\\'\\?</string>\n    <string name=\"allow_video_send\">Accept sending of video to \\'%1$s\\'\\?</string>\n    <string name=\"allow_video_recv\">Accept receiving video from \\'%1$s\\'\\?</string>\n    <string name=\"call_is_ringing\">Call is ringing</string>\n    <string name=\"call_is_on_hold\">Call is on hold</string>\n    <string name=\"call_is_connected\">Call is connected</string>\n    <string name=\"rec_in_call\">Recording can be turned on or off only when call is not\n        connected</string>\n    <string name=\"call_transfer\">Call Transfer</string>\n    <string name=\"blind\">Blind</string>\n    <string name=\"attended\">Attended</string>\n    <string name=\"transfer_destination\">Transfer destination</string>\n    <string name=\"choose_destination_uri\">Choose destination URI</string>\n    <string name=\"transfer\">Transfer</string>\n    <string name=\"transfer_failed\">Transfer failed</string>\n    <string name=\"replaces_not_supported\">Peer does not support REPLACES feature</string>\n    <string name=\"dtmf\">DTMF</string>\n    <string name=\"call_info\">Call Info</string>\n    <string name=\"call_info_not_available\">No info available</string>\n    <string name=\"duration\">Duration: %1$d (secs)</string>\n    <string name=\"codecs\">Codecs</string>\n    <string name=\"rate\">Current Rate: %1$s (Kbits/s)</string>\n    <string name=\"average_rate\">Average Rate: %1$s (Kbits/s)</string>\n    <string name=\"packets\">Packets</string>\n    <string name=\"lost\">Lost</string>\n    <string name=\"jitter\">Jitter: %1$s (ms)</string>\n    <string name=\"voicemail_messages\">Voicemail Messages</string>\n    <string name=\"you_have\">You have</string>\n    <string name=\"one_new_message\">one new message</string>\n    <string name=\"new_messages\">new messages</string>\n    <string name=\"one_old_message\">one old message</string>\n    <string name=\"old_messages\">old messages</string>\n    <string name=\"and\">and</string>\n    <string name=\"no_messages\">You have no messages</string>\n    <string name=\"listen\">Listen</string>\n    <string name=\"call_already_active\">You already have an active call.</string>\n    <string name=\"start_failed\">Baresip failed to start. This may be due to an invalid Settings value.\n        Check Listen Address, TLS Certificate File, and TLS CA File.  Then restart baresip.\n    </string>\n    <string name=\"registering_failed\">Registering of \\`%1$s\\` failed.</string>\n    <string name=\"verify\">Verify Request</string>\n    <string name=\"verify_sas\">Do you want to verify SAS &lt;%1$s&gt;\\?</string>\n    <string name=\"transfer_request\">Transfer Request</string>\n    <string name=\"transfer_request_query\">Do you accept to transfer this call to \\'%1$s\\'\\?</string>\n    <string name=\"call_request\">Call Request</string>\n    <string name=\"call_request_query\">Do you accept request to call \\'%1$s\\'\\?</string>\n    <string name=\"redirect_notice\">Automatic redirection to \\'%1$s\\'\\</string>\n    <string name=\"redirect_request\">Redirect Request</string>\n    <string name=\"redirect_request_query\">Do you accept call redirection to \\'%1$s\\'\\?</string>\n    <string name=\"call_failed\">Call failed</string>\n    <string name=\"call_closed\">Call is closed</string>\n    <string name=\"call_not_secure\">This call is NOT secure!</string>\n    <string name=\"peer_not_verified\">This call is SECURE, but peer is NOT verified!</string>\n    <string name=\"call_is_secure\">This call is SECURE and peer is VERIFIED!\n        Do you want to unverify the peer\\?\n    </string>\n    <string name=\"unverify\">Unverify</string>\n    <string name=\"backed_up\">Application data (excluding recordings) backed up\n        to file \\'%1$s\\'. In Android version 9, the file is in Download folder.</string>\n    <string name=\"backup_failed\">Failed to back up application data to file\n        \\'%1$s\\'. Check Apps → baresip → Permissions → Storage.</string>\n    <string name=\"restart_request\">Restart Request</string>\n    <string name=\"restored\">Application data restored. baresip needs to be restarted.\n        Restart now\\?\n    </string>\n    <string name=\"restore_failed\">Failed to restore application data. Check that you gave correct\n        password and that the backup file is from this application. In Android versions 9,\n        also check Apps → baresip → Permissions → Storage and that file \\'%1$s\\' exists\n        in Download folder.\n    </string>\n    <string name=\"restore_unzip_failed\">Failed to restore application data. Android version 14 and\n        above does not allow restoring data that was backed up before %1$s version %2$s.\n    </string>\n    <string name=\"no_notifications\">You are not able to use this application without \\\"Notifications\\\"\n        permission.</string>\n    <string name=\"no_calls\">baresip needs \\\"Microphone\\\" permission for voice calls.</string>\n    <string name=\"no_video_calls\">Grant \\\"Camera\\\" permission to make or answer video calls.</string>\n    <string name=\"no_backup\">You are not able create backup without \\\"Storage\\\" permission.</string>\n    <string name=\"no_restore\">You are not able restore backup without \\\"Storage\\\" permission.</string>\n    <string name=\"no_android_contacts\">You are not able to access Android contacts without\n        \\\"Contacts\\\" permission.</string>\n    <string name=\"no_cameras\">No supported video cameras!</string>\n    <string name=\"no_network\">No network connection!</string>\n    <string name=\"no_aec\">No hardware Acoustic Echo Cancellation!</string>\n    <string name=\"audio_focus_denied\">Audio focus denied!</string>\n    <string name=\"permissions_rationale\">Permissions rationale</string>\n    <string name=\"audio_permissions\">baresip needs \\\"Microphone\\\" permission for voice calls,\n        \\\"Nearby devices\\\" permission for Bluetooth microphone/speaker detection,\n        \\\"Notifications\\\" permission for posting notifications, and in Android 9\n        \\\"Storage\\\" permission for Backup/Restore operations.</string>\n    <string name=\"audio_and_video_permissions\">baresip+ needs \\\"Microphone\\\" permission\n        for voice calls, \\\"Camera\\\" permission for video calls, \\\"Nearby devices\\\" permission\n        for Bluetooth microphone/speaker detection, \\\"Notifications\\\" permission for posting\n        notifications, and in Android 9 \\\"Storage\\\" permissions for Backup/Restore operations.</string>\n    <string name=\"call_recording_title\">Call Recording</string>\n    <string name=\"call_recording_tip\">If activated, new incoming and outgoing calls will be recorded.\n        Recordings can be played on Call Details page.</string>\n    <string name=\"microphone_title\">Microphone</string>\n    <string name=\"microphone_tip\">If activated during call, microphone is muted.</string>\n    <string name=\"speakerphone_title\">Speakerphone</string>\n    <string name=\"speakerphone_tip\">If activated, audio is played via device speakerphone.</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-ar/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-bg/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <!-- About Activity -->\n    <string name=\"about_title\">Относно baresip</string>\n    <string name=\"about_text\"><![CDATA[\n        <h1>SES потребителско приложение, базирано на библиотеката Baresip</h1>\n        <p>Juha Heinanen &lt;jh@tutpro.com&gt;</p>\n        <p>версия %1$s</p>\n        <h2>Съвети за употреба</h2>\n        <ul>\n            <li>Проверете дали стойностите по подразбиране на конфигурацията отговарят на вашите нужди (заглавия на елементите с докосване).</li>\n            <li>След това създайте един или повече акаунти (отново докоснете заглавия на артикули за помощ).</li>\n            <li>Участниците в обажданията и съобщенията могат да се добавят към контактите с по-продължителни докосвания.</li>\n            <li>По същия начин може да се премахват повиквания, чатове, съобщения и контакти.</li>\n            <li>Имате възможност да преизберете повторно последния абонат, като кликнете на иконата за повикване, когато полето е празно.</li>\n            <li>Ако не можете да чуете другата страна по време на разговор, опитайте се да увеличите силата на звука на Media устройството си или задайте силата на звука на разговора по подразбиране в конфигурация.</li>\n        </ul>\n\t\t<h2>Програмен код</h2>\n        Изходният код е достъпен на адрес <a href=\"https://github.com/juha-h/baresip-studio\">GitHub</a>,\n        където също могат да се докладват проблеми.\n        ]]></string>\n    <!-- Account Activity -->\n    <string name=\"account\">Акаунт</string>\n    <string name=\"display_name\">Показване на име</string>\n    <string name=\"display_name_help\">Публично име (ако има такова), използвано в URI на изходящи заявки.</string>\n    <string name=\"authentication_username\">Потребителско име за удостоверяване</string>\n    <string name=\"authentication_username_help\">Потребителско име за удостоверяване, ако се изисква от изходящ прокси.</string>\n    <string name=\"authentication_password\">Парола за удостоверяване</string>\n    <string name=\"authentication_password_help\">Парола за удостоверяване, ако се изисква от изходящ прокси.</string>\n    <string name=\"outbound_proxies\">Изходящи прокси</string>\n    <string name=\"outbound_proxies_help\">SIP URI на един или два прокси сървъра, които трябва да се използват при изпращане на заявки. \n\t\tАко са дадени две, заявките за РЕГИСТРИРАНЕ се изпращат и до двете, и до други заявки \n\t\tтази, която отговаря. Ако не е даден изходящ прокси, заявките се изпращат въз основа на \n\t\tDNS NAPTR / SRV / Търсене на запис на хоризонталната част на URI на повикващия. Ако хостпарт на SIP URI е IPv6 \n\t\tадрес, адресът трябва да бъде написан в скоби []. \n\\nExamples: \n\\n • sip:foo.com:5060;transport=tls \n\\n • sip:[2001:67c:223:777::10]:5060;transport=tcp \n\t</string>\n    <string name=\"sip_uri_of_proxy_server\">SIP URI на прокси сървър</string>\n    <string name=\"sip_uri_of_another_proxy_server\">SIP URI на друг прокси сървър</string>\n    <string name=\"register\">Регистрирай</string>\n    <string name=\"register_help\">Ако е отметнато, регистрацията е активирана и заявките за РЕГИСТРИРАНЕ се изпращат на \n\t\t12 минути интервали. </string>\n    <string name=\"audio_codecs\">Аудио кодеци</string>\n    <string name=\"media_nat\">NAT Traversal Average</string>\n    <string name=\"media_nat_help\">Избира протокол за преминаване на носител NAT (ако има такъв). Възможният избор е STUN \n\t\t(Помощни програми за сесионно преминаване за NAT, RFC 5389) и ICE (Интерактивна свързаност \n\t\tУчредяване, RFC 5245). \n\t</string>\n    <string name=\"stun_server\">СТУН Сървър</string>\n    <string name=\"stun_server_help\">STUN сървър на хост на формуляра [: port]. \n\t\tФабричната стойност по подразбиране е \\'stun.l.google.com:19302\\', сочещи към обществен сървър на Google STUN. \n\t\tВ момента потребителското име и паролата не се поддържат. </string>\n    <string name=\"media_encryption\">Шифроване на медиите</string>\n    <string name=\"media_encryption_help\">Избира протокол за криптиране на медийния транспорт (ако има такъв). \n\\n • ZRTP (препоръчително) означава, че след изпробване на преговорите за криптиране на медиите от край до край ZRTP \n\t\t\tразговорът е установен. \n\\n • DTLS-SRTPF означава, че UDP / TLS / RTP / SAVPF се предлага при изходящо повикване и RTP / SAVP, \n\t\t\tRTP / SAVPF, UDP / TLS / RTP / SAVP или UDP / TLS / RTP / SAVPF се използва, ако се предлага при входящо повикване. \n\\n • SRTP-MANDF означава, че RTP / SAVPF се предлага при изходящо повикване и се изисква при входящо повикване. \n\\n • SRTP-MAND означава, че RTP / SAVP се предлага при изходящо повикване и се изисква при входящо повикване. \n\\n • SRTP означава, че RTP / AVP се предлага при изходящо повикване и че RTP / SAVP или RTP / SAVPF се използва \n\t\t\tако се предлага при входящо повикване. \n\t</string>\n    <string name=\"answer_mode\">Режим на отговори</string>\n    <string name=\"answer_mode_help\">Избира как се отговаря на входящите повиквания.</string>\n    <string name=\"manual\">Ръчно</string>\n    <string name=\"auto\">Автоматично</string>\n    <string name=\"voicemail_uri\">URI на гласова поща</string>\n    <string name=\"voicemain_uri_help\">SIP URI за проверка на гласови съобщения. Ако се остави празно, съобщенията на гласовата поща \n\t\t(Индикации за чакане на съобщение) не са активирани. \n\t</string>\n    <string name=\"default_account\">Профил по подразбиране</string>\n    <string name=\"default_account_help\">Ако е отметнато, този акаунт се избира при стартиране на baresip. </string>\n    <!-- Accounts Activity -->\n    <string name=\"accounts\">Профили</string>\n    <string name=\"invalid_aor\">Невалиден потребител@домейн \\'%1$s\\'</string>\n    <string name=\"account_exists\">Профила \\'%1$s\\' вече съществува.</string>\n    <string name=\"account_allocation_failure\">\"Неуспешно разпределяне на нов акаунт.</string>\n    <string name=\"encrypt_password\">Шифроване на парола</string>\n    <string name=\"decrypt_password\">Дешифриране на парола</string>\n    <string name=\"delete_account\">Искате ли да изтриете акаунта \\'%1$s\\'?</string>\n    <!-- Baresip Service -->\n    <string name=\"transfer_request\">Заявка за прехвърляне на обаждане до</string>\n    <!-- Calls Activity -->\n    <string name=\"call_history\">История на обажданията</string>\n    <string name=\"call\">Обади се</string>\n    <string name=\"calls_calls\">повиквания</string>\n    <string name=\"calls_call\">обади се</string>\n    <string name=\"calls_add_delete_question\">Искате ли да добавите \\'%1$s\\' към контакти или изтриване\n\t\t%2$s от историята на обажданията? \n\t</string>\n    <string name=\"calls_delete_question\">Искате ли да изтриете \\'%1$s\\' %2$s от историята на обажданията? </string>\n    <string name=\"disable_history\">Забрани история на обаждания</string>\n    <string name=\"enable_history\">Разреши история на обаждания</string>\n    <string name=\"delete_history_alert\">Искате ли да изтриете историята на обажданията на акаунта \\'%1$s\\'?</string>\n    <!-- Chat Activity -->\n    <string name=\"chat_with\">Чатя с %1$s</string>\n    <string name=\"new_message\">Ново съобщение</string>\n    <string name=\"long_message_question\">Искате ли да изтриете съобщението или да добавите потребителя \\'%1$s\\' в контакти?</string>\n    <string name=\"short_message_question\">Искате ли да изтриете съобщението?</string>\n    <string name=\"add_contact\">Добави контакт</string>\n    <string name=\"sending_failed\">Изпращането на съобщението не бе успешно</string>\n    <string name=\"message_failed\">Неуспешно</string>\n    <!-- Chats Activity -->\n    <string name=\"chats\">История на чата</string>\n    <string name=\"today\">днес</string>\n    <string name=\"you\">Вие</string>\n    <string name=\"new_chat_peer\">Нов потребител за чат</string>\n    <string name=\"long_chat_question\">Искате ли да изтриете чата с този потребител \\'%1$s\\' или\n\t\tда го добавяне в контактите?</string>\n    <string name=\"short_chat_question\">Искате ли да изтриете чата с \\'%1$s\\'?</string>\n    <string name=\"delete_chats_alert\">Искате ли да изтриете историята на чата на акаунта \\'%1$s\\'?</string>\n    <!-- Config Activity -->\n    <string name=\"configuration\">Конфигуриране</string>\n    <string name=\"start_automatically\">Стартирайте автоматично</string>\n    <string name=\"start_automatically_help\">Ако е отметнато, baresip се стартира автоматично след включване на устройството.</string>\n    <string name=\"listen_address\">Слушане на адрес</string>\n    <string name=\"listen_address_help\">IP адрес и порт на формата \\'адрес: порт\\', на който baresip слуша \n\t\tза входящи SIP заявки. Ако IP адресът е IPv6 адрес, той трябва да бъде написан вътре в скоби []. \n\t\tIPv4 адрес 0.0.0.0 или IPv6 адрес [::] позволява на baresip да слуша всички налични адреси. \n\t\tАко се остави празно (фабрично по подразбиране), baresip ще слуша на порт 5060 от всички налични адреси. \n\t</string>\n    <string name=\"invalid_listen_address\">Невалиден адрес за слушане</string>\n    <string name=\"dns_servers\">DNS сървъри</string>\n    <string name=\"dns_servers_help\">Списък разделен със запетая на адреси на DNS сървъри. Ако не е зададено, \n\t\tадресите на DNS сървъра се получават динамично от системата. Всеки DNS адрес е във форма \\'ip:port\\' \n\t\tили \\'ip\\'. Ако портът е пропуснат, той по подразбиране е 53. Ако ip е IPv6 адрес и също е зададен порт, \n\t\tip трябва да бъде написани вътре в скоби []. Като пример, посочете \\`8.8.8.8:53,[2001:4860:4860::8888]:53 \\'\n\t\tсочи към IPv4 и IPv6 адреси на обществени DNS сървъри на Google.\n\t</string>\n    <string name=\"invalid_dns_servers\">Невалидни DNS сървъри</string>\n    <string name=\"failed_to_set_dns_servers\">Неуспешно задаване на DNS сървъри</string>\n    <string name=\"tls_certificate_file\">Файл за сертификати TLS</string>\n    <string name=\"tls_certificate_file_help\">Ако е поставена отметка, файл \\'cert.pem\\' съдържащ TLS сертификатът и личният ключ на този екземпляр baresip е бил или ще бъде зареден от директорията за Изтегляне. От съображения за сигурност изтрийте файла след зареждането му.</string>\n    <string name=\"tls_ca_file\">TLS CA File</string>\n    <string name=\"tls_ca_file_help\">Ако е поставено отметка, файл \\'ca_certs.crt\\' съдържащи TLS сертификати на Сертифициращите органи е бил или ще бъде зареден от директорията за Изтегляне.</string>\n    <string name=\"opus_bit_rate\">Opus Bit Rate</string>\n    <string name=\"opus_bit_rate_help\">Средна максимална битова скорост, използвана от аудио поток на Opus. \n\t\tВалидните стойности са 6000-510000. Фабрично по подразбиране е 28000.\n\t</string>\n    <string name=\"opus_packet_loss\">Очаквана загуба на пакети Opus</string>\n    <string name=\"opus_packet_loss_help\">Очакван процент загуба на пакет от аудио поток на Opus, от 0–100. \n\t\tПо подразбиране 0 изключване на Opus Forward Error Correction (FEC).\n\t</string>\n    <string name=\"invalid_opus_bitrate\">Невалиден битрейт на Opus</string>\n    <string name=\"invalid_opus_packet_loss\">Невалиден процент загуба на пакет Opus</string>\n    <string name=\"default_call_volume\">Усилване на повикването по подразбиране</string>\n    <string name=\"default_call_volume_help\">Ако се задава, трябва да се избира сила на звука при \n\t\tповикване по подразбиране в мащаб 1–10.\n\t</string>\n    <string name=\"debug\">Debug</string>\n    <string name=\"debug_help\">Осигурява наличие на съобщения за грешки и информация на ниво информация в Logcat.</string>\n    <string name=\"reset_config\">Възстановяване на фабричните настройки</string>\n    <string name=\"reset_config_help\">Ако е отметнато, конфигурацията се нулира до фабрични стойности по подразбиране</string>\n    <string name=\"read_cert_error\">Неуспешно четене на файл \\'cert.pem\\' от директорията за изтегляне.</string>\n    <string name=\"read_ca_certs_error\">Файлът не беше прочетен \\'ca_certs.crt\\' от директорията за изтегляне.</string>\n    <string name=\"config_restart\">Трябва да рестартирате baresip, за да активирате новата конфигурация. Рестартирай сега?</string>\n    <!-- Contact Activity -->\n    <string name=\"new_contact\">Нов контакт</string>\n    <string name=\"contact_name\">име</string>\n    <string name=\"invalid_contact\">Невалидно име за контакт \\'%1$s\\'</string>\n    <string name=\"contact_already_exists\">Този контакт \\'%1$s\\' вече съществува.</string>\n    <!-- Contacts Activity -->\n    <string name=\"contacts\">Контакти</string>\n    <string name=\"contact_action_question\">Искате ли да се обадите или да изпратите съобщение до \\'%1$s\\'?</string>\n    <string name=\"send_message\">Изпрати съобщение</string>\n    <string name=\"contact_delete_question\">Искате ли да изтриете този контакт \\'%1$s\\'?</string>\n    <!-- Generic -->\n    <string name=\"alert\">Внимание</string>\n    <string name=\"info\">Информация</string>\n    <string name=\"notice\">известие</string>\n    <string name=\"cancel\">Отказ</string>\n    <string name=\"ok\">Добре</string>\n    <string name=\"yes\">да</string>\n    <string name=\"no\">не</string>\n    <string name=\"accept\">приемам</string>\n    <string name=\"deny\">отказвам</string>\n    <string name=\"add\">Добави</string>\n    <string name=\"delete\">Изтрий</string>\n    <string name=\"edit\">Редактиране</string>\n    <string name=\"status\">Статус</string>\n    <string name=\"error\">Грешка</string>\n    <!-- Main Activity -->\n    <string name=\"backup\">Създай Резервно копие</string>\n    <string name=\"restore\">Възстанови от Резервно копие</string>\n    <string name=\"about\">Относно</string>\n    <string name=\"restart\">Рестартирай</string>\n    <string name=\"quit\">Отписване</string>\n    <string name=\"outgoing_call_to_dots\">Изходящо повикване до…</string>\n    <string name=\"incoming_call_from_dots\">Входящо обаждане от…</string>\n    <string name=\"dtmf\">DTMF</string>\n    <string name=\"call_info\">Информация за обаждане</string>\n    <string name=\"duration\">Продължителност %1$d (s)</string>\n    <string name=\"codecs\">Кодеци</string>\n    <string name=\"rate\">Скорост: %1$s</string>\n    <string name=\"voicemail_messages\">Съобщения за гласова поща</string>\n    <string name=\"you_have\">Ти имаш</string>\n    <string name=\"one_new_message\">едно ново съобщение</string>\n    <string name=\"new_messages\">Нови съобщения</string>\n    <string name=\"one_old_message\">Едно старо съобщение</string>\n    <string name=\"old_messages\">Стари съобщения</string>\n    <string name=\"and\">и</string>\n    <string name=\"no_messages\">Нямате съобщения</string>\n    <string name=\"listen\">Слушам</string>\n    <string name=\"call_already_active\">Вече имате активно обаждане.</string>\n    <string name=\"start_failed\">Baresip не успя да стартира. Това може да се дължи на невалиден адрес за слушане или TLS файл. \n\t\tТе са нулирани. Рестартирайте baresip. \n\t</string>\n    <string name=\"registering_failed\">Регистрация на \\`%1$s\\` се провали.</string>\n    <string name=\"verify\">Потвърди</string>\n    <string name=\"verify_sas\">Искате ли да потвърдите &lt;%1$s&gt;\\?</string>\n    <string name=\"transfer_request_query\">Приемате ли да прехвърлите обаждане до \\'%1$s\\'?</string>\n    <string name=\"call_failed\">Обаждането не бе успешно</string>\n    <string name=\"call_closed\">Обаждането е затворено</string>\n    <string name=\"call_not_secure\">Това обаждане НЕ е сигурно!</string>\n    <string name=\"peer_not_verified\">Това обаждане е СИГУРНО, но другият абонат НЕ е потвърдил!</string>\n    <string name=\"call_is_secure\">Този разговор е СИГУРЕН и другият абонат е ПРОВЕРЕН! Искате ли да го потвърдите? </string>\n    <string name=\"unverify\">Отмяна на потвърждението</string>\n    <string name=\"backed_up\">Данните от приложението са архивирани във файла за изтегляне на папки \\'%1$s\\'.</string>\n    <string name=\"backup_failed\">Неуспешно архивиране на данни от приложението за изтегляне на файла с папки \\'%1$s\\'. Проверете Приложения → baresip → Разрешения → Съхранение</string>\n    <string name=\"restored\">Данните за приложението са възстановени. baresip трябва да се рестартира. Рестартирай сега?</string>\n    <string name=\"restore_failed\">Възстановяването на данните на приложението от папката за изтегляне не бе успешно. Проверете Приложения → baresip → Разрешения → Съхранение и този архивен файл \\'%1$s\\' съществува в папката и ако е така, вие сте дали правилна парола за дешифриране.</string>\n    <string name=\"audio_modules_title\">Аудио модули</string>\n    <string name=\"audio_modules_help\">Аудио кодеци предоставени от проверените модули могат да бъдат използвани от акаунтите.</string>\n    <string name=\"failed_to_load_module\">Не могат да бъдат заредени модулите.</string>\n    <string name=\"no_calls\">Разговори не могат да бъдат осъществени без разрешен достъп до микрофона.</string>\n    <string name=\"accounts_help\">Порта и транспортния протокол на акаунта могат да бъдат посочени по избор, когато се създаде нов акаунт: потребителско име@домейн[:port] [;transport=udp|tcp|tls]. Ако е даден порт и транспортният протокол не е даден, транспортният протокол по подразбиране е udp. Ако порт не е даден и транспортният протокол е даден, портът по подразбиране е 5060 или 5061 (tls). Ако нито едното не е посочено и не е посочен изходящ прокси, регистраторът на акаунта (ако има такъв) се определя единствено въз основа на DNS информация от домейна.</string>\n    <string name=\"new_account\">Нов Акаунт</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-ca/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources></resources>"
  },
  {
    "path": "app/src/main/res/values-cs/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"about_title\">O aplikaci baresip</string>\n    <string name=\"about_title_plus\">O aplikaci baresip+</string>\n    <string name=\"account_nickname_help\">Přezdívka (pokud existuje) používaná k identifikaci tohoto účtu v aplikaci baresip.</string>\n    <string name=\"nickname\">Přezdívka</string>\n    <string name=\"authentication_password\">Heslo</string>\n    <string name=\"outbound_proxies\">Odchozí proxy servery</string>\n    <string name=\"register\">Registrace</string>\n    <string name=\"invalid_proxy_server_uri\">Neplatná URI proxy serveru \\'%1$s\\'</string>\n    <string name=\"sip_uri_of_proxy_server\">SIP URI proxy serveru</string>\n    <string name=\"outbound_proxies_help\">SIP URI jednoho nebo dvou proxy serverů, které je třeba použít při odesílání požadavků. Pokud jsou zadány dva, jsou požadavky REGISTER zasílány oběma a ostatní požadavky jsou zasílány tomu, který odpoví. Pokud není zadán žádný odchozí proxy server, jsou požadavky odesílány na základě vyhledávání záznamů DNS NAPTR/SRV/A z hostitelského jména URI volaného. Pokud je hostitelské jméno URI SIP adresa IPv6, musí být adresa zapsána v závorkách [].\n\\nPříklady:\n\\n - sip:example.com:5061;transport=tls\n\\n • sip:[2001:67c:223:777::10];transport=tcp\n\\n • sip:192.168.43.50:443;transport=wss</string>\n    <string name=\"sip_uri_of_another_proxy_server\">SIP URI dalšího proxy serveru</string>\n    <string name=\"reg_int_help\">Udává, jak často (v sekundách) baresip odesílá požadavky REGISTER. Platné hodnoty jsou od 60 do 3600.</string>\n    <string name=\"invalid_reg_int\">Neplatný interval registrace \\'%1$s\\'</string>\n    <string name=\"register_help\">Pokud je zatrženo, registrace je povolena a požadavky REGISTER jsou odesílány v intervalu určeném parametrem Interval registrace.</string>\n    <string name=\"reg_int\">Interval registrace</string>\n    <string name=\"answer_mode_help\">Vybírá způsob přijímání příchozích hovorů.</string>\n    <string name=\"media_nat\">Průchod médií skrze NAT</string>\n    <string name=\"invalid_stun_server\">Neplatná URI serveru STUN/TURN \\'%1$s\\'</string>\n    <string name=\"media_nat_help\">Vybírá protokol pro průchod médií skrze NAT (pokud existuje). Možné volby jsou STUN (Session Traversal Utilities for NAT, RFC 5389) a ICE (Interactive Connectivity Establishment, RFC 5245).</string>\n    <string name=\"stun_server\">Server STUN/TURN</string>\n    <string name=\"stun_username_help\">Uživatelské jméno, pokud ho server STUN/TURN vyžaduje</string>\n    <string name=\"stun_server_help\">URI serveru STUN/TURN ve tvaru scheme:host[:port][\\?transport=udp|tcp], kde scheme je \\\"stun\\\", \\\"stuns\\\", \\\"turn\\\" nebo \\\"turns\\\". Výchozí server STUN pro protokoly STUN a ICE je \\'stun:stun.l.google.com:19302\\', který ukazuje na veřejný server STUN společnosti Google. Výchozí server TURN neexistuje.</string>\n    <string name=\"stun_password\">Heslo STUN/TURN</string>\n    <string name=\"stun_password_help\">Heslo, pokud ho server STUN/TURN vyžaduje</string>\n    <string name=\"invalid_stun_password\">Neplatné heslo \\'%1$s\\'</string>\n    <string name=\"media_encryption\">Šifrování médií</string>\n    <string name=\"rtcp_mux_help\">Pokud je zatrženo, pakety RTP a RTCP jsou multiplexovány na jednom portu (RFC 5761).</string>\n    <string name=\"rtcp_mux\">Multiplexování RTCP</string>\n    <string name=\"authentication_username_help\">Uživatelské jméno pro ověřování, pokud je vyžadováno ověřování požadavků SIP. Výchozí hodnotou je uživatelské jméno účtu.</string>\n    <string name=\"telephony_provider\">Poskytovatel telefonních služeb</string>\n    <string name=\"time\">Čas</string>\n    <string name=\"display_name\">Zobrazované jméno</string>\n    <string name=\"display_name_help\">Název (pokud existuje) použitý v URI odchozích požadavků.</string>\n    <string name=\"invalid_authentication_username\">Neplatné uživatelské jméno pro ověřování \\'%1$s\\'</string>\n    <string name=\"authentication_password_help\">Ověřovací heslo až 64 znaků. Pokud je zadáno Uživatelské jméno pro ověřování, ale není zadáno Heslo, bude po spuštění baresipu požadováno.</string>\n    <string name=\"peer\">Účastník</string>\n    <string name=\"direction\">Směr</string>\n    <string name=\"calls_add_delete_question\">Chcete přidat \\'%1$s\\' do kontaktů nebo odstranit %2$s z historie hovorů\\?</string>\n    <string name=\"delete_history_alert\">Chcete odstranit historii volání účtu \\\"%1$s\\\"\\?</string>\n    <string name=\"calls_duration\">Trvání</string>\n    <string name=\"long_chat_question\">Chcete smazat chat s účastníkem \\'%1$s\\' nebo přidat účastníka do kontaktů\\?</string>\n    <string name=\"long_message_question\">Chcete odstranit zprávu nebo přidat účastníka \\'%1$s\\' do kontaktů\\?</string>\n    <string name=\"disable_history\">Zakázat</string>\n    <string name=\"new_message\">Nová zpráva</string>\n    <string name=\"short_message_question\">Chcete zprávu odstranit\\?</string>\n    <string name=\"calls_delete_question\">Chcete odstranit \\'%1$s\\' %2$s z historie hovorů\\?</string>\n    <string name=\"chat_with\">Chatovat s %1$s</string>\n    <string name=\"sending_failed\">Odeslání zprávy se nezdařilo</string>\n    <string name=\"you\">Vy</string>\n    <string name=\"new_chat_peer\">Nový účastník chatu</string>\n    <string name=\"short_chat_question\">Chcete odstranit chat s \\'%1$s\\'\\?</string>\n    <string name=\"add_contact\">Přidat kontakt</string>\n    <string name=\"chats\">Historie chatu</string>\n    <string name=\"today\">Dnes</string>\n    <string name=\"message_failed\">Nepodařilo se</string>\n    <string name=\"invalid_dns_servers\">Neplatné servery DNS</string>\n    <string name=\"delete_chats_alert\">Chcete odstranit historii chatu účtu \\'%1$s\\'\\?</string>\n    <string name=\"audio_codecs\">Zvukové kodeky</string>\n    <string name=\"tls_certificate_file_help\">Pokud je zaškrtnuto, byl nebo bude načten soubor obsahující certifikát TLS a soukromý klíč této instance baresipu. Ve verzi systému Android 9 se soubor s názvem \\'cert.pem\\' načte ze složky Download. Z bezpečnostních důvodů tento soubor po načtení odstraňte.</string>\n    <string name=\"invalid_listen_address\">Neplatná naslouchací adresa</string>\n    <string name=\"configuration\">Nastavení</string>\n    <string name=\"battery_optimizations\">Optimalizace baterie</string>\n    <string name=\"listen_address\">Naslouchací adresa</string>\n    <string name=\"start_automatically\">Spustit automaticky</string>\n    <string name=\"start_automatically_help\">Pokud je zaškrtnuto, baresip se spustí automaticky po (opětovném) spuštění zařízení nebo po instalaci nové verze baresip. Ke spuštění dojde po odemčení zařízení.</string>\n    <string name=\"battery_optimizations_help\">Pokud chcete snížit pravděpodobnost, že systém Android omezí přístup zařízení baresip k síti nebo jej přepne do pohotovostního režimu, zakažte optimalizace baterie (doporučeno).</string>\n    <string name=\"listen_address_help\">IP adresa a port ve tvaru \\\"address:port\\\", na kterém baresip naslouchá příchozím požadavkům SIP. Pokud je IP adresa IPv6, musí být zapsána v závorce []. IPv4 adresa 0.0.0.0 nebo IPv6 adresa [::] způsobí, že baresip bude naslouchat na všech dostupných adresách. Pokud zůstane prázdné (výchozí nastavení), bude baresip naslouchat na libovolném portu na všech dostupných adresách.</string>\n    <string name=\"dns_servers\">Servery DNS</string>\n    <string name=\"dns_servers_help\">Seznam adres serverů DNS oddělených čárkou. Pokud nejsou zadány, adresy serverů DNS jsou získávány dynamicky ze systému. Každá adresa DNS má tvar \\'ip:port\\' nebo \\'ip\\'. Pokud je port vynechán, je výchozí hodnota 53. Pokud je ip adresa IPv6 a je zadán i port, musí být ip zapsáno v závorkách []. Příklad: seznam \\'8.8.8.8:53,[2001:4860:4860::8888]:53\\' ukazuje na adresy IPv4 a IPv6 veřejných serverů Google DNS.</string>\n    <string name=\"tls_ca_file\">Soubor TLS CA</string>\n    <string name=\"failed_to_set_dns_servers\">Nepodařilo se nastavit servery DNS</string>\n    <string name=\"verify_server\">Ověřit certifikáty serveru</string>\n    <string name=\"verify_server_help\">Pokud je tato možnost zaškrtnuta, ověřuje baresip certifikáty TLS uživatelského agenta SIP a proxy serverů SIP, pokud se používá přenos TLS.</string>\n    <string name=\"dtmf_inband\">Události v pásmu RTP</string>\n    <string name=\"dtmf_info\">Žádosti SIP INFO</string>\n    <string name=\"media_encryption_help\">Vybere protokol šifrování přenosu média (pokud existuje).\n\\n • ZRTP (doporučeno) znamená, že se po navázání hovoru vyzkouší vyjednávání o koncové šifrování médií ZRTP.\n\\n • DTLS-SRTPF znamená, že v odchozím hovoru je nabízen UDP/TLS/RTP/SAVPF a že v případě příchozího hovoru je použit RTP/SAVP, RTP/SAVPF, UDP/TLS/RTP/SAVP nebo UDP/TLS/RTP/SAVPF.\n\\n • SRTP-MANDF znamená, že RTP/SAVPF je nabízen v odchozím hovoru a vyžadován v příchozím hovoru.\n\\n • SRTP-MAND znamená, že RTP/SAVP je nabízen v odchozím hovoru a vyžadován v příchozím hovoru.\n\\n • SRTP znamená, že RTP/AVP je nabízen v odchozím hovoru a že RTP/SAVP nebo RTP/SAVPF je použit, pokud je nabízen v příchozím hovoru.</string>\n    <string name=\"dtmf_mode_help\">Vybírá způsob odesílání tónů DTMF 0-9, #, * a A-D.</string>\n    <string name=\"voicemail_uri\">URI hlasové schránky</string>\n    <string name=\"dtmf_mode\">Režim DTMF</string>\n    <string name=\"answer_mode\">Režim odpovědi</string>\n    <string name=\"manual\">Ručně</string>\n    <string name=\"auto\">Automaticky</string>\n    <string name=\"country_code\">Kód země</string>\n    <string name=\"voicemain_uri_help\">SIP URI pro kontrolu zpráv hlasové pošty. Pokud je ponecháno prázdné, zprávy hlasové pošty (indikace čekající zprávy) nejsou přihlášeny k odběru.</string>\n    <string name=\"country_code_help\">Kód země E.164 tohoto účtu. Pokud uživatelská část \\\"Od\\\" v URI příchozího hovoru nebo zprávy obsahuje telefonní číslo, které nezačíná znakem \\\"+\\\", a pokud se vyhledání kontaktu nezdaří, je číslo předřazeno tomuto kódu země a vyhledání kontaktu je zopakováno. Pokud telefonní číslo začíná jedinou číslicí \\\"0\\\", číslice \\\"0\\\" se před prefixací čísla odstraní.</string>\n    <string name=\"default_account\">Výchozí účet</string>\n    <string name=\"invalid_country_code\">Neplatný kód země \\'%1$s\\'</string>\n    <string name=\"telephony_provider_help\">Hostitelská část SIP URI používaná při volání na telefonní čísla. Výchozí tovární nastavení je doména účtu. Pokud není zadána, nelze tento účet použít pro volání na telefonní čísla.</string>\n    <string name=\"default_account_help\">Pokud je zatrženo, je tento účet vybrán při spuštění baresipu.</string>\n    <string name=\"invalid_sip_uri_hostpart\">Neplatná část hostitelského URI SIP \\'%1$s\\'</string>\n    <string name=\"new_account\">SIP URI nového účtu</string>\n    <string name=\"accounts\">Účty</string>\n    <string name=\"account_exists\">Účet \\'%1$s\\' již existuje.</string>\n    <string name=\"decrypt_password\">Dešifrovat heslo</string>\n    <string name=\"accounts_help\">SIP URI nového účtu v následující podobě: &lt;uživatel&gt;@&lt;doména&gt;[:&lt;port&gt;][;transport=udp|tcp|tls]. Pokud je zadán &lt;port&gt; a transportní protokol není zadán, je výchozí transportní protokol UDP. Pokud není zadán &lt;port&gt; a je zadán transportní protokol, je výchozí hodnota &lt;port&gt; 5060 nebo 5061 (TLS). Pokud není zadán ani jeden z těchto parametrů a není zadán žádný odchozí proxy server, je registrátor účtu (pokud existuje) určen pouze na základě DNS informací o doméně.</string>\n    <string name=\"account_allocation_failure\">Nepodařilo se vytvořit nový účet.</string>\n    <string name=\"delete_account\">Chcete odstranit účet \\'%1$s\\'\\?</string>\n    <string name=\"missed_call_from\">Zmeškaný hovor od</string>\n    <string name=\"missed_calls\">Zmeškané hovory</string>\n    <string name=\"call\">Hovor</string>\n    <string name=\"transfer_request_to\">Žádost o přepojení hovoru na</string>\n    <string name=\"enable_history\">Povolit</string>\n    <string name=\"call_history\">Historie volání</string>\n    <string name=\"calls_calls\">volání</string>\n    <string name=\"call_details\">Podrobnosti o volání</string>\n    <string name=\"calls_call\">volání</string>\n    <string name=\"tls_ca_file_help\">Pokud je zaškrtnuto, byl nebo bude načten soubor obsahující certifikáty TLS takových certifikačních autorit, které nejsou součástí operačního systému Android. Ve verzi systému Android 9 se ze složky Download načte soubor s názvem \\'ca_certs.crt\\'.</string>\n    <string name=\"audio_settings\">Nastavení zvuku</string>\n    <string name=\"opus_bit_rate_help\">Průměrná maximální přenosová rychlost používaná zvukovým tokem Opus. Platné hodnoty jsou 6000-510000. Výchozí hodnota je 28000.</string>\n    <string name=\"opus_packet_loss\">Očekávaná ztráta paketů Opus</string>\n    <string name=\"opus_packet_loss_help\">Očekávaná procentuální ztráta paketů zvukového toku Opus od 0 do 100. Výchozí hodnota je 1. Hodnota 0 také vypíná funkci Opus Forward Error Correction (FEC).</string>\n    <string name=\"audio_modules_title\">Zvukové moduly</string>\n    <string name=\"default_call_volume\">Výchozí hlasitost volání</string>\n    <string name=\"dark_theme_help\">Vynutit tmavé téma</string>\n    <string name=\"failed_to_load_module\">Nepodařilo se načíst modul.</string>\n    <string name=\"audio_modules_help\">Zvukové kodeky poskytované zaškrtnutými moduly jsou k dispozici pro použití v účtech.</string>\n    <string name=\"invalid_opus_packet_loss\">Neplatné procento ztráty paketů Opus</string>\n    <string name=\"invalid_opus_bitrate\">Neplatný datový tok Opus</string>\n    <string name=\"default_call_volume_help\">Pokud je nastaveno, je výchozí hlasitost zvuku hovoru na stupnici 1-10.</string>\n    <string name=\"sip_trace\">Trasování SIP</string>\n    <string name=\"dark_theme\">Tmavé téma</string>\n    <string name=\"both\">Obojí</string>\n    <string name=\"video_size\">Rozlišení videa</string>\n    <string name=\"video_size_help\">Rozlišení videa (šířka x výška)</string>\n    <string name=\"contacts_help\">Vybírá, zda se použijí kontakty baresip, kontakty Android nebo obojí. Pokud jsou použita volba obojí a v obou kontaktech existuje kontakt se stejným jménem, bude vybrán kontakt baresip.</string>\n    <string name=\"debug_help\">Pokud je zaškrtnuto, poskytuje zprávy protokolu Logcat na úrovni ladění a informací.</string>\n    <string name=\"sip_trace_help\">Pokud je zaškrtnuto a pokud je zaškrtnuta volba Debug, zprávy Logcat obsahují také trasování požadavků a odpovědí SIP. Zruší se automaticky při spuštění baresipu.</string>\n    <string name=\"reset_config\">Obnovit výchozí nastavení</string>\n    <string name=\"reset_config_help\">Pokud je zaškrtnuto, nastavení se obnoví na výchozí hodnoty.</string>\n    <string name=\"reset_config_alert\">Jste si jisti, že chcete obnovit výchozí hodnoty\\?</string>\n    <string name=\"reset\">Obnovit</string>\n    <string name=\"read_cert_error\">Nepodařilo se načíst soubor \\'cert.pem\\'.</string>\n    <string name=\"read_ca_certs_error\">Nepodařilo se načíst soubor \\'ca_certs.crt\\'.</string>\n    <string name=\"contacts\">Kontakty</string>\n    <string name=\"new_contact\">Nový kontakt</string>\n    <string name=\"config_restart\">Pro aktivaci nových nastavení je třeba restartovat baresip. Restartovat nyní\\?</string>\n    <string name=\"consent_request\">Žádost o souhlas</string>\n    <string name=\"sip_or_tel_uri\">SIP nebo tel. URI</string>\n    <string name=\"contacts_consent\">Pokud jsou vybrány kontakty Android, lze je použít při volání a zasílání zpráv jako odkazy na SIP a tel. URI. Aplikace baresip neukládá kontakty Android ani je s nikým nesdílí. Aby byly kontakty pro Android dostupné v baresip, Google vyžaduje, abyste souhlasili s jejich používáním, jak je popsáno zde v <a href=\"https://raw.githubusercontent.com/juha-h/baresip-studio/master/PrivacyPolicy aplikace. txt\">Zásadách ochrany osobních údajů</a>.</string>\n    <string name=\"contact_name\">Jméno</string>\n    <string name=\"user_domain_or_number\">uživatel@doména nebo telefonní číslo</string>\n    <string name=\"contact_already_exists\">Kontakt \\'%1$s\\' již existuje.</string>\n    <string name=\"android_contact_help\">Pokud je zaškrtnuto, je tento kontakt přidán do kontaktů systému Android.</string>\n    <string name=\"avatar_image\">Profilový obrázek</string>\n    <string name=\"invalid_contact\">Neplatné jméno kontaktu \\'%1$s\\'</string>\n    <string name=\"contact_delete_question\">Chcete odstranit kontakt \\'%1$s\\'\\?</string>\n    <string name=\"alert\">Upozornění</string>\n    <string name=\"add\">Přidat</string>\n    <string name=\"send_message\">Odeslat zprávu</string>\n    <string name=\"contact_action_question\">Chcete zavolat nebo odeslat zprávu \\'%1$s\\'\\?</string>\n    <string name=\"cancel\">Zrušit</string>\n    <string name=\"yes\">Ano</string>\n    <string name=\"deny\">Odmítnout</string>\n    <string name=\"info\">Informace</string>\n    <string name=\"notice\">Oznámení</string>\n    <string name=\"ok\">OK</string>\n    <string name=\"accept\">Přijmout</string>\n    <string name=\"delete\">Smazat</string>\n    <string name=\"no\">Ne</string>\n    <string name=\"error\">Chyba</string>\n    <string name=\"confirmation\">Potvrzení</string>\n    <string name=\"edit\">Upravit</string>\n    <string name=\"status\">Stav</string>\n    <string name=\"anonymous\">Anonymní</string>\n    <string name=\"unknown\">Neznámý</string>\n    <string name=\"invalid_sip_or_tel_uri\">Neplatný SIP nebo tel. URI \\'%1$s\\'</string>\n    <string name=\"restore\">Obnovení</string>\n    <string name=\"restart\">Restartovat</string>\n    <string name=\"backup\">Záloha</string>\n    <string name=\"about\">O aplikaci</string>\n    <string name=\"quit\">Ukončit</string>\n    <string name=\"outgoing_call_to_dots\">Zavolat na …</string>\n    <string name=\"diverted_by_dots\">Přesměrováno …</string>\n    <string name=\"incoming_call_from_dots\">Volání z …</string>\n    <string name=\"lost\">Ztracené</string>\n    <string name=\"call_transfer\">Přepojení hovoru</string>\n    <string name=\"no_telephony_provider\">Účet \\'%1$s\\' nemá žádného poskytovatele telefonních služeb</string>\n    <string name=\"video_request\">Žádost o video</string>\n    <string name=\"allow_video\">Akceptovat odesílání a příjem videa s \\'%1$s\\'\\?</string>\n    <string name=\"allow_video_send\">Přijmout odeslání videa na \\'%1$s\\'\\?</string>\n    <string name=\"allow_video_recv\">Přijímat video od \\'%1$s\\'\\?</string>\n    <string name=\"video_call\">Video hovor</string>\n    <string name=\"call_is_on_hold\">Hovor je přidržen</string>\n    <string name=\"attended\">S konzultací</string>\n    <string name=\"transfer_destination\">Cíl přepojení</string>\n    <string name=\"choose_destination_uri\">Zvolit cílové URI</string>\n    <string name=\"transfer\">Přepojit</string>\n    <string name=\"call_info\">Informace o volání</string>\n    <string name=\"duration\">Doba trvání: %1$d (secs)</string>\n    <string name=\"codecs\">Kodeky</string>\n    <string name=\"rate\">Aktuální rychlost: %1$s (Kbits/s)</string>\n    <string name=\"average_rate\">Průměrná rychlost: %1$s (Kbits/s)</string>\n    <string name=\"packets\">Pakety</string>\n    <string name=\"rec_in_call\">Nahrávání lze zapnout nebo vypnout pouze v případě, kdy hovor není spojen</string>\n    <string name=\"blind\">Naslepo</string>\n    <string name=\"transfer_failed\">Přepojení selhalo</string>\n    <string name=\"call_closed\">Volání ukončeno</string>\n    <string name=\"no_cameras\">Žádná podporovaná videokamera!</string>\n    <string name=\"dtmf\">DTMF</string>\n    <string name=\"call_info_not_available\">Žádné dostupné informace</string>\n    <string name=\"jitter\">Zpoždění paketů (Jitter:) %1$s (ms)</string>\n    <string name=\"new_messages\">nové zprávy</string>\n    <string name=\"and\">a</string>\n    <string name=\"listen\">Poslouchat</string>\n    <string name=\"verify\">Ověřit žádost</string>\n    <string name=\"verify_sas\">Chcete ověřit SAS &lt;%1$s&gt;\\?</string>\n    <string name=\"you_have\">Máte</string>\n    <string name=\"one_new_message\">jednu novou zprávu</string>\n    <string name=\"one_old_message\">jednu starou zprávu</string>\n    <string name=\"no_messages\">Nemáte žádné zprávy</string>\n    <string name=\"start_failed\">Baresip se nepodařilo spustit. Příčinou může být neplatná hodnota Nastavení. Zkontrolujte adresu pro naslouchání, soubor certifikátu TLS a soubor certifikační autority TLS. Poté restartujte baresip.</string>\n    <string name=\"registering_failed\">Registrace %1$s se nezdařila.</string>\n    <string name=\"voicemail_messages\">Hlasové zprávy</string>\n    <string name=\"old_messages\">staré zprávy</string>\n    <string name=\"transfer_request\">Požadavek na přepojení</string>\n    <string name=\"transfer_request_query\">Souhlasíte s přepojením tohoto hovoru na \\'%1$s\\'\\?</string>\n    <string name=\"call_is_secure\">Toto volání je BEZPEČNÉ a účastník je OVĚŘENÝ! Chcete zrušit ověření účastníka\\?</string>\n    <string name=\"backed_up\">Data aplikace byla zálohována do souboru \\'%1$s\\'. Ve verzi systému Android 9 se soubor nachází ve složce Download.</string>\n    <string name=\"call_failed\">Volání se nezdařilo</string>\n    <string name=\"peer_not_verified\">Toto volání je BEZPEČNÉ, ale účastník není ověřen!</string>\n    <string name=\"unverify\">Zrušit ověření</string>\n    <string name=\"no_notifications\">Tuto aplikaci nemůžete používat bez povolení \\\"Oznámení\\\".</string>\n    <string name=\"no_android_contacts\">Bez oprávnění \\\"Kontakty\\\" nemáte přístup ke kontaktům systému Android.</string>\n    <string name=\"permissions_rationale\">Odůvodnění povolení</string>\n    <string name=\"call_not_secure\">Tento hovor NENÍ zabezpečený!</string>\n    <string name=\"backup_failed\">Nepodařilo se zálohovat data aplikace do souboru \\'%1$s\\'. Zkontrolujte Aplikace → baresip → Oprávnění → Úložiště.</string>\n    <string name=\"restored\">Data aplikace jsou obnovena. baresip je třeba restartovat. Restartovat nyní\\?</string>\n    <string name=\"restore_failed\">Nepodařilo se obnovit data aplikace. Zkontrolujte, zda jste zadali správné heslo a zda záložní soubor pochází z této aplikace. Ve verzi systému Android 9 také zkontrolujte Aplikace → baresip → Oprávnění → Úložiště a zda soubor \\'%1$s\\' existuje ve složce Download.</string>\n    <string name=\"no_calls\">baresip pro hlasové hovory potřebuje oprávnění \\\"Mikrofon\\\".</string>\n    <string name=\"no_video_calls\">Pro uskutečňování nebo přijímání videohovorů udělte oprávnění pro přístup \\\"Kameře\\\".</string>\n    <string name=\"no_backup\">Zálohu nelze vytvořit bez oprávnění pro přístup k \\\"Úložišti\\\".</string>\n    <string name=\"no_restore\">Bez oprávnění pro přístup k \\\"Úložišti\\\" nelze zálohu obnovit.</string>\n    <string name=\"audio_focus_denied\">Zaměření zvuku zamítnuto!</string>\n    <string name=\"audio_and_video_permissions\">baresip+ potřebuje oprávnění \\\"Mikrofon\\\" pro hlasové hovory, oprávnění \\\"Kamera\\\" pro videohovory, oprávnění \\\"Blízká zařízení\\\" pro detekci mikrofonu/reproduktoru Bluetooth, oprávnění \\\"Oznámení\\\" pro odesílání oznámení a na Androidu 9 oprávnění „Úložiště“ pro operace zálohování a obnovení.</string>\n    <string name=\"no_network\">Bez připojení k síti!</string>\n    <string name=\"audio_permissions\">baresip potřebuje oprávnění \\\"Mikrofon\\\" pro hlasové hovory, oprávnění \\\"Blízká zařízení\\\" pro detekci mikrofonu/reproduktoru Bluetooth, oprávnění \\\"Oznámení\\\" pro odesílání oznámení a na Androidu 9 oprávnění „Úložiště“ pro operace zálohování a obnovení.</string>\n    <string name=\"address_family_help\">Vybírá, které IP adresy baresip používá. Pokud je vybráno IPv4 nebo IPv6, baresip používá pouze IPv4 nebo IPv6 adresy. Pokud není vybrána žádná z těchto možností, používá baresip jak IPv4, tak IPv6 adresy.</string>\n    <string name=\"address_family\">Rodina adres</string>\n    <string name=\"invalid_audio_delay\">Neplatná hodnota zpoždění zvuku \\'%1$s\\'. Platné hodnoty jsou od 100 do 3000 ms.</string>\n    <string name=\"audio_delay_help\">Doba (v milisekundách), po kterou se čeká na zvuk od volaného při navázání hovoru. Nastavte vyšší hodnotu, pokud na začátku hovoru zvuk volaného neslyšíte.</string>\n    <string name=\"audio_delay\">Zpoždění zvuku</string>\n    <string name=\"call_auto_rejected\">Automaticky odmítat volání od %1$s</string>\n    <string name=\"default_phone_app\">Výchozí aplikace telefonu</string>\n    <string name=\"dialer_role_not_available\">Role číselníku není k dispozici</string>\n    <string name=\"default_phone_app_help\">Pokud je zaškrtnuto, je výchozí aplikací telefonu baresip. Nezaškrtávejte, pokud vaše zařízení může potřebovat zpracovávat i jiné než SIP hovory nebo zprávy.</string>\n    <string name=\"redirect_notice\">Automatické přesměrování na \\'%1$s\\'\\\\</string>\n    <string name=\"favorite\">Oblíbené</string>\n    <string name=\"appear_on_top_permission\">Automatické spuštění vyžaduje oprávnění Zobrazení přes ostatní aplikace.</string>\n    <string name=\"redirect_mode_help\">Vybírá, zda bude požadavek na přesměrování hovoru následován automaticky, nebo zda bude vyžadováno potvrzení.</string>\n    <string name=\"redirect_request_query\">Souhlasíte s přesměrováním volání na \\'%1$s\\'?</string>\n    <string name=\"redirect_mode\">Režim přesměrování</string>\n    <string name=\"redirect_request\">Žádost o přesměrování</string>\n    <string name=\"tone_country\">Region tónů</string>\n    <string name=\"rel_100_help\">Pokud je zaškrtnuto, tak avizovat podporu spolehlivých předběžných odpovědí (RFC 3262).</string>\n    <string name=\"tone_country_help\">Země vyzvánění, čekajícího hovoru a obsazených tónů volajícího</string>\n    <string name=\"rel_100\">Spolehlivé předběžné odpovědi</string>\n    <string name=\"restore_unzip_failed\">Nepodařilo se obnovit data aplikace. Systém Android verze 14 a vyšší neumožňuje obnovení dat, která byla zálohována před verzí %1$s %2$s.</string>\n    <string name=\"speaker_phone\">Hlasitý poslech</string>\n    <string name=\"speaker_phone_help\">Pokud je zatrženo, hlasitý poslech se zapne automaticky při zahájení hovoru.</string>\n    <string name=\"dtmf_auto\">In-band RTP nebo SIP INFO</string>\n    <string name=\"call_request\">Žádost o volání</string>\n    <string name=\"call_request_query\">Přijímáte požadavek na volání \\'%1$s\\'?</string>\n    <string name=\"video_fps\">Snímky za sekundu</string>\n    <string name=\"video_fps_help\">Snímková frekvence videa, která bude nabídnuta během vyjednávání SDP. Platné hodnoty jsou od 10 do 30.</string>\n    <string name=\"invalid_fps\">Neplatný počet snímků za sekundu \\'%1$d\\'</string>\n    <string name=\"user_agent\">Uživatelský agent</string>\n    <string name=\"user_agent_help\">Vlastní hodnota pole hlavičky User-Agent pro SIP požadavek/odpověď</string>\n    <string name=\"invalid_user_agent\">Neplatná hodnota pole hlavičky User-Agent</string>\n    <string name=\"microphone_gain\">Zesílení mikrofonu</string>\n    <string name=\"microphone_gain_help\">Vynásobte hlasitost mikrofonu tímto desetinným číslem. Minimální hodnota je 1,0 (výchozí hodnota), která vypne zesílení mikrofonu. Vyšší hodnoty mohou negativně ovlivnit kvalitu zvuku.</string>\n    <string name=\"invalid_microphone_gain\">Neplatná hodnota zesílení mikrofonu</string>\n    <string name=\"ringtone\">Vyzvánění</string>\n    <string name=\"select_ringtone\">Vybrat vyzvánění</string>\n    <string name=\"numeric_keypad\">Numerická klávesnice</string>\n    <string name=\"numeric_keypad_help\">Pokud je zatrženo, zobrazí se při zaměření pole \\\"Zavolat na ...\\\" numerická klávesnice.</string>\n    <string name=\"reply\">Odpovědět</string>\n    <string name=\"save\">Uložit</string>\n    <string name=\"playing_recording\">Přehrávání nahrávky …</string>\n    <string name=\"call_answered\">Hovor byl přijat</string>\n    <string name=\"call_answered_elsewhere\">Hovor byl přijat jinde</string>\n    <string name=\"call_missed\">Zmeškaný hovor</string>\n    <string name=\"call_rejected\">Odmítnutý hovor</string>\n    <string name=\"no_read_permission\">Aplikace nemá oprávnění k externímu úložišti</string>\n    <string name=\"colorblind\">Barvoslepost</string>\n    <string name=\"colorblind_help\">Používat ikony stavu registrace přizpůsobené barvosleposti</string>\n    <string name=\"favorite_help\">Je-li zaškrtnuto, kontakt se zobrazí mezi oblíbenými v horní části seznamu kontaktů.</string>\n    <string name=\"no_aec\">Hardwarové potlačení ozvěny není dostupné!</string>\n    <string name=\"call_recording_title\">Nahrávání hovoru</string>\n    <string name=\"call_recording_tip\">Pokud je aktivováno, nové příchozí i odchozí hovory budou nahrávány. Nahrávky lze přehrát na stránce historie volání.</string>\n    <string name=\"microphone_title\">Mikrofon</string>\n    <string name=\"microphone_tip\">Při aktivaci během hovoru se mikrofon ztlumí.</string>\n    <string name=\"speakerphone_title\">Reproduktor</string>\n    <string name=\"speakerphone_tip\">Po aktivaci se zvuk přehrává přes reproduktor zařízení.</string>\n    <string name=\"proximity_sensing\">Detekce přiblížení</string>\n    <string name=\"proximity_sensing_help\">Při hovorech používat snímač přiblížení (automatické vypnutí displeje).</string>\n    <string name=\"dynamic_colors\">Dynamické barvy</string>\n    <string name=\"dynamic_colors_help\">Použijí se dynamické barvy, pokud jsou povoleny v nastavení Androidu</string>\n    <string name=\"about_text\">\n        <![CDATA[\n        <h1>SIP klient založený na knihovně <a href=\"https://github.com/baresip/baresip\">baresip</a></h1>\n        <br>\n        <p>Juha Heinanen &lt;jh@tutpro.com&gt;</p>\n        <p>Verze %1$s</p>\n        <br>\n        <h2>Nápověda</h2>\n        <ul>\n            <li>Zkontrolujte, zda výchozí hodnoty nastavení vyhovují vašim potřebám\n                (nápovědu zobrazíte klepnutím na názvy položek).</li>\n            <li>Poté v sekci Účty vytvořte jeden nebo více účtů (pro nápovědu opět klepněte na názvy položek).</li>\n            <li>Stav registrace účtu je indikován barevnou tečkou: zelená (registrace\n                      proběhla úspěšně), žlutá (registrace probíhá), červená (registrace se nezdařila), bílá (registrace \n                      nezačala).</li>\n            <li>Klepnutím na stavovou ikonu (nalevo od názvu účtu) spustí konfiguraci účtu.</li>\n            <li>Dlouhým dotykem na ikony na liště baresip zobrazíte informace o ikonách.</li>\n            <li>Přejetí prstem dolů po obrazovce (gesto) zahájí opětovnou registraci aktuálně zobrazeného účtu.</li>\n            <li>Dlouhým dotykem na aktuálně zobrazený název účtu povolíte nebo zakážete jeho registraci.</li>\n            <li>Mezi účty se můžete přepnout přejetím prstem vlevo/vpravo po obrazovce (gesto).</li>\n            <li>Poslední volaný kontakt lze znovu rychle vybrat klepnutím na ikonu hovoru, pokud je pole \"Zavolat na ...\" prázdné.</li>\n            <li>Účastníky hovorů a zpráv lze přidávat do kontaktů dlouhým podržením.</li>\n            <li>Dlouhým podržením lze také odstraňovat hovory, chaty, zprávy a kontakty.</li>\n            <li>Dotyk/dlouhý dotyk na ikonu kontaktu lze použít k přidání/odstranění obrázkového avatara.</li>\n            <li>Dlouhým dotykem na zvukový kodek lze kodek povolit/zakázat.</li>\n            <li>Navštivte <a href=\"https://github.com/juha-h/baresip-studio/wiki\">Wiki</a> pro více informací.</li>\n        </ul>\n        <h2>Zásady ochrany osobních údajů</h2>\n            <p>Zásady ochrany osobních údajů jsou k dispozici <a href=\"https://raw.githubusercontent.com/juha-h/baresip-studio/master/PrivacyPolicy.txt\">zde</a>.</p>\n        <br>\n        <h2>Zdrojový kód</h2>\n            <p>Zdrojový kód je k dispozici na <a href=\"https://github.com/juha-h/baresip-studio\">GitHubu</a>,\n            kde je také možno nahlásit problémy, náměty a požadavky.</p>\n        <br>\n        <h2>Překlady aplikace do jiných jazyků</h2>\n            <p>Překlady jsou realizovány skrze projekt baresip\n                <a href=\"https://hosted.weblate.org/projects/baresip/\">Weblate</a>.</p>\n        <br>\n        <h2>Licence</h2>\n        <ul>\n            <li><b>BSD-3-Clause</b> kromě následujících:</li>\n            <li><b>Apache 2.0</b> AMR kodeky a TLS zabezpečení</li>\n            <li><b>AGPLv4</b> ZRTP šifrování médií</li>\n            <li><b>GNU LGPL 2.1</b> Codec2 codec</li>\n            <li><b>Free</b> G.722 codec</li>\n            <li><b>GNU GPLv3</b> G.729 kodek</li>\n        </ul>\n        ]]>\n    </string>\n    <string name=\"check_origin\">Ověřovat požadavky na registraci</string>\n    <string name=\"check_origin_help\">Pokud je tato volba zapnutá, příchozí požadavky jsou povoleny pouze z IP adres, na které byly odeslány registrační požadavky.</string>\n    <string name=\"block_unknown\">Blokovat neznámé kontakty</string>\n    <string name=\"block_unknown_help\">Blokovat hovory a zprávy od protějšků, kteří nejsou v kontaktech.</string>\n    <string name=\"is_calling\">volá</string>\n    <string name=\"call_blocked\">Zablokovaný hovor od %1$s byl automaticky odmítnut</string>\n    <string name=\"message_blocked\">Zablokovaná zpráva od %1$s byla automaticky odmítnuta</string>\n    <string name=\"blocked\">Zablokované aktivity</string>\n    <string name=\"blocked_calls\">Zablokované hovory</string>\n    <string name=\"blocked_messages\">Zablokované zprávy</string>\n    <string name=\"blocked_delete_alert\">Smazat zablokované požadavky od \\'%1$s\\'?</string>\n    <string name=\"blocked_contact_question\">Přidat \\'%1$s\\' do kontaktů?</string>\n    <string name=\"save_recording\">Uložit nahrávku</string>\n    <string name=\"save_recording_question\">Uložit tuto nahrávku?</string>\n    <string name=\"recording_saved\">Nahrávka byla uložena</string>\n    <string name=\"delete_call_alert\">Smazat tento hovor z historie?</string>\n    <string name=\"transport_protocols\">Transportní protokoly</string>\n    <string name=\"transport_protocols_help\">Čárkami oddělený seznam podporovaných transportních protokolů SIP pro požadavky a odpovědi. Pokud je pole prázdné, použije se výchozí hodnota ‚udp,tcp,tls,ws,wss‘, která zahrnuje všechny podporované transportní protokoly. Uveďte pouze ty, které skutečně potřebujete. Použití UDP se z bezpečnostních i dalších důvodů nedoporučuje.</string>\n    <string name=\"invalid_transport_protocols\">Neplatný seznam transportních protokolů</string>\n    <string name=\"stop\">Zastavit</string>\n    <string name=\"account\">Účet</string>\n    <string name=\"invalid_account_nickname\">Neplatná přezdívka účtu \\'%1$s\\'</string>\n    <string name=\"non_unique_account_nickname\">Přezdívka \\'%1$s\\' již existuje</string>\n    <string name=\"invalid_display_name\">Neplatné zobrazované jméno \\'%1$s\\'</string>\n    <string name=\"authentication_username\">Uživatelské jméno</string>\n    <string name=\"invalid_authentication_password\">Neplatné heslo \\'%1$s\\'</string>\n    <string name=\"stun_username\">Uživatelské jméno STUN/TURN</string>\n    <string name=\"invalid_stun_username\">Neplatné uživatelské jméno \\'%1$s\\'</string>\n    <string name=\"invalid_aor\">Neplatný uživatel@doména[:port][;transport=udp|tcp|tls] \\'%1$s\\'</string>\n    <string name=\"encrypt_password\">Šifrovat heslo</string>\n    <string name=\"missed_calls_count\">%1$d zmeškaných hovorů</string>\n    <string name=\"video_codecs\">Video kodeky</string>\n    <string name=\"tls_certificate_file\">Soubor TLS certifikátu</string>\n    <string name=\"opus_bit_rate\">Datový tok Opus</string>\n    <string name=\"debug\">Ladění</string>\n    <string name=\"search\">Vyhledávání</string>\n    <string name=\"call_already_active\">Již probíhá jiný hovor.</string>\n    <string name=\"restart_request\">Požadavek na restart</string>\n    <string name=\"about_text_plus\"><![CDATA[\n        <h1>SIP klient založený na knihovně <a href=\"https://github.com/baresip/baresip\">baresip</a></h1>\n        <br>\n        <p>Juha Heinanen &lt;jh@tutpro.com&gt;</p>\n        <p>Verze %1$s</p>\n        <br>\n        <h2>Nápověda</h2>\n        <ul>\n                 <li>Zkontrolujte, zda výchozí hodnoty v nastavení baresipu vyhovují vašim potřebám \n                      (nápovědu získáte ťuknutím na názvy položek).</li>\n                 <li>Poté v sekci Účty vytvořte jeden nebo více účtů (pro nápovědu opět ťukněte na názvy položek).</li>\n                 <li>Stav registrace účtu je indikován barevnou tečkou: zelená (registrace \n                      proběhla úspěšně), žlutá (registrace probíhá), červená (registrace se nezdařila), bílá (registrace \n                      nezačala).</li>\n                 <li>Klepnutí na stavovou ikonu (nalevo od názvu účtu) spustí konfiguraci účtu.</li>\n                 <li>Dlouhým dotykem na ikony na liště baresip zobrazíte informace o ikonách.</li>\n                 <li>Přejetí prstem dolů po obrazovce (gesto) zahájí opětovnou registraci aktuálně zobrazeného účtu.</li>\n                 <li>Dlouhým dotykem na aktuálně zobrazený účet povolíte nebo zakážete jeho registraci.</li>\n                 <li>Mezi účty se můžete přepnout přejetím prstem vlevo/vpravo po obrazovce (gesto).</li>\n                 <li>Poslední volaný kontakt lze znovu rychle vybrat klepnutím na ikonu hovoru, pokud je pole \"Zavolat na ...\" prázdné.</li>\n                 <li>Účastníky hovorů a zpráv lze přidávat do kontaktů dlouhým podržením.</li>\n                 <li>Dlouhým podržením lze také odstraňovat hovory, chaty, zprávy a kontakty.</li> \n                 <li>Dotyk/dlouhý dotyk na ikonu kontaktu lze použít k přidání/odstranění obrázkového avatara.</li>\n                 <li>Dlouhým dotykem na zvukový kodek lze kodek povolit/zakázat.</li>\n                 <li>Navštivte <a href=https://github.com/juha-h/baresip-studio/wiki>Wiki</a> pro více\n                          informací.</li>\n        </ul>\n        <h2>Známé problémy</h2>\n        <ul>\n                <li>Náhled sebe sama se nezobrazuje správně, když je video stream pouze pro odesílání..</li>\n        </ul>\n        <h2>Zásady ochrany osobních údajů</h2>\n                <p>Zásady ochrany osobních údajů jsou k dispozici\n                        <a href=https://raw.githubusercontent.com/juha-h/baresip-studio/master/PrivacyPolicy.txt>zde</a>.</p>\n        <br>\n        <h2>Zdrojový kód</h2>\n               <p>Zdrojový kód je k dispozici na <a href=https://github.com/juha-h/baresip-studio>GitHubu</a>,\n                kde je také možno nahlásit problémy, náměty a požadavky.</p>\n        <br>\n        <h2>Překlady do jiných jazyků</h2>\n            <p>Překlady jsou realizovány skrze projekt baresip\n                    <a href=\"https://hosted.weblate.org/projects/baresip/\">Weblate</a>.</p>\n        <br>\n        <h2>Licence</h2>\n        <ul>\n                 <li><b>BSD-3-Clause</b> kromě následujících:</li>\n                 <li><b>Apache 2.0</b> AMR codecs a TLS security</li>\n                 <li><b>AGPLv4</b> šifrování médií ZRTP</li>\n                 <li><b>GNU LGPL 2.1</b> Codec2 codec</li>\n                 <li><b>Free</b> G.722 codec</li>\n                 <li><b>GNU GPLv3</b> kodek G.729 </li>\n                 <li><b>GNU GPLv2</b> H.264 a H.265 kodeky</li>\n                <li><b>AOMedia</b> AV1 kodek</li>\n        </ul>\n        ]]></string>\n    <string name=\"unique_contact_uri\">Jedinečné URI kontaktu</string>\n    <string name=\"unique_contact_uri_help\">Pokud je zaškrtnuto, URI kontaktu je zaručeně jedinečné. Je nutné tuto volbu zapnout, pokud existuje více účtů se stejnou uživatelskou částí SIP URI, a zároveň zlepšuje ochranu účtů proti útokům.</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-de/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"about_title\">Über baresip</string>\n    <string name=\"about_title_plus\">Über baresip+</string>\n    <string name=\"account\">Konto</string>\n    <string name=\"display_name\">Anzeigename</string>\n    <string name=\"non_unique_account_nickname\">Der Nickname \\'%1$s\\' existiert bereits</string>\n    <string name=\"account_nickname_help\">Der Nickname (falls vorhanden), der zur Identifikation des Kontos innerhalb der baresip App genutzt wird.</string>\n    <string name=\"nickname\">Nickname</string>\n    <string name=\"invalid_account_nickname\">Ungültiger Konto Nickname \\'%1$s\\'</string>\n    <string name=\"redirect_mode\">Rufumleitungsmodus</string>\n    <string name=\"telephony_provider\">Telefonieanbieter</string>\n    <string name=\"transfer_request_to\">Anrufübertragungsanfrage an</string>\n    <string name=\"about\">Über</string>\n    <string name=\"register_help\">Wenn angeklickt wird das Registrieren aktiviert und REGISTER-Anfragen werden in dem durch das Registrierenintervall angegebenen Intervall gesendet.</string>\n    <string name=\"reg_int\">Registrierenintervall</string>\n    <string name=\"reg_int_help\">Gibt an, wie oft (in Sekunden) baresip REGISTER-Anfragen sendet. Gültige Werte: 60 bis 3600.</string>\n    <string name=\"invalid_reg_int\">Ungültiges Registrierenintervall \\'%1$s\\'</string>\n    <string name=\"invalid_stun_username\">Ungültiger Benutzername \\'%1$s\\'</string>\n    <string name=\"stun_password\">STUN/TURN Passwort</string>\n    <string name=\"stun_password_help\">Passwort, wenn der STUN/TURN Server eines verlangt</string>\n    <string name=\"invalid_stun_password\">Ungültiges Passwort \\'%1$s\\'</string>\n    <string name=\"media_encryption\">Verschlüsselung</string>\n    <string name=\"manual\">Manuell</string>\n    <string name=\"auto\">Automatisch</string>\n    <string name=\"voicemail_uri\">Sprachnachrichten URI</string>\n    <string name=\"country_code\">Ländercode</string>\n    <string name=\"voicemain_uri_help\">SIP URI zur Überprüfung von Sprachnachrichten. Wenn leer gelassen, werden Voicemail-Nachrichten (Message Waiting Indications) nicht abonniert.</string>\n    <string name=\"account_exists\">Konto \\'%1$s\\' existiert bereits.</string>\n    <string name=\"account_allocation_failure\">Neues Konto konnte nicht zugeordnet werden.</string>\n    <string name=\"encrypt_password\">Passwort verschlüsseln</string>\n    <string name=\"delete_account\">Möchten Sie Konto \\'%1$s\\' löschen?</string>\n    <string name=\"missed_calls\">Verpasste Anrufe</string>\n    <string name=\"missed_calls_count\">%1$d verpasste Anrufe</string>\n    <string name=\"chat_with\">Chat mit %1$s</string>\n    <string name=\"today\">Heute</string>\n    <string name=\"short_chat_question\">Möchten Sie den Chat mit \\'%1$s\\' löschen?</string>\n    <string name=\"start_automatically\">Autostart</string>\n    <string name=\"call_info_not_available\">Keine Informationen verfügbar</string>\n    <string name=\"duration\">Dauer: %1$d (sek)</string>\n    <string name=\"rate\">Aktuelle Übertragungsrate: %1$s (Kbits/s)</string>\n    <string name=\"average_rate\">Mittlere Rate: %1$s (Kbits/s)</string>\n    <string name=\"packets\">Pakete</string>\n    <string name=\"sip_or_tel_uri\">SIP oder URI</string>\n    <string name=\"rtcp_mux_help\">Wenn angeklickt werden RTP und RTCP Pakete auf einen einzelnen Port gemultiplext (RFC 5761).</string>\n    <string name=\"answer_mode\">Antwort Modus</string>\n    <string name=\"dtmf_info\">SIP INFO Anfragen</string>\n    <string name=\"dtmf_mode_help\">Bestimmt, wie die DTMF Töne 0–9, #, *, und A-D gesendet werden.</string>\n    <string name=\"dtmf_mode\">DTMF Modus</string>\n    <string name=\"answer_mode_help\">Bestimmt wie eingehende Rufe beantwortet werden.</string>\n    <string name=\"telephony_provider_help\">SIP URI-Hostteil bei Anrufen zu Telefonnummern. Voreinstellung ist die Domain des Kontos. Wenn nicht angegeben, kann dieses Konto nicht verwendet werden, um Telefonnummern anzurufen.</string>\n    <string name=\"invalid_sip_uri_hostpart\">Ungültiger SIP URI-Hostteil \\'%1$s \\'</string>\n    <string name=\"default_account_help\">Wenn angeklickt, wird dieses Konto beim Start von Baresip vorausgewählt.</string>\n    <string name=\"default_account\">Voreingestelltes Konto</string>\n    <string name=\"peer\">Gesprächspartner</string>\n    <string name=\"invalid_dns_servers\">Ungültiger DNS Server</string>\n    <string name=\"call_details\">Anrufdetails</string>\n    <string name=\"display_name_help\">Name (falls gewünscht) für die Von-URI bei ausgehenden Anfragen.</string>\n    <string name=\"authentication_username_help\">Benutzername für die Authentifizierung, wenn eine Authentifizierung von SIP-Anfragen erforderlich ist. Standardwert ist der Benutzername des Kontos.</string>\n    <string name=\"invalid_display_name\">Ungültiger Anzeigename \\'%1$s\\'</string>\n    <string name=\"authentication_username\">Benutzername für die Authentifizierung</string>\n    <string name=\"invalid_authentication_username\">Ungültiger Benutzername für die Authentifizierung \\'%1$s\\'</string>\n    <string name=\"authentication_password\">Passwort für die Authentifizierung</string>\n    <string name=\"authentication_password_help\">Passwort mit max. 64 Zeichen. Wenn ein Benutzername eingegeben wurde, aber kein Passwort eingegeben wird, wird beim Start von baresip danach gefragt.</string>\n    <string name=\"invalid_authentication_password\">Ungültiges Passwort \\'%1$s\\'</string>\n    <string name=\"sip_uri_of_proxy_server\">SIP URI des Proxy Servers</string>\n    <string name=\"sip_uri_of_another_proxy_server\">SIP URI eines anderen Proxy Servers</string>\n    <string name=\"invalid_proxy_server_uri\">Ungültige Proxy Server URI \\'%1$s\\'</string>\n    <string name=\"register\">Registrieren</string>\n    <string name=\"media_nat_help\">Wählt ein oder kein media NAT traversal protocol. Mögliche Werte sind STUN (Session Traversal Utilities for NAT, RFC 5389) und ICE (Interactive Connectivity Establishment, RFC 5245).</string>\n    <string name=\"accounts\">Konten</string>\n    <string name=\"new_account\">SIP URI des neuen Kontos</string>\n    <string name=\"decrypt_password\">Passwort entschlüsseln</string>\n    <string name=\"new_message\">Neue Nachricht</string>\n    <string name=\"contact_name\">Name</string>\n    <string name=\"call_auto_rejected\">Auto-abgewiesener Anruf von %1$s</string>\n    <string name=\"call\">Anruf</string>\n    <string name=\"call_history\">Anrufliste</string>\n    <string name=\"invalid_listen_address\">Ungültige Listening-Adresse</string>\n    <string name=\"tls_certificate_file\">TLS Zertifikat-Datei</string>\n    <string name=\"contact_action_question\">Wollen Sie \\'%1$s\\' anrufen oder eine Nachricht senden?</string>\n    <string name=\"unknown\">Unbekannt</string>\n    <string name=\"no_telephony_provider\">Konto \\'%1$s\\' hat keinen Telefonanbieter</string>\n    <string name=\"lost\">Verloren</string>\n    <string name=\"peer_not_verified\">Dieser Anruf ist SICHER, aber die Gegenstelle ist NICHT verifiziert!</string>\n    <string name=\"calls_calls\">Anrufe</string>\n    <string name=\"calls_call\">Anruf</string>\n    <string name=\"direction\">Richtung</string>\n    <string name=\"time\">Zeit</string>\n    <string name=\"calls_add_delete_question\">Möchten Sie \\'%1$s\\' zu Kontakten hinzufügen oder %2$s aus der Anrufliste löschen?</string>\n    <string name=\"calls_delete_question\">Möchten Sie \\'%1$s\\' %2$s aus der Anrufliste löschen?</string>\n    <string name=\"delete_history_alert\">Möchten Sie den Anrufverlauf des Kontos \\'%1$s löschen?</string>\n    <string name=\"long_message_question\">Möchten Sie die Nachricht löschen oder Gesprächspartner \\'%1$s\\' zu Kontakten hinzufügen?</string>\n    <string name=\"message_failed\">Fehlgeschlagen</string>\n    <string name=\"configuration\">Einstellungen</string>\n    <string name=\"start_automatically_help\">Wenn angeklickt wird Baresip automatisch ausgeführt nach dem Gerät (Neu)Start.</string>\n    <string name=\"battery_optimizations\">Batterieoptimierungen</string>\n    <string name=\"failed_to_set_dns_servers\">DNS Server konnte nicht eingestellt werden</string>\n    <string name=\"voicemail_messages\">Sprachnachrichten</string>\n    <string name=\"you_have\">Sie haben</string>\n    <string name=\"new_messages\">neue Nachrichten</string>\n    <string name=\"new_contact\">Neuer Kontakt</string>\n    <string name=\"user_domain_or_number\">benutzer@domain oder Telefonnummer</string>\n    <string name=\"contact_already_exists\">Kontakt \\'%1$s\\' existiert schon.</string>\n    <string name=\"avatar_image\">Profilbild</string>\n    <string name=\"contact_delete_question\">Wollen Sie Kontakt \\'%1$s\\' löschen?</string>\n    <string name=\"alert\">Alarm</string>\n    <string name=\"notice\">Nachricht</string>\n    <string name=\"cancel\">Abbrechen</string>\n    <string name=\"yes\">Ja</string>\n    <string name=\"no\">Nein</string>\n    <string name=\"confirmation\">Bestätigung</string>\n    <string name=\"restart\">Neustarten</string>\n    <string name=\"restore\">Wiederherstellen</string>\n    <string name=\"quit\">Beenden</string>\n    <string name=\"outgoing_call_to_dots\">Anruf zu …</string>\n    <string name=\"incoming_call_from_dots\">Anruf von …</string>\n    <string name=\"diverted_by_dots\">Umgeleitet von …</string>\n    <string name=\"video_call\">Videoanruf</string>\n    <string name=\"call_is_on_hold\">Ruf gehalten</string>\n    <string name=\"rec_in_call\">Aufnahme kann nur ein- oder ausgeschalten werden wenn der Ruf nicht verbunden ist</string>\n    <string name=\"one_new_message\">eine neue Nachricht</string>\n    <string name=\"old_messages\">alte Nachrichten</string>\n    <string name=\"no_messages\">Sie haben keine Nachrichten</string>\n    <string name=\"and\">und</string>\n    <string name=\"listen\">Hören</string>\n    <string name=\"call_already_active\">Sie haben schon einen aktiven Anruf.</string>\n    <string name=\"registering_failed\">’%1$s‘ konnte nicht registriert werden.</string>\n    <string name=\"call_failed\">Anruf fehlgeschlagen</string>\n    <string name=\"call_closed\">Anruf beendet</string>\n    <string name=\"call_not_secure\">Dieser Anruf ist NICHT sicher!</string>\n    <string name=\"no_video_calls\">Geben Sie \\\"Kamera\\\" Berechtigung, um Videoanrufe zu machen oder zu empfangen.</string>\n    <string name=\"no_android_contacts\">Sie können auf Android-Kontakte ohne \\\"Kontakte\\\" Berechtigung nicht zugreifen.</string>\n    <string name=\"audio_and_video_permissions\">baresip+ benötigt die \\\"Mikrofon\\\"-Berechtigung für Sprachanrufe, die \\\"Kamera\\\"-Berechtigung für Videoanrufe, die \\\"Geräte in der Nähe\\\"-Berechtigung für Bluetooth-Mikrofon/Hörer-Erkennung, die \\\"Benachrichtigungen\\\"-Berechtigung für die Benachrichtigungen und bei Android 9 die \\\"Speicher\\\"-Berechtigung zur Datensicherung/ Wiederherstellung.</string>\n    <string name=\"invalid_contact\">Ungültiger Kontaktname \\'%1$s\\'</string>\n    <string name=\"favorite\">Favorit</string>\n    <string name=\"android_contact_help\">Wenn angeklickt, wird der Kontakt zum Android Adressbuch hinzugefügt.</string>\n    <string name=\"calls_duration\">Dauer</string>\n    <string name=\"invalid_country_code\">Ungültiger Ländercode \\'%1$s</string>\n    <string name=\"short_message_question\">Möchten Sie die Nachricht löschen?</string>\n    <string name=\"add_contact\">Kontakt hinzufügen</string>\n    <string name=\"chats\">Chatverlauf</string>\n    <string name=\"disable_history\">Deaktivieren</string>\n    <string name=\"enable_history\">Aktivieren</string>\n    <string name=\"one_old_message\">eine alte Nachricht</string>\n    <string name=\"call_is_secure\">Dieser Anruf ist SICHER, und die Gegenstelle ist VERIFIZIERT! Wollen Sie die Gegenstelle unsicher erklären?</string>\n    <string name=\"unverify\">Unsicher erklären</string>\n    <string name=\"backed_up\">Anwendungsdaten (keine Aufnahmen) gesichert in \\'%1$s\\'. In Android Version 9 ist die Datei im Download-Ordner.</string>\n    <string name=\"restart_request\">Neustarten</string>\n    <string name=\"anonymous\">Anonym</string>\n    <string name=\"send_message\">Nachricht senden</string>\n    <string name=\"contacts\">Kontakte</string>\n    <string name=\"accept\">Akzeptieren</string>\n    <string name=\"delete\">Löschen</string>\n    <string name=\"error\">Fehler</string>\n    <string name=\"add\">Hinzufügen</string>\n    <string name=\"edit\">Bearbeiten</string>\n    <string name=\"no_notifications\">Sie können diese Anwendung nicht ohne \\\"Benachrichtigungen\\\"-Berechtigung verwenden.</string>\n    <string name=\"invalid_sip_or_tel_uri\">Ungültiger SIP or tel URI \\'%1$s\\'</string>\n    <string name=\"deny\">Verweigern</string>\n    <string name=\"no_network\">Keine Netzwerk Verbindung!</string>\n    <string name=\"outbound_proxies\">Proxies für ausgehende Verbindungen</string>\n    <string name=\"battery_optimizations_help\">Deaktivieren Sie die Akku-Optimierungen (empfohlen), wenn Sie die Wahrscheinlichkeit reduzieren möchten, dass Android den Zugriff von Baresip auf das Netzwerk einschränkt oder Baresip in den Standby-Zustand versetzt.</string>\n    <string name=\"default_phone_app\">Standard Telefonie-App</string>\n    <string name=\"appear_on_top_permission\">Autostart benötigt Erlaubnis \\\"Über anderen Apps einblenden\\\".</string>\n    <string name=\"default_phone_app_help\">Wenn angeklickt ist Baresip die Standard-Telefon-App. Nicht anklicken, wenn Ihr Gerät auch andere als SIP-Anrufe oder -Nachrichten annehmen soll.</string>\n    <string name=\"listen_address\">Listening Adresse</string>\n    <string name=\"dialer_role_not_available\">kann nicht als Wählgerät dienen</string>\n    <string name=\"dns_servers\">DNS Server</string>\n    <string name=\"verify_server\">Serverzertifikate überprüfen</string>\n    <string name=\"verify_server_help\">Bei Anklicken werden von Baresip TLS-Zertifikate von SIP User Agent und SIP Proxy Servern bei Verwendung von TLS-Transport überprüft.</string>\n    <string name=\"backup_failed\">Sicherung nach \\'%1$s\\' fehlgeschlagen. Überprüfen Sie Apps → baresip → Berechtigungen → Speicher.</string>\n    <string name=\"restored\">Anwendungsdaten wurden wiederhergestellt und baresip muß neugestartet werden. Neustarten?</string>\n    <string name=\"restore_failed\">Wiederherstellung der Anwendungsdaten fehlgeschlagen. Überprüfen Sie das eingegebene Passwort und dass die Wiederherstellungsdatei von dieser Anwendung stammt. In Android Version 9: Überprüfen Sie Apps → baresip → Berechtigungen → Speicher, sowie, dass \\'%1$s\\' im Download Ordner existiert.</string>\n    <string name=\"no_calls\">baresip braucht \\\"Mikrofon\\\" Berechtigung für Telefonanrufe.</string>\n    <string name=\"no_cameras\">Keine unterstützten Kameras!</string>\n    <string name=\"no_backup\">Eine Sicherungskopie kann nicht ohne die \\\"Speicher\\\"-Berechtigung erstellt werden.</string>\n    <string name=\"no_restore\">Sie können keine Sicherung wiederherstellen ohne \\\"Speicher\\\"-Berechtigung.</string>\n    <string name=\"invalid_stun_server\">Ungültiger STUN/TURN Server URI \\'%1$s\\'</string>\n    <string name=\"stun_username\">STUN/TURN Benutzername</string>\n    <string name=\"stun_username_help\">Benutzername, wenn der STUN/TURN Server einen verlangt</string>\n    <string name=\"call_info\">Ruf Informationen</string>\n    <string name=\"missed_call_from\">Verpasster Anruf von</string>\n    <string name=\"sending_failed\">Nachricht senden fehlgeschlagen</string>\n    <string name=\"delete_chats_alert\">Möchten Sie den Chat-Verlauf des Kontos \\'%1$s\\' löschen?</string>\n    <string name=\"rel_100\">Zuverlässige temporäre Antworten</string>\n    <string name=\"rel_100_help\">Wenn angeklickt gibt das Unterstützung für zuverlässige vorläufige Antworten an (RFC 3262).</string>\n    <string name=\"redirect_mode_help\">Bestimmt ob eine Rufumleitungsanfrage automatisch befolgt wird oder erst nach einer Bestätigung (manuell).</string>\n    <string name=\"you\">Du</string>\n    <string name=\"invalid_aor\">Ungültiger benutzer@domain[:port][;transport=udp|tcp|tls] \\'%1$s\\'</string>\n    <string name=\"long_chat_question\">Möchten Sie den Chat mit Partner \\'%1$s\\' löschen oder Partner zu den Kontakten hinzufügen?</string>\n    <string name=\"new_chat_peer\">Neuer Chat Partner</string>\n    <string name=\"outbound_proxies_help\">SIP URI von einem oder zwei Proxies, die beim Senden von Anfragen verwendet werden müssen. Wenn zwei angegeben sind werden REGISTER Anfragen an beide gesendet, andere Anfragen an den ersten antwortenden. Wenn keine Proxies für ausgehende Verbindungen angegeben sind, werden Verbindungen basiert auf DNS NAPTR/SRV/A record lookup des hostparts der URI der Rufadresse gesendet. Wenn der hostpart der SIP URI eine IPv6 Adresse ist, muß diese in eckigen Klammern geschrieben werden [].\n\\nBeispiele:\n\\n • sip:example.com:5061;transport=tls\n\\n • sip:[2001:67c:223:777::10];transport=tcp\n\\n • sip:192.168.43.50:443;transport=wss</string>\n    <string name=\"stun_server_help\">Ein STUN/TURN Server URI in der Form: schema:host[:port][?transport=udp|tcp], wobei schema \\'stun\\', \\'stuns\\', \\'turn\\', oder \\'turns\\' ist. Voreingestellter STUN Server für STUN und ICE Protokoll ist \\'stun:stun.l.google.com:19302\\' der zum öffentlichen Google STUN Server verweist. Es gibt keinen voreingestellten TURN Server.</string>\n    <string name=\"media_encryption_help\">Wählt eine (oder keine) Medientransportverschlüsselung.\n\\n • ZRTP (recommended) means that ZRTP end-to-end media encryption negotiation is tried afterthe call has been established.\n\\n • DTLS-SRTPF means that UDP/TLS/RTP/SAVPF is offered in outgoing call and that RTP/SAVP,RTP/SAVPF, UDP/TLS/RTP/SAVP, or UDP/TLS/RTP/SAVPF is used if offered in incoming call.\n\\n • SRTP-MANDF means that RTP/SAVPF is offered in outgoing call and required in incoming call.\n\\n • SRTP-MAND means that RTP/SAVP is offered in outgoing call and required in incoming call.\n\\n • SRTP means that RTP/AVP is offered in outgoing call and that RTP/SAVP or RTP/SAVPF is usedif offered in incoming call.</string>\n    <string name=\"start_failed\">Baresip konnte nicht gestartet werden. Das kann wegen eines ungültigen Einstellungswertes sein. Überprüfen Sie die Hören (Listen) Adresse, TLS Zertifikat, TLS CA File. Starten Sie dann baresip neu.</string>\n    <string name=\"audio_permissions\">baresip benötigt die \\\"Mikrofon\\\"-Berechtigung für Sprachanrufe, die \\\"Geräte in der Nähe\\\"-Berechtigung für Bluetooth-Mikrofon/Hörer-Erkennung, die \\\"Benachrichtigungen\\\"-Berechtigung für die Benachrichtigungen und bei Android 9 die \\\"Speicher\\\"-Berechtigung zur Datensicherung/ Wiederherstellung.</string>\n    <string name=\"about_text\"><![CDATA[\n        <h1>SIP User Agent basierend auf der baresip Bibliothek </h1>\n        <br>\n        <p>Juha Heinanen &lt;jh@tutpro.com&gt;</p>\n        <p>Version %1$s</p>\n        <br>\n        <h2>Benutzerhinweise</h2>\n        <ul>\n            <li>Überprüfen Sie, dass die Einstellungen in baresip Ihren Bedürfnissen entsprechen (tippen Sie auf die Überschriften für Hilfe).</li>\n            <li>Unter Konten, erstellen Sie mindestens eines (tippen Sie für Hilfe auf die einzelnen Punkte).</li>\n            <li>Der Registrierungsstatus eines Kontos wird mit einem farbigen Punkt markiert: grün (Registrierung erfolgreich), gelb (Registrierung im Gange), rot (Registrierung fehlgeschlagen), weiß (Konto ist nicht aktiviert).</li>\n            <li>Tippen auf den Status-Punkt öffnet direkt die Kontokonfiguration.</li>\n            <li>Ein langes Antippen der Navigationssymbole zeigt weitere Informationen zu den Symbolen an.</li>\n            <li>Nach-unten-Wischen registriert das aktuelle Konto erneut.</li>\n            <li>Langes Drücken auf das gegenwärtige Konto aktiviert/ deaktiviert die Registrierung.</li>\n            <li>Links/Rechts-Wischen schaltet durch die verschiedenen Konten.</li>\n            <li>Durch Berühren des Anruf-Symbols kann der letzte Anrufer erneut gewählt werden, wenn das Anruffeld leer ist.</li>\n            <li>Anrufer und Absender von Nachrichten können durch langes Drücken zu den Kontakten hinzugefügt werden.</li>\n            <li>Langes Drücken kann ebenfalls verwendet werden um Anrufe, Chats, Nachrichten und Kontakte zu löschen.</li>\n            <li>Drücken/langes Drücken eines Kontaktfotos kann benutzt werden, um das Bild zu installieren/ entfernen.</li>\n            <li>Langes Drücken eines Audiocodecs kann den Codec aktivieren/ deaktivieren.</li>\n            <li>Im <a href=\"https://github.com/juha-h/baresip-studio/wiki\">Wiki</a> finden sich mehr Informationen.</li>\n        </ul>\n        <h2>Datenschutzerklärung</h2>\n            <p>Die Datenschutzerklärung ist <a href=\"https://raw.githubusercontent.com/juha-h/baresip-studio/master/PrivacyPolicy.txt\">hier</a> (Englisch) zu finden.</p>\n        <br>\n        <h2>Quellcode</h2>\n            <p>Der Quellcode ist auf <a href=\"https://github.com/juha-h/baresip-studio\">GitHub</a> verfügbar,\n            wo auch Fehler gemeldet werden können.</p>\n        <br>\n        <h2>Lizenzen</h2>\n        <ul>\n            <li><b>BSD-3-Clause</b> außer Folgendem:</li>\n            <li><b>Apache 2.0</b> AMR-Codecs und TLS-Sicherheit</li>\n            <li><b>AGPLv4</b> ZRTP-Medienverschlüsselung</li>\n            <li><b>GNU LGPL 2.1</b> G.722, G.726, und Codec2 Codecs</li>\n            <li><b>GNU GPLv3</b> G.729 Codec</li>\n        </ul>\n        ]]></string>\n    <string name=\"about_text_plus\"><![CDATA[\n        <h1>SIP User Agent basierend auf der baresip Bibliothek </h1>\n        <br>\n        <p>Juha Heinanen &lt;jh@tutpro.com&gt;</p>\n        <p>Version %1$s</p>\n        <br>\n        <h2>Benutzerhinweise</h2>\n        <ul>\n            <li>Überprüfen Sie, dass die Einstellungen in baresip+ Ihren Bedürfnissen entsprechen (tippen Sie auf die Überschriften für Hilfe).</li>\n            <li>Unter Konten, erstellen Sie mindestens eines (tippen Sie für Hilfe auf die einzelnen Punkte).</li>\n            <li>Der Registrierungsstatus eines Kontos wird mit einem farbigen Punkt markiert: grün (Registrierung erfolgreich), gelb (Registrierung im Gange), rot (Registrierung fehlgeschlagen), weiß (Konto ist nicht aktiviert).</li>\n            <li>Tippen auf den Status-Punkt öffnet direkt die Kontokonfiguration.</li>\n            <li>Ein langes Antippen der Navigationssymbole zeigt weitere Informationen zu den Symbolen an.</li>\n            <li>Nach-unten-Wischen registriert das aktuelle Konto erneut.</li>\n            <li>Langes Drücken auf das gegenwärtige Konto aktiviert/ deaktiviert die Registrierung.</li>\n            <li>Links/Rechts-Wischen schaltet durch die verschiedenen Konten.</li>\n            <li>Durch Berühren des Anruf-Symbols kann der letzte Anrufer erneut gewählt werden, wenn das Anruffeld leer ist.</li>\n            <li>Anrufer und Absender von Nachrichten können durch langes Drücken zu den Kontakten hinzugefügt werden.</li>\n            <li>Langes Drücken kann ebenfalls verwendet werden um Anrufe, Chats, Nachrichten und Kontakte zu löschen.</li>\n            <li>Drücken/langes Drücken eines Kontaktfotos kann benutzt werden, um das Bild zu installieren/ entfernen.</li>\n            <li>Langes Drücken eines Audio-/ Videocodecs kann den Codec aktivieren/ deaktivieren.</li>\n            <li>Im <a href=\"https://github.com/juha-h/baresip-studio/wiki\">Wiki</a> finden sich mehr Informationen.</li>\n        </ul>\n        <h2>Bekannte Probleme</h2>\n            <ul>\n                <li>Bei Videoanrufen muß das Gerät quer gehalten werden, um 90 Grad nach links geneigt, von der Hochkantposition betrachtet.</li>\n                <li>Das eigene Bild wird nicht richtig gezeigt, wenn Video auf Nursenden eingestellt ist.</li>\n            </ul>\n        <h2>Datenschutzerklärung</h2>\n            <p>Die Datenschutzerklärung ist <a href=\"https://raw.githubusercontent.com/juha-h/baresip-studio/master/PrivacyPolicy.txt\">hier</a> (Englisch) zu finden.</p>\n        <br>\n        <h2>Quellcode</h2>\n            <p>Der Quellcode ist auf <a href=\"https://github.com/juha-h/baresip-studio\">GitHub</a> verfügbar,\n            wo auch Fehler gemeldet werden können.</p>\n        <br>\n        <h2>Lizenzen</h2>\n        <ul>\n            <li><b>BSD-3-Clause</b> außer Folgendem:</li>\n            <li><b>Apache 2.0</b> AMR-Codecs und TLS-Sicherheit</li>\n            <li><b>AGPLv4</b> ZRTP-Medienverschlüsselung</li>\n            <li><b>GNU LGPL 2.1</b> G.722, G.726, und Codec2 Codecs</li>\n            <li><b>GNU GPLv3</b> G.729 Codec</li>\n            <li><b>GNU GPLv2</b> H.264 und H.265 Codecs</li>\n            <li><b>AOMedia</b> AV1 Codec</li>\n        </ul>\n        ]]></string>\n    <string name=\"country_code_help\">E.164 Ländercode dieses Kontos. Wenn der Benutzerteil der Von-URI des eingehenden Anrufs oder der Nachricht eine Telefonnummer enthält, die nicht mit \\'+\\'-Zeichen beginnt und wenn der Kontakt nicht bekannt ist, wird der Nummer dieser Ländercode vorangestellt und die Kontaktsuche beginnt erneut. Wenn die Telefonnummer mit einer einzigen Ziffer \\'0\\' beginnt, wird die Zahl \\'0\\' entfernt, bevor der Ländercode vorangestellt wird.</string>\n    <string name=\"accounts_help\">SIP URI des neuen Kontos nach dem Muster &lt;user&gt;@&lt;domain&gt;[:&lt;port&gt;][;transport=udp|tcp|tls]. Wird &lt;port&gt; angegeben und das Transportprotokoll nicht angegeben, wird das Transportprotokoll standardmäßig auf UDP gesetzt. Wenn &lt;port&gt; nicht und das Transportprotokoll schon angegeben ist, wird &lt;port&gt; 5060 oder 5061 eingestellt (TLS). Wenn weder noch angegeben wird und kein Proxy für ausgehende Verbindungen angegeben wird, wird der Registrar (falls vorhanden) des Kontos ausschließlich auf Basis der DNS-Informationen der Domain ermittelt.</string>\n    <string name=\"listen_address_help\">IP-Adresse und Port in der Form \\'address:port\\', an der Baresip für eingehende SIP-Anfragen lauscht. Wenn die IP-Adresse eine IPv6-Adresse ist, muss sie innerhalb von Klammern geschrieben werden []. IPv4 Adresse 0.0.0.0 oder IPv6 Adresse [:] läßt Baresip an allen verfügbaren Adressen lauschen. Wenn leer gelassen (Standard), hört Baresip auf einem zufälligen Port aller verfügbaren Adressen.</string>\n    <string name=\"address_family_help\">Wählt, welche IP-Adressen Baresip verwendet. Wird IPv4 oder IPv6 gewählt, verwendet Baresip nur IPv4 oder IPv6 Adressen. Wird nichts gewählt, verwendet Baresip sowohl IPv4 als auch IPv6 Adressen.</string>\n    <string name=\"dns_servers_help\">Kommagetrennte Liste von Adressen von DNS-Servern. Wenn nicht angegeben, werden DNS-Serveradressen dynamisch aus dem System gewonnen. Jede DNS-Adresse ist in der Form \\'ip:port\\' oder \\'ip\\'. Wenn der Port weggelassen ist, wird dieser auf 53 gesetzt. Wenn ip eine IPv6-Adresse ist und auch Port angegeben ist, muss ip in Klammern geschrieben werden []. Als Beispiel zeigt die Liste \\'8.8.8.8.8:53,[2001:4860:4860:::888888]:53\\' auf IPv4 und IPv6 Adressen öffentlicher Google DNS-Server.</string>\n    <string name=\"tls_certificate_file_help\">Falls angeklickt, wurde oder wird eine Datei mit TLS-Zertifikat und privatem Schlüssel dieser Baresip-Instanz geladen. In Android-Version 9 wird eine Datei namens \\'cert.pem\\' aus dem Download-Ordner geladen. Löschen Sie aus Sicherheitsgründen diese Datei nach dem Laden.</string>\n    <string name=\"video_codecs\">Video Codecs</string>\n    <string name=\"audio_settings\">Audioeinstellungen</string>\n    <string name=\"backup\">Sicherungskopie</string>\n    <string name=\"rtcp_mux\">RTCP Multiplexverfahren</string>\n    <string name=\"invalid_opus_packet_loss\">Ungültiger Opus Paketverlustprozentsatz</string>\n    <string name=\"default_call_volume\">Standardmäßige Anruflautstärke</string>\n    <string name=\"stun_server\">STUN/TURN Server</string>\n    <string name=\"dtmf_inband\">RTP Ergeignisse im Frequenzband</string>\n    <string name=\"audio_codecs\">Audio Codecs</string>\n    <string name=\"tls_ca_file\">TLS CA Datei</string>\n    <string name=\"contacts_consent\">Wenn Android Kontakte gewählt ist, können diese beim Telefonieren und Verschicken von Direktnachrichten als Referenzen für SIP und tel URIs genutzt werden. Die baresip App speichert keine Android Kontakte und teilt diese auch nicht. Um Android Kontakte in baresip verfügbar zu machen, erfordert Google Ihre Zustimmung zur Nutzung der Kontakte nach den Angaben in der <a href=\"https://raw.githubusercontent.com/juha-h/baresip-studio/master/PrivacyPolicy.txt\">Datenschutzerklärung</a> der App.</string>\n    <string name=\"attended\">Mit Rückfrage (attended)</string>\n    <string name=\"choose_destination_uri\">Ziel-URI auswählen</string>\n    <string name=\"transfer\">Transfer</string>\n    <string name=\"transfer_request_query\">Diesen Anruf zu \\'%1$s\\' übertragen?</string>\n    <string name=\"call_request_query\">Anrufsanfrage akzeptieren und \\'%1$s\\' anrufen?</string>\n    <string name=\"restore_unzip_failed\">App-Daten konnten nicht wiederhergestellt werden. Ab Android 14 einschließlich können Daten nicht wiederhergestellt werden, die vor %1$s Version %2$s als Sicherungskopie gespeichert wurden.</string>\n    <string name=\"audio_delay\">Tonverzögerung</string>\n    <string name=\"info\">Informationen</string>\n    <string name=\"speaker_phone\">Lautsprecher</string>\n    <string name=\"speaker_phone_help\">Falls angekreuzt, wird die Lautsprecherfunktion automatisch aktiviert, wenn der Anruf beginnt.</string>\n    <string name=\"audio_modules_title\">Audiomodule</string>\n    <string name=\"audio_modules_help\">Audio Codecs, die von den ausgewählten Modulen bereitgestellt werden, sind für die Benutzer verfügbar.</string>\n    <string name=\"opus_bit_rate\">Opus Bitrate</string>\n    <string name=\"opus_bit_rate_help\">Durchschnittliche maximale Bitrate, die vom Opus Audiostream genutzt wird. Gültige Werte sind 6000-510000. Die Werkseinstellung beträgt 28000.</string>\n    <string name=\"opus_packet_loss\">Erwarteter Paketverlust von Opus</string>\n    <string name=\"opus_packet_loss_help\">Erwarteter Paketverlust des Opus Audiostreams in Prozent. Werkseinstellung ist 1. Der Wert 0 deaktiviert die Fehlerkorrektur von Opus (FEC).</string>\n    <string name=\"invalid_opus_bitrate\">Ungültige Opus Bitrate</string>\n    <string name=\"audio_delay_help\">Zeit (in Millisekunden), um auf Ton vom Angerufenen zu warten, sobald eine Verbindung zustandekommt. Erhöhen Sie den Wert, wenn Tonverlust am Anfang des Telefonats auftritt.</string>\n    <string name=\"invalid_audio_delay\">Die Tonverzögerung von \\'%1$s\\' ist ungültig. Gültige Werte liegen zwischen 100 und 3000.</string>\n    <string name=\"dark_theme_help\">Immer das dunkle Farbschema nutzen</string>\n    <string name=\"debug\">Debug</string>\n    <string name=\"debug_help\">Falls angekreuzt, werden Debug- und Info-Level Lognachrichten für Logcat bereitgestellt.</string>\n    <string name=\"reset_config\">Auf Werkseinstellungen zurücksetzen</string>\n    <string name=\"reset\">Zurücksetzen</string>\n    <string name=\"config_restart\">Um die neuen Einstellungen zu aktivieren, ist ein Neustart von baresip erforderlich. Jetzt neustraten?</string>\n    <string name=\"read_cert_error\">Datei \\'cert.pem\\' konnte nicht gelesen werden.</string>\n    <string name=\"read_ca_certs_error\">Datei \\'ca_certs.crt\\' konnte nicht gelesen werden.</string>\n    <string name=\"video_request\">Videoanfrage</string>\n    <string name=\"allow_video\">Zustimmen, dass Video mit \\'%1$s\\' gesendet und empfangen wird?</string>\n    <string name=\"allow_video_recv\">Zustimmen, dass Video von \\'%1$s\\' empfangen wird?</string>\n    <string name=\"call_transfer\">Anruftransfer</string>\n    <string name=\"blind\">Ohne Rückfrage (blind)</string>\n    <string name=\"jitter\">Jitter: %1$s (ms)</string>\n    <string name=\"redirect_notice\">Automatische Weiterleitung zu \\'%1$s\\'\\\\</string>\n    <string name=\"redirect_request\">Umleitungsanfrage</string>\n    <string name=\"redirect_request_query\">Einer Anrufsumleitung zu \\'%1$s\\' zustimmen?</string>\n    <string name=\"call_request\">Anrufsanfrage</string>\n    <string name=\"audio_focus_denied\">Audio Fokus nicht gewährt!</string>\n    <string name=\"permissions_rationale\">Gründe für die Berechtigungen</string>\n    <string name=\"invalid_fps\">Ungültige Bilder pro Sekunde: \\'%1$d\\'</string>\n    <string name=\"reset_config_alert\">Wollen Sie wirklich die Einstellungen auf die Werkseinstellungen zurücksetzen?</string>\n    <string name=\"status\">Status</string>\n    <string name=\"verify_sas\">SAS &lt;%1$s&gt; verifizieren?</string>\n    <string name=\"transfer_request\">Transferanfrage</string>\n    <string name=\"allow_video_send\">Zustimmen, dass Video zu \\'%1$s\\' gesendet wird?</string>\n    <string name=\"verify\">Anfrage verifizieren</string>\n    <string name=\"sip_trace\">SIP Trace (Zurückverfolgung)</string>\n    <string name=\"sip_trace_help\">Falls angekreuzt und falls Debug aktiviert ist, werden Logcat Nachrichten SIP Anfragen und Antwort-Traces enthalten. Wird beim Starten von baresip deaktiviert.</string>\n    <string name=\"ok\">OK</string>\n    <string name=\"failed_to_load_module\">Fehler beim Laden des Moduls.</string>\n    <string name=\"contacts_help\">Wählt aus, ob baresip Kontakte, Android Kontakte, oder beide verwendet werden. Falls beide verwendet werden, werden bei Kontakten, die in beiden Kontaktverzeichnissen vorkommen, die baresip Kontakte genutzt.</string>\n    <string name=\"both\">Beide</string>\n    <string name=\"reset_config_help\">Falls angekreuzt, werden die Einstellungen auf die Werkseinstellungen zurückgesetzt.</string>\n    <string name=\"transfer_destination\">Transferziel</string>\n    <string name=\"transfer_failed\">Transfer fehlgeschlagen</string>\n    <string name=\"dtmf\">DTMF</string>\n    <string name=\"codecs\">Codecs</string>\n    <string name=\"dark_theme\">Dunkles Farbschema</string>\n    <string name=\"dtmf_auto\">RTP oder SIP INFO im Frequenzband</string>\n    <string name=\"tls_ca_file_help\">Falls angekreuzt, wurde/ wird eine Datei geladen werden, die ein TLS Zertifikat einer Zertifikatsauthorität beinhaltet, die nicht in Android vorhanden ist. In der Android Version 9 wird die Datei \\'ca_certs.crt\\' aus dem Download-Verzeichnis geladen.</string>\n    <string name=\"address_family\">Adressenfamilie</string>\n    <string name=\"media_nat\">NAT Durchquerung von Medien</string>\n    <string name=\"default_call_volume_help\">Falls aktiviert, liegt die standardmäßige Anruflautstärke auf einer Skala von 1-10.</string>\n    <string name=\"video_size\">Größe der Videoframes</string>\n    <string name=\"video_fps\">Videoframes pro Sekunde</string>\n    <string name=\"video_fps_help\">Bildwiederholungsrate, die während einem SDP-Handshake angeboten werden wird. Gültige Werte von 10 bis 30.</string>\n    <string name=\"video_size_help\">Größe der gesendeten Videoframes (Breite x Höhe)</string>\n    <string name=\"tone_country\">Herkunftsland der Töne</string>\n    <string name=\"tone_country_help\">Herkunftsland des Freitons, Halttons und Besetzttons</string>\n    <string name=\"consent_request\">Zustimmungsanfrage</string>\n    <string name=\"microphone_gain\">Mikrofonverstärkung</string>\n    <string name=\"microphone_gain_help\">Die Mikrofonlautstärke wird mit dieser Dezimalzahl multipliziert. Der Minimalwert ist 1.0 (Werkseinstellung) und deaktiviert die Mikrofonverstärkung. Größere Werte können die Tonqualität negativ beeinflussen.</string>\n    <string name=\"invalid_microphone_gain\">Ungültiger Wert für die Mikrofonverstärkung</string>\n    <string name=\"user_agent\">User Agent</string>\n    <string name=\"user_agent_help\">Benutzerdefinierter Wert für das SIP request/response User-Agent header-Feld</string>\n    <string name=\"invalid_user_agent\">Ungültiger Wert für das User-Agent header-Feld</string>\n    <string name=\"numeric_keypad\">Ziffernblock</string>\"\n    <string name=\"numeric_keypad_help\">Falls angekreuzt, wird der Ziffernblock angezeigt, sobald das \\\"Anruf an ...\\\"- Feld fokussiert wird.</string>\"\n    <string name=\"microphone_title\">Mikrofon</string>\n    <string name=\"microphone_tip\">Falls in einem Anruf aktiviert, wird das Mikrofon stummgestellt.</string>\n    <string name=\"speakerphone_title\">Lautsprecher</string>\n    <string name=\"no_read_permission\">Keine Berechtigung, den externen Speicher zu lesen</string>\n    <string name=\"favorite_help\">Falls angekreuzt, wird der Kontakt mit anderen Favoriten am Anfang der Kontaktliste angezeigt.</string>\n    <string name=\"speakerphone_tip\">Falls aktiviert, wird der Ton über den Lautsprecher des Geräts abgespielt.</string>\n    <string name=\"ringtone\">Klingelton</string>\n    <string name=\"select_ringtone\">Klingelton auswählen</string>\n    <string name=\"no_aec\">Keine hardwarebasierte akustische Echounterdrückung!</string>\n    <string name=\"call_recording_title\">Anrufsaufzeichnung</string>\n    <string name=\"call_recording_tip\">Falls aktiviert, werden ein-/ ausgehende Anrufe aufgenommen. Aufnahmen lassen sich unter den Anrufsdetails abspielen</string>\n    <string name=\"playing_recording\">Spiele Aufnahme ab…</string>\n    <string name=\"reply\">Antworten</string>\n    <string name=\"save\">Speichern</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-el/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"about_title\">Σχετικά για το baresip</string>\n    <string name=\"account\">Λογαριασμός</string>\n    <string name=\"display_name\">Εμφανιζόμενο όνομα</string>\n    <string name=\"display_name_help\">Ονομασία (εάν υπάρχει) που χρησιμοποιείται στο URI εξερχόμενων αιτήσεων.</string>\n    <string name=\"sip_uri_of_proxy_server\">SIP URI του διακομιστή μεσολάβησης</string>\n    <string name=\"sip_uri_of_another_proxy_server\">SIP URI του άλλου διακομιστή μεσολάβησης</string>\n    <string name=\"register\">Εγγραφή</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-es/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <!-- About Activity -->\n    <string name=\"about_title\">Acerca de Baresip</string>\n    <string name=\"about_text\"><![CDATA[\n        <h1> Agente de Usuario SIP basado en la biblioteca <a href=\"https://github.com/baresip/baresip\">baresip</a> </h1>\n        <br>\n        <p> Juha Heinanen &lt;jh@tutpro.com&gt; </p>\n        <p> Versión %1$s </p>\n         <br>\n        <h2> Consejos de uso </h2>\n        <ul>\n            <li> Compruebe que los valores predeterminados en la configuración de baresip se ajusten a sus necesidades\n                (toque los títulos de los elementos para obtener ayuda). </li>\n            <li> Luego, en Cuentas, crea una o más cuentas (nuevamente toca los títulos de los elementos para obtener ayuda). </li>\n            <li> El estado de registro de una cuenta se muestra con un punto de color: verde (registro logrado),\n                amarillo (el registro está en proceso), rojo (el registro falló), blanco (el registro está en progreso)\n                no ha sido activado). </li>\n            <li> Al tocar el punto de estado se accede directamente a la configuración de la cuenta. </li>\n            <li> Mantén pulsado un icono de la barra de baresip para ver información sobre dicho icono.</li>\n            <li> El gesto de deslizar hacia abajo provoca el registro nuevo de la cuenta mostrada actualmente. </li>\n            <li> Al mantener pulsada la cuenta que se muestra actualmente, se habilita o deshabilita el registro de la cuenta. </li>\n            <li> El gesto de deslizar hacia la izquierda o hacia la derecha alterna entre las cuentas. </li>\n            <li> Se puede volver a seleccionar la persona con la que se llamó anteriormente tocando el icono de llamada cuando el campo Destinatario esté vacío.</li>\n            <li> Las parejas de llamadas y mensajes se pueden agregar a los contactos mediante toques prolongados. </li>\n            <li> Los toques prolongados también se pueden utilizar para eliminar llamadas, chats, mensajes y contactos. </li>\n            <li> Se puede utilizar el toque o toque prolongado del icono de contacto para instalar o desinstalar la imagen del avatar. </li>\n            <li> Mantener pulsado un códice de audio permite activarlo o desactivarlo.</li>\n            <li> Consulte <a href=\"https://github.com/juha-h/baresip-studio/wiki\"> Wiki </a> para obtener más información.</li>\n        </ul>\n        <h2> Directiva de privacidad </h2>\n            <p>La directiva de privacidad está disponible <a href=\"https://raw.githubusercontent.com/juha-h/baresip-studio/master/PrivacyPolicy.txt\">aquí </a> .</p>\n        <br>\n        <h2> Código Fuente</h2>\n            <p>El código fuente está disponible en <a href=\"https://github.com/juha-h/baresip-studio\"> GitHub </a> ,\n            donde también se pueden reportar problemas.</p>\n          <br>\n        <h2>Traducciones de Idioma</h2>\n            <p>Las traducciones de idioma son gestionadas por baresip en el proyecto\n                <a href=\"https://hosted.weblate.org/projects/baresip/\">Weblate</a>.</p>\n        <br>\n        <h2> Licencias </h2>\n        <ul>\n            <li> <b> Cláusula BSD-3 </b> excepto lo siguiente: </li>\n            <li> <b> Codecs AMR y seguridad TLS de Apache 2.0 </b>\n            <li> <b> AGPLv4 </b> Cifrado de medios ZRTP </li>\n            <li> <b> GNU LGPL 2.1</b> codec Codec2</li>\n            <li> <b> Libre</b> G.722 codec</li>\n            <li> <b> GNU GPLv3 </b> Codec G.729 </li>\n        </ul>\n        ]]></string>\n    <!-- Account Activity -->\n    <string name=\"account\">Cuenta</string>\n    <string name=\"display_name\">Nombre para mostrar</string>\n    <string name=\"display_name_help\">Nombre (si lo hay) utilizado en el URI de origen de las solicitudes salientes.</string>\n    <string name=\"authentication_username\">Nombre de usuario de autenticación</string>\n    <string name=\"authentication_username_help\">Nombre de usuario de autenticación si se requiere la autenticación de las solicitudes SIP. El valor por defecto es el nombre de usuario de la cuenta.</string>\n    <string name=\"authentication_password\">Contraseña de autenticación</string>\n    <string name=\"authentication_password_help\">Contraseña de autenticación de hasta 64 caracteres. Si se proporciona el nombre de usuario, pero no la contraseña, esta se le pedirá cuando inicie baresip.</string>\n    <string name=\"outbound_proxies\">Proxies salientes</string>\n    <string name=\"outbound_proxies_help\">URI SIP de uno o dos proxies que deben utilizarse al enviar las solicitudes. Si se dan dos, las solicitudes de REGISTRO se envían a ambos y las demás solicitudes se envían a uno que responda. Si no se indica ningún proxy de salida, las solicitudes se envían basándose en la búsqueda del registro DNS NAPTR/SRV/A de la parte de host del URI del destinatario. Si la parte del host del URI SIP es una dirección IPv6, la dirección debe escribirse entre corchetes [].\n\\nEjemplos:\n\\n - sip:ejemplo.com:5061;transporte=tls\n\\n • sip:[2001:67c:223:777::10];transport=tcp\n\\n • sip:192.168.43.50:443;transport=wss</string>\n    <string name=\"sip_uri_of_proxy_server\">SIP URI del servidor proxy</string>\n    <string name=\"sip_uri_of_another_proxy_server\">SIP URI de otro servidor proxy</string>\n    <string name=\"register\">Registrar</string>\n    <string name=\"register_help\">Si está marcado, el registro está habilitado y las solicitudes de REGISTRO se envían en el intervalo especificado por Intervalo de registro.</string>\n    <string name=\"audio_codecs\">Códecs de audio</string>\n    <string name=\"media_nat\">Media NAT Transversal</string>\n    <string name=\"media_nat_help\">Selecciona el protocolo transversal de NAT media (si lo hay). Las posibles opciones son STUN\n        (Utilidades de recorrido de sesión para NAT, RFC 5389) e ICE (conectividad interactiva\n        Establecimiento, RFC 5245).\n    </string>\n    <string name=\"stun_server\">Servidor STUN/TURN</string>\n    <string name=\"stun_server_help\">Un URI de servidor STUN/TURN de la forma scheme:host[:port][\\?transport=udp|tcp], donde scheme es \\'stun\\', \\'stuns\\', \\'turn\\', o \\'turns\\'. El servidor STUN predeterminado de fábrica para los protocolos STUN e ICE es \\'stun:stun.l.google.com:19302\\' que apunta al servidor STUN público de Google. No hay servidor TURN por defecto.</string>\n    <string name=\"media_encryption\">Cifrado de medios</string>\n    <string name=\"media_encryption_help\">Selecciona el protocolo de cifrado de transporte de medios (si lo hay).\n\\n • ZRTP (recomendado) significa que la negociación de cifrado de medios de extremo a extremo de ZRTP se intenta después que\n            la llamada ha sido establecida.\n\\n • DTLS-SRTPF significa que UDP / TLS / RTP / SAVPF se ofrece en llamadas salientes y que RTP / SAVP,\n            RTP / SAVPF, UDP / TLS / RTP / SAVP o UDP / TLS / RTP / SAVPF se usa si se ofrece en la llamada entrante.\n\\n • SRTP-MANDF significa que RTP / SAVPF se ofrece en llamadas salientes y se requiere en llamadas entrantes.\n\\n • SRTP-MAND significa que RTP / SAVP se ofrece en llamadas salientes y se requiere en llamadas entrantes.\n\\n • SRTP significa que RTP / AVP se ofrece en llamadas salientes y que se utiliza RTP / SAVP o RTP / SAVPF\n            si se ofrece en llamada entrante.\n    </string>\n    <string name=\"answer_mode\">Modo de contestación</string>\n    <string name=\"answer_mode_help\">Selecciona cómo se contestan las llamadas entrantes.</string>\n    <string name=\"manual\">Manual</string>\n    <string name=\"auto\">Automático</string>\n    <string name=\"voicemail_uri\">URI de correo de voz</string>\n    <string name=\"voicemain_uri_help\">URI de SIP para comprobar los mensajes de correo de voz. Si se deja vacío, no se suscribirá a los mensajes de correo de voz (indicaciones de mensaje en espera).</string>\n    <string name=\"default_account\">Cuenta predeterminada</string>\n    <string name=\"default_account_help\">Si está marcada, esta cuenta se selecciona cuando se inicia baresip.\n    </string>\n    <!-- Accounts Activity -->\n    <string name=\"accounts\">Cuentas</string>\n    <string name=\"invalid_aor\">usuario@dominio[:puerto][;transport=udp|tcp|tls] «%1$s» no válido</string>\n    <string name=\"account_exists\">Ya existe la cuenta «%1$s».</string>\n    <string name=\"account_allocation_failure\">Error al asignar la cuenta nueva.</string>\n    <string name=\"encrypt_password\">Contraseña de cifrado</string>\n    <string name=\"decrypt_password\">Contraseña de descifrado</string>\n    <string name=\"delete_account\">¿Quiere eliminar la cuenta «%1$s»\\?</string>\n    <!-- Baresip Service -->\n    <string name=\"transfer_request\">Solicitud de transferencia</string>\n    <!-- Calls Activity -->\n    <string name=\"call_history\">Historial de llamadas</string>\n    <string name=\"call\">Llamada</string>\n    <string name=\"calls_calls\">llamadas</string>\n    <string name=\"calls_call\">llamar</string>\n    <string name=\"calls_add_delete_question\">¿Quiere añadir a «%1$s» a los contactos o eliminar %2$s del historial de llamadas\\?</string>\n    <string name=\"calls_delete_question\">¿Quiere eliminar «%1$s» %2$s del historial de llamadas\\?</string>\n    <string name=\"disable_history\">Desactivar</string>\n    <string name=\"enable_history\">Activar</string>\n    <string name=\"delete_history_alert\">¿Quiere eliminar el historial de llamadas de la cuenta «%1$s»\\?</string>\n    <!-- Chat Activity -->\n    <string name=\"chat_with\">Chatear con %1$s</string>\n    <string name=\"new_message\">Mensaje nuevo</string>\n    <string name=\"long_message_question\">¿Quiere eliminar el mensaje o añadir el par «%1$s» a los contactos\\?</string>\n    <string name=\"short_message_question\">¿Quiere eliminar el mensaje\\?</string>\n    <string name=\"add_contact\">Añadir contacto</string>\n    <string name=\"sending_failed\">Envío de mensaje fallido</string>\n    <string name=\"message_failed\">Ha fallado</string>\n    <!-- Chats Activity -->\n    <string name=\"chats\">Historial de chat</string>\n    <string name=\"today\">Hoy</string>\n    <string name=\"you\">Usted</string>\n    <string name=\"new_chat_peer\">Nuevo compañero de chat</string>\n    <string name=\"long_chat_question\">¿Quieres eliminar el chat con un compañero? \\'%1$s\\' o\n        agregar pares a los contactos?</string>\n    <string name=\"short_chat_question\">¿Quiere eliminar el chat con «%1$s»\\?</string>\n    <string name=\"delete_chats_alert\">¿Quiere eliminar el historial de chat de la cuenta «%1$s»\\?</string>\n    <!-- Config Activity -->\n    <string name=\"configuration\">Configuración</string>\n    <string name=\"start_automatically\">Comenzar automáticamente</string>\n    <string name=\"start_automatically_help\">Si está marcado, baresip se inicia automáticamente tras arrancar el dispositivo o tras estár instalada una versión nueva de baresip. El inicio está retardado hasta que el dispositivo sea desbloqueado.</string>\n    <string name=\"listen_address\">Dirección de escucha</string>\n    <string name=\"listen_address_help\">Dirección IP y puerto de formulario \\'address:port\\' en el que escucha baresip para solicitudes SIP entrantes. Si la dirección IP es una dirección IPv6, debe escribirse dentro soportes []. La dirección IPv4 0.0.0.0 o la dirección IPv6 [::] hace que la escucha de baresip sea Direcciones disponibles. Si se deja vacío (predeterminado de fábrica), baresip escucha en un puerto aleatorio en todas las direcciones disponibles.</string>\n    <string name=\"invalid_listen_address\">Dirección de escucha no válida</string>\n    <string name=\"dns_servers\">Servidores DNS</string>\n    <string name=\"dns_servers_help\">Lista separada por comas de direcciones de servidores DNS. Si no se da,\n        Las direcciones del servidor DNS se obtienen dinámicamente del sistema. Cada dirección DNS es de forma\n        \\'ip:port\\' o \\'ip\\'. Si se omite el puerto, el valor predeterminado es 53. Si ip es una dirección IPv6 y\n        también se da puerto, IP debe\n        estar escrito entre corchetes []. Como ejemplo, lista \\'8.8.8.8:53,[2001:4860:4860::8888]:53\\'\n        apunta a direcciones IPv4 e IPv6 de servidores DNS públicos de Google.</string>\n    <string name=\"invalid_dns_servers\">Servidores DNS no válidos</string>\n    <string name=\"failed_to_set_dns_servers\">Error al establecer servidores DNS</string>\n    <string name=\"tls_certificate_file\">Archivo de certificado TLS</string>\n    <string name=\"tls_certificate_file_help\">Si se marca, se ha cargado o se cargará un archivo que contiene el certificado TLS y la clave privada de esta instancia de baresip. En las versiones Android 9, se carga un archivo llamado \\'cert.pem\\' desde la carpeta Download. Por razones de seguridad, elimine el archivo después de cargarlo.</string>\n    <string name=\"tls_ca_file\">Archivo de CA de TLS</string>\n    <string name=\"tls_ca_file_help\">Si está marcada, se ha cargado o se cargará un archivo que contiene certificados TLS de dichas Autoridades de Certificación que no están incluidas en el SO Android. En las versión de Android 9, se carga un archivo llamado \\'ca_certs.crt\\' desde la carpeta Download.</string>\n    <string name=\"opus_bit_rate\">Tasa de bits de Opus</string>\n    <string name=\"opus_bit_rate_help\">Velocidad de bits máxima promedio utilizada por la transmisión de audio Opus.\n        Los valores válidos son 6000-510000. El valor predeterminado de fábrica es 28000.</string>\n    <string name=\"opus_packet_loss\">Pérdida de paquetes de Opus esperada</string>\n    <string name=\"opus_packet_loss_help\">Porcentaje esperado de la pérdida de los paquetes del flujo de audio Opus, de 0 a 100. El valor predeterminado de fábrica es 1. El valor 0 también desactiva la corrección de errores hacia delante (FEC) de Opus. El valor predeterminado de fábrica es 1. El valor 0 también desactiva Corrección de errores de reenvío de Opus (FEC).</string>\n    <string name=\"invalid_opus_bitrate\">Tasa de bits de Opus no válida</string>\n    <string name=\"invalid_opus_packet_loss\">Porcentaje de pérdida de paquetes opus no válido</string>\n    <string name=\"default_call_volume\">Volumen de llamada predeterminado</string>\n    <string name=\"default_call_volume_help\">Si está configurado, el volumen de audio predeterminado de la llamada en escala 1–10.</string>\n    <string name=\"debug\">Depurar</string>\n    <string name=\"debug_help\">Si se marca, proporciona mensajes de registro de nivel de depuración e información a Logcat.</string>\n    <string name=\"reset_config\">Restablecer los valores de fábrica</string>\n    <string name=\"reset_config_help\">Si está marcado, la configuración se restablece\n        a los valores predeterminados de fábrica.</string>\n    <string name=\"read_cert_error\">Fallo al leer el archivo \\'cert.pem\\'.</string>\n    <string name=\"read_ca_certs_error\">Error al leer el archivo \\'ca_certs.crt\\'.</string>\n    <!-- Contact Activity -->\n    <string name=\"new_contact\">Contacto nuevo</string>\n    <string name=\"contact_name\">Nombre</string>\n    <string name=\"invalid_contact\">El nombre de contacto «%1$s» no es válido</string>\n    <string name=\"contact_already_exists\">Ya existe el contacto «%1$s».</string>\n    <!-- Contacts Activity -->\n    <string name=\"contacts\">Contactos</string>\n    <string name=\"contact_action_question\">¿Quieres llamar o enviar un mensaje a \\'%1$s\\'?</string>\n    <string name=\"send_message\">Enviar mensaje</string>\n    <string name=\"contact_delete_question\">¿Quiere eliminar el contacto «%1$s»\\?</string>\n    <!-- Generic -->\n    <string name=\"alert\">Alerta</string>\n    <string name=\"info\">Información</string>\n    <string name=\"notice\">Aviso</string>\n    <string name=\"cancel\">Cancelar</string>\n    <string name=\"ok\">De acuerdo</string>\n    <string name=\"yes\">Sí</string>\n    <string name=\"no\">No</string>\n    <string name=\"accept\">Aceptar</string>\n    <string name=\"deny\">Denegar</string>\n    <string name=\"add\">Añadir</string>\n    <string name=\"delete\">Eliminar</string>\n    <string name=\"edit\">Editar</string>\n    <string name=\"status\">Status</string>\n    <string name=\"error\">Error</string>\n    <!-- Main Activity -->\n    <string name=\"backup\">Copia de respaldo</string>\n    <string name=\"restore\">Restaurar</string>\n    <string name=\"about\">Acerca de</string>\n    <string name=\"restart\">Reiniciar</string>\n    <string name=\"quit\">Salir</string>\n    <string name=\"outgoing_call_to_dots\">Llamar a …</string>\n    <string name=\"incoming_call_from_dots\">Llamada de …</string>\n    <string name=\"dtmf\">DTMF</string>\n    <string name=\"call_info\">Información de llamada</string>\n    <string name=\"duration\">Duración: %1$d (segs)</string>\n    <string name=\"codecs\">Códecs</string>\n    <string name=\"rate\">Velocidad actual: %1$s (Kbits/s)</string>\n    <string name=\"voicemail_messages\">Mensajes de correo de voz</string>\n    <string name=\"you_have\">Tiene</string>\n    <string name=\"one_new_message\">un mensaje nuevo</string>\n    <string name=\"new_messages\">mensajes nuevos</string>\n    <string name=\"one_old_message\">un mensaje antiguo</string>\n    <string name=\"old_messages\">mensajes antiguos</string>\n    <string name=\"and\">y</string>\n    <string name=\"no_messages\">No tiene mensajes</string>\n    <string name=\"listen\">Escuchar</string>\n    <string name=\"call_already_active\">Ya tiene una llamada activa.</string>\n    <string name=\"start_failed\">Baresip no se ha podido iniciar. Esto puede deberse a un valor de configuración no válido. Compruebe la dirección de escucha, el archivo de certificado TLS y el archivo TLS CA. Luego reinicie baresip.</string>\n    <string name=\"registering_failed\">Registro de \\`%1$s\\` ha fallado.</string>\n    <string name=\"verify\">Verificar la solicitud</string>\n    <string name=\"verify_sas\">¿Quieres verificar SAS &lt;%1$s&gt;\\?</string>\n    <string name=\"transfer_request_query\">¿Acepta transferir la llamada a «%1$s»\\?</string>\n    <string name=\"call_failed\">Llamada fallida</string>\n    <string name=\"call_closed\">La llamada está cerrada</string>\n    <string name=\"call_not_secure\">¡Esta llamada NO es segura!</string>\n    <string name=\"peer_not_verified\">¡Esta llamada es SEGURA, pero el par NO está verificado!</string>\n    <string name=\"call_is_secure\">¡Esta llamada es SEGURA y el compañero está VERIFICADO!\n        ¿Quieres desverificar al compañero?</string>\n    <string name=\"unverify\">No verificar</string>\n    <string name=\"backed_up\">Los datos de la aplicación (excluyendo grabaciones) respaldados en el archivo \\'%1$s\\'. En las versión de Android 9, el archivo está en la carpeta Descargas.</string>\n    <string name=\"backup_failed\">Ha fallado la copia de seguridad de los datos de la aplicación en el archivo \\'%1$s\\'. Compruebe Apps → baresip → Permisos → Almacenamiento.</string>\n    <string name=\"restored\">Se restauraron los datos de la aplicación. Baresip necesita reiniciarse. ¿Quiere reiniciar ahora\\?</string>\n    <string name=\"restore_failed\">No se han podido restaurar los datos de la aplicación. Compruebe que ha dado la contraseña correcta y que el archivo de copia de seguridad es de esta aplicación. En las versión de Android 9, compruebe también Aplicaciones → baresip → Permisos → Almacenamiento y que el archivo \\'%1$s\\' existe en la carpeta Download.</string>\n    <string name=\"config_restart\">Es necesario reiniciar Baresip para que surta efecto la configuración nueva. ¿Quiere reiniciar ahora\\?</string>\n    <string name=\"audio_modules_title\">Módulos de audio</string>\n    <string name=\"audio_modules_help\">Las cuentas pueden utiilzar códecs de audio provistos por los módulos comprobados.</string>\n    <string name=\"failed_to_load_module\">Falló la carga del módulo.</string>\n    <string name=\"no_calls\">baresip necesita permiso de \\\"Micrófono\\\" para las llamadas de voz.</string>\n    <string name=\"invalid_authentication_password\">Contraseña incorrecta%1$s</string>\n    <string name=\"invalid_authentication_username\">Nombre de usuario no válido \\'%1$s\\'</string>\n    <string name=\"no_cameras\">¡No admite cámaras de vídeo!</string>\n    <string name=\"no_video_calls\">Concede permiso a \\\"Cámara\\\" para realizar o responder videollamadas.</string>\n    <string name=\"restart_request\">Solicitud de reinicio</string>\n    <string name=\"call_info_not_available\">No hay información disponible</string>\n    <string name=\"transfer_failed\">Error en la transferencia</string>\n    <string name=\"transfer\">Transferir</string>\n    <string name=\"transfer_destination\">Destino de transferencia</string>\n    <string name=\"call_transfer\">Transferencia de llamadas</string>\n    <string name=\"allow_video_recv\">¿Aceptar la recepción de vídeo de \\'%1$s\\'\\?</string>\n    <string name=\"allow_video_send\">¿Acepta el envío de video a \\'%1$s\\'\\?</string>\n    <string name=\"allow_video\">¿Aceptar el envío y la recepción de vídeo con \\'%1$s\\'\\?</string>\n    <string name=\"video_request\">Solicitud de vídeo</string>\n    <string name=\"video_call\">Videollamada</string>\n    <string name=\"confirmation\">Confirmación</string>\n    <string name=\"android_contact_help\">Si está marcada, este contacto se añade a los contactos de Android.</string>\n    <string name=\"reset\">Restablecer</string>\n    <string name=\"reset_config_alert\">¿Estás seguro de que quieres restablecer los valores de fábrica\\?</string>\n    <string name=\"sip_trace_help\">Si está marcada y si Debug está marcada, los mensajes Logcat incluyen también la petición SIP y la traza de respuesta. Desmarcado automáticamente al iniciar baresip.</string>\n    <string name=\"sip_trace\">Seguimiento SIP</string>\n    <string name=\"video_size_help\">Tamaño de los cuadros de vídeo transmitidos (ancho x alto)</string>\n    <string name=\"video_size\">Tamaño del fotograma de vídeo</string>\n    <string name=\"dark_theme_help\">Forzar el tema de la pantalla oscura</string>\n    <string name=\"dark_theme\">Tema oscuro</string>\n    <string name=\"verify_server_help\">Si está marcada, baresip verifica los certificados TLS del Agente de Usuario SIP y de los Servidores Proxy SIP cuando se utiliza el transporte TLS.</string>\n    <string name=\"verify_server\">Verificar los certificados del servidor</string>\n    <string name=\"transfer_request_to\">Solicitud de transferencia de llamada a</string>\n    <string name=\"missed_call_from\">Llamada perdida desde</string>\n    <string name=\"accounts_help\">URL SIP de cuenta nueva de formulario @[:][;transport=udp|tcp|tls]. Si se indica y no se indica el protocolo de transporte, el protocolo de transporte será por defecto udp. Si no se da y se da el protocolo de transporte, es por defecto 5060 o 5061 (TLS). Si no se indica ninguno de los dos y no se especifica ningún proxy de salida, el registrador de la cuenta (si lo hay) se determina únicamente a partir de la información DNS del dominio.</string>\n    <string name=\"new_account\">URI SIP de cuenta nueva</string>\n    <string name=\"dtmf_info\">Solicitudes SIP INFO</string>\n    <string name=\"dtmf_inband\">Eventos RTP en banda</string>\n    <string name=\"dtmf_mode_help\">Selecciona cómo se envían los tonos DTMF 0-9, #, * y A-D.</string>\n    <string name=\"dtmf_mode\">Modo DTMF</string>\n    <string name=\"invalid_stun_password\">Contraseña \\'%1$s\\' no válida</string>\n    <string name=\"stun_password_help\">Contraseña si lo requiere el servidor STUN/TURN</string>\n    <string name=\"stun_password\">Contraseña STUN/TURN</string>\n    <string name=\"invalid_stun_username\">Nombre de usuario inválido \\'%1$s\\'</string>\n    <string name=\"stun_username_help\">Nombre de usuario si lo requiere el servidor STUN / TURN</string>\n    <string name=\"stun_username\">Nombre de usuario STUN/TURN</string>\n    <string name=\"invalid_stun_server\">URI del servidor STUN/TURN inválido \\'%1$s\\'</string>\n    <string name=\"video_codecs\">Codecs de vídeo</string>\n    <string name=\"invalid_proxy_server_uri\">URI del servidor proxy inválido \\'%1$s\\'</string>\n    <string name=\"invalid_display_name\">Nombre de pantalla inválido \\'%1$s\\'</string>\n    <string name=\"about_text_plus\"><![CDATA[\n        <h1>Agente de usuario SIP basado en biblioteca Baresip con video‐llamadas</h1>\n        <br>\n        <p>Juha Heinanen &lt;jh@tutpro.com&gt;</p>\n        <p>Versión %1$s</p>\n        <br>\n        <h2>Consejos de uso</h2>\n        <ul>\n            <li>Comprobar que los valores por defecto en la configuración de baresip+ se ajustan a sus necesidades\n                (Toque los títulos de los elementos para obtener ayuda). </li>\n            <li>Luego, en Cuentas, cree una o más cuentas (otra vez toque los títulos de los elementos para obtener ayuda). </li>\n            <li>El estado de registro de una cuenta se muestra con un punto de color: verde (registro\n                Ha sido aprobado), amarillo (el registro está en curso), rojo (el registro falló), blanco (el registro está en curso\n                no se ha activado).</li>\n            <li>Tocar el punto lleva directamente a la configuración de la cuenta. </li>\n            <li>Toque largo en iconos de barra bareship muestra información sobre los iconos.</li>\n            <li>El gesto de deslizar hacia abajo provoca el registro de nuevo de la cuenta que se muestra actualmente.</li>\n            <li>El toque largo en la cuenta mostrada actualmente activa o desactiva el registro de la cuenta.</li>\n            <li>El gesto de deslizar hacia la izquierda/derecha alterna entre las cuentas. </li>\n            <li>La llamada anterior puede ser re‐seleccionada tocando el icono de llamada cuando Callee está vacío.<</li>\n            <li>Las parejas de llamadas y mensajes se pueden agregar a contactos con toques largos.</li>\n            <li>Los toques, toques largos también se pueden usar para eliminar llamadas, charlas, mensajes y contactos.</li>\n            <li>El icono de contacto puede utilizarse para instalar/eliminar el avatar de la imagen. </li>\n            <li>Toque largo en un códice de sonido o vídeo puede ser utilizado para habilitar/inhabilitar el códice.</li>\n            <li>Consulte el <a href=\"https://github.com/juha-h/baresip-studio/wiki\">Wiki</a> para más\n                información.</li>\n        </ul>\n        <h2>Asuntos conocidos</h2>\n        <ul>\n            <li>La vista en primera persona no se muestra correctamente cuando la transmisión de vídeo es de solo envío.</li>\n        </ul>\n        <h2>Normativa de privacidad</h2>\n        <p>La normativa de privacidad está disponible\n            <a href=\"https://raw.githubusercontent.com/juha-h/baresip-studio/video/PrivacyPolicy.txt\">aquí</a>.</p>\n        <br>\n        <h2>Código fuente</h2>\n        <p>El código fuente está disponible en <a href=\"https://github.com/juha-h/baresip-studio\">GitHub</a>,\n          También se pueden comunicar problemas.</p>\n        <br>\n        <h2>Traducciones de idiomas</h2>\n        <p>La traducciones de idiomas es gestionada por medio del proyecto\n            <a href=\"https://hosted.weblate.org/projects/baresip/\">Weblate</a>.</p>\n        <br>\n        <h2>Licencias</h2>\n        <ul>\n            <li><b>BSD-3-Clause</b> excepto lo siguiente:</li>\n            <li>Los códices <b>Apache 2.0</b> de AMR y seguridad TLS</li>\n            <li>Cifrado de medios ZRTP bajo <b>AGPLv4</b></li>\n            <li>Códice Codec2 de <b>GNU LGPL 2.1</b></li>\n            <li>Códice <b>Free</b> G.722</li>\n            <li>Códice <b>GNU GPLv3</b> G.729</li>\n            <li>Códice H.264 y H.265 de <b>GNU GPLv2</b> </li>\n            <li>Códice AV1 de <b>AOMedia</b></li>\n        </ul>\n        ]]></string>\n    <string name=\"about_title_plus\">Acerca de baresip+</string>\n    <string name=\"reg_int\">Intervalo de inscripción</string>\n    <string name=\"invalid_reg_int\">Intervalo de registro no válido \\'%1$s\\'</string>\n    <string name=\"rtcp_mux\">Multiplexación RTCP</string>\n    <string name=\"country_code\">Código del país</string>\n    <string name=\"reg_int_help\">Indica la frecuencia (en segundos) con la que baresip envía peticiones REGISTRO. Los valores válidos van de 60 a 3600.</string>\n    <string name=\"rtcp_mux_help\">Si está marcada, los paquetes RTP y RTCP se multiplexan en un único puerto (RFC 5761).</string>\n    <string name=\"account_nickname_help\">Apodo (si lo hay) utilizado para identificar esta cuenta dentro de la aplicación baresip.</string>\n    <string name=\"invalid_account_nickname\">Apodo de la cuenta no válido \\\"%1$s</string>\n    <string name=\"non_unique_account_nickname\">El apodo \\'%1$s\\' ya existe</string>\n    <string name=\"nickname\">Apodo</string>\n    <string name=\"both\">Ambos</string>\n    <string name=\"call_details\">Detalles de la llamada</string>\n    <string name=\"peer\">Interlocutores</string>\n    <string name=\"direction\">Dirección</string>\n    <string name=\"calls_duration\">Duración</string>\n    <string name=\"audio_settings\">Ajustes de sonido</string>\n    <string name=\"user_domain_or_number\">usuario@dominio o número de teléfono</string>\n    <string name=\"avatar_image\">Imagen del perfil</string>\n    <string name=\"telephony_provider\">Proveedor de telefonía</string>\n    <string name=\"telephony_provider_help\">Parte del host SIP URI utilizada en las llamadas a números de teléfono. Por defecto es el dominio de la cuenta. Si no se indica, esta cuenta no puede utilizarse para llamar a números de teléfono.</string>\n    <string name=\"invalid_country_code\">Código del país \\\"%1$s\\\" no válido</string>\n    <string name=\"invalid_sip_uri_hostpart\">Parte del host SIP URI \\'%1$s\\' no válida</string>\n    <string name=\"contacts_help\">Elige si se utilizan contactos de Baresip, contactos de Android o ambos. Si se utilizan ambos y existe un contacto con el mismo nombre en ambos contactos, se elegirá el contacto bareip.</string>\n    <string name=\"consent_request\">Solicitud de autorización</string>\n    <string name=\"sip_or_tel_uri\">SIP o teléfono URI</string>\n    <string name=\"call_is_on_hold\">Llamada en espera</string>\n    <string name=\"no_restore\">No es posible restaurar la copia de seguridad sin el permiso de \\\"Almacenamiento\\\".</string>\n    <string name=\"no_network\">¡No hay conexion de red!</string>\n    <string name=\"no_telephony_provider\">La cuenta \\'%1$s\\' no tiene proveedor de telefonía</string>\n    <string name=\"average_rate\">Velocidad media: %1$s (Kbits/s)</string>\n    <string name=\"lost\">Perdidos</string>\n    <string name=\"jitter\">Oscilación: %1$s (ms)</string>\n    <string name=\"permissions_rationale\">Fundamentos de los permisos</string>\n    <string name=\"blind\">Oculto</string>\n    <string name=\"attended\">Asistió</string>\n    <string name=\"missed_calls_count\">%1$d llamadas perdidas</string>\n    <string name=\"battery_optimizations\">Optimizaciones de batería</string>\n    <string name=\"no_backup\">No podrá crear copias de seguridad sin el permiso \\\"Almacenamiento\\\".</string>\n    <string name=\"anonymous\">Anónimo</string>\n    <string name=\"unknown\">Desconocido</string>\n    <string name=\"invalid_sip_or_tel_uri\">URI del teléfono o SIP no válido \\'%1$s\\'</string>\n    <string name=\"packets\">Paquetes</string>\n    <string name=\"audio_permissions\">baresip necesita el permiso «Micrófono» para las llamadas de voz, el permiso «Dispositivos cercanos» para la detección de micrófonos/altavoces Bluetooth, el permiso «Notificaciones» para publicar notificaciones, y permisos de «Almacén» en Android 9 para operaciones de Respaldo/Restauración.</string>\n    <string name=\"missed_calls\">Llamadas perdidas</string>\n    <string name=\"country_code_help\">Código de país E.164 de esta cuenta. Si la parte de usuario From URI de la llamada entrante o del mensaje contiene un número de teléfono que no empieza por el signo \\\"+\\\" y si falla la búsqueda de contacto, se antepone al número este prefijo de país y se vuelve a intentar la búsqueda de contacto. Si el número de teléfono comienza con un solo dígito \\\"0\\\", el dígito \\\"0\\\" se elimina antes de anteponer el prefijo al número.</string>\n    <string name=\"time\">Tiempo</string>\n    <string name=\"diverted_by_dots\">Desviada por …</string>\n    <string name=\"no_notifications\">No podrá utilizar esta aplicación sin el permiso de \\\"Notificaciones\\\".</string>\n    <string name=\"battery_optimizations_help\">Desactive las optimizaciones de batería (recomendado) si desea reducir la probabilidad de que Android restrinja el acceso de baresip a la red o lo ponga en estado de espera.</string>\n    <string name=\"contacts_consent\">Si se eligen contactos de Android, pueden ser usados en llamadas y mensajes como referencias a SIP y tel URIs. baresip aplicación no almacena contactos de Android ni compartirlos con nadie. Para que los contactos de Android estén disponibles en baresip, Google requiere que aceptes su uso según se describe aquí y en la <a href=\"https://raw.githubusercontent.com/juha-h/baresip-studio/master/PrivacyPolicy.txt\">Política de privacidad</a> de la aplicación.</string>\n    <string name=\"no_android_contacts\">No se puede acceder a los contactos de Android sin el permiso \\\"Contactos\\\".</string>\n    <string name=\"audio_focus_denied\">¡Enfoque de audio denegado!</string>\n    <string name=\"audio_and_video_permissions\">baresip+ necesita el permiso \\\"Micrófono\\\" para las llamadas de voz, el permiso \\\"Cámara\\\" para las videollamadas, el permiso \\\"Dispositivos cercanos\\\" para la detección de micrófonos/altavoces Bluetooth y el permiso \\\"Notificaciones\\\" para la publicación de notificaciones, y en Android 9 los permisos de \\\"Almacén\\\" para operaciones de Resguardo/Restauración.</string>\n    <string name=\"choose_destination_uri\">Seleccione la URI de destino</string>\n    <string name=\"rec_in_call\">La grabación puede activarse o desactivarse sólo cuando la llamada no está conectada</string>\n    <string name=\"address_family_help\">Elige qué direcciones IP usará Baresip. Si se elige entre IPv4 o IPv6, Barsip usará una solamente. Si no se elige entre ninguna, Baresip ambas (IPv4 e IPv6).</string>\n    <string name=\"address_family\">Familia de direcciones</string>\n    <string name=\"audio_delay\">Retraso del audio</string>\n    <string name=\"audio_delay_help\">Tiempo (en milisegundos) para esperar el audio del destinatario cuando se establece la llamada. Ajústalo a un valor más alto si pierdes el audio del destinatario al principio de la llamada.</string>\n    <string name=\"invalid_audio_delay\">Retardo de audio no válido \\'%1$s\\'. Los valores válidos van de 100 a 3000.</string>\n    <string name=\"call_auto_rejected\">Llamada rechazada automáticamente de %1$s</string>\n    <string name=\"default_phone_app\">Aplicación de teléfono predeterminada</string>\n    <string name=\"dialer_role_not_available\">La función de teléfono no está disponible</string>\n    <string name=\"default_phone_app_help\">Si está marcada, baresip es la aplicación de teléfono predeterminada. No verifiques si tu dispositivo puede necesitar manejar también llamadas o mensajes que no sean SIP.</string>\n    <string name=\"redirect_notice\">Redirección automática a \\'%1$s\\'\\\\</string>\n    <string name=\"redirect_request\">Solicitud de redirección</string>\n    <string name=\"redirect_request_query\">¿Aceptas la redirección de llamadas a \\'%1$s\\'\\?</string>\n    <string name=\"redirect_mode\">Modo de redirección</string>\n    <string name=\"redirect_mode_help\">Selecciona si la solicitud de redirección de las llamadas se realiza automáticamente o si se solicita confirmación.</string>\n    <string name=\"tone_country\">Tono para el país</string>\n    <string name=\"tone_country_help\">Timbre de llamada, espera y tonos de ocupado de la llamada para un país</string>\n    <string name=\"rel_100_help\">Si está marcado, indica el soporte para las respuestas provisionales fiables (RFC 3262).</string>\n    <string name=\"rel_100\">Respuestas provisionales fiables</string>\n    <string name=\"favorite\">Favoritos</string>\n    <string name=\"appear_on_top_permission\">El inicio automático necesita el permiso mostrar en la parte superior.</string>\n    <string name=\"restore_unzip_failed\">Error al restaurar los datos de la aplicación. Android versión 14 y superiores no permiten restaurar datos de los que se hizo una copia de seguridad antes de %1$s la versión %2$s.</string>\n    <string name=\"speaker_phone\">Altavoz del teléfono</string>\n    <string name=\"speaker_phone_help\">Si está marcada, el altavoz se enciende automáticamente al iniciar la llamada.</string>\n    <string name=\"dtmf_auto\">RTP en la banda o imformación SIP</string>\n    <string name=\"invalid_fps\">\\'%1$d\\' Fps no válido(s)</string>\n    <string name=\"video_fps\">Fotogramas de vídeo por segundo (fps)</string>\n    <string name=\"video_fps_help\">Velocidad de fotogramas de vídeo que se ofrecerá durante el protocolo de enlace SDP. Los valores válidos son del 10 al 30.</string>\n    <string name=\"call_request\">Requerimiento</string>\n    <string name=\"call_request_query\">¿Aceptas el requerimiento \\'%1$s\\'?</string>\n    <string name=\"user_agent\">Agente de usuario</string>\n    <string name=\"user_agent_help\">Valor del campo de encabezado de agente de usuario de solicitud/respuesta SIP personalizado</string>\n    <string name=\"invalid_user_agent\">Valor de campo de encabezado del agente de usuario no válido</string>\n    <string name=\"microphone_gain_help\">Multiplica el volumen del micrófono por este número decimal. El valor mínimo es 1.0 (predeterminado de fábrica) que desactiva la ganancia del micrófono. Los valores más altos pueden afectar negativamente a la calidad del audio.</string>\n    <string name=\"microphone_gain\">Ganancia del micrófono</string>\n    <string name=\"invalid_microphone_gain\">Valor de ganancia de micrófono no válido</string>\n    <string name=\"check_origin\">Comprobar origen</string>\n    <string name=\"check_origin_help\">Si está marcado, las solicitudes entrantes solo están concedidas desde las direcciones IP donde fueron enviadas las peticiones del registro.</string>\n    <string name=\"block_unknown\">Bloque Desconocido</string>\n    <string name=\"block_unknown_help\">Llamadas y mensajes de bloque desde parejas que no son encontradas en contactos.</string>\n    <string name=\"numeric_keypad\">Teclado Numérico</string>\"\n    <string name=\"numeric_keypad_help\">Si se marcó, el teclado numérico se muestra cuando el campo «Llamada a …» está centrada.</string>\"\n    <string name=\"is_calling\">está llamando</string>\n    <string name=\"call_blocked\">Auto‐rechazar llamadas bloqueadas desde %1$s</string>\n    <string name=\"message_blocked\">Auto‐rechazar mensaje bloqueado desde %1$s</string>\n    <string name=\"blocked\">Bloqueado</string>\n    <string name=\"blocked_calls\">Llamadas Bloqueadas</string>\n    <string name=\"blocked_messages\">Mensajes Bloqueados</string>\n    <string name=\"blocked_delete_alert\">¿Desea borrar solicitudes bloqueadas de «%1$s»?</string>\n    <string name=\"blocked_contact_question\">¿Desea añadir pareja «%1$s» a contactos?</string>\n    <string name=\"call_answered\">Llamada respondida</string>\n    <string name=\"call_answered_elsewhere\">Llamada respondida donde sea</string>\n    <string name=\"call_missed\">Llamada perdida</string>\n    <string name=\"call_rejected\">Llamada rechazada</string>\n    <string name=\"playing_recording\">Reproduce registro …</string>\n    <string name=\"save_recording\">Guardar Registro</string>\n    <string name=\"save_recording_question\">¿Desea guardar esta grabación?</string>\n    <string name=\"recording_saved\">Grabación guardada</string>\n    <string name=\"delete_call_alert\">¿Desea eliminar esta llamada desde el historial?</string>\n    <string name=\"transport_protocols\">Protocolos de transporte</string>\n    <string name=\"transport_protocols_help\">Listado separada por comas de los protocolos de transporte de solicitud/respuesta SIP compatibles. Si se deja vacía, el valor predeterminado es \\'udp,tcp,tls,ws,wss\\', que incluye todos los protocolos de transporte compatibles. Incluya solo los que necesite. No se recomienda el uso de UDP por motivos de seguridad y otros.</string>\n    <string name=\"invalid_transport_protocols\">Listado no válidos del Protocolos de Transporte</string>\n    <string name=\"no_read_permission\">Sin permiso de lectura en Almacén Externo</string>\n    <string name=\"unique_contact_uri\">URI de Contacto único</string>\n    <string name=\"unique_contact_uri_help\">Si está marcado, está garantizada la URI de contacto para ser única. Necesita ser comprobado si hay más que una cuenta con el mismo URI SIP como parte de usuario, pero además mejora la protección de las cuentas desde ataques.</string>\n    <string name=\"dynamic_colors\">Colores dinámicos</string>\n    <string name=\"dynamic_colors_help\">Utilice colores dinámicos si habilitó un Ajuste de Androin</string>\n    <string name=\"colorblind\">Daltonía</string>\n    <string name=\"colorblind_help\">Utilizar registro amigable de estado de iconos para daltónicos</string>\n    <string name=\"proximity_sensing\">Detección de proximidad</string>\"&gt;\n    <string name=\"proximity_sensing_help\">Si está marcada, la detección de proximidad estará activa durante las llamadas.</string>\n    <string name=\"ringtone\">Tono de llamada</string>\n    <string name=\"select_ringtone\">Seleccionar tono de llamada</string>\n    <string name=\"favorite_help\">Si está marcada, se muestra el contacto entre otros favoritos en la cima del listado de contactos.</string>\n    <string name=\"search\">Búsqueda</string>\n    <string name=\"stop\">Detener</string>\n    <string name=\"reply\">Responder</string>\n    <string name=\"save\">Guardar</string>\n    <string name=\"call_is_connected\">Llamada conectada</string>\n    <string name=\"replaces_not_supported\">Pareja no admite característica REPLACES</string>\n    <string name=\"no_aec\">¡Sin Cancelación de Eco Acústico por hardware!</string>\n    <string name=\"call_recording_title\">Grabación de Llamada</string>\n    <string name=\"call_recording_tip\">Si está activado, las llamadas entrantes y salientes serán grabadas. Las grabaciones pueden ser reproducidas en la página Detalles de Llamada.</string>\n    <string name=\"microphone_title\">Micrófono</string>\n    <string name=\"microphone_tip\">Si está activado durante la llamada, el micrófono está enmudecido.</string>\n    <string name=\"speakerphone_title\">Altavoz</string>\n    <string name=\"speakerphone_tip\">Si está activado, el sonido es reproducido por medio de dispositivo de altavoz.</string>\n    <string name=\"call_is_ringing\">La llamada está sonando</string>\n    <string name=\"custom_parameters\">Parámetros personales</string>\n    <string name=\"custom_parameters_help\">Listado separador por punto y coma de parámetros de cuenta personal</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-fi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <!-- About Activity -->\n    <string name=\"about_title\">baresip sovelluksesta</string>\n    <string name=\"about_title_plus\">baresip+ sovelluksesta</string>\n    <string name=\"about_text\">\n        <![CDATA[\n            <h1><a href=\"https://github.com/baresip/baresip\">Baresip</a>-kirjastoon perustuva äänipuhelut ja\n                pikaviestit mahdollistava SIP-sovellus</h1>\n            <br>\n            <p>Juha Heinanen &lt;jh@tutpro.com&gt;</p>\n            <p>Versio %1$s</p>\n            <br>\n            <h2>Käyttövihjeitä</h2>\n            <ul>\n                <li>Tarkista, että baresip-sovelluksen Asetukset vastaavat tarpeitasi. Kunkin otsikon kosketus tarjoaa apua.</li>\n                <li>Sen jälkeen luo yksi tai useampi tili. Jälleen kunkin otsikon kosketus tarjoaa apua.</li>\n                <li>Tilin rekisteröintitila kerrotaan värillisellä pisteellä: vihreä (rekisteröinti onnistui),\n                    keltainen (rekisteröinti on meneillään), punainen (rekisteröinti epäonnistui),\n                    valkoinen (rekisteröintiä ei ole aktivoitu).</li>\n                <li>Pisteen kosketus johtaa suoraan tilin asetuksiin.</li>\n                <li>Pitkä kosketus baresip-palkin ikoneihin näyttää tietoa ikoneista.</li>\n                <li>Pyyhkäisy alas aikaansaa parhaillaan näkyvissä olevan tilin uudelleen rekisteröinnin.</li>\n                <li>Parhaillaan näkyvissä olevan tilin pitkä kosketus aktivoi tai passivoi tilin rekisteröinnin.</li>\n                <li>Pyyhkäisy vasemmalle/oikealle vaihtaa näkyvissä olevaa tiliä.</li>\n                <li>Voit valita edellisen puhelun uudelleen koskettamalla virheää luuria silloin, kun puhelun kohde on tyhjä.</li>\n                <li>Voit lisätä puheluiden ja viestien kohteet yhteystietoihin pitkällä kosketuksella.</li>\n                <li>Pitkillä kosketuksilla voit myös poistaa puheluita, viestiketjuja, viestejä ja yhteystietoja.</li>\n                <li>Voit lisätä/poistaa yhteystiedon avatar-kuvan koskettamalla yhteystiedon ikonia lyhyesti/pitkästi.</li>\n                <li>Audio-koodekin saa pitkällä kosketuksella käytöön/pois käytöstä.</li>\n                <li>Katso lisätietoja <a href=https://github.com/juha-h/baresip-studio/wiki>Wiki</a>-sivulta.</li>\n            </ul>\n            <h2>Tietosuoja</h2>\n            <p>Sovelluksen tietosuojakäytäntö on luettavissa <a href=\"https://raw.githubusercontent.com/juha-h/baresip-studio/master/PrivacyPolicy.txt\">täältä</a>.</p>\n            <br>\n            <h2>Lähdekoodi</h2>\n            <p>Lähdekoodi on saatavilla <a href=https://github.com/juha-h/baresip-studio>GitHub</a>:ssa,\n                missä voi myös raportoida virheistä.</p>\n            <br>\n            <h2>Kielikäännökset</h2>\n            <p>Kielikäännöksiä varten on olemassa baresip\n                <a href=\"https://hosted.weblate.org/projects/baresip/\">Weblate</a> projekti.</p>\n            <br>\n            <h2>Lisenssit</h2>\n                <ul>\n                    <li><b>BSD-3-Clause</b> paitsi seuraavat:</li>\n                    <li><b>Apache 2.0</b> AMR koodausmenetelmät ja TLS turvallisuus</li>\n                    <li><b>AGPLv4</b> ZRTP median salausmenetelmä</li>\n                    <li><b>GNU LGPL 2.1</b> Codec2 koodausmenetelmä</li>\n                    <li><b>Ilmainen</b> G.722 koodausmenetelmä</li>\n                    <li><b>GNU GPLv3</b> G.729 koodausmenetelmä</li>\n                </ul>\n        ]]>\n    </string>\n    <string name=\"about_text_plus\">\n        <![CDATA[\n            <h1><a href=\"https://github.com/baresip/baresip\">Baresip</a>-kirjastoon perustuva\n                 videopuhelut ja pikaviestit mahdollistava SIP-sovellus</h1>\n            <br>\n            <p>Juha Heinanen &lt;jh@tutpro.com&gt;</p>\n            <p>Versio %1$s</p>\n            <br>\n            <h2>Käyttövihjeitä</h2>\n            <ul>\n                <li>Tarkista, että baresip+-sovelluksen Asetukset vastaavat tarpeitasi. Kunkin otsikon kosketus tarjoaa apua.</li>\n                <li>Sen jälkeen luo yksi tai useampi tili. Jälleen kunkin otsikon kosketus tarjoaa apua.</li>\n                <li>Tilin rekisteröintitila kerrotaan värillisellä ympyrällä: vihreä (rekisteröinti onnistui),\n                    keltainen (rekisteröinti on meneillään), punainen (rekisteröinti epäonnistui),\n                    valkoinen (rekisteröintiä ei ole aktivoitu).</li>\n                <li>Ympyrän kosketus johtaa suoraan tilin asetuksiin.</li>\n                <li>Pitkä kosketus baresip-palkin ikoneihin näyttää tietoa ikoneista.</li>\n                <li>Pyyhkäisy alas aikaansaa parhaillaan näkyvissä olevan tilin uudelleen rekisteröinnin.</li>\n                <li>Parhaillaan näkyvissä olevan tilin pitkä kosketus aktivoi tai passivoi tilin rekisteröinnin.</li>\n                <li>Pyyhkäisyllä vasemmalle/oikealle voit vaihtaa näkyvissä olevaa tiliä.</li>\n                <li>Voit valita edellisen puhelun uudelleen koskettamalla virheää luuria silloin, kun puhelun kohde on tyhjä.</li>\n                <li>Voit lisätä puheluiden ja viestien kohteet yhteystietoihin pitkällä kosketuksella.</li>\n                <li>Pitkillä kosketuksilla voit myös poistaa puheluita, viestiketjuja, viestejä ja yhteystietoja.</li>\n                <li>Voit lisätä/poistaa yhteystiedon avatar-kuvan koskettamalla yhteystiedon ikonia lyhyesti/pitkästi.</li>\n                <li>Audio- ja video-koodekin saa pitkällä kosketuksella käytöön/pois käytöstä.</li>\n                <li>Katso lisätietoja <a href=https://github.com/juha-h/baresip-studio/wiki>Wiki</a>-sivulta.</li>\n            </ul>\n            <h2>Tunnetut ongelmat</h2>\n            <ul>\n                <li>Oman videokuvan näyttö ei toimi, jos video on vain lähetyssuuntainen.</li>\n            </ul>\n            <h2>Tietosuoja</h2>\n            <p>Sovelluksen tietosuojakäytäntö on luettavissa\n                <a href=\"https://raw.githubusercontent.com/juha-h/baresip-studio/video/PrivacyPolicy.txt\">täältä</a>.</p>\n            <h2>Lähdekoodi</h2>\n            <p>Lähdekoodi on saatavilla <a href=https://github.com/juha-h/baresip-studio>GitHub</a>:ssa,\n                missä voi myös raportoida virheistä.</p>\n            <br>\n            <h2>Kielikäännökset</h2>\n            <p>Kielikäännöksiä varten on olemassa baresip\n                <a href=\"https://hosted.weblate.org/projects/baresip/\">Weblate</a> projekti.</p>\n            <br>\n            <h2>Lisenssit</h2>\n            <ul>\n                <li><b>BSD-3-Clause</b> paitsi seuraavat:</li>\n                <li><b>Apache 2.0</b> AMR koodausmenetelmät ja TLS turvallisuus</li>\n                <li><b>AGPLv4</b> ZRTP median salausmenetelmä</li>\n                <li><b>GNU LGPL 2.1</b> Codec2 koodausmenetelmä</li>\n                <li><b>Ilmainen</b> G.722 koodausmenetelmä</li>\n                <li><b>GNU GPLv3</b> G.729 koodausmenetelmä</li>\n                <li><b>GNU GPLv2</b> H.264 ja H.265 koodausmenetelmät</li>\n                <li><b>AOMedia</b> AV1 koodausmenetelmä</li>\n            </ul>\n        ]]>\n    </string>\n    <!-- Account Activity -->\n    <string name=\"account\">Tili</string>\n    <string name=\"account_nickname_help\">Lempinimi (jos annettu) millä tämä tili identifioidaan\n        baresip sovelluksessa.</string>\n    <string name=\"nickname\">Lempinimi</string>\n    <string name=\"invalid_account_nickname\">Virheellinen tilin lempinimi \\'%1$s\\'</string>\n    <string name=\"non_unique_account_nickname\">Lempinimi \\'%1$s\\' on jo olemassa</string>\n    <string name=\"display_name\">Tilin käyttäjän nimi</string>\n    <string name=\"display_name_help\">Tilin käyttäjän nimi, joka esiintyy\n        SIP-sanomien From URI:ssa (vapaaehtoinen).</string>\n    <string name=\"invalid_display_name\">Virheellinen tilin käyttäjän nimi \\'%1$s\\'</string>\n    <string name=\"authentication_username\">Käyttäjätunnus</string>\n    <string name=\"authentication_username_help\">Todentamiseen käytettävä\n    käyttäjätunnus, jos välityspalvelin vaatii sellaisen. Oletusarvo on\n    tilin käyttäjätunnus.\n    </string>\n    <string name=\"invalid_authentication_username\">Virheellinen todennuskäyttäjänimi \\'%1$s\\'</string>\n    <string name=\"authentication_password\">Todennuksen salasana</string>\n    <string name=\"authentication_password_help\">Todentamiseen käytettävä\n    salasana, jonka pituus on enintään 64 ASCII merkkiä. Jos\n    käyttäjätunnus on annettu, mutta salasanaa ei ole annettu, se\n    kysytään, kun baresip käynnistetään.\n    </string>\n    <string name=\"invalid_authentication_password\">Virheellinen todennussalasana \\'%1$s\\'</string>\n    <string name=\"outbound_proxies\">Välityspalvelimet</string>\n    <string name=\"outbound_proxies_help\">Yhden tai kahden\n        välityspalvelimen SIP URI, joille SIP-sanomat lähetetään. Jos\n        välityspalvelimia on annettu kaksi, REGISTER-sanomat yritetään\n        lähettää molemmille välityspalvelimille ja muut sanomat yhdelle\n        toiminnassa olevalle välityspalvelimelle. Jos välityspalvelimia ei\n        ole annettu, SIP-sanomat lähetetään palvelimelle, mikä selviää\n        kohteen domainille tehtävien NAPTR/SRV/Animipalvelukyselyiden perusteella.\n        Jos välityspalvelimen osoite SIP\n        URI:ssa on IPv6-osoite, osoite pitää kirjoittaa sulkujen [] sisään.\n\\nEsimerkkejä:\n\\n • sip:example.com:5061;transport=tls\n\\n • sip:[2001:67c:223:777::10];transport=tcp\n\\n • sip:192.168.43.50:443;transport=wss\n    </string>\n    <string name=\"sip_uri_of_proxy_server\">Välityspalvelimen SIP URI</string>\n    <string name=\"sip_uri_of_another_proxy_server\">Toisen\n        välityspalvelimen SIP URI</string>\n    <string name=\"invalid_proxy_server_uri\">Virheellinen välityspalvelimen URI \\'%1$s\\'</string>\n    <string name=\"register\">Rekisteröi</string>\n    <string name=\"register_help\">Jos merkitty, rekisteröinti on aktiivinen ja REGISTER-sanomat\n        lähetetään Rekisteröintitaajuus-asetuksen mukaisesti.</string>\n    <string name=\"reg_int\">Rekisteröintitaajuus</string>\n    <string name=\"reg_int_help\">Kertoo kuinka usein baresip lähettää REGISTER-sanomia. Arvon on\n        oltava välillä 60-3600 (sekunttia).</string>\n    <string name=\"invalid_reg_int\">Virheellinen Rekisteröintitaajuus \\'%1$s\\'</string>\n    <string name=\"check_origin\">Tarkista lähdeosoite</string>\n    <string name=\"check_origin_help\">Merkitty, sisään tulevat SIP-sanomat sallitaan vain niistä\n        lähdeosoitteista, joihin REGISTER-sanomat lähetettiin.\n    </string>\n    <string name=\"media_nat\">Media NAT hallinta</string>\n    <string name=\"media_nat_help\">Valitsee media NAT hallintaprotokollan\n        (vapaaehtoinen). Vaihtoehtoja ovat STUN (Session Traversal Utilities\n        for NAT, RFC 5389) ja ICE (Interactive Connectivity Establishment, RFC 5245).\n    </string>\n    <string name=\"stun_server\">STUN/TURN-palvelin</string>\n    <string name=\"stun_server_help\">STUN/TURN-palvelimen muotoa \\'kaava:palvelin[:portti][\\?transport=udp|tcp]\\'\n        oleva URI, missä kaava on \\'stun\\', \\'stuns\\', \\'turn\\' tai \\'turns\\'. Oletus STUN-palvelin\n        STUN- ja ICE-protokollille on \\'stun:stun.l.google.com:19302\\', joka osoittaa Google:n\n        julkiseen STUN-palvelimeen. TURN-palvelimella ei ole oletusarvoa.\n    </string>\n    <string name=\"invalid_stun_server\">Virheellinen STUN/TURN-palvelimen URI \\'%1$s\\'</string>\n    <string name=\"stun_username\">STUN/TURN-käyttäjätunnus</string>\n    <string name=\"stun_username_help\">Käyttäjätunnus jos STUN/TURN-palvelin vaatii sellaisen</string>\n    <string name=\"invalid_stun_username\">Virheellinen käyttäjänimi %1$s</string>\n    <string name=\"stun_password\">STUN/TURN-salasana</string>\n    <string name=\"stun_password_help\">Salasana jos STUN/TURN-palvelin vaatii sellaisen</string>\n    <string name=\"invalid_stun_password\">Virheellinen salasana \\'%1$s\\'</string>\n    <string name=\"media_encryption\">Median salaus</string>\n    <string name=\"media_encryption_help\">Valitsee median salausprotokollan (vapaaehtoinen).\n\\n • ZRTP (suositeltu) tarkoittaa, että ZRTP-salausta yritetään neuvotella sen jälkeen, kun puhelu on alkanut.\n\\n • DTLS-SRTPF tarkoittaa, että UDP/TLS/RTP/SAVPF-protokollaa tarjotaan lähteviin puheluihin\n        ja että RTP/SAVP-, RTP/SAVPF-, UDP/TLS/RTP/SAVP- tai\n        UDP/TLS/RTP/SAVPF-protokollaa käytetään, jos sellaista tarjotaan tulevassa puhelussa.\n\\n • SRTP-MANDF tarkoittaa, että RTP/SAVPF-protokollaa tarjotaan\n        lähtevissä puheluissa ja että se vaaditaan tulevissa puheluissa.\n\\n • SRTP-MAND takoittaa, että RTP/SAVP-protokollaa tarjotaan\n        lähteviin puheluihin ja että se vaaditaan tulevissa puheluissa.\n\\n • SRTP tarkoittaa, että RTP/AVP-protokollaa tarjotaan\n        lähteviin puheluihin ja että RTP/SAVP- tai RTP/SAVPF-protokollaa\n        käytetään, jos sellaista tarjotaan tulevissa puheluissa.\n    </string>\n    <string name=\"rtcp_mux\">RTCP-multipleksaus</string>\n    <string name=\"rtcp_mux_help\">Jos merkitty, RTP- and RTCP-paketit multipleksataan samaan porttiin\n        (RFC 5761).\n    </string>\n    <string name=\"rel_100\">Luotettavat alustavat vastaukset</string>\n    <string name=\"rel_100_help\">Jos merkitty, kutsussa ilmoitetaan luotettavien alustavien vastausten\n        tukemisesta (RFC 3262).</string>\n    <string name=\"dtmf_mode\">DTMF-moodi</string>\n    <string name=\"dtmf_mode_help\">Valitsee tavan, miten DTMF-merkit 0–9, #, * ja A-D lähetetään.</string>\n    <string name=\"dtmf_inband\">RTP-tapahtuma</string>\n    <string name=\"dtmf_info\">SIP INFO -pyyntö</string>\n    <string name=\"dtmf_auto\">RTP tai SIP INFO</string>\n    <string name=\"answer_mode\">Vastaustapa</string>\n    <string name=\"answer_mode_help\">Valitsee tulevien puheluiden vastaustavan.</string>\n    <string name=\"manual\">Manuaalinen</string>\n    <string name=\"auto\">Automaattinen</string>\n    <string name=\"block_unknown\">Estä tuntemattomat</string>\n    <string name=\"block_unknown_help\">Estä puhelut ja viestit, joiden lähdettä ei löydy yhteystiedoista.</string>\n    <string name=\"redirect_mode\">Uudelleenohjaustapa</string>\n    <string name=\"redirect_mode_help\">Valitsee toteutetaanko puhelun uudelleenohjauspyyntö\n        automaattisesti vai kysytäänkö vahvistusta.</string>\n    <string name=\"voicemail_uri\">Puhepostin URI</string>\n    <string name=\"voicemain_uri_help\">SIP URI, jota käytetään\n        puhepostiviestien kuunteluun.  Jos URI:a ei ole annettu, tietoa\n        mahdollista puhepostiviesteistä (Message Waiting Indications) ei tilata.\n    </string>\n    <string name=\"country_code\">Maakoodi</string>\n    <string name=\"country_code_help\">Tämän tilin E.164-maakoodi. Jos tulevan puhelun tai viestin\n        From URI:n käyttäjäosa sisältää puhelinnumeron, joka ei ala \\'+\\' merkillä, ja jos sitä\n        ei löydy yhteystiedoista, niin tämä maakoodi lisätään numeron eteen ja etsintä tehdään\n        uudelleen. Jos puhelinnumero alkaa yhdellä numerolla \\'0\\', niin numero \\'0\\'\n        poistetaan ennen maakoodin lisäämistä.\n    </string>\n    <string name=\"invalid_country_code\">Virheellinen maakoodi \\'%1$s\\'</string>\n    <string name=\"telephony_provider\">Puhelinpalvelun tarjoaja</string>\n    <string name=\"telephony_provider_help\">SIP URI:n domain-osa, jota käytetään soitettaessa\n        puhelinnumeroihin. Tehdasasetus on tilin domain-osa.  Jos tyhjä, niin tätä tiliä ei\n        voi käyttää soitettaessa puhelinnumeroihin.\n    </string>\n    <string name=\"numeric_keypad\">Numeronäppäimistö</string>\"\n    <string name=\"numeric_keypad_help\">Jos valittu, \\\"Puhelu ulos …\\\" kentässä käytetään\n        numeronäppäimistöä.</string>\"\n    <string name=\"invalid_sip_uri_hostpart\">Virheellinen SIP URI:n domain-osa \\'%1$s\\'</string>\n    <string name=\"default_account\">Oletustili</string>\n    <string name=\"default_account_help\">Jos merkitty, niin tämä tili on\n        valittuna, kun baresip käynnistetään.\n    </string>\n    <string name=\"custom_parameters\">Räätälöidyt parametrit</string>\n    <string name=\"custom_parameters_help\">Puolipisteellä toisistaan eroteltu lista\n        räätälöityjä tilin parametrejä</string>\n    <!-- Accounts Activity -->\n    <string name=\"accounts\">Tilit</string>\n    <string name=\"new_account\">Uuden tilin SIP URI</string>\n    <string name=\"accounts_help\">Uuden tilin SIP URI muotoa\n        &lt;käyttäjä&gt;@&lt;domain&gt;[:&lt;portti&gt;][;transport=udp|tcp|tls].\n        Jos &lt;portti&gt; on annettu, mutta protokollaa ei ole annettu, protokolla on udp.\n        Jos &lt;portti&gt; ei ole annettu, mutta protokolla on annettu, portti on joko 5060 tai 5061 (tls).\n        Jos kumpaakaan ei ole annettu eikä välityspalvelinta ole määritelty, tilin mahdollinen\n        rekisteröintipalvelin päätellään pelkästään domainin DNS-informaation perusteella.\n    </string>\n    <string name=\"invalid_aor\">Virheellinen käyttäjä@domain[:portti][;transport=udp|tcp|tls] \\'%1$s\\'\n    </string>\n    <string name=\"account_exists\">Tili \\'%1$s\\' on jo olemassa.</string>\n    <string name=\"account_allocation_failure\">Uuden tilin luonti epäonnistui.</string>\n    <string name=\"encrypt_password\">Tallenna salasanalla</string>\n    <string name=\"decrypt_password\">Palauta salasanalla</string>\n    <string name=\"delete_account\">Haluatko poistaa tilin \\'%1$s\\'\\?</string>\n    <!-- Baresip Service -->\n    <string name=\"is_calling\">soittaa</string>\n    <string name=\"missed_call_from\">Vastaamaton puhelu soittajalta</string>\n    <string name=\"missed_calls\">Vastaamattomia puheluita</string>\n    <string name=\"missed_calls_count\">%1$d vastaamatonta puhelua</string>\n    <string name=\"transfer_request_to\">Puhelun siirtopyyntö kohteeseen</string>\n    <string name=\"call_auto_rejected\">Automaattisesti hylätty puhelu soittajalta \\`%1$s\\`</string>\n    <string name=\"call_blocked\">Estetty puhelu hylätty soittajalta \\`%1$s\\`</string>\n    <string name=\"message_blocked\">Estetty viesti hylätty lähettäjältä \\`%1$s\\`</string>\n    <!-- Blocked Activity -->\n    <string name=\"blocked\">Estetyt</string>\n    <string name=\"blocked_calls\">Estetyt puhelut</string>\n    <string name=\"blocked_messages\">Estetyt viestit</string>\n    <string name=\"blocked_delete_alert\">Haluatko poistaa tilin \\'%1$s\\' estetyt pyynnöt\\?</string>\n    <string name=\"blocked_contact_question\">Haluatko lisätä osoitteen \\'%1$s\\' yhteystietoihin\\?</string>\n    <!-- Calls Activity -->\n    <string name=\"call_history\">Puheluhistoria</string>\n    <string name=\"call\">Soita</string>\n    <string name=\"calls_calls\">puhelut</string>\n    <string name=\"calls_call\">puhelun</string>\n    <string name=\"direction\">Suunta</string>\n    <string name=\"peer\">Kumppani</string>\n    <string name=\"time\">Aika</string>\n    <string name=\"calls_duration\">Kesto</string>\n    <string name=\"calls_add_delete_question\">Haluatko luoda uuden yhteystiedon \\'%1$s\\' tai poistaa\n        %2$s puheluhistoriasta\\?\n    </string>\n    <string name=\"calls_delete_question\">Haluatko poistaa \\'%1$s\\' %2$s puheluhistoriasta\\?</string>\n    <string name=\"disable_history\">Poista käytöstä</string>\n    <string name=\"enable_history\">Ota käyttöön</string>\n    <string name=\"delete_history_alert\">Haluatko tyhjentää tilin \\'%1$s\\' puheluhistorian\\?</string>\n    <string name=\"call_answered\">Puheluun vastattu</string>\n    <string name=\"call_answered_elsewhere\">Puheluun vastattu muualla</string>\n    <string name=\"call_missed\">Puheluun ei vastattu</string>\n    <string name=\"call_rejected\">Puhelu hylätty</string>\n    <!-- Call Details Activity -->\n    <string name=\"playing_recording\">Tallenteen kuuntelu …</string>\n    <string name=\"save_recording\">Talleta tallenne</string>\n    <string name=\"save_recording_question\">Haluatko tallentaa tämän tallenteen?</string>\n    <string name=\"recording_saved\">Tallenne tallennettu</string>\n    <string name=\"delete_call_alert\">Haluatko poistaa tämän puhelun historiata?</string>\n    <!-- Chat Activity -->\n    <string name=\"chat_with\">Viestiketju %1$s</string>\n    <string name=\"new_message\">Uusi viesti</string>\n    <string name=\"long_message_question\">Haluatko poistaa viestin tai\n    luoda uuden yhteystiedon \\'%1$s\\'\\?</string>\n    <string name=\"short_message_question\">Haluatko poistaa viestin\\?</string>\n    <string name=\"add_contact\">Lisää yhteystieto</string>\n    <string name=\"sending_failed\">Viestin lähetys epäonnistui</string>\n    <string name=\"message_failed\">Epäonnistui</string>\n    <!-- Chats Activity -->\n    <string name=\"chats\">Viestiketjut</string>\n    <string name=\"today\">Tänään</string>\n    <string name=\"you\">Sinä</string>\n    <string name=\"new_chat_peer\">Uusi viestin kohde</string>\n    <string name=\"long_chat_question\">Haluatko poistaa viestiketjun tai\n    luoda uuden yhteystiedon \\'%1$s\\'\\?</string>\n    <string name=\"short_chat_question\">Haluatko poistaa viestiketjun \\'%1$s\\'\\?</string>\n    <string name=\"delete_chats_alert\">Haluatko tyhjentää tilin \\'%1$s\\' viestihistorian\\?</string>\n    <!-- Codecs Activity -->\n    <string name=\"audio_codecs\">Audion koodausmenetelmät</string>\n    <string name=\"video_codecs\">Videon koodausmenetelmät</string>\n    <!-- Config Activity -->\n    <string name=\"configuration\">Asetukset</string>\n    <string name=\"start_automatically\">Käynnistä automaattisesti</string>\n    <string name=\"start_automatically_help\">Jos merkitty, baresip\n    käynnistyy automaattisesti, kun laite käynnistyy tai kun uusi baresip-versio\n        on asennettu. Käynnistys tapahtuu, kun laite on avattu.</string>\n    <string name=\"appear_on_top_permission\">Automaattinen käynnistys vaatii \"Näytä päällimmäisenä\"\n        käyttöoikeuden.</string>\n    <string name=\"battery_optimizations\">Akun käytön optimointi</string>\n    <string name=\"battery_optimizations_help\">Ota akun käytön optimointi pois päältä (suositeltu),\n        jos haluat vähentää todennäköisyyttä, että Android rajoittaa baresip-sovelluksen toimintaa\n        ja pääsyä verkkoon.</string>\n    <string name=\"default_phone_app\">Oletuspuhelinsovellus</string>\n    <string name=\"dialer_role_not_available\">Puhelinrooli ei ole saatavana</string>\n    <string name=\"default_phone_app_help\">Jos merkity, baresip on oletuspuhelinsovellus. Älä merkitse, jos laitteesi täytyy hallita myös muita kuin SIP-puheluita tai -viestejä.</string>\n    <string name=\"listen_address\">Kuunteluosoite</string>\n    <string name=\"listen_address_help\">IP-osoite ja portti muotoa\n    \\'osoite:portti\\', missä baresip kuuntelee sisään tulevia\n    SIP-sanomia. Jos IP-osoite on IPv6-osoite, se täytyy kirjoittaa\n    sulkujen [] sisään. IPv4-osoite 0.0.0.0 tai IPv6-osoite [::]\n    tarkoittaa kaikkia käytössä olevia osoitteita.  Oletusarvo on tyhjä,\n    jolloin baresip kuuntelee satunnaisesti valittua porttia kaikilla\n    osoitteilla.\n    </string>\n    <string name=\"invalid_listen_address\">Virheellinen kuunteluosoite</string>\n    <string name=\"address_family\">Osoiteperhe</string>\n    <string name=\"address_family_help\">Valitsee, mitä IP-osoitteita baresip käyttää. Jos IPv4 tai IPv6\n        on valittu, baresip käyttää ainoastaan IPv4- tai IPv6-osoitteita. Jos kumpaakaan ei ole\n        valittu, baresip käyttää sekä IPv4- että IPv6-osoitteita.\n    </string>\n    <string name=\"transport_protocols\">Kuljetusprotokollat</string>\n    <string name=\"transport_protocols_help\">Pilkulla toisistaan erotettu lista tuettuja SIP-sanomien\n        kuljetusprotokollia.  Jos jätetään tyhjäksi, oletusarvo on \\'udp,tcp,tls,ws,wss\\', joka\n        sisältää kaikki tuetut kuljetusprotokollat.  Listaa vain ne joita tarvitset.  UDP-protokollan\n        käyttöä ei suositella turvallisuus- ja muista syistä.\n    </string>\n    <string name=\"invalid_transport_protocols\">Virheellinen kuljetusprotokollalista</string>s\n    <string name=\"dns_servers\">DNS-palvelimet</string>\n    <string name=\"dns_servers_help\">Pilkulla toisistaan erotettu luettelo DNS-palvelijoiden\n        osoitteita. Jos jätetään antamatta (oletus), osoitteet hankitaan dynaamisesti järjestelmästä.\n        Kukin osoite on muotoa \\'ip:portti\\' tai \\'ip\\', missä ip on IPv4 tai IPv6 osoite. Jos ip\n        on IPv6-osoite ja myös portti annetaan, pitää osoite kirjoittaa sulkujen [] sisään.\n        Esimerkiksi luettelo \\'8.8.8.8:53,[2001:4860:4860::8888]:53\\' osoittaa Googlen julkisiin\n        DNS-palvelijoihin.</string>\n    <string name=\"invalid_dns_servers\">Virheelliset DNS-palvelimet</string>\n    <string name=\"failed_to_set_dns_servers\">DNS-palvelinten asetus epäonnistui</string>\n    <string name=\"tls_certificate_file\">TLS-sertifikaattitiedosto</string>\n    <string name=\"tls_certificate_file_help\">Jos merkitty, tiedosto joka sisältää\n        tämän baresip-sovelluksen julkisen ja yksityisen TLS-sertifikaatin, on joko jo ladattu\n        tai tullaan lataamaan.  Android versiossa 9 tiedosto nimeltään \\'cert.pem\\'\n        ladataan Download-kansiosta. Turvallisuusyistä tuhoa tiedosto heti lataamisen jälkeen.\n    </string>\n    <string name=\"verify_server\">Tarkista palvelinten sertifikaatit</string>\n    <string name=\"verify_server_help\">Jos merkitty, baresip tarkistaa SIP-palvelinten\n        sertifikaatit, kun TLS-tiedonsiirto on käytössä.\n    </string>\n    <string name=\"tls_ca_file\">TLS CA-tiedosto</string>\n    <string name=\"tls_ca_file_help\">Jos merkitty, tiedosto on joko jo ladattu tai tullaan\n        lataamaan, joka sisältää sellaisten sertifikaattiauktoriteettien (CA) julkiset sertifikaatit,\n        jotka eivät sisälly Android-käyttöjärjestelmään. Android versiossa 9 tiedosto\n        nimeltään \\'ca_certs.crt\\' ladataan Download-kansiosta.\n    </string>\n    <string name=\"no_read_permission\">Ei ulkoisen muistin lukuoikeutta</string>\n    <string name=\"unique_contact_uri\">Uniikki Contact URI</string>\n    <string name=\"unique_contact_uri_help\">Jos merkitty, Contact URI:n taataan olevan uniikki.\n        Merkittävä, jos on useammalla tilillä on same SIP URI:n käyttäjäosa, mutta myös\n        suojaa tilejä hyökkäyksiltä.\n    </string>\n    <string name=\"audio_settings\">Audio-asetukset</string>\n    <string name=\"speaker_phone\">Kaiutin</string>\n    <string name=\"speaker_phone_help\">Jos merkitty, kaiutin kytketään sutomaattisesti päälle\n        kun puhelu alkaa.\n    </string>\n    <string name=\"audio_modules_title\">Audio-modulit</string>\n    <string name=\"audio_modules_help\">Merkittyjen modulien tarjoamat audio-koodekit ovat tilien\n        käytettävissä.\n    </string>\n    <string name=\"failed_to_load_module\">Modulin lataaminen epäonnistui.</string>\n    <string name=\"microphone_gain\">Mikrofonin äänikerroin</string>\n    <string name=\"microphone_gain_help\">Kertoo mikrofonin äänen voimakkuuden tällä desimaaliluvulla.\n        Minimikerroin on 1.0 (tehdasasetus), mikä ei muuta mikrofonin äänen voimakkuutta.  Suuremmat\n        arvot voivat vaikuttaa negatiivisesti äänen laatuun.</string>\n    <string name=\"invalid_microphone_gain\">Virheellinen mikrofonin äänikerroin</string>\n    <string name=\"opus_bit_rate\">Opus-koodekin bittinopeus</string>\n    <string name=\"opus_bit_rate_help\">Opus-koodekin käyttämä\n    keskimääräinen enimmäisnopeus. Mahdollisia arvoja ovat\n    6000-510000. Oletusarvo on 28000.\n    </string>\n    <string name=\"opus_packet_loss\">Odotettu opus-pakettihäviö</string>\n    <string name=\"opus_packet_loss_help\">Odotettu opus audio virran pakettihäviö prosentteina.\n        Mahdolliset arvot ovat 0-100. Oletusarvo on 1.  Arvo 0 poistaa käytöstä\n        ennakoivan virheenkorjauksen.</string>\n    <string name=\"invalid_opus_bitrate\">Virheellinen Opus-koodekin bittinopeus</string>\n    <string name=\"invalid_opus_packet_loss\">Virheellinen Opus-koodekin odotettu pakettihäviö</string>\n    <string name=\"audio_delay\">Audioviive</string>\n    <string name=\"audio_delay_help\">Audion odotusviive (millisekunneissa) soitetun puhelun alkaessa.\n        Aseta korkeampi arvo, jos et kuule vastaajan ääntä heti, kun puhelu alkaa.</string>\n    <string name=\"invalid_audio_delay\">Virheellinen audioviive \\'%1$s\\'. Sallittu arvo on välillä 100–3000.</string>\n    <string name=\"default_call_volume\">Oletus äänen voimakkuus</string>\n    <string name=\"default_call_volume_help\">Jos valittu, puhelun äänen voimakkuus\n        asteikolla 1–10.</string>\n    <string name=\"tone_country\">Puheluäänien maa</string>\n    <string name=\"tone_country_help\">Soitto-, odotus- ja varattuäänien maa</string>\n    <string name=\"dark_theme\">Tumma teema</string>\n    <string name=\"dark_theme_help\">Käytä aina tummaa näyttöteemaa</string>\n    <string name=\"dynamic_colors\">Dynaamiset värit</string>\n    <string name=\"dynamic_colors_help\">Käytä dynaamisia värejä, jos ne on otettu käyttöön\n        Android-asetuksissa</string>\n    <string name=\"colorblind\">Värisokea</string>\n    <string name=\"colorblind_help\">Käytä värisokealle ystävällisiä rekisteröintitilaikoineita</string>\n    <string name=\"proximity_sensing\">Läheisyyden tunnistus</string>\n    <string name=\"proximity_sensing_help\">Jos merkitty, läheisyyden tunnistus on aktiivinen\n        puhelun aikana.</string>\n    <string name=\"video_size\">Videon kehyskoko</string>\n    <string name=\"video_size_help\">Lähetettävän videon kehyskoko (leveys x korkeus)</string>\n    <string name=\"video_fps\">Videokehysten lähetystaajuus</string>\n    <string name=\"video_fps_help\">Videokehysten lähetystaajuus per sekuntti, jota tarjotaan\n        SDP-neuvottelun aikana. Arvon on oltava välillä 10-30.</string>\n    <string name=\"invalid_fps\">Virheellinen videokehysten lähetystaajuus \\'%1$d\\'</string>\n    <string name=\"ringtone\">Soittoääni</string>\n    <string name=\"select_ringtone\">Valitse soittoääni</string>\n    <string name=\"user_agent\">User Agent</string>\n    <string name=\"user_agent_help\">Räätälöity SIP sanoman User-Agent otsikkokentän arvo</string>\n    <string name=\"invalid_user_agent\">Virheellinen User-Agent otsikkokentän arvo</string>\n    <string name=\"contacts_help\">Valitsee, käytetäänkö Baresip-yhteystietoja, Android-yhteystietoja vai molempia. Jos molempia käytetään ja molemmissa yhteystiedoissa on samanniminen yhteystieto, valitaan baresip-yhteystieto.</string>\n    <string name=\"both\">Molemmat</string>\n    <string name=\"debug\">Lokiviestit</string>\n    <string name=\"debug_help\">Jos merkitty, baresip tuottaa debug- ja info-tason Logcat-viestejä.</string>\n    <string name=\"sip_trace\">SIP-sanomien jäljitys</string>\n    <string name=\"sip_trace_help\">Jos merkitty ja jos Lokiviestit on merkitty, Logcat-viestit\n        sisältävät myös lähetetyt ja vastaanotetut SIP-sanomat. On aina merkitsemättä sovelluksen\n        käynnistyessä.</string>\n    <string name=\"reset_config\">Palauta oletusasetukset</string>\n    <string name=\"reset_config_help\">Jos merkitty, oletusasetukset palautetaan, kun\n        baresip seuraavan kerran käynnistetään.\n    </string>\n    <string name=\"reset_config_alert\">Oletko varma, että haluat palauttaa oletusasetukset\\?</string>\n    <string name=\"reset\">Palauta</string>\n    <string name=\"read_cert_error\">Tiedoston \\'cert.pem\\' luku epäonnistui.</string>\n    <string name=\"read_ca_certs_error\">Tiedoston \\'ca_certs.crt\\' epäonnistui.</string>\n    <string name=\"config_restart\">baresip täytyy käynnistää uudelleen, jotta saat uudet asetukset\n        käyttöön. Käynnistä nyt\\?\n    </string>\n    <string name=\"consent_request\">Suostumuspyyntö</string>\n    <string name=\"contacts_consent\">Jos Androidin yhteystiedot on valittu, voit käyttää niitä puheluissa ja viesteissä SIP ja tel URI:en ohella. Baresip ei talleta Androidin yhteystietoja eikä jaa niitä kenellekään. Jotta Baresip saisi käyttöönsä Androidin yhteystiedot, Google vaatii, että annat siihen suostumuksen hyväksymällä Baresip-sovelluksen <a href=\"https://github.com/juha-h/baresip-studio/blob/master/PrivacyPolicy.txt\">yksityisyyskäytännöt</a>.</string>\n    <!-- Contact Activity -->\n    <string name=\"new_contact\">Uusi yhteystieto</string>\n    <string name=\"contact_name\">Nimi</string>\n    <string name=\"sip_or_tel_uri\">SIP tai tel URI</string>\n    <string name=\"user_domain_or_number\">käyttäjä@domain tai puhelinnumero</string>\n    <string name=\"favorite\">Suosikki</string>\n    <string name=\"favorite_help\">Jos ruksattu, kontakti näytetään muiden suosikkien kanssa yhteystietolistan alussa.</string>\n    <string name=\"invalid_contact\">Virheellinen yhteystiedon nimi \\'%1$s\\'</string>\n    <string name=\"contact_already_exists\">Yhteystieto \\'%1$s\\' on jo olemassa.</string>\n    <string name=\"android_contact_help\">Jos merkitty, tämä yhteystieto lisätään Androidin yhteystietoihin.</string>\n    <string name=\"avatar_image\">Profiilikuva</string>\n    <!-- Contacts Activity -->\n    <string name=\"contacts\">Yhteystiedot</string>\n    <string name=\"contact_action_question\">Haluatko soittaa tai lähettää\n    viestin ositteeseen \\'%1$s\\'\\?</string>\n    <string name=\"send_message\">Lähetä viesti</string>\n    <string name=\"contact_delete_question\">Haluatko poistaa yhteystiedon \\'%1$s\\'\\?</string>\n    <string name=\"search\">Etsi</string>\n    <!-- Generic -->\n    <string name=\"alert\">Varoitus</string>\n    <string name=\"info\">Tieto</string>\n    <string name=\"notice\">Huomio</string>\n    <string name=\"cancel\">Peruuta</string>\n    <string name=\"stop\">Lopeta</string>\n    <string name=\"reply\">Vastaa</string>\n    <string name=\"save\">Talleta</string>\n    <string name=\"ok\">OK</string>\n    <string name=\"yes\">Kyllä</string>\n    <string name=\"no\">Ei</string>\n    <string name=\"accept\">Hyväksy</string>\n    <string name=\"deny\">Estä</string>\n    <string name=\"add\">Lisää</string>\n    <string name=\"delete\">Poista</string>\n    <string name=\"edit\">Muokkaa</string>\n    <string name=\"status\">Tila</string>\n    <string name=\"error\">Virhe</string>\n    <string name=\"confirmation\">Vahvistus</string>\n    <string name=\"anonymous\">Anonyymi</string>\n    <string name=\"unknown\">Tuntematon</string>\n    <string name=\"invalid_sip_or_tel_uri\">Virheellinen SIP tai puh. URI \\'%1$s\\'</string>\n    <!-- Main Activity -->\n    <string name=\"backup\">Tallenna</string>\n    <string name=\"restore\">Palauta</string>\n    <string name=\"about\">Tietoja</string>\n    <string name=\"restart\">Käynnistä uudelleen</string>\n    <string name=\"quit\">Lopeta</string>\n    <string name=\"outgoing_call_to_dots\">Puhelu ulos …</string>\n    <string name=\"incoming_call_from_dots\">Puhelu sisään …</string>\n    <string name=\"diverted_by_dots\">Siirtäjä …</string>\n    <string name=\"no_telephony_provider\">Tilille \\'%1$s\\' ei ole konfiguroitu puhelinpalvelun\n        tarjoajaa</string>\n    <string name=\"video_call\">Videopuhelu</string>\n    <string name=\"video_request\">Videopyyntö</string>\n    <string name=\"allow_video\">Salli video videon lähetys ja vastaanotto puhelussa \\'%1$s\\'\\?</string>\n    <string name=\"allow_video_send\">Salli video videon lähetys puhelussa \\'%1$s\\'\\?</string>\n    <string name=\"allow_video_recv\">Salli video videon vastaanotto puhelussa \\'%1$s\\'\\?</string>\n    <string name=\"call_is_ringing\">Puhelu soi</string>\n    <string name=\"call_is_on_hold\">Puhelu on pidossa</string>\n    <string name=\"call_is_connected\">Puhelu on yhdistetty</string>\n    <string name=\"rec_in_call\">Tallennus voidaan asettaa päälle tai pois vain silloin, kun puhelu\n        ei ole yhdistetty</string>\n    <string name=\"call_transfer\">Puhelun siirto</string>\n    <string name=\"blind\">Sokeasti</string>\n    <string name=\"attended\">Osallistu</string>\n    <string name=\"transfer_destination\">Siirron kohde</string>\n    <string name=\"choose_destination_uri\">Valitse kohteen URI</string>\n    <string name=\"transfer\">Siirto</string>\n    <string name=\"transfer_failed\">Siirto epäonnistui</string>\n    <string name=\"replaces_not_supported\">Toinen osapuoli ei tue REPLACES ominaisuutta</string>\n    <string name=\"dtmf\">DTMF</string>\n    <string name=\"call_info\">Puhelutiedot</string>\n    <string name=\"call_info_not_available\">Ei saatavilla</string>\n    <string name=\"duration\">Kesto: %1$d (sek)</string>\n    <string name=\"codecs\">Koodekit</string>\n    <string name=\"rate\">Nykyinen nopeus: %1$s (Kbit/s)</string>\n    <string name=\"average_rate\">Keskinopeus: %1$s (Kbit/s)</string>\n    <string name=\"packets\">Paketit</string>\n    <string name=\"lost\">Hukkunut</string>\n    <string name=\"jitter\">Vaihtelu: %1$s (ms)</string>\n    <string name=\"voicemail_messages\">Puhepostiviestit</string>\n    <string name=\"you_have\">Sinulla on</string>\n    <string name=\"one_new_message\">yksi uusi viesti</string>\n    <string name=\"new_messages\">uutta viestiä</string>\n    <string name=\"one_old_message\">yksi vanha viesti</string>\n    <string name=\"old_messages\">vanhaa viestiä</string>\n    <string name=\"and\">ja</string>\n    <string name=\"no_messages\">Sinulla ei ole viestejä</string>\n    <string name=\"listen\">Kuuntele</string>\n    <string name=\"call_already_active\">Sinulla on jo puhelu käynnissä.</string>\n    <string name=\"start_failed\">Sovelluksen käynnistäminen epäonnistui. Tämä voi johtua virheellisestä\n        Asetukset arvosta.  Tarkista Kuunteluosoite, TLS-varmennintiedosto ja TLS CA-tiedosto.\n        Sen jälkeen käynnistä baresip uudelleen.</string>\n    <string name=\"registering_failed\">Tilin \\'%1$s\\' rekisteröinti epäonnistui.</string>\n    <string name=\"verify\">Todennuspyyntö</string>\n    <string name=\"verify_sas\">Todennatko SAS:n &lt;%1$s&gt;\\?</string>\n    <string name=\"transfer_request\">Siirtopyyntö</string>\n    <string name=\"transfer_request_query\">Hyväksytkö tämän puhelun siirron kohteeseen \\'%1$s\\'\\?</string>\n    <string name=\"call_request\">Soittopyyntö</string>\n    <string name=\"call_request_query\">Hyväksytkö pyynnön soittaa kohteeseen \\'%1$s\\'\\?</string>\n    <string name=\"redirect_notice\">Automaattinen uudelleenohjaus kohteeseen \\'%1$s\\'\\</string>\n    <string name=\"redirect_request\">Uudelleenohjauspyyntö</string>\n    <string name=\"redirect_request_query\">Hyväksytkö puhelun uudelleenohjauksen kohteeseen \\'%1$s\\'\\?</string>\n    <string name=\"call_failed\">Puhelu epäonnistui</string>\n    <string name=\"call_closed\">Puhelu on päättynyt</string>\n    <string name=\"call_not_secure\">Tämä puhelu EI ole turvallinen!</string>\n    <string name=\"peer_not_verified\">Tämä puhelu on turvallinen, mutta puhelun toista osapuolta ei ole\n        todennettu!</string>\n    <string name=\"call_is_secure\">Tämä puhelu on turvallinen ja puhelun toinen osapuoli on\n    todennettu! Haluatko poistaa todennuksen\\?</string>\n    <string name=\"unverify\">Poista todennus</string>\n    <string name=\"backed_up\">Sovelluksen tiedot (äänityksiä lukuunottamatta)\n        talletettiin tiedostoon \\'%1$s\\'. Android versiossa 9 tiedosto löytyy\n        Download-kansiosta.</string>\n    <string name=\"backup_failed\">Sovelluksen tietojen talletus\n        tiedostoon \\'%1$s\\' epäonnistui. Android versiossa 9 tarkista\n        Asetukset → Sovellukset → baresip → Käyttöluvat → Tallennustila.</string>\n    <string name=\"restart_request\">Uudelleenkäynnistyspyyntö</string>\n    <string name=\"restored\">Sovelluksen tiedot palautettiin. baresip pitää käynnistää\n        uudelleen. Käynnistä uudelleen nyt\\?</string>\n    <string name=\"restore_failed\">Sovelluksen tietojen palauttaminen epäonnistui. Tarkista, että\n        annoit oikean salasanan ja että palautustiedosto kuuluu tällä sovellukselle.\n        Android versiossa 9 tarkista myös Asetukset → Sovellukset → baresip → Käyttöluvat →\n        Tallennustila ja että tiedosto \\'%1$s\\' on Download-kansiossa.</string>\n    <string name=\"restore_unzip_failed\">Sovelluksen tietojen palauttaminen epäonnistui. Android\n        versiosta 14 alkaen ei salli tietojen palauttamista, jos ne on tallettu ennen %1$s versioa\n        %2$s.</string>\n    <string name=\"no_notifications\">Et voi käyttää tätä sovellusta ilman Ilmoitukset-lupaa.</string>\n    <string name=\"no_calls\">Et voi soittaa puheluita tai vastata niihin ilman Mikrofoni-lupaa.</string>\n    <string name=\"no_video_calls\">Salli Kamera-lupa jotta voit soittaa videopuheluita ja vastata niihin.</string>\n    <string name=\"no_backup\">Et voi tallentaa sovelluksen tietoja ilman Tallennustila-lupaa.</string>\n    <string name=\"no_restore\">Et voi palauttaa sovelluksen tietoja ilman Tallennustila-lupaa.</string>\n    <string name=\"no_cameras\">Ei tuettuja videokameroita!</string>\n    <string name=\"no_network\">Ei verkkoyhteyttä!</string>\n    <string name=\"no_aec\">Ei laitteistolla tuettua kaiun poistoa!</string>\n    <string name=\"call_details\">Puhelutiedot</string>\n    <string name=\"no_android_contacts\">Et voi käyttää Androidin yhteystietoja ilman Yhteystiedot-lupaa.</string>\n    <string name=\"audio_focus_denied\">Audiofokus on evätty!</string>\n    <string name=\"permissions_rationale\">Tarvittavat luvat</string>\n    <string name=\"audio_permissions\">baresip tarvitsee Mikrofoni-luvan puheluita varten, Lähellä olevat laitteet -luvan\n        Bluetooth-mikrofonin/kaiuttimen havaitsemista varten, Ilmoitukset-luvan ilmoitusten lähettämistä varten\n        ja Android versiossa 9 Tallennustila-luvan Talleta/Palauta-toimintoja varten.</string>\n    <string name=\"audio_and_video_permissions\">baresip+ tarvitsee Mikrofoni-luvan puheluita varten,\n        Kamera-luvan videopuheluita varten, Lähellä olevat laitteet -luvan Bluetooth-mikrofonin/kaiuttimen\n        havaitsemista varten, Ilmoitukset-luvan ilmoitusten näyttämistä varten ja Android versiossa 9\n        Tallennustila-luvan Talleta/Palauta-toimintoja varten.</string>\n    <string name=\"call_recording_title\">Puheluiden talletus</string>\n    <string name=\"call_recording_tip\">Jos aktivoitu, uudet soitetut ja vastatut puhelut talletetaan. Tallennukset voi kuunnella Puhelutiedot-sivulla.</string>\n    <string name=\"microphone_title\">Mikrofoni</string>\n    <string name=\"microphone_tip\">Jos aktivoidaan puhelun aikana, mikrofoni suljetaan.</string>\n    <string name=\"speakerphone_title\">Kaiutin</string>\n    <string name=\"speakerphone_tip\">Jos aktivoitu, laitteen kaiutin on päällä.</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"transfer\">Transférer</string>\n    <string name=\"video_request\">Demande de vidéo</string>\n    <string name=\"video_call\">Appel vidéo</string>\n    <string name=\"incoming_call_from_dots\">Appel entrant de…</string>\n    <string name=\"outgoing_call_to_dots\">Appel sortant à…</string>\n    <string name=\"quit\">Quitter</string>\n    <string name=\"restart\">Redémarrer</string>\n    <string name=\"about\">À propos</string>\n    <string name=\"restore\">Restaurer</string>\n    <string name=\"backup\">Sauvegarder</string>\n    <string name=\"confirmation\">Confirmation</string>\n    <string name=\"error\">Erreur</string>\n    <string name=\"status\">Statut</string>\n    <string name=\"edit\">Modifier</string>\n    <string name=\"delete\">Supprimer</string>\n    <string name=\"add\">Ajouter</string>\n    <string name=\"deny\">Refuser</string>\n    <string name=\"accept\">Accepter</string>\n    <string name=\"no\">Non</string>\n    <string name=\"yes\">Oui</string>\n    <string name=\"ok\">OK</string>\n    <string name=\"cancel\">Annuler</string>\n    <string name=\"info\">Infos</string>\n    <string name=\"send_message\">Envoyer le message</string>\n    <string name=\"contacts\">Contacts</string>\n    <string name=\"contact_already_exists\">Le contact « %1$s » existe déjà.</string>\n    <string name=\"invalid_contact\">Nom de contact invalide « %1$s »</string>\n    <string name=\"contact_name\">Nom</string>\n    <string name=\"new_contact\">Nouveau contact</string>\n    <string name=\"debug\">Débogage</string>\n    <string name=\"dns_servers\">Serveurs DNS</string>\n    <string name=\"start_automatically\">Démarrer automatiquement</string>\n    <string name=\"configuration\">Paramètres</string>\n    <string name=\"you\">Vous</string>\n    <string name=\"today\">Aujourd’hui</string>\n    <string name=\"sending_failed\">Échec de l’envoi du message</string>\n    <string name=\"add_contact\">Ajouter un contact</string>\n    <string name=\"new_message\">Nouveau message</string>\n    <string name=\"delete_history_alert\">Voulez-vous supprimer l’historique des appels du compte « %1$s » \\?</string>\n    <string name=\"enable_history\">Activer</string>\n    <string name=\"disable_history\">Désactiver</string>\n    <string name=\"calls_call\">appel</string>\n    <string name=\"calls_calls\">appels</string>\n    <string name=\"call\">Appel</string>\n    <string name=\"call_history\">Historique des appels</string>\n    <string name=\"transfer_request_to\">Demande de transfert d’appel vers</string>\n    <string name=\"delete_account\">Voulez-vous supprimer le compte « %1$s » \\?</string>\n    <string name=\"decrypt_password\">Déchiffrer le mot de passe</string>\n    <string name=\"encrypt_password\">Chiffrer le mot de passe</string>\n    <string name=\"new_account\">URI SIP du nouveau compte</string>\n    <string name=\"accounts\">Comptes</string>\n    <string name=\"media_encryption\">Chiffrement des médias</string>\n    <string name=\"stun_password\">Mot de passe STUN/TURN</string>\n    <string name=\"invalid_stun_username\">Nom d’utilisateur « %1$s » invalide</string>\n    <string name=\"stun_username\">Nom d’utilisateur STUN / TURN</string>\n    <string name=\"voicemail_messages\">Messages vocaux</string>\n    <string name=\"voicemail_uri\">URI de la messagerie vocale</string>\n    <string name=\"authentication_username\">Nom d’utilisateur d’authentification</string>\n    <string name=\"invalid_display_name\">Nom d’affichage invalide « %1$s »</string>\n    <string name=\"display_name_help\">Nom (le cas échéant) utilisé dans l\\'URI From des demandes sortantes.</string>\n    <string name=\"display_name\">Nom d’affichage</string>\n    <string name=\"account\">Compte</string>\n    <string name=\"about_title_plus\">À propos de baresip+</string>\n    <string name=\"about_title\">À propos de baresip</string>\n    <string name=\"invalid_authentication_password\">Mot de passe d\\'authentification invalide « %1$s »</string>\n    <string name=\"authentication_password_help\">Mot de passe d\\'authentification jusqu\\'à 64 caractères. Si le nom d\\'utilisateur d\\'authentification est donné mais que le mot de passe n\\'est pas donné, il sera demandé au démarrage de baresip.</string>\n    <string name=\"authentication_password\">Mot de passe d\\'authentification</string>\n    <string name=\"invalid_authentication_username\">Nom d\\'utilisateur d\\'authentification invalide « %1$s »</string>\n    <string name=\"call_already_active\">Vous avez déjà un appel actif.</string>\n    <string name=\"listen\">Écouter</string>\n    <string name=\"no_messages\">Vous n\\'avez aucun message</string>\n    <string name=\"and\">et</string>\n    <string name=\"old_messages\">anciens messages</string>\n    <string name=\"one_old_message\">un ancien message</string>\n    <string name=\"new_messages\">nouveaux messages</string>\n    <string name=\"one_new_message\">un nouveau message</string>\n    <string name=\"you_have\">Vous avez</string>\n    <string name=\"rate\">Débit : %1$s (kbit/s)</string>\n    <string name=\"call_info\">Infos de l\\'appel</string>\n    <string name=\"duration\">Durée %1$d</string>\n    <string name=\"call_info_not_available\">Aucune information disponible</string>\n    <string name=\"authentication_username_help\">Nom d\\'utilisateur d\\'authentification si l\\'authentification des demandes SIP est requise. La valeur par défaut est le nom d\\'utilisateur du compte.</string>\n    <string name=\"video_codecs\">Codecs vidéo</string>\n    <string name=\"message_failed\">Échec</string>\n    <string name=\"register\">S\\'enregistrer</string>\n    <string name=\"audio_codecs\">Codecs audio</string>\n    <string name=\"stun_server\">Serveur STUN/TURN</string>\n    <string name=\"manual\">Manuel</string>\n    <string name=\"answer_mode\">Mode de réponse</string>\n    <string name=\"default_account\">Compte par défaut</string>\n    <string name=\"dtmf\">DTMF</string>\n    <string name=\"alert\">Alerte</string>\n    <string name=\"notice\">Notice</string>\n    <string name=\"auto\">Automatique</string>\n    <string name=\"codecs\">Codecs</string>\n    <string name=\"chats\">Historique des conversations</string>\n    <string name=\"unverify\">Annuler la vérification</string>\n    <string name=\"listen_address\">Adresse d\\'écoute</string>\n    <string name=\"reset\">Restaurer</string>\n    <string name=\"audio_modules_title\">Modules audio</string>\n    <string name=\"invalid_account_nickname\">Surnom de compte invalide « %1$s »</string>\n    <string name=\"non_unique_account_nickname\">Le surnom « %1$s » existe déjà</string>\n    <string name=\"account_nickname_help\">Surnom (le cas échéant) utilisé pour identifier ce compte dans l\\'application baresip.</string>\n    <string name=\"nickname\">Surnom</string>\n    <string name=\"outbound_proxies\">Proxies sortants</string>\n    <string name=\"outbound_proxies_help\">URI SIP d’un ou deux proxies qui doit être utilisé lors de l’envoi des requêtes. Si deux sont données, les demandes REGISTER sont envoyées aux deux et les autres demandes sont envoyées à celui qui répond. Si aucun proxy sortant n’est donné, les demandes sont envoyées en fonction de la recherche d’enregistrement DNS NAPTR/SRV/A de l’hôte URI appelé. Si la partie hôte de l’URI SIP est une adresse IPv6, l’adresse doit être écrite entre crochets []. \\nExemples : \\n • sip:exemple.com:5061;transport=tls \\n • sip:[2001:67c:223:777::10];transport=tcp \\n • sip:192.168.43.50:443;transport=wss</string>\n    <string name=\"sip_uri_of_proxy_server\">URI SIP du serveur proxy</string>\n    <string name=\"sip_uri_of_another_proxy_server\">URI SIP d’un autre serveur proxy</string>\n    <string name=\"invalid_proxy_server_uri\">URI de serveur proxy invalide \\'%1$s’</string>\n    <string name=\"register_help\">Si coché, l’enregistrement est activé et les demandes REGISTER sont envoyées à l’intervalle spécifié par intervalle d’enregistrement.</string>\n    <string name=\"reg_int\">Intervalle d’enregistrement</string>\n    <string name=\"reg_int_help\">Indique à quelle fréquence (en secondes) baresip envoie des demandes REGISTER. Les valeurs valides sont comprises entre 60 et 3600.</string>\n    <string name=\"invalid_reg_int\">Intervalle d’enregistrement invalide \\'%1$s\\'</string>\n    <string name=\"invalid_stun_password\">Mot de passe invalide \\'%1$s\\'</string>\n    <string name=\"rtcp_mux\">Multiplexage RTCP</string>\n    <string name=\"rtcp_mux_help\">Si coché, les paquets RTP et RTCP sont multiplexés sur un seul port (RFC 5761).</string>\n    <string name=\"dtmf_mode\">Mode DTMF</string>\n    <string name=\"dtmf_mode_help\">Sélectionne comment les tonalités DTMF 0–9, #, *, et A-D sont envoyées.</string>\n    <string name=\"answer_mode_help\">Sélectionne la façon dont les appels entrants sont répondus.</string>\n    <string name=\"redirect_mode\">Mode redirection</string>\n    <string name=\"redirect_mode_help\">Sélectionne si la demande de redirection d’appel est suivie automatiquement ou si une confirmation est demandée.</string>\n    <string name=\"voicemain_uri_help\">URI SIP pour la vérification des messages vocaux. Si laissé vide, les messages vocaux (indications d’attente de message) ne sont pas souscrits.</string>\n    <string name=\"country_code\">Code pays</string>\n    <string name=\"invalid_country_code\">Code de pays invalide \\'%1$s’</string>\n    <string name=\"telephony_provider\">Fournisseur téléphonie</string>\n    <string name=\"telephony_provider_help\">Partie hôte URI SIP utilisée dans les appels vers des numéros de téléphone. La valeur par défaut d’usine est le domaine du compte. Si ce compte n’est pas indiqué, il ne peut pas être utilisé pour appeler des numéros de téléphone.</string>\n    <string name=\"numeric_keypad\">Pavé numérique</string>\"\n    <string name=\"numeric_keypad_help\">Si coché, le pavé numérique s’affiche lorsque le champ \\\"Appel à …\\\" est désigné.</string>\"\n    <string name=\"default_account_help\">Si coché, ce compte est sélectionné lorsque Baresip est démarré.</string>\n    <string name=\"account_exists\">Le compte \\'%1$s\\' existe déjà.</string>\n    <string name=\"account_allocation_failure\">Échec d’allocation d’un nouveau compte.</string>\n    <string name=\"save\">Enregistrer</string>\n    <string name=\"missed_call_from\">Appel manqué de la part de</string>\n    <string name=\"missed_calls\">Appels manqués</string>\n    <string name=\"missed_calls_count\">%1$d appels manqués</string>\n    <string name=\"call_auto_rejected\">Appel rejeté automatiquement de %1$s</string>\n    <string name=\"call_details\">Détails appel</string>\n    <string name=\"direction\">Direction</string>\n    <string name=\"calls_duration\">Durée</string>\n    <string name=\"playing_recording\">Lecture d’enregistrement …</string>\n    <string name=\"calls_add_delete_question\">Voulez-vous ajouter \\'%1$s\\' aux contacts ou supprimer %2$s de l’historique des appels ?</string>\n    <string name=\"calls_delete_question\">Voulez-vous supprimer \\'%1$s\\' %2$s de l’historique des appels ?</string>\n    <string name=\"call_answered\">Appel répondu</string>\n    <string name=\"call_answered_elsewhere\">Appel répondu ailleurs</string>\n    <string name=\"call_missed\">Appel manqué</string>\n    <string name=\"call_rejected\">Appel rejeté</string>\n    <string name=\"short_message_question\">Voulez-vous supprimer le message ?</string>\n    <string name=\"start_automatically_help\">Si coché, Baresip démarre automatiquement après le (re)démarrage de l’appareil.</string>\n    <string name=\"battery_optimizations\">Optimisations batterie</string>\n    <string name=\"battery_optimizations_help\">Désactivez les optimisations de la batterie (recommandé) si vous souhaitez réduire la probabilité qu’Android limite l’accès de Baresip au réseau ou entre en mode veille.</string>\n    <string name=\"default_phone_app\">App de téléphone par défaut</string>\n    <string name=\"invalid_listen_address\">Adresse d’écoute invalide</string>\n    <string name=\"address_family\">Famille d\\'adresse</string>\n    <string name=\"address_family_help\">Choisit les adresses IP que Baresip utilise. Si IPv4 ou IPv6 est choisi, Baresip n’utilise que des adresses IPv4 ou IPv6. Si aucun des deux n’est choisi, Baresip utilise les adresses IPv4 et IPv6.</string>\n    <string name=\"dns_servers_help\">Liste séparée par des virgules des adresses des serveurs DNS. Si elle n’est pas fournie, les adresses des serveurs DNS sont obtenues dynamiquement à partir du système. Chaque adresse DNS est de la forme \\'ip:port\\' ou \\'ip\\'. Si le port est omis, il vaut 53 par défaut. Si l’ip est une adresse IPv6 et que le port est également donné, l’ip doit être écrite entre crochets []. À titre d’exemple, liste \\'8.8.8.8:53,[2001:4860:4860::8888]:53\\' pointe vers les adresses IPv4 et IPv6 des serveurs DNS publics de Google.</string>\n    <string name=\"invalid_dns_servers\">Serveurs DNS invalides</string>\n    <string name=\"failed_to_set_dns_servers\">Échec de la configuration des serveurs DNS</string>\n    <string name=\"tls_certificate_file\">Fichier de certificat TLS</string>\n    <string name=\"tls_certificate_file_help\">Si coché, un fichier contenant le certificat TLS et la clé privée de cette instance Baresip a été ou sera chargé. Dans la version Android 9, un fichier appelé \\'cert.pem\\' est chargé à partir du dossier de téléchargement. Pour des raisons de sécurité, supprimez le fichier après le chargement.</string>\n    <string name=\"verify_server\">Vérifier certificats serveur</string>\n    <string name=\"verify_server_help\">Si coché, Baresip vérifie les certificats TLS de l’agent utilisateur SIP et des serveurs proxy SIP lorsque le transport TLS est utilisé.</string>\n    <string name=\"tls_ca_file\">Fichier CA TLS</string>\n    <string name=\"tls_ca_file_help\">Si cette case est cochée, un fichier contenant les certificats TLS des autorités de certification qui ne sont pas incluses dans le système d’exploitation Android a été ou sera chargé. Dans la version 9 d’Android, un fichier appelé \\'ca_certs.crt\\' est chargé à partir du dossier de téléchargement.</string>\n    <string name=\"no_read_permission\">Pas d’autorisation de lecture pour le stockage externe</string>\n    <string name=\"audio_settings\">Paramètres audio</string>\n    <string name=\"speaker_phone\">Téléphone avec haut-parleur</string>\n    <string name=\"speaker_phone_help\">Si coché, le téléphone haut-parleur est automatiquement allumé lorsque l’appel commence.</string>\n    <string name=\"audio_modules_help\">Les codecs audio fournis par les modules vérifiés sont disponibles pour une utilisation par les comptes.</string>\n    <string name=\"failed_to_load_module\">Échec du chargement du module.</string>\n    <string name=\"microphone_gain\">Gain du microphone</string>\n    <string name=\"microphone_gain_help\">Multipliez le volume du microphone par ce nombre décimal. La valeur minimale est de 1,0 (défaut d’usine) qui désactive le gain du microphone. Des valeurs plus grandes peuvent affecter négativement la qualité audio.</string>\n    <string name=\"invalid_microphone_gain\">Valeur de gain du microphone invalide</string>\n    <string name=\"opus_bit_rate\">Débit binaire Opus</string>\n    <string name=\"opus_bit_rate_help\">Débit binaire maximal moyen utilisé par le flux audio Opus. Les valeurs valides sont 6000-510000. La valeur par défaut est 28000.</string>\n    <string name=\"opus_packet_loss\">Perte de paquets prévue d’Opus</string>\n    <string name=\"opus_packet_loss_help\">Pourcentage attendu de perte de paquets du flux audio Opus, à partir de 0–100. La valeur par défaut d’usine est 1. La valeur 0 désactive également la correction d’erreur directe (FEC) Opus.</string>\n    <string name=\"audio_delay\">Délai audio</string>\n    <string name=\"dark_theme\">Thème sombre</string>\n    <string name=\"dark_theme_help\">Forcer le thème d’affichage sombre</string>\n    <string name=\"colorblind\">Daltonien</string>\n    <string name=\"colorblind_help\">Utilisez des icônes de statut d’inscription conviviales pour les personnes daltoniennes</string>\n    <string name=\"proximity_sensing\">Détection de proximité</string>\"&gt;\n    <string name=\"proximity_sensing_help\">Si coché, la détection de proximité est active pendant les appels.</string>\n    <string name=\"video_size\">Taille trame vidéo</string>\n    <string name=\"video_size_help\">Taille des trames vidéo transmises (largeur x hauteur)</string>\n    <string name=\"video_fps\">Trames vidéo par seconde</string>\n    <string name=\"video_fps_help\">Taux de trame vidéo qui sera offert lors de la poignée de main SDP. Les valeurs valides sont comprises entre 10 et 30.</string>\n    <string name=\"invalid_fps\">Trames invalides par seconde \\'%1$d\\'</string>\n    <string name=\"ringtone\">Sonnerie</string>\n    <string name=\"select_ringtone\">Sélection sonnerie</string>\n    <string name=\"user_domain_or_number\">utilisateur@domaine ou numéro de téléphone</string>\n    <string name=\"favorite\">Favoris</string>\n    <string name=\"favorite_help\">Si coché, le contact est affiché parmi les autres favoris en haut de la liste des contacts.</string>\n    <string name=\"android_contact_help\">Si coché, ce contact est ajouté aux contacts Android.</string>\n    <string name=\"avatar_image\">Image de profil</string>\n    <string name=\"contact_action_question\">Voulez-vous appeler ou envoyer un message à \\'%1$s\\' ?</string>\n    <string name=\"contact_delete_question\">Voulez-vous supprimer le contact \\'%1$s\\' ?</string>\n    <string name=\"anonymous\">Anonyme</string>\n    <string name=\"unknown\">Inconnu(e)</string>\n    <string name=\"no_telephony_provider\">Le compte \\'%1$s\\' n’a pas de fournisseur de téléphonie</string>\n    <string name=\"allow_video\">Accepter l’envoi et la réception de vidéos avec \\'%1$s\\' ?</string>\n    <string name=\"allow_video_send\">Accepter l’envoi de vidéo à \\'%1$s\\' ?</string>\n    <string name=\"allow_video_recv\">Accepter de recevoir une vidéo de \\'%1$s\\' ?</string>\n    <string name=\"call_is_on_hold\">L\\'appel est en attente</string>\n    <string name=\"rec_in_call\">L’enregistrement peut être activé ou désactivé uniquement lorsque l’appel n’est pas connecté</string>\n    <string name=\"call_transfer\">Transfert d\\'appel</string>\n    <string name=\"transfer_destination\">Destination de transfert</string>\n    <string name=\"choose_destination_uri\">Choisir l’URI de destination</string>\n    <string name=\"transfer_failed\">Transfert échoué</string>\n    <string name=\"packets\">Paquets</string>\n    <string name=\"lost\">Perdu(s)</string>\n    <string name=\"start_failed\">Baresip n’a pas réussi à démarrer. Cela peut être dû à une valeur de paramètres invalide. Vérifiez l’adresse d’écoute, le fichier de certificat TLS et le fichier CA TLS. Puis redémarrez Baresip.</string>\n    <string name=\"registering_failed\">L’enregistrement de %1$s a échoué.</string>\n    <string name=\"verify\">Vérifier requête</string>\n    <string name=\"verify_sas\">Voulez-vous vérifier SAS &lt;%1$s&gt; ?</string>\n    <string name=\"transfer_request_query\">Acceptez-vous de transférer cet appel à \\'%1$s\\' ?</string>\n    <string name=\"call_request\">Requête d\\'appel</string>\n    <string name=\"call_request_query\">Acceptez-vous la demande d’appeller \\'%1$s\\' ?</string>\n    <string name=\"redirect_notice\">Redirection automatique vers \\'%1$s\\'\\\\</string>\n    <string name=\"redirect_request\">Requête de redirection</string>\n    <string name=\"redirect_request_query\">Acceptez-vous la redirection d’appel vers \\'%1$s\\' ?</string>\n    <string name=\"call_failed\">Appel échoué</string>\n    <string name=\"call_closed\">Appel est fermé</string>\n    <string name=\"call_not_secure\">Cet appel n’est PAS sécurisé !</string>\n    <string name=\"peer_not_verified\">Cet appel est SÉCURISÉ, mais le pair n’est PAS vérifié !</string>\n    <string name=\"call_is_secure\">Cet appel est SÉCURISÉ et le pair est VÉRIFIÉ ! Voulez-vous dévérifier le pair ?</string>\n    <string name=\"backed_up\">Données de l’application (à l’exclusion des enregistrements) sauvegardées dans le fichier \\'%1$s\\'. Dans la version 9 d\\'Android, le fichier est dans le dossier Download.</string>\n    <string name=\"backup_failed\">Échec à sauvegarder les données de l’application dans le fichier \\'%1$s\\'. Vérifiez Apps → Baresip → Permissions → Stockage.</string>\n    <string name=\"restart_request\">Requête redémarrage</string>\n    <string name=\"restored\">Données de l’application restaurées. Baresip a besoin d\\'être redémarré. Redémarrer maintenant ?</string>\n    <string name=\"restore_unzip_failed\">Échec à restaurer les données de l’application. Android version 14 et ultérieure ne permet pas de restaurer les données qui ont été sauvegardées avant %1$s version %2$s.</string>\n    <string name=\"no_notifications\">Vous ne pouvez pas utiliser cette application sans la permission \\\"Notifications\\\".</string>\n    <string name=\"no_calls\">Baresip a besoin de la permission \\\"Microphone\\\" pour les appels vocaux.</string>\n    <string name=\"no_video_calls\">Accorder à la \\\"Caméra\\\" l’autorisation de passer ou de répondre aux appels vidéo.</string>\n    <string name=\"no_backup\">Vous ne pouvez pas créer de sauvegarde sans la permission \\\"Stockage\\\".</string>\n    <string name=\"no_restore\">Vous ne pouvez pas restaurer la sauvegarde sans l’autorisation \\\"Stockage\\\".</string>\n    <string name=\"no_android_contacts\">Vous ne pouvez pas accéder aux contacts Android sans la permission \\\"Contacts\\\".</string>\n    <string name=\"no_cameras\">Pas de caméras vidéo prises en charge !</string>\n    <string name=\"no_network\">Pas de connexion réseau !</string>\n    <string name=\"no_aec\">Pas d’annulation d’écho acoustique matérielle !</string>\n    <string name=\"audio_focus_denied\">Focus audio refusé !</string>\n    <string name=\"audio_permissions\">Baresip a besoin de l’autorisation \\\"Microphone\\\" pour les appels vocaux, de l’autorisation \\\"Appareils à proximité\\\" pour la détection Bluetooth microphone/haut-parleur, de l’autorisation \\\"Notifications\\\" pour publier des notifications, et dans Android 9, de l’autorisation \\\"Stockage\\\" pour les opérations de sauvegarde/restauration.</string>\n    <string name=\"call_recording_title\">Enregistrement d\\'appel</string>\n    <string name=\"call_recording_tip\">Si activé, les nouveaux appels entrants et sortants seront enregistrés. Les enregistrements peuvent être lus sur la page Détails d’appel</string>\n    <string name=\"microphone_title\">Microphone</string>\n    <string name=\"microphone_tip\">Si activé pendant l’appel, le microphone est coupé.</string>\n    <string name=\"stun_username_help\">Nom d’utilisateur si requis par le serveur STUN/TURN</string>\n    <string name=\"stun_password_help\">Mot de passe si requis par le serveur STUN/TURN</string>\n    <string name=\"dtmf_info\">Requêtes INFO SIP</string>\n    <string name=\"peer\">Pair</string>\n    <string name=\"default_phone_app_help\">Si coché, Baresip est l’application par défaut de téléphone. Ne cochez pas si votre appareil peut également avoir besoin de gérer d’autres appels ou messages que SIP.</string>\n    <string name=\"invalid_opus_bitrate\">Taux de débit Opus invalide</string>\n    <string name=\"invalid_opus_packet_loss\">Pourcentage de perte de paquet Opus invalide</string>\n    <string name=\"audio_delay_help\">Temps (en millisecondes) pour attendre l’audio de l’appelé lorsque l’appel est établi. Réglez sur une valeur plus élevée si vous manquez l’audio de l’appelé au début de l’appel.</string>\n    <string name=\"invalid_audio_delay\">Délai audio invalide \\'%1$s\\'. Les valeurs valides sont comprises entre 100 et 3000.</string>\n    <string name=\"default_call_volume\">Volume d’appel par défaut</string>\n    <string name=\"time\">Heure</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-hr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources></resources>"
  },
  {
    "path": "app/src/main/res/values-in/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-iw/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"about_title\">אודות baresip</string>\n    <string name=\"about_title_plus\">אודות baresip+</string>\n    <string name=\"about_text\"><![CDATA[\n        <h1>סוכן משתמש SIP המבוסס על ספריית <a href=\"https://github.com/baresip/baresip\">baresip</a></h1>\n        <br>\n        <p>Juha Heinanen &lt;jh@tutpro.com&gt;</p>\n        <p>גירסה %1$s</p>\n        <br>\n        <h2>טיפים לשימוש</h2>\n        <ul>\n            <li>בדוק שערכי ברירת מחדל בהגדרות baresip מתאימים לצרכיך\n                (גע בכותרות הפריטים לקבלת עזרה).</li>\n            <li>לאחר מכן, תחת \"חשבונות\", צור חשבון אחד או יותר (שוב, גע בכותרות הפריטים לקבלת עזרה).</li>\n            <li>סטטוס הרישום של חשבון מוצג באמצעות נקודה בצבעים שונים:\n                        ירוק (הרישום הצליח), צהוב (הרישום בתהליך),\n                        אדום (הרישום נכשל), לבן (הרישום לא הופעל).</li>\n            <li>נגיעה בנקודת הסטטוס מובילה ישירות להגדרת החשבון.</li>\n            <li>נגיעה ממושכת על אייקוני סרגל baresip מציגה מידע אודות האייקונים.</li>\n            <li>מחוות החלקה כלפי מטה גורמת לרישום מחדש של החשבון המוצג כרגע.</li>\n            <li>נגיעה ממושכת על החשבון המוצג כרגע מאפשרת או מבטלת את רישום החשבון.</li>\n            <li>מחוות החלקה שמאלה/ימינה מעבירה בין החשבונות.</li>\n            <li>הצד השני בשיחה האחרונה יכול להיבחר מחדש על ידי נגיעה באייקון השיחה כאשר שדה איש הקשר ריק.</li>\n            <li>השותפים בשיחות ובהודעות יכולים להתווסף לאנשי קשר על ידי נגיעה ממושכת.</li>\n            <li>נגיעות ממושכות יכולות לשמש גם כדי להסיר שיחות, צ\\'אטים, הודעות ואנשי קשר.</li>\n            <li>נגיעה/נגיעה ממושכת על אייקון אנשי קשר יכולה לשמש להתקנה/הסרה של תמונת אווטאר.</li>\n            <li>נגיעה ממושכת על קודק אודיו יכולה לשמש להפעלה/השעיית הקודק.</li>\n            <li>ראה את ה־<a href=\"https://github.com/juha-h/baresip-studio/wiki\">ויקי</a> למידע נוסף.</li>\n        </ul>\n        <h2>מדיניות פרטיות</h2>\n            <p>מדיניות פרטיות זמינה <a href=\"https://raw.githubusercontent.com/juha-h/baresip-studio/master/PrivacyPolicy.txt\">כאן</a>.</p>\n        <br>\n        <h2>קוד מקור</h2>\n            <p>קוד מקור זמין ב־<a href=\"https://github.com/juha-h/baresip-studio\">GitHub</a>,\n            שם ניתן גם לדווח על תקלות.</p>\n        <br>\n        <h2>תרגומים</h2>\n            <p>תרגומי שפות מנוהלים דרך פרויקט\n                <a href=\"https://hosted.weblate.org/projects/baresip/\">Weblate</a> של baresip.</p>\n        <br>\n        <h2>רשיונות</h2>\n        <ul>\n            <li><b>BSD-3-Clause</b> חוץ מהבאים:</li>\n            <li><b>Apache 2.0</b> AMR codecs and TLS security</li>\n            <li><b>AGPLv4</b> ZRTP media encryption</li>\n            <li><b>GNU LGPL 2.1</b> Codec2 codec</li>\n            <li><b>Free</b> G.722 codec</li>\n            <li><b>GNU GPLv3</b> G.729 codec</li>\n        </ul>\n        ]]></string>\n    <string name=\"about_text_plus\"><![CDATA[\n        <h1>\n        <h1>סוכן משתמש SIP המבוסס על ספריית <a href=\"https://github.com/baresip/baresip\">baresip</a></h1>\n        <br>\n        <p>Juha Heinanen &lt;jh@tutpro.com&gt;</p>\n        <p>גירסה %1$s</p>\n        <br>\n        <h2>טיפים לשימוש</h2>\n        <ul>\n            <li>בדוק שערכי ברירת מחדל בהגדרות baresip מתאימים לצרכיך\n                (גע בכותרות הפריטים לקבלת עזרה).</li>\n            <li>לאחר מכן, תחת \"חשבונות\", צור חשבון אחד או יותר (שוב, גע בכותרות הפריטים לקבלת עזרה).</li>\n            <li>סטטוס הרישום של חשבון מוצג באמצעות נקודה בצבעים שונים:\n                ירוק (הרישום הצליח), צהוב (הרישום בתהליך),\n                אדום (הרישום נכשל), לבן (הרישום לא הופעל).</li>\n            <li>נגיעה בנקודת הסטטוס מובילה ישירות להגדרת החשבון.</li>\n            <li>נגיעה ממושכת על אייקוני סרגל baresip מציגה מידע אודות האייקונים.</li>\n            <li>מחוות החלקה כלפי מטה גורמת לרישום מחדש של החשבון המוצג כרגע.</li>\n            <li>נגיעה ממושכת על החשבון המוצג כרגע מאפשרת או מבטלת את רישום החשבון.</li>\n            <li>מחוות החלקה שמאלה/ימינה מעבירה בין החשבונות.</li>\n            <li>הצד השני בשיחה האחרונה יכול להיבחר מחדש על ידי נגיעה באייקון השיחה כאשר שדה איש הקשר ריק.</li>\n            <li>השותפים בשיחות ובהודעות יכולים להתווסף לאנשי קשר על ידי נגיעה ממושכת.</li>\n            <li>נגיעות ממושכות יכולות לשמש גם כדי להסיר שיחות, צ\\'אטים, הודעות ואנשי קשר.</li>\n            <li>נגיעה/נגיעה ממושכת על אייקון אנשי קשר יכולה לשמש להתקנה/הסרה של תמונת אווטאר.</li>\n            <li>נגיעה ממושכת על קודק אודיו יכולה לשמש להפעלה/השעיית הקודק.</li>\n            <li>ראה את ה־<a href=\"https://github.com/juha-h/baresip-studio/wiki\">ויקי</a>\n                למידע נוסף.</li>\n        </ul>\n        <h2>תקלות ידועות</h2>\n        <ul>\n            <li>תצוגה עצמית אינה מוצגת כראוי כאשר זרם הווידאו מוגדר כשליחה בלבד.</li>\n        </ul>\n        <h2>מדיניות פרטיות</h2>\n            <p>מדיניות פרטיות זמינה <a href=\"https://raw.githubusercontent.com/juha-h/baresip-studio/master/PrivacyPolicy.txt\">כאן</a>.</p>\n        <br>\n        <h2>קוד מקור</h2>\n            <p>קוד מקור זמין ב־<a href=\"https://github.com/juha-h/baresip-studio\">GitHub</a>,\n            שם ניתן גם לדווח על תקלות.</p>\n        <br>\n        <h2>תרגומים</h2>\n            <p>תרגומי שפות מנוהלים דרך פרויקט\n                <a href=\"https://hosted.weblate.org/projects/baresip/\">Weblate</a> של baresip.</p>\n        <br>\n        <h2>רשיונות</h2>\n        <ul>\n            <li><b>BSD-3-Clause</b> חוץ מהבאים:</li>\n            <li><b>Apache 2.0</b> AMR codecs and TLS security</li>\n            <li><b>AGPLv4</b> ZRTP media encryption</li>\n            <li><b>GNU LGPL 2.1</b> Codec2 codec</li>\n            <li><b>Free</b> G.722 codec</li>\n            <li><b>GNU GPLv3</b> G.729 codec</li>\n            <li><b>GNU GPLv2</b> H.264 and H.265 codecs</li>\n            <li><b>AOMedia</b> AV1 codec</li>\n        </ul>\n        ]]></string>\n    <string name=\"account\">חשבון</string>\n    <string name=\"account_nickname_help\">כינוי (אם יש) המשמש לזיהוי החשבון הזה בתוך אפליקציית baresip.</string>\n    <string name=\"nickname\">כינוי</string>\n    <string name=\"invalid_account_nickname\">כינוי חשבון לא תקין \\'%1$s\\'</string>\n    <string name=\"non_unique_account_nickname\">כינוי \\'%1$s\\' כבר קיים</string>\n    <string name=\"display_name\">שם תצוגה</string>\n    <string name=\"display_name_help\">שם (אם יש) המשמש ב־From URI של בקשות יוצאות.</string>\n    <string name=\"invalid_display_name\">שם תצוגה לא תקין \\'%1$s\\'</string>\n    <string name=\"authentication_username\">שם משתמש לאימות</string>\n    <string name=\"authentication_username_help\">שם משתמש לאימות אם נדרש אימות לבקשות SIP. ברירת המחדל היא שם המשתמש של החשבון.</string>\n    <string name=\"invalid_authentication_username\">שם משתמש לאימות לא תקני \\'%1$s\\'</string>\n    <string name=\"authentication_password\">סיסמה לאימות</string>\n    <string name=\"authentication_password_help\">סיסמת אימות עד 64 תוים. אם שם המשתמש לאימות נמסר, אך הסיסמה לא נמסרה, היא תתבקש כאשר baresip יתחיל.</string>\n    <string name=\"invalid_authentication_password\">סיסמת אימות לא תקינה \\'%1$s\\'</string>\n    <string name=\"outbound_proxies\">פרוקסים יוצאים</string>\n    <string name=\"outbound_proxies_help\">כתובת SIP URI של פרוקסי אחד או שניים שיש להשתמש בהם בעת שליחת בקשות. אם ניתנים שניים, בקשות REGISTER נשלחות לשניהם, ושאר הבקשות נשלחות לזה שמגיב. אם לא הוגדר פרוקסי יוצא (outbound proxy), הבקשות נשלחות על פי חיפוש רשומות DNS מסוג NAPTR/SRV/A של ה־hostpart ב־URI של היעד. אם ה־hostpart של כתובת ה־SIP URI הוא כתובת IPv6, יש לכתוב את הכתובת בתוך סוגריים מרובעים []. \\nדוגמאות: \\n • sip:example.com:5061;transport=tls \\n • sip:[2001:67c:223:777::10];transport=tcp \\n • sip:192.168.43.50:443;transport=wss</string>\n    <string name=\"sip_uri_of_proxy_server\">כתובת SIP URI של שרת פרוקסי</string>\n    <string name=\"sip_uri_of_another_proxy_server\">כתובת SIP URI של שרת פרוקסי נוסף</string>\n    <string name=\"invalid_proxy_server_uri\">כתובת URI של שרת פרוקסי לא תקינה \\'%1$s\\'</string>\n    <string name=\"register\">הרשמה</string>\n    <string name=\"register_help\">אם האפשרות מסומנת, הרישום מופעל ובקשות REGISTER נשלחות במרווח הזמן המוגדר ב\\'מרווחי זמן רישום\\'.</string>\n    <string name=\"reg_int\">מרווחי זמן רישום</string>\n    <string name=\"reg_int_help\">מציין כל כמה זמן (בשניות) baresip שולח בקשות REGISTER. הערכים התקינים הם בין 60 ל־3600.</string>\n    <string name=\"invalid_reg_int\">מרווח זמן רישום לא תקין \\'%1$s\\'</string>\n    <string name=\"check_origin\">בדוק מקור</string>\n    <string name=\"check_origin_help\">אם האפשרות מסומנת, בקשות נכנסות מותרות רק מכתובות IP שאליהן נשלחו בקשות רישום.</string>\n    <string name=\"media_nat\">מעבר NAT למדיה</string>\n    <string name=\"media_nat_help\">בוחר את פרוטוקול מעבר ה־NAT למדיה (אם יש). האפשרויות הזמינות הן STUN ‏(Session Traversal Utilities for NAT, ‏RFC 5389) ו־ICE ‏(Interactive Connectivity Establishment, ‏RFC 5245).</string>\n    <string name=\"stun_server\">שרת STUN/TURN</string>\n    <string name=\"stun_server_help\">כתובת URI של שרת STUN/TURN בפורמט scheme:host[:port][?transport=udp|tcp], כאשר ‎scheme‎ הוא אחד מהבאים: ‎\\'stun\\' ‎, ‎\\'stuns\\'‎, \\'turn\\'‎ או ‎\\'turns\\'‎. שרת ה־STUN המוגדר כברירת מחדל מהיצרן עבור פרוטוקולי STUN ו־ICE הוא \\'stun:stun.l.google.com:19302\\', המצביע על שרת STUN ציבורי של Google. לא קיים שרת TURN המוגדר כברירת מחדל מהיצרן.</string>\n    <string name=\"invalid_stun_server\">כתובת URI לא חוקית של שרת STUN/TURN \\'%1$s\\'</string>\n    <string name=\"media_encryption\">הצפנת מדיה</string>\n    <string name=\"media_encryption_help\">בוחר את פרוטוקול הצפנת המדיה לתעבורה (אם קיים). \\n • ZRTP (מומלץ) משמעותו שמתבצע ניסיון לניהול משא ומתן להצפנת מדיה מקצה לקצה באמצעות ZRTP לאחר שהשיחה כבר נוצרה. \\n • DTLS-SRTPF משמעותו שהפרוטוקול UDP/TLS/RTP/SAVPF מוצע בשיחה יוצאת, ושבמקרה של שיחה נכנסת ייעשה שימוש באחד מהפרוטוקולים הבאים אם יוצעו: RTP/SAVP, RTP/SAVPF, UDP/TLS/RTP/SAVP או UDP/TLS/RTP/SAVPF. \\n • SRTP-MANDF משמעותו שהפרוטוקול RTP/SAVPF מוצע בשיחה יוצאת ונדרש בשיחה נכנסת. \\n • SRTP-MAND משמעותו שהפרוטוקול RTP/SAVP מוצע בשיחה יוצאת ונדרש בשיחה נכנסת. \\n • SRTP משמעותו שהפרוטוקול RTP/AVP מוצע בשיחה יוצאת, ובשיחה נכנסת ייעשה שימוש ב-RTP/SAVP או ב-RTP/SAVPF אם הם מוצעים.</string>\n    <string name=\"rtcp_mux\">ריבוב (Multiplexing) RTCP</string>\n    <string name=\"rtcp_mux_help\">אם האפשרות מסומנת, חבילות RTP ו־RTCP מרובבות (multiplexed) על גבי יציאה אחת (RFC 5761).</string>\n    <string name=\"rel_100\">תגובות זמניות אמינות</string>\n    <string name=\"rel_100_help\">אם האפשרות מסומנת, תצוין תמיכה בתגובות זמניות אמינות (RFC 3262).</string>\n    <string name=\"no_android_contacts\">אינך יכול לגשת לאנשי הקשר של אנדרואיד ללא הרשאת \\\"אנשי קשר\\\".</string>\n    <string name=\"restore_failed\">שחזור נתוני היישום נכשל. ודא שהזנת סיסמה נכונה ושהקובץ הגיבוי שייך ליישום זה. בגרסאות אנדרואיד 9, בדוק גם יישומים → baresip → הרשאות → אחסון ושקובץ \\'%1$s\\' קיים בתיקיית \\\"הורדות\\\".</string>\n    <string name=\"restore_unzip_failed\">שחזור נתוני היישום נכשל. גרסאות אנדרואיד 14 ומעלה אינן מאפשרות שחזור נתונים שגובו לפני %1$s גרסה %2$s.</string>\n    <string name=\"call_is_secure\">השיחה מאובטחת והצד השני מאומת! האם ברצונך לבטל את אימות הצד השני?</string>\n    <string name=\"call_request_query\">האם אתה מאשר בקשת שיחה אל \\'%1$s\\'?</string>\n    <string name=\"start_failed\">הפעלת Baresip נכשלה. ייתכן שהסיבה לכך היא ערך שגוי בהגדרות. בדוק את כתובת ההאזנה (Listen Address), את קובץ תעודת ה־TLS ואת קובץ ה־TLS CA. לאחר מכן הפעל מחדש את Baresip.</string>\n    <string name=\"new_messages\">הודעות חדשות</string>\n    <string name=\"average_rate\">קצב ממוצע: %1$s (Kbits/s)</string>\n    <string name=\"transfer_failed\">ההעברה נכשלה</string>\n    <string name=\"rec_in_call\">ניתן להפעיל או לכבות הקלטה רק כאשר אין שיחה מחוברת</string>\n    <string name=\"no_telephony_provider\">לחשבון \\'%1$s\\' אין ספק טלפוניה</string>\n    <string name=\"confirmation\">אישור</string>\n    <string name=\"cancel\">ביטול</string>\n    <string name=\"contact_action_question\">האם אתה רוצה להתקשר או לשלוח הודעה אל \\'%1$s\\'?</string>\n    <string name=\"favorite\">מועדף</string>\n    <string name=\"config_restart\">עליך להפעיל מחדש את baresip כדי להפעיל את ההגדרות החדשות. להפעיל מחדש כעת?</string>\n    <string name=\"sip_trace_help\">אם האפשרות מסומנת וגם מצב Debug מסומן, הודעות ה־Logcat יכללו גם מעקב אחר בקשות ותשובות SIP. האפשרות תבוטל אוטומטית בעת הפעלת baresip.</string>\n    <string name=\"reset_config\">איפוס לברירות מחדל של היצרן</string>\n    <string name=\"invalid_user_agent\">ערך שדה סוכן-משתמש לא תקין</string>\n    <string name=\"video_fps\">קצב פריימים לשניה</string>\n    <string name=\"dynamic_colors_help\">השתמש בצבעים דינמיים אם הם מופעלים בהגדרות אנדרואיד</string>\n    <string name=\"default_call_volume\">ברירת מחדל עוצמת שמע של שיחה</string>\n    <string name=\"opus_packet_loss\">צפוי אובדן חבילות Opus</string>\n    <string name=\"audio_modules_help\">מקודדי השמע המסופקים על ידי המודולים המסומנים זמינים לשימוש עבור החשבונות.</string>\n    <string name=\"tls_ca_file\">קובץ TLS CA</string>\n    <string name=\"dns_servers_help\">רשימה מופרדת בפסיקים של כתובות שרתי DNS. אם לא מצוין ערך, כתובות שרתי ה־DNS מתקבלות באופן דינמי מהמערכת. כל כתובת DNS היא בתבנית \\'ip:port\\' או \\'ip\\'. אם לא צוין מספר יציאה, ברירת המחדל היא 53. אם ה־ip הוא כתובת IPv6 וגם מצוין מספר יציאה, יש לכתוב את כתובת ה־ip בתוך סוגריים מרובעים []. לדוגמה, הרשימה \\'8.8.8.8:53,[2001:4860:4860::8888]:53\\' מצביעה על כתובות IPv4 ו־IPv6 של שרתי ה־DNS הציבוריים של Google.</string>\n    <string name=\"battery_optimizations\">מיטוב סוללה</string>\n    <string name=\"delete_chats_alert\">האם ברצונך למחוק היסטוריית צ\\'אט של חשבון \\'%1$s\\'?</string>\n    <string name=\"message_failed\">נכשל</string>\n    <string name=\"delete_call_alert\">האם ברצונך למחוק שיחה זו מההיסטוריה?</string>\n    <string name=\"call_answered_elsewhere\">השיחה נענתה במקום אחר</string>\n    <string name=\"calls_duration\">משך זמן</string>\n    <string name=\"calls_add_delete_question\">האם ברצונך להוסיף \\'%1$s\\' לאנשי קשר או למחוק %2$s מהיסטוריית השיחות?</string>\n    <string name=\"call\">שיחה</string>\n    <string name=\"blocked\">חסום</string>\n    <string name=\"missed_call_from\">שיחה שלא נענתה מ</string>\n    <string name=\"invalid_aor\">user@domain[:port][;transport=udp|tcp|tls] \\'%1$s\\' לא תקין</string>\n    <string name=\"numeric_keypad_help\">אם האפשרות מסומנת, לוח מקשים מספרי יוצג כאשר השדה \\\"חייג אל…\\\" נמצא במיקוד.</string>\"\n    <string name=\"voicemain_uri_help\">כתובת SIP URI לבדיקת הודעות דואר קולי. אם השדה נשאר ריק, לא תתבצע הרשמה לקבלת התראות על הודעות דואר קולי (Message Waiting Indications).</string>\n    <string name=\"redirect_mode\">מצב הפניה</string>\n    <string name=\"dtmf_mode\">מצב DTMF</string>\n    <string name=\"invalid_stun_password\">סיסמה לא תקינה \\'%1$s\\'</string>\n    <string name=\"stun_username\">שם משתמש STUN/TURN</string>\n    <string name=\"stun_username_help\">שם משתמש אם נדרש על ידי שרת STUN/TURN</string>\n    <string name=\"invalid_stun_username\">שם משתמש לא תקין \\'%1$s\\'</string>\n    <string name=\"stun_password\">סיסמת STUN/TURN</string>\n    <string name=\"dtmf_info\">בקשות SIP INFO</string>\n    <string name=\"dtmf_auto\">SIP INFO או RTP בתוך הפס (In-band)</string>\n    <string name=\"answer_mode\">מצב מענה</string>\n    <string name=\"answer_mode_help\">בוחר כיצד שיחות נכנסות נענות.</string>\n    <string name=\"redirect_mode_help\">בוחר האם בקשת הפניית שיחה תבוצע באופן אוטומטי, או שתידרש בקשת אישור מהמשתמש.</string>\n    <string name=\"manual\">מדריך למשתמש</string>\n    <string name=\"auto\">אוטומטית</string>\n    <string name=\"block_unknown\">חסום \\\"לא מזוהה\\\"</string>\n    <string name=\"block_unknown_help\">חסום שיחות והודעות ממתקשרים שלא קיימים באנשי הקשר.</string>\n    <string name=\"voicemail_uri\">כתובת URI של דואר קולי</string>\n    <string name=\"country_code\">קידומת מדינה</string>\n    <string name=\"country_code_help\">קוד מדינה בתבנית E.164 עבור חשבון זה. אם חלק המשתמש (userpart) ב־URI של שיחה נכנסת או הודעה נכנסת מכיל מספר טלפון שאינו מתחיל בסימן \\'+\\' ואם חיפוש איש הקשר נכשל, המספר יקבל קידומת של מדינה זו ויתבצע ניסיון נוסף לאיתור איש הקשר. אם מספר הטלפון מתחיל בספרה בודדת \\'0\\', הספרה \\'0\\' תוסר לפני הוספת קוד המדינה.</string>\n    <string name=\"invalid_country_code\">קידומת מדינה לא חוקית \\'%1$s\\'</string>\n    <string name=\"telephony_provider\">ספק טלפוניה</string>\n    <string name=\"telephony_provider_help\">חלק ה־host של כתובת ה־SIP URI שישמש בשיחות למספרי טלפון. ברירת המחדל של היצרן היא הדומיין של החשבון. אם לא צוין ערך, לא ניתן יהיה להשתמש בחשבון זה לביצוע שיחות למספרי טלפון.</string>\n    <string name=\"numeric_keypad\">לוח מקשים מספרי</string>\"\n    <string name=\"invalid_sip_uri_hostpart\">חלק ה־host בכתובת ה־SIP URI אינו חוקי \\'%1$s\\'</string>\n    <string name=\"default_account\">חשבון ברירת מחדל</string>\n    <string name=\"default_account_help\">אם האפשרות מסומנת, חשבון זה נבחר בהפעלת baresip.</string>\n    <string name=\"accounts\">חשבונות</string>\n    <string name=\"new_account\">כתובת SIP URI של חשבון חדש</string>\n    <string name=\"accounts_help\">כתובת SIP URI של חשבון חדש בתבנית ‎&lt;user&gt;@&lt;domain&gt;[:&lt;port&gt;][;transport=udp|tcp|tls]. אם מצוין &lt;‎port&gt; אך לא מצוין פרוטוקול תעבורה, ברירת המחדל של פרוטוקול התעבורה תהיה UDP. אם לא מצוין &lt;‎&lt;port אך מצוין פרוטוקול תעבורה, ברירת המחדל של &lt;‎&lt;port תהיה 5060 או 5061 ‏(TLS). אם אף אחד מהם לא מצוין ולא הוגדר פרוקסי יוצא, שרת הרישום (registrar) של החשבון (אם קיים) ייקבע אך ורק על סמך מידע ה־DNS של הדומיין.</string>\n    <string name=\"account_exists\">חשבון \\'%1$s\\' כבר קיים.</string>\n    <string name=\"account_allocation_failure\">הקצאת חשבון חדש נכשלה.</string>\n    <string name=\"encrypt_password\">הצפנת סיסמה</string>\n    <string name=\"stun_password_help\">סיסמה אם נדרשת על ידי שרת STUN/TURN</string>\n    <string name=\"dtmf_mode_help\">בוחר כיצד יישלחו צלילי DTMF עבור הספרות 0-9 והתווים ‎#, *, ו־A-D.</string>\n    <string name=\"dtmf_inband\">אירועי RTP בתוך־הפס</string>\n    <string name=\"decrypt_password\">פענוח סיסמה</string>\n    <string name=\"delete_account\">האם ברצונך למחוק את החשבון \\'%1$s\\'?</string>\n    <string name=\"is_calling\">מתקשר</string>\n    <string name=\"missed_calls\">שיחות שלא נענו</string>\n    <string name=\"missed_calls_count\">%1$d שיחות שלא נענו</string>\n    <string name=\"transfer_request_to\">בקשת העברת שיחה אל</string>\n    <string name=\"call_auto_rejected\">שיחה נדחתה אוטומטית מ־ %1$s</string>\n    <string name=\"call_blocked\">שיחה חסומה נדחתה אוטומטית מ־ %1$s</string>\n    <string name=\"message_blocked\">הודעה חסומה נדחתה אוטומטית מ־ %1$s</string>\n    <string name=\"blocked_calls\">שיחות חסומות</string>\n    <string name=\"blocked_messages\">הודעות חסומות</string>\n    <string name=\"blocked_delete_alert\">האם ברצונך למחוק בקשות חסומות של \\'%1$s\\'?</string>\n    <string name=\"blocked_contact_question\">האם ברצונך להוסיף את המתקשר \\'%1$s\\' לאנשי הקשר?</string>\n    <string name=\"call_history\">היסטוריית שיחות</string>\n    <string name=\"call_details\">פרטי שיחה</string>\n    <string name=\"calls_calls\">שיחות</string>\n    <string name=\"calls_call\">שיחה</string>\n    <string name=\"peer\">צד (peer)</string>\n    <string name=\"direction\">כיוון</string>\n    <string name=\"time\">זמן</string>\n    <string name=\"calls_delete_question\">האם ברצונך למחוק \\'%1$s\\' %2$s מהיסטוריית השיחות?</string>\n    <string name=\"disable_history\">השבת</string>\n    <string name=\"enable_history\">הפעל</string>\n    <string name=\"delete_history_alert\">האם ברצונך למחוק היסטוריית שיחות של חשבון \\'%1$s\\'?</string>\n    <string name=\"call_answered\">שיחה נענתה</string>\n    <string name=\"call_missed\">שיחה שלא נענתה</string>\n    <string name=\"call_rejected\">שיחה נדחתה</string>\n    <string name=\"playing_recording\">משמיע הקלטה …</string>\n    <string name=\"save_recording\">שמירת הקלטה</string>\n    <string name=\"save_recording_question\">האם ברצונך לשמור הקלטה זו?</string>\n    <string name=\"recording_saved\">ההקלטה נשמרה</string>\n    <string name=\"chat_with\">צ\\'אט עם %1$s</string>\n    <string name=\"new_message\">הודעה חדשה</string>\n    <string name=\"long_message_question\">האם ברצונך למחוק הודעה או להוסיף צד (peer) \\'%1$s\\' לאנשי הקשר?</string>\n    <string name=\"short_message_question\">האם ברצונך למחוק את ההודעה?</string>\n    <string name=\"add_contact\">הוספת איש קשר</string>\n    <string name=\"sending_failed\">שליחת ההודעה נכשלה</string>\n    <string name=\"chats\">היסטוריית צ\\'אט</string>\n    <string name=\"today\">היום</string>\n    <string name=\"you\">אתה</string>\n    <string name=\"new_chat_peer\">צד (peer) צ\\'אט חדש</string>\n    <string name=\"long_chat_question\">האם ברצונך למחוק צ\\'אט עם צד (peer) \\'%1$s\\' או להוסיף צד לאנשי הקשר?</string>\n    <string name=\"short_chat_question\">האם ברצונך למחוק צ\\'אט עם \\'%1$s\\'?</string>\n    <string name=\"audio_codecs\">מקודדי שמע</string>\n    <string name=\"video_codecs\">מקודדי וידאו</string>\n    <string name=\"configuration\">הגדרות</string>\n    <string name=\"start_automatically\">הפעל אוטומטית</string>\n    <string name=\"start_automatically_help\">אם האפשרות מסומנת, baresip יופעל אוטומטית לאחר אתחול המכשיר או לאחר התקנת גרסה חדשה של baresip. ההפעלה תתבצע רק לאחר ביטול נעילת המכשיר.</string>\n    <string name=\"appear_on_top_permission\">הפעלה אוטומטית דורשת הרשאת \\\"הצג מעל יישומים אחרים\\\".</string>\n    <string name=\"battery_optimizations_help\">השבת מיטובי סוללה (מומלץ) אם ברצונך להפחית את הסיכוי שאנדרואיד תגביל את הגישה של baresip לרשת או תעביר את baresip למצב המתנה.</string>\n    <string name=\"default_phone_app\">יישומון הטלפון המוגדר כברירת מחדל</string>\n    <string name=\"dialer_role_not_available\">תפקיד חייגן אינו זמין</string>\n    <string name=\"default_phone_app_help\">אם האפשרות מסומנת, baresip יוגדר כיישומון טלפון ברירת מחדל. אין לסמן אפשרות זו אם המכשיר שלך צריך לטפל גם בשיחות או הודעות שאינן מסוג SIP.</string>\n    <string name=\"listen_address\">כתובת האזנה</string>\n    <string name=\"listen_address_help\">כתובת IP ומספר יציאה בתבנית \\'‎address:port\\' שבהם baresip מאזין לבקשות SIP נכנסות. אם כתובת ה־IP היא כתובת IPv6, יש לכתוב אותה בתוך סוגריים מרובעים []. כתובת IPv4 ‏0.0.0.0 או כתובת IPv6 ‏[::] מאפשרות ל־baresip להאזין בכל הכתובות הזמינות. אם השדה נשאר ריק (ברירת מחדל של היצרן), baresip יאזין ביציאה שרירותית בכל הכתובות הזמינות.</string>\n    <string name=\"invalid_listen_address\">כתובת האזנה לא חוקית</string>\n    <string name=\"address_family\">משפחת כתובות</string>\n    <string name=\"address_family_help\">בוחר באילו כתובות IP ישתמש baresip. אם נבחר IPv4 או IPv6,‏ baresip ישתמש רק בכתובות מסוג IPv4 או IPv6. אם אף אחת מהאפשרויות לא נבחרה, baresip ישתמש גם בכתובות IPv4 וגם בכתובות IPv6.</string>\n    <string name=\"transport_protocols\">פרוטוקולי תעבורה</string>\n    <string name=\"transport_protocols_help\">רשימה מופרדת בפסיקים של פרוטוקולי תעבורה נתמכים עבור בקשות/תשובות SIP. אם השדה ריק, ערך ברירת המחדל הוא \\'udp,tcp,tls,ws,wss\\' הכולל את כל פרוטוקולי התעבורה הנתמכים. יש לציין ברשימה רק את הפרוטוקולים הנחוצים לך. השימוש ב־UDP אינו מומלץ מטעמי אבטחה וסיבות נוספות.</string>\n    <string name=\"invalid_transport_protocols\">רשימת פרוטוקולי תעבורה לא חוקית</string>\n    <string name=\"dns_servers\">שרתי DNS</string>\n    <string name=\"invalid_dns_servers\">שרתי DNS לא חוקיים</string>\n    <string name=\"failed_to_set_dns_servers\">נכשלה הגדרת שרתי DNS</string>\n    <string name=\"tls_certificate_file\">קובץ תעודת TLS</string>\n    <string name=\"tls_certificate_file_help\">אם האפשרות מסומנת, ייטען או נטען כבר קובץ המכיל תעודת TLS ומפתח פרטי עבור מופע זה של baresip. באנדרואיד גירסה 9, ייטען קובץ בשם \\'cert.pem\\' מתיקיית ההורדות. מטעמי אבטחה, מומלץ למחוק את הקובץ לאחר טעינתו.</string>\n    <string name=\"verify_server\">אימות תעודות שרת</string>\n    <string name=\"verify_server_help\">אם האפשרות מסומנת, baresip יאמת את תעודות ה־TLS של סוכן המשתמש (SIP User Agent) ושל שרתי ה־Proxy של SIP כאשר נעשה שימוש בתעבורת TLS.</string>\n    <string name=\"tls_ca_file_help\">אם האפשרות מסומנת, ייטען או נטען כבר קובץ המכיל תעודות TLS של רשויות אישורי תעודות (Certificate Authorities) שאינן כלולות במערכת ההפעלה אנדרואיד. באנדרואיד גירסה 9, ייטען קובץ בשם \\'ca_certs.crt\\' מתיקיית ההורדות.</string>\n    <string name=\"no_read_permission\">אין הרשאת קריאה מאחסון חיצוני</string>\n    <string name=\"audio_settings\">הגדרות שמע</string>\n    <string name=\"speaker_phone\">רמקול</string>\n    <string name=\"speaker_phone_help\">אם האפשרות מסומנת, הרמקול יופעל אוטומטית עם תחילת השיחה.</string>\n    <string name=\"audio_modules_title\">מודולי שמע</string>\n    <string name=\"failed_to_load_module\">טעינת מודול נכשלה.</string>\n    <string name=\"microphone_gain\">רמת הגברת מיקרופון (Gain)</string>\n    <string name=\"microphone_gain_help\">הכפל את עוצמת המיקרופון במספר עשרוני זה. הערך המינימלי הוא ‎1.0 (ברירת מחדל של היצרן) אשר מבטל את הגברת המיקרופון. ערכים גבוהים יותר עלולים לפגוע באיכות השמע.</string>\n    <string name=\"invalid_microphone_gain\">ערך הגברת מיקרופון (Gain) לא חוקי</string>\n    <string name=\"opus_bit_rate\">קצב סיביות של Opus</string>\n    <string name=\"opus_bit_rate_help\">קצב הסיביות המרבי הממוצע המשמש את זרם השמע מסוג Opus. ערכים חוקיים הם בטווח 6000-510000. ברירת המחדל של היצרן היא 28000.</string>\n    <string name=\"opus_packet_loss_help\">אחוז אובדן המנות הצפוי בזרם השמע מסוג Opus, בטווח 0-100. ערך ברירת המחדל של היצרן הוא 1. הערך 0 גם מבטל את מנגנון תיקון השגיאות קדימה (FEC) של Opus.</string>\n    <string name=\"invalid_opus_bitrate\">קצב סיביות של Opus לא חוקי</string>\n    <string name=\"invalid_opus_packet_loss\">אחוז אובדן מנות Opus אינו חוקי</string>\n    <string name=\"audio_delay\">השהיית שמע</string>\n    <string name=\"audio_delay_help\">זמן (באלפיות שנייה) להמתנה לקבלת שמע מהצד הנקרא לאחר יצירת השיחה. יש להגדיר ערך גבוה יותר אם חסר שמע מהצד השני בתחילת השיחה.</string>\n    <string name=\"invalid_audio_delay\">השהיית שמע לא חוקית \\'%1$s\\'. ערכים חוקיים הם בטווח 100 עד 3000.</string>\n    <string name=\"default_call_volume_help\">אם מוגדר ערך, זוהי עוצמת השמע המוגדרת כברירת מחדל לשיחה, בסולם של 1-10.</string>\n    <string name=\"tone_country\">מדינת צלילים</string>\n    <string name=\"tone_country_help\">המדינה שלפיה נקבעים צלילי הצלצול, ההמתנה ותפוס אצל הנמען</string>\n    <string name=\"dark_theme\">ערכת נושא כהה</string>\n    <string name=\"dark_theme_help\">כפה שימוש בערכת נושא כהה</string>\n    <string name=\"dynamic_colors\">צבעים דינמיים</string>\n    <string name=\"colorblind\">עיוורון צבעים</string>\n    <string name=\"colorblind_help\">השתמש בסמלי מצב רישום המותאמים לעיוורי צבעים</string>\n    <string name=\"proximity_sensing\">חישת קירבה</string>\"&gt;\n    <string name=\"proximity_sensing_help\">אם האפשרות מסומנת, חישת קירבה מופעלת בזמן שיחות.</string>\n    <string name=\"video_size\">גודל פריים וידאו</string>\n    <string name=\"video_size_help\">גודל פריימי הווידאו המשודרים (רוחב x גובה)</string>\n    <string name=\"video_fps_help\">קצב פריימים של הווידאו שיוצע במהלך תהליך ה־SDP handshake. ערכים חוקיים הם בטווח 10 עד 30.</string>\n    <string name=\"invalid_fps\">קצב פריימים לשניה לא חוקי \\'%1$d\\'</string>\n    <string name=\"ringtone\">צלצול</string>\n    <string name=\"select_ringtone\">בחר צלצול</string>\n    <string name=\"user_agent\">סוכן משתמש</string>\n    <string name=\"user_agent_help\">ערך מותאם אישית לשדה הכותרת סוכן-משתמש בבקשות/תשובות SIP</string>\n    <string name=\"contacts_help\">בוחר האם להשתמש באנשי קשר של baresip, באנשי הקשר של אנדרואיד, או בשניהם. אם נבחר להשתמש בשניהם וקיים איש קשר עם אותו שם בשתי הרשימות, תינתן עדיפות לאיש הקשר של baresip.</string>\n    <string name=\"both\">גם וגם</string>\n    <string name=\"debug\">ניפוי שגיאות (Debug)</string>\n    <string name=\"debug_help\">אם האפשרות מסומנת, יוצגו הודעות יומן ברמות Debug ו־Info ב־Logcat.</string>\n    <string name=\"sip_trace\">מעקב SIP</string>\n    <string name=\"reset_config_help\">אם האפשרות מסומנת, ההגדרות יאופסו לערכי ברירת המחדל של היצרן.</string>\n    <string name=\"reset_config_alert\">האם אתה בטוח שברצונך לאפס את ההגדרות לערכי ברירת המחדל של היצרן?</string>\n    <string name=\"reset\">איפוס</string>\n    <string name=\"read_cert_error\">קריאת קובץ \\'cert.pem\\' נכשלה.</string>\n    <string name=\"read_ca_certs_error\">קריאת קובץ \\'ca_certs.crt\\' נכשלה.</string>\n    <string name=\"consent_request\">בקשת הסכמה</string>\n    <string name=\"contacts_consent\">אם נבחר להשתמש באנשי הקשר של אנדרואיד, ניתן יהיה להשתמש בהם לצורך שיחות והודעות כהפניות לכתובות SIP ו־tel URI. אפליקציית baresip אינה שומרת את אנשי הקשר של אנדרואיד ואינה משתפת אותם עם אף גורם. כדי לאפשר שימוש באנשי הקשר של אנדרואיד בתוך baresip, נדרש אישור מ־Google עבור השימוש בהם כמתואר כאן וב<a href=\"https://raw.githubusercontent.com/juha-h/baresip-studio/master/PrivacyPolicy.txt\">מדיניות הפרטיות</a> של האפליקציה.</string>\n    <string name=\"new_contact\">איש קשר חדש</string>\n    <string name=\"contact_name\">שם</string>\n    <string name=\"sip_or_tel_uri\">כתובת SIP או tel URI</string>\n    <string name=\"user_domain_or_number\">user@domain או מספר טלפון</string>\n    <string name=\"favorite_help\">אם האפשרות מסומנת, איש הקשר יוצג יחד עם שאר המועדפים בראש רשימת אנשי הקשר.</string>\n    <string name=\"invalid_contact\">שם איש קשר לא חוקי \\'%1$s\\'</string>\n    <string name=\"contact_already_exists\">איש קשר \\'%1$s\\' כבר קיים.</string>\n    <string name=\"android_contact_help\">אם האפשרות מסומנת, איש קשר זה יתווסף לאנשי הקשר של אנדרואיד.</string>\n    <string name=\"avatar_image\">תמונת פרופיל</string>\n    <string name=\"contacts\">אנשי קשר</string>\n    <string name=\"send_message\">שלח הודעה</string>\n    <string name=\"contact_delete_question\">האם ברצונך למחוק את איש הקשר \\'%1$s\\'?</string>\n    <string name=\"search\">חיפוש</string>\n    <string name=\"alert\">התראה</string>\n    <string name=\"info\">מידע</string>\n    <string name=\"notice\">הודעת מערכת</string>\n    <string name=\"stop\">עצור</string>\n    <string name=\"reply\">השב</string>\n    <string name=\"save\">שמור</string>\n    <string name=\"ok\">OK</string>\n    <string name=\"yes\">כן</string>\n    <string name=\"no\">לא</string>\n    <string name=\"accept\">קבל</string>\n    <string name=\"deny\">דחה</string>\n    <string name=\"add\">הוסף</string>\n    <string name=\"delete\">מחק</string>\n    <string name=\"edit\">ערוך</string>\n    <string name=\"status\">מצב</string>\n    <string name=\"error\">שגיאה</string>\n    <string name=\"anonymous\">אנונימי</string>\n    <string name=\"unknown\">לא ידוע</string>\n    <string name=\"invalid_sip_or_tel_uri\">כתובת SIP או tel URI לא חוקית \\'%1$s\\'</string>\n    <string name=\"backup\">גיבוי</string>\n    <string name=\"restore\">שחזור</string>\n    <string name=\"about\">אודות</string>\n    <string name=\"restart\">הפעל מחדש</string>\n    <string name=\"quit\">יציאה</string>\n    <string name=\"outgoing_call_to_dots\">התקשר אל …</string>\n    <string name=\"incoming_call_from_dots\">שיחה מ …</string>\n    <string name=\"diverted_by_dots\">הועבר על ידי …</string>\n    <string name=\"video_call\">שיחת וידאו</string>\n    <string name=\"video_request\">בקשת וידאו</string>\n    <string name=\"allow_video\">לאשר שליחה וקבלה של וידאו עם \\'%1$s\\'?</string>\n    <string name=\"allow_video_send\">לאשר שליחת וידאו אל \\'%1$s\\'?</string>\n    <string name=\"allow_video_recv\">לאשר קבלת וידאו מאת \\'%1$s\\'?</string>\n    <string name=\"call_is_on_hold\">השיחה בהמתנה</string>\n    <string name=\"call_transfer\">העברת שיחה</string>\n    <string name=\"blind\">עיוורת</string>\n    <string name=\"attended\">מלווה</string>\n    <string name=\"transfer_destination\">יעד העברה</string>\n    <string name=\"choose_destination_uri\">בחר URI של יעד</string>\n    <string name=\"transfer\">העברה</string>\n    <string name=\"dtmf\">DTMF</string>\n    <string name=\"call_info\">פרטי שיחה</string>\n    <string name=\"call_info_not_available\">אין מידע זמין</string>\n    <string name=\"duration\">משך זמן: %1$d (שניות)</string>\n    <string name=\"codecs\">מקודדים</string>\n    <string name=\"rate\">קצב נוכחי: %1$s (Kbits/s)</string>\n    <string name=\"packets\">מנות</string>\n    <string name=\"lost\">מנות שאבדו</string>\n    <string name=\"jitter\">ריצוד: %1$s (ms)</string>\n    <string name=\"voicemail_messages\">הודעות דואר קולי</string>\n    <string name=\"you_have\">יש לך</string>\n    <string name=\"one_new_message\">הודעה חדשה אחת</string>\n    <string name=\"one_old_message\">הודעה ישנה אחת</string>\n    <string name=\"old_messages\">הודעות ישנות</string>\n    <string name=\"and\">ו</string>\n    <string name=\"no_messages\">אין הודעות</string>\n    <string name=\"listen\">האזן</string>\n    <string name=\"call_already_active\">כבר יש לך שיחה פעילה.</string>\n    <string name=\"registering_failed\">רישום של %1$s נכשל.</string>\n    <string name=\"verify\">בקשת אימות</string>\n    <string name=\"verify_sas\">האם ברצונך לאמת SAS &lt;%1$s&gt;?</string>\n    <string name=\"transfer_request\">בקשת העברה</string>\n    <string name=\"transfer_request_query\">האם לאשר העברת שיחה זו אל \\'%1$s\\'?</string>\n    <string name=\"call_request\">בקשת שיחה</string>\n    <string name=\"redirect_notice\">הפניה אוטומטית אל \\'%1$s\\'\\\\</string>\n    <string name=\"redirect_request\">בקשת הפניה</string>\n    <string name=\"redirect_request_query\">האם לאשר הפניית שיחה אל \\'%1$s\\'?</string>\n    <string name=\"call_failed\">שיחה נכשלה</string>\n    <string name=\"call_closed\">השיחה נסגרה</string>\n    <string name=\"call_not_secure\">שיחה זו אינה מאובטחת!</string>\n    <string name=\"peer_not_verified\">שיחה זו מאובטחת, אך הצד השני אינו מאומת!</string>\n    <string name=\"unverify\">בטל אימות</string>\n    <string name=\"backed_up\">נתוני היישום (למעט הקלטות) גובו לקובץ \\'%1$s\\'. באנדרואיד גירסה 9, הקובץ נמצא בתיקיית ההורדות.</string>\n    <string name=\"backup_failed\">גיבוי נתוני היישום לקובץ \\'%1$s\\' נכשל. יש לבדוק: יישומים ← baresip ← הרשאות ← אחסון.</string>\n    <string name=\"restart_request\">בקשת הפעלה מחדש</string>\n    <string name=\"restored\">נתוני היישום שוחזרו. יש להפעיל מחדש את baresip. האם להפעיל מחדש כעת?</string>\n    <string name=\"no_notifications\">לא ניתן להשתמש ביישומון זה ללא הרשאת \\\"התראות\\\".</string>\n    <string name=\"no_calls\">baresip זקוק להרשאת \\\"מיקרופון\\\" לצורך ביצוע שיחות קוליות.</string>\n    <string name=\"no_video_calls\">הענק הרשאת \\\"מצלמה\\\" כדי לבצע או לענות לשיחות וידאו.</string>\n    <string name=\"no_backup\">לא ניתן ליצור גיבוי ללא הרשאת \\\"אחסון\\\".</string>\n    <string name=\"no_restore\">לא ניתן לשחזר גיבוי ללא הרשאת \\\"אחסון\\\".</string>\n    <string name=\"no_cameras\">לא נמצאו מצלמות וידאו נתמכות!</string>\n    <string name=\"no_network\">אין חיבור לרשת!</string>\n    <string name=\"no_aec\">אין ביטול הד אקוסטי בחומרה!</string>\n    <string name=\"audio_focus_denied\">נדחתה בקשת מיקוד שמע!</string>\n    <string name=\"permissions_rationale\">הסבר על הרשאות</string>\n    <string name=\"audio_permissions\">baresip זקוק להרשאת \\\"מיקרופון\\\" לצורך ביצוע שיחות קוליות, להרשאת \\\"מכשירים בקרבת מקום\\\" לצורך זיהוי מיקרופון/רמקול בלוטות\\', להרשאת \\\"התראות\\\" לצורך הצגת הודעות מערכת, ובאנדרואיד גירסה 9 גם להרשאת \\\"אחסון\\\" לצורך פעולות גיבוי ושחזור.</string>\n    <string name=\"audio_and_video_permissions\">baresip+ זקוק להרשאת \\\"מיקרופון\\\" לצורך ביצוע שיחות קוליות, להרשאת \\\"מצלמה\\\" לצורך שיחות וידאו, להרשאת \\\"מכשירים בקרבת מקום\\\" לצורך זיהוי מיקרופון/רמקול בלוטות\\', להרשאת \\\"התראות\\\" לצורך הצגת הודעות מערכת, ובאנדרואיד גירסה 9 גם להרשאת \\\"אחסון\\\" לצורך פעולות גיבוי ושחזור.</string>\n    <string name=\"call_recording_title\">הקלטת שיחה</string>\n    <string name=\"call_recording_tip\">אם האפשרות מופעלת, שיחות נכנסות ויוצאות חדשות יוקלטו. ניתן יהיה להאזין להקלטות בדף פרטי השיחה.</string>\n    <string name=\"microphone_title\">מיקרופון</string>\n    <string name=\"microphone_tip\">אם האפשרות מופעלת במהלך שיחה, המיקרופון יושתק.</string>\n    <string name=\"speakerphone_title\">רמקול</string>\n    <string name=\"speakerphone_tip\">אם האפשרות מופעלת, השמע יושמע דרך הרמקול של המכשיר.</string>\n    <string name=\"unique_contact_uri\">URI ייחודי של איש קשר</string>\n    <string name=\"unique_contact_uri_help\">אם מסומן, URI של איש הקשר מובטח להיות ייחודי. יש לסמן זאת אם קיימים יותר מחשבון אחד עם חלק המשתמש זהה ב־SIP URI, אך גם משפר את ההגנה על החשבונות מפני תקיפות.</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-ja-rJP/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"about_text\"><![CDATA[\n        <h1>Baresip library based SIP User Agent</h1>\n        <p>Juha Heinanen &lt;jh@tutpro.com&gt;</p>\n        <p>Version %1$s</p>\n        <h2>Usage Hints</h2>\n        <ul>\n            <li>Check that default values in Settings meet your needs (touch item titles for help).</li>\n            <li>Then in Accounts, create one or more accounts (again touch item titles for help).</li>\n            <li>Registration status of an account is shown with a colored dot: green (registration\n            succeeded, yellow (registration is in progress), red (registration failed), white (registration\n            has not been activated).</li>\n            <li>Touch on the dot leads directly to account configuration.</li>\n            <li>Swipe down gesture causes re-registration of the currently shown account.</li>\n            <li>Peers of calls and messages can be added to contacts by long touches.</li>\n            <li>Long touches can also be used to remove calls, chats, messages, and contacts.</li>\n            <li>Touch/long touch of contact icon can be used to install/remove image avatar.</li>\n            <li>You can re-reselect the previous call party by touching the call icon when Callee is empty.</li>\n        </ul>\n        <h2>Known Issues</h2>\n        <ul>\n            <li>Due to limitations in underlying libraries, baresip does not currently support multiple,\n            concurrently active network interfaces. Active network interface preference order is VPN,\n            Internet, other.</li>\n        </ul>\n        <h2>Source code</h2>\n        Source code is available at <a href=\"https://github.com/juha-h/baresip-studio\">GitHub</a>,\n        where also issues can be reported.\n        <h2>Licenses</h2>\n        <ul>\n            <li><b>BSD-3-Clause</b> except the following:</li>\n            <li><b>Apache 2.0</b> AMR codec and TLS security</li>\n            <li><b>LGPL 2.1</b> G.722 and G.726 codecs</li>\n            <li><b>AGPLv4</b> ZRTP media encryption</li>\n            <li><b>GNU GPLv3</b> G.729 codec</li>\n        </ul>\n        ]]></string>\n    <string name=\"about_text_plus\"><![CDATA[\n        <h1>Baresip library based SIP User Agent with video calls</h1>\n        <p>Juha Heinanen &lt;jh@tutpro.com&gt;</p>\n        <p>Version %1$s</p>\n        <h2>Usage Hints</h2>\n        <ul>\n            <li>Check that default values in Settings meet your needs (touch item titles for help).</li>\n            <li>Then in Accounts, create one or more accounts (again touch item titles for help).</li>\n            <li>Registration status of an account is shown with a colored dot:  green (registration\n            succeeded, yellow (registration is in progress), red (registration failed), white (registration\n            has not been activated).</li>\n            <li>Touch on the dot leads directly to account configuration.</li>\n            <li>Swipe down gesture causes re-registration of the currently shown account.</li>\n            <li>Peers of calls and messages can be added to contacts by long touches.</li>\n            <li>Long touches can also be used to remove calls, chats, messages, and contacts.</li>\n            <li>Touch/long touch of contact icon can be used to install/remove image avatar.</li>\n            <li>You can re-reselect the previous call party by touching the call icon when Callee is empty.</li>\n        </ul>\n        <h2>Known Issues</h2>\n        <ul>\n            <li>Due to limitations in underlying libraries, multiple concurrently active network\n            interfaces are not supported. Active network interface preference order is VPN,\n            Internet, other.</li>\n            <li>In video calls, the device needs to be held in landscape\n            mode rotated 90 degrees left from portrait orientation.</li>\n            <li>Selfview is not properly shown when video stream is sendonly.</li>\n        </ul>\n        <h2>Source code</h2>\n        Source code is available at <a href=\"https://github.com/juha-h/baresip-studio\">GitHub</a>,\n        where also issues can be reported.\n        <h2>Licenses</h2>\n        <ul>\n            <li><b>BSD-3-Clause</b> except the following:</li>\n            <li><b>Apache 2.0</b> AMR codec and TLS security</li>\n            <li><b>LGPL 2.1</b> G.722 and G.726 codecs</li>\n            <li><b>AGPLv4</b> ZRTP media encryption</li>\n            <li><b>GNU GPLv2</b> H.264 codec</li>\n            <li><b>GNU GPLv3</b> G.729 codec</li>\n        </ul>\n        ]]></string>\n    <string name=\"authentication_username_help\">SIPリクエストの認証が必要な場合は、認証ユーザー名を入力して下さい。\n        デフォルト値はアカウントのユーザー名です。\n    </string>\n    <string name=\"authentication_password_help\">認証パスワードは64文字までです。\n        もし、ユーザー名を入力したのにパスワードが入力されていない場合は、baresipの起動時に問い合わせます。\n    </string>\n    <string name=\"outbound_proxies_help\">リクエストを送るときに、1つか2つSIP URIを使う必要がある。\n        2つとも入力された場合、両方にREGISTERリクエストが送られ、他のリクエストは応答する方に送られます。\n        outboundプロキシが与えられない場合、リクエストはcalllee URI hostpartのDNS NAPTR/SRV/Aレコード検索に基づいて送信される。\n        SIP URIのhostpartがIPv6アドレスの場合、アドレスは括弧[]内に記載しなければなりません。\n        \\n記入例:\n        \\n • sip:example.com:5061;transport=tls\n        \\n • sip:[2001:67c:223:777::10];transport=tcp\n        \\n • sip:192.168.43.50:443;transport=wss\n    </string>\n    <string name=\"register_help\">チェックを入れると、登録が有効になり、12 分間隔で REGISTER 要求が送信されます。</string>\n    <string name=\"media_nat_help\">必要であればメディアのNAT探索プロトコルを選択してください。\n        選択肢としては、STUN（Session Traversal Utilities for NAT、RFC 5389）\n        とICE（Interactive Connectivity Establishment、RFC 5245）があります。\n    </string>\n    <string name=\"stun_server_help\">A STUN/TURN Server URI of form scheme:host[:port], where scheme\n        is \\'stun\\' or \\'turn\\'. Factory default STUN Server for STUN and\n        ICE protocols is \\'stun:stun.l.google.com:19302\\' pointing to public Google STUN server.\n        There is no factory default TURN server.\n    </string>\n    <string name=\"media_encryption_help\">Selects media transport encryption protocol (if any).\n        \\n • ZRTP (recommended) means that ZRTP end-to-end media encryption negotiation is tried after\n        the call has been established.\n        \\n • DTLS-SRTPF means that UDP/TLS/RTP/SAVPF is offered in outgoing call and that RTP/SAVP,\n        RTP/SAVPF, UDP/TLS/RTP/SAVP, or UDP/TLS/RTP/SAVPF is used if offered in incoming call.\n        \\n • SRTP-MANDF means that RTP/SAVPF is offered in outgoing call and required in incoming call.\n        \\n • SRTP-MAND means that RTP/SAVP is offered in outgoing call and required in incoming call.\n        \\n • SRTP means that RTP/AVP is offered in outgoing call and that RTP/SAVP or RTP/SAVPF is used\n        if offered in incoming call.\n    </string>\n    <string name=\"voicemain_uri_help\">SIP URI for checking of voicemail messages. If left empty, voicemail\n        messages (Message Waiting Indications) are not subscribed to.\n    </string>\n    <string name=\"default_account_help\">If checked, this account is selected when baresip is started.</string>\n    <string name=\"accounts_help\">Account\\'s port number and transport protocol may be optionally given when a new account is created: username@domain[:port][;transport=udp|tcp|tls]. If port is given and transport protocol is not given, transport protocol defaults to udp. If port is not given and transport protocol is given, port defaults to 5060 or 5061 (TLS). If neither is given and no outbound proxy is specified, account\\'s registrar (if any) is determined solely based on domain\\'s DNS information.</string>\n    <string name=\"calls_add_delete_question\">Do you want to add \\'%1$s\\' to contacts or delete %2$s from call history?\n    </string>\n    <string name=\"long_chat_question\">Do you want to delete chat with peer \\'%1$s\\' or add peer to contacts?</string>\n    <string name=\"listen_address_help\">IP address and port of form \\'address:port\\' at which baresip listens\n        for incoming SIP requests. If IP address is an IPv6 address, it must be written inside\n        brackets []. IPv4 address 0.0.0.0 or IPv6 address [::] makes baresip listen at all\n        available addresses. If left empty (factory default), baresip listens at port 5060 of\n        all available addresses.\n    </string>\n    <string name=\"dns_servers_help\">Comma separated list of addresses of DNS servers. If not given,\n        DNS server addresses are obtained dynamically from the system. Each DNS address is of form\n        \\'ip:port\\' or \\'ip\\'. If port is omitted, it defaults to 53. If ip is an IPv6 address and\n        also port is given, ip must\n        be written inside brackets []. As an example, list \\'8.8.8.8:53,[2001:4860:4860::8888]:53\\'\n        points to IPv4 and IPv6 addresses of public Google DNS servers.\n    </string>\n    <string name=\"tls_certificate_file_help\">If checked, file \\'cert.pem\\' containing TLS certificate and private key of this baresip instance has been or will be loaded from Download directory. For security reasons, delete the file after loading.</string>\n    <string name=\"tls_ca_file_help\">If checked, file \\'ca_certs.crt\\' containing TLS certificates of Certificate Authorities has been or will be loaded from Download directory.</string>\n    <string name=\"audio_modules_help\">Audio codecs provided by the checked modules are\n        available for use by the accounts.\n    </string>\n    <string name=\"opus_bit_rate_help\">Average maximum bit rate used by Opus audio stream.\n        Valid values are 6000-510000. Factory default is 28000.\n    </string>\n    <string name=\"opus_packet_loss_help\">Expected Opus audio stream packet loss percentage,\n        from 0–100. By default 0, turning off Opus Forward Error Correction (FEC).\n    </string>\n    <string name=\"video_size_help\">Size of transmitted video frames (width x height)\n    </string>\n    <string name=\"sip_trace_help\">If checked and if Debug is checked, provides also SIP\n        request and response trace to Logcat. Unchecked automatically at baresip start.\n    </string>\n    <string name=\"config_restart\">You need to restart baresip in order to activate the new settings. Restart now?\n    </string>\n    <string name=\"start_failed\">Baresip failed to start. This may be due to an invalid Settings value.\n        Check Listen Address, TLS Certificate File, and TLS CA File. Then restart baresip.\n    </string>\n    <string name=\"call_is_secure\">This call is SECURE and peer is VERIFIED! Do you want to unverify the peer?</string>\n    <string name=\"backup_failed\">Failed to back up application data to Download folder file\n        \\'%1$s\\'. Check Apps → baresip → Permissions → Storage.\n    </string>\n    <string name=\"restore_failed\">Failed to restore application data from Download folder. Check Apps → baresip → Permissions → Storage and that backup file \\'%1$s\\' exists in the folder and, if so, you gave correct Decrypt Password.</string>\n    <string name=\"restored\">Application data restored. baresip needs to be restarted. Restart now?</string>\n    <string name=\"calls_delete_question\"> %1$s  %2$s を通話履歴から削除してもよいですか?</string>\n    <string name=\"long_message_question\"> %1$s のメッセージと連絡先を削除してもよいですか?</string>\n    <string name=\"invalid_authentication_username\">無効な認証ユーザー名 %1$s です</string>\n    <string name=\"transfer\">転送</string>\n    <string name=\"video_request\">テレビ電話リクエスト</string>\n    <string name=\"video_call\">テレビ電話</string>\n    <string name=\"incoming_call_from_dots\">着信中</string>\n    <string name=\"outgoing_call_to_dots\">発信中</string>\n    <string name=\"quit\">終了</string>\n    <string name=\"restart\">再起動</string>\n    <string name=\"about\">詳細</string>\n    <string name=\"restore\">復元</string>\n    <string name=\"backup\">バックアップ</string>\n    <string name=\"confirmation\">確認</string>\n    <string name=\"error\">エラー</string>\n    <string name=\"status\">状態</string>\n    <string name=\"edit\">編集</string>\n    <string name=\"delete\">削除</string>\n    <string name=\"add\">追加</string>\n    <string name=\"deny\">拒否</string>\n    <string name=\"accept\">承認</string>\n    <string name=\"no\">いいえ</string>\n    <string name=\"ok\">OK</string>\n    <string name=\"cancel\">キャンセル</string>\n    <string name=\"info\">情報</string>\n    <string name=\"send_message\">メッセージ送信</string>\n    <string name=\"contacts\">連絡先</string>\n    <string name=\"contact_already_exists\">連絡先名 %1$s は既に存在します</string>\n    <string name=\"invalid_contact\">無効な連絡先名 %1$s です</string>\n    <string name=\"contact_name\">名前</string>\n    <string name=\"new_contact\">新規連絡先</string>\n    <string name=\"debug\">デバック</string>\n    <string name=\"dns_servers\">DNSサーバー</string>\n    <string name=\"start_automatically\">自動的に開始</string>\n    <string name=\"configuration\">設定</string>\n    <string name=\"you\">あなた</string>\n    <string name=\"today\">今日</string>\n    <string name=\"sending_failed\">メッセージの送信に失敗</string>\n    <string name=\"add_contact\">連絡先の追加</string>\n    <string name=\"new_message\">新規メッセージ</string>\n    <string name=\"delete_history_alert\">アカウント %1$s の通話履歴を削除しますか?</string>\n    <string name=\"enable_history\">有効化</string>\n    <string name=\"disable_history\">無効化</string>\n    <string name=\"calls_call\">通話</string>\n    <string name=\"calls_calls\">通話</string>\n    <string name=\"call\">着信中</string>\n    <string name=\"call_history\">通話履歴</string>\n    <string name=\"transfer_request_to\">転送リクエスト</string>\n    <string name=\"delete_account\">アカウント %1$s を削除しますか?</string>\n    <string name=\"decrypt_password\">復号化パスワード</string>\n    <string name=\"encrypt_password\">暗号化パスワード</string>\n    <string name=\"new_account\">新規アカウント</string>\n    <string name=\"accounts\">アカウント</string>\n    <string name=\"media_encryption\">メディアの暗号化</string>\n    <string name=\"stun_password\">STUN/TURNパスワード</string>\n    <string name=\"invalid_stun_username\">無効な表示名 %1$s です</string>\n    <string name=\"stun_username\">STUN/TURNユーザー名</string>\n    <string name=\"voicemail_messages\">Voicemailメッセージ</string>\n    <string name=\"voicemail_uri\">VoicemailのURI</string>\n    <string name=\"authentication_username\">認証ユーザー名</string>\n    <string name=\"display_name_help\">必要であればFrom URIで使用される名前を入力</string>\n    <string name=\"display_name\">表示名</string>\n    <string name=\"account\">アカウント</string>\n    <string name=\"about_title_plus\">baresip+について</string>\n    <string name=\"about_title\">baresipについて</string>\n    <string name=\"invalid_display_name\">無効な表示名 %1$s です</string>\n    <string name=\"invalid_stun_password\">無効な表示名 %1$s です</string>\n    <string name=\"missed_call_from\">着信失敗</string>\n    <string name=\"yes\">はい</string>\n    <string name=\"authentication_password\">認証パスワード</string>\n    <string name=\"invalid_authentication_password\">無効な認証パスワード %1$s です</string>\n    <string name=\"outbound_proxies\">発信プレフィクス</string>\n    <string name=\"sip_uri_of_proxy_server\">SIP URIのプロキシサーバー</string>\n    <string name=\"sip_uri_of_another_proxy_server\">SIP URIの認証プロキシサーバー</string>\n    <string name=\"invalid_proxy_server_uri\">無効なプロキシサーバー %1$s です</string>\n    <string name=\"register\">登録</string>\n    <string name=\"audio_codecs\">オーディオコーデック</string>\n    <string name=\"video_codecs\">ビデオコーデック</string>\n    <string name=\"media_nat\">メディアのNAT探索</string>\n    <string name=\"stun_server\">STUN/TURNサーバー</string>\n    <string name=\"invalid_stun_server\">無効なSTUN/TURNサーバーのURI %1$s です</string>\n    <string name=\"invalid_aor\">無効なuser@domain[:port][;transport=udp|tcp|tls] %1$s です</string>\n    <string name=\"answer_mode\">応答モード</string>\n    <string name=\"manual\">マニュアル</string>\n    <string name=\"auto\">自動</string>\n    <string name=\"default_account\">既定のアカウント</string>\n    <string name=\"account_exists\">アカウント %1$s は既に存在します</string>\n    <string name=\"account_allocation_failure\">新しいアカウントの割り当てに失敗しました。</string>\n    <string name=\"chat_with\"> %1$s とチャット</string>\n    <string name=\"short_message_question\">メッセージを削除しますか?</string>\n    <string name=\"message_failed\">失敗</string>\n    <string name=\"chats\">チャット履歴</string>\n    <string name=\"new_chat_peer\">新しいチャット相手</string>\n    <string name=\"short_chat_question\"> %1$s とのチャットを削除しますか?</string>\n    <string name=\"delete_chats_alert\">アカウント %1$s とのチャット履歴を削除しますか?</string>\n    <string name=\"listen_address\">受信対象アドレス</string>\n    <string name=\"invalid_listen_address\">受信対象アドレスが無効です</string>\n    <string name=\"invalid_dns_servers\">DNSサーバーが無効です</string>\n    <string name=\"failed_to_set_dns_servers\">DNSサーバーの設定に失敗しました</string>\n    <string name=\"tls_certificate_file\">TLS証明書ファイル</string>\n    <string name=\"tls_ca_file\">TLS CA証明書ファイル</string>\n    <string name=\"audio_modules_title\">オーディオモジュール</string>\n    <string name=\"failed_to_load_module\">モジュールの読み込みに失敗しました</string>\n    <string name=\"opus_bit_rate\">Opusのビットレート</string>\n    <string name=\"opus_packet_loss\">期待されるOpusのパケットロス</string>\n    <string name=\"invalid_opus_bitrate\">Opusのビットレートが無効です</string>\n    <string name=\"invalid_opus_packet_loss\">Opusパケットロス率が無効です</string>\n    <string name=\"default_call_volume\">既定の通話ボリューム</string>\n    <string name=\"dark_theme\">ダークテーマ</string>\n    <string name=\"video_size\">ビデオフレームサイズ</string>\n    <string name=\"sip_trace\">SIP追跡</string>\n    <string name=\"reset_config\">工場出荷時設定にリセット</string>\n    <string name=\"read_cert_error\">ダウンロード・ディレクトリから \\'cert.pem\\' の読み込みに失敗しました。</string>\n    <string name=\"read_ca_certs_error\">ダウンロード・ディレクトリから \\'ca_certs.crt\\' の読み込みに失敗しました。</string>\n    <string name=\"alert\">警告</string>\n    <string name=\"notice\">通知</string>\n    <string name=\"call_transfer\">通話転送</string>\n    <string name=\"transfer_destination\">転送先</string>\n    <string name=\"transfer_failed\">転送失敗</string>\n    <string name=\"dtmf\">DTMF</string>\n    <string name=\"call_info_not_available\">情報はありません</string>\n    <string name=\"call_info\">電話情報</string>\n    <string name=\"duration\">通話時間 %1$d</string>\n    <string name=\"codecs\">コーデック</string>\n    <string name=\"rate\">レート: %1$s</string>\n    <string name=\"you_have\">あなたは</string>\n    <string name=\"one_new_message\">1件のメッセージ</string>\n    <string name=\"new_messages\">新規メッセージ</string>\n    <string name=\"one_old_message\">1件の古いメッセージ</string>\n    <string name=\"old_messages\">古いメッセージ</string>\n    <string name=\"and\">と</string>\n    <string name=\"no_messages\">メッセージはありません</string>\n    <string name=\"call_already_active\">すでに通話中です</string>\n    <string name=\"verify\">確認要求</string>\n    <string name=\"transfer_request\">転送要求</string>\n    <string name=\"call_failed\">呼び出し失敗</string>\n    <string name=\"call_closed\">通話終了</string>\n    <string name=\"call_not_secure\">この通話は安全ではありません!</string>\n    <string name=\"peer_not_verified\">この通話は安全ですが、相手は確認されていません!</string>\n    <string name=\"unverify\">未検証</string>\n    <string name=\"restart_request\">再起動要求</string>\n    <string name=\"no_video_calls\">カメラにビデオ通話の許可を与えます</string>\n    <string name=\"no_cameras\">対応しているビデオカメラがありません</string>\n    <string name=\"stun_username_help\">STUN/TURN サーバーで必要な場合のユーザー名</string>\n    <string name=\"stun_password_help\">STUN/TURN サーバーで必要な場合のパスワード</string>\n    <string name=\"answer_mode_help\">着信した電話にどのように応答するかを選択します。</string>\n    <string name=\"start_automatically_help\">チェックを入れると、デバイスの再起動後に自動的にbaresipが起動します</string>\n    <string name=\"default_call_volume_help\">設定されている場合、デフォルトの通話音声の音量は1～10段階です</string>\n    <string name=\"dark_theme_help\">ダークテーマを強制する</string>\n    <string name=\"debug_help\">チェックを入れると、デバッグおよび情報レベルのログメッセージをLogcatに提供します。</string>\n    <string name=\"reset_config_help\">チェックを入れると、設定は工場出荷時のデフォルト値にリセットされます。</string>\n    <string name=\"contact_action_question\"> %1$s に電話をかけるか、メッセージを送信しますか？</string>\n    <string name=\"contact_delete_question\">連絡先 %1$s を削除しますか?</string>\n    <string name=\"allow_video\"> %1$s でテレビ通話の送受信を許可しますか?</string>\n    <string name=\"allow_video_send\"> %1$s へのテレビ通話を許可しますか?</string>\n    <string name=\"allow_video_recv\"> %1$s からのテレビ通話を許可しますか?</string>\n    <string name=\"listen\">受信</string>\n    <string name=\"registering_failed\"> %1$s への登録失敗</string>\n    <string name=\"verify_sas\">SAS &lt;%1$s&gt; を検証しますか\\?</string>\n    <string name=\"transfer_request_query\"> %1$s への通話転送を許可しますか?</string>\n    <string name=\"backed_up\">アプリケーションデータがダウンロードフォルダ %1$s にバックアップされました</string>\n    <string name=\"no_calls\">マイクへの権限付与がなく電話をかけたり、応答したりすることはできません。</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-ko/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources></resources>"
  },
  {
    "path": "app/src/main/res/values-nb-rNO/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"about_title\">Om</string>\n    <string name=\"account\">Konto</string>\n    <string name=\"display_name\">Visningsnavn</string>\n    <string name=\"display_name_help\">Navn (hvis noe) brukt i skjema-URI for utgående forespørsler.</string>\n    <string name=\"authentication_username\">Brukernavn for identitetsbekreftelse</string>\n    <string name=\"authentication_username_help\">Brukernavn for identitetsbekreftelse av SIP-forespørsler er påkrevd. Forvalgt verdi er kontoens brukernavn.</string>\n    <string name=\"authentication_password\">Passord for identitetsbekreftelse</string>\n    <string name=\"authentication_password_help\">Passord for identitetsbekreftelse opptil 64 tegn. Hvis det angis, og passord ikke angis, vil det bli anmodet inntastet når baresip startes.</string>\n    <string name=\"outbound_proxies\">Utgående mellomtjenere</string>\n    <string name=\"audio_codecs\">Lyd-kodeker</string>\n    <string name=\"stun_server\">STUN-tjener</string>\n    <string name=\"media_encryption\">Mediakryptering</string>\n    <string name=\"default_account\">Forvalgt konto</string>\n    <string name=\"accounts\">Kontoer</string>\n    <string name=\"invalid_aor\">Ugyldig bruker@domene[:port][;transport=udp|tcp|tls] \\\"%1$s\\\"</string>\n    <string name=\"account_exists\">Kontoen \\\"%1$s\\\" finnes allerede.</string>\n    <string name=\"account_allocation_failure\">Klarte ikke å tildele ny konto.</string>\n    <string name=\"encrypt_password\">Krypter passord</string>\n    <string name=\"decrypt_password\">Dekrypter passord</string>\n    <string name=\"delete_account\">Ønsker du å slette kontoen \\\"%1$s\\\"\\?</string>\n    <string name=\"call_history\">Anropslogg</string>\n    <string name=\"call\">Ring</string>\n    <string name=\"calls_calls\">samtaler</string>\n    <string name=\"calls_call\">samtale</string>\n    <string name=\"calls_add_delete_question\">Ønsker du å legge til «%1$s» i kontaktlisten, eller slette «%2$s» fra anropsloggen\\?</string>\n    <string name=\"disable_history\">Skru av</string>\n    <string name=\"enable_history\">Skru på</string>\n    <string name=\"chat_with\">Sludre med %1$s</string>\n    <string name=\"new_message\">Ny melding</string>\n    <string name=\"long_message_question\">Ønsker du å slette meldingen eller legge til likemannen \\\"%1$s\\\" til i kontaktlisten\\?</string>\n    <string name=\"short_message_question\">Ønsker du å slette meldingen\\?</string>\n    <string name=\"add_contact\">Legg til kontakt</string>\n    <string name=\"sending_failed\">Meldingsforsendelse mislyktes</string>\n    <string name=\"chats\">Sludrehistorikk</string>\n    <string name=\"today\">I dag</string>\n    <string name=\"you\">Deg</string>\n    <string name=\"new_chat_peer\">Ny sludringslikemann</string>\n    <string name=\"long_chat_question\">Ønsker du å slette samtalen med likemannen \\\"%1$s\\\" eller legge vedkommende til i kontaktlisten\\?</string>\n    <string name=\"configuration\">Oppsett</string>\n    <string name=\"start_automatically\">Start automatisk</string>\n    <string name=\"listen_address\">Lytteadresse</string>\n    <string name=\"dns_servers\">DNS-tjenere</string>\n    <string name=\"opus_bit_rate\">Opus-bitrate</string>\n    <string name=\"reset_config\">Tilbakestill til fabrikkforvalg</string>\n    <string name=\"new_contact\">Ny kontakt</string>\n    <string name=\"contact_name\">Navn</string>\n    <string name=\"invalid_contact\">\\\"%1$s\\\" er et ugydlig kontaktnavn</string>\n    <string name=\"contacts\">Kontakter</string>\n    <string name=\"send_message\">Send melding</string>\n    <string name=\"contact_delete_question\">Ønsker du å slette kontakten \\\"%1$s\\\"\\?</string>\n    <string name=\"alert\">Varsel</string>\n    <string name=\"info\">Info</string>\n    <string name=\"notice\">Merknad</string>\n    <string name=\"cancel\">Avbryt</string>\n    <string name=\"ok\">OK</string>\n    <string name=\"yes\">Ja</string>\n    <string name=\"no\">Nei</string>\n    <string name=\"accept\">Godta</string>\n    <string name=\"deny\">Nekt</string>\n    <string name=\"add\">Legg til</string>\n    <string name=\"delete\">Slett</string>\n    <string name=\"edit\">Rediger</string>\n    <string name=\"status\">Status</string>\n    <string name=\"error\">Feil</string>\n    <string name=\"about\">Om</string>\n    <string name=\"quit\">Avslutt</string>\n    <string name=\"outgoing_call_to_dots\">Utgående anrop til …</string>\n    <string name=\"incoming_call_from_dots\">Innkommende anrop fra …</string>\n    <string name=\"duration\">Varighet %1$d (sek)</string>\n    <string name=\"codecs\">Kodeker</string>\n    <string name=\"you_have\">Du har</string>\n    <string name=\"one_new_message\">én ny melding</string>\n    <string name=\"new_messages\">nye meldinger</string>\n    <string name=\"one_old_message\">én gammel melding</string>\n    <string name=\"old_messages\">gamle meldinger</string>\n    <string name=\"and\">og</string>\n    <string name=\"no_messages\">Du har ingen meldinger</string>\n    <string name=\"listen\">Lytt</string>\n    <string name=\"verify\">Bekreft</string>\n    <string name=\"call_not_secure\">Denne samtalen er IKKE sikker!</string>\n    <string name=\"peer_not_verified\">Denne samtalen er SIKKER, men likemannen er IKKE bekreftet!</string>\n    <string name=\"call_is_secure\">Denne samtalen er SIKKER, og likemannen er BEKREFTET! Ønsker du å avkrefte likemannen\\?</string>\n    <string name=\"unverify\">Avkreft</string>\n    <string name=\"about_text\">&lt;h1&gt;Baresip-bibliotek basert på SIP-brukeragent&lt;/h1&gt; &lt;p&gt;Juha Heinanen &lt;jh@tutpro.com&gt;&lt;/p&gt; &lt;p&gt;Version %1$s&lt;/p&gt; &lt;h2&gt;Brukshint&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;Sjekk at oppsettets forvalgte verdier passer for deg (klikk på elementtitlene for hjelp).&lt;/li&gt;&lt;li&gt;Registreringstatus for konto vises med en fargelagt prikk: Grønn (vellykket registrering, gul (registrering underveis), rød (mislykket registrering), hvit (registrering har ikke blitt aktivert).&lt;/li&gt; &lt;li&gt;Opprett så én eller flere kontoer (igjen, klikk på elementtitlene for hjelp).&lt;/li&gt; &lt;li&gt;Likemenn i samtaler og meldinger kan legges til ved lange trykk.&lt;/li&gt; &lt;li&gt;Lange trykk kan også brukes til å fjerne samtaler, sludringer, meldinger, og kontakter.&lt;/li&gt; &lt;li&gt;Trykk/langt trykk på kontaktikon kan brukes for å installere/fjerne billedavatar.&lt;/li&gt; &lt;li&gt;Klikk når ringeren ikke er angitt for å ringe opp forrige samtalepartner.&lt;/li&gt; &lt;li&gt;Hvis du ikke kan høre motparten, prøv å øke media-lydstyrken.&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;Kjente problemer&lt;/h2&gt; &lt;ul&gt; &lt;li&gt;På grunn av en biblioteksbegrensning, støtter ikke baresip flerfoldige samtidige nettverksgrensesnitt.&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;Kildekode&lt;/h2&gt; Tilgjengelig på &lt;a href=https://github.com/juha-h/baresip-studio&gt;GitHub&lt;/a&gt;, der feil også kan innrapporteres.</string>\n    <string name=\"outbound_proxies_help\">SIP URI-til én eller to mellomtjenere som må brukes ved forsendelse av forespørsler. Hvis to angis, vil REGISTER-forespørsler sendes til begge, og andre forespørsler sendes til den som svarer. Hvis ingen utgående mellomtjener angis, blir forespørsler sent basert på DNS NAPTR/SRV/A-oppføringsoppslag fra ringerens URI-vertspart. Hvis vertsdelen av en SIP URI er en IPv6-adresse, må adressen skrives i hakeparenteser [].\n\\nEksempler:\n\\n • sip:foo.com:5060;transport=tls\n\\n • sip:[2001:67c:223:777::10]:5060;transport=tcp</string>\n    <string name=\"sip_uri_of_proxy_server\">SIP URI for mellomtjeneren</string>\n    <string name=\"sip_uri_of_another_proxy_server\">SIP URI til en annen mellomtjener</string>\n    <string name=\"register\">Registrer</string>\n    <string name=\"register_help\">Skrur på registrering og REGISTER-forespørsler blir sendt hvert tolvte minutt.</string>\n    <string name=\"media_nat\">Media NAT-gjennomgang</string>\n    <string name=\"media_nat_help\">Velger media NAT-gjennomgangsprotokoll (hvis noen). Mulige valg er STUN (øktgjennomgangsverktøy for NAT, RFC 5389) og ICE (interaktivt tilkoblingstilknytning, RFC 5245).</string>\n    <string name=\"stun_server_help\">En STUN-tjener i utførelsen host[:port]. Forvalgt fabrikkverdi er \\'stun.l.google.com:19302\\', som peker til offentlig Google STUN-tjener. Brukernavn og passord støttes foreløpig ikke.</string>\n    <string name=\"media_encryption_help\">Velger krypteringsprotokoll for mediatransport (hvis noen).\n\\n • ZRTP (anbefalt) betyr at ZRTP ende-til-ende -forhandling av mediakryptering forsøkes etter at samtalen har blitt opprettet.\n\\n • DTLS-SRTPF betyr at UDP/TLS/RTP/SAVPF tilbys i utgående samtale, og at RTP/SAVP, RTP/SAVPF, UDP/TLS/RTP/SAVP, eller UDP/TLS/RTP/SAVPF brukes hvis tilbudt i innkomende anrop.\n\\n • SRTP-MANDF betyr at RTP/SAVPF tilbys i utgående anrop, og krever et innkommende anrop.\n\\n • SRTP-MAND betyr at RTP/SAVP tilbys i utgående anrop, og kreves i innkommende anrop.\n\\n • SRTP betyr at RTP/AVP tilbys i utgående anrop, og at RTP/SAVP eller RTP/SAVPF brukes hvis tilbudt i innkommende anrop.</string>\n    <string name=\"voicemail_uri\">Telefonsvarer-URI</string>\n    <string name=\"voicemain_uri_help\">SIP-URI for sjekk av telefonsvarermeldinger. Hvis levnet tom, vil telefonsvarermeldinger (melding venter-anvisninger) ikke bli levert.</string>\n    <string name=\"default_account_help\">Velger denne kontoen når baresip startes.</string>\n    <string name=\"transfer_request\">Samtaleoverføringsforespørsel til</string>\n    <string name=\"calls_delete_question\">Ønsker du å slette \\\"%1$s\\\" %2$s fra anropshistorikk\\?</string>\n    <string name=\"message_failed\">Mislyktes</string>\n    <string name=\"short_chat_question\">Ønsker du å slette sludringen med \\\"%1$s\\\"\\?</string>\n    <string name=\"start_automatically_help\">Starter baresip automatisk sammen med enheten.</string>\n    <string name=\"listen_address_help\">IP-adresse og port i utførelsen \\\"adresse:port\\\" som baresip lytter til for innkommende SIP-forespørsler. Hvis en IP-adresse er en IPv6-adresse, må den skrives i klammeparenteser []. IPv4-adresse 0.0.0.0 eller IPv6-adresse [::] får baresip til å lytte til alle tilgjengelige adresser. Hvis levnet tom (fabrikksforvalg), vil baresip lytte til port 5060 for alle tilgjengelige adresser.</string>\n    <string name=\"dns_servers_help\">Kommainndelt liste over adresser til DNS-tjenere. Hvis ikke angitt, vil DNS-tjeneradresser hentes dynamisk fra systemet. Hver DNS-adresse er utførelsen \\\"ip:port\\\" eller \\\"ip\\\". Hvis porten utelates, brukes forvalget 53. Hvis IP-en er en IPv6-adresse, og port også angis, må IP-en skrives i hakeparenteser []. Som etksempel, list \\'8.8.8.8:53,[2001:4860:4860::8888]:53\\' peker til en IPv4-adresser og IPv6-adresser for offentlige Google DNS-tjenere.</string>\n    <string name=\"failed_to_load_module\">Klarte ikke å laste inn modul.</string>\n    <string name=\"opus_bit_rate_help\">Gjennomsnittlig maksimal bitrate ved bruk av Opus-lydstrøm. Gyldig verdi er 6000-510000. Fabrikksforvalg er 28000.</string>\n    <string name=\"opus_packet_loss\">Forventet Opus-pakketap</string>\n    <string name=\"opus_packet_loss_help\">Forventet prosentvis pakketap for Opus-lydstrøm. Gyldige verdier fra 0–100. Fabrikksforvalg er 0, som skrur av Opus-fremtidsfeilkorrigering (FEC).</string>\n    <string name=\"default_call_volume\">Forvalgt samtalelydstyrke</string>\n    <string name=\"default_call_volume_help\">Setter samtalelydstyrken på en skala fra 1-10.</string>\n    <string name=\"debug\">Feilretting</string>\n    <string name=\"debug_help\">Gjør feilrettings- og infonivå-loggmeldinger tilgjengelige i Logcat.</string>\n    <string name=\"reset_config_help\">Tilbakestiller oppsettet til fabrikkforvalgte verdier.</string>\n    <string name=\"contact_already_exists\">Kontakten \\\"%1$s\\\" finnes allerede.</string>\n    <string name=\"contact_action_question\">Ønsker du å ringe eller sende melding til \\\"%1$s\\\"\\?</string>\n    <string name=\"dtmf\">Tonesignalering</string>\n    <string name=\"call_info\">Samtaleinfo</string>\n    <string name=\"rate\">Takt: %1$s</string>\n    <string name=\"voicemail_messages\">Telefonsvarermeldinger</string>\n    <string name=\"call_already_active\">Du har allerede et aktivt anrop.</string>\n    <string name=\"start_failed\">Baresip kunne ikke starte. Dette kan ha oppstått på grunn av en ugyldig oppstartsverdi. Sjekk lytteadressen, TLS-sertifikatsfilen, og TLS CA-filen. Start så programmet på ny.</string>\n    <string name=\"registering_failed\">Registrering av %1$s mislyktes.</string>\n    <string name=\"verify_sas\">Ønsker du å bekrefte SAS &lt;%1$s&gt;\\?</string>\n    <string name=\"call_failed\">Anrop mislyktes.</string>\n    <string name=\"call_closed\">Samtale lukket.</string>\n    <string name=\"tls_certificate_file\">TLS-sertifikatsfil</string>\n    <string name=\"tls_certificate_file_help\">Hvis valgt vil eller har filen \\\"cert.pem\\\" som inneholder TLS-sertifikatet og den private nøkkelen tilhørende denne baresip-instansen blitt lastet inn fra Nedlastinger. Av sikkerhetshensyn må du slette filen etter innlasting.</string>\n    <string name=\"tls_ca_file\">TLS-CA-fil</string>\n    <string name=\"tls_ca_file_help\">Hvis valgt vil filen \\\"ca_certs.crt\\\" inneholdende TLS-sertifikatene tilhørende sertifikatsmyndighetene ha blitt lastet inn fra Nedlastinger.</string>\n    <string name=\"read_cert_error\">Klarte ikke å lese filen \\\"cert.pem\\\" fra Nedlastinger.</string>\n    <string name=\"read_ca_certs_error\">Klarte ikke å lese filen \\\"ca_certs.crt\\\" fra Nedlastinger.</string>\n    <string name=\"invalid_listen_address\">Ugyldig lyttadresse</string>\n    <string name=\"invalid_dns_servers\">Ugyldige DNS-tjenere</string>\n    <string name=\"failed_to_set_dns_servers\">Klarte ikke å sette DNS-tjenere</string>\n    <string name=\"invalid_opus_bitrate\">Ugyldig Opus-bitrate</string>\n    <string name=\"invalid_opus_packet_loss\">Ugyldig Opus-pakketapsprosent</string>\n    <string name=\"answer_mode\">Svarsmodus</string>\n    <string name=\"answer_mode_help\">Angir hvordan innkommende anrop besvares.</string>\n    <string name=\"manual\">Manuell</string>\n    <string name=\"auto\">Automatisk</string>\n    <string name=\"restart\">Programomstart</string>\n    <string name=\"delete_history_alert\">Ønsker du virkelig å slette samtalehistorikk for kontoen «%1$s»\\?</string>\n    <string name=\"delete_chats_alert\">Ønsker du å slette sludrehistorikken for kontoen «%1$s»\\?</string>\n    <string name=\"config_restart\">Ønsker du å starte baresip på ny for å aktivere de nye innstillingene nå\\?</string>\n    <string name=\"backup\">Sikkerhetskopier</string>\n    <string name=\"restore\">Gjenopprett</string>\n    <string name=\"backed_up\">Programdata sikkerhetskopiert til nedlastingsmappen som \\'%1$s\\'.</string>\n    <string name=\"backup_failed\">Klarte ikke å sikkerhetskopiere programdata til nedlastingsmappe som \\'%1$s\\'. Sjekk \\\"Programmer → baresip → Tilganger → Lagring.</string>\n    <string name=\"restored\">Programdata gjenopprettet. Start baresip på ny nå\\?</string>\n    <string name=\"restore_failed\">Klarte ikke å sikkerhetskopiere programdata til nedlastingsmappe. Sjekk \\\"Programmer → baresip → Tilganger → Lagring og at sikkerhetskopifilen \\'%1$s\\' finnes i mappen, og om det er tilfelle, at du har oppgitt rett dekrypteringspassord.</string>\n    <string name=\"audio_modules_title\">Lydmoduler</string>\n    <string name=\"audio_modules_help\">Lydkodeker tilbudt av de avkryssede modulene er tilgjengelig for bruk av kontoene.</string>\n    <string name=\"new_account\">Ny konto</string>\n    <string name=\"no_calls\">Du kan ikke utføre eller besvare anrop uten mikrofontilgang.</string>\n    <string name=\"accounts_help\">Det er mulig å sette kontoportnummer og transportprotokoll som username@domain[:port][;transport=udp|tcp|tls] ved opprettelse av en ny konto. UDP brukes hvis kun port angis. Port 5060 eller 5061 (TLS) brukes hvis kun transportprotokoll angis. Kontoens registrar (hvis noen) angis kun basert på info fra domenets DNS hvis hverken port, transportprotokoll, og heller ikke utgående mellomtjener angis.</string>\n    <string name=\"invalid_proxy_server_uri\">Ugyldig mellomtjener-URI \\\"%1$s\\\"</string>\n    <string name=\"invalid_authentication_password\">Ugyldig identitetsbekreftelsespassord \\\"%1$s\\\"</string>\n    <string name=\"invalid_authentication_username\">Ugylidg identitetsbekreftelsesbrukernavn \\\"%1$s\\\"</string>\n    <string name=\"invalid_display_name\">Ugyldig visningsnavn \\\"%1$s\\\"</string>\n    <string name=\"invalid_stun_server\">Ugyldig STUN-tjener \\\"%1$s\\\"</string>\n    <string name=\"call_info_not_available\">Ingen info tilgjengelig</string>\n    <string name=\"no_cameras\">Du har ikke noen støttede videokameraer.</string>\n    <string name=\"restart_request\">Start forespørsel på ny</string>\n    <string name=\"video_request\">Videoforespørsel</string>\n    <string name=\"video_call\">Videosamtale</string>\n    <string name=\"confirmation\">Bekreftelse</string>\n    <string name=\"invalid_stun_password\">«%1$s» er et ugyldig passord</string>\n    <string name=\"stun_password_help\">Passord hvis påkrevd av STUN/TURN-tjener</string>\n    <string name=\"stun_password\">STUN/TURN-passord</string>\n    <string name=\"invalid_stun_username\">«%1$s» er et ugyldig brukernavn</string>\n    <string name=\"stun_username_help\">Brukernavn hvis påkrevd av STUN/TURN-tjener</string>\n    <string name=\"stun_username\">STUN/TURN-brukernavn</string>\n    <string name=\"video_codecs\">Videokodek</string>\n    <string name=\"transfer_destination\">Videresend til</string>\n    <string name=\"no_video_calls\">Du kan ikke utføre eller svare på videoanrop uten kamera-tilgang.</string>\n    <string name=\"transfer_request_query\">Ønsker du å sette over denne samtalen til «%1$s»\\?</string>\n    <string name=\"transfer_failed\">Kunne ikke videresende anrop</string>\n    <string name=\"transfer\">Sett over</string>\n    <string name=\"call_transfer\">Videresending av anrop</string>\n    <string name=\"allow_video\">Godta videosamtale med «%1$s»\\?</string>\n    <string name=\"about_title_plus\">Om baresip+</string>\n    <string name=\"allow_video_recv\">Godta mottak av video fra «%1$s»\\?</string>\n    <string name=\"allow_video_send\">Godta forsendelse av video til «%1$s»\\?</string>\n    <string name=\"video_size\">Videorammestørrelse</string>\n    <string name=\"dark_theme\">Mørk drakt</string>\n    <string name=\"missed_call_from\">Tapt anrop fra</string>\n    <string name=\"time\">Tid</string>\n    <string name=\"calls_duration\">Varighet</string>\n    <string name=\"audio_settings\">Lydinnstillinger</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-night/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#84C1E6</color>\n    <color name=\"colorWhite\">#ffffff</color>\n    <color name=\"colorTrafficGreen\">#2E7D32</color>\n    <color name=\"colorTrafficYellow\">#F9A825</color>\n    <color name=\"colorTrafficRed\">#C62828</color>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-pl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources></resources>"
  },
  {
    "path": "app/src/main/res/values-pt/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"restored\">Os dados da aplicação foram restaurados. O baresip precisa ser reiniciado. Reiniciar agora\\?</string>\n    <string name=\"short_chat_question\">Quer apagar o chat com \\'%1$s\\'\\?</string>\n    <string name=\"start_automatically_help\">Se marcado, baresip iniciar automaticamente depois que o aparelho reiniciar.</string>\n    <string name=\"verify_sas\">Quer verificar SAS &lt;%1$s&gt;\\?</string>\n    <string name=\"tls_ca_file_help\">Se estiver marcada, foi ou será carregado um ficheiro que contém certificados TLS de tais autoridades de certificação que não estão incluídas no sistema operativo Android. Na versão 9 do Android, um ficheiro chamado “ca_certs.crt” é carregado da pasta Download.</string>\n    <string name=\"debug_help\">Se selecionado, disponibiliza mensagens de depuração no registo log e informações para o Logcat.</string>\n    <string name=\"opus_packet_loss_help\">Esperada percentagem de perda de pacotes do fluxo de áudio do Opus, de 0-100. Por padrão o valor é 1. O valor 0 desativa o Opus Forward Error Correction (FEC).</string>\n    <string name=\"outbound_proxies_help\">URI SIP de um ou dois proxies que precisam ser usados para enviar solicitações. Se dois forem informados, requisições de REGISTO serão enviados para ambos e as outras requisições serão enviadas para aquele que respondeu. Se nenhum proxy foi informado, requisições serão enviadas baseadas em DNS NAPTR/SRV/A pesquisa de registo da URI do host do destinatário. Se o host da URI SIP for um endereço IPv6, o endereço precisa ser escrito dentro de colchetes [].\n\\nExemplos:\n\\n • sip:example.com:5061;transport=tls\n\\n • sip:[2001:67c:223:777::10];transport=tcp\n\\n • sip:192.168.43.50:443;transport=wss</string>\n    <string name=\"send_message\">Enviar Mensagem</string>\n    <string name=\"account_allocation_failure\">Falha ao alocar nova conta.</string>\n    <string name=\"call_info_not_available\">Nenhuma informação disponível</string>\n    <string name=\"deny\">Recusar</string>\n    <string name=\"delete_history_alert\">Quer apagar o histórico de chamadas da conta \\'%1$s\\'\\?</string>\n    <string name=\"add_contact\">Adicionar Contato</string>\n    <string name=\"stun_server_help\">Um URI do servidor STUN/TURN na forma de scheme:host[:porta][\\?transport=udp|tcp]. Onde o scheme é \\'stun\\', \\'stuns\\', \\'turn\\', ou \\'turns\\'. O Servidor predefinido de fábrica para o STUN e os protocolos ICE são \\'stun:stun.l.google.com:19302\\' apontando para o servidor STUN público do Google. Não há qualquer servidor STUN predefinido de fábrica.</string>\n    <string name=\"start_failed\">Baresip falhou ao iniciar. Isso pode ser devido a um valor inválido das Configurações. Verifique Endereço de Escuta, Ficheiro de Certificado TLS e Ficheiro CA TLS. Em seguida, reinicie o baresip.</string>\n    <string name=\"call_already_active\">Já tem uma chamada ativa.</string>\n    <string name=\"calls_call\">chamada</string>\n    <string name=\"accounts_help\">SIP URI da nova conta no formato: &lt;utilizador&gt;@@&lt;domínio&gt;[:&lt;porta&gt;][;transport=udp|tcp|tls]. Se a &lt;porta&gt; for informada e o protocolo de transporte não, o padrão será UDP. Se a &lt;porta&gt; não for informada e o protocolo de transporte for, a &lt;porta&gt; padrão é 5060 ou 5061 (TLS). Se nenhum dos dois ou nenhum proxy for informado, o utilizador da conta (caso haja) é determinado exclusivamente com base nas informações do DNS do domínio.</string>\n    <string name=\"stun_password\">Palavra-passe STUN/TURN</string>\n    <string name=\"reset_config\">Redefinir os Padrões de Fábrica</string>\n    <string name=\"allow_video\">Aceitar o envio e o recebimento de vídeo com \\'%1$s\\'\\?</string>\n    <string name=\"incoming_call_from_dots\">Chamada de …</string>\n    <string name=\"add\">Adicionar</string>\n    <string name=\"alert\">Alerta</string>\n    <string name=\"one_new_message\">uma nova mensagem</string>\n    <string name=\"contact_delete_question\">Quer apagar o contato \\'%1$s\\'\\?</string>\n    <string name=\"default_call_volume\">Volume de Chamadas prefinido</string>\n    <string name=\"encrypt_password\">Criptografar Palavra-passe</string>\n    <string name=\"new_account\">SIP URI da nova conta</string>\n    <string name=\"contact_name\">Nome</string>\n    <string name=\"contact_action_question\">Quer ligar ou enviar mensagem para \\'%1$s\\'\\?</string>\n    <string name=\"tls_ca_file\">Ficheiro TLS CA</string>\n    <string name=\"sip_uri_of_another_proxy_server\">URI SIP de outro Servidor Proxy</string>\n    <string name=\"contact_already_exists\">Contato \\'%1$s\\' já existe.</string>\n    <string name=\"sip_uri_of_proxy_server\">URI SIP do Servidor Proxy</string>\n    <string name=\"backup\">Cópia de Segurança</string>\n    <string name=\"audio_modules_title\">Módulos de Áudio</string>\n    <string name=\"transfer\">Transferir</string>\n    <string name=\"status\">Estado</string>\n    <string name=\"peer_not_verified\">A chama é SEGURA, porém o par NÃO é verificado!</string>\n    <string name=\"disable_history\">Desativar</string>\n    <string name=\"long_message_question\">Quer apagar a mensagem ou adicionar o ponto \\'%1$s\\' aos contatos\\?</string>\n    <string name=\"voicemain_uri_help\">URI SIP para verificar mensagens de correio de voz. Se deixado em branco, as mensagens do correio de voz (Indicação de Mensagem em Espera) não serão assinadas.</string>\n    <string name=\"register_help\">Se marcado, o registo é ativado e as solicitações de REGISTO são enviadas no intervalo determinado pelo Intervalo de registo.</string>\n    <string name=\"rate\">Taxa atual: %1$s (Kbits/s)</string>\n    <string name=\"today\">Hoje</string>\n    <string name=\"dns_servers\">Servidores DNS</string>\n    <string name=\"no_video_calls\">Conceda permissão à \\\"Câmara\\\" para fazer ou atender chamadas de vídeo.</string>\n    <string name=\"failed_to_set_dns_servers\">Falha ao definir servidores DNS</string>\n    <string name=\"stun_password_help\">Palavra-passe, caso seja necessário pelo servidor STUN/TURN</string>\n    <string name=\"long_chat_question\">Quer apagar o chat com o par \\'%1$s\\' ou adicionar o par aos contatos\\?</string>\n    <string name=\"media_nat\">NAT Transversal de Mídia</string>\n    <string name=\"voicemail_messages\">Mensagens do Correio de Voz</string>\n    <string name=\"video_call\">Chamada de vídeo</string>\n    <string name=\"default_account_help\">Se marcado, esta conta é selecionada quando o baresip for iniciado.</string>\n    <string name=\"backup_failed\">Falha ao fazer o backup dos dados da aplicação no ficheiro \\'%1$s\\'. Verifique Aplicações → baresip → Permissões → Armazenamento.</string>\n    <string name=\"display_name_help\">Nome (se houver) usado em From URI de solicitações de saída.</string>\n    <string name=\"call_failed\">A ligação falhou</string>\n    <string name=\"verify\">Verificar a Solicitação</string>\n    <string name=\"new_contact\">Novo Contato</string>\n    <string name=\"audio_modules_help\">Codecs de áudio providos pelos módulos selecionados estão disponíveis para uso das contas.</string>\n    <string name=\"old_messages\">mensagens antigas</string>\n    <string name=\"outbound_proxies\">Próxies de Saída</string>\n    <string name=\"call_history\">Histórico de Chamadas</string>\n    <string name=\"contacts\">Contatos</string>\n    <string name=\"chat_with\">Falar com %1$s</string>\n    <string name=\"start_automatically\">Iniciar Automaticamente</string>\n    <string name=\"invalid_aor\">usuario@dominio[:porta][;transport=udp|tcp|tls] inválido \\'%1$s\\'</string>\n    <string name=\"invalid_proxy_server_uri\">URI do Servidor Proxy Inválido \\'%1$s\\'</string>\n    <string name=\"listen\">Ouvir</string>\n    <string name=\"accept\">Aceitar</string>\n    <string name=\"delete_chats_alert\">Quer apagar o histórico de chat da conta \\'%1$s\\'\\?</string>\n    <string name=\"default_call_volume_help\">Se ajustado, volume de áudio da chamada predefinida na escala de 1 a 10.</string>\n    <string name=\"accounts\">Contas</string>\n    <string name=\"invalid_contact\">Nome de contato \\'%1$s\\' inválido</string>\n    <string name=\"decrypt_password\">Descriptografar Palavra-passe</string>\n    <string name=\"invalid_display_name\">Nome de Exibição Inválido\\'%1$s\\'</string>\n    <string name=\"error\">Erro</string>\n    <string name=\"no\">Não</string>\n    <string name=\"new_message\">Nova mensagem</string>\n    <string name=\"info\">Informações</string>\n    <string name=\"manual\">Manual</string>\n    <string name=\"new_chat_peer\">Novo Par de Chat</string>\n    <string name=\"delete\">Apagar</string>\n    <string name=\"short_message_question\">Quer apagar a mensagem\\?</string>\n    <string name=\"confirmation\">Confirmação</string>\n    <string name=\"config_restart\">Precisa reiniciar o baresip para aplicar as novas configurações. Reiniciar agora\\?</string>\n    <string name=\"call\">Chamada</string>\n    <string name=\"video_codecs\">Codecs de Vídeo</string>\n    <string name=\"auto\">Automático</string>\n    <string name=\"calls_add_delete_question\">Quer adicionar \\'%1$s\\' aos contatos ou deletar %2$s do histórico de chamadas\\?</string>\n    <string name=\"you\">Você</string>\n    <string name=\"codecs\">Codecs</string>\n    <string name=\"failed_to_load_module\">Falha ao carregar módulo.</string>\n    <string name=\"outgoing_call_to_dots\">Ligar a …</string>\n    <string name=\"stun_server\">Servidor STUN/TURN</string>\n    <string name=\"you_have\">Tem</string>\n    <string name=\"no_messages\">Não tem mensagens</string>\n    <string name=\"duration\">Duração: %1$d (segs)</string>\n    <string name=\"call_info\">Informações da Chamada</string>\n    <string name=\"chats\">Histórico de Mensagens</string>\n    <string name=\"call_is_secure\">A chamada é SEGURA e o par é VERIFICADO! Deseja desverificar o par\\?</string>\n    <string name=\"opus_bit_rate_help\">Taxa de bits máxima média usada pelo fluxo de áudio do Opus. Os valores válidos são 6000-510000. A predefinição de fábrica é 28000.</string>\n    <string name=\"restart_request\">Solicitação para Reinicialização</string>\n    <string name=\"register\">Registo</string>\n    <string name=\"restore\">Restaurar</string>\n    <string name=\"opus_packet_loss\">Perda de pacotes Opus esperada</string>\n    <string name=\"transfer_request_query\">Aceita transferir esta chamada para \\'%1$s\\'\\?</string>\n    <string name=\"cancel\">Cancelar</string>\n    <string name=\"delete_account\">Quer apagar a conta \\'%1$s\\'\\?</string>\n    <string name=\"video_request\">Pedido de Vídeo</string>\n    <string name=\"and\">e</string>\n    <string name=\"opus_bit_rate\">Taxa de Bits Opus</string>\n    <string name=\"stun_username\">Nome do Utilizador STUN/TURN</string>\n    <string name=\"default_account\">Conta Predefinida</string>\n    <string name=\"ok\">OK</string>\n    <string name=\"message_failed\">Falhou</string>\n    <string name=\"call_closed\">A chamada está terminada</string>\n    <string name=\"invalid_opus_bitrate\">Taxa de bits Opus inválida</string>\n    <string name=\"transfer_request\">Pedido de Transferência</string>\n    <string name=\"authentication_password\">Palavra-passe de Autenticação</string>\n    <string name=\"registering_failed\">O registo de %1$s falhou.</string>\n    <string name=\"tls_certificate_file\">Ficheiro de Certificado TLS</string>\n    <string name=\"invalid_dns_servers\">Servidores de DNS inválidos</string>\n    <string name=\"media_encryption_help\">Selecione o protocolo de criptografia de transporte de mídia (se houver).\n\\n • ZRTP (recomendado) significa que a negociação de criptografia de mídia ZRTP ponta-a-ponta é tentada depois que a ligação foi estabelecida.\n\\n • DTLS-SRTPF significa que UDP/TLS/RTP/SAVPF é oferecido em chamadas efetuadas que RTP/SAVP, RTP/SAVPF, UDP/TLS/RTP/SAVP, ou UDP/TLS/RTP/SAVPF é usado se oferecida em chamadas recebidas.\n\\n • SRTP-MANDF significa que RTP/SAVPF é oferecido em chamadas efetuadas e requerido em chamadas recebidas.\n\\n • SRTP-MAND significa que RTP/SAVP é oferecido em chamadas efetuadas e requerido em chamadas recebidas.\n\\n • SRTP significa que RTP/AVP é oferecido em chamadas efetuadas e que RTP/SAVP ou RTP/SAVPF é usado se oferecido em chamadas recebidas.</string>\n    <string name=\"new_messages\">novas mensagens</string>\n    <string name=\"about_text\"><![CDATA[\n        <h1>Biblioteca Baresip baseado em agente de utilizador SIP</h1>\n        <br>\n        <p>Juha Heinanen &lt;jh@tutpro.com&gt;</p>\n        <p>Versão %1$s</p>\n        <br>\n        <h2>Dicas de Uso</h2>\n        <ul>\n            <li>Verifique se os valores padrão nas configurações do baresip atendem\n                 às suas necessidades (toque nos títulos dos elementos para obter ajuda).\n            <li>Em seguida, em Contas, crie uma ou mais contas (toque novamente nos títulos dos elementos para obter ajuda).</li>\n            <li>O estado de registo de uma conta é mostrado com um ponto colorido: verde (registo\n                bem-sucedido), amarelo (registo em andamento), vermelho (falha no registo), branco (registo\n                não foi ativado).</li>\n            <li>O toque no ponto leva diretamente à configuração da conta.</li>\n            <li>O gesto de deslizar para baixo causa o novo registo da conta exibida no momento.</li>\n            <li>Um toque longo na conta exibida no momento ativa ou desativa o registo da conta.</li>\n            <li>O participante da chamada anterior pode ser selecionado novamente tocando no ícone de chamada quando\n                Callee estiver vazio.</li>\n            <li>É possível adicionar pares de chamadas e mensagens aos contatos por meio de toques longos.</li>\n            <li>Também é possível usar toques longos para remover chamadas, bate-papos, mensagens e contatos.</li>\n            <li>Toque/toque longo no ícone do contato para instalar/remover o avatar.</li>\n            <li>Para mais informações, consulte <a href=\"https://github.com/juha-h/baresip-studio/wiki\">Wiki</a>.</li>\n        </ul>\n        <h2>Política de privacidade</h2>\n            <p>A política de privacidade está disponível em<a href=\"https://raw.githubusercontent.com/juha-h/baresip-studio/master/PrivacyPolicy.txt\">here</a>.</p>\n        <br>\n        <h2>Código fonte</h2>\n            <p>O código fonte está disponível no <a href=\"https://github.com/juha-h/baresip-studio\">GitHub</a>,\n            onde os problemas encontrados também podem ser reportados.</p>\n        <br>\n        <h2>Licenças</h2>\n        <ul>\n            <li><b>BSD-3-Clause</b> exceto os seguintes:</li>\n            <li><b>Apache 2.0</b> codecs AMR e segurança TLS</li>\n            <li><b>AGPLv4</b> criptografia de média ZRTP</li>\n            <li><b>GNU LGPL 2.1</b> G.722, G.726 e codecs Codec2</li>\n            <li><b>GNU GPLv3</b> codec G.729</li>\n        </ul>\n        ]]></string>\n    <string name=\"video_size\">Tamanho do Quadro do Vídeo</string>\n    <string name=\"restore_failed\">Falha ao restaurar os dados da aplicação. Verifique se inseriu a palavra-passe correta e que o ficheiro de backup seja deste aplicação, Nas versões do Android 9 e anteriores, verifique também em Apps → baresip → Permissões → Armazenamento e se o ficheiro \\'%1$s\\' existe na pasta Download.</string>\n    <string name=\"restart\">Reiniciar</string>\n    <string name=\"authentication_username\">Nome de Utilizador de Autenticação</string>\n    <string name=\"authentication_username_help\">Nome de utilizador de autenticação se a autenticação de solicitações SIP for necessária. O valor predefinido é o nome de utilizador da conta.</string>\n    <string name=\"yes\">Sim</string>\n    <string name=\"media_nat_help\">Selecione o protocolo de NAT transversal de mídia (se houver). Escolhas possíveis são STUN (Session Traversal Utilities para NAT, RFC 5389) e ICE (Interactive Connectivity Establishment, RFC 5245).</string>\n    <string name=\"invalid_stun_server\">Servidor URI STUN/TURN Inválido \\'%1$s\\'</string>\n    <string name=\"invalid_authentication_username\">Nome de Utilizador de Autenticação Inválido \\'%1$s\\'</string>\n    <string name=\"no_cameras\">Não possui câmaras de vídeo compatíveis.</string>\n    <string name=\"calls_calls\">chamadas</string>\n    <string name=\"audio_codecs\">Codecs de Áudio</string>\n    <string name=\"enable_history\">Ativar</string>\n    <string name=\"dtmf\">DTMF</string>\n    <string name=\"no_calls\">O baresip precisa de permissão do \\\"Microfone\\\" para realizar chamadas de voz.</string>\n    <string name=\"read_ca_certs_error\">Houve uma falha ao ler o ficheiro \\'ca_certs.crt\\'.</string>\n    <string name=\"account_exists\">A conta \\'%1$s\\' já existe.</string>\n    <string name=\"listen_address_help\">O endereço IP e a porta do formulário \\\"address:port\\\" são os endereços onde o baresip escuta as solicitações SIP recebidas. Se o endereço IP for IPv6, deve ser escrito entre colchetes []. O endereço IPv4 0.0.0.0 ou o endereço IPv6 [::] faz com que o baresip escute em todos os endereços disponíveis. Se for deixado em branco (padrão de fábrica), o baresip escuta numa porta arbitrária em todos os endereços disponíveis.</string>\n    <string name=\"reset_config_help\">Se selecionado, ajustes serão redefinidos para os valores de predefinição de fábrica.</string>\n    <string name=\"call_not_secure\">Esta chamada NÃO é segura!</string>\n    <string name=\"read_cert_error\">Houve uma falha ao ler o ficheiro \\'cert.pem\\'.</string>\n    <string name=\"media_encryption\">Criptografia de Mídia</string>\n    <string name=\"listen_address\">Endereço de Escuta</string>\n    <string name=\"display_name\">Nome de Exibição</string>\n    <string name=\"about\">Sobre</string>\n    <string name=\"sending_failed\">Envio de mensagem falhou</string>\n    <string name=\"invalid_listen_address\">Endereço de Escuta inválido</string>\n    <string name=\"stun_username_help\">Nome do utilizador, caso seja necessário pelo servidor STUN/TURN</string>\n    <string name=\"invalid_authentication_password\">Palavra-passe de Autenticação Inválida \\'%1$s\\'</string>\n    <string name=\"authentication_password_help\">Palavra-passe de Autenticação com até 64 caracteres. Se o Nome de Utilizador de Autenticação for informado mas a palavra-passe não, ela será solicitada quando o baresip for iniciado.</string>\n    <string name=\"answer_mode\">Modo de Atendimento</string>\n    <string name=\"account\">Conta</string>\n    <string name=\"edit\">Editar</string>\n    <string name=\"debug\">Depuração</string>\n    <string name=\"invalid_opus_packet_loss\">Percentagem de Perda de Pacotes do Opus inválida</string>\n    <string name=\"dns_servers_help\">Lista de endereço de servidores de DNS separados por vírgula. Endereços de servidores DNS são obtidos dinamicamente pelo sistema. Cada endereço DNS é na forma \\'ip:porta\\' ou \\'ip\\'. Se a porta é omitida, a predefinição será 53. Se o ip é um endereço IPv6 e também a porta é fornecida, o ip precisa ser escrito dentro de colchetes []. Por exemplo, escutar \\'8.8.8.8:53,[2001:4860:4860::8888]:53\\' aponta para endereços IPv4 e IPv6 dos servidores de DNS do Google.</string>\n    <string name=\"notice\">Aviso</string>\n    <string name=\"about_title\">Sobre o baresip</string>\n    <string name=\"invalid_stun_password\">Palavra-passe inválida \\'%1$s\\'</string>\n    <string name=\"one_old_message\">uma mensagem antiga</string>\n    <string name=\"configuration\">Ajustes</string>\n    <string name=\"answer_mode_help\">Selecione como chamadas recebidas são respondidas.</string>\n    <string name=\"video_size_help\">Tamanho dos quadros de vídeo que foram transmitidos (largura x altura)</string>\n    <string name=\"voicemail_uri\">URI do Correio de Voz</string>\n    <string name=\"backed_up\">Os dados as aplicações foram armazenados num backup no ficheiro \\'%1$s\\'. Nas versões do Android 9 e anteriores, o ficheiro está na pasta Download.</string>\n    <string name=\"quit\">Sair</string>\n    <string name=\"unverify\">Desverificar</string>\n    <string name=\"tls_certificate_file_help\">Se marcado, o ficheiro que contém o certificado TLS e a chave privada desta instância do baresip foi ou será carregado. Na versão do Android 9, um ficheiro chamado \\'cert.pen\\' será lido a partir da pasta Download. Por motivos de segurança, apague o ficheiro após o carregamento.</string>\n    <string name=\"invalid_stun_username\">Nome do utilizador inválido \\'%1$s\\'</string>\n    <string name=\"calls_delete_question\">Quer apagar %1$s\\' %2$s do histórico de chamadas\\?</string>\n    <string name=\"transfer_failed\">A transferência falhou</string>\n    <string name=\"transfer_destination\">Destino da transferência</string>\n    <string name=\"call_transfer\">Transferência de chamada</string>\n    <string name=\"allow_video_recv\">Aceitar o recebimento de vídeo a partir do \\'%1$s\\'\\?</string>\n    <string name=\"allow_video_send\">Aceitar o envio de vídeo para \\'%1$s\\'\\?</string>\n    <string name=\"android_contact_help\">Se for marcado, este contato será adicionado nos contatos do Android.</string>\n    <string name=\"sip_trace_help\">Se selecionado em conjunto com Debug, as mensagens Logcat incluem também o pedido SIP e o rastreamento da resposta. A opção não é ativada automaticamente durante a inicialização do baresip.</string>\n    <string name=\"sip_trace\">Rastreio SIP</string>\n    <string name=\"dark_theme_help\">Impor o uso do tema escuro no ecrã</string>\n    <string name=\"dark_theme\">Tema Escuro</string>\n    <string name=\"verify_server_help\">Se estiver marcado, o baresip verifica os certificados TLS do SIP User Agent e o SIP Proxy Servers quando o transporte TLS for usado.</string>\n    <string name=\"verify_server\">Verifique os certificados do servidor</string>\n    <string name=\"transfer_request_to\">Chamada de solicitação de transferência para</string>\n    <string name=\"missed_call_from\">Ligação perdida de</string>\n    <string name=\"dtmf_info\">Solicitações INFO SIP</string>\n    <string name=\"dtmf_inband\">Eventos na Banda RTP</string>\n    <string name=\"dtmf_mode_help\">Selecione como os tons DTMF 0–9, #, * e A-D serão enviados.</string>\n    <string name=\"dtmf_mode\">Modo DTMF</string>\n    <string name=\"about_text_plus\"><![CDATA[\n        <h1>Biblioteca Baresip baseado em agente de utilizador SIP com vídeo chamadas</h1>\n        <p>Juha Heinanen &lt;jh@tutpro.com&gt;</p>\n        <p>Versão %1$s</p>\n        <h2>Dicas de uso</h2>\n        <ul>\n            <li>Verifique se os valores padrão nas configurações do baresip+\\ atendem às as suas necessidades\n                (toque nos títulos dos elementos para obter ajuda).</li>\n            <li>Então em Contas, crie uma ou mais contas (novamente clique nos títulos para ajuda).</li>\n            <li>O estado de uma conta é mostrado com um ponto colorido: verde (registo\n                bem sucedido), amarelo (registo em progresso), vermelho (registo falhou), branco\n                (registo não foi ativado).</li>\n            <li>Toque nos três pontos para ir direto para a configuração da conta.</li>\n            <li>O gesto de deslizar para baixo causa o novo registo da conta exibida no momento.</li>\n            <li>Um toque longo na conta exibida no momento ativa ou desativa o registo da conta.</li>\n            <li>Gestos para a esquerda/direita alternam as contas.</li>\n            <li>O participante da chamada anterior pode ser selecionado novamente tocando no ícone\n                de chamada quando o \"Callee\" estiver vazio.</li>\n            <li>Os pares de chamadas e mensagens podem ser adicionados aos contatos por meio de toques longos.</li>\n            <li>Os toques longos também podem ser usados para remover chamadas, bate-papos, mensagens e contatos.</li>\n            <li>Toque/toque longo no ícone do contato pode ser usado para instalar/remover o avatar da imagem.</li>\n            <li>Consulte a <a href=\"https://github.com/juha-h/baresip-studio/wiki\">Wiki</a> para obter\n                mais informações.</li>\n        </ul>\n        <h2>Problemas conhecidos</h2>\n        <ul>\n            <li>Nas chamadas com vídeo, o dispositivo precisa ser mantido no modo paisagem,\n                girado 90 graus para a esquerda em relação à orientação retrato.</li>\n            <li>A própia visualização automática não é exibida corretamente quando o\n                fluxo de vídeo é somente envio.</li>\n        </ul>\n        <h2>Política de privacidade</h2>\n            A política de privacidade está disponível <a href=\"https://raw.githubusercontent.com/juha-h/baresip-studio/video/PrivacyPolicy.txt\">aqui</a>.\n        <h2>Código fonte</h2>\n        O código fonte está disponível no <a href=\"https://github.com/juha-h/baresip-studio\">GitHub</a>,\n        onde os problemas encontrados também podem ser reportados.\n        <h2>Licenças</h2>\n        <ul>\n            <li><b>Cláusula BSD-3</b> exceto os seguintes:</li>\n            <li><b>Apache 2.0</b>, codecs AMR e segurança TLS</li>\n            <li><b>AGPLv4</b>, criptografia de média ZRTP</li>\n            <li><b>GNU LGPL 2.1</b> G.722, G.726 e codecs Codec2</li>\n            <li><b>GNU GPLv3</b> codec G.729</li>\n            <li><b>GNU GPLv2</b> codecs H.264 e H.265</li>\n            <li><b>AOMedia</b> codec AV1</li>\n        </ul>\n        ]]></string>\n    <string name=\"about_title_plus\">Sobre o baresip+</string>\n    <string name=\"battery_optimizations\">Otimizações da bateria</string>\n    <string name=\"missed_calls\">Chamadas perdidas</string>\n    <string name=\"missed_calls_count\">%1$d chamadas perdidas</string>\n    <string name=\"battery_optimizations_help\">Desative as otimizações da bateria (recomendado) caso queira reduzir a probabilidade do Android restringir o acesso do baresip à rede ou ponha o baresip no modo de espera.</string>\n    <string name=\"reset_config_alert\">Tem certeza de que deseja redefinir as configurações para os valores predefinidos de fábrica\\?</string>\n    <string name=\"reset\">Redefinir</string>\n    <string name=\"avatar_image\">Imagem do perfil</string>\n    <string name=\"packets\">Pacotes</string>\n    <string name=\"lost\">Perda</string>\n    <string name=\"jitter\">Variação: %1$s (ms)</string>\n    <string name=\"no_restore\">Não pode restaurar o backup sem a permissão de \\\"Armazenamento\\\".</string>\n    <string name=\"call_is_on_hold\">Chamada em espera</string>\n    <string name=\"no_network\">Nenhuma conexão de rede!</string>\n    <string name=\"average_rate\">Taxa média: %1$s (Kbits/s)</string>\n    <string name=\"no_backup\">Não pode criar backups sem a permissão de \\\"Armazenamento\\\".</string>\n    <string name=\"audio_settings\">Configurações de áudio</string>\n    <string name=\"peer\">Par</string>\n    <string name=\"direction\">Direção</string>\n    <string name=\"call_details\">Detalhes da chamada</string>\n    <string name=\"time\">Hora</string>\n    <string name=\"calls_duration\">Duração</string>\n    <string name=\"telephony_provider_help\">Parte do host SIP URI usada em chamadas para números de telefone. O padrão de fábrica é o domínio da conta. Se não for fornecida, esta conta não pode ser usada para ligar para números de telefone.</string>\n    <string name=\"no_android_contacts\">Você não pode acessar os contatos do Android sem a permissão \\\"Contatos\\\".</string>\n    <string name=\"sip_or_tel_uri\">SIP ou tel URI</string>\n    <string name=\"user_domain_or_number\">usuário@domínio ou número de telefone</string>\n    <string name=\"contacts_help\">Escolhe se serão usados os contatos do baresip, os contatos do Android ou ambos. Se ambos forem usados e houver um contato com o mesmo nome em ambos os contatos, o contato baresip será escolhido.</string>\n    <string name=\"both\">Ambos</string>\n    <string name=\"invalid_sip_or_tel_uri\">SIP ou tel URI \\'%1$s\\' inválido</string>\n    <string name=\"blind\">Cego</string>\n    <string name=\"attended\">Participou</string>\n    <string name=\"telephony_provider\">Provedor de telefonia</string>\n    <string name=\"invalid_sip_uri_hostpart\">Parte do host URI SIP inválida \\'%1$s\\'</string>\n    <string name=\"reg_int\">Intervalo de registo</string>\n    <string name=\"invalid_reg_int\">Intervalo de registo inválido\\'%1$s\\'</string>\n    <string name=\"rtcp_mux_help\">Se marcada, os pacotes RTP e RTCP são multiplexados numa única porta (RFC 5761).</string>\n    <string name=\"rtcp_mux\">Multiplexagem RTCP</string>\n    <string name=\"country_code\">Código do país</string>\n    <string name=\"no_telephony_provider\">A conta \\'%1$s\\' não tem provedor telefonico</string>\n    <string name=\"no_notifications\">Não pode usar esta aplicação sem a permissão \\\"Notificações\\\".</string>\n    <string name=\"audio_focus_denied\">Foco de áudio negado!</string>\n    <string name=\"invalid_country_code\">Código do país inválido \\'%1$s\\'</string>\n    <string name=\"diverted_by_dots\">Desviado por …</string>\n    <string name=\"choose_destination_uri\">Escolha a URI de destino</string>\n    <string name=\"anonymous\">Anônimo</string>\n    <string name=\"unknown\">Desconhecido</string>\n    <string name=\"consent_request\">Solicitação de consentimento</string>\n    <string name=\"country_code_help\">O código do país E.164 desta conta. Se durante o recebimento da chamada ou mensagem do utilizador a parte URI recebida tiver um número de telefone que não começa com o sinal \\'+\\' e se a busca de contato falhar, o número será prefixado com este código do país e a busca pelo contato será testada novamente. Se o número de telefone começar com um único dígito \\'0\\', o dígito \\'0\\' será removido antes que o número seja prefixado.</string>\n    <string name=\"account_nickname_help\">Apelido (se houver) é utilizado para identificar essa conta dentro da app baresip.</string>\n    <string name=\"invalid_account_nickname\">Apelido inválido da conta \\'%1$s\\'</string>\n    <string name=\"non_unique_account_nickname\">O apelido \\'%1$s\\' já existe</string>\n    <string name=\"nickname\">Apelido</string>\n    <string name=\"reg_int_help\">Informa com que frequência (em segundos) o baresip envia solicitações REGISTER. Os valores válidos são entre 60 a 3600.</string>\n    <string name=\"contacts_consent\">Se os contatos so android forem escolhidos, eles podem ser usados nas chamadas e nas mensagens como referências ao SIP e so tel das URIs. a app baresip não armazena os contatos do Android nem os compartilha com ninguém. Para disponibilizar os contatos android no baresip, o Google exige que aceite o uso como descrito aqui e na <a href=\"https://raw.githubusercontent.com/juha-h/baresip-studio/master/PrivacyPolicy.txt\">Política de Privacidade</a>. </string>\n    <string name=\"permissions_rationale\">Justificativa das permissões</string>\n    <string name=\"audio_permissions\">O baresip precisa da permissão \\\"Microfone\\\" para realizar chamadas de voz, da permissão \\\"Aparelhos próximos\\\" para fazer a detecção do microfone/alto-falante Bluetooth e a permissão \\\"Notificações\\\" para postar notificações.</string>\n    <string name=\"audio_and_video_permissions\">O baresip+ precisa da permissão \\\"Microfone\\\" para realizar chamadas de voz, da permissão \\\"Câmara\\\" para realizar chamadas de vídeo. da permissão \\\"Aparelhos próximos\\\" para fazer a detecção do microfone/alto-falante Bluetooth caso queira usar tal aparelho, assim como a permissão \\\"Notificações\\\" para postar notificações.</string>\n    <string name=\"address_family\">Família de endereço</string>\n    <string name=\"address_family_help\">Escolhe quais endereços IP o baresip usa. Se IPv4 ou IPv6 for escolhido, o baresip usa apenas endereços de IPv4 ou IPv6. Se nenhum for escolhido, o baresip usará endereços de IPv4 e IPv6.</string>\n    <string name=\"rec_in_call\">A gravação só pode ser ativada ou desativada quando não estiver numa chamada</string>\n    <string name=\"redirect_mode\">Modo de redirecionamento</string>\n    <string name=\"call_auto_rejected\">Chamada rejeitada automaticamente de %1$s</string>\n    <string name=\"default_phone_app\">Aplicação de telefone padrão</string>\n    <string name=\"default_phone_app_help\">Se for marcado, o baresip será a aplicaöäao de telefone padrão. Não marque caso o seu dispositivo também precise lidar com outras chamadas ou mensagens SIP.</string>\n    <string name=\"favorite\">Favorito</string>\n    <string name=\"redirect_notice\">Redirecionamento automático para \\'%1$s\\'\\\\</string>\n    <string name=\"audio_delay_help\">Tempo (em milissegundos) para o áudio esperar de quem chama quando a chamada for estabelecida. Defina um valor mais alto se perde o áudio de quem chama no início da chamada.</string>\n    <string name=\"audio_delay\">Atraso do áudio</string>\n    <string name=\"invalid_audio_delay\">Atraso inválido de áudio \\'%1$s\\'. Os valores válidos são entre 100 a 3000.</string>\n    <string name=\"appear_on_top_permission\">A inicialização automática precisa aparecer no topo das permissões.</string>\n    <string name=\"redirect_request\">Solicitação de redirecionamento</string>\n    <string name=\"redirect_request_query\">Aceita o redirecionamento de chamadas para \\'%1$s\\'?</string>\n    <string name=\"tone_country\">Tom do país</string>\n    <string name=\"tone_country_help\">Toque da chamada do país, espera e tom de chamada ocupada</string>\n    <string name=\"rel_100\">Respostas provisórias confiáveis</string>\n    <string name=\"rel_100_help\">Se for marcado, indica suporte para respostas provisórias confiáveis (RFC 3262).</string>\n    <string name=\"call_request\">Pedido de chamada</string>\n    <string name=\"call_request_query\">Aceita o pedido de chamada para \\'%1$s\\'?</string>\n    <string name=\"video_fps\">Quadros de vídeo por segundo</string>\n    <string name=\"microphone_gain_help\">Multiplique o volume do microfone por este número decimal. O valor mínimo é 1,0 (padrão de fábrica), que desativa o ganho do microfone. Valores maiores podem afetar negativamente a qualidade do áudio.</string>\n    <string name=\"video_fps_help\">Taxa de quadros de vídeo que será oferecida durante o handshake SDP. Os valores válidos são de 10 a 30.</string>\n    <string name=\"invalid_fps\">Quadros por segundo inválidos \\'%1$d\\'</string>\n    <string name=\"user_agent_help\">Valor do campo de cabeçalho User-Agent personalizado da solicitação/resposta SIP</string>\n    <string name=\"invalid_user_agent\">Valor do campo do cabeçalho User-Agent inválido</string>\n    <string name=\"redirect_mode_help\">Seleciona caso a solicitação de redirecionamento da chamada seja seguida automaticamente ou se caso uma confirmação seja solicitada.</string>\n    <string name=\"speaker_phone\">Alto-falante do telefone</string>\n    <string name=\"speaker_phone_help\">Se estiver marcada, o alto-falante do telefone é ligado automaticamente quando a chamada for iniciada.</string>\n    <string name=\"invalid_microphone_gain\">Valor inválido do ganho do microfone</string>\n    <string name=\"user_agent\">Agente do utilizador</string>\n    <string name=\"microphone_gain\">Ganho do microfone</string>\n    <string name=\"favorite_help\">Se estiver marcada, o contacto será exibido entre os favoritos na parte superior da lista.</string>\n    <string name=\"dtmf_auto\">INFO RTP ou SIP em banda</string>\n    <string name=\"dialer_role_not_available\">A função de discador não está disponível</string>\n    <string name=\"restore_unzip_failed\">Falha ao restaurar os dados da aplicação. A versão 14 e superior do Android não permite a restauração de dados cujo backup foi feito %1$s antes da versão %2$s.</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-pt-rBR/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"codecs\">Codecs</string>\n    <string name=\"status\">Status</string>\n    <string name=\"dtmf\">DTMF</string>\n    <string name=\"backup\">Cópia de Segurança</string>\n    <string name=\"no_calls\">O baresip precisa de permissão do \\\"Microfone\\\" para realizar chamadas de voz.</string>\n    <string name=\"restore_failed\">Falha ao restaurar os dados do aplicativo. Verifique se inseriu a senha correta e que o arquivo de backup seja deste aplicativo, Na versão 9 do Android, verifique também em Aplicativos → baresip → Permissões → Armazenamento e se o arquivo \\'%1$s\\' existe na pasta Download.</string>\n    <string name=\"restored\">Os dados do aplicativo foram restaurados. O baresip precisa ser reiniciado. Reiniciar agora\\?</string>\n    <string name=\"backup_failed\">Falha ao fazer o backup dos dados do aplicativo no arquivo \\'%1$s\\'. Verifique Aplicativos → baresip → Permissões → Armazenamento.</string>\n    <string name=\"backed_up\">Os dados do aplicativo (excluindo gravações) em um backup no arquivo \\'%1$s\\'. Na versão do 9 Android, o arquivo está na pasta Download.</string>\n    <string name=\"unverify\">Desverificar</string>\n    <string name=\"call_is_secure\">A chamada é SEGURA e o par é VERIFICADO! Deseja desverificar o par\\?</string>\n    <string name=\"peer_not_verified\">A chama é SEGURA, porém o par NÃO é verificado!</string>\n    <string name=\"call_not_secure\">Esta chamada NÃO é segura!</string>\n    <string name=\"call_closed\">A chamada está encerrada</string>\n    <string name=\"call_failed\">A ligação falhou</string>\n    <string name=\"verify_sas\">Você quer verificar SAS &lt;%1$s&gt;\\?</string>\n    <string name=\"verify\">Verificar a Solicitação</string>\n    <string name=\"registering_failed\">O registro de %1$s falhou.</string>\n    <string name=\"start_failed\">Baresip falhou ao iniciar. Isso pode ser devido a um valor inválido das Configurações. Verifique Endereço de Escuta, Arquivo de Certificado TLS e Arquivo CA TLS. Em seguida, reinicie o baresip.</string>\n    <string name=\"call_already_active\">Você já tem uma chamada ativa.</string>\n    <string name=\"listen\">Ouvir</string>\n    <string name=\"no_messages\">Você não tem mensagens</string>\n    <string name=\"and\">e</string>\n    <string name=\"old_messages\">mensagens antigas</string>\n    <string name=\"one_old_message\">uma mensagem antiga</string>\n    <string name=\"new_messages\">novas mensagens</string>\n    <string name=\"one_new_message\">uma nova mensagem</string>\n    <string name=\"you_have\">Você tem</string>\n    <string name=\"voicemail_messages\">Mensagens do Correio de Voz</string>\n    <string name=\"rate\">Taxa atual: %1$s (Kbits/s)</string>\n    <string name=\"duration\">Duração: %1$d (segs)</string>\n    <string name=\"call_info_not_available\">Nenhuma informação disponível</string>\n    <string name=\"call_info\">Informações da Chamada</string>\n    <string name=\"incoming_call_from_dots\">Chamada de …</string>\n    <string name=\"outgoing_call_to_dots\">Chamando …</string>\n    <string name=\"quit\">Sair</string>\n    <string name=\"restart\">Reiniciar</string>\n    <string name=\"about\">Sobre</string>\n    <string name=\"restore\">Restaurar</string>\n    <string name=\"error\">Erro</string>\n    <string name=\"edit\">Editar</string>\n    <string name=\"delete\">Excluir</string>\n    <string name=\"add\">Adicionar</string>\n    <string name=\"deny\">Recusar</string>\n    <string name=\"accept\">Aceitar</string>\n    <string name=\"no\">Não</string>\n    <string name=\"yes\">Sim</string>\n    <string name=\"ok\">OK</string>\n    <string name=\"cancel\">Cancelar</string>\n    <string name=\"notice\">Aviso</string>\n    <string name=\"info\">Informações</string>\n    <string name=\"alert\">Alerta</string>\n    <string name=\"contact_delete_question\">Você quer excluir o contato \\'%1$s\\'\\?</string>\n    <string name=\"send_message\">Enviar Mensagem</string>\n    <string name=\"contact_action_question\">Você quer ligar ou enviar mensagem para \\'%1$s\\'\\?</string>\n    <string name=\"contacts\">Contatos</string>\n    <string name=\"contact_already_exists\">Contato \\'%1$s\\' já existe.</string>\n    <string name=\"invalid_contact\">Nome de contato \\'%1$s\\' inválido</string>\n    <string name=\"contact_name\">Nome</string>\n    <string name=\"new_contact\">Novo Contato</string>\n    <string name=\"config_restart\">Você precisa reiniciar o baresip para aplicar as novas configurações. Reiniciar agora\\?</string>\n    <string name=\"read_ca_certs_error\">Houve uma falha ao ler o arquivo \\'ca_certs.crt\\'.</string>\n    <string name=\"read_cert_error\">Houve uma falha ao ler o arquivo \\'cert.pem\\'.</string>\n    <string name=\"reset_config_help\">Se selecionado, ajustes serão redefinidos para os valores de padrão de fábrica.</string>\n    <string name=\"reset_config\">Redefinir os Padrões de Fábrica</string>\n    <string name=\"debug\">Depuração</string>\n    <string name=\"debug_help\">Se selecionado, disponibiliza mensagens de depuração no registro log e informações para o Logcat.</string>\n    <string name=\"default_call_volume_help\">Se ajustado, volume de áudio da chamada padrão na escala de 1 a 10.</string>\n    <string name=\"default_call_volume\">Volume de Chamadas Padrão</string>\n    <string name=\"invalid_opus_packet_loss\">Porcentagem de Perda de Pacotes do Opus inválida</string>\n    <string name=\"invalid_opus_bitrate\">Taxa de bits Opus inválida</string>\n    <string name=\"opus_packet_loss_help\">Esperada porcentagem de perda de pacotes do fluxo de áudio do Opus, de 0-100. Por padrão o valor é 1. O valor 0 desativa o Opus Forward Error Correction (FEC).</string>\n    <string name=\"opus_packet_loss\">Perda de pacotes Opus esperada</string>\n    <string name=\"opus_bit_rate_help\">Taxa de bits máxima média usada pelo fluxo de áudio do Opus. Os valores válidos são 6000-510000. O padrão de fábrica é 28000.</string>\n    <string name=\"opus_bit_rate\">Taxa de Bits Opus</string>\n    <string name=\"failed_to_load_module\">Falha ao carregar módulo.</string>\n    <string name=\"audio_modules_help\">Codecs de áudio providos pelos módulos selecionados estão disponíveis para uso das contas.</string>\n    <string name=\"audio_modules_title\">Módulos de Áudio</string>\n    <string name=\"tls_ca_file_help\">Se estiver marcada, foi ou será carregado um arquivo que contém certificados TLS de tais autoridades de certificação que não estão incluídas no sistema operacional Android. Na versão 9 do Android, um arquivo chamado “ca_certs.crt” é carregado da pasta Download.</string>\n    <string name=\"tls_ca_file\">Arquivo TLS CA</string>\n    <string name=\"tls_certificate_file_help\">Se marcado, o arquivo que contém o certificado TLS e a chave privada desta instância do baresip foi ou será carregado. Na versão do Android 9, um arquivo chamado \\'cert.pen\\' será lido a partir da pasta Download. Por motivos de segurança, exclua o arquivo após o carregamento.</string>\n    <string name=\"tls_certificate_file\">Arquivo de Certificado TLS</string>\n    <string name=\"failed_to_set_dns_servers\">Falha ao definir servidores DNS</string>\n    <string name=\"invalid_dns_servers\">Servidores de DNS inválidos</string>\n    <string name=\"dns_servers_help\">Lista de endereço de servidores de DNS separados por vírgula. Endereços de servidores DNS são obtidos dinamicamente pelo sistema. Cada endereço DNS é na forma \\'ip:porta\\' ou \\'ip\\'. Se a porta é omitida, o padrão será 53. Se o ip é um endereço IPv6 e também a porta é fornecida, o ip precisa ser escrito dentro de colchetes []. Por exemplo, escutar \\'8.8.8.8:53,[2001:4860:4860::8888]:53\\' aponta para endereços IPv4 e IPv6 dos servidores de DNS do Google.</string>\n    <string name=\"dns_servers\">Servidores DNS</string>\n    <string name=\"invalid_listen_address\">Endereço de Escuta inválido</string>\n    <string name=\"listen_address_help\">O endereço IP e a porta do formulário \\\"address:port\\\" são os endereços onde o baresip escuta as solicitações SIP recebidas. Se o endereço IP for IPv6, deve ser escrito entre colchetes []. O endereço IPv4 0.0.0.0 ou o endereço IPv6 [::] faz com que o baresip escute em todos os endereços disponíveis. Se for deixado em branco (padrão de fábrica), o baresip escuta em uma porta arbitrária em todos os endereços disponíveis.</string>\n    <string name=\"listen_address\">Endereço de Escuta</string>\n    <string name=\"start_automatically_help\">Se marcado, baresip iniciar automaticamente depois que o dispositivo reiniciar.</string>\n    <string name=\"start_automatically\">Iniciar Automaticamente</string>\n    <string name=\"configuration\">Ajustes</string>\n    <string name=\"delete_chats_alert\">Você quer excluir o histórico de chat da conta \\'%1$s\\'\\?</string>\n    <string name=\"short_chat_question\">Você quer excluir o chat com \\'%1$s\\'\\?</string>\n    <string name=\"long_chat_question\">Você quer excluir o chat com o par \\'%1$s\\' ou adicionar o par aos contatos\\?</string>\n    <string name=\"new_chat_peer\">Novo Par de Chat</string>\n    <string name=\"you\">Você</string>\n    <string name=\"today\">Hoje</string>\n    <string name=\"chats\">Histórico de Mensagens</string>\n    <string name=\"message_failed\">Falhou</string>\n    <string name=\"sending_failed\">Envio de mensagem falhou</string>\n    <string name=\"add_contact\">Adicionar Contato</string>\n    <string name=\"short_message_question\">Você quer excluir a mensagem\\?</string>\n    <string name=\"long_message_question\">Você quer excluir a mensagem ou adicionar o ponto \\'%1$s\\' aos contatos\\?</string>\n    <string name=\"new_message\">Nova mensagem</string>\n    <string name=\"chat_with\">Falar com %1$s</string>\n    <string name=\"delete_history_alert\">Você quer excluir o histórico de chamadas da conta \\'%1$s\\'\\?</string>\n    <string name=\"enable_history\">Habilitar</string>\n    <string name=\"disable_history\">Desabilitar</string>\n    <string name=\"calls_delete_question\">Você quer apagar %1$s\\' %2$s do histórico de chamadas\\?</string>\n    <string name=\"calls_add_delete_question\">Você quer adicionar \\'%1$s\\' aos contatos ou deletar %2$s do histórico de chamadas\\?</string>\n    <string name=\"calls_call\">chamada</string>\n    <string name=\"calls_calls\">chamadas</string>\n    <string name=\"call\">Chamada</string>\n    <string name=\"call_history\">Histórico de Chamadas</string>\n    <string name=\"transfer_request\">Pedido de Transferência</string>\n    <string name=\"delete_account\">Você quer excluir a conta \\'%1$s\\'\\?</string>\n    <string name=\"decrypt_password\">Descriptografar Senha</string>\n    <string name=\"encrypt_password\">Criptografar Senha</string>\n    <string name=\"account_allocation_failure\">Houve uma falha ao alocar a nova conta.</string>\n    <string name=\"account_exists\">A conta \\'%1$s\\' já existe.</string>\n    <string name=\"invalid_aor\">usuario@dominio[:porta][;transport=udp|tcp|tls] inválido \\'%1$s\\'</string>\n    <string name=\"accounts_help\">SIP URI da nova conta no formato: &lt;usuário&gt;@@&lt;domínio&gt;[:&lt;porta&gt;][;transport=udp|tcp|tls]. Se a &lt;porta&gt; for informada e o protocolo de transporte não, o padrão será UDP. Se a &lt;porta&gt; não for informada e o protocolo de transporte for, a &lt;porta&gt; padrão é 5060 ou 5061 (TLS). Se nenhum dos dois ou nenhum proxy for informado, o usuário da conta (caso haja) é determinado exclusivamente com base nas informações do DNS do domínio.</string>\n    <string name=\"new_account\">SIP URI da nova conta</string>\n    <string name=\"accounts\">Contas</string>\n    <string name=\"default_account_help\">Se marcado, esta conta é selecionada quando o baresip for iniciado.</string>\n    <string name=\"default_account\">Conta Padrão</string>\n    <string name=\"voicemain_uri_help\">URI SIP para checar mensagens de correio de voz. Se deixado em branco, as mensagens do correio de voz (Indicação de Mensagem em Espera) não serão assinadas.</string>\n    <string name=\"voicemail_uri\">URI do Correio de Voz</string>\n    <string name=\"auto\">Automático</string>\n    <string name=\"manual\">Manual</string>\n    <string name=\"answer_mode_help\">Selecione como chamadas recebidas são respondidas.</string>\n    <string name=\"answer_mode\">Modo de Atendimento</string>\n    <string name=\"media_encryption_help\">Selecione o protocolo de criptografia de transporte de mídia (se houver).\n\\n • ZRTP (recomendado) significa que a negociação de criptografia de mídia ZRTP ponta-a-ponta é tentada depois que a ligação foi estabelecida. \n\\n • DTLS-SRTPF significa que UDP/TLS/RTP/SAVPF é oferecido em chamadas efetuadas que RTP/SAVP, RTP/SAVPF, UDP/TLS/RTP/SAVP, ou UDP/TLS/RTP/SAVPF é usado se oferecida em chamadas recebidas. \n\\n • SRTP-MANDF significa que RTP/SAVPF é oferecido em chamadas efetuadas e requerido em chamadas recebidas. \n\\n • SRTP-MAND significa que RTP/SAVP é oferecido em chamadas efetuadas e requerido em chamadas recebidas. \n\\n • SRTP significa que RTP/AVP é oferecido em chamadas efetuadas e que RTP/SAVP ou RTP/SAVPF é usado se oferecido em chamadas recebidas.</string>\n    <string name=\"media_encryption\">Criptografia de Mídia</string>\n    <string name=\"invalid_stun_server\">Servidor URI STUN/TURN Inválido \\'%1$s\\'</string>\n    <string name=\"stun_server_help\">Um URI do servidor STUN/TURN na forma de scheme:host[:porta][\\?transport=udp|tcp]. Onde o scheme é \\'stun\\', \\'stuns\\', \\'turn\\', ou \\'turns\\'. O Servidor predefinido de fábrica para o STUN e os protocolos ICE são \\'stun:stun.l.google.com:19302\\' apontando para o servidor STUN público do Google. Não há qualquer servidor STUN predefinido de fábrica.</string>\n    <string name=\"stun_server\">Servidor STUN/TURN</string>\n    <string name=\"media_nat_help\">Selecione o protocolo de NAT transversal de mídia (se houver). Escolhas possíveis são STUN (Session Traversal Utilities para NAT, RFC 5389) e ICE (Interactive Connectivity Establishment, RFC 5245).</string>\n    <string name=\"media_nat\">NAT Transversal de Mídia</string>\n    <string name=\"audio_codecs\">Codecs de Áudio</string>\n    <string name=\"register_help\">Se marcado, o registro é ativado e as solicitações de REGISTRO são enviadas no intervalo determinado pelo Intervalo de registro.</string>\n    <string name=\"register\">Registro</string>\n    <string name=\"invalid_proxy_server_uri\">URI do Servidor Proxy Inválido \\'%1$s\\'</string>\n    <string name=\"sip_uri_of_another_proxy_server\">URI SIP de outro Servidor Proxy</string>\n    <string name=\"sip_uri_of_proxy_server\">URI SIP do Servidor Proxy</string>\n    <string name=\"outbound_proxies_help\">URI SIP de um ou dois proxies que precisam ser usados para enviar solicitações. Se dois forem informados, requisições de REGISTRO serão enviados para ambos e as outras requisições serão enviadas para aquele que respondeu. Se nenhum proxy foi informado, requisições serão enviadas baseadas em DNS NAPTR/SRV/A pesquisa de registro da URI do host do destinatário. Se o host da URI SIP for um endereço IPv6, o endereço precisa ser escrito dentro de colchetes [].\n\\nExemplos:\n\\n • sip:example.com:5061;transport=tls\n\\n • sip:[2001:67c:223:777::10];transport=tcp\n\\n • sip:192.168.43.50:443;transport=wss</string>\n    <string name=\"outbound_proxies\">Próxies de Saída</string>\n    <string name=\"invalid_authentication_password\">Senha de Autenticação Inválida \\'%1$s\\'</string>\n    <string name=\"authentication_password_help\">Senha de Autenticação com até 64 caracteres. Se o Nome de Usuário de Autenticação for informado mas a senha não, ela será solicitada quando o baresip for iniciado.</string>\n    <string name=\"authentication_password\">Senha de Autenticação</string>\n    <string name=\"invalid_authentication_username\">Nome de Usuário de Autenticação Inválido \\'%1$s\\'</string>\n    <string name=\"authentication_username_help\">Nome de usuário de autenticação se a autenticação de solicitações SIP for necessária. O valor padrão é o nome de usuário da conta.</string>\n    <string name=\"authentication_username\">Nome de Usuário de Autenticação</string>\n    <string name=\"invalid_display_name\">Nome de Exibição Inválido\\'%1$s\\'</string>\n    <string name=\"display_name_help\">Nome (se houver) usado em From URI de solicitações de saída.</string>\n    <string name=\"display_name\">Nome de Exibição</string>\n    <string name=\"account\">Conta</string>\n    <string name=\"about_text\"><![CDATA[\n        <h1>Biblioteca Baresip baseado em agente de usuário SIP</h1>\n        <br>\n        <p>Juha Heinanen &lt;jh@tutpro.com&gt;</p>\n        <p>Versão %1$s</p>\n        <br>\n        <h2>Dicas de Uso</h2>\n        <ul>\n            <li>Verifique se os valores padrão nas configurações do baresip atendem\n                 às suas necessidades (toque nos títulos dos itens para obter ajuda).\n            <li>Em seguida, em Contas, crie uma ou mais contas (toque novamente nos títulos dos itens para obter ajuda).</li>\n            <li>O status de registro de uma conta é mostrado com um ponto colorido: verde (registro\n                bem-sucedido), amarelo (registro em andamento), vermelho (falha no registro), branco (registro\n                não foi ativado).</li>\n            <li>O toque no ponto leva diretamente à configuração da conta.</li>\n            <li>O gesto de deslizar para baixo causa o novo registro da conta exibida no momento.</li>\n            <li>Um toque longo na conta exibida no momento ativa ou desativa o registro da conta.</li>\n            <li>O participante da chamada anterior pode ser selecionado novamente tocando no ícone de chamada quando\n                Callee estiver vazio.</li>\n            <li>É possível adicionar pares de chamadas e mensagens aos contatos por meio de toques longos.</li>\n            <li>Também é possível usar toques longos para remover chamadas, bate-papos, mensagens e contatos.</li>\n            <li>Toque/toque longo no ícone do contato para instalar/remover o avatar.</li>\n            <li>Para mais informações, consulte <a href=\"https://github.com/juha-h/baresip-studio/wiki\">Wiki</a>.</li>\n        </ul>\n        <h2>Política de privacidade</h2>\n            <p>A política de privacidade está disponível em<a href=\"https://raw.githubusercontent.com/juha-h/baresip-studio/master/PrivacyPolicy.txt\">here</a>.</p>\n        <br>\n        <h2>Código fonte</h2>\n            <p>O código fonte está disponível no <a href=\"https://github.com/juha-h/baresip-studio\">GitHub</a>,\n            onde os problemas encontrados também podem ser reportados.</p>\n        <br>\n        <h2>Licenças</h2>\n        <ul>\n            <li><b>BSD-3-Clause</b> exceto os seguintes:</li>\n            <li><b>Apache 2.0</b> codecs AMR e segurança TLS</li>\n            <li><b>AGPLv4</b> criptografia de mídia ZRTP</li>\n            <li><b>GNU LGPL 2.1</b> G.722, G.726, e codecs Codec2</li>\n            <li><b>GNU GPLv3</b> codec G.729</li>\n        </ul>\n        ]]></string>\n    <string name=\"about_title\">Sobre o baresip</string>\n    <string name=\"no_cameras\">Você não possui câmeras de vídeo compatíveis.</string>\n    <string name=\"no_video_calls\">Conceda permissão à \\\"Câmera\\\" para fazer ou atender chamadas de vídeo.</string>\n    <string name=\"restart_request\">Solicitação para Reinicialização</string>\n    <string name=\"transfer\">Transferir</string>\n    <string name=\"allow_video\">Aceitar o envio e o recebimento de vídeo com \\'%1$s\\'\\?</string>\n    <string name=\"video_request\">Pedido de Vídeo</string>\n    <string name=\"video_call\">Chamada de vídeo</string>\n    <string name=\"confirmation\">Confirmação</string>\n    <string name=\"video_size_help\">Tamanho dos quadros de vídeo que foram transmitidos (largura x altura)</string>\n    <string name=\"video_size\">Tamanho do Quadro do Vídeo</string>\n    <string name=\"invalid_stun_password\">Senha inválida \\'%1$s\\'</string>\n    <string name=\"stun_password_help\">Senha, caso seja necessário pelo servidor STUN/TURN</string>\n    <string name=\"stun_password\">Senha STUN/TURN</string>\n    <string name=\"invalid_stun_username\">Nome do usuário inválido \\'%1$s\\'</string>\n    <string name=\"stun_username_help\">Nome do usuário, caso seja necessário pelo servidor STUN/TURN</string>\n    <string name=\"stun_username\">Nome do Usuário STUN/TURN</string>\n    <string name=\"video_codecs\">Codecs de Vídeo</string>\n    <string name=\"transfer_request_query\">Você aceita transferir esta chamada para \\'%1$s\\'\\?</string>\n    <string name=\"transfer_failed\">A transferência falhou</string>\n    <string name=\"transfer_destination\">Destino da transferência</string>\n    <string name=\"call_transfer\">Transferência de chamada</string>\n    <string name=\"transfer_request_to\">Chamada de solicitação de transferência para</string>\n    <string name=\"about_text_plus\"><![CDATA[\n        <h1>Biblioteca Baresip baseado em agente de usuário SIP com vídeo chamadas</h1>\n        <p>Juha Heinanen &lt;jh@tutpro.com&gt;</p>\n        <p>Versão %1$s</p>\n        <h2>Dicas de uso</h2>\n        <ul>\n            <li>Verifique se os valores padrão nas configurações do baresip+\\ atendem às suas necessidades\n                (toque nos títulos dos itens para obter ajuda).</li>\n            <li>Então em Contas, crie uma ou mais contas (novamente clique nos títulos para ajuda).</li>\n            <li>O estado de uma conta é mostrado com um ponto colorido: verde (registro\n                bem sucedido), amarelo (registro em progresso), vermelho (registro falhou), branco\n                (registro não foi ativado).</li>\n            <li>Toque nos três pontos para ir direto para a configuração da conta.</li>\n            <li>O gesto de deslizar para baixo causa o novo registro da conta exibida no momento.</li>\n            <li>Um toque longo na conta exibida no momento ativa ou desativa o registro da conta.</li>\n            <li>Gestos para a esquerda/direita alternam as contas.</li>\n            <li>O participante da chamada anterior pode ser selecionado novamente tocando no ícone\n                de chamada quando o \"Callee\" estiver vazio.</li>\n            <li>Os pares de chamadas e mensagens podem ser adicionados aos contatos por meio de toques longos.</li>\n            <li>Os toques longos também podem ser usados para remover chamadas, bate-papos, mensagens e contatos.</li>\n            <li>Toque/toque longo no ícone do contato pode ser usado para instalar/remover o avatar da imagem.</li>\n            <li>Consulte a <a href=\"https://github.com/juha-h/baresip-studio/wiki\">Wiki</a> para obter\n                mais informações.</li>\n        </ul>\n        <h2>Problemas conhecidos</h2>\n        <ul>\n            <li>Nas chamadas com vídeo, o dispositivo precisa ser mantido no modo paisagem,\n                girado 90 graus para a esquerda em relação à orientação retrato.</li>\n            <li>A própia visualização automática não é exibida corretamente quando o\n                fluxo de vídeo é somente envio.</li>\n        </ul>\n        <h2>Política de privacidade</h2>\n            A política de privacidade está disponível <a href=\"https://raw.githubusercontent.com/juha-h/baresip-studio/video/PrivacyPolicy.txt\">aqui</a>.\n        <h2>Código fonte</h2>\n        O código fonte está disponível no <a href=\"https://github.com/juha-h/baresip-studio\">GitHub</a>,\n        onde os problemas encontrados também podem ser reportados.\n        <h2>Licenças</h2>\n        <ul>\n            <li><b>Cláusula BSD-3</b> exceto os seguintes:</li>\n            <li><b>Apache 2.0</b>, codecs AMR e segurança TLS</li>\n            <li><b>AGPLv4</b>, criptografia de mídia ZRTP</li>\n            <li><b>GNU LGPL 2.1</b> G.722, G.726, e codecs Codec2</li>\n            <li><b>GNU GPLv3</b> codec G.729</li>\n            <li><b>GNU GPLv2</b> codecs H.264 e H.265</li>\n            <li><b>AOMedia</b> codec AV1</li>\n        </ul>\n        ]]></string>\n    <string name=\"about_title_plus\">Sobre o baresip+</string>\n    <string name=\"sip_trace_help\">Se selecionado em conjunto com Debug, as mensagens Logcat incluem também o pedido SIP e o rastreamento da resposta. A opção não é ativada automaticamente durante a inicialização do baresip.</string>\n    <string name=\"sip_trace\">Rastreio SIP</string>\n    <string name=\"allow_video_recv\">Aceitar o recebimento de vídeo a partir do \\'%1$s\\'\\?</string>\n    <string name=\"allow_video_send\">Aceitar o envio de vídeo para \\'%1$s\\'\\?</string>\n    <string name=\"missed_call_from\">Ligação perdida de</string>\n    <string name=\"dark_theme_help\">Impor o uso do tema escuro na tela</string>\n    <string name=\"dark_theme\">Tema Escuro</string>\n    <string name=\"android_contact_help\">Se for marcado, este contato será adicionado nos contatos do Android.</string>\n    <string name=\"verify_server_help\">Se estiver marcado, o baresip verifica os certificados TLS do SIP User Agent e o SIP Proxy Servers quando o transporte TLS for usado.</string>\n    <string name=\"verify_server\">Verifique os certificados do servidor</string>\n    <string name=\"dtmf_info\">Solicitações INFO SIP</string>\n    <string name=\"dtmf_inband\">Eventos na Banda RTP</string>\n    <string name=\"dtmf_mode_help\">Selecione como os tons DTMF 0–9, #, *, e A-D serão enviados.</string>\n    <string name=\"dtmf_mode\">Modo DTMF</string>\n    <string name=\"reset\">Redefinir</string>\n    <string name=\"reset_config_alert\">Tem certeza de que deseja redefinir as configurações para os valores predefinidos de fábrica\\?</string>\n    <string name=\"no_network\">Nenhuma conexão de rede!</string>\n    <string name=\"missed_calls_count\">%1$d chamadas perdidas</string>\n    <string name=\"missed_calls\">Chamadas perdidas</string>\n    <string name=\"avatar_image\">Imagem do perfil</string>\n    <string name=\"no_restore\">Você não pode restaurar o backup sem a permissão de \\\"Armazenamento\\\".</string>\n    <string name=\"no_backup\">Você não pode criar backups sem a permissão de \\\"Armazenamento\\\".</string>\n    <string name=\"battery_optimizations\">Otimizações da bateria</string>\n    <string name=\"battery_optimizations_help\">Desative as otimizações da bateria (recomendado) caso queira reduzir a probabilidade do Android restringir o acesso do baresip à rede ou coloque o baresip no modo de espera.</string>\n    <string name=\"lost\">Perda</string>\n    <string name=\"average_rate\">Taxa média: %1$s (Kbits/s)</string>\n    <string name=\"packets\">Pacotes</string>\n    <string name=\"call_is_on_hold\">Chamada em espera</string>\n    <string name=\"jitter\">Variação: %1$s (ms)</string>\n    <string name=\"audio_settings\">Configurações do áudio</string>\n    <string name=\"calls_duration\">Duração</string>\n    <string name=\"peer\">Par</string>\n    <string name=\"call_details\">Detalhes da chamada</string>\n    <string name=\"direction\">Direção</string>\n    <string name=\"time\">Hora</string>\n    <string name=\"telephony_provider\">Operadora de telefonia</string>\n    <string name=\"invalid_sip_uri_hostpart\">Parte inválida do host SIP URI \\'%1$s\\'</string>\n    <string name=\"sip_or_tel_uri\">SIP ou tel. URI</string>\n    <string name=\"user_domain_or_number\">usuário@domínio ou o número de telefone</string>\n    <string name=\"telephony_provider_help\">SIP URI host é parcialmente usado nas chamadas para números de telefone. O padrão de fábrica é domínio da conta. Caso não seja informada, essa conta não pode ser usada para ligar para os números de telefone.</string>\n    <string name=\"invalid_sip_or_tel_uri\">SIP ou tel. URI inválido \\'%1$s\\'</string>\n    <string name=\"contacts_help\">Escolhe se serão usados os contatos do baresip, os contatos do Android ou ambos. Se ambos forem usados e houver um contato com o mesmo nome em ambos os contatos, o contato baresip será escolhido.</string>\n    <string name=\"both\">Ambos</string>\n    <string name=\"no_android_contacts\">Você não pode acessar os contatos do Android sem a permissão \\\"Contatos\\\".</string>\n    <string name=\"blind\">Cego</string>\n    <string name=\"attended\">Participou</string>\n    <string name=\"anonymous\">Anônimo</string>\n    <string name=\"unknown\">Desconhecido</string>\n    <string name=\"diverted_by_dots\">Desviado por …</string>\n    <string name=\"consent_request\">Solicitação de consentimento</string>\n    <string name=\"contacts_consent\">Se os contatos do Android forem escolhidos, eles podem ser usados em chamadas e mensagens como referências a URIs SIP e tel. O aplicativo baresip não armazena contatos do Android nem os compartilha com ninguém. Para tornar os contatos do Android disponíveis no baresip, o Google exige que você aceite seu uso conforme descrito aqui e na <a href=\"https://raw.githubusercontent.com/juha-h/baresip-studio/master/PrivacyPolicy.txt\">política de privacidade</a>.</string>\n    <string name=\"country_code\">Código do país</string>\n    <string name=\"country_code_help\">O código do país E.164 desta conta. Se durante o recebimento da chamada ou mensagem do usuário a parte URI recebida tiver um número de telefone que não começa com o sinal \\'+\\' e se a busca de contato falhar, o número será prefixado com este código do país e a busca pelo contato será testada novamente. Se o número de telefone começar com um único dígito \\'0\\', o dígito \\'0\\' será removido antes que o número seja prefixado.</string>\n    <string name=\"invalid_country_code\">Código do país inválido \\'%1$s\\'</string>\n    <string name=\"account_nickname_help\">Apelido (se houver) é utilizado para identificar essa conta dentro do aplicativo baresip.</string>\n    <string name=\"invalid_account_nickname\">Apelido inválido da conta \\'%1$s\\'</string>\n    <string name=\"non_unique_account_nickname\">O apelido \\'%1$s\\' já existe</string>\n    <string name=\"nickname\">Apelido</string>\n    <string name=\"rtcp_mux\">Multiplexagem RTCP</string>\n    <string name=\"rtcp_mux_help\">Se marcada, os pacotes RTP e RTCP são multiplexados numa única porta (RFC 5761).</string>\n    <string name=\"no_notifications\">Você não pode usar este aplicativo sem a permissão \\\"Notificações\\\".</string>\n    <string name=\"audio_focus_denied\">Foco de áudio negado!</string>\n    <string name=\"permissions_rationale\">Justificativa das permissões</string>\n    <string name=\"audio_permissions\">O baresip precisa da permissão \\\"Microfone\\\" para realizar chamadas de voz, da permissão \\\"Dispositivos próximos\\\" para fazer a detecção do microfone/alto-falante Bluetooth e a permissão \\\"Notificações\\\" para postar notificações.</string>\n    <string name=\"audio_and_video_permissions\">O baresip+ precisa da permissão \\\"Microfone\\\" para realizar chamadas de voz, da permissão \\\"Câmera\\\" para realizar chamadas de vídeo. da permissão \\\"Dispositivos próximos\\\" para fazer a detecção do microfone/alto-falante Bluetooth caso queira usar tal dispositivo, assim como a permissão \\\"Notificações\\\" para postar notificações.</string>\n    <string name=\"reg_int\">Intervalo de registro</string>\n    <string name=\"invalid_reg_int\">Intervalo de registro inválido \\'%1$s\\'</string>\n    <string name=\"reg_int_help\">Informa com que frequência (em segundos) o baresip envia solicitações REGISTER. Os valores válidos são entre 60 a 3600.</string>\n    <string name=\"no_telephony_provider\">A conta \\'%1$s\\' não tem provedor telefonico</string>\n    <string name=\"choose_destination_uri\">Escolha a URI de destino</string>\n    <string name=\"rec_in_call\">A gravação só pode ser ativada ou desativada quando não estiver em chamada</string>\n    <string name=\"address_family\">Família de endereço</string>\n    <string name=\"address_family_help\">Escolhe quais endereços IP o baresip está usando. Se for escolhido IPv4 ou IPv6, o baresip usa apenas endereços IPv4 ou IPv6. Se nenhum for escolhido, o baresip usará os endereços IPv4 e IPv6.</string>\n    <string name=\"audio_delay\">Atraso do áudio</string>\n    <string name=\"audio_delay_help\">Tempo (em milissegundos) para esperar o áudio de quem chama quando a chamada for estabelecida. Defina um valor mais alto caso perca o áudio de quem chama no início da chamada.</string>\n    <string name=\"invalid_audio_delay\">Atraso inválido de áudio \\'%1$s\\'. Os valores válidos ficam entre 100 a 3000.</string>\n    <string name=\"call_auto_rejected\">Chamada rejeitada automaticamente de %1$s</string>\n    <string name=\"default_phone_app\">Aplicativo de telefone padrão</string>\n    <string name=\"dialer_role_not_available\">A função de discador não está disponível</string>\n    <string name=\"default_phone_app_help\">Se marcado, o baresip será o aplicativo de telefone padrão. Não marque caso o seu dispositivo também precise lidar com outras chamadas ou mensagens SIP.</string>\n    <string name=\"redirect_mode\">Modo de redirecionamento</string>\n    <string name=\"redirect_request\">Solicitação de redirecionamento</string>\n    <string name=\"redirect_notice\">Redirecionamento automático para \\'%1$s\\'\\\\</string>\n    <string name=\"redirect_request_query\">Você aceita o redirecionamento de chamada para \\'%1$s\\'\\?</string>\n    <string name=\"redirect_mode_help\">Seleciona caso a solicitação de redirecionamento da chamada seja seguida automaticamente ou se caso uma confirmação seja solicitada.</string>\n    <string name=\"tone_country\">Tom do país</string>\n    <string name=\"tone_country_help\">Toque da chamada do país, espera e tom de chamada ocupada</string>\n    <string name=\"rel_100_help\">Se marcado, indica suporte para respostas provisórias confiáveis (RFC 3262).</string>\n    <string name=\"rel_100\">Respostas provisórias confiáveis</string>\n    <string name=\"favorite\">Favorito</string>\n    <string name=\"appear_on_top_permission\">A inicialização automática precisa aparecer no top das permissões.</string>\n    <string name=\"restore_unzip_failed\">Falha ao restaurar os dados do aplicativo. A versão 14 e superior do Android não permite a restauração de dados cujo backup foi feito %1$s antes da versão %2$s.</string>\n    <string name=\"dtmf_auto\">INFO RTP ou SIP em banda</string>\n    <string name=\"speaker_phone\">Alto-falante do telefone</string>\n    <string name=\"speaker_phone_help\">Se marcada, o viva-voz do telefone é ligado automaticamente quando a chamada for iniciada.</string>\n    <string name=\"invalid_fps\">Quadros por segundo inválidos \\'%1$d\\'</string>\n    <string name=\"video_fps\">Quadros de vídeo por segundo</string>\n    <string name=\"video_fps_help\">Taxa de quadros de vídeo que será oferecida durante o handshake SDP. Os valores válidos são de 10 a 30.</string>\n    <string name=\"call_request\">Pedido de chamada</string>\n    <string name=\"call_request_query\">Aceita o pedido de chamada para \\'%1$s\\'?</string>\n    <string name=\"user_agent\">Agente do usuário</string>\n    <string name=\"user_agent_help\">Valor do campo de cabeçalho User-Agent personalizado da solicitação/resposta SIP</string>\n    <string name=\"invalid_user_agent\">Valor do campo do cabeçalho User-Agent inválido</string>\n    <string name=\"invalid_microphone_gain\">Valor inválido do ganho do microfone</string>\n    <string name=\"microphone_gain\">Ganho do microfone</string>\n    <string name=\"microphone_gain_help\">Multiplique o volume do microfone por esse número decimal. O valor mínimo é 1,0 (padrão de fábrica), que desativa o ganho do microfone. Valores maiores podem afetar negativamente a qualidade do áudio.</string>\n    <string name=\"favorite_help\">Se marcada, o contato será exibido entre os favoritos na parte superior da lista.</string>\n    <string name=\"check_origin\">Verificar Origem</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-ro/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"about_title\">Despre baresip</string>\n    <string name=\"account\">Cont</string>\n    <string name=\"display_name\">Nume afișat</string>\n    <string name=\"display_name_help\">Nume (dacă este cazul) utilizat în adresa solicitărilor de conectare.</string>\n    <string name=\"authentication_username\">Nume utilizator de autentificare</string>\n    <string name=\"authentication_username_help\">Numele de utilizator dacă este necesar pentru proxy de ieșire.</string>\n    <string name=\"authentication_password\">Parolă de autentificare</string>\n    <string name=\"authentication_password_help\">Parola de autentificare dacă este necesar pentru proxy de ieșire.</string>\n    <string name=\"outbound_proxies\">Proxy-uri de ieșire</string>\n    <string name=\"sip_uri_of_proxy_server\">Adresă SIP server proxy</string>\n    <string name=\"sip_uri_of_another_proxy_server\">Adresa SIP a altui server proxy</string>\n    <string name=\"register\">Înregistrare</string>\n    <string name=\"audio_codecs\">Codec-uri audio</string>\n    <string name=\"media_nat\">Traversare NAT pentru media</string>\n    <string name=\"stun_server\">Server STUN</string>\n    <string name=\"media_encryption\">Criptare media</string>\n    <string name=\"voicemail_uri\">Adresă căsuță vocală</string>\n    <string name=\"default_account\">Cont implicit</string>\n    <string name=\"default_account_help\">Dacă este bifată, acest cont este selectat atunci când baresip este pornit.</string>\n    <string name=\"accounts\">Conturi</string>\n    <string name=\"invalid_aor\">utilizator@domeniu[:port][;transport=udp|tcp|tls] \\'%1$s\\' este invalid</string>\n    <string name=\"account_exists\">Contul \\'%1$s\\' exista deja.</string>\n    <string name=\"account_allocation_failure\">Nu s-a putut aloca un cont nou.</string>\n    <string name=\"encrypt_password\">Parolă criptare</string>\n    <string name=\"decrypt_password\">Parolă decriptare</string>\n    <string name=\"delete_account\">Doriți să ștergeți contul \\'%1$s\\'\\?</string>\n    <string name=\"transfer_request\">Cerere transfer apel către</string>\n    <string name=\"call_history\">Istoric apeluri</string>\n    <string name=\"call\">Apel</string>\n    <string name=\"calls_calls\">apeluri</string>\n    <string name=\"calls_call\">apel</string>\n    <string name=\"calls_add_delete_question\">Doriți să adăugați \\'%1$s\\' la contacte sau să ștergeți %2$s din istoricul apelurilor\\?</string>\n    <string name=\"calls_delete_question\">Doriți să ștergeți \\'%1$s\\' %2$s din istoricul apelurilor\\?</string>\n    <string name=\"chat_with\">Discuție cu %1$s</string>\n    <string name=\"new_message\">Mesaj nou</string>\n    <string name=\"long_message_question\">Doriți să ștergeți mesajul sau să adăugați \\'%1$s\\' la contacte\\?</string>\n    <string name=\"short_message_question\">Doriți să ștergeți mesajul\\?</string>\n    <string name=\"add_contact\">Adaugă contact</string>\n    <string name=\"sending_failed\">Trimiterea mesajului a eșuat</string>\n    <string name=\"message_failed\">Eșuat</string>\n    <string name=\"chats\">Istoric discuție</string>\n    <string name=\"today\">Azi</string>\n    <string name=\"you\">Tu</string>\n    <string name=\"new_chat_peer\">Discuție cu un contact nou</string>\n    <string name=\"long_chat_question\">Doriți să ștergeți discuția cu \\'%1$s\\' sau să adăugați la contacte\\?</string>\n    <string name=\"short_chat_question\">Doriți să ștergeți discuția cu \\'%1$s\\'\\?</string>\n    <string name=\"configuration\">Configurație</string>\n    <string name=\"start_automatically\">Pornește automat</string>\n    <string name=\"start_automatically_help\">Dacă este bifată, baresip pornește automat după (re)pornirea dispozitivului.</string>\n    <string name=\"listen_address\">Adresă de conectare</string>\n    <string name=\"dns_servers\">Servere DNS</string>\n    <string name=\"opus_bit_rate\">Rată de eșantionare Opus</string>\n    <string name=\"default_call_volume\">Volum implicit de apel</string>\n    <string name=\"default_call_volume_help\">Dacă este setat, volumul audio de apel implicit între 1–10.</string>\n    <string name=\"debug\">Depanare</string>\n    <string name=\"debug_help\">Mesajele de depanarea și informare sunt scrise în jurnalul dispozitivului.</string>\n    <string name=\"reset_config\">Resetare la setările inițiale</string>\n    <string name=\"reset_config_help\">Dacă este bifată, configurația este resetară la valorile implicite.</string>\n    <string name=\"new_contact\">Contact nou</string>\n    <string name=\"contact_name\">Nume</string>\n    <string name=\"invalid_contact\">Nume de contact invalid \\'%1$s\\'</string>\n    <string name=\"contact_already_exists\">Contactul \\'%1$s\\' deja există.</string>\n    <string name=\"contacts\">Contacte</string>\n    <string name=\"contact_action_question\">Doriți să apelați sau să trimiteți un mesaj către \\'%1$s\\'\\?</string>\n    <string name=\"send_message\">Trimite mesaj</string>\n    <string name=\"contact_delete_question\">Doriți să ștergeți contactul \\'%1$s\\'\\?</string>\n    <string name=\"alert\">Alertă</string>\n    <string name=\"info\">Informații</string>\n    <string name=\"notice\">Notificări</string>\n    <string name=\"cancel\">Anulare</string>\n    <string name=\"ok\">Bine</string>\n    <string name=\"yes\">Da</string>\n    <string name=\"no\">Nu</string>\n    <string name=\"accept\">Acceptă</string>\n    <string name=\"deny\">Refuză</string>\n    <string name=\"add\">Adaugă</string>\n    <string name=\"delete\">Șterge</string>\n    <string name=\"edit\">Editare</string>\n    <string name=\"status\">Stare</string>\n    <string name=\"error\">Eroare</string>\n    <string name=\"about\">Despre</string>\n    <string name=\"quit\">Ieșire</string>\n    <string name=\"outgoing_call_to_dots\">Apel efectuat către …</string>\n    <string name=\"incoming_call_from_dots\">Apel primit de la …</string>\n    <string name=\"dtmf\">DTMF</string>\n    <string name=\"call_info\">Informații apel</string>\n    <string name=\"duration\">Durată %1$d</string>\n    <string name=\"codecs\">Codec-uri</string>\n    <string name=\"rate\">Rată: %1$s</string>\n    <string name=\"voicemail_messages\">Mesaje căsuță vocală</string>\n    <string name=\"you_have\">Aveți</string>\n    <string name=\"one_new_message\">un mesaj nou</string>\n    <string name=\"new_messages\">mesaje noi</string>\n    <string name=\"one_old_message\">un mesaj vechi</string>\n    <string name=\"old_messages\">mesaje vechi</string>\n    <string name=\"and\">și</string>\n    <string name=\"no_messages\">Nu aveți mesaje</string>\n    <string name=\"listen\">Ascultă</string>\n    <string name=\"call_already_active\">Aveți deja un apel activ.</string>\n    <string name=\"registering_failed\">Înregistrarea %1$s a eșuat.</string>\n    <string name=\"verify\">Verificare</string>\n    <string name=\"verify_sas\">Doriți să verificați SAS &lt;%1$s&gt;\\?</string>\n    <string name=\"transfer_request_query\">Acceptați să transferați apelul către \\'%1$s\\'\\?</string>\n    <string name=\"call_failed\">Apelul a eșuat.</string>\n    <string name=\"call_closed\">Apel închis.</string>\n    <string name=\"call_not_secure\">Acest apel NU este securizat!</string>\n    <string name=\"peer_not_verified\">Acest apel este SECURIZAT, dar contactul NU este verificat!</string>\n    <string name=\"call_is_secure\">Acest apel este SECURIZAT și contactul este VERIFICAT! Doriți să anulați verificarea\\?</string>\n    <string name=\"unverify\">Anulare verificare</string>\n    <string name=\"register_help\">Se activeaza înregistrarea și cererile REGISTER vor fi trimise la fiecare 12 minute.</string>\n    <string name=\"media_nat_help\">Selectare protocol traversare NAT (dacă există). Posibile opțiuni: STUN (Session Traversal Utilities for NAT, RFC 5389) și ICE (Interactive Connectivity Establishment, RFC 5245).</string>\n    <string name=\"stun_server_help\">Un server STUN este de forma gazdă[:port]. Setarea implicită este \\'stun.l.google.com:19302\\', ce folosește un server public STUN găzduit de Google. Momentan nu se poate seta un nume de utilizator și parolă.</string>\n    <string name=\"voicemain_uri_help\">Adresă SIP pentru verificarea mesajelor din căsuța vocală. Dacă nu este completată, nu se va face abonarea (Message Waiting Indications) la căsuța vocală.</string>\n    <string name=\"start_failed\">Baresip nu a putut porni! Aceasta se poate datora unei setări invalide. Verificați adresa de ascultare, fișierul certificat TLS și fișierul TLS al CA. Apoi reporniți baresip.</string>\n    <string name=\"tls_certificate_file\">Fișier certificat TLS</string>\n    <string name=\"tls_ca_file\">Fișier CA TLS</string>\n    <string name=\"read_cert_error\">Nu s-a putut citi fișierul \\'cert.pem\\' din dosarul Download.</string>\n    <string name=\"read_ca_certs_error\">Nu s-a putut citi fișierul \\'ca_certs.crt\\' din dosarul Download.</string>\n    <string name=\"opus_packet_loss\">Pachete pierdute Opus</string>\n    <string name=\"invalid_listen_address\">Adresă de ascultare invalidă</string>\n    <string name=\"invalid_dns_servers\">Servere DNS invalide</string>\n    <string name=\"failed_to_set_dns_servers\">Nu s-au putut seta serverele DNS</string>\n    <string name=\"invalid_opus_bitrate\">Rată de biți Opus invalidă</string>\n    <string name=\"invalid_opus_packet_loss\">Procentaj de pachete pierdute Opus invalid</string>\n    <string name=\"answer_mode\">Mod de răspuns</string>\n    <string name=\"answer_mode_help\">Selectează modul de răspuns al apelurilor primite.</string>\n    <string name=\"manual\">Manual</string>\n    <string name=\"auto\">Automat</string>\n    <string name=\"restart\">Repornire</string>\n    <string name=\"opus_bit_rate_help\">Rata medie maximă de biți utilizată de fluxul audio Opus. Valorile valide sunt 6000-510000. Valoarea implicită din fabrică este 28000.</string>\n    <string name=\"disable_history\">Dezactivează</string>\n    <string name=\"enable_history\">Activează</string>\n    <string name=\"backup\">Copie de rezervă</string>\n    <string name=\"restore\">Restaurare</string>\n    <string name=\"audio_modules_help\">Codecurile audio furnizate de modulele bifate sunt disponibile pentru utilizare de către conturi.</string>\n    <string name=\"invalid_display_name\">Nume afișat nevalid \\\"%1$s\\'</string>\n    <string name=\"about_title_plus\">Despre baresip+</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-ru/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"about\">О программе</string>\n    <string name=\"add\">Добавить</string>\n    <string name=\"add_contact\">Добавить контакт</string>\n    <string name=\"alert\">Внимание</string>\n    <string name=\"audio_codecs\">Аудио кодеки</string>\n    <string name=\"authentication_password\">Пароль аутентификации</string>\n    <string name=\"authentication_username\">Имя пользователя</string>\n    <string name=\"auto\">Авто</string>\n    <string name=\"call\">Вызов</string>\n    <string name=\"call_already_active\">У вас уже есть активный звонок.</string>\n    <string name=\"call_closed\">Вызов закончен</string>\n    <string name=\"call_failed\">Вызов не удался</string>\n    <string name=\"call_history\">История звонков</string>\n    <string name=\"call_info\">Информация о звонке</string>\n    <string name=\"call_is_secure\">Этот вызов БЕЗОПАСЕН, и собеседник ПОДТВЕРЖДЕН! Вы хотите отменить верификацию собеседника\\?</string>\n    <string name=\"calls_add_delete_question\">Вы хитите добавить \\'%1$s\\' в контакты или удалить %2$s из истории звинков?</string>\n    <string name=\"calls_call\">вызов</string>\n    <string name=\"calls_calls\">Вызовы</string>\n    <string name=\"calls_delete_question\">Хотите удалить \\'%1$s\\' %2$s из истории вызовов?</string>\n    <string name=\"cancel\">Отмена</string>\n    <string name=\"chat_with\">Чат с %1$s</string>\n    <string name=\"chats\">История чата</string>\n    <string name=\"codecs\">Кодеки</string>\n    <string name=\"config_restart\">Хотите перегрузить для активации новых установок?</string>\n    <string name=\"configuration\">Настройки</string>\n    <string name=\"contact_action_question\">Хотите позвонить или послать сообщение \\'%1$s\\'?</string>\n    <string name=\"contact_already_exists\">Контакт \\'%1$s\\' уже существует.</string>\n    <string name=\"contact_delete_question\">Хотите удалить контакт \\'%1$s\\'?</string>\n    <string name=\"contact_name\">Имя</string>\n    <string name=\"contacts\">Контакты</string>\n    <string name=\"debug\">Отладка</string>\n    <string name=\"decrypt_password\">Расшифровать пароль</string>\n    <string name=\"default_account\">Аккаунт по умолчанию</string>\n    <string name=\"default_account_help\">Если отмечено, эта учетная запись будет выбрана при запуске baresip.</string>\n    <string name=\"default_call_volume\">Громкость вызова по умолчанию</string>\n    <string name=\"default_call_volume_help\">Если установлено громкость вызова 1-10.</string>\n    <string name=\"delete\">Удалить</string>\n    <string name=\"delete_account\">Хотите удалить аккаунт \\'%1$s\\'?</string>\n    <string name=\"delete_chats_alert\">Хотите удалить историю чата с \\'%1$s\\'?</string>\n    <string name=\"delete_history_alert\">Хотите удалить историю вызовов с \\'%1$s\\'?</string>\n    <string name=\"deny\">Запрет</string>\n    <string name=\"disable_history\">Отключить</string>\n    <string name=\"display_name\">Отображаемое имя</string>\n    <string name=\"display_name_help\">Имя используемое в From URI исходящих вызовов.</string>\n    <string name=\"dns_servers\">ДНС сервера</string>\n    <string name=\"dns_servers_help\">Список адресов DNS-серверов, разделенных запятыми. Если не указан, адреса DNS-серверов получаются из системы динамически. Каждый DNS-адрес имеет форму «ip:порт» или «ip». Если порт не указан, по умолчанию используется значение 53. Если ip является адресом IPv6 и также указан порт, ip должен быть записан в квадратных скобках []. Например, список «8.8.8.8:53,[2001:4860:4860::8888]:53» указывает на IPv4- и IPv6-адреса общедоступных DNS-серверов Google.</string>\n    <string name=\"duration\">Длительность: %1$d (с)</string>\n    <string name=\"edit\">Редактировать</string>\n    <string name=\"enable_history\">Включить</string>\n    <string name=\"encrypt_password\">Шифровать пароль</string>\n    <string name=\"error\">Ошибка</string>\n    <string name=\"failed_to_load_module\">Не могу загрузить модуль.</string>\n    <string name=\"failed_to_set_dns_servers\">Не могу установиь ДНС сервера</string>\n    <string name=\"incoming_call_from_dots\">Вызов от …</string>\n    <string name=\"info\">Информация</string>\n    <string name=\"invalid_contact\">Неверное имя контакта \\'%1$s\\'</string>\n    <string name=\"invalid_dns_servers\">Неверный ДНС сервер</string>\n    <string name=\"invalid_listen_address\">Неверный адрес</string>\n    <string name=\"invalid_opus_bitrate\">Неверный Opus bitrate</string>\n    <string name=\"send_message\">Послать сообщение</string>\n    <string name=\"sending_failed\">Не удалось отправить сообщение</string>\n    <string name=\"short_chat_question\">Хотите удалить чат с \\'%1$s\\'?</string>\n    <string name=\"short_message_question\">Хотите удалить сообщение?</string>\n    <string name=\"start_automatically\">Запускать автоматически</string>\n    <string name=\"start_automatically_help\">Если выбрано, baresip запускается автоматически после (пере)запуска устройства.</string>\n    <string name=\"status\">Статус</string>\n    <string name=\"voicemail_messages\">Сообщения голосовой почты</string>\n    <string name=\"voicemail_uri\">URI голововой почты</string>\n    <string name=\"yes\">Да</string>\n    <string name=\"you\">Вы</string>\n    <string name=\"you_have\">Вы имеете</string>\n    <string name=\"about_title\">О baresip</string>\n    <string name=\"audio_modules_title\">Аудио модули</string>\n    <string name=\"authentication_password_help\">Пароль аутентификации до 64 символов. Если указано имя пользователя для аутентификации, но нет пароля, он будет запрошен при запуске baresip.</string>\n    <string name=\"backup\">Резервное копирование</string>\n    <string name=\"media_encryption\">Шифрование медиа</string>\n    <string name=\"media_encryption_help\">Выбирает протокол шифрования медиа-транспорта (если есть).\n\\n • ZRTP (рекомендуется) означает, что согласование сквозного шифрования данных ZRTP предпринимается после установления соединения.\n\\n • DTLS-SRTPF означает, что UDP/TLS/RTP/SAVPF предлагается в исходящем вызове и что RTP/SAVP, RTP/SAVPF, UDP/TLS/RTP/SAVP или UDP/TLS/RTP/SAVPF используется, если предлагается во входящем вызов.\n\\n • SRTP-MANDF означает, что RTP/SAVPF предлагается при исходящем вызове и требуется при входящем вызове.\n\\n • SRTP-MAND означает, что RTP/SAVP предлагается при исходящем вызове и требуется при входящем вызове.\n\\n • SRTP означает, что RTP/AVP предлагается в исходящем вызове и что RTP/SAVP или RTP/SAVPF используется, если предлагается во входящем вызове.</string>\n    <string name=\"message_failed\">Неудачно</string>\n    <string name=\"new_account\">SIP URI или Новый аккаунт</string>\n    <string name=\"new_chat_peer\">Новый партнер чата</string>\n    <string name=\"new_contact\">Новый контакт</string>\n    <string name=\"new_message\">Новое сообщение</string>\n    <string name=\"new_messages\">Новые сообщения</string>\n    <string name=\"no\">Нет</string>\n    <string name=\"no_messages\">Вы не имеете сообщений</string>\n    <string name=\"notice\">Оповещение</string>\n    <string name=\"ok\">Ок</string>\n    <string name=\"old_messages\">старые сообщения</string>\n    <string name=\"one_new_message\">одно новое сообщение</string>\n    <string name=\"one_old_message\">одно старое сообщение</string>\n    <string name=\"quit\">Выход</string>\n    <string name=\"rate\">Текущая скорость: %1$s (Кбит/с)</string>\n    <string name=\"register\">Регистрация</string>\n    <string name=\"reset_config\">Сброс к заводским настройкам</string>\n    <string name=\"restart\">Перезагрузка</string>\n    <string name=\"restore\">Восстановление</string>\n    <string name=\"verify\">Запрос на проверку</string>\n    <string name=\"no_calls\">baresip требуется разрешение доступа к микрофону для голосовых вызовов.</string>\n    <string name=\"long_message_question\">Хотите удалить сообщения или добавить пользователя \\'%1$s\\' в контакты?</string>\n    <string name=\"accept\">Принять</string>\n    <string name=\"account\">Профиль</string>\n    <string name=\"account_allocation_failure\">Не могу создать новый аккаунт.</string>\n    <string name=\"account_exists\">Профиль \\'%1$s\\' уже существует.</string>\n    <string name=\"accounts\">Профили</string>\n    <string name=\"and\">и</string>\n    <string name=\"answer_mode\">Режим ответа</string>\n    <string name=\"answer_mode_help\">Выберите как отвечать на входящие вызовы.</string>\n    <string name=\"backed_up\">Данные приложения (за исключением записей) сохранены в файле \\'%1$s\\'. В Android версии 9 этот файл находится в папке Download.</string>\n    <string name=\"call_not_secure\">Вызов не безопасен!</string>\n    <string name=\"listen_address\">Прослушивать адрес</string>\n    <string name=\"outbound_proxies\">Исходящие прокси</string>\n    <string name=\"outgoing_call_to_dots\">Вызов на …</string>\n    <string name=\"registering_failed\">Регистрация `%1$s` не удалась.</string>\n    <string name=\"restored\">Данные приложения восстановлены. Нужна перезагрузка baresip. Перезагрузить\\?</string>\n    <string name=\"today\">Сегодня</string>\n    <string name=\"transfer_request_query\">Принять перевод этого вызова на \\'%1$s\\'\\?</string>\n    <string name=\"transfer_request\">Запрос перевода на</string>\n    <string name=\"unverify\">Отменить подтверждение</string>\n    <string name=\"invalid_opus_packet_loss\">Неверный процент потерь Opus</string>\n    <string name=\"media_nat\">Прохождение медиа NAT</string>\n    <string name=\"media_nat_help\">Выбирает протокол прохождения медиа NAT (если есть). Возможные варианты: STUN (Session Traversal Utilities for NAT, RFC 5389) и ICE (Interactive Connectivity Establishment, RFC 5245).</string>\n    <string name=\"no_cameras\">У вас нет поддерживаемых камер!</string>\n    <string name=\"no_video_calls\">Выдайте разрешение доступа к камере для совершения видеозвонков или ответа на них.</string>\n    <string name=\"restore_failed\">Не удалось восстановить данные приложения. Проверьте, что вы указали правильный пароль и что файл резервной копии принадлежит этому приложению. В Android версии 9 также проверьте Apps → baresip → Permissions → Storage и наличие файла \\'%1$s\\' в папке Download.</string>\n    <string name=\"restart_request\">Запрос на перезапуск</string>\n    <string name=\"backup_failed\">Не удалось создать резервную копию данных приложения в файл \\'%1$s\\'. Проверьте Приложения → baresip → Разрешения → Хранилище.</string>\n    <string name=\"peer_not_verified\">Этот вызов БЕЗОПАСНЫЙ, но собеседник НЕ проверен!</string>\n    <string name=\"transfer\">Передача</string>\n    <string name=\"verify_sas\">Вы хотите проверить SAS &lt;%1$s&gt;\\?</string>\n    <string name=\"start_failed\">Baresip не запустился. Это может быть из-за неверного значения настроек. Проверьте адрес прослушивания, файл сертификата TLS и файл CA TLS. Затем перезапустите baresip.</string>\n    <string name=\"listen\">Слушать</string>\n    <string name=\"call_info_not_available\">Нет информации</string>\n    <string name=\"dtmf\">DTMF (тональный)</string>\n    <string name=\"allow_video\">Разрешить передачу и приём видео с \\'%1$s\\'\\?</string>\n    <string name=\"video_request\">Видео запрос</string>\n    <string name=\"video_call\">Видеозвонок</string>\n    <string name=\"confirmation\">Подтверждение</string>\n    <string name=\"read_ca_certs_error\">Не удалось прочитать файл \\'ca_certs.crt\\'.</string>\n    <string name=\"read_cert_error\">Не удалось прочитать файл \\'cert.pem\\'.</string>\n    <string name=\"reset_config_help\">Если этот флажок установлен, настройки сбрасываются до значений по умолчанию.</string>\n    <string name=\"debug_help\">При отметке отправляет отладочные и информационные сообщения в Logcat.</string>\n    <string name=\"video_size_help\">Размер передаваемых видеокадров (ширина x высота)</string>\n    <string name=\"video_size\">Размер кадра видео</string>\n    <string name=\"opus_packet_loss_help\">Ожидаемый процент потери пакетов аудиопотока Opus, от 0 до 100. По умолчанию 1. Значение 0 также выключает Opus Forward Error Correction (FEC).</string>\n    <string name=\"opus_packet_loss\">Ожидаемая потеря пакетов Opus</string>\n    <string name=\"opus_bit_rate_help\">Средняя максимальная скорость передачи данных, используемая в аудиопотоке Opus. Допустимые значения: 6000-510000. Значение по умолчанию - 28000.</string>\n    <string name=\"opus_bit_rate\">Скорость передачи данных Opus</string>\n    <string name=\"audio_modules_help\">Аудиокодеки, предоставленные выбранными модулями, доступны для использования в аккаунтах.</string>\n    <string name=\"tls_ca_file_help\">Если флажок установлен, то был или будет загружен файл, содержащий TLS-сертификаты таких центров сертификации, которые не включены в ОС Android. В Android версии 9 файл под названием \\'ca_certs.crt\\' загружается из папки Download.</string>\n    <string name=\"tls_ca_file\">Файл TLS CA</string>\n    <string name=\"tls_certificate_file_help\">Если флажок установлен, загружен или будет загружен файл, содержащий TLS-сертификат и закрытый ключ данного экземпляра baresip. В Android версии 9 файл под названием \\'cert.pem\\' загружается из папки Download. В целях безопасности удалите этот файл после загрузки.</string>\n    <string name=\"tls_certificate_file\">Файл сертификата TLS</string>\n    <string name=\"listen_address_help\">IP-адрес и порт вида \\'адрес:порт\\', по которым baresip прослушивает входящие SIP-запросы. Если IP-адрес является IPv6-адресом, он должен быть написан в скобках []. IPv4-адрес 0.0.0.0 или IPv6-адрес [::] заставляет baresip прослушивать все доступные адреса. Если оставить пустым (заводское значение по умолчанию), baresip будет прослушивать произвольный порт на всех доступных адресах.</string>\n    <string name=\"long_chat_question\">Вы хотите удалить чат с собеседником \\'%1$s\\' или добавить собеседника в контакты\\?</string>\n    <string name=\"invalid_aor\">Недействительный user@domain[:port][;transport=udp|tcp|tls] \\'%1$s\\'</string>\n    <string name=\"accounts_help\">SIP URI или новая учётная в форме: &lt;пользователь&gt;@&lt;домен&gt;[:&lt;порт&gt;][;transport=udp|tcp|tls]. Если порт указан, а транспортный протокол отсутствует, по умолчанию используется протокол UDP. Если же указан транспортный протокол и отсутствует порт, по умолчанию используется порт 5060 или 5061 (TLS). Если ни один из них не указан и не используется исходящий прокси-сервер, регистратор учётной записи (если есть) определяется исключительно на основе DNS-информации домена.</string>\n    <string name=\"voicemain_uri_help\">SIP URI для проверки сообщений голосовой почты. Если оставить поле пустым, сообщения голосовой почты (индикаторы ожидающего сообщения) не задействуются.</string>\n    <string name=\"manual\">Вручную</string>\n    <string name=\"invalid_stun_password\">Неверный пароль \\'%1$s\\'</string>\n    <string name=\"stun_password_help\">Пароль, если требуется сервером STUN/TURN</string>\n    <string name=\"stun_password\">Пароль STUN/TURN</string>\n    <string name=\"invalid_stun_username\">Недействительное имя пользователя \\'%1$s\\'</string>\n    <string name=\"stun_username_help\">Имя пользователя, если требуется сервером STUN/TURN</string>\n    <string name=\"stun_username\">Имя пользователя STUN/TURN</string>\n    <string name=\"invalid_stun_server\">Недействительный URI сервера STUN/TURN \\'%1$s\\'</string>\n    <string name=\"stun_server_help\">URI сервера STUN/TURN в виде схема:хост[:порт][\\?transport=udp|tcp], где схема — \\'stun\\', \\'turn\\' или \\'turns\\'. STUN-сервер по умолчанию для протоколов STUN и ICE — \\'stun:stun.l.google.com:19302\\' — общедоступный STUN-сервер Google. TURN-сервер по умолчанию не используется.</string>\n    <string name=\"stun_server\">STUN/TURN сервер</string>\n    <string name=\"video_codecs\">Видео кодеки</string>\n    <string name=\"register_help\">При отметке включается регистрация и REGISTER-запросы отправляются с указанным интервалом.</string>\n    <string name=\"invalid_proxy_server_uri\">Недействительный URI прокси-сервера \\'%1$s\\'</string>\n    <string name=\"sip_uri_of_another_proxy_server\">SIP URI другого прокси-сервера</string>\n    <string name=\"sip_uri_of_proxy_server\">SIP URI прокси-сервера</string>\n    <string name=\"about_text\">&lt;h1&gt;SIP-клиент на основе библиотеки Baresip&lt;/h1&gt;\\n&lt;p&gt;Juha Heinanen &amp;lt;jh@tutpro.com&amp;gt;&lt;/p&gt;\\n&lt;p&gt;Версия %1$s&lt;/p&gt;\\n&lt;h2&gt;Подсказки по использованию&lt;/h2&gt;\\n&lt;ul&gt;\\n&lt;li&gt;Убедитесь, что значения параметров по умолчанию соответствуют вашим потребностям (касайтесь их названий для получения справки).&lt;/li&gt;\\n&lt;li&gt;Затем создайте одну или несколько учётных записей (опять же касайтесь заголовков элементов для получения справки).&lt;/li&gt;\\n&lt;li&gt;Новая учётная запись может частично настраиваться автоматически. Смотрите подробности в &lt;a href=https://github.com/juha-h/baresip-studio/wiki/Automatic-Account-Configuration&gt;вики&lt;/a&gt;.&lt;/li&gt;\\n&lt;li&gt;Состояние регистрации учётной записи отображается цветной точкой: зелёной (регистрация успешна), жёлтой (регистрация выполняется), красной (регистрация не удалась), белой (регистрация отключена).&lt;/li&gt;\\n&lt;li&gt;Касание точки состояния открывает параметры учётной записи.&lt;/li&gt;\\n&lt;li&gt;Долгое нажатие на значках панели baresip показывает информацию про значки.&lt;/li&gt;\\n&lt;li&gt;Жест смахивания вниз вызывает повторную регистрацию текущей учётной записи.&lt;/li&gt;\\n&lt;li&gt;Долгое нажатие на текущей учётной записи включает или отключет её регистрацию.&lt;/li&gt;\\n&lt;li&gt;Смахивание влево или вправо переключает между учётными записями.&lt;/li&gt;\\n&lt;li&gt;Значок сверху основного экрана переключает динамик.&lt;/li&gt;\\n&lt;li&gt;Значки снизу основного экрана открывают голосовую почту (если её URI указан в учётной записи), контакты, сообщения и историю вызовов, а также позволяют переключаться между цифровой и буквенно-цифровой клавиатурами.&lt;/li&gt;\\n&lt;li&gt;Предыдущего собеседника можно выбрать касанием значка вызова при пустом поле «Вызываемый».&lt;/li&gt;\\n&lt;li&gt;Собеседников в вызовах и сообщениях можно добавлять в контакты долгими нажатиями.&lt;/li&gt;\\n&lt;li&gt;Также долгие нажатия можно использовать для удаления вызовов, чатов, сообщений и контактов.&lt;/li&gt;\\n&lt;li&gt;Долгое нажатие на аудио кодеке позволяет включать или отключать его.&lt;/li&gt;\\n&lt;li&gt;Долгое нажатие значка контакта можно использовать для установки/удаления изображения аватара.&lt;/li&gt;\\n&lt;/ul&gt;\\n&lt;h2&gt;Политика конфиденциальности&lt;/h2&gt;\\n&lt;ul&gt;\\n&lt;li&gt;Политика конфиденциальности доступна &lt;a href=https://raw.githubusercontent.com/juha-h/baresip-studio/master/PrivacyPolicy.txt&gt;здесь&lt;/a&gt;.&lt;/li&gt;\\n&lt;/ul&gt;\\n&lt;h2&gt;Исходный код&lt;/h2&gt; Исходный код доступен на &lt;a href=https://github.com/juha-h/baresip-studio&gt;GitHub&lt;/a&gt;, там же можно сообщить о проблемах.\\n&lt;h2&gt;Лицензии&lt;/h2&gt;\\n&lt;ul&gt;\\n&lt;li&gt;&lt;b&gt;BSD-3-Clause&lt;/b&gt;, за исключением:&lt;/li&gt;\\n&lt;li&gt;&lt;b&gt;Apache 2.0&lt;/b&gt; Кодеки AMR и защита TLS&lt;/li&gt;\\n&lt;li&gt;&lt;b&gt;AGPLv4&lt;/b&gt; ZRTP-шифрофание&lt;/li&gt;\\n&lt;li&gt;&lt;b&gt;LGPL 2.1&lt;/b&gt; Кодеки G.722, G.726 и Codec2&lt;/li&gt;\\n&lt;li&gt;&lt;b&gt;GNU GPLv3&lt;/b&gt; Кодек G.729&lt;/li&gt;\\n&lt;/ul&gt;</string>\n    <string name=\"outbound_proxies_help\">SIP URI одного или двух прокси, которые необходимо использовать при отправке запросов. Если задано два, запросы РЕГИСТРАЦИИ отправляются обоим, а другие запросы отправляются тому, кто отвечает. Если исходящий прокси-сервер не указан, запросы отправляются на основе поиска записи DNS NAPTR/SRV/A для URI хоста вызываемого объекта. Если hostpart SIP URI является адресом IPv6, адрес должен быть записан в скобках [].\n\\nПримеры:\n\\n • sip:fooexample.com:50601;transport=tls\n\\n • sip:[2001:67c:223:777::10]:5060;transport=tcp\n\\n • sip:192.168.43.50:443;transport=wss</string>\n    <string name=\"invalid_authentication_username\">Неверное имя пользователя для аутентификации \\'%1$s\\'</string>\n    <string name=\"authentication_username_help\">Имя пользователя для аутентификации, если требуется аутентификация SIP-запросов. Значение по умолчанию - имя пользователя учетной записи.</string>\n    <string name=\"invalid_display_name\">Неверное отображаемое имя \\'%1$s\\'</string>\n    <string name=\"invalid_authentication_password\">Неверный пароль аутентификации \\'%1$s\\'</string>\n    <string name=\"transfer_failed\">Ошибка передачи</string>\n    <string name=\"transfer_destination\">Переадресовать на</string>\n    <string name=\"call_transfer\">Переадресация вызова</string>\n    <string name=\"transfer_request_to\">Переадресовать вызов</string>\n    <string name=\"about_text_plus\"><![CDATA[\n        <h1>SIP-клиент на основе библиотеки Baresip с видеозвонками</h1>\n        <br>\n        <p>Juha Heinanen &lt;jh@tutpro.com&gt;</p>\n        <p>Version %1$s</p>\n        <br>\n        <h2>Подсказки по использованию</h2>\n        <ul>\n            <li>Проверьте, что значения умолчальных параметров соответствуют вашим потребностям (касайтесь их названий для получения справки).</li>\n            <li>Затем создайте одну или несколько учётных записей (касайтесь заголовков элементов для получения справки).</li>\n            <li>Состояние регистрации учётной записи отображается цветной точкой: зелёная (регистрация успешна), жёлтая (регистрация выполняется), красная (регистрация не удалась), белая (регистрация отключена).</li>\n            <li>Касание точки открывает параметры учётной записи.</li>\n            <li>Долгое нажатие на текущей учётной записи включает или отключает её регистрацию.</li>\n            <li>Жест смахивания вниз вызывает повторную регистрацию текущей учётной записи.</li>\n            <li>Долгое нажатие на текущей учётной записи включает или отключает её регистрацию.</li>\n            <li>Смахивание влево или вправо переключает между учётными записями.</li>\n            <li>редыдущего собеседника можно выбрать касанием значка вызова при пустом поле «Вызываемый».</li>\n            <li>Собеседников в вызовах и сообщениях можно добавлять в контакты долгими нажатиями.</li>\n            <li>Также долгие нажатия можно использовать для удаления вызовов, чатов, сообщений и контактов.</li>\n            <li>Касание / долгое нажатие значка контакта можно использовать для установки/удаления изображения аватара.</li>\n            <li>См. <a href=\"https://github.com/juha-h/baresip-studio/wiki\">Wiki</a> для подробностей.</li>\n        </ul>\n        <h2>Известные проблемы</h2>\n        <ul>\n            <li>При видеозвонках устройство необходимо удерживать в альбомной ориентации.\nрежим повернут на 90 градусов влево от книжной ориентации.</li>\n            <li>Selfview не отображается должным образом, когда видеопоток используется только для отправки.</li>\n        </ul>\n        <h2>Политика конфиденциальности</h2>\n        <p>Политика конфиденциальности доступна <a href=\"https://raw.githubusercontent.com/juha-h/baresip-studio/video/PrivacyPolicy.txt\">здесь</a>.</p>\n        <br>\n        <h2>Исходный код</h2>\n        <p>Исходный код доступен на <a href=\"https://github.com/juha-h/baresip-studio\">GitHub</a>,\n        там же можно сообщать об ошибках.</p>\n        <br>\n        <h2>Лицензии</h2>\n        <ul>\n            <li><b>BSD-3-Clause</b> except the following:</li>\n            <li><b>Apache 2.0</b> AMR codecs and TLS security</li>\n            <li><b>AGPLv4</b> ZRTP media encryption</li>\n            <li><b>GNU LGPL 2.1</b> G.722, G.726, and Codec2 codecs</li>\n            <li><b>GNU GPLv3</b> G.729 codec</li>\n            <li><b>GNU GPLv2</b> H.264 and H.265 codecs</li>\n            <li><b>AOMedia</b> AV1 codec</li>\n        </ul>\n        ]]></string>\n    <string name=\"about_title_plus\">О baresip+</string>\n    <string name=\"dark_theme\">Темная тема</string>\n    <string name=\"verify_server\">Проверка сертификатов сервера</string>\n    <string name=\"peer\">Пир</string>\n    <string name=\"direction\">Направление</string>\n    <string name=\"calls_duration\">Продолжительность</string>\n    <string name=\"battery_optimizations\">Оптимизация работы батареи</string>\n    <string name=\"dtmf_mode\">Режим DTMF</string>\n    <string name=\"dtmf_mode_help\">Выбирает способ отправки сигналов DTMF 0-9, #, * и A-D.</string>\n    <string name=\"dtmf_inband\">Внутриполосные события RTP</string>\n    <string name=\"dtmf_info\">Запросы SIP INFO</string>\n    <string name=\"missed_calls\">Пропущенные вызовы</string>\n    <string name=\"missed_calls_count\">%1$d пропущенных вызовов</string>\n    <string name=\"reset\">Сброс</string>\n    <string name=\"sip_trace\">Трассировка SIP</string>\n    <string name=\"missed_call_from\">Пропущенный вызов от</string>\n    <string name=\"call_details\">Детали вызовов</string>\n    <string name=\"time\">Время</string>\n    <string name=\"verify_server_help\">Если флажок установлен, baresip проверяет TLS-сертификаты SIP User Agent и SIP Proxy Servers, когда используется транспорт TLS.</string>\n    <string name=\"dark_theme_help\">Принудительное отображение темной темы</string>\n    <string name=\"reset_config_alert\">Вы уверены, что хотите сбросить настройки до значений по умолчанию\\?</string>\n    <string name=\"sip_trace_help\">Если флажок установлен и если установлен флажок Отладка, сообщения Logcat также включают трассировку запросов и ответов SIP. Автоматически снимается при запуске baresip.</string>\n    <string name=\"battery_optimizations_help\">Отключите оптимизацию работы батареи (рекомендуется), если вы хотите снизить вероятность того, что Android ограничит доступ baresip к сети или переведет baresip в режим ожидания.</string>\n    <string name=\"audio_settings\">Аудионастройки</string>\n    <string name=\"account_nickname_help\">Псевдоним (если есть), используемый для идентификации этой учетной записи в baresip app.</string>\n    <string name=\"invalid_account_nickname\">Неверный никнейм учетной записи \\'%1$s\\'</string>\n    <string name=\"non_unique_account_nickname\">Никнейм \\'%1$s\\' уже существует</string>\n    <string name=\"country_code\">Код страны</string>\n    <string name=\"nickname\">Никнейм</string>\n    <string name=\"reg_int\">Интервал регистрации</string>\n    <string name=\"invalid_reg_int\">Неверный интервал регистрации \\'%1$s\\'</string>\n    <string name=\"reg_int_help\">Указывает baresip как часто (в секундах) отправлять REGISTER-запросы. Допустимы значения от 60 до 3600.</string>\n    <string name=\"invalid_sip_uri_hostpart\">Неверная хостовая часть SIP URI \\'%1$s\\'</string>\n    <string name=\"allow_video_recv\">Разрешить приём видео с сайта \\'%1$s\\'\\?</string>\n    <string name=\"allow_video_send\">Разрешить передачу видео на \\'%1$s\\'\\?</string>\n    <string name=\"rtcp_mux\">Мультиплексирование RTCP</string>\n    <string name=\"consent_request\">Запрос согласия</string>\n    <string name=\"sip_or_tel_uri\">SIP или tel URI</string>\n    <string name=\"invalid_sip_or_tel_uri\">Неверный SIP или tel URI \\'%1$s\\'</string>\n    <string name=\"diverted_by_dots\">Отклонено…</string>\n    <string name=\"call_is_on_hold\">Вызов на удержании</string>\n    <string name=\"attended\">С участием</string>\n    <string name=\"lost\">Потеряно</string>\n    <string name=\"no_android_contacts\">Вы не можете получить доступ к контактам Android без разрешения «Контакты».</string>\n    <string name=\"jitter\">Джиттер: %1$s (мс)</string>\n    <string name=\"invalid_country_code\">Неверный код страны \\'%1$s\\'</string>\n    <string name=\"choose_destination_uri\">Выберите целевой URI</string>\n    <string name=\"packets\">Пакеты</string>\n    <string name=\"average_rate\">Средняя скорость: %1$s (кбит/с)</string>\n    <string name=\"anonymous\">Анонимно</string>\n    <string name=\"unknown\">Неизвестно</string>\n    <string name=\"audio_permissions\">baresip необходимо разрешение \\\"Микрофон\\\" для голосовых вызовов, разрешение \\\"Близлежащие устройства\\\" для обнаружения микрофона/динамика Bluetooth, разрешение \\\"Уведомления\\\" для отправки уведомлений, а в Android 9 - разрешение \\\"Хранилище\\\" для операций резервного копирования/восстановления.</string>\n    <string name=\"contacts_consent\">Если выбраны контакты Android, они могут быть использованы в звонках и сообщениях как ссылки для SIP и tel URI. Приложение baresip не хранит контакты Android и никому их не передаёт. Чтобы контакты Android стали доступны в baresip, Google требует вашего согласия на их использование в соответствии с этим описанием и <a href=\"https://raw.githubusercontent.com/juha-h/baresip-studio/master/PrivacyPolicy.txt\">политикой конфиденциальности</a> . </string>\n    <string name=\"country_code_help\">Код страны в формате E.164 для данной учетной записи. Если пользовательская часть URI инициатора входящего вызова или отправителя сообщения содержит телефонный номер, не начинающийся со знака \\'+\\', и контакт не найден, к номеру добавляется префикс с этим кодом страны и повторяется поиск контакта. Если номер телефона начинается с одной цифры \\'0\\', она удаляется перед добавления номеру префикса.</string>\n    <string name=\"address_family\">Семейство адресов</string>\n    <string name=\"avatar_image\">Изображение профиля</string>\n    <string name=\"no_telephony_provider\">В учётной записи \\'%1$s\\' нет поставщика услуг телефонии</string>\n    <string name=\"permissions_rationale\">Обоснование разрешений</string>\n    <string name=\"audio_focus_denied\">Аудиофокус запрещён!</string>\n    <string name=\"both\">Оба</string>\n    <string name=\"no_network\">Нет подключения к сети!</string>\n    <string name=\"user_domain_or_number\">пользователь@домен или номер телефона</string>\n    <string name=\"rtcp_mux_help\">При отметке RTP- и RTCP-пакеты мультиплексируются на единственном порту (RFC 5761).</string>\n    <string name=\"telephony_provider_help\">Хостовая часть SIP URI используется при звонках на телефонные номера. По умолчанию это домен учётной записи. Если не указана, эта учётная запись не сможет использоваться для телефонных вызовов.</string>\n    <string name=\"contacts_help\">Выбирает, будут ли использоваться контакты baresip, контакты Android или оба контакта. Если используется и то, и другое, и контакт с одинаковым именем существует в обоих контактах, будет выбран контакт baresip.</string>\n    <string name=\"address_family_help\">Определяет, какие IP-адреса использует baresip. Если выбрано IPv4 или IPv6, baresip использует либо IPv4, либо IPv6 адреса. Если ничего не выбрано, baresip использует оба типа.</string>\n    <string name=\"audio_and_video_permissions\">baresip+ необходимо разрешение \\\"Микрофон\\\" для голосовых вызовов, разрешение \\\"Камера\\\" для видеозвонков, разрешение \\\"Близлежащие устройства\\\" для обнаружения микрофона/динамика Bluetooth, разрешение \\\"Уведомления\\\" для отправки уведомлений, а в Android 9 - разрешение \\\"Хранилище\\\" для операций резервного копирования/восстановления.</string>\n    <string name=\"telephony_provider\">Поставщик услуг телефонии</string>\n    <string name=\"android_contact_help\">При отметке этот контакт будет добавлен в контакты Android.</string>\n    <string name=\"blind\">Вслепую</string>\n    <string name=\"no_backup\">Вы не можете создавать резервные копии без разрешения доступа к хранилищу.</string>\n    <string name=\"no_restore\">Вы не можете восстановить резервную копию без разрешения доступа к хранилищу.</string>\n    <string name=\"no_notifications\">Вы не можете использовать это приложение без разрешения «Уведомления».</string>\n    <string name=\"rec_in_call\">Запись может быть включена или отключена только при отсутствии соединения</string>\n    <string name=\"audio_delay\">Задержка звука</string>\n    <string name=\"audio_delay_help\">Время (в миллисекундах) ожидания звука вызываемой стороны при установлении соединения. Установите большее значение, если звук собеседника теряется в начале звонка.</string>\n    <string name=\"invalid_audio_delay\">Некорректная Задержка звука \\'%1$s\\'. Допускаются значения от 100 до 3000.</string>\n    <string name=\"call_auto_rejected\">Автоматически отклонённый вызов от %1$s</string>\n    <string name=\"redirect_notice\">Автоматическая переадресация на \\'%1$s\\'\\\\</string>\n    <string name=\"dialer_role_not_available\">Роль номеронабирателя недоступна</string>\n    <string name=\"favorite\">Предпочтительный</string>\n    <string name=\"redirect_mode_help\">Определяет, будет ли переадресация вызова выполняться автоматически или будет запрошено подтверждение.</string>\n    <string name=\"redirect_request_query\">Согласны ли вы переадресовать вызов на \\'%1$s\\'?</string>\n    <string name=\"redirect_mode\">Режим переадресации</string>\n    <string name=\"default_phone_app\">Телефонное приложение по умолчанию</string>\n    <string name=\"redirect_request\">Запрос переадресации</string>\n    <string name=\"tone_country\">Страна тонов</string>\n    <string name=\"rel_100_help\">При отметке показывается поддержка надёжных предварительных ответов (RFC 3262).</string>\n    <string name=\"default_phone_app_help\">Если отмечено, baresip является приложением по умолчанию. Не устанавливайте, если устройству может потребоваться обработка не только SIP-вызовов или сообщений.</string>\n    <string name=\"tone_country_help\">Страна для определения тонов вызова, ожидания и занятости</string>\n    <string name=\"rel_100\">Надёжные предварительные ответы</string>\n    <string name=\"appear_on_top_permission\">Для автоматического запуска требуется разрешение отображения поверх других приложений.</string>\n    <string name=\"video_fps\">Кадровая частота</string>\n    <string name=\"numeric_keypad\">Цифровая клавиатура</string>\"\n    <string name=\"user_agent\">Юзер агент</string>\n    <string name=\"speaker_phone\">Громкоговоритель</string>\n    <string name=\"speaker_phone_help\">Если этот флажок установлен, громкоговоритель автоматически включается при начале вызова.</string>\n    <string name=\"invalid_fps\">Недопустимые кадры В секунду \\'%1$d\\'</string>\n    <string name=\"invalid_user_agent\">Недопустимое значение поля заголовка User-Agent</string>\n    <string name=\"microphone_title\">Микрофон</string>\n    <string name=\"video_fps_help\">Частота кадров видео, которая будет предлагаться во время рукопожатия SDP. Допустимые значения - от 10 до 30.</string>\n    <string name=\"microphone_tip\">Если он активирован во время вызова, микрофон отключается.</string>\n    <string name=\"speakerphone_title\">Громкоговоритель</string>\n    <string name=\"speakerphone_tip\">Если активирован, звук воспроизводится через громкоговоритель устройства.</string>\n    <string name=\"call_recording_title\">Запись звонков</string>\n    <string name=\"call_recording_tip\">Если активирован, будут записываться новые входящие и исходящие вызовы. Записи можно воспроизвести на странице Сведений о вызове</string>\n    <string name=\"numeric_keypad_help\">Если флажок установлен, цифровая клавиатура отображается, когда выделено поле \\'Позвонить на ...\\'.</string>\"\n    <string name=\"microphone_gain_help\">Умножить громкость микрофона на это десятичное число. Минимальное значение равно 1.0 (заводское значение по умолчанию), которое отключает усиление микрофона. Большие значения могут негативно повлиять на качество звука.</string>\n    <string name=\"no_read_permission\">Нет разрешения на чтение из внешнего хранилища</string>\n    <string name=\"favorite_help\">Если этот флажок установлен, контакт отображается среди других избранных в верхней части списка контактов.</string>\n    <string name=\"restore_unzip_failed\">Не удалось восстановить данные приложения. Android версии 14 и выше не позволяет восстанавливать данные, резервные копии которых были созданы ранее %1$s version %2$s.</string>\n    <string name=\"microphone_gain\">Усиление микрофона</string>\n    <string name=\"ringtone\">Рингтон</string>\n    <string name=\"select_ringtone\">Выберите рингтон</string>\n    <string name=\"user_agent_help\">Значение поля пользовательского заголовка User-Agent SIP-запроса / ответа</string>\n    <string name=\"no_aec\">Отсутствие аппаратного акустического эхоподавления!</string>\n    <string name=\"call_request\">Запрос на вызов</string>\n    <string name=\"call_request_query\">Принимаете ли вы запрос на звонок \\'%1$s\\'?</string>\n    <string name=\"dtmf_auto\">In-band RTP или SIP INFO</string>\n    <string name=\"playing_recording\">Воспроизведение записи …</string>\n    <string name=\"invalid_microphone_gain\">Недопустимое значение усиления микрофона</string>\n    <string name=\"reply\">Ответить</string>\n    <string name=\"save\">Сохранить</string>\n    <string name=\"call_answered\">Звонок принят</string>\n    <string name=\"call_answered_elsewhere\">Ответ на звонок в другом месте</string>\n    <string name=\"call_missed\">Пропущенный звонок</string>\n    <string name=\"call_rejected\">Звонок отклонен</string>\n    <string name=\"colorblind\">Цвет для слепых</string>\n    <string name=\"colorblind_help\">Используйте удобные для слепых значки состояния регистрации</string>\n    <string name=\"proximity_sensing\">Датчик приближения</string>\"&gt;\n    <string name=\"proximity_sensing_help\">Если флажок установлен, функция определения приближения активна во время вызовов.</string>\n    <string name=\"block_unknown\">Блокировать неизвестных</string>\n    <string name=\"block_unknown_help\">Блокировать звонки и сообщения от участников которые не найдены в контактах.</string>\n    <string name=\"is_calling\">звонит</string>\n    <string name=\"save_recording\">Сохранить запись</string>\n    <string name=\"save_recording_question\">Вы хотите сохранить эту запись?</string>\n    <string name=\"recording_saved\">Запись сохранена</string>\n    <string name=\"delete_call_alert\">Вы хотите удалить этот звонок из истории?</string>\n    <string name=\"transport_protocols\">Протоколы передачи</string>\n    <string name=\"transport_protocols_help\">Список поддерживаемых транспортных протоколов передачи SIP-запросов/ответов, разделенных запятыми. Если поле оставить пустым, по умолчанию будет использоваться значение \\'udp,tcp,tls,ws,wss\\', включающее все поддерживаемые протоколы передачи.</string>\n    <string name=\"invalid_transport_protocols\">Неправильный список протоколов передачи</string>\n    <string name=\"dynamic_colors\">Динамические цвета</string>\n    <string name=\"dynamic_colors_help\">Использовать динамические цвета, если они включены в настройках Android</string>\n    <string name=\"stop\">Стоп</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-sl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"authentication_username\">Uporabniško Ime</string>\n    <string name=\"invalid_display_name\">Napačno ime za prikaz %1$s</string>\n    <string name=\"display_name_help\">Ime (če je) uporabljen v polju From URI za odhodne akcije.</string>\n    <string name=\"display_name\">Prikazano ime</string>\n    <string name=\"account\">Račun</string>\n    <string name=\"about_title_plus\">O baresip+</string>\n    <string name=\"about_title\">O baresip-u</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-sv/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"ok\">OK</string>\n    <string name=\"contact_delete_question\">Vill du ta bort kontakten \\'%1$s\\'\\?</string>\n    <string name=\"send_message\">Skicka meddelande</string>\n    <string name=\"contact_action_question\">Vill du ringa eller skicka meddelande till \\'%1$s\\'\\?</string>\n    <string name=\"contacts\">Kontakter</string>\n    <string name=\"contact_name\">Namn</string>\n    <string name=\"new_contact\">Ny kontakt</string>\n    <string name=\"dark_theme\">Mörkt tema</string>\n    <string name=\"invalid_dns_servers\">Ogiltiga DNS-servrar</string>\n    <string name=\"dns_servers\">DNS-servrar</string>\n    <string name=\"start_automatically\">Starta automatiskt</string>\n    <string name=\"configuration\">Inställningar</string>\n    <string name=\"you\">Du</string>\n    <string name=\"today\">Idag</string>\n    <string name=\"add_contact\">Lägg till kontakt</string>\n    <string name=\"short_message_question\">Vill du ta bort meddelandet\\?</string>\n    <string name=\"new_message\">Nytt meddelande</string>\n    <string name=\"delete_history_alert\">Vill du ta bort samtalshistoriken för kontot \\'%1$s\\'\\?</string>\n    <string name=\"enable_history\">Aktivera</string>\n    <string name=\"disable_history\">Inaktivera</string>\n    <string name=\"calls_delete_question\">Vill du ta bort \\'%1$s\\' %2$s från samtalshistoriken\\?</string>\n    <string name=\"calls_add_delete_question\">Vill du lägga till \\'%1$s\\' to dina kontakter eller ta bort %2$s från samtalshistoriken\\?</string>\n    <string name=\"calls_call\">samtal</string>\n    <string name=\"calls_calls\">samtal</string>\n    <string name=\"call\">Ring</string>\n    <string name=\"call_history\">Samtalshistorik</string>\n    <string name=\"missed_call_from\">Missat samtal från</string>\n    <string name=\"delete_account\">Vill du ta bort kontot \\'%1$s\\'\\?</string>\n    <string name=\"account_exists\">Kontot \\'%1$s\\' finns redan.</string>\n    <string name=\"invalid_aor\">Ogiltig användare@domän[:port][;transport=udp|tcp|tls] \\'%1$s\\'</string>\n    <string name=\"new_account\">SIP URI för nytt konto</string>\n    <string name=\"accounts\">Konton</string>\n    <string name=\"default_account\">Förvalt konto</string>\n    <string name=\"dtmf_info\">SIP INFO-förfrågningar</string>\n    <string name=\"dtmf_inband\">Inuti RTP</string>\n    <string name=\"dtmf_mode_help\">Väljer hur DTMF-toner för 0-9, #, * och A-D skickas.</string>\n    <string name=\"dtmf_mode\">DTMF-läge</string>\n    <string name=\"media_encryption\">Mediakryptering</string>\n    <string name=\"invalid_stun_password\">Ogiltigt lösenord \\'%1$s\\'</string>\n    <string name=\"stun_password_help\">Lösenord om STUN/TURN-servern kräver det</string>\n    <string name=\"stun_password\">STUN/TURN-lösenord</string>\n    <string name=\"invalid_stun_username\">Ogiltigt användarnamn \\'%1$s\\'</string>\n    <string name=\"stun_username_help\">Användarnamn om STUN/TURN-servern kräver det</string>\n    <string name=\"invalid_stun_server\">Ogiltig STUN/TURN-server-URI \\'%1$s\\'</string>\n    <string name=\"stun_server_help\">En STUN/TURN-server-URI på formatet schema:värd[:port][\\?transport=udp|tcp], där schema är \\'stun\\', \\'stuns\\', \\'turn\\', eller \\'turns\\'. Förvald STUN-server för STUN- och ICE-protokollen är \\'stun:stun.l.google.com:19302\\' som är Googles publika STUN-server. Det finns ingen förvald TURN-server.</string>\n    <string name=\"media_nat\">NAT-passering av media</string>\n    <string name=\"media_nat_help\">Väljer protokoll för NAT-passering av media. Möjliga val är STUN (Session Traversal Utilities for NAT, RFC 5389) och ICE (Interactive Connectivity Establishment, RFC 5245).</string>\n    <string name=\"no\">Nej</string>\n    <string name=\"yes\">Ja</string>\n    <string name=\"add\">Lägg till</string>\n    <string name=\"delete\">Ta bort</string>\n    <string name=\"edit\">Ändra</string>\n    <string name=\"status\">Status</string>\n    <string name=\"error\">Fel</string>\n    <string name=\"backup\">Säkerhetskopiera</string>\n    <string name=\"restore\">Återställ</string>\n    <string name=\"about\">Om</string>\n    <string name=\"restart\">Starta om</string>\n    <string name=\"quit\">Avsluta</string>\n    <string name=\"outgoing_call_to_dots\">Ring till …</string>\n    <string name=\"incoming_call_from_dots\">Samtal från …</string>\n    <string name=\"call_transfer\">Koppla samtal</string>\n    <string name=\"transfer_destination\">Koppla till</string>\n    <string name=\"transfer_failed\">Koppling misslyckades</string>\n    <string name=\"transfer\">Koppling</string>\n    <string name=\"dtmf\">DTMF</string>\n    <string name=\"codecs\">Kodekar</string>\n    <string name=\"voicemail_messages\">Röstmeddelanden</string>\n    <string name=\"one_new_message\">ett nytt meddelande</string>\n    <string name=\"you_have\">Du har</string>\n    <string name=\"new_messages\">nya meddelanden</string>\n    <string name=\"one_old_message\">ett gammalt meddelande</string>\n    <string name=\"old_messages\">gamla meddelanden</string>\n    <string name=\"and\">och</string>\n    <string name=\"listen\">Lyssna</string>\n    <string name=\"no_messages\">Du har inga meddelanden</string>\n    <string name=\"registering_failed\">Registrering av %1$s misslyckades.</string>\n    <string name=\"call_failed\">Samtal misslyckades</string>\n    <string name=\"call_closed\">Samtal avslutat</string>\n    <string name=\"call_not_secure\">Detta samtal är INTE säkert!</string>\n    <string name=\"unverify\">Ta bort verifiering</string>\n    <string name=\"call_is_secure\">Detta samtal är SÄKERT och motparten är VERIFIERAD! Vill du ta bort verifieringen av motparten\\?</string>\n    <string name=\"peer_not_verified\">Detta samtal är SÄKERT, men motparten är INTE verifierad!</string>\n    <string name=\"stun_username\">STUN/TURN-användarnamn</string>\n    <string name=\"stun_server\">STUN/TURN-server</string>\n    <string name=\"video_codecs\">Videokodekar</string>\n    <string name=\"audio_codecs\">Ljudkodekar</string>\n    <string name=\"register_help\">Kryssa i för att aktivera registrering och att REGISTER-förfrågningar skickas periodiskt enligt angett registreringsintervall.</string>\n    <string name=\"register\">Registrera</string>\n    <string name=\"invalid_proxy_server_uri\">Ogiltig proxyserver-URI \\'%1$s\\'</string>\n    <string name=\"sip_uri_of_proxy_server\">SIP-URI för en proxyserver</string>\n    <string name=\"sip_uri_of_another_proxy_server\">SIP-URI för en annan proxyserver</string>\n    <string name=\"outbound_proxies_help\">SIP-URI för en eller två proxyservrar som måste användas när SIP-förfrågningar skickas. Om två anges skickas REGISTER-förfrågningar till båda och andra förfrågningar skickas till den som svarar. Om ingen utgående proxyserver anges skickas förfrågningar genom att DNS NAPTR/SRV/A-poster slås upp från värdnamnet i mottagarens URI. Om värdnamnet är en IPv6-adress måste det skrivas inom hakparenteser [].\n\\nExempel:\n\\n • sip:example.com:5061;transport=tls\n\\n • sip:[2001:67c:223:777::10];transport=tcp\n\\n • sip:192.168.43.50:443;transport=wss</string>\n    <string name=\"outbound_proxies\">Utgående proxyservrar</string>\n    <string name=\"invalid_authentication_password\">Ogiltigt lösenord för autentisering \\'%1$s\\'</string>\n    <string name=\"authentication_password_help\">Lösenord för autentisering, upp till 64 tecken. Om användarnamn för autentisering är ifyllt, men lösenordet lämnas tomt kommer baresip fråga om lösenord vid uppstart.</string>\n    <string name=\"authentication_password\">Lösenord för autentisering</string>\n    <string name=\"authentication_username_help\">Användarnamn som används om SIP-förfrågningar kräver autentisering. Förvalt värde är kontots användarnamn.</string>\n    <string name=\"authentication_username\">Användarnamn för autentisering</string>\n    <string name=\"invalid_authentication_username\">Ogiltigt användarnamn för autentisering \\'%1$s\\'</string>\n    <string name=\"invalid_display_name\">Ogiltigt visat namn \\'%1$s\\'</string>\n    <string name=\"display_name_help\">Namn som, om det anges, används i From-URI i utgående SIP-förfrågningar.</string>\n    <string name=\"display_name\">Visat namn</string>\n    <string name=\"account\">Konto</string>\n    <string name=\"about_title_plus\">Om baresip+</string>\n    <string name=\"about_title\">Om baresip</string>\n    <string name=\"about_text\"><![CDATA[\n<h1>SIP-användaragent baserad på <a href=\"https://github.com/baresip/baresip\">baresip</a>-biblioteket</h1>\n<br>\n<p>Juha Heinanen &lt;jh@tutpro.com></p>\n<p>Version %1$s</p>\n<br>\n<h2>Användningstips</h2>\n<ul>\n<li>Kontrollera att standardvärdena i baresips inställningar uppfyller dina behov\n(tryck på objektets titel för hjälp).</li>\n<li>Skapa sedan ett eller flera konton i Konton (tryck på objektets titel för hjälp).</li>\n<li>Registreringsstatus för ett konto visas med en färgad punkt: grön (registrering\nlyckades), gul (registrering pågår), röd (registrering misslyckades), vit (registrering\nhar inte aktiverats).</li>\n<li>Tryck på statuspunkten för att komma direkt till kontoinställningarna.\n<li>Långt tryck på baresip-ikonerna visar information om ikonerna.\n<li>Svep nedåt för att omregistrera det aktuella kontot.\n<li>Långt tryck på det aktuella kontot aktiverar eller inaktiverar kontots registrering.\n<li>Svep åt vänster/höger för att växla mellan kontona.\n<li>Tidigare samtalspartner kan väljas igen genom att trycka på samtalsikonen när mottagaren är tom.\n<li>Samtal och meddelanden kan läggas till i kontakter genom att trycka länge på dem.\n<li>Långt tryck kan också användas för att ta bort samtal, chattar, meddelanden och kontakter.\n<li>Tryck/långtryck på kontaktikonen kan användas för att installera/ta bort bildavatar.\n<li>Långtryck på en ljudkodek kan användas för att aktivera/inaktivera kodeken.\n<li>Se <a href=\"https://github.com/juha-h/baresip-studio/wiki\">Wiki</a> för mer information.\n</ul>\n<h2>Integritet</h2>\n<p>Integritetspolicyn finns tillgänglig <a href=\"https://raw.githubusercontent.com/juha-h/baresip-studio/master/PrivacyPolicy.txt\">här</a>.</p>\n<br>\n<h2>Källkod</h2>\n<p>Källkoden finns tillgänglig på <a href=\"https://github.com/juha-h/baresip-studio\">GitHub</a>,\ndär även problem kan rapporteras.</p>\n<br>\n<h2>Språköversättningar</h2>\n<p>Språköversättningar hanteras via baresip\n<a href=\"https://hosted.weblate.org/projects/baresip/\">Weblate</a>-projektet.</p>\n<br>\n<h2>Licenser</h2>\n<ul>\n<li><b>BSD-3-klausul</b> med undantag för följande:</li>\n<li><b>Apache 2.0</b> AMR-kodekter och TLS-säkerhet</li>\n<li><b>AGPLv4</b> ZRTP-mediekryptering</li>\n<li><b>GNU LGPL 2.1</b> G.722-, G.726- och Codec2-kodeken</li>\n<li><b>GNU GPLv3</b> G.729-kodeken</li>\n</ul>\n]]></string>\n    <string name=\"transfer_request_query\">Accepterar du att koppla det här samtalet till \\'%1$s\\'\\?</string>\n    <string name=\"audio_modules_title\">Ljudmoduler</string>\n    <string name=\"start_failed\">Baresip kunde inte starta. Det kan bero på ett ogiltigt värde för en inställning. Kontrollera Adress att lyssna på, TLS certifikat-fil och TLS CA-fil. Starta sedan om baresip.</string>\n    <string name=\"call_already_active\">Du har redan ett pågående samtal.</string>\n    <string name=\"rate\">Nuvarande hastighet: %1$s (kbits/s)</string>\n    <string name=\"call_info_not_available\">Ingen information tillgänglig</string>\n    <string name=\"call_info\">Samtalsinformation</string>\n    <string name=\"duration\">Längd: %1$d (sekunder)</string>\n    <string name=\"allow_video_recv\">Acceptera att ta emot video från \\'%1$s\\'\\?</string>\n    <string name=\"allow_video_send\">Acceptera att skicka video till \\'%1$s\\'\\?</string>\n    <string name=\"allow_video\">Acceptera att skicka och ta emot video med \\'%1$s\\'\\?</string>\n    <string name=\"video_request\">Videoförfrågan</string>\n    <string name=\"video_call\">Videosamtal</string>\n    <string name=\"deny\">Avvisa</string>\n    <string name=\"cancel\">Avbryt</string>\n    <string name=\"contact_already_exists\">Kontakten \\'%1$s\\' finns redan.</string>\n    <string name=\"invalid_contact\">Ogiltigt kontaktnamn \\'%1$s\\'</string>\n    <string name=\"config_restart\">Du måste starta om baresip för att aktivera de nya inställningarna. Starta om nu\\?</string>\n    <string name=\"read_ca_certs_error\">Kunde inte läsa filen \\'ca_certs.crt\\'.</string>\n    <string name=\"read_cert_error\">Kunde inte läsa filen \\'cert.pem\\'.</string>\n    <string name=\"reset\">Återställ</string>\n    <string name=\"reset_config_alert\">Är du säker på att du vill återställa alla inställningar till deras ursprungsvärden\\?</string>\n    <string name=\"reset_config_help\">Kryssa i för att återställa alla inställningar till deras ursprungsvärden.</string>\n    <string name=\"reset_config\">Återställ grundinställningarna</string>\n    <string name=\"dark_theme_help\">Framtvinga mörkt skärmtema</string>\n    <string name=\"invalid_opus_bitrate\">Ogiltig Opus-bitrate</string>\n    <string name=\"opus_packet_loss\">Förväntat Opus-pakettapp</string>\n    <string name=\"opus_bit_rate\">Opus-bitrate</string>\n    <string name=\"failed_to_load_module\">Kunde inte ladda modul.</string>\n    <string name=\"audio_modules_help\">Ljudkodekar som tillhandahålls av de förkryssade modulerna är tillgängliga för konton.</string>\n    <string name=\"tls_ca_file\">TLS CA-fil</string>\n    <string name=\"verify_server\">Verifiera servercertifikat</string>\n    <string name=\"chat_with\">Chatta med %1$s</string>\n    <string name=\"transfer_request_to\">Begäran att koppla samtalet till</string>\n    <string name=\"account_allocation_failure\">Kunde inte skapa ett nytt konto.</string>\n    <string name=\"default_account_help\">Kryssa i för att välja detta konto när baresip startar.</string>\n    <string name=\"voicemain_uri_help\">SIP-URI för att kontrollera röstmeddelanden. Om fältet lämnas tomt kommer röstmeddelanden (Message Waiting Indications) inte kontrolleras.</string>\n    <string name=\"voicemail_uri\">URI till röstbrevlåda</string>\n    <string name=\"auto\">Automatiskt</string>\n    <string name=\"manual\">Manuellt</string>\n    <string name=\"answer_mode_help\">Anger hur inkommande samtal besvaras.</string>\n    <string name=\"answer_mode\">Metod att svara</string>\n    <string name=\"media_encryption_help\">Väljer eventuellt krypteringsprotokoll för media.\n\\n • ZRTP (rekommenderad) innebär att ändpunktskryptering via ZRTP försöker förhandlas fram efter att samtalet kopplats upp.\n\\n • DTLS-SRTPF innebär att UDP/TLS/RTP/SAVPF erbjuds i utgående samtal och att RTP/SAVP, RTP/SAVPF, UDP/TLS/RTP/SAVP eller UDP/TLS/RTP/SAVPF används om det erbjuds i inkommande samtal.\n\\n • SRTP-MANDF innebär att RTP/SAVPF erbjuds i utgående samtal och är ett krav för inkommande samtal.\n\\n • SRTP-MAND innebär att RTP/SAVP erbjuds i utgående samtal och är ett krav för inkommande samtal.\n\\n • SRTP innebär att RTP/AVP erbjuds i utgående samtal och att RTP/SAVP eller RTP/SAVPF används om de erbjuds i inkommande samtal.</string>\n    <string name=\"tls_certificate_file\">TLS-certifikatfil</string>\n    <string name=\"failed_to_set_dns_servers\">Kunde inte ställa in DNS-servrar</string>\n    <string name=\"dns_servers_help\">Kommaseparerad lista med adresser på DNS-servrar. Om listan lämnas tom hämtas DNS-serveradresser automatiskt från systemet. Varje DNS-adress skrivs på formatet \\'ip:port\\' eller \\'ip\\'. Om port utelämnas används port 53. Om ip är en IPv6-adress och en port anges då måste ip skrivas inom hakparenteser []. Som exempel, i listan \\'8.8.8.8:53,[2001:4860:4860::8888]:53\\' anges IPv4- och IPv6-adresser för Googles publika DNS-servrar.</string>\n    <string name=\"invalid_listen_address\">Ogiltig adress att lyssna på</string>\n    <string name=\"listen_address_help\">IP-adress och port i formatet ”adress:port” där baresip lyssnar efter inkommande SIP-förfrågningar. Om IP-adressen är en IPv6-adress måste den skrivas inom hakparenteser []. IPv4-adressen 0.0.0.0 eller IPv6-adressen [::] gör att baresip lyssnar på alla tillgängliga adresser. Om fältet lämnas tomt (fabriksinställning) lyssnar baresip på en godtycklig port på alla tillgängliga adresser.</string>\n    <string name=\"listen_address\">Adress att lyssna på</string>\n    <string name=\"start_automatically_help\">Kryssa i för att starta baresip automatiskt när enheten startat.</string>\n    <string name=\"delete_chats_alert\">Vill du ta bort chatt-historiken för kontot \\'%1$s\\'\\?</string>\n    <string name=\"short_chat_question\">Vill du ta bort chatten med \\'%1$s\\'\\?</string>\n    <string name=\"long_chat_question\">Vill du ta bort chatten med motparten \\'%1$s\\' eller lägga till motparten till dina kontakter\\?</string>\n    <string name=\"new_chat_peer\">Ny chat-motpart</string>\n    <string name=\"chats\">Chat-historik</string>\n    <string name=\"message_failed\">Misslyckades</string>\n    <string name=\"sending_failed\">Kunde inte skicka meddelande</string>\n    <string name=\"long_message_question\">Vill du ta bort meddelandet eller lägga till motparten \\'%1$s\\' till dina kontakter\\?</string>\n    <string name=\"decrypt_password\">Lösenord för avkryptering</string>\n    <string name=\"encrypt_password\">Lösenord för kryptering</string>\n    <string name=\"accounts_help\">SIP URI för nytt konto i formatet &lt;användare&gt;@&lt;domän&gt;[:&lt;port&gt;][;transport=udp|tcp|tls]. Om &lt;port&gt; anges och transportprotokollet inte anges, är standardtransportprotokollet UDP. Om &lt;port&gt; inte anges och transportprotokollet anges, är standardporten 5060 eller 5061 (TLS). Om varken det ena eller det andra anges och ingen utgående proxy specificeras, bestäms kontots registrator (om sådan finns) enbart utifrån domänens DNS-information.</string>\n    <string name=\"about_text_plus\"><![CDATA[\n<h1>SIP-användaragent med videosamtal baserat på <a href=\"https://github.com/baresip/baresip\">baresip</a>-biblioteket</h1>\n<br>\n<p>Juha Heinanen &lt;jh@tutpro.com></p>\n<p>Version %1$s</p>\n<br>\n<h2>Användningstips</h2>\n<ul>\n<li>Kontrollera att standardvärdena i baresip+\\'s Inställningar uppfyller dina behov\n(tryck på objektets titel för hjälp).</li>\n<li>Skapa sedan ett eller flera konton i Konton (tryck på objektets titel för hjälp).</li>\n<li>Registreringsstatus för ett konto visas med en färgad punkt: grön (registrering\nlyckades), gul (registrering pågår), röd (registrering misslyckades), vit (registrering\nhar inte aktiverats).</li>\n<li>Tryck på statuspunkten för att komma direkt till kontoinställningarna.\n<li>Långt tryck på baresip-ikonerna visar information om ikonerna.\n<li>Svep nedåt för att omregistrera det aktuella kontot.\n<li>Långt tryck på det aktuella kontot aktiverar eller inaktiverar kontots registrering.\n<li>Svep åt vänster/höger för att växla mellan kontona.\n<li>Tidigare samtalspartner kan väljas igen genom att trycka på samtalsikonen när mottagaren är tom.\n<li>Samtalspartner och meddelanden kan läggas till i kontakter genom att trycka länge på dem.\n<li>Långt tryck kan också användas för att ta bort samtal, chattar, meddelanden och kontakter.\n<li>Tryck/långt tryck på kontaktikonen kan användas för att installera/ta bort bildavatar.\n<li>Långt tryck på en ljud- eller videokodek kan användas för att aktivera/inaktivera kodeken.\n<li>Se <a href=\"https://github.com/juha-h/baresip-studio/wiki\">Wiki</a> för mer\ninformation.\n</li>\n</ul>\n<h2>Kända problem\n<ul>\n<li>Vid videosamtal måste enheten hållas i liggande\n \nläge, roterat 90 grader åt vänster från stående läge.\n<li>Självbilden visas inte korrekt när videoströmmen är sendonly. </li>\n</ul>\n<h2>Integritet</h2>\n<p>Integritet finns\n<a href=\"https://raw.githubusercontent.com/juha-h/baresip-studio/video/PrivacyPolicy.txt\">här</a>.</p>\n<br>\n<h2>Källkod</h2>\n<p>Källkoden finns tillgänglig på <a href=\"https://github.com/juha-h/baresip-studio\">GitHub</a>,\ndär även problem kan rapporteras.</p>\n<br>\n<h2>Språköversättningar</h2>\n<p>Språköversättningar hanteras via baresip\n<a href=\"https://hosted.weblate.org/projects/baresip/\">Weblate</a>-projektet.</p>\n<br>\n<h2>Licenser</h2>\n<ul>\n<li><b>BSD-3-klausul</b> med undantag för följande:</li>\n<li><b>Apache 2.0</b> AMR-kodeken och TLS-säkerhet</li>\n<li><b>AGPLv4</b> ZRTP-mediekryptering</li>\n<li><b>GNU LGPL 2.1</b> G.722-, G.726- och Codec2-kodeken</li>\n<li><b>GNU GPLv3</b> G.729-kodeken</li>\n<li><b>GNU GPLv2</b> H.264- och H.265-kodeken</li>\n<li><b>AOMedia</b> AV1-kodeken</li>\n</ul>\n]]></string>\n    <string name=\"default_call_volume\">Förvald samtalsvolym</string>\n    <string name=\"invalid_opus_packet_loss\">Ogiltig procentuell paketförlust för Opus</string>\n    <string name=\"opus_packet_loss_help\">Förväntad procentuell förlust av Opus-ljudströmspaket, från 0 till 100. Fabriksinställt standardvärde är 1. Värdet 0 tur inaktiverar även Opus Forward Error Correction (FEC).</string>\n    <string name=\"opus_bit_rate_help\">Genomsnittlig maximal bitrate för Opus-ljudström. Giltiga värden är 6000-510000. Förvalt värde är 28000.</string>\n    <string name=\"restart_request\">Omstartsbegäran</string>\n    <string name=\"verify\">Verifikationsbegäran</string>\n    <string name=\"verify_sas\">Vill du verifiera SAS &lt;%1$s&gt;\\?</string>\n    <string name=\"transfer_request\">Kopplingsbegäran</string>\n    <string name=\"default_call_volume_help\">Kryssa i för att använda en förvald ljudvolym, 1–10, vid samtal.</string>\n    <string name=\"accept\">Acceptera</string>\n    <string name=\"android_contact_help\">Kryssa i för att lägga till kontakten till dina Android-kontakter.</string>\n    <string name=\"sip_trace_help\">Kryssa i för att inkludera SIP-meddelanden i Logcat-meddelanden när Felsökning är aktiverat. Funktionen inaktiveras automatiskt när baresip startar.</string>\n    <string name=\"debug_help\">Kryssa i för att skicka felsöknings- och info-meddelanden till Logcat.</string>\n    <string name=\"tls_ca_file_help\">Om denna ruta är markerad har en fil läst in eller kommer att läst in som innehåller TLS-certifikat från certifikatutfärdare som inte ingår i Android OS. I Android version 9 läst in en fil med namnet ”ca_certs.crt” från mappen Hämtningar.</string>\n    <string name=\"verify_server_help\">Om denna ruta är markerad verifierar baresip TLS-certifikat för SIP-användaragenter och SIP-proxyservrar när TLS-transport används.</string>\n    <string name=\"tls_certificate_file_help\">Om denna ruta är markerad har en fil som innehåller TLS-certifikatet och den privata nyckeln för denna baresip-instans läsas in eller kommer att läsas in. I Android version 9 läses in en fil med namnet ”cert.pem” från mappen Download. Av säkerhetsskäl bör du radera filen efter läsin.</string>\n    <string name=\"confirmation\">Bekräftelse</string>\n    <string name=\"notice\">Notis</string>\n    <string name=\"info\">Info</string>\n    <string name=\"alert\">Varning</string>\n    <string name=\"sip_trace\">SIP-trafik</string>\n    <string name=\"debug\">Felsökning</string>\n    <string name=\"video_size_help\">Storlek på bildrutorna i skickad video (bredd x höjd)</string>\n    <string name=\"video_size\">Storlek på bildrutor</string>\n    <string name=\"no_cameras\">Du har ingen videokamera som stöds!</string>\n    <string name=\"no_video_calls\">Aktivera behörigheten \\\"Kamera\\\" för att ringa eller ta emot samtal.</string>\n    <string name=\"no_calls\">baresip behöver behörigheten \\\"Mikrofon\\\" för röstsamtal.</string>\n    <string name=\"restore_failed\">Det gick inte att återställa programdata. Kontrollera att du har angett rätt lösenord och att säkerhetskopian kommer från det här programmet. I Android version 9 ska du också kontrollera Appar → baresip → Behörighet → Lagring och att filen ’%1$s’ finns i mappen Nedladdningar.</string>\n    <string name=\"restored\">Programdata återställd. baresip behöver startas om. Starta om nu\\?</string>\n    <string name=\"backup_failed\">Kunde inte säkerhetskopiera programdata till filen \\'%1$s\\'. Kontrollera Appar → baresip → Behörigheter → Lagring.</string>\n    <string name=\"backed_up\">Applikationsdata (exklusive inspelningar) säkerhetskopierade till filen ’%1$s’. I Android version 9 finns filen i mappen Hämtningar.</string>\n    <string name=\"no_network\">Inget nätverk tillgängligt!</string>\n    <string name=\"missed_calls\">Missade samtal</string>\n    <string name=\"missed_calls_count\">%1$d missade samtal</string>\n    <string name=\"battery_optimizations\">Batterioptimering</string>\n    <string name=\"avatar_image\">Profilbild</string>\n    <string name=\"no_backup\">Det går inte att skapa en backup utan behörigheten \\\"Lagring\\\".</string>\n    <string name=\"no_restore\">Det går inte att återställa en backup utan behörigheten \\\"Lagring\\\".</string>\n    <string name=\"battery_optimizations_help\">Inaktivera batterioptimering (rekommenderas) om du vill minska risken att Android begränsar baresips åtkomst till nätverk eller inaktiverar baresip för att spara ström.</string>\n    <string name=\"audio_settings\">Ljudinställningar</string>\n    <string name=\"jitter\">Jitter: %1$s (ms)</string>\n    <string name=\"call_is_on_hold\">Samtalet är parkerat</string>\n    <string name=\"average_rate\">Medelhastighet: %1$s (kbits/s)</string>\n    <string name=\"packets\">Paket</string>\n    <string name=\"call_details\">Samtalsdetaljer</string>\n    <string name=\"time\">Tid</string>\n    <string name=\"telephony_provider_help\">Värdnamn att använda i SIP-URI:er vid samtal till telefonnummer. Förvalt är att använda kontots domän. Om fältet lämnas tomt kan inte detta konto användas för att ringa telefonnummer.</string>\n    <string name=\"peer\">Motpart</string>\n    <string name=\"telephony_provider\">Telefonileverantör</string>\n    <string name=\"invalid_sip_uri_hostpart\">Ogiltigt värdnamn i SIP-URI \\'%1$s\\'</string>\n    <string name=\"direction\">Riktning</string>\n    <string name=\"calls_duration\">Längd</string>\n    <string name=\"sip_or_tel_uri\">SIP- eller tel-URI</string>\n    <string name=\"user_domain_or_number\">användare@domän eller telefonnummer</string>\n    <string name=\"lost\">Tappade</string>\n    <string name=\"invalid_sip_or_tel_uri\">Ogiltig SIP- eller tel-URI \\'%1$s\\'</string>\n    <string name=\"contacts_help\">Väljer om baresip-kontakter, Android-kontakter eller båda ska användas. Om båda används och en kontakt med samma namn finns i båda kontakterna, väljs baresip-kontakten.</string>\n    <string name=\"both\">Båda</string>\n    <string name=\"no_android_contacts\">Det går inte att komma åt Androids kontakter utan behörigheten \\\"Kontakter\\\".</string>\n    <string name=\"country_code\">Landsnummer</string>\n    <string name=\"invalid_country_code\">Ogiltigt landsnummer \\'%1$s\\'</string>\n    <string name=\"country_code_help\">Det här kontots landsnummer, enligt E.164. Om telefonnumret i From-URI för ett inkommande samtal, eller meddelande, inte börjar med en \\'+\\'-symbol och en matchande kontakt inte kan hittas, då läggs landsnumret till telefonnumret och en kontaktsökning görs igen. Om telefonnumret börjar med siffran \\'0\\' tas denna siffra bort innan landsnumret läggs till.</string>\n    <string name=\"anonymous\">Anonym</string>\n    <string name=\"unknown\">Okänd</string>\n    <string name=\"consent_request\">Begäran om medgivande</string>\n    <string name=\"diverted_by_dots\">Omdirigerad av…</string>\n    <string name=\"attended\">Vägledd</string>\n    <string name=\"blind\">Blind</string>\n    <string name=\"contacts_consent\">Om Androids kontakter används då kan de användas som SIP- eller tel-URI-referenser för samtal och för att skicka meddelanden. baresip sparar inte Androids kontakter och delar dem inte heller med någon. För att Androids kontakter ska bli tillgängliga i baresip kräver Google att du accepterar användningen som beskrivs här och i appens <a href=\"https://raw.githubusercontent.com/juha-h/baresip-studio/blob/master/PrivacyPolicy.txt\">integritetspolicy</a>. </string>\n    <string name=\"rtcp_mux_help\">Kryssa i för att låta RTP- och RTCP-paket använda samma port (RFC 5761).</string>\n    <string name=\"rtcp_mux\">RTCP-multiplex</string>\n    <string name=\"no_notifications\">Det går inte att använda den här appen utan behörigheten \\\"Aviseringar\\\".</string>\n    <string name=\"account_nickname_help\">Namn (valfritt) som används för att identifiera det här kontot i baresip-appen.</string>\n    <string name=\"nickname\">Smeknamn</string>\n    <string name=\"invalid_account_nickname\">Ogiltigt kontonamn \\'%1$s\\'</string>\n    <string name=\"non_unique_account_nickname\">Namnet \\'%1$s\\' används redan</string>\n    <string name=\"audio_focus_denied\">Ljudfokus nekad!</string>\n    <string name=\"reg_int\">Registreringsintervall</string>\n    <string name=\"invalid_reg_int\">Ogiltigt registreringsintervall \\'%1$s\\'</string>\n    <string name=\"no_telephony_provider\">Kontot \\'%1$s\\' har ingen telefonileverantör</string>\n    <string name=\"address_family_help\">Väljer vilken eller vilka IP-adress baresip använder. Om IPv4 eller IPv6 väljs använder baresip enbart vald adresstyp. Om ingen adresstyp väljs använder baresip både IPv4- och IPv6-adresser.</string>\n    <string name=\"address_family\">Adresstyp</string>\n    <string name=\"rec_in_call\">Inspelning kan bara slås på/av när inget samtal pågår</string>\n    <string name=\"choose_destination_uri\">Välj destinations-URI</string>\n    <string name=\"audio_permissions\">baresip behöver behörighet för ”Mikrofon” för röstsamtal, behörighet för ”Närliggande enheter” för Bluetooth-mikrofon-/högtalardetektering, behörighet för ’Meddelanden’ för att publicera meddelanden och i Android 9 behörighet för ”Lagring” för säkerhetskopierings-/återställningsåtgärder.</string>\n    <string name=\"audio_delay\">Fördröjt ljud</string>\n    <string name=\"invalid_audio_delay\">Ogiltigt värde för fördröjt ljud \\'%1$s\\'. Giltiga värden är från 100 till 3000.</string>\n    <string name=\"permissions_rationale\">Behörigheters motivering</string>\n    <string name=\"reg_int_help\">Avgör hur ofta (i sekunder) baresip skickar REGISTER-förfrågningar. Giltiga värden är från 60 till 3600.</string>\n    <string name=\"audio_delay_help\">Tid (i millisekunder) att vänta på ljud från motparten när ett samtal upprättats. Sätt till ett högre värde om du saknar ljud från motparten i början av samtalet.</string>\n    <string name=\"audio_and_video_permissions\">baresip+ behöver behörighet för ”Mikrofon” för röstsamtal, behörighet för ”Kamera” för videosamtal, behörighet för ”Närliggande enheter” för Bluetooth-mikrofon-/högtalardetektering, behörighet för ’Meddelanden’ för att publicera meddelanden och i Android 9 behörighet för ”Lagring” för säkerhetskopierings-/återställningsåtgärder.</string>\n    <string name=\"call_answered\">Samtal besvarat</string>\n    <string name=\"call_answered_elsewhere\">Samtal besvarat någon annanstans</string>\n    <string name=\"call_missed\">Samtal missat</string>\n    <string name=\"call_rejected\">Samtal nekat</string>\n    <string name=\"call_recording_title\">Samtalsinspelning</string>\n    <string name=\"microphone_title\">Mikrofon</string>\n    <string name=\"speakerphone_title\">Högtalartelefon</string>\n    <string name=\"redirect_mode_help\">Väljer om begäran om vidarekoppling av samtal ska följas automatiskt eller om bekräftelse ska begäras.</string>\n    <string name=\"redirect_mode\">Vidarekopplingsläge</string>\n    <string name=\"numeric_keypad\">Numeriskt tangentbord</string>\"\n    <string name=\"numeric_keypad_help\">Om denna ruta är markerad visas det numeriska tangentbordet när fältet ”Ring till …” är markerat.</string>\"\n    <string name=\"reply\">Svara</string>\n    <string name=\"save\">Spara</string>\n    <string name=\"call_auto_rejected\">Automatiskt avvisat samtal från %1$s</string>\n    <string name=\"playing_recording\">Spelar upp inspelning …</string>\n    <string name=\"appear_on_top_permission\">Automatisk start kräver behörighet för att visas överst.</string>\n    <string name=\"default_phone_app\">Telefonapp som standard</string>\n    <string name=\"dialer_role_not_available\">Uppringningsrollen är inte tillgänglig</string>\n    <string name=\"default_phone_app_help\">Om denna ruta är markerad är baresip standardappen för telefoni. Markera inte denna ruta om din enhet kan behöva hantera andra samtal eller meddelanden än SIP-samtal.</string>\n    <string name=\"no_read_permission\">Ingen läsbehörighet för extern lagring</string>\n    <string name=\"speaker_phone\">Högtalartelefon</string>\n    <string name=\"speaker_phone_help\">Om denna ruta är markerad aktiveras högtalartelefonen automatiskt när samtalet inleds.</string>\n    <string name=\"microphone_gain_help\">Multiplicera mikrofonvolymen med detta decimaltal. Minsta värde är 1,0 (fabriksinställning) som inaktiverar mikrofonförstärkningen. Högre värden kan påverka ljudkvaliteten negativt.</string>\n    <string name=\"microphone_gain\">Mikrofonförstärkning</string>\n    <string name=\"invalid_microphone_gain\">Ogiltigt värde för mikrofonförstärkning</string>\n    <string name=\"tone_country_help\">Land för ringsignal, vänteläge och upptaget-signaler</string>\n    <string name=\"colorblind\">Färgblind</string>\n    <string name=\"colorblind_help\">Använd vänliga ikoner för registreringsstatus för färgblinda</string>\n    <string name=\"proximity_sensing\">Närhetssensor</string>\"&gt;\n    <string name=\"proximity_sensing_help\">Om denna ruta är markerad är närhetssensorn aktiv under samtal.</string>\n    <string name=\"video_fps\">Bildfrekvens för video</string>\n    <string name=\"video_fps_help\">Videobildfrekvens som kommer att erbjudas under SDP-handskakningen. Giltiga värden är mellan 10 och 30.</string>\n    <string name=\"invalid_fps\">Ogiltig bildfrekvens \\'%1$d\\'</string>\n    <string name=\"ringtone\">Ringsignal</string>\n    <string name=\"select_ringtone\">Välj ringsignal</string>\n    <string name=\"user_agent\">User Agent</string>\n    <string name=\"user_agent_help\">Anpassat värde för SIP-begäran/svar User-Agent-rubrikfält</string>\n    <string name=\"invalid_user_agent\">Ogiltigt värde i fältet User-Agent-header</string>\n    <string name=\"favorite\">Favorit</string>\n    <string name=\"favorite_help\">Om denna ruta är markerad visas kontakten bland andra favoriter högst upp i kontaktlistan.</string>\n    <string name=\"call_request\">Samtalsbegäran</string>\n    <string name=\"call_request_query\">Accepterar du begäran om att ringa ’%1$s’?</string>\n    <string name=\"redirect_notice\">Automatisk omkoppling till ’%1$s’\\\\</string>\n    <string name=\"redirect_request\">Omkopplingsbegäran</string>\n    <string name=\"redirect_request_query\">Accepterar du vidarekoppling av samtal till ’%1$s’?</string>\n    <string name=\"restore_unzip_failed\">Det gick inte att återställa applikationsdata. Android version 14 och senare tillåter inte återställning av data som säkerhetskopierats före %1$s version %2$s.</string>\n    <string name=\"no_aec\">Ingen hårdvara för akustisk ekokompensering!</string>\n    <string name=\"call_recording_tip\">Om funktionen är aktiverad kommer nya inkommande och utgående samtal att spelas in. Inspelningarna kan spelas upp på sidan Samtalsdetaljer</string>\n    <string name=\"microphone_tip\">Om funktionen aktiveras under samtalet stängs mikrofonen av.</string>\n    <string name=\"speakerphone_tip\">Om funktionen är aktiverad spelas ljudet upp via enhetens högtalartelefon.</string>\n    <string name=\"rel_100\">Reliable Provisional Responses</string>\n    <string name=\"rel_100_help\">Om detta är markerat, ange stöd för tillförlitliga provisoriska svar (RFC 3262).</string>\n    <string name=\"dtmf_auto\">In-band RTP eller SIP INFO</string>\n    <string name=\"tone_country\">Signalland</string>\n    <string name=\"dynamic_colors\">Dynamiska färger</string>\n    <string name=\"dynamic_colors_help\">Använd dynamiska färger om det är aktiverat i Android-inställningarna</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-ta/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"about_title\">பரேசிப் பற்றி</string>\n    <string name=\"about_title_plus\">Paresip+ பற்றி</string>\n    <string name=\"account\">கணக்கு</string>\n    <string name=\"account_nickname_help\">இந்த கணக்கை PARESIP பயன்பாட்டில் அடையாளம் காண புனைப்பெயர் (ஏதேனும் இருந்தால்) பயன்படுத்தப்படுகிறது.</string>\n    <string name=\"invalid_authentication_password\">தவறான அங்கீகார கடவுச்சொல் \\'%1$s\\'</string>\n    <string name=\"outbound_proxies\">வெளிச்செல்லும் ப்ராக்சிகள்</string>\n    <string name=\"sip_uri_of_proxy_server\">பதிலாள் சேவையகத்தின் SIP யூரி</string>\n    <string name=\"sip_uri_of_another_proxy_server\">மற்றொரு பதிலாள் சேவையகத்தின் யூரி</string>\n    <string name=\"register\">பதிவு செய்யுங்கள்</string>\n    <string name=\"video_codecs\">வீடியோ கோடெக்குகள்</string>\n    <string name=\"appear_on_top_permission\">தானியங்கி தொடக்க தேவைகள் சிறந்த அனுமதியில் தோன்றும்.</string>\n    <string name=\"battery_optimizations\">பேட்டரி மேம்படுத்தல்கள்</string>\n    <string name=\"battery_optimizations_help\">பேட்டரி மேம்படுத்தல்களை முடக்கு (பரிந்துரைக்கப்படுகிறது) ஆண்ட்ராய்டு ப்ரெசிப்பின் நெட்வொர்க்கிற்கான அணுகலை கட்டுப்படுத்தும் அல்லது காத்திருப்பு நிலைக்கு பெரெசிப்பில் நுழைகிறது என்ற வாய்ப்பைக் குறைக்க விரும்பினால்.</string>\n    <string name=\"default_phone_app\">இயல்புநிலை தொலைபேசி பயன்பாடு</string>\n    <string name=\"dialer_role_not_available\">டயலர் பங்கு கிடைக்கவில்லை</string>\n    <string name=\"default_phone_app_help\">சரிபார்க்கப்பட்டால், PARESIP இயல்புநிலை தொலைபேசி பயன்பாடாகும். SIP அழைப்புகள் அல்லது செய்திகளைத் தவிர உங்கள் சாதனம் கையாள வேண்டுமா என்று சரிபார்க்க வேண்டாம்.</string>\n    <string name=\"listen_address\">முகவரியைக் கேளுங்கள்</string>\n    <string name=\"invalid_listen_address\">தவறான கேளுங்கள் முகவரி</string>\n    <string name=\"address_family\">முகவரி குடும்பம்</string>\n    <string name=\"microphone_gain\">மைக்ரோஃபோன் ஆதாயம்</string>\n    <string name=\"invalid_opus_bitrate\">தவறான ஓபச் பிட்ரேட்</string>\n    <string name=\"invalid_opus_packet_loss\">தவறான ஓபச் பாக்கெட் இழப்பு விழுக்காடு</string>\n    <string name=\"invalid_fps\">ஒரு நொடிக்கு தவறான பிரேம்கள் \\'%1$d\\'</string>\n    <string name=\"user_agent\">பயனர் முகவர்</string>\n    <string name=\"accept\">ஏற்றுக்கொள்</string>\n    <string name=\"deny\">மறுக்கவும்</string>\n    <string name=\"add\">கூட்டு</string>\n    <string name=\"delete\">நீக்கு</string>\n    <string name=\"edit\">தொகு</string>\n    <string name=\"status\">நிலை</string>\n    <string name=\"error\">பிழை</string>\n    <string name=\"restart_request\">கோரிக்கையை மறுதொடக்கம் செய்யுங்கள்</string>\n    <string name=\"invalid_authentication_username\">தவறான அங்கீகார பயனர்பெயர் \\'%1$s\\'</string>\n    <string name=\"nickname\">புனைப்பெயர்</string>\n    <string name=\"invalid_account_nickname\">தவறான கணக்கு புனைப்பெயர் \\'%1$s\\'</string>\n    <string name=\"non_unique_account_nickname\">\\'%1$s\\' என்ற புனைப்பெயர் ஏற்கனவே உள்ளது</string>\n    <string name=\"display_name\">காட்சி பெயர்</string>\n    <string name=\"display_name_help\">வெளிச்செல்லும் கோரிக்கைகளின் யூரி இலிருந்து பெயர் (ஏதேனும் இருந்தால்) பயன்படுத்தப்படுகிறது.</string>\n    <string name=\"invalid_display_name\">தவறான காட்சி பெயர் \\'%1$s\\'</string>\n    <string name=\"authentication_username\">அங்கீகார பயனர்பெயர்</string>\n    <string name=\"authentication_username_help\">அங்கீகார பயனர்பெயர் SIP கோரிக்கைகளின் ஏற்பு தேவைப்பட்டால். இயல்புநிலை மதிப்பு கணக்கின் பயனர்பெயர்.</string>\n    <string name=\"authentication_password\">அங்கீகார கடவுச்சொல்</string>\n    <string name=\"authentication_password_help\">64 எழுத்துக்கள் வரை அங்கீகார கடவுச்சொல். அங்கீகார பயனர்பெயர் வழங்கப்பட்டால், ஆனால் கடவுச்சொல் வழங்கப்படவில்லை என்றால், பெரெசிப் எப்போது தொடங்கப்படுகிறது என்று கேட்கப்படும்.</string>\n    <string name=\"stun_server\">ச்டன்/டர்ன் சேவையகம்</string>\n    <string name=\"stun_server_help\">படிவத் திட்டத்தின் ச்டன்/டர்ன் சர்வர் யூரி: புரவலன் [: போர்ட்] [? போக்குவரத்து = யுடிபி | டி.சி.பி], அங்கு திட்டம் \\'ச்டன்\\', \\'ச்டன்ச்\\', \\'டர்ன்\\' அல்லது \\'திருப்பங்கள்\\'. ச்டன் மற்றும் பனி நெறிமுறைகளுக்கான தொழிற்சாலை இயல்புநிலை ச்டன் சேவையகம் \\'ச்டன்: stun.l.google.com: 19302\\' பொது கூகிள் ச்டன் சேவையகத்தை சுட்டிக்காட்டுகிறது. தொழிற்சாலை இயல்புநிலை திருப்ப சேவையகம் இல்லை.</string>\n    <string name=\"outbound_proxies_help\">கோரிக்கைகளை அனுப்பும்போது பயன்படுத்தப்பட வேண்டிய ஒன்று அல்லது இரண்டு ப்ராக்சிகளின் சிப் யூரி. இரண்டு வழங்கப்பட்டால், பதிவு கோரிக்கைகள் இரண்டிற்கும் அனுப்பப்படும் மற்றும் பிற கோரிக்கைகள் பதிலளிக்கும் ஒன்றுக்கு அனுப்பப்படும். வெளிச்செல்லும் பதிலாள் எதுவும் வழங்கப்படாவிட்டால், டி.என்.எச் நாப்டிஆர்/எச்.ஆர்.வி/காலீ யூரி ஓச்ட்பார்ட்டின் பதிவு தேடல் ஆகியவற்றின் அடிப்படையில் கோரிக்கைகள் அனுப்பப்படுகின்றன. SIP யூரி இன் ஓச்ட்பார்ட் ஒரு IPv6 முகவரி என்றால், முகவரி அடைப்புக்குறிக்குள் எழுதப்பட வேண்டும் [].\\n எடுத்துக்காட்டுகள்:\\n • SIP: எடுத்துக்காட்டு.காம்: 5061; போக்குவரத்து = TLS\\n • சிப்: [2001: 67 சி: 223: 777 :: 10]; போக்குவரத்து = டி.சி.பி.\\n • SIP: 192.168.43.50: 443; போக்குவரத்து = WSS</string>\n    <string name=\"invalid_proxy_server_uri\">தவறான பதிலாள் சர்வர் யூரி \\'%1$s\\'</string>\n    <string name=\"register_help\">சரிபார்க்கப்பட்டால், பதிவு இயக்கப்பட்டால் மற்றும் பதிவு வேண்டுகோள்களால் குறிப்பிடப்பட்ட இடைவெளியில் பதிவு கோரிக்கைகள் அனுப்பப்படுகின்றன.</string>\n    <string name=\"reg_int\">பதிவு இடைவெளி</string>\n    <string name=\"reg_int_help\">எவ்வளவு அடிக்கடி (நொடிகளில்) பரேசிப் பதிவு கோரிக்கைகளை அனுப்புகிறார் என்று சொல்கிறது. செல்லுபடியாகும் மதிப்புகள் 60 முதல் 3600 வரை.</string>\n    <string name=\"invalid_reg_int\">தவறான பதிவு இடைவெளி \\'%1$s\\'</string>\n    <string name=\"media_nat\">மீடியா நாட் டிராவர்சல்</string>\n    <string name=\"media_nat_help\">மீடியா நாட் டிராவர்சல் நெறிமுறையைத் தேர்ந்தெடுக்கிறது (ஏதேனும் இருந்தால்). சாத்தியமான தேர்வுகள் ச்டன் (NAT, RFC 5389 க்கான அமர்வு பயண பயன்பாடுகள்) மற்றும் ICE (ஊடாடும் இணைப்பு நிறுவனம், RFC 5245).</string>\n    <string name=\"invalid_stun_server\">தவறான ச்டன்/டர்ன் சர்வர் யூரி \\'%1$s\\' \\'</string>\n    <string name=\"stun_username\">ச்டன்/டர்ன் பயனர்பெயர்</string>\n    <string name=\"stun_username_help\">ச்டன்/டர்ன் சேவையகத்தால் தேவைப்பட்டால் பயனர்பெயர்</string>\n    <string name=\"invalid_stun_username\">தவறான பயனர்பெயர் \\'%1$s\\'</string>\n    <string name=\"stun_password\">கடவுச்சொல்லை ச்டன்/திருப்புங்கள்</string>\n    <string name=\"stun_password_help\">கடவுச்சொல் ச்டன்/டர்ன் சேவையகம் தேவைப்பட்டால்</string>\n    <string name=\"invalid_stun_password\">தவறான கடவுச்சொல் \\'%1$s\\'</string>\n    <string name=\"media_encryption\">ஊடக குறியாக்கம்</string>\n    <string name=\"media_encryption_help\">மீடியா போக்குவரத்து குறியாக்க நெறிமுறையைத் தேர்ந்தெடுக்கிறது (ஏதேனும் இருந்தால்).\\n • ZRTP (பரிந்துரைக்கப்படுகிறது) என்பது அழைப்பு நிறுவப்பட்ட பின்னர் ZRTP இறுதி முதல் இறுதி ஊடக குறியாக்க பேச்சுவார்த்தை முயற்சிக்கப்படுகிறது.\\n • டி.டி.எல்.எச்-எச்.ஆர்.டி.பி.எஃப் என்பது வெளிச்செல்லும் அழைப்பில் யுடிபி/டி.எல்.எச்/ஆர்.டி.பி/எச்.ஏ.வி.பி.எஃப் வழங்கப்படுகிறது, மேலும் RTP/SAVP, RTP/SAVPF, UDP/TLS/RTP/SAVP, அல்லது UDP/TLS/RTP/SAVPF ஆகியவை உள்நுழைவுக்கு வழங்கப்பட்டால் பயன்படுத்தப்பட்டால் பயன்படுத்தப்படுகின்றன அழைப்பு.\\n • SRTP-MANDF என்றால், RTP/SAVPF வெளிச்செல்லும் அழைப்பில் வழங்கப்படுகிறது மற்றும் உள்வரும் அழைப்பில் தேவைப்படுகிறது.\\n • SRTP-MAND என்பது RTP/SAVP வெளிச்செல்லும் அழைப்பில் வழங்கப்படுகிறது மற்றும் உள்வரும் அழைப்பில் தேவைப்படுகிறது.\\n • SRTP என்றால், வெளிச்செல்லும் அழைப்பில் RTP/AVP வழங்கப்படுகிறது மற்றும் உள்வரும் அழைப்பில் வழங்கப்பட்டால் RTP/SAVP அல்லது RTP/SAVPF பயன்படுத்தப்படுகிறது.</string>\n    <string name=\"rtcp_mux\">RTCP மல்டிபிளெக்சிங்</string>\n    <string name=\"rtcp_mux_help\">சரிபார்க்கப்பட்டால், RTP மற்றும் RTCP பாக்கெட்டுகள் ஒரு துறைமுகத்தில் (RFC 5761) மல்டிபிளெக்ச் செய்யப்படுகின்றன.</string>\n    <string name=\"rel_100\">நம்பகமான தற்காலிக பதில்கள்</string>\n    <string name=\"rel_100_help\">சரிபார்க்கப்பட்டால், நம்பகமான தற்காலிக பதில்களுக்கான ஆதரவைக் குறிக்கவும் (RFC 3262).</string>\n    <string name=\"dtmf_mode\">டிடிஎம்எஃப் பயன்முறை</string>\n    <string name=\"dtmf_mode_help\">டி.டி.எம்.எஃப் டோன்கள் 0–9, #, *மற்றும் ஏ-டி எவ்வாறு அனுப்பப்படுகின்றன என்பதைத் தேர்ந்தெடுக்கிறது.</string>\n    <string name=\"dtmf_inband\">இன்-பேண்ட் ஆர்டிபி நிகழ்வுகள்</string>\n    <string name=\"dtmf_info\">SIP செய்தி கோரிக்கைகள்</string>\n    <string name=\"dtmf_auto\">இன்-பேண்ட் ஆர்டிபி அல்லது எச்ஐபி செய்தி</string>\n    <string name=\"answer_mode\">பதில் பயன்முறை</string>\n    <string name=\"answer_mode_help\">உள்வரும் அழைப்புகளுக்கு எவ்வாறு பதிலளிக்கப்படுகிறது என்பதைத் தேர்ந்தெடுக்கிறது.</string>\n    <string name=\"redirect_mode\">திருப்பி பயன்முறை</string>\n    <string name=\"redirect_mode_help\">அழைப்பு திருப்பி கோரிக்கை தானாகவே பின்பற்றப்பட்டால் அல்லது உறுதிப்படுத்தல் கோரப்பட்டால் தேர்ந்தெடுக்கிறது.</string>\n    <string name=\"manual\">கையேடு</string>\n    <string name=\"auto\">தானியங்கி</string>\n    <string name=\"voicemail_uri\">குரல் அஞ்சல் யூரி</string>\n    <string name=\"voicemain_uri_help\">குரல் அஞ்சல் செய்திகளைச் சரிபார்க்க யூரி ஐ சிப் செய்யுங்கள். காலியாக இருந்தால், குரல் அஞ்சல் செய்திகள் (செய்தி காத்திருப்பு அறிகுறிகள்) சந்தா செலுத்தப்படவில்லை.</string>\n    <string name=\"country_code\">நாட்டின் குறியீடு</string>\n    <string name=\"country_code_help\">E.164 இந்த கணக்கின் நாடு குறியீடு. உள்வரும் அழைப்பு அல்லது செய்தியின் யூரி USERPART இலிருந்து \\'+\\' அடையாளத்துடன் தொடங்காத ஒரு தொலைபேசி எண்ணைக் கொண்டிருந்தால், தொடர்பு தேடல் தோல்வியுற்றால், இந்த நாட்டின் குறியீட்டில் எண் முன்னொட்டு மற்றும் தொடர்பு தேடல் மீண்டும் முயற்சிக்கப்படுகிறது. தொலைபேசி எண் \\'0\\' ஒற்றை இலக்கத்துடன் தொடங்கினால், எண் முன்னொட்டப்படுவதற்கு முன்பு \\'0\\' இலக்கத்தை அகற்றும்.</string>\n    <string name=\"invalid_country_code\">தவறான நாட்டு குறியீடு \\'%1$s\\'</string>\n    <string name=\"telephony_provider\">தொலைபேசி வழங்குநர்</string>\n    <string name=\"telephony_provider_help\">தொலைபேசி எண்களுக்கான அழைப்புகளில் பயன்படுத்தப்படும் SIP யூரி புரவலன் பகுதி. தொழிற்சாலை இயல்புநிலை என்பது கணக்கின் களமாகும். வழங்கப்படாவிட்டால், தொலைபேசி எண்களை அழைக்க இந்த கணக்கைப் பயன்படுத்த முடியாது.</string>\n    <string name=\"invalid_sip_uri_hostpart\">தவறான SIP யூரி புரவலன் பகுதி \\'%1$s\\'</string>\n    <string name=\"default_account\">இயல்புநிலை கணக்கு</string>\n    <string name=\"default_account_help\">சரிபார்க்கப்பட்டால், பரெசிப் தொடங்கும்போது இந்த கணக்கு தேர்ந்தெடுக்கப்படுகிறது.</string>\n    <string name=\"accounts\">கணக்குகள்</string>\n    <string name=\"new_account\">சிப் புதிய கணக்கு முகவரி</string>\n    <string name=\"accounts_help\">சிப் புதிய கணக்கு முகவரி வடிவம்: &lt;பயனர்&gt;@&lt;டொமைன்&gt; [: &lt;port&gt;] [; போக்குவரத்து = யுடிபி | டி.சி.பி | டி.எல்.எச்]. &lt;Port&gt; வழங்கப்பட்டு போக்குவரத்து நெறிமுறை வழங்கப்படாவிட்டால், போக்குவரத்து நெறிமுறை இயல்புநிலையாக UDP க்கு. &lt;port&gt; வழங்கப்படாவிட்டால் மற்றும் போக்குவரத்து நெறிமுறை வழங்கப்பட்டால், &lt;PORT&gt; இயல்புநிலை 5060 அல்லது 5061 (TLS) க்கு. எதுவும் வழங்கப்படவில்லை மற்றும் வெளிச்செல்லும் பதிலாள் குறிப்பிடப்படவில்லை என்றால், கணக்கின் பதிவாளர் (ஏதேனும் இருந்தால்) டொமைனின் டிஎன்எச் தகவல்களை மட்டுமே அடிப்படையாகக் கொண்டது.</string>\n    <string name=\"invalid_aor\">தவறான பயனர்@டொமைன் [: போர்ட்] [; போக்குவரத்து = யுடிபி | டி.சி.பி | டி.எல்.எச்] \\'%1$s\\' \\'</string>\n    <string name=\"account_exists\">கணக்கு \\'%1$s\\' ஏற்கனவே உள்ளது.</string>\n    <string name=\"account_allocation_failure\">புதிய கணக்கை ஒதுக்கத் தவறிவிட்டது.</string>\n    <string name=\"encrypt_password\">கடவுச்சொல்லை குறியாக்கவும்</string>\n    <string name=\"decrypt_password\">கடவுச்சொல்லை டிக்ரிப்ட் செய்யுங்கள்</string>\n    <string name=\"delete_account\">\\'%1$s\\' கணக்கை நீக்க விரும்புகிறீர்களா?</string>\n    <string name=\"missed_call_from\">இருந்து தவறவிட்ட அழைப்பு</string>\n    <string name=\"missed_calls\">தவறவிட்ட அழைப்புகள்</string>\n    <string name=\"missed_calls_count\">%1$d தவறவிட்ட அழைப்புகள்</string>\n    <string name=\"transfer_request_to\">அழைப்பு பரிமாற்ற கோரிக்கை</string>\n    <string name=\"call_auto_rejected\">%1$s இலிருந்து தானாக நிராகரிக்கப்பட்ட அழைப்பு</string>\n    <string name=\"call_history\">வரலாற்றை அழைக்கவும்</string>\n    <string name=\"call_details\">அழைப்பு விவரங்கள்</string>\n    <string name=\"call\">அழைப்பு</string>\n    <string name=\"calls_calls\">அழைப்புகள்</string>\n    <string name=\"calls_call\">அழைப்பு</string>\n    <string name=\"peer\">ஒப்பி</string>\n    <string name=\"direction\">திசை</string>\n    <string name=\"time\">நேரம்</string>\n    <string name=\"calls_duration\">காலம்</string>\n    <string name=\"calls_delete_question\">அழைப்பு வரலாற்றிலிருந்து \\' %1$s\\' %2$s ஐ நீக்க விரும்புகிறீர்களா?</string>\n    <string name=\"disable_history\">முடக்கு</string>\n    <string name=\"enable_history\">இயக்கு</string>\n    <string name=\"delete_history_alert\">கணக்கின் அழைப்பு வரலாற்றை \\'%1$s\\' ஐ நீக்க விரும்புகிறீர்களா?</string>\n    <string name=\"chat_with\">%1$s உடன் அரட்டையடிக்கவும்</string>\n    <string name=\"new_message\">புதிய செய்தி</string>\n    <string name=\"long_message_question\">செய்திகளை நீக்க விரும்புகிறீர்களா அல்லது தொடர்புகளில் \\'%1$s\\' ஐ சேர்க்க விரும்புகிறீர்களா?</string>\n    <string name=\"short_message_question\">செய்தியை நீக்க விரும்புகிறீர்களா?</string>\n    <string name=\"add_contact\">தொடர்பைச் சேர்க்கவும்</string>\n    <string name=\"sending_failed\">செய்தியை அனுப்புவது தோல்வியடைந்தது</string>\n    <string name=\"message_failed\">தோல்வியுற்றது</string>\n    <string name=\"chats\">அரட்டை வரலாறு</string>\n    <string name=\"today\">இன்று</string>\n    <string name=\"you\">நீங்கள்</string>\n    <string name=\"new_chat_peer\">புதிய அரட்டை பியர்</string>\n    <string name=\"long_chat_question\">பியர் \\'%1$s\\' உடன் அரட்டையை நீக்க விரும்புகிறீர்களா அல்லது தொடர்புகளுக்கு சகாக்களைச் சேர்க்க விரும்புகிறீர்களா?</string>\n    <string name=\"short_chat_question\">\\'%1$s\\' உடன் அரட்டையை நீக்க விரும்புகிறீர்களா?</string>\n    <string name=\"delete_chats_alert\">கணக்கின் அரட்டை வரலாற்றை \\'%1$s\\' ஐ நீக்க விரும்புகிறீர்களா?</string>\n    <string name=\"audio_codecs\">ஆடியோ கோடெக்குகள்</string>\n    <string name=\"configuration\">அமைப்புகள்</string>\n    <string name=\"start_automatically\">தானாகத் தொடங்குங்கள்</string>\n    <string name=\"start_automatically_help\">சரிபார்க்கப்பட்டால், சாதனம் (மறு) தொடங்கிய பின் PARESIP தானாகத் தொடங்குகிறது.</string>\n    <string name=\"restore\">மீட்டெடு</string>\n    <string name=\"listen_address_help\">ஐபி முகவரி மற்றும் படிவத்தின் துறை \\'முகவரி: துறைமுகம்\\' உள்வரும் எச்ஐபி கோரிக்கைகளுக்குப் பெரெசிப் கேட்கிறது. ஐபி முகவரி ஒரு ஐபிவி 6 முகவரி என்றால், அது அடைப்புக்குறிக்குள் எழுதப்பட வேண்டும் []. ஐபிவி 4 முகவரி 0.0.0.0 அல்லது ஐபிவி 6 முகவரி [::] கிடைக்கக்கூடிய எல்லா முகவரிகளிலும் பரேசிப் கேட்க வைக்கிறது. காலியாக இருந்தால் (தொழிற்சாலை இயல்புநிலை), கிடைக்கக்கூடிய அனைத்து முகவரிகளிலும் ஏதேனும் துறைமுகத்தில் பரெசிப் கேட்கிறது.</string>\n    <string name=\"address_family_help\">பரேசிப் எந்த ஐபி முகவரிகளைப் பயன்படுத்துகிறது என்பதைத் தேர்வுசெய்கிறது. IPv4 அல்லது IPv6 தேர்ந்தெடுக்கப்பட்டால், PARESIP IPv4 அல்லது IPv6 முகவரிகளை மட்டுமே பயன்படுத்துகிறது. இரண்டுமே தேர்வு செய்யப்படாவிட்டால், பரெசிப் ஐபிவி 4 மற்றும் ஐபிவி 6 முகவரிகளைப் பயன்படுத்துகிறது.</string>\n    <string name=\"dns_servers\">டிஎன்எச் சேவையகங்கள்</string>\n    <string name=\"dns_servers_help\">கமா டிஎன்எச் சேவையகங்களின் முகவரிகளின் பட்டியல். வழங்கப்படாவிட்டால், டிஎன்எச் சேவையக முகவரிகள் கணினியிலிருந்து மாறும் வகையில் பெறப்படுகின்றன. ஒவ்வொரு டிஎன்எச் முகவரியும் \\'ஐபி: துறைமுகம்\\' அல்லது \\'ஐபி\\' வடிவத்தில் இருக்கும். துறைமுகம் தவிர்க்கப்பட்டால், அது இயல்புநிலையாக 53 ஆக இருக்கும். ஐபி ஒரு ஐபிவி 6 முகவரி மற்றும் துறைமுகம் வழங்கப்பட்டால், ஐபி அடைப்புக்குறிக்குள் எழுதப்பட வேண்டும் []. உதாரணமாக, பட்டியல் \\'8.8.8.8:53, LEISER2001:4860:4860:8888]: :53\\' ஐ ஐபிவி 4 மற்றும் பொது கூகிள் டிஎன்எச் சேவையகங்களின் ஐபிவி 6 முகவரிகளுக்கு புள்ளிகள்.</string>\n    <string name=\"invalid_dns_servers\">தவறான டிஎன்எச் சேவையகங்கள்</string>\n    <string name=\"failed_to_set_dns_servers\">டிஎன்எச் சேவையகங்களை அமைப்பதில் தோல்வி</string>\n    <string name=\"tls_certificate_file\">டி.எல்.எச் சான்றிதழ் கோப்பு</string>\n    <string name=\"tls_certificate_file_help\">சரிபார்க்கப்பட்டால், TLS சான்றிதழ் கொண்ட ஒரு கோப்பு மற்றும் இந்த PARESIP நிகழ்வின் தனிப்பட்ட விசை ஆகியவை ஏற்றப்பட்டுள்ளன அல்லது ஏற்றப்படும். ஆண்ட்ராய்டு பதிப்பு 9 இல், \\'cert.pem\\' எனப்படும் கோப்பு பதிவிறக்க கோப்புறையிலிருந்து ஏற்றப்படுகிறது. பாதுகாப்பு காரணங்களுக்காக, ஏற்றப்பட்ட பிறகு கோப்பை நீக்கவும்.</string>\n    <string name=\"verify_server\">சேவையக சான்றிதழ்களை சரிபார்க்கவும்</string>\n    <string name=\"verify_server_help\">சரிபார்க்கப்பட்டால், டி.எல்.எச் போக்குவரத்து பயன்படுத்தப்படும்போது எச்ஐபி பயனர் முகவர் மற்றும் எச்ஐபி பதிலாள் சேவையகங்களின் டிஎல்எச் சான்றிதழ்களை பாரெசிப் சரிபார்க்கிறது.</string>\n    <string name=\"tls_ca_file\">TLS CA கோப்பு</string>\n    <string name=\"audio_settings\">ஆடியோ அமைப்புகள்</string>\n    <string name=\"tls_ca_file_help\">சரிபார்க்கப்பட்டால், ஆண்ட்ராய்டு OS இல் சேர்க்கப்படாத அத்தகைய சான்றிதழ் அதிகாரிகளின் TLS சான்றிதழ்களைக் கொண்ட ஒரு கோப்பு அல்லது ஏற்றப்படும். ஆண்ட்ராய்டு பதிப்பு 9 இல், \\'Ca_certs.crt\\' என்ற கோப்பு பதிவிறக்க கோப்புறையிலிருந்து ஏற்றப்படுகிறது.</string>\n    <string name=\"speaker_phone\">அவைத்தலைவர் தொலைபேசி</string>\n    <string name=\"microphone_gain_help\">இந்த தசம எண்ணால் மைக்ரோஃபோன் அளவைப் பெருக்கவும். மைக்ரோஃபோன் ஆதாயத்தை முடக்கும் குறைந்தபட்ச மதிப்பு 1.0 (தொழிற்சாலை இயல்புநிலை) ஆகும். பெரிய மதிப்புகள் ஆடியோ தரத்தை எதிர்மறையாக பாதிக்கலாம்.</string>\n    <string name=\"speaker_phone_help\">சரிபார்க்கப்பட்டால், அழைப்பு தொடங்கும் போது ச்பீக்கர் தொலைபேசி தானாக இயக்கப்படும்.</string>\n    <string name=\"audio_modules_title\">ஆடியோ தொகுதிகள்</string>\n    <string name=\"audio_modules_help\">சரிபார்க்கப்பட்ட தொகுதிகள் வழங்கிய ஆடியோ கோடெக்குகள் கணக்குகளால் பயன்படுத்தப்படுகின்றன.</string>\n    <string name=\"failed_to_load_module\">தொகுதி ஏற்றுவதில் தோல்வி.</string>\n    <string name=\"invalid_microphone_gain\">தவறான மைக்ரோஃபோன் ஆதாய மதிப்பு</string>\n    <string name=\"opus_bit_rate\">ஓபச் பிட் வீதம்</string>\n    <string name=\"opus_bit_rate_help\">ஓபச் ஆடியோ ச்ட்ரீம் பயன்படுத்தும் சராசரி அதிகபட்ச பிட் வீதம். செல்லுபடியாகும் மதிப்புகள் 6000-510000. தொழிற்சாலை இயல்புநிலை 28000 ஆகும்.</string>\n    <string name=\"opus_packet_loss\">எதிர்பார்க்கப்படும் ஓபச் பாக்கெட் இழப்பு</string>\n    <string name=\"opus_packet_loss_help\">எதிர்பார்க்கப்படும் ஓபச் ஆடியோ ச்ட்ரீம் பாக்கெட் இழப்பு விழுக்காடு, 0–100 முதல். தொழிற்சாலை இயல்புநிலை மதிப்பு 1. மதிப்பு 0 மேலும் ஓபச் முன்னோக்கி பிழை திருத்தம் (FEC) ஐ முடக்குகிறது.</string>\n    <string name=\"audio_delay\">ஆடியோ நேரந்தவறுகை</string>\n    <string name=\"invalid_audio_delay\">தவறான ஆடியோ நேரந்தவறுகை \\'%1$s\\'. செல்லுபடியாகும் மதிப்புகள் 100 முதல் 3000 வரை.</string>\n    <string name=\"default_call_volume\">இயல்புநிலை அழைப்பு தொகுதி</string>\n    <string name=\"default_call_volume_help\">அமைக்கப்பட்டால், இயல்புநிலை அழைப்பு ஆடியோ தொகுதி 1–10.</string>\n    <string name=\"tone_country\">தொனி நாடு</string>\n    <string name=\"tone_country_help\">அழைப்பு ரிங்கிங், காத்திருப்பு மற்றும் காலீ பிசியான டோன்களின் நாடு</string>\n    <string name=\"audio_delay_help\">அழைப்பு நிறுவப்படும்போது காலியில் இருந்து ஆடியோவைக் காத்திருக்க நேரம் (மில்லி விநாடிகளில்). அழைப்பின் தொடக்கத்தில் காலியிலிருந்து ஆடியோவைத் தவறவிட்டால் அதிக மதிப்புக்கு அமைக்கவும்.</string>\n    <string name=\"dark_theme\">இருண்ட கருப்பொருள்</string>\n    <string name=\"dark_theme_help\">இருண்ட காட்சி கருப்பொருள் கட்டாயப்படுத்துங்கள்</string>\n    <string name=\"video_size\">வீடியோ பிரேம் அளவு</string>\n    <string name=\"video_size_help\">கடத்தப்பட்ட வீடியோ பிரேம்களின் அளவு (அகலம் ஃச் உயரம்)</string>\n    <string name=\"video_fps\">நொடிக்கு வீடியோ பிரேம்கள்</string>\n    <string name=\"reset_config_alert\">தொழிற்சாலை இயல்புநிலை மதிப்புகளுக்கு அமைப்புகளை மீட்டமைக்க விரும்புகிறீர்களா?</string>\n    <string name=\"video_fps_help\">எச்.டி.பி ஏண்ட்சேக்கின் போது வழங்கப்படும் வீடியோ பிரேம் வீதம். செல்லுபடியாகும் மதிப்புகள் 10 முதல் 30 வரை.</string>\n    <string name=\"user_agent_help\">தனிப்பயன் SIP கோரிக்கை/பதில் பயனர்-முகவர் தலைப்பு புல மதிப்பு</string>\n    <string name=\"invalid_user_agent\">தவறான பயனர்-முகவர் தலைப்பு புல மதிப்பு</string>\n    <string name=\"contacts_help\">பேராசிப் தொடர்புகள், ஆண்ட்ராய்டு தொடர்புகள் அல்லது இரண்டும் பயன்படுத்தப்பட்டால் தேர்வுசெய்கின்றன. இரண்டும் பயன்படுத்தப்பட்டு, ஒரே பெயருடன் ஒரு தொடர்பு இரு தொடர்புகளிலும் இருந்தால், பெரெசிப் தொடர்பு தேர்ந்தெடுக்கப்படும்.</string>\n    <string name=\"both\">இரண்டும்</string>\n    <string name=\"debug\">பிழைத்திருத்தம்</string>\n    <string name=\"debug_help\">சரிபார்க்கப்பட்டால், பிழைத்திருத்த மற்றும் செய்தி நிலை பதிவு செய்திகளை Logcat க்கு வழங்குகிறது.</string>\n    <string name=\"sip_trace\">சிப் சுவடு</string>\n    <string name=\"sip_trace_help\">சரிபார்க்கப்பட்டால், பிழைத்திருத்தத்தை சரிபார்க்கினால், லோகாட் செய்திகளில் SIP கோரிக்கை மற்றும் மறுமொழி சுவடு ஆகியவை அடங்கும். பரேசிப் தொடக்கத்தில் தானாக சரிபார்க்கப்படாமல்.</string>\n    <string name=\"reset_config\">தொழிற்சாலை இயல்புநிலைகளுக்கு மீட்டமைக்கவும்</string>\n    <string name=\"reset_config_help\">சரிபார்க்கப்பட்டால், அமைப்புகள் தொழிற்சாலை இயல்புநிலை மதிப்புகளுக்கு மீட்டமைக்கப்படும்.</string>\n    <string name=\"reset\">மீட்டமை</string>\n    <string name=\"read_cert_error\">\\'Cert.pem\\' கோப்பைப் படிக்கத் தவறிவிட்டது.</string>\n    <string name=\"config_restart\">புதிய அமைப்புகளை செயல்படுத்த நீங்கள் பேராசிப்பை மறுதொடக்கம் செய்ய வேண்டும். இப்போது மறுதொடக்கம் செய்யவா?</string>\n    <string name=\"consent_request\">ஒப்புதல் கோரிக்கை</string>\n    <string name=\"read_ca_certs_error\">\\'Ca_certs.crt\\' கோப்பைப் படிக்கத் தவறிவிட்டது.</string>\n    <string name=\"contacts_consent\">ஆண்ட்ராய்டு தொடர்புகள் தேர்ந்தெடுக்கப்பட்டால், அவை SIP மற்றும் TEL URIS பற்றிய குறிப்புகளாக அழைப்பு மற்றும் செய்தியிடலில் பயன்படுத்தப்படலாம். பரேசிப் பயன்பாடு ஆண்ட்ராய்டு தொடர்புகளை சேமிக்காது அல்லது அவற்றை யாருடனும் பகிராது. ஆண்ட்ராய்டு தொடர்புகளை Paresip இல் கிடைக்கச் செய்வதற்கு, இங்கே விவரிக்கப்பட்டுள்ளபடி அவற்றின் பயன்பாட்டை நீங்கள் ஏற்றுக்கொள்ள வேண்டும் மற்றும் பயன்பாட்டின் <a href=\"https://raw.githubusercontent. txt \"> தனியுரிமைக் கொள்கை </a>.</string>\n    <string name=\"new_contact\">புதிய தொடர்பு</string>\n    <string name=\"contact_name\">பெயர்</string>\n    <string name=\"sip_or_tel_uri\">சிப் அல்லது டெல் யூரி</string>\n    <string name=\"user_domain_or_number\">பயனர்@டொமைன் அல்லது தொலைபேசி எண்</string>\n    <string name=\"favorite\">பிடித்த</string>\n    <string name=\"invalid_contact\">தவறான தொடர்பு பெயர் \\'%1$s\\'</string>\n    <string name=\"contact_already_exists\">\\'%1$s\\' ஐ தொடர்பு கொள்ளுங்கள்.</string>\n    <string name=\"android_contact_help\">சரிபார்க்கப்பட்டால், இந்த தொடர்பு ஆண்ட்ராய்டு தொடர்புகளில் சேர்க்கப்படும்.</string>\n    <string name=\"avatar_image\">சுயவிவர படம்</string>\n    <string name=\"contacts\">தொடர்புகள்</string>\n    <string name=\"contact_action_question\">\\'%1$s\\' க்கு அழைக்க அல்லது செய்தியை அனுப்ப விரும்புகிறீர்களா?</string>\n    <string name=\"send_message\">செய்தி அனுப்பவும்</string>\n    <string name=\"contact_delete_question\">\\'%1$s\\' என்ற தொடர்பை நீக்க விரும்புகிறீர்களா?</string>\n    <string name=\"alert\">விழிப்புணர்வு</string>\n    <string name=\"info\">தகவல்</string>\n    <string name=\"notice\">அறிவிப்பு</string>\n    <string name=\"cancel\">ரத்துசெய்</string>\n    <string name=\"ok\">சரி</string>\n    <string name=\"yes\">ஆம்</string>\n    <string name=\"no\">இல்லை</string>\n    <string name=\"confirmation\">உறுதிப்படுத்தல்</string>\n    <string name=\"anonymous\">அநாமதேய</string>\n    <string name=\"unknown\">தெரியவில்லை</string>\n    <string name=\"invalid_sip_or_tel_uri\">தவறான SIP அல்லது TEL யூரி \\'%1$s\\'</string>\n    <string name=\"backup\">காப்புப்பிரதி</string>\n    <string name=\"about\">பற்றி</string>\n    <string name=\"restart\">மறுதொடக்கம்</string>\n    <string name=\"quit\">வெளியேறு</string>\n    <string name=\"outgoing_call_to_dots\">அழைக்கவும்…</string>\n    <string name=\"incoming_call_from_dots\">இருந்து அழைக்கவும்…</string>\n    <string name=\"allow_video\">\\'%1$s\\' உடன் வீடியோவை அனுப்புவதையும் பெறுவதையும் ஏற்றுக்கொள்கிறீர்களா?</string>\n    <string name=\"allow_video_send\">\\'%1$s\\' க்கு வீடியோ அனுப்புவதை ஏற்றுக்கொள்கிறீர்களா?</string>\n    <string name=\"allow_video_recv\">\\'%1$s\\' இலிருந்து வீடியோவைப் பெறுவதை ஏற்றுக்கொள்கிறீர்களா?</string>\n    <string name=\"diverted_by_dots\">திருப்பி விடப்பட்டது…</string>\n    <string name=\"no_telephony_provider\">கணக்கு \\'%1$s\\' தொலைபேசி வழங்குநர் இல்லை</string>\n    <string name=\"video_call\">வீடியோ அழைப்பு</string>\n    <string name=\"video_request\">வீடியோ கோரிக்கை</string>\n    <string name=\"call_is_on_hold\">அழைப்பு நிறுத்தி வைக்கப்பட்டுள்ளது</string>\n    <string name=\"rec_in_call\">அழைப்பு இணைக்கப்படாதபோது மட்டுமே பதிவை இயக்கலாம் அல்லது முடக்கலாம்</string>\n    <string name=\"call_transfer\">அழைப்பு பரிமாற்றம்</string>\n    <string name=\"blind\">குருட்டு</string>\n    <string name=\"attended\">கலந்து கொண்டார்</string>\n    <string name=\"transfer_destination\">இடத்தை மாற்றவும்</string>\n    <string name=\"choose_destination_uri\">இலக்கு யூரி ஐத் தேர்வுசெய்க</string>\n    <string name=\"transfer\">இடமாற்றம்</string>\n    <string name=\"dtmf\">டி.டி.எம்.எஃப்</string>\n    <string name=\"call_info\">அழைப்பு செய்தி</string>\n    <string name=\"transfer_failed\">இடமாற்றம் தோல்வியுற்றது</string>\n    <string name=\"call_info_not_available\">செய்தி எதுவும் கிடைக்கவில்லை</string>\n    <string name=\"duration\">காலம்: %1$d (நொடி)</string>\n    <string name=\"codecs\">கோடெக்குகள்</string>\n    <string name=\"rate\">தற்போதைய வீதம்: %1$s (kbits/s)</string>\n    <string name=\"average_rate\">சராசரி வீதம்: %1$s (kbits/s)</string>\n    <string name=\"packets\">பாக்கெட்டுகள்</string>\n    <string name=\"lost\">இழந்தது</string>\n    <string name=\"jitter\">நடுக்கம்: %1$s (எம்.எச்)</string>\n    <string name=\"voicemail_messages\">குரல் அஞ்சல் செய்திகள்</string>\n    <string name=\"you_have\">உங்களிடம் உள்ளது</string>\n    <string name=\"one_new_message\">ஒரு புதிய செய்தி</string>\n    <string name=\"listen\">கேளுங்கள்</string>\n    <string name=\"new_messages\">புதிய செய்திகள்</string>\n    <string name=\"one_old_message\">ஒரு பழைய செய்தி</string>\n    <string name=\"old_messages\">பழைய செய்திகள்</string>\n    <string name=\"and\">மற்றும்</string>\n    <string name=\"no_messages\">உங்களிடம் செய்திகள் இல்லை</string>\n    <string name=\"call_already_active\">உங்களிடம் ஏற்கனவே செயலில் அழைப்பு உள்ளது.</string>\n    <string name=\"start_failed\">பரேசிப் தொடங்கத் தவறிவிட்டார். இது தவறான அமைப்புகளின் மதிப்பு காரணமாக இருக்கலாம். கேளுங்கள் முகவரி, TLS சான்றிதழ் கோப்பு மற்றும் TLS CA கோப்பை சரிபார்க்கவும். பின்னர் பரேசிப்பை மறுதொடக்கம் செய்யுங்கள்.</string>\n    <string name=\"registering_failed\">%1$s இன் பதிவு தோல்வியுற்றது.</string>\n    <string name=\"verify\">கோரிக்கையை சரிபார்க்கவும்</string>\n    <string name=\"verify_sas\">SAS &lt;%1$s&gt; ஐ சரிபார்க்க விரும்புகிறீர்களா?</string>\n    <string name=\"transfer_request\">பரிமாற்ற கோரிக்கை</string>\n    <string name=\"transfer_request_query\">இந்த அழைப்பை \\'%1$s\\' க்கு மாற்ற ஏற்றுக்கொள்கிறீர்களா?</string>\n    <string name=\"call_request\">அழைப்பு கோரிக்கை</string>\n    <string name=\"call_request_query\">\\'%1$s\\' என்று அழைப்பதற்கான கோரிக்கையை நீங்கள் ஏற்றுக்கொள்கிறீர்களா?</string>\n    <string name=\"redirect_notice\">\\'%1$s\\' \\\\ க்கு தானியங்கி திருப்பிவிடுதல் \\\\</string>\n    <string name=\"redirect_request\">கோரிக்கையை திருப்பி விடுங்கள்</string>\n    <string name=\"redirect_request_query\">\\'%1$s\\' க்கு அழைப்பு திருப்பிவிடுவதை நீங்கள் ஏற்றுக்கொள்கிறீர்களா?</string>\n    <string name=\"call_failed\">அழைப்பு தோல்வியடைந்தது</string>\n    <string name=\"call_closed\">அழைப்பு மூடப்பட்டுள்ளது</string>\n    <string name=\"call_not_secure\">இந்த அழைப்பு பாதுகாப்பாக இல்லை!</string>\n    <string name=\"peer_not_verified\">இந்த அழைப்பு பாதுகாப்பானது, ஆனால் பியர் சரிபார்க்கப்படவில்லை!</string>\n    <string name=\"call_is_secure\">இந்த அழைப்பு பாதுகாப்பானது மற்றும் பியர் சரிபார்க்கப்பட்டது! சகாக்களை அவிழ்க்க விரும்புகிறீர்களா?</string>\n    <string name=\"unverify\">திறக்கவும்</string>\n    <string name=\"backed_up\">பயன்பாட்டு தரவு (பதிவுகள் தவிர) \\'%1$s\\' கோப்பில் காப்பக்கப்பட்டுள்ளது. ஆண்ட்ராய்டு பதிப்பு 9 இல், கோப்பு பதிவிறக்க கோப்புறையில் உள்ளது.</string>\n    <string name=\"backup_failed\">\\'%1$s\\' ஐ தாக்கல் செய்ய பயன்பாட்டு தரவை காப்புப் பிரதி எடுக்கத் தவறிவிட்டது. பயன்பாடுகளை சரிபார்க்கவும் → Paresip → அனுமதிகள் → சேமிப்பிடம்.</string>\n    <string name=\"restored\">பயன்பாட்டு தரவு மீட்டெடுக்கப்பட்டது. பரேசிப் மறுதொடக்கம் செய்யப்பட வேண்டும். இப்போது மறுதொடக்கம் செய்யவா?</string>\n    <string name=\"restore_failed\">பயன்பாட்டு தரவை மீட்டெடுப்பதில் தோல்வி. நீங்கள் சரியான கடவுச்சொல்லை வழங்கியிருக்கிறீர்களா என்பதையும், காப்புப்பிரதி கோப்பு இந்த பயன்பாட்டிலிருந்து வந்ததா என்பதையும் சரிபார்க்கவும். ஆண்ட்ராய்டு பதிப்புகள் 9 இல், பயன்பாடுகளையும் சரிபார்க்கவும் → Paresip → அனுமதிகள் → சேமிப்பிடம் மற்றும் அந்த கோப்பு \\'%1$s\\' பதிவிறக்க கோப்புறையில் உள்ளது.</string>\n    <string name=\"restore_unzip_failed\">பயன்பாட்டு தரவை மீட்டெடுப்பதில் தோல்வி. ஆண்ட்ராய்டு பதிப்பு 14 மற்றும் அதற்கு மேற்பட்டவை %1$s பதிப்பு %2$s க்கு முன் காப்புப் பிரதி எடுக்கப்பட்ட தரவை மீட்டமைக்க அனுமதிக்காது.</string>\n    <string name=\"no_notifications\">\\\"அறிவிப்புகள்\\\" இசைவு இல்லாமல் இந்த பயன்பாட்டை நீங்கள் பயன்படுத்த முடியாது.</string>\n    <string name=\"no_calls\">குரல் அழைப்புகளுக்கு BARESIP க்கு \\\"மைக்ரோஃபோன்\\\" இசைவு தேவை.</string>\n    <string name=\"no_video_calls\">வீடியோ அழைப்புகளைச் செய்ய அல்லது பதிலளிக்க \\\"கேமரா\\\" இசைவு வழங்கவும்.</string>\n    <string name=\"no_backup\">\\\"சேமிப்பு\\\" இசைவு இல்லாமல் காப்புப்பிரதியை உருவாக்க முடியாது.</string>\n    <string name=\"no_restore\">\\\"சேமிப்பு\\\" அனுமதியின்றி காப்புப்பிரதியை மீட்டெடுக்க முடியாது.</string>\n    <string name=\"no_android_contacts\">\\\"தொடர்புகள்\\\" அனுமதியின்றி நீங்கள் ஆண்ட்ராய்டு தொடர்புகளை அணுக முடியாது.</string>\n    <string name=\"no_cameras\">காணொளி நிழற்படபதிப்பான்கள் எதுவும் இல்லை!</string>\n    <string name=\"no_network\">பிணைய இணைப்பு இல்லை!</string>\n    <string name=\"audio_focus_denied\">ஆடியோ கவனம் மறுக்கப்பட்டது!</string>\n    <string name=\"permissions_rationale\">அனுமதிகள் பகுத்தறிவு</string>\n    <string name=\"audio_permissions\">குரல் அழைப்புகளுக்கான \\\"மைக்ரோஃபோன்\\\" இசைவு தேவை, ஊடலை மைக்ரோஃபோன்/ச்பீக்கர் கண்டறிதலுக்கான \\\"அருகிலுள்ள சாதனங்கள்\\\" இசைவு, அறிவிப்புகளை இடுகையிடுவதற்கான \\\"அறிவிப்புகள்\\\" இசைவு, மற்றும் காப்புப்பிரதி/மீட்டெடுப்பு செயல்பாடுகளுக்கான ஆண்ட்ராய்டு 9 \\\"சேமிப்பிடம்\\\" இசைவு.</string>\n    <string name=\"audio_and_video_permissions\">PARESIP+ தேவை குரல் அழைப்புகளுக்கான \\\"மைக்ரோஃபோன்\\\" இசைவு, வீடியோ அழைப்புகளுக்கான \\\"கேமரா\\\" இசைவு, \\\"அருகிலுள்ள சாதனங்கள்\\\" ஊடலை மைக்ரோஃபோன்/ச்பீக்கர் கண்டறிதலுக்கான இசைவு, அறிவிப்புகளை இடுகையிடுவதற்கான \\\"அறிவிப்புகள்\\\" இசைவு, மற்றும் ஆண்ட்ராய்டு 9 \\\"சேமிப்பிடம்\\\" காப்புப்பிரதி/மீட்டெடுப்பு நடவடிக்கைகளுக்கான அனுமதிகள்.</string>\n    <string name=\"calls_add_delete_question\">நீங்கள் தொடர்புகளுக்கு \\'%1$s\\' ஐ சேர்க்க விரும்புகிறீர்களா அல்லது அழைப்பு வரலாற்றிலிருந்து %2$s ஐ நீக்க விரும்புகிறீர்களா?</string>\n    <string name=\"numeric_keypad\">எண் விசைப்பலகை</string>\"\n    <string name=\"numeric_keypad_help\">சரிபார்க்கப்பட்டால், \\\"அழைப்பு…\\\" புலம் கவனம் செலுத்தும்போது எண் விசைப்பலகை காண்பிக்கப்படும்.</string>\"\n    <string name=\"ringtone\">ரிங்டோன்</string>\n    <string name=\"reply\">பதில்</string>\n    <string name=\"save\">சேமி</string>\n    <string name=\"call_recording_title\">அழைப்பு பதிவு</string>\n    <string name=\"microphone_title\">ஒலிவாங்கி</string>\n    <string name=\"microphone_tip\">அழைப்பின் போது செயல்படுத்தப்பட்டால், மைக்ரோஃபோன் முடக்கப்பட்டுள்ளது.</string>\n    <string name=\"speakerphone_tip\">செயல்படுத்தப்பட்டால், சாதன ச்பீக்கர்ஃபோன் வழியாக ஆடியோ இயக்கப்படுகிறது.</string>\n    <string name=\"no_read_permission\">வெளிப்புற சேமிப்பு வாசிப்பு இசைவு இல்லை</string>\n    <string name=\"select_ringtone\">ரிங்டோனைத் தேர்ந்தெடுக்கவும்</string>\n    <string name=\"call_recording_tip\">செயல்படுத்தப்பட்டால், புதிய உள்வரும் மற்றும் வெளிச்செல்லும் அழைப்புகள் பதிவு செய்யப்படும். அழைப்பு விவரங்கள் பக்கத்தில் பதிவுகளை இயக்கலாம்</string>\n    <string name=\"speakerphone_title\">ச்பீக்கர்ஃபோன்</string>\n    <string name=\"about_text\"><![CDATA[\n        <h1>PARESIP நூலக அடிப்படையிலான SIP பயனர் முகவர் </h1> \n<br> \n<p> சுஆ எய்னனென் & lt; jh@tutpro.com&gt; </p> \n<p> பதிப்பு %1$s </p> \n<br> \n<H2> பயன்பாட்டு குறிப்புகள் </H2> \n<ul> \n<li> பேராசிப்பின் அமைப்புகளில் இயல்புநிலை மதிப்புகள் உங்கள் தேவைகளைப் நிறைவு செய்யுங்கள் \n(உதவிக்கு உருப்படி தலைப்புகளைத் தொடவும்). </li> \n<li> பின்னர் கணக்குகளில், ஒன்று அல்லது அதற்கு மேற்பட்ட கணக்குகளை உருவாக்கவும் (மீண்டும் உதவிக்கு உருப்படி தலைப்புகளைத் தொடவும்). </li> \n<li> ஒரு கணக்கின் பதிவு நிலை வண்ண புள்ளியுடன் காட்டப்பட்டுள்ளது: பச்சை (பதிவு \nவெற்றி பெற்றது), மஞ்சள் (பதிவு நடந்து கொண்டிருக்கிறது), சிவப்பு (பதிவு தோல்வியுற்றது), வெள்ளை (பதிவு \nசெயல்படுத்தப்படவில்லை). </li> \n<li> நிலை புள்ளியைத் தொடுவது நேரடியாக கணக்கு உள்ளமைவுக்கு வழிவகுக்கிறது. </li> \n<li> பரேசிப் பார் ஐகான்களில் நீண்ட தொடுதல் ஐகான்களைப் பற்றிய தகவல்களைக் காட்டுகிறது. </li> \n<li> சைகை ச்வைப் கீழே காட்டப்பட்ட கணக்கை மீண்டும் பதிவு செய்கிறது. </li> \n<li> தற்போது காட்டப்பட்ட கணக்கில் நீண்ட தொடுதல் கணக்கின் பதிவை இயக்குகிறது அல்லது முடக்குகிறது. </li> \n<li> இடது/வலது சைகை கணக்குகளுக்கு இடையில் மாற்றுகிறது. </li> \n<li> காலீ காலியாக இருக்கும்போது அழைப்பு ஐகானைத் தொடுவதன் மூலம் முந்தைய அழைப்பு விருந்தை மீண்டும் காணலாம். </li> \n<li> அழைப்புகள் மற்றும் செய்திகளின் சகாக்கள் நீண்ட தொடுதல்களால் தொடர்புகளில் சேர்க்கப்படலாம். </li> \n<li> அழைப்புகள், அரட்டைகள், செய்திகள் மற்றும் தொடர்புகளை அகற்ற நீண்ட தொடுதல்களைப் பயன்படுத்தலாம். </li> \n<li> தொடர்பு ஐகானில் தொடுதல்/நீண்ட தொடுதல் பட அவதாரத்தை நிறுவ/அகற்ற பயன்படுத்தலாம். </li> \n<li> கோடெக்கை இயக்க/முடக்க ஆடியோ கோடெக்கில் நீண்ட தொடுதல் பயன்படுத்தப்படலாம். </li> \n<li> <a href = \"https://github.com/juha-h/baresip-studio/wiki\"> விக்கி </a> ஐப் பார்க்கவும். </li> \n</ul> \n<H2> தனியுரிமைக் கொள்கை </H2> \n<p> தனியுரிமைக் கொள்கை <a href = \"https://raw.githubusercontent.com/juha-p-studio/master/privacypolicy.txt\"> இங்கே </a>. </p> \n<br> \n<h2> மூலக் குறியீடு </h2> \n<p> மூலக் குறியீடு <a href = \"https://github.com/juha-h/baresip-studio\"> github </a>, \nசிக்கல்களைப் புகாரளிக்கலாம். </p> \n<br> \n<H2> உரிமங்கள் </H2> \n<ul> \n<li> <b> BSD-3- அடைப்பு </b> பின்வருவதைத் தவிர: </li> \n<li> <b> அப்பாச்சி 2.0 </b> AMR கோடெக்குகள் மற்றும் TLS பாதுகாப்பு </li> \n<li> <b> agplv4 </b> ZRTP மீடியா குறியாக்க </li> \n<li> <b> குனு எல்சிபிஎல் 2.1 </b> ஐயா .722, ஐயா .726, மற்றும் கோடெக் 2 கோடெக்ச் </li> \n<li> <b> gnu gplv3 </b> G.729 கோடெக் </li> \n</ul> \n]]></string>\n    <string name=\"favorite_help\">சரிபார்க்கப்பட்டால், தொடர்புகள் பட்டியலில் முதலிடத்தில் உள்ள பிற பிடித்தவைகளில் தொடர்பு காட்டப்படும்.</string>\n    <string name=\"no_aec\">வன்பொருள் ஒலி எதிரொலி ரத்து செய்யப்படவில்லை!</string>\n    <string name=\"about_text_plus\"><![CDATA[\n        <h1>வீடியோ அழைப்புகள் </h1> உடன் PARESIP நூலக அடிப்படையிலான SIP பயனர் முகவர் \n<br> \n<p> சுஆ எய்னனென் & lt; jh@tutpro.com&gt; </p> \n<p> பதிப்பு %1$s </p> \n<br> \n<H2> பயன்பாட்டு குறிப்புகள் </H2> \n<ul> \n<li> paresip+\\ இன் அமைப்புகளில் இயல்புநிலை மதிப்புகள் உங்கள் தேவைகளைப் நிறைவு செய்யுங்கள் \n(உதவிக்கு உருப்படி தலைப்புகளைத் தொடவும்). </li> \n<li> பின்னர் கணக்குகளில், ஒன்று அல்லது அதற்கு மேற்பட்ட கணக்குகளை உருவாக்கவும் (மீண்டும் உதவிக்கு உருப்படி தலைப்புகளைத் தொடவும்). </li> \n<li> ஒரு கணக்கின் பதிவு நிலை வண்ண புள்ளியுடன் காட்டப்பட்டுள்ளது: பச்சை (பதிவு \nவெற்றி பெற்றது), மஞ்சள் (பதிவு நடந்து கொண்டிருக்கிறது), சிவப்பு (பதிவு தோல்வியுற்றது), வெள்ளை (பதிவு \nசெயல்படுத்தப்படவில்லை). </li> \n<li> நிலை புள்ளியைத் தொடுவது நேரடியாக கணக்கு உள்ளமைவுக்கு வழிவகுக்கிறது. </li> \n<li> பரேசிப் பார் ஐகான்களில் நீண்ட தொடுதல் ஐகான்களைப் பற்றிய தகவல்களைக் காட்டுகிறது. </li> \n<li> சைகை ச்வைப் கீழே காட்டப்பட்ட கணக்கை மீண்டும் பதிவு செய்கிறது. </li> \n<li> தற்போது காட்டப்பட்ட கணக்கில் நீண்ட தொடுதல் கணக்கின் பதிவை இயக்குகிறது அல்லது முடக்குகிறது. </li> \n<li> இடது/வலது சைகை கணக்குகளுக்கு இடையில் மாற்றுகிறது. </li> \n<li> காலீ காலியாக இருக்கும்போது அழைப்பு ஐகானைத் தொடுவதன் மூலம் முந்தைய அழைப்பு விருந்தை மீண்டும் காணலாம். </li> \n<li> அழைப்புகள் மற்றும் செய்திகளின் சகாக்கள் நீண்ட தொடுதல்களால் தொடர்புகளில் சேர்க்கப்படலாம். </li> \n<li> அழைப்புகள், அரட்டைகள், செய்திகள் மற்றும் தொடர்புகளை அகற்ற நீண்ட தொடுதல்களைப் பயன்படுத்தலாம். </li> \n<li> தொடர்பு ஐகானின் தொடுதல்/நீண்ட தொடுதல் பட அவதாரத்தை நிறுவ/அகற்ற பயன்படுத்தலாம். </li> \n<li> கோடெக்கை இயக்க/முடக்க ஆடியோ அல்லது வீடியோ கோடெக்கில் நீண்ட தொடுதல் பயன்படுத்தப்படலாம். </li> \n<li> <a href = \"https://github.com/juha-h/baresip-studio/wiki\"> விக்கி </a> ஐக் காண்க \nதகவல். </li> \n</ul> \n<H2> அறியப்பட்ட சிக்கல்கள் </H2> \n<ul> \n<li> வீடியோ அழைப்புகளில், சாதனம் நிலப்பரப்பில் நடத்தப்பட வேண்டும் \nஉருவப்படம் நோக்குநிலையிலிருந்து 90 டிகிரி மீதமுள்ள பயன்முறை சுழற்றப்பட்டது. </li> \n<li> வீடியோ ச்ட்ரீம் அனுப்பும்போது தன்வய பார்வை சரியாகக் காட்டப்படவில்லை. </li> \n</ul> \n<H2> தனியுரிமைக் கொள்கை </H2> \n<p> தனியுரிமைக் கொள்கை <a href = \"https://raw.githubusercontent.com/juha-p-studio/video/privacypolicy.txt\"> இங்கே </a>. </p> \n<br> \n<H2> மூலக் குறியீடு </H2> \n<p> மூலக் குறியீடு <a href = \"https://github.com/juha-h/baresip-studio\"> github </a>, \nசிக்கல்களைப் புகாரளிக்கலாம். </p> \n<br> \n<h2> உரிமங்கள் </h2> \n<ul> \n<li> <b> BSD-3- அடைப்பு </b> பின்வருவதைத் தவிர: </li> \n<li> <b> அப்பாச்சி 2.0 </b> AMR கோடெக்குகள் மற்றும் TLS பாதுகாப்பு </li> \n<li> <b> agplv4 </b> ZRTP மீடியா குறியாக்க </li> \n<li> <b> குனு எல்சிபிஎல் 2.1 </b> ஐயா .722, ஐயா .726, மற்றும் கோடெக் 2 கோடெக்ச் </li> \n<li> <b> gnu gplv3 </b> G.729 கோடெக் </li> \n<li> <b> குனு சி.பி.எல்.வி 2 </b> எச் .264 மற்றும் எச் .265 கோடெக்ச் </li> \n<li> <b> aomedia </b> av1 கோடெக் </li> \n</ul> \n]]></string>\n    <string name=\"playing_recording\">பதிவு விளையாடுவது…</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-uk/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-zh-rCN/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"about_title\">关于 baresip</string>\n    <string name=\"about_title_plus\">关于 baresip+</string>\n    <string name=\"account\">账号</string>\n    <string name=\"nickname\">昵称</string>\n    <string name=\"invalid_account_nickname\">账号昵称“%1$s”无效</string>\n    <string name=\"non_unique_account_nickname\">昵称“%1$s”已存在</string>\n    <string name=\"account_nickname_help\">昵称（如果有）用于在 baresip 应用中识别此账号。</string>\n    <string name=\"display_name\">显示名称</string>\n    <string name=\"display_name_help\">名称（如果有）用于在出站请求中的 From URI。</string>\n    <string name=\"invalid_display_name\">显示名称“%1$s”无效</string>\n    <string name=\"authentication_username\">身份验证用户名</string>\n    <string name=\"authentication_username_help\">如果需要对 SIP 请求进行身份验证，则输入身份验证用户名。默认值是账号的用户名。</string>\n    <string name=\"invalid_authentication_username\">身份验证用户名“%1$s”无效</string>\n    <string name=\"authentication_password\">身份验证密码</string>\n    <string name=\"authentication_password_help\">身份验证密码最多 64 个字符。如果提供了身份验证用户名，但未提供密码，则在启动 baresip 时会询问。</string>\n    <string name=\"invalid_authentication_password\">身份验证密码“%1$s”无效</string>\n    <string name=\"outbound_proxies\">出站代理</string>\n    <string name=\"outbound_proxies_help\">当发送请求时，必须使用一个或两个代理的 SIP URI。如果指定两个，则 REGISTER 请求将同时发送给二者，其他请求则发送给最先响应的一方。如果没有指定出站代理，则根据被叫 URI 主机部分的 DNS NAPTR/SRV/A 记录查找发送请求。如果 SIP URI 的主机部分是 IPv6 地址，则该地址必须写在方括号 [] 内。\\n示例：\\n • sip:example.com:5061;transport=tls \\n • sip:[2001:67c:223:777::10];transport=tcp \\n • sip:192.168.43.50:443;transport=wss</string>\n    <string name=\"sip_uri_of_proxy_server\">代理服务器的 SIP URI</string>\n    <string name=\"sip_uri_of_another_proxy_server\">另一个代理服务器的 SIP URI</string>\n    <string name=\"invalid_proxy_server_uri\">代理服务器 URI“%1$s”无效</string>\n    <string name=\"register\">注册</string>\n    <string name=\"register_help\">如果选中，则启用注册，并按照注册间隔指定的间隔发送 REGISTER 请求。</string>\n    <string name=\"reg_int\">注册间隔</string>\n    <string name=\"reg_int_help\">告诉 baresip 发送 REGISTER 请求的频率（以秒为单位）。有效值在 60 到 3600 之间。</string>\n    <string name=\"invalid_reg_int\">注册间隔“%1$s”无效</string>\n    <string name=\"media_nat\">媒体 NAT 穿越</string>\n    <string name=\"media_nat_help\">选择媒体 NAT 穿越协议（如果有）。可能的选择是 STUN（ NAT 会话穿越实用程序，RFC 5389）和 ICE（交互式连接建立，RFC 5245）。</string>\n    <string name=\"stun_server\">STUN/TURN 服务器</string>\n    <string name=\"stun_server_help\">STUN/TURN 服务器 URI 的格式为 scheme:host[:port][?transport=udp|tcp]，其中 scheme 为“stun”、“stuns”、“turn”或“turns”。STUN 和 ICE 协议的出厂默认 STUN 服务器是指向公共 Google STUN 服务器的“stun:stun.l.google.com:19302”。没有出厂默认的 TURN 服务器。</string>\n    <string name=\"invalid_stun_server\">STUN/TURN 服务器 URI“%1$s”无效</string>\n    <string name=\"stun_username\">STUN/TURN 用户名</string>\n    <string name=\"stun_username_help\">用户名（如果 STUN/TURN 服务器需要）</string>\n    <string name=\"invalid_stun_username\">用户名“%1$s”无效</string>\n    <string name=\"stun_password\">STUN/TURN 密码</string>\n    <string name=\"stun_password_help\">密码（如果 STUN/TURN 服务器需要）</string>\n    <string name=\"invalid_stun_password\">密码“%1$s”无效</string>\n    <string name=\"media_encryption\">媒体加密</string>\n    <string name=\"media_encryption_help\">选择媒体传输加密协议（如果有）。\\n • ZRTP（推荐）意味着在建立呼叫后尝试通过 ZRTP 进行端到端媒体加密协商。\\n • DTLS-SRTPF 意味着在传出呼叫中提供 UDP/TLS/RTP/SAVPF，如果在传入呼叫中提供 RTP/SAVP、RTP/SAVPF、UDP/TLS/RTP/SAVP 或 UDP/TLS/RTP/SAVPF 则使用。\\n • SRTP-MANDF 意味着在传出呼叫中提供 RTP/SAVPF，在传入呼叫中也必须提供。\\n • SRTP-MAND 意味着在传出呼叫中提供 RTP/SAVP，在传入呼叫中也必须提供。\\n • SRTP 意味着在传出呼叫中提供 RTP/AVP，如果在传入呼叫中提供 RTP/SAVP 或 RTP/SAVPF 则使用。</string>\n    <string name=\"rtcp_mux\">RTCP 多路复用</string>\n    <string name=\"rtcp_mux_help\">如果选中，RTP 和 RTCP 数据包会在单个端口上多路复用（RFC 5761）。</string>\n    <string name=\"rel_100\">可靠临时响应</string>\n    <string name=\"rel_100_help\">如果选中，则表示支持可靠临时响应（RFC 3262）。</string>\n    <string name=\"dtmf_mode\">DTMF 模式</string>\n    <string name=\"dtmf_mode_help\">选择 DTMF 音的发送方式（0–9、#、* 和 A-D）。</string>\n    <string name=\"dtmf_inband\">带内 RTP 事件</string>\n    <string name=\"dtmf_info\">SIP INFO 请求</string>\n    <string name=\"dtmf_auto\">带内 RTP 或 SIP INFO</string>\n    <string name=\"answer_mode\">接听模式</string>\n    <string name=\"answer_mode_help\">选择接听来电的方式。</string>\n    <string name=\"redirect_mode\">重定向模式</string>\n    <string name=\"redirect_mode_help\">选择是否会自动遵循呼叫重定向请求，或者是否请求确认。</string>\n    <string name=\"manual\">手动</string>\n    <string name=\"auto\">自动</string>\n    <string name=\"voicemail_uri\">语音信箱 URI</string>\n    <string name=\"voicemain_uri_help\">用于检查语音信箱消息的 SIP URI。如果留空，则不订阅语音信箱消息（消息等待指示）。</string>\n    <string name=\"country_code\">国家/地区代码</string>\n    <string name=\"country_code_help\">此账号的 E.164 国家/地区代码。如果来电或消息的 From URI 用户部分包含不以“+”符号开头的电话号码，并且如果联系人查找失败，则该号码将以该国家/地区代码作为前缀，并再次尝试联系人查找。如果电话号码以单数字“0”开头，则在号码前缀之前移除数字“0”。</string>\n    <string name=\"invalid_country_code\">国家/地区代码“%1$s”无效</string>\n    <string name=\"telephony_provider\">电话服务提供者</string>\n    <string name=\"telephony_provider_help\">用于拨打电话号码的 SIP URI 主机部分。出厂默认为账号的域名。如果未提供，则此账号不能用于拨打电话号码。</string>\n    <string name=\"numeric_keypad\">数字键盘</string>\"\n    <string name=\"numeric_keypad_help\">如果选中，当“呼叫…”字段聚焦时，将显示数字键盘。</string>\"\n    <string name=\"invalid_sip_uri_hostpart\">SIP URI 主机部分“%1$s”无效</string>\n    <string name=\"default_account\">默认账号</string>\n    <string name=\"default_account_help\">如果选中，则在启动 baresip 时选择此账号。</string>\n    <string name=\"accounts\">账号</string>\n    <string name=\"new_account\">新账号的 SIP URI</string>\n    <string name=\"accounts_help\">新账号的 SIP URI 格式为 &lt;user&gt;@&lt;domain&gt;[:&lt;port&gt;][;transport=udp|tcp|tls]。如果指定了 &lt;port&gt;，但未指定传输协议，则传输协议默认为 UDP。如果未指定 &lt;port&gt;，但指定了传输协议，则 &lt;port&gt; 默认为 5060 或 5061（TLS）。如果二者均未指定，也没有指定出站代理，则将仅根据域名的 DNS 信息确定账号的注册服务器（如果有）。</string>\n    <string name=\"invalid_aor\">user@domain[:port][;transport=udp|tcp|tls]“%1$s”无效</string>\n    <string name=\"account_exists\">账号“%1$s”已存在。</string>\n    <string name=\"account_allocation_failure\">无法分配新账号。</string>\n    <string name=\"encrypt_password\">加密密码</string>\n    <string name=\"decrypt_password\">解密密码</string>\n    <string name=\"delete_account\">是否要删除账号“%1$s”？</string>\n    <string name=\"reply\">回复</string>\n    <string name=\"save\">保存</string>\n    <string name=\"missed_call_from\">未接来电来自</string>\n    <string name=\"missed_calls\">未接来电</string>\n    <string name=\"missed_calls_count\">%1$d 个未接来电</string>\n    <string name=\"transfer_request_to\">通话转接请求至</string>\n    <string name=\"call_auto_rejected\">已自动拒绝来自 %1$s 的通话</string>\n    <string name=\"call_history\">通话记录</string>\n    <string name=\"call_details\">通话详情</string>\n    <string name=\"call\">呼叫</string>\n    <string name=\"calls_calls\">通话</string>\n    <string name=\"calls_call\">通话</string>\n    <string name=\"peer\">对方</string>\n    <string name=\"direction\">方向</string>\n    <string name=\"time\">时间</string>\n    <string name=\"calls_duration\">时长</string>\n    <string name=\"playing_recording\">正在播放录音…</string>\n    <string name=\"calls_add_delete_question\">您要将“%1$s”添加到联系人中，还是从通话记录中删除 %2$s？</string>\n    <string name=\"calls_delete_question\">是否要从通话记录中删除“%1$s”%2$s？</string>\n    <string name=\"disable_history\">禁用</string>\n    <string name=\"enable_history\">启用</string>\n    <string name=\"delete_history_alert\">是否要删除账号“%1$s”的通话记录？</string>\n    <string name=\"call_answered\">通话已接听</string>\n    <string name=\"call_answered_elsewhere\">通话已在别处接听</string>\n    <string name=\"call_missed\">通话未接</string>\n    <string name=\"call_rejected\">通话遭拒</string>\n    <string name=\"chat_with\">与 %1$s 聊天</string>\n    <string name=\"new_message\">新消息</string>\n    <string name=\"long_message_question\">您要删除消息还是将对方“%1$s”添加到联系人中？</string>\n    <string name=\"short_message_question\">是否要删除消息？</string>\n    <string name=\"add_contact\">添加联系人</string>\n    <string name=\"sending_failed\">消息发送失败</string>\n    <string name=\"message_failed\">失败</string>\n    <string name=\"chats\">聊天记录</string>\n    <string name=\"today\">今天</string>\n    <string name=\"you\">您</string>\n    <string name=\"new_chat_peer\">新的聊天伙伴</string>\n    <string name=\"long_chat_question\">您要删除与对方“%1$s”的聊天，还是将对方添加到联系人中？</string>\n    <string name=\"short_chat_question\">是否要删除与“%1$s”的聊天？</string>\n    <string name=\"delete_chats_alert\">是否要删除账号“%1$s”的聊天记录？</string>\n    <string name=\"audio_codecs\">音频编解码器</string>\n    <string name=\"video_codecs\">视频编解码器</string>\n    <string name=\"configuration\">设置</string>\n    <string name=\"start_automatically\">自动启动</string>\n    <string name=\"start_automatically_help\">如果选中，在设备启动后或安装新版本 baresip 后，baresip 将自动启动。启动会延迟至设备解锁后执行。</string>\n    <string name=\"appear_on_top_permission\">自动启动需要“悬浮窗”权限。</string>\n    <string name=\"battery_optimizations\">电池优化</string>\n    <string name=\"battery_optimizations_help\">如果您想降低 Android 限制 baresip 访问网络或让 baresip 进入待机状态的概率，请禁用电池优化（推荐）。</string>\n    <string name=\"default_phone_app\">默认电话应用</string>\n    <string name=\"dialer_role_not_available\">拨号器角色不可用</string>\n    <string name=\"default_phone_app_help\">如果选中，则 baresip 是默认电话应用。如果您的设备还需要处理除 SIP 通话或消息之外的其他通话或消息，请勿选中。</string>\n    <string name=\"listen_address\">监听地址</string>\n    <string name=\"listen_address_help\">baresip 监听传入 SIP 请求的 IP 地址和端口，格式为“地址:端口”。如果 IP 地址是 IPv6 地址，则必须将其写在方括号 [] 内。IPv4 地址 0.0.0.0 或 IPv6 地址 [::] 使 baresip 监听所有可用地址。如果留空（出厂默认），baresip 将在所有可用地址的任意端口上监听。</string>\n    <string name=\"invalid_listen_address\">监听地址无效</string>\n    <string name=\"address_family\">地址族</string>\n    <string name=\"address_family_help\">选择 baresip 正在使用的 IP 地址。如果选择 IPv4 或 IPv6，则 baresip 仅使用 IPv4 或 IPv6 地址。如果两者都没有选择，baresip 将同时使用 IPv4 和 IPv6 地址。</string>\n    <string name=\"dns_servers\">DNS 服务器</string>\n    <string name=\"dns_servers_help\">以英文逗号分隔的 DNS 服务器地址列表。如果未指定，则从系统中动态获取 DNS 服务器地址。每个 DNS 地址的格式为“ip:端口”或“ip”。如果省略端口，则默认为 53。如果 ip 是 IPv6 地址，并且指定了端口，则 ip 必须写在方括号 [] 内。例如，列表“8.8.8.8:53,[2001:4860:4860::8888]:53”指向公共 Google DNS 服务器的 IPv4 和 IPv6 地址。</string>\n    <string name=\"invalid_dns_servers\">DNS 服务器无效</string>\n    <string name=\"failed_to_set_dns_servers\">无法设置 DNS 服务器</string>\n    <string name=\"tls_certificate_file\">TLS 证书文件</string>\n    <string name=\"tls_certificate_file_help\">如果选中，则已加载或将加载包含此 baresip 实例的 TLS 证书和私钥的文件。在 Android 9 中，会从下载文件夹加载名为“cert.pem”的文件。出于安全原因，请在加载后删除该文件。</string>\n    <string name=\"verify_server\">验证服务器证书</string>\n    <string name=\"verify_server_help\">如果选中，则在使用 TLS 传输时，baresip 会验证 SIP 用户代理和 SIP 代理服务器的 TLS 证书。</string>\n    <string name=\"tls_ca_file\">TLS CA 文件</string>\n    <string name=\"tls_ca_file_help\">如果选中，则已加载或将加载包含 Android 操作系统中未包含的证书颁发机构的 TLS 证书的文件。在 Android 9 中，会从下载文件夹加载名为“ca_certs.crt”的文件。</string>\n    <string name=\"no_read_permission\">无外部存储读取权限</string>\n    <string name=\"audio_settings\">音频设置</string>\n    <string name=\"speaker_phone_help\">如果选中，通话开始时扬声器会自动打开。</string>\n    <string name=\"speaker_phone\">扬声器</string>\n    <string name=\"audio_modules_title\">音频模块</string>\n    <string name=\"audio_modules_help\">已选中的模块提供的音频编解码器可供账号使用。</string>\n    <string name=\"failed_to_load_module\">无法加载模块。</string>\n    <string name=\"microphone_gain\">麦克风增益</string>\n    <string name=\"microphone_gain_help\">将麦克风音量乘以此十进制数。最小值为 1.0（出厂默认），禁用麦克风增益。较大的值可能会对音频质量产生负面影响。</string>\n    <string name=\"invalid_microphone_gain\">麦克风增益值无效</string>\n    <string name=\"opus_bit_rate\">Opus 比特率</string>\n    <string name=\"opus_bit_rate_help\">Opus 音频流使用的平均最大比特率。有效值为 6000-510000。出厂默认为 28000。</string>\n    <string name=\"opus_packet_loss\">预期 Opus 丢包率</string>\n    <string name=\"opus_packet_loss_help\">预期 Opus 音频流丢包率，范围为 0–100。出厂默认值为 1。值为 0 时也会关闭 Opus 前向纠错（FEC）。</string>\n    <string name=\"invalid_opus_bitrate\">Opus 比特率无效</string>\n    <string name=\"invalid_opus_packet_loss\">Opus 丢包率无效</string>\n    <string name=\"audio_delay\">音频延迟</string>\n    <string name=\"audio_delay_help\">通话建立后等待被叫方音频的时间（以毫秒为单位）。如果您在通话开始时未收到被叫方的音频，请设置为更高的值。</string>\n    <string name=\"invalid_audio_delay\">音频延迟“%1$s”无效。有效值为 100 至 3000。</string>\n    <string name=\"default_call_volume\">默认通话音量</string>\n    <string name=\"default_call_volume_help\">如果设置，则默认通话音量为 1–10 级。</string>\n    <string name=\"tone_country_help\">来电铃声、等待铃声和被叫忙音的国家/地区</string>\n    <string name=\"tone_country\">提示音国家/地区</string>\n    <string name=\"dark_theme\">深色主题</string>\n    <string name=\"dark_theme_help\">强制启用深色显示主题</string>\n    <string name=\"colorblind\">色盲</string>\n    <string name=\"colorblind_help\">使用色盲友好型注册状态图标</string>\n    <string name=\"proximity_sensing\">近距离感应</string>\"&gt;\n    <string name=\"proximity_sensing_help\">如果选中，则在通话期间将启用近距离感应功能。</string>\n    <string name=\"video_size\">视频帧大小</string>\n    <string name=\"video_size_help\">传输的视频帧大小（宽 × 高）</string>\n    <string name=\"video_fps\">每秒视频帧数</string>\n    <string name=\"video_fps_help\">SDP 握手期间提供的视频帧速率。有效值为 10 至 30。</string>\n    <string name=\"invalid_fps\">每秒帧数“%1$d”无效</string>\n    <string name=\"ringtone\">铃声</string>\n    <string name=\"select_ringtone\">选择铃声</string>\n    <string name=\"user_agent\">用户代理</string>\n    <string name=\"user_agent_help\">自定义 SIP 请求/响应 User-Agent 标头字段值</string>\n    <string name=\"invalid_user_agent\">User-Agent 标头字段值无效</string>\n    <string name=\"contacts_help\">选择是否使用 baresip 联系人、Android 联系人或两者都使用。如果两者都使用，并且两个联系人中都存在同名联系人，则将选择 baresip 联系人。</string>\n    <string name=\"both\">两者</string>\n    <string name=\"debug\">调试</string>\n    <string name=\"debug_help\">如果选中，则向 Logcat 提供调试和信息级别的日志消息。</string>\n    <string name=\"sip_trace\">SIP 跟踪</string>\n    <string name=\"sip_trace_help\">如果选中且调试也已选中，则 Logcat 消息还包括 SIP 请求和响应跟踪。在 baresip 启动时自动取消选中。</string>\n    <string name=\"reset_config\">重置为出厂默认</string>\n    <string name=\"reset_config_help\">如果选中，设置将重置为出厂默认值。</string>\n    <string name=\"reset_config_alert\">是否确定要将设置重置为出厂默认值？</string>\n    <string name=\"reset\">重置</string>\n    <string name=\"read_cert_error\">无法读取“cert.pem”文件。</string>\n    <string name=\"read_ca_certs_error\">无法读取“ca_certs.crt”文件。</string>\n    <string name=\"config_restart\">您需要重启 baresip 才能激活新设置。是否立即重启？</string>\n    <string name=\"consent_request\">同意请求</string>\n    <string name=\"contacts_consent\">如果选择了 Android 联系人，可将其作为 SIP 和 tel URI 的引用用于通话和消息。baresip 应用不会存储 Android 联系人，也不会与任何人共享。为了使 Android 联系人在 baresip 中可用，Google 要求您接受此处和应用<a href=\"https://raw.githubusercontent.com/juha-h/baresip-studio/master/PrivacyPolicy.txt\">隐私政策</a>中所述的使用。</string>\n    <string name=\"new_contact\">新联系人</string>\n    <string name=\"contact_name\">名称</string>\n    <string name=\"sip_or_tel_uri\">SIP 或 tel URI</string>\n    <string name=\"user_domain_or_number\">user@domain 或电话号码</string>\n    <string name=\"favorite\">收藏</string>\n    <string name=\"favorite_help\">如果选中，联系人将显示在联系人列表顶部的其他收藏中。</string>\n    <string name=\"invalid_contact\">联系人名称“%1$s”无效</string>\n    <string name=\"contact_already_exists\">联系人“%1$s”已存在。</string>\n    <string name=\"android_contact_help\">如果选中，此联系人将添加到 Android 联系人中。</string>\n    <string name=\"avatar_image\">头像</string>\n    <string name=\"contacts\">联系人</string>\n    <string name=\"contact_action_question\">您要呼叫还是发送消息至“%1$s”？</string>\n    <string name=\"send_message\">发送消息</string>\n    <string name=\"contact_delete_question\">是否要删除联系人“%1$s”？</string>\n    <string name=\"alert\">提醒</string>\n    <string name=\"info\">信息</string>\n    <string name=\"notice\">通知</string>\n    <string name=\"cancel\">取消</string>\n    <string name=\"ok\">确定</string>\n    <string name=\"yes\">是</string>\n    <string name=\"no\">否</string>\n    <string name=\"accept\">接受</string>\n    <string name=\"deny\">拒绝</string>\n    <string name=\"add\">添加</string>\n    <string name=\"delete\">删除</string>\n    <string name=\"edit\">编辑</string>\n    <string name=\"status\">状态</string>\n    <string name=\"error\">错误</string>\n    <string name=\"confirmation\">确认</string>\n    <string name=\"anonymous\">匿名</string>\n    <string name=\"unknown\">未知</string>\n    <string name=\"invalid_sip_or_tel_uri\">SIP 或 tel URI“%1$s”无效</string>\n    <string name=\"backup\">备份</string>\n    <string name=\"restore\">恢复</string>\n    <string name=\"about\">关于</string>\n    <string name=\"restart\">重启</string>\n    <string name=\"quit\">退出</string>\n    <string name=\"outgoing_call_to_dots\">呼叫…</string>\n    <string name=\"incoming_call_from_dots\">通话来自…</string>\n    <string name=\"diverted_by_dots\">已转接…</string>\n    <string name=\"no_telephony_provider\">账号“%1$s”没有电话服务提供者</string>\n    <string name=\"video_call\">视频通话</string>\n    <string name=\"video_request\">视频请求</string>\n    <string name=\"allow_video_send\">是否接受向“%1$s”发送视频？</string>\n    <string name=\"allow_video\">是否接受“%1$s”发送和接收视频？</string>\n    <string name=\"allow_video_recv\">是否接受从“%1$s”接收视频？</string>\n    <string name=\"call_is_on_hold\">通话处于保持状态</string>\n    <string name=\"rec_in_call\">仅在未连接通话时才可以打开或关闭录音</string>\n    <string name=\"call_transfer\">通话转接</string>\n    <string name=\"blind\">盲转</string>\n    <string name=\"attended\">协商转接</string>\n    <string name=\"transfer_destination\">转接目标</string>\n    <string name=\"choose_destination_uri\">选择目标 URI</string>\n    <string name=\"transfer\">转接</string>\n    <string name=\"transfer_failed\">转接失败</string>\n    <string name=\"dtmf\">DTMF</string>\n    <string name=\"call_info\">通话信息</string>\n    <string name=\"call_info_not_available\">无可用信息</string>\n    <string name=\"duration\">时长：%1$d（秒）</string>\n    <string name=\"codecs\">编解码器</string>\n    <string name=\"rate\">当前速率：%1$s（Kbits/s）</string>\n    <string name=\"average_rate\">平均速率：%1$s（Kbits/s）</string>\n    <string name=\"packets\">数据包</string>\n    <string name=\"lost\">丢包数</string>\n    <string name=\"jitter\">抖动：%1$s（毫秒）</string>\n    <string name=\"voicemail_messages\">语音信箱消息</string>\n    <string name=\"you_have\">您有</string>\n    <string name=\"one_new_message\">一条新消息</string>\n    <string name=\"new_messages\">新消息</string>\n    <string name=\"one_old_message\">一条旧消息</string>\n    <string name=\"old_messages\">旧消息</string>\n    <string name=\"and\">以及</string>\n    <string name=\"no_messages\">您没有消息</string>\n    <string name=\"listen\">听</string>\n    <string name=\"call_already_active\">您已有正在进行的通话。</string>\n    <string name=\"start_failed\">baresip 无法启动。这可能是由于设置值无效。请检查监听地址、TLS 证书文件和 TLS CA 文件。然后重启 baresip。</string>\n    <string name=\"registering_failed\">注册 %1$s 失败。</string>\n    <string name=\"verify\">验证请求</string>\n    <string name=\"verify_sas\">是否要验证 SAS &lt;%1$s&gt;？</string>\n    <string name=\"transfer_request\">转接请求</string>\n    <string name=\"transfer_request_query\">是否接受将此通话转接到“%1$s”？</string>\n    <string name=\"call_request\">呼叫请求</string>\n    <string name=\"call_request_query\">是否接受呼叫“%1$s”的请求？</string>\n    <string name=\"redirect_notice\">自动重定向至“%1$s”</string>\n    <string name=\"redirect_request\">重定向请求</string>\n    <string name=\"redirect_request_query\">是否接受将通话重定向至“%1$s”？</string>\n    <string name=\"call_failed\">通话失败</string>\n    <string name=\"call_closed\">通话已关闭</string>\n    <string name=\"call_not_secure\">此通话不安全！</string>\n    <string name=\"peer_not_verified\">此通话安全，但对方未验证！</string>\n    <string name=\"call_is_secure\">此通话安全且对方已验证！是否要取消验证对方？</string>\n    <string name=\"unverify\">取消验证</string>\n    <string name=\"backed_up\">应用程序数据（不包括录音）已备份到文件“%1$s”。在 Android 9 中，该文件位于下载文件夹中。</string>\n    <string name=\"backup_failed\">无法将应用程序数据备份至文件“%1$s”。请检查“应用”→“baresip”→“权限”→“存储”。</string>\n    <string name=\"restart_request\">重启请求</string>\n    <string name=\"restored\">应用程序数据已恢复。需要重启 baresip。是否立即重启？</string>\n    <string name=\"restore_failed\">无法恢复应用程序数据。请检查您输入的密码是否正确，备份文件是否来自此应用程序。在 Android 9 中，请检查“应用”→“baresip”→“权限”→“存储”，并确保文件“%1$s”存在于下载文件夹中。</string>\n    <string name=\"restore_unzip_failed\">无法恢复应用程序数据。Android 14 及更高版本不允许恢复在 %1$s %2$s 版本之前备份的数据。</string>\n    <string name=\"no_notifications\">没有“通知”权限，您无法使用此应用程序。</string>\n    <string name=\"no_calls\">baresip 需要“麦克风”权限才能进行语音通话。</string>\n    <string name=\"no_video_calls\">授予“相机”权限即可拨打或接听视频通话。</string>\n    <string name=\"no_backup\">没有“存储”权限，您无法创建备份。</string>\n    <string name=\"no_restore\">没有“存储”权限，您无法恢复备份。</string>\n    <string name=\"no_android_contacts\">没有“联系人”权限，您无法访问 Android 联系人。</string>\n    <string name=\"no_cameras\">没有支持的视频相机！</string>\n    <string name=\"no_network\">无网络连接！</string>\n    <string name=\"no_aec\">无硬件声学回声消除！</string>\n    <string name=\"audio_focus_denied\">音频焦点被拒绝！</string>\n    <string name=\"audio_permissions\">baresip 需要“麦克风”权限才能进行语音通话，需要“附近的设备”权限才能检测蓝牙麦克风/扬声器，需要“通知”权限才能发布通知，并且在 Android 9 中需要“存储”权限才能进行备份/恢复操作。</string>\n    <string name=\"audio_and_video_permissions\">baresip+ 需要“麦克风”权限才能进行语音通话，需要“相机”权限才能进行视频通话，需要“附近的设备”权限才能检测蓝牙麦克风/扬声器，需要“通知”权限才能发布通知，并且在 Android 9 中需要“存储”权限才能进行备份/恢复操作。</string>\n    <string name=\"permissions_rationale\">权限理由</string>\n    <string name=\"call_recording_title\">通话录音</string>\n    <string name=\"call_recording_tip\">如果激活，新的来电和去电都会被录音。可以在通话详情页面播放录音。</string>\n    <string name=\"microphone_title\">麦克风</string>\n    <string name=\"microphone_tip\">如果在通话期间激活，麦克风将静音。</string>\n    <string name=\"speakerphone_title\">扬声器</string>\n    <string name=\"speakerphone_tip\">如果激活，音频将通过设备扬声器播放。</string>\n    <string name=\"about_text_plus\"><![CDATA[\n        <h1>基于 <a href=\"https://github.com/baresip/baresip\">baresip</a> 库的带视频通话功能的 SIP 用户代理</h1>\n        <br>\n        <p>Juha Heinanen &lt;jh@tutpro.com&gt;</p>\n        <p>版本 %1$s</p>\n        <br>\n        <h2>使用提示</h2>\n        <ul>\n            <li>检查 baresip+ 设置中的默认值是否满足您的需求（触摸项目标题获取帮助）。</li>\n            <li>然后在“账号”中，创建一个或多个账号（再次触摸项目标题以获取帮助）。</li>\n            <li>账号的注册状态用彩色圆点显示：绿色（注册成功）、黄色（注册正在进行）、红色（注册失败）、白色（注册尚未激活）。</li>\n            <li>触摸状态点可直接进入账号配置。</li>\n            <li>长按 baresip 栏图标可显示有关图标的信息。</li>\n            <li>向下滑动手势会导致当前显示的账号重新注册。</li>\n            <li>长按当前显示的账号可启用或禁用账号的注册。</li>\n            <li>向左/向右滑动手势可在账号之间切换。</li>\n            <li>当被叫方为空时，可以通过触摸通话图标来重新选择先前的呼叫方。</li>\n            <li>长按即可将通话和消息联系人添加到联系人中。</li>\n            <li>长按也可用于移除通话、聊天、消息和联系人。</li>\n            <li>触摸/长按联系人图标可用于设置/移除头像。</li>\n            <li>长按音频或视频编解码器可以启用/禁用该编解码器。</li>\n            <li>如需更多信息，请参阅 <a href=\"https://github.com/juha-h/baresip-studio/wiki\">Wiki</a>。</li>\n        </ul>\n        <h2>已知问题</h2>\n        <ul>\n            <li>当视频流处于仅发送模式时，自显画面无法正常显示。</li>\n        </ul>\n        <h2>隐私政策</h2>\n        <p>隐私政策可在<a href=\"https://raw.githubusercontent.com/juha-h/baresip-studio/video/PrivacyPolicy.txt\">此处</a>获取。</p>\n        <br>\n        <h2>源代码</h2>\n        <p>源代码可在 <a href=\"https://github.com/juha-h/baresip-studio\">GitHub</a> 上找到，您也可以在此报告问题。</p>\n        <br>\n        <h2>语言翻译</h2>\n        <p>语言翻译通过 baresip <a href=\"https://hosted.weblate.org/projects/baresip/\">Weblate</a> 项目进行管理。</p>\n        <br>\n        <h2>许可</h2>\n        <ul>\n            <li><b>BSD-3-Clause</b>，但以下内容除外：</li>\n            <li><b>Apache 2.0</b> AMR 编解码器和 TLS 安全</li>\n            <li><b>AGPLv4</b> ZRTP 媒体加密</li>\n            <li><b>GNU LGPL 2.1</b> Codec2 编解码器</li>\n            <li><b>Free</b> G.722 编解码器</li>\n            <li><b>GNU GPLv3</b> G.729 编解码器</li>\n            <li><b>GNU GPLv2</b> H.264 和 H.265 编解码器</li>\n            <li><b>AOMedia</b> AV1 编解码器</li>\n        </ul>\n        ]]></string>\n    <string name=\"about_text\"><![CDATA[\n        <h1>基于 <a href=\"https://github.com/baresip/baresip\">baresip</a> 库的 SIP 用户代理</h1>\n        <br>\n        <p>Juha Heinanen &lt;jh@tutpro.com&gt;</p>\n        <p>版本 %1$s</p>\n        <br>\n        <h2>使用提示</h2>\n        <ul>\n            <li>检查 baresip 设置中的默认值是否满足您的需求（触摸项目标题获取帮助）。</li>\n            <li>然后在“账号”中，创建一个或多个账号（再次触摸项目标题以获取帮助）。</li>\n            <li>账号的注册状态用彩色圆点显示：绿色（注册成功）、黄色（注册正在进行）、红色（注册失败）、白色（注册尚未激活）。</li>\n            <li>触摸状态点可直接进入账号配置。</li>\n            <li>长按 baresip 栏图标可显示有关图标的信息。</li>\n            <li>向下滑动手势会导致当前显示的账号重新注册。</li>\n            <li>长按当前显示的账号可启用或禁用账号的注册。</li>\n            <li>向左/向右滑动手势可在账号之间切换。</li>\n            <li>当被叫方为空时，可以通过触摸通话图标来重新选择先前的呼叫方。</li>\n            <li>长按即可将通话和消息联系人添加到联系人中。</li>\n            <li>长按也可用于移除通话、聊天、消息和联系人。</li>\n            <li>触摸/长按联系人图标可用于设置/移除头像。</li>\n            <li>长按音频编解码器可以启用/禁用该编解码器。</li>\n            <li>如需更多信息，请参阅 <a href=\"https://github.com/juha-h/baresip-studio/wiki\">Wiki</a>。</li>\n        </ul>\n        <h2>隐私政策</h2>\n            <p>隐私政策可在<a href=\"https://raw.githubusercontent.com/juha-h/baresip-studio/master/PrivacyPolicy.txt\">此处</a>获取。</p>\n        <br>\n        <h2>源代码</h2>\n            <p>源代码可在 <a href=\"https://github.com/juha-h/baresip-studio\">GitHub</a> 上找到，您也可以在此报告问题。</p>\n        <br>\n        <h2>语言翻译</h2>\n            <p>语言翻译通过 baresip <a href=\"https://hosted.weblate.org/projects/baresip/\">Weblate</a> 项目进行管理。</p>\n        <br>\n        <h2>许可</h2>\n        <ul>\n            <li><b>BSD-3-Clause</b>，但以下内容除外：</li>\n            <li><b>Apache 2.0</b> AMR 编解码器和 TLS 安全</li>\n            <li><b>AGPLv4</b> ZRTP 媒体加密</li>\n            <li><b>GNU LGPL 2.1</b> Codec2 编解码器</li>\n            <li><b>Free</b> G.722 编解码器</li>\n            <li><b>GNU GPLv3</b> G.729 编解码器</li>\n        </ul>\n        ]]></string>\n    <string name=\"dynamic_colors\">动态配色</string>\n    <string name=\"dynamic_colors_help\">如果在 Android 设置中启用，则使用动态配色</string>\n    <string name=\"is_calling\">正在呼叫</string>\n    <string name=\"save_recording\">保存录音</string>\n    <string name=\"save_recording_question\">是否要保存此录音？</string>\n    <string name=\"recording_saved\">录音已保存</string>\n    <string name=\"delete_call_alert\">是否要从通话记录中删除此通话？</string>\n    <string name=\"stop\">停止</string>\n    <string name=\"block_unknown_help\">屏蔽非联系人列表中的联系人的通话和消息。</string>\n    <string name=\"block_unknown\">屏蔽未知联系人</string>\n    <string name=\"transport_protocols\">传输协议</string>\n    <string name=\"transport_protocols_help\">以英文逗号分隔的受支持的 SIP 请求/响应传输协议列表。如果留空，则默认值为“udp,tcp,tls,ws,wss”，其中包含所有受支持的传输协议。仅列出您需要的。出于安全和其他原因，不推荐使用 UDP。</string>\n    <string name=\"invalid_transport_protocols\">传输协议列表无效</string>\n    <string name=\"check_origin\">检查来源</string>\n    <string name=\"check_origin_help\">如果选中，则仅允许来自发送注册请求的 IP 地址的入站请求。</string>\n    <string name=\"blocked\">已屏蔽</string>\n    <string name=\"blocked_calls\">已屏蔽的通话</string>\n    <string name=\"blocked_messages\">已屏蔽的消息</string>\n    <string name=\"blocked_delete_alert\">是否要删除“%1$s”的屏蔽请求？</string>\n    <string name=\"blocked_contact_question\">是否要将对方“%1$s”添加到联系人？</string>\n    <string name=\"call_blocked\">已自动拒绝来自 %1$s 的已屏蔽通话</string>\n    <string name=\"message_blocked\">已自动拒绝来自 %1$s 的已屏蔽消息</string>\n    <string name=\"search\">搜索</string>\n    <string name=\"unique_contact_uri\">唯一联系人 URI</string>\n    <string name=\"unique_contact_uri_help\">如果选中，则保证联系人 URI 是唯一的。如果存在多个账号具有相同的 SIP URI 用户部分时需要选中，同时还能保护账号免受攻击。</string>\n    <string name=\"call_is_connected\">通话已连接</string>\n    <string name=\"replaces_not_supported\">对方不支持 REPLACES 功能</string>\n    <string name=\"call_is_ringing\">通话正在响铃</string>\n    <string name=\"custom_parameters\">自定义参数</string>\n    <string name=\"custom_parameters_help\">以英文分号分隔的自定义账号参数列表</string>\n</resources>\n"
  },
  {
    "path": "build.gradle.kts",
    "content": "buildscript {\n    repositories {\n        google()\n        mavenCentral()\n    }\n    dependencies {\n        classpath(libs.gradle)\n        classpath(libs.kotlin.gradle.plugin)\n    }\n}\n\nallprojects {\n    repositories {\n        google()\n        mavenCentral()\n    }\n}\n\ntasks.register(\"clean\", Delete::class) {\n    description = \"Deletes the build directory\"\n    group = \"cleanup\"\n    delete(layout.buildDirectory)\n}\n\n"
  },
  {
    "path": "fastlane/metadata/android/de-DE/changelogs/10.1.0.txt",
    "content": "-Kompatibilität wurde für den G722 codec hinzugefügt.\n- Das Speichern von Accounts wurde repariert.\n"
  },
  {
    "path": "fastlane/metadata/android/de-DE/changelogs/10.2.0.txt",
    "content": "-Kompatibilität wurde für den G.726 codec hinzugefügt.\n- Die Grund-Rangliste der codecs wurde verbessert.\n"
  },
  {
    "path": "fastlane/metadata/android/de-DE/changelogs/10.3.0.txt",
    "content": "- Ein Anruflisten-menü wurde hinzugefügt welches Löschen, Deaktivieren und Aktivieren \ndes Konto Anrufverlaufes erlaubt.\n-Spanische \"Sprach-Strings\" wurden hinzugefügt (Dank an Javier Falbo).\n-Das automatische Starten von Baresip ist jetzt Grundeinstellung auf neuen Installationen.\n"
  },
  {
    "path": "fastlane/metadata/android/de-DE/full_description.txt",
    "content": "baresip ist eine SIP \"User Agent\" App für Android, die auf <a href=\"https://github.com/baresip/baresip\">baresip</a> basiert.\n\nAktuell unterstüzt die Baresip App Anrufe mit Ton, Textnachrichten, Anrufbeantworter (mit Anzeige für nicht abgehörte Nachrichten) und Anrufsweiterleitungen (direkt/ mit Zusatzinformationen). Zudem werden Opus, AMR, Codec2, G.729, G.722, G.722.1, G.726, oder PCMU/PCMA Codecs unterstüzt. Eine sichere Verbindung wird mit TLS oder WSS SIP und ZRTP oder (DTLS) SRTP Medienverkapselung sichergestellt.\n\nDie Entwicklung der Baresip App wurde durch das Fehlen eines sicheren, quelloffenen und SIP-basierten VoIP \"User Agent\" für Android motiviert, der nicht auf proprietäre und externe Push-Benachrichtigungsdienste angewiesen ist.\n\nFalls Sie Videoanrufe tätigen wollen und ein Gerät mit Android 9 oder höher besitzen, das die Camera2 API auf dem Hardware-Unterstützungslevel LEVEL 3 oder FULL unterstüzt, können Sie die Schwester-App baresip+ nutzen.\n\nDer Quellcode befindet sich auf <a href=\"https://github.com/juha-h/baresip-studio\">GitHub</a>, dort können sie Auch Fehler/ Bugs melden.\n"
  },
  {
    "path": "fastlane/metadata/android/de-DE/short_description.txt",
    "content": "VoIP User Agent App für Android basierend auf der baresip SIP Bibliothek\n"
  },
  {
    "path": "fastlane/metadata/android/de-DE/title.txt",
    "content": "baresip\n"
  },
  {
    "path": "fastlane/metadata/android/el/title.txt",
    "content": "baresip\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/10.0.0.txt",
    "content": "- Resume baresip to paused activity instead of main activity.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/10.1.0.txt",
    "content": "- Added support for G722 codec.\n- Fixed saving of accounts.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/10.2.0.txt",
    "content": "- Added support for G.726 codec.\n- Improved default codec priority order.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/10.3.0.txt",
    "content": "- Added Call History menu that allows deleting, disabling, and enabling\n  of account's call history.\n- Added Spanish language strings (credits to Javier Falbo).\n- Auto start of baresip is now default in new installations.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/10.4.0.txt",
    "content": "- Allow deletion of account's chat history via chats activity menu item.\n- Ask confirmation before deleting chat and call history.\n- Improved updating of main activity voicemail, chat, and call icons.\n- Go directly to Accounts activity when baresip is started without any accounts.\n- Swapped positions of call Accept and Reject buttons.\n- Do not allow terminating outgoing call before call attempt has started.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/11.0.0.txt",
    "content": "- Replaced exporting and importing of accounts and contacts with backup\n  and restore of all application data.\n- Improved security by disallowing Android based application backup and\n  installation of application to external storage.\n- When account is deleted, delete also account's call and chat history.\n- Fixed deleting of messages and chats.\n- Minor UI and string improvements.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/11.1.0.txt",
    "content": "- Added 'Bind Address' configuration variable that allows choosing which\n  network interface or IP address baresip is using.\n- Always update status icon of accounts when resuming to main activity.\n- Fixed crash when resuming to previously destroyed main activity.\n- Correctly show active incoming call when resuming to main activity.\n- Updated Spanish strings.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/11.2.0.txt",
    "content": "- Added support for AMR narrowband codec.\n- Added Bulgarian strings.\n- Renamed main menu Configuration item to Settings.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/11.3.0.txt",
    "content": "- Added to Settings possibility to choose which audio modules are\n  loaded. Audio codecs provided by the loaded modules are available for\n  accounts to use.\n- Minor code improvements.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/12.0.0.txt",
    "content": "- Initial implementation of audio through Bluetooth headset.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/12.1.0.txt",
    "content": "- Allow port number in account's Address of Record (AoR).\n- Fixed checking if account already exists.\n- Updated NO and BG strings.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/12.2.0.txt",
    "content": "- Used new API functions 'net_set_address' and 'net_set_af' to implement\n  Prefer IPv6 functionality.\n- Removed Bind Address from Settings (it didn't work as expected).\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/12.2.1.txt",
    "content": "- Improved and fixed handling of dynamic network changes.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/13.0.0.txt",
    "content": "- Play notification sound also when message arrives and baresip is visible.\n- If contact's name is empty, use contact's SIP URI AoR as contact's name.\n- Relaxed checking of contact's name.\n- Avoid occasional crash when selecting an account in the spinner.\n- Ask Record Audio (Microphone) permission when baresip starts (if not\n  already granted or permanently denied).\n- Improved handling of Write/Read External Storage (Storage Space)\n  permission upon Backup/Restore.\n- Read Phone State (Phone) permission is not needed.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/13.0.1.txt",
    "content": "- Always notify user agent spinner about possible data set change when\n  resuming main activity without intent.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/14.0.0.txt",
    "content": "- Added character based avatars to contacts, chats, and call history.\n- Fixed saving/restoring of contacts with non-ASCII contact names.\n- Improved formatting of dates and times.\n- Added About limitation note about multiple network interfaces.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/15.0.0.txt",
    "content": "- Added image based contact avatars as alternative to character ones.\n- Small user interface improvements and fixes.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/15.1.0.txt",
    "content": "- Added possibility to give transport protocol for account's AoR.\n- Do not show account's port or transport protocol except in Account Activity.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/15.1.1.txt",
    "content": "- Minor string fixes and improvements.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/16.0.0.txt",
    "content": "- Replaced global setting \"Prefer IPv6\" with account specific \"Prefer IPv6 Media\" setting.\n- Improved handling of changes in network connectivity.\n- Do not require port in IPv6 outbound URI.\n- Abandoned audio focus also at quit if not already done earlier.\n- Added initial Russian language support.\n- Updated some Norwegian strings.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/16.0.1.txt",
    "content": "- Re-register UAs when a new active network becomes available or\n  when link properties change even if IP addresses remain the same.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/16.1.0.txt",
    "content": "- Do not re-register accounts when link properties change, but IP addresses\n  remain the same.\n- If baresip doeds not start, let the user change the Settings that might\n  have caused the problem.\n- String updates and fixes.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/17.0.0.txt",
    "content": "- Fixed saving of an account that has ;transport parameter in its AoR.\n- Fixed enabling of notifications when call is closed.\n- Use white status icon when account's registration has not been enabled.\n- Default call volume now effects both voice call and music streams.\n- Improved Accounts activity layout and implementation.\n- Do not show baresip lib debug messages if Debug has not been enabled.\n- Do not re-create activities when screen orientation changes.\n- Properly show chat information that does not fit one line\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/17.1.0.txt",
    "content": "- Use usage type instead of stream type when requesting audio focus.\n- Fixed formatting of Account English Help text.\n- Don't show possible port of an AoR when asked about deleting the AoR.\n- Filled the non-register status icon with white color.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/17.2.0.txt",
    "content": "- Authentication username now defaults to account's username.\n- Ask authentication password at baresip start if authentication\n  username is given, but password is not.\n- Initialize account's status dot to yellow, when Register is checked\n  in account activity.\n- Fixed bug related to creation of first account on older Android versions.\n- Minor string improvements.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/17.2.1.txt",
    "content": "- Fixed bug in storing of accounts to file system.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/17.2.2.txt",
    "content": "- Do not subscribe to message waiting indication when account is created\n  and voicemail URI is not set.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/17.2.3.txt",
    "content": "- Prevent crashes resulting from double clicking of various icons and\n  list items.\n- Change color of account's notification icon to white when account is\n  unregistered.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/17.3.0.txt",
    "content": "- Added \"Show Password\" checkbox to authentication password prompt.\n- Ask microphone permission as the first thing when baresip starts.\n- Do not allow \" character in account's authentication password.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/17.4.0.txt",
    "content": "- Added support for Android 10 (API level 29).\n- Improved asking of microphone permission when baresip is started the first time.\n- Avoid crash if call button is touched without accounts.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/18.0.0.txt",
    "content": "- Due to Android 10 restrictions, baresip cannot anymore be automatically\n  started after boot without a notification.\n- Fixed restart of baresip on Android 10.\n- Removed setting of ICE Lite Mode due to upstream removal.\n- Added Brazilian Portuguese strings from Weblate.\n- Small fixes in Spanish and Norwegian strings.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/18.0.1.txt",
    "content": "- Upstream increase of maximum number of account's audio codecs from 8 to 16.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/18.1.0.txt",
    "content": "- Improved configuration of account's audio codecs.\n- Always show incoming call (if any) when baresip is resumed.\n- Improved and fixed resuming to non-main activities.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/18.1.1.txt",
    "content": "- Fixed crash when calling from Call History.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/18.1.2.txt",
    "content": "- Fixed crash when returning to the previous activity after received\n  call or message.\n- Better handling of proximity sensor and speaker phone.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/18.1.3.txt",
    "content": "- Simplified control of proximity sensing.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/18.2.0.txt",
    "content": "- Separated configuration of account's audio codecs to a new view.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/18.2.1.txt",
    "content": "- Fixed two chat related crashes.\n- Minor code improvements.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/18.3.0.txt",
    "content": "- Dialog improvements.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/19.0.0.txt",
    "content": "- Added TURN account medianat option.\n- Added possibility to give STUN/TURN server username/password.\n- STUN/TURN related strings for some languages have not been updated yet.\n- More dialog improvements.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/19.1.0.txt",
    "content": "- Alert dialog improvements.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/20.0.0.txt",
    "content": "- Acoustic Echo Cancellation improvements.\n- Moved audio settings from Settings to new activity.\n- Added AEC Extented Filter setting.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/20.0.1.txt",
    "content": "- Fixed returning to Audio Activity after pause.\n- Fixed adding of audio modules in Audio settings.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/20.0.2.txt",
    "content": "- Dialpad button related fixes and improvements.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/20.1.0.txt",
    "content": "- Added contributed RU strings.\n- Upstream fix of possible crash.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/21.0.0.txt",
    "content": "- Added initial support for baresip originated call transfer.\n- Added new language Portuguese from Weblate.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/21.1.0.txt",
    "content": "- Added possibility to re-register selected account by swipe down gesture.\n- Automatically focus and show soft keyboard when passwords or transfer\ndestination is asked.\n- Various string related fixes.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/21.2.0.txt",
    "content": "- Added AMR-WB (Adaptive Multi-Rate Wideband) speech codec.\n- Added Licenses section to About text.\n- New Portuguese (Brazil) translations.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/22.0.0.txt",
    "content": "- Added Websocket transport for SIP (RFC 7118).\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/22.1.0.txt",
    "content": "- New Portuguese (Brazil) and Russian translations.\n- Update icons after resuming to the application.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/23.0.0.txt",
    "content": "- Added visibility toggle to Account's Authentication and STUN/TURN Passwords\n- Prefer VPN interface (if any) when choosing from available network interfaces\n- Added note to About text about interface preference order\n- Improved configuration of Account's Media NAT Traversal\n- Default to Google's STUN server also when Media NAT Traversal protocol is ICE\n- Increased maximum lengths of Account's Authentication and STUN/TURN Usernames and Display Name to 64 characters\n- Added/updated FR, NO, and FI string translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/23.1.0.txt",
    "content": "- Added G.729 audio codec.\n- Added a Setting to turn on/off tracing of SIP messages to logcat.\n- STUN/TURN Server URI schemes 'stuns' and 'turns' are not currently supported.\n- New Portuguese (Brazil) string translations.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/23.2.0.txt",
    "content": "- Improved detection of VPN connectivity changes\n- New Portuguese (Brazil) translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/24.0.0.txt",
    "content": "- Use adaptive jitter buffer\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/24.1.0.txt",
    "content": "- Improved handling of volume up/down keys\n- Improved handling of speakerphone\n- New RU translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/24.2.0.txt",
    "content": "- Automatically show DTMF soft keyboard when call is connected\n- Avoid crash caused by pressing volume control keys when not in call\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/24.3.0.txt",
    "content": "- Show dialpad automatically only when device orientation is portrait\n- Don't play call waiting sound when second call comes in\n- Improved About text\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/24.4.0.txt",
    "content": "- Do not default STUN/TURN server Username/Password to Authentication Username/Password\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/25.0.0.txt",
    "content": "- Improved incoming call notification.\n- Added missed call notification.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/26.0.0.txt",
    "content": "- Added dark theme and dark theme setting.\n- Account spinner enhancements.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/26.0.1.txt",
    "content": "- Fixed turning on dark theme when baresip application is launched\n- New translations (Portuguese (Brazil))\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/26.1.0.txt",
    "content": "- Other apps can now see baresip as a phone app for sip: and tel: URIs\n- Improved save/restore of call URI text when main activity is paused\n- Introduced Japanese translation\n- Color enhancements\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/26.1.1.txt",
    "content": "- Fixed handling of call action when baresip app is not running.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/26.1.2.txt",
    "content": "- Try to detect rotation of contact avatar images\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/27.0.0.txt",
    "content": "- Added possibility to export baresip contacts to Android contacts\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/27.0.1.txt",
    "content": "- Fixed crash when baresip is started the first time\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/28.0.0.txt",
    "content": "- Added possibility to partially auto-configure new account from web page (see https://github.com/juha-h/baresip-studio/wiki/Automatic-Account-Configuration for details)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/28.1.0.txt",
    "content": "- Improved finding of a contact that matches a SIP URI\n- Toolbar and menu style enhancements\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/28.1.1.txt",
    "content": "- Fixed coloring of audio modules setting\n- Fixed call list related crash and appearance\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/28.2.0.txt",
    "content": "- Added 'Verify Server Certificates' setting\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/29.0.0.txt",
    "content": "- Enabled swipe left/right to toggle between accounts\n- Added DTMF Mode account setting\n- Translations update from Weblate (French)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/29.1.0.txt",
    "content": "- Portuguese (Brazil) translations from Weblate\n- Fixed F-Droid nb-NO locale tag\n- Avoid 'duplicate finish request' warnings\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/29.2.0.txt",
    "content": "- Chat, call history, and contact related bug fixes\n- Fixed URI completion bugs and improved URI related checks\n- Translations from Weblate (French, Portuguese (Brazil))\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/29.2.1.txt",
    "content": "- Avoid possible contacts related crash at baresip start\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/3.2.0.txt",
    "content": "- Added dialpad button for choosing between phone number and text soft keyboard.\n- Fixed auto completion of callee based on contacts.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/3.2.2.txt",
    "content": "- Added handling of call transfer failed event.\n- Avoided crash at device (re)start.\n- Avoided crash when coming first time back from accounts to main activity.\n- Used \"textEmailAddress\" input type for account URIs.\n- Changed importance of default notification channel to low.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/30.0.0.txt",
    "content": "- Upgraded target SDK version to API level 30\n- In Android versions 10 and above, let user choose the file in\n  backup, restore, TLS certificate related operations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/30.0.1.txt",
    "content": "- Upstream fix in selecting correct account for incoming call.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/30.1.0.txt",
    "content": "- Password dialog and account password entry improvements.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/30.2.0.txt",
    "content": "- Added support for AMR codec Bandwidth Efficient Mode\n- Allow escaped characters in SIP URI user part and authentication username\n- Use coroutine instead of async task to fetch account config from network\n- In Android 10+ use picker to choose TLS Certificate and TLS CA Files\n- Ask confirmation to reset settings to factory defaults\n- New translations (Portuguese and Portuguese (Brazil))\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/30.3.0.txt",
    "content": "- Play auto-answer sound when auto-answering\n- Removed iLBC codec (removed from upstream)\n- Removed unused audio files\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/31.0.0.txt",
    "content": "- Improved About text\n- Restore chat list position when returning from chat\n- Fixed crash when asking for account's password\n- Don't try to load iLBC module\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/31.1.0.txt",
    "content": "- Upstream fix of websocket transport\n- New Portuguese (Brazil) translations\n- Check Outbound Proxy URI transport\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/31.2.0.txt",
    "content": "- Account STUN/TURN URI enhancements\n- Various audio related improvements\n- New Portuguese (Brazil) and Slovenian translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/31.2.1.txt",
    "content": "- Fixed crash when selecting an account in accounts activity\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/32.0.0.txt",
    "content": "- Added button to turn microphone on/off during call\n- Used horizontal scroll view for call control buttons\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/32.1.0.txt",
    "content": "- New Spanish and Portuguese (Brazil) translations\n- Minor AudioManager related improvements\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/32.2.0.txt",
    "content": "- Improved checking of SIP URI parameters\n- More audio manager/bluetooth related fixes and improvements\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/32.3.0.txt",
    "content": "- Acquire WiFi lock while baresip is using WiFi network\n- Restored accidently removed ringback asset\n- Added About note regarding turning off battery optimizations\n- Swedish language translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/33.0.0.txt",
    "content": "- Acquire WiFi lock when baresip is using WiFi network\n- Added note to About text regarding battery optimization\n- New Swedish and Portuguese (Brazil) translations\n- Various implementation improvements\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/34.0.0.txt",
    "content": "- Added limited support for multiple simultaneously active network interfaces\n- Show notification when baresip is started without network access\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/34.1.0.txt",
    "content": "- Use WebRTC Acoustic Echo Cancellation Mobile Mode filter\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/35.0.0.txt",
    "content": "- Use WebRTC Acoustic Echo Cancellation Mobile Mode filter\n- Show in notification the number missed calls if more than one\n- When Default Call Volume setting is set, set both media and call volume\n- Cancel notifications when main activity is resumed\n- Fixed stop ringing in older Android versions\n- New Swedish and Portuguese (Brazil) translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/35.1.0.txt",
    "content": "- When aec is enabled/disabled, load/unload webrtc_aecm module\n- Use BaresipService to play all sounds\n- Proper handling of session progress response\n- During call, volume keys now control media volume\n- Use communication audio mode when not in ringing audio mode\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/35.2.0.txt",
    "content": "- Audio focus related fixes and improvements\n- Remove all whitespace from callee field\n- New Portuguese (Brazil) translation\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/36.0.0.txt",
    "content": "- Migrated baresip to API level 31 (Android 12)\n- Improved available network detection\n- Improved permission requests\n- Removed account's Prefer IPv6 Media setting\n- New Portuguese (Brazil) translation\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/36.1.0.txt",
    "content": "- Moved call mute button from call control to action bar\n- Added Battery Optimizations setting\n- Ask record audio permission when baresip is started\n- New Portuguese (Brazil), French, and Swedish translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/36.1.1.txt",
    "content": "- Moved call mute button from call control to action bar\n- Added Battery Optimizations setting\n- Ask record audio permission when baresip is started\n- New Portuguese (Brazil), French, and Swedish translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/36.1.2.txt",
    "content": "- Prevented swipe related crash\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/36.2.0.txt",
    "content": "- Currently mic icon is active only when a call is connected\n- New Swedish translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/37.0.0.txt",
    "content": "- RFC 5589 Basic Transfer compliant call transfer implementation\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/37.0.1.txt",
    "content": "Fixed outgoing call audio settings\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/37.1.0.txt",
    "content": "- Added average bit rate, jitter, packet count, and packet lost count data to call info\n- Use accent color pause icon to indicate that user has put the call on hold\n- Inform user when the other party has put the call on hold\n- Minor soft input related improvements\n- New Portuguese (Brazil) translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/37.2.0.txt",
    "content": "- Better protection of baresip app when device is locked with secure keyguard\n- New Portuguese (Brazil) translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/37.3.0.txt",
    "content": "- Moved Default Call Volume setting under Audio settings\n- New language (German)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/37.4.0.txt",
    "content": "- New Portuguese and German translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/38.0.0.txt",
    "content": "- Improved device lock/unlock handling\n- In Settings, added link to Android settings\n- Properly check if Battery Optimizations setting has been changed\n- New Swedish and Portuguese (Brazil) translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/39.0.0.txt",
    "content": "- Improved display of call history time value in call history list\n- Added call details activity that can be accessed by touching time in\ncall history list\n- Fixed showing of avatars in contact, call, and chat lists\n- Allow cancelling of answered call that waits to be established\n- New Portuguese, Portuguese Brazil, Swedish, and Norwegian translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/39.1.0.txt",
    "content": "- Improved and fixed handling of message and call transfer events\n- Toast registration failure it to any current activity\n- Automatically capitalize message sentences\n- Replaced deprecated local broadcasts with LiveData events\n- Fixed some bugs related to handling of external call actions\n- New Portuguese (Brazil) translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/4.0.0.txt",
    "content": "- Improved implementation of contacts, messages, and call history.\n- Avoided crash when messages arrive before accounts have been added.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/4.1.0.txt",
    "content": "- All user interface unrelated data is now kept in baresip service.\nallowing proper recovery in case Android kills other baresip activities.\n- Many other improvements and bugfixes.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/4.1.1.txt",
    "content": "- Fixed critical bug in checking of record audio permission that\nprevented new baresip installation from starting.\n- Small improvements.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/4.1.2.txt",
    "content": "- Fixed crash when empty accounts spinner is clicked.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/40.0.0.txt",
    "content": "- Added Telephony Provider account configuration item\n- Added support for tel URI as Callee value and Contact URI\n- Prompt user for Telephony Provider account if needed to make the call\n- Show account's STUN configuration items only if needed\n- Improved handling of CALL action from other applications\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/40.0.1.txt",
    "content": "- Added Telephony Provider account configuration item\n- Added support for tel URI as Callee value and Contact URI\n- Prompt user for Telephony Provider account if needed to make the call\n- Show account's STUN configuration items only if needed\n- Improved handling of CALL action from other applications\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/40.1.0.txt",
    "content": "- Do not play busy or error sound when call is closed\n- Improved reliability of Bluetooth usage\n- Better handling of events from baresip core\n- New Portuguese (Brazil) translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/41.0.0.txt",
    "content": "- Added call timer chronometer to \"Call to .../Call from ...\" row\n- Added possibility to use Android contacts\n- Added \"Show Android Contacts\" setting to choose if Android contacts are shown when contacts button is touched\n- Check that restore file has been created by baresip app\n- New Swedish and Portuguese (Brazil) translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/42.0.0.txt",
    "content": "- Replaced 'Show Android Contacts' setting with 'Contacts' setting that allows to choose if baresip contacts, Android contacts, or both are used\n- Contact name <-> contact URI mapping fixes and improvements\n- Avoid crash when Android forces baresip app restart\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/42.1.0.txt",
    "content": "- Replaced AppCompat theme with MaterialComponents theme\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/42.2.0.txt",
    "content": "- Contact selection list now contains only those Android contacts that have exactly one URI\n- New Swedish and Portuguese (Brazil) translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/42.3.0.txt",
    "content": "- Fixed adding of new baresip contact\n- When selecting a new default account, keep the order of the rest unchanged\n- Improved handling of call auto-rejection\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/43.0.0.txt",
    "content": "- Added initial support for attended call transfer\n- In chat activity, replaced long click with short click\n- Made message text selectable\n- Use bold typeface in AoR spinner text when call is active\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/43.0.2.txt",
    "content": "- Added initial support for attended call transfer\n- In chat activity, replaced long click with short click\n- Made message text selectable\n- Use bold typeface in AoR spinner text when call is active\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/43.1.0.txt",
    "content": "- Fixed 32 bit armeabi-v7a architecture related bug\n- Display Anonymous or Unknown if URI host is anonymous.invalid or unknown.invalid\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/44.0.0.txt",
    "content": "- Show diverter if incoming call has been diverted\n- Backup related fixes in older Android versions\n- Improved URI to Contact matching\n- New Portuguese (Brazil) translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/44.1.0.txt",
    "content": "-  Due to Google's requirement, ask user's consent if Android contacts is chosen in Settings\n- New Portuguese (Brazil) translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/44.2.0.txt",
    "content": "- Fixed crash on ARMv7 devices when message was received\n- New Portuguese (Brazil) translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/446.txt",
    "content": "- Fixed Do Not Disturb check\n- New translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/447.txt",
    "content": "- Change color of call button to yellow when call button has been touched, but call has not been made yet\n- Do not activate useless software based echo canceling if hardware based is not available\n- Always clear security icon and DTMF field when call is closed\n- For registered accounts, check that user part of incoming Request URI matches user part of REGISTER request contact URI\n- Upstream audio jitter buffer improvements\n- New translations (French)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/448.txt",
    "content": "- Added Weblate note to About text\n- New translations (Chinese)\n- Baresip lib logging macro fix\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/449.txt",
    "content": "- Fixed adding of new call to history\n- Avoid screen \"flash\" when calling from contacts or call history\n- New translations (Czech)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/45.0.0.txt",
    "content": "- Added Country Code account configuration option\n- Audio related improvements from upstream\n- New Portuguese (Brazil) and Finnish translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/45.1.0.txt",
    "content": "- Use elliptic curve cryptography based DTLS Key Establishment Protocol\n- New Swedish translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/45.1.1.txt",
    "content": "- Use elliptic curve cryptography based DTLS Key Establishment Protocol\n- Fixed backup of call history\n- New Swedish translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/45.1.2.txt",
    "content": "Ring and notification tone related fixes and improvements\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/450.txt",
    "content": "- Exclude starred Android contacts from Do Not Disturb\n- Don't loose content of new message that has not been sent yet\n- New translations (Swedish)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/451.txt",
    "content": "- Added initial support for dynamic colors on many screens\n- Improved showing and hiding of soft keyboard\n- New translations (Chinese (Simplified Han script))\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/452.txt",
    "content": "- Retain account and settings modifications until check or back button is clicked\n- Use TLS CA File certificates also when pre-configuring account from network\n- Do not leave Settings in case of input error\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/453.txt",
    "content": "- Use Material3 color theme everywhere\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/454.txt",
    "content": "- Improved visibility of Main screen Call from field and Account screen SIP URI field\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/455.txt",
    "content": "- Improved surface container colors\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/456.txt",
    "content": "- Removed G.726 codec due to too many dependencies\n- New G.722 codec with less dependencies\n- New translations (Swedish, Czech, and Chinese)\n\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/457.txt",
    "content": "- Fixed possible crash related to attended transfer\n- Show original call as being on hold when waiting for blind transfer\n- Use Material3 colors also in Hold, Transfer, and Info buttons\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/458.txt",
    "content": "- Replaced most drawables with Material Design icons\n- Avoided possible memory leak in chronometer\n- New translations (Chinese)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/459.txt",
    "content": "- Replaced baresip launcher PNG image with vector drawable\n- Replaced call, hangup, lock, camera front/back, and screenshot icons with Material Design icons\n- Used outlined text field for DTMF digits\n- Notification fixes and improvements\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/46.0.0.txt",
    "content": "- Added possibility to turn on/off account's registration via\n  broadcast intents (see Wiki for details)\n- Upstream addition of DNS query caching\n- Upstream fix of ICE candidate priorities\n- Increased Android target API level to 32 (Android 12L)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/46.0.1.txt",
    "content": "- Do not use DNS cache that may cause long delays to address resolution\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/46.1.0.txt",
    "content": "- Long touch on current account enables or disables account's registration\n- Added possibility to nickname accounts\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/46.1.1.txt",
    "content": "- Long touch on current account enables or disables account's registration\n- Added possibility to nickname accounts\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/46.2.0.txt",
    "content": "- If unregistering fails, update registration status to white anyway\n- Set asked password when user agent is created\n- New Portuguese (Brazil) translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/460.txt",
    "content": "- After restore, cleared recorded call indications from calls and call details screens\n- On call details screen, added possibility to save call recording by long click on call duration and to delete call history by long click on call time value\n- Added progress bar and stop button to play recording dialog\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/461.txt",
    "content": "- Use compose based call timer\n- Fixed possible race condition when screen is rotated\n- New translations (Chinese)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/462.txt",
    "content": "- Added proper response code and reason to user agent hangup calls\n- Removed possible old recordings when baresip is restored\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/463.txt",
    "content": "- Optimized amr, g7221, and opus codecs\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/464.txt",
    "content": "- Added Block Unknown account setting that allows blocking of calls and messages from peers not found in contacts\n- Added Check Origin account setting that allows blocking of SIP requests whose source IP address does not match IP address where register requests are sent\n- Added supported Transport Protocols setting\n- New translations (Chinese and Russian)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/466.txt",
    "content": "- Fixed bug in restoring Block Unknown account setting\n- New translations (Chinese)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/467.txt",
    "content": "- Added screens to show blocked calls and messages\n- Avoid showing of empty screen when back or check icons are touched repeatedly\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/468.txt",
    "content": "- Added Search to Contacts Screen\n- New translations (Czech)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/469.txt",
    "content": "- Fixed baresip contact long click alert\n- New translations (Chinese)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/47.0.0.txt",
    "content": "- Added support for reliable provisional response messages (RFC 3262)\n- Added QUIT action to intent receiver\n\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/47.1.0.txt",
    "content": "- Added support for SIP UPDATE method (RFC 3311)\n- Upstream fix of early audio (183 Session Progress) related bug\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/47.2.0.txt",
    "content": "- Delay calling by 1 sec after call icon is touched because audio mode\n  does not immediately change to communication\n- Upstream fix of PRACK handling\n- New Swedish translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/470.txt",
    "content": "- Added support for conference calls\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/471.txt",
    "content": "- Added support for conference calls\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/472.txt",
    "content": "- In contact search, match also diacritic characters\n- In contact search, highlight matched characters\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/473.txt",
    "content": "- Improved showing of contact suggestions on Main and Chats screens\n- New translations (Czech)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/474.txt",
    "content": "- Added BaresipApp to avoid baresip service starting before baresip library is loaded\n- Improved showing of call transfer destination suggestions\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/475.txt",
    "content": "- Fixed launch icon background\n- Improved showing of call icons\n- New translations (Hebrew)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/476.txt",
    "content": "- Fixed crash related to deleting of messages\n- Start automatically also when new version of baresip is installed\n- Improved Audio Settings check\n- Improved alert dialog background color\n- Improved suggestion coloring\n- New translations (Chinese and Hebrew)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/477.txt",
    "content": "- Migrated alert dialogs to Material design style\n- Improved dropdown menu coloring\n- Added timeout for getting account template from network\n- Fixed call redirect URI\n- New translations (Hebrew)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/478.txt",
    "content": "- Prevent multiple call button actions when the buttons are touched repeatedly\n- Avoid crash if getting of call's codec info fails\n- Avoid possible crash related to settings viewmodel\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/479.txt",
    "content": "- Unlock locked screen and turn it on when new call is received\n- New translations (Portuguese (Brazil)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/48.0.0.txt",
    "content": "- Use lock icons for call security indication\n- When calling Android contact, use the latest peer URI if Android contact has it\n- Use ZRTPCPP lib for ZRTP media encryption (peers need to be re-verified)\n- Enable DNS caching\n- New Portuguese (Brazil) translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/48.0.1.txt",
    "content": "- Use lock icons for call security indication\n- When calling Android contact, use the latest peer URI if Android contact has it\n- Use ZRTPCPP lib for ZRTP media encryption (peers need to be re-verified)\n- Enable DNS caching\n- New Portuguese (Brazil) translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/48.1.0.txt",
    "content": "- Added GSM codec\n- Made window un-touchable and started 5 second timer at Quit or Restart\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/48.2.0.txt",
    "content": "- Disable PRACK (RFC 3262) feature due to severe upstream bug\n- New Portuguese (Brazil) and Russian translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/480.txt",
    "content": "- Cancel full screen notification when call is closed\n- Issue missed call notification also when baresip app is not visible\n- Cancel full screen notification when call is closed\n\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/481.txt",
    "content": "- Avoid crash related to screen rotation\n- Upstream fix related to SIP 183 Session Progress response\n- Translations update (English, Finnish)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/482.txt",
    "content": "- Added Unique Contact URI setting\n- New translations (Chinese)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/483.txt",
    "content": "- Fixed bug in codecs screen\n- New translations (French and Hebrew)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/484.txt",
    "content": "- Baresip foreground service notification is now always visible also when Android version is 14 or newer\n- New translations (Chinese and Czech)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/487.txt",
    "content": "- Baresip app is now integrated with Android Telecom Framework\n- Added support for multiple simultaneous calls (only one can be active)\n- Acquire partial wake lock only if there is active registrations or calls\n- Acquire WiFi lock also when WiFi is available but not actively used\n- Always resume to active call on main screen if there is one\n- In attended call transfer, check if peer supports REPLACES header\n- Print network debug only if there was a change\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/488.txt",
    "content": "- Baresip app is now integrated with Android Telecom Framework\n- Added support for multiple simultaneous calls (only one can be active)\n- Allow hanging up individual members of conference call\n- Improved status notification content texts\n- Acquire partial wake lock only if there is active registrations or calls\n- Acquire WiFi lock also when WiFi is available but not actively used\n- Always resume to active call on main screen if there is one\n- In attended call transfer, check if peer supports REPLACES header\n- Print network debug only if there was a change\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/489.txt",
    "content": "- Prevent other conference calls from closing when one participant hangs up\n- Fixed initialization of account Check Origin setting\n- Audio improvements\n- New translations (Chinese and Spanish)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/49.0.0.txt",
    "content": "- Improved selection and ordering of account's audio codecs\n- Due to problems, disabled DNS query cache\n- Migration to Android API 33\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/49.0.1.txt",
    "content": "- Fixed upstream bug that caused long Quit delay if Voicemail checking was\n  activated for an account\n- Simplified Quit/Restart process and avoided crash at exit\n- New Portuguese (Brazil) translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/49.1.0.txt",
    "content": "- Toast reason when incoming call gets closed by baresip lib\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/49.1.1",
    "content": "- Fixed audio crackling when G722 codec is used"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/49.1.2.txt",
    "content": "- Upstream fix of Media NAT Traversal (STUN/TURN/ICE) bug\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/49.1.3.txt",
    "content": "- Upstream fix of codec negotiation\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/49.2.0.txt",
    "content": "- Added \"RTCP Multiplexing\" account setting\n- Distributes action buttons evenly at the bottom of main activity\n- Used colorSecondaryDark as text view hint color\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/49.3.1.txt",
    "content": "- Added \"RTCP Multiplexing\" account setting\n- Distributes action buttons evenly at the bottom of main activity\n- Used colorSecondaryDark as text view hint color\n- Show correct status notification when there is no registered UAs\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/49.4.0.txt",
    "content": "- Fixed crash at network changes\n- New Portuguese (Brazil)) translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/491.txt",
    "content": "- Added Custom Parameters account setting\n- Retain value of account's Check Origin when Register is toggled\n- New translations (Chinese)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/492.txt",
    "content": "- Ensure that ringback and busy tone are played in communication mode\n- Improved network and DNS server update management\n- New translations (Spanish)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/5.0.0.txt",
    "content": "- Replaced text mode EditConfigActivity with graphical config activity.\n- Current configuration items are auto-start, DNS servers, Opus bitrate,\nand ICE-Lite.\n- Added ICE and STUN server account options.\n- Fixed bug in account deletion.\n- Unsent messages not lost when chat activity is interrupted.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/5.1.0.txt",
    "content": "- Added possibility to export/import contacts to/from the \"Download\" folder.\nIf you want to use this feature, grant baresip storage permission\nin Settings → Apps → baresip → Permissions.\n- Fixed slow quitting of baresip when there is no network connectivity.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/5.2.0.txt",
    "content": "- Notify user about registration failure.\n- Added call info button for an active call.\n- Do not require \"sip:\" scheme in Outbound Proxy URI.\n- Allow domain label to start with a digit.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/5.2.1.txt",
    "content": "- Relaxed checking of Display Name and Authentication Username\n- Added version name to About page\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/5.3.0.txt",
    "content": "- Added notification popup telling why call was closed.\n- Fixed showing of callee URI if it contains port number or parameters.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/5.3.1.txt",
    "content": "- Relaxed checking of userpart in account's SIP URI.\n- Added support for ARMv8-A architecture.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/5.4.0.txt",
    "content": "- Added SRTP-MANDF media encryption protocol.\n- Security button color now changes from red to yellow also when call is\nsecured by an SRTP* media encyption protocol.\n- Fixed setting of account media encryption to DTLS-SRTPF.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/5.4.1.txt",
    "content": "- Removed unused baresip modules from config and libraries.\n- The baresip service now lets baresip activity die on its own after\nreceiving kill intent from it.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/5.4.2.txt",
    "content": "- Fixed rare crash that can happen when first account is created and\nmessage or call comes in before user has returned to main activity.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/50.0.0.txt",
    "content": "- Ask POST_NOTIFICATIONS permission on Android 13+ devices\n- Use gray Microphone icon when microphone is not turned off\n- Use \"Busy Here\" response message when call is rejected\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/50.1.0.txt",
    "content": "- Quit/Restart process improvements\n- Try to support toggling of SpeakerPhone at API levels 31+\n- New Portuguese (Brazil) translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/50.1.1.txt",
    "content": "- Quit/Restart process improvements\n- Speakerphone and call related fixes and enhancements for API 31+\n- New Portuguese (Brazil) translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/50.1.3.txt",
    "content": "- Quit/Restart process improvements\n- Speakerphone and call related fixes and enhancements for API 31+\n- New Portuguese (Brazil) translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/50.1.4.txt",
    "content": "- More communication device related fixes and enhancements\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/50.1.5.txt",
    "content": "- Fixed WiFi hotspot interface detection on some devices\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/50.2.0.txt",
    "content": "- Audio routing related fixed for Android 12+ devices\n- Use secondary dark color in codec name when in dark mode\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/50.2.1.txt",
    "content": "- Audio routing related fixed for Android 12+ devices\n- Use secondary dark color in codec name when in dark mode\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/50.2.2.txt",
    "content": "- Audio routing related fixed for Android 12+ devices\n- Use secondary dark color in codec name when in dark mode\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/51.0.0.txt",
    "content": "- Vibrate and vibrate while ringing according to Sound and vibration settings\n- Bluetooth fixes and improvements for Android 12+ devices\n- Show permissions rationale when baresip is started the first time\n- New Swedish and Portuguese (Brazil) translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/51.1.0.txt",
    "content": "- Added registration interval account setting\n- Use Telephony Provider also when sending messages to tel URIs\n- Now also ( and ) characters are accepted in telephone number\n- Fixed posting notifications in later Android versions\n- Improved permission handling in later Android versions\n- New Spanish, Portuguese (Brazil)), and Swedish translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/51.2.0.txt",
    "content": "- Several contact related fixes and improvements\n- New Portuguese (Brazil) translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/52.0.0.txt",
    "content": "- Added support for selecting from multiple tel:/sip: URIs in Android\n- contacts \n- Active account now has to have Telephony Provider configured in order to\n  make call or send message to tel: URI\n- New Portuguese (Brazil), Spanish, Swedish, and Finnish translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/52.1.0.txt",
    "content": "- Added preliminary support for call recording\n- Many fixes related to asking of passwords\n- Prevent outside dismiss of some dialogs\n- New Spanish, Portuguese, and Brazil translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/52.2.0.txt",
    "content": "- Added possibility to stop recording playback\n- Added adaptive launcher icon\n- New Spanish translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/53.0.0.txt",
    "content": "- Added setting to choose IP address family\n- Added monochrome launcher icon\n- Removed icon and text from status notification when Android version is 13+\n- New Spanish, Russian, Portuguese (Brazil), and Czech translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/53.1.0.txt",
    "content": "- Launcher icon improvements\n- New Russian translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/53.1.1.txt",
    "content": "- Launcher icon improvements\n- Do not try to register new account if Register has not been checked\n- Added Registration Interval help text\n- New Russian translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/53.1.2.txt",
    "content": "- Upstream increase of max size of account string\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/53.2.0.txt",
    "content": "- Registration status notification improvements\n- Replaced several icon images with material design vector drawables\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/53.2.1.txt",
    "content": "- Fixed several audio focus/routing/volume control issues\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/53.2.2.txt",
    "content": "- Fixed several audio focus/routing/volume control issues\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/53.2.3.txt",
    "content": "- Simplified playing of ringtone\n- New Portuguese translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/54.0.0.txt",
    "content": "- Requires at least Android version 6 (API level 23)\n- In Android versions below 12, add 1.5 sec delay before making a call\nin order to avoid missing audio from callee\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/54.1.0.txt",
    "content": "- Added 'Audio Delay' Audio Setting\n- Improved Bluetooth headset connected test\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/54.2.0.txt",
    "content": "- Play busy tone when \"486 Busy Here\" or \"603 Decline\" response is received\n- Avoid unnecessary re-registration when network capabilities change\n- Simplified abandoning of audio focus\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/55.0.0.txt",
    "content": "- Prevent starting of baresip service more than once\n- Color improvements\n- New Spanish, Russian, Portuguese (Brazil), Swedish, and Czech translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/55.0.1.txt",
    "content": "- Upstream fix of call remaining in active list even when closed\n- Better handling of WiFi hotspot enable/disable events\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/55.0.2.txt",
    "content": "- Upstream fix of call remaining in active list even when closed\n- Better handling of WiFi hotspot enable/disable events\n- Improved alert dialog text button dark theme color contrast\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/55.1.0.txt",
    "content": "- Dark theme color improvements\n- Improved call auto-rejection\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/55.2.0.txt",
    "content": "- In addition to CALL, handle also DIAL actions from other applications\n- New Spanish and Portuguese (Brazil) translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/56.0.0.txt",
    "content": "- Allow selecting baresip as the default Phone app (do not select if also using telephony services)\n- Support both CALL and DIAL actions from other apps\n- Fixed bug in setting account's Telephony Provider\n- In messages, support also other character sets than UTF-8\n- Use default values for audio_buffer and jitter_buffer_delay\n- New Czech and Russian translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/56.1.0.txt",
    "content": "- In call history, Show missed (not rejected) calls using yellow up/down arrows\n- Use audio_buffer size 20-300 (ms) and jitter_buffer delay 0-20 (frames)\n- Avoid potential getNetworkInterfaces() related crash\n- Simplified About text and added \"more information\" link to Wiki\n- New Spanish, Portuguese (Brazil), and Czech translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/56.2.0.txt",
    "content": "- Added separate jitter buffer delays for audio and video\n- In call info, show packets, lost, and jitter as ?/? if info is not available\n- Upstream fix that now allows escaped characters in account's <user> part\n- New Czech translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/56.3.0.txt",
    "content": "- Fixed jitter buffer settings that prevented hearing of incoming audio\n- Terminate call if no media has been received within 60 seconds\n- New Spanish translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/57.0.0.txt",
    "content": "- Major rewrite of settings implementation by separating static and dynamic settings\n- Revert back to original jitter buffer delays now when libre bug is fixed\n- Configuration cannot be reloaded on the fly when opus settings are changed\n- New Spanish and Portuguese (Brazil) translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/57.1.0.txt",
    "content": "- Added more vertical padding to account configuration\n- Hack to fix wrongly escaped URI when call or dial request comes from outside\n- New Czech translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/57.1.1.txt",
    "content": "- Fixed typo in Default Phone App help text\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/57.2.0.txt",
    "content": "- Added Redirect Mode account setting to choose if call redirect requests\n  (3xx responses) are be followed automatically or if confirmation needs\n  to be asked\n- New Spanish and Portuguese (Brazil) translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/58.0.0.txt",
    "content": "- Added Reliable Provisional Responses Account Configuration option\n- Added Tone Country Audio Configuration option (credits to Robert Averbeck for providing the tone files)\n- Fixed AudioManager related SDK version test\n- New  Spanish, Portuguese and Portuguese (Brazil) translations\n- Upstream bug fixes and improvements\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/59.0.0.txt",
    "content": "- Now targeting API level 34 (Android 14)\n- Added support for Codec2 audio codec\n- Recording of next call is not cleared when call is closed\n- Included also Dark Theme setting in backup file\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/59.0.1.txt",
    "content": "- Now targeting API level 34 (Android 14)\n- Added support for Codec2 audio codec\n- Recording of next call is not cleared when call is closed\n- Included also Dark Theme setting in backup file\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/59.0.2.txt",
    "content": "- Now targeting API level 34 (Android 14)\n- Added support for Codec2 audio codec\n- Recording of next call is not cleared when call is closed\n- Included also Dark Theme setting in backup file\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/59.0.3.txt",
    "content": "- Now targeting API level 34 (Android 14)\n- Added support for Codec2 audio codec\n- Recording of next call is not cleared when call is closed\n- Included also Dark Theme setting in backup file\n- Upstream fix of re-INVITE (call hold/resume)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/59.0.4.txt",
    "content": "- Upstream fix of crash related to handling of OPTIONS request\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/59.1.0.txt",
    "content": "- Added support for favorite (starred) contacts\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/59.2.0.txt",
    "content": "- Start Automatically setting now starts the app rather than notifying about it\n- New Spanish, Portuguese (Brazil), and Russian translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/59.3.0.txt",
    "content": "- Minor upstream bug fixes\n- New German translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/59.4.0.txt",
    "content": "- Use thread RTP receive mode\n- New Czech and Portuguese translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/59.4.1.txt",
    "content": "- Updated libbaresip-android submodule (all modules are now found)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/59.4.2.txt",
    "content": "- Upstream, by default, denied incoming messages.  Can be allowed again\n  by making any change in configuration of an account.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/59.4.3.txt",
    "content": "- Allow receiving of messages after upstream (by default) denied them\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/59.5.0",
    "content": "- Automatically use CA certificates provided by Android OS\n- New Bulgarian, Japanese, Spanish, and Portuguese (Brazil) translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/59.6.0.txt",
    "content": "- Added baresip, CPU, and Android information in User-Agent header\n- Time of recorded call is now shown in accent color\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/59.7.0.txt",
    "content": "- Upstream fix of bug that may cause baresip app to stop responding\n- New Czech translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/6.0.0.txt",
    "content": "- Several user Interface improvements (mostly chat related).\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/6.0.1.txt",
    "content": "- Resume baresip app to the activity that was stopped.\n- Add example contact when baresip is started the first time.\n- Fixed showing of new message in the chats and messages list.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/6.0.2.txt",
    "content": "- Fixed crash when STUN server is defined without port number.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/6.1.0.txt",
    "content": "- Go to chat window when message notification is clicked.\n- Fixed bug in call transfer.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/6.2.0.txt",
    "content": "- Upgraded target Android API level to 28 (Android 9).\n- Moved some non-UI functionality from main activity to baresip service.\n- Simplified implementation of call transfer.\n- Updated baresip launcher and status bar images.\n- Un-REGISTERed UA when account is deleted.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/6.2.1.txt",
    "content": "- Used ConnectivityManager NetworkCallbacks to detect when network\nconnectivity becomes available or is lost.\n- Added Android 8.1+ way of screen handling.\n- Set volume controls to apply to current audio stream (may not always\nwork).\n- Fixed possible crash when destroyed baresip app is started again.\n- Shown notice when account to be created already exists.\n- Added note to \"About\" text about media volume.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/6.3.0.txt",
    "content": "- Added Debug Config option to turn adb logcat debug on and off.\n- Added possibility to export and import accounts to/from file in Download directory.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/6.3.1.txt",
    "content": "- Fixed and improved DTMF watcher implementation.\n- Turned off editing of incoming/outgoing call URI when call is in progress.\n- Destroyed user agent when account is deleted.\n- Removed unused KILL_BACKGROUND_PROCESSES permission.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/6.4.0.txt",
    "content": "- Added possibility to set an account as the default account.\n- Show red messages icon when unread messages exist.\n- Ask PIN to release key guard when Callee URI is clicked.\n- Fixed call, message, and call transfer actions when initiated from notifications.\n- Fixed sending a message from contacts or call history.\n- Used SingleTask instead of SingleTop as Main Activity launch mode.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/60.0.0.txt",
    "content": "- Use Android 14 compatible backup file format. Backup again before upgrading Android to version 14.\n- Improved thread handling\n- New German translations\n\n\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/60.1.0.txt",
    "content": "- Fixed deleting of avatar file when avatar is removed from contact or when contact with avatar is removed\n- Fixed saving of account's DTMF Mode\n- New Spanish, Portuguese, and Portuguese (Brazil) translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/60.2.0.txt",
    "content": "- Added 'In-band RTP or SIP INFO' account DTMF Mode\n- Added Speaker Phone audio setting\n- New German, Portuguese (Brazil), and Spanish translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/60.3.0.txt",
    "content": "- Added support for SHA-256 digest authentication\n- Verify Server Certificates settings can now be changed without restart\n- New Czech, German, Spanish, and Portuguese (Brazil) translations\n- Fixed formats of some strings in some languages\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/60.4.0.txt",
    "content": "- Properly handle transfer request when original call is already closed\n- Fixed handling of stateless TLS SIP requests\n- New Portuguese, Portuguese (Brazil), Spanish, French, Russian, English, Finnish, Swedish, German, and Czech translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/60.4.1.txt",
    "content": "- SRTP related security fix from upstream\n- New Czech translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/60.4.2.txt",
    "content": "- Fixed call recording\n- Do not send '180 Ringing' response if incoming call is auto-rejected\n- Remove also %20 (space) from call tel URI\n- New German translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/61.0.0.txt",
    "content": "- Added User Agent setting that can be used to set custom SIP request/response User-Agent header field value\n- Allow , and # characters in tel number (for example when calling Teams)\n- Use accent color instead of bold in Call History time column if a call in that call row has been recorded\n- Do not include uuid in backup file (prevents having many baresip UAs with same identity)\n- By default, include date and time in backup file name\n- New Spanish and Portuguese (Brazil) translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/61.0.1.txt",
    "content": "- If DTMF is enabled, keep focus at screen orientation changes\n- Changed 'The Test Call' contact's URI to 'sip:thetestcall@sip2sip.info'\n- When showing URI in friendly form, show URI parameters except ';transport=udp'\n- Removed '.bs' suffix from backup file name\n- New translations (Russian)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/61.1.0.txt",
    "content": "- Added Microphone Gain audio setting\n- Added Logcat main menu item\n- New Czech, Norwegian (Bokmål), German, Portuguese (Brazil), and Spanish translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/62.0.0.txt",
    "content": "- If available, use Acoustic Echo Canceler, Automatic Gain Control, and\n  Noise Suppressor provided by the device\n\n- Android 9 (API level 28) is now the minimum supported version\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/62.1.0.txt",
    "content": "- Opt out edge-to-edge enforcement for Android 15 compatibility\n- Avoid crashes in BootCompletedReceiver and when checking outbound proxy URI\n- New Finnish, Bulgarian, Japanese, Portuguese (Brazil), Spanish, Czech,\nand Polish translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/63.0.0.txt",
    "content": "- Added support for edge-to-edge content display for Android 15 compatibility\n- Rounded alert dialog corners\n- Don't automatically show soft keyboard when configuration changes\n- Added new language (Tamil)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/63.1.0.txt",
    "content": "- Prevent soft keyboard from hiding input text field in some activities\n- Fixed telephone-event payload type number\n- Prevent crash when backup file is large\n- New translations (German)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/63.2.0.txt",
    "content": "- Apply Acoustic Echo Cancellation only to recorder session\n- Do not backup recordings\n- New Romanian, Portuguese (Brazil), German and Czech translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/63.2.1.txt",
    "content": "- Improved setting of audio effects\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/63.2.2.txt",
    "content": "- Reinstalled player AEC\n\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/63.2.3.txt",
    "content": "- Allow use of software AEC even when hardware one would be available\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/63.2.5.txt",
    "content": "- Alert if hardware AEC is not available when software AEC is disabled in audio settings\n- New translations (Portuguese Brazil)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/63.3.0.txt",
    "content": "- Allow * and # characters in telephone number\n- New translations (Portuguese (Brazil))\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/64.0.0.txt",
    "content": "- Migrated of user interface to Jetpack Compose (may include bugs)\n- Always try to use hardware Acoustic Echo Canceler if available\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/64.1.0.txt",
    "content": "- Several user interface related fixes and improvements\n- New translations (Portuguese (Brazil))\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/64.2.0.txt",
    "content": "- Added vertical scrollbars to Settings, Account, and Audio activities\n- Fixed call and transfer URI suggestions handling\n- Dialog style improvements\n- New translations (Portuguese)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/64.3.0.txt",
    "content": "- Fixed handling of DTMF backspace character\n- Chat and contact related UI improvements\n- Use background color also in system bars\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/64.3.1.txt",
    "content": "- Fixed handling of DTMF backspace character\n- Chat and contact related UI improvements\n- Use background color also in system bars\n- Fixed accounts activity crash when no accounts\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/65.0.0.txt",
    "content": "- Completed user interface conversion to Jetpack Compose\n- Fixed Verify Server Certificates setting\n- New Portuguese translations\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/65.1.0.txt",
    "content": "- Restored swipe left/right gesture to toggle between accounts\n- Account and Settings user interface fixes and improvements\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/65.2.0.txt",
    "content": "- Improved pull-to-register look and implementation\n- Make sure bluetoothreceiver is registered before unregistering it\n- Fixed back press action for API < 33 compatibility\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/65.2.1.txt",
    "content": "- Improved pull-to-register look and implementation\n- Make sure bluetoothreceiver is registered before unregistering it\n- Fixed back press action for API < 33 compatibility\n- Fixed ZRTP verify query\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/65.3.0.txt",
    "content": "- Added Ringtone setting\n- Improved pull-to-register look and implementation\n- Make sure bluetoothreceiver is registered before unregistering it\n- Fixed back press action for API < 33 compatibility\n- Fixed ZRTP verify query\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/66.0.0.txt",
    "content": "- Use switches instead of check boxes in all activities\n- Alert at start if network or hardware Acoustic Echo Cancellation is not available\n- Fixed bug that caused truncation of called telephone number\n- Avoid crash related to using baresip as the dialer app\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/66.1.0.txt",
    "content": "- Added Numeric Keypad account settings\n- Added tooltips to main activity top app bar icons\n- Show account's full SIP URI on Account page\n- New translations (Japanese and Tamil)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/66.1.1.txt",
    "content": "- Added Numeric Keypad account settings\n- Added tooltips to main activity top app bar icons\n- Show account's full SIP URI on Account page\n- Restored unsent new message after pause/resume\n- New translations (Japanese and Tamil)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/66.1.2.txt",
    "content": "- Added Numeric Keypad account settings\n- Added tooltips to main activity top app bar icons\n- Show account's full SIP URI on Account page\n- Restored unsent new message after pause/resume\n- In chat activity, fixed adding chat peer to contacts\n- New translations (Japanese and Tamil)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/66.1.3.txt",
    "content": "- Added Numeric Keypad account settings\n- Added tooltips to main activity top app bar icons\n- Show account's full SIP URI on Account page\n- Restored unsent new message after pause/resume\n- In chat activity, fixed adding chat peer to contact\n- Added cancel button for canceling outgoing call\n- Fixed showing of call transfer target suggestions\n- New translations (Japanese and Tamil)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/66.1.4.txt",
    "content": "- Several Settings related fixes\n- Improved transfer dialog layout\n- New translations (German)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/66.1.5.txt",
    "content": "- Fixed initialization of account from network\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/66.1.6.txt",
    "content": "- Apply volume keys to ringtone volume when call is coming in and to in-call volume during call\n- Use toasts for no network and no hw aec notices\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/67.0.0.txt",
    "content": "- Migrated baresip app from activities to Jetpack Compose navigation\n- Improved handling of calls and messages that arrive when baresip is not visible\n- Some other minor user interface improvements\n- Upstream fix of Refer-To header To/From tags\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/67.0.1.txt",
    "content": "- Stop call timer chronometer when call is closed\n- Add microphone to status notification only when record audio permission has been granted\n- New translations (Tamil)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/67.0.2.txt",
    "content": "- Default Call Volume audio setting bug fix\n- Backup/restore improvement and bug fix\n- New translations (German)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/67.0.3.txt",
    "content": "- Account and Settings screen fixes and improvements\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/67.1.0.txt",
    "content": "- In call history, use blue arrow down icon to indicate that call has been answered elsewhere\n- Added on-click help texts to call details arrow icons\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/67.2.0.txt",
    "content": "- Added Colorblind setting that currently affects registration status icons\n- Improved names of notification channels\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/68.0.0.txt",
    "content": "- Targeting Android 16 API level 36\n- Added Proximity Sensing setting\n- Fixed Contacts setting Both value\n- New translations (Czech)\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/68.1.0.txt",
    "content": "- Improved display of call history time\n- Changed background color of colorblind status icons\n- Fixed update of status notification\n- Fixed possible crash related to Registration interval account setting\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/68.1.1.txt",
    "content": "- Improved display of call history time\n- Changed background color of colorblind status icons\n- Fixed possible crash related to Registration interval account setting\n- Fixed update of status notification\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/68.1.2.txt",
    "content": "- Improved display of call history time\n- Changed background color of colorblind status icons\n- Fixed possible crash related to Registration interval account setting\n- Fixed update of status notification\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/7.0.0.txt",
    "content": "- Added support for IPv6 (not tested due to lack of access to IPv6 network).\n- Added \"Prefer IPv6\" configuration option.\n- Added \"Reset to Factory Default\" configuration option.\n- At Quit, make sure that also baresip task is stopped.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/7.1.0.txt",
    "content": "- Added 'Default Call Volume' configuration option.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/7.1.1.txt",
    "content": "- Update account spinner also when main activity is resumed without action.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/7.1.2.txt",
    "content": "- Tried to make sure that also account spinner view is updated when\naccount spinner is updated.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/7.2.0.txt",
    "content": "- Added new config variable Listen Address that allows specifying an IP\naddress (or all addresses) and a port at which baresip is listening at for SIP requests.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/7.3.0.txt",
    "content": "- Account media NAT protocol can now be chosen between STUN, ICE, or none.\n- Set account status to yellow immediately when account's registration is off.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/7.4.0.txt",
    "content": "- Aligned alerts with material design guidelines.\n- Improved language string handling.\n- Added Finnish language translation as an example.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/8.0.0.txt",
    "content": "- Added support for G.722.1 audio codec.\n- Added support for WebRTC based audio echo cancellation (excludes Opus codec).\n- Minor call notification changes.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/8.0.1.txt",
    "content": "- Fixed restoring of volume to original level if default call volume is in use.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/8.0.2.txt",
    "content": "- Fixed adding of new account.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/8.1.0.txt",
    "content": "- Obtain DNS server addresses dynamically from the system if DNS Servers\nare not given in Configuration.\n- If there is only one account, append account's domain to contact URI\ngiven without hostpart.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/8.2.0.txt",
    "content": "- Improved content and appearance of About page.\n- Always show currently active call (if any) when app is relaunched.\n- Changed location of alert dialog OK button.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/8.3.0.txt",
    "content": "- Added Internet Low Bitrate Codec (iLBC).\n- Improved account's codec selection.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/8.3.1.txt",
    "content": "- Don't set audio to communication mode when call is established,\nbecause on some devices it adds 3-4 sec delay to playing of audio.\n- Made Acoustic Echo Cancellation configurable.\n- Enable call info button when it becomes visible.\n- Use default device theme when displaying spinners.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/8.3.2.txt",
    "content": "- If account is accessed from AoR spinner, return directly to Main\nActivity.\n- Use HtmlCompat to convert About HTML to text (should work on all\nAndroid API levels).\n- Use Ringtone looping capability on Android P+.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/8.3.3.txt",
    "content": "- Get DNS servers dynamically also when baresip is started the very first time.\n- Avoid possible crash when getting account's audio codecs.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/8.3.4,txt",
    "content": "- Use communication mode only when call is (being) established.\n- Introduced 2.5 sec delay to placing of a call in order to avoid loss of\n  audio at the beginning of call.\n- Fixed initializing default contacts when baresip is started the first time.\n- Clear call URI when call is closed.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/8.3.5.txt",
    "content": "- Surround contact URIs between < and > only when contacts are saved to file.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/8.3.6.txt",
    "content": "- Allow changing of contact name also when only case of letters differ.\n- When restoring contacts, do not require that URIs are surrounded by angle brackets.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/8.4.0.txt",
    "content": "- Echo cancellation is now also applied to calls using the Opus codec.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/8.4.1.txt",
    "content": "- Upstream acoustic echo cancellation improvement.\n- New translations: el, nb_NO, ro.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/8.4.2.txt",
    "content": "- Update DNS servers and register UAs always when network becomes available.\n- Fixed and updated some texts.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/8.4.3.txt",
    "content": "- Fixed showing of account status, when default account is changed.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/8.4.4.txt",
    "content": "- Upstream fix in opus codec implementation.\n- Volume control stream fixes.\n- Fixed auto-rejecting of new incoming call.\n- Minor string improvements.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/8.5.0.txt",
    "content": "- Preliminary use of proximity sensor to turn off touch screen during call.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/8.5.1.txt",
    "content": "- Moved handling of proximity sensing from MainActivity to BaresipService.\n- Fixed crash when call or message comes in and MainActivity is not running.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/8.5.2.txt",
    "content": "- Fixed checking of Configuration DNS Servers.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/8.6.0.txt",
    "content": "- Added support for SIP registrars that do not correctly handle nonce reuse.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/9.0.0.txt",
    "content": "-  Added possibility to configure baresip a TLS certificate and CA certificates.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/9.1.0.txt",
    "content": "- Auto-reject incoming call if phone state is not idle.\n- Suppress ringtone of incoming call if device is in \"Do not disturb\" mode.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/9.2.0.txt",
    "content": "- Added support for Opus Forward Error Correction (FEC) via Opus\n  Expected Packet Loss configuration variable.\n- Try to update dynamic DNS information if registration fails due to\n  \"Invalid argument\" error.\n- Use translatable string in all configuration related alert messages.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/9.3.0.txt",
    "content": "- Added Answer Mode (Manual or Automatic) account configuration option.\n- Added Norwegian Bokmål strings and changelogs from Weblate.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/changelogs/9.4.0.txt",
    "content": "- Added \"Restart\" Main Activity menu option.\n- When registration fails due to DNS lookup failure, try registration\n  once more after updating DNS servers.\n- Small logging improvements.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/full_description.txt",
    "content": "This is a <a href=\"https://github.com/baresip/baresip\">baresip</a> based SIP User Agent application for Android.\n\nCurrently baresip app supports voice calling, voice conference calls, text messages, voicemail Message Waiting Indication as well as blind and attended call transfers.  Voice can be coded with Opus, AMR, Codec2, G.729, G.722, G.722.1, or PCMU/PCMA codecs. Security is achieved via TLS or WSS SIP signaling transport and ZRTP or (DTLS) SRTP media encapsulation.\n\nDevelopment of baresip app is motivated by need for a secure, open source SIP based VoIP User Agent for Android that does not depend on proprietary, third party push notification services.\n\nThis application can be installed on Android devices running Android version 9 or later.\n\nIf you need video calling, you can instead of this application install its sister application baresip+.\n\nSource code is available in master branch of <a href=\"https://github.com/juha-h/baresip-studio\">GitHub project</a>, where also issues can be reported.\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/short_description.txt",
    "content": "VoIP User Agent app for Android based on baresip SIP library\n"
  },
  {
    "path": "fastlane/metadata/android/en-US/title.txt",
    "content": "baresip\n"
  },
  {
    "path": "fastlane/metadata/android/fi-FI/changelogs/10.0.0.txt",
    "content": "- Jatka Baresip keskeytettyyn toimintaan päätoiminnan sijaan.\n"
  },
  {
    "path": "fastlane/metadata/android/fi-FI/changelogs/10.1.0.txt",
    "content": "- Lisätty tuki G722-koodekille.\n- korjattu tilien tallentaminen.\n"
  },
  {
    "path": "fastlane/metadata/android/fi-FI/changelogs/10.2.0.txt",
    "content": "- Lisätty tuki G.726-koodekille.\n- Parannettu koodekin oletusetusijajärjestys.\n"
  },
  {
    "path": "fastlane/metadata/android/fi-FI/changelogs/10.3.0.txt",
    "content": "- Lisätty Puheluhistoria-valikko, joka mahdollistaa tilin puheluhistorian poistamisen,\npoistamisen käytöstä ja käyttöönoton.\n- Lisätty espanjankieliset merkkijonot (hyvityksiä Javier Falbolle).\n- Baresipin automaattinen käynnistys on nyt oletuksena uusissa asennuksissa.\n"
  },
  {
    "path": "fastlane/metadata/android/fi-FI/changelogs/10.4.0.txt",
    "content": "- Salli tilin pikakeskusteluhistorian poistaminen pikakeskustelutoimintavalikkokohdan kautta.\n- Pyydä vahvistus ennen pikakeskustelun ja puheluhistorian poistamista.\n- Parannettu päätoimintojen puhepostin, pikakeskustelun ja puhelukuvakkeiden päivitys.\n- Siirry suoraan Tilitoimintaan, kun baresip käynnistetään ilman tilejä.\n- Vaihdettu puhelun hyväksymis- ja hylkäämispainikkeiden paikkoja.\n- Älä salli lähtevän puhelun lopettamista ennen kuin soittoyritys on alkanut.\n"
  },
  {
    "path": "fastlane/metadata/android/fi-FI/changelogs/11.0.0.txt",
    "content": "- Tilien ja yhteystietojen vienti ja tuonti korvattu kaikkien sovellustietojen\nvarmuuskopioinnilla ja palautuksella.\n- Parannettu suojaus estämällä Android-pohjaisten sovellusten varmuuskopiointi\nja sovelluksen asentaminen ulkoiseen tallennustilaan.\n- Kun tili poistetaan, poista myös tilin puhelu- ja pikakeskusteluhistoria.\n- Korjattu viestien ja pikakeskustelujen poistaminen.\n- Pienet käyttöliittymä- ja merkkijonoparannukset.\n"
  },
  {
    "path": "fastlane/metadata/android/fi-FI/changelogs/11.1.0.txt",
    "content": "- Lisätty 'Bind Address' -kokoonpanomuuttuja, jonka avulla voit valita, mitä\nverkkoliitäntää tai IP-osoitetta Baresip käyttää.\n- Päivitä aina tilien tilakuvake, kun palaat päätoimintaan.\n- Korjattu kaatuminen, kun palataan aiemmin tuhoutuneeseen päätoimintaan.\n- Näytä aktiiviset saapuvat puhelut oikein, kun palaat päätoimintaan.\n- Päivitetyt espanjankieliset merkkijonot.\n"
  },
  {
    "path": "fastlane/metadata/android/fi-FI/changelogs/11.2.0.txt",
    "content": "- Lisätty tuki AMR-kapeakaistaiselle koodekille.\n- Lisätty bulgarialaiset merkkijonot.\n- Päävalikon Kokoonpanokohde nimettiin uudelleen Asetuksiksi.\n"
  },
  {
    "path": "fastlane/metadata/android/fi-FI/changelogs/11.3.0.txt",
    "content": "- Lisätty Asetuksiin mahdollisuus valita ladattavat äänimoduulit. Ladattujen\nmoduulien toimittamat äänikoodekit ovat saatavilla tilien käyttöön.\n- Pienet koodin parannukset.\n"
  },
  {
    "path": "fastlane/metadata/android/fi-FI/changelogs/12.0.0.txt",
    "content": "- Alkutoteutus äänen käyttöönotosta Bluetooth-kuulokkeiden kautta.\n"
  },
  {
    "path": "fastlane/metadata/android/fi-FI/changelogs/12.1.0.txt",
    "content": "- Salli portin numero tilin tietueosoitteessa (AoR).\n- Korjattu tarkistus, onko tili jo olemassa.\n- Päivitetty NO- ja BG-kieliset merkkijonot.\n"
  },
  {
    "path": "fastlane/metadata/android/fi-FI/changelogs/12.2.0.txt",
    "content": "- Käytettiin uusia API-funktioita 'net_set_address' ja 'net_set_af' Prefer IPv6 \n-toiminnallisuuden toteuttamiseen.\n- Sidososoite poistettiin asetuksista (se ei toiminut odotetulla tavalla).\n"
  },
  {
    "path": "fastlane/metadata/android/fi-FI/changelogs/12.2.1.txt",
    "content": "- Dynaamisten verkkojen käsittely parannettu ja korjattu.\n"
  },
  {
    "path": "fastlane/metadata/android/fi-FI/changelogs/13.0.0.txt",
    "content": "- Toista ilmoitusääni myös kun viesti saapuu ja baresip on näkyvissä.\n- Jos yhteyshenkilön nimi on tyhjä, käytä yhteyshenkilön nimenä yhteyshenkilön\nSIP URI AoR :ta.\n- Yhteyshenkilön nimen tarkistaminen tehtiin rennommaksi.\n- Vältä satunnaista kaatumista valitessasi tiliä spinnerissä.\n- Pyydä äänitallentamisen (mikrofonin) lupa, kun Baresip alkaa (jos sitä ei ole jo\nmyönnetty tai evätty pysyvästi).\n- Ulkoisen tallennustilan (tallennustilan) kirjoitus-/lukulupien parannettu käsittely\nvarmuuskopioinnin/palautuksen yhteydessä.\n- Puhelimen tilan (Puhelin) -lupaa ei tarvita.\n"
  },
  {
    "path": "fastlane/metadata/android/fi-FI/full_description.txt",
    "content": "Tämä on <a href=\"https://github.com/baresip/baresip\">baresip</a>-pohjainen SIP-käyttäjäagenttisovellus Androidille.\n\nTällä hetkellä baresip-sovellus tukee äänipuheluita. tekstiviestejä, vastaajaviestin odotusilmoitus sekä sokeat ja osallistuneet puhelunsiirrot. Ääni voidaan koodata Opus-, AMR-, Codec2-, G.729-, G.722-, G.722.1-, G.726- tai PCMU/PCMA-koodekeilla. Turvallisuus saavutetaan TLS- tai WSS-SIP-signalointikuljetuksella ja ZRTP- tai (DTLS) SRTP-mediakapseloinnilla.\n\nBaresip-sovelluksen kehitystä motivoi tarve turvalliselle, avoimen lähdekoodin SIP-pohjaiselle VoIP-käyttäjäagentille Androidille, joka ei ole riippuvainen patentoiduista, kolmannen osapuolen työntö-ilmoituspalveluista.\n\nJos tarvitset videopuheluita ja sinulla on Android-versio 9.0 tai uudempi laite, joka tukee Camera2 API:a laitteistotukitasolla LEVEL 3 tai FULL, voit tämän sovelluksen sijaan asentaa sen sisarsovelluksen baresip+.\n\nLähdekoodi on saatavilla <a href=\"https://github.com/juha-h/baresip-studio\">GitHubissa</a>, jossa myös ongelmista voi ilmoittaa.\n"
  },
  {
    "path": "fastlane/metadata/android/fi-FI/short_description.txt",
    "content": "VoIP-käyttäjäagenttisovellus Androidille, joka perustuu Baresip SIP -kirjastoon\n"
  },
  {
    "path": "fastlane/metadata/android/fi-FI/title.txt",
    "content": "baresip\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/10.0.0.txt",
    "content": "- שיחזור של baresip לפעילות שהושהתה במקום לפעילות הראשית.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/10.1.0.txt",
    "content": "- נוספה תמיכה לקודק G722.\n- תוקנה שמירת חשבונות.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/10.2.0.txt",
    "content": "- נוספה תמיכה בקודק G.726..\n- שיפור סדר עדיפויות ברירת המחדל של הקודקים.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/10.3.0.txt",
    "content": "- נוסף תפריט \"היסטוריית שיחות\" שמאפשר מחיקה, השבתה והפעלה מחדש \n  של היסטוריית השיחות בחשבון.\n- נוספו מיתוגים בשפה הספרדית (תודה ל־Javier Falbo).\n- הפעלה אוטומטית של baresip היא עכשיו ברירת המחדל בהתקנות חדשות.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/10.4.0.txt",
    "content": "- מאפשרת מחיקת היסטוריית הצ'אטים של החשבון דרך תפריט פעילות הצ'אטים.\n- בקשת אישור לפני מחיקת היסטוריית הצ'אטים והשיחות.\n- שיפור בעדכון אייקוני תיבת דואר קולי, צ'אט ושיחות בפעילות הראשית.\n- העברה ישירה לפעילות החשבונות כאשר baresip מתחיל ללא חשבונות.\n- הוחלפו המקומות של כפתורי קבלה ודחיית שיחה.\n- אין אפשרות לסיום שיחה יוצאת לפני שהניסיון להתחיל את השיחה התרחש.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/11.0.0.txt",
    "content": "- הוחלפו ייבוא ויצוא של חשבונות ואנשי קשר בגיבוי\n  ושחזור של כל נתוני האפליקציה.\n- שיפור האבטחה על ידי מניעת גיבוי של אפליקציות מבוססות אנדרואיד\n  והתקנת אפליקציות בזיכרון חיצוני.\n- כאשר חשבון נמחק, נמחקים גם היסטוריית השיחות והצ'אטים של החשבון.\n- תוקנה בעיית מחיקת הודעות וצ'אטים.\n- שיפורים קטנים בממשק ובמיתוגים.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/11.1.0.txt",
    "content": "- נוספה משתנה קונפיגורציה 'Bind Address' שמאפשר לבחור איזה \n  ממשק רשת או כתובת IP אפליקציית baresip תשתמש.\n- תמיד לעדכן את אייקון הסטטוס של החשבונות בעת חזרה לפעילות הראשית.\n- תוקנה קריסה בעת חזרה לפעילות הראשית שהושמדה קודם.\n- שיחה נכנסת פעילה מוצגת כראוי בעת חזרה לפעילות הראשית.\n- עודכנו המיתוגים בספרדית.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/11.2.0.txt",
    "content": "- נוספה תמיכה בקודק AMR narrowband.\n- נוספו מיתוגים בשפה הבולגרית.\n- שונה שם פריט בתפריט הראשי מ-\"Configuration\" ל-\"Settings\".\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/11.3.0.txt",
    "content": "- נוספה בהגדרות אפשרות לבחור אילו מודולי אודיו ייטענו.\n  קודקי האודיו שסופקו על ידי המודולים הטעונים\n  זמינים לשימוש עבור החשבונות.\n- שיפורי קוד קטנים.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/12.0.0.txt",
    "content": "- יישום ראשוני של אודיו דרך אוזניות Bluetooth.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/12.1.0.txt",
    "content": "- אפשור מספר פורט בכתובת הרשומה של החשבון (AoR).\n- תוקנה בדיקה אם החשבון כבר קיים.\n- עודכנו המיתוגים בנורווגית ובולגרית.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/12.2.0.txt",
    "content": "- השתמש בפונקציות API חדשות 'net_set_address' ו־'net_set_af' כדי ליישם\n  את פונקציית ההעדפה ל־IPv6.\n- הוסר פריט \"Bind Address\" מהגדרות (כיוון שלא פעל כפי שצפוי).\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/12.2.1.txt",
    "content": "- שיפור ותיקון טיפול בשינויים דינמיים ברשת.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/13.0.0.txt",
    "content": "- הפעלת צליל התראה גם כאשר הודעה מגיעה ו־baresip נמצא במצב גלוי.\n- אם שם איש הקשר ריק, השתמש ב־SIP URI AoR של איש הקשר כשם.\n- שיפור בבדיקת שם איש הקשר.\n- מניעת קריסה אקראית בעת בחירת חשבון ברשימת הספינר.\n- בקשת הרשאת הקלטת אודיו (מיקרופון) כאשר baresip מתחיל (אם לא\n  אושרה או הושללה לצמיתות).\n- שיפור בטיפול בהרשאות קריאה/כתיבה לאחסון חיצוני\n  בעת גיבוי/שחזור.\n- הרשאת קריאת מצב הטלפון (Phone) איננה נדרשת.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/13.0.1.txt",
    "content": "- תמיד להודיע לספינר של סוכן המשתמש על שינוי אפשרי בסט הנתונים בעת\n  חזרה לפעילות הראשית ללא כוונה.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/14.0.0.txt",
    "content": "- נוספו אווטארים מבוססי תווים לאנשי קשר, צ'אטים והיסטוריית שיחות.\n- תוקנה שמירה/שחזור של אנשי קשר עם שמות לא ASCII.\n- שיפור בעיצוב התאריכים והשעות.\n- נוספה הערת הגבלה ב\"אודות\" לגבי ממשקי רשת מרובים.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/15.0.0.txt",
    "content": "- נוספה תמיכה בתמונות כאווטארים של אנשי קשר, כחלופה לאווטארים מבוססי תווים.\n- שיפורים ותיקונים קלים בממשק המשתמש.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/15.1.0.txt",
    "content": "- נוספה אפשרות להגדיר פרוטוקול תעבורה עבור ה־AoR של החשבון.\n- אל תציג את יציאת (port) החשבון או את פרוטוקול התעבורה, למעט במסך פעילות החשבון.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/15.1.1.txt",
    "content": "- תיקונים ושיפורים קטנים במיתוגים.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/16.0.0.txt",
    "content": "- הוחלפה ההגדרה הגלובלית \"העדף IPv6\" בהגדרה ייעודית לחשבון \"העדף מדיית IPv6\".\n- שיפור הטיפול בשינויים בקישוריות הרשת.\n- לא נדרש ציון פורט ב־URI יוצא של IPv6.\n- נטישת מיקוד השמע מתבצע גם בעת יציאה מהיישומון, אם לא בוצע קודם לכן.\n- נוספה תמיכה ראשונית בשפה הרוסית.\n- עודכנו מספר מחרוזות בנורווגית.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/16.0.1.txt",
    "content": "- רישום מחדש של סוכני משתמש (UA) מתבצע כאשר רשת פעילה חדשה זמינה או\n  כאשר מאפייני הקישור משתנים, גם אם כתובות ה־IP נשארות זהות.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/16.1.0.txt",
    "content": "- אין לבצע רישום מחדש של חשבונות כאשר מאפייני הקישור משתנים אך כתובות ה־IP\n  נשארות זהות.\n- אם baresip אינו מופעל, אפשר למשתמש לשנות את ההגדרות שייתכן\n  וגרמו לבעיה.\n- עדכונים ותיקונים למחרוזות.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/17.0.0.txt",
    "content": "- תוקנה שמירת חשבון שיש בפרמטר ה־AoR שלו ;transport.\n- תוקנה הפעלת ההתראות כאשר שיחה נסגרת.\n- שימוש באייקון סטטוס לבן כאשר רישום החשבון אינו מופעל.\n- עוצמת השיחה המוגדרת כברירת מחדל משפיעה כעת גם על שיחה קולית וגם על מוזיקה.\n- שופר העיצוב והמימוש של מסך החשבונות.\n- אין להציג הודעות ניפוי באגים של ספריית baresip כאשר האפשרות 'ניפוי באגים' אינה מופעלת.\n- אין ליצור מחדש פעילויות כאשר כיוון המסך משתנה.\n- הצגה תקינה של פרטי צ’אט שאינם נכנסים לשורה אחת\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/17.1.0.txt",
    "content": "- שימוש בסוג שימוש (usage type) במקום בסוג זרם (stream type) בעת בקשת מיקוד שמע.\n- תוקן העיצוב של טקסט העזרה לחשבון באנגלית.\n- אין להציג את הפורט האפשרי של ה־AoR בעת בקשה למחיקת ה־AoR.\n- אייקון מצב חשבון לא רשום נצבע בלבן.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/17.2.0.txt",
    "content": "- שם המשתמש לאימות מוגדר כעת כברירת מחדל לשם המשתמש של החשבון.\n- בעת הפעלת baresip, אם צוין שם משתמש לאימות\n  אך לא סיסמה, תתבצע בקשה להזנת סיסמת האימות.\n- אתחול נקודת מצב החשבון לצהוב כאשר האפשרות 'הרשמה' מסומנת\n  בפעילות החשבון.\n- תוקן באג הקשור ליצירת החשבון הראשון בגרסאות אנדרואיד ישנות יותר.\n- שיפורים קלים במחרוזות.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/17.2.1.txt",
    "content": "- תוקן באג בשמירת חשבונות במערכת הקבצים.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/17.2.2.txt",
    "content": "- אין לבצע הרשמה (subscribe) לחיווי המתנה להודעות בעת יצירת חשבון\n  כשכתובת ה־URI של התא הקולי אינה מוגדרת.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/17.2.3.txt",
    "content": "- מניעת קריסות הנגרמות מלחיצה כפולה על אייקונים\n  ופריטי רשימה שונים.\n- שינוי צבע אייקון ההתראה של החשבון ללבן כאשר החשבון\n  אינו רשום.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/17.3.0.txt",
    "content": "- נוספה תיבת סימון \"הצג סיסמה\" למסך הזנת סיסמת האימות.\n- בקשת הרשאת מיקרופון מתבצעת כפעולה הראשונה עם הפעלת baresip.\n- אין לאפשר שימוש בתו \" בסיסמת האימות של החשבון.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/17.4.0.txt",
    "content": "- נוספה תמיכה באנדרואיד 10 (API רמה 29).\n- שיפור תהליך בקשת הרשאת המיקרופון בעת הפעלת baresip בפעם הראשונה.\n- מניעת קריסה בלחיצה על כפתור השיחה כשאין חשבונות מוגדרים.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/18.0.0.txt",
    "content": "- בשל מגבלות אנדרואיד 10, אין אפשרות עוד להפעיל את baresip\n  אוטומטית לאחר אתחול המכשיר ללא הצגת התראה.\n- תוקנה הפעלה מחדש של baresip באנדרואיד 10.\n- הוסרה ההגדרה של ICE Lite Mode עקב הסרתה במקור (upstream).\n- נוספו מחרוזות בפורטוגזית ברזילאית מ־Weblate.\n- תיקונים קטנים במחרוזות בספרדית ובנורווגית.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/18.0.1.txt",
    "content": "- הגדלה בצד ה־Upstream של המספר המרבי של קודקי האודיו בחשבון מ־8 ל־16.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/18.1.0.txt",
    "content": "- שופרה הגדרת מקודדי השמע של החשבון.\n- הצג תמיד שיחה נכנסת (אם קיימת) בעת חידוש הפעילות של baresip.\n- שופרו ותוקנו חזרה לפעילויות שאינן הפעילות הראשית.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/18.1.1.txt",
    "content": "- תוקנה קריסה כאשר מוציאים שיחה ממסך היסטוריית השיחות.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/18.1.2.txt",
    "content": "- תוקנה קריסה בעת חזרה לפעילות הקודמת לאחר קבלת\n  שיחה או הודעה.\n- שיפור הטיפול בחיישן הקרבה וברמקול.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/18.1.3.txt",
    "content": "- פישוט השליטה בחיישן הקרבה.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/18.2.0.txt",
    "content": "- הגדרת מקודדי השמע של החשבון הופרדה למסך חדש.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/18.2.1.txt",
    "content": "- תוקנו 2 קריסות הקשורות לצ'אט.\n- שיפורי קוד קטנים.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/18.3.0.txt",
    "content": "- שיפורי דיאלוגים.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/19.0.0.txt",
    "content": "- נוספה אפשרות medianat בחשבון TURN.\n- נוספה אפשרות להגדיר שם משתמש וסיסמה לשרת STUN/TURN.\n- מחרוזות הקשורות ל-STUN/TURN בחלק מהשפות עדיין לא עודכנו.\n- שיפורים נוספים בתיבות דו־שיח.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/19.1.0.txt",
    "content": "- שיפורים בתיבת הדו־שיח של ההתראה.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/20.0.0.txt",
    "content": "- שיפורים בביטול הד אקוסטי.\n- הגדרות השמע הועברו מתפריט ההגדרות לפעילות חדשה.\n- נוספה הגדרת AEC Extended Filter.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/20.0.1.txt",
    "content": "- תוקנה חזרה לפעילות השמע לאחר השהיה.\n- תוקנה הוספת מודולי שמע בהגדרות שמע.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/20.0.2.txt",
    "content": "- תיקונים ושיפורים הקשורים ללחצן לוח המקשים.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/20.1.0.txt",
    "content": "- נוספו מחרוזות בשפה הרוסית (RU) שתרמו משתמשים.\n- תיקון במקור (Upstream) לבעיה שעלולה הייתה לגרום לקריסה.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/21.0.0.txt",
    "content": "- נוספה תמיכה ראשונית בהעברת שיחות שמקורן ב־Baresip.\n- נוספה שפה חדשה פורטוגזית מתוך Weblate.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/21.1.0.txt",
    "content": "- נוספה אפשרות לרישום מחדש של חשבון נבחר באמצעות מחוות החלקה כלפי מטה.\n- מיקוד אוטומטי והצגת המקלדת הווירטואלית כאשר מתבקשת סיסמה\nאו יעד להעברה.\n- תיקונים שונים הקשורים למחרוזות.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/21.2.0.txt",
    "content": "- נוסף מקודד הדיבור AMR-WB (Adaptive Multi-Rate Wideband).\n- נוסף סעיף רישיונות לטקסט \"אודות\".\n- נוספו תרגומים חדשים לפורטוגזית (ברזיל).\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/22.0.0.txt",
    "content": "- נוספה תמיכת WebSocket עבור SIP (RFC 7118).\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/22.1.0.txt",
    "content": "- תרגומים חדשים בפורטוגזית (ברזיל) וברוסית.\n- עדכון האייקונים לאחר חזרה לאפליקציה.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/23.0.0.txt",
    "content": "- נוספה אפשרות להסתיר/להציג סיסמאות אימות בחשבון ו־STUN/TURN\n- העדפת ממשק VPN (אם קיים) בבחירה מתוך ממשקי הרשת הזמינים\n- נוספה הערה בטקסט \"אודות\" לגבי סדר העדיפויות של הממשקים\n- שיפור הגדרת Media NAT Traversal בחשבון\n- שימוש בשרת STUN של Google כברירת מחדל גם כאשר פרוטוקול Media NAT Traversal הוא ICE\n- הגדלת האורך המקסימלי של שמות משתמש אימות החשבון ו־STUN/TURN ושם התצוגה ל־64 תווים\n- נוספו/עודכנו תרגומים בשפות צרפתית (FR), נורווגית (NO) ופינית (FI)\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/23.1.0.txt",
    "content": "- נוסף קודק אודיו G.729.\n- נוספה הגדרה להפעלת/כיבוי מעקב אחרי הודעות SIP ל־logcat.\n- בשרתי STUN/TURN סכמות URI של 'stuns' ו־'turns' אינן נתמכות כרגע.\n- נוספו תרגומים חדשים בפורטוגזית (ברזיל).\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/23.2.0.txt",
    "content": "- זיהוי משופר של שינויים בחיבור VPN\n* תרגומים חדשים בפורטוגזית (ברזיל).\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/24.0.0.txt",
    "content": "- השתמש בחוצץ ריצוד אדפטיבי\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/24.1.0.txt",
    "content": "- שיפור התנהגות מקשי הגברת/הנמכת עוצמת הקול\n- שיפור התנהגות הרמקול\n- תרגומים חדשים לרוסית (RU)\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/24.2.0.txt",
    "content": "- הצגת מקלדת DTMF אוטומטית כאשר השיחה מחוברת\n- מניעת קריסה שנגרמת מלחיצה על מקשי עוצמת קול כאשר אין שיחה פעילה\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/24.3.0.txt",
    "content": "- הצג לוח המקשים באופן אוטומטי רק כאשר כיוון המכשיר הוא לאורך\n- אל תשמיע צליל שיחה ממתינה כאשר נכנסת שיחה שנייה\n- שיפור טקסט \"אודות\"\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/24.4.0.txt",
    "content": "- אל תגדיר כברירת מחדל את שם המשתמש/סיסמה של שרת STUN/TURN לפי שם המשתמש/סיסמה של האימות\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/25.0.0.txt",
    "content": "- שיפור התראת שיחה נכנסת.\n- הוספת התראת שיחה שלא נענתה.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/26.0.0.txt",
    "content": "- נוספה ערכת נושא כהה והגדרות ערכת נושא כהה.\n- שיפורים ברשימת הבחירה של חשבונות (Account spinner).\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/26.0.1.txt",
    "content": "- תוקנה הפעלת מצב כהה בעת הפעלת אפליקציית Baresip\n* תרגומים חדשים בפורטוגזית (ברזיל)\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/26.1.0.txt",
    "content": "- אפליקציות אחרות יכולות כעת לזהות את Baresip כאפליקציית טלפון עבור sip: ו־tel: מסוג URI\n- שיפור שמירה/שחזור של טקסט URI של שיחה כאשר הפעילות הראשית במצב הפסקה\n- נוסף תרגום ליפנית\n- שיפור בצבעים\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/26.1.1.txt",
    "content": "- תוקן הטיפול בפעולת שיחה כאשר אפליקציית Baresip אינה פעילה.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/26.1.2.txt",
    "content": "- נסה לזהות כיוון סיבוב של תמונות אווטאר אנשי הקשר\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/27.0.0.txt",
    "content": "- נוספה אפשרות לייצא את אנשי הקשר של baresip לאנשי הקשר של אנדרואיד\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/27.0.1.txt",
    "content": "- תיקון קריסה בהפעלת baresip בפעם הראשונה\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/28.0.0.txt",
    "content": "- נוספה אפשרות להגדרה אוטומטית חלקית של חשבון חדש דרך דף אינטרנט (ראו https://github.com/juha-h/baresip-studio/wiki/Automatic-Account-Configuration for details)\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/28.1.0.txt",
    "content": "- שיפור במציאת איש קשר שתואם ל־SIP URI\n- שיפורי סרגל כלים ותפריטים\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/28.1.1.txt",
    "content": "- תוקנה צביעת הגדרות מודולי שמע\n- תוקנה קריסה הקשורה לרשימת השיחות ונראות\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/28.2.0.txt",
    "content": "- נוספה הגדרת 'ודא תעודות שרת'\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/29.0.0.txt",
    "content": "- הפעלת מחוות החלקה ימינה/שמאלה להחלפה בין חשבונות\n- הוספת הגדרת חשבון של מצב DTMF\n- עדכוני תרגום מ־Weblate (צרפתית)\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/29.1.0.txt",
    "content": "- תרגומים מ־Weblate שלפורטוגזית (ברזיל)\n- תוקן F-Droid nb-NO locale tag\n- מניעת התראות מסוג \"duplicate finish request\"\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/29.2.0.txt",
    "content": "- תיקון תקלות הקשורות לצ'אט, היסטוריית שיחות ואנשי קשר\n- תוקנו תקלות בהשלמת URI ושופרו הבדיקות הקשורות ל־URI.\n- תרגומים מ־Weblate (צרפתית, פורטוגזית (ברזיל))\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/29.2.1.txt",
    "content": "- הימנעות מקריסה אפשרית הקשורה לאנשי קשר בהפעלת baresip\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/3.2.0.txt",
    "content": "- נוסף לחצן בלוח חיוג לבחירה בין מספר טלפון למקלדת טקסט.\n- תוקנה השלמה אוטומטית של הנמען בהתבסס על אנשי קשר.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/3.2.2.txt",
    "content": "- נוסף טיפול באירוע כשל בהעברת שיחה.\n- מניעת קריסה בעת הפעלה או הפעלה מחדש של המכשיר.\n- מניעת קריסה כאשר חוזרים לראשונה מחשבונות לפעילות הראשית.\n- שימוש בסוג קלט \"textEmailAddress\" עבור URI של חשבונות.\n- שינוי חשיבות ערוץ ההתראות המוגדר כברירת מחדל לנמוך.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/30.0.0.txt",
    "content": "- שודרגה גרסת ה־SDK המיועדת ל־API level 30\n- באנדרואיד גרסאות 10 ומעלה, אפשר למשתמש לבחור קובץ\n  בגיבוי, שחזור או פעולות הקשורות לתעודת TLS\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/30.1.0.txt",
    "content": "- שיפורים בתיבת הדו־שיח של הסיסמה ובהזנת סיסמת החשבון.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/30.2.0.txt",
    "content": "- נוספה תמיכה בקודק AMR במצב Bandwidth Efficient\n- אפשרות להשתמש בתווים מנותבים (escaped) בחלק המשתמש של URI ב־SIP ובשם המשתמש לאימות\n- שימוש ב־coroutine במקום ב־async task לקבלת הגדרות חשבון מהרשת\n- באנדרואיד 10 ומעלה, שימוש בבורר (picker) לבחירת תעודת TLS וקבצי TLS CA\n- בקשת אישור לאיפוס ההגדרות לברירת המחדל של היצרן\n- תרגומים חדשים בפורטוגזית ובפורטוגזית (ברזיל)\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/30.3.0.txt",
    "content": "- השמע צליל מענה אוטומטי כאשר השיחה נענית אוטומטית\n- הסרת קודק iLBC (הוסר מקוד המקור)\n- הוסרו קובצי קול שאינם בשימוש\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/37.4.0.txt",
    "content": "- תרגומים חדשים של פורטוגזית וגרמנית\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/53.2.2.txt",
    "content": "- תוקנו מספר בעיות הקשורות למיקוד שמע/ניתוב שמע/בקרת עוצמת קול\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/changelogs/8.5.0.txt",
    "content": "- שימוש ראשוני בחיישן הקרבה לכיבוי מסך המגע במהלך שיחה.\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/full_description.txt",
    "content": "זוהי אפליקציית סוכן משתמש SIP לאנדרואיד המבוססת על <a href=\"https://github.com/baresip/baresip\">baresip</a>.\n\nנכון לעכשיו, אפליקציית baresip תומכת בשיחות קוליות ושיחות ועידה, הודעות טקסט, תא קולי עם חיווי להמתנת הודעות (Message Waiting Indication), וכן בהעברות שיחה עיוורות ומלוות. הקול יכול להיות מקודד באמצעות הקודקים Opus, ‏AMR, ‏Codec2, ‏G.729, ‏G.722, ‏G.722.1, ‏G.726 או ‏PCMU/PCMA. האבטחה מושגת באמצעות תעבורת איתות SIP מעל TLS או WSS, והצפנת מדיה באמצעות ZRTP או (DTLS) SRTP.\n\nפיתוח אפליקציית baresip מונע מהצורך בסוכן משתמש VoIP מבוסס SIP, מאובטח ובקוד פתוח לאנדרואיד, שאינו תלוי בשירותי התראות דחיפה קנייניים של צד שלישי.\n\nאם אתם זקוקים לשיחות וידאו, תוכלו להתקין במקום אפליקציה זו את אפליקציית האחות שלה, baresip+.\n\nקוד המקור זמין ב־<a href=\"https://github.com/juha-h/baresip-studio\">GitHub</a>, שם ניתן גם לדווח על תקלות (issues).\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/short_description.txt",
    "content": "אפליקציית סוכן משתמש VoIP לאנדרואיד המבוססת על ספריית ה־SIP של baresip\n"
  },
  {
    "path": "fastlane/metadata/android/iw-IL/title.txt",
    "content": "baresip\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/10.2.0.txt",
    "content": "- Tillagt støtte for G.726-kodek.\n- Forbedret prioritetsrang for forvalgt kodek.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/12.0.0.txt",
    "content": "- Første implementasjon av lyd gjennom Blåtannshodesett.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/12.2.1.txt",
    "content": "- Forbedret og fikset håndtering av dynamiske nettverksendringer.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/3.2.0.txt",
    "content": "- Tillagt nummerskiveknapp for valg mellom telefonnummer og tekst for skjermtastatur.\n- Fikset autofullføring av ringer basert på kontakter.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/3.2.2.txt",
    "content": "- Håntering av anropsvideresending som mislykkes lagt til.\n- Unngått krasj ved (om)start av enhet.\n- Unngått krasj ved første gangs navigasjon fra kontoer til hovedaktivitet.\n- Brukt \"textEmailAddress\"-inndatatype for konto-URI-er.\n- Endret vektlegging av forvalgt merknadskanal til \"lav\".\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/36.0.0.txt",
    "content": "- Flyttet til API-nivå 31 (Android 12)\n- Forbedret oppdagelse av tilgjengelig nettverk\n- Forbedrede tilgangsforespørsler\n- Fjernet kontoens «Foretrekk IPv6-media»-innstilling\n- Ny Portugisisk (Brasil)-oversettelse\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/36.1.2.txt",
    "content": "- Forhindret dragnigsrelatert krasj\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/4.0.0.txt",
    "content": "- Forbedret implementasjon av kontakter, melding og anropshistorikk.\n- Unngå krasj når meldinger ankommer før kontoer har blitt lagt til.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/4.1.0.txt",
    "content": "- All urelatert data fra brukergrensesnitt beholdes nå i baresip-tjeneste,\nnoe som tillater riktig gjenoppretting i fall Android dreper andre baresp-aktiviteter.\n- Mange andre forbedringer og feilrettinger.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/4.1.1.txt",
    "content": "- Fikset kritisk feil i sjekking av lydopptakstilgang som\nforhindret ny baresip-installasjon fra å starte.\n- Små forbedringer.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/4.1.2.txt",
    "content": "- Fikset krasj når tom kontospinner klikkes.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/5.0.0.txt",
    "content": "- Erstattet tekstmodus-EditConfigActivity med grafisk oppsettsaktivitet.\n- Nåværende oppsettselementer er autostart, DNS-tjenere, Opus-bitrate,\nog ICE-Lite.\n- Tillagt ICE og STUN-tjenerkontovalg.\n- Fikset feil i kontosletting.\n- Usendte meldinger går ikke tapt når sludreaktivitet forstyrres.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/5.1.0.txt",
    "content": "- Tillagt mulighet for eksport/import av kontakter til/fra \"Nedlastinger\"-mappe.\nHvis du ønsker å bruke denne funksjonen, innvilg baresip lagringstilgang\ni Innstillinger → Apper → baresip → Tilganger.\n- Fikset treg avslutning av baresip når nettverkstilkobling mangler.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/5.2.0.txt",
    "content": "- Gi bruker merknad om mislykket registrering.\n- Tillagt samtaleinfoknapp for aktiv samtale.\n- Ikke krev \"sip:\"-del i utgående mellomtjener-URI.\n- Tillat domeneetikett å starte med et siffer.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/5.2.1.txt",
    "content": "- Mindre strikt sjekking av visningsnavn og identitetsbekreftelses-brukernavn\n- Tillagt versjonsnavn i Om-side\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/5.3.0.txt",
    "content": "- Tillagt støtte for merknadsoppsprett som forteller hvorfor en samtale ble lukket.\n- Fikset visning av ringer-URI hvis det inneholder portnummer eller parameter.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/5.3.1.txt",
    "content": "- Mindre strikt sjekking av brukerdel i kontoens SIP-URI.\n- Tillagt støtte for ARMv8-A-arkitektur.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/5.4.0.txt",
    "content": "- Tillagt SRTP-MANDF-mediakrypteringsprotokoll.\n- Sikkerhetsknappefarge endres fra rød til gul også når samtalen er\nsikret av en SRTP*-mediakrypteringsprotokoll.\n- Fikset innstilling av kontomediakryptering til DTLS-SRTPF.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/5.4.1.txt",
    "content": "- Fjernet ubrukt baresip-moduler fra oppsett og bilbiotek.\n- Nå lar baresip-tjenesten baresip-aktivitet dø på egenhånd etter\nå ha mottatt drapsforespørsel fra den.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/5.4.2.txt",
    "content": "- Fikset sjeldent krasj som kan inntreffe når første konto opprettes og\nmelding eller anrop kommer inn før brukeren har gått tilbake til hovedaktivitet.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/6.0.0.txt",
    "content": "- Flere brukergrensesnittforbedringer (for det mestre sludringsrelatert).\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/6.0.1.txt",
    "content": "- Gjenoppta baresip-program til aktiviteten som ble stoppet.\n- Tillagt eksempelkontakt når baresip startes for første gang.\n- Fikset visning av ny melding i sludre- og meldingslisten.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/6.0.2.txt",
    "content": "- Fikset krasj når STUN-tjener er definert uten portnummer.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/6.1.0.txt",
    "content": "- Gå til sludrevindu når meldingsmerknad klikkes.\n- Fikset feil i anropsoverføring.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/6.2.0.txt",
    "content": "- Oppgadert mål for Android API-nivå til 28 (Android 9).\n- Flyttet noe funksjonalitet som ikke har med grensesnitt å gjøre fra hovedaktivitet til baresip-tjeneste.\n- Oppdatert baresip-oppstarter og statusfeltbilder.\n- Avregistrert UA når konto slettes.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/6.2.1.txt",
    "content": "- ConnectivityManager NetworkCallbacks brukt til å fastsette når nettverks-\ntilgang er tilgjengelig eller tapes.\n- Bruk av Android 8.1+måten å håndtere skjerm.\n- Lydstyrkekontroller satt til å håndtere nåværende lydstrøm (kan fuske).\n- Fikset mulig krasj når ødelagt baresip-program startes igjen.\n- Notis vist når kontoen som skal opprettes allerede finnes.\n- Tillagt notis i \"Om\"-tekst om medialydstyrke.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/6.3.0.txt",
    "content": "- Tillagt avlusingsoppsettsvalg for å skru adb-logcat-avlusing av og på.\n- Tillagt mulighet for å eksportere og importere kontoer til/fra fil i nedlastingsmappe.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/6.3.1.txt",
    "content": "- Fikset og forbedret implementasjon av DTMF-tonesignalering.\n- Avskrudd redigering av innkommende/utgående anrops-URI når samtale pågår.\n- Slettet brukeragent når konto slettes.\n- Fjernet ubrukt DREP_BAKGRUNNSPROSESSER-tilgang.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/6.4.0.txt",
    "content": "- Tillagt mulighet for å sette konto som forvalg.\n- Vis rødt meldingsikon når det finnes uleste meldinger.\n- Spør PIN om å slippe nøkkelvakt når oppringer-URI klikkes.\n- Fikset anrop, melding og anropsvideresendingshandlinger når igangsatt fra merknader.\n- Fikset forsendelse av melding fra kontakter eller samtalehistorikk.\n- Bruk av SingleTask istedenfor SingleTop som oppstartsmodus for hovedaktivitet.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/7.0.0.txt",
    "content": "- Tillagt støtte for IPv6 (ikke testet som følge av mangel på IPv6-nettverk).\n- Tillagt \"Foretrekk IPv6\"-oppsettsvalg.\n- Tillagt \"Tilbakestill til fabrikkforvalg\"-oppsettsvalg.\n- Ved avslutning, forsikre at baresip-oppgaven stoppes.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/7.1.0.txt",
    "content": "- \"Forvalgt samtalelydstyrke\" lagt til som oppsettsvalg.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/7.1.1.txt",
    "content": "- Oppdater kontospinner også når hovedaktivitet gjenopptas uten handling.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/7.1.2.txt",
    "content": "- Forsøksvis oppdatering av kontospinnervisning når kontospinneren\noppdateres.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/7.2.0.txt",
    "content": "- Tillagt variabel, lytteadresse som tillater spesifisering av IP-adresse\n(eller alle adresser) og en port som baresip lytter til for SIP-forespørsler.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/7.3.0.txt",
    "content": "- Mulighet for å velge STUN, ICE eller ingen NAT-protokoll.\n- Kontostatus satt til gul umiddelbart når kontoregistrering er av.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/7.4.0.txt",
    "content": "- Varslinger nå i henhold til designretningslinjer for materielt design.\n- Forbedret språkstrengshåndtering.\n- Finsk lagt til som språkeksempel.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/8.0.0.txt",
    "content": "- Støtte for G.722.1-lydkodek lagt til.\n- Støtte for WebRTC-basert lydekkokansellering (ikke for Opus-kodek).\n- Mindre samtalemerknadsinnstillinger.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/8.0.1.txt",
    "content": "- Fikset gjenoppretting av lydstyrke til opprinnelig nivå hvis forvalg lydstyrke brukes.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/8.0.2.txt",
    "content": "- Fikset opprettelse av ny konto.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/8.1.0.txt",
    "content": "- Innhenting av DNS-tjeneradresser dynamisk fra systemet hvis DNS-tjenere\nikke oppgis i oppsettet.\n- Hvis det er kun én konto, legg til kontoens domene i kontakt-URI\nuten gitt vertsdel.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/8.2.0.txt",
    "content": "- Forbedret innhold og utseende for \"Om\"-siden.\n- Alltid vis aktivt anrop (hvis noe), når programmet startes på ny.\n- Endret plassering for varselsdialog for OK-knapp.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/8.3.0.txt",
    "content": "- Internett lav bitratekodek (iLBC) lagt til.\n- Forbedret kontokodekutvelgelse.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/8.3.1.txt",
    "content": "- Ikke sett lyd til kommunikasjonsmodus når anrop opprettes,\nfordi på noen enheter legger det til 3-4 sekunders forsinkelse for\navspilling av lyd.\n- Akustisk ekkokansellering nå justerbar.\n- Skru på samtaleinfoknapp når den blir synlig.\n- Bruk forvalgt enhetsdrakt ved visning av spinnere.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/8.3.2.txt",
    "content": "- Hvis kontoen åpnes fra AoR-spinneren, gå direkte til hovedaktivitet.\n- Bruk HtmlCompat til konvertering av Om-HTML til tekst (burde virke på\nalle API-nivåer).\n- Bruk ringetone-gjentagelsesmulighet på Android P+.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/8.3.3.txt",
    "content": "- DNS-tjenere hentet dynamisk, også når baresip startes for første gang.\n- Mulig krasj unngått under henting av kontoens lydkodeker.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/8.3.5.txt",
    "content": "- Omkrans kontakt-URI-er i < og > når kontakter lagres til fil.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/8.3.6.txt",
    "content": "- Tillat endring av kontaktnavn også når det kun er forskjell på små og store bokstaver.\n- Ved gjenoppretting av kontakter, ikke krev at URI-er omkranses av vinkelparenteser.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/8.4.0.txt",
    "content": "- Ekkokansellering virker nå for samtaler som bruker Opus-kodeket.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/8.4.1.txt",
    "content": "- Forbedring av ekko-kansellering oppstrøms.\n- Nye oversettelser: el, nb_NO, ro.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/8.4.2.txt",
    "content": "- Alltid oppdater DNS-tjenere og registrer UA-er når nettverket blir tilgjengelig.\n- Fikset og oppdaterte noen tekster.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/8.4.3.txt",
    "content": "- Fikset visning av kontostatus, når forvalgt konto endres.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/8.4.4.txt",
    "content": "- Oppstrømsfiks i implementasjon av Opus-kodek.\n- Fiks for kontroll av lydstyrkestrøm.\n- Fiks for å legge på røret automatisk ved innkommende anrop.\n- Mindre strengforbedringer.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/8.5.0.txt",
    "content": "- Foreløpig bruk av nærhetsføler for å skru av skjer under samtale.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/8.5.1.txt",
    "content": "- Flyttet håndtering av nærhetsføling fra MainActivity til BaresipService.\n- Fikset krasj ved anrop eller innkommende melding når MainActivity ikke kjører.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/8.5.2.txt",
    "content": "- Fikset sjekking av DNS-tjeneroppsett.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/8.6.0.txt",
    "content": "- Tillagt støtte for SIP-registeringstjenester som ikke håndterer gjenbruk av engangsord rett.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/9.0.0.txt",
    "content": "- Tillagt mulighet for å sette opp baresip med TLS- og CA-sertifikater.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/9.1.0.txt",
    "content": "- Automatisk avslag av innkommende samtale hvis telefontilstand ikke er ledig.\n- Forstum ringetone for innkommende samtale hvis enheten er i \"Ikke forstyrr\"-modus.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/9.2.0.txt",
    "content": "- Tillagt støtte for Opus framtidsfeilkorrigering (FEC) via Opus\n  sin oppsettsvariabel for forventet pakketap.\n- Forsøksvis oppdatering av dynamisk DNS-info hvis registrering mislykkes\n  som følge av \"ugyldig argument\"-feil.\n- Bruk av oversettbar streng i alle oppsettsrelaterte varselsmeldinger.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/changelogs/9.3.0.txt",
    "content": "- Svarsmodus (manuell eller automatisk) -kontooppsettsvalg lagt til.\n- Strenger for norsk bokmål lagt til, sammen med endringslogger fra Weblate.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/full_description.txt",
    "content": "baresip VoIP-brukeragent støtter nå SIP-stemmeanrop, og meldinger,\ntelefonsvar-meldingsventingsindikasjon (MWI), og innkommende androps-\nvideresendinger via REFERENT-metode. Stemme kan kodes via PCMU/PCMA, Opus,\nG.722.1, ok iLBC -kodek. Sikkerhets oppnås via TLS-signaleringstransport,\nZRTP og (DTLS) SRTP mediainnkapsling.\n\nUtvikling av baresip-programmet er motivert av behovet for en sikker og\nfri SIP-brukeragent for Android, som ikke avhenger av proprietære\ndyttingsmerknadstjenester fra tredjeparter.\n\nKildekode tilgjengelig fra https://github.com/juha-h/baresip-studio,\nder problemer kan innrapporteres.\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/short_description.txt",
    "content": "VoIP-brukeragentprogram for Android, basert på SIP-biblioteket baresip\n"
  },
  {
    "path": "fastlane/metadata/android/nb-NO/title.txt",
    "content": "baresip\n"
  },
  {
    "path": "fastlane/metadata/android/pl/changelogs/15.1.1.txt",
    "content": "- Drobne poprawki i ulepszenia łańcucha.\n"
  },
  {
    "path": "fastlane/metadata/android/ro/full_description.txt",
    "content": "Clientul VoIP baresip suportă apeluri vocale și mesaje SIP, notificări căsuță\nvocală în așteptare (Message Waiting Indication - MWI) și transferuri apeluri\nprimite prin metoda REFER.  Vocea poate fi transmisă utilizând codecurile\nPCMU/PCMA, opus, G.722.1 și iLBC.  Securizarea conexiunii este realizată\nfolosind transportul TLS precum și încapsularea media ZRTP și (DTLS) SRTP.\n\nDezvoltarea aplicației baresip este motivată de nevoia de a avea un client\nAndroid SIP securizat, cu sursă deschisă ce nu depinde de servicii terțe\nproprietare de notificare.\n\nCodul sursă este disponibil la https://github.com/juha-h/baresip-studio unde\nse și pot raporta eventualele probleme.\n"
  },
  {
    "path": "fastlane/metadata/android/ro/short_description.txt",
    "content": "Client VoIP pentru Android bazat pe biblioteca SIP baresip\n"
  },
  {
    "path": "fastlane/metadata/android/ro/title.txt",
    "content": "baresip\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/10.0.0.txt",
    "content": "- Возобновлять baresip на приостановленном действии вместо основного.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/10.1.0.txt",
    "content": "- Добавлена поддержка кодека G722.\n- Исправлено сохранение учётных записей.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/10.2.0.txt",
    "content": "- Добавлена поддержка кодека G.726.\n- Улучшен порядок кодеков по умолчанию.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/10.3.0.txt",
    "content": "- Добавлено меню История звонков, позволяющее удалять,\n  отключать и включать историю вызовов учётной записи.\n- Добавлен испанский перевод (спасибо, Javier Falbo).\n- Автозапуск baresip по умолчанию в новых установках.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/10.4.0.txt",
    "content": "- Разрешить удаление истории чатов учётной записи через меню действий с ними.\n- Запрашивать подтверждение перед удалением истории чатов и звонков.\n- Улучшено обновление значков голосовой почты, чатов и звонков.\n- Открытие раздела учётных записей при запуске baresip без учётных записей.\n- Кнопки приёма и отклонения вызова поменялись местами.\n- Не позволять завершать исходящий вызов до начала попытки его выполнения.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/11.0.0.txt",
    "content": "- Экспорт и импорт учётных записей и контактов заменйнён с резервным\n  копированием и восстановлением всех данных приложения.\n- Улучшена безопасность за счёт запрета Android-резервирования приложения\n  и установки приложения на внешнее хранилище.\n- При удалении учётной записи, удалять также её историю звонков и чатов.\n- Исправлено удаление сообщений и чатов.\n- Незначительные улучшения пользовательского интерфейса и текстов.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/11.1.0.txt",
    "content": "- Добавлена переменная конфигурации «Bind Address», которая позволяет выбирать,\n  какой сетевой интерфейс или IP-адрес использует baresip.\n- Всегда обновлять значок статуса учётных записей при возобновлении основной деятельности.\n- Устранён сбой при возобновлении уничтоженного ранее основного действия.\n- Правильно отображать активный входящий вызов при возобновлении основной деятельности.\n- Обновлены испанские тексты.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/11.2.0.txt",
    "content": "- Добавлена поддержка узкополосного кодека AMR.\n- Добавлены болгарские тексты.\n- Пункт Конфигурация главного меню переименован в Настройки.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/11.3.0.txt",
    "content": "- В Настройки добавлен выбор, какие аудиомодули загружать.\n  Для использования в учётных записях доступны аудиокодеки\n  загруженных модулей.\n- Незначительные улучшения кода.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/12.0.0.txt",
    "content": "- Начальная реализация передачи звука через Bluetooth-гарнитуру.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/12.1.0.txt",
    "content": "- Разрешить номер порта в Адресе записи учётки (AoR).\n- Исправлена проверка существования учётной записи.\n- Обновлены норвежские и болгарские тексты.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/12.2.0.txt",
    "content": "- Новые функции API 'net_set_address' и 'net_set_af'\n  использованы для реализации предпочтения IPv6.\n- Удалена настройка привязки к адресу (не работала должным образом).\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/12.2.1.txt",
    "content": "- Улучшена и исправлена обработка динамичных сетевых изменений.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/15.1.1.txt",
    "content": "- Незначительные исправления и улучшения текстов.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/17.2.1.txt",
    "content": "- Исправлена ошибка при сохранении учётных записей в файловую систему.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/18.0.1.txt",
    "content": "- Увеличение максимального количества аудиокодеков учётной записи с 8 до 16.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/18.1.1.txt",
    "content": "- Устранён сбой при звонке из Истории вызовов.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/18.1.3.txt",
    "content": "- Упрощённое управление датчиком приближения.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/18.2.0.txt",
    "content": "- Настройка аудиокодеков учётной записи выделена в новый экран.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/18.2.1.txt",
    "content": "— Устранены два сбоя, связанных с чатом.\n— Незначительные улучшения кода.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/18.3.0.txt",
    "content": "- Улучшения диалогов.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/19.1.0.txt",
    "content": "- Улучшения диалогов-предупреждений.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/20.0.2.txt",
    "content": "- Исправления и улучшения, связанные с кнопками панели набора номера.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/20.1.0.txt",
    "content": "- Обновлён русский перевод.\n- Устранён возможный сбой.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/22.0.0.txt",
    "content": "- Добавлен Websocket-транспорт для SIP (RFC 7118).\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/22.1.0.txt",
    "content": "- Новые переводы на бразильский португальский и русский.\n- Обновлять значки после возобновления работы приложения.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/23.2.0.txt",
    "content": "- Улучшенное обнаружение изменений VPN-подключения\n- Новые переводы на бразильский португальский\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/24.0.0.txt",
    "content": "- Использовать адаптивный джиттер-буфер\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/24.4.0.txt",
    "content": "- Не использовать по умолчанию логин/пароль сервера STUN/TURN в качестве логина/пароля для аутентификации\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/25.0.0.txt",
    "content": "- Улучшено уведомление о входящем вызове.\n- Добавлено уведомление о пропущенном вызове.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/26.0.0.txt",
    "content": "- Добавлены тёмная тема и настройка тёмной темы.\n- Улучшен спиннер учётных записей.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/26.1.1.txt",
    "content": "- Исправлена обработка вызова при незапущенном приложении baresip.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/26.1.2.txt",
    "content": "- Пытаться определять повёрнутость изображений аватаров контактов\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/27.0.0.txt",
    "content": "- Добавлена возможность экспорта контактов baresip в контакты Android\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/27.0.1.txt",
    "content": "- Устранён сбой при первом запуске baresip\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/28.0.0.txt",
    "content": "- Добавлена возможность частичной автоконфигурации новой учётной записи с веб-страницы (подробнее см. https://github.com/juha-h/baresip-studio/wiki/Automatic-Account-Configuration)\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/28.1.1.txt",
    "content": "- Исправлено окрашивание настроек аудиомодулей\n- Устранён сбой, связанный со списком вызовов и исправлен его внешний вид\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/28.2.0.txt",
    "content": "- Добавлен параметр «Проверка сертификатов сервера»\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/29.2.1.txt",
    "content": "- Устранение возможных сбоев, связанных с контактами, при запуске baresip\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/30.0.1.txt",
    "content": "- Исправление выбора учётной записи для входящего вызова.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/30.1.0.txt",
    "content": "- Улучшения диалогового окна ввода пароля и поля с паролем учётной записи.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/31.2.1.txt",
    "content": "- Устранён сбой при выборе учётной записи\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/32.1.0.txt",
    "content": "- Новые переводы на испанский и бразильский португальский\n- Незначительные улучшения, связанные с AudioManager'ом\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/32.2.0.txt",
    "content": "- Улучшена проверка параметров SIP URI\n- Больше исправлений и улучшений, связанных с аудиоменеджером и Bluetooth\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/34.1.0.txt",
    "content": "- Использовать фильтр мобильного режима акустического эхоподавления WebRTC\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/36.1.2.txt",
    "content": "- Устранён сбой, связанный со смахиванием\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/37.0.0.txt",
    "content": "- Реализация перевода вызова, совместимая с Basic Transfer из RFC 5589\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/37.0.1.txt",
    "content": "Исправлены настройки звука исходящего вызова\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/37.3.0.txt",
    "content": "- Параметр Громкость вызова по умолчанию перемещён в раздел Настройки звука\n- Новый язык (немецкий)\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/37.4.0.txt",
    "content": "- Новые переводы на португальский и немецкий\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/4.1.2.txt",
    "content": "- Устранён сбой при нажатии на спиннер отсутствия учётных записей.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/42.1.0.txt",
    "content": "- Тема AppCompat заменена на MaterialComponents\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/44.2.0.txt",
    "content": "- Устранён сбой при получении сообщения на устройствах ARMv7\n- Новые переводы на бразильский португальский\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/45.1.0.txt",
    "content": "- Использовать протокол установления ключа DTLS на основе криптографии на эллиптических кривых\n- Новые шведские переводы\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/45.1.2.txt",
    "content": "Исправления и улучшения, связанные с мелодией звонка и уведомлений\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/46.0.1.txt",
    "content": "- Не использовать кэш DNS, который может вызвать длительные задержки при разрешении адресов\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/49.1.0.txt",
    "content": "- Показывать уведомление с причиной прекращения входящего вызова библиотекой baresip\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/49.1.2.txt",
    "content": "- Исправлена ошибка обхода Media NAT (STUN/TURN/ICE)\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/49.1.3.txt",
    "content": "- Исправление согласования кодеков\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/49.4.0.txt",
    "content": "- Устранён сбой при сетевых изменениях\n- Новые переводы на бразильский португальский\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/5.3.1.txt",
    "content": "- Ослаблена проверка пользовательской части в SIP URI учётной записи.\n- Добавлена поддержка архитектуры ARMv8-A.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/50.1.4.txt",
    "content": "- Больше исправлений и улучшений, связанных с устройствами связи\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/50.1.5.txt",
    "content": "- Исправлено обнаружение интерфейса точки доступа Wi-Fi на некоторых устройствах\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/51.2.0.txt",
    "content": "- Несколько исправлений и улучшений, связанных с контактами\n- Новые переводы на бразильский португальский\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/53.0.0.txt",
    "content": "- Добавлена настройка для выбора семейства IP-адресов\n- Добавлен монохромный значок для лаунчера\n- Значок и текст убраны из уведомления о статусе для Android версии 13+\n- Новые переводы на испанский, русский, бразильский португальский и чешский\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/53.1.0.txt",
    "content": "- Улучшения значка для лаунчера\n- Новые переводы на русский\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/53.1.1.txt",
    "content": "- Улучшен значок для лаунчера\n- Не пытаться регистрировать новую учётную запись, если её регистрация отключена\n- Добавлена подсказка для Интервала регистрации\n- Новые переводы на русский\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/6.0.0.txt",
    "content": "- Несколько улучшений интерфейса (в основном связанных с чатом).\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/6.0.2.txt",
    "content": "- Устранён сбой при указании STUN-сервера без номера порта.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/7.1.0.txt",
    "content": "- Добавлен параметр «Громкость вызова по умолчанию».\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/7.1.1.txt",
    "content": "- Обновлять спиннер учётных записей и при возобновлении основной активности без каких-либо действий.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/8.0.1.txt",
    "content": "- Исправлено восстановление исходного уровня громкости, если используется громкость вызова по умолчанию.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/8.0.2.txt",
    "content": "- Исправлено создание учётной записи.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/8.2.0.txt",
    "content": "- Улучшено содержание и вид страницы О приложении.\n- Всегда отображать текущий активный вызов (если он есть) при перезапуске приложения.\n- Изменено расположение кнопки OK диалога предупреждений.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/8.3.0.txt",
    "content": "- Добавлен Internet Low Bitrate Codec (iLBC).\n- Улучшен выбор кодека для учётной записи.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/8.3.5.txt",
    "content": "- Заключать URI контактов в < и > только при сохранении контактов в файл.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/8.4.0.txt",
    "content": "- Эхоподавление теперь применяется и при звонках с использованием кодека Opus.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/8.4.1.txt",
    "content": "- Улучшение акустического эхоподавления.\n- Новые переводы: греческий, норвежский букмол, румынский.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/8.4.3.txt",
    "content": "- Исправлено отображение состояния учётной записи при смене учётной записи по умолчанию.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/8.5.0.txt",
    "content": "- Предварительное использование датчика приближения для отключения сенсорного экрана во время вызова.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/8.5.2.txt",
    "content": "- Исправлена проверка конфигурации DNS-серверов.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/8.6.0.txt",
    "content": "- Добавлена поддержка SIP-регистраторов, которые некорректно обрабатывают повторное использование параметра «nonce».\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/9.0.0.txt",
    "content": "- Добавлена возможность настраивать в baresip TLS-сертификат и корневые сертификаты.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/9.1.0.txt",
    "content": "- Автоотклонение входящего вызова, если телефон не в режиме ожидания.\n- Подавление мелодии входящего вызова, если устройство находится в режиме «Не беспокоить».\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/9.2.0.txt",
    "content": "- Добавлена поддержка Opus Forward Error Correction (FEC)\n  через переменную конфигурации Opus Expected Packet Loss.\n- Пытаться обновить информацию динамического DNS, если\n  регистрация не удалась из-за ошибки «Недопустимый аргумент».\n- Использовать переводимые строки во всех предупреждениях, связанных с конфигурацией.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/9.3.0.txt",
    "content": "— Добавлена настройка учётной записи Режим ответа (ручной или автоматический).\n— Добавлены переводы на норвежский букмол и журналы изменений из Weblate.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/changelogs/9.4.0.txt",
    "content": "- В главное меню добавлен пункт «Перезапуск».\n- Если регистрация не удалась из-за сбоя DNS,\n  пробовать ещё раз после обновления DNS-серверов.\n- Небольшие улучшения логирования.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/full_description.txt",
    "content": "Это приложение SIP-клиент для Android, основанный на <a href=\"https://github.com/baresip/baresip\">baresip</a>.\n\nВ настоящее время приложение baresip поддерживает голосовые вызовы, текстовые сообщения, уведомления голосовой почты, а также переводы вызовов «вслепую» и «с участием». Голос может кодироваться с помощью Opus, AMR, GSM, G.729, G.722, G.722.1, G.726 или PCMU/PCMA. Безопасность обеспечивается посредством TLS или WSS для SIP-сигнализации и инкапсуляции мультимедиа в ZRTP или (DTLS) SRTP.\n\nРазработка приложения baresip обусловлена необходимостью создания безопасного VoIP клиента на основе SIP с открытым исходным кодом для Android, который не зависит от закрытых сторонних сервисов пуш-уведомлений.\n\nЕсли вам нужны видеозвонки и имеется устройство с Android версии 9 или новее, поддерживающее API Camera2 на третьем или полном уровне аппаратной поддержки, можете вместо этого приложения установить родственное baresip+.\n\nИсходный код доступен на <a href=\"https://github.com/juha-h/baresip-studio\">GitHub</a>, там же можно сообщить о проблемах.\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/short_description.txt",
    "content": "VoIP-клиент для Android, основанный на одноимённой SIP-библиотеке\n"
  },
  {
    "path": "fastlane/metadata/android/ru-RU/title.txt",
    "content": "baresip\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/10.0.0.txt",
    "content": "- Återuppta baresip till pausad aktivitet istället för huvudaktivitet.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/10.1.0.txt",
    "content": "- Lagt till stöd för G722-codec.\n- Fixat sparande av konton.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/10.2.0.txt",
    "content": "- Stöd för G.726-codec har lagts till.\n- Förbättrad prioritetsordning för standardkodek.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/10.3.0.txt",
    "content": "- Lagt till menyn Samtalshistorik som gör det möjligt att radera, inaktivera och aktivera\n  av kontots samtalshistorik.\n- Spanska språksträngar tillagda (tack till Javier Falbo).\n- Automatisk start av baresip är nu standard i nya installationer.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/10.4.0.txt",
    "content": "- Tillåt radering av kontots chatthistorik via menyalternativet Chattaktivitet.\n- Be om bekräftelse innan du raderar chatt- och samtalshistorik.\n- Förbättrad uppdatering av ikonerna för röstbrevlåda, chatt och samtal i huvudaktiviteten.\n- Gå direkt till aktiviteten Konton när baresip startas utan några konton.\n- Ombytta positioner för knapparna för att acceptera och avvisa samtal.\n- Tillåt inte att avsluta utgående samtal innan samtalsförsöket har påbörjats.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/11.0.0.txt",
    "content": "- Ersatte export och import av konton och kontakter med säkerhetskopiering\n  och återställning av all applikationsdata.\n- Förbättrad säkerhet genom att inte tillåta säkerhetskopiering av Android-baserade applikationer och\n  installation av applikationer till extern lagring.\n- När ett konto raderas kommer även kontots samtals- och chatthistorik raderas.\n- Fast radering av meddelanden och chattar.\n- Mindre förbättringar av användargränssnitt och strängar.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/11.1.0.txt",
    "content": "- Lagt till konfigurationsvariabeln \"Bind Address\" som gör det möjligt att välja vilket\n  nätverksgränssnitt eller IP-adress som baresip använder.\n- Uppdatera alltid statusikonen för konton när du återupptar huvudaktiviteten.\n- Fixad krasch när man återupptar en tidigare förstörd huvudaktivitet.\n- Visar korrekt aktivt inkommande samtal när du återupptar huvudaktiviteten.\n- Uppdaterade spanska strängar.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/11.2.0.txt",
    "content": "- Lagt till stöd för AMR narrowband codec.\n- Bulgariska strängar har lagts till.\n- Konfigurationsobjektet i huvudmenyn har bytt namn till Inställningar.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/11.3.0.txt",
    "content": "- Lagt till möjligheten att välja vilka ljudmoduler som ska laddas i Settings\n  laddas. Ljudcodecs som tillhandahålls av de laddade modulerna är tillgängliga för\n  konton att använda.\n- Mindre förbättringar av koden.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/12.0.0.txt",
    "content": "- Initial implementering av ljud via Bluetooth-headset.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/12.1.0.txt",
    "content": "- Tillåt portnummer i kontots Address of Record (AoR).\n- Fixat kontroll av om konto redan finns.\n- Uppdaterade NO- och BG-strängar.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/12.2.0.txt",
    "content": "- Använde nya API-funktioner 'net_set_address' och 'net_set_af' för att implementera\n  Prefer IPv6-funktionalitet.\n- Tog bort Bind Address från Inställningar (det fungerade inte som förväntat).\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/12.2.1.txt",
    "content": "- Förbättrad och fixerad hantering av dynamiska nätverksförändringar.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/13.0.0.txt",
    "content": "- Spela upp aviseringsljud även när meddelandet anländer och baresip är synligt.\n- Om kontaktens namn är tomt, använd kontaktens SIP URI AoR som kontaktens namn.\n- Avslappnad kontroll av kontaktens namn.\n- Undvik enstaka krascher när du väljer ett konto i spinnern.\n- Be om tillstånd att spela in ljud (mikrofon) när baresip startar (om det inte\n  redan beviljats eller permanent nekats).\n- Förbättrad hantering av skriv-/läsbehörighet för extern lagring (lagringsutrymme)\n  vid säkerhetskopiering/återställning.\n- Behörigheten Read Phone State (Phone) behövs inte längre.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/13.0.1.txt",
    "content": "- Meddela alltid user agent spinner om eventuell ändring av datauppsättning när\n  återuppta huvudaktivitet utan avsikt.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/14.0.0.txt",
    "content": "- Lagt till karaktärsbaserade avatarer i kontakter, chattar och samtalshistorik.\n- Fixat sparande/återställning av kontakter med icke-ASCII-kontaktnamn.\n- Förbättrad formatering av datum och klockslag.\n- Lagt till anmärkning om begränsning av flera nätverksgränssnitt.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/15.0.0.txt",
    "content": "- Bildbaserade kontaktavatarer har lagts till som alternativ till karaktärsavatarer.\n- Små förbättringar och korrigeringar av användargränssnittet.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/15.1.0.txt",
    "content": "- Lagt till möjlighet att ange transportprotokoll för kontots AoR.\n- Visa inte kontots port eller transportprotokoll förutom i Account Activity.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/15.1.1.txt",
    "content": "- Mindre strängfixar och förbättringar.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/16.0.0.txt",
    "content": "- Ersatte den globala inställningen \"Prefer IPv6\" med den kontospecifika inställningen \"Prefer IPv6 Media\".\n- Förbättrad hantering av förändringar i nätverksanslutningen.\n- Kräver inte port i utgående URI för IPv6.\n- Övergav ljudfokus även vid avslutning om det inte redan gjorts tidigare.\n- Lagt till initialt stöd för ryska språket.\n- Uppdaterade vissa norska strängar.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/16.0.1.txt",
    "content": "- Omregistrera UA:er när ett nytt aktivt nätverk blir tillgängligt eller\n  när länkens egenskaper ändras även om IP-adresserna förblir desamma.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/16.1.0.txt",
    "content": "- Omregistrera inte konton när länkens egenskaper ändras, men IP-adresserna\n  förblir desamma.\n- Om baresip inte startar, låt användaren ändra de inställningar som kan\n  ha orsakat problemet.\n- String-uppdateringar och korrigeringar.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/17.0.0.txt",
    "content": "- Fixat sparande av ett konto som har ;transportparameter i sin AoR.\n- Fixat aktivering av meddelanden när samtal stängs.\n- Använd vit statusikon när kontots registrering inte har aktiverats.\n- Standardvolymen för samtal påverkar nu både röstsamtal och musikströmmar.\n- Förbättrad layout och implementering av kontoaktivitet.\n- Visa inte debug-meddelanden från Baresip Lib om Debug inte har aktiverats.\n- Skapa inte om aktiviteter när skärmens orientering ändras.\n- Visa chattinformation som inte ryms på en rad på rätt sätt\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/17.1.0.txt",
    "content": "- Använd användningstyp istället för strömtyp vid begäran om ljudfokus.\n- Fixad formatering av engelsk hjälptext för konton.\n- Visa inte möjlig port för en AoR när du får frågan om att radera AoR.\n- Fyllde ikonen för icke-registerstatus med vit färg.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/17.2.0.txt",
    "content": "- Autentiseringens användarnamn är nu standard för kontots användarnamn.\n- Fråga om autentiseringslösenord vid baresip-start om autentiserings\n  användarnamn har angetts, men inte lösenordet.\n- Initialisera kontots statuspunkt till gul, när Register är markerat\n  i kontoaktivitet.\n- Åtgärdat fel i samband med skapandet av första kontot på äldre Android-versioner.\n- Mindre förbättringar av strängar.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/17.2.1.txt",
    "content": "- Åtgärdat fel i lagring av konton i filsystemet.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/17.2.2.txt",
    "content": "- Prenumerera inte på indikering av väntande meddelande när konto skapas\n  och URI för röstbrevlåda inte är inställd.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/17.2.3.txt",
    "content": "- Förhindra krascher till följd av dubbelklick på olika ikoner och\n  listobjekt.\n- Ändra färgen på kontots meddelandeikon till vit när kontot är\n  avregistrerat.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/17.3.0.txt",
    "content": "- Lagt till kryssrutan \"Visa lösenord\" i meddelandet om autentiseringslösenord.\n- Be om mikrofontillstånd som det första när baresip startar.\n- Tillåt inte \" tecken i kontots autentiseringslösenord.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/17.4.0.txt",
    "content": "- Lagt till stöd för Android 10 (API-nivå 29).\n- Förbättrad fråga om mikrofontillstånd när baresip startas för första gången.\n- Undvik krasch om samtalsknappen vidrörs utan konton.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/18.0.0.txt",
    "content": "- På grund av Android 10-begränsningar kan baresip inte längre startas automatiskt\n  startas automatiskt efter uppstart utan ett meddelande.\n- Fixad omstart av baresip på Android 10.\n- Borttagen inställning av ICE Lite Mode på grund av borttagning uppströms.\n- Lagt till brasiliansk portugisiska strängar från Weblate.\n- Små korrigeringar i spanska och norska strängar.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/18.0.1.txt",
    "content": "- Ökning av det maximala antalet ljudcodecs för kontot från 8 till 16.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/18.1.0.txt",
    "content": "- Förbättrad konfiguration av kontots ljudcodecs.\n- Visa alltid inkommande samtal (om något) när baresip återupptas.\n- Förbättrad och fixad återupptagning till icke-huvudaktiviteter.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/18.1.1.txt",
    "content": "- Fixad krasch när du ringer från samtalshistoriken.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/18.1.2.txt",
    "content": "- Fixad krasch när man återvänder till föregående aktivitet efter att ha fått\n  samtal eller meddelande.\n- Bättre hantering av närhetssensor och högtalartelefon.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/18.1.3.txt",
    "content": "- Förenklad styrning av närhetsavkänning.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/18.2.0.txt",
    "content": "- Separat konfiguration av kontots ljudcodecs till en ny vy.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/18.2.1.txt",
    "content": "- Fixat två chattrelaterade krascher.\n- Mindre förbättringar av koden.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/18.3.0.txt",
    "content": "- Förbättringar av dialogen.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/19.0.0.txt",
    "content": "- Lagt till alternativet TURN-konto medianat.\n- Lagt till möjlighet att ange användarnamn/lösenord för STUN/TURN-servern.\n- STUN/TURN-relaterade strängar för vissa språk har inte uppdaterats ännu.\n- Fler förbättringar av dialogrutan.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/19.1.0.txt",
    "content": "- Förbättringar i dialogrutan för varningar.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/20.0.0.txt",
    "content": "- Förbättringar av akustisk ekoavstörning.\n- Flyttat ljudinställningar från Inställningar till ny aktivitet.\n- Lagt till inställningen AEC Extented Filter.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/20.0.1.txt",
    "content": "- Fixat återgång till Audio Activity efter paus.\n- Fast tillägg av ljudmoduler i ljudinställningar.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/20.0.2.txt",
    "content": "- Fixar och förbättringar relaterade till knappen Dialpad.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/20.1.0.txt",
    "content": "- Lagt till bidragande RU-strängar.\n- Uppströmsfix av möjlig krasch.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/21.0.0.txt",
    "content": "- Inledande stöd för överföring av samtal från baresip har lagts till.\n- Lagt till nytt språk portugisiska från Weblate.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/21.1.0.txt",
    "content": "- Lagt till möjlighet att omregistrera valt konto genom att svepa nedåt.\n- Automatisk fokus och visning av mjukt tangentbord när lösenord eller överförings\ndestination frågas.\n- Olika strängrelaterade korrigeringar.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/21.2.0.txt",
    "content": "- Lagt till AMR-WB (Adaptive Multi-Rate Wideband) talkodek.\n- Avsnittet Licenser har lagts till i texten Om.\n- Nya översättningar till portugisiska (Brasilien).\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/22.0.0.txt",
    "content": "- Websocket-transport för SIP har lagts till (RFC 7118).\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/22.1.0.txt",
    "content": "- Nya översättningar till portugisiska (Brasilien) och ryska.\n- Uppdatera ikoner efter återupptagande av applikationen.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/23.0.0.txt",
    "content": "- Lagt till synlighetsknapp för kontots autentiserings- och STUN/TURN-lösenord\n- Föredrar VPN-gränssnitt (om det finns något) när du väljer bland tillgängliga nätverksgränssnitt\n- Lagt till en anmärkning i About-texten om preferensordning för gränssnitt\n- Förbättrad konfiguration av kontots Media NAT Traversal\n- Standard till Googles STUN-server även när protokollet Media NAT Traversal är ICE\n- Ökat maxlängden för Kontots autentiserings- och STUN/TURN-användarnamn samt visningsnamn till 64 tecken\n- Lagt till/uppdaterat översättningar av FR-, NO- och FI-strängar\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/23.1.0.txt",
    "content": "- Ljudkodek G.729 har lagts till.\n- Lagt till en inställning för att slå på/av spårning av SIP-meddelanden till logcat.\n- STUN/TURN Server URI-scheman \"stuns\" och \"turns\" stöds inte för närvarande.\n- Nya portugisiska (Brasilien) strängöversättningar.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/23.2.0.txt",
    "content": "- Förbättrad upptäckt av förändringar i VPN-anslutningen\n- Nya översättningar till portugisiska (Brasilien)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/24.0.0.txt",
    "content": "- Använd adaptiv jitterbuffert\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/24.1.0.txt",
    "content": "- Förbättrad hantering av volymknappar för upp/ned\n- Förbättrad hantering av högtalartelefon\n- Nya RU-översättningar\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/24.2.0.txt",
    "content": "- Visa automatiskt DTMF soft keyboard när samtalet är anslutet\n- Undvik krasch som orsakas av att du trycker på volymkontrollknapparna när du inte är i samtal\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/24.3.0.txt",
    "content": "- Visa nummerplatta automatiskt endast när enheten är orienterad i stående läge\n- Spela inte upp samtalsvänteljudet när ett andra samtal kommer in\n- Förbättrad Om-text\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/24.4.0.txt",
    "content": "- Ange inte STUN/TURN-serverns användarnamn/lösenord som standard till Authentication Username/Password\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/25.0.0.txt",
    "content": "- Förbättrad avisering av inkommande samtal.\n- Meddelande om missat samtal har lagts till.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/26.0.0.txt",
    "content": "- Lagt till mörkt tema och inställning för mörkt tema.\n- Förbättringar av kontospinnare.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/26.0.1.txt",
    "content": "- Fixade att slå på mörkt tema när Baresip-applikationen startas\n- Nya översättningar (portugisiska (Brasilien))\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/26.1.0.txt",
    "content": "- Andra appar kan nu se baresip som en telefonapp för sip: och tel: URI:er\n- Förbättrat sparande/återställning av samtals-URI-text när huvudaktiviteten pausas\n- Introducerad japansk översättning\n- Förbättringar av färger\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/26.1.1.txt",
    "content": "- Korrigerad hantering av samtalsåtgärd när Baresip-appen inte körs.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/26.1.2.txt",
    "content": "- Försök att upptäcka rotation av kontaktavatarbilder\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/27.0.0.txt",
    "content": "- Lagt till möjlighet att exportera Baresip-kontakter till Android-kontakter\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/27.0.1.txt",
    "content": "- Fixad krasch när baresip startas första gången\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/28.0.0.txt",
    "content": "- Lagt till möjlighet att delvis autokonfigurera ett nytt konto från en webbsida (se https://github.com/juha-h/baresip-studio/wiki/Automatic-Account-Configuration för detaljer)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/28.1.0.txt",
    "content": "- Förbättrad sökning av en kontakt som matchar en SIP URI\n- Förbättringar av verktygsfält och menystil\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/28.1.1.txt",
    "content": "- Fixad färgning av inställningen för ljudmoduler\n- Fixad krasch och utseende relaterad till samtalslista\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/28.2.0.txt",
    "content": "- Lagt till inställningen \"Verifiera servercertifikat\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/29.0.0.txt",
    "content": "- Aktiverat svep vänster/höger för att växla mellan konton\n- Lagt till kontoinställning för DTMF-läge\n- Uppdatering av översättningar från Weblate (franska)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/29.1.0.txt",
    "content": "- Portugisiska (Brasilien) översättningar från Weblate\n- Fixad F-Droid nb-NO locale tagg\n- Undvik varningar för \"duplicerad begäran om slutförande\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/29.2.0.txt",
    "content": "- Buggfixar relaterade till chatt, samtalshistorik och kontakter\n- Fixade buggar i URI-komplettering och förbättrade URI-relaterade kontroller\n- Översättningar från Weblate (franska, portugisiska (Brasilien))\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/29.2.1.txt",
    "content": "- Undvik eventuella kontaktrelaterade krascher vid start av Baresip\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/3.2.0.txt",
    "content": "- Lagt till knapp för att välja mellan telefonnummer och mjukt tangentbord för text.\n- Fixat automatisk komplettering av callee baserat på kontakter.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/3.2.2.txt",
    "content": "- Lagt till hantering av händelsen \"Samtalsöverföring misslyckades\".\n- Undvikit krasch vid (åter)start av enhet.\n- Undvikit krasch när man för första gången kommer tillbaka från konton till huvudaktivitet.\n- Använd \"textEmailAddress\"-inmatningstyp för konto-URI:er.\n- Ändrade betydelsen av standardmeddelandekanal till låg.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/30.0.0.txt",
    "content": "- Uppgraderad mål SDK-version till API-nivå 30\n- I Android-versioner 10 och högre, låt användaren välja filen i\n  säkerhetskopiering, återställning, TLS-certifikatrelaterade operationer\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/30.0.1.txt",
    "content": "- Uppströmslösning för att välja rätt konto för inkommande samtal.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/30.1.0.txt",
    "content": "- Lösenordsdialog och inmatning av kontolösenord har förbättrats.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/30.2.0.txt",
    "content": "- Lagt till stöd för AMR-codec Bandwidth Efficient Mode\n- Tillåt undangömda tecken i SIP URI-användardelen och autentiseringsanvändarnamnet\n- Använd coroutine istället för async task för att hämta kontokonfiguration från nätverket\n- I Android 10+ använd väljare för att välja TLS-certifikat och TLS CA-filer\n- Be om bekräftelse för att återställa inställningar till fabriksinställningar\n- Nya översättningar (portugisiska och portugisiska (Brasilien))\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/30.3.0.txt",
    "content": "- Spela upp autosvarsljud vid autosvar\n- Borttagen iLBC-codec (borttagen från uppströms)\n- Tog bort oanvända ljudfiler\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/31.0.0.txt",
    "content": "- Förbättrad Om-text\n- Återställer chattlistans position när du återvänder från chatten\n- Fixad krasch när man frågar efter kontots lösenord\n- Försök inte ladda iLBC-modulen\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/31.1.0.txt",
    "content": "- Uppströmsfix av websocket-transport\n- Nya översättningar till portugisiska (Brasilien)\n- Kontrollera utgående proxy URI-transport\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/31.2.0.txt",
    "content": "- Förbättringar av konto STUN/TURN URI\n- Diverse ljudrelaterade förbättringar\n- Nya översättningar till portugisiska (Brasilien) och slovenska\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/31.2.1.txt",
    "content": "- Korrigerad krasch när du väljer ett konto i kontoaktiviteten\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/32.0.0.txt",
    "content": "- Lagt till knapp för att slå på/av mikrofonen under samtal\n- Använd horisontell rullningsvy för knappar för samtalsstyrning\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/32.1.0.txt",
    "content": "- Nya översättningar till spanska och portugisiska (Brasilien)\n- Mindre förbättringar relaterade till AudioManager\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/32.2.0.txt",
    "content": "- Förbättrad kontroll av SIP URI-parametrar\n- Fler korrigeringar och förbättringar relaterade till ljudhanterare/bluetooth\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/32.3.0.txt",
    "content": "- Förvärva WiFi-lås medan baresip använder WiFi-nätverk\n- Återställde oavsiktligt borttagen ringsignal\n- Lagt till en notis om att stänga av batterioptimering\n- Svenska språköversättningar\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/33.0.0.txt",
    "content": "- Förvärva WiFi-lås när baresip använder WiFi-nätverk\n- Lagt till anmärkning i Om-texten angående batterioptimering\n- Nya översättningar till svenska och portugisiska (Brasilien)\n- Diverse implementeringsförbättringar\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/34.0.0.txt",
    "content": "- Lagt till begränsat stöd för flera samtidigt aktiva nätverksgränssnitt\n- Visa meddelande när baresip startas utan nätverksåtkomst\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/34.1.0.txt",
    "content": "- Använd WebRTC Acoustic Echo Cancellation Mobile Mode-filter\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/35.0.0.txt",
    "content": "- Använd WebRTC Acoustic Echo Cancellation Mobile Mode-filter\n- Visa antalet missade samtal i meddelandet om det är fler än ett\n- När standardinställningen för samtalsvolym är inställd, ställ in både media- och samtalsvolym\n- Avbryt aviseringar när huvudaktiviteten återupptas\n- Fixat stopp för ringning i äldre Android-versioner\n- Nya översättningar till svenska och portugisiska (Brasilien)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/35.1.0.txt",
    "content": "- När aec är aktiverat/inaktiverat, ladda/avladda webrtc_aecm-modulen\n- Använd BaresipService för att spela upp alla ljud\n- Korrekt hantering av svar på sessionsförlopp\n- Under samtal styr volymknapparna nu medievolymen\n- Använd kommunikationsljudläge när du inte är i ringljudläge\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/35.2.0.txt",
    "content": "- Fixar och förbättringar relaterade till ljudfokus\n- Ta bort alla blanksteg från callee-fältet\n- Ny översättning till portugisiska (Brasilien)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/36.0.0.txt",
    "content": "- Migrerade baresip till API-nivå 31 (Android 12)\n- Förbättrad detektering av tillgängliga nätverk\n- Förbättrade behörighetsförfrågningar\n- Tog bort kontoinställningen Föredra IPv6 Media\n- Ny översättning till portugisiska (Brasilien)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/36.1.0.txt",
    "content": "- Flyttat knappen för att stänga av samtal från samtalskontrollen till åtgärdsfältet\n- Lagt till inställning för batterioptimering\n- Be om tillstånd att spela in ljud när baresip startas\n- Nya översättningar till portugisiska (Brasilien), franska och svenska\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/36.1.1.txt",
    "content": "- Flyttat knappen för att stänga av samtal från samtalskontrollen till åtgärdsfältet\n- Lagt till inställning för batterioptimering\n- Be om tillstånd att spela in ljud när baresip startas\n- Nya översättningar till portugisiska (Brasilien), franska och svenska\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/36.1.2.txt",
    "content": "- Förhindrade swipe-relaterad krasch\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/36.2.0.txt",
    "content": "- För närvarande är mikrofonikonen endast aktiv när ett samtal är anslutet\n- Nya svenska översättningar\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/37.0.0.txt",
    "content": "- Implementering av samtalsöverföring enligt RFC 5589 Basic Transfer\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/37.0.1.txt",
    "content": "Fastställda ljudinställningar för utgående samtal\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/37.1.0.txt",
    "content": "- Lagt till uppgifter om genomsnittlig bithastighet, jitter, antal paket och antal förlorade paket i samtalsinfo\n- Använd pausikonen med accentfärg för att indikera att användaren har satt samtalet på vänt\n- Informera användaren när den andra parten har satt samtalet i vänteläge\n- Mindre förbättringar relaterade till mjuk inmatning\n- Nya översättningar till portugisiska (Brasilien)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/37.2.0.txt",
    "content": "- Bättre skydd för baresip-appen när enheten är låst med secure keyguard\n- Nya översättningar till portugisiska (Brasilien)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/37.3.0.txt",
    "content": "- Flyttad inställning för standardvolym för samtal under Ljudinställningar\n- Nytt språk (tyska)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/37.4.0.txt",
    "content": "- Nya översättningar till portugisiska och tyska\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/38.0.0.txt",
    "content": "- Förbättrad hantering av låsning/upplåsning av enheter\n- I Inställningar har en länk till Android-inställningar lagts till\n- Kontrollera korrekt om inställningen för batterioptimering har ändrats\n- Nya översättningar till svenska och portugisiska (Brasilien)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/39.0.0.txt",
    "content": "- Förbättrad visning av tidsvärdet i samtalshistoriken i samtalshistoriklistan\n- Lagt till aktivitet för samtalsinformation som kan nås genom att trycka på tid i\nsamtalshistoriklistan\n- Fixat visning av avatarer i kontakt-, samtals- och chattlistor\n- Tillåt avbrytande av besvarat samtal som väntar på att etableras\n- Nya översättningar till portugisiska, portugisiska Brasilien, svenska och norska\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/39.1.0.txt",
    "content": "- Förbättrad och korrigerad hantering av meddelanden och samtalsöverföring\n- Toastregistrering misslyckas med alla aktuella aktiviteter\n- Automatisk kapitalisering av meddelandemeningar\n- Ersatte föråldrade lokala sändningar med LiveData-händelser\n- Fixat några buggar relaterade till hantering av externa samtalsåtgärder\n- Nya översättningar till portugisiska (Brasilien)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/4.0.0.txt",
    "content": "- Förbättrad implementering av kontakter, meddelanden och samtalshistorik.\n- Undviker krasch när meddelanden kommer innan konton har lagts till.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/4.1.0.txt",
    "content": "- Alla data som inte är relaterade till användargränssnittet sparas nu i baresip-tjänsten.\nvilket möjliggör korrekt återställning om Android dödar andra baresip-aktiviteter.\n- Många andra förbättringar och buggfixar.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/4.1.1.txt",
    "content": "- Åtgärdat en kritisk bugg i kontrollen av inspelningstillstånd för ljud som\nförhindrade ny baresip-installation från att starta.\n- Små förbättringar.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/4.1.2.txt",
    "content": "- Korrigerad krasch när man klickar på spinnern för tomma konton.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/40.0.0.txt",
    "content": "- Konfigurationsobjekt för konto för telefonileverantör har lagts till\n- Lagt till stöd för tel URI som Callee-värde och Contact URI\n- Fråga användaren efter Telephony Provider-konto om det behövs för att ringa samtalet\n- Visar kontots STUN-konfigurationsobjekt endast om det behövs\n- Förbättrad hantering av CALL-åtgärder från andra program\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/40.0.1.txt",
    "content": "- Konfigurationsobjekt för konto för telefonileverantör har lagts till\n- Lagt till stöd för tel URI som Callee-värde och Contact URI\n- Fråga användaren efter Telephony Provider-konto om det behövs för att ringa samtalet\n- Visar kontots STUN-konfigurationsobjekt endast om det behövs\n- Förbättrad hantering av CALL-åtgärder från andra program\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/40.1.0.txt",
    "content": "- Spela inte upptaget- eller felljud när samtalet är avslutat\n- Förbättrad tillförlitlighet vid användning av Bluetooth\n- Bättre hantering av händelser från baresip-kärnan\n- Nya översättningar till portugisiska (Brasilien)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/41.0.0.txt",
    "content": "- Lagt till kronometer för samtalstimer i raden \"Samtal till .../Samtal från ...\"\n- Lagt till möjlighet att använda Android-kontakter\n- Lagt till inställningen \"Visa Android-kontakter\" för att välja om Android-kontakter ska visas när du trycker på kontaktknappen\n- Kontrollera att återställningsfilen har skapats av Baresip-appen\n- Nya översättningar till svenska och portugisiska (Brasilien)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/42.0.0.txt",
    "content": "- Ersatt inställningen ”Visa Android-kontakter” med inställningen ”Kontakter” som gör det möjligt att välja om baresip-kontakter, Android-kontakter eller båda ska användas\n- Korrigeringar och förbättringar av mappningen mellan kontaktnamn och kontakt-URI\n- Undvik krasch när Android tvingar baresip-appen att starta om\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/42.1.0.txt",
    "content": "- Ersatte AppCompat-temat med MaterialComponents-temat\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/42.2.0.txt",
    "content": "- Kontaktvalslistan innehåller nu bara de Android-kontakter som har exakt en URI\n- Nya översättningar till svenska och portugisiska (Brasilien)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/42.3.0.txt",
    "content": "- Fast tillägg av ny baresip-kontakt\n- När du väljer ett nytt standardkonto, behåll ordningen på resten oförändrad\n- Förbättrad hantering av samtal som avvisas automatiskt\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/43.0.0.txt",
    "content": "- Inledande stöd för överföring av samtal med uppvaktning har lagts till\n- I chattaktivitet ersattes långklick med kortklick\n- Gjorde meddelandetexten valbar\n- Använd fetstil i AoR-spinnertexten när samtalet är aktivt\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/43.0.2.txt",
    "content": "- Inledande stöd för överföring av samtal med uppvaktning har lagts till\n- I chattaktivitet ersattes långklick med kortklick\n- Gjorde meddelandetexten valbar\n- Använd fetstil i AoR-spinnertexten när samtalet är aktivt\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/43.1.0.txt",
    "content": "- Fixat 32 bitars armeabi-v7a arkitekturrelaterad bugg\n- Visa Anonym eller Okänd om URI-värden är anonymous.invalid eller unknown.invalid\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/44.0.0.txt",
    "content": "- Visa vidarekoppling om inkommande samtal har vidarekopplats\n- Backup-relaterade korrigeringar i äldre Android-versioner\n- Förbättrad matchning av URI till kontakt\n- Nya översättningar till portugisiska (Brasilien)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/44.1.0.txt",
    "content": "-  På grund av Googles krav, be användarens samtycke om Android-kontakter väljs i Inställningar\n- Nya översättningar till portugisiska (Brasilien)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/44.2.0.txt",
    "content": "- Fixad krasch på ARMv7-enheter när meddelande togs emot\n- Nya översättningar till portugisiska (Brasilien)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/446.txt",
    "content": "- Fixad Kontrollera att du inte stör\n- Nya översättningar\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/447.txt",
    "content": "- Ändra färgen på samtalsknappen till gul när samtalsknappen har vidrörts, men samtalet ännu inte har ringts\n- Aktivera inte värdelös mjukvarubaserad ekosläckning om hårdvarubaserad inte är tillgänglig\n- Ta alltid bort säkerhetsikonen och DTMF-fältet när samtalet avslutas\n- För registrerade konton, kontrollera att användardelen av inkommande Request URI matchar användardelen av REGISTER request contact URI\n- Förbättringar av jitterbufferten för uppströms ljud\n- Nya översättningar (franska)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/448.txt",
    "content": "- Weblate-anteckning tillagd i texten Om\n- Nya översättningar (kinesiska)\n- Baresip lib loggning makro fix\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/449.txt",
    "content": "- Fixat tillägg av nytt samtal till historik\n- Undvik \"flash\" på skärmen när du ringer från kontakter eller samtalshistorik\n- Nya översättningar (tjeckiska)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/45.0.0.txt",
    "content": "- Lagt till konfigurationsalternativ för landskodskonto\n- Ljudrelaterade förbättringar från uppströms\n- Nya översättningar till portugisiska (Brasilien) och finska\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/45.1.0.txt",
    "content": "- Använd elliptisk kurvkryptografi baserad på DTLS Key Establishment Protocol\n- Nya svenska översättningar\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/45.1.1.txt",
    "content": "- Använd elliptisk kurvkryptografi baserad på DTLS Key Establishment Protocol\n- Fixad säkerhetskopiering av samtalshistorik\n- Nya svenska översättningar\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/45.1.2.txt",
    "content": "Fixar och förbättringar relaterade till ring- och notifieringston\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/46.0.0.txt",
    "content": "- Lagt till möjlighet att aktivera/avaktivera registrering av konto via\n  sändningsintentioner (se Wiki för detaljer)\n- Uppströms tillägg av cachelagring av DNS-förfrågningar\n- Uppströmsfix av ICE-kandidatprioriteringar\n- Höjd API-nivå för Android-mål till 32 (Android 12L)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/46.0.1.txt",
    "content": "- Använd inte DNS-cache som kan orsaka långa fördröjningar i adresslösningen\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/46.1.0.txt",
    "content": "- Lång tryckning på aktuellt konto aktiverar eller inaktiverar registrering av kontot\n- Lagt till möjlighet att ge konton smeknamn\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/46.1.1.txt",
    "content": "- Lång tryckning på aktuellt konto aktiverar eller inaktiverar registrering av kontot\n- Lagt till möjlighet att ge konton smeknamn\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/46.2.0.txt",
    "content": "- Om avregistreringen misslyckas, uppdatera ändå registreringsstatusen till vit\n- Ställ in lösenord när användaragent skapas\n- Nya översättningar till portugisiska (Brasilien)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/47.0.0.txt",
    "content": "- Lagt till stöd för tillförlitliga provisoriska svarsmeddelanden (RFC 3262)\n- Lagt till QUIT-åtgärd för avsiktsmottagare\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/47.1.0.txt",
    "content": "- Lagt till stöd för SIP UPDATE-metoden (RFC 3311)\n- Uppströmsfix av tidig ljudrelaterad bugg (183 Session Progress)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/47.2.0.txt",
    "content": "- Fördröj uppringning med 1 sek efter att samtalsikonen har tryckts på eftersom ljudläget\n  inte omedelbart ändras till kommunikation\n- Uppströmsfix av PRACK-hantering\n- Nya svenska översättningar\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/48.0.0.txt",
    "content": "- Använd låsikoner för säkerhetsindikering av samtal\n- När du ringer till Android-kontakt, använd den senaste peer URI om Android-kontakten har den\n- Använd ZRTPCPP lib för ZRTP-mediekryptering (peers måste verifieras på nytt)\n- Aktivera DNS-cachelagring\n- Nya översättningar till portugisiska (Brasilien)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/48.0.1.txt",
    "content": "- Använd låsikoner för säkerhetsindikering av samtal\n- När du ringer till Android-kontakt, använd den senaste peer URI om Android-kontakten har den\n- Använd ZRTPCPP lib för ZRTP-mediekryptering (peers måste verifieras på nytt)\n- Aktivera DNS-cachelagring\n- Nya översättningar till portugisiska (Brasilien)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/48.1.0.txt",
    "content": "- Lade till GSM-codec\n- Gjorde fönstret orörbart och startade 5 sekunders timer vid Avsluta eller Starta om\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/48.2.0.txt",
    "content": "- Inaktivera PRACK (RFC 3262)-funktionen på grund av ett allvarligt uppströmsfel\n- Nya översättningar till portugisiska (Brasilien) och ryska\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/49.0.0.txt",
    "content": "- Förbättrat urval och ordning av kontots ljudcodecs\n- På grund av problem inaktiverades DNS-frågans cache\n- Migrering till Android API 33\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/49.0.1.txt",
    "content": "- Fixat uppströmsbugg som orsakade lång avslutningsfördröjning om röstbrevlådekontroll var\n  aktiverad för ett konto\n- Förenklad Avsluta/omstarta-process och undvikit krasch vid avslutning\n- Nya översättningar till portugisiska (Brasilien)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/49.1.0.txt",
    "content": "- Anledning till skål när inkommande samtal stängs av baresip lib\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/49.1.2.txt",
    "content": "- Uppströmsfix av felet Media NAT Traversal (STUN/TURN/ICE)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/49.1.3.txt",
    "content": "- Uppströms fixering av codec-förhandling\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/49.2.0.txt",
    "content": "- Lagt till kontoinställningen \"RTCP Multiplexing\"\n- Fördelar åtgärdsknappar jämnt längst ner i huvudaktiviteten\n- Använde colorSecondaryDark som ledtrådsfärg i textvyn\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/49.3.1.txt",
    "content": "- Lagt till kontoinställningen \"RTCP Multiplexing\"\n- Fördelar åtgärdsknappar jämnt längst ner i huvudaktiviteten\n- Använde colorSecondaryDark som färg för textvyns ledtråd\n- Visa korrekt statusmeddelande när det inte finns några registrerade UA\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/49.4.0.txt",
    "content": "- Fixad krasch vid nätverksändringar\n- Nya översättningar till portugisiska (Brasilien)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/5.0.0.txt",
    "content": "- Ersatte textläget EditConfigActivity med en grafisk konfigurationsaktivitet.\n- Aktuella konfigurationsobjekt är automatisk start, DNS-servrar, Opus bithastighet,\noch ICE-Lite.\n- Lagt till kontoalternativ för ICE- och STUN-server.\n- Fixat fel vid borttagning av konto.\n- Ej skickade meddelanden går inte förlorade när chattaktiviteten avbryts.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/5.1.0.txt",
    "content": "- Lagt till möjlighet att exportera/importera kontakter till/från mappen \"Download\".\nOm du vill använda den här funktionen ska du ge baresip lagringsbehörighet\ni Inställningar → Appar → baresip → Behörigheter.\n- Fixat långsam avslutning av baresip när det inte finns någon nätverksanslutning.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/5.2.0.txt",
    "content": "- Meddela användaren om misslyckad registrering.\n- Lagt till knapp för samtalsinfo för ett aktivt samtal.\n- Kräver inte \"sip:\"-schema i URI för utgående proxy.\n- Tillåt att domänetiketten börjar med en siffra.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/5.2.1.txt",
    "content": "- Lättare kontroll av visningsnamn och autentiseringsnamn\n- Lagt till versionsnamn på sidan Om\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/5.3.0.txt",
    "content": "- Lagt till popup-meddelande som berättar varför samtalet avslutades.\n- Fixat visning av URI för anropande part om den innehåller portnummer eller parametrar.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/5.3.1.txt",
    "content": "- Lättare kontroll av userpart i kontots SIP URI.\n- Lagt till stöd för ARMv8-A-arkitektur.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/5.4.0.txt",
    "content": "- Protokollet SRTP-MANDF för kryptering av media har lagts till.\n- Säkerhetsknappens färg ändras nu från rött till gult även när samtalet är\nskyddas av ett SRTP*-protokoll för mediekryptering.\n- Fast inställning av kontomediakryptering till DTLS-SRTPF.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/5.4.1.txt",
    "content": "- Tog bort oanvända baresip-moduler från konfiguration och bibliotek.\n- Baresip-tjänsten låter nu baresip-aktiviteten dö på egen hand efter att ha\nefter att ha fått en dödsavsikt från den.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/5.4.2.txt",
    "content": "- Åtgärdat en sällsynt krasch som kan inträffa när det första kontot skapas och\nmeddelande eller samtal kommer in innan användaren har återgått till huvudaktiviteten.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/50.0.0.txt",
    "content": "- Be om POST_NOTIFICATIONS-behörighet på Android 13+-enheter\n- Använd grå mikrofonikon när mikrofonen inte är avstängd\n- Använd svarsmeddelandet \"Upptagen här\" när ett samtal avvisas\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/50.1.0.txt",
    "content": "- Förbättringar av processen Avsluta/omstarta\n- Försök att stödja växling av SpeakerPhone på API-nivåer 31+\n- Nya översättningar till portugisiska (Brasilien)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/50.1.1.txt",
    "content": "- Förbättringar av processen Avsluta/omstarta\n- Fixar och förbättringar relaterade till högtalartelefon och samtal för API 31+\n- Nya översättningar till portugisiska (Brasilien)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/50.1.3.txt",
    "content": "- Förbättringar av processen Avsluta/omstarta\n- Fixar och förbättringar relaterade till högtalartelefon och samtal för API 31+\n- Nya översättningar till portugisiska (Brasilien)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/50.1.4.txt",
    "content": "- Fler korrigeringar och förbättringar relaterade till kommunikationsenheter\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/50.1.5.txt",
    "content": "- Fixat detektering av WiFi hotspot-gränssnitt på vissa enheter\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/50.2.0.txt",
    "content": "- Problem med ljudrouting åtgärdat för Android 12+-enheter\n- Använd sekundär mörk färg i codec-namnet när du är i mörkt läge\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/50.2.1.txt",
    "content": "- Problem med ljudrouting åtgärdat för Android 12+-enheter\n- Använd sekundär mörk färg i codec-namnet när du är i mörkt läge\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/50.2.2.txt",
    "content": "- Problem med ljudrouting åtgärdat för Android 12+-enheter\n- Använd sekundär mörk färg i codec-namnet när du är i mörkt läge\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/51.0.0.txt",
    "content": "- Vibrera och vibrera medan du ringer enligt inställningarna för ljud och vibrationer\n- Bluetooth-fixar och förbättringar för Android 12+-enheter\n- Visa behörighetsinformation när baresip startas för första gången\n- Nya översättningar till svenska och portugisiska (Brasilien)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/51.1.0.txt",
    "content": "- Lagt till kontoinställning för registreringsintervall\n- Använd telefoniprovider även när du skickar meddelanden till tel URI:er\n- Nu accepteras även ( och ) tecken i telefonnummer\n- Fixade aviseringar om inlägg i senare Android-versioner\n- Förbättrad behörighetshantering i senare Android-versioner\n- Nya översättningar till spanska, portugisiska (Brasilien) och svenska\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/51.2.0.txt",
    "content": "- Flera kontaktrelaterade korrigeringar och förbättringar\n- Nya översättningar till portugisiska (Brasilien)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/52.0.0.txt",
    "content": "- Lagt till stöd för att välja från flera tel:/sip: URI:er i Android\n- kontakter\n- Ett aktivt konto måste nu ha en telefoniprovider konfigurerad för att kunna\n  ringa eller skicka ett meddelande till tel: URI\n- Nya översättningar till portugisiska (Brasilien), spanska, svenska och finska\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/52.1.0.txt",
    "content": "- Lagt till preliminärt stöd för samtalsinspelning\n- Många korrigeringar relaterade till att fråga efter lösenord\n- Förhindrar att vissa dialogrutor avfärdas utifrån\n- Nya översättningar till spanska, portugisiska och brasilianska\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/52.2.0.txt",
    "content": "- Lagt till möjlighet att stoppa inspelningsuppspelning\n- Lagt till adaptiv startikon\n- Nya spanska översättningar\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/53.0.0.txt",
    "content": "- Lagt till inställning för att välja IP-adressfamilj\n- Lagt till monokrom lanseringsikon\n- Tog bort ikon och text från statusmeddelande när Android-versionen är 13+\n- Nya översättningar till spanska, ryska, portugisiska (Brasilien) och tjeckiska\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/53.1.0.txt",
    "content": "- Förbättringar av ikonerna i startprogrammet\n- Nya ryska översättningar\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/53.1.1.txt",
    "content": "- Förbättringar av ikonen för startprogrammet\n- Försök inte att registrera ett nytt konto om Register inte har markerats\n- Lagt till hjälptext för registreringsintervall\n- Nya ryska översättningar\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/53.1.2.txt",
    "content": "- Uppströms ökning av max storlek på kontosträng\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/53.2.0.txt",
    "content": "- Förbättringar av meddelanden om registreringsstatus\n- Flera ikonbilder har ersatts med vektorritningar i materialdesign\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/53.2.1.txt",
    "content": "- Åtgärdat flera problem med ljudfokus/routing/volymkontroll\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/53.2.2.txt",
    "content": "- Åtgärdat flera problem med ljudfokus/routing/volymkontroll\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/53.2.3.txt",
    "content": "- Förenklad uppspelning av ringsignal\n- Nya portugisiska översättningar\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/54.0.0.txt",
    "content": "- Kräver minst Android version 6 (API-nivå 23)\n- I Android-versioner under 12, lägg till 1,5 sekunders fördröjning innan du ringer ett samtal\nför att undvika att missa ljud från den som ringer\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/54.1.0.txt",
    "content": "- Ljudinställningen \"Audio Delay\" har lagts till\n- Förbättrat test för anslutning av Bluetooth-headset\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/54.2.0.txt",
    "content": "- Spela upptagetton när svaret \"486 Upptagen här\" eller \"603 Avböj\" tas emot\n- Undvik onödig omregistrering när nätverkskapaciteten ändras\n- Förenklat övergivande av ljudfokus\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/55.0.0.txt",
    "content": "- Förhindra att baresip-tjänsten startas mer än en gång\n- Förbättringar av färger\n- Nya översättningar till spanska, ryska, portugisiska (Brasilien), svenska och tjeckiska\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/55.0.1.txt",
    "content": "- Uppströmsfix för samtal som ligger kvar i aktiv lista även när det stängs\n- Bättre hantering av händelser för aktivering/inaktivering av WiFi-hotspot\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/55.0.2.txt",
    "content": "- Uppströmsfix för samtal som ligger kvar i aktiv lista även när det stängs\n- Bättre hantering av händelser för aktivering/inaktivering av WiFi-hotspot\n- Förbättrad färgkontrast för textknappen i varningsdialogen i mörka teman\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/55.1.0.txt",
    "content": "- Förbättringar av färger i mörkt tema\n- Förbättrat automatiskt avvisande av samtal\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/55.2.0.txt",
    "content": "- Förutom CALL kan du även hantera DIAL-åtgärder från andra applikationer\n- Nya översättningar till spanska och portugisiska (Brasilien)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/56.0.0.txt",
    "content": "- Tillåt att baresip väljs som standardtelefonapp (välj inte om du också använder telefonitjänster)\n- Stöd för både CALL- och DIAL-åtgärder från andra appar\n- Åtgärdat fel vid inställning av kontots telefonileverantör\n- I meddelanden, stöd även för andra teckenuppsättningar än UTF-8\n- Använd standardvärden för audio_buffer och jitter_buffer_delay\n- Nya översättningar till tjeckiska och ryska\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/56.1.0.txt",
    "content": "- I samtalshistoriken, Visa missade (inte avvisade) samtal med gula upp-/nerpilar\n- Använd audio_buffer size 20-300 (ms) och jitter_buffer delay 0-20 (frames)\n- Undvik potentiell krasch relaterad till getNetworkInterfaces()\n- Förenklad \"Om\"-text och länk till \"mer information\" i Wiki\n- Nya översättningar till spanska, portugisiska (Brasilien) och tjeckiska\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/56.2.0.txt",
    "content": "- Lagt till separata jitterbuffertfördröjningar för ljud och video\n- I samtalsinformation visas paket, förlorade paket och jitter som ?/? om informationen inte är tillgänglig\n- Uppströmsfix som nu tillåter escape-tecken i kontots <user>-del\n- Ny tjeckisk översättning\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/56.3.0.txt",
    "content": "- Korrigerade inställningar för jitterbuffert som förhindrade att inkommande ljud hördes\n- Avsluta samtal om ingen media har tagits emot inom 60 sekunder\n- Nya spanska översättningar\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/57.0.0.txt",
    "content": "- Större omskrivning av inställningsimplementeringen genom att separera statiska och dynamiska inställningar\n- Återgå till ursprungliga jitterbuffertfördröjningar nu när libre-buggen är fixad\n- Konfigurationen kan inte laddas om i farten när opus-inställningarna ändras\n- Nya översättningar till spanska och portugisiska (Brasilien)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/57.1.0.txt",
    "content": "- Lagt till mer vertikal utfyllnad i kontokonfigurationen\n- Hack för att fixa felaktigt escaped URI när samtal eller uppringningsbegäran kommer från utsidan\n- Nya tjeckiska översättningar\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/57.1.1.txt",
    "content": "- Korrigerat skrivfel i hjälptexten för standardtelefonappen\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/57.2.0.txt",
    "content": "- Lagt till kontoinställningen Redirect Mode för att välja om begäran om vidarekoppling av samtal\n  (3xx-svar) ska följas automatiskt eller om bekräftelse behöver\n  att bli tillfrågad\n- Nya översättningar till spanska och portugisiska (Brasilien)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/58.0.0.txt",
    "content": "- Lagt till konfigurationsalternativ för konto för tillförlitliga provisoriska svar\n- Lagt till alternativet Tone Country Audio Configuration (tack till Robert Averbeck för att han tillhandahöll tonfilerna)\n- Fixat AudioManager-relaterat SDK-versionstest\n- Nya översättningar till spanska, portugisiska och portugisiska (Brasilien)\n- Uppströms buggfixar och förbättringar\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/59.0.0.txt",
    "content": "- Riktar sig nu mot API-nivå 34 (Android 14)\n- Lagt till stöd för Codec2 ljudkodek\n- Inspelning av nästa samtal rensas inte när samtalet avslutas\n- Inkluderade även Dark Theme-inställning i backup-fil\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/59.0.1.txt",
    "content": "- Riktar sig nu mot API-nivå 34 (Android 14)\n- Lagt till stöd för Codec2 ljudkodek\n- Inspelning av nästa samtal rensas inte när samtalet avslutas\n- Inkluderade även Dark Theme-inställning i backup-fil\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/59.0.2.txt",
    "content": "- Riktar sig nu mot API-nivå 34 (Android 14)\n- Lagt till stöd för Codec2 ljudkodek\n- Inspelning av nästa samtal rensas inte när samtalet avslutas\n- Inkluderade även Dark Theme-inställning i backup-fil\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/59.0.3.txt",
    "content": "- Riktar sig nu mot API-nivå 34 (Android 14)\n- Lagt till stöd för Codec2 ljudkodek\n- Inspelning av nästa samtal rensas inte när samtalet avslutas\n- Inkluderade även Dark Theme-inställning i backup-fil\n- Uppströmsfix av re-INVITE (samtal väntar/återupptas)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/59.0.4.txt",
    "content": "- Uppströmsfix av krasch relaterad till hantering av OPTIONS-begäran\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/59.1.0.txt",
    "content": "- Stöd för favoritkontakter (stjärnmärkta) har lagts till\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/59.2.0.txt",
    "content": "- Inställningen Starta automatiskt startar nu appen i stället för att meddela om den\n- Nya översättningar till spanska, portugisiska (Brasilien) och ryska\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/59.3.0.txt",
    "content": "- Mindre buggfixar uppströms\n- Nya tyska översättningar\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/59.4.0.txt",
    "content": "- Använda RTP-mottagningsläge för tråd\n- Nya översättningar till tjeckiska och portugisiska\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/59.4.1.txt",
    "content": "- Uppdaterad undermodul för libbaresip-android (alla moduler hittas nu)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/59.4.2.txt",
    "content": "- Upstream nekade som standard inkommande meddelanden.  Kan tillåtas igen\n  genom att göra någon ändring i konfigurationen av ett konto.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/59.4.3.txt",
    "content": "- Tillåt mottagande av meddelanden efter att uppströms (som standard) nekat dem\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/59.6.0.txt",
    "content": "- Lagt till information om baresip, CPU och Android i User-Agent-huvudet\n- Tidpunkten för det inspelade samtalet visas nu i accentfärg\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/59.7.0.txt",
    "content": "- Uppströmsfix av bugg som kan orsaka att Baresip-appen slutar svara\n- Nya tjeckiska översättningar\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/6.0.0.txt",
    "content": "- Flera förbättringar av användargränssnittet (mestadels chattrelaterade).\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/6.0.1.txt",
    "content": "- Återuppta baresip app till den aktivitet som stoppades.\n- Lägg till exempelkontakt när baresip startas första gången.\n- Fixat visning av nytt meddelande i chatt- och meddelandelistan.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/6.0.2.txt",
    "content": "- Korrigerad krasch när STUN-servern definieras utan portnummer.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/6.1.0.txt",
    "content": "- Gå till chattfönstret när du klickar på meddelandeaviseringen.\n- Åtgärdat fel i samtalsöverföring.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/6.2.0.txt",
    "content": "- Uppgraderade målnivån för Android API till 28 (Android 9).\n- Flyttat viss icke-UI-funktionalitet från huvudaktiviteten till baresip-tjänsten.\n- Förenklad implementering av samtalsöverföring.\n- Uppdaterade bilderna för baresip-startprogrammet och statusfältet.\n- Avregistrerat UA när konto raderas.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/6.2.1.txt",
    "content": "- Använder ConnectivityManager NetworkCallbacks för att upptäcka när nätverks\nnätverksanslutning blir tillgänglig eller förloras.\n- Lagt till Android 8.1+ sätt att hantera skärmen.\n- Ställ in volymkontroller så att de gäller för aktuell ljudström (kanske inte alltid\nfungerar).\n- Fixade möjlig krasch när förstörd baresip-app startas igen.\n- Visar meddelande när kontot som ska skapas redan existerar.\n- Lagt till en notis i \"Om\"-texten om medievolym.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/6.3.0.txt",
    "content": "- Lagt till Debug Config-alternativ för att slå på och av adb logcat debug.\n- Lagt till möjlighet att exportera och importera konton till/från fil i Download-katalogen.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/6.3.1.txt",
    "content": "- Fixat och förbättrat implementeringen av DTMF Watcher.\n- Stängde av redigering av inkommande/utgående samtals URI när samtal pågår.\n- Förstörde användaragenten när kontot raderas.\n- Tog bort oanvänd KILL_BACKGROUND_PROCESSES-behörighet.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/6.4.0.txt",
    "content": "- Lagt till möjlighet att ange ett konto som standardkonto.\n- Visa röd meddelandeikon när det finns olästa meddelanden.\n- Fråga PIN för att släppa nyckelvakten när Callee URI klickas.\n- Fixat åtgärder för samtal, meddelande och samtalsöverföring när de initieras från meddelanden.\n- Fixat att skicka ett meddelande från kontakter eller samtalshistorik.\n- Använde SingleTask istället för SingleTop som startläge för huvudaktivitet.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/60.0.0.txt",
    "content": "- Använd Android 14-kompatibelt filformat för säkerhetskopiering. Säkerhetskopiera igen innan du uppgraderar Android till version 14.\n- Förbättrad trådhantering\n- Nya tyska översättningar\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/60.1.0.txt",
    "content": "- Fixad radering av avatarfilen när avataren tas bort från kontakten eller när kontakten med avataren tas bort\n- Fixat sparande av kontots DTMF-läge\n- Nya översättningar av spanska, portugisiska och portugisiska (Brasilien)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/60.2.0.txt",
    "content": "- Lagt till DTMF-läge för \"RTP eller SIP INFO i band\n- Ljudinställning för högtalartelefon har lagts till\n- Nya översättningar till tyska, portugisiska (Brasilien) och spanska\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/60.3.0.txt",
    "content": "- Stöd för autentisering med SHA-256 digest har lagts till\n- Inställningarna för Verifiera servercertifikat kan nu ändras utan omstart\n- Nya översättningar till tjeckiska, tyska, spanska och portugisiska (Brasilien)\n- Fixade format för vissa strängar på vissa språk\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/60.4.0.txt",
    "content": "- Korrekt hantering av överföringsbegäran när det ursprungliga samtalet redan är avslutat\n- Korrigerad hantering av stateless TLS SIP-begäranden\n- Nya översättningar till portugisiska, portugisiska (Brasilien), spanska, franska, ryska, engelska, finska, svenska, tyska och tjeckiska\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/60.4.1.txt",
    "content": "- SRTP-relaterad säkerhetsfix från uppströms\n- Nya tjeckiska översättningar\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/60.4.2.txt",
    "content": "- Fast samtalsinspelning\n- Skicka inte \"180 Ringing\"-svar om inkommande samtal avvisas automatiskt\n- Ta även bort %20 (mellanslag) från URI för tel-samtal\n- Nya tyska översättningar\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/61.0.0.txt",
    "content": "- Inställningen User Agent har lagts till och kan användas för att ange värdet för User-Agent-fältet i SIP-begäran/-svaret\n- Tillåt , och #-tecken i telefonnumret (till exempel när du ringer Teams)\n- Använd accentfärg i stället för fetstil i kolumnen Samtalshistorik om ett samtal i den samtalsraden har spelats in\n- Inkludera inte uuid i backupfilen (förhindrar att många baresip UAs har samma identitet)\n- Som standard inkluderas datum och tid i backupfilens namn\n- Nya översättningar till spanska och portugisiska (Brasilien)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/61.0.1.txt",
    "content": "- Om DTMF är aktiverat, behåll fokus när skärmen ändrar orientering\n- Ändrade kontaktens URI för \"The Test Call\" till \"sip:thetestcall@sip2sip.info\n- När du visar URI i vänlig form, visa URI-parametrar utom ';transport=udp'\n- Tog bort suffixet \".bs\" från backupfilens namn\n- Nya översättningar (ryska)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/61.1.0.txt",
    "content": "- Ljudinställningen Microphone Gain har lagts till\n- Lagt till huvudmenyalternativet Logcat\n- Nya översättningar till tjeckiska, norska (bokmål), tyska, portugisiska (Brasilien) och spanska\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/62.0.0.txt",
    "content": "- Om tillgängligt, använd akustisk ekoreducering, automatisk förstärkningskontroll och\n  Brusreducerare som tillhandahålls av enheten\n\n- Android 9 (API-nivå 28) är nu den version som stöds minst\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/62.1.0.txt",
    "content": "- Välj bort kant-till-kant-implementering för Android 15-kompatibilitet\n- Undvik krascher i BootCompletedReceiver och vid kontroll av utgående proxy-URI\n- Nya översättningar till finska, bulgariska, japanska, portugisiska (Brasilien), spanska, tjeckiska\noch polska översättningar\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/63.0.0.txt",
    "content": "- Nytt stöd för kant-till-kant-visning av innehåll för kompatibilitet med Android 15\n- Rundade hörn i dialogrutan för varningar\n- Visa inte automatiskt mjukt tangentbord när konfigurationen ändras\n- Lagt till nytt språk (tamil)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/63.1.0.txt",
    "content": "- Förhindra att mjukt tangentbord döljer inmatningstextfältet i vissa aktiviteter\n- Fixat typnummer för nyttolast för telefonhändelse\n- Förhindra krasch när backupfilen är stor\n- Nya översättningar (tyska)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/63.2.0.txt",
    "content": "- Använd akustisk ekoavstängning endast på inspelningssessionen\n- Gör inte backup av inspelningar\n- Nya översättningar till rumänska, portugisiska (Brasilien), tyska och tjeckiska\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/63.2.1.txt",
    "content": "- Förbättrad inställning av ljudeffekter\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/63.2.2.txt",
    "content": "- Installerade om spelaren AEC\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/63.2.3.txt",
    "content": "- Tillåt användning av mjukvaru-AEC även om hårdvaru-AEC skulle vara tillgänglig\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/63.2.5.txt",
    "content": "- Varning om AEC för hårdvara inte är tillgängligt när AEC för mjukvara är inaktiverat i ljudinställningarna\n- Nya översättningar (portugisiska Brasilien)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/63.3.0.txt",
    "content": "- Tillåt *- och #-tecken i telefonnummer\n- Nya översättningar (portugisiska (Brasilien))\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/64.0.0.txt",
    "content": "- Migrerat användargränssnitt till Jetpack Compose (kan innehålla buggar)\n- Försök alltid använda Acoustic Echo Canceler i hårdvara om det finns tillgängligt\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/64.1.0.txt",
    "content": "- Flera korrigeringar och förbättringar relaterade till användargränssnittet\n- Nya översättningar (portugisiska (Brasilien))\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/64.2.0.txt",
    "content": "- Vertikala rullningslister har lagts till i aktiviteterna Inställningar, Konto och Ljud\n- Fixat hantering av URI-förslag för samtal och överföring\n- Förbättringar av dialogstil\n- Nya översättningar (portugisiska)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/64.3.0.txt",
    "content": "- Fixad hantering av DTMF backspace-tecken\n- Förbättringar i användargränssnittet för chatt och kontakt\n- Använd bakgrundsfärg även i systemfält\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/64.3.1.txt",
    "content": "- Fixad hantering av DTMF backspace-tecken\n- Förbättringar i användargränssnittet för chatt och kontakt\n- Använd bakgrundsfärg även i systemfält\n- Fast krasch i kontoaktivitet när inga konton\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/65.0.0.txt",
    "content": "- Slutförd konvertering av användargränssnittet till Jetpack Compose\n- Fixat inställningen Verifiera servercertifikat\n- Nya portugisiska översättningar\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/65.1.0.txt",
    "content": "- Återställd svep vänster/höger för att växla mellan konton\n- Fixar och förbättringar i användargränssnittet för Konto och Inställningar\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/65.2.0.txt",
    "content": "- Förbättrad utseende och implementering av pull-to-register\n- Se till att bluetoothreceiver är registrerad innan du avregistrerar den\n- Fixad back-press-åtgärd för API < 33-kompatibilitet\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/65.2.1.txt",
    "content": "- Förbättrad utseende och implementering av pull-to-register\n- Se till att bluetoothreceiver är registrerad innan du avregistrerar den\n- Fixad back-press-åtgärd för API < 33-kompatibilitet\n- Fixad ZRTP-verifieringsfråga\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/65.3.0.txt",
    "content": "- Lagt till inställning för ringsignal\n- Förbättrat utseende och implementering av pull-to-register\n- Se till att bluetoothreceiver är registrerad innan du avregistrerar den\n- Fixat back-tryck-åtgärd för API < 33-kompatibilitet\n- Fixat ZRTP-verifieringsfråga\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/66.0.0.txt",
    "content": "- Använd switchar istället för kryssrutor i alla aktiviteter\n- Varning vid start om Acoustic Echo Cancellation för nätverk eller hårdvara inte är tillgänglig\n- Åtgärdat fel som orsakade trunkering av uppringt telefonnummer\n- Undvik krasch relaterad till att använda baresip som uppringningsapp\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/66.1.0.txt",
    "content": "- Lagt till kontoinställningar för numeriskt tangentbord\n- Lagt till verktygstips till ikonerna i huvudaktivitetens övre appfält\n- Visa kontots fullständiga SIP URI på kontosidan\n- Nya översättningar (japanska och tamilska)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/66.1.1.txt",
    "content": "- Lagt till kontoinställningar för numeriskt tangentbord\n- Lagt till verktygstips till ikonerna i huvudaktivitetens övre appfält\n- Visa kontots fullständiga SIP URI på kontosidan\n- Återställer osänt nytt meddelande efter paus/återupptagning\n- Nya översättningar (japanska och tamilska)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/66.1.2.txt",
    "content": "- Lagt till kontoinställningar för numeriskt tangentbord\n- Lagt till verktygstips till ikonerna i huvudaktivitetens övre appfält\n- Visa kontots fullständiga SIP URI på kontosidan\n- Återställde osänt nytt meddelande efter paus/återupptagning\n- I chattaktivitet, fixat att lägga till chattkollega i kontakter\n- Nya översättningar (japanska och tamilska)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/66.1.3.txt",
    "content": "- Lagt till kontoinställningar för numeriskt tangentbord\n- Lagt till verktygstips till ikonerna i huvudaktivitetens övre appfält\n- Visa kontots fullständiga SIP URI på kontosidan\n- Återställde osänt nytt meddelande efter paus/återupptagning\n- I chattaktivitet, fixat att lägga till chattkollega till kontakt\n- Lagt till avbryt-knapp för att avbryta utgående samtal\n- Fixat visning av förslag på mål för överföring av samtal\n- Nya översättningar (japanska och tamilska)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/66.1.4.txt",
    "content": "- Flera inställningsrelaterade korrigeringar\n- Förbättrad layout för dialogrutan för överföring\n- Nya översättningar (tyska)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/66.1.5.txt",
    "content": "- Fixad initiering av konto från nätverk\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/66.1.6.txt",
    "content": "- Använd volymknappar för ringsignalvolym när samtal kommer in och för samtalsvolym under samtal\n- Använd toasts för no network och no hw aec notiser\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/67.0.0.txt",
    "content": "- Migrerade baresip-appen från aktiviteter till Jetpack Compose-navigering\n- Förbättrad hantering av samtal och meddelanden som kommer när baresip inte är synlig\n- Några andra mindre förbättringar av användargränssnittet\n- Uppströmsfix av Refer-To-rubrikernas Till/Från-taggar\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/67.0.1.txt",
    "content": "- Stoppa samtalstimern kronometer när samtalet är avslutat\n- Lägg till mikrofon i statusmeddelande endast när inspelningstillstånd har beviljats\n- Nya översättningar (tamil)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/67.0.2.txt",
    "content": "- Buggfix för ljudinställning för standardvolym för samtal\n- Förbättring och buggfix för säkerhetskopiering/återställning\n- Nya översättningar (tyska)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/67.0.3.txt",
    "content": "- Fixar och förbättringar på skärmen för konto och inställningar\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/67.1.0.txt",
    "content": "- Använd den blå nedåtpilen i samtalshistoriken för att ange att samtalet har besvarats någon annanstans\n- Lagt till hjälptexter vid klick på pilikonerna för samtalsinformation\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/67.2.0.txt",
    "content": "- Lagt till Colorblind-inställning som för närvarande påverkar ikoner för registreringsstatus\n- Förbättrade namn på meddelandekanaler\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/68.0.0.txt",
    "content": "- Riktar sig mot Android 16 API-nivå 36\n- Lagt till inställning för närhetsavkänning\n- Fast inställning för kontakter Båda värdena\n- Nya översättningar (tjeckiska)\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/68.1.0.txt",
    "content": "- Förbättrad visning av tid för samtalshistorik\n- Ändrad bakgrundsfärg på statusikoner för färgblinda\n- Fixad uppdatering av statusmeddelanden\n- Åtgärdat möjlig krasch relaterad till kontoinställning för registreringsintervall\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/68.1.1.txt",
    "content": "- Förbättrad visning av tid för samtalshistorik\n- Ändrad bakgrundsfärg på statusikoner för färgblinda\n- Åtgärdat möjlig krasch relaterad till kontoinställning för registreringsintervall\n- Fixad uppdatering av statusmeddelanden\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/68.1.2.txt",
    "content": "- Förbättrad visning av tid för samtalshistorik\n- Ändrad bakgrundsfärg på statusikoner för färgblinda\n- Åtgärdat möjlig krasch relaterad till kontoinställning för registreringsintervall\n- Fixad uppdatering av statusmeddelanden\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/7.0.0.txt",
    "content": "- Lagt till stöd för IPv6 (ej testat på grund av bristande tillgång till IPv6-nätverk).\n- Konfigurationsalternativet \"Prefer IPv6\" har lagts till.\n- Lagt till konfigurationsalternativet \"Reset to Factory Default\".\n- Vid Avsluta, se till att även baresip-aktiviteten stoppas.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/7.1.0.txt",
    "content": "- Konfigurationsalternativet \"Standardvolym för samtal\" har lagts till.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/7.1.1.txt",
    "content": "- Uppdatera kontospinnaren även när huvudaktiviteten återupptas utan åtgärd.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/7.1.2.txt",
    "content": "- Försökte se till att även kontospinnarvyn uppdateras när\nkontospinnaren uppdateras.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/7.2.0.txt",
    "content": "- Lagt till ny konfigurationsvariabel Listen Address som gör det möjligt att ange en IP-adress\nadress (eller alla adresser) och en port som baresip lyssnar på för SIP-begäranden.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/7.3.0.txt",
    "content": "- NAT-protokollet för kontomedia kan nu väljas mellan STUN, ICE eller inget.\n- Sätt kontostatus till gult omedelbart när kontots registrering är avstängd.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/7.4.0.txt",
    "content": "- Anpassade varningar till riktlinjer för materialdesign.\n- Förbättrad hantering av språksträngar.\n- Finsk språköversättning har lagts till som ett exempel.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/8.0.0.txt",
    "content": "- Lagt till stöd för ljudkodek G.722.1.\n- Stöd för WebRTC-baserad ekosläckning för ljud har lagts till (gäller inte Opus-codec).\n- Mindre ändringar av samtalsmeddelanden.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/8.0.1.txt",
    "content": "- Återställning av volymen till ursprunglig nivå om standardvolymen för samtal används har åtgärdats.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/8.0.2.txt",
    "content": "- Fixat tillägg av nytt konto.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/8.1.0.txt",
    "content": "- Hämta DNS-serveradresser dynamiskt från systemet om DNS-servrar\ninte anges i Konfiguration.\n- Om det bara finns ett konto, lägg till kontots domän till kontakt-URI\nsom anges utan hostpart.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/8.2.0.txt",
    "content": "- Förbättrat innehåll och utseende på About-sidan.\n- Visar alltid det aktiva samtalet (om något) när appen startas om.\n- Ändrad placering av OK-knappen i varningsdialogen.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/8.3.0.txt",
    "content": "- Lade till Internet Low Bitrate Codec (iLBC).\n- Förbättrat val av codec för kontot.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/8.3.1.txt",
    "content": "- Ställ inte in ljudet på kommunikationsläge när samtalet är etablerat,\neftersom det på vissa enheter ger 3-4 sekunders fördröjning av uppspelningen av ljudet.\n- Gjorde akustisk ekoreducering konfigurerbar.\n- Aktivera knappen för samtalsinformation när den blir synlig.\n- Använd standardtema för enheten när spinners visas.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/8.3.2.txt",
    "content": "- Om kontot nås från AoR spinner, gå direkt tillbaka till Main\nAktivitet.\n- Använd HtmlCompat för att konvertera About HTML till text (bör fungera på alla\nAndroid API-nivåer).\n- Använd ringsignalens loopingfunktion på Android P+.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/8.3.3.txt",
    "content": "- Hämta DNS-servrar dynamiskt även när baresip startas för första gången.\n- Undvik möjlig krasch när du hämtar kontots ljudkodek.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/8.3.5.txt",
    "content": "- Omge kontakt-URI:er med < och > endast när kontakterna sparas i en fil.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/8.3.6.txt",
    "content": "- Tillåt ändring av kontaktnamn även när endast bokstävernas versaler skiljer sig åt.\n- När du återställer kontakter behöver du inte kräva att URI:er omges av vinkelparenteser.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/8.4.0.txt",
    "content": "- Ekoavstängning tillämpas nu även på samtal som använder Opus-codec.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/8.4.1.txt",
    "content": "- Förbättring av akustisk ekoundertryckning uppströms.\n- Nya översättningar: el, nb_NO, ro.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/8.4.2.txt",
    "content": "- Uppdatera DNS-servrar och registrera UA:er alltid när nätverket blir tillgängligt.\n- Fixat och uppdaterat några texter.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/8.4.3.txt",
    "content": "- Fixat visning av kontostatus när standardkonto ändras.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/8.4.4.txt",
    "content": "- Uppströmsfix i opus codec-implementering.\n- Fixar för volymkontrollström.\n- Fixat automatiskt avvisande av nytt inkommande samtal.\n- Mindre förbättringar av strängar.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/8.5.0.txt",
    "content": "- Preliminär användning av närhetssensor för att stänga av pekskärmen under samtal.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/8.5.1.txt",
    "content": "- Flyttad hantering av närhetssensorer från MainActivity till BaresipService.\n- Fixade krasch när samtal eller meddelande kommer in och MainActivity inte körs.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/8.5.2.txt",
    "content": "- Korrigerad kontroll av DNS-servrar för konfiguration.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/8.6.0.txt",
    "content": "- Lagt till stöd för SIP-registratorer som inte hanterar återanvändning av nonce på rätt sätt.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/9.0.0.txt",
    "content": "-  Lagt till möjlighet att baresip-konfigurera ett TLS-certifikat och CA-certifikat.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/9.1.0.txt",
    "content": "- Avvisa inkommande samtal automatiskt om telefonen inte är i viloläge.\n- Undertryck ringsignalen för inkommande samtal om enheten är i läget \"Stör ej\".\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/9.2.0.txt",
    "content": "- Lagt till stöd för Opus Forward Error Correction (FEC) via konfigurationsvariabeln Opus\n  Konfigurationsvariabeln Förväntad paketförlust.\n- Försök att uppdatera dynamisk DNS-information om registreringen misslyckas på grund av\n  felet \"Ogiltigt argument\".\n- Använd översättningsbar sträng i alla konfigurationsrelaterade varningsmeddelanden.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/9.3.0.txt",
    "content": "- Lagt till konfigurationsalternativet Svarsläge (Manuell eller Automatisk) för kontot.\n- Lagt till norska bokmålssträngar och ändringsloggar från Weblate.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/changelogs/9.4.0.txt",
    "content": "- Lagt till menyalternativet \"Starta om\" för huvudaktivitet.\n- När registreringen misslyckas på grund av fel i DNS-uppslagningen, försök registrera\n  en gång till efter uppdatering av DNS-servrar.\n- Små förbättringar av loggning.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/full_description.txt",
    "content": "Detta är en <a href=\"https://github.com/baresip/baresip\">baresip-baserad</a> SIP User Agent-applikation för Android.\n\nFör närvarande stöder baresip-appen röstsamtal, textmeddelanden, röstbrevlåda Message Waiting Indication samt blinda och uppvaktade samtalsöverföringar.  Röst kan kodas med Opus, AMR, Codec2, G.729, G.722, G.722.1, G.726 eller PCMU/PCMA-codecs. Säkerhet uppnås via TLS- eller WSS SIP-signaltransport och ZRTP- eller (DTLS) SRTP-mediekapsling.\n\nUtvecklingen av baresip-appen motiveras av behovet av en säker SIP-baserad VoIP-användaragent med öppen källkod för Android som inte är beroende av proprietära push-notifikationstjänster från tredje part.\n\nOm du behöver videosamtal och har en enhet med Android version 9 eller nyare som stöder Camera2 API på hårdvarusupportnivå LEVEL3 eller FULL, kan du istället för den här applikationen installera dess systerapplikation baresip+.\n\nKällkoden finns tillgänglig på <a href=\"https://github.com/juha-h/baresip-studio\">GitHub</a>, där även problem kan rapporteras.\n"
  },
  {
    "path": "fastlane/metadata/android/sv/short_description.txt",
    "content": "VoIP User Agent-app för Android baserad på baresip SIP-bibliotek\n"
  },
  {
    "path": "fastlane/metadata/android/sv/title.txt",
    "content": "baresip\n"
  },
  {
    "path": "fastlane/metadata/android/zh-CN/full_description.txt",
    "content": "这是一款基于 <a href=\"https://github.com/baresip/baresip\">baresip</a> 的 Android SIP 用户代理应用程序。\n\n目前，baresip 应用支持语音通话、语音电话会议、文本消息、语音信箱消息等待指示以及盲转和协商转接。语音编码支持 Opus、AMR、Codec2、G.729、G.722、G.722.1 或 PCMU/PCMA 编解码器。通过 TLS 或 WSS SIP 信令传输以及 ZRTP 或（DTLS）SRTP 媒体封装实现安全保障。\n\n开发 baresip 应用的初衷是打造一款安全、开源的 Android SIP VoIP 用户代理，且不依赖任何专有的第三方推送通知服务。\n\n此应用程序可以在 Android 9 或更高版本的设备上运行。\n\n如果您需要视频通话，可以安装其姊妹应用 baresip+ 来代替此应用。\n\n源代码可在 <a href=\"https://github.com/juha-h/baresip-studio\">GitHub 项目</a> 的 master 分支中找到，您也可以在此报告问题。\n"
  },
  {
    "path": "fastlane/metadata/android/zh-CN/short_description.txt",
    "content": "基于 baresip SIP 库的 Android VoIP 用户代理应用\n"
  },
  {
    "path": "fastlane/metadata/android/zh-CN/title.txt",
    "content": "baresip\n"
  },
  {
    "path": "gradle/libs.versions.toml",
    "content": "[versions]\nactivityCompose = \"1.13.0\"\ncoilCompose = \"2.7.0\"\ncomposeBom = \"2026.03.01\"\ncoreKtx = \"1.18.0\"\nexifinterface = \"1.4.2\"\nfragmentKtx = \"1.8.9\"\ngradleVersion = \"9.1.1\"\nkotlin = \"2.3.20\"\nkotlinStdlibJdk8 = \"2.3.10\"\nkotlinSerializationPlugin = \"2.3.20\"\nkotlinxCoroutinesAndroid = \"1.10.2\"\nkotlinxSerializationJson = \"1.11.0\"\nlifecycleProcess = \"2.10.0\"\nlifecycleViewmodelKtx = \"2.10.0\"\nmaterial = \"1.13.0\"\nmedia = \"1.7.1\"\nnavigationCompose = \"2.9.7\"\npreferenceKtx = \"1.2.1\"\nui = \"1.10.6\"\nfoundationAndroid = \"1.10.6\"\nruntimeLivedata = \"1.10.6\"\ncomposeMaterial3 = \"1.4.0\"\nnavigationRuntimeAndroid = \"2.9.7\"\ncomposeMaterialIcons = \"1.7.8\"\nruntime = \"1.10.6\"\n\n[libraries]\nandroidx-activity-compose = { module = \"androidx.activity:activity-compose\", version.ref = \"activityCompose\" }\nandroidx-activity-ktx = { module = \"androidx.activity:activity-ktx\", version.ref = \"activityCompose\" }\nandroidx-compose-bom = { module = \"androidx.compose:compose-bom\", version.ref = \"composeBom\" }\nandroidx-core-ktx = { module = \"androidx.core:core-ktx\", version.ref = \"coreKtx\" }\nandroidx-exifinterface = { module = \"androidx.exifinterface:exifinterface\", version.ref = \"exifinterface\" }\nandroidx-fragment-ktx = { module = \"androidx.fragment:fragment-ktx\", version.ref = \"fragmentKtx\" }\nandroidx-lifecycle-process = { group = \"androidx.lifecycle\", name = \"lifecycle-process\", version.ref = \"lifecycleProcess\" }\nandroidx-lifecycle-viewmodel-ktx = { module = \"androidx.lifecycle:lifecycle-viewmodel-ktx\", version.ref = \"lifecycleViewmodelKtx\" }\nandroidx-media = { module = \"androidx.media:media\", version.ref = \"media\" }\nandroidx-navigation-compose = { module = \"androidx.navigation:navigation-compose\", version.ref = \"navigationCompose\" }\nandroidx-preference-ktx = { module = \"androidx.preference:preference-ktx\", version.ref = \"preferenceKtx\" }\nandroidx-ui = { module = \"androidx.compose.ui:ui\", version.ref = \"ui\" }\ncoil-compose = { module = \"io.coil-kt:coil-compose\", version.ref = \"coilCompose\" }\ngradle = { module = \"com.android.tools.build:gradle\", version.ref = \"gradleVersion\" }\nkotlin-stdlib-jdk8 = { module = \"org.jetbrains.kotlin:kotlin-stdlib-jdk8\", version.ref = \"kotlinStdlibJdk8\" }\nkotlinx-coroutines-android = { module = \"org.jetbrains.kotlinx:kotlinx-coroutines-android\", version.ref = \"kotlinxCoroutinesAndroid\" }\nkotlin-gradle-plugin = { module = \"org.jetbrains.kotlin:kotlin-gradle-plugin\", version.ref = \"kotlin\" }\nkotlinx-serialization-json = { module = \"org.jetbrains.kotlinx:kotlinx-serialization-json\", version.ref = \"kotlinxSerializationJson\" }\nmaterial = { module = \"com.google.android.material:material\", version.ref = \"material\" }\nandroidx-foundation-android = { group = \"androidx.compose.foundation\", name = \"foundation-android\", version.ref = \"foundationAndroid\" }\nandroidx-runtime-livedata = { group = \"androidx.compose.runtime\", name = \"runtime-livedata\", version.ref = \"runtimeLivedata\" }\nandroidx-compose-material3 = { group = \"androidx.compose.material3\", name = \"material3\", version.ref = \"composeMaterial3\" }\nandroidx-navigation-runtime-android = { group = \"androidx.navigation\", name = \"navigation-runtime-android\", version.ref = \"navigationRuntimeAndroid\" }\nandroidx-compose-material-icons-core = { group = \"androidx.compose.material\", name = \"material-icons-core\", version.ref = \"composeMaterialIcons\" }\nandroidx-compose-material-icons-extended = { group = \"androidx.compose.material\", name = \"material-icons-extended\", version.ref = \"composeMaterialIcons\" }\nandroidx-compose-runtime = { group = \"androidx.compose.runtime\", name = \"runtime\", version.ref = \"runtime\" }\n\n[plugins]\ncompose-compiler = { id = \"org.jetbrains.kotlin.plugin.compose\", version.ref = \"kotlin\" }\nkotlin-serialization = { id = \"org.jetbrains.kotlin.plugin.serialization\", version.ref = \"kotlinSerializationPlugin\" }\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Mon Jan 30 05:45:34 EET 2023\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.4.0-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradle.properties",
    "content": "android.enableJetifier=false\nandroid.nonFinalResIds=true\nandroid.useAndroidX=true\nandroid.nonTransitiveRClass=true\nandroid.uniquePackageNames=false\nandroid.dependency.useConstraints=true\nandroid.r8.strictFullModeForKeepRules=false\nandroid.generateSyncIssueWhenLibraryConstraintsAreEnabled=false\nkotlin.code.style=official\norg.gradle.jvmargs=-Xmx2G -Dfile.encoding=UTF-8\n"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=$(save \"$@\")\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\n# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong\nif [ \"$(uname)\" = \"Darwin\" ] && [ \"$HOME\" = \"$PWD\" ]; then\n  cd \"$(dirname \"$0\")\"\nfi\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      http://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n\r\n@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windows variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "settings.gradle.kts",
    "content": "plugins {\n    id(\"org.gradle.toolchains.foojay-resolver-convention\") version \"1.0.0\"\n}\n\ninclude (\":app\")\n"
  }
]