diff --git a/app/build.gradle b/app/build.gradle
index 4daab8d8..f93290f1 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -6,7 +6,7 @@ android {
defaultConfig {
applicationId = 'com.tutpro.baresip'
minSdkVersion 21
- targetSdkVersion 29
+ targetSdkVersion 30
versionCode = 163
versionName = '29.2.1'
externalNativeBuild {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 42848c7d..d03cf471 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -11,7 +11,8 @@
-
+
diff --git a/app/src/main/cpp/baresip.c b/app/src/main/cpp/baresip.c
index 4d0c061e..01578e35 100644
--- a/app/src/main/cpp/baresip.c
+++ b/app/src/main/cpp/baresip.c
@@ -1103,18 +1103,17 @@ Java_com_tutpro_baresip_Api_account_1mediaaf(JNIEnv *env, jobject thiz, jstring
return account_mediaaf(acc);
}
-JNIEXPORT void JNICALL
+JNIEXPORT jint JNICALL
Java_com_tutpro_baresip_Api_account_1set_1mediaaf(JNIEnv *env, jobject thiz, jstring jAcc, jint jAf) {
const char *native_acc = (*env)->GetStringUTFChars(env, jAcc, 0);
struct account *acc = (struct account *)strtoul(native_acc, NULL, 10);
(*env)->ReleaseStringUTFChars(env, jAcc, native_acc);
LOGD("setting account mediaaf to '%d'\n", jAf);
- account_set_mediaaf(acc, jAf);
+ return account_set_mediaaf(acc, jAf);
}
JNIEXPORT jstring JNICALL
-Java_com_tutpro_baresip_Api_account_1extra(JNIEnv *env, jobject thiz, jstring javaAcc)
-{
+Java_com_tutpro_baresip_Api_account_1extra(JNIEnv *env, jobject thiz, jstring javaAcc) {
const char *native_acc = (*env)->GetStringUTFChars(env, javaAcc, 0);
struct account *acc = (struct account *) strtoul(native_acc, NULL, 10);
(*env)->ReleaseStringUTFChars(env, javaAcc, native_acc);
diff --git a/app/src/main/kotlin/com/tutpro/baresip/Api.kt b/app/src/main/kotlin/com/tutpro/baresip/Api.kt
index cc31edc0..f6d5247f 100644
--- a/app/src/main/kotlin/com/tutpro/baresip/Api.kt
+++ b/app/src/main/kotlin/com/tutpro/baresip/Api.kt
@@ -45,7 +45,7 @@ object Api {
external fun account_dtmfmode(acc: String): Int
external fun account_set_dtmfmode(acc: String, mode: Int): Int
external fun account_mediaaf(acc: String): Int
- external fun account_set_mediaaf(acc: String, af: Int)
+ external fun account_set_mediaaf(acc: String, af: Int): Int
external fun account_extra(acc: String): String
external fun account_debug(acc: String)
diff --git a/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt b/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt
index 481a5ec9..856fa513 100644
--- a/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt
+++ b/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt
@@ -415,7 +415,7 @@ class BaresipService: Service() {
Log.d(TAG, "uaAdd ${ua.account.aor} at BaresipService")
uas.add(ua)
if (ua.account.preferIPv6Media)
- Api.ua_set_media_af(ua.uap, Api.AF_INET6)
+ Api.account_set_mediaaf(ua.account.accp, Api.AF_INET6)
if (Api.ua_isregistered(uap)) {
Log.d(TAG, "Ua ${ua.account.aor} is registered")
status.add(R.drawable.dot_green)
diff --git a/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt
index e17b770d..690b4521 100644
--- a/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt
+++ b/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt
@@ -14,6 +14,8 @@ import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.CountDownTimer
+import android.provider.DocumentsContract
+import android.provider.MediaStore
import android.text.InputType
import android.text.TextWatcher
import android.text.method.HideReturnsTransformationMethod
@@ -21,6 +23,7 @@ import android.text.method.PasswordTransformationMethod
import android.view.*
import android.view.inputmethod.InputMethodManager
import android.widget.*
+import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
@@ -28,6 +31,8 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.tutpro.baresip.databinding.ActivityMainBinding
import java.io.File
+import java.io.FileInputStream
+import java.io.FileOutputStream
import java.net.URLDecoder
class MainActivity : AppCompatActivity() {
@@ -62,6 +67,8 @@ class MainActivity : AppCompatActivity() {
private lateinit var stopState: String
private var speakerIcon: MenuItem? = null
private lateinit var swipeRefresh: SwipeRefreshLayout
+ private var downloadsInputStream: FileInputStream? = null
+ private var downloadsOutputStream: FileOutputStream? = null
private lateinit var baresipService: Intent
@@ -1074,15 +1081,23 @@ class MainActivity : AppCompatActivity() {
}
R.id.backup -> {
- if (Utils.requestPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE,
- BACKUP_PERMISSION_REQUEST_CODE))
- askPassword(getString(R.string.encrypt_password))
+ if (Build.VERSION.SDK_INT >= 29) {
+ pickupFileFromDownloads(BACKUP_CODE)
+ } else {
+ if (Utils.requestPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ BACKUP_PERMISSION_REQUEST_CODE)) {
+ val path = BaresipService.downloadsPath + "/baresip.bs"
+ downloadsOutputStream = FileOutputStream(File(path))
+ askPassword(getString(R.string.encrypt_password))
+ }
+ }
}
R.id.restore -> {
- if (Utils.requestPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE,
+ if (Build.VERSION.SDK_INT >= 29 ||
+ Utils.requestPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE,
RESTORE_PERMISSION_REQUEST_CODE))
- askPassword(getString(R.string.decrypt_password))
+ startRestore()
}
R.id.about -> {
@@ -1163,6 +1178,27 @@ class MainActivity : AppCompatActivity() {
updateIcons(Account.ofAor(activityAor)!!)
}
+ BACKUP_CODE -> {
+ if (resultCode == Activity.RESULT_OK)
+ data?.data?.also {
+ Log.d(TAG, "******** $it")
+ downloadsOutputStream = applicationContext.contentResolver.openOutputStream(it)
+ as FileOutputStream
+ if (downloadsOutputStream != null)
+ askPassword(getString(R.string.encrypt_password))
+ }
+ }
+
+ RESTORE_CODE -> {
+ if (resultCode == Activity.RESULT_OK)
+ data?.data?.also {
+ downloadsInputStream = applicationContext.contentResolver.openInputStream(it)
+ as FileInputStream
+ if (downloadsInputStream != null)
+ askPassword(getString(R.string.decrypt_password))
+ }
+ }
+
ABOUT_CODE -> {
}
@@ -1176,7 +1212,7 @@ class MainActivity : AppCompatActivity() {
when (requestCode) {
RECORD_PERMISSION_REQUEST_CODE -> {
- if ((grantResults.size > 0) && (grantResults[0] != PackageManager.PERMISSION_GRANTED))
+ if ((grantResults.isNotEmpty()) && (grantResults[0] != PackageManager.PERMISSION_GRANTED))
Utils.alertView(this, getString(R.string.notice),
getString(R.string.no_calls), ::startBaresip)
else
@@ -1184,16 +1220,49 @@ class MainActivity : AppCompatActivity() {
}
BACKUP_PERMISSION_REQUEST_CODE ->
- if ((grantResults.size > 0) && (grantResults[0] == PackageManager.PERMISSION_GRANTED))
+ if ((grantResults.isNotEmpty()) && (grantResults[0] == PackageManager.PERMISSION_GRANTED))
askPassword(getString(R.string.encrypt_password))
RESTORE_PERMISSION_REQUEST_CODE ->
- if ((grantResults.size > 0) && (grantResults[0] == PackageManager.PERMISSION_GRANTED))
- askPassword(getString(R.string.decrypt_password))
+ if ((grantResults.isNotEmpty()) && (grantResults[0] == PackageManager.PERMISSION_GRANTED))
+ startRestore()
}
}
+ private fun startRestore() {
+ if (Build.VERSION.SDK_INT >= 29) {
+ pickupFileFromDownloads(RESTORE_CODE)
+ } else {
+ val path = BaresipService.downloadsPath + "/baresip.bs"
+ downloadsInputStream = FileInputStream(File(path))
+ askPassword(getString(R.string.decrypt_password))
+ }
+ }
+
+ @RequiresApi(29)
+ private fun pickupFileFromDownloads(activityCode: Int) {
+ val intent = when (activityCode) {
+ BACKUP_CODE -> {
+ Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
+ addCategory(Intent.CATEGORY_OPENABLE)
+ type = "application/octet-stream"
+ putExtra(Intent.EXTRA_TITLE, "baresip.bs")
+ putExtra(DocumentsContract.EXTRA_INITIAL_URI, MediaStore.Downloads.EXTERNAL_CONTENT_URI)
+ }
+ }
+ RESTORE_CODE -> {
+ Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
+ addCategory(Intent.CATEGORY_OPENABLE)
+ type = "application/octet-stream"
+ putExtra(DocumentsContract.EXTRA_INITIAL_URI, MediaStore.Downloads.EXTERNAL_CONTENT_URI)
+ }
+ }
+ else -> null
+ }
+ startActivityForResult(intent, activityCode)
+ }
+
private fun quitRestart(reStart: Boolean) {
if (stopState == "initial") {
Log.d(TAG, "quitRestart Restart = $reStart")
@@ -1378,54 +1447,57 @@ class MainActivity : AppCompatActivity() {
File(BaresipService.filesPath).walk().forEach {
if (it.name.endsWith(".png")) files.add(it.name)
}
- val bsFile = getString(R.string.app_name) + ".bs"
- val backupFilePath = BaresipService.downloadsPath + "/$bsFile"
- val zipFile = getString(R.string.app_name) + ".zip"
+ val zipFile = getString(R.string.app_name_plus) + ".zip"
val zipFilePath = BaresipService.filesPath + "/$zipFile"
if (!Utils.zip(files, zipFile)) {
Log.w(TAG, "Failed to write zip file '$zipFile'")
Utils.alertView(this, getString(R.string.error),
- String.format(getString(R.string.backup_failed), bsFile))
+ String.format(getString(R.string.backup_failed),
+ downloadsOutputFile))
return
}
val content = Utils.getFileContents(zipFilePath)
if (content == null) {
Log.w(TAG, "Failed to read zip file '$zipFile'")
Utils.alertView(this, getString(R.string.error),
- String.format(getString(R.string.backup_failed), bsFile))
+ String.format(getString(R.string.backup_failed),
+ downloadsOutputFile))
return
}
- if (!Utils.encryptToFile(backupFilePath, content, password)) {
+ if (!Utils.encryptToStream(downloadsOutputStream, content, password)) {
Utils.alertView(this, getString(R.string.error),
- String.format(getString(R.string.backup_failed), bsFile))
+ String.format(getString(R.string.backup_failed),
+ downloadsOutputFile))
return
}
Utils.alertView(this, getString(R.string.info),
- String.format(getString(R.string.backed_up), bsFile))
+ String.format(getString(R.string.backed_up),
+ downloadsOutputFile))
Utils.deleteFile(File(zipFilePath))
}
private fun restore(password: String) {
- val bsFile = getString(R.string.app_name) + ".bs"
- val backupFilePath = BaresipService.downloadsPath + "/$bsFile"
- val zipFile = getString(R.string.app_name) + ".zip"
+ val zipFile = getString(R.string.app_name_plus) + ".zip"
val zipFilePath = BaresipService.filesPath + "/$zipFile"
- val zipData = Utils.decryptFromFile(backupFilePath, password)
+ val zipData = Utils.decryptFromStream(downloadsInputStream, password)
if (zipData == null) {
Utils.alertView(this, getString(R.string.error),
- String.format(getString(R.string.restore_failed), bsFile))
+ String.format(getString(R.string.restore_failed),
+ downloadsInputFile))
return
}
if (!Utils.putFileContents(zipFilePath, zipData)) {
Log.w(TAG, "Failed to write zip file '$zipFile'")
Utils.alertView(this, getString(R.string.error),
- String.format(getString(R.string.restore_failed), bsFile))
+ String.format(getString(R.string.restore_failed),
+ downloadsInputFile))
return
}
if (!Utils.unZip(zipFilePath)) {
Log.w(TAG, "Failed to unzip file '$zipFile'")
Utils.alertView(this, getString(R.string.error),
- String.format(getString(R.string.restore_failed), bsFile))
+ String.format(getString(R.string.restore_failed),
+ downloadsInputFile))
return
}
Utils.deleteFile(File(zipFilePath))
@@ -1797,6 +1869,8 @@ class MainActivity : AppCompatActivity() {
const val CONTACT_CODE = 7
const val CHATS_CODE = 8
const val CHAT_CODE = 9
+ const val BACKUP_CODE = 10
+ const val RESTORE_CODE = 11
const val BACKUP_PERMISSION_REQUEST_CODE = 1
const val RESTORE_PERMISSION_REQUEST_CODE = 2
diff --git a/app/src/main/kotlin/com/tutpro/baresip/Utils.kt b/app/src/main/kotlin/com/tutpro/baresip/Utils.kt
index f9235e17..11307276 100644
--- a/app/src/main/kotlin/com/tutpro/baresip/Utils.kt
+++ b/app/src/main/kotlin/com/tutpro/baresip/Utils.kt
@@ -11,7 +11,9 @@ import android.graphics.Bitmap.createScaledBitmap
import android.graphics.Color
import android.net.LinkAddress
import android.net.LinkProperties
+import android.net.Uri
import android.os.Bundle
+import android.provider.OpenableColumns
import android.text.Editable
import android.text.TextWatcher
import android.view.View
@@ -418,14 +420,14 @@ object Utils {
}
fun getFileContents(filePath: String): ByteArray? {
- try {
- return File(filePath).readBytes()
+ return try {
+ File(filePath).readBytes()
} catch (e: FileNotFoundException) {
Log.e("Baresip", "File '$filePath' not found: ${e.printStackTrace()}")
- return null
+ null
} catch (e: Exception) {
Log.e("Baresip", "Failed to read file '$filePath': ${e.printStackTrace()}")
- return null
+ null
}
}
@@ -440,6 +442,18 @@ object Utils {
return true
}
+ fun fileNameOfUri(ctx: Context, uri: Uri): String {
+ val cursor = ctx.contentResolver.query(uri, null, null, null, null)
+ var name = ""
+ if (cursor != null) {
+ val index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
+ cursor.moveToFirst()
+ name = cursor.getString(index)
+ cursor.close()
+ }
+ return name
+ }
+
fun saveBitmap(bitmap: Bitmap, file: File): Boolean {
if (file.exists()) file.delete()
try {
@@ -504,29 +518,28 @@ object Utils {
return plainData
}
-
- fun encryptToFile(filePath: String, content: ByteArray, password: String): Boolean {
+ fun encryptToStream(stream: FileOutputStream?, content: ByteArray, password: String): Boolean {
val obj = encrypt(content, password.toCharArray())
try {
- ObjectOutputStream(FileOutputStream(File(filePath))).use {
+ ObjectOutputStream(stream).use {
it.writeObject(obj)
}
} catch (e: Exception) {
- Log.e("Baresip", "Write failed: ${e.printStackTrace()}")
+ Log.w("Baresip", "encryptToStream failed: $e")
return false
}
return true
}
- fun decryptFromFile(filePath: String, password: String): ByteArray? {
+ fun decryptFromStream(stream: FileInputStream?, password: String): ByteArray? {
var plainData: ByteArray? = null
try {
- ObjectInputStream(FileInputStream(File(filePath))).use {
+ ObjectInputStream(stream).use {
val obj = it.readObject() as Crypto
plainData = decrypt(obj, password.toCharArray())
}
} catch (e: Exception) {
- Log.e("Baresip", "Decrypt failed from file '$filePath'")
+ Log.w("Baresip", "decryptFromStream failed: $e")
}
return plainData
}