- 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.
This commit is contained in:
Juha Heinanen
2019-10-09 11:48:26 +03:00
parent f40c097766
commit ac26c2256d
19 changed files with 420 additions and 300 deletions

View File

@ -3,7 +3,6 @@ package com.tutpro.baresip
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.os.Environment
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
@ -12,7 +11,6 @@ import android.app.AlertDialog
import android.view.ViewGroup import android.view.ViewGroup
import android.view.LayoutInflater import android.view.LayoutInflater
import java.io.File
import java.util.ArrayList import java.util.ArrayList
class AccountsActivity : AppCompatActivity() { class AccountsActivity : AppCompatActivity() {
@ -20,7 +18,6 @@ class AccountsActivity : AppCompatActivity() {
internal lateinit var alAdapter: AccountListAdapter internal lateinit var alAdapter: AccountListAdapter
internal var aor = "" internal var aor = ""
internal var password = ""
public override fun onCreate(savedInstanceState: Bundle?) { public override fun onCreate(savedInstanceState: Bundle?) {
@ -69,6 +66,7 @@ class AccountsActivity : AppCompatActivity() {
} }
} }
} }
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
@ -92,7 +90,6 @@ class AccountsActivity : AppCompatActivity() {
if (Utils.requestPermission(this, if (Utils.requestPermission(this,
android.Manifest.permission.READ_EXTERNAL_STORAGE)) android.Manifest.permission.READ_EXTERNAL_STORAGE))
askPassword(getString(R.string.decrypt_password)) askPassword(getString(R.string.decrypt_password))
} }
android.R.id.home -> { android.R.id.home -> {
@ -120,7 +117,6 @@ class AccountsActivity : AppCompatActivity() {
} }
private fun askPassword(title: String) { private fun askPassword(title: String) {
val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
val builder = AlertDialog.Builder(this) val builder = AlertDialog.Builder(this)
builder.setTitle(title) builder.setTitle(title)
val viewInflated = LayoutInflater.from(this) val viewInflated = LayoutInflater.from(this)
@ -130,17 +126,17 @@ class AccountsActivity : AppCompatActivity() {
builder.setView(viewInflated) builder.setView(viewInflated)
builder.setPositiveButton(android.R.string.ok) { dialog, _ -> builder.setPositiveButton(android.R.string.ok) { dialog, _ ->
dialog.dismiss() dialog.dismiss()
password = input.text.toString() val password = input.text.toString()
if (password != "") { if (password != "") {
if (title == getString(R.string.encrypt_password)) { if (title == getString(R.string.encrypt_password)) {
if (exportAccounts(dir, password)) if (exportAccounts(password))
Utils.alertView(this, getString(R.string.info), Utils.alertView(this, getString(R.string.info),
getString(R.string.exported_accounts)) getString(R.string.exported_accounts))
else else
Utils.alertView(this, getString(R.string.error), Utils.alertView(this, getString(R.string.error),
getString(R.string.export_accounts_error)) getString(R.string.export_accounts_error))
} else { } else {
if (importAccounts(dir, password)) if (importAccounts(password))
Utils.alertView(this, getString(R.string.info), Utils.alertView(this, getString(R.string.info),
getString(R.string.imported_accounts)) getString(R.string.imported_accounts))
else else
@ -153,6 +149,7 @@ class AccountsActivity : AppCompatActivity() {
dialog.cancel() dialog.cancel()
} }
builder.show() builder.show()
} }
companion object { companion object {
@ -169,30 +166,27 @@ class AccountsActivity : AppCompatActivity() {
fun saveAccounts() { fun saveAccounts() {
var accounts = "" var accounts = ""
for (a in Account.accounts()) accounts = accounts + a.print() + "\n" 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'") // Log.d("Baresip", "Saved accounts '${accounts}' to '${BaresipService.filesPath}/accounts'")
} }
fun exportAccounts(path: File, password: String): Boolean { fun exportAccounts(password: String): Boolean {
var accounts = "" val content = Utils.getFileContents("${BaresipService.filesPath}/accounts")
for (a in Account.accounts()) if (content == null) return false
accounts = accounts + a.print() + "\n" return Utils.encryptToFile("${BaresipService.downloadsPath}/accounts.bs",
return Utils.putFileContents(File(path, "accounts.bs"), content, password)
Utils.encrypt(accounts, password))
} }
fun importAccounts(path: File, password: String): Boolean { fun importAccounts(password: String): Boolean {
val content = Utils.getFileContents(File(path, "accounts.bs")) val content = Utils.decryptFromFile("${BaresipService.downloadsPath}/accounts.bs",
if (content == "Failed") return false password)
val accounts = Utils.decrypt(content, password) if (content == null) return false
if (accounts == "") return false return Utils.putFileContents("${BaresipService.filesPath}/accounts", content)
return Utils.putFileContents(File(BaresipService.filesPath + "/accounts"),
accounts)
} }
fun noAccounts(): Boolean { fun noAccounts(): Boolean {
return Utils.getFileContents(File(BaresipService.filesPath + "/accounts")) val contents = Utils.getFileContents(BaresipService.filesPath + "/accounts")
.length == 0 return contents == null || contents.size == 0
} }
} }

View File

@ -15,6 +15,7 @@ import android.view.View
import android.widget.RemoteViews import android.widget.RemoteViews
import android.support.v4.content.LocalBroadcastManager import android.support.v4.content.LocalBroadcastManager
import android.os.Build import android.os.Build
import android.os.Environment
import android.support.v4.app.NotificationCompat.VISIBILITY_PRIVATE import android.support.v4.app.NotificationCompat.VISIBILITY_PRIVATE
import android.support.v4.content.ContextCompat import android.support.v4.content.ContextCompat
import android.provider.Settings import android.provider.Settings
@ -55,6 +56,7 @@ class BaresipService: Service() {
intent.setPackage("com.tutpro.baresip") intent.setPackage("com.tutpro.baresip")
filesPath = filesDir.absolutePath filesPath = filesDir.absolutePath
downloadsPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).path
am = getSystemService(Context.AUDIO_SERVICE) as AudioManager am = getSystemService(Context.AUDIO_SERVICE) as AudioManager
@ -146,7 +148,7 @@ class BaresipService: Service() {
try { try {
File(filesPath).mkdirs() File(filesPath).mkdirs()
} catch (e: Error) { } 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) { for (a in assets) {
@ -172,8 +174,9 @@ class BaresipService: Service() {
} }
} }
ContactsActivity.restoreContacts(applicationContext.filesDir, "contacts") Contact.restore()
Message.restoreMessages() CallHistory.restore()
Message.restore()
Thread(Runnable { baresipStart(filesPath) }).start() Thread(Runnable { baresipStart(filesPath) }).start()
isServiceRunning = true isServiceRunning = true
@ -212,7 +215,7 @@ class BaresipService: Service() {
Api.ua_hangup(call.ua.uap, callp, 486, "Rejected") Api.ua_hangup(call.ua.uap, callp, 486, "Rejected")
if (call.ua.account.callHistory) { if (call.ua.account.callHistory) {
CallHistory.add(CallHistory(aor, peerUri, "in", false)) 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") Api.ua_hangup(uap, callp, 486, "Busy Here")
if (ua.account.callHistory) { if (ua.account.callHistory) {
CallHistory.add(CallHistory(aor, peerUri, "in", false)) CallHistory.add(CallHistory(aor, peerUri, "in", false))
CallHistory.save(filesPath) CallHistory.save()
ua.account.missedCalls = true ua.account.missedCalls = true
} }
if (!Utils.isVisible()) if (!Utils.isVisible())
@ -499,7 +502,7 @@ class BaresipService: Service() {
call.onhold = false call.onhold = false
if (ua.account.callHistory) { if (ua.account.callHistory) {
CallHistory.add(CallHistory(aor, call.peerURI, call.dir, true)) CallHistory.add(CallHistory(aor, call.peerURI, call.dir, true))
CallHistory.save(filesPath) CallHistory.save()
call.hasHistory = true call.hasHistory = true
} }
if (call.dir == "in") { if (call.dir == "in") {
@ -589,7 +592,7 @@ class BaresipService: Service() {
calls.remove(call) calls.remove(call)
if (ua.account.callHistory && !call.hasHistory) { if (ua.account.callHistory && !call.hasHistory) {
CallHistory.add(CallHistory(aor, call.peerURI, call.dir, false)) CallHistory.add(CallHistory(aor, call.peerURI, call.dir, false))
CallHistory.save(filesPath) CallHistory.save()
if (call.dir == "in") ua.account.missedCalls = true if (call.dir == "in") ua.account.missedCalls = true
} }
if (Call.calls().size == 0) { if (Call.calls().size == 0) {
@ -636,7 +639,7 @@ class BaresipService: Service() {
Log.d(LOG_TAG, "Message event for $uap from $peer at $timeStamp") Log.d(LOG_TAG, "Message event for $uap from $peer at $timeStamp")
Message.add(Message(ua.account.aor, peer, text, timeStamp.toLong(), Message.add(Message(ua.account.aor, peer, text, timeStamp.toLong(),
R.drawable.arrow_down_green, 0, "", true)) R.drawable.arrow_down_green, 0, "", true))
Message.saveMessages() Message.save()
ua.account.unreadMessages = true ua.account.unreadMessages = true
if (!Utils.isVisible()) { if (!Utils.isVisible()) {
val intent = Intent(this, BaresipService::class.java) val intent = Intent(this, BaresipService::class.java)
@ -930,6 +933,7 @@ class BaresipService: Service() {
var dynDns = false var dynDns = false
var filesPath = "" var filesPath = ""
var downloadsPath = ""
var uas = ArrayList<UserAgent>() var uas = ArrayList<UserAgent>()
var status = ArrayList<Int>() var status = ArrayList<Int>()
var calls = ArrayList<Call>() var calls = ArrayList<Call>()

View File

@ -45,9 +45,9 @@ class CallHistory(val aor: String, val peerURI: String, val direction: String,
return null return null
} }
fun save(path: String) { fun save() {
Log.d("Baresip", "Saving history of size ${BaresipService.history.size}") Log.d("Baresip", "Saving call history of size ${BaresipService.history.size}")
val file = File(path, "history") val file = File(BaresipService.filesPath, "history")
try { try {
val fos = FileOutputStream(file) val fos = FileOutputStream(file)
val oos = ObjectOutputStream(fos) val oos = ObjectOutputStream(fos)
@ -60,21 +60,19 @@ class CallHistory(val aor: String, val peerURI: String, val direction: String,
} }
} }
fun restore(path: String) { fun restore() {
val file = File(path + "/history") val file = File(BaresipService.filesPath, "history")
if (file.exists()) { if (file.exists())
try { try {
val fis = FileInputStream(file) val fis = FileInputStream(file)
val ois = ObjectInputStream(fis) val ois = ObjectInputStream(fis)
BaresipService.history = ois.readObject() as ArrayList<CallHistory> BaresipService.history = ois.readObject() as ArrayList<CallHistory>
ois.close() ois.close()
fis.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) { } catch (e: Exception) {
Log.e("Baresip", "InputStream exception: - " + e.toString()) Log.e("Baresip", "InputStream exception: - " + e.toString())
} }
}
} }
fun print() { fun print() {

View File

@ -127,7 +127,7 @@ class CallsActivity : AppCompatActivity() {
override fun onPause() { override fun onPause() {
CallHistory.save(applicationContext.filesDir.absolutePath) CallHistory.save()
super.onPause() super.onPause()
} }
@ -142,7 +142,7 @@ class CallsActivity : AppCompatActivity() {
aor.substringAfter(":"))) aor.substringAfter(":")))
deleteDialog.setPositiveButton(getText(R.string.delete)) { dialog, _ -> deleteDialog.setPositiveButton(getText(R.string.delete)) { dialog, _ ->
CallHistory.clear(aor) CallHistory.clear(aor)
CallHistory.save(applicationContext.filesDir.absolutePath) CallHistory.save()
aorGenerateHistory(aor) aorGenerateHistory(aor)
clAdapter.notifyDataSetChanged() clAdapter.notifyDataSetChanged()
dialog.dismiss() dialog.dismiss()

View File

@ -97,7 +97,7 @@ class ChatActivity : AppCompatActivity() {
if (chatMessages.size == 0) { if (chatMessages.size == 0) {
listView.removeFooterView(footerView) listView.removeFooterView(footerView)
} }
Message.saveMessages() Message.save()
} }
DialogInterface.BUTTON_NEUTRAL -> { DialogInterface.BUTTON_NEUTRAL -> {
} }
@ -224,7 +224,7 @@ class ChatActivity : AppCompatActivity() {
save = true save = true
} }
} }
if (save) Message.saveMessages() if (save) Message.save()
imm.hideSoftInputFromWindow(newMessage.windowToken, 0) imm.hideSoftInputFromWindow(newMessage.windowToken, 0)
BaresipService.activities.removeAt(0) BaresipService.activities.removeAt(0)
val i = Intent() val i = Intent()

View File

@ -72,7 +72,7 @@ class ChatsActivity: AppCompatActivity() {
clAdapter.remove(m) clAdapter.remove(m)
clAdapter.notifyDataSetChanged() clAdapter.notifyDataSetChanged()
BaresipService.messages = msgs BaresipService.messages = msgs
Message.saveMessages() Message.save()
uaMessages = uaMessages(aor) uaMessages = uaMessages(aor)
} }
DialogInterface.BUTTON_NEUTRAL -> { DialogInterface.BUTTON_NEUTRAL -> {
@ -159,7 +159,7 @@ class ChatsActivity: AppCompatActivity() {
aor.substringAfter(":"))) aor.substringAfter(":")))
deleteDialog.setPositiveButton(getText(R.string.delete)) { dialog, _ -> deleteDialog.setPositiveButton(getText(R.string.delete)) { dialog, _ ->
Message.clear(aor) Message.clear(aor)
Message.saveMessages() Message.save()
uaMessages.clear() uaMessages.clear()
clAdapter.notifyDataSetChanged() clAdapter.notifyDataSetChanged()
Account.findUa(aor)!!.account.unreadMessages = false Account.findUa(aor)!!.account.unreadMessages = false
@ -227,7 +227,7 @@ class ChatsActivity: AppCompatActivity() {
if ((Message.messages()[i].aor == aor) && if ((Message.messages()[i].aor == aor) &&
(Message.messages()[i].timeStamp == time)) { (Message.messages()[i].timeStamp == time)) {
Message.messages()[i].new = false Message.messages()[i].new = false
Message.saveMessages() Message.save()
return return
} }
} }
@ -237,7 +237,7 @@ class ChatsActivity: AppCompatActivity() {
if ((Message.messages()[i].aor == aor) && if ((Message.messages()[i].aor == aor) &&
(Message.messages()[i].timeStamp == time)) { (Message.messages()[i].timeStamp == time)) {
Message.messages().removeAt(i) Message.messages().removeAt(i)
Message.saveMessages() Message.save()
return return
} }
} }

View File

@ -1,16 +1,17 @@
package com.tutpro.baresip package com.tutpro.baresip
import android.content.Context import android.content.Context
import java.io.File
import java.net.InetAddress import java.net.InetAddress
import java.nio.charset.StandardCharsets
object Config { object Config {
private val path = BaresipService.filesPath + "/config" private val configPath = BaresipService.filesPath + "/config"
private val file = File(path) private var config = String(Utils.getFileContents(configPath)!!, StandardCharsets.ISO_8859_1)
private var config = Utils.getFileContents(file)
fun initialize(dnsServers: List<InetAddress>) { fun initialize(dnsServers: List<InetAddress>) {
Log.d("Baresip", "Config is '$config'")
var write = false var write = false
if (!config.contains("zrtp_hash")) { if (!config.contains("zrtp_hash")) {
config = "${config}zrtp_hash yes\n" config = "${config}zrtp_hash yes\n"
@ -41,10 +42,10 @@ object Config {
} }
if (!config.contains("opus_samplerate")) { if (!config.contains("opus_samplerate")) {
config = "${config}opus_samplerate 16000\n" config = "${config}opus_samplerate 16000\n"
val accountsFile = File(BaresipService.filesPath + "/config") val accountsPath = BaresipService.filesPath + "/accounts"
var accounts = Utils.getFileContents(accountsFile) var accounts = String(Utils.getFileContents(accountsPath)!!, StandardCharsets.ISO_8859_1)
accounts = accounts.replace("opus/48000/1", "opus/16000/1") accounts = accounts.replace("opus/48000/1", "opus/16000/1")
Utils.putFileContents(accountsFile, accounts) Utils.putFileContents(accountsPath, accounts.toByteArray())
write = true write = true
} }
if (!config.contains("opus_stereo")) { if (!config.contains("opus_stereo")) {
@ -95,7 +96,7 @@ object Config {
} }
if (write) { if (write) {
Log.e("Baresip", "Writing config '$config'") 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) { fun remove(variable: String) {
config = Utils.removeLinesStartingWithName(config, variable) config = Utils.removeLinesStartingWithName(config, variable)
Utils.putFileContents(file, config) Utils.putFileContents(configPath, config.toByteArray())
} }
fun replace(variable: String, value: String) { fun replace(variable: String, value: String) {
@ -124,7 +125,7 @@ object Config {
} }
fun reset(ctx: Context) { fun reset(ctx: Context) {
Utils.copyAssetToFile(ctx, "config", path) Utils.copyAssetToFile(ctx, "config", configPath)
} }
fun save() { fun save() {
@ -133,7 +134,7 @@ object Config {
if (line.length > 0) if (line.length > 0)
result = result + line + '\n' result = result + line + '\n'
config = result config = result
Utils.putFileContents(file, config) Utils.putFileContents(configPath, config.toByteArray())
Log.d("Baresip", "New config '$result'") Log.d("Baresip", "New config '$result'")
// Api.reload_config() // Api.reload_config()
} }

View File

@ -6,15 +6,12 @@ import android.content.Intent
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Environment
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.* import android.widget.*
import java.io.File
class ConfigActivity : AppCompatActivity() { class ConfigActivity : AppCompatActivity() {
internal lateinit var autoStart: CheckBox internal lateinit var autoStart: CheckBox
@ -44,8 +41,6 @@ class ConfigActivity : AppCompatActivity() {
private var callVolume = BaresipService.callVolume private var callVolume = BaresipService.callVolume
private var save = false private var save = false
private var restart = false private var restart = false
private var downloadsDir =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -226,14 +221,13 @@ class ConfigActivity : AppCompatActivity() {
if (!Utils.requestPermission(this, if (!Utils.requestPermission(this,
android.Manifest.permission.READ_EXTERNAL_STORAGE)) android.Manifest.permission.READ_EXTERNAL_STORAGE))
return false return false
val content = Utils.getFileContents(File(downloadsDir, "cert.pem")) val content = Utils.getFileContents(BaresipService.downloadsPath + "/cert.pem")
if (content == "Failed") { if (content == null) {
Utils.alertView(this, getString(R.string.error), Utils.alertView(this, getString(R.string.error),
getString(R.string.read_cert_error)) getString(R.string.read_cert_error))
return false return false
} }
Utils.putFileContents(File(BaresipService.filesPath + "/cert.pem"), Utils.putFileContents(BaresipService.filesPath + "/cert.pem", content)
content)
Config.remove("sip_certificate") Config.remove("sip_certificate")
Config.add("sip_certificate", BaresipService.filesPath + "/cert.pem") Config.add("sip_certificate", BaresipService.filesPath + "/cert.pem")
} else { } else {
@ -248,13 +242,13 @@ class ConfigActivity : AppCompatActivity() {
if (!Utils.requestPermission(this, if (!Utils.requestPermission(this,
android.Manifest.permission.READ_EXTERNAL_STORAGE)) android.Manifest.permission.READ_EXTERNAL_STORAGE))
return false return false
val content = Utils.getFileContents(File(downloadsDir, "ca_certs.crt")) val content = Utils.getFileContents(BaresipService.downloadsPath + "/ca_certs.crt")
if (content == "Failed") { if (content == null) {
Utils.alertView(this, getString(R.string.error), Utils.alertView(this, getString(R.string.error),
getString(R.string.read_ca_certs_error)) getString(R.string.read_ca_certs_error))
return false return false
} }
Utils.putFileContents(File(BaresipService.filesPath + "/ca_certs.crt"), Utils.putFileContents(BaresipService.filesPath + "/ca_certs.crt",
content) content)
Config.remove("sip_cafile") Config.remove("sip_cafile")
Config.add("sip_cafile", BaresipService.filesPath + "/ca_certs.crt") Config.add("sip_cafile", BaresipService.filesPath + "/ca_certs.crt")

View File

@ -1,5 +1,6 @@
package com.tutpro.baresip package com.tutpro.baresip
import java.nio.charset.StandardCharsets
import java.util.ArrayList import java.util.ArrayList
class Contact(var name: String, var uri: String) { class Contact(var name: String, var uri: String) {
@ -12,5 +13,47 @@ class Contact(var name: String, var uri: String) {
return BaresipService.contacts 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
}
} }
} }

View File

@ -119,7 +119,7 @@ class ContactActivity : AppCompatActivity() {
} }
Contact.contacts().sortBy { Contact -> Contact.name } Contact.contacts().sortBy { Contact -> Contact.name }
ContactsActivity.saveContacts(applicationContext.filesDir, "contacts") Contact.save()
i.putExtra("name", newName) i.putExtra("name", newName)
setResult(Activity.RESULT_OK, i) setResult(Activity.RESULT_OK, i)

View File

@ -67,7 +67,7 @@ class ContactListAdapter(private val cxt: Context, private val rows: ArrayList<C
when (which) { when (which) {
DialogInterface.BUTTON_POSITIVE -> { DialogInterface.BUTTON_POSITIVE -> {
Contact.contacts().removeAt(pos) Contact.contacts().removeAt(pos)
ContactsActivity.saveContacts(cxt.applicationContext.filesDir, "contacts") Contact.save()
this.notifyDataSetChanged() this.notifyDataSetChanged()
} }
DialogInterface.BUTTON_NEGATIVE -> { DialogInterface.BUTTON_NEGATIVE -> {

View File

@ -3,15 +3,12 @@ package com.tutpro.baresip
import android.app.Activity import android.app.Activity
import android.content.* import android.content.*
import android.os.Bundle import android.os.Bundle
import android.os.Environment
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.widget.ImageButton import android.widget.ImageButton
import android.widget.ListView import android.widget.ListView
import java.io.File
class ContactsActivity : AppCompatActivity() { class ContactsActivity : AppCompatActivity() {
internal lateinit var clAdapter: ContactListAdapter internal lateinit var clAdapter: ContactListAdapter
@ -63,12 +60,10 @@ class ContactsActivity : AppCompatActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
when (item.itemId) { when (item.itemId) {
R.id.export_contacts -> { R.id.export_contacts -> {
if (saveContacts(dir, "contacts.bs")) if (Contact.export())
Utils.alertView(this, "", Utils.alertView(this, "",
getString(R.string.exported_contacts)) getString(R.string.exported_contacts))
else else
@ -77,11 +72,11 @@ class ContactsActivity : AppCompatActivity() {
} }
R.id.import_contacts -> { R.id.import_contacts -> {
if (restoreContacts(dir, "contacts.bs")) { if (Contact.import()) {
Utils.alertView(this, "", Utils.alertView(this, "",
getString(R.string.imported_contacts)) getString(R.string.imported_contacts))
clAdapter.notifyDataSetChanged() clAdapter.notifyDataSetChanged()
saveContacts(applicationContext.filesDir, "contacts") Contact.save()
} else } else
Utils.alertView(this,getString(R.string.error), Utils.alertView(this,getString(R.string.error),
getString(R.string.import_error)) getString(R.string.import_error))
@ -111,33 +106,6 @@ class ContactsActivity : AppCompatActivity() {
companion object { 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 { fun findContactURI(name: String): String {
for (c in Contact.contacts()) for (c in Contact.contacts())
if (c.name == name) if (c.name == name)

View File

@ -404,8 +404,6 @@ class MainActivity : AppCompatActivity() {
} }
} }
if (intentAction == null)
CallHistory.restore(applicationContext.filesDir.absolutePath)
callsButton.setOnClickListener { callsButton.setOnClickListener {
if (aorSpinner.selectedItemPosition >= 0) { if (aorSpinner.selectedItemPosition >= 0) {
val i = Intent(this@MainActivity, CallsActivity::class.java) val i = Intent(this@MainActivity, CallsActivity::class.java)
@ -891,22 +889,28 @@ class MainActivity : AppCompatActivity() {
} }
baresipService.setAction("ToggleSpeaker") baresipService.setAction("ToggleSpeaker")
startService(baresipService) startService(baresipService)
return true
} }
R.id.config -> { R.id.config -> {
i = Intent(this, ConfigActivity::class.java) i = Intent(this, ConfigActivity::class.java)
startActivityForResult(i, CONFIG_CODE) startActivityForResult(i, CONFIG_CODE)
return true
} }
R.id.accounts -> { R.id.accounts -> {
i = Intent(this, AccountsActivity::class.java) i = Intent(this, AccountsActivity::class.java)
startActivityForResult(i, ACCOUNTS_CODE) 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 -> { R.id.about -> {
i = Intent(this, AboutActivity::class.java) i = Intent(this, AboutActivity::class.java)
startActivityForResult(i, ABOUT_CODE) startActivityForResult(i, ABOUT_CODE)
return true
} }
R.id.restart, R.id.quit -> { R.id.restart, R.id.quit -> {
if (stopState == "initial") { if (stopState == "initial") {
@ -921,10 +925,77 @@ class MainActivity : AppCompatActivity() {
System.exit(0) 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?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {

View File

@ -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() while (it.hasNext()) if (it.next().aor == aor) it.remove()
} }
fun saveMessages() { fun save() {
val file = File(BaresipService.filesPath, "messages") val file = File(BaresipService.filesPath, "messages")
try { try {
val fos = FileOutputStream(file) 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") val file = File(BaresipService.filesPath, "messages")
if (file.exists()) { if (file.exists()) {
try { try {

View File

@ -5,7 +5,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import java.io.File import java.nio.charset.StandardCharsets
class RunOnStartup : BroadcastReceiver() { class RunOnStartup : BroadcastReceiver() {
@ -13,8 +13,8 @@ class RunOnStartup : BroadcastReceiver() {
Log.i("Baresip", "RunOnStartup received intent ${intent.action}") Log.i("Baresip", "RunOnStartup received intent ${intent.action}")
if ((intent.action == Intent.ACTION_BOOT_COMPLETED) or if ((intent.action == Intent.ACTION_BOOT_COMPLETED) or
(intent.action == "com.tutpro.baresip.Restart")) { (intent.action == "com.tutpro.baresip.Restart")) {
val configFile = File(context.filesDir.absolutePath + "/config") val configPath = context.filesDir.absolutePath + "/config"
val config = Utils.getFileContents(configFile) val config = String(Utils.getFileContents(configPath)!!, StandardCharsets.ISO_8859_1)
val asCv = Utils.getNameValue(config,"auto_start") val asCv = Utils.getNameValue(config,"auto_start")
if ((asCv.size > 0) && (asCv[0] == "yes")) { if ((asCv.size > 0) && (asCv[0] == "yes")) {
Log.d("Baresip", "Start baresip upon boot completed or restart") Log.d("Baresip", "Start baresip upon boot completed or restart")

View File

@ -4,8 +4,6 @@ import android.app.Activity
import android.app.ActivityManager import android.app.ActivityManager
import android.content.Context import android.content.Context
import android.support.v7.app.AlertDialog import android.support.v7.app.AlertDialog
import android.os.PowerManager
import android.app.KeyguardManager
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Bundle import android.os.Bundle
@ -13,8 +11,6 @@ import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat import android.support.v4.content.ContextCompat
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.util.Base64
import android.util.Base64.encodeToString
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.Exception import kotlin.Exception
@ -22,57 +18,16 @@ import kotlin.Exception
import java.io.* import java.io.*
import java.security.SecureRandom import java.security.SecureRandom
import java.util.* import java.util.*
import java.nio.ByteBuffer import java.util.zip.*
import javax.crypto.Cipher import javax.crypto.Cipher
import javax.crypto.SecretKeyFactory import javax.crypto.SecretKeyFactory
import javax.crypto.spec.GCMParameterSpec import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.PBEKeySpec import javax.crypto.spec.PBEKeySpec
import javax.crypto.spec.SecretKeySpec
object Utils { 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<String> { fun getNameValue(string: String, name: String): ArrayList<String> {
val lines = string.split("\n") val lines = string.split("\n")
val result = ArrayList<String>() val result = ArrayList<String>()
@ -90,24 +45,6 @@ object Utils {
return result 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) { fun alertView(context: Context, title: String, message: String) {
val builder = AlertDialog.Builder(context) val builder = AlertDialog.Builder(context)
builder.setTitle(title) builder.setTitle(title)
@ -142,7 +79,7 @@ object Utils {
} }
} }
fun aorUser(aor: String): String { private fun aorUser(aor: String): String {
val user = aor.substringBefore("@") val user = aor.substringBefore("@")
return if (user == aor) "" else user 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) 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) 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("]") && return bracketedIp.startsWith("[") && bracketedIp.endsWith("]") &&
checkIpV6(bracketedIp.substring(1, bracketedIp.length - 2)) checkIpV6(bracketedIp.substring(1, bracketedIp.length - 2))
} }
fun checkIp(ip: String): Boolean { private fun checkIp(ip: String): Boolean {
return checkIpV4(ip) || checkIpV6(ip) return checkIpV4(ip) || checkIpV6(ip)
} }
fun checkUriUser(user: String): Boolean { private fun checkUriUser(user: String): Boolean {
for (c in user) for (c in user)
if (!(c.isLetterOrDigit() || c in "-_.!~*'()&=+$,;?/")) return false if (!(c.isLetterOrDigit() || c in "-_.!~*'()&=+$,;?/")) return false
return user.length > 0 return user.length > 0
@ -193,7 +130,7 @@ object Utils {
return true return true
} }
fun checkPort(port: String): Boolean { private fun checkPort(port: String): Boolean {
val number = port.toIntOrNull() val number = port.toIntOrNull()
if (number == null) return false if (number == null) return false
return (number > 0) && (number < 65536) return (number > 0) && (number < 65536)
@ -208,7 +145,7 @@ object Utils {
checkPort(ipPort.substringAfterLast(":")) checkPort(ipPort.substringAfterLast(":"))
} }
fun checkDomainPort(domainPort: String): Boolean { private fun checkDomainPort(domainPort: String): Boolean {
return checkDomain(domainPort.substringBeforeLast(":")) && return checkDomain(domainPort.substringBeforeLast(":")) &&
checkPort(domainPort.substringAfterLast(":")) checkPort(domainPort.substringAfterLast(":"))
} }
@ -218,13 +155,13 @@ object Utils {
checkIpPort(hostPort) || checkDomainPort(hostPort) checkIpPort(hostPort) || checkDomainPort(hostPort)
} }
fun checkParams(params: String): Boolean { private fun checkParams(params: String): Boolean {
for (param in params.split(";")) for (param in params.split(";"))
if (!checkParam(param)) return false if (!checkParam(param)) return false
return true return true
} }
fun checkParam(param: String): Boolean { private fun checkParam(param: String): Boolean {
val nameValue = param.split("=") val nameValue = param.split("=")
if (nameValue.size == 1) if (nameValue.size == 1)
/* Todo: do proper check */ /* Todo: do proper check */
@ -295,23 +232,6 @@ object Utils {
return appProcessInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND 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 { fun dtmfWatcher(callp: String): TextWatcher {
return object : TextWatcher { return object : TextWatcher {
override fun beforeTextChanged(sequence: CharSequence, start: Int, count: Int, after: Int) {} override fun beforeTextChanged(sequence: CharSequence, start: Int, count: Int, after: Int) {}
@ -341,81 +261,183 @@ object Utils {
return true return true
} }
fun encrypt(plainText: String, password: String): String { fun copyAssetToFile(context: Context, asset: String, path: String) {
val sr = SecureRandom.getInstance("SHA1PRNG") try {
val salt = ByteArray(16) val `is` = context.assets.open(asset)
sr.nextBytes(salt) val os = FileOutputStream(path)
val iterationCount = Random().nextInt(1024) + 512 val buffer = ByteArray(512)
val spec = PBEKeySpec(password.toCharArray(), salt, iterationCount, 256) var byteRead: Int = `is`.read(buffer)
val secretKey = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").generateSecret(spec) while (byteRead != -1) {
val iv = ByteArray(12) os.write(buffer, 0, byteRead)
SecureRandom().nextBytes(iv) byteRead = `is`.read(buffer)
val cipher = Cipher.getInstance("AES/GCM/NoPadding") }
val parameterSpec = GCMParameterSpec(128, iv) os.close()
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec) `is`.close()
val cipherText = cipher.doFinal(plainText.toByteArray()) } catch (e: IOException) {
val byteBuffer = ByteBuffer.allocate(Int.SIZE_BYTES + salt.size + Int.SIZE_BYTES + Log.e("Baresip", "Failed to copy asset '$asset' to file: $e")
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 decrypt(cipherMessage: String, password: String): String { fun deleteFile(filePath: String) {
val byteBuffer = ByteBuffer.wrap(Base64.decode(cipherMessage, Base64.DEFAULT)) val file = File(filePath)
val saltLength = byteBuffer.getInt() if (file.exists()) file.delete()
if (saltLength != 16) { }
Log.w("Baresip", "invalid salt length $saltLength")
return "" fun getFileContents(filePath: String): ByteArray? {
}
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))
try { 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) { } catch (e: Exception) {
Log.w("Baresip", "Decryption failed ${e.printStackTrace()}") Log.e("Baresip", "Failed to read file '$filePath': ${e.printStackTrace()}")
return "" 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<String>, 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) { fun dumpIntent(intent: Intent) {
val bundle: Bundle = intent.extras ?: return val bundle: Bundle = intent.extras ?: return
val keys = bundle.keySet() val keys = bundle.keySet()
val it = keys.iterator() val it = keys.iterator()
Log.d("Baresip", "Dumping intent start") Log.d("Baresip", "Dumping intent start")
while (it.hasNext()) { while (it.hasNext()) {
val key = it.next() val key = it.next()
Log.d("Baresip","[" + key + "=" + bundle.get(key)+"]"); Log.d("Baresip","[" + key + "=" + bundle.get(key)+"]");
} }
Log.d("Baresip", "Dumping intent finish") Log.d("Baresip", "Dumping intent finish")
} }
} }

View File

@ -6,6 +6,12 @@
<item android:id="@+id/accounts" <item android:id="@+id/accounts"
android:title="@string/accounts" /> android:title="@string/accounts" />
<item android:id="@+id/export_all"
android:title="@string/export_all" />
<item android:id="@+id/import_all"
android:title="@string/import_all" />
<item android:id="@+id/about" <item android:id="@+id/about"
android:title="@string/about" /> android:title="@string/about" />

View File

@ -238,26 +238,20 @@
<string name="contact_action_question">Haluatko soittaa tai lähettää <string name="contact_action_question">Haluatko soittaa tai lähettää
viestin kontaktille \'%1$s\'?</string> viestin kontaktille \'%1$s\'?</string>
<string name="send_message">Lähetä viesti</string> <string name="send_message">Lähetä viesti</string>
<string name="contact_delete_question">Haluatko poistaa kontaktin <string name="contact_delete_question">Haluatko poistaa kontaktin \'%1$s\'?</string>
\'%1$s\'? <string name="contacts_exceeded">Kontaktiesi enimmäismäärä %1$d on ylittynyt.</string>
</string>
<string name="contacts_exceeded">Kontaktiesi enimmäismäärä %1$d on
ylittynyt.
</string>
<string name="export_contacts">Tallenna</string> <string name="export_contacts">Tallenna</string>
<string name="exported_contacts">Kontaktit tallennettiin <string name="exported_contacts">Kontaktit tallennettiin Download-kansion tiedostoon
Download-kansion tiedostoon \'contacts.bs\'. \'contacts.bs\'.
</string> </string>
<string name="export_error">"Kontaktien tallennus Download kansioon epäonnistui. <string name="export_error">"Kontaktien tallennus Download kansioon epäonnistui.
Tarkista Sovellukset → baresip → Käyttöluvat → Tallennustila. Tarkista Sovellukset → baresip → Käyttöluvat → Tallennustila.
</string> </string>
<string name="import_contacts">Palauta</string> <string name="import_contacts">Palauta</string>
<string name="imported_contacts">Kontaktit palautettiin Download-kansiosta. <string name="imported_contacts">Kontaktit palautettiin.</string>
</string> <string name="import_error">Kontantien palauttaminen Download-kansiosta epäonnistui.
<string name="import_error">Kontantien palauttaminen
Download-kansiosta epäonnistui.
Tarkista Sovellukset → baresip → Käyttöluvat → Tallennustila ja että tiedosto \'contacts.bs\' on Tarkista Sovellukset → baresip → Käyttöluvat → Tallennustila ja että tiedosto \'contacts.bs\' on
kansiossa. Download-kansiossa.
</string> </string>
<!-- Generic --> <!-- Generic -->
<string name="alert">Varoitus</string> <string name="alert">Varoitus</string>
@ -311,14 +305,12 @@
TLS sertifikaattitiedostosta. Niiden oletusarvot on palautettu. Käynnistä TLS sertifikaattitiedostosta. Niiden oletusarvot on palautettu. Käynnistä
baresip uudelleen. baresip uudelleen.
</string> </string>
<string name="registering_failed">Tilin \'%1$s\' rekisteröinti <string name="registering_failed">Tilin \'%1$s\' rekisteröinti epäonnistui.</string>
epäonnistui.</string>
<string name="no_microphone_permission">Et ole sallinut mikrofonin <string name="no_microphone_permission">Et ole sallinut mikrofonin
käyttöä. käyttöä.
</string> </string>
<string name="verify">Todenna</string> <string name="verify">Todenna</string>
<string name="verify_sas">Todennatko SAS:n &lt;%1$s&gt; <string name="verify_sas">Todennatko SAS:n &lt;%1$s&gt; &lt;%2$s&gt;?
&lt;%2$s&gt;?
</string> </string>
<string name="transfer_query">Hyväksytkö puhelun siirron kohteeseen <string name="transfer_query">Hyväksytkö puhelun siirron kohteeseen
\'%1$s\'? \'%1$s\'?
@ -334,8 +326,23 @@
<string name="call_is_secure">Tämä pulelu on turvallinen ja kohde on <string name="call_is_secure">Tämä pulelu on turvallinen ja kohde on
todennettu! Haluatko poistaa todennuksen?</string> todennettu! Haluatko poistaa todennuksen?</string>
<string name="unverify">Poista todennus</string> <string name="unverify">Poista todennus</string>
qqqq <string name="tls_ca_file">TLS CA-tiedosto</string> <string name="tls_ca_file">TLS CA-tiedosto</string>
<string name="tls_ca_file_help">Jos merkitty, tiedosto \'ca_certs.crt\', joka sisältää todennusauktoriteettien TLS-sertifikaatit, on ladattu tai ladataan Download-kansiosta.</string> <string name="tls_ca_file_help">Jos merkitty, tiedosto \'ca_certs.crt\', joka sisältää
todennusauktoriteettien TLS-sertifikaatit, on ladattu tai ladataan Download-kansiosta.
</string>
<string name="tls_certificate_file">TLS sertifikaattitiedosto</string> <string name="tls_certificate_file">TLS sertifikaattitiedosto</string>
<string name="tls_certificate_file_help">"Jos merkitty, tiedosto \'cert.pem\', joka sisältää tämän baresip-sovelluksen TLS-sertifikaatin ja yksityisen avaimen, on ladattu tai ladataan Download-kansiosta."</string> <string name="tls_certificate_file_help">"Jos merkitty, tiedosto \'cert.pem\', joka sisältää
tämän baresip-sovelluksen TLS-sertifikaatin ja yksityisen avaimen, on ladattu tai ladataan
Download-kansiosta."
</string>
<string name="exported_all">Sovelluksen kaikki data talletettiin Download-kansion tiedostoon
\'baresip.bs\'.
</string>
<string name="imported_all">Sovelluksen kaikki data palautettiin. Sinun pitää käynnistää
baresip uudelleen.
</string>
<string name="import_all_failed">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.
</string>
</resources> </resources>

View File

@ -103,10 +103,9 @@
Check Apps → baresip → Permissions → Storage." Check Apps → baresip → Permissions → Storage."
</string> </string>
<string name="import_accounts">Import</string> <string name="import_accounts">Import</string>
<string name="imported_accounts">Imported accounts from Download folder. You need to restart <string name="imported_accounts">Accounts imported. You need to restart baresip.</string>
baresip.</string>
<string name="import_accounts_error">Failed to import accounts from Download folder file. <string name="import_accounts_error">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." the folder and, if so, you gave correct Decrypt Password."
</string> </string>
<string name="delete_account">Do you want to delete account \'%1$s\'?</string> <string name="delete_account">Do you want to delete account \'%1$s\'?</string>
@ -195,7 +194,8 @@ Check Apps → baresip → Permissions → Storage and that file \'accounts.bs\'
Valid values are 6000-510000. Factory default is 28000.</string> Valid values are 6000-510000. Factory default is 28000.</string>
<string name="_28000" translatable="false">28000</string> <string name="_28000" translatable="false">28000</string>
<string name="opus_packet_loss">Expected Opus packet-loss</string> <string name="opus_packet_loss">Expected Opus packet-loss</string>
<string name="opus_packet_loss_help">Expected Opus audio stream packet loss percentage, from 0-100. By default 0, turning off Opus Forward Error Correction (FEC).</string> <string name="opus_packet_loss_help">Expected Opus audio stream packet loss percentage,
from 0100. By default 0, turning off Opus Forward Error Correction (FEC).</string>
<string name="_0" translatable="false">0</string> <string name="_0" translatable="false">0</string>
<string name="invalid_opus_bitrate">Invalid Opus bitrate</string> <string name="invalid_opus_bitrate">Invalid Opus bitrate</string>
<string name="invalid_opus_packet_loss">Invalid Opus Packet Loss Percentage</string> <string name="invalid_opus_packet_loss">Invalid Opus Packet Loss Percentage</string>
@ -230,7 +230,7 @@ Check Apps → baresip → Permissions → Storage and that file \'accounts.bs\'
<string name="exported_contacts">Exported contacts to Download folder file \'contacts.bs\'.</string> <string name="exported_contacts">Exported contacts to Download folder file \'contacts.bs\'.</string>
<string name="export_error">"Failed to export contacts to Download folder. <string name="export_error">"Failed to export contacts to Download folder.
Check Apps → baresip → Permissions → Storage."</string> Check Apps → baresip → Permissions → Storage."</string>
<string name="imported_contacts">Imported contacts from Download folder.</string> <string name="imported_contacts">Contacs imported.</string>
<string name="import_error">Failed to import contacts from Download folder. Check Apps → <string name="import_error">Failed to import contacts from Download folder. Check Apps →
baresip → Permissions → Storage and that file \'contacts.bs\' exists in the folder.</string> baresip → Permissions → Storage and that file \'contacts.bs\' exists in the folder.</string>
@ -256,6 +256,8 @@ Check Apps → baresip → Permissions → Storage and that file \'accounts.bs\'
<string name="dots" translatable="false"></string> <string name="dots" translatable="false"></string>
<!-- Main Activity --> <!-- Main Activity -->
<string name="export_all">Export</string>
<string name="import_all">Import</string>
<string name="about">About</string> <string name="about">About</string>
<string name="restart">Restart</string> <string name="restart">Restart</string>
<string name="quit">Quit</string> <string name="quit">Quit</string>
@ -285,7 +287,8 @@ Check Apps → baresip → Permissions → Storage and that file \'accounts.bs\'
<string name="dialpad">Dialpad</string> <string name="dialpad">Dialpad</string>
<string name="call_already_active">You already have an active call.</string> <string name="call_already_active">You already have an active call.</string>
<string name="start_failed">Baresip failed to start. This may be due to invalid Listen Address <string name="start_failed">Baresip failed to start. This may be due to invalid Listen Address
or TLS file. They have been reset. Restart baresip.</string> or TLS file. They have been reset. Restart baresip.
</string>
<string name="registering_failed">Registering of \`%1$s\` failed.</string> <string name="registering_failed">Registering of \`%1$s\` failed.</string>
<string name="no_microphone_permission">You have not granted microphone permission.</string> <string name="no_microphone_permission">You have not granted microphone permission.</string>
<string name="verify">Verify</string> <string name="verify">Verify</string>
@ -297,7 +300,16 @@ Check Apps → baresip → Permissions → Storage and that file \'accounts.bs\'
<string name="call_not_secure">This call is NOT secure!</string> <string name="call_not_secure">This call is NOT secure!</string>
<string name="peer_not_verified">This call is SECURE, but peer is NOT verified!</string> <string name="peer_not_verified">This call is SECURE, but peer is NOT verified!</string>
<string name="call_is_secure">This call is SECURE and peer is VERIFIED! <string name="call_is_secure">This call is SECURE and peer is VERIFIED!
Do you want to unverify the peer?</string> Do you want to unverify the peer?
</string>
<string name="unverify">Unverify</string> <string name="unverify">Unverify</string>
<string name="exported_all">Exported all application data to Download folder file \'baresip.bs\'.</string>
<string name="export_all_failed">Failed to export application data to Download folder file
\'baresip.bs\'. Check Apps → baresip → Permissions → Storage</string>
<string name="imported_all">All application data imported. You need to restart baresip.</string>
<string name="import_all_failed">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.
</string>
</resources> </resources>