Work towards API 30 compatibility

This commit is contained in:
Juha Heinanen
2021-04-28 09:04:47 +03:00
parent f879ba78b6
commit 42cccb0416
7 changed files with 130 additions and 43 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ android {
defaultConfig { defaultConfig {
applicationId = 'com.tutpro.baresip' applicationId = 'com.tutpro.baresip'
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 29 targetSdkVersion 30
versionCode = 163 versionCode = 163
versionName = '29.2.1' versionName = '29.2.1'
externalNativeBuild { externalNativeBuild {
+2 -1
View File
@@ -11,7 +11,8 @@
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" /> <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <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.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" /> <uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" /> <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
+3 -4
View File
@@ -1103,18 +1103,17 @@ Java_com_tutpro_baresip_Api_account_1mediaaf(JNIEnv *env, jobject thiz, jstring
return account_mediaaf(acc); 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) { 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); const char *native_acc = (*env)->GetStringUTFChars(env, jAcc, 0);
struct account *acc = (struct account *)strtoul(native_acc, NULL, 10); struct account *acc = (struct account *)strtoul(native_acc, NULL, 10);
(*env)->ReleaseStringUTFChars(env, jAcc, native_acc); (*env)->ReleaseStringUTFChars(env, jAcc, native_acc);
LOGD("setting account mediaaf to '%d'\n", jAf); LOGD("setting account mediaaf to '%d'\n", jAf);
account_set_mediaaf(acc, jAf); return account_set_mediaaf(acc, jAf);
} }
JNIEXPORT jstring JNICALL 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); const char *native_acc = (*env)->GetStringUTFChars(env, javaAcc, 0);
struct account *acc = (struct account *) strtoul(native_acc, NULL, 10); struct account *acc = (struct account *) strtoul(native_acc, NULL, 10);
(*env)->ReleaseStringUTFChars(env, javaAcc, native_acc); (*env)->ReleaseStringUTFChars(env, javaAcc, native_acc);
@@ -45,7 +45,7 @@ object Api {
external fun account_dtmfmode(acc: String): Int external fun account_dtmfmode(acc: String): Int
external fun account_set_dtmfmode(acc: String, mode: Int): Int external fun account_set_dtmfmode(acc: String, mode: Int): Int
external fun account_mediaaf(acc: String): 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_extra(acc: String): String
external fun account_debug(acc: String) external fun account_debug(acc: String)
@@ -415,7 +415,7 @@ class BaresipService: Service() {
Log.d(TAG, "uaAdd ${ua.account.aor} at BaresipService") Log.d(TAG, "uaAdd ${ua.account.aor} at BaresipService")
uas.add(ua) uas.add(ua)
if (ua.account.preferIPv6Media) 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)) { if (Api.ua_isregistered(uap)) {
Log.d(TAG, "Ua ${ua.account.aor} is registered") Log.d(TAG, "Ua ${ua.account.aor} is registered")
status.add(R.drawable.dot_green) status.add(R.drawable.dot_green)
@@ -14,6 +14,8 @@ import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.CountDownTimer import android.os.CountDownTimer
import android.provider.DocumentsContract
import android.provider.MediaStore
import android.text.InputType import android.text.InputType
import android.text.TextWatcher import android.text.TextWatcher
import android.text.method.HideReturnsTransformationMethod import android.text.method.HideReturnsTransformationMethod
@@ -21,6 +23,7 @@ import android.text.method.PasswordTransformationMethod
import android.view.* import android.view.*
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.* import android.widget.*
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
@@ -28,6 +31,8 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.tutpro.baresip.databinding.ActivityMainBinding import com.tutpro.baresip.databinding.ActivityMainBinding
import java.io.File import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.net.URLDecoder import java.net.URLDecoder
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
@@ -62,6 +67,8 @@ class MainActivity : AppCompatActivity() {
private lateinit var stopState: String private lateinit var stopState: String
private var speakerIcon: MenuItem? = null private var speakerIcon: MenuItem? = null
private lateinit var swipeRefresh: SwipeRefreshLayout private lateinit var swipeRefresh: SwipeRefreshLayout
private var downloadsInputStream: FileInputStream? = null
private var downloadsOutputStream: FileOutputStream? = null
private lateinit var baresipService: Intent private lateinit var baresipService: Intent
@@ -1074,15 +1081,23 @@ class MainActivity : AppCompatActivity() {
} }
R.id.backup -> { R.id.backup -> {
if (Utils.requestPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE, if (Build.VERSION.SDK_INT >= 29) {
BACKUP_PERMISSION_REQUEST_CODE)) pickupFileFromDownloads(BACKUP_CODE)
askPassword(getString(R.string.encrypt_password)) } 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 -> { 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)) RESTORE_PERMISSION_REQUEST_CODE))
askPassword(getString(R.string.decrypt_password)) startRestore()
} }
R.id.about -> { R.id.about -> {
@@ -1163,6 +1178,27 @@ class MainActivity : AppCompatActivity() {
updateIcons(Account.ofAor(activityAor)!!) 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 -> { ABOUT_CODE -> {
} }
@@ -1176,7 +1212,7 @@ class MainActivity : AppCompatActivity() {
when (requestCode) { when (requestCode) {
RECORD_PERMISSION_REQUEST_CODE -> { 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), Utils.alertView(this, getString(R.string.notice),
getString(R.string.no_calls), ::startBaresip) getString(R.string.no_calls), ::startBaresip)
else else
@@ -1184,16 +1220,49 @@ class MainActivity : AppCompatActivity() {
} }
BACKUP_PERMISSION_REQUEST_CODE -> 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)) askPassword(getString(R.string.encrypt_password))
RESTORE_PERMISSION_REQUEST_CODE -> RESTORE_PERMISSION_REQUEST_CODE ->
if ((grantResults.size > 0) && (grantResults[0] == PackageManager.PERMISSION_GRANTED)) if ((grantResults.isNotEmpty()) && (grantResults[0] == PackageManager.PERMISSION_GRANTED))
askPassword(getString(R.string.decrypt_password)) 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) { private fun quitRestart(reStart: Boolean) {
if (stopState == "initial") { if (stopState == "initial") {
Log.d(TAG, "quitRestart Restart = $reStart") Log.d(TAG, "quitRestart Restart = $reStart")
@@ -1378,54 +1447,57 @@ class MainActivity : AppCompatActivity() {
File(BaresipService.filesPath).walk().forEach { File(BaresipService.filesPath).walk().forEach {
if (it.name.endsWith(".png")) files.add(it.name) if (it.name.endsWith(".png")) files.add(it.name)
} }
val bsFile = getString(R.string.app_name) + ".bs" val zipFile = getString(R.string.app_name_plus) + ".zip"
val backupFilePath = BaresipService.downloadsPath + "/$bsFile"
val zipFile = getString(R.string.app_name) + ".zip"
val zipFilePath = BaresipService.filesPath + "/$zipFile" val zipFilePath = BaresipService.filesPath + "/$zipFile"
if (!Utils.zip(files, zipFile)) { if (!Utils.zip(files, zipFile)) {
Log.w(TAG, "Failed to write zip file '$zipFile'") Log.w(TAG, "Failed to write zip file '$zipFile'")
Utils.alertView(this, getString(R.string.error), Utils.alertView(this, getString(R.string.error),
String.format(getString(R.string.backup_failed), bsFile)) String.format(getString(R.string.backup_failed),
downloadsOutputFile))
return return
} }
val content = Utils.getFileContents(zipFilePath) val content = Utils.getFileContents(zipFilePath)
if (content == null) { if (content == null) {
Log.w(TAG, "Failed to read zip file '$zipFile'") Log.w(TAG, "Failed to read zip file '$zipFile'")
Utils.alertView(this, getString(R.string.error), Utils.alertView(this, getString(R.string.error),
String.format(getString(R.string.backup_failed), bsFile)) String.format(getString(R.string.backup_failed),
downloadsOutputFile))
return return
} }
if (!Utils.encryptToFile(backupFilePath, content, password)) { if (!Utils.encryptToStream(downloadsOutputStream, content, password)) {
Utils.alertView(this, getString(R.string.error), Utils.alertView(this, getString(R.string.error),
String.format(getString(R.string.backup_failed), bsFile)) String.format(getString(R.string.backup_failed),
downloadsOutputFile))
return return
} }
Utils.alertView(this, getString(R.string.info), 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)) Utils.deleteFile(File(zipFilePath))
} }
private fun restore(password: String) { private fun restore(password: String) {
val bsFile = getString(R.string.app_name) + ".bs" val zipFile = getString(R.string.app_name_plus) + ".zip"
val backupFilePath = BaresipService.downloadsPath + "/$bsFile"
val zipFile = getString(R.string.app_name) + ".zip"
val zipFilePath = BaresipService.filesPath + "/$zipFile" val zipFilePath = BaresipService.filesPath + "/$zipFile"
val zipData = Utils.decryptFromFile(backupFilePath, password) val zipData = Utils.decryptFromStream(downloadsInputStream, password)
if (zipData == null) { if (zipData == null) {
Utils.alertView(this, getString(R.string.error), Utils.alertView(this, getString(R.string.error),
String.format(getString(R.string.restore_failed), bsFile)) String.format(getString(R.string.restore_failed),
downloadsInputFile))
return return
} }
if (!Utils.putFileContents(zipFilePath, zipData)) { if (!Utils.putFileContents(zipFilePath, zipData)) {
Log.w(TAG, "Failed to write zip file '$zipFile'") Log.w(TAG, "Failed to write zip file '$zipFile'")
Utils.alertView(this, getString(R.string.error), Utils.alertView(this, getString(R.string.error),
String.format(getString(R.string.restore_failed), bsFile)) String.format(getString(R.string.restore_failed),
downloadsInputFile))
return return
} }
if (!Utils.unZip(zipFilePath)) { if (!Utils.unZip(zipFilePath)) {
Log.w(TAG, "Failed to unzip file '$zipFile'") Log.w(TAG, "Failed to unzip file '$zipFile'")
Utils.alertView(this, getString(R.string.error), Utils.alertView(this, getString(R.string.error),
String.format(getString(R.string.restore_failed), bsFile)) String.format(getString(R.string.restore_failed),
downloadsInputFile))
return return
} }
Utils.deleteFile(File(zipFilePath)) Utils.deleteFile(File(zipFilePath))
@@ -1797,6 +1869,8 @@ class MainActivity : AppCompatActivity() {
const val CONTACT_CODE = 7 const val CONTACT_CODE = 7
const val CHATS_CODE = 8 const val CHATS_CODE = 8
const val CHAT_CODE = 9 const val CHAT_CODE = 9
const val BACKUP_CODE = 10
const val RESTORE_CODE = 11
const val BACKUP_PERMISSION_REQUEST_CODE = 1 const val BACKUP_PERMISSION_REQUEST_CODE = 1
const val RESTORE_PERMISSION_REQUEST_CODE = 2 const val RESTORE_PERMISSION_REQUEST_CODE = 2
+24 -11
View File
@@ -11,7 +11,9 @@ import android.graphics.Bitmap.createScaledBitmap
import android.graphics.Color import android.graphics.Color
import android.net.LinkAddress import android.net.LinkAddress
import android.net.LinkProperties import android.net.LinkProperties
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.provider.OpenableColumns
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.view.View import android.view.View
@@ -418,14 +420,14 @@ object Utils {
} }
fun getFileContents(filePath: String): ByteArray? { fun getFileContents(filePath: String): ByteArray? {
try { return try {
return File(filePath).readBytes() File(filePath).readBytes()
} catch (e: FileNotFoundException) { } catch (e: FileNotFoundException) {
Log.e("Baresip", "File '$filePath' not found: ${e.printStackTrace()}") Log.e("Baresip", "File '$filePath' not found: ${e.printStackTrace()}")
return null null
} catch (e: Exception) { } catch (e: Exception) {
Log.e("Baresip", "Failed to read file '$filePath': ${e.printStackTrace()}") Log.e("Baresip", "Failed to read file '$filePath': ${e.printStackTrace()}")
return null null
} }
} }
@@ -440,6 +442,18 @@ object Utils {
return true 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 { fun saveBitmap(bitmap: Bitmap, file: File): Boolean {
if (file.exists()) file.delete() if (file.exists()) file.delete()
try { try {
@@ -504,29 +518,28 @@ object Utils {
return plainData return plainData
} }
fun encryptToStream(stream: FileOutputStream?, content: ByteArray, password: String): Boolean {
fun encryptToFile(filePath: String, content: ByteArray, password: String): Boolean {
val obj = encrypt(content, password.toCharArray()) val obj = encrypt(content, password.toCharArray())
try { try {
ObjectOutputStream(FileOutputStream(File(filePath))).use { ObjectOutputStream(stream).use {
it.writeObject(obj) it.writeObject(obj)
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e("Baresip", "Write failed: ${e.printStackTrace()}") Log.w("Baresip", "encryptToStream failed: $e")
return false return false
} }
return true return true
} }
fun decryptFromFile(filePath: String, password: String): ByteArray? { fun decryptFromStream(stream: FileInputStream?, password: String): ByteArray? {
var plainData: ByteArray? = null var plainData: ByteArray? = null
try { try {
ObjectInputStream(FileInputStream(File(filePath))).use { ObjectInputStream(stream).use {
val obj = it.readObject() as Crypto val obj = it.readObject() as Crypto
plainData = decrypt(obj, password.toCharArray()) plainData = decrypt(obj, password.toCharArray())
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e("Baresip", "Decrypt failed from file '$filePath'") Log.w("Baresip", "decryptFromStream failed: $e")
} }
return plainData return plainData
} }