introduced support for sending and receiving messages

This commit is contained in:
Juha Heinanen
2018-07-22 20:27:57 +03:00
parent 2c7c8ec8b9
commit 679c6855e0
20 changed files with 792 additions and 51 deletions

View File

@ -9,14 +9,18 @@
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-feature
android:name="android.hardware.telephony"
android:required="false" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
@ -36,9 +40,10 @@
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity"
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:launchMode="singleTask" >
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@ -47,7 +52,7 @@
<activity
android:name=".AccountsActivity"
android:label="Accounts"
android:parentActivityName=".MainActivity" >
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.tutpro.baresip.MainActivity" />
@ -64,8 +69,8 @@
<activity
android:name=".ContactsActivity"
android:label="Contacts"
android:windowSoftInputMode="adjustPan"
android:parentActivityName=".MainActivity" >
android:parentActivityName=".MainActivity"
android:windowSoftInputMode="adjustPan">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.tutpro.baresip.MainActivity" />
@ -74,7 +79,7 @@
android:name=".ContactActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:label="Contact"
android:parentActivityName=".MainActivity" >
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.tutpro.baresip.MainActivity" />
@ -95,6 +100,22 @@
android:name="android.support.PARENT_ACTIVITY"
android:value="com.tutpro.baresip.MainActivity" />
</activity>
<activity
android:name=".MessagesActivity"
android:label="Message History"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.tutpro.baresip.MainActivity" />
</activity>
<activity
android:name=".MessageActivity"
android:label="Message"
android:parentActivityName=".MessagesActivity" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.tutpro.baresip.MainActivity" />
</activity>
<activity
android:name=".AboutActivity"
android:label="About"
@ -104,21 +125,21 @@
android:value="com.tutpro.baresip.MainActivity" />
</activity>
<service android:name=".BaresipService"
<service
android:name=".BaresipService"
android:enabled="true" >
</service>
<receiver
android:name=".RunOnStartup"
android:enabled="true"
android:exported="true"
android:name="com.tutpro.baresip.RunOnStartup"
android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="com.tutpro.baresip.Restart" />
</intent-filter>
</receiver>
</application>
</manifest>

View File

@ -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 <unistd.h>
@ -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;

View File

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

View File

@ -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<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()
@ -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,8 +313,73 @@ 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)
}
@ -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

View File

@ -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<String>) {
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<Int>()
var history: ArrayList<History> = ArrayList()
internal var calls = ArrayList<Call>()
internal var messages = ArrayList<Message>()
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
}

View File

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

View File

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

View File

@ -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<Message>) :
ArrayAdapter<Message>(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
}
}

View File

@ -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<Message> {
val res = ArrayList<Message>()
for (m in MainActivity.messages.reversed()) {
if (m.aor == aor) {
res.add(m)
}
}
return res
}
companion object {
var uaMessages = ArrayList<Message>()
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()
}
}
}
}

View File

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

View File

@ -0,0 +1,5 @@
<vector android:height="48dp" android:tint="#00ACC1"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z"/>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,5 @@
<vector android:height="44dp" android:tint="#00ACC1"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="44dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM6,9h12v2L6,11L6,9zM14,14L6,14v-2h8v2zM18,8L6,8L6,6h12v2z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="48dp" android:tint="#00ACC1"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM17,13h-4v4h-2v-4L7,13v-2h4L11,7h2v4h4v2z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="36dp" android:tint="#00ACC1"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="36dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"/>
</vector>

View File

@ -100,16 +100,30 @@
</EditText>
<ImageButton
android:id="@+id/historyButton"
android:id="@+id/messagesButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/calleeRow"
android:layout_toStartOf="@id/callsButton"
android:padding="0dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="4dp"
android:background="@null"
android:src="@drawable/messages"
android:visibility="visible" >
</ImageButton>
<ImageButton
android:id="@+id/callsButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/calleeRow"
android:padding="0dp"
android:background="@null"
android:src="@drawable/clock"
android:src="@drawable/calls"
android:layout_marginEnd="0dp"
android:layout_alignParentEnd="true"
android:visibility="invisible" >
android:visibility="visible" >
</ImageButton>
</RelativeLayout>

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="16dp"
android:orientation="vertical"
tools:context="com.tutpro.baresip.MessageActivity" >
<TextView
android:id="@+id/messageToTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:textColor="@android:color/black"
android:text="Message to ..." >
</TextView>
<AutoCompleteTextView
android:id="@+id/receiver"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:inputType="textEmailAddress"
android:textSize="20sp"
android:hint="Contact name or SIP URI" >
<requestFocus />
</AutoCompleteTextView>
<EditText
android:id="@+id/text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:inputType="textMultiLine"
android:scrollHorizontally="false"
android:hint="Message">
</EditText>
<ImageButton
android:id="@+id/sendButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:src="@drawable/send"
android:background="@null" >
</ImageButton>
</LinearLayout>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.tutpro.baresip.MessagesActivity">
<ListView
android:id="@+id/message"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
<ImageButton
android:id="@+id/plusButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/plus"
android:background="@null"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true" >
</ImageButton>
</RelativeLayout>

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="0dp"
android:layout_marginTop="0dp" >
<LinearLayout
android:id="@+id/header"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp" >
<ImageView
android:id="@+id/direction"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
</ImageView>
<TextView
android:id="@+id/peer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:textSize="16sp"
android:text="" >
</TextView>
<TextView
android:id="@+id/time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:textSize="16sp"
android:text="" >
</TextView>
</LinearLayout>
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:textSize="16sp"
android:text="" >
</TextView>
</LinearLayout>

View File

@ -36,4 +36,5 @@
EMPTY value means that media encryption is not supported and that media transport protocol
thus is RTP/AVP.
</string>
<string name="title_activity_message">MessageActivity</string>
</resources>