diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1f668d14..5ed8cf75 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -9,14 +9,18 @@
+
+
+
+
@@ -36,9 +40,10 @@
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/AppTheme">
-
+ android:launchMode="singleTask">
@@ -47,7 +52,7 @@
+ android:parentActivityName=".MainActivity">
@@ -64,8 +69,8 @@
+ android:parentActivityName=".MainActivity"
+ android:windowSoftInputMode="adjustPan">
@@ -74,7 +79,7 @@
android:name=".ContactActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:label="Contact"
- android:parentActivityName=".MainActivity" >
+ android:parentActivityName=".MainActivity">
@@ -95,6 +100,22 @@
android:name="android.support.PARENT_ACTIVITY"
android:value="com.tutpro.baresip.MainActivity" />
+
+
+
+
+
+
-
-
+
-
diff --git a/app/src/main/cpp/baresip.c b/app/src/main/cpp/baresip.c
index a3d03158..43ee9e25 100644
--- a/app/src/main/cpp/baresip.c
+++ b/app/src/main/cpp/baresip.c
@@ -166,7 +166,7 @@ static void ua_event_handler(struct ua *ua, enum ua_event ev,
}
}
jmethodID statusId = (*env)->GetMethodID(env, pctx->mainActivityClz,
- "updateStatus",
+ "uaEvent",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
sprintf(ua_buf, "%lu", (unsigned long)ua);
jstring javaUA = (*env)->NewStringUTF(env, ua_buf);
@@ -180,16 +180,71 @@ static void ua_event_handler(struct ua *ua, enum ua_event ev,
(*env)->DeleteLocalRef(env, javaEvent);
}
-static void message_handler(const struct pl *peer, const struct pl *ctype,
+static void message_handler(struct ua *ua, const struct pl *peer, const struct pl *ctype,
struct mbuf *body, void *arg)
{
(void)ctype;
(void)arg;
+ char ua_buf[32];
+ char peer_buf[256];
+ char msg_buf[1024];
+ char utf_buf[2048];
+ char *in, *out;
+ int size;
LOGD("got message '%.*s' from peer '%.*s'", mbuf_get_left(body), mbuf_buf(body),
peer->l, peer->p);
- (void)play_file(NULL, baresip_player(), "message.wav", 0);
+ UpdateContext *pctx = (UpdateContext*)(&g_ctx);
+ JavaVM *javaVM = pctx->javaVM;
+ JNIEnv *env;
+ jint res = (*javaVM)->GetEnv(javaVM, (void**)&env, JNI_VERSION_1_6);
+ if (res != JNI_OK) {
+ res = (*javaVM)->AttachCurrentThread(javaVM, &env, NULL);
+ if (JNI_OK != res) {
+ LOGE("Failed to AttachCurrentThread, ErrorCode = %d", res);
+ return;
+ }
+ }
+ jmethodID methodId = (*env)->GetMethodID(env, pctx->mainActivityClz,
+ "messageEvent",
+ "(Ljava/lang/String;Ljava/lang/String;[B)V");
+ sprintf(ua_buf, "%lu", (unsigned long)ua);
+ jstring javaUA = (*env)->NewStringUTF(env, ua_buf);
+ sprintf(peer_buf, "%.*s", peer->l, peer->p);
+ jstring javaPeer = (*env)->NewStringUTF(env, peer_buf);
+ jbyteArray javaMsg;
+ size = mbuf_get_left(body);
+ javaMsg = (*env)->NewByteArray(env, size);
+ if ((*env)->GetArrayLength(env, javaMsg) != size) {
+ (*env)->DeleteLocalRef(env, javaMsg);
+ javaMsg = (*env)->NewByteArray(env, size);
+ }
+ void *temp = (*env)->GetPrimitiveArrayCritical(env, (jarray)javaMsg, 0);
+ memcpy(temp, mbuf_buf(body), size);
+ (*env)->ReleasePrimitiveArrayCritical(env, javaMsg, temp, 0);
+ LOGD("sending message %s/%s/%.*s\n", ua_buf, peer_buf, size, mbuf_buf(body));
+ (*env)->CallVoidMethod(env, pctx->mainActivityObj, methodId, javaUA, javaPeer, javaMsg);
+ (*env)->DeleteLocalRef(env, javaUA);
+ (*env)->DeleteLocalRef(env, javaPeer);
+ (*env)->DeleteLocalRef(env, javaMsg);
+}
+
+static void send_resp_handler(int err, const struct sip_msg *msg, void *arg)
+{
+ (void)arg;
+
+ LOGD("received message response %u\n", msg->scode);
+
+ if (err) {
+ (void)re_fprintf(stderr, " \x1b[31m%m\x1b[;m\n", err);
+ return;
+ }
+
+ if (msg->scode >= 300) {
+ (void)re_fprintf(stderr, " \x1b[31m%u %r\x1b[;m\n",
+ msg->scode, &msg->reason);
+ }
}
#include
@@ -332,7 +387,7 @@ Java_com_tutpro_baresip_BaresipService_baresipStart(JNIEnv *env, jobject instanc
sprintf(ua_buf, "%lu", (unsigned long) ua);
jstring javaUA = (*env)->NewStringUTF(env, ua_buf);
LOGD("adding UA for AoR %s/%s\n", ua_aor(ua), ua_buf);
- jmethodID accountId = (*env)->GetMethodID(env, pctx->mainActivityClz, "addUA",
+ jmethodID accountId = (*env)->GetMethodID(env, pctx->mainActivityClz, "uaAdd",
"(Ljava/lang/String;)V");
(*env)->CallVoidMethod(env, pctx->mainActivityObj, accountId, javaUA);
(*env)->DeleteLocalRef(env, javaUA);
@@ -909,6 +964,27 @@ Java_com_tutpro_baresip_MainActivity_call_1send_1digit(JNIEnv *env, jobject thiz
return res;
}
+JNIEXPORT jint JNICALL
+Java_com_tutpro_baresip_MessageActivity_message_1send(JNIEnv *env, jobject thiz, jstring javaUA,
+ jstring javaPeer, jstring javaMsg) {
+ struct ua *ua;
+ const char *native_ua = (*env)->GetStringUTFChars(env, javaUA, 0);
+ const char *native_peer = (*env)->GetStringUTFChars(env, javaPeer, 0);
+ const char *native_msg = (*env)->GetStringUTFChars(env, javaMsg, 0);
+
+ LOGD("sending message from ua %s to %s\n", native_ua, native_peer);
+ ua = (struct ua *)strtoul(native_ua, NULL, 10);
+ int err = message_send(ua, native_peer, native_msg, send_resp_handler, NULL);
+ if (err) {
+ LOGW("message_send failed with error %d\n", err);
+ }
+
+ (*env)->ReleaseStringUTFChars(env, javaUA, native_ua);
+ (*env)->ReleaseStringUTFChars(env, javaPeer, native_peer);
+ (*env)->ReleaseStringUTFChars(env, javaMsg, native_msg);
+ return err;
+}
+
JNIEXPORT void JNICALL
Java_com_tutpro_baresip_ContactsActivity_00024Companion_contacts_1remove(JNIEnv *env, jobject thiz) {
struct le *le;
diff --git a/app/src/main/kotlin/com/tutpro/baresip/Account.kt b/app/src/main/kotlin/com/tutpro/baresip/Account.kt
index 622adbed..304d12c7 100644
--- a/app/src/main/kotlin/com/tutpro/baresip/Account.kt
+++ b/app/src/main/kotlin/com/tutpro/baresip/Account.kt
@@ -91,7 +91,7 @@ class Account(val accp: String) {
return null
}
- fun findUA(aor: String): UserAgent? {
+ fun findUa(aor: String): UserAgent? {
for (ua in MainActivity.uas) {
if (ua.account.aor == aor) return ua
}
diff --git a/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt b/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt
index ff34b88b..4b5b583d 100644
--- a/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt
+++ b/app/src/main/kotlin/com/tutpro/baresip/BaresipService.kt
@@ -21,6 +21,10 @@ 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() {
@@ -120,6 +124,21 @@ class BaresipService: Service() {
}
}
+ file = File(path, "messages")
+ if (file.exists()) {
+ try {
+ val fis = FileInputStream(file)
+ val ois = ObjectInputStream(fis)
+ @SuppressWarnings("unchecked")
+ MainActivity.messages = ois.readObject() as ArrayList
+ 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()
@@ -191,7 +210,7 @@ class BaresipService: Service() {
}
@Keep
- fun addUA(uap: String) {
+ fun uaAdd(uap: String) {
Log.d(LOG_TAG, "addUA at BaresipService")
val ua = UserAgent(uap)
MainActivity.uas.add(ua)
@@ -205,14 +224,13 @@ class BaresipService: Service() {
}
val intent = Intent("service event")
intent.putExtra("event", "ua added")
- intent.putExtra("uap", uap)
- intent.putExtra("callp", "")
+ intent.putExtra("params", arrayListOf(uap))
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
updateStatusNotification()
}
@Keep
- fun updateStatus(event: String, uap: String, callp: String) {
+ fun uaEvent(event: String, uap: String, callp: String) {
Log.d(LOG_TAG, "updateStatus got event $event/$uap/$callp")
if (!IS_SERVICE_RUNNING) return
val ua = UserAgent.find(MainActivity.uas, uap)
@@ -295,11 +313,76 @@ class BaresipService: Service() {
}
val intent = Intent("service event")
intent.putExtra("event", event)
- intent.putExtra("uap", uap)
- intent.putExtra("callp", callp)
+ intent.putExtra("params", arrayListOf(uap, callp))
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
}
-
+
+ @Keep
+ fun messageEvent(uap: String, peer: String, msg: ByteArray) {
+ Log.d(LOG_TAG, "message event $uap/$peer")
+ var s = "Decoding of message failed!"
+ try {
+ s = String(msg, StandardCharsets.UTF_8)
+ Log.d(LOG_TAG, "UTF-8 $s")
+ } catch (e: Exception) {
+ Log.e(LOG_TAG, "UTF-8 decode failed")
+ }
+ 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)
+ }
+
private fun updateStatusNotification() {
val contentView = RemoteViews(getPackageName(), R.layout.status_notification)
for (i: Int in 0 .. 5) {
@@ -321,9 +404,11 @@ class BaresipService: Service() {
private fun cleanStop() {
HistoryActivity.saveHistory()
+ MessagesActivity.saveMessages()
MainActivity.uas.clear()
MainActivity.images.clear()
MainActivity.history.clear()
+ MainActivity.messages.clear()
baresipStop()
BaresipService.IS_SERVICE_RUNNING = false
unregisterReceiver(nr)
@@ -339,8 +424,9 @@ class BaresipService: Service() {
var IS_SERVICE_RUNNING = false
val STATUS_NOTIFICATION_ID = 101
- val DEFAULT_CHANNEL_ID = "com.tutpro.baresip.default"
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
diff --git a/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt
index 9c4b8e35..781accda 100644
--- a/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt
+++ b/app/src/main/kotlin/com/tutpro/baresip/MainActivity.kt
@@ -29,6 +29,7 @@ import android.widget.RelativeLayout
import android.widget.*
import android.view.*
import java.util.*
+import kotlin.collections.ArrayList
class MainActivity : AppCompatActivity() {
@@ -39,7 +40,8 @@ class MainActivity : AppCompatActivity() {
internal lateinit var securityButton: ImageButton
internal lateinit var callButton: ImageButton
internal lateinit var holdButton: ImageButton
- internal lateinit var historyButton: ImageButton
+ internal lateinit var messagesButton: ImageButton
+ internal lateinit var callsButton: ImageButton
internal lateinit var dtmf: EditText
internal lateinit var uaAdapter: UaSpinnerAdapter
internal lateinit var aorSpinner: Spinner
@@ -70,7 +72,8 @@ class MainActivity : AppCompatActivity() {
securityButton = findViewById(R.id.securityButton) as ImageButton
callButton = findViewById(R.id.callButton) as ImageButton
holdButton = findViewById(R.id.holdButton) as ImageButton
- historyButton = findViewById(R.id.historyButton) as ImageButton
+ messagesButton = findViewById(R.id.messagesButton) as ImageButton
+ callsButton = findViewById(R.id.callsButton) as ImageButton
dtmf = findViewById(R.id.dtmf) as EditText
am = getSystemService(Context.AUDIO_SERVICE) as AudioManager
@@ -83,8 +86,7 @@ class MainActivity : AppCompatActivity() {
serviceEventReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
handleServiceEvent(intent.getStringExtra("event"),
- intent.getStringExtra("uap"),
- intent.getStringExtra("callp"))
+ intent.getStringArrayListExtra("params"))
}
}
LocalBroadcastManager.getInstance(this).registerReceiver(serviceEventReceiver,
@@ -114,11 +116,6 @@ class MainActivity : AppCompatActivity() {
callee.hint = "Callee"
callButton.tag = "Call"
callButton.setImageResource(R.drawable.call)
- if (History.aorHistory(history, aor) > 0) {
- historyButton.visibility = View.VISIBLE
- } else {
- historyButton.visibility = View.INVISIBLE
- }
securityButton.visibility = View.INVISIBLE
dtmf.visibility = View.INVISIBLE
} else {
@@ -147,8 +144,8 @@ class MainActivity : AppCompatActivity() {
}
val view_count = layout.childCount
// Log.d("Baresip", "View count is $view_count")
- if (view_count > 7)
- layout.removeViews(7, view_count - 7)
+ if (view_count > 8)
+ layout.removeViews(8, view_count - 8)
for (c in Call.uaCalls(calls, ua, "in"))
for (call_index in Call.uaCalls(calls, ua, "in").indices) {
val startIndex = (call_index + 1) * 10
@@ -294,8 +291,16 @@ class MainActivity : AppCompatActivity() {
}
}
- historyButton.visibility = View.INVISIBLE
- historyButton.setOnClickListener {
+ messagesButton.setOnClickListener {
+ val i = Intent(this@MainActivity, MessagesActivity::class.java)
+ val b = Bundle()
+ b.putString("aor", uas[aorSpinner.selectedItemPosition].account.aor)
+ i.putExtras(b)
+ startActivityForResult(i, MESSAGES_CODE)
+ }
+
+ callsButton.visibility = View.VISIBLE
+ callsButton.setOnClickListener {
val i = Intent(this@MainActivity, HistoryActivity::class.java)
val b = Bundle()
b.putString("aor", uas[aorSpinner.selectedItemPosition].account.aor)
@@ -313,16 +318,17 @@ class MainActivity : AppCompatActivity() {
moveTaskToBack(true)
}
- private fun handleServiceEvent(event: String, uap: String, callp: String) {
+ private fun handleServiceEvent(event: String, params: ArrayList) {
+ val uap = params[0]
val ua = UserAgent.find(uas, uap)
if (ua == null) {
Log.w("Baresip", "handleServiceEvent '$event' did not find ua $uap")
return
}
+ val ev = event.split(",")
+ Log.d("Baresip", "Handling service event ${ev[0]} for $uap")
val aor = ua.account.aor
val acc = ua.account
- val ev = event.split(",")
- Log.d("Baresip", "Handling service event ${ev[0]} for $uap/$callp/$aor")
for (account_index in uas.indices) {
if (uas[account_index].account.aor == aor) {
when (ev[0]) {
@@ -334,6 +340,7 @@ class MainActivity : AppCompatActivity() {
uaAdapter.notifyDataSetChanged()
}
"call incoming" -> {
+ val callp = params[1]
val peer_uri = Api.call_peeruri(callp)
val new_call = Call(callp, ua, peer_uri, "in", "Answer", null)
if (ONE_CALL_ONLY && (calls.size > 0)) {
@@ -359,6 +366,7 @@ class MainActivity : AppCompatActivity() {
}
}
"call established" -> {
+ val callp = params[1]
val call = Call.find(calls, callp)
if (call == null) {
Log.e("Baresip", "Established call $callp not found")
@@ -424,6 +432,7 @@ class MainActivity : AppCompatActivity() {
History.aorRemoveHistory(history, aor)
}
"call verify" -> {
+ val callp = params[1]
val call = Call.find(calls, callp)
if (call == null) {
Log.e("Baresip", "Call $callp to be verified is not found")
@@ -478,6 +487,7 @@ class MainActivity : AppCompatActivity() {
verifyDialog.create().show()
}
"call verified", "call secure" -> {
+ val callp = params[1]
val call = Call.find(calls, callp)
if (call == null) {
Log.e("Baresip", "Call $callp that is verified is not found")
@@ -509,6 +519,7 @@ class MainActivity : AppCompatActivity() {
}
}
"call closed" -> {
+ val callp = params[1]
val call = Call.find(calls, callp)
if (call == null) {
Log.d("Baresip", "Call $callp that is closed is not found")
@@ -534,7 +545,7 @@ class MainActivity : AppCompatActivity() {
addCallViews(ua, callsIn[i], (i + 1) * 10)
}
}
- historyButton.visibility = View.VISIBLE
+ callsButton.visibility = View.VISIBLE
}
if (!call.hasHistory) {
if (History.aorHistory(history, aor) > HISTORY_SIZE)
@@ -558,7 +569,8 @@ class MainActivity : AppCompatActivity() {
securityButton.visibility = View.INVISIBLE
dtmf.removeTextChangedListener(call.dtmfWatcher)
dtmf.visibility = View.INVISIBLE
- historyButton.visibility = View.VISIBLE
+ messagesButton.visibility = View.VISIBLE
+ callsButton.visibility = View.VISIBLE
}
calls.remove(call)
if (!call.hasHistory) {
@@ -570,6 +582,25 @@ class MainActivity : AppCompatActivity() {
}
if (calls.size == 0) am.mode = AudioManager.MODE_NORMAL
}
+ "message" -> {
+ val peer_uri = params[1]
+ val msg = params[2]
+ val time = params[3]
+ val new_message = Message(aor, peer_uri, R.drawable.arrow_down_green, msg,
+ time.toLong(), true)
+ Log.d("Baresip", "Incoming message $aor/$peer_uri/$msg")
+ messages.add(new_message)
+ if (Utils.isVisible()) {
+ if (ua != uas[aorSpinner.selectedItemPosition])
+ aorSpinner.setSelection(account_index)
+ val i = Intent(applicationContext, MessagesActivity::class.java)
+ i.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
+ val b = Bundle()
+ b.putString("aor", ua.account.aor)
+ i.putExtras(b)
+ startActivity(i)
+ }
+ }
else -> Log.w("Baresip", "Unknown event '${ev[0]}'")
}
break
@@ -581,16 +612,52 @@ class MainActivity : AppCompatActivity() {
setIntent(intent)
val action = intent.getStringExtra("action")
Log.d("Baresip", "Got onNewIntent action $action")
- if (action == null) return
- val callp = intent.getStringExtra("callp")
when (action) {
"answer" -> {
- answerCall = callp
+ answerCall = intent.getStringExtra("callp")
}
"reject" -> {
- rejectCall = callp
+ rejectCall = intent.getStringExtra("callp")
moveTaskToBack(true)
}
+ "reply", "archive", "delete" -> {
+ val uap = intent.getStringExtra("uap")
+ val ua = UserAgent.find(MainActivity.uas, uap)
+ if (ua == null) {
+ Log.e("Baresip", "onNewIntent did not find ua $uap")
+ return
+ }
+ val aor = ua.account.aor
+ when (action) {
+ "reply" -> {
+ val i = Intent(this@MainActivity, MessagesActivity::class.java)
+ val b = Bundle()
+ if (ua != uas[aorSpinner.selectedItemPosition]) {
+ for (account_index in uas.indices) {
+ if (uas[account_index].account.aor == aor) {
+ aorSpinner.setSelection(account_index)
+ break
+ }
+ }
+ }
+ b.putString("aor", aor)
+ b.putString("peer", intent.getStringExtra("peer"))
+ i.putExtras(b)
+ startActivityForResult(i, MESSAGES_CODE)
+ }
+ "archive" -> {
+ MessagesActivity.archiveUaMessage(ua.account.aor,
+ intent.getStringExtra("time").toLong())
+ moveTaskToBack(true)
+ }
+ "delete" -> {
+ MessagesActivity.deleteUaMessage(ua.account.aor,
+ intent.getStringExtra("time").toLong())
+ moveTaskToBack(true)
+ }
+ }
+ nm.cancel(BaresipService.MESSAGE_NOTIFICATION_ID)
+ }
}
}
@@ -605,13 +672,13 @@ class MainActivity : AppCompatActivity() {
Log.d("Baresip", "Resumed")
imm.hideSoftInputFromWindow(callee.windowToken, 0)
visible = true
- if ((answerCall != "") && (layout.childCount > 7)) {
+ if ((answerCall != "") && (layout.childCount > 8)) {
answerCall = ""
/* if multiple calls, right answer button needs to be searched */
val answerButton = layout.findViewById(10 + 5) as ImageButton
answerButton.performClick()
}
- if ((rejectCall != "") && (layout.childCount > 7)) {
+ if ((rejectCall != "") && (layout.childCount > 8)) {
rejectCall = ""
/* if multiple calls, right reject button needs to be searched */
val rejectButton = layout.findViewById(10 + 6) as ImageButton
@@ -996,6 +1063,7 @@ class MainActivity : AppCompatActivity() {
internal var images = ArrayList()
var history: ArrayList = ArrayList()
internal var calls = ArrayList()
+ internal var messages = ArrayList()
var filesPath = ""
var visible = true
var answerCall = ""
@@ -1008,9 +1076,12 @@ class MainActivity : AppCompatActivity() {
const val ABOUT_CODE = 5
const val ACCOUNT_CODE = 6
const val CONTACT_CODE = 7
+ const val MESSAGES_CODE = 8
+ const val MESSAGE_CODE = 9
const val RECORD_AUDIO_PERMISSION = 1
const val HISTORY_SIZE = 100
+ const val MESSAGE_HISTORY_SIZE = 100
const val ONE_CALL_ONLY = true
}
diff --git a/app/src/main/kotlin/com/tutpro/baresip/Message.kt b/app/src/main/kotlin/com/tutpro/baresip/Message.kt
new file mode 100644
index 00000000..9d200950
--- /dev/null
+++ b/app/src/main/kotlin/com/tutpro/baresip/Message.kt
@@ -0,0 +1,7 @@
+package com.tutpro.baresip
+
+import java.io.Serializable
+
+class Message(val aor: String, val peerURI: String, val direction: Int, val message: String,
+ val timeStamp: Long, var new: Boolean): Serializable {
+}
diff --git a/app/src/main/kotlin/com/tutpro/baresip/MessageActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/MessageActivity.kt
new file mode 100644
index 00000000..6d85f7ac
--- /dev/null
+++ b/app/src/main/kotlin/com/tutpro/baresip/MessageActivity.kt
@@ -0,0 +1,102 @@
+package com.tutpro.baresip
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.support.v7.app.AppCompatActivity
+import android.util.Log
+import android.view.MenuItem
+import android.view.inputmethod.InputMethodManager
+import android.widget.*
+
+class MessageActivity : AppCompatActivity() {
+
+ internal lateinit var receiver: AutoCompleteTextView
+ internal lateinit var message: EditText
+ internal lateinit var sendButton: ImageButton
+ internal lateinit var imm: InputMethodManager
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setContentView(R.layout.activity_message)
+
+ val aor = intent.extras.getString("aor")
+ val peerURI = intent.extras.getString("peer")
+
+ val ua = Account.findUa(aor)
+ if (ua == null) {
+ Log.e("Baresip", "MessageActivity did not find ua of $aor")
+ val i = Intent()
+ setResult(Activity.RESULT_CANCELED, i)
+ finish()
+ }
+
+ val title = findViewById(R.id.messageToTitle) as TextView
+ message = findViewById(R.id.text) as EditText
+ receiver = findViewById(R.id.receiver) as AutoCompleteTextView
+
+ imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+
+ if (peerURI == "") {
+ title.text = "Message to ..."
+ receiver.threshold = 2
+ receiver.setAdapter(ArrayAdapter(this, android.R.layout.select_dialog_item,
+ ContactsActivity.contacts.map { Contact -> Contact.name }))
+ receiver.requestFocus()
+ } else {
+ title.text = "Reply to ..."
+ receiver.setText(ContactsActivity.contactName(peerURI), false)
+ message.requestFocus()
+ }
+ imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY)
+
+ sendButton = findViewById(R.id.sendButton) as ImageButton
+ sendButton.setOnClickListener {
+ val receiverText = receiver.text.toString()
+ val msg = message.text.toString()
+ if (receiverText.length > 0) {
+ var uri = ContactsActivity.findContactURI(receiverText)
+ if (!uri.startsWith("sip:")) uri = "sip:$uri"
+ if (!uri.contains("@")) {
+ val host = aor.substring(aor.indexOf("@") + 1)
+ uri = "$uri@$host"
+ }
+ if (msg.length > 0) {
+ imm.hideSoftInputFromWindow(receiver.getWindowToken(), 0)
+ imm.hideSoftInputFromWindow(message.getWindowToken(), 0)
+ val res = message_send(ua!!.uap, uri, msg)
+ if (res != 0) {
+ Toast.makeText(getApplicationContext(), "Sending of message failed!",
+ Toast.LENGTH_SHORT).show()
+ } else {
+ val new_message = Message(aor, uri, R.drawable.arrow_up_green, msg,
+ System.currentTimeMillis(), false)
+ MainActivity.messages.add(new_message)
+ MessagesActivity.uaMessages.add(0, new_message)
+ val i = Intent()
+ setResult(Activity.RESULT_OK, i)
+ finish()
+ }
+ }
+ }
+ }
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ android.R.id.home -> {
+ imm.hideSoftInputFromWindow(receiver.getWindowToken(), 0)
+ imm.hideSoftInputFromWindow(message.getWindowToken(), 0)
+ val i = Intent()
+ setResult(Activity.RESULT_CANCELED, i)
+ finish()
+ }
+ }
+ return true
+ }
+
+ external fun message_send(uap: String, peer_uri: String, message: String) : Int
+
+}
diff --git a/app/src/main/kotlin/com/tutpro/baresip/MessageListAdapter.kt b/app/src/main/kotlin/com/tutpro/baresip/MessageListAdapter.kt
new file mode 100644
index 00000000..b381a6c8
--- /dev/null
+++ b/app/src/main/kotlin/com/tutpro/baresip/MessageListAdapter.kt
@@ -0,0 +1,47 @@
+package com.tutpro.baresip
+
+import android.content.Context
+import android.graphics.Typeface
+import android.text.format.DateUtils.isToday
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ArrayAdapter
+import android.widget.ImageView
+import android.widget.TextView
+import java.text.SimpleDateFormat
+import java.util.*
+
+class MessageListAdapter(private val cxt: Context, private val rows: ArrayList) :
+ ArrayAdapter(cxt, R.layout.message, rows) {
+
+ override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
+ val message = rows[position]
+ val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
+ val messageView = inflater.inflate(R.layout.message, parent, false)
+ val directionView = messageView.findViewById(R.id.direction) as ImageView
+ directionView.setImageResource(message.direction)
+ val peerView = messageView.findViewById(R.id.peer) as TextView
+ peerView.text = ContactsActivity.contactName(message.peerURI)
+ val timeView = messageView.findViewById(R.id.time) as TextView
+ val time: String
+ val cal = GregorianCalendar()
+ cal.timeInMillis = message.timeStamp
+ if (isToday(message.timeStamp)) {
+ val fmt = SimpleDateFormat("HH:mm")
+ time = fmt.format(cal.time)
+ } else {
+ val fmt = SimpleDateFormat("dd.MM")
+ time = fmt.format(cal.time)
+ }
+ timeView.text = time
+ val textView = messageView.findViewById(R.id.text) as TextView
+ textView.text = message.message
+ if (message.new) {
+ textView.setTypeface(null, Typeface.BOLD)
+ message.new = false
+ }
+ return messageView
+ }
+
+}
diff --git a/app/src/main/kotlin/com/tutpro/baresip/MessagesActivity.kt b/app/src/main/kotlin/com/tutpro/baresip/MessagesActivity.kt
new file mode 100644
index 00000000..03a6ae70
--- /dev/null
+++ b/app/src/main/kotlin/com/tutpro/baresip/MessagesActivity.kt
@@ -0,0 +1,166 @@
+package com.tutpro.baresip
+
+import android.app.Activity
+import android.content.DialogInterface
+import android.content.Intent
+import android.os.Bundle
+import android.support.v7.app.AlertDialog
+import android.support.v7.app.AppCompatActivity
+import android.util.Log
+import android.view.MenuItem
+import android.widget.AdapterView
+import android.widget.ImageButton
+import android.widget.ListView
+import java.io.File
+import java.io.FileOutputStream
+import java.io.IOException
+import java.io.ObjectOutputStream
+import java.util.*
+
+class MessagesActivity: AppCompatActivity() {
+
+ internal lateinit var aor: String
+ internal lateinit var mlAdapter: MessageListAdapter
+ internal lateinit var plusButton: ImageButton
+
+ public override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_messages)
+
+ val listView = findViewById(R.id.message) as ListView
+ plusButton = findViewById(R.id.plusButton) as ImageButton
+
+ aor = intent.extras.getString("aor")
+ uaMessages = uaMessages(aor)
+
+ mlAdapter = MessageListAdapter(this, uaMessages)
+ listView.adapter = mlAdapter
+ listView.isLongClickable = true
+
+ listView.onItemClickListener = AdapterView.OnItemClickListener { _, _, pos, _ ->
+ val i = Intent(this@MessagesActivity, MessageActivity::class.java)
+ val b = Bundle()
+ b.putString("aor", aor)
+ b.putString("peer", uaMessages[pos].peerURI)
+ i.putExtras(b)
+ startActivityForResult(i, MainActivity.MESSAGE_CODE)
+ }
+
+ listView.onItemLongClickListener = AdapterView.OnItemLongClickListener { _, _, pos, _ ->
+ val dialogClickListener = DialogInterface.OnClickListener { _, which ->
+ when (which) {
+ DialogInterface.BUTTON_NEGATIVE -> {
+ MainActivity.messages.remove(uaMessages[pos])
+ uaMessages.removeAt(pos)
+ if (uaMessages.size == 0) {
+ val i = Intent()
+ setResult(Activity.RESULT_CANCELED, i)
+ finish()
+ }
+ mlAdapter.notifyDataSetChanged()
+ }
+ DialogInterface.BUTTON_POSITIVE -> {
+ }
+ }
+ }
+
+ val builder = AlertDialog.Builder(this@MessagesActivity,
+ R.style.Theme_AppCompat)
+ builder.setMessage("Delete message from " +
+ ContactsActivity.contactName(uaMessages[pos].peerURI) + "?")
+ .setPositiveButton("Cancel", dialogClickListener)
+ .setNegativeButton("Delete", dialogClickListener)
+ .show()
+ true
+ }
+
+ plusButton.setOnClickListener {
+ val i = Intent(this@MessagesActivity, MessageActivity::class.java)
+ val b = Bundle()
+ b.putString("aor", aor)
+ b.putString("peer", "")
+ i.putExtras(b)
+ startActivityForResult(i, MainActivity.MESSAGE_CODE)
+ }
+
+ val peer = intent.extras.getString("peer")
+ if (peer != null) {
+ val i = Intent(this@MessagesActivity, MessageActivity::class.java)
+ val b = Bundle()
+ b.putString("aor", aor)
+ b.putString("peer", peer)
+ i.putExtras(b)
+ startActivityForResult(i, MainActivity.MESSAGE_CODE)
+ }
+
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ android.R.id.home -> {
+ Log.d("Baresip", "Back array was pressed at Messages")
+ val i = Intent()
+ setResult(Activity.RESULT_CANCELED, i)
+ finish()
+ }
+ }
+ return true
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ when (requestCode) {
+ MainActivity.MESSAGE_CODE -> {
+ if (resultCode == RESULT_OK) {
+ mlAdapter.notifyDataSetChanged()
+ }
+ }
+ }
+ }
+
+ private fun uaMessages(aor: String) : ArrayList {
+ val res = ArrayList()
+ for (m in MainActivity.messages.reversed()) {
+ if (m.aor == aor) {
+ res.add(m)
+ }
+ }
+ return res
+ }
+
+ companion object {
+
+ var uaMessages = ArrayList()
+
+ fun archiveUaMessage(aor: String, time: Long) {
+ for (i in MainActivity.messages.indices.reversed())
+ if ((MainActivity.messages[i].aor == aor) &&
+ (MainActivity.messages[i].timeStamp == time)) {
+ MainActivity.messages[i].new = false
+ return
+ }
+ }
+
+ fun deleteUaMessage(aor: String, time: Long) {
+ for (i in MainActivity.messages.indices.reversed())
+ if ((MainActivity.messages[i].aor == aor) &&
+ (MainActivity.messages[i].timeStamp == time)) {
+ MainActivity.messages.removeAt(i)
+ return
+ }
+ }
+
+ fun saveMessages() {
+ val file = File(MainActivity.filesPath, "messages")
+ try {
+ val fos = FileOutputStream(file)
+ val oos = ObjectOutputStream(fos)
+ oos.writeObject(MainActivity.messages)
+ oos.close()
+ fos.close()
+ } catch (e: IOException) {
+ Log.w("Baresip", "OutputStream exception: " + e.toString())
+ e.printStackTrace()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/com/tutpro/baresip/RunOnStartup.kt b/app/src/main/kotlin/com/tutpro/baresip/RunOnStartup.kt
index 02f15778..fb811d78 100644
--- a/app/src/main/kotlin/com/tutpro/baresip/RunOnStartup.kt
+++ b/app/src/main/kotlin/com/tutpro/baresip/RunOnStartup.kt
@@ -11,7 +11,7 @@ class RunOnStartup : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if ((intent.action == Intent.ACTION_BOOT_COMPLETED) or
(intent.action == "com.tutpro.baresip.Restart")) {
- Log.d("Baresip", "Start baresip upon boot completed or restart")
+ Log.d("Baresip", "Start baresip upon boot completed or restart")
val i = Intent(context, MainActivity::class.java)
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val b = Bundle()
diff --git a/app/src/main/res/drawable/calls.xml b/app/src/main/res/drawable/calls.xml
new file mode 100644
index 00000000..6095689a
--- /dev/null
+++ b/app/src/main/res/drawable/calls.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/clock.png b/app/src/main/res/drawable/clock.png
deleted file mode 100644
index b4fa3631..00000000
Binary files a/app/src/main/res/drawable/clock.png and /dev/null differ
diff --git a/app/src/main/res/drawable/messages.xml b/app/src/main/res/drawable/messages.xml
new file mode 100644
index 00000000..5e3cf2ad
--- /dev/null
+++ b/app/src/main/res/drawable/messages.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/plus.xml b/app/src/main/res/drawable/plus.xml
new file mode 100644
index 00000000..3c1f7c0b
--- /dev/null
+++ b/app/src/main/res/drawable/plus.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/send.xml b/app/src/main/res/drawable/send.xml
new file mode 100644
index 00000000..b4200017
--- /dev/null
+++ b/app/src/main/res/drawable/send.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index ede58845..18273238 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -100,16 +100,30 @@
+
+
+
+ android:visibility="visible" >
diff --git a/app/src/main/res/layout/activity_message.xml b/app/src/main/res/layout/activity_message.xml
new file mode 100644
index 00000000..3c661bf2
--- /dev/null
+++ b/app/src/main/res/layout/activity_message.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_messages.xml b/app/src/main/res/layout/activity_messages.xml
new file mode 100644
index 00000000..61b01871
--- /dev/null
+++ b/app/src/main/res/layout/activity_messages.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/message.xml b/app/src/main/res/layout/message.xml
new file mode 100644
index 00000000..7d3e1939
--- /dev/null
+++ b/app/src/main/res/layout/message.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 23bbc2ef..b965faf2 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -36,4 +36,5 @@
EMPTY value means that media encryption is not supported and that media transport protocol
thus is RTP/AVP.
+ MessageActivity