Work towards API 30 compatibility
This commit is contained in:
@ -6,7 +6,7 @@ android {
|
||||
defaultConfig {
|
||||
applicationId = 'com.tutpro.baresip'
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
targetSdkVersion 30
|
||||
versionCode = 163
|
||||
versionName = '29.2.1'
|
||||
externalNativeBuild {
|
||||
|
||||
@ -11,7 +11,8 @@
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
tools:ignore="ScopedStorage" android:maxSdkVersion="28" />
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
|
||||
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user