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

View File

@ -6,7 +6,7 @@ android {
defaultConfig {
applicationId = 'com.tutpro.baresip'
minSdkVersion 21
targetSdkVersion 29
targetSdkVersion 30
versionCode = 163
versionName = '29.2.1'
externalNativeBuild {

View File

@ -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" />

View File

@ -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);

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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
}