- 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:
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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<UserAgent>()
|
||||
var status = ArrayList<Int>()
|
||||
var calls = ArrayList<Call>()
|
||||
|
||||
@ -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<CallHistory>
|
||||
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() {
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<InetAddress>) {
|
||||
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()
|
||||
}
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
|
||||
@ -67,7 +67,7 @@ class ContactListAdapter(private val cxt: Context, private val rows: ArrayList<C
|
||||
when (which) {
|
||||
DialogInterface.BUTTON_POSITIVE -> {
|
||||
Contact.contacts().removeAt(pos)
|
||||
ContactsActivity.saveContacts(cxt.applicationContext.filesDir, "contacts")
|
||||
Contact.save()
|
||||
this.notifyDataSetChanged()
|
||||
}
|
||||
DialogInterface.BUTTON_NEGATIVE -> {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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?) {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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<String> {
|
||||
val lines = string.split("\n")
|
||||
val result = ArrayList<String>()
|
||||
@ -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<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) {
|
||||
|
||||
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")
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -6,6 +6,12 @@
|
||||
<item android:id="@+id/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"
|
||||
android:title="@string/about" />
|
||||
|
||||
|
||||
@ -238,26 +238,20 @@
|
||||
<string name="contact_action_question">Haluatko soittaa tai lähettää
|
||||
viestin kontaktille \'%1$s\'?</string>
|
||||
<string name="send_message">Lähetä viesti</string>
|
||||
<string name="contact_delete_question">Haluatko poistaa kontaktin
|
||||
\'%1$s\'?
|
||||
</string>
|
||||
<string name="contacts_exceeded">Kontaktiesi enimmäismäärä %1$d on
|
||||
ylittynyt.
|
||||
</string>
|
||||
<string name="contact_delete_question">Haluatko poistaa kontaktin \'%1$s\'?</string>
|
||||
<string name="contacts_exceeded">Kontaktiesi enimmäismäärä %1$d on ylittynyt.</string>
|
||||
<string name="export_contacts">Tallenna</string>
|
||||
<string name="exported_contacts">Kontaktit tallennettiin
|
||||
Download-kansion tiedostoon \'contacts.bs\'.
|
||||
<string name="exported_contacts">Kontaktit tallennettiin Download-kansion tiedostoon
|
||||
\'contacts.bs\'.
|
||||
</string>
|
||||
<string name="export_error">"Kontaktien tallennus Download kansioon epäonnistui.
|
||||
Tarkista Sovellukset → baresip → Käyttöluvat → Tallennustila.
|
||||
</string>
|
||||
<string name="import_contacts">Palauta</string>
|
||||
<string name="imported_contacts">Kontaktit palautettiin Download-kansiosta.
|
||||
</string>
|
||||
<string name="import_error">Kontantien palauttaminen
|
||||
Download-kansiosta epäonnistui.
|
||||
<string name="imported_contacts">Kontaktit palautettiin.</string>
|
||||
<string name="import_error">Kontantien palauttaminen Download-kansiosta epäonnistui.
|
||||
Tarkista Sovellukset → baresip → Käyttöluvat → Tallennustila ja että tiedosto \'contacts.bs\' on
|
||||
kansiossa.
|
||||
Download-kansiossa.
|
||||
</string>
|
||||
<!-- Generic -->
|
||||
<string name="alert">Varoitus</string>
|
||||
@ -311,14 +305,12 @@
|
||||
TLS sertifikaattitiedostosta. Niiden oletusarvot on palautettu. Käynnistä
|
||||
baresip uudelleen.
|
||||
</string>
|
||||
<string name="registering_failed">Tilin \'%1$s\' rekisteröinti
|
||||
epäonnistui.</string>
|
||||
<string name="registering_failed">Tilin \'%1$s\' rekisteröinti epäonnistui.</string>
|
||||
<string name="no_microphone_permission">Et ole sallinut mikrofonin
|
||||
käyttöä.
|
||||
</string>
|
||||
<string name="verify">Todenna</string>
|
||||
<string name="verify_sas">Todennatko SAS:n <%1$s>
|
||||
<%2$s>?
|
||||
<string name="verify_sas">Todennatko SAS:n <%1$s> <%2$s>?
|
||||
</string>
|
||||
<string name="transfer_query">Hyväksytkö puhelun siirron kohteeseen
|
||||
\'%1$s\'?
|
||||
@ -334,8 +326,23 @@
|
||||
<string name="call_is_secure">Tämä pulelu on turvallinen ja kohde on
|
||||
todennettu! Haluatko poistaa todennuksen?</string>
|
||||
<string name="unverify">Poista todennus</string>
|
||||
qqqq <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">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_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>
|
||||
|
||||
@ -103,10 +103,9 @@
|
||||
Check Apps → baresip → Permissions → Storage."
|
||||
</string>
|
||||
<string name="import_accounts">Import</string>
|
||||
<string name="imported_accounts">Imported accounts from Download folder. You need to restart
|
||||
baresip.</string>
|
||||
<string name="imported_accounts">Accounts imported. You need to restart baresip.</string>
|
||||
<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."
|
||||
</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>
|
||||
<string name="_28000" translatable="false">28000</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 0–100. By default 0, turning off Opus Forward Error Correction (FEC).</string>
|
||||
<string name="_0" translatable="false">0</string>
|
||||
<string name="invalid_opus_bitrate">Invalid Opus bitrate</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="export_error">"Failed to export contacts to Download folder.
|
||||
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 →
|
||||
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>
|
||||
|
||||
<!-- Main Activity -->
|
||||
<string name="export_all">Export</string>
|
||||
<string name="import_all">Import</string>
|
||||
<string name="about">About</string>
|
||||
<string name="restart">Restart</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="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
|
||||
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="no_microphone_permission">You have not granted microphone permission.</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="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!
|
||||
Do you want to unverify the peer?</string>
|
||||
Do you want to unverify the peer?
|
||||
</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>
|
||||
|
||||
Reference in New Issue
Block a user