major change: use service to run baresip

This commit is contained in:
Juha Heinanen
2018-06-21 19:31:45 +03:00
parent cc7f34bfdd
commit 4388c20ec9
9 changed files with 675 additions and 585 deletions
+7 -4
View File
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tutpro.baresip" package="com.tutpro.baresip">
android:installLocation="auto">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.RECORD_AUDIO" />
@@ -38,10 +37,10 @@
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<activity <activity android:name=".MainActivity"
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|screenSize" android:configChanges="orientation|keyboardHidden|screenSize"
android:launchMode="singleTask" > android:launchMode="singleTask" >
<intent-filter> <intent-filter>
@@ -108,6 +107,10 @@
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value="com.tutpro.baresip.MainActivity" /> android:value="com.tutpro.baresip.MainActivity" />
</activity> </activity>
<service android:name=".BaresipService" >
</service>
</application> </application>
</manifest> </manifest>
@@ -0,0 +1,7 @@
package com.tutpro.baresip
object Api {
external fun call_peeruri(callp: String): String
}
@@ -0,0 +1,271 @@
package com.tutpro.baresip
import android.app.Notification
import android.app.Notification.VISIBILITY_PUBLIC
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.NetworkInfo
import android.os.IBinder
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
class BaresipService: Service() {
private val LOG_TAG = "Baresip Service"
internal lateinit var intent: Intent
internal lateinit var nm: NotificationManager
internal lateinit var nb: NotificationCompat.Builder
internal lateinit var ni: Intent
internal lateinit var npi: PendingIntent
internal lateinit var nr: BroadcastReceiver
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
nb = NotificationCompat.Builder(this)
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 {
when (intent.getAction()) {
"Start" -> {
Log.i(LOG_TAG, "Received Start Foreground Intent ");
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<History>
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())
}
}
ContactsActivity.generateContacts(path + "/contacts")
Thread(Runnable { baresipStart(path) }).start()
BaresipService.IS_SERVICE_RUNNING = true
registerReceiver(nr, IntentFilter("android.net.conn.CONNECTIVITY_CHANGE"))
showNotification()
}
"UpdateNotification" -> {
Log.i(LOG_TAG, "UpdateNotification")
updateNotification()
}
"Stop" -> {
Log.i(LOG_TAG, "Received Stop Foreground Intent")
baresipStop()
BaresipService.IS_SERVICE_RUNNING = false
unregisterReceiver(nr)
stopForeground(true)
stopSelf()
}
}
return START_STICKY;
}
private fun showNotification() {
nb.setVisibility(VISIBILITY_PUBLIC)
.setSmallIcon(R.drawable.ic_stat)
.setContentIntent(npi)
.setPriority(Notification.PRIORITY_MAX)
.setOngoing(true)
.setContent(RemoteViews(packageName, R.layout.notification))
startForeground(STATUS_NOTIFICATION_ID, nb.build());
}
@Keep
fun addUA(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("uap", uap)
intent.putExtra("callp", "")
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
updateNotification()
}
@Keep
fun updateStatus(event: String, uap: String, callp: String) {
Log.d(LOG_TAG, "updateStatus got event $event")
if (!IS_SERVICE_RUNNING) return
val ua = UserAgent.find(MainActivity.uas, uap)
if (ua == null) {
Log.e(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", "unregistering" -> {
return
}
"registered" -> {
MainActivity.images[account_index] = R.drawable.dot_green
updateNotification()
if (!MainActivity.visible) return
}
"registering failed" -> {
MainActivity.images[account_index] = R.drawable.dot_red
updateNotification()
if (!MainActivity.visible) return
}
"call ringing" -> {
return
}
"call incoming" -> {
if (!Utils.isVisible()) {
Log.d(LOG_TAG, "Baresip is NOT visible")
val peer_uri = Api.call_peeruri(callp)
val huBuilder = NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_stat)
.setContentText("Incoming call from $peer_uri")
.setDefaults(Notification.DEFAULT_ALL)
.setPriority(Notification.PRIORITY_HIGH)
.setAutoCancel(true)
.setContentIntent(npi)
nm.notify(INCOMING_NOTIFICATION_ID, huBuilder.build())
}
}
"call established", "call closed" -> {
nb.mActions.clear()
nb.mContentText = ""
nm.notify(STATUS_NOTIFICATION_ID, nb.build())
}
}
}
}
val intent = Intent("service event")
intent.putExtra("event", event)
intent.putExtra("uap", uap)
intent.putExtra("callp", callp)
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
}
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onDestroy() {
Log.i(LOG_TAG, "In onDestroy")
super.onDestroy();
}
fun updateNotification() {
val contentView = RemoteViews(getPackageName(), R.layout.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)
nb.setContent(contentView)
nm.notify(STATUS_NOTIFICATION_ID, nb.build())
}
external fun baresipStart(path: String)
external fun baresipStop()
companion object {
var IS_SERVICE_RUNNING = false
val STATUS_NOTIFICATION_ID = 101
val INCOMING_NOTIFICATION_ID = 102
var disconnected = false
}
init {
System.loadLibrary("baresip")
}
}
@@ -85,14 +85,14 @@ class ContactsActivity : AppCompatActivity() {
fun generateContacts(path: String) { fun generateContacts(path: String) {
val content = Utils.getFileContents(File(path)) val content = Utils.getFileContents(File(path))
MainActivity.contacts_remove() contacts_remove()
contacts.clear() contacts.clear()
content.lines().forEach { content.lines().forEach {
val parts = it.split("\"") val parts = it.split("\"")
if (parts.size == 3) { if (parts.size == 3) {
val name = parts[1] val name = parts[1]
val uri = parts[2].trim() val uri = parts[2].trim()
MainActivity.contact_add("\"$name\" $uri") contact_add("\"$name\" $uri")
contacts.add(Contact(name, uri)) contacts.add(Contact(name, uri))
} }
} }
@@ -121,6 +121,10 @@ class ContactsActivity : AppCompatActivity() {
if (c.name.equals(name, ignoreCase = true)) return true if (c.name.equals(name, ignoreCase = true)) return true
return false return false
} }
external fun contacts_remove()
external fun contact_add(contact: String)
} }
} }
@@ -10,6 +10,10 @@ import android.util.Log
import android.view.MenuItem import android.view.MenuItem
import android.widget.AdapterView import android.widget.AdapterView
import android.widget.ListView import android.widget.ListView
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.ObjectOutputStream
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.ArrayList import java.util.ArrayList
@@ -147,4 +151,20 @@ class HistoryActivity : AppCompatActivity() {
now.get(Calendar.DAY_OF_MONTH) == time.get(Calendar.DAY_OF_MONTH) now.get(Calendar.DAY_OF_MONTH) == time.get(Calendar.DAY_OF_MONTH)
} }
companion object {
fun saveHistory() {
val file = File(MainActivity.filesPath, "history")
try {
val fos = FileOutputStream(file)
val oos = ObjectOutputStream(fos)
oos.writeObject(MainActivity.history)
oos.close()
fos.close()
} catch (e: IOException) {
Log.w("Baresip", "OutputStream exception: " + e.toString())
e.printStackTrace()
}
}
}
} }
File diff suppressed because it is too large Load Diff
+24 -19
View File
@@ -4,13 +4,11 @@ import android.app.ActivityManager
import android.content.Context import android.content.Context
import android.support.v7.app.AlertDialog import android.support.v7.app.AlertDialog
import android.util.Log import android.util.Log
import android.net.ConnectivityManager
import java.io.*
import android.os.Build
import android.os.PowerManager import android.os.PowerManager
import android.app.KeyguardManager import android.app.KeyguardManager
import java.io.*
object Utils { object Utils {
fun getFileContents(file: File): String { fun getFileContents(file: File): String {
@@ -56,6 +54,25 @@ object Utils {
} }
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("Baresip", "Failed to read asset " + asset + ": " +
e.toString())
}
}
fun alertView(context: Context, title: String, message: String) { fun alertView(context: Context, title: String, message: String) {
// val alertDialog = AlertDialog.Builder(context, android.R.style.Theme_Material_Dialog_Alert).create() // val alertDialog = AlertDialog.Builder(context, android.R.style.Theme_Material_Dialog_Alert).create()
val alertDialog = AlertDialog.Builder(context).create() val alertDialog = AlertDialog.Builder(context).create()
@@ -142,22 +159,10 @@ object Utils {
return res return res
} }
fun isConnected(context: Context): Boolean { fun isVisible(): Boolean {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val activeNetwork = cm.activeNetworkInfo
if (activeNetwork != null && activeNetwork.isConnected) {
val networkType = activeNetwork.type
return networkType == ConnectivityManager.TYPE_WIFI || networkType == ConnectivityManager.TYPE_MOBILE
} else {
return false
}
}
fun foregrounded(): Boolean {
val appProcessInfo = ActivityManager.RunningAppProcessInfo() val appProcessInfo = ActivityManager.RunningAppProcessInfo()
ActivityManager.getMyMemoryState(appProcessInfo); ActivityManager.getMyMemoryState(appProcessInfo)
return ((appProcessInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) or return appProcessInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
(appProcessInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE))
} }
fun isDeviceLocked(context: Context): Boolean { fun isDeviceLocked(context: Context): Boolean {
@@ -21,7 +21,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="18sp" android:textSize="18sp"
android:textColor="@android:color/black" android:textColor="@android:color/black"
android:onClick="onClick"
android:focusable="true" android:focusable="true"
android:focusableInTouchMode="true" android:focusableInTouchMode="true"
android:text="Display Name" > android:text="Display Name" >
@@ -42,7 +41,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="18sp" android:textSize="18sp"
android:textColor="@android:color/black" android:textColor="@android:color/black"
android:onClick="onClick"
android:text="Authentication Username" > android:text="Authentication Username" >
</TextView> </TextView>
@@ -60,7 +58,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="18sp" android:textSize="18sp"
android:textColor="@android:color/black" android:textColor="@android:color/black"
android:onClick="onClick"
android:text="Authentication Password" > android:text="Authentication Password" >
</TextView> </TextView>
@@ -79,7 +76,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="18sp" android:textSize="18sp"
android:textColor="@android:color/black" android:textColor="@android:color/black"
android:onClick="onClick"
android:text="Outbound Proxies" > android:text="Outbound Proxies" >
</TextView> </TextView>
@@ -107,7 +103,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="18sp" android:textSize="18sp"
android:textColor="@android:color/black" android:textColor="@android:color/black"
android:onClick="onClick"
android:text="Registration Interval (sec)" > android:text="Registration Interval (sec)" >
</TextView> </TextView>
@@ -125,7 +120,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="18sp" android:textSize="18sp"
android:textColor="@android:color/black" android:textColor="@android:color/black"
android:onClick="onClick"
android:text="Audio Codecs" > android:text="Audio Codecs" >
</TextView> </TextView>
@@ -143,7 +137,6 @@
android:paddingTop="10dp" android:paddingTop="10dp"
android:textSize="18sp" android:textSize="18sp"
android:textColor="@android:color/black" android:textColor="@android:color/black"
android:onClick="onClick"
android:text="Media Encryption" > android:text="Media Encryption" >
</TextView> </TextView>
+2 -4
View File
@@ -27,10 +27,8 @@
<string name="mediaEnc">Selects media transport encryption protocol.\n <string name="mediaEnc">Selects media transport encryption protocol.\n
ZRTP (recommended) means that ZRTP end-to-end media encryption negotiation is tried after ZRTP (recommended) means that ZRTP end-to-end media encryption negotiation is tried after
the call has been established.\n the call has been established.\n
DTLS-SRTP means that UDP/TLS/RTP/SAVP is offered in outgoing call and that RTP/SAVP, DTLS-SRTPF means that UDP/TLS/RTP/SAVPF is offered in outgoing call and that RTP/SAVP,
RTP/SAVPF, or UDP/TLS/RTP/SAVP is used if offered in incoming call.\n RTP/SAVPF, UDP/TLS/RTP/SAVP, or UDP/TLS/RTP/SAVPF is used if offered in incoming call.\n
SRTP-MANF means that RTP/SAVPS is offered in outgoing call and that RTP/SAVP or RTP/SAVPF
is used if offered in incoming call.\n
SRTP-MAND means that RTP/SAVP is offered in outgoing call and that RTP/SAVP or RTP/SAVPF SRTP-MAND means that RTP/SAVP is offered in outgoing call and that RTP/SAVP or RTP/SAVPF
is used if offered in incoming call.\n is used if offered in incoming call.\n
SRTP means that RTP/AVP is offered in outgoing call and that RTP/SAVP or RTP/SAVPF is used SRTP means that RTP/AVP is offered in outgoing call and that RTP/SAVP or RTP/SAVPF is used