From ac26c2256dc862c9638966e03459933e1eeff4ee Mon Sep 17 00:00:00 2001 From: Juha Heinanen Date: Wed, 9 Oct 2019 11:48:26 +0300 Subject: [PATCH] - Added to main menu possibility to export and import all application date. - Improved encryption/decryption of exported/imported data. If accounts were exported, they need to be exported again. - Lots of other implemetation improvements. --- .../com/tutpro/baresip/AccountsActivity.kt | 42 +-- .../com/tutpro/baresip/BaresipService.kt | 20 +- .../kotlin/com/tutpro/baresip/CallHistory.kt | 16 +- .../com/tutpro/baresip/CallsActivity.kt | 4 +- .../kotlin/com/tutpro/baresip/ChatActivity.kt | 4 +- .../com/tutpro/baresip/ChatsActivity.kt | 8 +- .../main/kotlin/com/tutpro/baresip/Config.kt | 23 +- .../com/tutpro/baresip/ConfigActivity.kt | 18 +- .../main/kotlin/com/tutpro/baresip/Contact.kt | 43 +++ .../com/tutpro/baresip/ContactActivity.kt | 2 +- .../com/tutpro/baresip/ContactListAdapter.kt | 2 +- .../com/tutpro/baresip/ContactsActivity.kt | 38 +- .../kotlin/com/tutpro/baresip/MainActivity.kt | 87 ++++- .../main/kotlin/com/tutpro/baresip/Message.kt | 4 +- .../kotlin/com/tutpro/baresip/RunOnStartup.kt | 6 +- .../main/kotlin/com/tutpro/baresip/Utils.kt | 324 ++++++++++-------- app/src/main/res/menu/main_menu.xml | 6 + app/src/main/res/values-fi/strings.xml | 47 +-- app/src/main/res/values/strings.xml | 26 +- 19 files changed, 420 insertions(+), 300 deletions(-) diff --git a/app/src/main/kotlin/com/tutpro/baresip/AccountsActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/AccountsActivity.kt index 6a85de82..6060b73a 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/AccountsActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/AccountsActivity.kt @@ -3,7 +3,6 @@ package com.tutpro.baresip import android.app.Activity import android.content.Intent import android.os.Bundle -import android.os.Environment import android.support.v7.app.AppCompatActivity import android.view.Menu import android.view.MenuItem @@ -12,7 +11,6 @@ import android.app.AlertDialog import android.view.ViewGroup import android.view.LayoutInflater -import java.io.File import java.util.ArrayList class AccountsActivity : AppCompatActivity() { @@ -20,7 +18,6 @@ class AccountsActivity : AppCompatActivity() { internal lateinit var alAdapter: AccountListAdapter internal var aor = "" - internal var password = "" public override fun onCreate(savedInstanceState: Bundle?) { @@ -69,6 +66,7 @@ class AccountsActivity : AppCompatActivity() { } } } + } override fun onCreateOptionsMenu(menu: Menu): Boolean { @@ -92,7 +90,6 @@ class AccountsActivity : AppCompatActivity() { if (Utils.requestPermission(this, android.Manifest.permission.READ_EXTERNAL_STORAGE)) askPassword(getString(R.string.decrypt_password)) - } android.R.id.home -> { @@ -120,7 +117,6 @@ class AccountsActivity : AppCompatActivity() { } private fun askPassword(title: String) { - val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) val builder = AlertDialog.Builder(this) builder.setTitle(title) val viewInflated = LayoutInflater.from(this) @@ -130,17 +126,17 @@ class AccountsActivity : AppCompatActivity() { builder.setView(viewInflated) builder.setPositiveButton(android.R.string.ok) { dialog, _ -> dialog.dismiss() - password = input.text.toString() + val password = input.text.toString() if (password != "") { if (title == getString(R.string.encrypt_password)) { - if (exportAccounts(dir, password)) + if (exportAccounts(password)) Utils.alertView(this, getString(R.string.info), getString(R.string.exported_accounts)) else Utils.alertView(this, getString(R.string.error), getString(R.string.export_accounts_error)) } else { - if (importAccounts(dir, password)) + if (importAccounts(password)) Utils.alertView(this, getString(R.string.info), getString(R.string.imported_accounts)) else @@ -153,6 +149,7 @@ class AccountsActivity : AppCompatActivity() { dialog.cancel() } builder.show() + } companion object { @@ -169,30 +166,27 @@ class AccountsActivity : AppCompatActivity() { fun saveAccounts() { var accounts = "" for (a in Account.accounts()) accounts = accounts + a.print() + "\n" - Utils.putFileContents(File(BaresipService.filesPath + "/accounts"), accounts) + Utils.putFileContents(BaresipService.filesPath + "/accounts", accounts.toByteArray()) // Log.d("Baresip", "Saved accounts '${accounts}' to '${BaresipService.filesPath}/accounts'") } - fun exportAccounts(path: File, password: String): Boolean { - var accounts = "" - for (a in Account.accounts()) - accounts = accounts + a.print() + "\n" - return Utils.putFileContents(File(path, "accounts.bs"), - Utils.encrypt(accounts, password)) + fun exportAccounts(password: String): Boolean { + val content = Utils.getFileContents("${BaresipService.filesPath}/accounts") + if (content == null) return false + return Utils.encryptToFile("${BaresipService.downloadsPath}/accounts.bs", + content, password) } - fun importAccounts(path: File, password: String): Boolean { - val content = Utils.getFileContents(File(path, "accounts.bs")) - if (content == "Failed") return false - val accounts = Utils.decrypt(content, password) - if (accounts == "") return false - return Utils.putFileContents(File(BaresipService.filesPath + "/accounts"), - accounts) + fun importAccounts(password: String): Boolean { + val content = Utils.decryptFromFile("${BaresipService.downloadsPath}/accounts.bs", + password) + if (content == null) return false + return Utils.putFileContents("${BaresipService.filesPath}/accounts", content) } fun noAccounts(): Boolean { - return Utils.getFileContents(File(BaresipService.filesPath + "/accounts")) - .length == 0 + val contents = Utils.getFileContents(BaresipService.filesPath + "/accounts") + return contents == null || contents.size == 0 } } diff --git a/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt b/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt index 176fa0c8..3d73377b 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt @@ -15,6 +15,7 @@ import android.view.View import android.widget.RemoteViews import android.support.v4.content.LocalBroadcastManager import android.os.Build +import android.os.Environment import android.support.v4.app.NotificationCompat.VISIBILITY_PRIVATE import android.support.v4.content.ContextCompat import android.provider.Settings @@ -55,6 +56,7 @@ class BaresipService: Service() { intent.setPackage("com.tutpro.baresip") filesPath = filesDir.absolutePath + downloadsPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).path am = getSystemService(Context.AUDIO_SERVICE) as AudioManager @@ -146,7 +148,7 @@ class BaresipService: Service() { try { File(filesPath).mkdirs() } catch (e: Error) { - Log.e(LOG_TAG, "Failed to create directory: " + e.toString()) + Log.e(LOG_TAG, "Failed to create directory: $e") } } for (a in assets) { @@ -172,8 +174,9 @@ class BaresipService: Service() { } } - ContactsActivity.restoreContacts(applicationContext.filesDir, "contacts") - Message.restoreMessages() + Contact.restore() + CallHistory.restore() + Message.restore() Thread(Runnable { baresipStart(filesPath) }).start() isServiceRunning = true @@ -212,7 +215,7 @@ class BaresipService: Service() { Api.ua_hangup(call.ua.uap, callp, 486, "Rejected") if (call.ua.account.callHistory) { CallHistory.add(CallHistory(aor, peerUri, "in", false)) - CallHistory.save(filesPath) + CallHistory.save() } } } @@ -420,7 +423,7 @@ class BaresipService: Service() { Api.ua_hangup(uap, callp, 486, "Busy Here") if (ua.account.callHistory) { CallHistory.add(CallHistory(aor, peerUri, "in", false)) - CallHistory.save(filesPath) + CallHistory.save() ua.account.missedCalls = true } if (!Utils.isVisible()) @@ -499,7 +502,7 @@ class BaresipService: Service() { call.onhold = false if (ua.account.callHistory) { CallHistory.add(CallHistory(aor, call.peerURI, call.dir, true)) - CallHistory.save(filesPath) + CallHistory.save() call.hasHistory = true } if (call.dir == "in") { @@ -589,7 +592,7 @@ class BaresipService: Service() { calls.remove(call) if (ua.account.callHistory && !call.hasHistory) { CallHistory.add(CallHistory(aor, call.peerURI, call.dir, false)) - CallHistory.save(filesPath) + CallHistory.save() if (call.dir == "in") ua.account.missedCalls = true } if (Call.calls().size == 0) { @@ -636,7 +639,7 @@ class BaresipService: Service() { Log.d(LOG_TAG, "Message event for $uap from $peer at $timeStamp") Message.add(Message(ua.account.aor, peer, text, timeStamp.toLong(), R.drawable.arrow_down_green, 0, "", true)) - Message.saveMessages() + Message.save() ua.account.unreadMessages = true if (!Utils.isVisible()) { val intent = Intent(this, BaresipService::class.java) @@ -930,6 +933,7 @@ class BaresipService: Service() { var dynDns = false var filesPath = "" + var downloadsPath = "" var uas = ArrayList() var status = ArrayList() var calls = ArrayList() diff --git a/app/src/main/kotlin/com/tutpro/baresip/CallHistory.kt b/app/src/main/kotlin/com/tutpro/baresip/CallHistory.kt index b66e6df2..bd84c13d 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/CallHistory.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/CallHistory.kt @@ -45,9 +45,9 @@ class CallHistory(val aor: String, val peerURI: String, val direction: String, return null } - fun save(path: String) { - Log.d("Baresip", "Saving history of size ${BaresipService.history.size}") - val file = File(path, "history") + fun save() { + Log.d("Baresip", "Saving call history of size ${BaresipService.history.size}") + val file = File(BaresipService.filesPath, "history") try { val fos = FileOutputStream(file) val oos = ObjectOutputStream(fos) @@ -60,21 +60,19 @@ class CallHistory(val aor: String, val peerURI: String, val direction: String, } } - fun restore(path: String) { - val file = File(path + "/history") - if (file.exists()) { + fun restore() { + val file = File(BaresipService.filesPath, "history") + if (file.exists()) try { val fis = FileInputStream(file) val ois = ObjectInputStream(fis) BaresipService.history = ois.readObject() as ArrayList ois.close() fis.close() - Log.d("Baresip", "Restored history of size ${BaresipService.history.size}") + Log.d("Baresip", "Restored call history of size ${BaresipService.history.size}") } catch (e: Exception) { Log.e("Baresip", "InputStream exception: - " + e.toString()) } - } - } fun print() { diff --git a/app/src/main/kotlin/com/tutpro/baresip/CallsActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/CallsActivity.kt index 4a0fd4ab..a113847c 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/CallsActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/CallsActivity.kt @@ -127,7 +127,7 @@ class CallsActivity : AppCompatActivity() { override fun onPause() { - CallHistory.save(applicationContext.filesDir.absolutePath) + CallHistory.save() super.onPause() } @@ -142,7 +142,7 @@ class CallsActivity : AppCompatActivity() { aor.substringAfter(":"))) deleteDialog.setPositiveButton(getText(R.string.delete)) { dialog, _ -> CallHistory.clear(aor) - CallHistory.save(applicationContext.filesDir.absolutePath) + CallHistory.save() aorGenerateHistory(aor) clAdapter.notifyDataSetChanged() dialog.dismiss() diff --git a/app/src/main/kotlin/com/tutpro/baresip/ChatActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/ChatActivity.kt index 7e4734fa..47e9d75f 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ChatActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ChatActivity.kt @@ -97,7 +97,7 @@ class ChatActivity : AppCompatActivity() { if (chatMessages.size == 0) { listView.removeFooterView(footerView) } - Message.saveMessages() + Message.save() } DialogInterface.BUTTON_NEUTRAL -> { } @@ -224,7 +224,7 @@ class ChatActivity : AppCompatActivity() { save = true } } - if (save) Message.saveMessages() + if (save) Message.save() imm.hideSoftInputFromWindow(newMessage.windowToken, 0) BaresipService.activities.removeAt(0) val i = Intent() diff --git a/app/src/main/kotlin/com/tutpro/baresip/ChatsActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/ChatsActivity.kt index 7125a9fa..d1afa1e5 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ChatsActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ChatsActivity.kt @@ -72,7 +72,7 @@ class ChatsActivity: AppCompatActivity() { clAdapter.remove(m) clAdapter.notifyDataSetChanged() BaresipService.messages = msgs - Message.saveMessages() + Message.save() uaMessages = uaMessages(aor) } DialogInterface.BUTTON_NEUTRAL -> { @@ -159,7 +159,7 @@ class ChatsActivity: AppCompatActivity() { aor.substringAfter(":"))) deleteDialog.setPositiveButton(getText(R.string.delete)) { dialog, _ -> Message.clear(aor) - Message.saveMessages() + Message.save() uaMessages.clear() clAdapter.notifyDataSetChanged() Account.findUa(aor)!!.account.unreadMessages = false @@ -227,7 +227,7 @@ class ChatsActivity: AppCompatActivity() { if ((Message.messages()[i].aor == aor) && (Message.messages()[i].timeStamp == time)) { Message.messages()[i].new = false - Message.saveMessages() + Message.save() return } } @@ -237,7 +237,7 @@ class ChatsActivity: AppCompatActivity() { if ((Message.messages()[i].aor == aor) && (Message.messages()[i].timeStamp == time)) { Message.messages().removeAt(i) - Message.saveMessages() + Message.save() return } } diff --git a/app/src/main/kotlin/com/tutpro/baresip/Config.kt b/app/src/main/kotlin/com/tutpro/baresip/Config.kt index 3bdf74f0..f97f3f77 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/Config.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/Config.kt @@ -1,16 +1,17 @@ package com.tutpro.baresip import android.content.Context -import java.io.File + import java.net.InetAddress +import java.nio.charset.StandardCharsets object Config { - private val path = BaresipService.filesPath + "/config" - private val file = File(path) - private var config = Utils.getFileContents(file) + private val configPath = BaresipService.filesPath + "/config" + private var config = String(Utils.getFileContents(configPath)!!, StandardCharsets.ISO_8859_1) fun initialize(dnsServers: List) { + Log.d("Baresip", "Config is '$config'") var write = false if (!config.contains("zrtp_hash")) { config = "${config}zrtp_hash yes\n" @@ -41,10 +42,10 @@ object Config { } if (!config.contains("opus_samplerate")) { config = "${config}opus_samplerate 16000\n" - val accountsFile = File(BaresipService.filesPath + "/config") - var accounts = Utils.getFileContents(accountsFile) + val accountsPath = BaresipService.filesPath + "/accounts" + var accounts = String(Utils.getFileContents(accountsPath)!!, StandardCharsets.ISO_8859_1) accounts = accounts.replace("opus/48000/1", "opus/16000/1") - Utils.putFileContents(accountsFile, accounts) + Utils.putFileContents(accountsPath, accounts.toByteArray()) write = true } if (!config.contains("opus_stereo")) { @@ -95,7 +96,7 @@ object Config { } if (write) { Log.e("Baresip", "Writing config '$config'") - Utils.putFileContents(file, config) + Utils.putFileContents(configPath, config.toByteArray()) } } @@ -115,7 +116,7 @@ object Config { fun remove(variable: String) { config = Utils.removeLinesStartingWithName(config, variable) - Utils.putFileContents(file, config) + Utils.putFileContents(configPath, config.toByteArray()) } fun replace(variable: String, value: String) { @@ -124,7 +125,7 @@ object Config { } fun reset(ctx: Context) { - Utils.copyAssetToFile(ctx, "config", path) + Utils.copyAssetToFile(ctx, "config", configPath) } fun save() { @@ -133,7 +134,7 @@ object Config { if (line.length > 0) result = result + line + '\n' config = result - Utils.putFileContents(file, config) + Utils.putFileContents(configPath, config.toByteArray()) Log.d("Baresip", "New config '$result'") // Api.reload_config() } diff --git a/app/src/main/kotlin/com/tutpro/baresip/ConfigActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/ConfigActivity.kt index 0c51ff7a..66d12b04 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ConfigActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ConfigActivity.kt @@ -6,15 +6,12 @@ import android.content.Intent import android.net.ConnectivityManager import android.os.Build import android.os.Bundle -import android.os.Environment import android.support.v7.app.AppCompatActivity import android.view.Menu import android.view.MenuItem import android.view.View import android.widget.* -import java.io.File - class ConfigActivity : AppCompatActivity() { internal lateinit var autoStart: CheckBox @@ -44,8 +41,6 @@ class ConfigActivity : AppCompatActivity() { private var callVolume = BaresipService.callVolume private var save = false private var restart = false - private var downloadsDir = - Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) override fun onCreate(savedInstanceState: Bundle?) { @@ -226,14 +221,13 @@ class ConfigActivity : AppCompatActivity() { if (!Utils.requestPermission(this, android.Manifest.permission.READ_EXTERNAL_STORAGE)) return false - val content = Utils.getFileContents(File(downloadsDir, "cert.pem")) - if (content == "Failed") { + val content = Utils.getFileContents(BaresipService.downloadsPath + "/cert.pem") + if (content == null) { Utils.alertView(this, getString(R.string.error), getString(R.string.read_cert_error)) return false } - Utils.putFileContents(File(BaresipService.filesPath + "/cert.pem"), - content) + Utils.putFileContents(BaresipService.filesPath + "/cert.pem", content) Config.remove("sip_certificate") Config.add("sip_certificate", BaresipService.filesPath + "/cert.pem") } else { @@ -248,13 +242,13 @@ class ConfigActivity : AppCompatActivity() { if (!Utils.requestPermission(this, android.Manifest.permission.READ_EXTERNAL_STORAGE)) return false - val content = Utils.getFileContents(File(downloadsDir, "ca_certs.crt")) - if (content == "Failed") { + val content = Utils.getFileContents(BaresipService.downloadsPath + "/ca_certs.crt") + if (content == null) { Utils.alertView(this, getString(R.string.error), getString(R.string.read_ca_certs_error)) return false } - Utils.putFileContents(File(BaresipService.filesPath + "/ca_certs.crt"), + Utils.putFileContents(BaresipService.filesPath + "/ca_certs.crt", content) Config.remove("sip_cafile") Config.add("sip_cafile", BaresipService.filesPath + "/ca_certs.crt") diff --git a/app/src/main/kotlin/com/tutpro/baresip/Contact.kt b/app/src/main/kotlin/com/tutpro/baresip/Contact.kt index d8ee917b..c412fce0 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/Contact.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/Contact.kt @@ -1,5 +1,6 @@ package com.tutpro.baresip +import java.nio.charset.StandardCharsets import java.util.ArrayList class Contact(var name: String, var uri: String) { @@ -12,5 +13,47 @@ class Contact(var name: String, var uri: String) { return BaresipService.contacts } + fun save(): Boolean { + var contents = "" + for (c in BaresipService.contacts) contents += "\"${c.name}\" <${c.uri}>\n" + return Utils.putFileContents(BaresipService.filesPath + "/contacts", + contents.toByteArray()) + } + + fun restore(): Boolean { + val content = Utils.getFileContents(BaresipService.filesPath + "/contacts") + if (content == null) return false + val contacts = String(content, StandardCharsets.ISO_8859_1) + Api.contacts_remove() + BaresipService.contacts.clear() + contacts.lines().forEach { + val parts = it.split("\"") + if (parts.size == 3) { + val name = parts[1] + var uri = parts[2].trim() + if (uri.startsWith("<")) + uri = uri.substringAfter("<").substringBefore(">") + // Currently no need to make baresip aware of the contact + // Api.contact_add("\"$name\" $uri") + BaresipService.contacts.add(Contact(name, uri)) + } + } + return true + } + + fun export(): Boolean { + return Utils.putFileContents(BaresipService.downloadsPath + "/contacts.bs", + Utils.getFileContents(BaresipService.filesPath + "/contacts")!!) + } + + fun import(): Boolean { + val contacts = Utils.getFileContents(BaresipService.downloadsPath + "/contacts.bs") + if (contacts != null) { + Utils.putFileContents(BaresipService.filesPath + "/contacts", contacts) + return restore() + } + return false + } + } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/tutpro/baresip/ContactActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/ContactActivity.kt index cb938e46..c5a7a414 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ContactActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ContactActivity.kt @@ -119,7 +119,7 @@ class ContactActivity : AppCompatActivity() { } Contact.contacts().sortBy { Contact -> Contact.name } - ContactsActivity.saveContacts(applicationContext.filesDir, "contacts") + Contact.save() i.putExtra("name", newName) setResult(Activity.RESULT_OK, i) diff --git a/app/src/main/kotlin/com/tutpro/baresip/ContactListAdapter.kt b/app/src/main/kotlin/com/tutpro/baresip/ContactListAdapter.kt index ad273f25..f739f132 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ContactListAdapter.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ContactListAdapter.kt @@ -67,7 +67,7 @@ class ContactListAdapter(private val cxt: Context, private val rows: ArrayList { Contact.contacts().removeAt(pos) - ContactsActivity.saveContacts(cxt.applicationContext.filesDir, "contacts") + Contact.save() this.notifyDataSetChanged() } DialogInterface.BUTTON_NEGATIVE -> { diff --git a/app/src/main/kotlin/com/tutpro/baresip/ContactsActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/ContactsActivity.kt index 5ed72235..5e9f5d0c 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/ContactsActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/ContactsActivity.kt @@ -3,15 +3,12 @@ package com.tutpro.baresip import android.app.Activity import android.content.* import android.os.Bundle -import android.os.Environment import android.support.v7.app.AppCompatActivity import android.view.Menu import android.view.MenuItem import android.widget.ImageButton import android.widget.ListView -import java.io.File - class ContactsActivity : AppCompatActivity() { internal lateinit var clAdapter: ContactListAdapter @@ -63,12 +60,10 @@ class ContactsActivity : AppCompatActivity() { override fun onOptionsItemSelected(item: MenuItem): Boolean { - val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) - when (item.itemId) { R.id.export_contacts -> { - if (saveContacts(dir, "contacts.bs")) + if (Contact.export()) Utils.alertView(this, "", getString(R.string.exported_contacts)) else @@ -77,11 +72,11 @@ class ContactsActivity : AppCompatActivity() { } R.id.import_contacts -> { - if (restoreContacts(dir, "contacts.bs")) { + if (Contact.import()) { Utils.alertView(this, "", getString(R.string.imported_contacts)) clAdapter.notifyDataSetChanged() - saveContacts(applicationContext.filesDir, "contacts") + Contact.save() } else Utils.alertView(this,getString(R.string.error), getString(R.string.import_error)) @@ -111,33 +106,6 @@ class ContactsActivity : AppCompatActivity() { companion object { - fun saveContacts(path: File, file: String): Boolean { - var contents = "" - for (c in Contact.contacts()) - contents += "\"${c.name}\" <${c.uri}>\n" - return Utils.putFileContents(File(path, file), contents) - } - - fun restoreContacts(path: File, file: String): Boolean { - val content = Utils.getFileContents(File(path, file)) - if (content == "Failed") return false - Api.contacts_remove() - Contact.contacts().clear() - content.lines().forEach { - val parts = it.split("\"") - if (parts.size == 3) { - val name = parts[1] - var uri = parts[2].trim() - if (uri.startsWith("<")) - uri = uri.substringAfter("<").substringBefore(">") - // Currently no need to make baresip aware of the contact - // Api.contact_add("\"$name\" $uri") - Contact.contacts().add(Contact(name, uri)) - } - } - return true - } - fun findContactURI(name: String): String { for (c in Contact.contacts()) if (c.name == name) diff --git a/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt index 7b7fa48f..4790c8ee 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt @@ -404,8 +404,6 @@ class MainActivity : AppCompatActivity() { } } - if (intentAction == null) - CallHistory.restore(applicationContext.filesDir.absolutePath) callsButton.setOnClickListener { if (aorSpinner.selectedItemPosition >= 0) { val i = Intent(this@MainActivity, CallsActivity::class.java) @@ -891,22 +889,28 @@ class MainActivity : AppCompatActivity() { } baresipService.setAction("ToggleSpeaker") startService(baresipService) - return true } R.id.config -> { i = Intent(this, ConfigActivity::class.java) startActivityForResult(i, CONFIG_CODE) - return true } R.id.accounts -> { i = Intent(this, AccountsActivity::class.java) startActivityForResult(i, ACCOUNTS_CODE) - return true + } + R.id.export_all -> { + if (Utils.requestPermission(this, + android.Manifest.permission.WRITE_EXTERNAL_STORAGE)) + askPassword(getString(R.string.encrypt_password)) + } + R.id.import_all -> { + if (Utils.requestPermission(this, + android.Manifest.permission.READ_EXTERNAL_STORAGE)) + askPassword(getString(R.string.decrypt_password)) } R.id.about -> { i = Intent(this, AboutActivity::class.java) startActivityForResult(i, ABOUT_CODE) - return true } R.id.restart, R.id.quit -> { if (stopState == "initial") { @@ -921,10 +925,77 @@ class MainActivity : AppCompatActivity() { System.exit(0) } } - return true } - else -> return super.onOptionsItemSelected(item) } + return true + } + + private fun askPassword(title: String) { + val builder = android.app.AlertDialog.Builder(this) + builder.setTitle(title) + val viewInflated = LayoutInflater.from(this) + .inflate(R.layout.password_dialog, findViewById(android.R.id.content) as ViewGroup, + false) + val input = viewInflated.findViewById(R.id.password) as EditText + builder.setView(viewInflated) + builder.setPositiveButton(android.R.string.ok) { dialog, _ -> + dialog.dismiss() + val password = input.text.toString() + if (password != "") { + if (title == getString(R.string.encrypt_password)) + exportAll(password) + else + importAll(password) + } + } + builder.setNegativeButton(android.R.string.cancel) { dialog, _ -> + dialog.cancel() + } + builder.show() + } + + private fun exportAll(password: String) { + val files = arrayOf("accounts", "config", "contacts", "history", "messages", "uuid", + "zrtp_cache.dat", "zrtp_zid", "cert.pem", "ca_cert", "ca_certs.crt") + val exportFilePath = BaresipService.downloadsPath + "/baresip.bs" + val zipFilePath = BaresipService.filesPath + "/baresip.zip" + if (!Utils.zip(files, "baresip.zip")) { + Utils.alertView(this, getString(R.string.error), "Failed to write zip file 'baresip.zip") + return + } + val content = Utils.getFileContents(zipFilePath) + if (content == null) { + Utils.alertView(this, getString(R.string.error), "Failed to read zip file 'baresip.zip") + return + } + if (!Utils.encryptToFile(exportFilePath, content, password)) { + Utils.alertView(this, getString(R.string.error), getString(R.string.export_all_failed)) + return + } + Utils.alertView(this, getString(R.string.info), getString(R.string.exported_all)) + Utils.deleteFile(zipFilePath) + } + + private fun importAll(password: String) { + val importFilePath = BaresipService.downloadsPath + "/baresip.bs" + val zipFilePath = BaresipService.filesPath + "/baresip.zip" + val zipData = Utils.decryptFromFile(importFilePath, password) + if (zipData == null) { + Utils.alertView(this, getString(R.string.error), getString(R.string.import_all_failed)) + return + } + if (!Utils.putFileContents(zipFilePath, zipData)) { + Utils.alertView(this, getString(R.string.error), + "Failed to write file 'baresip.zip'") + return + } + if (!Utils.unZip(zipFilePath)) { + Utils.alertView(this, getString(R.string.error), + "Failed to unzip file 'baresip.zip'") + return + } + Utils.alertView(this, getString(R.string.info), getString(R.string.imported_all)) + Utils.deleteFile(zipFilePath) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { diff --git a/app/src/main/kotlin/com/tutpro/baresip/Message.kt b/app/src/main/kotlin/com/tutpro/baresip/Message.kt index 7569434e..05e31dc3 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/Message.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/Message.kt @@ -36,7 +36,7 @@ class Message(val aor: String, val peerUri: String, val message: String, val tim while (it.hasNext()) if (it.next().aor == aor) it.remove() } - fun saveMessages() { + fun save() { val file = File(BaresipService.filesPath, "messages") try { val fos = FileOutputStream(file) @@ -51,7 +51,7 @@ class Message(val aor: String, val peerUri: String, val message: String, val tim } } - fun restoreMessages() { + fun restore() { val file = File(BaresipService.filesPath, "messages") if (file.exists()) { try { diff --git a/app/src/main/kotlin/com/tutpro/baresip/RunOnStartup.kt b/app/src/main/kotlin/com/tutpro/baresip/RunOnStartup.kt index 8e1adbdf..c8a16f05 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/RunOnStartup.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/RunOnStartup.kt @@ -5,7 +5,7 @@ import android.content.Context import android.content.Intent import android.os.Bundle -import java.io.File +import java.nio.charset.StandardCharsets class RunOnStartup : BroadcastReceiver() { @@ -13,8 +13,8 @@ class RunOnStartup : BroadcastReceiver() { Log.i("Baresip", "RunOnStartup received intent ${intent.action}") if ((intent.action == Intent.ACTION_BOOT_COMPLETED) or (intent.action == "com.tutpro.baresip.Restart")) { - val configFile = File(context.filesDir.absolutePath + "/config") - val config = Utils.getFileContents(configFile) + val configPath = context.filesDir.absolutePath + "/config" + val config = String(Utils.getFileContents(configPath)!!, StandardCharsets.ISO_8859_1) val asCv = Utils.getNameValue(config,"auto_start") if ((asCv.size > 0) && (asCv[0] == "yes")) { Log.d("Baresip", "Start baresip upon boot completed or restart") diff --git a/app/src/main/kotlin/com/tutpro/baresip/Utils.kt b/app/src/main/kotlin/com/tutpro/baresip/Utils.kt index 58833e63..7c3f599d 100644 --- a/app/src/main/kotlin/com/tutpro/baresip/Utils.kt +++ b/app/src/main/kotlin/com/tutpro/baresip/Utils.kt @@ -4,8 +4,6 @@ import android.app.Activity import android.app.ActivityManager import android.content.Context import android.support.v7.app.AlertDialog -import android.os.PowerManager -import android.app.KeyguardManager import android.content.Intent import android.content.pm.PackageManager import android.os.Bundle @@ -13,8 +11,6 @@ import android.support.v4.app.ActivityCompat import android.support.v4.content.ContextCompat import android.text.Editable import android.text.TextWatcher -import android.util.Base64 -import android.util.Base64.encodeToString import kotlin.collections.ArrayList import kotlin.Exception @@ -22,57 +18,16 @@ import kotlin.Exception import java.io.* import java.security.SecureRandom import java.util.* -import java.nio.ByteBuffer +import java.util.zip.* + import javax.crypto.Cipher import javax.crypto.SecretKeyFactory -import javax.crypto.spec.GCMParameterSpec +import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.PBEKeySpec +import javax.crypto.spec.SecretKeySpec object Utils { - fun getFileContents(file: File): String { - if (!file.exists()) { - Log.e("Baresip", "Failed to find file: " + file.path) - return "Failed" - } else { - val length = file.length().toInt() - val bytes = ByteArray(length) - try { - val `in` = FileInputStream(file) - try { - `in`.read(bytes) - } finally { - `in`.close() - } - return String(bytes) - } catch (e: java.io.IOException) { - Log.e("Baresip", "Failed to read file: " + file.path + ": " + - e.toString()) - return "Failed" - } - } - } - - fun putFileContents(file: File, contents: String): Boolean { - try { - val fOut = FileOutputStream(file.absoluteFile, false) - val fWriter = OutputStreamWriter(fOut) - try { - fWriter.write(contents) - fWriter.close() - fOut.close() - } catch (e: java.io.IOException) { - Log.e("Baresip", "Failed to put contents to file: " + e.toString()) - return false - } - - } catch (e: java.io.FileNotFoundException) { - Log.e("Baresip", "Failed to find contents file: " + e.toString()) - return false - } - return true - } - fun getNameValue(string: String, name: String): ArrayList { val lines = string.split("\n") val result = ArrayList() @@ -90,24 +45,6 @@ object Utils { return result } - fun copyAssetToFile(context: Context, asset: String, path: String) { - try { - val `is` = context.assets.open(asset) - val os = FileOutputStream(path) - val buffer = ByteArray(512) - var byteRead: Int = `is`.read(buffer) - while (byteRead != -1) { - os.write(buffer, 0, byteRead) - byteRead = `is`.read(buffer) - } - os.close() - `is`.close() - } catch (e: IOException) { - Log.e("Baresip", "Failed to read asset " + asset + ": " + - e.toString()) - } - } - fun alertView(context: Context, title: String, message: String) { val builder = AlertDialog.Builder(context) builder.setTitle(title) @@ -142,7 +79,7 @@ object Utils { } } - fun aorUser(aor: String): String { + private fun aorUser(aor: String): String { val user = aor.substringBefore("@") return if (user == aor) "" else user } @@ -164,20 +101,20 @@ object Utils { return Regex("^(([0-1]?[0-9]{1,2}\\.)|(2[0-4][0-9]\\.)|(25[0-5]\\.)){3}(([0-1]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))$").matches(ip) } - fun checkIpV6(ip: String): Boolean { + private fun checkIpV6(ip: String): Boolean { return Regex("^(([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4})$").matches(ip) } - fun checkIpv6InBrackets(bracketedIp: String): Boolean { + private fun checkIpv6InBrackets(bracketedIp: String): Boolean { return bracketedIp.startsWith("[") && bracketedIp.endsWith("]") && checkIpV6(bracketedIp.substring(1, bracketedIp.length - 2)) } - fun checkIp(ip: String): Boolean { + private fun checkIp(ip: String): Boolean { return checkIpV4(ip) || checkIpV6(ip) } - fun checkUriUser(user: String): Boolean { + private fun checkUriUser(user: String): Boolean { for (c in user) if (!(c.isLetterOrDigit() || c in "-_.!~*'()&=+$,;?/")) return false return user.length > 0 @@ -193,7 +130,7 @@ object Utils { return true } - fun checkPort(port: String): Boolean { + private fun checkPort(port: String): Boolean { val number = port.toIntOrNull() if (number == null) return false return (number > 0) && (number < 65536) @@ -208,7 +145,7 @@ object Utils { checkPort(ipPort.substringAfterLast(":")) } - fun checkDomainPort(domainPort: String): Boolean { + private fun checkDomainPort(domainPort: String): Boolean { return checkDomain(domainPort.substringBeforeLast(":")) && checkPort(domainPort.substringAfterLast(":")) } @@ -218,13 +155,13 @@ object Utils { checkIpPort(hostPort) || checkDomainPort(hostPort) } - fun checkParams(params: String): Boolean { + private fun checkParams(params: String): Boolean { for (param in params.split(";")) if (!checkParam(param)) return false return true } - fun checkParam(param: String): Boolean { + private fun checkParam(param: String): Boolean { val nameValue = param.split("=") if (nameValue.size == 1) /* Todo: do proper check */ @@ -295,23 +232,6 @@ object Utils { return appProcessInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND } - fun isDeviceLocked(context: Context): Boolean { - val isLocked: Boolean - - val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager - val inKeyguardRestrictedInputMode = keyguardManager.inKeyguardRestrictedInputMode() - - if (inKeyguardRestrictedInputMode) { - isLocked = true - } else { - val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager - isLocked = !powerManager.isInteractive - } - - Log.d("Baresip", "Now device is ${if (isLocked) "locked" else "unlocked"}") - return isLocked - } - fun dtmfWatcher(callp: String): TextWatcher { return object : TextWatcher { override fun beforeTextChanged(sequence: CharSequence, start: Int, count: Int, after: Int) {} @@ -341,81 +261,183 @@ object Utils { return true } - fun encrypt(plainText: String, password: String): String { - val sr = SecureRandom.getInstance("SHA1PRNG") - val salt = ByteArray(16) - sr.nextBytes(salt) - val iterationCount = Random().nextInt(1024) + 512 - val spec = PBEKeySpec(password.toCharArray(), salt, iterationCount, 256) - val secretKey = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").generateSecret(spec) - val iv = ByteArray(12) - SecureRandom().nextBytes(iv) - val cipher = Cipher.getInstance("AES/GCM/NoPadding") - val parameterSpec = GCMParameterSpec(128, iv) - cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec) - val cipherText = cipher.doFinal(plainText.toByteArray()) - val byteBuffer = ByteBuffer.allocate(Int.SIZE_BYTES + salt.size + Int.SIZE_BYTES + - Int.SIZE_BYTES + iv.size + cipherText.size) - byteBuffer.putInt(salt.size) - byteBuffer.put(salt) - byteBuffer.putInt(iterationCount) - byteBuffer.putInt(iv.size) - byteBuffer.put(iv) - byteBuffer.put(cipherText) - return encodeToString(byteBuffer.array(), Base64.DEFAULT) + fun copyAssetToFile(context: Context, asset: String, path: String) { + try { + val `is` = context.assets.open(asset) + val os = FileOutputStream(path) + val buffer = ByteArray(512) + var byteRead: Int = `is`.read(buffer) + while (byteRead != -1) { + os.write(buffer, 0, byteRead) + byteRead = `is`.read(buffer) + } + os.close() + `is`.close() + } catch (e: IOException) { + Log.e("Baresip", "Failed to copy asset '$asset' to file: $e") + } } - fun decrypt(cipherMessage: String, password: String): String { - val byteBuffer = ByteBuffer.wrap(Base64.decode(cipherMessage, Base64.DEFAULT)) - val saltLength = byteBuffer.getInt() - if (saltLength != 16) { - Log.w("Baresip", "invalid salt length $saltLength") - return "" - } - val salt = ByteArray(saltLength) - byteBuffer.get(salt) - val iterationCount = byteBuffer.getInt() - if ((iterationCount < 512) || (iterationCount > 1023 + 512)) { - Log.w("Baresip", "invalid iteratorCount $iterationCount") - return "" - } - val spec = PBEKeySpec(password.toCharArray(), salt, iterationCount, 256) - val secretKey = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").generateSecret(spec) - val ivLength = byteBuffer.getInt() - if (ivLength != 12) { - Log.d("Baresip", "invalid iv length $ivLength") - return "" - } - val iv = ByteArray(ivLength) - byteBuffer.get(iv) - val cipherText = ByteArray(byteBuffer.remaining()) - byteBuffer.get(cipherText) - val cipher = Cipher.getInstance("AES/GCM/NoPadding") - cipher.init(Cipher.DECRYPT_MODE, secretKey, GCMParameterSpec(128, iv)) + fun deleteFile(filePath: String) { + val file = File(filePath) + if (file.exists()) file.delete() + } + + fun getFileContents(filePath: String): ByteArray? { try { - return String(cipher.doFinal(cipherText)) + return File(filePath).readBytes() + } catch(e: FileNotFoundException) { + Log.e("Baresip", "File '$filePath' not found: ${e.printStackTrace()}") + return null } catch (e: Exception) { - Log.w("Baresip", "Decryption failed ${e.printStackTrace()}") - return "" + Log.e("Baresip", "Failed to read file '$filePath': ${e.printStackTrace()}") + return null } } + fun putFileContents(filePath: String, contents: ByteArray): Boolean { + try { + File(filePath).writeBytes(contents) + } + catch (e: IOException) { + Log.e("Baresip", "Failed to write file '$filePath': $e") + return false + } + return true + } + + class Crypto(val salt: ByteArray, val iter: Int, val iv: ByteArray, val data: ByteArray): + Serializable { + val serialVersionUID = -29238082928391L + } + + private fun encrypt(content: ByteArray, password: CharArray): Crypto? { + var obj: Crypto? = null + try { + val sr = SecureRandom() + val salt = ByteArray(128) + sr.nextBytes(salt) + val iterationCount = Random().nextInt(1024) + 512 + val pbKeySpec = PBEKeySpec(password, salt, iterationCount, 128) + val secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1") + val keyBytes = secretKeyFactory.generateSecret(pbKeySpec).encoded + val keySpec = SecretKeySpec(keyBytes, "AES") + val ivRandom = SecureRandom() + val iv = ByteArray(16) + ivRandom.nextBytes(iv) + val ivSpec = IvParameterSpec(iv) + val cipher = Cipher.getInstance("AES/CBC/PKCS7Padding") + cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec) + val cipherData = cipher.doFinal(content) + obj = Crypto(salt, iterationCount, iv, cipherData) + } catch (e: Exception) { + Log.e("Baresip", "Encrypt failed: ${e.printStackTrace()}") + } + return obj + + } + + private fun decrypt(obj: Crypto, password: CharArray): ByteArray? { + var plainData: ByteArray? = null + try { + val pbKeySpec = PBEKeySpec(password, obj.salt, obj.iter, 128) + val secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1") + val keyBytes = secretKeyFactory.generateSecret(pbKeySpec).encoded + val keySpec = SecretKeySpec(keyBytes, "AES") + val cipher = Cipher.getInstance("AES/CBC/PKCS7Padding") + val ivSpec = IvParameterSpec(obj.iv) + cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec) + plainData = cipher.doFinal(obj.data) + } catch (e: Exception) { + Log.e("Baresip", "Decrypt failed: ${e.printStackTrace()}") + } + return plainData + } + + + fun encryptToFile(filePath: String, content: ByteArray, password: String): Boolean { + val obj = encrypt(content, password.toCharArray()) + try { + ObjectOutputStream(FileOutputStream(File(filePath))).use { + it.writeObject(obj) + } + } catch (e: Exception) { + Log.e("Baresip", "Write failed: ${e.printStackTrace()}") + return false + } + return true + } + + fun decryptFromFile(filePath: String, password: String): ByteArray? { + var plainData: ByteArray? = null + try { + ObjectInputStream(FileInputStream(File(filePath))).use { it -> + val obj = it.readObject() as Crypto + plainData = decrypt(obj, password.toCharArray()) + } + } catch (e: Exception) { + Log.e("Baresip", "Decrypt failed from file '$filePath'") + } + return plainData + } + + fun zip(fileNames: Array, zipFileName: String): Boolean { + val zipFilePath = BaresipService.filesPath + "/" + zipFileName + try { + ZipOutputStream(BufferedOutputStream(FileOutputStream(zipFilePath))).use { out -> + val data = ByteArray(1024) + for (file in fileNames) { + val filePath = BaresipService.filesPath + "/" + file + if (File(filePath).exists()) { + FileInputStream(filePath).use { fi -> + BufferedInputStream(fi).use { origin -> + val entry = ZipEntry(filePath) + out.putNextEntry(entry) + while (true) { + val readBytes = origin.read(data) + if (readBytes == -1) break + out.write(data, 0, readBytes) + } + } + } + } + } + } + } catch (e: IOException) { + Log.e("Baresip", "Failed to zip file '$zipFilePath': $e") + return false + } + return true + } + + fun unZip(zipFilePath: String): Boolean { + try { + ZipFile(zipFilePath).use { zip -> + zip.entries().asSequence().forEach { entry -> + zip.getInputStream(entry).use { input -> + File(entry.name).outputStream().use { output -> + input.copyTo(output) + } + } + } + } + } catch (e: IOException) { + Log.e("Baresip", "Failed to unzip file '$zipFilePath': $e") + return false + } + return true + } + fun dumpIntent(intent: Intent) { - val bundle: Bundle = intent.extras ?: return - val keys = bundle.keySet() val it = keys.iterator() - Log.d("Baresip", "Dumping intent start") - while (it.hasNext()) { val key = it.next() Log.d("Baresip","[" + key + "=" + bundle.get(key)+"]"); } - Log.d("Baresip", "Dumping intent finish") - } } diff --git a/app/src/main/res/menu/main_menu.xml b/app/src/main/res/menu/main_menu.xml index 226e56d4..62fcc10f 100644 --- a/app/src/main/res/menu/main_menu.xml +++ b/app/src/main/res/menu/main_menu.xml @@ -6,6 +6,12 @@ + + + + diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index abced341..23583d1a 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -238,26 +238,20 @@ Haluatko soittaa tai lähettää viestin kontaktille \'%1$s\'? Lähetä viesti - Haluatko poistaa kontaktin - \'%1$s\'? - - Kontaktiesi enimmäismäärä %1$d on - ylittynyt. - + Haluatko poistaa kontaktin \'%1$s\'? + Kontaktiesi enimmäismäärä %1$d on ylittynyt. Tallenna - Kontaktit tallennettiin - Download-kansion tiedostoon \'contacts.bs\'. + Kontaktit tallennettiin Download-kansion tiedostoon + \'contacts.bs\'. "Kontaktien tallennus Download kansioon epäonnistui. Tarkista Sovellukset → baresip → Käyttöluvat → Tallennustila. Palauta - Kontaktit palautettiin Download-kansiosta. - - Kontantien palauttaminen - Download-kansiosta epäonnistui. + Kontaktit palautettiin. + Kontantien palauttaminen Download-kansiosta epäonnistui. Tarkista Sovellukset → baresip → Käyttöluvat → Tallennustila ja että tiedosto \'contacts.bs\' on - kansiossa. + Download-kansiossa. Varoitus @@ -311,14 +305,12 @@ TLS sertifikaattitiedostosta. Niiden oletusarvot on palautettu. Käynnistä baresip uudelleen. - Tilin \'%1$s\' rekisteröinti - epäonnistui. + Tilin \'%1$s\' rekisteröinti epäonnistui. Et ole sallinut mikrofonin käyttöä. Todenna - Todennatko SAS:n <%1$s> - <%2$s>? + Todennatko SAS:n <%1$s> <%2$s>? Hyväksytkö puhelun siirron kohteeseen \'%1$s\'? @@ -334,8 +326,23 @@ Tämä pulelu on turvallinen ja kohde on todennettu! Haluatko poistaa todennuksen? Poista todennus -qqqq TLS CA-tiedosto - Jos merkitty, tiedosto \'ca_certs.crt\', joka sisältää todennusauktoriteettien TLS-sertifikaatit, on ladattu tai ladataan Download-kansiosta. + TLS CA-tiedosto + Jos merkitty, tiedosto \'ca_certs.crt\', joka sisältää + todennusauktoriteettien TLS-sertifikaatit, on ladattu tai ladataan Download-kansiosta. + TLS sertifikaattitiedosto - "Jos merkitty, tiedosto \'cert.pem\', joka sisältää tämän baresip-sovelluksen TLS-sertifikaatin ja yksityisen avaimen, on ladattu tai ladataan Download-kansiosta." + "Jos merkitty, tiedosto \'cert.pem\', joka sisältää + tämän baresip-sovelluksen TLS-sertifikaatin ja yksityisen avaimen, on ladattu tai ladataan + Download-kansiosta." + + Sovelluksen kaikki data talletettiin Download-kansion tiedostoon + \'baresip.bs\'. + + Sovelluksen kaikki data palautettiin. Sinun pitää käynnistää + baresip uudelleen. + + Sovelluksen data palauttaminen Download-kansiosta epäonnistui. + Tarkista Sovellukset → baresip → Käyttöluvat → Tallennustila ja että tallennettu tiedosto + \'baresip.bs\' on kansiossa ja (jos on) että annoit oikean salasanan. + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ee876cf7..3a53dd1c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -103,10 +103,9 @@ Check Apps → baresip → Permissions → Storage." Import - Imported accounts from Download folder. You need to restart - baresip. + Accounts imported. You need to restart baresip. Failed to import accounts from Download folder file. -Check Apps → baresip → Permissions → Storage and that file \'accounts.bs\' exists in + Check Apps → baresip → Permissions → Storage and that exported file \'accounts.bs\' exists in the folder and, if so, you gave correct Decrypt Password." Do you want to delete account \'%1$s\'? @@ -195,7 +194,8 @@ Check Apps → baresip → Permissions → Storage and that file \'accounts.bs\' Valid values are 6000-510000. Factory default is 28000. 28000 Expected Opus packet-loss - Expected Opus audio stream packet loss percentage, from 0-100. By default 0, turning off Opus Forward Error Correction (FEC). + Expected Opus audio stream packet loss percentage, + from 0–100. By default 0, turning off Opus Forward Error Correction (FEC). 0 Invalid Opus bitrate Invalid Opus Packet Loss Percentage @@ -230,7 +230,7 @@ Check Apps → baresip → Permissions → Storage and that file \'accounts.bs\' Exported contacts to Download folder file \'contacts.bs\'. "Failed to export contacts to Download folder. Check Apps → baresip → Permissions → Storage." - Imported contacts from Download folder. + Contacs imported. Failed to import contacts from Download folder. Check Apps → baresip → Permissions → Storage and that file \'contacts.bs\' exists in the folder. @@ -256,6 +256,8 @@ Check Apps → baresip → Permissions → Storage and that file \'accounts.bs\' + Export + Import About Restart Quit @@ -285,7 +287,8 @@ Check Apps → baresip → Permissions → Storage and that file \'accounts.bs\' Dialpad You already have an active call. Baresip failed to start. This may be due to invalid Listen Address - or TLS file. They have been reset. Restart baresip. + or TLS file. They have been reset. Restart baresip. + Registering of \`%1$s\` failed. You have not granted microphone permission. Verify @@ -297,7 +300,16 @@ Check Apps → baresip → Permissions → Storage and that file \'accounts.bs\' This call is NOT secure! This call is SECURE, but peer is NOT verified! This call is SECURE and peer is VERIFIED! - Do you want to unverify the peer? + Do you want to unverify the peer? + Unverify + Exported all application data to Download folder file \'baresip.bs\'. + Failed to export application data to Download folder file + \'baresip.bs\'. Check Apps → baresip → Permissions → Storage + All application data imported. You need to restart baresip. + Failed to import application data from Download folder. Check Apps → + baresip → Permissions → Storage and that exported file \'baresip.bs\' exists in the folder + and, if so, you gave correct Decrypt Password. +