Showing preview only (1,910K chars total). Download the full file or copy to clipboard to get everything.
Repository: juha-h/baresip-studio
Branch: master
Commit: 5742a1c3f727
Files: 1047
Total size: 1.5 MB
Directory structure:
gitextract_1vqbv6_p/
├── .clang-format
├── .gitignore
├── .gitmodules
├── LICENSE
├── PrivacyPolicy.txt
├── README.md
├── app/
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── assets/
│ │ ├── accounts
│ │ ├── config.static
│ │ └── contacts
│ ├── cpp/
│ │ ├── CMakeLists.txt
│ │ ├── baresip.c
│ │ └── logger.h
│ ├── kotlin/
│ │ └── com/
│ │ └── tutpro/
│ │ └── baresip/
│ │ ├── AboutScreen.kt
│ │ ├── Account.kt
│ │ ├── AccountScreen.kt
│ │ ├── AccountViewModel.kt
│ │ ├── AccountsScreen.kt
│ │ ├── AndroidContactScreen.kt
│ │ ├── Api.kt
│ │ ├── AudioScreen.kt
│ │ ├── BaresipApp.kt
│ │ ├── BaresipContactScreen.kt
│ │ ├── BaresipService.kt
│ │ ├── Blocked.kt
│ │ ├── BlockedScreen.kt
│ │ ├── BootCompletedReceiver.kt
│ │ ├── Call.kt
│ │ ├── CallDetailsScreen.kt
│ │ ├── CallHistory.kt
│ │ ├── CallRow.kt
│ │ ├── CallsScreen.kt
│ │ ├── ChatScreen.kt
│ │ ├── ChatsScreen.kt
│ │ ├── Codec.kt
│ │ ├── CodecsScreen.kt
│ │ ├── Colors.kt
│ │ ├── Config.kt
│ │ ├── ConnectionService.kt
│ │ ├── Constants.kt
│ │ ├── Contact.kt
│ │ ├── ContactsScreen.kt
│ │ ├── CustomElements.kt
│ │ ├── DraggableLazyList.kt
│ │ ├── Event.kt
│ │ ├── InCallService.kt
│ │ ├── Log.kt
│ │ ├── MainActivity.kt
│ │ ├── MainScreen.kt
│ │ ├── Message.kt
│ │ ├── Preferences.kt
│ │ ├── ServiceEvent.kt
│ │ ├── SettingsScreen.kt
│ │ ├── SettingsViewModel.kt
│ │ ├── TaskReceiver.kt
│ │ ├── Theme.kt
│ │ ├── UserAgent.kt
│ │ ├── Utils.kt
│ │ └── ViewModel.kt
│ └── res/
│ ├── drawable/
│ │ ├── circle_green.xml
│ │ ├── circle_green_blind.xml
│ │ ├── circle_red.xml
│ │ ├── circle_red_blind.xml
│ │ ├── circle_white.xml
│ │ ├── circle_yellow.xml
│ │ ├── circle_yellow_blind.xml
│ │ ├── ic_launcher_foreground.xml
│ │ ├── ic_notification_b.xml
│ │ ├── ic_notification_call.xml
│ │ ├── ic_notification_call_end.xml
│ │ ├── ic_notification_call_missed.xml
│ │ ├── ic_notification_delete.xml
│ │ ├── ic_notification_message.xml
│ │ ├── ic_notification_reply.xml
│ │ └── ic_notification_save.xml
│ ├── layout/
│ │ └── status_notification.xml
│ ├── mipmap-anydpi/
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ ├── values/
│ │ ├── colors.xml
│ │ └── strings.xml
│ ├── values-ar/
│ │ └── strings.xml
│ ├── values-bg/
│ │ └── strings.xml
│ ├── values-ca/
│ │ └── strings.xml
│ ├── values-cs/
│ │ └── strings.xml
│ ├── values-de/
│ │ └── strings.xml
│ ├── values-el/
│ │ └── strings.xml
│ ├── values-es/
│ │ └── strings.xml
│ ├── values-fi/
│ │ └── strings.xml
│ ├── values-fr/
│ │ └── strings.xml
│ ├── values-hr/
│ │ └── strings.xml
│ ├── values-in/
│ │ └── strings.xml
│ ├── values-iw/
│ │ └── strings.xml
│ ├── values-ja-rJP/
│ │ └── strings.xml
│ ├── values-ko/
│ │ └── strings.xml
│ ├── values-nb-rNO/
│ │ └── strings.xml
│ ├── values-night/
│ │ └── colors.xml
│ ├── values-pl/
│ │ └── strings.xml
│ ├── values-pt/
│ │ └── strings.xml
│ ├── values-pt-rBR/
│ │ └── strings.xml
│ ├── values-ro/
│ │ └── strings.xml
│ ├── values-ru/
│ │ └── strings.xml
│ ├── values-sl/
│ │ └── strings.xml
│ ├── values-sv/
│ │ └── strings.xml
│ ├── values-ta/
│ │ └── strings.xml
│ ├── values-uk/
│ │ └── strings.xml
│ └── values-zh-rCN/
│ └── strings.xml
├── build.gradle.kts
├── fastlane/
│ └── metadata/
│ └── android/
│ ├── de-DE/
│ │ ├── changelogs/
│ │ │ ├── 10.1.0.txt
│ │ │ ├── 10.2.0.txt
│ │ │ └── 10.3.0.txt
│ │ ├── full_description.txt
│ │ ├── short_description.txt
│ │ └── title.txt
│ ├── el/
│ │ └── title.txt
│ ├── en-US/
│ │ ├── changelogs/
│ │ │ ├── 10.0.0.txt
│ │ │ ├── 10.1.0.txt
│ │ │ ├── 10.2.0.txt
│ │ │ ├── 10.3.0.txt
│ │ │ ├── 10.4.0.txt
│ │ │ ├── 11.0.0.txt
│ │ │ ├── 11.1.0.txt
│ │ │ ├── 11.2.0.txt
│ │ │ ├── 11.3.0.txt
│ │ │ ├── 12.0.0.txt
│ │ │ ├── 12.1.0.txt
│ │ │ ├── 12.2.0.txt
│ │ │ ├── 12.2.1.txt
│ │ │ ├── 13.0.0.txt
│ │ │ ├── 13.0.1.txt
│ │ │ ├── 14.0.0.txt
│ │ │ ├── 15.0.0.txt
│ │ │ ├── 15.1.0.txt
│ │ │ ├── 15.1.1.txt
│ │ │ ├── 16.0.0.txt
│ │ │ ├── 16.0.1.txt
│ │ │ ├── 16.1.0.txt
│ │ │ ├── 17.0.0.txt
│ │ │ ├── 17.1.0.txt
│ │ │ ├── 17.2.0.txt
│ │ │ ├── 17.2.1.txt
│ │ │ ├── 17.2.2.txt
│ │ │ ├── 17.2.3.txt
│ │ │ ├── 17.3.0.txt
│ │ │ ├── 17.4.0.txt
│ │ │ ├── 18.0.0.txt
│ │ │ ├── 18.0.1.txt
│ │ │ ├── 18.1.0.txt
│ │ │ ├── 18.1.1.txt
│ │ │ ├── 18.1.2.txt
│ │ │ ├── 18.1.3.txt
│ │ │ ├── 18.2.0.txt
│ │ │ ├── 18.2.1.txt
│ │ │ ├── 18.3.0.txt
│ │ │ ├── 19.0.0.txt
│ │ │ ├── 19.1.0.txt
│ │ │ ├── 20.0.0.txt
│ │ │ ├── 20.0.1.txt
│ │ │ ├── 20.0.2.txt
│ │ │ ├── 20.1.0.txt
│ │ │ ├── 21.0.0.txt
│ │ │ ├── 21.1.0.txt
│ │ │ ├── 21.2.0.txt
│ │ │ ├── 22.0.0.txt
│ │ │ ├── 22.1.0.txt
│ │ │ ├── 23.0.0.txt
│ │ │ ├── 23.1.0.txt
│ │ │ ├── 23.2.0.txt
│ │ │ ├── 24.0.0.txt
│ │ │ ├── 24.1.0.txt
│ │ │ ├── 24.2.0.txt
│ │ │ ├── 24.3.0.txt
│ │ │ ├── 24.4.0.txt
│ │ │ ├── 25.0.0.txt
│ │ │ ├── 26.0.0.txt
│ │ │ ├── 26.0.1.txt
│ │ │ ├── 26.1.0.txt
│ │ │ ├── 26.1.1.txt
│ │ │ ├── 26.1.2.txt
│ │ │ ├── 27.0.0.txt
│ │ │ ├── 27.0.1.txt
│ │ │ ├── 28.0.0.txt
│ │ │ ├── 28.1.0.txt
│ │ │ ├── 28.1.1.txt
│ │ │ ├── 28.2.0.txt
│ │ │ ├── 29.0.0.txt
│ │ │ ├── 29.1.0.txt
│ │ │ ├── 29.2.0.txt
│ │ │ ├── 29.2.1.txt
│ │ │ ├── 3.2.0.txt
│ │ │ ├── 3.2.2.txt
│ │ │ ├── 30.0.0.txt
│ │ │ ├── 30.0.1.txt
│ │ │ ├── 30.1.0.txt
│ │ │ ├── 30.2.0.txt
│ │ │ ├── 30.3.0.txt
│ │ │ ├── 31.0.0.txt
│ │ │ ├── 31.1.0.txt
│ │ │ ├── 31.2.0.txt
│ │ │ ├── 31.2.1.txt
│ │ │ ├── 32.0.0.txt
│ │ │ ├── 32.1.0.txt
│ │ │ ├── 32.2.0.txt
│ │ │ ├── 32.3.0.txt
│ │ │ ├── 33.0.0.txt
│ │ │ ├── 34.0.0.txt
│ │ │ ├── 34.1.0.txt
│ │ │ ├── 35.0.0.txt
│ │ │ ├── 35.1.0.txt
│ │ │ ├── 35.2.0.txt
│ │ │ ├── 36.0.0.txt
│ │ │ ├── 36.1.0.txt
│ │ │ ├── 36.1.1.txt
│ │ │ ├── 36.1.2.txt
│ │ │ ├── 36.2.0.txt
│ │ │ ├── 37.0.0.txt
│ │ │ ├── 37.0.1.txt
│ │ │ ├── 37.1.0.txt
│ │ │ ├── 37.2.0.txt
│ │ │ ├── 37.3.0.txt
│ │ │ ├── 37.4.0.txt
│ │ │ ├── 38.0.0.txt
│ │ │ ├── 39.0.0.txt
│ │ │ ├── 39.1.0.txt
│ │ │ ├── 4.0.0.txt
│ │ │ ├── 4.1.0.txt
│ │ │ ├── 4.1.1.txt
│ │ │ ├── 4.1.2.txt
│ │ │ ├── 40.0.0.txt
│ │ │ ├── 40.0.1.txt
│ │ │ ├── 40.1.0.txt
│ │ │ ├── 41.0.0.txt
│ │ │ ├── 42.0.0.txt
│ │ │ ├── 42.1.0.txt
│ │ │ ├── 42.2.0.txt
│ │ │ ├── 42.3.0.txt
│ │ │ ├── 43.0.0.txt
│ │ │ ├── 43.0.2.txt
│ │ │ ├── 43.1.0.txt
│ │ │ ├── 44.0.0.txt
│ │ │ ├── 44.1.0.txt
│ │ │ ├── 44.2.0.txt
│ │ │ ├── 446.txt
│ │ │ ├── 447.txt
│ │ │ ├── 448.txt
│ │ │ ├── 449.txt
│ │ │ ├── 45.0.0.txt
│ │ │ ├── 45.1.0.txt
│ │ │ ├── 45.1.1.txt
│ │ │ ├── 45.1.2.txt
│ │ │ ├── 450.txt
│ │ │ ├── 451.txt
│ │ │ ├── 452.txt
│ │ │ ├── 453.txt
│ │ │ ├── 454.txt
│ │ │ ├── 455.txt
│ │ │ ├── 456.txt
│ │ │ ├── 457.txt
│ │ │ ├── 458.txt
│ │ │ ├── 459.txt
│ │ │ ├── 46.0.0.txt
│ │ │ ├── 46.0.1.txt
│ │ │ ├── 46.1.0.txt
│ │ │ ├── 46.1.1.txt
│ │ │ ├── 46.2.0.txt
│ │ │ ├── 460.txt
│ │ │ ├── 461.txt
│ │ │ ├── 462.txt
│ │ │ ├── 463.txt
│ │ │ ├── 464.txt
│ │ │ ├── 466.txt
│ │ │ ├── 467.txt
│ │ │ ├── 468.txt
│ │ │ ├── 469.txt
│ │ │ ├── 47.0.0.txt
│ │ │ ├── 47.1.0.txt
│ │ │ ├── 47.2.0.txt
│ │ │ ├── 470.txt
│ │ │ ├── 471.txt
│ │ │ ├── 472.txt
│ │ │ ├── 473.txt
│ │ │ ├── 474.txt
│ │ │ ├── 475.txt
│ │ │ ├── 476.txt
│ │ │ ├── 477.txt
│ │ │ ├── 478.txt
│ │ │ ├── 479.txt
│ │ │ ├── 48.0.0.txt
│ │ │ ├── 48.0.1.txt
│ │ │ ├── 48.1.0.txt
│ │ │ ├── 48.2.0.txt
│ │ │ ├── 480.txt
│ │ │ ├── 481.txt
│ │ │ ├── 482.txt
│ │ │ ├── 483.txt
│ │ │ ├── 484.txt
│ │ │ ├── 487.txt
│ │ │ ├── 488.txt
│ │ │ ├── 489.txt
│ │ │ ├── 49.0.0.txt
│ │ │ ├── 49.0.1.txt
│ │ │ ├── 49.1.0.txt
│ │ │ ├── 49.1.1
│ │ │ ├── 49.1.2.txt
│ │ │ ├── 49.1.3.txt
│ │ │ ├── 49.2.0.txt
│ │ │ ├── 49.3.1.txt
│ │ │ ├── 49.4.0.txt
│ │ │ ├── 491.txt
│ │ │ ├── 492.txt
│ │ │ ├── 5.0.0.txt
│ │ │ ├── 5.1.0.txt
│ │ │ ├── 5.2.0.txt
│ │ │ ├── 5.2.1.txt
│ │ │ ├── 5.3.0.txt
│ │ │ ├── 5.3.1.txt
│ │ │ ├── 5.4.0.txt
│ │ │ ├── 5.4.1.txt
│ │ │ ├── 5.4.2.txt
│ │ │ ├── 50.0.0.txt
│ │ │ ├── 50.1.0.txt
│ │ │ ├── 50.1.1.txt
│ │ │ ├── 50.1.3.txt
│ │ │ ├── 50.1.4.txt
│ │ │ ├── 50.1.5.txt
│ │ │ ├── 50.2.0.txt
│ │ │ ├── 50.2.1.txt
│ │ │ ├── 50.2.2.txt
│ │ │ ├── 51.0.0.txt
│ │ │ ├── 51.1.0.txt
│ │ │ ├── 51.2.0.txt
│ │ │ ├── 52.0.0.txt
│ │ │ ├── 52.1.0.txt
│ │ │ ├── 52.2.0.txt
│ │ │ ├── 53.0.0.txt
│ │ │ ├── 53.1.0.txt
│ │ │ ├── 53.1.1.txt
│ │ │ ├── 53.1.2.txt
│ │ │ ├── 53.2.0.txt
│ │ │ ├── 53.2.1.txt
│ │ │ ├── 53.2.2.txt
│ │ │ ├── 53.2.3.txt
│ │ │ ├── 54.0.0.txt
│ │ │ ├── 54.1.0.txt
│ │ │ ├── 54.2.0.txt
│ │ │ ├── 55.0.0.txt
│ │ │ ├── 55.0.1.txt
│ │ │ ├── 55.0.2.txt
│ │ │ ├── 55.1.0.txt
│ │ │ ├── 55.2.0.txt
│ │ │ ├── 56.0.0.txt
│ │ │ ├── 56.1.0.txt
│ │ │ ├── 56.2.0.txt
│ │ │ ├── 56.3.0.txt
│ │ │ ├── 57.0.0.txt
│ │ │ ├── 57.1.0.txt
│ │ │ ├── 57.1.1.txt
│ │ │ ├── 57.2.0.txt
│ │ │ ├── 58.0.0.txt
│ │ │ ├── 59.0.0.txt
│ │ │ ├── 59.0.1.txt
│ │ │ ├── 59.0.2.txt
│ │ │ ├── 59.0.3.txt
│ │ │ ├── 59.0.4.txt
│ │ │ ├── 59.1.0.txt
│ │ │ ├── 59.2.0.txt
│ │ │ ├── 59.3.0.txt
│ │ │ ├── 59.4.0.txt
│ │ │ ├── 59.4.1.txt
│ │ │ ├── 59.4.2.txt
│ │ │ ├── 59.4.3.txt
│ │ │ ├── 59.5.0
│ │ │ ├── 59.6.0.txt
│ │ │ ├── 59.7.0.txt
│ │ │ ├── 6.0.0.txt
│ │ │ ├── 6.0.1.txt
│ │ │ ├── 6.0.2.txt
│ │ │ ├── 6.1.0.txt
│ │ │ ├── 6.2.0.txt
│ │ │ ├── 6.2.1.txt
│ │ │ ├── 6.3.0.txt
│ │ │ ├── 6.3.1.txt
│ │ │ ├── 6.4.0.txt
│ │ │ ├── 60.0.0.txt
│ │ │ ├── 60.1.0.txt
│ │ │ ├── 60.2.0.txt
│ │ │ ├── 60.3.0.txt
│ │ │ ├── 60.4.0.txt
│ │ │ ├── 60.4.1.txt
│ │ │ ├── 60.4.2.txt
│ │ │ ├── 61.0.0.txt
│ │ │ ├── 61.0.1.txt
│ │ │ ├── 61.1.0.txt
│ │ │ ├── 62.0.0.txt
│ │ │ ├── 62.1.0.txt
│ │ │ ├── 63.0.0.txt
│ │ │ ├── 63.1.0.txt
│ │ │ ├── 63.2.0.txt
│ │ │ ├── 63.2.1.txt
│ │ │ ├── 63.2.2.txt
│ │ │ ├── 63.2.3.txt
│ │ │ ├── 63.2.5.txt
│ │ │ ├── 63.3.0.txt
│ │ │ ├── 64.0.0.txt
│ │ │ ├── 64.1.0.txt
│ │ │ ├── 64.2.0.txt
│ │ │ ├── 64.3.0.txt
│ │ │ ├── 64.3.1.txt
│ │ │ ├── 65.0.0.txt
│ │ │ ├── 65.1.0.txt
│ │ │ ├── 65.2.0.txt
│ │ │ ├── 65.2.1.txt
│ │ │ ├── 65.3.0.txt
│ │ │ ├── 66.0.0.txt
│ │ │ ├── 66.1.0.txt
│ │ │ ├── 66.1.1.txt
│ │ │ ├── 66.1.2.txt
│ │ │ ├── 66.1.3.txt
│ │ │ ├── 66.1.4.txt
│ │ │ ├── 66.1.5.txt
│ │ │ ├── 66.1.6.txt
│ │ │ ├── 67.0.0.txt
│ │ │ ├── 67.0.1.txt
│ │ │ ├── 67.0.2.txt
│ │ │ ├── 67.0.3.txt
│ │ │ ├── 67.1.0.txt
│ │ │ ├── 67.2.0.txt
│ │ │ ├── 68.0.0.txt
│ │ │ ├── 68.1.0.txt
│ │ │ ├── 68.1.1.txt
│ │ │ ├── 68.1.2.txt
│ │ │ ├── 7.0.0.txt
│ │ │ ├── 7.1.0.txt
│ │ │ ├── 7.1.1.txt
│ │ │ ├── 7.1.2.txt
│ │ │ ├── 7.2.0.txt
│ │ │ ├── 7.3.0.txt
│ │ │ ├── 7.4.0.txt
│ │ │ ├── 8.0.0.txt
│ │ │ ├── 8.0.1.txt
│ │ │ ├── 8.0.2.txt
│ │ │ ├── 8.1.0.txt
│ │ │ ├── 8.2.0.txt
│ │ │ ├── 8.3.0.txt
│ │ │ ├── 8.3.1.txt
│ │ │ ├── 8.3.2.txt
│ │ │ ├── 8.3.3.txt
│ │ │ ├── 8.3.4,txt
│ │ │ ├── 8.3.5.txt
│ │ │ ├── 8.3.6.txt
│ │ │ ├── 8.4.0.txt
│ │ │ ├── 8.4.1.txt
│ │ │ ├── 8.4.2.txt
│ │ │ ├── 8.4.3.txt
│ │ │ ├── 8.4.4.txt
│ │ │ ├── 8.5.0.txt
│ │ │ ├── 8.5.1.txt
│ │ │ ├── 8.5.2.txt
│ │ │ ├── 8.6.0.txt
│ │ │ ├── 9.0.0.txt
│ │ │ ├── 9.1.0.txt
│ │ │ ├── 9.2.0.txt
│ │ │ ├── 9.3.0.txt
│ │ │ └── 9.4.0.txt
│ │ ├── full_description.txt
│ │ ├── short_description.txt
│ │ └── title.txt
│ ├── fi-FI/
│ │ ├── changelogs/
│ │ │ ├── 10.0.0.txt
│ │ │ ├── 10.1.0.txt
│ │ │ ├── 10.2.0.txt
│ │ │ ├── 10.3.0.txt
│ │ │ ├── 10.4.0.txt
│ │ │ ├── 11.0.0.txt
│ │ │ ├── 11.1.0.txt
│ │ │ ├── 11.2.0.txt
│ │ │ ├── 11.3.0.txt
│ │ │ ├── 12.0.0.txt
│ │ │ ├── 12.1.0.txt
│ │ │ ├── 12.2.0.txt
│ │ │ ├── 12.2.1.txt
│ │ │ └── 13.0.0.txt
│ │ ├── full_description.txt
│ │ ├── short_description.txt
│ │ └── title.txt
│ ├── iw-IL/
│ │ ├── changelogs/
│ │ │ ├── 10.0.0.txt
│ │ │ ├── 10.1.0.txt
│ │ │ ├── 10.2.0.txt
│ │ │ ├── 10.3.0.txt
│ │ │ ├── 10.4.0.txt
│ │ │ ├── 11.0.0.txt
│ │ │ ├── 11.1.0.txt
│ │ │ ├── 11.2.0.txt
│ │ │ ├── 11.3.0.txt
│ │ │ ├── 12.0.0.txt
│ │ │ ├── 12.1.0.txt
│ │ │ ├── 12.2.0.txt
│ │ │ ├── 12.2.1.txt
│ │ │ ├── 13.0.0.txt
│ │ │ ├── 13.0.1.txt
│ │ │ ├── 14.0.0.txt
│ │ │ ├── 15.0.0.txt
│ │ │ ├── 15.1.0.txt
│ │ │ ├── 15.1.1.txt
│ │ │ ├── 16.0.0.txt
│ │ │ ├── 16.0.1.txt
│ │ │ ├── 16.1.0.txt
│ │ │ ├── 17.0.0.txt
│ │ │ ├── 17.1.0.txt
│ │ │ ├── 17.2.0.txt
│ │ │ ├── 17.2.1.txt
│ │ │ ├── 17.2.2.txt
│ │ │ ├── 17.2.3.txt
│ │ │ ├── 17.3.0.txt
│ │ │ ├── 17.4.0.txt
│ │ │ ├── 18.0.0.txt
│ │ │ ├── 18.0.1.txt
│ │ │ ├── 18.1.0.txt
│ │ │ ├── 18.1.1.txt
│ │ │ ├── 18.1.2.txt
│ │ │ ├── 18.1.3.txt
│ │ │ ├── 18.2.0.txt
│ │ │ ├── 18.2.1.txt
│ │ │ ├── 18.3.0.txt
│ │ │ ├── 19.0.0.txt
│ │ │ ├── 19.1.0.txt
│ │ │ ├── 20.0.0.txt
│ │ │ ├── 20.0.1.txt
│ │ │ ├── 20.0.2.txt
│ │ │ ├── 20.1.0.txt
│ │ │ ├── 21.0.0.txt
│ │ │ ├── 21.1.0.txt
│ │ │ ├── 21.2.0.txt
│ │ │ ├── 22.0.0.txt
│ │ │ ├── 22.1.0.txt
│ │ │ ├── 23.0.0.txt
│ │ │ ├── 23.1.0.txt
│ │ │ ├── 23.2.0.txt
│ │ │ ├── 24.0.0.txt
│ │ │ ├── 24.1.0.txt
│ │ │ ├── 24.2.0.txt
│ │ │ ├── 24.3.0.txt
│ │ │ ├── 24.4.0.txt
│ │ │ ├── 25.0.0.txt
│ │ │ ├── 26.0.0.txt
│ │ │ ├── 26.0.1.txt
│ │ │ ├── 26.1.0.txt
│ │ │ ├── 26.1.1.txt
│ │ │ ├── 26.1.2.txt
│ │ │ ├── 27.0.0.txt
│ │ │ ├── 27.0.1.txt
│ │ │ ├── 28.0.0.txt
│ │ │ ├── 28.1.0.txt
│ │ │ ├── 28.1.1.txt
│ │ │ ├── 28.2.0.txt
│ │ │ ├── 29.0.0.txt
│ │ │ ├── 29.1.0.txt
│ │ │ ├── 29.2.0.txt
│ │ │ ├── 29.2.1.txt
│ │ │ ├── 3.2.0.txt
│ │ │ ├── 3.2.2.txt
│ │ │ ├── 30.0.0.txt
│ │ │ ├── 30.1.0.txt
│ │ │ ├── 30.2.0.txt
│ │ │ ├── 30.3.0.txt
│ │ │ ├── 37.4.0.txt
│ │ │ ├── 53.2.2.txt
│ │ │ └── 8.5.0.txt
│ │ ├── full_description.txt
│ │ ├── short_description.txt
│ │ └── title.txt
│ ├── nb-NO/
│ │ ├── changelogs/
│ │ │ ├── 10.2.0.txt
│ │ │ ├── 12.0.0.txt
│ │ │ ├── 12.2.1.txt
│ │ │ ├── 3.2.0.txt
│ │ │ ├── 3.2.2.txt
│ │ │ ├── 36.0.0.txt
│ │ │ ├── 36.1.2.txt
│ │ │ ├── 4.0.0.txt
│ │ │ ├── 4.1.0.txt
│ │ │ ├── 4.1.1.txt
│ │ │ ├── 4.1.2.txt
│ │ │ ├── 5.0.0.txt
│ │ │ ├── 5.1.0.txt
│ │ │ ├── 5.2.0.txt
│ │ │ ├── 5.2.1.txt
│ │ │ ├── 5.3.0.txt
│ │ │ ├── 5.3.1.txt
│ │ │ ├── 5.4.0.txt
│ │ │ ├── 5.4.1.txt
│ │ │ ├── 5.4.2.txt
│ │ │ ├── 6.0.0.txt
│ │ │ ├── 6.0.1.txt
│ │ │ ├── 6.0.2.txt
│ │ │ ├── 6.1.0.txt
│ │ │ ├── 6.2.0.txt
│ │ │ ├── 6.2.1.txt
│ │ │ ├── 6.3.0.txt
│ │ │ ├── 6.3.1.txt
│ │ │ ├── 6.4.0.txt
│ │ │ ├── 7.0.0.txt
│ │ │ ├── 7.1.0.txt
│ │ │ ├── 7.1.1.txt
│ │ │ ├── 7.1.2.txt
│ │ │ ├── 7.2.0.txt
│ │ │ ├── 7.3.0.txt
│ │ │ ├── 7.4.0.txt
│ │ │ ├── 8.0.0.txt
│ │ │ ├── 8.0.1.txt
│ │ │ ├── 8.0.2.txt
│ │ │ ├── 8.1.0.txt
│ │ │ ├── 8.2.0.txt
│ │ │ ├── 8.3.0.txt
│ │ │ ├── 8.3.1.txt
│ │ │ ├── 8.3.2.txt
│ │ │ ├── 8.3.3.txt
│ │ │ ├── 8.3.5.txt
│ │ │ ├── 8.3.6.txt
│ │ │ ├── 8.4.0.txt
│ │ │ ├── 8.4.1.txt
│ │ │ ├── 8.4.2.txt
│ │ │ ├── 8.4.3.txt
│ │ │ ├── 8.4.4.txt
│ │ │ ├── 8.5.0.txt
│ │ │ ├── 8.5.1.txt
│ │ │ ├── 8.5.2.txt
│ │ │ ├── 8.6.0.txt
│ │ │ ├── 9.0.0.txt
│ │ │ ├── 9.1.0.txt
│ │ │ ├── 9.2.0.txt
│ │ │ └── 9.3.0.txt
│ │ ├── full_description.txt
│ │ ├── short_description.txt
│ │ └── title.txt
│ ├── pl/
│ │ └── changelogs/
│ │ └── 15.1.1.txt
│ ├── ro/
│ │ ├── full_description.txt
│ │ ├── short_description.txt
│ │ └── title.txt
│ ├── ru-RU/
│ │ ├── changelogs/
│ │ │ ├── 10.0.0.txt
│ │ │ ├── 10.1.0.txt
│ │ │ ├── 10.2.0.txt
│ │ │ ├── 10.3.0.txt
│ │ │ ├── 10.4.0.txt
│ │ │ ├── 11.0.0.txt
│ │ │ ├── 11.1.0.txt
│ │ │ ├── 11.2.0.txt
│ │ │ ├── 11.3.0.txt
│ │ │ ├── 12.0.0.txt
│ │ │ ├── 12.1.0.txt
│ │ │ ├── 12.2.0.txt
│ │ │ ├── 12.2.1.txt
│ │ │ ├── 15.1.1.txt
│ │ │ ├── 17.2.1.txt
│ │ │ ├── 18.0.1.txt
│ │ │ ├── 18.1.1.txt
│ │ │ ├── 18.1.3.txt
│ │ │ ├── 18.2.0.txt
│ │ │ ├── 18.2.1.txt
│ │ │ ├── 18.3.0.txt
│ │ │ ├── 19.1.0.txt
│ │ │ ├── 20.0.2.txt
│ │ │ ├── 20.1.0.txt
│ │ │ ├── 22.0.0.txt
│ │ │ ├── 22.1.0.txt
│ │ │ ├── 23.2.0.txt
│ │ │ ├── 24.0.0.txt
│ │ │ ├── 24.4.0.txt
│ │ │ ├── 25.0.0.txt
│ │ │ ├── 26.0.0.txt
│ │ │ ├── 26.1.1.txt
│ │ │ ├── 26.1.2.txt
│ │ │ ├── 27.0.0.txt
│ │ │ ├── 27.0.1.txt
│ │ │ ├── 28.0.0.txt
│ │ │ ├── 28.1.1.txt
│ │ │ ├── 28.2.0.txt
│ │ │ ├── 29.2.1.txt
│ │ │ ├── 30.0.1.txt
│ │ │ ├── 30.1.0.txt
│ │ │ ├── 31.2.1.txt
│ │ │ ├── 32.1.0.txt
│ │ │ ├── 32.2.0.txt
│ │ │ ├── 34.1.0.txt
│ │ │ ├── 36.1.2.txt
│ │ │ ├── 37.0.0.txt
│ │ │ ├── 37.0.1.txt
│ │ │ ├── 37.3.0.txt
│ │ │ ├── 37.4.0.txt
│ │ │ ├── 4.1.2.txt
│ │ │ ├── 42.1.0.txt
│ │ │ ├── 44.2.0.txt
│ │ │ ├── 45.1.0.txt
│ │ │ ├── 45.1.2.txt
│ │ │ ├── 46.0.1.txt
│ │ │ ├── 49.1.0.txt
│ │ │ ├── 49.1.2.txt
│ │ │ ├── 49.1.3.txt
│ │ │ ├── 49.4.0.txt
│ │ │ ├── 5.3.1.txt
│ │ │ ├── 50.1.4.txt
│ │ │ ├── 50.1.5.txt
│ │ │ ├── 51.2.0.txt
│ │ │ ├── 53.0.0.txt
│ │ │ ├── 53.1.0.txt
│ │ │ ├── 53.1.1.txt
│ │ │ ├── 6.0.0.txt
│ │ │ ├── 6.0.2.txt
│ │ │ ├── 7.1.0.txt
│ │ │ ├── 7.1.1.txt
│ │ │ ├── 8.0.1.txt
│ │ │ ├── 8.0.2.txt
│ │ │ ├── 8.2.0.txt
│ │ │ ├── 8.3.0.txt
│ │ │ ├── 8.3.5.txt
│ │ │ ├── 8.4.0.txt
│ │ │ ├── 8.4.1.txt
│ │ │ ├── 8.4.3.txt
│ │ │ ├── 8.5.0.txt
│ │ │ ├── 8.5.2.txt
│ │ │ ├── 8.6.0.txt
│ │ │ ├── 9.0.0.txt
│ │ │ ├── 9.1.0.txt
│ │ │ ├── 9.2.0.txt
│ │ │ ├── 9.3.0.txt
│ │ │ └── 9.4.0.txt
│ │ ├── full_description.txt
│ │ ├── short_description.txt
│ │ └── title.txt
│ ├── sv/
│ │ ├── changelogs/
│ │ │ ├── 10.0.0.txt
│ │ │ ├── 10.1.0.txt
│ │ │ ├── 10.2.0.txt
│ │ │ ├── 10.3.0.txt
│ │ │ ├── 10.4.0.txt
│ │ │ ├── 11.0.0.txt
│ │ │ ├── 11.1.0.txt
│ │ │ ├── 11.2.0.txt
│ │ │ ├── 11.3.0.txt
│ │ │ ├── 12.0.0.txt
│ │ │ ├── 12.1.0.txt
│ │ │ ├── 12.2.0.txt
│ │ │ ├── 12.2.1.txt
│ │ │ ├── 13.0.0.txt
│ │ │ ├── 13.0.1.txt
│ │ │ ├── 14.0.0.txt
│ │ │ ├── 15.0.0.txt
│ │ │ ├── 15.1.0.txt
│ │ │ ├── 15.1.1.txt
│ │ │ ├── 16.0.0.txt
│ │ │ ├── 16.0.1.txt
│ │ │ ├── 16.1.0.txt
│ │ │ ├── 17.0.0.txt
│ │ │ ├── 17.1.0.txt
│ │ │ ├── 17.2.0.txt
│ │ │ ├── 17.2.1.txt
│ │ │ ├── 17.2.2.txt
│ │ │ ├── 17.2.3.txt
│ │ │ ├── 17.3.0.txt
│ │ │ ├── 17.4.0.txt
│ │ │ ├── 18.0.0.txt
│ │ │ ├── 18.0.1.txt
│ │ │ ├── 18.1.0.txt
│ │ │ ├── 18.1.1.txt
│ │ │ ├── 18.1.2.txt
│ │ │ ├── 18.1.3.txt
│ │ │ ├── 18.2.0.txt
│ │ │ ├── 18.2.1.txt
│ │ │ ├── 18.3.0.txt
│ │ │ ├── 19.0.0.txt
│ │ │ ├── 19.1.0.txt
│ │ │ ├── 20.0.0.txt
│ │ │ ├── 20.0.1.txt
│ │ │ ├── 20.0.2.txt
│ │ │ ├── 20.1.0.txt
│ │ │ ├── 21.0.0.txt
│ │ │ ├── 21.1.0.txt
│ │ │ ├── 21.2.0.txt
│ │ │ ├── 22.0.0.txt
│ │ │ ├── 22.1.0.txt
│ │ │ ├── 23.0.0.txt
│ │ │ ├── 23.1.0.txt
│ │ │ ├── 23.2.0.txt
│ │ │ ├── 24.0.0.txt
│ │ │ ├── 24.1.0.txt
│ │ │ ├── 24.2.0.txt
│ │ │ ├── 24.3.0.txt
│ │ │ ├── 24.4.0.txt
│ │ │ ├── 25.0.0.txt
│ │ │ ├── 26.0.0.txt
│ │ │ ├── 26.0.1.txt
│ │ │ ├── 26.1.0.txt
│ │ │ ├── 26.1.1.txt
│ │ │ ├── 26.1.2.txt
│ │ │ ├── 27.0.0.txt
│ │ │ ├── 27.0.1.txt
│ │ │ ├── 28.0.0.txt
│ │ │ ├── 28.1.0.txt
│ │ │ ├── 28.1.1.txt
│ │ │ ├── 28.2.0.txt
│ │ │ ├── 29.0.0.txt
│ │ │ ├── 29.1.0.txt
│ │ │ ├── 29.2.0.txt
│ │ │ ├── 29.2.1.txt
│ │ │ ├── 3.2.0.txt
│ │ │ ├── 3.2.2.txt
│ │ │ ├── 30.0.0.txt
│ │ │ ├── 30.0.1.txt
│ │ │ ├── 30.1.0.txt
│ │ │ ├── 30.2.0.txt
│ │ │ ├── 30.3.0.txt
│ │ │ ├── 31.0.0.txt
│ │ │ ├── 31.1.0.txt
│ │ │ ├── 31.2.0.txt
│ │ │ ├── 31.2.1.txt
│ │ │ ├── 32.0.0.txt
│ │ │ ├── 32.1.0.txt
│ │ │ ├── 32.2.0.txt
│ │ │ ├── 32.3.0.txt
│ │ │ ├── 33.0.0.txt
│ │ │ ├── 34.0.0.txt
│ │ │ ├── 34.1.0.txt
│ │ │ ├── 35.0.0.txt
│ │ │ ├── 35.1.0.txt
│ │ │ ├── 35.2.0.txt
│ │ │ ├── 36.0.0.txt
│ │ │ ├── 36.1.0.txt
│ │ │ ├── 36.1.1.txt
│ │ │ ├── 36.1.2.txt
│ │ │ ├── 36.2.0.txt
│ │ │ ├── 37.0.0.txt
│ │ │ ├── 37.0.1.txt
│ │ │ ├── 37.1.0.txt
│ │ │ ├── 37.2.0.txt
│ │ │ ├── 37.3.0.txt
│ │ │ ├── 37.4.0.txt
│ │ │ ├── 38.0.0.txt
│ │ │ ├── 39.0.0.txt
│ │ │ ├── 39.1.0.txt
│ │ │ ├── 4.0.0.txt
│ │ │ ├── 4.1.0.txt
│ │ │ ├── 4.1.1.txt
│ │ │ ├── 4.1.2.txt
│ │ │ ├── 40.0.0.txt
│ │ │ ├── 40.0.1.txt
│ │ │ ├── 40.1.0.txt
│ │ │ ├── 41.0.0.txt
│ │ │ ├── 42.0.0.txt
│ │ │ ├── 42.1.0.txt
│ │ │ ├── 42.2.0.txt
│ │ │ ├── 42.3.0.txt
│ │ │ ├── 43.0.0.txt
│ │ │ ├── 43.0.2.txt
│ │ │ ├── 43.1.0.txt
│ │ │ ├── 44.0.0.txt
│ │ │ ├── 44.1.0.txt
│ │ │ ├── 44.2.0.txt
│ │ │ ├── 446.txt
│ │ │ ├── 447.txt
│ │ │ ├── 448.txt
│ │ │ ├── 449.txt
│ │ │ ├── 45.0.0.txt
│ │ │ ├── 45.1.0.txt
│ │ │ ├── 45.1.1.txt
│ │ │ ├── 45.1.2.txt
│ │ │ ├── 46.0.0.txt
│ │ │ ├── 46.0.1.txt
│ │ │ ├── 46.1.0.txt
│ │ │ ├── 46.1.1.txt
│ │ │ ├── 46.2.0.txt
│ │ │ ├── 47.0.0.txt
│ │ │ ├── 47.1.0.txt
│ │ │ ├── 47.2.0.txt
│ │ │ ├── 48.0.0.txt
│ │ │ ├── 48.0.1.txt
│ │ │ ├── 48.1.0.txt
│ │ │ ├── 48.2.0.txt
│ │ │ ├── 49.0.0.txt
│ │ │ ├── 49.0.1.txt
│ │ │ ├── 49.1.0.txt
│ │ │ ├── 49.1.2.txt
│ │ │ ├── 49.1.3.txt
│ │ │ ├── 49.2.0.txt
│ │ │ ├── 49.3.1.txt
│ │ │ ├── 49.4.0.txt
│ │ │ ├── 5.0.0.txt
│ │ │ ├── 5.1.0.txt
│ │ │ ├── 5.2.0.txt
│ │ │ ├── 5.2.1.txt
│ │ │ ├── 5.3.0.txt
│ │ │ ├── 5.3.1.txt
│ │ │ ├── 5.4.0.txt
│ │ │ ├── 5.4.1.txt
│ │ │ ├── 5.4.2.txt
│ │ │ ├── 50.0.0.txt
│ │ │ ├── 50.1.0.txt
│ │ │ ├── 50.1.1.txt
│ │ │ ├── 50.1.3.txt
│ │ │ ├── 50.1.4.txt
│ │ │ ├── 50.1.5.txt
│ │ │ ├── 50.2.0.txt
│ │ │ ├── 50.2.1.txt
│ │ │ ├── 50.2.2.txt
│ │ │ ├── 51.0.0.txt
│ │ │ ├── 51.1.0.txt
│ │ │ ├── 51.2.0.txt
│ │ │ ├── 52.0.0.txt
│ │ │ ├── 52.1.0.txt
│ │ │ ├── 52.2.0.txt
│ │ │ ├── 53.0.0.txt
│ │ │ ├── 53.1.0.txt
│ │ │ ├── 53.1.1.txt
│ │ │ ├── 53.1.2.txt
│ │ │ ├── 53.2.0.txt
│ │ │ ├── 53.2.1.txt
│ │ │ ├── 53.2.2.txt
│ │ │ ├── 53.2.3.txt
│ │ │ ├── 54.0.0.txt
│ │ │ ├── 54.1.0.txt
│ │ │ ├── 54.2.0.txt
│ │ │ ├── 55.0.0.txt
│ │ │ ├── 55.0.1.txt
│ │ │ ├── 55.0.2.txt
│ │ │ ├── 55.1.0.txt
│ │ │ ├── 55.2.0.txt
│ │ │ ├── 56.0.0.txt
│ │ │ ├── 56.1.0.txt
│ │ │ ├── 56.2.0.txt
│ │ │ ├── 56.3.0.txt
│ │ │ ├── 57.0.0.txt
│ │ │ ├── 57.1.0.txt
│ │ │ ├── 57.1.1.txt
│ │ │ ├── 57.2.0.txt
│ │ │ ├── 58.0.0.txt
│ │ │ ├── 59.0.0.txt
│ │ │ ├── 59.0.1.txt
│ │ │ ├── 59.0.2.txt
│ │ │ ├── 59.0.3.txt
│ │ │ ├── 59.0.4.txt
│ │ │ ├── 59.1.0.txt
│ │ │ ├── 59.2.0.txt
│ │ │ ├── 59.3.0.txt
│ │ │ ├── 59.4.0.txt
│ │ │ ├── 59.4.1.txt
│ │ │ ├── 59.4.2.txt
│ │ │ ├── 59.4.3.txt
│ │ │ ├── 59.6.0.txt
│ │ │ ├── 59.7.0.txt
│ │ │ ├── 6.0.0.txt
│ │ │ ├── 6.0.1.txt
│ │ │ ├── 6.0.2.txt
│ │ │ ├── 6.1.0.txt
│ │ │ ├── 6.2.0.txt
│ │ │ ├── 6.2.1.txt
│ │ │ ├── 6.3.0.txt
│ │ │ ├── 6.3.1.txt
│ │ │ ├── 6.4.0.txt
│ │ │ ├── 60.0.0.txt
│ │ │ ├── 60.1.0.txt
│ │ │ ├── 60.2.0.txt
│ │ │ ├── 60.3.0.txt
│ │ │ ├── 60.4.0.txt
│ │ │ ├── 60.4.1.txt
│ │ │ ├── 60.4.2.txt
│ │ │ ├── 61.0.0.txt
│ │ │ ├── 61.0.1.txt
│ │ │ ├── 61.1.0.txt
│ │ │ ├── 62.0.0.txt
│ │ │ ├── 62.1.0.txt
│ │ │ ├── 63.0.0.txt
│ │ │ ├── 63.1.0.txt
│ │ │ ├── 63.2.0.txt
│ │ │ ├── 63.2.1.txt
│ │ │ ├── 63.2.2.txt
│ │ │ ├── 63.2.3.txt
│ │ │ ├── 63.2.5.txt
│ │ │ ├── 63.3.0.txt
│ │ │ ├── 64.0.0.txt
│ │ │ ├── 64.1.0.txt
│ │ │ ├── 64.2.0.txt
│ │ │ ├── 64.3.0.txt
│ │ │ ├── 64.3.1.txt
│ │ │ ├── 65.0.0.txt
│ │ │ ├── 65.1.0.txt
│ │ │ ├── 65.2.0.txt
│ │ │ ├── 65.2.1.txt
│ │ │ ├── 65.3.0.txt
│ │ │ ├── 66.0.0.txt
│ │ │ ├── 66.1.0.txt
│ │ │ ├── 66.1.1.txt
│ │ │ ├── 66.1.2.txt
│ │ │ ├── 66.1.3.txt
│ │ │ ├── 66.1.4.txt
│ │ │ ├── 66.1.5.txt
│ │ │ ├── 66.1.6.txt
│ │ │ ├── 67.0.0.txt
│ │ │ ├── 67.0.1.txt
│ │ │ ├── 67.0.2.txt
│ │ │ ├── 67.0.3.txt
│ │ │ ├── 67.1.0.txt
│ │ │ ├── 67.2.0.txt
│ │ │ ├── 68.0.0.txt
│ │ │ ├── 68.1.0.txt
│ │ │ ├── 68.1.1.txt
│ │ │ ├── 68.1.2.txt
│ │ │ ├── 7.0.0.txt
│ │ │ ├── 7.1.0.txt
│ │ │ ├── 7.1.1.txt
│ │ │ ├── 7.1.2.txt
│ │ │ ├── 7.2.0.txt
│ │ │ ├── 7.3.0.txt
│ │ │ ├── 7.4.0.txt
│ │ │ ├── 8.0.0.txt
│ │ │ ├── 8.0.1.txt
│ │ │ ├── 8.0.2.txt
│ │ │ ├── 8.1.0.txt
│ │ │ ├── 8.2.0.txt
│ │ │ ├── 8.3.0.txt
│ │ │ ├── 8.3.1.txt
│ │ │ ├── 8.3.2.txt
│ │ │ ├── 8.3.3.txt
│ │ │ ├── 8.3.5.txt
│ │ │ ├── 8.3.6.txt
│ │ │ ├── 8.4.0.txt
│ │ │ ├── 8.4.1.txt
│ │ │ ├── 8.4.2.txt
│ │ │ ├── 8.4.3.txt
│ │ │ ├── 8.4.4.txt
│ │ │ ├── 8.5.0.txt
│ │ │ ├── 8.5.1.txt
│ │ │ ├── 8.5.2.txt
│ │ │ ├── 8.6.0.txt
│ │ │ ├── 9.0.0.txt
│ │ │ ├── 9.1.0.txt
│ │ │ ├── 9.2.0.txt
│ │ │ ├── 9.3.0.txt
│ │ │ └── 9.4.0.txt
│ │ ├── full_description.txt
│ │ ├── short_description.txt
│ │ └── title.txt
│ └── zh-CN/
│ ├── full_description.txt
│ ├── short_description.txt
│ └── title.txt
├── gradle/
│ ├── libs.versions.toml
│ └── wrapper/
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle.kts
================================================
FILE CONTENTS
================================================
================================================
FILE: .clang-format
================================================
---
Language: Cpp
# BasedOnStyle: LLVM
AccessModifierOffset: -4
AlignAfterOpenBracket: DontAlign
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlinesLeft: true
AlignOperands: true
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: false
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: true
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
BreakBeforeBinaryOperators: NonAssignment
BreakBeforeBraces: Mozilla
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
ColumnLimit: 100
CommentPragmas: '^ IWYU pragma:'
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 8
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
- Regex: '^(<|"(gtest|isl|json)/)'
Priority: 3
- Regex: '.*'
Priority: 1
IndentCaseLabels: true
IndentWidth: 4
IndentWrappedFunctionNames: false
IndentPPDirectives: BeforeHash
KeepEmptyLinesAtTheStartOfBlocks: true
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 2
NamespaceIndentation: None
ObjCBlockIndentWidth: 4
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right
ReflowComments: false
SortIncludes: false
SpaceAfterCStyleCast: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp11
UseTab: Never
TabWidth: 4
...
================================================
FILE: .gitignore
================================================
/.idea
local.properties
.DS_Store
*~
*.iml
*.orig
*.rej
java_*
.gradle
/local.properties
build
/captures
.externalNativeBuild
/app/release
/app/.cxx
gradle/wrapper/gradle-wrapper.jar
/distribution/*
/distribution.video/*
================================================
FILE: .gitmodules
================================================
[submodule "libbaresip-android"]
path = libbaresip-android
url = https://github.com/juha-h/libbaresip-android.git
branch = master
================================================
FILE: LICENSE
================================================
BSD 3-Clause License
Copyright (c) 2018, TutPro Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: PrivacyPolicy.txt
================================================
baresip Privacy Policy
Last updated: Sept 25, 2022
baresip 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.
You 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.
Use https://github.com/juha-h/baresip-studio/discussions page for questions regarding baresip app's privacy policy.
================================================
FILE: README.md
================================================
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.
Currently 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).
If 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.
After 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>.
Then in Android Studio (tested with Android Studio Panda 2 | 2025.3.2):
- Open an existing Android Studio project
- File -> Invalidate Caches ... -> Invalidate & Restart
- Build -> Generate Signed Bundle / APK ...
Ready 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.
Language translations are managed via baresip <a href="https://hosted.weblate.org/projects/baresip/">Weblate</a> project.
Copyright (c) 2018 TutPro Inc. Distributed under BSD-3-Clause license.
================================================
FILE: app/build.gradle.kts
================================================
import com.android.build.api.dsl.ApplicationExtension
plugins {
alias(libs.plugins.compose.compiler)
alias(libs.plugins.kotlin.serialization)
id("com.android.application")
}
configure<ApplicationExtension> {
compileSdk = 36
ndkVersion = "29.0.14206865"
defaultConfig {
applicationId = "com.tutpro.baresip"
minSdk = 28
versionCode = 492
versionName = "78.1.1"
@Suppress("UnstableApiUsage")
externalNativeBuild {
cmake {
cFlags += "-DHAVE_INTTYPES_H -lstdc++"
arguments.addAll(listOf("-DANDROID_STL=c++_shared"))
}
}
ndk {
// noinspection ChromeOsAbiSupport
abiFilters.addAll(listOf("armeabi-v7a", "arm64-v8a"))
}
vectorDrawables.useSupportLibrary = true
}
buildTypes {
debug {
ndk {
abiFilters.add("x86_64")
}
}
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
buildFeatures {
viewBinding = true
buildConfig = true
compose = true
}
externalNativeBuild {
cmake {
path = file("src/main/cpp/CMakeLists.txt")
version = "3.31.6"
}
}
namespace = "com.tutpro.baresip"
}
composeCompiler {
reportsDestination = layout.buildDirectory.dir("compose_compiler")
}
dependencies {
implementation(libs.androidx.foundation.android)
implementation(libs.androidx.runtime.livedata)
implementation(libs.androidx.compose.material3)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.ui)
implementation(libs.kotlin.stdlib.jdk8)
implementation(libs.material)
implementation(libs.androidx.preference.ktx)
implementation(libs.androidx.exifinterface)
implementation(libs.androidx.core.ktx)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.kotlinx.serialization.json)
implementation(libs.androidx.activity.ktx)
implementation(libs.androidx.fragment.ktx)
implementation(libs.androidx.media)
implementation(libs.coil.compose)
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.navigation.runtime.android)
implementation(libs.androidx.lifecycle.process)
implementation(libs.androidx.lifecycle.viewmodel.ktx)
implementation(libs.androidx.compose.material.icons.core)
implementation(libs.androidx.compose.material.icons.extended)
implementation(libs.androidx.compose.runtime)
}
================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/gfan/dev/sdk_current/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
-keepattributes LineNumberTable,SourceFile
-dontobfuscate
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"
tools:ignore="ForegroundServicesPolicy" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" tools:ignore="FullScreenIntentPolicy" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-feature
android:name="android.hardware.telephony"
android:required="false" />
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<application
android:name=".BaresipApp"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:installLocation="internalOnly"
android:label="@string/app_name"
android:supportsRtl="true"
android:largeHeap="true"
android:enableOnBackInvokedCallback="true"
android:theme="@style/Theme.Material3.DayNight.NoActionBar"
tools:remove="android:appComponentFactory"
tools:targetApi="33">
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:exported="true"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize"
android:showWhenLocked="true"
android:turnScreenOn="true" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.DIAL" />
<action android:name="android.intent.action.CALL" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.DIAL" />
<action android:name="android.intent.action.CALL" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="tel" />
<data android:scheme="sip" />
</intent-filter>
</activity>
<service
android:name=".BaresipService"
android:enabled="true"
android:foregroundServiceType="microphone|phoneCall|specialUse"
android:stopWithTask="false" >
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="Maintaining SIP registration for incoming VoIP calls" />
</service>
<service
android:name=".ConnectionService"
android:enabled="true"
android:exported="true"
android:label="@string/app_name"
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" >
<intent-filter>
<action android:name="android.telecom.ConnectionService" />
</intent-filter>
</service>
<service
android:name=".InCallService"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BIND_INCALL_SERVICE" >
<meta-data
android:name="android.telecom.IN_CALL_SERVICE_UI"
android:value="true" />
<meta-data
android:name="android.telecom.IN_CALL_SERVICE_CAR_MODE_UI"
android:value="false" />
<intent-filter>
<action android:name="android.telecom.InCallService" />
</intent-filter>
</service>
<receiver
android:name=".BootCompletedReceiver"
android:enabled="true"
android:exported="false"
android:permission="android.permission.RECEIVE_BOOT_COMPLETED" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter>
</receiver>
<receiver
android:name=".TaskReceiver"
android:enabled="true"
android:exported="true"
tools:ignore="ExportedReceiver" >
<intent-filter>
<action android:name="com.tutpro.baresip.REGISTER" />
<action android:name="com.tutpro.baresip.UNREGISTER" />
<action android:name="com.tutpro.baresip.QUIT" />
</intent-filter>
</receiver>
</application>
</manifest>
================================================
FILE: app/src/main/assets/accounts
================================================
================================================
FILE: app/src/main/assets/config.static
================================================
poll_method epoll
call_local_timeout 120
call_max_calls 4
call_hold_other_calls yes
filter_registrar udp,tcp,tls,ws,wss
audio_player aaudio,nil
audio_source aaudio,nil
audio_alert aaudio,nil
audio_level no
ausrc_format s16
auplay_format s16
auenc_format s16
audec_format s16
audio_buffer 20-160
audio_buffer_mode adaptive
audio_silence -35.0
audio_telev_pt 101
audio_jitter_buffer_type adaptive
audio_jitter_buffer_ms 100-200
audio_jitter_buffer_size 50
rtp_stats yes
rtp_timeout 60
rtp_rxmode thread
module aaudio.so
module stun.so
module turn.so
module ice.so
module srtp.so
module dtls_srtp.so
module gzrtp.so
module uuid.so
module_app account.so
module_app debug_cmd.so
module_app mwi.so
opus_samplerate 16000
opus_stereo no
opus_sprop_stereo no
opus_cbr no
opus_inbandfec yes
opus_application voip
dtls_srtp_use_ec prime256v1
================================================
FILE: app/src/main/assets/contacts
================================================
"The Test Call" <sip:thetestcall@sip2sip.info>
================================================
FILE: app/src/main/cpp/CMakeLists.txt
================================================
cmake_minimum_required(VERSION 3.18...4.0)
project(baresip)
add_link_options("LINKER:--build-id=none")
set(distribution_DIR ${CMAKE_SOURCE_DIR}/../../../../distribution)
add_library(lib_crypto STATIC IMPORTED)
set_target_properties(lib_crypto PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/openssl/lib/${ANDROID_ABI}/libcrypto.a)
add_library(lib_ssl STATIC IMPORTED)
set_target_properties(lib_ssl PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/openssl/lib/${ANDROID_ABI}/libssl.a)
add_library(lib_re STATIC IMPORTED)
set_target_properties(lib_re PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/re/lib/${ANDROID_ABI}/libre.a)
add_library(lib_opus STATIC IMPORTED)
set_target_properties(lib_opus PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/opus/lib/${ANDROID_ABI}/libopus.a)
add_library(lib_g722 STATIC IMPORTED)
set_target_properties(lib_g722 PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/g722/lib/${ANDROID_ABI}/libg722.a)
add_library(lib_g722_1 STATIC IMPORTED)
set_target_properties(lib_g722_1 PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/g7221/lib/${ANDROID_ABI}/libg722_1.a)
add_library(lib_g729 STATIC IMPORTED)
set_target_properties(lib_g729 PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/g729/lib/${ANDROID_ABI}/libbcg729.a)
add_library(lib_codec2 STATIC IMPORTED)
set_target_properties(lib_codec2 PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/codec2/lib/${ANDROID_ABI}/libcodec2.a)
add_library(lib_amrnb STATIC IMPORTED)
set_target_properties(lib_amrnb PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/amr/lib/${ANDROID_ABI}/libamrnb.a)
add_library(lib_amrwb STATIC IMPORTED)
set_target_properties(lib_amrwb PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/amr/lib/${ANDROID_ABI}/libamrwb.a)
add_library(lib_amrwbenc STATIC IMPORTED)
set_target_properties(lib_amrwbenc PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/amr/lib/${ANDROID_ABI}/libamrwbenc.a)
add_library(lib_zrtpcppcore STATIC IMPORTED)
set_target_properties(lib_zrtpcppcore PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/gzrtp/lib/${ANDROID_ABI}/libzrtpcppcore.a)
add_library(lib_sndfile STATIC IMPORTED)
set_target_properties(lib_sndfile PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/sndfile/lib/${ANDROID_ABI}/libsndfile.a)
add_library(lib_baresip STATIC IMPORTED)
set_target_properties(lib_baresip PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/baresip/lib/${ANDROID_ABI}/libbaresip.a)
add_library(baresip SHARED ${CMAKE_SOURCE_DIR}/baresip.c)
target_include_directories(baresip PRIVATE
${distribution_DIR}/openssl/include
${distribution_DIR}/re/include
${distribution_DIR}/baresip/include)
add_definitions(-DHAVE_PTHREAD)
target_link_libraries(
baresip
android
aaudio
lib_baresip
lib_re
lib_ssl
lib_crypto
lib_opus
lib_g722
lib_g722_1
lib_g729
lib_codec2
lib_amrnb
lib_amrwb
lib_amrwbenc
lib_zrtpcppcore
lib_sndfile
z
log)
================================================
FILE: app/src/main/cpp/baresip.c
================================================
#include <string.h>
#include <pthread.h>
#include <jni.h>
#include <aaudio/AAudio.h>
#include <stdlib.h>
#include <re.h>
#include <baresip.h>
#include "logger.h"
enum
{
ASYNC_WORKERS = 4
};
typedef struct baresip_context
{
JavaVM *javaVM;
jclass mainActivityClz;
jobject mainActivityObj;
} BaresipContext;
BaresipContext g_ctx;
static int vprintf_null(const char *p, size_t size, void *arg)
{
(void)p;
(void)size;
(void)arg;
return 0;
}
static void net_debug_log()
{
char debug_buf[2048];
int l;
l = re_snprintf(&(debug_buf[0]), 2047, "%H", net_debug, baresip_network());
if (l != -1) {
debug_buf[l] = '\0';
LOGD("%s\n", debug_buf);
}
}
static void net_dns_debug_log()
{
char debug_buf[2048];
int l;
LOGD("net_dns_debug_log\n");
l = re_snprintf(&(debug_buf[0]), 2047, "%H", net_dns_debug, baresip_network());
if (l != -1) {
debug_buf[l] = '\0';
LOGD("%s\n", debug_buf);
}
}
static void ua_debug_log(struct ua *ua)
{
char debug_buf[2048];
int l;
l = re_snprintf(&(debug_buf[0]), 2047, "%H", ua_debug, ua);
if (l != -1) {
debug_buf[l] = '\0';
LOGD("%s\n", debug_buf);
}
}
static void account_debug_log(struct account *acc)
{
char debug_buf[2048];
int l;
l = re_snprintf(&(debug_buf[0]), 2047, "%H", account_debug, acc);
if (l != -1) {
debug_buf[l] = '\0';
LOGD("%s\n", debug_buf);
}
}
#if 0
static void ua_print_status_log(struct ua *ua)
{
char debug_buf[2048];
int l;
l = re_snprintf(&(debug_buf[0]), 2047, "%H", ua_print_status, ua);
if (l != -1) {
debug_buf[l] = '\0';
LOGD("%s\n", debug_buf);
}
}
#endif
static struct re_printf pf_null = {vprintf_null, 0};
static void signal_handler(int sig)
{
static bool term = false;
if (term) {
module_app_unload();
mod_close();
exit(0);
}
term = true;
LOGI("terminated by signal (%d)\n", sig);
ua_stop_all(false);
}
static void ua_exit_handler(void *arg)
{
(void)arg;
LOGD("ua exited -- stopping main runloop\n");
re_cancel();
}
static const char *bevent_reg_str(enum bevent_ev ev)
{
switch (ev) {
case BEVENT_REGISTERING:
return "registering";
case BEVENT_REGISTER_OK:
case BEVENT_FALLBACK_OK:
return "registered";
case BEVENT_REGISTER_FAIL:
case BEVENT_FALLBACK_FAIL:
return "registering failed";
case BEVENT_UNREGISTERING:
return "unregistering";
default:
return "?";
}
}
static const char *translate_errorcode(uint16_t scode)
{
switch (scode) {
case 404:
return ""; /* ignore */
case 486:
case 603:
return "busy";
case 487:
return ""; /* ignore */
default:
return "error";
}
}
static void event_handler(enum bevent_ev ev, struct bevent *event, void *arg)
{
(void)arg;
const char *prm = bevent_get_text(event);
struct call *call = bevent_get_call(event);
struct ua *ua = bevent_get_ua(event);
const struct sip_msg *msg = bevent_get_msg(event);
struct account *acc = ua_account(bevent_get_ua(event));
const char *tone;
char event_buf[256];
enum sdp_dir ardir;
int len, err;
struct pl module, module_event, data;
LOGD("ua event (%s) %s\n", bevent_str(ev), prm);
switch (ev) {
case BEVENT_CREATE:
len = re_snprintf(event_buf, sizeof event_buf, "create", "");
break;
case BEVENT_REGISTERING:
case BEVENT_UNREGISTERING:
case BEVENT_REGISTER_OK:
case BEVENT_FALLBACK_OK:
len = re_snprintf(event_buf, sizeof event_buf, "%s", bevent_reg_str(ev));
break;
case BEVENT_REGISTER_FAIL:
case BEVENT_FALLBACK_FAIL:
len = re_snprintf(event_buf, sizeof event_buf, "registering failed,%s", prm);
break;
case BEVENT_SIPSESS_CONN:
ua = uag_find_msg(msg);
// There is no call yet and call is thus used to hold SIP message
call = (struct call *)msg;
len = re_snprintf(event_buf, sizeof event_buf, "%s,%r,%ld", prm, &msg->from.auri, (long)event);
break;
case BEVENT_CALL_INCOMING:
len = re_snprintf(event_buf, sizeof event_buf, "call incoming,%s", prm);
break;
case BEVENT_CALL_OUTGOING:
len = re_snprintf(event_buf, sizeof event_buf, "call outgoing", "");
break;
case BEVENT_CALL_ANSWERED:
len = re_snprintf(event_buf, sizeof event_buf, "call answered", "");
break;
case BEVENT_CALL_REDIRECT:
len = re_snprintf(event_buf, sizeof event_buf, "call redirect,%s", prm + 4);
break;
case BEVENT_CALL_LOCAL_SDP:
if (strcmp(prm, "offer") == 0)
return;
len = re_snprintf(event_buf, sizeof event_buf, "call %sed", prm);
break;
case BEVENT_CALL_RINGING:
len = re_snprintf(event_buf, sizeof event_buf, "call ringing", "");
break;
case BEVENT_CALL_PROGRESS:
ardir = sdp_media_rdir(stream_sdpmedia(audio_strm(call_audio(call))));
len = re_snprintf(event_buf, sizeof event_buf, "call progress,%d", ardir);
break;
case BEVENT_CALL_ESTABLISHED:
len = re_snprintf(event_buf, sizeof event_buf, "call established", "");
break;
case BEVENT_CALL_REMOTE_SDP:
ardir = sdp_media_rdir(stream_sdpmedia(audio_strm(call_audio(call))));
len = re_snprintf(event_buf, sizeof event_buf, "call update,%d,%ld", ardir, (long)event);
break;
case BEVENT_CALL_MENC:
if (prm[0] == '0')
len = re_snprintf(event_buf, sizeof event_buf, "call secure", "");
else if (prm[0] == '1')
len = re_snprintf(event_buf, sizeof event_buf, "call verify,%s", prm + 2);
else if (prm[0] == '2')
len = re_snprintf(event_buf, sizeof event_buf, "call verified,%s", prm + 2);
else
len = re_snprintf(event_buf, sizeof event_buf, "unknown menc event", "");
break;
case BEVENT_CALL_TRANSFER:
len = re_snprintf(event_buf, sizeof event_buf, "call transfer,%s", prm);
break;
case BEVENT_CALL_TRANSFER_FAILED:
call_hold(call, false);
len = re_snprintf(event_buf, sizeof event_buf, "transfer failed,%s", prm);
break;
case BEVENT_CALL_CLOSED:
tone = call_scode(call) ? translate_errorcode(call_scode(call)) : "";
len = re_snprintf(event_buf, sizeof event_buf, "call closed,%s,%s", prm, tone);
break;
case BEVENT_MWI_NOTIFY:
len = re_snprintf(event_buf, sizeof event_buf, "mwi notify,%s", prm);
break;
case BEVENT_MODULE:
err = re_regex(prm, strlen(prm), "[^,]*,[^,]*,[~]*", &module, &module_event, &data);
if (err)
return;
if (!pl_strcmp(&module_event, "dump")) {
len = re_snprintf(event_buf, sizeof event_buf, "sndfile dump,%r", &data);
break;
}
if (!pl_strcmp(&module_event, "recorder sessionid")) {
len = re_snprintf(event_buf, sizeof event_buf, "recorder sessionid,%r", &data);
break;
}
default:
return;
}
if (len == -1) {
LOGE("failed to print event to buffer\n");
return;
}
JavaVM *javaVM = g_ctx.javaVM;
JNIEnv *env;
jint res = (*javaVM)->GetEnv(javaVM, (void**)&env, JNI_VERSION_1_6);
if (res != JNI_OK) {
LOGD("failed to get javaVM environment, ErrorCode = %d\n", res);
res = (*javaVM)->AttachCurrentThread(javaVM, &env, NULL);
if (JNI_OK != res) {
LOGE("failed to AttachCurrentThread, ErrorCode = %d\n", res);
return;
}
}
jmethodID methodId =
(*env)->GetMethodID(env, g_ctx.mainActivityClz, "uaEvent", "(Ljava/lang/String;JJ)V");
jstring jEvent = (*env)->NewStringUTF(env, event_buf);
LOGD("sending ua/call %ld/%ld event %s\n", (long)ua, (long)call, event_buf);
(*env)->CallVoidMethod(env, g_ctx.mainActivityObj, methodId, jEvent, (jlong)ua, (jlong)call);
(*env)->DeleteLocalRef(env, jEvent);
}
static void message_handler(
struct ua *ua, const struct pl *peer, const struct pl *ctype, struct mbuf *body, void *arg)
{
(void)arg;
char ctype_buf[128];
char peer_buf[256];
size_t size;
if (snprintf(peer_buf, 256, "%.*s", (int)peer->l, peer->p) >= 256) {
LOGE("message peer is too long (max 255 characters)\n");
return;
}
JavaVM *javaVM = g_ctx.javaVM;
JNIEnv *env;
jint res = (*javaVM)->GetEnv(javaVM, (void **)&env, JNI_VERSION_1_6);
if (res != JNI_OK) {
res = (*javaVM)->AttachCurrentThread(javaVM, &env, NULL);
if (JNI_OK != res) {
LOGE("failed to AttachCurrentThread, ErrorCode = %d\n", res);
return;
}
}
jmethodID methodId = (*env)->GetMethodID(env, g_ctx.mainActivityClz, "messageEvent",
"(JLjava/lang/String;Ljava/lang/String;[B)V");
jstring jPeer = (*env)->NewStringUTF(env, peer_buf);
pl_strcpy(ctype, ctype_buf, 256);
jstring jCtype = (*env)->NewStringUTF(env, ctype_buf);
jbyteArray jMsg;
size = mbuf_get_left(body);
jMsg = (*env)->NewByteArray(env, (jsize)size);
if ((*env)->GetArrayLength(env, jMsg) != size) {
(*env)->DeleteLocalRef(env, jMsg);
jMsg = (*env)->NewByteArray(env, (jsize)size);
}
void *temp = (*env)->GetPrimitiveArrayCritical(env, (jarray)jMsg, 0);
memcpy(temp, mbuf_buf(body), size);
(*env)->ReleasePrimitiveArrayCritical(env, jMsg, temp, 0);
LOGD("sending message %ld/%s/%s/%.*s\n", (long)ua, peer_buf, ctype_buf, (int)size,
mbuf_buf(body));
(*env)->CallVoidMethod(env, g_ctx.mainActivityObj, methodId, (jlong)ua, jPeer, jCtype, jMsg);
(*env)->DeleteLocalRef(env, jCtype);
(*env)->DeleteLocalRef(env, jPeer);
(*env)->DeleteLocalRef(env, jMsg);
}
static void send_resp_handler(int err, const struct sip_msg *msg, void *arg)
{
(void)arg;
char reason_buf[64];
if (err) {
LOGD("send_response_handler received error %d\n", err);
return;
}
pl_strcpy(&(msg->reason), reason_buf, 64);
LOGD("send_response_handler received response '%u %s' at %s\n", msg->scode, reason_buf,
(char *)arg);
JavaVM *javaVM = g_ctx.javaVM;
JNIEnv *env;
jint res = (*javaVM)->GetEnv(javaVM, (void **)&env, JNI_VERSION_1_6);
if (res != JNI_OK) {
res = (*javaVM)->AttachCurrentThread(javaVM, &env, NULL);
if (JNI_OK != res) {
LOGE("failed to AttachCurrentThread, ErrorCode = %d\n", res);
return;
}
}
jmethodID methodId = (*env)->GetMethodID(env, g_ctx.mainActivityClz, "messageResponse",
"(ILjava/lang/String;Ljava/lang/String;)V");
jstring javaReason = (*env)->NewStringUTF(env, reason_buf);
jstring javaTime = (*env)->NewStringUTF(env, (char *)arg);
(*env)->CallVoidMethod(env, g_ctx.mainActivityObj, methodId, msg->scode, javaReason, javaTime);
(*env)->DeleteLocalRef(env, javaReason);
(*env)->DeleteLocalRef(env, javaTime);
}
enum
{
ID_UA_STOP_ALL
};
static struct mqueue *mq;
static void mqueue_handler(int id, void *data, void *arg)
{
(void)arg;
if (id == ID_UA_STOP_ALL) {
LOGD("calling ua_stop_all with force %u\n", (unsigned)(uintptr_t)data);
ua_stop_all((bool)(uintptr_t)data);
}
}
#include <unistd.h>
static int pfd[2];
static pthread_t loggingThread;
static void *loggingFunction(void *arg)
{
(void)arg;
ssize_t readSize;
char buf[128];
while ((readSize = read(pfd[0], buf, sizeof buf - 1)) > 0) {
if (buf[readSize - 1] == '\n') {
--readSize;
}
buf[readSize] = 0;
LOGD("%s", buf);
}
return 0;
}
static int runLoggingThread()
{
setvbuf(stdout, 0, _IOLBF, 0);
setvbuf(stderr, 0, _IONBF, 0);
pipe(pfd);
dup2(pfd[1], 1);
dup2(pfd[1], 2);
int ret = pthread_create(&loggingThread, NULL, loggingFunction, NULL);
if (ret != 0) {
LOGE("failed to create logging thread: %d", ret);
return ret;
}
pthread_detach(loggingThread);
return 0;
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
{
(void)reserved;
LOGD("at JNI_OnLoad\n");
memset(&g_ctx, 0, sizeof(g_ctx));
g_ctx.javaVM = vm;
g_ctx.mainActivityClz = NULL;
g_ctx.mainActivityObj = NULL;
return JNI_VERSION_1_6;
}
JNIEXPORT void JNICALL Java_com_tutpro_baresip_BaresipService_baresipStart(
JNIEnv *env, jobject instance, jstring jPath, jstring jAddrs, jint jLogLevel,
jstring jSoftware)
{
LOGI("starting baresip\n");
int err;
char start_error[64] = "";
JavaVM *javaVM = g_ctx.javaVM;
jclass clz = (*env)->GetObjectClass(env, instance);
g_ctx.mainActivityClz = (*env)->NewGlobalRef(env, clz);
g_ctx.mainActivityObj = (*env)->NewGlobalRef(env, instance);
const char *path = (*env)->GetStringUTFChars(env, jPath, 0);
const char *addrs = (*env)->GetStringUTFChars(env, jAddrs, 0);
const char *software = (*env)->GetStringUTFChars(env, jSoftware, 0);
runLoggingThread();
err = libre_init();
if (err) {
LOGE("failed to init libre");
goto out;
}
if (re_thread_check(true) == 0) {
LOGI("attaching to re thread\n");
jint res = (*javaVM)->AttachCurrentThread(javaVM, &env, NULL);
if (JNI_OK != res) {
LOGE("failed to AttachCurrentThread: %d\n", res);
goto out;
}
} else {
LOGE("not on re thread\n");
goto out;
}
conf_path_set(path);
log_level_set((enum log_level)jLogLevel);
err = conf_configure();
if (err) {
LOGW("conf_configure() failed: (%d)\n", err);
strcpy(start_error, "conf_configure");
goto out;
}
re_thread_async_init(ASYNC_WORKERS);
err = baresip_init(conf_config());
if (err) {
LOGW("baresip_init() failed (%d)\n", err);
strcpy(start_error, "baresip_init");
goto out;
}
// Turn off DNS client cache (should be OK with async workers, but it not)
dnsc_cache_max(net_dnsc(baresip_network()), 0);
if (strlen(addrs) > 0) {
char *addr_list = (char *)malloc(strlen(addrs) + 1);
struct sa temp_sa;
char buf[256];
net_flush_addresses(baresip_network());
strcpy(addr_list, addrs);
char *ptr = strtok(addr_list, ";");
while (ptr != NULL) {
if (0 == sa_set_str(&temp_sa, ptr, 0)) {
sa_ntop(&temp_sa, buf, 256);
ptr = strtok(NULL, ";");
net_add_address_ifname(baresip_network(), &temp_sa, ptr);
} else {
LOGE("invalid ip address (%s)\n", ptr);
ptr = strtok(NULL, ";");
}
*(ptr - 1) = ';';
ptr = strtok(NULL, ";");
}
free(addr_list);
}
// net_debug_log();
err = ua_init(software, true, true, true);
if (err) {
LOGE("ua_init() failed (%d)\n", err);
strcpy(start_error, "ua_init");
goto out;
}
uag_set_exit_handler(ua_exit_handler, NULL);
err = bevent_register(event_handler, NULL);
if (err) {
LOGE("bevent_register() failed (%d)\n", err);
strcpy(start_error, "bevent_register");
goto out;
}
err = message_listen(baresip_message(), message_handler, NULL);
if (err) {
LOGE("message_listen() failed (%d)\n", err);
strcpy(start_error, "message_listen");
goto out;
}
err = conf_modules();
if (err) {
LOGW("conf_modules() failed (%d)\n", err);
strcpy(start_error, "conf_modules");
goto out;
}
err = mqueue_alloc(&mq, mqueue_handler, NULL);
if (err) {
LOGW("mqueue_alloc failed (%d)\n", err);
strcpy(start_error, "mqueue_alloc");
goto out;
}
// no need to call re_leave/enter since main is not running yet
jmethodID startedId = (*env)->GetMethodID(env, g_ctx.mainActivityClz, "started", "()V");
(*env)->CallVoidMethod(env, g_ctx.mainActivityObj, startedId);
LOGI("running main loop ...\n");
err = re_main(signal_handler);
out:
if (err) {
LOGE("stopping UAs due to error: (%d)\n", err);
ua_stop_all(true);
} else {
LOGI("main loop exit\n");
}
mq = mem_deref(mq);
LOGD("closing ...");
ua_close();
module_app_unload();
conf_close();
baresip_close();
bevent_unregister(event_handler);
LOGD("unloading modules ...");
mod_close();
LOGD("closing re thread\n");
re_thread_async_close();
LOGD("closing libre\n");
libre_close();
// tmr_debug();
// mem_debug();
LOGD("sending stopped event");
jstring javaError = (*env)->NewStringUTF(env, start_error);
jmethodID stoppedId =
(*env)->GetMethodID(env, g_ctx.mainActivityClz, "stopped", "(Ljava/lang/String;)V");
(*env)->CallVoidMethod(env, g_ctx.mainActivityObj, stoppedId, javaError);
(*env)->DeleteLocalRef(env, javaError);
(*env)->ReleaseStringUTFChars(env, jPath, path);
(*env)->ReleaseStringUTFChars(env, jAddrs, addrs);
(*env)->ReleaseStringUTFChars(env, jSoftware, software);
}
JNIEXPORT void JNICALL Java_com_tutpro_baresip_BaresipService_baresipStop(
JNIEnv *env, jobject obj, jboolean force)
{
(void)env;
(void)obj;
LOGD("ua_stop_all upon baresipStop");
mqueue_push(mq, ID_UA_STOP_ALL, (void *)((long)force));
}
JNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_account_1display_1name(
JNIEnv *env, jobject obj, jlong acc)
{
(void)obj;
if (acc) {
const char *dn = account_display_name((struct account *)acc);
if (dn)
return (*env)->NewStringUTF(env, dn);
else
return (*env)->NewStringUTF(env, "");
} else
return (*env)->NewStringUTF(env, "");
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1display_1name(
JNIEnv *env, jobject obj, jlong acc, jstring jDn)
{
(void)obj;
const char *dn = (*env)->GetStringUTFChars(env, jDn, 0);
int res;
if (strlen(dn) > 0)
res = account_set_display_name((struct account *)acc, dn);
else
res = account_set_display_name((struct account *)acc, NULL);
(*env)->ReleaseStringUTFChars(env, jDn, dn);
return res;
}
JNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_account_1aor(
JNIEnv *env, jobject obj, jlong acc)
{
(void)obj;
if (acc)
return (*env)->NewStringUTF(env, account_aor((struct account *)acc));
else
return (*env)->NewStringUTF(env, "");
}
JNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_account_1luri(
JNIEnv *env, jobject obj, jlong acc)
{
(void)obj;
const struct uri *uri = account_luri((struct account *)acc);
char uri_buf[512];
int l;
l = re_snprintf(&(uri_buf[0]), 511, "%H", uri_encode, uri);
if (l != -1)
uri_buf[l] = '\0';
else
uri_buf[0] = '\0';
return (*env)->NewStringUTF(env, uri_buf);
}
JNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_account_1auth_1user(
JNIEnv *env, jobject obj, jlong acc)
{
(void)obj;
if (acc) {
const char *au = account_auth_user((struct account *)acc);
if (au)
return (*env)->NewStringUTF(env, au);
else
return (*env)->NewStringUTF(env, "");
} else
return (*env)->NewStringUTF(env, "");
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1auth_1user(
JNIEnv *env, jobject obj, jlong acc, jstring jUser)
{
(void)obj;
const char *user = (*env)->GetStringUTFChars(env, jUser, 0);
int res;
if (strlen(user) > 0)
res = account_set_auth_user((struct account *)acc, user);
else
res = account_set_auth_user((struct account *)acc, NULL);
(*env)->ReleaseStringUTFChars(env, jUser, user);
return res;
}
JNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_account_1auth_1pass(
JNIEnv *env, jobject obj, jlong acc)
{
(void)obj;
if (acc) {
const char *ap = account_auth_pass((struct account *)acc);
if (ap)
return (*env)->NewStringUTF(env, ap);
else
return (*env)->NewStringUTF(env, "");
} else
return (*env)->NewStringUTF(env, "");
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1auth_1pass(
JNIEnv *env, jobject obj, jlong acc, jstring jPass)
{
(void)obj;
const char *pass = (*env)->GetStringUTFChars(env, jPass, 0);
int res;
if (strlen(pass) > 0)
res = account_set_auth_pass((struct account *)acc, pass);
else
res = account_set_auth_pass((struct account *)acc, NULL);
(*env)->ReleaseStringUTFChars(env, jPass, pass);
return res;
}
JNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_account_1outbound(
JNIEnv *env, jobject obj, jlong acc, jint jIx)
{
(void)obj;
const uint16_t native_ix = jIx;
const char *outbound;
if (acc) {
outbound = account_outbound((struct account *)acc, native_ix);
if (outbound)
return (*env)->NewStringUTF(env, outbound);
else
return (*env)->NewStringUTF(env, "");
} else {
return (*env)->NewStringUTF(env, "");
}
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1outbound(
JNIEnv *env, jobject obj, jlong acc, jstring jOb, jint jIx)
{
(void)obj;
const char *ob = (*env)->GetStringUTFChars(env, jOb, 0);
const uint16_t ix = jIx;
int res;
if (strlen(ob) > 0)
res = account_set_outbound((struct account *)acc, ob, ix);
else
res = account_set_outbound((struct account *)acc, NULL, ix);
(*env)->ReleaseStringUTFChars(env, jOb, ob);
return res;
}
JNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_account_1audio_1codec(
JNIEnv *env, jobject obj, jlong acc, jint ix)
{
(void)obj;
const struct list *codecl;
char codec_buf[32];
int len;
struct le *le;
codec_buf[0] = '\0';
if (acc) {
codecl = account_aucodecl((struct account *)acc);
if (!list_isempty(codecl)) {
int i = -1;
for (le = list_head(codecl); le != NULL; le = le->next) {
i++;
if (i == ix) {
const struct aucodec *ac = le->data;
len = re_snprintf(
codec_buf, sizeof codec_buf, "%s/%u/%u", ac->name, ac->srate, ac->ch);
if (len == -1) {
LOGE("failed to print audio codec to buffer\n");
codec_buf[0] = '\0';
}
break;
}
}
}
}
return (*env)->NewStringUTF(env, codec_buf);
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1audio_1codecs(
JNIEnv *env, jobject obj, jlong acc, jstring jCodecs)
{
(void)obj;
const char *codecs = (*env)->GetStringUTFChars(env, jCodecs, 0);
int res = account_set_audio_codecs((struct account *)acc, codecs);
(*env)->ReleaseStringUTFChars(env, jCodecs, codecs);
return res;
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1video_1codecs(
JNIEnv *env, jobject obj, jlong acc, jstring jCodecs)
{
(void)obj;
const char *codecs = (*env)->GetStringUTFChars(env, jCodecs, 0);
int res = account_set_video_codecs((struct account *)acc, codecs);
(*env)->ReleaseStringUTFChars(env, jCodecs, codecs);
return res;
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1regint(
JNIEnv *env, jobject obj, jlong acc)
{
(void)env;
(void)obj;
if (acc)
return (jint)account_regint((struct account *)acc);
else
return 0;
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1regint(
JNIEnv *env, jobject obj, jlong acc, jint jRegint)
{
(void)env;
(void)obj;
const uint32_t regint = (uint32_t)jRegint;
return account_set_regint((struct account *)acc, regint);
}
JNIEXPORT jboolean JNICALL Java_com_tutpro_baresip_Api_account_1check_1origin(
JNIEnv *env, jobject obj, jlong acc)
{
(void)env;
(void)obj;
return account_check_origin((struct account *)acc);
}
JNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_account_1set_1check_1origin(
JNIEnv *env, jobject obj, jlong acc, jboolean value)
{
(void)env;
(void)obj;
account_set_check_origin((struct account *)acc, value);
}
JNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_account_1mediaenc(
JNIEnv *env, jobject obj, jlong acc)
{
(void)obj;
if (acc) {
const char *mediaenc = account_mediaenc((struct account *)acc);
if (mediaenc)
return (*env)->NewStringUTF(env, mediaenc);
}
return (*env)->NewStringUTF(env, "");
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1mediaenc(
JNIEnv *env, jobject obj, jlong acc, jstring jMencid)
{
(void)obj;
const char *mencid = (*env)->GetStringUTFChars(env, jMencid, 0);
int res;
if (strlen(mencid) > 0)
res = account_set_mediaenc((struct account *)acc, mencid);
else
res = account_set_mediaenc((struct account *)acc, NULL);
(*env)->ReleaseStringUTFChars(env, jMencid, mencid);
return res;
}
JNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_account_1medianat(
JNIEnv *env, jobject obj, jlong acc)
{
(void)obj;
if (acc) {
const char *medianat = account_medianat((struct account *)acc);
if (medianat)
return (*env)->NewStringUTF(env, medianat);
}
return (*env)->NewStringUTF(env, "");
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1medianat(
JNIEnv *env, jobject obj, jlong acc, jstring jMedNat)
{
(void)obj;
const char *mednat = (*env)->GetStringUTFChars(env, jMedNat, 0);
int res;
if (strlen(mednat) > 0)
res = account_set_medianat((struct account *)acc, mednat);
else
res = account_set_medianat((struct account *)acc, NULL);
(*env)->ReleaseStringUTFChars(env, jMedNat, mednat);
return res;
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1sipnat(
JNIEnv *env, jobject obj, jlong acc, jstring jSipNat)
{
(void)obj;
const char *sipnat = (*env)->GetStringUTFChars(env, jSipNat, 0);
int res;
if (strlen(sipnat) > 0)
res = account_set_sipnat((struct account *)acc, sipnat);
else
res = account_set_sipnat((struct account *)acc, NULL);
(*env)->ReleaseStringUTFChars(env, jSipNat, sipnat);
return res;
}
JNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_account_1stun_1uri(
JNIEnv *env, jobject obj, jlong acc)
{
(void)obj;
if (acc) {
const struct stun_uri *stun_uri = account_stun_uri((struct account *)acc);
if (stun_uri) {
char uri_str[256];
if (stun_uri->port != 0) {
if (stun_uri->proto == IPPROTO_TCP)
sprintf(uri_str, "%s:%s:%d?transport=tcp",
stunuri_scheme_name(stun_uri->scheme), stun_uri->host, stun_uri->port);
else
sprintf(uri_str, "%s:%s:%d", stunuri_scheme_name(stun_uri->scheme),
stun_uri->host, stun_uri->port);
} else {
if (stun_uri->proto == IPPROTO_TCP)
sprintf(uri_str, "%s:%s?transport=tcp", stunuri_scheme_name(stun_uri->scheme),
stun_uri->host);
else
sprintf(uri_str, "%s:%s", stunuri_scheme_name(stun_uri->scheme),
stun_uri->host);
}
return (*env)->NewStringUTF(env, uri_str);
}
}
return (*env)->NewStringUTF(env, "");
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1stun_1uri(
JNIEnv *env, jobject obj, jlong acc, jstring jUri)
{
(void)obj;
const char *uri = (*env)->GetStringUTFChars(env, jUri, 0);
int res;
if (strlen(uri) > 0)
res = account_set_stun_uri((struct account *)acc, uri);
else
res = account_set_stun_uri((struct account *)acc, NULL);
(*env)->ReleaseStringUTFChars(env, jUri, uri);
return res;
}
JNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_account_1stun_1user(
JNIEnv *env, jobject obj, jlong acc)
{
(void)obj;
if (acc) {
const char *stun_user = account_stun_user((struct account *)acc);
if (stun_user)
return (*env)->NewStringUTF(env, stun_user);
}
return (*env)->NewStringUTF(env, "");
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1stun_1user(
JNIEnv *env, jobject obj, jlong acc, jstring jUser)
{
(void)obj;
const char *user = (*env)->GetStringUTFChars(env, jUser, 0);
int res;
if (strlen(user) > 0)
res = account_set_stun_user((struct account *)acc, user);
else
res = account_set_stun_user((struct account *)acc, NULL);
(*env)->ReleaseStringUTFChars(env, jUser, user);
return res;
}
JNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_account_1stun_1pass(
JNIEnv *env, jobject obj, jlong acc)
{
(void)obj;
if (acc) {
const char *stun_pass = account_stun_pass((struct account *)acc);
if (stun_pass)
return (*env)->NewStringUTF(env, stun_pass);
}
return (*env)->NewStringUTF(env, "");
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1stun_1pass(
JNIEnv *env, jobject obj, jlong acc, jstring jPass)
{
(void)obj;
const char *pass = (*env)->GetStringUTFChars(env, jPass, 0);
int res;
if (strlen(pass) > 0)
res = account_set_stun_pass((struct account *)acc, pass);
else
res = account_set_stun_pass((struct account *)acc, NULL);
(*env)->ReleaseStringUTFChars(env, jPass, pass);
return res;
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1mwi(
JNIEnv *env, jobject obj, jlong acc, jboolean value)
{
(void)env;
(void)obj;
return account_set_mwi((struct account *)acc, value);
}
JNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_account_1vm_1uri(
JNIEnv *env, jobject obj, jlong acc)
{
(void)obj;
char uri_buf[256];
if (acc) {
struct pl pl;
const struct sip_addr *addr = account_laddr((struct account *)acc);
int err = msg_param_decode(&(addr->params), "vm_uri", &pl);
if (err) {
return (*env)->NewStringUTF(env, "");
} else {
pl_strcpy(&pl, uri_buf, 256);
return (*env)->NewStringUTF(env, uri_buf);
}
} else
return (*env)->NewStringUTF(env, "");
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1answermode(
JNIEnv *env, jobject obj, jlong acc)
{
(void)env;
(void)obj;
return account_answermode((struct account *)acc);
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1answermode(
JNIEnv *env, jobject obj, jlong acc, jint jMode)
{
(void)env;
(void)obj;
const uint32_t mode = (uint32_t)jMode;
return account_set_answermode((struct account *)acc, mode);
}
JNIEXPORT jboolean JNICALL Java_com_tutpro_baresip_Api_account_1sip_1autoredirect(
JNIEnv *env, jobject obj, jlong acc)
{
(void)env;
(void)obj;
return account_sip_autoredirect((struct account *)acc);
}
JNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_account_1set_1sip_1autoredirect(
JNIEnv *env, jobject obj, jlong acc, jboolean allow)
{
(void)env;
(void)obj;
return account_set_sip_autoredirect((struct account *)acc, allow);
}
JNIEXPORT jboolean JNICALL Java_com_tutpro_baresip_Api_account_1rtcp_1mux(
JNIEnv *env, jobject obj, jlong acc)
{
(void)env;
(void)obj;
return account_rtcp_mux((struct account *)acc);
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1rtcp_1mux(
JNIEnv *env, jobject obj, jlong acc, jboolean value)
{
(void)env;
(void)obj;
return account_set_rtcp_mux((struct account *)acc, value);
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1rel100_1mode(
JNIEnv *env, jobject obj, jlong acc)
{
(void)env;
(void)obj;
return account_rel100_mode((struct account *)acc);
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1rel100_1mode(
JNIEnv *env, jobject obj, jlong acc, jint jMode)
{
(void)env;
(void)obj;
const uint32_t mode = (uint32_t)jMode;
return account_set_rel100_mode((struct account *)acc, mode);
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1dtmfmode(
JNIEnv *env, jobject obj, jlong acc)
{
(void)env;
(void)obj;
return account_dtmfmode((struct account *)acc);
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_account_1set_1dtmfmode(
JNIEnv *env, jobject obj, jlong acc, jint jMode)
{
(void)env;
(void)obj;
const uint32_t mode = (uint32_t)jMode;
return account_set_dtmfmode((struct account *)acc, mode);
}
JNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_account_1extra(
JNIEnv *env, jobject obj, jlong acc)
{
(void)obj;
if (acc) {
const char *extra = account_extra((struct account *)acc);
if (extra)
return (*env)->NewStringUTF(env, extra);
}
return (*env)->NewStringUTF(env, "");
}
JNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_account_1debug(
JNIEnv *env, jobject obj, jlong acc)
{
(void)env;
(void)obj;
account_debug_log((struct account *)acc);
}
JNIEXPORT jlong JNICALL Java_com_tutpro_baresip_Api_ua_1alloc(
JNIEnv *env, jobject obj, jstring jUri)
{
(void)obj;
const char *uri = (*env)->GetStringUTFChars(env, jUri, 0);
struct ua *ua;
LOGD("allocating UA '%s'\n", uri);
re_thread_enter();
int res = ua_alloc(&ua, uri);
re_thread_leave();
if (res == 0) {
LOGD("allocated ua '%ld'\n", (long)ua);
} else {
LOGE("failed to allocate ua '%s'\n", uri);
}
(*env)->ReleaseStringUTFChars(env, jUri, uri);
return (jlong)ua;
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_ua_1register(JNIEnv *env, jobject obj, jlong ua)
{
(void)env;
(void)obj;
LOGD("registering UA '%ld'\n", (long)ua);
re_thread_enter();
int res = ua_register((struct ua *)ua);
re_thread_leave();
return res;
}
JNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_ua_1unregister(
JNIEnv *env, jobject obj, jlong ua)
{
(void)env;
(void)obj;
re_thread_enter();
ua_unregister((struct ua *)ua);
re_thread_leave();
}
JNIEXPORT jboolean JNICALL Java_com_tutpro_baresip_Api_ua_1isregistered(
JNIEnv *env, jobject obj, jlong ua)
{
(void)env;
(void)obj;
return ua_isregistered((struct ua *)ua) ? true : false;
}
JNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_ua_1destroy(JNIEnv *env, jobject obj, jlong ua)
{
(void)env;
(void)obj;
LOGD("destroying ua %ld\n", (long)ua);
re_thread_enter();
(void)ua_destroy((struct ua *)ua);
re_thread_leave();
}
JNIEXPORT jlong JNICALL Java_com_tutpro_baresip_Api_ua_1account(JNIEnv *env, jobject obj, jlong ua)
{
(void)env;
(void)obj;
struct account *acc = 0;
if (ua)
acc = ua_account((struct ua *)ua);
return (jlong)acc;
}
JNIEXPORT jlong JNICALL Java_com_tutpro_baresip_Api_ua_1update_1account(
JNIEnv *env, jobject obj, jlong ua)
{
(void)env;
(void)obj;
LOGD("updating account of ua %ld\n", (long)ua);
return ua_update_account((struct ua *)ua);
}
JNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_ua_1hangup(
JNIEnv *env, jobject obj, jlong ua, jlong call, jint code, jstring reason)
{
(void)obj;
const uint16_t native_code = code;
const char *native_reason = (*env)->GetStringUTFChars(env, reason, 0);
LOGD("hanging up call %ld/%ld\n", (long)ua, (long)call);
re_thread_enter();
if (strlen(native_reason) == 0)
ua_hangup((struct ua *)ua, (struct call *)call, native_code, NULL);
else
ua_hangup((struct ua *)ua, (struct call *)call, native_code, native_reason);
re_thread_leave();
(*env)->ReleaseStringUTFChars(env, reason, native_reason);
}
JNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_ua_1accept(
JNIEnv *env, jobject obj, jlong ua, jlong msg)
{
(void)env;
(void)obj;
int err;
char *from_uri;
pl_strdup(&from_uri, &((struct sip_msg *)msg)->from.auri);
LOGD("accepting incoming call for ua %ld from %s\n", (long)ua, from_uri);
mem_deref(from_uri);
re_thread_enter();
err = ua_accept((struct ua *)ua, (struct sip_msg *)msg);
re_thread_leave();
if (err)
LOGW("accepting incoming call for ua %ld failed with error %d\n", (long)ua, err);
}
JNIEXPORT jlong JNICALL Java_com_tutpro_baresip_Api_ua_1call_1alloc(
JNIEnv *env, jobject obj, jlong ua, jlong xCall, jint vidMode)
{
(void)env;
(void)obj;
struct call *call = NULL;
int err;
LOGD("allocating new call for ua %ld xcall %ld\n", (long)ua, (long)xCall);
re_thread_enter();
err = ua_call_alloc(&call, (struct ua *)ua, (enum vidmode)vidMode, NULL, (struct call *)xCall,
call_localuri((struct call *)xCall), true);
re_thread_leave();
if (err)
LOGW("call allocation for ua %ld failed with error %d\n", (long)ua, err);
return (jlong)call;
}
JNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_ua_1answer(
JNIEnv *env, jobject obj, jlong ua, jlong call, jint vidMode)
{
(void)env;
(void)obj;
LOGD("answering ua/call %ld/%ld with video mode %d\n", (long)ua, (long)call, vidMode);
re_thread_enter();
ua_answer((struct ua *)ua, (struct call *)call, (enum vidmode)vidMode);
re_thread_leave();
}
JNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_ua_1add_1custom_1header(
JNIEnv *env, jobject obj, jlong ua, jstring jname, jstring jbody)
{
(void)obj;
const char *name = (*env)->GetStringUTFChars(env, jname, 0);
const char *body = (*env)->GetStringUTFChars(env, jbody, 0);
LOGD("adding header to %ld with name/body %s/%s\n", (long)ua, name, body);
re_thread_enter();
struct pl pl_name, pl_body;
pl_set_str(&pl_name, name);
pl_set_str(&pl_body, body);
int err = ua_add_custom_hdr((struct ua *)ua, &pl_name, &pl_body);
re_thread_leave();
if (err)
LOGW("adding custom header to ua %ld failed with error %d\n", (long)ua, err);
(*env)->ReleaseStringUTFChars(env, jname, name);
(*env)->ReleaseStringUTFChars(env, jbody, body);
}
JNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_ua_1debug(JNIEnv *env, jobject obj, jlong ua)
{
(void)env;
(void)obj;
ua_debug_log((struct ua *)ua);
}
JNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_sip_1treply(
JNIEnv *env, jobject obj, jlong msg, jint code, jstring reason)
{
(void)obj;
const uint16_t native_code = code;
const char *native_reason = (*env)->GetStringUTFChars(env, reason, 0);
LOGD("replying with %d/%s\n", native_code, native_reason);
re_thread_enter();
(void)sip_treply(NULL, uag_sip(), (const struct sip_msg *)(struct msg *)msg,
native_code, native_reason);
re_thread_leave();
(*env)->ReleaseStringUTFChars(env, reason, native_reason);
}
JNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_bevent_1stop(
JNIEnv *env, jobject obj, jlong event)
{
(void)env;
(void)obj;
re_thread_enter();
bevent_stop((struct bevent *)event);
re_thread_leave();
}
JNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_calls_1mute(
JNIEnv *env, jobject obj, jboolean mute)
{
(void)env;
(void)obj;
struct le *ua_le;
struct le *call_le;
LOGD("muting calls %d\n", mute);
re_thread_enter();
for (ua_le = list_head(uag_list()); ua_le != NULL; ua_le = ua_le->next) {
const struct ua *ua = ua_le->data;
for (call_le = list_head(ua_calls(ua)); call_le != NULL; call_le = call_le->next) {
const struct call *call = call_le->data;
audio_mute(call_audio(call), mute);
}
}
re_thread_leave();
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_call_1connect(
JNIEnv *env, jobject obj, jlong call, jstring jPeer)
{
(void)obj;
const char *native_peer = (*env)->GetStringUTFChars(env, jPeer, 0);
LOGD("connecting call %ld to %s\n", (long)call, native_peer);
re_thread_enter();
struct pl pl;
pl_set_str(&pl, native_peer);
int err = call_connect((struct call *)call, &pl);
re_thread_leave();
if (err)
LOGW("call_connect error: %d\n", err);
(*env)->ReleaseStringUTFChars(env, jPeer, native_peer);
return err;
}
JNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_call_1notify_1sipfrag(
JNIEnv *env, jobject obj, jlong call, jint code, jstring reason)
{
(void)obj;
const uint16_t native_code = code;
const char *native_reason = (*env)->GetStringUTFChars(env, reason, 0);
LOGD("notifying call %ld/%s\n", (long)call, native_reason);
re_thread_enter();
(void)call_notify_sipfrag((struct call *)call, native_code, native_reason);
re_thread_leave();
(*env)->ReleaseStringUTFChars(env, reason, native_reason);
}
JNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_call_1start_1audio(
JNIEnv *env, jobject obj, jlong call)
{
(void)env;
(void)obj;
LOGD("starting audio of call %ld\n", (long)call);
re_thread_enter();
struct audio *a = call_audio((struct call *)call);
if (!audio_started(a))
audio_update(a);
re_thread_leave();
}
JNIEXPORT jboolean JNICALL Java_com_tutpro_baresip_Api_call_1hold(
JNIEnv *env, jobject obj, jlong call, jboolean hold)
{
(void)env;
(void)obj;
int err;
if (hold) {
LOGD("holding call %ld\n", (long)call);
re_thread_enter();
err = call_hold((struct call *)call, true);
re_thread_leave();
} else {
LOGD("resuming call %ld\n", (long)call);
re_thread_enter();
err = call_hold((struct call *)call, false);
re_thread_leave();
}
if (err)
LOGW("call_hold error: %d\n", err);
return err == 0;
}
JNIEXPORT jboolean JNICALL Java_com_tutpro_baresip_Api_call_1ismuted(
JNIEnv *env, jobject obj, jlong call)
{
(void)env;
(void)obj;
return audio_ismuted(call_audio((struct call *)call));
}
JNIEXPORT jboolean JNICALL Java_com_tutpro_baresip_Api_call_1supported(
JNIEnv *env, jobject obj, jlong call, jint tags)
{
(void)env;
(void)obj;
return call_supported((struct call *)call, tags);
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_call_1transfer(
JNIEnv *env, jobject obj, jlong call, jstring jPeer)
{
(void)obj;
const char *native_peer = (*env)->GetStringUTFChars(env, jPeer, 0);
LOGD("transfering call %ld to %s\n", (long)call, native_peer);
re_thread_enter();
int err = call_transfer((struct call *)call, native_peer);
re_thread_leave();
if (err)
LOGW("call_transfer error: %d\n", err);
(*env)->ReleaseStringUTFChars(env, jPeer, native_peer);
return err;
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_call_1send_1digit(
JNIEnv *env, jobject obj, jlong call, jchar digit)
{
(void)env;
(void)obj;
const uint16_t native_digit = digit;
LOGD("sending DTMF digit '%c' to call %ld\n", (char)native_digit, (long)call);
re_thread_enter();
int res = call_send_digit((struct call *)call, (char)native_digit);
if (!res)
res = call_send_digit((struct call *)call, KEYCODE_REL);
re_thread_leave();
return res;
}
JNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_call_1audio_1codecs(
JNIEnv *env, jobject obj, jlong call)
{
(void)obj;
const struct aucodec *tx = audio_codec(call_audio((struct call *)call), true);
const struct aucodec *rx = audio_codec(call_audio((struct call *)call), false);
char codec_buf[256];
char *start = &(codec_buf[0]);
unsigned int left = sizeof codec_buf;
int len = -1;
if (tx && rx)
len = re_snprintf(start, left, "%s/%u/%u,%s/%u/%u", tx->name, tx->srate, tx->ch, rx->name,
rx->srate, rx->ch);
else {
LOGE("failed to get audio codecs of call %ld\n", (long)call);
len = re_snprintf(start, left, "%s/%u/%u,%s/%u/%u", '?', 0, 0, '?', 0, 0);
}
return (*env)->NewStringUTF(env, codec_buf);
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_call_1duration(
JNIEnv *env, jobject obj, jlong call)
{
(void)env;
(void)obj;
return (jint)call_duration((struct call *)call);
}
JNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_call_1stats(
JNIEnv *env, jobject obj, jlong call, jstring jStream)
{
(void)obj;
const char *native_stream = (*env)->GetStringUTFChars(env, jStream, 0);
const struct stream *s;
if (strcmp(native_stream, "audio") == 0)
s = audio_strm(call_audio((struct call *)call));
else
s = video_strm(call_video((struct call *)call));
const struct rtcp_stats *stats = stream_rtcp_stats(s);
char stats_buf[256];
int len;
if (stats) {
const double tx_rate = 1.0 * stream_metric_get_tx_bitrate(s) / 1000.0;
const double rx_rate = 1.0 * stream_metric_get_rx_bitrate(s) / 1000.0;
const double tx_avg_rate = 1.0 * stream_metric_get_tx_avg_bitrate(s) / 1000.0;
const double rx_avg_rate = 1.0 * stream_metric_get_rx_avg_bitrate(s) / 1000.0;
len = re_snprintf(&(stats_buf[0]), 256, "%.1f/%.1f,%.1f/%.1f,%u/%u,%d/%d,%.1f/%.1f",
tx_rate, rx_rate, tx_avg_rate, rx_avg_rate, stats->tx.sent, stats->rx.sent,
stats->tx.lost, stats->rx.lost, 1.0 * stats->tx.jit / 1000,
1.0 * stats->rx.jit / 1000);
if (len == -1) {
LOGE("failed to get stats of call %ld %s stream\n", (long)call, native_stream);
stats_buf[0] = '\0';
}
}
(*env)->ReleaseStringUTFChars(env, jStream, native_stream);
return (*env)->NewStringUTF(env, stats_buf);
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_call_1state(
JNIEnv *env, jobject obj, jlong call)
{
(void)env;
(void)obj;
return call_state((struct call *)call);
}
JNIEXPORT jboolean JNICALL Java_com_tutpro_baresip_Api_call_1replaces(
JNIEnv *env, jobject obj, jlong call)
{
(void)env;
(void)obj;
return call_supported((struct call *)call, REPLACES) ? true : false;
}
JNIEXPORT jboolean JNICALL Java_com_tutpro_baresip_Api_call_1replace_1transfer(
JNIEnv *env, jobject obj, jlong xferCall, jlong call)
{
(void)env;
(void)obj;
re_thread_enter();
int res = call_replace_transfer((struct call *)xferCall, (struct call *)call);
re_thread_leave();
return res == 0 ? true : false;
}
JNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_call_1peer_1uri(
JNIEnv *env, jobject obj, jlong call)
{
(void)obj;
const char *uri = call_peeruri((struct call *)call);
if (uri)
return (*env)->NewStringUTF(env, uri);
return (*env)->NewStringUTF(env, "");
}
JNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_call_1diverter_1uri(
JNIEnv *env, jobject obj, jlong call)
{
(void)obj;
const char *uri = call_diverteruri((struct call *)call);
if (uri)
return (*env)->NewStringUTF(env, uri);
return (*env)->NewStringUTF(env, "");
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_message_1send(
JNIEnv *env, jobject obj, jlong ua, jstring jPeer, jstring jMsg, jstring jTime)
{
(void)obj;
const char *native_peer = (*env)->GetStringUTFChars(env, jPeer, 0);
const char *native_msg = (*env)->GetStringUTFChars(env, jMsg, 0);
const char *native_time = (*env)->GetStringUTFChars(env, jTime, 0);
LOGD("sending message from ua %ld to %s at %s\n", (long)ua, native_peer, native_time);
re_thread_enter();
int err = message_send(
(struct ua *)ua, native_peer, native_msg, send_resp_handler, (void *)native_time);
re_thread_leave();
if (err) {
LOGW("message_send failed with error %d\n", err);
}
(*env)->ReleaseStringUTFChars(env, jPeer, native_peer);
(*env)->ReleaseStringUTFChars(env, jMsg, native_msg);
return err;
}
JNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_call_1destroy(
JNIEnv *env, jobject obj, jlong call)
{
(void)env;
(void)obj;
re_thread_enter();
mem_deref((struct call *)call);
re_thread_leave();
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_cmd_1exec(
JNIEnv *env, jobject obj, jstring javaCmd)
{
(void)obj;
const char *native_cmd = (*env)->GetStringUTFChars(env, javaCmd, 0);
LOGD("processing command '%s'\n", native_cmd);
re_thread_enter();
int res = cmd_process_long(baresip_commands(), native_cmd, strlen(native_cmd), &pf_null, NULL);
re_thread_leave();
(*env)->ReleaseStringUTFChars(env, javaCmd, native_cmd);
return res;
}
JNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_audio_1codecs(JNIEnv *env, jobject obj)
{
(void)obj;
struct list *aucodecl = baresip_aucodecl();
struct le *le;
char codec_buf[256];
char *start = &(codec_buf[0]);
unsigned int left = sizeof codec_buf;
int len;
for (le = list_head(aucodecl); le != NULL; le = le->next) {
const struct aucodec *ac = le->data;
if (start == &(codec_buf[0]))
len = re_snprintf(start, left, "%s/%u/%u", ac->name, ac->srate, ac->ch);
else
len = re_snprintf(start, left, ",%s/%u/%u", ac->name, ac->srate, ac->ch);
if (len == -1) {
LOGE("failed to print codec to buffer\n");
codec_buf[0] = '\0';
return (*env)->NewStringUTF(env, codec_buf);
}
start = start + len;
left = left - len;
}
*start = '\0';
return (*env)->NewStringUTF(env, codec_buf);
}
JNIEXPORT jstring JNICALL Java_com_tutpro_baresip_Api_video_1codecs(JNIEnv *env, jobject obj)
{
(void)obj;
struct list *vidcodecl = baresip_vidcodecl();
struct le *le;
char codec_buf[256];
char *start = &(codec_buf[0]);
unsigned int left = sizeof codec_buf;
int len;
for (le = list_head(vidcodecl); le != NULL; le = le->next) {
const struct vidcodec *vc = le->data;
if (start == &(codec_buf[0]))
len = re_snprintf(start, left, "%s", vc->name);
else
len = re_snprintf(start, left, ",%s", vc->name);
if (len == -1) {
LOGE("failed to print codec to buffer\n");
codec_buf[0] = '\0';
return (*env)->NewStringUTF(env, codec_buf);
}
start = start + len;
left = left - len;
}
*start = '\0';
return (*env)->NewStringUTF(env, codec_buf);
}
JNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_log_1level_1set(
JNIEnv *env, jobject obj, jint level)
{
(void)env;
(void)obj;
const enum log_level native_level = (enum log_level)level;
LOGD("setting log level to '%u'\n", native_level);
log_level_set(native_level);
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_net_1use_1nameserver(
JNIEnv *env, jobject obj, jstring javaServers)
{
(void)obj;
const char *native_servers = (*env)->GetStringUTFChars(env, javaServers, 0);
char servers[256];
char *server;
struct sa nsv[NET_MAX_NS];
uint32_t count = 0;
char *comma;
int res;
int err;
LOGD("setting dns servers '%s'\n", native_servers);
if (str_len(native_servers) > 255) {
LOGW("net_use_nameserver: too long servers list (%s)\n", native_servers);
return 1;
}
str_ncpy(servers, native_servers, 256);
(*env)->ReleaseStringUTFChars(env, javaServers, native_servers);
server = &(servers[0]);
while ((count < NET_MAX_NS) && ((comma = strchr(server, ',')) != NULL)) {
*comma = '\0';
err = sa_decode(&(nsv[count]), server, str_len(server));
if (err) {
LOGW("net_use_nameserver: could not decode '%s' (%u)\n", server, err);
return err;
}
server = ++comma;
count++;
}
if ((count < NET_MAX_NS) && (str_len(server) > 0)) {
err = sa_decode(&(nsv[count]), server, str_len(server));
if (err) {
LOGW("net_use_nameserver: could not decode `%s' (%u)\n", server, err);
return err;
}
count++;
}
res = net_use_nameserver(baresip_network(), nsv, count);
return res;
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_net_1add_1address_1ifname(
JNIEnv *env, jobject obj, jstring jAddr, jstring jIfName)
{
(void)obj;
const char *addr = (*env)->GetStringUTFChars(env, jAddr, 0);
const char *name = (*env)->GetStringUTFChars(env, jIfName, 0);
int res;
struct sa temp_sa;
char buf[256];
LOGD("adding address/ifname '%s/%s'\n", addr, name);
if (0 == sa_set_str(&temp_sa, addr, 0)) {
sa_ntop(&temp_sa, buf, 256);
re_thread_enter();
res = net_add_address_ifname(baresip_network(), &temp_sa, name);
re_thread_leave();
} else {
LOGE("invalid ip address %s\n", addr);
res = EAFNOSUPPORT;
}
(*env)->ReleaseStringUTFChars(env, jAddr, addr);
(*env)->ReleaseStringUTFChars(env, jIfName, name);
return res;
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_net_1rm_1address(
JNIEnv *env, jobject obj, jstring jIp)
{
(void)obj;
const char *native_ip = (*env)->GetStringUTFChars(env, jIp, 0);
int res;
struct sa temp_sa;
char buf[256];
LOGD("removing address '%s'\n", native_ip);
if (str_len(native_ip) == 0) {
(*env)->ReleaseStringUTFChars(env, jIp, native_ip);
return 0;
}
if (0 == sa_set_str(&temp_sa, native_ip, 0)) {
sa_ntop(&temp_sa, buf, 256);
re_thread_enter();
res = net_rm_address(baresip_network(), &temp_sa);
re_thread_leave();
} else {
LOGE("invalid ip address %s\n", native_ip);
res = EAFNOSUPPORT;
}
(*env)->ReleaseStringUTFChars(env, jIp, native_ip);
return res;
}
JNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_uag_1reset_1transp(
JNIEnv *env, jobject obj, jboolean reg, jboolean reinvite)
{
(void)env;
(void)obj;
LOGD("resetting transports (%d, %d)\n", reg, reinvite);
re_thread_enter();
(void)uag_reset_transp(reg, reinvite);
re_thread_leave();
}
JNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_uag_1enable_1sip_1trace(
JNIEnv *env, jobject obj, jboolean enable)
{
(void)env;
(void)obj;
LOGD("enabling sip trace (%d)\n", enable);
uag_enable_sip_trace(enable);
}
JNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_config_1verify_1server_1set(
JNIEnv *env, jobject obj, jboolean verify)
{
(void)env;
(void)obj;
struct config *conf = conf_config();
LOGD("setting verify_server (%d)\n", verify);
conf->sip.verify_server = verify;
}
JNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_net_1debug(JNIEnv *env, jobject obj)
{
(void)env;
(void)obj;
re_thread_enter();
net_debug_log();
re_thread_leave();
}
JNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_net_1dns_1debug(JNIEnv *env, jobject obj)
{
(void)env;
(void)obj;
re_thread_enter();
net_dns_debug_log();
re_thread_leave();
}
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_Api_module_1load(
JNIEnv *env, jobject obj, jstring javaModule)
{
(void)obj;
const char *native_module = (*env)->GetStringUTFChars(env, javaModule, 0);
int result = module_load(".", native_module);
(*env)->ReleaseStringUTFChars(env, javaModule, native_module);
return result;
}
JNIEXPORT void JNICALL Java_com_tutpro_baresip_Api_module_1unload(
JNIEnv *env, jobject obj, jstring javaModule)
{
(void)obj;
const char *native_module = (*env)->GetStringUTFChars(env, javaModule, 0);
module_unload(native_module);
LOGD("unloaded module %s\n", native_module);
(*env)->ReleaseStringUTFChars(env, javaModule, native_module);
}
AAudioStream *AAudio_stream = NULL;
JNIEXPORT jint JNICALL
Java_com_tutpro_baresip_Api_AAudio_1open_1stream(JNIEnv *env, jobject obj) {
AAudioStreamBuilder *builder = NULL;
jint sessionId = -1;
if (AAudio_createStreamBuilder(&builder) != AAUDIO_OK) {
return -1;
}
AAudioStreamBuilder_setDirection(builder, AAUDIO_DIRECTION_INPUT);
AAudioStreamBuilder_setSharingMode(builder, AAUDIO_SHARING_MODE_SHARED);
AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
AAudioStreamBuilder_setUsage(builder,AAUDIO_USAGE_VOICE_COMMUNICATION);
AAudioStreamBuilder_setInputPreset(builder, AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION);
AAudioStreamBuilder_setFormat(builder, AAUDIO_FORMAT_PCM_I16);
AAudioStreamBuilder_setSampleRate(builder, 16000);
AAudioStreamBuilder_setChannelCount(builder, 1);
AAudioStreamBuilder_setSessionId(builder, AAUDIO_SESSION_ID_ALLOCATE);
if (AAudioStreamBuilder_openStream(builder, &AAudio_stream) == AAUDIO_OK)
sessionId = AAudioStream_getSessionId(AAudio_stream);
else {
LOGE("Failed to open AAudio stream\n");
AAudio_stream = NULL;
}
AAudioStreamBuilder_delete(builder);
return sessionId;
}
JNIEXPORT void JNICALL
Java_com_tutpro_baresip_Api_AAudio_1close_1stream(JNIEnv *env, jobject obj)
{
if (AAudio_stream != NULL) {
AAudioStream_close(AAudio_stream);
AAudio_stream = NULL;
}
}
================================================
FILE: app/src/main/cpp/logger.h
================================================
#include <baresip.h>
#include <android/log.h>
#ifndef BARESIP_LOGGER_H
#define BARESIP_LOGGER_H
#define LOG_TAG "Baresip Lib"
#define LOGD(...) \
if (log_level_get() < LEVEL_INFO) \
((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
#define LOGI(...) \
if (log_level_get() < LEVEL_WARN) \
((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))
#define LOGW(...) \
if (log_level_get() < LEVEL_ERROR) \
((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__))
#define LOGE(...) \
if (log_level_get() <= LEVEL_ERROR) \
((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
#endif //BARESIP_LOGGER_H
================================================
FILE: app/src/main/kotlin/com/tutpro/baresip/AboutScreen.kt
================================================
package com.tutpro.baresip
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextLinkStyles
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.fromHtml
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
fun NavGraphBuilder.aboutScreenRoute(navController: NavController) {
composable("about") {
AboutScreen(onBack = { navController.navigateUp() })
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun AboutScreen(onBack: () -> Unit) {
Scaffold(
modifier = Modifier.fillMaxSize().imePadding(),
containerColor = MaterialTheme.colorScheme.background,
topBar = {
Column(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.background)
.padding(
top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
)
) {
TopAppBar(
title = {
Text(
text = stringResource(R.string.about_title),
fontWeight = FontWeight.Bold
)
},
navigationIcon = {
IconButton(onClick = onBack) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = null,
)
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primary,
titleContentColor = MaterialTheme.colorScheme.onPrimary,
navigationIconContentColor = MaterialTheme.colorScheme.onPrimary,
),
windowInsets = WindowInsets(0, 0, 0, 0)
)
}
}
) { contentPadding ->
Text(
text = AnnotatedString.fromHtml(
htmlString = stringResource(R.string.about_text, BuildConfig.VERSION_NAME),
linkStyles = TextLinkStyles(SpanStyle(color = MaterialTheme.colorScheme.error))
),
color = MaterialTheme.colorScheme.onBackground,
modifier = Modifier
.padding(horizontal = 16.dp, vertical = 16.dp)
.padding(contentPadding)
.verticalScroll(rememberScrollState())
.fillMaxSize()
)
}
}
================================================
FILE: app/src/main/kotlin/com/tutpro/baresip/Account.kt
================================================
package com.tutpro.baresip
import android.content.Context
import java.net.URLDecoder
import java.net.URLEncoder
import java.util.Locale
class Account(val accp: Long) {
var nickName = ""
var displayName = Api.account_display_name(accp)
val aor = Api.account_aor(accp)
val luri = Api.account_luri(accp)
var authUser = Api.account_auth_user(accp)
var authPass = Api.account_auth_pass(accp)
var outbound = ArrayList<String>()
var mediaNat = Api.account_medianat(accp)
var stunServer = Api.account_stun_uri(accp)
var stunUser = Api.account_stun_user(accp)
var stunPass = Api.account_stun_pass(accp)
var audioCodec = ArrayList<String>()
var videoCodec = ArrayList<String>()
var regint = Api.account_regint(accp)
var checkOrigin = Api.account_check_origin(accp)
var configuredRegInt = REGISTRATION_INTERVAL
var mediaEnc = Api.account_mediaenc(accp)
var rtcpMux = Api.account_rtcp_mux(accp)
var rel100Mode = Api.account_rel100_mode(accp)
var dtmfMode = Api.account_dtmfmode(accp)
var answerMode = Api.account_answermode(accp)
var autoRedirect = Api.account_sip_autoredirect(accp)
var blockUnknown = false
var vmUri = Api.account_vm_uri(accp)
var vmNew = 0
var vmOld = 0
var missedCalls = false
var unreadMessages = false
var callHistory = true
var countryCode = ""
var telProvider = Utils.aorDomain(aor)
var resumeUri = ""
var numericKeypad = false
var customParams = ""
init {
if (authPass == "")
authPass = NO_AUTH_PASS
var i = 0
while (true) {
val ob = Api.account_outbound(accp, i)
if (ob != "") {
outbound.add(ob)
i++
} else {
break
}
}
i = 0
while (true) {
val ac = Api.account_audio_codec(accp, i)
if (ac != "") {
audioCodec.add(ac)
i++
} else {
break
}
}
val extra = Api.account_extra(accp)
if (Utils.paramExists(extra, "nickname"))
nickName = Utils.paramValue(extra, "nickname")
if (Utils.paramExists(extra, "regint"))
configuredRegInt = Utils.paramValue(extra, "regint").toInt()
callHistory = Utils.paramValue(extra, "call_history") == ""
blockUnknown= Utils.paramExists(extra, "block_unknown")
if (Utils.paramExists(extra, "country_code"))
countryCode = Utils.paramValue(extra, "country_code")
if (Utils.paramExists(extra, "tel_provider"))
telProvider = URLDecoder.decode(Utils.paramValue(extra, "tel_provider"), "UTF-8")
numericKeypad = Utils.paramExists(extra, "numeric_keypad")
customParams = extra.substringAfter("last=empty").substringAfter(";")
}
fun print() : String {
var res = if (displayName != "")
"\"${displayName}\" "
else
""
res = "$res<$luri>"
if (authUser != "") res += ";auth_user=\"${authUser}\""
if ((authPass != "") && !BaresipService.aorPasswords.containsKey(aor))
res += ";auth_pass=\"${authPass}\""
if (outbound.isNotEmpty()) {
res += ";outbound=\"${outbound[0]}\""
if (outbound.size > 1) res += ";outbound2=\"${outbound[1]}\""
res = "$res;sipnat=outbound"
}
if (!checkOrigin) res += ";check_origin=no"
if (mediaNat != "") res += ";medianat=${mediaNat}"
if (stunServer != "")
res += ";stunserver=\"${stunServer}\""
if (stunUser != "")
res += ";stunuser=\"${stunUser}\""
if (stunPass != "")
res += ";stunpass=\"${stunPass}\""
if (audioCodec.isNotEmpty()) {
var first = true
res = "$res;audio_codecs="
for (c in audioCodec)
if (first) {
res += c
first = false
} else {
res = "$res,$c"
}
}
if (mediaEnc != "") res += ";mediaenc=${mediaEnc}"
if (rtcpMux)
res += ";rtcp_mux=yes"
res += if (rel100Mode == Api.REL100_ENABLED)
";100rel=yes"
else
";100rel=no"
if (dtmfMode == Api.DTMFMODE_SIP_INFO)
res += ";dtmfmode=info"
else if (dtmfMode == Api.DTMFMODE_AUTO)
res += ";dtmfmode=auto"
res = if (vmUri == "")
"$res;mwi=no"
else
"$res;mwi=yes;vm_uri=\"$vmUri\""
if (answerMode == Api.ANSWERMODE_AUTO)
res += ";answermode=auto"
if (autoRedirect)
res += ";sip_autoredirect=yes"
res += ";ptime=20;regint=${regint};regq=0.5;pubint=0;inreq_allowed=yes;call_transfer=yes"
var extra = ""
if (nickName != "")
extra += ";nickname=${nickName}"
if (!callHistory)
extra += ";call_history=no"
if (blockUnknown)
extra += ";block_unknown=yes"
if (telProvider != "")
extra += ";tel_provider=${URLEncoder.encode(telProvider, "UTF-8")}"
if (countryCode != "")
extra += ";country_code=$countryCode"
if (configuredRegInt != REGISTRATION_INTERVAL)
extra += ";regint=$configuredRegInt"
if (numericKeypad)
extra += ";numeric_keypad=yes"
extra += ";last=empty"
if (customParams != "")
extra += ";$customParams"
res += ";extra=\"" + extra.substringAfter(";") + "\""
if (customParams != "")
res += ";$customParams"
return res
}
fun vmMessages(cxt: Context) : String {
val new = if (vmNew > 0) {
if (vmNew == 1)
cxt.getString(R.string.one_new_message)
else
"$vmNew ${cxt.getString(R.string.new_messages)}"
} else
""
val old = if (vmOld > 0) {
if (vmOld == 1)
cxt.getString(R.string.one_old_message)
else
"$vmOld ${cxt.getString(R.string.old_messages)}"
} else
""
var msg = cxt.getString(R.string.you_have)
if (new != "") {
msg = "$msg $new"
if (old != "") msg = "$msg ${cxt.getString(R.string.and)} $old"
} else {
msg = if (old != "")
"$msg $old"
else
cxt.getString(R.string.no_messages)
}
return "$msg."
}
fun host() : String {
return aor.split("@")[1]
}
fun text(): String {
return if (nickName != "")
nickName
else
aor.split(":")[1].substringBefore(";")
}
private fun removeAudioCodecsStartingWith(prefix: String) {
val newCodecs = ArrayList<String>()
for (acSpec in audioCodec)
if (!acSpec.lowercase(Locale.ROOT).startsWith(prefix)) newCodecs.add(acSpec)
audioCodec = newCodecs
}
fun removeAudioCodecs(codecModule: String) {
when (codecModule) {
"g711" ->
removeAudioCodecsStartingWith("pcm")
"g722" ->
removeAudioCodecsStartingWith("g722/")
else ->
removeAudioCodecsStartingWith(codecModule)
}
}
companion object {
fun accounts(): ArrayList<Account> {
val res = ArrayList<Account>()
for (ua in BaresipService.uas.value) {
res.add(ua.account)
}
return res
}
fun saveAccounts() {
var accounts = ""
for (a in accounts()) accounts = accounts + a.print() + "\n"
Utils.putFileContents(
BaresipService.filesPath + "/accounts",
accounts.toByteArray(Charsets.UTF_8)
)
Log.d(TAG, "Saved accounts '${accounts}' to '${BaresipService.filesPath}/accounts'")
}
fun ofAor(aor: String): Account? {
for (ua in BaresipService.uas.value)
if (ua.account.aor == aor) return ua.account
return null
}
fun checkDisplayName(dn: String): Boolean {
if (dn == "") return true
val dnRegex = Regex("^([* .!%_`'~]|[+]|[-a-zA-Z0-9]){1,64}$")
return dnRegex.matches(dn)
}
fun checkAuthUser(au: String): Boolean {
if (au == "") return true
val ud = au.split("@")
return when (ud.size) {
1 -> Utils.checkUriUser(au)
2 -> Utils.checkUriUser(ud[0]) && Utils.checkDomain(ud[1])
else -> false
}
}
fun checkAuthPass(ap: String): Boolean {
return (ap.isNotEmpty()) && (ap.length <= 64) &&
Regex("^[ -~]*$").matches(ap) && !ap.contains('"')
}
fun uniqueNickName(nickName: String): Boolean {
for (ua in BaresipService.uas.value)
if (ua.account.nickName == nickName)
return false
return true
}
}
}
================================================
FILE: app/src/main/kotlin/com/tutpro/baresip/AccountScreen.kt
================================================
package com.tutpro.baresip
import android.content.Context
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.OutlinedTextFieldDefaults
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.SoftwareKeyboardController
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavType
import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import com.tutpro.baresip.CustomElements.AlertDialog
import com.tutpro.baresip.CustomElements.verticalScrollbar
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserFactory
import java.io.File
import java.io.StringReader
import java.net.URL
import java.util.Locale
import javax.net.ssl.HttpsURLConnection
fun NavGraphBuilder.accountScreenRoute(navController: NavController) {
composable(
route = "account/{aor}/{kind}",
arguments = listOf(
navArgument("aor") { type = NavType.StringType },
navArgument("kind") { type = NavType.StringType }
)
) { backStackEntry ->
val ctx = LocalContext.current
val viewModel = viewModel<AccountViewModel>()
val aor = backStackEntry.arguments?.getString("aor")!!
val kind = backStackEntry.arguments?.getString("kind")!!
val ua = UserAgent.ofAor(aor)!!
AccountScreen(
viewModel = viewModel,
navController = navController,
onBack = { navController.navigateUp() },
checkOnClick = {
val ok = checkOnClick(ctx, viewModel, ua)
if (ok) {
if (reRegister) ua.reRegister()
navController.navigateUp()
}
},
aor = aor,
kind = kind
)
}
}
private var keyboardController: SoftwareKeyboardController? = null
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun AccountScreen(
viewModel: AccountViewModel,
navController: NavController,
onBack: () -> Unit,
checkOnClick: () -> Unit,
aor: String,
kind: String
) {
val ua = UserAgent.ofAor(aor)!!
val acc = ua.account
var isAccountAvailable by remember { mutableStateOf(false) }
var isAccountLoaded by remember { mutableStateOf(false) }
LaunchedEffect(kind, acc) {
if (kind == "new")
initAccountFromNetwork(acc) { isAccountAvailable = true }
else
isAccountAvailable = true
}
if (isAccountAvailable)
LaunchedEffect(acc) {
viewModel.loadAccount(acc)
isAccountLoaded = true
}
Scaffold(
modifier = Modifier.fillMaxSize().imePadding(),
containerColor = MaterialTheme.colorScheme.background,
topBar = {
Column(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.background)
.padding(
top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
)
) {
TopAppBar(
title = {
Text(
text = acc.text(),
fontWeight = FontWeight.Bold
)
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primary,
navigationIconContentColor = MaterialTheme.colorScheme.onPrimary,
titleContentColor = MaterialTheme.colorScheme.onPrimary,
actionIconContentColor = MaterialTheme.colorScheme.onPrimary
),
navigationIcon = {
IconButton(onClick = onBack) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = null,
)
}
},
windowInsets = WindowInsets(0, 0, 0, 0),
actions = {
IconButton(onClick = checkOnClick) {
Icon(
imageVector = Icons.Filled.Check,
contentDescription = "Check"
)
}
},
)
}
}
) { contentPadding ->
if (isAccountLoaded)
AccountContent(viewModel, navController, contentPadding, ua)
else
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
}
}
private val password = mutableStateOf("")
private val showPasswordDialog = mutableStateOf(false)
private val alertTitle = mutableStateOf("")
private val alertMessage = mutableStateOf("")
private val showAlert = mutableStateOf(false)
private var reRegister = false
@Composable
private fun AccountContent(
viewModel: AccountViewModel,
navController: NavController,
contentPadding: PaddingValues,
ua: UserAgent
) {
val ctx = LocalContext.current
val aor = ua.account.aor
val mediaNat by viewModel.mediaNat.collectAsState()
val showStun by remember { derivedStateOf { mediaNat.isNotEmpty() } }
@Composable
fun AoR() {
Row(
Modifier
.fillMaxWidth()
.padding(top = 8.dp, end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
OutlinedTextField(
value = ua.account.luri,
enabled = false,
onValueChange = {},
modifier = Modifier.fillMaxWidth(),
textStyle = TextStyle(fontSize = 18.sp),
label = {
Text(text = stringResource(R.string.sip_uri), fontWeight = FontWeight.Bold)
},
colors = OutlinedTextFieldDefaults.colors(
disabledTextColor = MaterialTheme.colorScheme.onSurface,
disabledBorderColor = MaterialTheme.colorScheme.outline,
disabledLeadingIconColor = MaterialTheme.colorScheme.onSurfaceVariant,
disabledTrailingIconColor = MaterialTheme.colorScheme.onSurfaceVariant,
disabledLabelColor = MaterialTheme.colorScheme.onSurfaceVariant,
)
)
}
}
@Composable
fun Nickname() {
val nickNameTitle = stringResource(R.string.nickname)
val nickNameHelp = stringResource(R.string.account_nickname_help)
Row(
Modifier.fillMaxWidth().padding(top = 8.dp, end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
val nickName by viewModel.nickName.collectAsState()
OutlinedTextField(
value = nickName,
placeholder = { Text(nickNameTitle) },
onValueChange = { viewModel.nickName.value = it },
modifier = Modifier
.fillMaxWidth()
.clickable {
alertTitle.value = nickNameTitle
alertMessage.value = nickNameHelp
showAlert.value = true
},
singleLine = true,
textStyle = TextStyle(fontSize = 18.sp),
label = { Text(nickNameTitle) },
keyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.Words,
keyboardType = KeyboardType.Text),
)
}
}
@Composable
fun DisplayName() {
val displayNameTitle = stringResource(R.string.display_name)
val displayNameHelp = stringResource(R.string.display_name_help)
Row(
Modifier.fillMaxWidth().padding(top = 8.dp, end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
val displayName by viewModel.displayName.collectAsState()
OutlinedTextField(
value = displayName,
placeholder = { Text(displayNameTitle) },
onValueChange = { viewModel.displayName.value = it },
modifier = Modifier
.fillMaxWidth()
.clickable {
alertTitle.value = displayNameTitle
alertMessage.value = displayNameHelp
showAlert.value = true
},
singleLine = true,
textStyle = TextStyle(fontSize = 18.sp),
label = { Text(displayNameTitle) },
keyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.Sentences,
keyboardType = KeyboardType.Text
),
)
}
}
@Composable
fun AuthUser() {
val authenticationUserNameTitle = stringResource(R.string.authentication_username)
val authenticationUserNameHelp = stringResource(R.string.authentication_username_help)
Row(
Modifier.fillMaxWidth().padding(top = 8.dp, end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
val authUser by viewModel.authUser.collectAsState()
OutlinedTextField(
value = authUser,
placeholder = { Text(authenticationUserNameTitle) },
onValueChange = { viewModel.authUser.value = it },
modifier = Modifier
.fillMaxWidth()
.clickable {
alertTitle.value = authenticationUserNameTitle
alertMessage.value = authenticationUserNameHelp
showAlert.value = true
},
textStyle = TextStyle(fontSize = 18.sp),
label = { Text(authenticationUserNameTitle) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
)
}
}
@Composable
fun AuthPass() {
val authenticationPasswordTitle = stringResource(R.string.authentication_password)
val authenticationPasswordHelp = stringResource(R.string.authentication_password_help)
val showPassword = remember { mutableStateOf(false) }
Row(
Modifier.fillMaxWidth().padding(top = 8.dp, end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
val authPass by viewModel.authPass.collectAsState()
OutlinedTextField(
value = authPass,
placeholder = { Text(authenticationPasswordTitle) },
onValueChange = { viewModel.authPass.value = it },
singleLine = true,
visualTransformation = if (showPassword.value)
VisualTransformation.None
else
PasswordVisualTransformation(),
trailingIcon = {
IconButton(onClick = {
showPassword.value = !showPassword.value
}) {
Icon(
if (showPassword.value)
Icons.Filled.Visibility
else
Icons.Filled.VisibilityOff,
contentDescription = "Visibility",
tint = MaterialTheme.colorScheme.onSurfaceVariant
)
}
},
modifier = Modifier
.fillMaxWidth()
.clickable {
alertTitle.value = authenticationPasswordTitle
alertMessage.value = authenticationPasswordHelp
showAlert.value = true
},
textStyle = TextStyle(fontSize = 18.sp),
label = { Text(authenticationPasswordTitle) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
)
}
}
@Composable
fun AskPassword(ctx: Context, navController: NavController, ua: UserAgent) {
CustomElements.PasswordDialog(
ctx = ctx,
showPasswordDialog = showPasswordDialog,
password = password,
keyboardController = keyboardController,
title = stringResource(R.string.authentication_password),
okAction = {
if (password.value != "") {
BaresipService.aorPasswords[ua.account.aor] = password.value
Api.account_set_auth_pass(ua.account.accp, password.value)
password.value = ""
ua.reRegister()
navController.navigateUp()
}
},
cancelAction = {
ua.reRegister()
navController.navigateUp()
}
)
}
@Composable
fun Outbound() {
val outboundProxiesTitle = stringResource(R.string.outbound_proxies)
val outboundProxiesHelp = stringResource(R.string.outbound_proxies_help)
Text(text = stringResource(R.string.outbound_proxies),
fontSize = 18.sp,
modifier = Modifier
.padding(top = 8.dp)
.clickable {
alertTitle.value = outboundProxiesTitle
alertMessage.value = outboundProxiesHelp
showAlert.value = true
}
)
Row(
Modifier.fillMaxWidth().padding(top = 8.dp, end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
val outbound1 by viewModel.outbound1.collectAsState()
val sipUriOfProxyServerTitle = stringResource(R.string.sip_uri_of_proxy_server)
OutlinedTextField(
value = outbound1,
placeholder = { Text(sipUriOfProxyServerTitle) },
onValueChange = { viewModel.outbound1.value = it },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
textStyle = TextStyle(fontSize = 18.sp),
label = { Text(sipUriOfProxyServerTitle) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
)
}
Row(
Modifier.fillMaxWidth().padding(top = 8.dp, end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
val outbound2 by viewModel.outbound2.collectAsState()
val anotherProxyServerTitle = stringResource(R.string.sip_uri_of_another_proxy_server)
OutlinedTextField(
value = outbound2,
placeholder = { Text(anotherProxyServerTitle) },
onValueChange = { viewModel.outbound2.value = it },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
textStyle = TextStyle(fontSize = 18.sp),
label = { Text(anotherProxyServerTitle) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
)
}
}
@Composable
fun Register() {
val registerTitle = stringResource(R.string.register)
val registerHelp = stringResource(R.string.register_help)
Row(
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
Text(text = registerTitle,
modifier = Modifier
.weight(1f)
.clickable {
alertTitle.value = registerTitle
alertMessage.value = registerHelp
showAlert.value = true
},
fontSize = 18.sp,
)
val register by viewModel.register.collectAsState()
Switch(
checked = register,
onCheckedChange = { viewModel.register.value = it }
)
}
}
@Composable
fun RegInt() {
val regIntTitle = stringResource(R.string.reg_int)
val regIntHelp = stringResource(R.string.reg_int_help)
Row(
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
val regInt by viewModel.regInt.collectAsState()
OutlinedTextField(
value = regInt,
placeholder = { Text(regIntTitle) },
onValueChange = { viewModel.regInt.value = it },
modifier = Modifier
.fillMaxWidth()
.clickable {
alertTitle.value = regIntTitle
alertMessage.value = regIntHelp
showAlert.value = true
},
singleLine = true,
textStyle = TextStyle(fontSize = 18.sp),
label = { Text(regIntTitle) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
}
}
@Composable
fun CheckOrigin() {
val checkOriginTitle = stringResource(R.string.check_origin)
val checkOriginHelp = stringResource(R.string.check_origin_help)
Row(
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
Text(text = checkOriginTitle,
modifier = Modifier
.weight(1f)
.clickable {
alertTitle.value = checkOriginTitle
alertMessage.value = checkOriginHelp
showAlert.value = true
},
fontSize = 18.sp,
)
val checkOrigin by viewModel.checkOrigin.collectAsState()
Switch(
checked = checkOrigin,
onCheckedChange = { viewModel.checkOrigin.value = it }
)
}
}
@Composable
fun BlockUnknown() {
val blockUnknownTitle = stringResource(R.string.block_unknown)
val blockUnknownHelp = stringResource(R.string.block_unknown_help)
val block by viewModel.blockUnknown.collectAsState()
Row(
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
Text(text = blockUnknownTitle,
modifier = Modifier
.weight(1f)
.clickable {
alertTitle.value = blockUnknownTitle
alertMessage.value = blockUnknownHelp
showAlert.value = true
},
fontSize = 18.sp
)
Switch(
checked = block,
onCheckedChange = { viewModel.blockUnknown.value = it }
)
}
}
@Composable
fun AudioCodecs(navController: NavController, aor: String) {
Row(
Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
Text(
text = stringResource(R.string.audio_codecs),
modifier = Modifier
.weight(1f)
.clickable {
val route = "codecs/$aor/audio"
navController.navigate(route)
},
fontSize = 18.sp,
fontWeight = FontWeight.Bold
)
}
}
@Composable
fun MediaEnc() {
val mediaEncryptionTitle = stringResource(R.string.media_encryption)
val mediaEncryptionHelp = stringResource(R.string.media_encryption_help)
Row(
Modifier.fillMaxWidth().padding(top = 2.dp, end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
Text(text = mediaEncryptionTitle,
modifier = Modifier
.weight(1f)
.clickable {
alertTitle.value = mediaEncryptionTitle
alertMessage.value = mediaEncryptionHelp
showAlert.value = true
},
fontSize = 18.sp
)
val isDropDownExpanded = remember { mutableStateOf(false) }
val mediaEnc by viewModel.mediaEnc.collectAsState()
Box {
Row(
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.clickable {
isDropDownExpanded.value = true
}
) {
Text(text = mediaEncMap[mediaEnc]!!)
Icon(
imageVector = Icons.Filled.ArrowDropDown,
contentDescription = null,
modifier = Modifier.size(36.dp)
)
}
DropdownMenu(
expanded = isDropDownExpanded.value,
onDismissRequest = {
isDropDownExpanded.value = false
}) {
var index = 0
mediaEncMap.forEach {
DropdownMenuItem(text = { Text(text = it.value) },
onClick = {
isDropDownExpanded.value = false
viewModel.mediaEnc.value = it.key
}
)
if (index < 4)
HorizontalDivider(thickness = 1.dp)
index++
}
}
}
}
}
@Composable
fun MediaNat() {
val mediaNatTitle = stringResource(R.string.media_nat)
val mediaNatHelp = stringResource(R.string.media_nat_help)
Row(
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
Text(mediaNatTitle,
modifier = Modifier
.weight(1f)
.clickable {
alertTitle.value = mediaNatTitle
alertMessage.value = mediaNatHelp
showAlert.value = true
},
fontSize = 18.sp
)
val isDropDownExpanded = remember { mutableStateOf(false) }
val mediaNat by viewModel.mediaNat.collectAsState()
Box {
Row(
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.clickable {
isDropDownExpanded.value = true
}
) {
Text(text = mediaNatMap[mediaNat]!!)
Icon(
imageVector = Icons.Filled.ArrowDropDown,
contentDescription = null,
modifier = Modifier.size(36.dp)
)
}
DropdownMenu(
expanded = isDropDownExpanded.value,
onDismissRequest = {
isDropDownExpanded.value = false
}
) {
var index = 0
mediaNatMap.forEach {
DropdownMenuItem(text = { Text(text = it.value) },
onClick = {
isDropDownExpanded.value = false
viewModel.mediaNat.value = it.key
}
)
if (index < 3)
HorizontalDivider(thickness = 1.dp)
index++
}
}
}
}
}
@Composable
fun StunServer() {
val stunServerTitle = stringResource(R.string.stun_server)
val stunServerHelp = stringResource(R.string.stun_server_help)
Row(
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
val stunServer by viewModel.stunServer.collectAsState()
OutlinedTextField(
value = stunServer,
placeholder = { Text(stunServerTitle) },
onValueChange = { viewModel.stunServer.value = it },
modifier = Modifier
.fillMaxWidth()
.clickable {
alertTitle.value = stunServerTitle
alertMessage.value = stunServerHelp
showAlert.value = true
},
textStyle = TextStyle(fontSize = 18.sp),
label = { Text(stunServerTitle) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
)
}
}
@Composable
fun StunUser() {
val stunUsernameTitle = stringResource(R.string.stun_username)
val stunUsernameHelp = stringResource(R.string.stun_username_help)
Row(
Modifier.fillMaxWidth().padding(top = 8.dp, end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
val stunUser by viewModel.stunUser.collectAsState()
OutlinedTextField(
value = stunUser,
placeholder = { Text(stunUsernameTitle) },
onValueChange = { viewModel.stunUser.value = it },
modifier = Modifier
.fillMaxWidth()
.clickable {
alertTitle.value = stunUsernameTitle
alertMessage.value = stunUsernameHelp
showAlert.value = true
},
textStyle = TextStyle(fontSize = 18.sp),
label = { Text(stunUsernameTitle) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
)
}
}
@Composable
fun StunPass() {
val stunPasswordTitle = stringResource(R.string.stun_password)
val stunPasswordHelp = stringResource(R.string.stun_password_help)
val showPassword = remember { mutableStateOf(false) }
Row(
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
val stunPass by viewModel.stunPass.collectAsState()
OutlinedTextField(
value = stunPass,
placeholder = { Text(stunPasswordTitle) },
onValueChange = { viewModel.stunPass.value = it },
singleLine = true,
visualTransformation = if (showPassword.value)
VisualTransformation.None
else
PasswordVisualTransformation(),
trailingIcon = {
IconButton(onClick = { showPassword.value = !showPassword.value }) {
Icon(
if (showPassword.value)
Icons.Filled.Visibility
else
Icons.Filled.VisibilityOff,
contentDescription = "Visibility",
tint = MaterialTheme.colorScheme.onSurfaceVariant
)
}
},
modifier = Modifier
.fillMaxWidth()
.clickable {
alertTitle.value = stunPasswordTitle
alertMessage.value = stunPasswordHelp
showAlert.value = true
},
textStyle = TextStyle(fontSize = 18.sp),
label = { Text(stunPasswordTitle) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
)
}
}
@Composable
fun RtcpMux() {
val rtcpMuxTitle = stringResource(R.string.rtcp_mux)
val rtcpMuxHelp = stringResource(R.string.rtcp_mux_help)
Row(
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
val rtcpMux by viewModel.rtcpMux.collectAsState()
Text(text = rtcpMuxTitle,
modifier = Modifier
.weight(1f)
.clickable {
alertTitle.value = rtcpMuxTitle
alertMessage.value = rtcpMuxHelp
showAlert.value = true
},
fontSize = 18.sp
)
Switch(
checked = rtcpMux,
onCheckedChange = { viewModel.rtcpMux.value = it }
)
}
}
@Composable
fun Rel100() {
val rel100Title = stringResource(R.string.rel_100)
val rel100Help = stringResource(R.string.rel_100_help)
Row(
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
val rel100 by viewModel.rel100.collectAsState()
Text(
text = rel100Title,
modifier = Modifier
.weight(1f)
.clickable {
alertTitle.value = rel100Title
alertMessage.value = rel100Help
showAlert.value = true
},
fontSize = 18.sp
)
Switch(
checked = rel100,
onCheckedChange = { viewModel.rel100.value = it }
)
}
}
@Composable
fun Dtmf() {
val dtmfModeTitle = stringResource(R.string.dtmf_mode)
val dtmfModeHelp = stringResource(R.string.dtmf_mode_help)
val dtmfInbandText = stringResource(R.string.dtmf_inband)
val dtmfInfoText = stringResource(R.string.dtmf_info)
val dtmfAutoText = stringResource(R.string.dtmf_auto)
val dtmfMode by viewModel.dtmfMode.collectAsState()
Row(
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
val dtmfModeMap = mapOf(Api.DTMFMODE_RTP_EVENT to dtmfInbandText,
Api.DTMFMODE_SIP_INFO to dtmfInfoText,
Api.DTMFMODE_AUTO to dtmfAutoText)
Text(text = dtmfModeTitle,
modifier = Modifier
.weight(1f)
.clickable {
alertTitle.value = dtmfModeTitle
alertMessage.value = dtmfModeHelp
showAlert.value = true
},
fontSize = 18.sp
)
val isDropDownExpanded = remember { mutableStateOf(false) }
Box {
Row(
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.clickable {
isDropDownExpanded.value = true
}
) {
Text(text = dtmfModeMap[dtmfMode]!!)
Icon(
imageVector = Icons.Filled.ArrowDropDown,
contentDescription = null,
modifier = Modifier.size(36.dp)
)
}
DropdownMenu(
expanded = isDropDownExpanded.value,
onDismissRequest = {
isDropDownExpanded.value = false
}) {
var index = 0
dtmfModeMap.forEach {
DropdownMenuItem(text = { Text(text = it.value) },
onClick = {
isDropDownExpanded.value = false
viewModel.dtmfMode.value = it.key
}
)
if (index < 2)
HorizontalDivider(thickness = 1.dp)
index++
}
}
}
}
}
val manualText = stringResource(R.string.manual)
val autoText = stringResource(R.string.auto)
@Composable
fun Answer() {
val answerModeTitle = stringResource(R.string.answer_mode)
val answerModeHelp = stringResource(R.string.answer_mode_help)
val answerMode by viewModel.answerMode.collectAsState()
Row(
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
val answerModeMap = mapOf(Api.ANSWERMODE_MANUAL to manualText,
Api.ANSWERMODE_AUTO to autoText)
Text(text = answerModeTitle,
modifier = Modifier
.weight(1f)
.clickable {
alertTitle.value = answerModeTitle
alertMessage.value = answerModeHelp
showAlert.value = true
},
fontSize = 18.sp
)
val isDropDownExpanded = remember { mutableStateOf(false) }
Box {
Row(
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.clickable { isDropDownExpanded.value = true }
) {
Text(text = answerModeMap[answerMode]!!)
Icon(
imageVector = Icons.Filled.ArrowDropDown,
contentDescription = null,
modifier = Modifier.size(36.dp)
)
}
DropdownMenu(
expanded = isDropDownExpanded.value,
onDismissRequest = { isDropDownExpanded.value = false }
) {
var index = 0
answerModeMap.forEach {
DropdownMenuItem(text = { Text(text = it.value) },
onClick = {
isDropDownExpanded.value = false
viewModel.answerMode.value = it.key
})
if (index < 1)
HorizontalDivider(thickness = 1.dp)
index++
}
}
}
}
}
@Composable
fun Redirect() {
val redirectModeTitle = stringResource(R.string.redirect_mode)
val redirectModeHelp = stringResource(R.string.redirect_mode_help)
val autoRedirect by viewModel.autoRedirect.collectAsState()
Row(
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
val redirectModeMap = mapOf(false to manualText,
true to autoText)
Text(text = redirectModeTitle,
modifier = Modifier
.weight(1f)
.clickable {
alertTitle.value = redirectModeTitle
alertMessage.value = redirectModeHelp
showAlert.value = true
},
fontSize = 18.sp
)
val isDropDownExpanded = remember { mutableStateOf(false) }
Box {
Row(
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.clickable { isDropDownExpanded.value = true }
) {
Text(text = redirectModeMap[autoRedirect]!!)
Icon(
imageVector = Icons.Filled.ArrowDropDown,
contentDescription = null,
modifier = Modifier.size(36.dp)
)
}
DropdownMenu(
expanded = isDropDownExpanded.value,
onDismissRequest = { isDropDownExpanded.value = false }
) {
var index = 0
redirectModeMap.forEach {
DropdownMenuItem(
text = { Text(text = it.value) },
onClick = {
isDropDownExpanded.value = false
viewModel.autoRedirect.value = it.key
}
)
if (index < 1)
HorizontalDivider(thickness = 1.dp)
index++
}
}
}
}
}
@Composable
fun Voicemail() {
val voicemailUriTitle = stringResource(R.string.voicemail_uri)
val voicemailUriHelp = stringResource(R.string.voicemain_uri_help)
val vmUri by viewModel.vmUri.collectAsState()
Row(
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
OutlinedTextField(
value = vmUri,
placeholder = { Text(voicemailUriTitle) },
onValueChange = { viewModel.vmUri.value = it },
modifier = Modifier
.fillMaxWidth()
.clickable {
alertTitle.value = voicemailUriTitle
alertMessage.value = voicemailUriHelp
showAlert.value = true
},
singleLine = true,
textStyle = TextStyle(fontSize = 18.sp),
label = { Text(voicemailUriTitle) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
)
}
}
@Composable
fun CountryCode() {
val countryCodeTitle = stringResource(R.string.country_code)
val countryCodeHelp = stringResource(R.string.country_code_help)
val countryCode by viewModel.countryCode.collectAsState()
Row(
Modifier.fillMaxWidth().padding(top = 8.dp, end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
OutlinedTextField(
value = countryCode,
placeholder = { Text(countryCodeTitle) },
onValueChange = { viewModel.countryCode.value = it },
modifier = Modifier
.fillMaxWidth()
.clickable {
alertTitle.value = countryCodeTitle
alertMessage.value = countryCodeHelp
showAlert.value = true
},
singleLine = true,
textStyle = TextStyle(fontSize = 18.sp),
label = { Text(countryCodeTitle) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
)
}
}
@Composable
fun TelProvider() {
val telephonyProviderTitle = stringResource(R.string.telephony_provider)
val telephonyProviderHelp = stringResource(R.string.telephony_provider_help)
val telProvider by viewModel.telProvider.collectAsState()
Row(
Modifier.fillMaxWidth().padding(top = 8.dp, end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
OutlinedTextField(
value = telProvider,
placeholder = { Text(telephonyProviderTitle) },
onValueChange = { viewModel.telProvider.value = it },
modifier = Modifier
.fillMaxWidth()
.clickable {
alertTitle.value = telephonyProviderTitle
alertMessage.value = telephonyProviderHelp
showAlert.value = true
},
singleLine = true,
textStyle = TextStyle(fontSize = 18.sp),
label = { Text(telephonyProviderTitle) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
)
}
}
@Composable
fun NumericKeypad() {
val numericKeypadTitle = stringResource(R.string.numeric_keypad)
val numericKeypadHelp = stringResource(R.string.numeric_keypad_help)
val numericKeypad by viewModel.numericKeypad.collectAsState()
Row(
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
Text(text = numericKeypadTitle,
modifier = Modifier
.weight(1f)
.clickable {
alertTitle.value = numericKeypadTitle
alertMessage.value = numericKeypadHelp
showAlert.value = true
},
fontSize = 18.sp
)
Switch(
checked = numericKeypad,
onCheckedChange = { viewModel.numericKeypad.value = it }
)
}
}
@Composable
fun DefaultAccount() {
val defaultAccountTitle = stringResource(R.string.default_account)
val defaultAccountHelp = stringResource(R.string.default_account_help)
val defaultAccount by viewModel.defaultAccount.collectAsState()
Row(
Modifier.fillMaxWidth().padding(end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
Text(text = defaultAccountTitle,
modifier = Modifier
.weight(1f)
.clickable {
alertTitle.value = defaultAccountTitle
alertMessage.value = defaultAccountHelp
showAlert.value = true
},
fontSize = 18.sp
)
Switch(
checked = defaultAccount,
onCheckedChange = { viewModel.defaultAccount.value = it }
)
}
}
@Composable
fun CustomParams() {
val customParamsTitle = stringResource(R.string.custom_parameters)
val customParamsHelp = stringResource(R.string.custom_parameters_help)
val customParams by viewModel.customParams.collectAsState()
Row(
Modifier.fillMaxWidth().padding(top = 8.dp, end = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
OutlinedTextField(
value = customParams,
placeholder = { Text(customParamsTitle) },
onValueChange = { viewModel.customParams.value = it },
modifier = Modifier
.fillMaxWidth()
.clickable {
alertTitle.value = customParamsTitle
alertMessage.value = customParamsHelp
showAlert.value = true
},
singleLine = true,
textStyle = TextStyle(fontSize = 18.sp),
label = { Text(customParamsTitle) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
)
}
}
if (showAlert.value) {
AlertDialog(
showDialog = showAlert,
title = alertTitle.value,
message = alertMessage.value,
lastButtonText = stringResource(R.string.ok),
)
}
keyboardController = LocalSoftwareKeyboardController.current
val scrollState = rememberScrollState()
Column(
modifier = Modifier
.fillMaxWidth()
.padding(contentPadding)
.padding(start = 16.dp, end = 4.dp, top = 8.dp, bottom = 16.dp)
.verticalScrollbar(scrollState)
.verticalScroll(state = scrollState),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
AoR()
Nickname()
DisplayName()
AuthUser()
AuthPass()
if (showPasswordDialog.value)
AskPassword(ctx, navController, ua)
Outbound()
Register()
if (viewModel.register.collectAsState().value) {
RegInt()
CheckOrigin()
}
BlockUnknown()
AudioCodecs(navController, aor)
MediaEnc()
MediaNat()
if (showStun) {
StunServer()
StunUser()
StunPass()
}
RtcpMux()
Rel100()
Dtmf()
Answer()
Redirect()
Voicemail()
CountryCode()
TelProvider()
NumericKeypad()
DefaultAccount()
CustomParams()
}
}
private fun checkOnClick(ctx: Context, viewModel: AccountViewModel, ua: UserAgent): Boolean {
val acc = ua.account
val noticeTitle = ctx.getString(R.string.notice)
val nn = viewModel.nickName.value.trim()
if (nn != acc.nickName) {
if (Account.checkDisplayName(nn)) {
if (nn == "" || Account.uniqueNickName(nn)) {
acc.nickName = nn
Log.d(TAG, "New nickname is ${acc.nickName}")
}
else {
alertTitle.value = noticeTitle
alertMessage.value = String.format(ctx.getString(R.string.non_unique_account_nickname), nn)
showAlert.value = true
return false
}
}
else {
alertTitle.value = noticeTitle
alertMessage.value = String.format(ctx.getString(R.string.invalid_account_nickname), nn)
showAlert.value = true
return false
}
}
val dn = viewModel.displayName.value.trim()
if (dn != acc.displayName) {
if (Account.checkDisplayName(dn)) {
if (Api.account_set_display_name(acc.accp, dn) == 0) {
acc.displayName = Api.account_display_name(acc.accp)
Log.d(TAG, "New display name is ${acc.displayName}")
} else {
Log.e(TAG, "Setting of display name failed")
}
}
else {
alertTitle.value = noticeTitle
alertMessage.value = String.format(ctx.getString(R.string.invalid_display_name), dn)
showAlert.value = true
return false
}
}
val au = viewModel.authUser.value.trim()
if (au != acc.authUser) {
if (Account.checkAuthUser(au)) {
if (Api.account_set_auth_user(acc.accp, au) == 0) {
acc.authUser = Api.account_auth_user(acc.accp)
Log.d(TAG, "New auth user is ${acc.authUser}")
if (acc.regint > 0)
reRegister = true
}
else {
Log.e(TAG, "Setting of auth user failed")
}
}
else {
alertTitle.value = noticeTitle
alertMessage.value = String.format(ctx.getString(R.string.invalid_authentication_username), au)
showAlert.value = true
return false
}
}
val ap = viewModel.authPass.value.trim()
if (ap != "") {
if (ap != acc.authPass) {
if (Account.checkAuthPass(ap)) {
if (Api.account_set_auth_pass(acc.accp, ap) == 0) {
acc.authPass = Api.account_auth_pass(acc.accp)
if (acc.regint > 0)
reRegister = true
}
else
Log.e(TAG, "Setting of auth pass failed")
BaresipService.aorPasswords.remove(acc.aor)
}
else {
alertTitle.value = noticeTitle
alertMessage.value = String.format(ctx.getString(R.string.invalid_authentication_password), ap)
showAlert.value = true
return false
}
}
else
BaresipService.aorPasswords.remove(acc.aor)
}
else { // ap == ""
if (acc.authPass != NO_AUTH_PASS && acc.authPass != BaresipService.aorPasswords[acc.aor])
if (Api.account_set_auth_pass(acc.accp, "") == 0) {
acc.authPass = NO_AUTH_PASS
BaresipService.aorPasswords[acc.aor] = NO_AUTH_PASS
}
}
val ob = ArrayList<String>()
var ob1 = viewModel.outbound1.value.trim().replace(" ", "")
if (ob1 != "") {
if (!ob1.startsWith("sip:"))
ob1 = "sip:$ob1"
if (checkOutboundUri(ob1)) {
ob.add(ob1)
}
else {
alertTitle.value = noticeTitle
alertMessage.value = String.format(ctx.getString(R.string.invalid_proxy_server_uri), ob1)
showAlert.value = true
return false
}
}
var ob2 = viewModel.outbound2.value.trim().replace(" ", "")
if (ob2 != "") {
if (!ob2.startsWith("sip:"))
ob2 = "sip:$ob2"
if (checkOutboundUri(ob2))
ob.add(ob2)
else {
alertTitle.value = noticeTitle
alertMessage.value = String.format(ctx.getString(R.string.invalid_proxy_server_uri), ob2)
showAlert.value = true
return false
}
}
if (ob != acc.outbound) {
for (i in 0..1) {
val uri = if (ob.size > i)
ob[i]
else
""
if (Api.account_set_outbound(acc.accp, uri, i) != 0)
Log.e(TAG, "Setting of outbound proxy $i uri '$uri' failed")
}
Log.d(TAG, "New outbound proxies are $ob")
acc.outbound = ob
if (ob.isEmpty())
Api.account_set_sipnat(acc.accp, "")
else
Api.account_set_sipnat(acc.accp, "outbound")
if (acc.regint > 0)
reRegister = true
}
val regInt = try {
viewModel.regInt.value.trim().toInt()
} catch (_: NumberFormatException) {
0
}
if (regInt !in 60..3600) {
alertTitle.value = noticeTitle
alertMessage.value = String.format(ctx.getString(R.string.invalid_reg_int), "$regInt")
showAlert.value = true
return false
}
val reReg = (viewModel.register.value != acc.regint > 0) ||
(viewModel.register.value && regInt != acc.configuredRegInt)
if (reReg) {
if (Api.account_set_regint(acc.accp,
if (viewModel.register.value) regInt else 0) != 0) {
Log.e(TAG, "Setting of regint failed")
} else {
acc.regint = Api.account_regint(acc.accp)
acc.configuredRegInt = regInt
Log.d(TAG, "New regint is ${acc.regint}")
reRegister = true
}
}
else {
if (regInt != acc.configuredRegInt)
acc.configuredRegInt = regInt
}
val newCheckOrigin = viewModel.checkOrigin.value
if (newCheckOrigin != acc.checkOrigin) {
Api.account_set_check_origin(ua.account.accp, newCheckOrigin)
acc.checkOrigin = Api.account_check_origin(ua.account.accp)
Log.d(TAG, "New checkOrigin is ${acc.checkOrigin}")
}
val newMediaNat = viewModel.mediaNat.value
if (newMediaNat != acc.mediaNat) {
if (Api.account_set_medianat(acc.accp, newMediaNat) == 0) {
acc.mediaNat = Api.account_medianat(acc.accp)
Log.d(TAG, "New medianat is ${acc.mediaNat}")
}
else
Log.e(TAG, "Setting of medianat $newMediaNat failed")
}
var newStunServer = viewModel.stunServer.value.trim()
if (newMediaNat != "") {
if ((newMediaNat == "stun" || newMediaNat == "ice") && newStunServer == "")
newStunServer = ctx.getString(R.string.stun_server_default)
if (!Utils.checkStunUri(newStunServer) ||
(newMediaNat == "turn" &&
newStunServer.substringBefore(":") !in setOf("turn", "turns"))) {
alertTitle.value = ctx.getString(R.string.notice)
alertMessage.value = String.format(ctx.getString(R.string.invalid_stun_server),
newStunServer)
showAlert.value = true
return false
}
}
if (acc.stunServer != newStunServer) {
if (Api.account_set_stun_uri(acc.accp, newStunServer) == 0) {
acc.stunServer = Api.account_stun_uri(acc.accp)
Log.d(TAG, "New STUN/TURN server URI is '${acc.stunServer}'")
} else {
Log.e(TAG, "Setting of STUN/TURN URI server $newStunServer failed")
}
}
val newStunUser = viewModel.stunUser.value.trim()
if (acc.stunUser != newStunUser) {
if (Account.checkAuthUser(newStunUser)) {
if (Api.account_set_stun_user(acc.accp, newStunUser) == 0) {
acc.stunUser = Api.account_stun_user(acc.accp)
Log.d(TAG, "New STUN/TURN user is ${acc.stunUser}")
}
else
Log.e(TAG, "Setting of STUN/TURN user $newStunUser failed")
}
else {
alertTitle.value = noticeTitle
alertMessage.value = String.format(ctx.getString(R.string.invalid_stun_username),
newStunUser)
showAlert.value = true
return false
}
}
val newStunPass = viewModel.stunPass.value.trim()
if (acc.stunPass != newStunPass) {
if (newStunPass.isEmpty() || Account.checkAuthPass(newStunPass)) {
if (Api.account_set_stun_pass(acc.accp, newStunPass) == 0)
acc.stunPass = Api.account_stun_pass(acc.accp)
else
Log.e(TAG, "Setting of stun pass failed")
}
else {
alertTitle.value = noticeTitle
alertMessage.value = String.format(ctx.getString(R.string.invalid_stun_password),
newStunPass)
showAlert.value = true
return false
}
}
val newRtcpMux = viewModel.rtcpMux.value
if (newRtcpMux != acc.rtcpMux)
if (Api.account_set_rtcp_mux(acc.accp, newRtcpMux) == 0) {
acc.rtcpMux = Api.account_rtcp_mux(acc.accp)
Log.d(TAG, "New rtcpMux is ${acc.rtcpMux}")
} else {
Log.e(TAG, "Setting of account_rtc_mux $newRtcpMux failed")
}
val new100Rel = viewModel.rel100.value
if (new100Rel != (acc.rel100Mode == Api.REL100_ENABLED)) {
val mode = if (new100Rel) Api.REL100_ENABLED else Api.REL100_DISABLED
if (Api.account_set_rel100_mode(acc.accp, mode) == 0) {
acc.rel100Mode = Api.account_rel100_mode(acc.accp)
Api.ua_update_account(ua.uap)
Log.d(TAG, "New rel100Mode is ${acc.rel100Mode}")
} else {
Log.e(TAG, "Setting of account_rel100Mode $mode failed")
}
}
val newDtmfMode = viewModel.dtmfMode.value
if (newDtmfMode != acc.dtmfMode) {
if (Api.account_set_dtmfmode(acc.accp, newDtmfMode) == 0) {
acc.dtmfMode = Api.account_dtmfmode(acc.accp)
Log.d(TAG, "New dtmfmode is ${acc.dtmfMode}")
} else {
Log.e(TAG, "Setting of dtmfmode $newDtmfMode failed")
}
}
val newAnswerMode = viewModel.answerMode.value
if (newAnswerMode != acc.answerMode) {
if (Api.account_set_answermode(acc.accp, newAnswerMode) == 0) {
acc.answerMode = Api.account_answermode(acc.accp)
Log.d(TAG, "New answermode is ${acc.answerMode}")
} else {
Log.e(TAG, "Setting of answermode $newAnswerMode failed")
}
}
val newAutoRedirect = viewModel.autoRedirect.value
if (newAutoRedirect != acc.autoRedirect) {
Api.account_set_sip_autoredirect(acc.accp, newAutoRedirect)
acc.autoRedirect = newAutoRedirect
Log.d(TAG, "New autoRedirect is ${acc.autoRedirect}")
}
val newBlockUnknown = viewModel.blockUnknown.value
if (newBlockUnknown != acc.blockUnknown) {
acc.blockUnknown = newBlockUnknown
Log.d(TAG, "New blockUnknown is ${acc.blockUnknown}")
}
var newVmUri = viewModel.vmUri.value.trim()
if (newVmUri != acc.vmUri) {
if (newVmUri != "") {
if (!newVmUri.startsWith("sip:")) newVmUri = "sip:$newVmUri"
if (!newVmUri.contains("@")) newVmUri = "$newVmUri@${acc.host()}"
if (!Utils.checkUri(newVmUri)) {
alertTitle.value = noticeTitle
alertMessage.value = String.format(ctx.getString(R.string.invalid_sip_or_tel_uri),
newVmUri)
showAlert.value = true
return false
}
Api.account_set_mwi(acc.accp, true)
}
else
Api.account_set_mwi(acc.accp, false)
acc.vmUri = newVmUri
Log.d(TAG, "New voicemail URI is ${acc.vmUri}")
}
val newCountryCode = viewModel.countryCode.value.trim()
if (newCountryCode != acc.countryCode) {
if (newCountryCode != "" && !Utils.checkCountryCode(newCountryCode)) {
alertTitle.value = ctx.getString(R.string.notice)
alertMessage.value = String.format(ctx.getString(R.string.invalid_country_code),
newCountryCode)
showAlert.value = true
return false
}
acc.countryCode = newCountryCode
Log.d(TAG, "New country code is ${acc.countryCode}")
}
val hostPart = viewModel.telProvider.value.trim()
if (hostPart != acc.telProvider) {
if (hostPart != "" && !Utils.checkHostPortParams(hostPart)) {
alertTitle.value = noticeTitle
alertMessage.value = String.format(ctx.getString(R.string.invalid_sip_uri_hostpart), hostPart)
showAlert.value = true
return false
}
acc.telProvider = hostPart
Log.d(TAG, "New tel provider is ${acc.telProvider}")
}
val newNumericKeypad = viewModel.numericKeypad.value
if (newNumericKeypad != acc.numericKeypad) {
acc.numericKeypad = newNumericKeypad
Log.d(TAG, "New numericKeyboard is ${acc.numericKeypad}")
}
val newCustomParams = viewModel.customParams.value
if (newCustomParams != acc.customParams) {
acc.customParams = newCustomParams
Log.d(TAG, "New customParams is ${acc.customParams}")
}
if (viewModel.defaultAccount.value) ua.makeDefault()
Api.account_debug(acc.accp)
Account.saveAccounts()
if (acc.authUser != "" && BaresipService.aorPasswords[acc.aor] == NO_AUTH_PASS) {
showPasswordDialog.value = true
return false
}
else
return true
}
private fun initAccountFromNetwork(acc: Account, onConfigLoaded: () -> Unit) {
val scope = CoroutineScope(Job() + Dispatchers.Main)
scope.launch(Dispatchers.IO) {
val url = "https://${Utils.uriHostPart(acc.aor)}/baresip/account_config.xml"
val urlConnection = URL(url).openConnection() as HttpsURLConnection
urlConnection.connectTimeout = 5000
urlConnection.readTimeout = 3000
val caFile = File(BaresipService.filesPath + "/ca_certs.crt")
val template = try {
if (caFile.exists())
Utils.readUrlWithCustomCAs(urlConnection, caFile)
else
urlConnection.inputStream.bufferedReader().use { it.readText() }
} catch (e: java.lang.Exception) {
Log.d(TAG, "Failed to get account template from network: ${e.message}")
null
}
if (template != null) {
Log.d(TAG, "Got account template '$template'")
val parserFactory: XmlPullParserFactory = XmlPullParserFactory.newInstance()
val parser: XmlPullParser = parserFactory.newPullParser()
parser.setInput(StringReader(template))
var tag: String?
var text = ""
var event = parser.eventType
val audioCodecs = ArrayList(Api.audio_codecs().split(","))
val videoCodecs = ArrayList(Api.video_codecs().split(","))
while (event != XmlPullParser.END_DOCUMENT) {
tag = parser.name
when (event) {
XmlPullParser.TEXT ->
text = parser.text
XmlPullParser.START_TAG -> {
if (tag == "audio-codecs")
acc.audioCodec.clear()
if (tag == "video-codecs")
acc.videoCodec.clear()
}
XmlPullParser.END_TAG ->
when (tag) {
"outbound-proxy-1" ->
if (text.isNotEmpty())
acc.outbound.add(text)
"outbound-proxy-2" ->
if (text.isNotEmpty())
acc.outbound.add(text)
"registration-interval" ->
acc.configuredRegInt = try {
text.toInt()
} catch (_: NumberFormatException) {
900
}
"register" -> {
acc.regint = if (text == "yes") acc.configuredRegInt else 0
if (acc.regint > 0)
acc.checkOrigin = true
}
"audio-codec" ->
if (text in audioCodecs)
acc.audioCodec.add(text)
"video-codec" ->
if (text in videoCodecs)
acc.videoCodec.add(text)
"media-encoding" -> {
val enc = text.lowercase(Locale.ROOT)
if (enc in mediaEncMap.keys && enc.isNotEmpty())
acc.mediaEnc = enc
}
"media-nat" -> {
val nat = text.lowercase(Locale.ROOT)
if (nat in mediaNatMap.keys && nat.isNotEmpty())
acc.mediaNat = nat
}
"stun-turn-server" ->
if (text.isNotEmpty())
acc.stunServer = text
"rtcp-mux" ->
acc.rtcpMux = text == "yes"
"100rel-mode" ->
acc.rel100Mode = if (text == "yes")
Api.REL100_ENABLED
else
Api.REL100_DISABLED
"dtmf-mode" ->
if (text in arrayOf("rtp-event", "sip-info", "auto"))
acc.dtmfMode = when (text) {
"rtp-event" -> Api.DTMFMODE_RTP_EVENT
"sip-info" -> Api.DTMFMODE_SIP_INFO
else -> Api.DTMFMODE_AUTO
}
"answer-mode" ->
if (text in arrayOf("manual", "auto"))
acc.answerMode = if (text == "manual")
Api.ANSWERMODE_MANUAL
else
Api.ANSWERMODE_AUTO
"redirect-mode" ->
acc.autoRedirect = text == "yes"
"voicemail-uri" ->
if (text.isNotEmpty())
acc.vmUri = text
"country-code" ->
acc.countryCode = text
"tel-provider" ->
acc.telProvider = text
}
}
event = parser.next()
}
}
onConfigLoaded()
}
}
private fun checkOutboundUri(uri: String): Boolean {
if (!uri.startsWith("sip:")) return false
return Utils.checkHostPortParams(uri.substring(4))
}
================================================
FILE: app/src/main/kotlin/com/tutpro/baresip/AccountViewModel.kt
================================================
package com.tutpro.baresip
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
class AccountViewModel: ViewModel() {
val nickName = MutableStateFlow("")
val displayName = MutableStateFlow("")
val authUser = MutableStateFlow("")
val authPass = MutableStateFlow("")
val outbound1 = MutableStateFlow("")
val outbound2 = MutableStateFlow("")
val register = MutableStateFlow(false)
val regInt = MutableStateFlow("")
val checkOrigin = MutableStateFlow(true)
val mediaEnc = MutableStateFlow("")
val mediaNat = MutableStateFlow("")
val stunServer = MutableStateFlow("")
val stunUser = MutableStateFlow("")
val stunPass = MutableStateFlow("")
val rtcpMux = MutableStateFlow(false)
val rel100 = MutableStateFlow(false)
val dtmfMode = MutableStateFlow(0)
val answerMode = MutableStateFlow(0)
val autoRedirect = MutableStateFlow(false)
val blockUnknown = MutableStateFlow(false)
val vmUri = MutableStateFlow("")
val countryCode = MutableStateFlow("")
val telProvider = MutableStateFlow("")
val numericKeypad = MutableStateFlow(false)
val defaultAccount = MutableStateFlow(false)
val customParams = MutableStateFlow("")
private var isLoaded = false
fun loadAccount(acc: Account) {
if (isLoaded) return else isLoaded = true
nickName.value = acc.nickName
displayName.value = acc.displayName
authUser.value = acc.authUser
authPass.value = if (BaresipService.aorPasswords[acc.aor] == null && acc.authPass != NO_AUTH_PASS)
acc.authPass
else
""
outbound1.value = if (acc.outbound.isNotEmpty()) acc.outbound[0] else ""
outbound2.value = if (acc.outbound.size > 1) acc.outbound[1] else ""
register.value = acc.regint > 0
regInt.value = acc.configuredRegInt.toString()
checkOrigin.value = acc.checkOrigin
mediaEnc.value = acc.mediaEnc
mediaNat.value = acc.mediaNat
stunServer.value = acc.stunServer
stunUser.value = acc.stunUser
stunPass.value = acc.stunPass
rtcpMux.value = acc.rtcpMux
rel100.value = acc.rel100Mode == Api.REL100_ENABLED
dtmfMode.value = acc.dtmfMode
answerMode.value = acc.answerMode
autoRedirect.value = acc.autoRedirect
blockUnknown.value = acc.blockUnknown
vmUri.value = acc.vmUri
countryCode.value = acc.countryCode
telProvider.value = acc.telProvider
numericKeypad.value = acc.numericKeypad
defaultAccount.value = UserAgent.findAorIndex(acc.aor)!! == 0
customParams.value = acc.customParams
}
}
================================================
FILE: app/src/main/kotlin/com/tutpro/baresip/AccountsScreen.kt
================================================
package com.tutpro.baresip
import android.content.Context
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.outlined.Clear
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SmallFloatingActionButton
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import com.tutpro.baresip.CustomElements.AlertDialog
import com.tutpro.baresip.CustomElements.verticalScrollbar
fun NavGraphBuilder.accountsScreenRoute(navController: NavController) {
composable("accounts") {
AccountsScreen(navController)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AccountsScreen(navController: NavController) {
Scaffold(
modifier = Modifier.fillMaxSize().imePadding(),
containerColor = MaterialTheme.colorScheme.background,
topBar = {
Column(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.background)
.padding(
top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
)
) {
TopAppBar(
title = {
Text(
text = stringResource(R.string.accounts),
fontWeight = FontWeight.Bold
)
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primary,
navigationIconContentColor = MaterialTheme.colorScheme.onPrimary,
titleContentColor = MaterialTheme.colorScheme.onPrimary,
),
navigationIcon = {
IconButton(onClick = { navController.navigateUp() }) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = null,
)
}
},
windowInsets = WindowInsets(0, 0, 0, 0)
)
}
},
bottomBar = { NewAccount(navController) },
content = { contentPadding ->
AccountsContent(contentPadding, navController)
},
)
}
@Composable
fun AccountsContent(contentPadding: PaddingValues, navController: NavController) {
val showDialog = remember { mutableStateOf(false) }
val message = remember { mutableStateOf("") }
val lastAction = remember { mutableStateOf({}) }
AlertDialog(
showDialog = showDialog,
title = stringResource(R.string.confirmation),
message = message.value,
firstButtonText = stringResource(R.string.cancel),
lastButtonText = stringResource(R.string.delete),
onLastClicked = lastAction.value,
)
val showAccounts = remember { mutableStateOf(true) }
if (showAccounts.value && BaresipService.uas.value.isNotEmpty()) {
val lazyListState = rememberLazyListState()
LazyColumn(
state = lazyListState,
modifier = Modifier
.fillMaxWidth()
.padding(contentPadding)
.padding(start = 16.dp, end = 4.dp, top = 8.dp, bottom = 8.dp)
.verticalScrollbar(state = lazyListState),
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
items(BaresipService.uas.value) { ua ->
val account = ua.account
val aor = account.aor
val text = account.text()
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = text,
fontSize = 20.sp,
color = MaterialTheme.colorScheme.onBackground,
modifier = Modifier
.weight(1f)
.padding(start = 10.dp)
.clickable {
navController.navigate("account/$aor/old")
}
)
val deleteAccountMessage = stringResource(R.string.delete_account)
SmallFloatingActionButton(
modifier = Modifier.padding(end = 8.dp),
onClick = {
message.value = String.format(deleteAccountMessage, text)
lastAction.value = {
CallHistoryNew.clear(aor)
Message.clearMessagesOfAor(aor)
ua.remove()
Api.ua_destroy(ua.uap)
Account.saveAccounts()
showAccounts.value = false
showAccounts.value = true
}
showDialog.value = true
},
containerColor = MaterialTheme.colorScheme.errorContainer,
contentColor = MaterialTheme.colorScheme.onErrorContainer
) {
Icon(
Icons.Filled.Delete,
contentDescription = stringResource(R.string.delete)
)
}
}
}
}
}
}
@Composable
fun NewAccount(navController: NavController) {
val alertTitle = remember { mutableStateOf("") }
val alertMessage = remember { mutableStateOf("") }
val showAlert = remember { mutableStateOf(false) }
if (showAlert.value)
AlertDialog(
showDialog = showAlert,
title = alertTitle.value,
message = alertMessage.value,
lastButtonText = stringResource(R.string.ok),
)
fun createNew(ctx: Context, newAor: String): Account? {
val aor = if (newAor.startsWith("sip:"))
newAor
else
"sip:$newAor"
if (!Utils.checkAor(aor)) {
alertTitle.value = ctx.getString(R.string.notice)
alertMessage.value =
String.format(ctx.getString(R.string.invalid_aor), aor.split(":")[1])
showAlert.value = true
return null
}
if (Account.ofAor(aor) != null) {
alertTitle.value = ctx.getString(R.string.notice)
alertMessage.value =
String.format(ctx.getString(R.string.account_exists), aor.split(":")[1])
showAlert.value = true
return null
}
val ua = UserAgent.uaAlloc(
"<$aor>;stunserver=\"stun:stun.l.google.com:19302\";regq=0.5;pubint=0;regint=0;check_origin=no;mwi=no"
)
if (ua == null) {
alertTitle.value = ctx.getString(R.string.notice)
alertMessage.value = ctx.getString(R.string.account_allocation_failure)
showAlert.value = true
return null
}
val acc = ua.account
acc.checkOrigin = true
Log.d(TAG, "Allocated UA ${ua.uap} with SIP URI ${acc.luri}")
Account.saveAccounts()
return acc
} // createNew
var newAor by remember { mutableStateOf("") }
val focusManager = LocalFocusManager.current
Row(
modifier = Modifier
.fillMaxWidth()
.navigationBarsPadding()
.padding(start = 16.dp, end = 8.dp, top = 10.dp, bottom = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
val ctx = LocalContext.current
val newAccountTitle = stringResource(R.string.new_account)
val accountsHelp = stringResource(R.string.accounts_help)
OutlinedTextField(
value = newAor,
placeholder = { Text(text = stringResource(R.string.new_account)) },
onValueChange = { newAor = it },
modifier = Modifier
.weight(1f)
.padding(end = 8.dp)
.verticalScroll(rememberScrollState())
.clickable {
alertTitle.value = newAccountTitle
alertMessage.value = accountsHelp
showAlert.value = true
},
singleLine = false,
trailingIcon = {
if (newAor.isNotEmpty()) {
Icon(
Icons.Outlined.Clear,
contentDescription = "Clear",
modifier = Modifier.clickable { newAor = "" },
tint = MaterialTheme.colorScheme.onSurfaceVariant
)
}
},
label = { Text(stringResource(R.string.new_account)) },
textStyle = TextStyle(fontSize = 18.sp),
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Text,
)
)
SmallFloatingActionButton(
modifier = Modifier.offset(y = 2.dp),
onClick = {
val account = createNew(ctx, newAor.trim())
if (account != null) {
navController.navigate("account/${account.aor}/new")
newAor = ""
focusManager.clearFocus()
}
},
containerColor = MaterialTheme.colorScheme.secondary,
contentColor = MaterialTheme.colorScheme.onSecondary
) {
Icon(
imageVector = Icons.Filled.Add,
modifier = Modifier.size(36.dp),
contentDescription = stringResource(R.string.add)
)
}
}
}
================================================
FILE: app/src/main/kotlin/com/tutpro/baresip/AndroidContactScreen.kt
================================================
package com.tutpro.baresip
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.Chat
import androidx.compose.material.icons.outlined.Call
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavType
import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import coil.compose.AsyncImage
fun NavGraphBuilder.androidContactScreenRoute(navController: NavController, viewModel: ViewModel) {
composable(
route = "android_contact/{name}",
arguments = listOf(navArgument("name") { type = NavType.StringType })
) { backStackEntry ->
val ctx = LocalContext.current
val name = backStackEntry.arguments?.getString("name")!!
ContactScreen(
ctx = ctx,
viewModel = viewModel,
navController = navController,
name = name
)
}
}
@Composable
private fun ContactScreen(ctx: Context, viewModel: ViewModel, navController: NavController, name: String) {
val contact = Contact.androidContact(name)
if (contact == null) {
Log.e(TAG, "No Android contact found with name $name")
navController.navigateUp()
}
Scaffold(
modifier = Modifier.fillMaxSize().imePadding(),
containerColor = MaterialTheme.colorScheme.background,
topBar = {
Column(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.background)
.padding(top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding())
) {
TopAppBar(name, navController)
}
},
content = { contentPadding ->
ContactContent(ctx, viewModel, navController, contentPadding, contact!!)
}
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun TopAppBar(title: String, navController: NavController) {
TopAppBar(
title = { Text(text = title, fontWeight = FontWeight.Bold) },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primary,
navigationIconContentColor = MaterialTheme.colorScheme.onPrimary,
titleContentColor = MaterialTheme.colorScheme.onPrimary,
),
windowInsets = WindowInsets(0, 0, 0, 0),
navigationIcon = {
IconButton(onClick = { navController.navigateUp() }) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Back",
)
}
}
)
}
@Composable
private fun ContactContent(
ctx: Context,
viewModel: ViewModel,
navController: NavController,
contentPadding: PaddingValues,
contact: Contact.AndroidContact
) {
Column(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.background)
.padding(contentPadding)
.padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 52.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
Avatar(contact)
ContactName(contact.name)
Uris(ctx, viewModel, navController, contact)
}
}
@Composable
private fun TextAvatar(text: String, color: Int) {
Box(
modifier = Modifier.size(avatarSize.dp),
contentAlignment = Alignment.Center
) {
Canvas(modifier = Modifier.fillMaxSize()) {
drawCircle(SolidColor(Color(color)))
}
Text(text, fontSize = 72.sp, color = Color.White)
}
}
@Composable
private fun ImageAvatar(uri: Uri) {
AsyncImage(
model = uri,
contentDescription = stringResource(R.string.avatar_image),
contentScale = ContentScale.Crop,
modifier = Modifier.size(avatarSize.dp).clip(CircleShape)
)
}
@Composable
private fun Avatar(contact: Contact.AndroidContact) {
Row(
Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
val color = contact.color
val name = contact.name
val thumbnailUri = contact.thumbnailUri
if (thumbnailUri != null)
ImageAvatar(thumbnailUri)
else
TextAvatar(if (name == "") "" else name[0].toString(), color)
}
}
@Composable
private fun ContactName(name: String) {
Row(
Modifier.fillMaxWidth().padding(top = 16.dp, bottom = 8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
Text(name, fontSize = 24.sp, color = MaterialTheme.colorScheme.onBackground)
}
}
@Composable
private fun Uris(
ctx: Context,
viewModel: ViewModel,
navController: NavController,
contact: Contact.AndroidContact
) {
val lazyListState = rememberLazyListState()
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 4.dp)
.background(MaterialTheme.colorScheme.background),
state = lazyListState,
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
items(contact.uris) { uri ->
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = uri.substringAfter(":"),
modifier = Modifier.weight(1f),
fontSize = 18.sp,
color = MaterialTheme.colorScheme.onBackground,
)
// Chat Button
IconButton(
onClick = {
val aor = viewModel.selectedAor.value
val ua = UserAgent.ofAor(aor)
if (ua == null)
Log.w(TAG, "Message clickable did not find AoR $aor")
else {
val intent = Intent(ctx, MainActivity::class.java)
intent.putExtra("uap", ua.uap)
intent.putExtra("peer", uri)
handleIntent(ctx, viewModel, intent, "message")
navController.navigate("main") {
popUpTo("main") { inclusive = false }
launchSingleTop = true
}
}
}
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.Chat,
contentDescription = "Send Message",
tint = MaterialTheme.colorScheme.onBackground
)
}
// Call Button
IconButton(
onClick = {
val aor = viewModel.selectedAor.value
val ua = UserAgent.ofAor(aor)
if (ua == null)
Log.w(TAG, "Call clickable did not find AoR $aor")
else {
val intent = Intent(ctx, MainActivity::class.java)
intent.putExtra("uap", ua.uap)
intent.putExtra("peer", uri)
handleIntent(ctx, viewModel, intent, "call")
navController.navigate("main") {
popUpTo("main") { inclusive = false }
launchSingleTop = true
}
}
}
) {
Icon(
imageVector = Icons.Outlined.Call,
contentDescription = "Call",
tint = MaterialTheme.colorScheme.onBackground
)
}
}
}
}
}
================================================
FILE: app/src/main/kotlin/com/tutpro/baresip/Api.kt
================================================
package com.tutpro.baresip
object Api {
const val VIDMODE_OFF = 0
// const val VIDMODE_ON = 1
const val ANSWERMODE_MANUAL = 0
const val ANSWERMODE_AUTO = 2
const val DTMFMODE_RTP_EVENT = 0
const val DTMFMODE_SIP_INFO = 1
const val DTMFMODE_AUTO = 2
const val REL100_DISABLED = 0
const val REL100_ENABLED = 1
// const cal REL100_REQUIRED = 2
const val SDP_INACTIVE = 0
const val SDP_RECVONLY = 1
// const val SDP_SENDONLY = 2
// const val SDP_SENDRECV = 3
const val CALL_STATE_EARLY = 4
const val REPLACES = 1
external fun account_set_display_name(acc: Long, dn: String): Int
external fun account_display_name(acc: Long): String
external fun account_aor(acc: Long): String
external fun account_luri(acc: Long): String
external fun account_auth_user(acc: Long): String
external fun account_set_auth_user(acc: Long, user: String): Int
external fun account_auth_pass(acc: Long): String
external fun account_set_auth_pass(acc: Long, pass: String): Int
external fun account_outbound(acc: Long, ix: Int): String
external fun account_set_outbound(acc: Long, ob: String, ix: Int): Int
external fun account_set_sipnat(acc: Long, sipnat: String): Int
external fun account_audio_codec(acc: Long, ix: Int): String
external fun account_regint(acc: Long): Int
external fun account_set_regint(acc: Long, regint: Int): Int
external fun account_check_origin(acc: Long): Boolean
external fun account_set_check_origin(acc: Long, check: Boolean)
external fun account_stun_uri(acc: Long): String
external fun account_set_stun_uri(acc: Long, uri: String): Int
external fun account_stun_user(acc: Long): String
external fun account_set_stun_user(acc: Long, user: String): Int
external fun account_stun_pass(acc: Long): String
external fun account_set_stun_pass(acc: Long, pass: String): Int
external fun account_mediaenc(acc: Long): String
external fun account_set_mediaenc(acc: Long, mediaenc: String): Int
external fun account_medianat(acc: Long): String
external fun account_set_medianat(acc: Long, medianat: String): Int
external fun account_set_audio_codecs(acc: Long, codecs: String): Int
external fun account_set_video_codecs(acc: Long, codecs: String): Int
external fun account_set_mwi(acc: Long, value: Boolean): Int
external fun account_vm_uri(acc: Long): String
external fun account_answermode(acc: Long): Int
external fun account_set_answermode(acc: Long, mode: Int): Int
external fun account_sip_autoredirect(acc: Long): Boolean
external fun account_set_sip_autoredirect(acc: Long, allow: Boolean)
external fun account_rel100_mode(acc: Long): Int
external fun account_set_rel100_mode(acc: Long, mode: Int): Int
external fun account_rtcp_mux(acc: Long): Boolean
external fun account_set_rtcp_mux(acc: Long, value: Boolean): Int
external fun account_dtmfmode(acc: Long): Int
external fun account_set_dtmfmode(acc: Long, mode: Int): Int
external fun account_extra(acc: Long): String
external fun account_debug(acc: Long)
external fun uag_reset_transp(register: Boolean, reinvite: Boolean)
external fun uag_enable_sip_trace(enable: Boolean)
external fun ua_account(uap: Long): Long
external fun ua_update_account(uap: Long): Long
external fun ua_alloc(uri: String): Long
external fun ua_destroy(uap: Long)
external fun ua_register(uap: Long): Int
@Suppress("unused")
external fun ua_isregistered(uap: Long): Boolean
external fun ua_unregister(uap: Long)
external fun ua_accept(uap: Long, msg: Long)
external fun ua_hangup(uap: Long, callp: Long, code: Int, reason: String)
external fun ua_call_alloc(uap: Long, xcallp: Long, video: Int): Long
external fun ua_answer(uap: Long, callp: Long, video: Int)
@Suppress("unused")
external fun ua_add_custom_header(uap: Long, name: String, body: String)
@Suppress("unused")
external fun ua_debug(uap: Long)
external fun sip_treply(msg: Long, code: Int, reason: String)
external fun bevent_stop(event: Long)
external fun call_connect(callp: Long, peer_uri: String): Int
external fun call_hold(callp: Long, hold: Boolean): Boolean
@Suppress("unused")
external fun call_ismuted(callp: Long): Boolean
external fun call_transfer(callp: Long, peer_uri: String): Int
external fun call_send_digit(callp: Long, digit: Char): Int
external fun call_notify_sipfrag(callp: Long, code: Int, reason: String)
@Suppress("unused")
external fun call_start_audio(callp: Long)
external fun call_audio_codecs(callp: Long): String
external fun call_duration(callp: Long): Int
external fun call_stats(callp: Long, stream: String): String
external fun call_state(callp: Long): Int
external fun call_replaces(callp: Long): Boolean
external fun call_replace_transfer(xfer_callp: Long, callp: Long): Boolean
external fun call_peer_uri(callp: Long): String
external fun call_diverter_uri(callp: Long): String
external fun call_supported(callp: Long, header: Int): Boolean
external fun call_destroy(callp: Long)
external fun calls_mute(mute: Boolean)
external fun message_send(uap: Long, peer_uri: String, message: String, time: String): Int
external fun audio_codecs(): String
external fun video_codecs(): String
external fun log_level_set(level: Int)
external fun net_use_nameserver(servers: String): Int
external fun net_add_address_ifname(ip_addr: String, if_name: String): Int
external fun net_rm_address(ip_addr: String): Int
external fun net_debug()
@Suppress("unused")
external fun net_dns_debug()
external fun config_verify_server_set(verify: Boolean)
external fun cmd_exec(cmd: String): Int
external fun module_load(module: String): Int
external fun module_unload(module: String)
external fun AAudio_open_stream(): Int
external fun AAudio_close_stream()
}
================================================
FILE: app/src/main/kotlin/com/tutpro/baresip/AudioScreen.kt
================================================
package com.tutpro.baresip
import android.content.Context
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.DropdownMenu
import androidx.co
gitextract_1vqbv6_p/ ├── .clang-format ├── .gitignore ├── .gitmodules ├── LICENSE ├── PrivacyPolicy.txt ├── README.md ├── app/ │ ├── build.gradle.kts │ ├── proguard-rules.pro │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── assets/ │ │ ├── accounts │ │ ├── config.static │ │ └── contacts │ ├── cpp/ │ │ ├── CMakeLists.txt │ │ ├── baresip.c │ │ └── logger.h │ ├── kotlin/ │ │ └── com/ │ │ └── tutpro/ │ │ └── baresip/ │ │ ├── AboutScreen.kt │ │ ├── Account.kt │ │ ├── AccountScreen.kt │ │ ├── AccountViewModel.kt │ │ ├── AccountsScreen.kt │ │ ├── AndroidContactScreen.kt │ │ ├── Api.kt │ │ ├── AudioScreen.kt │ │ ├── BaresipApp.kt │ │ ├── BaresipContactScreen.kt │ │ ├── BaresipService.kt │ │ ├── Blocked.kt │ │ ├── BlockedScreen.kt │ │ ├── BootCompletedReceiver.kt │ │ ├── Call.kt │ │ ├── CallDetailsScreen.kt │ │ ├── CallHistory.kt │ │ ├── CallRow.kt │ │ ├── CallsScreen.kt │ │ ├── ChatScreen.kt │ │ ├── ChatsScreen.kt │ │ ├── Codec.kt │ │ ├── CodecsScreen.kt │ │ ├── Colors.kt │ │ ├── Config.kt │ │ ├── ConnectionService.kt │ │ ├── Constants.kt │ │ ├── Contact.kt │ │ ├── ContactsScreen.kt │ │ ├── CustomElements.kt │ │ ├── DraggableLazyList.kt │ │ ├── Event.kt │ │ ├── InCallService.kt │ │ ├── Log.kt │ │ ├── MainActivity.kt │ │ ├── MainScreen.kt │ │ ├── Message.kt │ │ ├── Preferences.kt │ │ ├── ServiceEvent.kt │ │ ├── SettingsScreen.kt │ │ ├── SettingsViewModel.kt │ │ ├── TaskReceiver.kt │ │ ├── Theme.kt │ │ ├── UserAgent.kt │ │ ├── Utils.kt │ │ └── ViewModel.kt │ └── res/ │ ├── drawable/ │ │ ├── circle_green.xml │ │ ├── circle_green_blind.xml │ │ ├── circle_red.xml │ │ ├── circle_red_blind.xml │ │ ├── circle_white.xml │ │ ├── circle_yellow.xml │ │ ├── circle_yellow_blind.xml │ │ ├── ic_launcher_foreground.xml │ │ ├── ic_notification_b.xml │ │ ├── ic_notification_call.xml │ │ ├── ic_notification_call_end.xml │ │ ├── ic_notification_call_missed.xml │ │ ├── ic_notification_delete.xml │ │ ├── ic_notification_message.xml │ │ ├── ic_notification_reply.xml │ │ └── ic_notification_save.xml │ ├── layout/ │ │ └── status_notification.xml │ ├── mipmap-anydpi/ │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ ├── values/ │ │ ├── colors.xml │ │ └── strings.xml │ ├── values-ar/ │ │ └── strings.xml │ ├── values-bg/ │ │ └── strings.xml │ ├── values-ca/ │ │ └── strings.xml │ ├── values-cs/ │ │ └── strings.xml │ ├── values-de/ │ │ └── strings.xml │ ├── values-el/ │ │ └── strings.xml │ ├── values-es/ │ │ └── strings.xml │ ├── values-fi/ │ │ └── strings.xml │ ├── values-fr/ │ │ └── strings.xml │ ├── values-hr/ │ │ └── strings.xml │ ├── values-in/ │ │ └── strings.xml │ ├── values-iw/ │ │ └── strings.xml │ ├── values-ja-rJP/ │ │ └── strings.xml │ ├── values-ko/ │ │ └── strings.xml │ ├── values-nb-rNO/ │ │ └── strings.xml │ ├── values-night/ │ │ └── colors.xml │ ├── values-pl/ │ │ └── strings.xml │ ├── values-pt/ │ │ └── strings.xml │ ├── values-pt-rBR/ │ │ └── strings.xml │ ├── values-ro/ │ │ └── strings.xml │ ├── values-ru/ │ │ └── strings.xml │ ├── values-sl/ │ │ └── strings.xml │ ├── values-sv/ │ │ └── strings.xml │ ├── values-ta/ │ │ └── strings.xml │ ├── values-uk/ │ │ └── strings.xml │ └── values-zh-rCN/ │ └── strings.xml ├── build.gradle.kts ├── fastlane/ │ └── metadata/ │ └── android/ │ ├── de-DE/ │ │ ├── changelogs/ │ │ │ ├── 10.1.0.txt │ │ │ ├── 10.2.0.txt │ │ │ └── 10.3.0.txt │ │ ├── full_description.txt │ │ ├── short_description.txt │ │ └── title.txt │ ├── el/ │ │ └── title.txt │ ├── en-US/ │ │ ├── changelogs/ │ │ │ ├── 10.0.0.txt │ │ │ ├── 10.1.0.txt │ │ │ ├── 10.2.0.txt │ │ │ ├── 10.3.0.txt │ │ │ ├── 10.4.0.txt │ │ │ ├── 11.0.0.txt │ │ │ ├── 11.1.0.txt │ │ │ ├── 11.2.0.txt │ │ │ ├── 11.3.0.txt │ │ │ ├── 12.0.0.txt │ │ │ ├── 12.1.0.txt │ │ │ ├── 12.2.0.txt │ │ │ ├── 12.2.1.txt │ │ │ ├── 13.0.0.txt │ │ │ ├── 13.0.1.txt │ │ │ ├── 14.0.0.txt │ │ │ ├── 15.0.0.txt │ │ │ ├── 15.1.0.txt │ │ │ ├── 15.1.1.txt │ │ │ ├── 16.0.0.txt │ │ │ ├── 16.0.1.txt │ │ │ ├── 16.1.0.txt │ │ │ ├── 17.0.0.txt │ │ │ ├── 17.1.0.txt │ │ │ ├── 17.2.0.txt │ │ │ ├── 17.2.1.txt │ │ │ ├── 17.2.2.txt │ │ │ ├── 17.2.3.txt │ │ │ ├── 17.3.0.txt │ │ │ ├── 17.4.0.txt │ │ │ ├── 18.0.0.txt │ │ │ ├── 18.0.1.txt │ │ │ ├── 18.1.0.txt │ │ │ ├── 18.1.1.txt │ │ │ ├── 18.1.2.txt │ │ │ ├── 18.1.3.txt │ │ │ ├── 18.2.0.txt │ │ │ ├── 18.2.1.txt │ │ │ ├── 18.3.0.txt │ │ │ ├── 19.0.0.txt │ │ │ ├── 19.1.0.txt │ │ │ ├── 20.0.0.txt │ │ │ ├── 20.0.1.txt │ │ │ ├── 20.0.2.txt │ │ │ ├── 20.1.0.txt │ │ │ ├── 21.0.0.txt │ │ │ ├── 21.1.0.txt │ │ │ ├── 21.2.0.txt │ │ │ ├── 22.0.0.txt │ │ │ ├── 22.1.0.txt │ │ │ ├── 23.0.0.txt │ │ │ ├── 23.1.0.txt │ │ │ ├── 23.2.0.txt │ │ │ ├── 24.0.0.txt │ │ │ ├── 24.1.0.txt │ │ │ ├── 24.2.0.txt │ │ │ ├── 24.3.0.txt │ │ │ ├── 24.4.0.txt │ │ │ ├── 25.0.0.txt │ │ │ ├── 26.0.0.txt │ │ │ ├── 26.0.1.txt │ │ │ ├── 26.1.0.txt │ │ │ ├── 26.1.1.txt │ │ │ ├── 26.1.2.txt │ │ │ ├── 27.0.0.txt │ │ │ ├── 27.0.1.txt │ │ │ ├── 28.0.0.txt │ │ │ ├── 28.1.0.txt │ │ │ ├── 28.1.1.txt │ │ │ ├── 28.2.0.txt │ │ │ ├── 29.0.0.txt │ │ │ ├── 29.1.0.txt │ │ │ ├── 29.2.0.txt │ │ │ ├── 29.2.1.txt │ │ │ ├── 3.2.0.txt │ │ │ ├── 3.2.2.txt │ │ │ ├── 30.0.0.txt │ │ │ ├── 30.0.1.txt │ │ │ ├── 30.1.0.txt │ │ │ ├── 30.2.0.txt │ │ │ ├── 30.3.0.txt │ │ │ ├── 31.0.0.txt │ │ │ ├── 31.1.0.txt │ │ │ ├── 31.2.0.txt │ │ │ ├── 31.2.1.txt │ │ │ ├── 32.0.0.txt │ │ │ ├── 32.1.0.txt │ │ │ ├── 32.2.0.txt │ │ │ ├── 32.3.0.txt │ │ │ ├── 33.0.0.txt │ │ │ ├── 34.0.0.txt │ │ │ ├── 34.1.0.txt │ │ │ ├── 35.0.0.txt │ │ │ ├── 35.1.0.txt │ │ │ ├── 35.2.0.txt │ │ │ ├── 36.0.0.txt │ │ │ ├── 36.1.0.txt │ │ │ ├── 36.1.1.txt │ │ │ ├── 36.1.2.txt │ │ │ ├── 36.2.0.txt │ │ │ ├── 37.0.0.txt │ │ │ ├── 37.0.1.txt │ │ │ ├── 37.1.0.txt │ │ │ ├── 37.2.0.txt │ │ │ ├── 37.3.0.txt │ │ │ ├── 37.4.0.txt │ │ │ ├── 38.0.0.txt │ │ │ ├── 39.0.0.txt │ │ │ ├── 39.1.0.txt │ │ │ ├── 4.0.0.txt │ │ │ ├── 4.1.0.txt │ │ │ ├── 4.1.1.txt │ │ │ ├── 4.1.2.txt │ │ │ ├── 40.0.0.txt │ │ │ ├── 40.0.1.txt │ │ │ ├── 40.1.0.txt │ │ │ ├── 41.0.0.txt │ │ │ ├── 42.0.0.txt │ │ │ ├── 42.1.0.txt │ │ │ ├── 42.2.0.txt │ │ │ ├── 42.3.0.txt │ │ │ ├── 43.0.0.txt │ │ │ ├── 43.0.2.txt │ │ │ ├── 43.1.0.txt │ │ │ ├── 44.0.0.txt │ │ │ ├── 44.1.0.txt │ │ │ ├── 44.2.0.txt │ │ │ ├── 446.txt │ │ │ ├── 447.txt │ │ │ ├── 448.txt │ │ │ ├── 449.txt │ │ │ ├── 45.0.0.txt │ │ │ ├── 45.1.0.txt │ │ │ ├── 45.1.1.txt │ │ │ ├── 45.1.2.txt │ │ │ ├── 450.txt │ │ │ ├── 451.txt │ │ │ ├── 452.txt │ │ │ ├── 453.txt │ │ │ ├── 454.txt │ │ │ ├── 455.txt │ │ │ ├── 456.txt │ │ │ ├── 457.txt │ │ │ ├── 458.txt │ │ │ ├── 459.txt │ │ │ ├── 46.0.0.txt │ │ │ ├── 46.0.1.txt │ │ │ ├── 46.1.0.txt │ │ │ ├── 46.1.1.txt │ │ │ ├── 46.2.0.txt │ │ │ ├── 460.txt │ │ │ ├── 461.txt │ │ │ ├── 462.txt │ │ │ ├── 463.txt │ │ │ ├── 464.txt │ │ │ ├── 466.txt │ │ │ ├── 467.txt │ │ │ ├── 468.txt │ │ │ ├── 469.txt │ │ │ ├── 47.0.0.txt │ │ │ ├── 47.1.0.txt │ │ │ ├── 47.2.0.txt │ │ │ ├── 470.txt │ │ │ ├── 471.txt │ │ │ ├── 472.txt │ │ │ ├── 473.txt │ │ │ ├── 474.txt │ │ │ ├── 475.txt │ │ │ ├── 476.txt │ │ │ ├── 477.txt │ │ │ ├── 478.txt │ │ │ ├── 479.txt │ │ │ ├── 48.0.0.txt │ │ │ ├── 48.0.1.txt │ │ │ ├── 48.1.0.txt │ │ │ ├── 48.2.0.txt │ │ │ ├── 480.txt │ │ │ ├── 481.txt │ │ │ ├── 482.txt │ │ │ ├── 483.txt │ │ │ ├── 484.txt │ │ │ ├── 487.txt │ │ │ ├── 488.txt │ │ │ ├── 489.txt │ │ │ ├── 49.0.0.txt │ │ │ ├── 49.0.1.txt │ │ │ ├── 49.1.0.txt │ │ │ ├── 49.1.1 │ │ │ ├── 49.1.2.txt │ │ │ ├── 49.1.3.txt │ │ │ ├── 49.2.0.txt │ │ │ ├── 49.3.1.txt │ │ │ ├── 49.4.0.txt │ │ │ ├── 491.txt │ │ │ ├── 492.txt │ │ │ ├── 5.0.0.txt │ │ │ ├── 5.1.0.txt │ │ │ ├── 5.2.0.txt │ │ │ ├── 5.2.1.txt │ │ │ ├── 5.3.0.txt │ │ │ ├── 5.3.1.txt │ │ │ ├── 5.4.0.txt │ │ │ ├── 5.4.1.txt │ │ │ ├── 5.4.2.txt │ │ │ ├── 50.0.0.txt │ │ │ ├── 50.1.0.txt │ │ │ ├── 50.1.1.txt │ │ │ ├── 50.1.3.txt │ │ │ ├── 50.1.4.txt │ │ │ ├── 50.1.5.txt │ │ │ ├── 50.2.0.txt │ │ │ ├── 50.2.1.txt │ │ │ ├── 50.2.2.txt │ │ │ ├── 51.0.0.txt │ │ │ ├── 51.1.0.txt │ │ │ ├── 51.2.0.txt │ │ │ ├── 52.0.0.txt │ │ │ ├── 52.1.0.txt │ │ │ ├── 52.2.0.txt │ │ │ ├── 53.0.0.txt │ │ │ ├── 53.1.0.txt │ │ │ ├── 53.1.1.txt │ │ │ ├── 53.1.2.txt │ │ │ ├── 53.2.0.txt │ │ │ ├── 53.2.1.txt │ │ │ ├── 53.2.2.txt │ │ │ ├── 53.2.3.txt │ │ │ ├── 54.0.0.txt │ │ │ ├── 54.1.0.txt │ │ │ ├── 54.2.0.txt │ │ │ ├── 55.0.0.txt │ │ │ ├── 55.0.1.txt │ │ │ ├── 55.0.2.txt │ │ │ ├── 55.1.0.txt │ │ │ ├── 55.2.0.txt │ │ │ ├── 56.0.0.txt │ │ │ ├── 56.1.0.txt │ │ │ ├── 56.2.0.txt │ │ │ ├── 56.3.0.txt │ │ │ ├── 57.0.0.txt │ │ │ ├── 57.1.0.txt │ │ │ ├── 57.1.1.txt │ │ │ ├── 57.2.0.txt │ │ │ ├── 58.0.0.txt │ │ │ ├── 59.0.0.txt │ │ │ ├── 59.0.1.txt │ │ │ ├── 59.0.2.txt │ │ │ ├── 59.0.3.txt │ │ │ ├── 59.0.4.txt │ │ │ ├── 59.1.0.txt │ │ │ ├── 59.2.0.txt │ │ │ ├── 59.3.0.txt │ │ │ ├── 59.4.0.txt │ │ │ ├── 59.4.1.txt │ │ │ ├── 59.4.2.txt │ │ │ ├── 59.4.3.txt │ │ │ ├── 59.5.0 │ │ │ ├── 59.6.0.txt │ │ │ ├── 59.7.0.txt │ │ │ ├── 6.0.0.txt │ │ │ ├── 6.0.1.txt │ │ │ ├── 6.0.2.txt │ │ │ ├── 6.1.0.txt │ │ │ ├── 6.2.0.txt │ │ │ ├── 6.2.1.txt │ │ │ ├── 6.3.0.txt │ │ │ ├── 6.3.1.txt │ │ │ ├── 6.4.0.txt │ │ │ ├── 60.0.0.txt │ │ │ ├── 60.1.0.txt │ │ │ ├── 60.2.0.txt │ │ │ ├── 60.3.0.txt │ │ │ ├── 60.4.0.txt │ │ │ ├── 60.4.1.txt │ │ │ ├── 60.4.2.txt │ │ │ ├── 61.0.0.txt │ │ │ ├── 61.0.1.txt │ │ │ ├── 61.1.0.txt │ │ │ ├── 62.0.0.txt │ │ │ ├── 62.1.0.txt │ │ │ ├── 63.0.0.txt │ │ │ ├── 63.1.0.txt │ │ │ ├── 63.2.0.txt │ │ │ ├── 63.2.1.txt │ │ │ ├── 63.2.2.txt │ │ │ ├── 63.2.3.txt │ │ │ ├── 63.2.5.txt │ │ │ ├── 63.3.0.txt │ │ │ ├── 64.0.0.txt │ │ │ ├── 64.1.0.txt │ │ │ ├── 64.2.0.txt │ │ │ ├── 64.3.0.txt │ │ │ ├── 64.3.1.txt │ │ │ ├── 65.0.0.txt │ │ │ ├── 65.1.0.txt │ │ │ ├── 65.2.0.txt │ │ │ ├── 65.2.1.txt │ │ │ ├── 65.3.0.txt │ │ │ ├── 66.0.0.txt │ │ │ ├── 66.1.0.txt │ │ │ ├── 66.1.1.txt │ │ │ ├── 66.1.2.txt │ │ │ ├── 66.1.3.txt │ │ │ ├── 66.1.4.txt │ │ │ ├── 66.1.5.txt │ │ │ ├── 66.1.6.txt │ │ │ ├── 67.0.0.txt │ │ │ ├── 67.0.1.txt │ │ │ ├── 67.0.2.txt │ │ │ ├── 67.0.3.txt │ │ │ ├── 67.1.0.txt │ │ │ ├── 67.2.0.txt │ │ │ ├── 68.0.0.txt │ │ │ ├── 68.1.0.txt │ │ │ ├── 68.1.1.txt │ │ │ ├── 68.1.2.txt │ │ │ ├── 7.0.0.txt │ │ │ ├── 7.1.0.txt │ │ │ ├── 7.1.1.txt │ │ │ ├── 7.1.2.txt │ │ │ ├── 7.2.0.txt │ │ │ ├── 7.3.0.txt │ │ │ ├── 7.4.0.txt │ │ │ ├── 8.0.0.txt │ │ │ ├── 8.0.1.txt │ │ │ ├── 8.0.2.txt │ │ │ ├── 8.1.0.txt │ │ │ ├── 8.2.0.txt │ │ │ ├── 8.3.0.txt │ │ │ ├── 8.3.1.txt │ │ │ ├── 8.3.2.txt │ │ │ ├── 8.3.3.txt │ │ │ ├── 8.3.4,txt │ │ │ ├── 8.3.5.txt │ │ │ ├── 8.3.6.txt │ │ │ ├── 8.4.0.txt │ │ │ ├── 8.4.1.txt │ │ │ ├── 8.4.2.txt │ │ │ ├── 8.4.3.txt │ │ │ ├── 8.4.4.txt │ │ │ ├── 8.5.0.txt │ │ │ ├── 8.5.1.txt │ │ │ ├── 8.5.2.txt │ │ │ ├── 8.6.0.txt │ │ │ ├── 9.0.0.txt │ │ │ ├── 9.1.0.txt │ │ │ ├── 9.2.0.txt │ │ │ ├── 9.3.0.txt │ │ │ └── 9.4.0.txt │ │ ├── full_description.txt │ │ ├── short_description.txt │ │ └── title.txt │ ├── fi-FI/ │ │ ├── changelogs/ │ │ │ ├── 10.0.0.txt │ │ │ ├── 10.1.0.txt │ │ │ ├── 10.2.0.txt │ │ │ ├── 10.3.0.txt │ │ │ ├── 10.4.0.txt │ │ │ ├── 11.0.0.txt │ │ │ ├── 11.1.0.txt │ │ │ ├── 11.2.0.txt │ │ │ ├── 11.3.0.txt │ │ │ ├── 12.0.0.txt │ │ │ ├── 12.1.0.txt │ │ │ ├── 12.2.0.txt │ │ │ ├── 12.2.1.txt │ │ │ └── 13.0.0.txt │ │ ├── full_description.txt │ │ ├── short_description.txt │ │ └── title.txt │ ├── iw-IL/ │ │ ├── changelogs/ │ │ │ ├── 10.0.0.txt │ │ │ ├── 10.1.0.txt │ │ │ ├── 10.2.0.txt │ │ │ ├── 10.3.0.txt │ │ │ ├── 10.4.0.txt │ │ │ ├── 11.0.0.txt │ │ │ ├── 11.1.0.txt │ │ │ ├── 11.2.0.txt │ │ │ ├── 11.3.0.txt │ │ │ ├── 12.0.0.txt │ │ │ ├── 12.1.0.txt │ │ │ ├── 12.2.0.txt │ │ │ ├── 12.2.1.txt │ │ │ ├── 13.0.0.txt │ │ │ ├── 13.0.1.txt │ │ │ ├── 14.0.0.txt │ │ │ ├── 15.0.0.txt │ │ │ ├── 15.1.0.txt │ │ │ ├── 15.1.1.txt │ │ │ ├── 16.0.0.txt │ │ │ ├── 16.0.1.txt │ │ │ ├── 16.1.0.txt │ │ │ ├── 17.0.0.txt │ │ │ ├── 17.1.0.txt │ │ │ ├── 17.2.0.txt │ │ │ ├── 17.2.1.txt │ │ │ ├── 17.2.2.txt │ │ │ ├── 17.2.3.txt │ │ │ ├── 17.3.0.txt │ │ │ ├── 17.4.0.txt │ │ │ ├── 18.0.0.txt │ │ │ ├── 18.0.1.txt │ │ │ ├── 18.1.0.txt │ │ │ ├── 18.1.1.txt │ │ │ ├── 18.1.2.txt │ │ │ ├── 18.1.3.txt │ │ │ ├── 18.2.0.txt │ │ │ ├── 18.2.1.txt │ │ │ ├── 18.3.0.txt │ │ │ ├── 19.0.0.txt │ │ │ ├── 19.1.0.txt │ │ │ ├── 20.0.0.txt │ │ │ ├── 20.0.1.txt │ │ │ ├── 20.0.2.txt │ │ │ ├── 20.1.0.txt │ │ │ ├── 21.0.0.txt │ │ │ ├── 21.1.0.txt │ │ │ ├── 21.2.0.txt │ │ │ ├── 22.0.0.txt │ │ │ ├── 22.1.0.txt │ │ │ ├── 23.0.0.txt │ │ │ ├── 23.1.0.txt │ │ │ ├── 23.2.0.txt │ │ │ ├── 24.0.0.txt │ │ │ ├── 24.1.0.txt │ │ │ ├── 24.2.0.txt │ │ │ ├── 24.3.0.txt │ │ │ ├── 24.4.0.txt │ │ │ ├── 25.0.0.txt │ │ │ ├── 26.0.0.txt │ │ │ ├── 26.0.1.txt │ │ │ ├── 26.1.0.txt │ │ │ ├── 26.1.1.txt │ │ │ ├── 26.1.2.txt │ │ │ ├── 27.0.0.txt │ │ │ ├── 27.0.1.txt │ │ │ ├── 28.0.0.txt │ │ │ ├── 28.1.0.txt │ │ │ ├── 28.1.1.txt │ │ │ ├── 28.2.0.txt │ │ │ ├── 29.0.0.txt │ │ │ ├── 29.1.0.txt │ │ │ ├── 29.2.0.txt │ │ │ ├── 29.2.1.txt │ │ │ ├── 3.2.0.txt │ │ │ ├── 3.2.2.txt │ │ │ ├── 30.0.0.txt │ │ │ ├── 30.1.0.txt │ │ │ ├── 30.2.0.txt │ │ │ ├── 30.3.0.txt │ │ │ ├── 37.4.0.txt │ │ │ ├── 53.2.2.txt │ │ │ └── 8.5.0.txt │ │ ├── full_description.txt │ │ ├── short_description.txt │ │ └── title.txt │ ├── nb-NO/ │ │ ├── changelogs/ │ │ │ ├── 10.2.0.txt │ │ │ ├── 12.0.0.txt │ │ │ ├── 12.2.1.txt │ │ │ ├── 3.2.0.txt │ │ │ ├── 3.2.2.txt │ │ │ ├── 36.0.0.txt │ │ │ ├── 36.1.2.txt │ │ │ ├── 4.0.0.txt │ │ │ ├── 4.1.0.txt │ │ │ ├── 4.1.1.txt │ │ │ ├── 4.1.2.txt │ │ │ ├── 5.0.0.txt │ │ │ ├── 5.1.0.txt │ │ │ ├── 5.2.0.txt │ │ │ ├── 5.2.1.txt │ │ │ ├── 5.3.0.txt │ │ │ ├── 5.3.1.txt │ │ │ ├── 5.4.0.txt │ │ │ ├── 5.4.1.txt │ │ │ ├── 5.4.2.txt │ │ │ ├── 6.0.0.txt │ │ │ ├── 6.0.1.txt │ │ │ ├── 6.0.2.txt │ │ │ ├── 6.1.0.txt │ │ │ ├── 6.2.0.txt │ │ │ ├── 6.2.1.txt │ │ │ ├── 6.3.0.txt │ │ │ ├── 6.3.1.txt │ │ │ ├── 6.4.0.txt │ │ │ ├── 7.0.0.txt │ │ │ ├── 7.1.0.txt │ │ │ ├── 7.1.1.txt │ │ │ ├── 7.1.2.txt │ │ │ ├── 7.2.0.txt │ │ │ ├── 7.3.0.txt │ │ │ ├── 7.4.0.txt │ │ │ ├── 8.0.0.txt │ │ │ ├── 8.0.1.txt │ │ │ ├── 8.0.2.txt │ │ │ ├── 8.1.0.txt │ │ │ ├── 8.2.0.txt │ │ │ ├── 8.3.0.txt │ │ │ ├── 8.3.1.txt │ │ │ ├── 8.3.2.txt │ │ │ ├── 8.3.3.txt │ │ │ ├── 8.3.5.txt │ │ │ ├── 8.3.6.txt │ │ │ ├── 8.4.0.txt │ │ │ ├── 8.4.1.txt │ │ │ ├── 8.4.2.txt │ │ │ ├── 8.4.3.txt │ │ │ ├── 8.4.4.txt │ │ │ ├── 8.5.0.txt │ │ │ ├── 8.5.1.txt │ │ │ ├── 8.5.2.txt │ │ │ ├── 8.6.0.txt │ │ │ ├── 9.0.0.txt │ │ │ ├── 9.1.0.txt │ │ │ ├── 9.2.0.txt │ │ │ └── 9.3.0.txt │ │ ├── full_description.txt │ │ ├── short_description.txt │ │ └── title.txt │ ├── pl/ │ │ └── changelogs/ │ │ └── 15.1.1.txt │ ├── ro/ │ │ ├── full_description.txt │ │ ├── short_description.txt │ │ └── title.txt │ ├── ru-RU/ │ │ ├── changelogs/ │ │ │ ├── 10.0.0.txt │ │ │ ├── 10.1.0.txt │ │ │ ├── 10.2.0.txt │ │ │ ├── 10.3.0.txt │ │ │ ├── 10.4.0.txt │ │ │ ├── 11.0.0.txt │ │ │ ├── 11.1.0.txt │ │ │ ├── 11.2.0.txt │ │ │ ├── 11.3.0.txt │ │ │ ├── 12.0.0.txt │ │ │ ├── 12.1.0.txt │ │ │ ├── 12.2.0.txt │ │ │ ├── 12.2.1.txt │ │ │ ├── 15.1.1.txt │ │ │ ├── 17.2.1.txt │ │ │ ├── 18.0.1.txt │ │ │ ├── 18.1.1.txt │ │ │ ├── 18.1.3.txt │ │ │ ├── 18.2.0.txt │ │ │ ├── 18.2.1.txt │ │ │ ├── 18.3.0.txt │ │ │ ├── 19.1.0.txt │ │ │ ├── 20.0.2.txt │ │ │ ├── 20.1.0.txt │ │ │ ├── 22.0.0.txt │ │ │ ├── 22.1.0.txt │ │ │ ├── 23.2.0.txt │ │ │ ├── 24.0.0.txt │ │ │ ├── 24.4.0.txt │ │ │ ├── 25.0.0.txt │ │ │ ├── 26.0.0.txt │ │ │ ├── 26.1.1.txt │ │ │ ├── 26.1.2.txt │ │ │ ├── 27.0.0.txt │ │ │ ├── 27.0.1.txt │ │ │ ├── 28.0.0.txt │ │ │ ├── 28.1.1.txt │ │ │ ├── 28.2.0.txt │ │ │ ├── 29.2.1.txt │ │ │ ├── 30.0.1.txt │ │ │ ├── 30.1.0.txt │ │ │ ├── 31.2.1.txt │ │ │ ├── 32.1.0.txt │ │ │ ├── 32.2.0.txt │ │ │ ├── 34.1.0.txt │ │ │ ├── 36.1.2.txt │ │ │ ├── 37.0.0.txt │ │ │ ├── 37.0.1.txt │ │ │ ├── 37.3.0.txt │ │ │ ├── 37.4.0.txt │ │ │ ├── 4.1.2.txt │ │ │ ├── 42.1.0.txt │ │ │ ├── 44.2.0.txt │ │ │ ├── 45.1.0.txt │ │ │ ├── 45.1.2.txt │ │ │ ├── 46.0.1.txt │ │ │ ├── 49.1.0.txt │ │ │ ├── 49.1.2.txt │ │ │ ├── 49.1.3.txt │ │ │ ├── 49.4.0.txt │ │ │ ├── 5.3.1.txt │ │ │ ├── 50.1.4.txt │ │ │ ├── 50.1.5.txt │ │ │ ├── 51.2.0.txt │ │ │ ├── 53.0.0.txt │ │ │ ├── 53.1.0.txt │ │ │ ├── 53.1.1.txt │ │ │ ├── 6.0.0.txt │ │ │ ├── 6.0.2.txt │ │ │ ├── 7.1.0.txt │ │ │ ├── 7.1.1.txt │ │ │ ├── 8.0.1.txt │ │ │ ├── 8.0.2.txt │ │ │ ├── 8.2.0.txt │ │ │ ├── 8.3.0.txt │ │ │ ├── 8.3.5.txt │ │ │ ├── 8.4.0.txt │ │ │ ├── 8.4.1.txt │ │ │ ├── 8.4.3.txt │ │ │ ├── 8.5.0.txt │ │ │ ├── 8.5.2.txt │ │ │ ├── 8.6.0.txt │ │ │ ├── 9.0.0.txt │ │ │ ├── 9.1.0.txt │ │ │ ├── 9.2.0.txt │ │ │ ├── 9.3.0.txt │ │ │ └── 9.4.0.txt │ │ ├── full_description.txt │ │ ├── short_description.txt │ │ └── title.txt │ ├── sv/ │ │ ├── changelogs/ │ │ │ ├── 10.0.0.txt │ │ │ ├── 10.1.0.txt │ │ │ ├── 10.2.0.txt │ │ │ ├── 10.3.0.txt │ │ │ ├── 10.4.0.txt │ │ │ ├── 11.0.0.txt │ │ │ ├── 11.1.0.txt │ │ │ ├── 11.2.0.txt │ │ │ ├── 11.3.0.txt │ │ │ ├── 12.0.0.txt │ │ │ ├── 12.1.0.txt │ │ │ ├── 12.2.0.txt │ │ │ ├── 12.2.1.txt │ │ │ ├── 13.0.0.txt │ │ │ ├── 13.0.1.txt │ │ │ ├── 14.0.0.txt │ │ │ ├── 15.0.0.txt │ │ │ ├── 15.1.0.txt │ │ │ ├── 15.1.1.txt │ │ │ ├── 16.0.0.txt │ │ │ ├── 16.0.1.txt │ │ │ ├── 16.1.0.txt │ │ │ ├── 17.0.0.txt │ │ │ ├── 17.1.0.txt │ │ │ ├── 17.2.0.txt │ │ │ ├── 17.2.1.txt │ │ │ ├── 17.2.2.txt │ │ │ ├── 17.2.3.txt │ │ │ ├── 17.3.0.txt │ │ │ ├── 17.4.0.txt │ │ │ ├── 18.0.0.txt │ │ │ ├── 18.0.1.txt │ │ │ ├── 18.1.0.txt │ │ │ ├── 18.1.1.txt │ │ │ ├── 18.1.2.txt │ │ │ ├── 18.1.3.txt │ │ │ ├── 18.2.0.txt │ │ │ ├── 18.2.1.txt │ │ │ ├── 18.3.0.txt │ │ │ ├── 19.0.0.txt │ │ │ ├── 19.1.0.txt │ │ │ ├── 20.0.0.txt │ │ │ ├── 20.0.1.txt │ │ │ ├── 20.0.2.txt │ │ │ ├── 20.1.0.txt │ │ │ ├── 21.0.0.txt │ │ │ ├── 21.1.0.txt │ │ │ ├── 21.2.0.txt │ │ │ ├── 22.0.0.txt │ │ │ ├── 22.1.0.txt │ │ │ ├── 23.0.0.txt │ │ │ ├── 23.1.0.txt │ │ │ ├── 23.2.0.txt │ │ │ ├── 24.0.0.txt │ │ │ ├── 24.1.0.txt │ │ │ ├── 24.2.0.txt │ │ │ ├── 24.3.0.txt │ │ │ ├── 24.4.0.txt │ │ │ ├── 25.0.0.txt │ │ │ ├── 26.0.0.txt │ │ │ ├── 26.0.1.txt │ │ │ ├── 26.1.0.txt │ │ │ ├── 26.1.1.txt │ │ │ ├── 26.1.2.txt │ │ │ ├── 27.0.0.txt │ │ │ ├── 27.0.1.txt │ │ │ ├── 28.0.0.txt │ │ │ ├── 28.1.0.txt │ │ │ ├── 28.1.1.txt │ │ │ ├── 28.2.0.txt │ │ │ ├── 29.0.0.txt │ │ │ ├── 29.1.0.txt │ │ │ ├── 29.2.0.txt │ │ │ ├── 29.2.1.txt │ │ │ ├── 3.2.0.txt │ │ │ ├── 3.2.2.txt │ │ │ ├── 30.0.0.txt │ │ │ ├── 30.0.1.txt │ │ │ ├── 30.1.0.txt │ │ │ ├── 30.2.0.txt │ │ │ ├── 30.3.0.txt │ │ │ ├── 31.0.0.txt │ │ │ ├── 31.1.0.txt │ │ │ ├── 31.2.0.txt │ │ │ ├── 31.2.1.txt │ │ │ ├── 32.0.0.txt │ │ │ ├── 32.1.0.txt │ │ │ ├── 32.2.0.txt │ │ │ ├── 32.3.0.txt │ │ │ ├── 33.0.0.txt │ │ │ ├── 34.0.0.txt │ │ │ ├── 34.1.0.txt │ │ │ ├── 35.0.0.txt │ │ │ ├── 35.1.0.txt │ │ │ ├── 35.2.0.txt │ │ │ ├── 36.0.0.txt │ │ │ ├── 36.1.0.txt │ │ │ ├── 36.1.1.txt │ │ │ ├── 36.1.2.txt │ │ │ ├── 36.2.0.txt │ │ │ ├── 37.0.0.txt │ │ │ ├── 37.0.1.txt │ │ │ ├── 37.1.0.txt │ │ │ ├── 37.2.0.txt │ │ │ ├── 37.3.0.txt │ │ │ ├── 37.4.0.txt │ │ │ ├── 38.0.0.txt │ │ │ ├── 39.0.0.txt │ │ │ ├── 39.1.0.txt │ │ │ ├── 4.0.0.txt │ │ │ ├── 4.1.0.txt │ │ │ ├── 4.1.1.txt │ │ │ ├── 4.1.2.txt │ │ │ ├── 40.0.0.txt │ │ │ ├── 40.0.1.txt │ │ │ ├── 40.1.0.txt │ │ │ ├── 41.0.0.txt │ │ │ ├── 42.0.0.txt │ │ │ ├── 42.1.0.txt │ │ │ ├── 42.2.0.txt │ │ │ ├── 42.3.0.txt │ │ │ ├── 43.0.0.txt │ │ │ ├── 43.0.2.txt │ │ │ ├── 43.1.0.txt │ │ │ ├── 44.0.0.txt │ │ │ ├── 44.1.0.txt │ │ │ ├── 44.2.0.txt │ │ │ ├── 446.txt │ │ │ ├── 447.txt │ │ │ ├── 448.txt │ │ │ ├── 449.txt │ │ │ ├── 45.0.0.txt │ │ │ ├── 45.1.0.txt │ │ │ ├── 45.1.1.txt │ │ │ ├── 45.1.2.txt │ │ │ ├── 46.0.0.txt │ │ │ ├── 46.0.1.txt │ │ │ ├── 46.1.0.txt │ │ │ ├── 46.1.1.txt │ │ │ ├── 46.2.0.txt │ │ │ ├── 47.0.0.txt │ │ │ ├── 47.1.0.txt │ │ │ ├── 47.2.0.txt │ │ │ ├── 48.0.0.txt │ │ │ ├── 48.0.1.txt │ │ │ ├── 48.1.0.txt │ │ │ ├── 48.2.0.txt │ │ │ ├── 49.0.0.txt │ │ │ ├── 49.0.1.txt │ │ │ ├── 49.1.0.txt │ │ │ ├── 49.1.2.txt │ │ │ ├── 49.1.3.txt │ │ │ ├── 49.2.0.txt │ │ │ ├── 49.3.1.txt │ │ │ ├── 49.4.0.txt │ │ │ ├── 5.0.0.txt │ │ │ ├── 5.1.0.txt │ │ │ ├── 5.2.0.txt │ │ │ ├── 5.2.1.txt │ │ │ ├── 5.3.0.txt │ │ │ ├── 5.3.1.txt │ │ │ ├── 5.4.0.txt │ │ │ ├── 5.4.1.txt │ │ │ ├── 5.4.2.txt │ │ │ ├── 50.0.0.txt │ │ │ ├── 50.1.0.txt │ │ │ ├── 50.1.1.txt │ │ │ ├── 50.1.3.txt │ │ │ ├── 50.1.4.txt │ │ │ ├── 50.1.5.txt │ │ │ ├── 50.2.0.txt │ │ │ ├── 50.2.1.txt │ │ │ ├── 50.2.2.txt │ │ │ ├── 51.0.0.txt │ │ │ ├── 51.1.0.txt │ │ │ ├── 51.2.0.txt │ │ │ ├── 52.0.0.txt │ │ │ ├── 52.1.0.txt │ │ │ ├── 52.2.0.txt │ │ │ ├── 53.0.0.txt │ │ │ ├── 53.1.0.txt │ │ │ ├── 53.1.1.txt │ │ │ ├── 53.1.2.txt │ │ │ ├── 53.2.0.txt │ │ │ ├── 53.2.1.txt │ │ │ ├── 53.2.2.txt │ │ │ ├── 53.2.3.txt │ │ │ ├── 54.0.0.txt │ │ │ ├── 54.1.0.txt │ │ │ ├── 54.2.0.txt │ │ │ ├── 55.0.0.txt │ │ │ ├── 55.0.1.txt │ │ │ ├── 55.0.2.txt │ │ │ ├── 55.1.0.txt │ │ │ ├── 55.2.0.txt │ │ │ ├── 56.0.0.txt │ │ │ ├── 56.1.0.txt │ │ │ ├── 56.2.0.txt │ │ │ ├── 56.3.0.txt │ │ │ ├── 57.0.0.txt │ │ │ ├── 57.1.0.txt │ │ │ ├── 57.1.1.txt │ │ │ ├── 57.2.0.txt │ │ │ ├── 58.0.0.txt │ │ │ ├── 59.0.0.txt │ │ │ ├── 59.0.1.txt │ │ │ ├── 59.0.2.txt │ │ │ ├── 59.0.3.txt │ │ │ ├── 59.0.4.txt │ │ │ ├── 59.1.0.txt │ │ │ ├── 59.2.0.txt │ │ │ ├── 59.3.0.txt │ │ │ ├── 59.4.0.txt │ │ │ ├── 59.4.1.txt │ │ │ ├── 59.4.2.txt │ │ │ ├── 59.4.3.txt │ │ │ ├── 59.6.0.txt │ │ │ ├── 59.7.0.txt │ │ │ ├── 6.0.0.txt │ │ │ ├── 6.0.1.txt │ │ │ ├── 6.0.2.txt │ │ │ ├── 6.1.0.txt │ │ │ ├── 6.2.0.txt │ │ │ ├── 6.2.1.txt │ │ │ ├── 6.3.0.txt │ │ │ ├── 6.3.1.txt │ │ │ ├── 6.4.0.txt │ │ │ ├── 60.0.0.txt │ │ │ ├── 60.1.0.txt │ │ │ ├── 60.2.0.txt │ │ │ ├── 60.3.0.txt │ │ │ ├── 60.4.0.txt │ │ │ ├── 60.4.1.txt │ │ │ ├── 60.4.2.txt │ │ │ ├── 61.0.0.txt │ │ │ ├── 61.0.1.txt │ │ │ ├── 61.1.0.txt │ │ │ ├── 62.0.0.txt │ │ │ ├── 62.1.0.txt │ │ │ ├── 63.0.0.txt │ │ │ ├── 63.1.0.txt │ │ │ ├── 63.2.0.txt │ │ │ ├── 63.2.1.txt │ │ │ ├── 63.2.2.txt │ │ │ ├── 63.2.3.txt │ │ │ ├── 63.2.5.txt │ │ │ ├── 63.3.0.txt │ │ │ ├── 64.0.0.txt │ │ │ ├── 64.1.0.txt │ │ │ ├── 64.2.0.txt │ │ │ ├── 64.3.0.txt │ │ │ ├── 64.3.1.txt │ │ │ ├── 65.0.0.txt │ │ │ ├── 65.1.0.txt │ │ │ ├── 65.2.0.txt │ │ │ ├── 65.2.1.txt │ │ │ ├── 65.3.0.txt │ │ │ ├── 66.0.0.txt │ │ │ ├── 66.1.0.txt │ │ │ ├── 66.1.1.txt │ │ │ ├── 66.1.2.txt │ │ │ ├── 66.1.3.txt │ │ │ ├── 66.1.4.txt │ │ │ ├── 66.1.5.txt │ │ │ ├── 66.1.6.txt │ │ │ ├── 67.0.0.txt │ │ │ ├── 67.0.1.txt │ │ │ ├── 67.0.2.txt │ │ │ ├── 67.0.3.txt │ │ │ ├── 67.1.0.txt │ │ │ ├── 67.2.0.txt │ │ │ ├── 68.0.0.txt │ │ │ ├── 68.1.0.txt │ │ │ ├── 68.1.1.txt │ │ │ ├── 68.1.2.txt │ │ │ ├── 7.0.0.txt │ │ │ ├── 7.1.0.txt │ │ │ ├── 7.1.1.txt │ │ │ ├── 7.1.2.txt │ │ │ ├── 7.2.0.txt │ │ │ ├── 7.3.0.txt │ │ │ ├── 7.4.0.txt │ │ │ ├── 8.0.0.txt │ │ │ ├── 8.0.1.txt │ │ │ ├── 8.0.2.txt │ │ │ ├── 8.1.0.txt │ │ │ ├── 8.2.0.txt │ │ │ ├── 8.3.0.txt │ │ │ ├── 8.3.1.txt │ │ │ ├── 8.3.2.txt │ │ │ ├── 8.3.3.txt │ │ │ ├── 8.3.5.txt │ │ │ ├── 8.3.6.txt │ │ │ ├── 8.4.0.txt │ │ │ ├── 8.4.1.txt │ │ │ ├── 8.4.2.txt │ │ │ ├── 8.4.3.txt │ │ │ ├── 8.4.4.txt │ │ │ ├── 8.5.0.txt │ │ │ ├── 8.5.1.txt │ │ │ ├── 8.5.2.txt │ │ │ ├── 8.6.0.txt │ │ │ ├── 9.0.0.txt │ │ │ ├── 9.1.0.txt │ │ │ ├── 9.2.0.txt │ │ │ ├── 9.3.0.txt │ │ │ └── 9.4.0.txt │ │ ├── full_description.txt │ │ ├── short_description.txt │ │ └── title.txt │ └── zh-CN/ │ ├── full_description.txt │ ├── short_description.txt │ └── title.txt ├── gradle/ │ ├── libs.versions.toml │ └── wrapper/ │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts
SYMBOL INDEX (112 symbols across 1 files) FILE: app/src/main/cpp/baresip.c type BaresipContext (line 15) | typedef struct baresip_context function vprintf_null (line 25) | static int vprintf_null(const char *p, size_t size, void *arg) function net_debug_log (line 33) | static void net_debug_log() function net_dns_debug_log (line 44) | static void net_dns_debug_log() function ua_debug_log (line 56) | static void ua_debug_log(struct ua *ua) function account_debug_log (line 67) | static void account_debug_log(struct account *acc) function ua_print_status_log (line 79) | static void ua_print_status_log(struct ua *ua) type re_printf (line 91) | struct re_printf function signal_handler (line 93) | static void signal_handler(int sig) function ua_exit_handler (line 108) | static void ua_exit_handler(void *arg) type bevent_ev (line 115) | enum bevent_ev function event_handler (line 148) | static void event_handler(enum bevent_ev ev, struct bevent *event, void ... function message_handler (line 281) | static void message_handler( function send_resp_handler (line 329) | static void send_resp_handler(int err, const struct sip_msg *msg, void *... type mqueue (line 369) | struct mqueue function mqueue_handler (line 371) | static void mqueue_handler(int id, void *data, void *arg) function runLoggingThread (line 402) | static int runLoggingThread() function JNICALL (line 422) | JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) function JNICALL (line 437) | JNICALL Java_com_tutpro_baresip_BaresipService_baresipStart( function JNICALL (line 609) | JNICALL Java_com_tutpro_baresip_BaresipService_baresipStop( function JNICALL (line 618) | JNICALL Java_com_tutpro_baresip_Api_account_1display_1name( function JNICALL (line 632) | JNICALL Java_com_tutpro_baresip_Api_account_1set_1display_1name( function JNICALL (line 646) | JNICALL Java_com_tutpro_baresip_Api_account_1aor( function JNICALL (line 656) | JNICALL Java_com_tutpro_baresip_Api_account_1luri( function JNICALL (line 671) | JNICALL Java_com_tutpro_baresip_Api_account_1auth_1user( function JNICALL (line 685) | JNICALL Java_com_tutpro_baresip_Api_account_1set_1auth_1user( function JNICALL (line 699) | JNICALL Java_com_tutpro_baresip_Api_account_1auth_1pass( function JNICALL (line 713) | JNICALL Java_com_tutpro_baresip_Api_account_1set_1auth_1pass( function JNICALL (line 727) | JNICALL Java_com_tutpro_baresip_Api_account_1outbound( function JNICALL (line 744) | JNICALL Java_com_tutpro_baresip_Api_account_1set_1outbound( function JNICALL (line 759) | JNICALL Java_com_tutpro_baresip_Api_account_1audio_1codec( function JNICALL (line 790) | JNICALL Java_com_tutpro_baresip_Api_account_1set_1audio_1codecs( function JNICALL (line 800) | JNICALL Java_com_tutpro_baresip_Api_account_1set_1video_1codecs( function JNICALL (line 810) | JNICALL Java_com_tutpro_baresip_Api_account_1regint( function JNICALL (line 821) | JNICALL Java_com_tutpro_baresip_Api_account_1set_1regint( function JNICALL (line 830) | JNICALL Java_com_tutpro_baresip_Api_account_1check_1origin( function JNICALL (line 838) | JNICALL Java_com_tutpro_baresip_Api_account_1set_1check_1origin( function JNICALL (line 846) | JNICALL Java_com_tutpro_baresip_Api_account_1mediaenc( function JNICALL (line 858) | JNICALL Java_com_tutpro_baresip_Api_account_1set_1mediaenc( function JNICALL (line 872) | JNICALL Java_com_tutpro_baresip_Api_account_1medianat( function JNICALL (line 884) | JNICALL Java_com_tutpro_baresip_Api_account_1set_1medianat( function JNICALL (line 898) | JNICALL Java_com_tutpro_baresip_Api_account_1set_1sipnat( function JNICALL (line 912) | JNICALL Java_com_tutpro_baresip_Api_account_1stun_1uri( function JNICALL (line 941) | JNICALL Java_com_tutpro_baresip_Api_account_1set_1stun_1uri( function JNICALL (line 955) | JNICALL Java_com_tutpro_baresip_Api_account_1stun_1user( function JNICALL (line 967) | JNICALL Java_com_tutpro_baresip_Api_account_1set_1stun_1user( function JNICALL (line 981) | JNICALL Java_com_tutpro_baresip_Api_account_1stun_1pass( function JNICALL (line 993) | JNICALL Java_com_tutpro_baresip_Api_account_1set_1stun_1pass( function JNICALL (line 1007) | JNICALL Java_com_tutpro_baresip_Api_account_1set_1mwi( function JNICALL (line 1015) | JNICALL Java_com_tutpro_baresip_Api_account_1vm_1uri( function JNICALL (line 1034) | JNICALL Java_com_tutpro_baresip_Api_account_1answermode( function JNICALL (line 1042) | JNICALL Java_com_tutpro_baresip_Api_account_1set_1answermode( function JNICALL (line 1051) | JNICALL Java_com_tutpro_baresip_Api_account_1sip_1autoredirect( function JNICALL (line 1059) | JNICALL Java_com_tutpro_baresip_Api_account_1set_1sip_1autoredirect( function JNICALL (line 1067) | JNICALL Java_com_tutpro_baresip_Api_account_1rtcp_1mux( function JNICALL (line 1075) | JNICALL Java_com_tutpro_baresip_Api_account_1set_1rtcp_1mux( function JNICALL (line 1083) | JNICALL Java_com_tutpro_baresip_Api_account_1rel100_1mode( function JNICALL (line 1091) | JNICALL Java_com_tutpro_baresip_Api_account_1set_1rel100_1mode( function JNICALL (line 1100) | JNICALL Java_com_tutpro_baresip_Api_account_1dtmfmode( function JNICALL (line 1108) | JNICALL Java_com_tutpro_baresip_Api_account_1set_1dtmfmode( function JNICALL (line 1117) | JNICALL Java_com_tutpro_baresip_Api_account_1extra( function JNICALL (line 1129) | JNICALL Java_com_tutpro_baresip_Api_account_1debug( function JNICALL (line 1137) | JNICALL Java_com_tutpro_baresip_Api_ua_1alloc( function JNICALL (line 1156) | JNICALL Java_com_tutpro_baresip_Api_ua_1register(JNIEnv *env, jobject ob... function JNICALL (line 1167) | JNICALL Java_com_tutpro_baresip_Api_ua_1unregister( function JNICALL (line 1177) | JNICALL Java_com_tutpro_baresip_Api_ua_1isregistered( function JNICALL (line 1185) | JNICALL Java_com_tutpro_baresip_Api_ua_1destroy(JNIEnv *env, jobject obj... function JNICALL (line 1195) | JNICALL Java_com_tutpro_baresip_Api_ua_1account(JNIEnv *env, jobject obj... function JNICALL (line 1205) | JNICALL Java_com_tutpro_baresip_Api_ua_1update_1account( function JNICALL (line 1214) | JNICALL Java_com_tutpro_baresip_Api_ua_1hangup( function JNICALL (line 1230) | JNICALL Java_com_tutpro_baresip_Api_ua_1accept( function JNICALL (line 1247) | JNICALL Java_com_tutpro_baresip_Api_ua_1call_1alloc( function JNICALL (line 1264) | JNICALL Java_com_tutpro_baresip_Api_ua_1answer( function JNICALL (line 1275) | JNICALL Java_com_tutpro_baresip_Api_ua_1add_1custom_1header( function JNICALL (line 1294) | JNICALL Java_com_tutpro_baresip_Api_ua_1debug(JNIEnv *env, jobject obj, ... function JNICALL (line 1301) | JNICALL Java_com_tutpro_baresip_Api_sip_1treply( function JNICALL (line 1315) | JNICALL Java_com_tutpro_baresip_Api_bevent_1stop( function JNICALL (line 1325) | JNICALL Java_com_tutpro_baresip_Api_calls_1mute( function JNICALL (line 1344) | JNICALL Java_com_tutpro_baresip_Api_call_1connect( function JNICALL (line 1361) | JNICALL Java_com_tutpro_baresip_Api_call_1notify_1sipfrag( function JNICALL (line 1374) | JNICALL Java_com_tutpro_baresip_Api_call_1start_1audio( function JNICALL (line 1387) | JNICALL Java_com_tutpro_baresip_Api_call_1hold( function JNICALL (line 1409) | JNICALL Java_com_tutpro_baresip_Api_call_1ismuted( function JNICALL (line 1417) | JNICALL Java_com_tutpro_baresip_Api_call_1supported( function JNICALL (line 1425) | JNICALL Java_com_tutpro_baresip_Api_call_1transfer( function JNICALL (line 1440) | JNICALL Java_com_tutpro_baresip_Api_call_1send_1digit( function JNICALL (line 1455) | JNICALL Java_com_tutpro_baresip_Api_call_1audio_1codecs( function JNICALL (line 1475) | JNICALL Java_com_tutpro_baresip_Api_call_1duration( function JNICALL (line 1483) | JNICALL Java_com_tutpro_baresip_Api_call_1stats( function JNICALL (line 1514) | JNICALL Java_com_tutpro_baresip_Api_call_1state( function JNICALL (line 1522) | JNICALL Java_com_tutpro_baresip_Api_call_1replaces( function JNICALL (line 1530) | JNICALL Java_com_tutpro_baresip_Api_call_1replace_1transfer( function JNICALL (line 1541) | JNICALL Java_com_tutpro_baresip_Api_call_1peer_1uri( function JNICALL (line 1551) | JNICALL Java_com_tutpro_baresip_Api_call_1diverter_1uri( function JNICALL (line 1561) | JNICALL Java_com_tutpro_baresip_Api_message_1send( function JNICALL (line 1581) | JNICALL Java_com_tutpro_baresip_Api_call_1destroy( function JNICALL (line 1591) | JNICALL Java_com_tutpro_baresip_Api_cmd_1exec( function JNICALL (line 1604) | JNICALL Java_com_tutpro_baresip_Api_audio_1codecs(JNIEnv *env, jobject obj) function JNICALL (line 1631) | JNICALL Java_com_tutpro_baresip_Api_video_1codecs(JNIEnv *env, jobject obj) function JNICALL (line 1658) | JNICALL Java_com_tutpro_baresip_Api_log_1level_1set( function JNICALL (line 1668) | JNICALL Java_com_tutpro_baresip_Api_net_1use_1nameserver( function JNICALL (line 1710) | JNICALL Java_com_tutpro_baresip_Api_net_1add_1address_1ifname( function JNICALL (line 1734) | JNICALL Java_com_tutpro_baresip_Api_net_1rm_1address( function JNICALL (line 1760) | JNICALL Java_com_tutpro_baresip_Api_uag_1reset_1transp( function JNICALL (line 1771) | JNICALL Java_com_tutpro_baresip_Api_uag_1enable_1sip_1trace( function JNICALL (line 1780) | JNICALL Java_com_tutpro_baresip_Api_config_1verify_1server_1set( function JNICALL (line 1790) | JNICALL Java_com_tutpro_baresip_Api_net_1debug(JNIEnv *env, jobject obj) function JNICALL (line 1799) | JNICALL Java_com_tutpro_baresip_Api_net_1dns_1debug(JNIEnv *env, jobject... function JNICALL (line 1808) | JNICALL Java_com_tutpro_baresip_Api_module_1load( function JNICALL (line 1818) | JNICALL Java_com_tutpro_baresip_Api_module_1unload( function JNICALL (line 1830) | JNICALL function JNICALL (line 1860) | JNICALL
Condensed preview — 1047 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,828K chars).
[
{
"path": ".clang-format",
"chars": 2676,
"preview": "---\nLanguage: Cpp\n# BasedOnStyle: LLVM\nAccessModifierOffset: -4\nAlignAfterOpenBracket: DontAlign\nAlignConsecutiv"
},
{
"path": ".gitignore",
"chars": 221,
"preview": "/.idea\nlocal.properties\n.DS_Store\n*~\n*.iml\n*.orig\n*.rej\njava_*\n.gradle\n/local.properties\nbuild\n/captures\n.externalNative"
},
{
"path": ".gitmodules",
"chars": 133,
"preview": "[submodule \"libbaresip-android\"]\n\tpath = libbaresip-android\n\turl = https://github.com/juha-h/libbaresip-android.git\n\tbra"
},
{
"path": "LICENSE",
"chars": 1511,
"preview": "BSD 3-Clause License\n\nCopyright (c) 2018, TutPro Inc.\nAll rights reserved.\n\nRedistribution and use in source and binary "
},
{
"path": "PrivacyPolicy.txt",
"chars": 632,
"preview": "baresip Privacy Policy\n\nLast updated: Sept 25, 2022\n\nbaresip does not collect any personal, app usage, or any other info"
},
{
"path": "README.md",
"chars": 1973,
"preview": "This is a bare-bones Android Studio project implementing <a href=\"https://github.com/alfredh/baresip\">baresip</a> based "
},
{
"path": "app/build.gradle.kts",
"chars": 2810,
"preview": "import com.android.build.api.dsl.ApplicationExtension\n\nplugins {\n alias(libs.plugins.compose.compiler)\n alias(libs"
},
{
"path": "app/proguard-rules.pro",
"chars": 716,
"preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /U"
},
{
"path": "app/src/main/AndroidManifest.xml",
"chars": 6997,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:to"
},
{
"path": "app/src/main/assets/accounts",
"chars": 0,
"preview": ""
},
{
"path": "app/src/main/assets/config.static",
"chars": 831,
"preview": "poll_method epoll\ncall_local_timeout 120\ncall_max_calls 4\ncall_hold_other_calls yes\nfilter_registrar udp,tcp,tls,ws,wss\n"
},
{
"path": "app/src/main/assets/contacts",
"chars": 47,
"preview": "\"The Test Call\" <sip:thetestcall@sip2sip.info>\n"
},
{
"path": "app/src/main/cpp/CMakeLists.txt",
"chars": 3138,
"preview": "cmake_minimum_required(VERSION 3.18...4.0)\n\nproject(baresip)\n\nadd_link_options(\"LINKER:--build-id=none\")\n\nset(distributi"
},
{
"path": "app/src/main/cpp/baresip.c",
"chars": 58449,
"preview": "#include <string.h>\n#include <pthread.h>\n#include <jni.h>\n#include <aaudio/AAudio.h>\n#include <stdlib.h>\n#include <re.h>"
},
{
"path": "app/src/main/cpp/logger.h",
"chars": 832,
"preview": "#include <baresip.h>\n#include <android/log.h>\n\n#ifndef BARESIP_LOGGER_H\n\n #define BARESIP_LOGGER_H\n\n #define LOG_T"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/AboutScreen.kt",
"chars": 3985,
"preview": "package com.tutpro.baresip\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.layout.Colu"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/Account.kt",
"chars": 9267,
"preview": "package com.tutpro.baresip\n\nimport android.content.Context\nimport java.net.URLDecoder\nimport java.net.URLEncoder\nimport "
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/AccountScreen.kt",
"chars": 70173,
"preview": "package com.tutpro.baresip\n\nimport android.content.Context\nimport androidx.compose.foundation.background\nimport androidx"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/AccountViewModel.kt",
"chars": 2699,
"preview": "package com.tutpro.baresip\n\nimport androidx.lifecycle.ViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\n\nclass A"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/AccountsScreen.kt",
"chars": 12299,
"preview": "package com.tutpro.baresip\n\nimport android.content.Context\nimport androidx.compose.foundation.background\nimport androidx"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/AndroidContactScreen.kt",
"chars": 10043,
"preview": "package com.tutpro.baresip\n\nimport android.content.Context\nimport android.content.Intent\nimport android.net.Uri\nimport a"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/Api.kt",
"chars": 6039,
"preview": "package com.tutpro.baresip\n\nobject Api {\n\n const val VIDMODE_OFF = 0\n // const val VIDMODE_ON = 1\n const val AN"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/AudioScreen.kt",
"chars": 25148,
"preview": "package com.tutpro.baresip\n\nimport android.content.Context\nimport androidx.compose.foundation.background\nimport androidx"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/BaresipApp.kt",
"chars": 354,
"preview": "package com.tutpro.baresip\n\nimport android.app.Application\n\nclass BaresipApp : Application() {\n\n override fun onCreat"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/BaresipContactScreen.kt",
"chars": 30424,
"preview": "package com.tutpro.baresip\n\nimport android.content.ContentProviderOperation\nimport android.content.ContentValues\nimport "
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt",
"chars": 99149,
"preview": "package com.tutpro.baresip\n\nimport android.Manifest.permission.RECORD_AUDIO\nimport android.annotation.SuppressLint\nimpor"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/Blocked.kt",
"chars": 1999,
"preview": "package com.tutpro.baresip\n\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.json.Json\n\nimport jav"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/BlockedScreen.kt",
"chars": 11951,
"preview": "package com.tutpro.baresip\n\nimport android.content.Context\nimport androidx.activity.compose.BackHandler\nimport androidx."
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/BootCompletedReceiver.kt",
"chars": 1034,
"preview": "package com.tutpro.baresip\n\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.conte"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/Call.kt",
"chars": 5798,
"preview": "package com.tutpro.baresip\n\nimport android.content.Context\nimport android.media.AudioManager\nimport androidx.compose.run"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/CallDetailsScreen.kt",
"chars": 26667,
"preview": "package com.tutpro.baresip\n\nimport android.content.Context\nimport android.media.AudioAttributes\nimport android.media.Med"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/CallHistory.kt",
"chars": 5481,
"preview": "package com.tutpro.baresip\n\nimport java.io.File\nimport java.io.FileInputStream\nimport java.io.FileOutputStream\nimport ja"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/CallRow.kt",
"chars": 530,
"preview": "package com.tutpro.baresip\n\nimport java.util.GregorianCalendar\n\ndata class CallRow(\n val aor: String,\n val peerUri"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/CallsScreen.kt",
"chars": 22049,
"preview": "package com.tutpro.baresip\n\nimport android.content.Context\nimport android.content.Intent\nimport androidx.activity.compos"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/ChatScreen.kt",
"chars": 23695,
"preview": "package com.tutpro.baresip\n\nimport android.content.Context\nimport android.content.Intent\nimport android.text.format.Date"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/ChatsScreen.kt",
"chars": 26987,
"preview": "package com.tutpro.baresip\n\nimport android.content.Context\nimport android.text.format.DateUtils.isToday\nimport androidx."
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/Codec.kt",
"chars": 144,
"preview": "package com.tutpro.baresip\n\nimport androidx.compose.runtime.MutableState\n\ndata class Codec(val name: String, var enabled"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/CodecsScreen.kt",
"chars": 10468,
"preview": "package com.tutpro.baresip\n\nimport androidx.compose.foundation.ExperimentalFoundationApi\nimport androidx.compose.foundat"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/Colors.kt",
"chars": 2424,
"preview": "package com.tutpro.baresip\n\nimport androidx.compose.ui.graphics.Color\n\nval Primary = Color(0xFF0CA1FD)\nval OnPrimary = C"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/Config.kt",
"chars": 11673,
"preview": "package com.tutpro.baresip\n\nimport android.Manifest\nimport android.content.Context\nimport androidx.appcompat.app.AppComp"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/ConnectionService.kt",
"chars": 10216,
"preview": "package com.tutpro.baresip\n\nimport android.content.Intent\nimport android.telecom.CallAudioState\nimport android.telecom.C"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/Constants.kt",
"chars": 1508,
"preview": "package com.tutpro.baresip\n\nconst val TAG = \"Baresip\"\n\nconst val LOW_CHANNEL_ID = \"com.tutpro.baresip.low\"\nconst val MED"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/Contact.kt",
"chars": 13513,
"preview": "package com.tutpro.baresip\n\nimport android.content.Context\nimport android.database.Cursor\nimport android.graphics.Bitmap"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/ContactsScreen.kt",
"chars": 19026,
"preview": "package com.tutpro.baresip\n\nimport android.content.Context\nimport android.content.Intent\nimport android.provider.Contact"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/CustomElements.kt",
"chars": 28411,
"preview": "package com.tutpro.baresip\n\nimport android.content.Context\nimport android.graphics.Bitmap\nimport android.widget.Toast\nim"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/DraggableLazyList.kt",
"chars": 14270,
"preview": "/*\n * Copyright 2024 Allan Veloso Lopes\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/Event.kt",
"chars": 372,
"preview": "package com.tutpro.baresip\n\nimport java.util.concurrent.atomic.AtomicBoolean\n\nopen class Event<out T>(private val conten"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/InCallService.kt",
"chars": 696,
"preview": "package com.tutpro.baresip\n\nimport android.app.Service\nimport android.content.Intent\nimport android.os.IBinder\n\n// This "
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/Log.kt",
"chars": 890,
"preview": "package com.tutpro.baresip\n\nobject Log {\n\n enum class LogLevel {\n DEBUG, INFO, WARN, ERROR, OFF\n }\n\n var"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt",
"chars": 16654,
"preview": "@file:OptIn(ExperimentalMaterial3Api::class)\n\npackage com.tutpro.baresip\n\nimport android.Manifest.permission.BLUETOOTH_C"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/MainScreen.kt",
"chars": 121305,
"preview": "package com.tutpro.baresip\n\nimport android.Manifest.permission.READ_EXTERNAL_STORAGE\nimport android.Manifest.permission."
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/Message.kt",
"chars": 5027,
"preview": "package com.tutpro.baresip\n\nimport java.io.*\nimport java.util.ArrayList\n\nclass Message(val aor: String, val peerUri: Str"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/Preferences.kt",
"chars": 645,
"preview": "package com.tutpro.baresip\n\nimport android.content.Context\nimport androidx.preference.PreferenceManager\nimport androidx."
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/ServiceEvent.kt",
"chars": 115,
"preview": "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",
"chars": 65000,
"preview": "package com.tutpro.baresip\n\nimport android.Manifest\nimport android.app.Activity\nimport android.app.Activity.RESULT_OK\nim"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/SettingsViewModel.kt",
"chars": 3557,
"preview": "package com.tutpro.baresip\n\nimport android.app.role.RoleManager\nimport android.content.Context\nimport android.content.Co"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/TaskReceiver.kt",
"chars": 2107,
"preview": "package com.tutpro.baresip\n\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.conte"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/Theme.kt",
"chars": 5556,
"preview": "package com.tutpro.baresip\n\nimport android.app.Activity\nimport android.os.Build.VERSION\nimport androidx.activity.Compone"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/UserAgent.kt",
"chars": 4537,
"preview": "package com.tutpro.baresip\n\nimport com.tutpro.baresip.BaresipService.Companion.circleYellow\nimport com.tutpro.baresip.Ba"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/Utils.kt",
"chars": 50987,
"preview": "package com.tutpro.baresip\n\nimport android.annotation.SuppressLint\nimport android.app.Activity\nimport android.app.Keygua"
},
{
"path": "app/src/main/kotlin/com/tutpro/baresip/ViewModel.kt",
"chars": 4483,
"preview": "package com.tutpro.baresip\n\nimport androidx.compose.runtime.MutableState\nimport androidx.compose.runtime.mutableStateOf\n"
},
{
"path": "app/src/main/res/drawable/circle_green.xml",
"chars": 387,
"preview": "<vector android:autoMirrored=\"true\" android:height=\"28dp\"\nandroid:viewportHeight=\"100\" android:viewportWidth=\"100\"\nandro"
},
{
"path": "app/src/main/res/drawable/circle_green_blind.xml",
"chars": 1865,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"28dp\"\n android:height=\"28dp\"\n "
},
{
"path": "app/src/main/res/drawable/circle_red.xml",
"chars": 408,
"preview": "<vector android:autoMirrored=\"true\" android:height=\"28dp\"\n android:viewportHeight=\"100\" android:viewportWidth=\"100\"\n "
},
{
"path": "app/src/main/res/drawable/circle_red_blind.xml",
"chars": 2278,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"28dp\"\n android:height=\"28dp\"\n "
},
{
"path": "app/src/main/res/drawable/circle_white.xml",
"chars": 391,
"preview": "<vector android:autoMirrored=\"true\" android:height=\"28dp\"\n android:viewportHeight=\"100\" android:viewportWidth=\"100\"\n "
},
{
"path": "app/src/main/res/drawable/circle_yellow.xml",
"chars": 409,
"preview": "<vector android:autoMirrored=\"true\" android:height=\"28dp\"\n android:viewportHeight=\"100\" android:viewportWidth=\"100\"\n "
},
{
"path": "app/src/main/res/drawable/circle_yellow_blind.xml",
"chars": 2954,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"28dp\"\n android:height=\"28dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_launcher_foreground.xml",
"chars": 5771,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"108dp\"\n android:height=\"108dp\"\n"
},
{
"path": "app/src/main/res/drawable/ic_notification_b.xml",
"chars": 1824,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_notification_call.xml",
"chars": 625,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:height=\"24dp\"\n android:viewportHeight="
},
{
"path": "app/src/main/res/drawable/ic_notification_call_end.xml",
"chars": 784,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:height=\"24dp\"\n android:viewportHeight="
},
{
"path": "app/src/main/res/drawable/ic_notification_call_missed.xml",
"chars": 353,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:autoMirrored=\"true\"\n android:height=\"2"
},
{
"path": "app/src/main/res/drawable/ic_notification_delete.xml",
"chars": 353,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:height=\"24dp\"\n android:viewportHeight="
},
{
"path": "app/src/main/res/drawable/ic_notification_message.xml",
"chars": 454,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:autoMirrored=\"true\"\n android:height=\"2"
},
{
"path": "app/src/main/res/drawable/ic_notification_reply.xml",
"chars": 364,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:autoMirrored=\"true\"\n android:height=\"2"
},
{
"path": "app/src/main/res/drawable/ic_notification_save.xml",
"chars": 438,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:height=\"24dp\"\n android:viewportHeight="
},
{
"path": "app/src/main/res/layout/status_notification.xml",
"chars": 1797,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n andr"
},
{
"path": "app/src/main/res/mipmap-anydpi/ic_launcher.xml",
"chars": 271,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <b"
},
{
"path": "app/src/main/res/mipmap-anydpi/ic_launcher_round.xml",
"chars": 270,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <b"
},
{
"path": "app/src/main/res/values/colors.xml",
"chars": 462,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"colorPrimary\">#0ca1fd</color>\n <color name=\"color"
},
{
"path": "app/src/main/res/values/strings.xml",
"chars": 39566,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n <string name=\"app_"
},
{
"path": "app/src/main/res/values-ar/strings.xml",
"chars": 63,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n</resources>"
},
{
"path": "app/src/main/res/values-bg/strings.xml",
"chars": 16979,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <!-- About Activity -->\n <string name=\"about_title\">Относно ba"
},
{
"path": "app/src/main/res/values-ca/strings.xml",
"chars": 62,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources></resources>"
},
{
"path": "app/src/main/res/values-cs/strings.xml",
"chars": 38742,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"about_title\">O aplikaci baresip</string>\n <strin"
},
{
"path": "app/src/main/res/values-de/strings.xml",
"chars": 36573,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"about_title\">Über baresip</string>\n <string name"
},
{
"path": "app/src/main/res/values-el/strings.xml",
"chars": 586,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"about_title\">Σχετικά για το baresip</string>\n <s"
},
{
"path": "app/src/main/res/values-es/strings.xml",
"chars": 42110,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <!-- About Activity -->\n <string name=\"about_title\">Acerca de "
},
{
"path": "app/src/main/res/values-fi/strings.xml",
"chars": 41419,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <!-- About Activity -->\n <string name=\"about_title\">baresip so"
},
{
"path": "app/src/main/res/values-fr/strings.xml",
"chars": 23236,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"transfer\">Transférer</string>\n <string name=\"vid"
},
{
"path": "app/src/main/res/values-hr/strings.xml",
"chars": 62,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources></resources>"
},
{
"path": "app/src/main/res/values-in/strings.xml",
"chars": 63,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n</resources>"
},
{
"path": "app/src/main/res/values-iw/strings.xml",
"chars": 35247,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"about_title\">אודות baresip</string>\n <string nam"
},
{
"path": "app/src/main/res/values-ja-rJP/strings.xml",
"chars": 19840,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"about_text\"><![CDATA[\n <h1>Baresip library b"
},
{
"path": "app/src/main/res/values-ko/strings.xml",
"chars": 62,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources></resources>"
},
{
"path": "app/src/main/res/values-nb-rNO/strings.xml",
"chars": 18253,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"about_title\">Om</string>\n <string name=\"account\""
},
{
"path": "app/src/main/res/values-night/colors.xml",
"chars": 311,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"colorPrimary\">#84C1E6</color>\n <color name=\"color"
},
{
"path": "app/src/main/res/values-pl/strings.xml",
"chars": 62,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources></resources>"
},
{
"path": "app/src/main/res/values-pt/strings.xml",
"chars": 34799,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"restored\">Os dados da aplicação foram restaurados. "
},
{
"path": "app/src/main/res/values-pt-rBR/strings.xml",
"chars": 34850,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"codecs\">Codecs</string>\n <string name=\"status\">S"
},
{
"path": "app/src/main/res/values-ro/strings.xml",
"chars": 10059,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"about_title\">Despre baresip</string>\n <string na"
},
{
"path": "app/src/main/res/values-ru/strings.xml",
"chars": 37618,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"about\">О программе</string>\n <string name=\"add\">"
},
{
"path": "app/src/main/res/values-sl/strings.xml",
"chars": 517,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"authentication_username\">Uporabniško Ime</string>\n "
},
{
"path": "app/src/main/res/values-sv/strings.xml",
"chars": 35075,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"ok\">OK</string>\n <string name=\"contact_delete_qu"
},
{
"path": "app/src/main/res/values-ta/strings.xml",
"chars": 36806,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"about_title\">பரேசிப் பற்றி</string>\n <string nam"
},
{
"path": "app/src/main/res/values-uk/strings.xml",
"chars": 63,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n</resources>"
},
{
"path": "app/src/main/res/values-zh-rCN/strings.xml",
"chars": 25880,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"about_title\">关于 baresip</string>\n <string name=\""
},
{
"path": "build.gradle.kts",
"chars": 413,
"preview": "buildscript {\n repositories {\n google()\n mavenCentral()\n }\n dependencies {\n classpath(libs"
},
{
"path": "fastlane/metadata/android/de-DE/changelogs/10.1.0.txt",
"chars": 100,
"preview": "-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",
"chars": 106,
"preview": "-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",
"chars": 283,
"preview": "- Ein Anruflisten-menü wurde hinzugefügt welches Löschen, Deaktivieren und Aktivieren \ndes Konto Anrufverlaufes erlaubt."
},
{
"path": "fastlane/metadata/android/de-DE/full_description.txt",
"chars": 1111,
"preview": "baresip ist eine SIP \"User Agent\" App für Android, die auf <a href=\"https://github.com/baresip/baresip\">baresip</a> basi"
},
{
"path": "fastlane/metadata/android/de-DE/short_description.txt",
"chars": 73,
"preview": "VoIP User Agent App für Android basierend auf der baresip SIP Bibliothek\n"
},
{
"path": "fastlane/metadata/android/de-DE/title.txt",
"chars": 8,
"preview": "baresip\n"
},
{
"path": "fastlane/metadata/android/el/title.txt",
"chars": 8,
"preview": "baresip\n"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/10.0.0.txt",
"chars": 62,
"preview": "- Resume baresip to paused activity instead of main activity.\n"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/10.1.0.txt",
"chars": 60,
"preview": "- Added support for G722 codec.\n- Fixed saving of accounts.\n"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/10.2.0.txt",
"chars": 74,
"preview": "- Added support for G.726 codec.\n- Improved default codec priority order.\n"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/10.3.0.txt",
"chars": 222,
"preview": "- Added Call History menu that allows deleting, disabling, and enabling\n of account's call history.\n- Added Spanish lan"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/10.4.0.txt",
"chars": 411,
"preview": "- Allow deletion of account's chat history via chats activity menu item.\n- Ask confirmation before deleting chat and cal"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/11.0.0.txt",
"chars": 382,
"preview": "- Replaced exporting and importing of accounts and contacts with backup\n and restore of all application data.\n- Improve"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/11.1.0.txt",
"chars": 361,
"preview": "- Added 'Bind Address' configuration variable that allows choosing which\n network interface or IP address baresip is us"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/11.2.0.txt",
"chars": 121,
"preview": "- Added support for AMR narrowband codec.\n- Added Bulgarian strings.\n- Renamed main menu Configuration item to Settings."
},
{
"path": "fastlane/metadata/android/en-US/changelogs/11.3.0.txt",
"chars": 184,
"preview": "- Added to Settings possibility to choose which audio modules are\n loaded. Audio codecs provided by the loaded modules "
},
{
"path": "fastlane/metadata/android/en-US/changelogs/12.0.0.txt",
"chars": 61,
"preview": "- Initial implementation of audio through Bluetooth headset.\n"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/12.1.0.txt",
"chars": 131,
"preview": "- Allow port number in account's Address of Record (AoR).\n- Fixed checking if account already exists.\n- Updated NO and B"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/12.2.0.txt",
"chars": 169,
"preview": "- Used new API functions 'net_set_address' and 'net_set_af' to implement\n Prefer IPv6 functionality.\n- Removed Bind Add"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/12.2.1.txt",
"chars": 58,
"preview": "- Improved and fixed handling of dynamic network changes.\n"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/13.0.0.txt",
"chars": 523,
"preview": "- Play notification sound also when message arrives and baresip is visible.\n- If contact's name is empty, use contact's "
},
{
"path": "fastlane/metadata/android/en-US/changelogs/13.0.1.txt",
"chars": 112,
"preview": "- 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",
"chars": 244,
"preview": "- Added character based avatars to contacts, chats, and call history.\n- Fixed saving/restoring of contacts with non-ASCI"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/15.0.0.txt",
"chars": 117,
"preview": "- 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",
"chars": 145,
"preview": "- Added possibility to give transport protocol for account's AoR.\n- Do not show account's port or transport protocol exc"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/15.1.1.txt",
"chars": 39,
"preview": "- Minor string fixes and improvements.\n"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/16.0.0.txt",
"chars": 333,
"preview": "- Replaced global setting \"Prefer IPv6\" with account specific \"Prefer IPv6 Media\" setting.\n- Improved handling of change"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/16.0.1.txt",
"chars": 133,
"preview": "- Re-register UAs when a new active network becomes available or\n when link properties change even if IP addresses rema"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/16.1.0.txt",
"chars": 224,
"preview": "- Do not re-register accounts when link properties change, but IP addresses\n remain the same.\n- If baresip doeds not st"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/17.0.0.txt",
"chars": 520,
"preview": "- Fixed saving of an account that has ;transport parameter in its AoR.\n- Fixed enabling of notifications when call is cl"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/17.1.0.txt",
"chars": 246,
"preview": "- Use usage type instead of stream type when requesting audio focus.\n- Fixed formatting of Account English Help text.\n- "
},
{
"path": "fastlane/metadata/android/en-US/changelogs/17.2.0.txt",
"chars": 367,
"preview": "- Authentication username now defaults to account's username.\n- Ask authentication password at baresip start if authenti"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/17.2.1.txt",
"chars": 51,
"preview": "- Fixed bug in storing of accounts to file system.\n"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/17.2.2.txt",
"chars": 105,
"preview": "- 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",
"chars": 171,
"preview": "- Prevent crashes resulting from double clicking of various icons and\n list items.\n- Change color of account's notifica"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/17.3.0.txt",
"chars": 201,
"preview": "- Added \"Show Password\" checkbox to authentication password prompt.\n- Ask microphone permission as the first thing when "
},
{
"path": "fastlane/metadata/android/en-US/changelogs/17.4.0.txt",
"chars": 188,
"preview": "- Added support for Android 10 (API level 29).\n- Improved asking of microphone permission when baresip is started the fi"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/18.0.0.txt",
"chars": 320,
"preview": "- Due to Android 10 restrictions, baresip cannot anymore be automatically\n started after boot without a notification.\n-"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/18.0.1.txt",
"chars": 78,
"preview": "- 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",
"chars": 168,
"preview": "- Improved configuration of account's audio codecs.\n- Always show incoming call (if any) when baresip is resumed.\n- Impr"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/18.1.1.txt",
"chars": 46,
"preview": "- Fixed crash when calling from Call History.\n"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/18.1.2.txt",
"chars": 145,
"preview": "- Fixed crash when returning to the previous activity after received\n call or message.\n- Better handling of proximity s"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/18.1.3.txt",
"chars": 43,
"preview": "- Simplified control of proximity sensing.\n"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/18.2.0.txt",
"chars": 67,
"preview": "- Separated configuration of account's audio codecs to a new view.\n"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/18.2.1.txt",
"chars": 61,
"preview": "- Fixed two chat related crashes.\n- Minor code improvements.\n"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/18.3.0.txt",
"chars": 23,
"preview": "- Dialog improvements.\n"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/19.0.0.txt",
"chars": 204,
"preview": "- Added TURN account medianat option.\n- Added possibility to give STUN/TURN server username/password.\n- STUN/TURN relate"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/19.1.0.txt",
"chars": 29,
"preview": "- Alert dialog improvements.\n"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/20.0.0.txt",
"chars": 134,
"preview": "- Acoustic Echo Cancellation improvements.\n- Moved audio settings from Settings to new activity.\n- Added AEC Extented Fi"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/20.0.1.txt",
"chars": 100,
"preview": "- 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",
"chars": 49,
"preview": "- Dialpad button related fixes and improvements.\n"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/20.1.0.txt",
"chars": 66,
"preview": "- Added contributed RU strings.\n- Upstream fix of possible crash.\n"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/21.0.0.txt",
"chars": 108,
"preview": "- 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",
"chars": 201,
"preview": "- Added possibility to re-register selected account by swipe down gesture.\n- Automatically focus and show soft keyboard "
},
{
"path": "fastlane/metadata/android/en-US/changelogs/21.2.0.txt",
"chars": 140,
"preview": "- Added AMR-WB (Adaptive Multi-Rate Wideband) speech codec.\n- Added Licenses section to About text.\n- New Portuguese (Br"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/22.0.0.txt",
"chars": 48,
"preview": "- Added Websocket transport for SIP (RFC 7118).\n"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/22.1.0.txt",
"chars": 102,
"preview": "- 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",
"chars": 521,
"preview": "- Added visibility toggle to Account's Authentication and STUN/TURN Passwords\n- Prefer VPN interface (if any) when choos"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/23.1.0.txt",
"chars": 222,
"preview": "- Added G.729 audio codec.\n- Added a Setting to turn on/off tracing of SIP messages to logcat.\n- STUN/TURN Server URI sc"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/23.2.0.txt",
"chars": 88,
"preview": "- Improved detection of VPN connectivity changes\n- New Portuguese (Brazil) translations\n"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/24.0.0.txt",
"chars": 29,
"preview": "- Use adaptive jitter buffer\n"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/24.1.0.txt",
"chars": 101,
"preview": "- 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",
"chars": 133,
"preview": "- Automatically show DTMF soft keyboard when call is connected\n- Avoid crash caused by pressing volume control keys when"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/24.3.0.txt",
"chars": 150,
"preview": "- Show dialpad automatically only when device orientation is portrait\n- Don't play call waiting sound when second call c"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/24.4.0.txt",
"chars": 88,
"preview": "- Do not default STUN/TURN server Username/Password to Authentication Username/Password\n"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/25.0.0.txt",
"chars": 73,
"preview": "- Improved incoming call notification.\n- Added missed call notification.\n"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/26.0.0.txt",
"chars": 75,
"preview": "- Added dark theme and dark theme setting.\n- Account spinner enhancements.\n"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/26.0.1.txt",
"chars": 108,
"preview": "- 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",
"chars": 196,
"preview": "- Other apps can now see baresip as a phone app for sip: and tel: URIs\n- Improved save/restore of call URI text when mai"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/26.1.1.txt",
"chars": 65,
"preview": "- Fixed handling of call action when baresip app is not running.\n"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/26.1.2.txt",
"chars": 50,
"preview": "- Try to detect rotation of contact avatar images\n"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/27.0.0.txt",
"chars": 67,
"preview": "- Added possibility to export baresip contacts to Android contacts\n"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/27.0.1.txt",
"chars": 53,
"preview": "- Fixed crash when baresip is started the first time\n"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/28.0.0.txt",
"chars": 170,
"preview": "- Added possibility to partially auto-configure new account from web page (see https://github.com/juha-h/baresip-studio/"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/28.1.0.txt",
"chars": 93,
"preview": "- 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",
"chars": 89,
"preview": "- 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",
"chars": 45,
"preview": "- Added 'Verify Server Certificates' setting\n"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/29.0.0.txt",
"chars": 132,
"preview": "- Enabled swipe left/right to toggle between accounts\n- Added DTMF Mode account setting\n- Translations update from Webla"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/29.1.0.txt",
"chars": 125,
"preview": "- Portuguese (Brazil) translations from Weblate\n- Fixed F-Droid nb-NO locale tag\n- Avoid 'duplicate finish request' warn"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/29.2.0.txt",
"chars": 170,
"preview": "- Chat, call history, and contact related bug fixes\n- Fixed URI completion bugs and improved URI related checks\n- Transl"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/29.2.1.txt",
"chars": 57,
"preview": "- Avoid possible contacts related crash at baresip start\n"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/3.2.0.txt",
"chars": 134,
"preview": "- Added dialpad button for choosing between phone number and text soft keyboard.\n- Fixed auto completion of callee based"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/3.2.2.txt",
"chars": 277,
"preview": "- Added handling of call transfer failed event.\n- Avoided crash at device (re)start.\n- Avoided crash when coming first t"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/30.0.0.txt",
"chars": 164,
"preview": "- Upgraded target SDK version to API level 30\n- In Android versions 10 and above, let user choose the file in\n backup, "
},
{
"path": "fastlane/metadata/android/en-US/changelogs/30.0.1.txt",
"chars": 63,
"preview": "- Upstream fix in selecting correct account for incoming call.\n"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/30.1.0.txt",
"chars": 59,
"preview": "- Password dialog and account password entry improvements.\n"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/30.2.0.txt",
"chars": 390,
"preview": "- Added support for AMR codec Bandwidth Efficient Mode\n- Allow escaped characters in SIP URI user part and authenticatio"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/30.3.0.txt",
"chars": 119,
"preview": "- 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",
"chars": 157,
"preview": "- Improved About text\n- Restore chat list position when returning from chat\n- Fixed crash when asking for account's pass"
},
{
"path": "fastlane/metadata/android/en-US/changelogs/31.1.0.txt",
"chars": 114,
"preview": "- 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",
"chars": 127,
"preview": "- Account STUN/TURN URI enhancements\n- Various audio related improvements\n- New Portuguese (Brazil) and Slovenian transl"
}
]
// ... and 847 more files (download for full content)
About this extraction
This page contains the full source code of the juha-h/baresip-studio GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 1047 files (1.5 MB), approximately 416.8k tokens, and a symbol index with 112 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.