since this is a fun learning project, moved from java to kotlin for

more fun (or desperation)
This commit is contained in:
Juha Heinanen
2018-03-17 11:18:44 +13:00
parent 2a3802a951
commit afb4635af7
31 changed files with 1352 additions and 1593 deletions

View File

@ -1,4 +1,5 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
android { android {
compileSdkVersion = 25 compileSdkVersion = 25
@ -20,6 +21,9 @@ android {
'proguard-rules.pro' 'proguard-rules.pro'
} }
} }
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
externalNativeBuild { externalNativeBuild {
cmake { cmake {
path 'src/main/cpp/CMakeLists.txt' path 'src/main/cpp/CMakeLists.txt'
@ -31,4 +35,8 @@ dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs') compile fileTree(include: ['*.jar'], dir: 'libs')
compile 'com.android.support:appcompat-v7:25.2.0' compile 'com.android.support:appcompat-v7:25.2.0'
compile 'com.android.support.constraint:constraint-layout:1.0.1' compile 'com.android.support.constraint:constraint-layout:1.0.1'
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
}
repositories {
mavenCentral()
} }

View File

@ -314,22 +314,7 @@ Java_com_tutpro_baresip_MainActivity_ua_1aor(JNIEnv *env, jobject thiz, jstring
return (*env)->NewStringUTF(env, ""); return (*env)->NewStringUTF(env, "");
} }
JNIEXPORT jboolean JNICALL JNIEXPORT void JNICALL /* currently not in use */
Java_com_tutpro_baresip_MainActivity_ua_1isregistered(JNIEnv *env, jobject thiz, jlong ua_ptr)
{
struct ua *ua = (struct ua *)ua_ptr;
bool result;
result = ua_isregistered(ua);
if (ua == NULL) {
LOGD("ua_ptr is null\n");
} else {
LOGD("ua_ptr is NOT null\n");
}
return result;
}
JNIEXPORT void JNICALL
Java_com_tutpro_baresip_MainActivity_ua_1current_1set(JNIEnv *env, jobject thiz, Java_com_tutpro_baresip_MainActivity_ua_1current_1set(JNIEnv *env, jobject thiz,
jstring javaAoR) jstring javaAoR)
{ {
@ -343,7 +328,7 @@ Java_com_tutpro_baresip_MainActivity_ua_1current_1set(JNIEnv *env, jobject thiz,
return; return;
} }
JNIEXPORT jstring JNICALL JNIEXPORT jstring JNICALL /* currently not in use */
Java_com_tutpro_baresip_MainActivity_ua_1current(JNIEnv *env, jobject thiz) Java_com_tutpro_baresip_MainActivity_ua_1current(JNIEnv *env, jobject thiz)
{ {
struct ua *current_ua = uag_current(); struct ua *current_ua = uag_current();
@ -357,32 +342,18 @@ Java_com_tutpro_baresip_MainActivity_ua_1current(JNIEnv *env, jobject thiz)
} }
JNIEXPORT jstring JNICALL JNIEXPORT jstring JNICALL
Java_com_tutpro_baresip_MainActivity_ua_1prev_1call(JNIEnv *env, jobject thiz, jstring javaUA) Java_com_tutpro_baresip_MainActivity_aor_1ua(JNIEnv *env, jobject thiz,
jstring javaAoR)
{ {
const char *native_ua = (*env)->GetStringUTFChars(env, javaUA, 0); const char *native_aor = (*env)->GetStringUTFChars(env, javaAoR, 0);
struct ua *ua = (struct ua *)strtoul(native_ua, NULL, 10); struct ua *ua = uag_find_aor(native_aor);
(*env)->ReleaseStringUTFChars(env, javaUA, native_ua); char ua_buf[256];
struct call *call = ua_prev_call(ua); if (ua == NULL)
char call_buf[256]; ua_buf[0] = '\0';
if (call == NULL)
call_buf[0] = '\0';
else else
sprintf(call_buf, "%lu", (unsigned long)call); sprintf(ua_buf, "%lu", (unsigned long)ua);
return (*env)->NewStringUTF(env, call_buf); return (*env)->NewStringUTF(env, ua_buf);
}
JNIEXPORT jstring JNICALL
Java_com_tutpro_baresip_MainActivity_ua_1call(JNIEnv *env, jobject thiz, jstring javaUA)
{
const char *native_ua = (*env)->GetStringUTFChars(env, javaUA, 0);
struct ua *ua = (struct ua *)strtoul(native_ua, NULL, 10);
(*env)->ReleaseStringUTFChars(env, javaUA, native_ua);
struct call *call = ua_call(ua);
char call_buf[256];
sprintf(call_buf, "%lu", (unsigned long)call);
return (*env)->NewStringUTF(env, call_buf);
} }
JNIEXPORT jstring JNICALL JNIEXPORT jstring JNICALL
@ -398,28 +369,26 @@ Java_com_tutpro_baresip_MainActivity_call_1peeruri(JNIEnv *env, jobject thiz, js
JNIEXPORT jstring JNICALL JNIEXPORT jstring JNICALL
Java_com_tutpro_baresip_MainActivity_ua_1connect(JNIEnv *env, jobject thiz, Java_com_tutpro_baresip_MainActivity_ua_1connect(JNIEnv *env, jobject thiz,
jstring uri) { jstring javaUA, jstring javaURI) {
struct call *call; struct call *call;
struct ua *ua; struct ua *ua;
int err; int err;
const char *native_uri = (*env)->GetStringUTFChars(env, uri, 0); const char *native_ua = (*env)->GetStringUTFChars(env, javaUA, 0);
const char *native_uri = (*env)->GetStringUTFChars(env, javaURI, 0);
char call_buf[256]; char call_buf[256];
LOGD("connecting to %s\n", native_uri); LOGD("connecting ua %s to %s\n", native_ua, native_uri);
ua = uag_current(); ua = (struct ua *)strtoul(native_ua, NULL, 10);
if (ua != NULL) { err = ua_connect(ua, &call, NULL, native_uri, NULL, VIDMODE_ON);
err = ua_connect(ua, &call, NULL, native_uri, NULL, VIDMODE_ON); if (err) {
if (err) { LOGW("connecting to %s failed with error %d\n", native_uri, err);
LOGW("connecting to %s failed with error %d\n", native_uri, err);
call_buf[0] = '\0';
} else {
sprintf(call_buf, "%lu", (unsigned long)call);
}
} else {
LOGE("no current ua\n");
call_buf[0] = '\0'; call_buf[0] = '\0';
} else {
sprintf(call_buf, "%lu", (unsigned long)call);
} }
(*env)->ReleaseStringUTFChars(env, uri, native_uri);
(*env)->ReleaseStringUTFChars(env, javaUA, native_ua);
(*env)->ReleaseStringUTFChars(env, javaURI, native_uri);
return (*env)->NewStringUTF(env, call_buf); return (*env)->NewStringUTF(env, call_buf);
} }
@ -440,7 +409,8 @@ Java_com_tutpro_baresip_MainActivity_ua_1answer(JNIEnv *env, jobject thiz,
} }
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_MainActivity_call_1hold(JNIEnv *env, jobject thiz, JNIEXPORT jint JNICALL
Java_com_tutpro_baresip_MainActivity_call_1hold(JNIEnv *env, jobject thiz,
jstring javaCall) { jstring javaCall) {
const char *native_call = (*env)->GetStringUTFChars(env, javaCall, 0); const char *native_call = (*env)->GetStringUTFChars(env, javaCall, 0);
LOGD("holding call %s\n", native_call); LOGD("holding call %s\n", native_call);
@ -449,7 +419,8 @@ JNIEXPORT jint JNICALL Java_com_tutpro_baresip_MainActivity_call_1hold(JNIEnv *
return res; return res;
} }
JNIEXPORT jint JNICALL Java_com_tutpro_baresip_MainActivity_call_1unhold(JNIEnv *env, jobject thiz, JNIEXPORT jint JNICALL
Java_com_tutpro_baresip_MainActivity_call_1unhold(JNIEnv *env, jobject thiz,
jstring javaCall) { jstring javaCall) {
const char *native_call = (*env)->GetStringUTFChars(env, javaCall, 0); const char *native_call = (*env)->GetStringUTFChars(env, javaCall, 0);
LOGD("holding call %s\n", native_call); LOGD("holding call %s\n", native_call);
@ -477,7 +448,7 @@ Java_com_tutpro_baresip_MainActivity_ua_1hangup(JNIEnv *env, jobject thiz,
} }
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_com_tutpro_baresip_MainActivity_contacts_1remove(JNIEnv *env, jobject thiz) { Java_com_tutpro_baresip_MainActivity_00024Companion_contacts_1remove(JNIEnv *env, jobject thiz) {
struct le *le; struct le *le;
le = list_head(contact_list(baresip_contacts())); le = list_head(contact_list(baresip_contacts()));
while ((le = list_head(contact_list(baresip_contacts())))) { while ((le = list_head(contact_list(baresip_contacts())))) {
@ -488,8 +459,8 @@ Java_com_tutpro_baresip_MainActivity_contacts_1remove(JNIEnv *env, jobject thiz)
} }
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_com_tutpro_baresip_MainActivity_contact_1add(JNIEnv *env, jobject thiz, Java_com_tutpro_baresip_MainActivity_00024Companion_contact_1add(JNIEnv *env, jobject thiz,
jstring javaContact) { jstring javaContact) {
struct pl pl_addr; struct pl pl_addr;
const struct list *lst; const struct list *lst;
struct le *le; struct le *le;

View File

@ -1,35 +0,0 @@
package com.tutpro.baresip;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.MenuItem;
import android.widget.TextView;
public class AboutActivity extends AppCompatActivity {
private TextView aboutView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_about);
aboutView = (TextView)findViewById(R.id.aboutText);
aboutView.setEnabled(false);
aboutView.setText(getString(R.string.aboutText));
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
Intent i = new Intent();
setResult(RESULT_CANCELED, i);
finish();
break;
}
return true;
}
}

View File

@ -1,34 +0,0 @@
package com.tutpro.baresip;
public class Account {
private String ua, aor, status;
private int status_image;
public Account(String ua, String aor) {
this.ua = ua;
this.aor = aor;
this.status = "";
this.status_image = 0;
}
public void setStatusImage(int status_image) {
this.status_image = status_image;
}
public int getStatusImage() {
return status_image;
}
public String getUA() {
return ua;
}
public String getAoR() {
return aor;
}
public void setStatus(String status) { this.status = status; }
public String getStatus() {
return status;
}
}

View File

@ -1,47 +0,0 @@
package com.tutpro.baresip;
import android.content.Context;
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.util.ArrayList;
public class AccountSpinnerAdapter extends ArrayAdapter<Integer> {
private ArrayList<Integer> images;
private ArrayList<String> aors;
private Context context;
public AccountSpinnerAdapter(Context context, ArrayList<String> aors, ArrayList<Integer> images) {
super(context, android.R.layout.simple_spinner_item, images);
this.images = images;
this.aors = aors;
this.context = context;
}
@Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
return getImageForPosition(position, convertView, parent);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return getImageForPosition(position, convertView, parent);
}
private View getImageForPosition(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View row = inflater.inflate(R.layout.account_spinner, parent, false);
TextView textView = (TextView) row.findViewById(R.id.spinnerText);
textView.setText(aors.get(position));
textView.setTextSize(17);
ImageView imageView = (ImageView) row.findViewById(R.id.spinnerImage);
imageView.setImageResource(images.get(position));
return row;
}
}

View File

@ -1,40 +0,0 @@
package com.tutpro.baresip;
public class Call {
private String ua, call, peer_uri, status;
Boolean hold;
public Call(String ua, String call, String peer_uri, String status) {
this.ua = ua;
this.call = call;
this.peer_uri = peer_uri;
this.status = status;
}
public String getUA() {
return ua;
}
public String getCall() {
return call;
}
public String getPeerURI() {
return peer_uri;
}
public void setStatus(String status) {
this.status = status;
}
public String getStatus() {
return status;
}
public void setHold(Boolean hold) {
this.hold = hold;
}
public Boolean getHold() {
return hold;
}
}

View File

@ -1,20 +0,0 @@
package com.tutpro.baresip;
public class Contact {
private String name, uri;
public Contact(String name, String uri) {
this.name = name;
this.uri = uri;
}
public String getName() {
return name;
}
public String getURI() {
return uri;
}
}

View File

@ -1,104 +0,0 @@
package com.tutpro.baresip;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.util.TypedValue;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.EditText;
import android.widget.TextView;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
public class EditAccountsActivity extends AppCompatActivity {
private EditText editText;
private String path;
private File file;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_edit_accounts);
editText = (EditText)findViewById(R.id.editAccounts);
path = getApplicationContext().getFilesDir().getAbsolutePath() + "/accounts";
file = new File(path);
String content;
if (!file.exists()) {
Log.e("Baresip", "Failed to find accounts file");
content = "No accounts";
} else {
Log.e("Baresip", "Found accounts file");
int length = (int)file.length();
byte[] bytes = new byte[length];
try {
FileInputStream in = new FileInputStream(file);
try {
in.read(bytes);
} finally {
in.close();
}
content = new String(bytes);
} catch (java.io.IOException e) {
Log.e("Baresip", "Failed to read accounts file: " + e.toString());
content = "Failed to read account file";
}
}
Log.d("Baresip", "Content length is: " + content.length());
editText.setTextSize(TypedValue.COMPLEX_UNIT_PX,
getResources().getDimension(R.dimen.textsize));
editText.setText(content, TextView.BufferType.EDITABLE);
}
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.edit_menu, menu);
return true;
}
public boolean onOptionsItemSelected(MenuItem item) {
Intent i = new Intent();
switch (item.getItemId()) {
case R.id.save:
try {
FileOutputStream fOut =
new FileOutputStream(file.getAbsoluteFile(), false);
OutputStreamWriter fWriter = new OutputStreamWriter(fOut);
String res = editText.getText().toString();
try {
fWriter.write(res);
fWriter.close();
fOut.close();
} catch (java.io.IOException e) {
Log.e("Baresip", "Failed to write accounts file: " +
e.toString());
}
} catch (java.io.FileNotFoundException e) {
Log.e("Baresip", "Failed to find accounts file: " +
e.toString());
}
Log.d("Baresip", "Updated accounts file");
setResult(RESULT_OK, i);
finish();
return true;
case R.id.cancel:
case android.R.id.home:
setResult(RESULT_CANCELED, i);
finish();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}

View File

@ -1,104 +0,0 @@
package com.tutpro.baresip;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.util.TypedValue;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.EditText;
import android.widget.TextView;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
public class EditConfigActivity extends AppCompatActivity {
private EditText editText;
private String path;
private File file;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_edit_config);
editText = (EditText)findViewById(R.id.editConfig);
path = getApplicationContext().getFilesDir().getAbsolutePath() + "/config";
// path = "/sdcard/baresip/config";
file = new File(path);
String content;
if (!file.exists()) {
Log.e("Baresip", "Failed to find config file");
content = "No config";
} else {
Log.e("Baresip", "Found config file");
int length = (int)file.length();
byte[] bytes = new byte[length];
try {
FileInputStream in = new FileInputStream(file);
try {
in.read(bytes);
} finally {
in.close();
}
content = new String(bytes);
} catch (java.io.IOException e) {
Log.e("Baresip", "Failed to read config file: " + e.toString());
content = "Failed to read account file";
}
}
Log.d("Baresip", "Content length is: " + content.length());
editText.setTextSize(TypedValue.COMPLEX_UNIT_PX,
getResources().getDimension(R.dimen.textsize));
editText.setText(content, TextView.BufferType.EDITABLE);
}
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.edit_menu, menu);
return true;
}
public boolean onOptionsItemSelected(MenuItem item) {
Intent i = new Intent(this, MainActivity.class);
switch (item.getItemId()) {
case R.id.save:
try {
FileOutputStream fOut =
new FileOutputStream(file.getAbsoluteFile(), false);
OutputStreamWriter fWriter = new OutputStreamWriter(fOut);
String res = editText.getText().toString();
try {
fWriter.write(res);
fWriter.close();
fOut.close();
} catch (java.io.IOException e) {
Log.e("Baresip", "Failed to write config file: " +
e.toString());
}
} catch (java.io.FileNotFoundException e) {
Log.e("Baresip", "Failed to find config file: " +
e.toString());
}
Log.d("Baresip", "Updated config file");
setResult(RESULT_OK, i);
finish();
return true;
case R.id.cancel:
case android.R.id.home:
setResult(RESULT_CANCELED, i);
finish();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}

View File

@ -1,117 +0,0 @@
package com.tutpro.baresip;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.util.TypedValue;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.TextView;
import java.io.File;
import java.util.ArrayList;
public class EditContactsActivity extends AppCompatActivity {
private EditText editText;
static public ArrayList<Contact> Contacts = new java.util.ArrayList<>();
static public ArrayList<String> Names = new java.util.ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_edit_contacts);
editText = (EditText)findViewById(R.id.editText);
String path = getApplicationContext().getFilesDir().getAbsolutePath() + "/contacts";
File file = new File(path);
String content = Utils.getFileContents(file);
Log.d("Baresip", "Contacts length is: " + content.length());
editText.setTextSize(TypedValue.COMPLEX_UNIT_PX,
getResources().getDimension(R.dimen.textsize));
editText.setText(content, TextView.BufferType.EDITABLE);
}
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.edit_menu, menu);
return true;
}
public boolean onOptionsItemSelected(MenuItem item) {
Intent i = new Intent(this, MainActivity.class);
switch (item.getItemId()) {
case R.id.save:
String path = getApplicationContext().getFilesDir().getAbsolutePath() + "/contacts";
File file = new File(path);
Utils.putFileContents(file, editText.getText().toString());
Log.d("Baresip", "Updated contacts file");
MainActivity.contacts_remove();
updateContactsAndNames();
// MainActivity.CalleeAdapter.notifyDataSetChanged();
MainActivity.CalleeAdapter = new ArrayAdapter<String>
(this,android.R.layout.select_dialog_item, Names);
MainActivity.callee.setThreshold(2);
MainActivity.callee.setAdapter(MainActivity.CalleeAdapter);
i.putExtra("action", "save");
setResult(RESULT_OK, i);
finish();
return true;
case R.id.cancel:
case android.R.id.home:
i.putExtra("action", "cancel");
setResult(RESULT_OK, i);
finish();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
static public void updateContactsAndNames() {
String path = MainActivity.mainActivityContext.getFilesDir().getAbsolutePath() + "/contacts";
File file = new File(path);
String[] lines = Utils.getFileContents(file).split("\n");
String name, uri;
Contacts.clear();
Names.clear();
for (String line : lines) {
line.trim();
if (line.startsWith("#") || line.length() == 0) continue;
String[] parts = line.split("\"");
if (parts.length != 3) {
Log.e("Baresip", "Invalid contacts line: " + line);
continue;
}
name = parts[1];
if (name.length() < 2) {
Log.e("Baresip", "Too short contact display name: " + name);
continue;
}
uri = parts[2].trim();
if (!uri.startsWith("<") || !uri.contains(">")) {
Log.e("Baresip", "Invalid contact uri: " + uri);
continue;
}
MainActivity.contact_add("\"" + name + "\" " + uri);
if (uri.indexOf(";access") > 0) continue;
uri = uri.substring(1, uri.indexOf(">"));
Log.d("Baresip", "Adding contact name/uri: " + name + "/" + uri);
Contacts.add(new Contact(name, uri));
Names.add(name);
}
}
static public String findContactURI(String name) {
for (Contact c : Contacts) {
if (c.getName().equals(name)) return c.getURI();
}
return name;
}
}

View File

@ -1,32 +0,0 @@
package com.tutpro.baresip;
import java.io.Serializable;
import java.util.GregorianCalendar;
public class History implements Serializable {
private static final long serialVersionUID = -299482035708790407L;
private String ua, call, aor, peer_uri, direction;
private GregorianCalendar time;
private Boolean connected;
public History(String ua, String call, String aor, String peer_uri, String direction,
Boolean connected) {
this.ua = ua;
this.call = call;
this.aor = aor;
this.peer_uri = peer_uri;
this.direction = direction;
this.time = new GregorianCalendar();
this.connected = connected;
}
public String getUA() { return ua; }
public String getCall() { return call; }
public String getAoR() { return aor; }
public String getPeerURI() { return peer_uri; }
public String getDirection() { return direction; }
public GregorianCalendar getTime() { return time; }
public Boolean getConnected() { return connected; }
}

View File

@ -1,135 +0,0 @@
package com.tutpro.baresip;
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.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
public class HistoryActivity extends AppCompatActivity {
ArrayList<HistoryRow> uaHistory = new ArrayList<>();
ArrayList<Integer> posAtHistory = new ArrayList<>();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_history);
final ListView listview = (ListView) findViewById(R.id.history);
generate_ua_history();
final HistoryListAdapter adapter = new HistoryListAdapter(this, uaHistory);
listview.setAdapter(adapter);
listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, final View view, int position, long id) {
HistoryRow row = uaHistory.get(position);
Intent i = new Intent();
i.putExtra("peer_uri", row.getPeerURI());
setResult(RESULT_OK, i);
finish();
}
});
listview.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, final int pos, long id) {
DialogInterface.OnClickListener dialogClickListener =
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which){
case DialogInterface.BUTTON_POSITIVE:
MainActivity.History.remove((posAtHistory.get(pos)).intValue());
generate_ua_history();
if (uaHistory.size() == 0) {
Intent i = new Intent();
setResult(RESULT_CANCELED, i);
finish();
}
adapter.notifyDataSetChanged();
break;
case DialogInterface.BUTTON_NEGATIVE:
break;
}
}
};
AlertDialog.Builder builder =
new AlertDialog.Builder(HistoryActivity.this,
R.style.Theme_AppCompat);
builder.setMessage("Do you want to delete " +
MainActivity.History.get(pos).getPeerURI() + "?")
.setPositiveButton("Yes", dialogClickListener)
.setNegativeButton("No", dialogClickListener).show();
return true;
}
});
listview.setLongClickable(true);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
Log.d("Baresip", "Back array was pressed");
Intent i = new Intent();
setResult(RESULT_CANCELED, i);
finish();
break;
}
return true;
}
private void generate_ua_history() {
uaHistory.clear();
posAtHistory.clear();
for (Integer i = MainActivity.History.size() - 1; i >= 0; i--) {
History h = MainActivity.History.get(i);
if (h.getAoR().equals(MainActivity.ua_aor(MainActivity.ua_current()))) {
String time;
if (isToday(h.getTime())) {
SimpleDateFormat fmt = new SimpleDateFormat("HH:mm");
time = fmt.format(h.getTime().getTime());
} else {
SimpleDateFormat fmt = new SimpleDateFormat("MMM dd");
time = fmt.format(h.getTime().getTime());
}
if (h.getDirection().equals("in")) {
if (h.getConnected()) {
uaHistory.add(new HistoryRow(h.getPeerURI(), R.drawable.arrow_down_green, time));
} else {
uaHistory.add(new HistoryRow(h.getPeerURI(), R.drawable.arrow_down_red, time));
}
} else {
if (h.getConnected()) {
uaHistory.add(new HistoryRow(h.getPeerURI(), R.drawable.arrow_up_green, time));
} else {
uaHistory.add(new HistoryRow(h.getPeerURI(), R.drawable.arrow_up_red, time));
}
}
posAtHistory.add(i);
}
}
}
private Boolean isToday(GregorianCalendar time) {
GregorianCalendar now = new GregorianCalendar();
return now.get(Calendar.YEAR) == time.get(Calendar.YEAR) &&
now.get(Calendar.MONTH) == time.get(Calendar.MONTH) &&
now.get(Calendar.DAY_OF_MONTH) == time.get(Calendar.DAY_OF_MONTH);
}
}

View File

@ -1,38 +0,0 @@
package com.tutpro.baresip;
import android.content.Context;
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.util.ArrayList;
public class HistoryListAdapter extends ArrayAdapter<HistoryRow> {
private Context context;
private ArrayList<HistoryRow> rows;
public HistoryListAdapter(Context context, ArrayList<HistoryRow> rows) {
super(context, R.layout.history_row, rows);
this.context = context;
this.rows = rows;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
HistoryRow row = rows.get(position);
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(R.layout.history_row, parent, false);
ImageView directionView = (ImageView) rowView.findViewById(R.id.direction);
directionView.setImageResource(row.getDirection());
TextView peerURIView = (TextView) rowView.findViewById(R.id.peer_uri);
peerURIView.setText(row.getPeerURI());
TextView timeView = (TextView) rowView.findViewById(R.id.time);
timeView.setText(row.getTime());
return rowView;
}
}

View File

@ -1,19 +0,0 @@
package com.tutpro.baresip;
public class HistoryRow {
private String peer_uri;
private Integer direction;
private String time;
public HistoryRow(String peer_uri, Integer direction, String time) {
this.peer_uri = peer_uri;
this.direction = direction;
this.time = time;
}
public String getPeerURI() { return peer_uri; }
public Integer getDirection() { return direction; }
public String getTime() { return time; }
}

View File

@ -1,737 +0,0 @@
package com.tutpro.baresip;
import android.Manifest;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.graphics.Color;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.*;
import android.widget.RelativeLayout.LayoutParams;
import android.widget.AutoCompleteTextView;
import android.view.*;
import android.util.Log;
import android.content.Context;
import java.util.*;
import java.io.*;
public class MainActivity extends AppCompatActivity {
static Context mainActivityContext;
static Boolean running = false;
static AutoCompleteTextView callee;
static RelativeLayout layout;
static Button callButton;
static Button holdButton;
static ArrayList<Account> Accounts = new ArrayList<>();
static ArrayList<String> AoRs = new ArrayList<>();
static ArrayList<Integer> Images = new ArrayList<>();
static AccountSpinnerAdapter AccountAdapter = null;
static ArrayAdapter<String> CalleeAdapter = null;
static ArrayList<Call> In = new ArrayList<>();
static ArrayList<Call> Out = new ArrayList<>();
static ArrayList<History> History = new ArrayList<>();
static String CurrentUA = null;
private static final int RECORD_AUDIO_PERMISSION = 1;
private static final int EDIT_ACCOUNTS_CODE = 1;
private static final int EDIT_CONTACTS_CODE = 2;
private static final int EDIT_CONFIG_CODE = 3;
private static final int HISTORY_CODE = 4;
private static final int ABOUT_CODE = 5;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mainActivityContext = getApplicationContext();
Spinner AoRSpinner = (Spinner) findViewById(R.id.AoRList);
Log.i("Baresip", "Setting AccountAdapter");
AccountAdapter = new AccountSpinnerAdapter(getApplicationContext(), AoRs, Images);
AoRSpinner.setAdapter(AccountAdapter);
AoRSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
String aor = AoRs.get(position);
Log.i("Baresip", "Setting " + aor + " current");
ua_current_set(aor);
CurrentUA = ua_current();
ArrayList<Call> out = uaCalls(Out, CurrentUA);
if (out.size() == 0) {
callee.setHint("Callee");
callButton.setText("Call");
if (aorHasHistory(History, aor)) {
holdButton.setText("History");
holdButton.setVisibility(View.VISIBLE);
} else {
holdButton.setVisibility(View.INVISIBLE);
}
} else {
callee.setText(out.get(0).getPeerURI());
callButton.setText(out.get(0).getStatus());
if (out.get(0).getStatus().equals("Hangup")) {
if (out.get(0).getHold()) {
holdButton.setText("Unhold");
} else {
holdButton.setText("Hold");
}
holdButton.setVisibility(View.VISIBLE);
} else {
holdButton.setVisibility(View.INVISIBLE);
}
}
ArrayList<Call> in = uaCalls(In, CurrentUA);
int view_count = layout.getChildCount();
Log.d("Baresip", "View count is " + view_count);
if (view_count > 5) {
layout.removeViews(5, view_count - 5);
}
for (Call c: in) {
for (int call_index = 0; call_index < In.size(); call_index++) {
addCallViews(c,(call_index + 1) * 10);
}
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
Log.i("Baresip", "Nothing selected");
}
});
String[] assets = {"accounts", "contacts", "config", "busy.wav", "callwaiting.wav",
"error.wav", "message.wav", "notfound.wav", "ring.wav", "ringback.wav"};
final String path = mainActivityContext.getFilesDir().getPath();
Log.d("Baresip", "path is: " + path);
File file = new File(path);
if (!file.exists()) {
Log.d("Baresip", "Creating baresip directory");
try {
new File(path).mkdirs();
} catch (Error e) {
Log.e("Baresip", "Failed to create directory: " +
e.toString());
}
}
for (String a : assets) {
file = new File(path + "/" + a);
if (!file.exists()) {
Log.d("Baresip", "Copying asset " + a);
copyAssetToFile(a, path + "/" + a);
} else {
Log.d("Baresip", "Asset " + a + " already copied");
}
}
file = new File(path, "history");
try {
FileInputStream fis = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(fis);
History = (ArrayList<History>)ois.readObject();
Log.d("Baresip", "Restored History");
ois.close();
fis.close();
} catch (Exception e) {
Log.w("Baresip", "InputStream exception: - " + e.toString());
}
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
!= PackageManager.PERMISSION_GRANTED) {
Log.d("Baresip", "Baresip does not have RECORD_AUDIO permission");
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.RECORD_AUDIO}, RECORD_AUDIO_PERMISSION);
}
if (!running) {
Log.i("Baresip", "Starting Baresip with path " + path);
new Thread(new Runnable() {
public void run() {
baresipStart(path);
}
}).start();
running = true;
}
layout = (RelativeLayout) findViewById(R.id.mainActivityLayout);
callee = (AutoCompleteTextView)findViewById(R.id.callee);
EditContactsActivity.updateContactsAndNames();
CalleeAdapter = new ArrayAdapter<String>
(this,android.R.layout.select_dialog_item, EditContactsActivity.Names);
callee.setThreshold(2);
callee.setAdapter(CalleeAdapter);
callButton = (Button)findViewById(R.id.callButton);
callButton.setText("Call");
callButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (callButton.getText().toString().equals("Call")) {
call(((EditText) findViewById(R.id.callee)).getText().toString());
} else {
Log.i("Baresip", "Canceling UA " + CurrentUA + " call " +
Out.get(0).getCall() + " to " +
((EditText) findViewById(R.id.callee)).getText());
ua_hangup(CurrentUA, Out.get(0).getCall(), 486, "Rejected");
}
}
});
holdButton = (Button)findViewById(R.id.holdButton);
if (aorHasHistory(History, ua_aor(ua_current()))) {
holdButton.setText("History");
} else {
holdButton.setVisibility(View.INVISIBLE);
}
holdButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
switch (holdButton.getText().toString()) {
case "Hold":
Log.i("Baresip", "Holding up " +
((EditText) findViewById(R.id.callee)).getText());
call_hold(Out.get(0).getCall());
Out.get(0).setHold(true);
holdButton.setText("Unhold");
break;
case "Unhold":
Log.i("Baresip", "Unholding " +
((EditText) findViewById(R.id.callee)).getText());
call_unhold(Out.get(0).getCall());
Out.get(0).setHold(false);
holdButton.setText("Hold");
break;
case "History":
Intent i = new Intent(MainActivity.this, HistoryActivity.class);
startActivityForResult(i, HISTORY_CODE);
break;
}
}
});
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
Log.d("Baresip", "Screen orientation change to landscape");
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
Log.d("Baresip", "Screen orientation change to portrait");
}
}
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case RECORD_AUDIO_PERMISSION: {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d("Baresip", "RECORD_AUDIO permission granted");
} else {
Log.d("Baresip", "RECORD_AUDIO permission NOT granted");
}
return;
}
default:
Log.e("Baresip", "Unknown permissions request code: " + requestCode);
}
}
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main_menu, menu);
return true;
}
public boolean onOptionsItemSelected(MenuItem item) {
Intent i;
switch (item.getItemId()) {
case R.id.accounts:
i = new Intent(this, EditAccountsActivity.class);
startActivityForResult(i, EDIT_ACCOUNTS_CODE);
return true;
case R.id.contacts:
i = new Intent(this, EditContactsActivity.class);
startActivityForResult(i, EDIT_CONTACTS_CODE);
return true;
case R.id.config:
i = new Intent(this, EditConfigActivity.class);
startActivityForResult(i, EDIT_CONFIG_CODE);
return true;
case R.id.about:
i = new Intent(this, AboutActivity.class);
startActivityForResult(i, ABOUT_CODE);
return true;
case R.id.quit:
if (running) {
Log.d("Baresip", "Stopping");
final String path = mainActivityContext.getFilesDir().getPath();
File file = new File(path,"history");
try {
FileOutputStream fos = new FileOutputStream(file);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(History);
oos.close();
fos.close();
} catch (IOException e) {
Log.w("Baresip", "OutputStream exception: " + e.toString());
e.printStackTrace();
}
History.clear();
Accounts.clear();
AoRs.clear();
Images.clear();
baresipStop();
running = false;
}
finish();
System.exit(0);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == EDIT_ACCOUNTS_CODE) {
if(resultCode == RESULT_OK) {
AlertDialog alertDialog = new AlertDialog.Builder(this).create();
alertDialog.setTitle("Alert");
alertDialog.setMessage("You need to restart baresip in order to activate saved accounts!");
alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
alertDialog.show();
}
if (resultCode == RESULT_CANCELED) {
Log.d("Baresip", "Edit accounts canceled");
}
}
if (requestCode == EDIT_CONFIG_CODE) {
if(resultCode == RESULT_OK) {
Utils.alertView(this,
"You need to restart baresip in order to activate saved config!");
reload_config();
}
if (resultCode == RESULT_CANCELED) {
Log.d("Baresip", "Edit config canceled");
}
}
if (requestCode == HISTORY_CODE) {
if(resultCode == RESULT_OK) {
callee.setText(data.getStringExtra("peer_uri"));
}
if(resultCode == RESULT_CANCELED) {
Log.d("Baresip", "History canceled");
if (!aorHasHistory(History, ua_aor(ua_current()))) {
holdButton.setVisibility(View.INVISIBLE);
}
}
}
if ((requestCode == EDIT_CONTACTS_CODE) || (requestCode == ABOUT_CODE)) {
Log.d("Baresip", "Back arrow or Cancel pressed at request: " + requestCode);
}
}
public void addAccount(String ua) {
String aor = ua_aor(ua);
Log.d("Baresip", "Adding account " + ua + " with AoR " + aor);
Accounts.add(new Account(ua, aor));
AoRs.add(aor);
Images.add(R.drawable.yellow);
runOnUiThread(new Runnable() {
@Override
public void run() {
AccountAdapter.notifyDataSetChanged();
}
});
}
private void call(String callee) {
if (callee.length() == 0) return;
String uri = EditContactsActivity.findContactURI(callee);
if (!uri.startsWith("sip:")) uri = "sip:" + uri;
if (!uri.contains("@")) {
String aor = ua_aor(CurrentUA);
String host = aor.substring(aor.indexOf("@") + 1);
uri = uri + "@" + host;
}
((EditText) findViewById(R.id.callee)).setText(uri);
Log.i("Baresip", "Calling " + uri);
String call = ua_connect(uri);
if (!call.equals("")) {
Log.i("Baresip", "Adding outgoing call " + CurrentUA + "/" + call +
"/" + uri);
Out.add(new Call(CurrentUA, call, uri, "Cancel"));
// History.add(new History(ua_aor(CurrentUA), uri, "out"));
callButton.setText("Cancel");
holdButton.setVisibility(View.INVISIBLE);
}
}
private void copyAssetToFile(String asset, String path) {
try {
AssetManager assetManager = getAssets();
InputStream is = assetManager.open(asset);
OutputStream os = new FileOutputStream(path);
byte [] buffer = new byte[512];
int byteRead;
while ((byteRead = is.read(buffer)) != -1) {
os.write(buffer, 0, byteRead);
}
} catch (IOException e) {
Log.e("Baresip", "Failed to read asset " + asset + ": " +
e.toString());
}
}
private void addCallViews(final Call call, int id) {
Log.d("Baresip", "Creating new Incoming textview at " + id);
TextView caller_heading = new TextView(mainActivityContext);
caller_heading.setText("Incoming call from ...");
caller_heading.setTextColor(Color.BLACK);
caller_heading.setTextSize(20);
caller_heading.setPadding(10, 20, 0, 0);
caller_heading.setId(id);
LayoutParams heading_params = new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
if (id == 10)
heading_params.addRule(RelativeLayout.BELOW, callButton.getId());
else
heading_params.addRule(RelativeLayout.BELOW,id - 10 + 3);
caller_heading.setLayoutParams(heading_params);
layout.addView(caller_heading);
TextView caller_uri = new TextView(mainActivityContext);
caller_uri.setText(In.get(In.size() - 1).getPeerURI());
caller_uri.setTextColor(Color.GREEN);
caller_uri.setTextSize(20);
caller_uri.setPadding(10, 10, 0, 10);
Log.d("Baresip", "Creating new caller textview at " + (id + 1));
caller_uri.setId(id + 1);
LayoutParams caller_uri_params = new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
caller_uri_params.addRule(RelativeLayout.BELOW, id);
caller_uri.setLayoutParams(caller_uri_params);
layout.addView(caller_uri);
Button answer_button = new Button(mainActivityContext);
answer_button.setText(call.getStatus());
answer_button.setBackgroundResource(android.R.drawable.btn_default);
answer_button.setTextColor(Color.BLACK);
Log.d("Baresip", "Creating new answer button at " + (id + 2));
answer_button.setId(id + 2);
answer_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String call_ua = call.getUA();
String call_call = call.getCall();
switch (((Button)v).getText().toString()) {
case "Answer":
Log.i("Baresip", "UA " + call_ua + " accepting incoming call " +
call_call);
ua_answer(call_ua, call_call);
final int final_in_index = callIndex(In, call_ua, call_call);
if (final_in_index >= 0) {
Log.d("Baresip", "Updating Hangup and Hold");
In.get(final_in_index).setStatus("Hangup");
In.get(final_in_index).setHold(false);
runOnUiThread(new Runnable() {
@Override
public void run() {
final int answer_id = (final_in_index + 1) * 10 + 2;
Button answer_button = (Button)layout.findViewById(answer_id);
answer_button.setText("Hangup");
Button reject_button = (Button)layout.findViewById(answer_id + 1);
reject_button.setText("Hold");
}
});
}
break;
case "Hangup":
Log.i("Baresip", "UA " + call_ua + " hanging up call " +
call_call);
ua_hangup(call_ua, call_call,200, "OK");
break;
default:
Log.e("Baresip", "Invalid answer button text: " +
((Button)v).getText().toString());
break;
}
}
});
LayoutParams answer_button_params = new LayoutParams(200,
LayoutParams.WRAP_CONTENT);
answer_button_params.addRule(RelativeLayout.BELOW, id + 1);
answer_button_params.setMargins(3, 10, 0, 0);
answer_button.setLayoutParams(answer_button_params);
layout.addView(answer_button);
Button reject_button = new Button(mainActivityContext);
if (call.getStatus().equals("Answer")) {
reject_button.setText("Reject");
} else {
if (call.getHold()) {
reject_button.setText("Unhold");
} else {
reject_button.setText("Hold");
}
}
reject_button.setBackgroundResource(android.R.drawable.btn_default);
reject_button.setTextColor(Color.BLACK);
Log.d("Baresip", "Creating new reject button at " + (id + 3));
reject_button.setId(id + 3);
reject_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
switch (((Button)v).getText().toString()) {
case "Reject":
Log.i("Baresip", "UA " + call.getUA() +
" rejecting incoming call " + call.getCall());
ua_hangup(call.getUA(), call.getCall(), 486, "Rejected");
break;
case "Hold":
call_hold(call.getCall());
((Button)v).setText("Unhold");
call.setHold(true);
break;
case "Unhold":
call_unhold(call.getCall());
((Button)v).setText("Hold");
call.setHold(false);
break;
}
}
});
LayoutParams reject_button_params = new LayoutParams(200,
LayoutParams.WRAP_CONTENT);
reject_button_params.addRule(RelativeLayout.BELOW, id + 1);
reject_button_params.setMargins(225, 10, 0, 0);
reject_button.setLayoutParams(reject_button_params);
layout.addView(reject_button);
}
private int callIndex(ArrayList<Call> calls, String ua, String call) {
for (int i = 0; i < calls.size(); i++) {
if (calls.get(i).getUA().equals(ua) && calls.get(i).getCall().equals(call))
return i;
}
return -1;
}
private ArrayList<Call> uaCalls(ArrayList<Call> calls, String ua) {
ArrayList<Call> result = new ArrayList<>();
for (int i = 0; i < calls.size(); i++) {
if (calls.get(i).getUA().equals(ua)) result.add(calls.get(i));
}
return result;
}
private Boolean aorHasHistory(ArrayList<History> history, String aor) {
for (History h : history) {
if (h.getAoR().equals(aor)) return true;
}
return false;
}
private Boolean callHasHistory(ArrayList<History> history, String ua, String call) {
for (History h : history) {
if (h.getUA().equals(ua) && h.getCall().equals(call)) return true;
}
return false;
}
private void updateStatus(String event, final String ua, final String call) {
String aor = ua_aor(ua);
int call_index;
Log.d("Baresip", "Handling event " + event + " for " + ua + "/" + call + "/" +
aor);
for (int account_index = 0; account_index < Accounts.size(); account_index++) {
if (Accounts.get(account_index).getAoR().equals(aor)) {
Log.d("Baresip", "Found AoR at index " + account_index);
switch (event) {
case "registering":
case "unregistering":
// Log.d("Baresip", "Setting status to yellow");
break;
case "registered":
Log.d("Baresip", "Setting status to green");
Accounts.get(account_index).setStatus("OK");
AoRs.set(account_index, aor);
Images.set(account_index, R.drawable.green);
runOnUiThread(new Runnable() {
@Override
public void run() {
AccountAdapter.notifyDataSetChanged();
}
});
break;
case "registering failed":
Log.d("Baresip", "Setting status to red");
Accounts.get(account_index).setStatus("FAIL");
AoRs.set(account_index, aor);
Images.set(account_index, R.drawable.red);
runOnUiThread(new Runnable() {
@Override
public void run() {
AccountAdapter.notifyDataSetChanged();
}
});
break;
case "call ringing":
break;
case "call established":
Log.d("Baresip", "Out call index is " + (Out.size() - 1));
int out_index = callIndex(Out, ua, call);
if (out_index >= 0) {
Log.d("Baresip", "Update Hangup and Hold");
Out.get(out_index).setStatus("Hangup");
Out.get(out_index).setHold(false);
if (ua.equals(CurrentUA)) {
runOnUiThread(new Runnable() {
@Override
public void run() {
callButton.setText("Hangup");
holdButton.setText("Hold");
holdButton.setVisibility(View.VISIBLE);
}
});
}
History.add(new History(ua, call, aor, call_peeruri(call),
"out", true));
break;
} else {
History.add(new History(ua, call, aor, call_peeruri(call),
"in", true));
}
Log.e("Baresip", "Unknown call " + ua + "/" + call +
" established");
break;
case "call incoming":
final String peer_uri = call_peeruri(call);
Log.d("Baresip", "Incoming call " + ua + "/" + call + "/" +
peer_uri);
final Call new_call = new Call(ua, call, peer_uri, "Answer");
In.add(new_call);
// History.add(new History(aor, call_peeruri(call), "in"));
Log.d("Baresip", "Current UA is " + CurrentUA);
if (ua.equals(CurrentUA)) {
runOnUiThread(new Runnable() {
@Override
public void run() {
addCallViews(new_call, In.size() * 10);
}
});
}
break;
case "call closed":
call_index = callIndex(In, ua, call);
Log.d("Baresip", "Incoming call index is " + call_index);
if (call_index != -1) {
Log.d("Baresip", "Removing inbound call " + ua + "/" +
call + "/" + In.get(call_index).getPeerURI());
final int view_id = (call_index + 1) * 10;
final int remove_count = In.size() - call_index;
final int final_call_index = call_index;
In.remove(call_index);
Log.d("Baresip", "Current UA is " + CurrentUA);
if (ua.equals(CurrentUA)) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (callButton.getText().equals("Call")) {
holdButton.setText("History");
holdButton.setVisibility(View.VISIBLE);
}
View caller_heading = layout.findViewById(view_id);
int view_index = layout.indexOfChild(caller_heading);
Log.d("Baresip", "Index of caller heading is " +
view_index);
layout.removeViews(view_index, 4 * remove_count);
for (int i = final_call_index; i < In.size(); i++) {
addCallViews(In.get(i), (i + 1) * 10);
}
}
});
}
if (!callHasHistory(History, ua, call)) {
History.add(new History(ua, call, aor, call_peeruri(call),
"in", false));
}
break;
}
call_index = callIndex(Out, ua, call);
Log.d("Baresip", "Outgoing call index is " + call_index);
if (call_index != -1) {
Log.d("Baresip", "Removing outgoing call " + ua + "/" +
call + "/" + Out.get(call_index).getPeerURI());
Out.remove(call_index);
if (ua.equals(CurrentUA)) {
runOnUiThread(new Runnable() {
@Override
public void run() {
callButton.setText("Call");
callButton.setEnabled(true);
callee.setText("");
callee.setHint("Callee");
holdButton.setText("History");
holdButton.setVisibility(View.VISIBLE);
}
});
}
if (!callHasHistory(History, ua, call)) {
History.add(new History(ua, call, aor, call_peeruri(call),
"out", false));
}
break;
}
Log.e("Baresip", "Unknown call " + ua + "/" + call +
" closed");
break;
default:
Log.d("Baresip", "Unknown event '" + event + "'");
break;
}
}
}
}
public native void baresipStart(String path);
public native void baresipStop();
public static native String ua_current();
public static native String ua_aor(String ua);
public native Boolean ua_isregistered(long ua_ptr);
public native String ua_call(String ua);
public native String call_peeruri(String call);
public static native void ua_current_set(String s);
public native String ua_connect(String s);
public native void ua_answer(String ua, String call);
public native Integer call_hold(String call);
public native Integer call_unhold(String call);
public native void ua_hangup(String ua, String call, int code, String reason);
static public native void contacts_remove();
static public native void contact_add(String contact);
static public native int reload_config();
static {
System.loadLibrary("baresip");
}
}

View File

@ -1,71 +0,0 @@
package com.tutpro.baresip;
import android.content.Context;
import android.content.DialogInterface;
import android.support.v7.app.AlertDialog;
import android.util.Log;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
public class Utils {
static public String getFileContents(File file) {
if (!file.exists()) {
Log.e("Baresip", "Failed to find file: " + file.getPath());
return "";
} else {
Log.e("Baresip", "Found file: " + file.getPath());
int length = (int) file.length();
byte[] bytes = new byte[length];
try {
FileInputStream in = new FileInputStream(file);
try {
in.read(bytes);
} finally {
in.close();
}
return new String(bytes);
} catch (java.io.IOException e) {
Log.e("Baresip", "Failed to read file: " + file.getPath() + ": " +
e.toString());
return "";
}
}
}
static public void putFileContents(File file, String contents) {
try {
FileOutputStream fOut =
new FileOutputStream(file.getAbsoluteFile(), false);
OutputStreamWriter fWriter = new OutputStreamWriter(fOut);
try {
fWriter.write(contents);
fWriter.close();
fOut.close();
} catch (java.io.IOException e) {
Log.e("Baresip", "Failed to put contents to file: " +
e.toString());
}
} catch (java.io.FileNotFoundException e) {
Log.e("Baresip", "Failed to find contents file: " +
e.toString());
}
}
static public void alertView( Context context, String message ) {
AlertDialog alertDialog = new AlertDialog.Builder(context).create();
alertDialog.setTitle("Alert");
alertDialog.setMessage(message);
alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
alertDialog.show();
}
}

View File

@ -0,0 +1,31 @@
package com.tutpro.baresip
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.MenuItem
import android.widget.TextView
class AboutActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_about)
val aboutView = findViewById(R.id.aboutText) as TextView
aboutView.isEnabled = false
aboutView.text = getString(R.string.aboutText)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
val i = Intent()
setResult(Activity.RESULT_CANCELED, i)
finish()
}
}
return true
}
}

View File

@ -0,0 +1,10 @@
package com.tutpro.baresip
class Account(val ua: String, val aoR: String, var status: String) {
private var statusImage: Int = 0
init {
this.statusImage = 0
}
}

View File

@ -0,0 +1,36 @@
package com.tutpro.baresip
import android.content.Context
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.util.ArrayList
class AccountSpinnerAdapter(private val cxt: Context, private val aors: ArrayList<String>,
private val images: ArrayList<Int>) :
ArrayAdapter<Int>(cxt, android.R.layout.simple_spinner_item, images) {
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
return getImageForPosition(position, parent)
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
return getImageForPosition(position, parent)
}
private fun getImageForPosition(position: Int, parent: ViewGroup): View {
val inflater = cxt.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val row = inflater.inflate(R.layout.account_spinner, parent, false)
val textView = row.findViewById(R.id.spinnerText) as TextView
textView.text = aors[position]
textView.textSize = 17f
val imageView = row.findViewById(R.id.spinnerImage) as ImageView
imageView.setImageResource(images[position])
return row
}
}

View File

@ -0,0 +1,5 @@
package com.tutpro.baresip
class Call(val ua: String, val call: String, val peerURI: String, var status: String) {
var hold: Boolean = false
}

View File

@ -0,0 +1,3 @@
package com.tutpro.baresip
class Contact(val name: String, val uri: String)

View File

@ -0,0 +1,103 @@
package com.tutpro.baresip
import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.util.Log
import android.util.TypedValue
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.widget.EditText
import android.widget.TextView
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.OutputStreamWriter
class EditAccountsActivity : AppCompatActivity() {
private var editText: EditText? = null
private var path: String? = null
private var file: File? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_edit_accounts)
editText = findViewById(R.id.editAccounts) as EditText
path = applicationContext.filesDir.absolutePath + "/accounts"
file = File(path!!)
var content: String
if (!file!!.exists()) {
Log.e("Baresip", "Failed to find accounts file")
content = "No accounts"
} else {
Log.e("Baresip", "Found accounts file")
val length = file!!.length().toInt()
val bytes = ByteArray(length)
try {
val `in` = FileInputStream(file!!)
try {
`in`.read(bytes)
} finally {
`in`.close()
}
content = String(bytes)
} catch (e: java.io.IOException) {
Log.e("Baresip", "Failed to read accounts file: " + e.toString())
content = "Failed to read account file"
}
}
Log.d("Baresip", "Content length is: " + content.length)
editText!!.setTextSize(TypedValue.COMPLEX_UNIT_PX,
resources.getDimension(R.dimen.textsize))
editText!!.setText(content, TextView.BufferType.EDITABLE)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
val inflater = menuInflater
inflater.inflate(R.menu.edit_menu, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val i = Intent()
when (item.itemId) {
R.id.save -> {
try {
val fOut = FileOutputStream(file!!.absoluteFile, false)
val fWriter = OutputStreamWriter(fOut)
val res = editText!!.text.toString()
try {
fWriter.write(res)
fWriter.close()
fOut.close()
} catch (e: java.io.IOException) {
Log.e("Baresip", "Failed to write accounts file: " + e.toString())
}
} catch (e: java.io.FileNotFoundException) {
Log.e("Baresip", "Failed to find accounts file: " + e.toString())
}
Log.d("Baresip", "Updated accounts file")
setResult(RESULT_OK, i)
finish()
return true
}
R.id.cancel, android.R.id.home -> {
setResult(RESULT_CANCELED, i)
finish()
return true
}
else -> return super.onOptionsItemSelected(item)
}
}
}

View File

@ -0,0 +1,103 @@
package com.tutpro.baresip
import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.util.Log
import android.util.TypedValue
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.widget.EditText
import android.widget.TextView
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.OutputStreamWriter
class EditConfigActivity : AppCompatActivity() {
private var editText: EditText? = null
private var path: String? = null
private var file: File? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_edit_config)
editText = findViewById(R.id.editConfig) as EditText
path = applicationContext.filesDir.absolutePath + "/config"
// path = "/sdcard/baresip/config";
file = File(path!!)
var content: String
if (!file!!.exists()) {
Log.e("Baresip", "Failed to find config file")
content = "No config"
} else {
Log.e("Baresip", "Found config file")
val length = file!!.length().toInt()
val bytes = ByteArray(length)
try {
val `in` = FileInputStream(file!!)
try {
`in`.read(bytes)
} finally {
`in`.close()
}
content = String(bytes)
} catch (e: java.io.IOException) {
Log.e("Baresip", "Failed to read config file: " + e.toString())
content = "Failed to read account file"
}
}
Log.d("Baresip", "Content length is: " + content.length)
editText!!.setTextSize(TypedValue.COMPLEX_UNIT_PX,
resources.getDimension(R.dimen.textsize))
editText!!.setText(content, TextView.BufferType.EDITABLE)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
val inflater = menuInflater
inflater.inflate(R.menu.edit_menu, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val i = Intent(this, MainActivity::class.java)
when (item.itemId) {
R.id.save -> {
try {
val fOut = FileOutputStream(file!!.absoluteFile, false)
val fWriter = OutputStreamWriter(fOut)
val res = editText!!.text.toString()
try {
fWriter.write(res)
fWriter.close()
fOut.close()
} catch (e: java.io.IOException) {
Log.e("Baresip", "Failed to write config file: " + e.toString())
}
} catch (e: java.io.FileNotFoundException) {
Log.e("Baresip", "Failed to find config file: " + e.toString())
}
Log.d("Baresip", "Updated config file")
setResult(RESULT_OK, i)
finish()
return true
}
R.id.cancel, android.R.id.home -> {
setResult(RESULT_CANCELED, i)
finish()
return true
}
else -> return super.onOptionsItemSelected(item)
}
}
}

View File

@ -0,0 +1,113 @@
package com.tutpro.baresip
import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.util.Log
import android.util.TypedValue
import android.view.Menu
import android.view.MenuItem
import android.widget.ArrayAdapter
import android.widget.EditText
import android.widget.TextView
import java.io.File
import java.util.ArrayList
class EditContactsActivity : AppCompatActivity() {
private var editText: EditText? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_edit_contacts)
editText = findViewById(R.id.editText) as EditText
val path = applicationContext.filesDir.absolutePath + "/contacts"
val file = File(path)
val content = Utils.getFileContents(file)
Log.d("Baresip", "Contacts length is: " + content.length)
editText!!.setTextSize(TypedValue.COMPLEX_UNIT_PX,
resources.getDimension(R.dimen.textsize))
editText!!.setText(content, TextView.BufferType.EDITABLE)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
val inflater = menuInflater
inflater.inflate(R.menu.edit_menu, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val i = Intent(this, MainActivity::class.java)
when (item.itemId) {
R.id.save -> {
val path = applicationContext.filesDir.absolutePath + "/contacts"
val file = File(path)
Utils.putFileContents(file, editText!!.text.toString())
Log.d("Baresip", "Updated contacts file")
MainActivity.contacts_remove()
updateContactsAndNames(path)
// MainActivity.CalleeAdapter.notifyDataSetChanged(); does not work
setResult(RESULT_OK, i)
finish()
return true
}
R.id.cancel, android.R.id.home -> {
i.putExtra("action", "cancel")
setResult(RESULT_OK, i)
finish()
return true
}
else -> return super.onOptionsItemSelected(item)
}
}
companion object {
var Contacts: ArrayList<Contact> = java.util.ArrayList()
var Names: ArrayList<String> = java.util.ArrayList()
fun updateContactsAndNames(path: String) {
val file = File(path)
val lines = Utils.getFileContents(file).split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
var name: String
var uri: String
Contacts.clear()
Names.clear()
for (line in lines) {
line.trim { it <= ' ' }
if (line.startsWith("#") || line.length == 0) continue
val parts = line.split("\"".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
if (parts.size != 3) {
Log.e("Baresip", "Invalid contacts line: $line")
continue
}
name = parts[1]
if (name.length < 2) {
Log.e("Baresip", "Too short contact display name: $name")
continue
}
uri = parts[2].trim { it <= ' ' }
if (!uri.startsWith("<") || !uri.contains(">")) {
Log.e("Baresip", "Invalid contact uri: $uri")
continue
}
MainActivity.contact_add("\"$name\" $uri")
if (uri.indexOf(";access") > 0) continue
uri = uri.substring(1, uri.indexOf(">"))
Log.d("Baresip", "Adding contact name/uri: $name/$uri")
Contacts.add(Contact(name, uri))
Names.add(name)
}
}
fun findContactURI(name: String): String {
for (c in Contacts) {
if (c.name == name) return c.uri
}
return name
}
}
}

View File

@ -0,0 +1,11 @@
package com.tutpro.baresip
import java.io.Serializable
import java.util.GregorianCalendar
class History(val ua: String, val call: String, val aor: String, val peerURI: String,
val direction: String, val connected: Boolean) : Serializable {
val time: GregorianCalendar = GregorianCalendar()
}

View File

@ -0,0 +1,129 @@
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.ListView
import java.text.SimpleDateFormat
import java.util.ArrayList
import java.util.Calendar
import java.util.GregorianCalendar
class HistoryActivity : AppCompatActivity() {
internal var uaHistory = ArrayList<HistoryRow>()
internal var posAtHistory = ArrayList<Int>()
internal var aor: String = ""
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_history)
val listview = findViewById(R.id.history) as ListView
val b = intent.extras
aor = b.getString("aor")
generate_ua_history(aor)
val adapter = HistoryListAdapter(this, uaHistory)
listview.adapter = adapter
listview.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->
val row = uaHistory[position]
val i = Intent()
i.putExtra("peer_uri", row.peerURI)
setResult(Activity.RESULT_OK, i)
finish()
}
listview.onItemLongClickListener = AdapterView.OnItemLongClickListener { _, _, pos, _ ->
val dialogClickListener = DialogInterface.OnClickListener { _, which ->
when (which) {
DialogInterface.BUTTON_POSITIVE -> {
History.removeAt(posAtHistory[pos])
generate_ua_history(aor)
if (uaHistory.size == 0) {
val i = Intent()
setResult(Activity.RESULT_CANCELED, i)
finish()
}
adapter.notifyDataSetChanged()
}
DialogInterface.BUTTON_NEGATIVE -> {
}
}
}
val builder = AlertDialog.Builder(this@HistoryActivity,
R.style.Theme_AppCompat)
builder.setMessage("Do you want to delete " +
History[pos].peerURI + "?")
.setPositiveButton("Yes", dialogClickListener)
.setNegativeButton("No", dialogClickListener).show()
true
}
listview.isLongClickable = true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
Log.d("Baresip", "Back array was pressed")
val i = Intent()
setResult(Activity.RESULT_CANCELED, i)
finish()
}
}
return true
}
private fun generate_ua_history(aor: String) {
uaHistory.clear()
posAtHistory.clear()
for (i in History.indices.reversed()) {
val h = History[i]
if (h.aor == aor) {
val time: String
if (isToday(h.time)) {
val fmt = SimpleDateFormat("HH:mm")
time = fmt.format(h.time.time)
} else {
val fmt = SimpleDateFormat("MMM dd")
time = fmt.format(h.time.time)
}
if (h.direction == "in") {
if (h.connected) {
uaHistory.add(HistoryRow(h.peerURI, R.drawable.arrow_down_green, time))
} else {
uaHistory.add(HistoryRow(h.peerURI, R.drawable.arrow_down_red, time))
}
} else {
if (h.connected) {
uaHistory.add(HistoryRow(h.peerURI, R.drawable.arrow_up_green, time))
} else {
uaHistory.add(HistoryRow(h.peerURI, R.drawable.arrow_up_red, time))
}
}
posAtHistory.add(i)
}
}
}
private fun isToday(time: GregorianCalendar): Boolean {
val now = GregorianCalendar()
return now.get(Calendar.YEAR) == time.get(Calendar.YEAR) &&
now.get(Calendar.MONTH) == time.get(Calendar.MONTH) &&
now.get(Calendar.DAY_OF_MONTH) == time.get(Calendar.DAY_OF_MONTH)
}
companion object {
var History: ArrayList<History> = ArrayList<History>()
}
}

View File

@ -0,0 +1,29 @@
package com.tutpro.baresip
import android.content.Context
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.util.ArrayList
class HistoryListAdapter(private val cxt: Context, private val rows: ArrayList<HistoryRow>) :
ArrayAdapter<HistoryRow>(cxt, R.layout.history_row, rows) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val row = rows[position]
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val rowView = inflater.inflate(R.layout.history_row, parent, false)
val directionView = rowView.findViewById(R.id.direction) as ImageView
directionView.setImageResource(row.direction)
val peerURIView = rowView.findViewById(R.id.peer_uri) as TextView
peerURIView.text = row.peerURI
val timeView = rowView.findViewById(R.id.time) as TextView
timeView.text = row.time
return rowView
}
}

View File

@ -0,0 +1,3 @@
package com.tutpro.baresip
class HistoryRow(val peerURI: String, val direction: Int, val time: String)

View File

@ -0,0 +1,668 @@
package com.tutpro.baresip
import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.graphics.Color
import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat
import android.support.v7.app.AlertDialog
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.widget.*
import android.widget.RelativeLayout.LayoutParams
import android.widget.AutoCompleteTextView
import android.view.*
import android.util.Log
import java.util.*
import java.io.*
import android.widget.RelativeLayout
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mainActivityContext = applicationContext
layout = findViewById(R.id.mainActivityLayout) as RelativeLayout
callee = findViewById(R.id.callee) as AutoCompleteTextView
callButton = findViewById(R.id.callButton) as Button
holdButton = findViewById(R.id.holdButton) as Button
AoRSpinner = findViewById(R.id.AoRList) as Spinner
Log.i("Baresip", "Setting AccountAdapter")
AccountAdapter = AccountSpinnerAdapter(applicationContext, AoRs, Images)
AoRSpinner.adapter = AccountAdapter
AoRSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
val aor = AoRs[position]
val callee = findViewById(R.id.callee) as AutoCompleteTextView
val callButton = findViewById(R.id.callButton) as Button
val holdButton = findViewById(R.id.holdButton) as Button
Log.i("Baresip", "Setting $aor current")
val out = uaCalls(Out, aor_ua(aor))
if (out.size == 0) {
callee.text.clear()
callee.hint = "Callee"
callButton.text = "Call"
if (aorHasHistory(aor)) {
holdButton.text = "History"
holdButton.visibility = View.VISIBLE
} else {
holdButton.visibility = View.INVISIBLE
}
} else {
callee.setText(out[0].peerURI)
callButton.text = out[0].status
if (out[0].status == "Hangup") {
if (out[0].hold) {
holdButton.text = "Unhold"
} else {
holdButton.text = "Hold"
}
holdButton.visibility = View.VISIBLE
} else {
holdButton.visibility = View.INVISIBLE
}
}
val `in` = uaCalls(In, aor_ua(aor))
val view_count = layout.childCount
Log.d("Baresip", "View count is $view_count")
if (view_count > 5) {
layout.removeViews(5, view_count - 5)
}
for (c in `in`) {
for (call_index in In.indices) {
addCallViews(c, (call_index + 1) * 10)
}
}
}
override fun onNothingSelected(parent: AdapterView<*>) {
Log.i("Baresip", "Nothing selected")
}
}
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
Log.d("Baresip", "path is: $path")
var file = File(path)
if (!file.exists()) {
Log.d("Baresip", "Creating baresip directory")
try {
File(path).mkdirs()
} catch (e: Error) {
Log.e("Baresip", "Failed to create directory: " + e.toString())
}
}
for (a in assets) {
file = File("$path/$a")
if (!file.exists()) {
Log.d("Baresip", "Copying asset $a")
copyAssetToFile(a, "$path/$a")
} else {
Log.d("Baresip", "Asset $a already copied")
}
}
file = File(path, "history")
try {
val fis = FileInputStream(file)
val ois = ObjectInputStream(fis)
@SuppressWarnings("unchecked")
HistoryActivity.History = ois.readObject() as ArrayList<History>
Log.d("Baresip", "Restored History of " + HistoryActivity.History.size + " entries")
ois.close()
fis.close()
} catch (e: Exception) {
Log.w("Baresip", "InputStream exception: - " + e.toString())
}
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
Log.d("Baresip", "Baresip does not have RECORD_AUDIO permission")
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.RECORD_AUDIO), RECORD_AUDIO_PERMISSION)
}
if (!running) {
Log.i("Baresip", "Starting Baresip with path $path")
Thread(Runnable { baresipStart(path) }).start()
running = true
}
EditContactsActivity.updateContactsAndNames(applicationContext.filesDir.absolutePath + "/contacts")
CalleeAdapter = ArrayAdapter(this, android.R.layout.select_dialog_item, EditContactsActivity.Names)
callee.threshold = 2
callee.setAdapter<ArrayAdapter<String>>(CalleeAdapter)
callButton.text = "Call"
callButton.setOnClickListener {
Log.d("Baresip", "AoR at position is " + AoRs[AoRSpinner.selectedItemPosition])
val aor = AoRs[AoRSpinner.selectedItemPosition]
if (callButton.text.toString() == "Call") {
call(aor, (findViewById(R.id.callee) as EditText).text.toString())
} else {
Log.i("Baresip", "Canceling UA " + aor_ua(aor) + " call " +
Out[0].call + " to " +
(findViewById(R.id.callee) as EditText).text)
ua_hangup(aor_ua(aor), Out[0].call, 486, "Rejected")
}
}
val holdButton = findViewById(R.id.holdButton) as Button
holdButton.setOnClickListener {
when (holdButton.text.toString()) {
"Hold" -> {
Log.i("Baresip", "Holding up " + (findViewById(R.id.callee) as EditText).text)
call_hold(Out[0].call)
Out[0].hold = true
holdButton.text = "Unhold"
}
"Unhold" -> {
Log.i("Baresip", "Unholding " + (findViewById(R.id.callee) as EditText).text)
call_unhold(Out[0].call)
Out[0].hold = false
holdButton.text = "Hold"
}
"History" -> {
val i = Intent(this@MainActivity, HistoryActivity::class.java)
val b = Bundle()
b.putString("aor", AoRs[AoRSpinner.selectedItemPosition])
i.putExtras(b)
startActivityForResult(i, HISTORY_CODE)
}
}
}
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
Log.d("Baresip", "Screen orientation change to landscape")
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
Log.d("Baresip", "Screen orientation change to portrait")
}
}
override fun onRequestPermissionsResult(requestCode: Int,
permissions: Array<String>, grantResults: IntArray) {
when (requestCode) {
RECORD_AUDIO_PERMISSION -> {
if (grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d("Baresip", "RECORD_AUDIO permission granted")
} else {
Log.d("Baresip", "RECORD_AUDIO permission NOT granted")
}
return
}
else -> Log.e("Baresip", "Unknown permissions request code: $requestCode")
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
val inflater = menuInflater
inflater.inflate(R.menu.main_menu, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val i: Intent
when (item.itemId) {
R.id.accounts -> {
i = Intent(this, EditAccountsActivity::class.java)
startActivityForResult(i, EDIT_ACCOUNTS_CODE)
return true
}
R.id.contacts -> {
i = Intent(this, EditContactsActivity::class.java)
startActivityForResult(i, EDIT_CONTACTS_CODE)
return true
}
R.id.config -> {
i = Intent(this, EditConfigActivity::class.java)
startActivityForResult(i, EDIT_CONFIG_CODE)
return true
}
R.id.about -> {
i = Intent(this, AboutActivity::class.java)
startActivityForResult(i, ABOUT_CODE)
return true
}
R.id.quit -> {
if (running) {
Log.d("Baresip", "Stopping")
val path = applicationContext.filesDir.path
Log.d("Baresip", "Saving history to $path")
val file = File(path, "history")
try {
val fos = FileOutputStream(file)
val oos = ObjectOutputStream(fos)
oos.writeObject(HistoryActivity.History)
oos.close()
fos.close()
} catch (e: IOException) {
Log.w("Baresip", "OutputStream exception: " + e.toString())
e.printStackTrace()
}
baresipStop()
running = false
}
finish()
System.exit(0)
return true
}
else -> return super.onOptionsItemSelected(item)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == EDIT_ACCOUNTS_CODE) {
if (resultCode == RESULT_OK) {
val alertDialog = AlertDialog.Builder(this).create()
alertDialog.setTitle("Alert")
alertDialog.setMessage("You need to restart baresip in order to activate saved accounts!")
alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK"
) { dialog, _ -> dialog.dismiss() }
alertDialog.show()
}
if (resultCode == RESULT_CANCELED) {
Log.d("Baresip", "Edit accounts canceled")
}
}
if (requestCode == EDIT_CONFIG_CODE) {
if (resultCode == RESULT_OK) {
Utils.alertView(this,
"You need to restart baresip in order to activate saved config!")
reload_config()
}
if (resultCode == RESULT_CANCELED) {
Log.d("Baresip", "Edit config canceled")
}
}
if (requestCode == HISTORY_CODE) {
if (resultCode == RESULT_OK) {
if (data != null) {
(findViewById(R.id.callee) as EditText).setText(data.getStringExtra("peer_uri"))
}
}
if (resultCode == RESULT_CANCELED) {
Log.d("Baresip", "History canceled")
if (!aorHasHistory(AoRs[AoRSpinner.selectedItemPosition])) {
(findViewById(R.id.holdButton) as Button).visibility = View.INVISIBLE
}
}
}
if (requestCode == EDIT_CONTACTS_CODE || requestCode == ABOUT_CODE) {
Log.d("Baresip", "Back arrow or Cancel pressed at request: $requestCode")
}
}
fun addAccount(ua: String) {
val aor = ua_aor(ua)
Log.d("Baresip", "Adding account $ua with AoR $aor")
Accounts.add(Account(ua, aor, ""))
AoRs.add(aor)
Images.add(R.drawable.yellow)
runOnUiThread { AccountAdapter.notifyDataSetChanged() }
}
private fun call(aor: String, callee: String) {
if (callee.length == 0) return
var uri = EditContactsActivity.findContactURI(callee)
if (!uri.startsWith("sip:")) uri = "sip:$uri"
if (!uri.contains("@")) {
val host = aor.substring(aor.indexOf("@") + 1)
uri = "$uri@$host"
}
(findViewById(R.id.callee) as EditText).setText(uri)
val ua = aor_ua(aor)
Log.i("Baresip", "Calling $ua / $uri")
val call = ua_connect(ua, uri)
if (call != "") {
Log.i("Baresip", "Adding outgoing call $ua / $call / $uri")
Out.add(Call(ua, call, uri, "Cancel"))
(findViewById(R.id.callButton) as Button).text = "Cancel"
(findViewById(R.id.holdButton) as Button).visibility = View.INVISIBLE
}
}
private fun copyAssetToFile(asset: String, path: String) {
try {
val assetManager = assets
val `is` = assetManager.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)
}
} catch (e: IOException) {
Log.e("Baresip", "Failed to read asset " + asset + ": " +
e.toString())
}
}
private fun addCallViews(call: Call, id: Int) {
Log.d("Baresip", "Creating new Incoming textview at $id")
val caller_heading = TextView(mainActivityContext)
caller_heading.text = "Incoming call from ..."
caller_heading.setTextColor(Color.BLACK)
caller_heading.textSize = 20f
caller_heading.setPadding(10, 20, 0, 0)
caller_heading.id = id
val heading_params = LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT)
if (id == 10)
heading_params.addRule(RelativeLayout.BELOW, callButton.id)
else
heading_params.addRule(RelativeLayout.BELOW, id - 10 + 3)
caller_heading.layoutParams = heading_params
layout.addView(caller_heading)
val caller_uri = TextView(mainActivityContext)
caller_uri.text = In[In.size - 1].peerURI
caller_uri.setTextColor(Color.GREEN)
caller_uri.textSize = 20f
caller_uri.setPadding(10, 10, 0, 10)
Log.d("Baresip", "Creating new caller textview at " + (id + 1))
caller_uri.id = id + 1
val caller_uri_params = LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT)
caller_uri_params.addRule(RelativeLayout.BELOW, id)
caller_uri.layoutParams = caller_uri_params
layout.addView(caller_uri)
val answer_button = Button(mainActivityContext)
answer_button.text = call.status
answer_button.setBackgroundResource(android.R.drawable.btn_default)
answer_button.setTextColor(Color.BLACK)
Log.d("Baresip", "Creating new answer button at " + (id + 2))
answer_button.id = id + 2
answer_button.setOnClickListener { v ->
val call_ua = call.ua
val call_call = call.call
when ((v as Button).text.toString()) {
"Answer" -> {
Log.i("Baresip", "UA " + call_ua + " accepting incoming call " +
call_call)
ua_answer(call_ua, call_call)
val final_in_index = callIndex(In, call_ua, call_call)
if (final_in_index >= 0) {
Log.d("Baresip", "Updating Hangup and Hold")
In[final_in_index].status = "Hangup"
In[final_in_index].hold = false
runOnUiThread {
val answer_id = (final_in_index + 1) * 10 + 2
val answer_but = layout.findViewById(answer_id) as Button
answer_but.text = "Hangup"
val reject_button = layout.findViewById(answer_id + 1) as Button
reject_button.text = "Hold"
}
}
}
"Hangup" -> {
Log.i("Baresip", "UA " + call_ua + " hanging up call " +
call_call)
ua_hangup(call_ua, call_call, 200, "OK")
}
else -> Log.e("Baresip", "Invalid answer button text: " + v.text.toString())
}
}
val answer_button_params = LayoutParams(200,
LayoutParams.WRAP_CONTENT)
answer_button_params.addRule(RelativeLayout.BELOW, id + 1)
answer_button_params.setMargins(3, 10, 0, 0)
answer_button.layoutParams = answer_button_params
layout.addView(answer_button)
val reject_button = Button(mainActivityContext)
if (call.status == "Answer") {
reject_button.text = "Reject"
} else {
if (call.hold) {
reject_button.text = "Unhold"
} else {
reject_button.text = "Hold"
}
}
reject_button.setBackgroundResource(android.R.drawable.btn_default)
reject_button.setTextColor(Color.BLACK)
Log.d("Baresip", "Creating new reject button at " + (id + 3))
reject_button.id = id + 3
reject_button.setOnClickListener { v ->
when ((v as Button).text.toString()) {
"Reject" -> {
Log.i("Baresip", "UA " + call.ua +
" rejecting incoming call " + call.call)
ua_hangup(call.ua, call.call, 486, "Rejected")
}
"Hold" -> {
call_hold(call.call)
v.text = "Unhold"
call.hold = true
}
"Unhold" -> {
call_unhold(call.call)
v.text = "Hold"
call.hold = false
}
}
}
val reject_button_params = LayoutParams(200,
LayoutParams.WRAP_CONTENT)
reject_button_params.addRule(RelativeLayout.BELOW, id + 1)
reject_button_params.setMargins(225, 10, 0, 0)
reject_button.layoutParams = reject_button_params
layout.addView(reject_button)
}
private fun callIndex(calls: ArrayList<Call>, ua: String, call: String): Int {
for (i in calls.indices) {
if (calls[i].ua.equals(ua) && calls[i].call == call)
return i
}
return -1
}
private fun uaCalls(calls: ArrayList<Call>, ua: String): ArrayList<Call> {
val result = ArrayList<Call>()
for (i in calls.indices) {
if (calls[i].ua == ua) result.add(calls[i])
}
return result
}
private fun aorHasHistory(aor: String): Boolean {
for (h in HistoryActivity.History) {
if (h.aor == aor) return true
}
return false
}
private fun callHasHistory(ua: String, call: String): Boolean {
for (h in HistoryActivity.History) {
if (h.ua == ua && h.call == call) return true
}
return false
}
private fun updateStatus(event: String, ua: String, call: String) {
val aor = ua_aor(ua)
var call_index: Int
Log.d("Baresip", "Handling event " + event + " for " + ua + "/" + call + "/" +
aor)
for (account_index in Accounts.indices) {
if (Accounts[account_index].aoR == aor) {
Log.d("Baresip", "Found AoR at index $account_index")
when (event) {
"registering", "unregistering" -> {
}
"registered" -> {
Log.d("Baresip", "Setting status to green")
Accounts[account_index].status = "OK"
AoRs[account_index] = aor
Images[account_index] = R.drawable.green
runOnUiThread { AccountAdapter.notifyDataSetChanged() }
}
"registering failed" -> {
Log.d("Baresip", "Setting status to red")
Accounts[account_index].status = "FAIL"
AoRs[account_index] = aor
Images[account_index] = R.drawable.red
runOnUiThread { AccountAdapter.notifyDataSetChanged() }
}
"call ringing" -> {
}
"call established" -> {
val out_index = callIndex(Out, ua, call)
if (out_index >= 0) {
Log.d("Baresip", "Outbound call " + call + " established")
Out[out_index].status = "Hangup"
Out[out_index].hold = false
runOnUiThread {
if (ua == aor_ua(AoRs[AoRSpinner.selectedItemPosition])) {
callButton.text = "Hangup"
holdButton.text = "Hold"
holdButton.visibility = View.VISIBLE
}
}
HistoryActivity.History.add(History(ua, call, aor, call_peeruri(call),
"out", true))
} else {
Log.d("Baresip", "Inbound call " + call + " established")
HistoryActivity.History.add(History(ua, call, aor, call_peeruri(call),
"in", true))
}
}
"call incoming" -> {
val peer_uri = call_peeruri(call)
Log.d("Baresip", "Incoming call " + ua + "/" + call + "/" +
peer_uri)
val new_call = Call(ua, call, peer_uri, "Answer")
In.add(new_call)
this@MainActivity.runOnUiThread {
if (ua == aor_ua(AoRs[AoRSpinner.selectedItemPosition])) {
addCallViews(new_call, In.size * 10)
}
}
}
"call closed" -> {
call_index = callIndex(In, ua, call)
if (call_index != -1) {
Log.d("Baresip", "Removing inbound call " + ua + "/" +
call + "/" + In[call_index].peerURI)
val view_id = (call_index + 1) * 10
val remove_count = In.size - call_index
In.removeAt(call_index)
this@MainActivity.runOnUiThread {
if (ua == aor_ua(AoRs[AoRSpinner.selectedItemPosition])) {
if (callButton.text == "Call") {
holdButton.text = "History"
holdButton.visibility = View.VISIBLE
}
val caller_heading = layout.findViewById(view_id)
val view_index = layout.indexOfChild(caller_heading)
Log.d("Baresip", "Index of caller heading is $view_index")
layout.removeViews(view_index, 4 * remove_count)
for (i in call_index until In.size) {
this@MainActivity.addCallViews(In[i], (i + 1) * 10)
}
}
}
if (!callHasHistory(ua, call)) {
HistoryActivity.History.add(History(ua, call, aor, call_peeruri(call),
"in", false))
}
} else {
call_index = callIndex(Out, ua, call)
if (call_index != -1) {
Log.d("Baresip", "Removing outgoing call " + ua + "/" +
call + "/" + Out[call_index].peerURI)
Out.removeAt(call_index)
runOnUiThread {
if (ua == aor_ua(AoRs[AoRSpinner.selectedItemPosition])) {
callButton.text = "Call"
callButton.isEnabled = true
callee.setText("")
callee.hint = "Callee"
holdButton.text = "History"
holdButton.visibility = View.VISIBLE
}
}
if (!callHasHistory(ua, call)) {
HistoryActivity.History.add(History(ua, call, aor, call_peeruri(call),
"out", false))
}
} else {
Log.e("Baresip", "Unknown call " + ua + "/" + call +
" closed")
}
}
}
else -> Log.d("Baresip", "Unknown event '$event'")
}
}
}
}
external fun baresipStart(path: String)
external fun baresipStop()
external fun call_peeruri(call: String): String
external fun ua_aor(ua: String): String
external fun aor_ua(aor: String): String
external fun ua_connect(ua: String, peer_uri: String): String
external fun ua_answer(ua: String, call: String)
external fun call_hold(call: String): Int?
external fun call_unhold(call: String): Int?
external fun ua_hangup(ua: String, call: String, code: Int, reason: String)
external fun reload_config(): Int
companion object {
internal lateinit var mainActivityContext: Context
internal lateinit var layout: RelativeLayout
internal lateinit var callee: AutoCompleteTextView
internal lateinit var callButton: Button
internal lateinit var holdButton: Button
internal lateinit var AccountAdapter: AccountSpinnerAdapter
internal lateinit var AoRSpinner: Spinner
internal var running: Boolean = false
internal var Accounts = ArrayList<Account>()
internal var AoRs = ArrayList<String>()
internal var Images = ArrayList<Int>()
internal var CalleeAdapter: ArrayAdapter<String>? = null
internal var In = ArrayList<Call>()
internal var Out = ArrayList<Call>()
private val RECORD_AUDIO_PERMISSION = 1
private val EDIT_ACCOUNTS_CODE = 1
private val EDIT_CONTACTS_CODE = 2
private val EDIT_CONFIG_CODE = 3
private val HISTORY_CODE = 4
private val ABOUT_CODE = 5
external fun contacts_remove()
external fun contact_add(contact: String)
init {
System.loadLibrary("baresip")
}
}
}

View File

@ -0,0 +1,67 @@
package com.tutpro.baresip
import android.content.Context
import android.content.DialogInterface
import android.support.v7.app.AlertDialog
import android.util.Log
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.OutputStreamWriter
object Utils {
fun getFileContents(file: File): String {
if (!file.exists()) {
Log.e("Baresip", "Failed to find file: " + file.path)
return ""
} else {
Log.e("Baresip", "Found file: " + file.path)
val length = file.length().toInt()
val bytes = ByteArray(length)
try {
val `in` = FileInputStream(file)
try {
`in`.read(bytes)
} finally {
`in`.close()
}
return String(bytes)
} catch (e: java.io.IOException) {
Log.e("Baresip", "Failed to read file: " + file.path + ": " +
e.toString())
return ""
}
}
}
fun putFileContents(file: File, contents: String) {
try {
val fOut = FileOutputStream(file.absoluteFile, false)
val fWriter = OutputStreamWriter(fOut)
try {
fWriter.write(contents)
fWriter.close()
fOut.close()
} catch (e: java.io.IOException) {
Log.e("Baresip", "Failed to put contents to file: " + e.toString())
}
} catch (e: java.io.FileNotFoundException) {
Log.e("Baresip", "Failed to find contents file: " + e.toString())
}
}
fun alertView(context: Context, message: String) {
val alertDialog = AlertDialog.Builder(context).create()
alertDialog.setTitle("Alert")
alertDialog.setMessage(message)
alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK"
) { dialog, _ -> dialog.dismiss() }
alertDialog.show()
}
}

View File

@ -1,11 +1,13 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = '1.2.30'
repositories { repositories {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.0.1' classpath 'com.android.tools.build:gradle:3.0.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files