Files
2022-02-14 09:54:33 +02:00

856 lines
31 KiB
Kotlin

package com.tutpro.baresip
import android.app.Activity
import android.app.KeyguardManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.Bitmap.createScaledBitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Color
import android.net.*
import android.net.wifi.WifiManager
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.provider.DocumentsContract
import android.provider.MediaStore
import android.provider.OpenableColumns
import android.text.Editable
import android.text.TextWatcher
import android.text.format.DateUtils
import android.view.View
import android.view.WindowManager
import android.widget.ImageView
import android.widget.TextView
import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ProcessLifecycleOwner
import com.google.android.material.snackbar.Snackbar
import java.io.*
import java.lang.reflect.Method
import java.security.SecureRandom
import java.util.*
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream
import java.net.InetAddress
import java.net.NetworkInterface
import java.net.SocketException
import java.text.DateFormat
import javax.crypto.Cipher
import javax.crypto.SecretKeyFactory
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.PBEKeySpec
import javax.crypto.spec.SecretKeySpec
import kotlin.collections.ArrayList
object Utils {
fun getNameValue(string: String, name: String): ArrayList<String> {
val lines = string.split("\n")
val result = ArrayList<String>()
for (line in lines) {
if (line.startsWith(name))
result.add((line.substring(name.length).trim()).split(" \t")[0])
}
return result
}
fun removeLinesStartingWithString(lines: String, string: String): String {
var result = ""
for (line in lines.split("\n"))
if (!line.startsWith(string) && (line.isNotEmpty())) result += line + "\n"
return result
}
fun alertView(context: Context, title: String, message: String, action: () -> (Unit) = {}) {
val titleView = View.inflate(context, R.layout.alert_title, null) as TextView
titleView.text = title
with(AlertDialog.Builder(context, R.style.AlertDialog)) {
setCustomTitle(titleView)
setMessage(message)
setPositiveButton(R.string.ok) { dialog, _ ->
dialog.dismiss()
action()
}
show()
}
}
fun uriHostPart(uri: String): String {
return if (uri.contains("@")) {
uri.substringAfter("@")
.substringBefore(":")
.substringBefore(";")
.substringBefore(">")
} else {
val parts = uri.split(":")
when (parts.size) {
2 -> parts[1].substringBefore(";")
.substringBefore(">")
3 -> parts[1]
else -> ""
}
}
}
fun uriUserPart(uri: String): String {
return if (uri.contains("@"))
uri.substringAfter(":").substringBefore("@")
else
""
}
fun uriMatch(firstUri: String, secondUri: String): Boolean {
if (firstUri.startsWith("tel:"))
return firstUri == secondUri || firstUri.substringAfter(":") == uriUserPart(secondUri)
if (firstUri.startsWith("sip:"))
return uriUserPart(firstUri) == uriUserPart(secondUri) &&
uriHostPart(firstUri) == uriHostPart(secondUri)
return false
}
private fun uriParams(uri: String): List<String> {
val params = uri.split(";")
return if (params.size == 1) listOf() else params.subList(1, params.size)
}
fun friendlyUri(uri: String, domain: String): String {
var u = uri
val params = uriParams(u)
if (uri.startsWith("<") && (uri.endsWith(">")))
u = uri.substring(1).substringBeforeLast(">")
u = u.replace(":5060", "")
u = u.replace(";transport=udp", "", true)
if (u.split(":").size == 3 ||
(params.isNotEmpty() && !params.contains("user=phone")))
return u
return if (u.contains("@")) {
val user = uriUserPart(u)
val host = uriHostPart(u)
if (isTelNumber(user) || host == domain)
user
else
"$user@$host"
} else {
u
}
}
fun uriComplete(uri: String, domain: String): String {
val res = if (!uri.startsWith("sip:")) "sip:$uri" else uri
return if (checkUriUser(uri)) "$res@$domain" else res
}
fun aorDomain(aor: String): String {
return uriHostPart(aor)
}
fun plainAor(aor: String): String {
return uriUserPart(aor) + "@" + uriHostPart(aor)
}
fun checkAor(aor: String): Boolean {
if (!checkSipUri(aor)) return false
val params = uriParams(aor)
return params.isEmpty() ||
((params.size == 1) &&
params[0] in arrayOf("transport=udp", "transport=tcp", "transport=tls"))
}
private fun checkTransport(transport: String, transports: Set<String>): Boolean {
return transport.split("=")[0] == "transport" &&
transport.split("=")[1].lowercase() in transports
}
fun checkStunUri(uri: String): Boolean {
if (uri.substringBefore(":").lowercase() !in setOf("stun", "stuns", "turn", "turns"))
return false
return checkHostPort(uri.substringAfter(":").substringBefore("?")) &&
(uri.indexOf("?") == -1 ||
checkTransport(uri.substringAfter("?"), setOf("udp", "tcp")))
}
fun checkIpV4(ip: String): Boolean {
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)
}
private fun checkIpV6(ip: String): Boolean {
return Regex("^(([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4})$").matches(ip)
}
private fun checkIpv6InBrackets(bracketedIp: String): Boolean {
return bracketedIp.startsWith("[") && bracketedIp.endsWith("]") &&
checkIpV6(bracketedIp.substring(1, bracketedIp.length - 2))
}
fun checkUriUser(user: String): Boolean {
val escaped = """%(\d|A|B|C|D|E|F|a|b|c|d|e|f){2}""".toRegex()
escaped.replace(user, "").forEach {
if (!(it.isLetterOrDigit() || "-_.!~*\'()&=+\$,;?/".contains(it))) return false }
return user.isNotEmpty() && !checkIpV4(user) && !checkIpV6(user)
}
fun checkDomain(domain: String): Boolean {
val parts = domain.split(".")
for (p in parts) {
if (p.endsWith("-") || p.startsWith("-") ||
!Regex("^[-a-zA-Z0-9]+\$").matches(p))
return false
}
return true
}
private fun checkPort(port: String): Boolean {
val number = port.toIntOrNull() ?: return false
return (number > 0) && (number < 65536)
}
fun checkIpPort(ipPort: String): Boolean {
return if (ipPort.startsWith("["))
checkIpv6InBrackets(ipPort.substringBeforeLast(":")) &&
checkPort(ipPort.substringAfterLast(":"))
else
checkIpV4(ipPort.substringBeforeLast(":")) &&
checkPort(ipPort.substringAfterLast(":"))
}
private fun checkDomainPort(domainPort: String): Boolean {
return checkDomain(domainPort.substringBeforeLast(":")) &&
checkPort(domainPort.substringAfterLast(":"))
}
private fun checkHostPort(hostPort: String): Boolean {
return checkIpV4(hostPort) || checkIpv6InBrackets(hostPort) || checkDomain(hostPort) ||
checkIpPort(hostPort) || checkDomainPort(hostPort)
}
private fun checkParams(params: String): Boolean {
for (param in params.split(";"))
if (!checkParam(param)) return false
return true
}
private fun checkParam(param: String): Boolean {
val nameValue = param.split("=")
if (nameValue.size == 1)
return checkParamChars(nameValue[0])
if (nameValue.size == 2) {
if (nameValue[0] == "transport")
return setOf("udp", "tcp", "tls", "wss").contains(nameValue[1].lowercase())
return checkParamChars(nameValue[1])
}
return false
}
private fun checkParamChars(s: String): Boolean {
// Does not currently allow escaped characters
val allowed = "[]/:&+$-_.!~*'()"
for (c in s)
if (!allowed.contains(c) && !c.isLetterOrDigit())
return false
return true
}
fun paramValue(params: String, name: String): String {
if (params == "") return ""
for (param in params.split(";"))
if (param.substringBefore("=") == name) return param.substringAfter("=")
return ""
}
fun paramExists(params: String, name: String): Boolean {
for (param in params.split(";"))
if (param.substringBefore("=") == name) return true
return false
}
fun checkHostPortParams(hpp: String) : Boolean {
val restParams = hpp.split(";", limit = 2)
return if (restParams.size == 1)
checkHostPort(restParams[0])
else
checkHostPort(restParams[0]) && checkParams(restParams[1])
}
private fun checkSipUri(uri: String): Boolean {
return if (uri.startsWith("sip:")) {
val userRest = uri.substring(4).split("@")
when (userRest.size) {
1 ->
checkHostPortParams(userRest[0])
2 ->
checkUriUser(userRest[0]) && checkHostPortParams(userRest[1])
else -> false
}
} else {
false
}
}
fun isTelNumber(no: String): Boolean {
return Regex("^([+][1-9])?[0-9- ]{0,15}\$").matches(no)
}
fun isTelUri(uri: String): Boolean {
return uri.startsWith("tel:") && isTelNumber(uri.substring(4))
}
fun checkUri(uri: String): Boolean {
return checkSipUri(uri) || isTelUri(uri)
}
fun telToSip(telUri: String, account: Account): String {
val hostPart = if (account.telProvider != "")
account.telProvider
else
aorDomain(account.aor)
return "sip:" + telUri.substring(4).filterNot{setOf('-', ' ').contains(it)} +
"@" + hostPart + ";user=phone"
}
fun checkName(name: String): Boolean {
return name.isNotEmpty() && name == String(name.toByteArray(), Charsets.UTF_8) &&
name.lines().size == 1 && !name.contains('"')
}
fun contactName(uri: String) : String {
return ContactsActivity.contactName(uri) ?: AndroidContactsActivity.contactName(uri)
}
fun contactUri(name: String) : String? {
return ContactsActivity.contactUri(name) ?: AndroidContactsActivity.contactUri(name)
}
fun setAvatar(ctx: Context, imageView: ImageView, textView: TextView, uri: String) {
val contact = ContactsActivity.findContact(uri)
if (contact != null) {
val avatarImage = contact.avatarImage
if (avatarImage != null) {
imageView.setImageBitmap(avatarImage)
} else {
textView.background.setTint(contact.color)
if (contact.name.isNotEmpty())
textView.text = "${contact.name[0]}"
else
textView.text = ""
imageView.setImageBitmap(bitmapFromView(textView))
}
} else {
val androidContact = AndroidContactsActivity.findContact(uri)
if (androidContact != null) {
val thumbnailUri = androidContact.thumbnailUri
if (thumbnailUri != null) {
imageView.setImageURI(thumbnailUri)
} else {
textView.background.setTint(androidContact.color)
if (androidContact.name.isNotEmpty())
textView.text = "${androidContact.name[0]}"
else
textView.text = ""
imageView.setImageBitmap(bitmapFromView(textView))
}
} else {
val bitmap = BitmapFactory.decodeResource(ctx.resources, R.drawable.person_image)
imageView.setImageBitmap(bitmap)
}
}
}
@Suppress("unused")
fun checkIfName(name: String): Boolean {
if ((name.length < 2) || !name.first().isLetter()) return false
for (c in name)
if (!c.isLetterOrDigit()) return false
return true
}
fun implode(list: List<String>, sep: String): String {
var res = ""
for (s in list) {
res = if (res == "")
s
else
res + sep + s
}
return res
}
fun isVisible(): Boolean {
return ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)
}
fun isHotSpotOn(wm: WifiManager): Boolean {
try {
val method: Method = wm.javaClass.getDeclaredMethod("isWifiApEnabled")
method.isAccessible = true
return method.invoke(wm) as Boolean
} catch (ignored: Throwable) {
}
return false
}
fun hotSpotAddresses(): Map<String, String> {
val result = mutableMapOf<String, String>()
try {
val interfaces: Enumeration<NetworkInterface> = NetworkInterface.getNetworkInterfaces()
while (interfaces.hasMoreElements()) {
val iface: NetworkInterface = interfaces.nextElement()
val ifName = iface.name
if (ifName.startsWith("ap") || ifName.startsWith("wlan")) {
val addresses: Enumeration<InetAddress> = iface.inetAddresses
while (addresses.hasMoreElements()) {
val inetAddress: InetAddress = addresses.nextElement()
if (inetAddress.hostAddress != null && !inetAddress.isLoopbackAddress &&
!inetAddress.isLinkLocalAddress)
result[inetAddress.hostAddress!!] = ifName
}
if (result.isNotEmpty()) return result
}
}
} catch (ex: SocketException) {
Log.e(TAG, ex.toString())
}
return result
}
fun dtmfWatcher(callp: String): TextWatcher {
return object : TextWatcher {
override fun beforeTextChanged(sequence: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(sequence: CharSequence, start: Int, before: Int, count: Int) {
val text = sequence.subSequence(start, start + count).toString()
if (text.isNotEmpty()) {
val digit = text[0]
val call = Call.ofCallp(callp)
if (call == null) {
Log.w(TAG, "dtmfWatcher did not find call $callp")
} else {
Log.d(TAG, "Got DTMF digit '$digit'")
if (((digit >= '0') && (digit <= '9')) || (digit == '*') || (digit == '#'))
call.sendDigit(digit)
}
}
}
override fun afterTextChanged(sequence: Editable) {
// KEYCODE_REL
// call_send_digit(callp, 4.toChar())
}
}
}
fun checkPermissions(ctx: Context, permissions: Array<String>) : Boolean {
if (Build.VERSION.SDK_INT < 23)
return true
for (p in permissions) {
if (ContextCompat.checkSelfPermission(ctx, p) != PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "Permission $p is denied")
return false
} else {
Log.d(TAG, "Permission $p is granted")
}
}
return true
}
fun View.showSnackBar(
view: View,
msg: String,
length: Int,
actionMessage: CharSequence?,
action: (View) -> Unit
) {
val snackBar = Snackbar.make(view, msg, length)
if (actionMessage != null) {
snackBar.setAction(actionMessage) {
action(this)
}.show()
} else {
snackBar.show()
}
}
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(TAG, "Failed to copy asset '$asset' to file: $e")
}
}
fun deleteFile(file: File) {
if (file.exists()) {
try {
file.delete()
} catch (e: IOException) {
Log.e(TAG, "Could not delete file ${file.absolutePath}")
}
}
}
fun getFileContents(filePath: String): ByteArray? {
return try {
File(filePath).readBytes()
} catch (e: FileNotFoundException) {
Log.e(TAG, "File '$filePath' not found: ${e.printStackTrace()}")
null
} catch (e: Exception) {
Log.e(TAG, "Failed to read file '$filePath': ${e.printStackTrace()}")
null
}
}
fun putFileContents(filePath: String, contents: ByteArray): Boolean {
try {
File(filePath).writeBytes(contents)
}
catch (e: IOException) {
Log.e(TAG, "Failed to write file '$filePath': $e")
return false
}
return true
}
fun File.copyInputStreamToFile(inputStream: InputStream): Boolean {
try {
this.outputStream().use { fileOut ->
inputStream.copyTo(fileOut)
}
return true
}
catch (e: IOException) {
Log.e(TAG, "Failed to write file '${this.absolutePath}': $e")
}
return false
}
@RequiresApi(29)
fun selectInputFile(request: ActivityResultLauncher<Intent>) {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
putExtra(DocumentsContract.EXTRA_INITIAL_URI, MediaStore.Downloads.EXTERNAL_CONTENT_URI)
}
request.launch(intent)
}
@RequiresApi(29)
@Suppress("unused")
fun selectOutputFile(title: String) {
Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/octet-stream"
putExtra(Intent.EXTRA_TITLE, title)
putExtra(DocumentsContract.EXTRA_INITIAL_URI, MediaStore.Downloads.EXTERNAL_CONTENT_URI)
}
}
@Suppress("DEPRECATION")
fun downloadsPath(fileName: String): String {
return Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS).path + "/$fileName"
}
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 {
val out = FileOutputStream(file)
val scaledBitmap = createScaledBitmap(bitmap, 96, 96, true)
scaledBitmap.compress(Bitmap.CompressFormat.PNG, 100, out)
out.flush()
out.close()
Log.d(TAG, "Saved bitmap to ${file.absolutePath} of length ${file.length()}")
} catch (e: Exception) {
Log.e(TAG, "Failed to save bitmap to ${file.absolutePath}")
return false
}
return true
}
class Crypto(val salt: ByteArray, val iter: Int, val iv: ByteArray, val data: ByteArray): Serializable {
companion object {
private const val serialVersionUID: Long = -29238082928391L
}
}
private fun encrypt(content: ByteArray, password: CharArray): ByteArray? {
fun intToByteArray(int: Int): ByteArray {
val bytes = ByteArray(2)
bytes[0] = (int shr 0).toByte()
bytes[1] = (int shr 8).toByte()
return bytes
}
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)
val res = ByteArray(128 + 2 + 16 + cipherData.size)
salt.copyInto(res, 0)
intToByteArray(iterationCount).copyInto(res, salt.size)
iv.copyInto(res, salt.size + 2)
cipherData.copyInto(res, salt.size + 2 + iv.size)
return res
} catch (e: Exception) {
Log.e(TAG, "Encrypt failed: ${e.printStackTrace()}")
}
return null
}
private fun decrypt(content: ByteArray, password: CharArray): ByteArray? {
fun byteArrayToInt(bytes: ByteArray) : Int {
return (bytes[1].toInt() and 0xff shl 8) or (bytes[0].toInt() and 0xff)
}
try {
val salt = content.copyOfRange(0, 128)
val iterationCount = byteArrayToInt(content.copyOfRange(128, 130))
val iv = content.copyOfRange(130, 146)
val data = content.copyOfRange(146, content.size)
val pbKeySpec = PBEKeySpec(password, salt, iterationCount, 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(iv)
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec)
return cipher.doFinal(data)
} catch (e: Exception) {
Log.e(TAG, "Decrypt failed: ${e.printStackTrace()}")
}
return null
}
private fun decryptOld(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(TAG, "Decrypt failed: ${e.printStackTrace()}")
}
return plainData
}
fun encryptToUri(ctx: Context, uri: Uri, content: ByteArray, password: String): Boolean {
val obj = encrypt(content, password.toCharArray())
val stream = ctx.contentResolver.openOutputStream(uri) as FileOutputStream
try {
ObjectOutputStream(stream).use {
it.writeObject(obj)
}
} catch (e: Exception) {
Log.w(TAG, "encryptToUri failed: $e")
return false
}
return true
}
fun decryptFromUri(ctx: Context, uri: Uri, password: String): ByteArray? {
var plainData: ByteArray? = null
var stream = ctx.contentResolver.openInputStream(uri) as FileInputStream
try {
ObjectInputStream(stream).use {
val content = it.readObject() as ByteArray
plainData = decrypt(content, password.toCharArray())
}
stream.close()
} catch (e: Exception) {
Log.w(TAG, "decryptFromUri as ByteArray failed: $e")
stream.close()
try {
stream = ctx.contentResolver.openInputStream(uri) as FileInputStream
ObjectInputStream(stream).use {
val obj = it.readObject() as Crypto
plainData = decryptOld(obj, password.toCharArray())
}
stream.close()
} catch (e: Exception) {
Log.w(TAG, "decryptFromUri as Crypto failed: $e")
}
}
return plainData
}
fun zip(fileNames: ArrayList<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(TAG, "Failed to zip file '$zipFilePath': $e")
return false
}
return true
}
fun unZip(zipFilePath: String): Boolean {
val allFiles = listOf("accounts", "calls", "config", "contacts", "messages", "uuid",
"zrtp_cache.dat", "zrtp_zid", "cert.pem", "ca_cert", "ca_certs.crt")
val zipFiles = mutableListOf<String>()
try {
ZipFile(zipFilePath).use { zip ->
zip.entries().asSequence().forEach { entry ->
if (!entry.name.contains("/com.tutpro.baresip/")) {
Log.e(TAG, "Backup file is not from baresip application")
return false
}
zipFiles.add(entry.name.substringAfterLast("/"))
zip.getInputStream(entry).use { input ->
File(entry.name).outputStream().use { output ->
input.copyTo(output)
}
}
}
}
} catch (e: IOException) {
Log.e(TAG, "Failed to unzip file '$zipFilePath': $e")
return false
}
(allFiles - zipFiles.toSet()).iterator().forEach {
deleteFile(File(BaresipService.filesPath, it))
}
return true
}
@Suppress("unused")
fun dumpIntent(intent: Intent) {
val bundle: Bundle = intent.extras ?: return
val keys = bundle.keySet()
val it = keys.iterator()
Log.d(TAG, "Dumping intent start")
while (it.hasNext()) {
val key = it.next()
Log.d(TAG, "[" + key + "=" + bundle.get(key) + "]")
}
Log.d(TAG, "Dumping intent finish")
}
fun randomColor(): Int {
val rnd = Random()
return Color.argb(255, rnd.nextInt(256), rnd.nextInt(256),
rnd.nextInt(256))
}
fun addActivity(activity: String) {
if ((BaresipService.activities.size == 0) || (BaresipService.activities[0] != activity))
BaresipService.activities.add(0, activity)
}
@Suppress("DEPRECATION")
fun setShowWhenLocked(activity: Activity, show: Boolean) {
when {
Build.VERSION.SDK_INT >= 27 -> activity.setShowWhenLocked(show)
show -> activity.window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
else -> activity.window.clearFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
}
}
fun setTurnScreenOn(activity: Activity, turn: Boolean) {
if (Build.VERSION.SDK_INT >= 27) {
activity.setTurnScreenOn(turn)
} else
@Suppress("DEPRECATION")
if (turn) {
activity.window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
} else {
activity.window.clearFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
}
}
@Suppress("DEPRECATION")
fun requestDismissKeyguard(activity: Activity) {
if (Build.VERSION.SDK_INT >= 26) {
val kgm = activity.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
kgm.requestDismissKeyguard(activity, null)
} else {
activity.window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD)
}
}
fun relativeTime(ctx: Context, time: GregorianCalendar): String {
return if (DateUtils.isToday(time.timeInMillis)) {
val fmt = DateFormat.getTimeInstance(DateFormat.SHORT)
ctx.getString(R.string.today) + "\n" + fmt.format(time.time)
} else {
val month = time.getDisplayName(Calendar.MONTH, Calendar.SHORT, Locale.getDefault())
val day = time.get(Calendar.DAY_OF_MONTH)
val currentYear = Calendar.getInstance().get(Calendar.YEAR)
if (time.get(Calendar.YEAR) == currentYear) {
val fmt = DateFormat.getTimeInstance(DateFormat.SHORT)
"$month $day" + "\n" + fmt.format(time.time)
} else {
"$month $day" + "\n" + time.get(Calendar.YEAR)
}
}
}
fun bitmapFromView(view: View): Bitmap? {
val bitmap = Bitmap.createBitmap(view.layoutParams.width, view.layoutParams.height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
view.layout(0, 0, view.layoutParams.width, view.layoutParams.height)
view.draw(canvas)
return bitmap
}
}