455 lines
19 KiB
Kotlin
455 lines
19 KiB
Kotlin
package com.tutpro.baresip
|
|
|
|
import android.app.*
|
|
import android.app.Notification.VISIBILITY_PUBLIC
|
|
import android.content.BroadcastReceiver
|
|
import android.content.Context
|
|
import android.content.Intent
|
|
import android.content.IntentFilter
|
|
import android.net.NetworkInfo
|
|
import android.net.wifi.WifiManager
|
|
import android.os.IBinder
|
|
import android.os.PowerManager
|
|
import android.support.annotation.Keep
|
|
import android.support.v4.app.NotificationCompat
|
|
import android.util.Log
|
|
import android.view.View
|
|
import android.widget.RemoteViews
|
|
import java.io.File
|
|
import java.io.FileInputStream
|
|
import java.io.ObjectInputStream
|
|
import android.support.v4.content.LocalBroadcastManager
|
|
import android.os.Build
|
|
import android.support.v4.app.NotificationCompat.VISIBILITY_PRIVATE
|
|
import java.nio.charset.StandardCharsets
|
|
import kotlin.experimental.and
|
|
import android.R.attr.name
|
|
import java.nio.charset.Charset
|
|
|
|
class BaresipService: Service() {
|
|
|
|
private val LOG_TAG = "Baresip Service"
|
|
internal lateinit var intent: Intent
|
|
internal lateinit var nm: NotificationManager
|
|
internal lateinit var snb: NotificationCompat.Builder
|
|
internal lateinit var npi: PendingIntent
|
|
internal lateinit var nr: BroadcastReceiver
|
|
internal lateinit var wl: PowerManager.WakeLock
|
|
internal lateinit var fl: WifiManager.WifiLock
|
|
|
|
override fun onCreate() {
|
|
|
|
Log.d(LOG_TAG, "At onCreate")
|
|
|
|
intent = Intent("com.tutpro.baresip.EVENT")
|
|
intent.setPackage("com.tutpro.baresip")
|
|
|
|
nm = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
|
createNotificationChannels()
|
|
snb = NotificationCompat.Builder(this, DEFAULT_CHANNEL_ID)
|
|
|
|
val ni = Intent(this, MainActivity::class.java)
|
|
.setAction(Intent.ACTION_MAIN)
|
|
.addCategory(Intent.CATEGORY_LAUNCHER)
|
|
npi = PendingIntent.getActivity(this, 0, ni, 0)
|
|
|
|
nr = object : BroadcastReceiver() {
|
|
override fun onReceive(context: Context, intent: Intent) {
|
|
val intentExtras = intent.extras
|
|
val info = intentExtras.getParcelable<NetworkInfo>("networkInfo")
|
|
Log.d("Baresip", "Got event $info")
|
|
if (info.isConnected) {
|
|
if (disconnected) {
|
|
UserAgent.register(MainActivity.uas)
|
|
disconnected = false
|
|
}
|
|
} else {
|
|
disconnected = true
|
|
}
|
|
}
|
|
}
|
|
|
|
super.onCreate()
|
|
}
|
|
|
|
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
|
|
|
Log.d(LOG_TAG, "Received onStartCommand action ${intent.getAction()}")
|
|
|
|
when (intent.getAction()) {
|
|
|
|
"Start" -> {
|
|
val pm = getSystemService(Context.POWER_SERVICE) as PowerManager
|
|
wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Baresip")
|
|
wl.acquire()
|
|
|
|
val wm = applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
|
|
fl = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "Baresip")
|
|
|
|
val assets = arrayOf("accounts", "contacts", "config", "busy.wav", "callwaiting.wav",
|
|
"error.wav", "message.wav", "notfound.wav", "ring.wav", "ringback.wav")
|
|
val path = applicationContext.filesDir.path
|
|
var file = File(path)
|
|
if (!file.exists()) {
|
|
Log.d(LOG_TAG, "Creating baresip directory")
|
|
try {
|
|
File(path).mkdirs()
|
|
} catch (e: Error) {
|
|
Log.e(LOG_TAG, "Failed to create directory: " + e.toString())
|
|
}
|
|
|
|
}
|
|
for (a in assets) {
|
|
file = File("$path/$a")
|
|
if (!file.exists()) {
|
|
Log.d(LOG_TAG, "Copying asset $a")
|
|
Utils.copyAssetToFile(applicationContext, a, "$path/$a")
|
|
} else {
|
|
Log.d(LOG_TAG, "Asset $a already copied")
|
|
}
|
|
}
|
|
|
|
file = File(path, "history")
|
|
if (file.exists()) {
|
|
try {
|
|
val fis = FileInputStream(file)
|
|
val ois = ObjectInputStream(fis)
|
|
@SuppressWarnings("unchecked")
|
|
MainActivity.history = ois.readObject() as ArrayList<CallHistory>
|
|
Log.d(LOG_TAG, "Restored History of ${MainActivity.history.size} entries")
|
|
ois.close()
|
|
fis.close()
|
|
} catch (e: Exception) {
|
|
Log.w(LOG_TAG, "InputStream exception: - " + e.toString())
|
|
}
|
|
}
|
|
|
|
file = File(path, "messages")
|
|
if (file.exists()) {
|
|
try {
|
|
val fis = FileInputStream(file)
|
|
val ois = ObjectInputStream(fis)
|
|
@SuppressWarnings("unchecked")
|
|
MainActivity.messages = ois.readObject() as ArrayList<Message>
|
|
Log.d(LOG_TAG, "Restored ${MainActivity.messages.size} messages")
|
|
ois.close()
|
|
fis.close()
|
|
} catch (e: Exception) {
|
|
Log.w(LOG_TAG, "InputStream exception: - " + e.toString())
|
|
}
|
|
}
|
|
|
|
ContactsActivity.generateContacts(path + "/contacts")
|
|
|
|
Thread(Runnable { baresipStart(path) }).start()
|
|
BaresipService.IS_SERVICE_RUNNING = true
|
|
registerReceiver(nr, IntentFilter("android.net.conn.CONNECTIVITY_CHANGE"))
|
|
showStatusNotification()
|
|
if (!RUN_FOREGROUNG) super.onStartCommand(intent, flags, startId)
|
|
}
|
|
|
|
"UpdateNotification" -> {
|
|
updateStatusNotification()
|
|
}
|
|
|
|
"Stop" -> {
|
|
cleanStop()
|
|
if (RUN_FOREGROUNG) stopForeground(true)
|
|
stopSelf()
|
|
}
|
|
|
|
"Kill" -> {
|
|
stopSelf()
|
|
}
|
|
}
|
|
|
|
return START_STICKY
|
|
}
|
|
|
|
override fun onBind(intent: Intent): IBinder? {
|
|
return null
|
|
}
|
|
|
|
override fun onDestroy() {
|
|
Log.i(LOG_TAG, "In onDestroy")
|
|
super.onDestroy()
|
|
if (IS_SERVICE_RUNNING) {
|
|
Log.i(LOG_TAG, "Restart baresip killed by Android")
|
|
cleanStop()
|
|
val broadcastIntent = Intent("com.tutpro.baresip.Restart")
|
|
sendBroadcast(broadcastIntent)
|
|
}
|
|
}
|
|
|
|
private fun createNotificationChannels() {
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
val defaultChannel = NotificationChannel(DEFAULT_CHANNEL_ID, "Default",
|
|
NotificationManager.IMPORTANCE_DEFAULT)
|
|
defaultChannel.description = "Tells that baresip is running"
|
|
defaultChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
|
nm.createNotificationChannel(defaultChannel)
|
|
val highChannel = NotificationChannel(HIGH_CHANNEL_ID, "High",
|
|
NotificationManager.IMPORTANCE_HIGH)
|
|
highChannel.description = "Tells about incoming call or message"
|
|
highChannel.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
|
|
highChannel.enableVibration(true)
|
|
nm.createNotificationChannel(highChannel)
|
|
}
|
|
}
|
|
|
|
private fun showStatusNotification() {
|
|
snb.setVisibility(VISIBILITY_PUBLIC)
|
|
.setSmallIcon(R.drawable.ic_stat)
|
|
.setContentIntent(npi)
|
|
.setOngoing(true)
|
|
.setContent(RemoteViews(packageName, R.layout.status_notification))
|
|
if (RUN_FOREGROUNG)
|
|
startForeground(STATUS_NOTIFICATION_ID, snb.build())
|
|
else
|
|
nm.notify(STATUS_NOTIFICATION_ID, snb.build())
|
|
}
|
|
|
|
@Keep
|
|
fun uaAdd(uap: String) {
|
|
Log.d(LOG_TAG, "addUA at BaresipService")
|
|
val ua = UserAgent(uap)
|
|
MainActivity.uas.add(ua)
|
|
if (UserAgent.ua_isregistered(uap)) {
|
|
Log.d(LOG_TAG, "Ua ${ua.account.aor} is registered")
|
|
MainActivity.images.add(R.drawable.dot_green)
|
|
} else {
|
|
Log.d(LOG_TAG, "Ua ${ua.account.aor} is NOT registered")
|
|
MainActivity.images.add(R.drawable.dot_yellow)
|
|
ua.register()
|
|
}
|
|
val intent = Intent("service event")
|
|
intent.putExtra("event", "ua added")
|
|
intent.putExtra("params", arrayListOf(uap))
|
|
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
|
|
updateStatusNotification()
|
|
}
|
|
|
|
@Keep
|
|
fun uaEvent(event: String, uap: String, callp: String) {
|
|
Log.d(LOG_TAG, "updateStatus got event $event/$uap/$callp")
|
|
if (!IS_SERVICE_RUNNING) return
|
|
if (event == "exit") {
|
|
val intent = Intent("service event")
|
|
intent.putExtra("event", event)
|
|
intent.putExtra("params", arrayListOf<String>())
|
|
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
|
|
return
|
|
}
|
|
val ua = UserAgent.find(MainActivity.uas, uap)
|
|
if (ua == null) {
|
|
Log.w(LOG_TAG, "updateStatus did not find ua $uap")
|
|
return
|
|
}
|
|
val aor = ua.account.aor
|
|
for (account_index in MainActivity.uas.indices) {
|
|
if (MainActivity.uas[account_index].account.aor == aor) {
|
|
when (event) {
|
|
"registering" -> {
|
|
return
|
|
}
|
|
"registered" -> {
|
|
if (ua.account.regint == 0)
|
|
MainActivity.images[account_index] = R.drawable.dot_yellow
|
|
else
|
|
MainActivity.images[account_index] = R.drawable.dot_green
|
|
updateStatusNotification()
|
|
if (!Utils.isVisible()) return
|
|
}
|
|
"registering failed" -> {
|
|
MainActivity.images[account_index] = R.drawable.dot_red
|
|
updateStatusNotification()
|
|
if (!Utils.isVisible()) return
|
|
}
|
|
"unregistering" -> {
|
|
MainActivity.images[account_index] = R.drawable.dot_yellow
|
|
updateStatusNotification()
|
|
if (!Utils.isVisible()) return
|
|
}
|
|
"call ringing" -> {
|
|
return
|
|
}
|
|
"call incoming" -> {
|
|
if (!Utils.isVisible()) {
|
|
val cnb = NotificationCompat.Builder(this, HIGH_CHANNEL_ID)
|
|
cnb.setSmallIcon(R.drawable.ic_stat)
|
|
.setColor(0x0ca1fd)
|
|
.setContentIntent(npi)
|
|
.setAutoCancel(true)
|
|
.setContentTitle("Call from ${ContactsActivity.contactName(
|
|
Api.call_peeruri(callp))}")
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
|
cnb.setVibrate(LongArray(0))
|
|
.setVisibility(VISIBILITY_PRIVATE)
|
|
.setPriority(Notification.PRIORITY_HIGH)
|
|
}
|
|
/* val view = RemoteViews(getPackageName(), R.layout.call_notification)
|
|
view.setTextViewText(R.id.callFrom, "Call from ${Api.call_peeruri(callp)}")
|
|
cnb.setContent(view) */
|
|
val answerIntent = Intent(this, MainActivity::class.java)
|
|
answerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or
|
|
Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
|
answerIntent.putExtra("action", "answer")
|
|
answerIntent.putExtra("callp", callp)
|
|
val answerPendingIntent = PendingIntent.getActivity(this,
|
|
0, answerIntent, PendingIntent.FLAG_UPDATE_CURRENT)
|
|
val rejectIntent = Intent(this, MainActivity::class.java)
|
|
rejectIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or
|
|
Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
rejectIntent.putExtra("action", "reject")
|
|
rejectIntent.putExtra("callp", callp)
|
|
val rejectPendingIntent = PendingIntent.getActivity(this,
|
|
1, rejectIntent, PendingIntent.FLAG_UPDATE_CURRENT)
|
|
/* view.setOnClickPendingIntent(R.id.answerButton, answerPendingIntent)
|
|
view.setOnClickPendingIntent(R.id.rejectButton, rejectPendingIntent)
|
|
cnb.setCustomBigContentView(view) */
|
|
cnb.addAction(R.drawable.ic_stat, "Answer", answerPendingIntent)
|
|
cnb.addAction(R.drawable.ic_stat, "Reject", rejectPendingIntent)
|
|
nm.notify(CALL_NOTIFICATION_ID, cnb.build())
|
|
}
|
|
}
|
|
"call established", "call closed" -> {
|
|
nm.cancel(CALL_NOTIFICATION_ID)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
val intent = Intent("service event")
|
|
intent.putExtra("event", event)
|
|
intent.putExtra("params", arrayListOf(uap, callp))
|
|
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
|
|
}
|
|
|
|
@Keep
|
|
fun messageEvent(uap: String, peer: String, msg: ByteArray) {
|
|
var s = "Decoding of message failed!"
|
|
try {
|
|
s = String(msg, StandardCharsets.UTF_8)
|
|
} catch (e: Exception) {
|
|
Log.e(LOG_TAG, "UTF-8 decode failed")
|
|
}
|
|
Log.d(LOG_TAG, "Message event $uap/$peer/$s")
|
|
val timeStamp = System.currentTimeMillis().toString()
|
|
if (!Utils.isVisible()) {
|
|
val cnb = NotificationCompat.Builder(this, HIGH_CHANNEL_ID)
|
|
cnb.setSmallIcon(R.drawable.ic_stat)
|
|
.setColor(0x0ca1fd)
|
|
.setContentIntent(npi)
|
|
.setDefaults(Notification.DEFAULT_SOUND)
|
|
.setAutoCancel(true)
|
|
.setContentTitle("Message from ${ContactsActivity.contactName(peer)}")
|
|
.setContentText(s)
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
|
cnb.setVibrate(LongArray(0))
|
|
.setVisibility(VISIBILITY_PRIVATE)
|
|
.setPriority(Notification.PRIORITY_HIGH)
|
|
}
|
|
/* val view = RemoteViews(getPackageName(), R.layout.call_notification)
|
|
view.setTextViewText(R.id.callFrom, "Call from ${Api.call_peeruri(callp)}")
|
|
cnb.setContent(view) */
|
|
val replyIntent = Intent(this, MainActivity::class.java)
|
|
replyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or
|
|
Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
|
replyIntent.putExtra("action", "reply")
|
|
replyIntent.putExtra("uap", uap)
|
|
replyIntent.putExtra("peer", peer)
|
|
val replyPendingIntent = PendingIntent.getActivity(this,
|
|
0, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT)
|
|
val archiveIntent = Intent(this, MainActivity::class.java)
|
|
archiveIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or
|
|
Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
archiveIntent.putExtra("action", "archive")
|
|
archiveIntent.putExtra("uap", uap)
|
|
archiveIntent.putExtra("time", timeStamp)
|
|
val archivePendingIntent = PendingIntent.getActivity(this,
|
|
1, archiveIntent, PendingIntent.FLAG_UPDATE_CURRENT)
|
|
val deleteIntent = Intent(this, MainActivity::class.java)
|
|
deleteIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or
|
|
Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
deleteIntent.putExtra("action", "delete")
|
|
deleteIntent.putExtra("uap", uap)
|
|
deleteIntent.putExtra("time", timeStamp)
|
|
val deletePendingIntent = PendingIntent.getActivity(this,
|
|
2, deleteIntent, PendingIntent.FLAG_UPDATE_CURRENT)
|
|
/* view.setOnClickPendingIntent(R.id.answerButton, answerPendingIntent)
|
|
view.setOnClickPendingIntent(R.id.rejectButton, rejectPendingIntent)
|
|
cnb.setCustomBigContentView(view) */
|
|
cnb.addAction(R.drawable.ic_stat, "Reply", replyPendingIntent)
|
|
cnb.addAction(R.drawable.ic_stat, "Archive", archivePendingIntent)
|
|
cnb.addAction(R.drawable.ic_stat, "Delete", deletePendingIntent)
|
|
nm.notify(MESSAGE_NOTIFICATION_ID, cnb.build())
|
|
}
|
|
val intent = Intent("service event")
|
|
intent.putExtra("event", "message")
|
|
intent.putExtra("params", arrayListOf(uap, peer, s, timeStamp))
|
|
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
|
|
}
|
|
|
|
@Keep
|
|
fun messageResponse(responseCode: Int, time: String) {
|
|
Log.d(LOG_TAG, "Message response $responseCode at $time")
|
|
val intent = Intent("message response")
|
|
intent.putExtra("response code", responseCode)
|
|
intent.putExtra("time", time)
|
|
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
|
|
}
|
|
|
|
private fun updateStatusNotification() {
|
|
val contentView = RemoteViews(getPackageName(), R.layout.status_notification)
|
|
for (i: Int in 0 .. 5) {
|
|
val resID = resources.getIdentifier("status$i", "id", packageName)
|
|
if (i < MainActivity.images.size) {
|
|
contentView.setImageViewResource(resID, MainActivity.images[i])
|
|
contentView.setViewVisibility(resID, View.VISIBLE)
|
|
} else {
|
|
contentView.setViewVisibility(resID, View.INVISIBLE)
|
|
}
|
|
}
|
|
if (MainActivity.images.size > 4)
|
|
contentView.setViewVisibility(R.id.etc, View.VISIBLE)
|
|
else
|
|
contentView.setViewVisibility(R.id.etc, View.INVISIBLE)
|
|
snb.setContent(contentView)
|
|
nm.notify(STATUS_NOTIFICATION_ID, snb.build())
|
|
}
|
|
|
|
private fun cleanStop() {
|
|
CallsActivity.saveHistory()
|
|
MessagesActivity.saveMessages()
|
|
MainActivity.uas.clear()
|
|
MainActivity.images.clear()
|
|
MainActivity.history.clear()
|
|
MainActivity.messages.clear()
|
|
baresipStop()
|
|
BaresipService.IS_SERVICE_RUNNING = false
|
|
unregisterReceiver(nr)
|
|
nm.cancelAll()
|
|
if (wl.isHeld) wl.release()
|
|
if (fl.isHeld) fl.release()
|
|
}
|
|
|
|
external fun baresipStart(path: String)
|
|
external fun baresipStop()
|
|
|
|
companion object {
|
|
|
|
var IS_SERVICE_RUNNING = false
|
|
val STATUS_NOTIFICATION_ID = 101
|
|
val CALL_NOTIFICATION_ID = 102
|
|
val MESSAGE_NOTIFICATION_ID = 103
|
|
val DEFAULT_CHANNEL_ID = "com.tutpro.baresip.default"
|
|
val HIGH_CHANNEL_ID = "com.tutpro.baresip.high"
|
|
var disconnected = false
|
|
val RUN_FOREGROUNG = false
|
|
|
|
}
|
|
|
|
init {
|
|
System.loadLibrary("baresip")
|
|
}
|
|
}
|